mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Compare commits
75
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a967ce648 | ||
|
|
4c7eac870d | ||
|
|
ff58e01551 | ||
|
|
004ea6cbcb | ||
|
|
a42b88aa78 | ||
|
|
43088cf8b8 | ||
|
|
9460584891 | ||
|
|
570118ca94 | ||
|
|
d9f0de2e05 | ||
|
|
3d5dcfb21d | ||
|
|
e0dc9ea737 | ||
|
|
b4d0904f55 | ||
|
|
2caee59713 | ||
|
|
6712ba78ba | ||
|
|
cba30aee47 | ||
|
|
c6cd8d68f7 | ||
|
|
d2fbf9d79f | ||
|
|
738a35dbe9 | ||
|
|
1278ec55c2 | ||
|
|
95ff34b373 | ||
|
|
1329429130 | ||
|
|
8c3c09dc03 | ||
|
|
10bf65bd09 | ||
|
|
b57b3458ab | ||
|
|
6aeee4816c | ||
|
|
3c8f24fa50 | ||
|
|
5e60629b4c | ||
|
|
21d740ae51 | ||
|
|
f4e8f08f4f | ||
|
|
7bf472023f | ||
|
|
01fc9654f8 | ||
|
|
c1b247c219 | ||
|
|
101fb68205 | ||
|
|
cda16ca06a | ||
|
|
e9b29a2a43 | ||
|
|
5df809a492 | ||
|
|
ffe8a58746 | ||
|
|
eab0296125 | ||
|
|
2ff143b858 | ||
|
|
3b2dcc1a5c | ||
|
|
d59dc8c225 | ||
|
|
877c5d9013 | ||
|
|
11a19da0b8 | ||
|
|
eda81822aa | ||
|
|
3b00613dde | ||
|
|
a434a0bee0 | ||
|
|
33ca43eaf1 | ||
|
|
df4ec3501b | ||
|
|
fc54e540e8 | ||
|
|
e8481cc8cd | ||
|
|
eb8a9b3b46 | ||
|
|
79ec3ba932 | ||
|
|
8f910b6dcc | ||
|
|
92fa181925 | ||
|
|
ca82f96b30 | ||
|
|
2ea2bb1765 | ||
|
|
fb22798469 | ||
|
|
d48f475a34 | ||
|
|
c88c57e856 | ||
|
|
17892fbd24 | ||
|
|
2352974d4d | ||
|
|
e216fbf66e | ||
|
|
f3a947a8a3 | ||
|
|
00264c1d2b | ||
|
|
54a02837fd | ||
|
|
7a5af76ebb | ||
|
|
92bd754623 | ||
|
|
85b9978e05 | ||
|
|
1ebb2319d4 | ||
|
|
f956625d2e | ||
|
|
9c2ebd9d04 | ||
|
|
fb0527b0bf | ||
|
|
6b922e8845 | ||
|
|
663e2d3b08 | ||
|
|
858b86af6a |
@@ -0,0 +1,42 @@
|
||||
name: 'Common project setup'
|
||||
description: 'Setup the base project'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@3a4f6e1af504cf6a31855fa899c6aa5355ba6c12 #v4.7.0
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
shell: bash
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Cache Gradle Wrapper
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/wrapper
|
||||
!~/.gradle/wrapper/dists/**/gradle*.zip
|
||||
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-wrapper-
|
||||
|
||||
- name: Cache Gradle Dependencies
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-caches-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', 'buildSrc/**/*.kt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-gradle-caches-
|
||||
|
||||
- name: Cache Android Global Build-Cache
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 #v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.android/build-cache
|
||||
key: ${{ runner.os }}-android-build-cache-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-android-build-cache-
|
||||
@@ -0,0 +1,14 @@
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- changelog-ignore
|
||||
categories:
|
||||
- title: ":rocket: Enhancements"
|
||||
labels:
|
||||
- enhancement
|
||||
- title: ":lady_beetle: Bug fixes"
|
||||
labels:
|
||||
- bug
|
||||
- title: ":shrug: Other changes"
|
||||
labels:
|
||||
- "*"
|
||||
@@ -7,6 +7,24 @@ on:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
lint-vital:
|
||||
name: Lint vitals
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
flavor: [ Foss,Gplay ]
|
||||
variant: [ Beta,Release ]
|
||||
module: [ app ]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
- name: Check for fatal lint issues
|
||||
run: ./gradlew lintVital${{ matrix.flavor }}${{ matrix.variant }}
|
||||
|
||||
build-modules:
|
||||
name: Build apps
|
||||
strategy:
|
||||
@@ -14,20 +32,15 @@ jobs:
|
||||
matrix:
|
||||
flavor: [ Foss,Gplay ]
|
||||
variant: [ Debug ]
|
||||
module: [ app,app-wear ]
|
||||
runs-on: ubuntu-latest
|
||||
module: [ app ]
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
cache: gradle
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
- name: Build module
|
||||
- name: Build modules
|
||||
run: ./gradlew ${{ matrix.module }}:assemble${{ matrix.flavor }}${{ matrix.variant }}
|
||||
|
||||
test-modules:
|
||||
@@ -35,19 +48,14 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: [ Release ]
|
||||
variant: [ Debug,Beta,Release ]
|
||||
flavor: [ testFoss,testGplay ]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
cache: gradle
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
- name: Test modules
|
||||
run: ./gradlew ${{ matrix.flavor }}${{ matrix.variant }}UnitTest
|
||||
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
validation:
|
||||
name: "Validation"
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: gradle/wrapper-validation-action@v1
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
- uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 #v4.3.1
|
||||
|
||||
@@ -8,7 +8,9 @@ on:
|
||||
jobs:
|
||||
release-github:
|
||||
name: Create GitHub release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-22.04
|
||||
environment: foss-production
|
||||
steps:
|
||||
- name: Decode Keystore
|
||||
@@ -21,25 +23,22 @@ jobs:
|
||||
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
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
- name: Get the version
|
||||
id: tagger
|
||||
uses: jimschubert/query-tag-action@v2
|
||||
uses: jimschubert/query-tag-action@0b288a5fff630fea2e96d61b99047ed823ca19dc #v2.2
|
||||
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: 17
|
||||
|
||||
- name: Assemble beta APK
|
||||
if: contains(steps.tagger.outputs.tag, '-beta')
|
||||
run: ./gradlew assembleFossBeta
|
||||
@@ -60,7 +59,7 @@ jobs:
|
||||
|
||||
- name: Create pre-release
|
||||
if: contains(steps.tagger.outputs.tag, '-beta')
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda #v2.2.1
|
||||
with:
|
||||
prerelease: true
|
||||
tag_name: ${{ steps.tagger.outputs.tag }}
|
||||
@@ -74,21 +73,19 @@ jobs:
|
||||
|
||||
- name: Create release
|
||||
if: "!contains(steps.tagger.outputs.tag, '-beta')"
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@c95fe1489396fe8a9eb87c0abf8aa5b2ef267fda #v2.2.1
|
||||
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
|
||||
files: app/build/outputs/apk/foss/release/*.apk
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release-gplay:
|
||||
name: Create Google Play release
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
environment: gplay-production
|
||||
steps:
|
||||
- name: Decode Keystore
|
||||
@@ -111,29 +108,26 @@ jobs:
|
||||
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
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup project and build environment
|
||||
uses: ./.github/actions/common-setup
|
||||
|
||||
- name: Get the version
|
||||
id: tagger
|
||||
uses: jimschubert/query-tag-action@v2
|
||||
uses: jimschubert/query-tag-action@0b288a5fff630fea2e96d61b99047ed823ca19dc #v2.2
|
||||
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: 17
|
||||
|
||||
- name: Set up ruby env
|
||||
uses: ruby/setup-ruby@v1
|
||||
uses: ruby/setup-ruby@354a1ad156761f5ee2b7b13fa8e09943a5e8d252 #v1.229.0
|
||||
with:
|
||||
ruby-version: 2.7.6
|
||||
ruby-version: 3.3.6
|
||||
bundler-cache: true
|
||||
|
||||
# - name: Assemble WearOS beta and upload to Google Play
|
||||
|
||||
+2
-1
@@ -12,4 +12,5 @@
|
||||
*.jks
|
||||
.local/*
|
||||
/fastlane/report.xml
|
||||
/fastlane/Appfile
|
||||
/fastlane/Appfile
|
||||
/fastlane/README.md
|
||||
Generated
+1
@@ -9,6 +9,7 @@
|
||||
<option name="DO_NOT_WRAP_AFTER_SINGLE_ANNOTATION" value="true" />
|
||||
<option name="IMPORT_LAYOUT_TABLE">
|
||||
<value>
|
||||
<package name="" withSubpackages="true" static="false" module="true" />
|
||||
<package name="android" withSubpackages="true" static="false" />
|
||||
<emptyLine />
|
||||
<package name="com" withSubpackages="true" static="false" />
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
layout: plain
|
||||
permalink: /changelog
|
||||
title: "Changelog"
|
||||
---
|
||||
|
||||
# Changelog for CAPod
|
||||
|
||||
{% for release in site.github.releases %}
|
||||
|
||||
## {{ release.tag_name }} - {{ release.published_at | date: "%B %d, %Y" }}
|
||||
|
||||
{% assign clean_body = release.body | strip %}
|
||||
{% assign no_comments = clean_body | replace: "<!-- Release notes generated using configuration in .github/release.yml", "" %}
|
||||
{% assign no_comments = no_comments | split: "-->" %}
|
||||
{% if no_comments.size > 1 %}
|
||||
{% assign clean_content = no_comments[1] | strip %}
|
||||
{% else %}
|
||||
{% assign clean_content = no_comments[0] | strip %}
|
||||
{% endif %}
|
||||
|
||||
{% comment %} Make links clickable {% endcomment %}
|
||||
{% assign lines = clean_content | split: "
|
||||
" %}
|
||||
{% assign processed_lines = "" %}
|
||||
{% for line in lines %}
|
||||
{% if line contains "**Full Changelog**:" %}
|
||||
{% comment %} Handle Full Changelog links {% endcomment %}
|
||||
{% assign parts = line | split: ": " %}
|
||||
{% if parts.size > 1 %}
|
||||
{% assign url = parts[1] | strip %}
|
||||
{% assign clickable_line = "**[View Changes](" | append: url | append: ")**" %}
|
||||
{% assign processed_lines = processed_lines | append: clickable_line | append: "
|
||||
" %}
|
||||
{% else %}
|
||||
{% assign processed_lines = processed_lines | append: line | append: "
|
||||
" %}
|
||||
{% endif %}
|
||||
{% elsif line contains " in https://github.com/" and line contains "/pull/" %}
|
||||
{% comment %} Handle pull request links {% endcomment %}
|
||||
{% assign pr_parts = line | split: " in https://github.com/" %}
|
||||
{% if pr_parts.size > 1 %}
|
||||
{% assign before_url = pr_parts[0] %}
|
||||
{% assign after_url = pr_parts[1] %}
|
||||
{% assign url = "https://github.com/" | append: after_url %}
|
||||
{% assign pr_number = after_url | split: "/pull/" %}
|
||||
{% if pr_number.size > 1 %}
|
||||
{% assign pr_num = pr_number[1] | split: " " | first %}
|
||||
{% assign clickable_line = before_url | append: " in [#" | append: pr_num | append: "](" | append: url | append: ")" %}
|
||||
{% assign processed_lines = processed_lines | append: clickable_line | append: "
|
||||
" %}
|
||||
{% else %}
|
||||
{% assign processed_lines = processed_lines | append: line | append: "
|
||||
" %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% assign processed_lines = processed_lines | append: line | append: "
|
||||
" %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% assign processed_lines = processed_lines | append: line | append: "
|
||||
" %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% comment %} Add proper spacing between sections and bullet points {% endcomment %}
|
||||
{% assign final_content = processed_lines | replace: "
|
||||
### ", "
|
||||
|
||||
### " %}
|
||||
{% assign final_content = final_content | replace: "
|
||||
## ", "
|
||||
|
||||
## " %}
|
||||
{% assign final_content = final_content | replace: "
|
||||
- ", "
|
||||
|
||||
- " %}
|
||||
|
||||
{% comment %} Check if there are any bullet points (actual release notes) {% endcomment %}
|
||||
{% if final_content contains "## " or final_content contains "- " %}
|
||||
{{ final_content | markdownify }}
|
||||
{% else %}
|
||||
*No release notes available.*
|
||||
|
||||
{{ final_content | markdownify }}
|
||||
{% endif %}
|
||||
|
||||
---
|
||||
{% endfor %}
|
||||
+87
-76
@@ -1,43 +1,48 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.6)
|
||||
CFPropertyList (3.0.7)
|
||||
base64
|
||||
nkf
|
||||
rexml
|
||||
addressable (2.8.4)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
artifactory (3.0.15)
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
artifactory (3.0.17)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.784.0)
|
||||
aws-sdk-core (3.177.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-eventstream (1.3.2)
|
||||
aws-partitions (1.1109.0)
|
||||
aws-sdk-core (3.224.1)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
base64
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.70.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.128.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
logger
|
||||
aws-sdk-kms (1.101.0)
|
||||
aws-sdk-core (~> 3, >= 3.216.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sdk-s3 (1.188.0)
|
||||
aws-sdk-core (~> 3, >= 3.224.1)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.6)
|
||||
aws-sigv4 (1.6.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
aws-sigv4 (1.11.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
base64 (0.3.0)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.5)
|
||||
digest-crc (0.7.0)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.100.0)
|
||||
faraday (1.10.3)
|
||||
excon (0.109.0)
|
||||
faraday (1.10.4)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
@@ -53,27 +58,27 @@ GEM
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-em_synchrony (1.0.1)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-multipart (1.1.1)
|
||||
multipart-post (~> 2.0)
|
||||
faraday-net_http (1.0.2)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday_middleware (1.2.1)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.7)
|
||||
fastlane (2.213.0)
|
||||
fastimage (2.4.0)
|
||||
fastlane (2.228.0)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
colored (~> 1.2)
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
@@ -82,33 +87,38 @@ GEM
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
fastlane-sirp (>= 1.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
http-cookie (~> 1.0.5)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (>= 2.0.0, < 3.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (~> 0.1.1)
|
||||
optparse (>= 0.1.1, < 1.0.0)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
security (= 0.1.5)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
terminal-table (~> 3)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
xcpretty (~> 0.4.1)
|
||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
||||
fastlane-sirp (1.0.0)
|
||||
sysrandom (~> 1.0)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.45.0)
|
||||
google-apis-androidpublisher_v3 (0.54.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-core (0.11.0)
|
||||
google-apis-core (0.11.3)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
@@ -116,64 +126,66 @@ GEM
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.17.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.13.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-apis-storage_v1 (0.19.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-apis-storage_v1 (0.29.0)
|
||||
google-apis-core (>= 0.11.0, < 2.a)
|
||||
google-cloud-core (1.6.1)
|
||||
google-cloud-env (>= 1.0, < 3.a)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.3.1)
|
||||
google-cloud-storage (1.44.0)
|
||||
google-cloud-storage (1.45.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.19.0)
|
||||
google-apis-storage_v1 (~> 0.29.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.6.0)
|
||||
googleauth (1.8.1)
|
||||
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.5)
|
||||
http-cookie (1.0.8)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
httpclient (2.9.0)
|
||||
mutex_m
|
||||
jmespath (1.6.2)
|
||||
json (2.6.3)
|
||||
jwt (2.7.1)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.2)
|
||||
json (2.7.6)
|
||||
jwt (2.10.1)
|
||||
base64
|
||||
logger (1.7.0)
|
||||
mini_magick (4.13.2)
|
||||
mini_mime (1.1.5)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.3.0)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.1)
|
||||
optparse (0.1.1)
|
||||
multipart-post (2.4.1)
|
||||
mutex_m (0.3.0)
|
||||
nanaimo (0.4.0)
|
||||
naturally (2.3.0)
|
||||
nkf (0.2.0)
|
||||
optparse (0.6.0)
|
||||
os (1.1.4)
|
||||
plist (3.7.0)
|
||||
public_suffix (5.0.1)
|
||||
rake (13.0.6)
|
||||
plist (3.7.2)
|
||||
public_suffix (5.1.1)
|
||||
rake (13.3.0)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.5)
|
||||
rouge (2.0.7)
|
||||
rexml (3.4.1)
|
||||
rouge (3.28.0)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.17.0)
|
||||
rubyzip (2.4.1)
|
||||
security (0.1.5)
|
||||
signet (0.18.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
@@ -181,34 +193,33 @@ GEM
|
||||
simctl (1.6.10)
|
||||
CFPropertyList
|
||||
naturally
|
||||
sysrandom (1.0.5)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-screen (0.8.2)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.8.1)
|
||||
unf (0.2.0)
|
||||
unicode-display_width (2.6.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.22.0)
|
||||
xcodeproj (1.27.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (~> 3.2.4)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
nanaimo (~> 0.4.0)
|
||||
rexml (>= 3.3.6, < 4.0)
|
||||
xcpretty (0.4.1)
|
||||
rouge (~> 3.28.0)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
universal-darwin-24
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
|
||||
+7
-1
@@ -1,5 +1,11 @@
|
||||
---
|
||||
layout: plain
|
||||
permalink: /privacy
|
||||
title: "Privacy Policy"
|
||||
---
|
||||
|
||||
# Privacy policy
|
||||
This is the privacy policy for the Android app "CAPod - Companion for AirPods".
|
||||
This is the privacy policy for the Android app "CAPod - Companion for AirPods" by Matthias Urhahn (darken).
|
||||
|
||||
## Preamble
|
||||
CAPod respects your privacy.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<img src="https://github.com/d4rken-org/capod/raw/main/.assets/banner.png" width="400">
|
||||
|
||||
# Companion App for AirPods (CAPod)
|
||||
|
||||
[](https://github.com/d4rken-org/capod/releases/latest)
|
||||
[](https://github.com/d4rken/capod/actions/workflows/code-checks.yml)
|
||||
[](https://crowdin.com/project/capod)
|
||||
@@ -56,7 +57,6 @@ Currently supported models:
|
||||
| [F-Droid](https://f-droid.org/en/packages/eu.darken.capod/) | [](https://f-droid.org/en/packages/eu.darken.capod/) |
|
||||
| [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)
|
||||
@@ -72,6 +72,7 @@ Currently supported models:
|
||||
|
||||
<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,
|
||||
@@ -82,6 +83,8 @@ Currently supported models:
|
||||
* Celosia, Guillaume. (2020). Privacy Challenges in Wireless Communications of the Internet of Things.
|
||||
* The [MagicPods](https://github.com/steam3d/MagicPods-Windows) project. If you are looking for "CAPod for Windows",
|
||||
check it out.
|
||||
* [@kavishdevar](https://github.com/kavishdevar/librepods) and
|
||||
his [LibrePods project](https://github.com/kavishdevar/librepods) for sharing a lot of cool stuff.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
theme: minima
|
||||
plugins:
|
||||
- jekyll-relative-links
|
||||
- jekyll-github-metadata
|
||||
- jemoji
|
||||
|
||||
relative_links:
|
||||
enabled: true
|
||||
collections: true
|
||||
|
||||
title: "CAPod"
|
||||
description: "A companion app for AirPods on Android."
|
||||
author: "by Matthias Urhahn"
|
||||
|
||||
include:
|
||||
- PRIVACY_POLICY.md
|
||||
exclude:
|
||||
- buildSrc
|
||||
- gradle/wrapper
|
||||
- fastlane
|
||||
- Gemfile
|
||||
- Gemfile.lock
|
||||
- crowdin*
|
||||
- app
|
||||
- app-common
|
||||
- app-wear
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ page.lang | default: site.lang | default: 'en' }}">
|
||||
|
||||
{%- include head.html -%}
|
||||
|
||||
<body>
|
||||
|
||||
<main class="page-content" aria-label="Content">
|
||||
<div class="wrapper">
|
||||
{{ content }}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{%- include footer.html -%}
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,6 +1,7 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("kotlin-android")
|
||||
id("com.google.devtools.ksp")
|
||||
id("kotlin-kapt")
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
@@ -84,7 +85,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:1.1.5")
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5")
|
||||
|
||||
addBaseAndroid()
|
||||
addBaseAndroidUi()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package eu.darken.capod.common
|
||||
|
||||
import java.util.*
|
||||
import java.util.BitSet
|
||||
|
||||
fun Byte.toHex(): String = String.format("%02X", this)
|
||||
fun UByte.toHex(): String = this.toByte().toHex()
|
||||
@@ -18,3 +18,12 @@ fun UShort.isBitSet(pos: Int): Boolean = this.toShort().isBitSet(pos)
|
||||
|
||||
fun UShort.toBinaryString(): String = Integer.toBinaryString(this.toInt()).padStart(4, '0')
|
||||
fun UByte.toBinaryString(): String = Integer.toBinaryString(this.toInt()).padStart(8, '0')
|
||||
|
||||
|
||||
fun ByteArray.toHex(separator: String = "-"): String = joinToString(separator) { "%02X".format(it) }
|
||||
fun String.fromHex(): ByteArray = this
|
||||
.replace(" ", "")
|
||||
.replace("-", "")
|
||||
.also { require(it.length % 2 == 0) { "Not a HEX string, length: ${it.length}" } }
|
||||
.chunked(2).map { it.toInt(16).toByte() }
|
||||
.toByteArray()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package eu.darken.capod.common
|
||||
|
||||
object PrivacyPolicy {
|
||||
const val URL = "https://github.com/d4rken-org/capod/blob/main/PRIVACY_POLICY.md"
|
||||
const val URL = "https://capod.darken.eu/privacy"
|
||||
}
|
||||
@@ -2,10 +2,11 @@ package eu.darken.capod.common
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import dagger.Reusable
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.ERROR
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -15,13 +16,13 @@ class WebpageTool @Inject constructor(
|
||||
) {
|
||||
|
||||
fun open(address: String) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(address)).apply {
|
||||
val intent = Intent(Intent.ACTION_VIEW, address.toUri()).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
try {
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
log(ERROR) { "Failed to launch" }
|
||||
log(ERROR) { "Failed to launch: ${e.asLog()}" }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
package eu.darken.capod.common.bluetooth
|
||||
|
||||
typealias BluetoothAddress = String
|
||||
@@ -7,7 +7,7 @@ data class BluetoothDevice2(
|
||||
internal val internal: BluetoothDevice,
|
||||
val seenFirstAt: Instant,
|
||||
) {
|
||||
val address: String
|
||||
val address: BluetoothAddress
|
||||
get() = internal.address
|
||||
|
||||
val name: String?
|
||||
|
||||
@@ -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.DebugSettings
|
||||
import eu.darken.capod.common.fromHex
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
import kotlin.random.Random
|
||||
@@ -25,7 +26,7 @@ class FakeBleData @Inject constructor(
|
||||
address = "78:73:AF:B4:85:22",
|
||||
rssi = Random.nextInt(100) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 02 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 02 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
@@ -36,7 +37,7 @@ class FakeBleData @Inject constructor(
|
||||
address = "78:73:FF:B4:85:5E",
|
||||
rssi = Random.nextInt(100) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 100,
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 0F 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 0F 20 75 AA B6 31 00 05 9C 5A A4 5D C0 2C A0 B4 6F B9 ED 8E CE 03 97 CA".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
@@ -47,7 +48,7 @@ class FakeBleData @Inject constructor(
|
||||
address = "4E:9E:D1:49:D2:6D",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 200,
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 13 20 55 AF 56 31 00 06 6F E4 DF 10 AF 10 60 81 03 3B 76 D9 C7 11 22 88".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 13 20 55 AF 56 31 00 06 6F E4 DF 10 AF 10 60 81 03 3B 76 D9 C7 11 22 88".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
@@ -57,7 +58,7 @@ class FakeBleData @Inject constructor(
|
||||
address = "7E:E5:C7:65:D2:B5",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 300,
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
@@ -67,7 +68,7 @@ class FakeBleData @Inject constructor(
|
||||
address = "5E:9E:D1:49:D2:6D",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 10 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 10 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
@@ -78,7 +79,7 @@ class FakeBleData @Inject constructor(
|
||||
address = "5E:9E:D1:29:D2:6D",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 400,
|
||||
manufacturerSpecificData = mapOf(76 to "07 13 01 02 20 71 AA 37 32 00 10 00 64 64 FF 00 00 00 00 00 00".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 13 01 02 20 71 AA 37 32 00 10 00 64 64 FF 00 00 00 00 00 00".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
@@ -89,22 +90,11 @@ class FakeBleData @Inject constructor(
|
||||
address = "6E:9E:D1:49:D2:6D",
|
||||
rssi = Random.nextInt(15, 75) * -1,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 500,
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 FF 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".hexToByteArray())
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 FF 20 0A F4 8F 00 01 00 C4 71 9F 9C EF A2 E3 BA 66 FE 1D 45 9F C9 2F A0".fromHex())
|
||||
).run {
|
||||
fakeDevices.add(this)
|
||||
}
|
||||
|
||||
return fakeDevices
|
||||
}
|
||||
|
||||
|
||||
private fun String.hexToByteArray(): ByteArray {
|
||||
val trimmed = this
|
||||
.replace(" ", "")
|
||||
.replace(">", "")
|
||||
.replace("<", "")
|
||||
require(trimmed.length % 2 == 0) { "Not a HEX string" }
|
||||
return trimmed.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package eu.darken.capod.common.serialization
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.ToJson
|
||||
import kotlin.io.encoding.Base64
|
||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||
|
||||
@Suppress("unused")
|
||||
@OptIn(ExperimentalEncodingApi::class)
|
||||
class ByteArrayAdapter {
|
||||
@ToJson
|
||||
fun toJson(obj: ByteArray): String = Base64.encode(obj)
|
||||
|
||||
@FromJson
|
||||
fun fromJson(base64: String): ByteArray? = Base64.decode(base64)
|
||||
}
|
||||
@@ -15,6 +15,7 @@ class SerializationModule {
|
||||
@Singleton
|
||||
fun moshi(): Moshi = Moshi.Builder()
|
||||
.add(JavaInstantAdapter())
|
||||
.add(ByteArrayAdapter())
|
||||
.build()
|
||||
|
||||
}
|
||||
|
||||
@@ -5,12 +5,15 @@ 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.BluetoothAddress
|
||||
import eu.darken.capod.common.bluetooth.ScannerMode
|
||||
import eu.darken.capod.common.debug.DebugSettings
|
||||
import eu.darken.capod.common.preferences.PreferenceStoreMapper
|
||||
import eu.darken.capod.common.preferences.Settings
|
||||
import eu.darken.capod.common.preferences.createFlowPreference
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.protocol.IdentityResolvingKey
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityEncryptionKey
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -24,14 +27,27 @@ class GeneralSettings @Inject constructor(
|
||||
override val preferences: SharedPreferences = context.getSharedPreferences("settings_general", Context.MODE_PRIVATE)
|
||||
|
||||
val monitorMode = preferences.createFlowPreference("core.monitor.mode", MonitorMode.AUTOMATIC, moshi)
|
||||
|
||||
val useExtraMonitorNotification = preferences.createFlowPreference("core.monitor.notification.connected", false)
|
||||
|
||||
val scannerMode = preferences.createFlowPreference("core.scanner.mode", ScannerMode.BALANCED, moshi)
|
||||
|
||||
val showAll = preferences.createFlowPreference("core.showall.enabled", true)
|
||||
|
||||
val minimumSignalQuality = preferences.createFlowPreference("core.signal.minimum", 0.25f)
|
||||
val minimumSignalQuality = preferences.createFlowPreference("core.signal.minimum", 0.20f)
|
||||
|
||||
val mainDeviceAddress = preferences.createFlowPreference<String?>("core.maindevice.address", null)
|
||||
val mainDeviceAddress = preferences.createFlowPreference<BluetoothAddress?>("core.maindevice.address", null)
|
||||
val mainDeviceModel = preferences.createFlowPreference("core.maindevice.model", PodDevice.Model.UNKNOWN, moshi)
|
||||
val mainDeviceIdentityKey = preferences.createFlowPreference<IdentityResolvingKey?>(
|
||||
"core.maindevice.identitykey",
|
||||
null,
|
||||
moshi
|
||||
)
|
||||
val mainDeviceEncryptionKey = preferences.createFlowPreference<ProximityEncryptionKey?>(
|
||||
"core.maindevice.encryptionkey",
|
||||
null,
|
||||
moshi
|
||||
)
|
||||
|
||||
val isOffloadedFilteringDisabled = preferences.createFlowPreference(
|
||||
"core.compat.offloaded.filtering.disabled",
|
||||
@@ -44,6 +60,7 @@ class GeneralSettings @Inject constructor(
|
||||
|
||||
override val preferenceDataStore: PreferenceDataStore = PreferenceStoreMapper(
|
||||
monitorMode,
|
||||
useExtraMonitorNotification,
|
||||
scannerMode,
|
||||
showAll,
|
||||
minimumSignalQuality,
|
||||
|
||||
@@ -19,10 +19,18 @@ import eu.darken.capod.main.core.GeneralSettings
|
||||
import eu.darken.capod.main.core.PermissionTool
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.PodFactory
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.retryWhen
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import java.time.Duration
|
||||
@@ -36,10 +44,10 @@ class PodMonitor @Inject constructor(
|
||||
private val bleScanner: BleScanner,
|
||||
private val podFactory: PodFactory,
|
||||
private val generalSettings: GeneralSettings,
|
||||
private val bluetoothManager: BluetoothManager2,
|
||||
bluetoothManager: BluetoothManager2,
|
||||
private val debugSettings: DebugSettings,
|
||||
private val podDeviceCache: PodDeviceCache,
|
||||
private val permissionTool: PermissionTool,
|
||||
permissionTool: PermissionTool,
|
||||
) {
|
||||
|
||||
private val deviceCache = mutableMapOf<PodDevice.Id, PodDevice>()
|
||||
@@ -121,6 +129,7 @@ class PodMonitor @Inject constructor(
|
||||
log(TAG, WARN) { "Using unfiltered scan mode" }
|
||||
setOf(ScanFilter.Builder().build())
|
||||
}
|
||||
|
||||
else -> ProximityPairing.getBleScanFilter()
|
||||
}
|
||||
|
||||
@@ -189,8 +198,13 @@ class PodMonitor @Inject constructor(
|
||||
}
|
||||
|
||||
private fun determineMainDevice(pods: List<PodDevice>): PodDevice? {
|
||||
val mainDeviceModel = generalSettings.mainDeviceModel.value
|
||||
val irkHit = pods.filterIsInstance<ApplePods>().firstOrNull { it.flags.isIRKMatch }
|
||||
if (irkHit != null) {
|
||||
log(TAG) { "Main device determined via IRK: $irkHit" }
|
||||
return irkHit
|
||||
}
|
||||
|
||||
val mainDeviceModel = generalSettings.mainDeviceModel.value
|
||||
val presorted = sortPodsToInterest(pods).sortedByDescending {
|
||||
it.model == mainDeviceModel && it.model != PodDevice.Model.UNKNOWN
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import eu.darken.capod.common.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.bluetooth.BluetoothAddress
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import java.time.Duration
|
||||
@@ -20,7 +21,7 @@ interface PodDevice {
|
||||
|
||||
val model: Model
|
||||
|
||||
val address: String
|
||||
val address: BluetoothAddress
|
||||
get() = scanResult.address
|
||||
|
||||
val seenLastAt: Instant
|
||||
@@ -107,6 +108,10 @@ interface PodDevice {
|
||||
"AirPods Max",
|
||||
R.drawable.devic_headphones_generic
|
||||
),
|
||||
@Json(name = "airpods.max.usbc") AIRPODS_MAX_USBC(
|
||||
"AirPods Max USB-C",
|
||||
R.drawable.devic_headphones_generic
|
||||
),
|
||||
@Json(name = "beats.flex") BEATS_FLEX(
|
||||
"Beats Flex"
|
||||
),
|
||||
@@ -128,6 +133,9 @@ interface PodDevice {
|
||||
@Json(name = "beats.powerbeats.pro") POWERBEATS_PRO(
|
||||
"Power Beats Pro"
|
||||
),
|
||||
@Json(name = "beats.powerbeats.pro2") POWERBEATS_PRO2(
|
||||
"Power Beats Pro 2"
|
||||
),
|
||||
@Json(name = "beats.fit.pro") BEATS_FIT_PRO(
|
||||
"Beats Fit Pro"
|
||||
),
|
||||
|
||||
@@ -5,10 +5,14 @@ import eu.darken.capod.common.debug.logging.Logging.Priority.WARN
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.main.core.GeneralSettings
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.misc.UnknownAppleDevice
|
||||
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import eu.darken.capod.pods.core.apple.protocol.RPAChecker
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import javax.inject.Inject
|
||||
@@ -18,13 +22,16 @@ import javax.inject.Singleton
|
||||
class AppleFactory @Inject constructor(
|
||||
private val continuityProtocolDecoder: ContinuityProtocol.Decoder,
|
||||
private val proximityPairingDecoder: ProximityPairing.Decoder,
|
||||
private val podFactories: @JvmSuppressWildcards Set<ApplePodsFactory<out ApplePods>>,
|
||||
private val proximityMessageDecrypter: ProximityMessage.Decrypter,
|
||||
private val podFactories: @JvmSuppressWildcards Set<ApplePodsFactory>,
|
||||
private val unknownAppleFactory: UnknownAppleDevice.Factory,
|
||||
private val generalSettings: GeneralSettings,
|
||||
private val rpaChecker: RPAChecker,
|
||||
) {
|
||||
|
||||
private val lock = Mutex()
|
||||
|
||||
private fun getMessage(scanResult: BleScanResult): ProximityPairing.Message? {
|
||||
private fun getMessage(scanResult: BleScanResult): ProximityMessage? {
|
||||
val messages = try {
|
||||
continuityProtocolDecoder.decode(scanResult)
|
||||
} catch (e: Exception) {
|
||||
@@ -50,13 +57,39 @@ class AppleFactory @Inject constructor(
|
||||
}
|
||||
|
||||
suspend fun create(scanResult: BleScanResult): PodDevice? = lock.withLock {
|
||||
val pm = getMessage(scanResult) ?: return@withLock null
|
||||
val proximityMessage = getMessage(scanResult) ?: return@withLock null
|
||||
|
||||
val isIrkMatch = generalSettings.mainDeviceIdentityKey.value
|
||||
?.let { rpaChecker.verify(scanResult.address, it) }
|
||||
?: false
|
||||
|
||||
val payload = ProximityPayload(
|
||||
public = ProximityPayload.Public(
|
||||
proximityMessage.data.take(9).toUByteArray()
|
||||
),
|
||||
private = run {
|
||||
if (!isIrkMatch) return@run null
|
||||
if (proximityMessage.data.size != ProximityPairing.PAIRING_MESSAGE_LENGTH) return@run null
|
||||
|
||||
val encKey = generalSettings.mainDeviceEncryptionKey.value
|
||||
if (encKey == null) return@run null
|
||||
|
||||
val encrypted = proximityMessage.data.takeLast(16).toUByteArray().toByteArray()
|
||||
proximityMessageDecrypter.decrypt(encrypted, encKey)?.let {
|
||||
ProximityPayload.Private(data = it)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
val factory = podFactories.firstOrNull { it.isResponsible(proximityMessage) }
|
||||
|
||||
val factory = podFactories.firstOrNull { it.isResponsible(pm) }
|
||||
|
||||
return@withLock (factory ?: unknownAppleFactory).create(
|
||||
scanResult = scanResult,
|
||||
message = pm,
|
||||
payload = payload,
|
||||
flags = ApplePods.Flags(
|
||||
isIRKMatch = isIrkMatch,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import eu.darken.capod.pods.core.apple.airpods.AirPodsGen3
|
||||
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen4
|
||||
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen4Anc
|
||||
import eu.darken.capod.pods.core.apple.airpods.AirPodsMax
|
||||
import eu.darken.capod.pods.core.apple.airpods.AirPodsMaxUsbc
|
||||
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro
|
||||
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro2
|
||||
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro2Usbc
|
||||
@@ -22,6 +23,7 @@ import eu.darken.capod.pods.core.apple.beats.BeatsX
|
||||
import eu.darken.capod.pods.core.apple.beats.PowerBeats3
|
||||
import eu.darken.capod.pods.core.apple.beats.PowerBeats4
|
||||
import eu.darken.capod.pods.core.apple.beats.PowerBeatsPro
|
||||
import eu.darken.capod.pods.core.apple.beats.PowerBeatsPro2
|
||||
import eu.darken.capod.pods.core.apple.misc.FakeAirPodsGen1
|
||||
import eu.darken.capod.pods.core.apple.misc.FakeAirPodsGen2
|
||||
import eu.darken.capod.pods.core.apple.misc.FakeAirPodsGen3
|
||||
@@ -32,29 +34,31 @@ import eu.darken.capod.pods.core.apple.misc.FakeAirPodsPro2
|
||||
@Module
|
||||
abstract class AppleFactoryModule {
|
||||
|
||||
@Binds @IntoSet abstract fun airPodsGen1(factory: AirPodsGen1.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun airPodsGen2(factory: AirPodsGen2.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun airPodsGen3(factory: AirPodsGen3.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun airPodsGen4(factory: AirPodsGen4.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun airPodsGen4Anc(factory: AirPodsGen4Anc.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun airPodsGen1(factory: AirPodsGen1.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun airPodsGen2(factory: AirPodsGen2.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun airPodsGen3(factory: AirPodsGen3.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun airPodsGen4(factory: AirPodsGen4.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun airPodsGen4Anc(factory: AirPodsGen4Anc.Factory): ApplePodsFactory
|
||||
|
||||
@Binds @IntoSet abstract fun airPodsPro(factory: AirPodsPro.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun airPodsPro2(factory: AirPodsPro2.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun airPodsPro2Usbc(factory: AirPodsPro2Usbc.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun airPodsMax(factory: AirPodsMax.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun airPodsPro(factory: AirPodsPro.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun airPodsPro2(factory: AirPodsPro2.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun airPodsPro2Usbc(factory: AirPodsPro2Usbc.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun airPodsMax(factory: AirPodsMax.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun airPodsMax2(factory: AirPodsMaxUsbc.Factory): ApplePodsFactory
|
||||
|
||||
@Binds @IntoSet abstract fun beatsFlex(factory: BeatsFlex.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun beatsSolo3(factory: BeatsSolo3.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun beatsStudio3(factory: BeatsStudio3.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun beatsX(factory: BeatsX.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun powerBeats3(factory: PowerBeats3.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun powerBeats4(factory: PowerBeats4.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun powerBeatsPro(factory: PowerBeatsPro.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun beatsFitPro(factory: BeatsFitPro.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun beatsFlex(factory: BeatsFlex.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun beatsSolo3(factory: BeatsSolo3.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun beatsStudio3(factory: BeatsStudio3.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun beatsX(factory: BeatsX.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun powerBeats3(factory: PowerBeats3.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun powerBeats4(factory: PowerBeats4.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun powerBeatsPro(factory: PowerBeatsPro.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun powerBeatsPro2(factory: PowerBeatsPro2.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun beatsFitPro(factory: BeatsFitPro.Factory): ApplePodsFactory
|
||||
|
||||
@Binds @IntoSet abstract fun fakeAirPodsGen1(factory: FakeAirPodsGen1.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun fakeAirPodsGen2(factory: FakeAirPodsGen2.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun fakeAirPodsGen3(factory: FakeAirPodsGen3.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun fakeAirPodsPro(factory: FakeAirPodsPro.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun fakeAirPodsPro2(factory: FakeAirPodsPro2.Factory): ApplePodsFactory<out ApplePods>
|
||||
@Binds @IntoSet abstract fun fakeAirPodsGen1(factory: FakeAirPodsGen1.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun fakeAirPodsGen2(factory: FakeAirPodsGen2.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun fakeAirPodsGen3(factory: FakeAirPodsGen3.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun fakeAirPodsPro(factory: FakeAirPodsPro.Factory): ApplePodsFactory
|
||||
@Binds @IntoSet abstract fun fakeAirPodsPro2(factory: FakeAirPodsPro2.Factory): ApplePodsFactory
|
||||
}
|
||||
@@ -1,48 +1,62 @@
|
||||
package eu.darken.capod.pods.core.apple
|
||||
|
||||
import eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.common.toHex
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
|
||||
interface ApplePods : PodDevice {
|
||||
|
||||
val proximityMessage: ProximityPairing.Message
|
||||
val payload: ProximityPayload
|
||||
val flags: Flags
|
||||
|
||||
// We start counting at the airpods prefix byte
|
||||
val rawPrefix: UByte
|
||||
get() = proximityMessage.data[0]
|
||||
val pubPrefix: UByte
|
||||
get() = payload.public.data[0]
|
||||
|
||||
val rawDeviceModel: UShort
|
||||
get() = (((proximityMessage.data[1].toInt() and 255) shl 8) or (proximityMessage.data[2].toInt() and 255)).toUShort()
|
||||
val pubDeviceModel: UShort
|
||||
get() = (((payload.public.data[1].toInt() and 255) shl 8) or (payload.public.data[2].toInt() and 255)).toUShort()
|
||||
|
||||
val rawStatus: UByte
|
||||
get() = proximityMessage.data[3]
|
||||
val pubStatus: UByte
|
||||
get() = payload.public.data[3]
|
||||
|
||||
val rawStatusHex: String
|
||||
get() = rawStatus.toHex()
|
||||
val pubPodsBattery: UByte
|
||||
get() = payload.public.data[4]
|
||||
|
||||
val rawPodsBattery: UByte
|
||||
get() = proximityMessage.data[4]
|
||||
val pubFlags: UShort
|
||||
get() = payload.public.data[5].upperNibble
|
||||
|
||||
val rawPodsBatteryHex: String
|
||||
get() = rawPodsBattery.toHex()
|
||||
val pubCaseBattery: UShort
|
||||
get() = payload.public.data[5].lowerNibble
|
||||
|
||||
val rawFlags: UShort
|
||||
get() = proximityMessage.data[5].upperNibble
|
||||
val pubCaseLidState: UByte
|
||||
get() = payload.public.data[6]
|
||||
|
||||
val rawCaseBattery: UShort
|
||||
get() = proximityMessage.data[5].lowerNibble
|
||||
val pubDeviceColor: UByte
|
||||
get() = payload.public.data[7]
|
||||
|
||||
val rawCaseLidState: UByte
|
||||
get() = proximityMessage.data[6]
|
||||
val pubSuffix: UByte
|
||||
get() = payload.public.data[8]
|
||||
|
||||
val rawDeviceColor: UByte
|
||||
get() = proximityMessage.data[7]
|
||||
data class BatteryState(
|
||||
val isCharging: Boolean,
|
||||
val level: Float,
|
||||
)
|
||||
|
||||
val rawSuffix: UByte
|
||||
get() = proximityMessage.data[8]
|
||||
fun ProximityPayload.Private.asBatteryState(pos: Int): BatteryState? {
|
||||
val raw = data[pos]
|
||||
val level = (raw and 0x7Fu).toInt() / 100f
|
||||
if (level < 0f || level > 1.0f) return null
|
||||
|
||||
val isCharging = (raw and 0x80u).toInt() != 0
|
||||
|
||||
return BatteryState(
|
||||
isCharging = isCharging,
|
||||
level = level
|
||||
)
|
||||
}
|
||||
|
||||
data class Flags(
|
||||
val isIRKMatch: Boolean,
|
||||
)
|
||||
}
|
||||
@@ -1,108 +1,26 @@
|
||||
package eu.darken.capod.pods.core.apple
|
||||
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.collections.median
|
||||
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 eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.pods.core.HasCase
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import kotlin.math.max
|
||||
import eu.darken.capod.pods.core.apple.history.KnownDevice
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
|
||||
abstract class ApplePodsFactory<PodType : ApplePods>(private val tag: String) {
|
||||
interface ApplePodsFactory {
|
||||
fun isResponsible(message: ProximityMessage): Boolean
|
||||
fun create(scanResult: BleScanResult, payload: ProximityPayload, flags: ApplePods.Flags): ApplePods
|
||||
|
||||
data class Markings(
|
||||
val vendor: UByte,
|
||||
val length: UByte,
|
||||
val device: UShort,
|
||||
val podBatteryData: Set<UShort>,
|
||||
val caseBatteryData: UShort,
|
||||
val deviceColor: UByte,
|
||||
data class ModelInfo(
|
||||
val full: UShort,
|
||||
val dirty: UByte,
|
||||
)
|
||||
|
||||
private fun ProximityPairing.Message.getApplePodsMarkings(): Markings = Markings(
|
||||
vendor = ProximityPairing.CONTINUITY_PROTOCOL_MESSAGE_TYPE_PROXIMITY_PAIRING,
|
||||
length = ProximityPairing.PAIRING_MESSAGE_LENGTH.toUByte(),
|
||||
device = (((data[1].toInt() and 255) shl 8) or (data[2].toInt() and 255)).toUShort(),
|
||||
// Make comparison order independent
|
||||
podBatteryData = setOf(data[4].upperNibble, data[4].lowerNibble),
|
||||
caseBatteryData = data[5].lowerNibble,
|
||||
deviceColor = data[7]
|
||||
fun ProximityMessage.getModelInfo(): ModelInfo = ModelInfo(
|
||||
full = (((data[1].toInt() and 255) shl 8) or (data[2].toInt() and 255)).toUShort(),
|
||||
dirty = data[1]
|
||||
)
|
||||
|
||||
data class KnownDevice(
|
||||
val id: PodDevice.Id,
|
||||
val seenFirstAt: Instant,
|
||||
val seenCounter: Int,
|
||||
val history: List<ApplePods>,
|
||||
val lastCaseBattery: Float?,
|
||||
) {
|
||||
val lastMessage: ProximityPairing.Message
|
||||
get() = history.last().proximityMessage
|
||||
|
||||
val lastAddress: String
|
||||
get() = history.last().address
|
||||
|
||||
val reliability: Float
|
||||
get() {
|
||||
if (history.size < 2) return 0f
|
||||
|
||||
val now = Instant.now()
|
||||
|
||||
val pingInterval = List(history.dropLast(1).size) { index ->
|
||||
Duration.between(history[index].seenLastAt, history[index + 1].seenLastAt).toMillis()
|
||||
}.map { it.toInt() }.median()
|
||||
|
||||
val expectedPings: Float = LOOKBACK.toMillis() / pingInterval.toFloat()
|
||||
|
||||
val recentPings = history.filter {
|
||||
Duration.between(it.seenLastAt, now).toMillis() < LOOKBACK.toMillis() + pingInterval
|
||||
}.size
|
||||
|
||||
val reliability: Float = (recentPings / expectedPings)
|
||||
// log("#####") { "interval=$pingInterval, expectedPerMinute=$expectedPings, count=$recentPings" }
|
||||
|
||||
return max(0.0f, reliability).coerceAtMost(1f)
|
||||
}
|
||||
|
||||
fun rssiSmoothed(latest: Int): Int {
|
||||
val now = Instant.now()
|
||||
return history
|
||||
.filter { Duration.between(it.seenLastAt, now) < LOOKBACK }
|
||||
.map { it.rssi }
|
||||
.plus(latest)
|
||||
.median()
|
||||
}
|
||||
|
||||
fun isOlderThan(age: Duration): Boolean {
|
||||
val now = Instant.now()
|
||||
return Duration.between(history.last().seenLastAt, now) > age
|
||||
}
|
||||
|
||||
override fun toString(): String = "KnownDevice(history=${history.size}, last=${history.last()})"
|
||||
|
||||
companion object {
|
||||
// 30 seconds at fastest interval
|
||||
const val MAX_HISTORY = 60
|
||||
val LOOKBACK = Duration.ofSeconds(30)
|
||||
}
|
||||
}
|
||||
|
||||
internal val knownDevices = mutableMapOf<PodDevice.Id, KnownDevice>()
|
||||
|
||||
|
||||
fun KnownDevice.getLatestCaseBattery(): Float? = this.lastCaseBattery
|
||||
|
||||
private fun Collection<ApplePods>.determineLatestCaseBattery(): Float? = this
|
||||
.filterIsInstance<HasCase>()
|
||||
.mapNotNull { it.batteryCasePercent }
|
||||
.lastOrNull()
|
||||
|
||||
fun KnownDevice.getLatestCaseLidState(basic: DualApplePods): DualApplePods.LidState? {
|
||||
val definitive = setOf(
|
||||
DualApplePods.LidState.OPEN,
|
||||
@@ -118,89 +36,4 @@ abstract class ApplePodsFactory<PodType : ApplePods>(private val tag: String) {
|
||||
?.caseLidState
|
||||
?: DualApplePods.LidState.NOT_IN_CASE
|
||||
}
|
||||
|
||||
open fun historyTrimmer(
|
||||
pods: List<ApplePods>
|
||||
): List<ApplePods> {
|
||||
return pods.takeLast(KnownDevice.MAX_HISTORY)
|
||||
}
|
||||
|
||||
internal open fun searchHistory(current: PodType): KnownDevice? {
|
||||
val scanResult = current.scanResult
|
||||
val message = current.proximityMessage
|
||||
|
||||
knownDevices.values.toList().forEach { knownDevice ->
|
||||
if (knownDevice.isOlderThan(Duration.ofSeconds(30))) {
|
||||
log(tag, VERBOSE) { "searchHistory1: Removing stale known device: $knownDevice" }
|
||||
knownDevices.remove(knownDevice.id)
|
||||
}
|
||||
}
|
||||
|
||||
knownDevices.values
|
||||
.filter { it.history.size > KnownDevice.MAX_HISTORY }
|
||||
.toList()
|
||||
.forEach {
|
||||
knownDevices[it.id] = it.copy(history = historyTrimmer(it.history))
|
||||
}
|
||||
|
||||
var recognizedDevice: KnownDevice? = knownDevices.values
|
||||
.firstOrNull { it.lastAddress == scanResult.address }
|
||||
?.also { log(tag, VERBOSE) { "searchHistory1: Recovered previous ID via address: $it" } }
|
||||
|
||||
if (recognizedDevice == null) {
|
||||
val currentMarkers = message.getApplePodsMarkings()
|
||||
recognizedDevice = knownDevices.values
|
||||
.firstOrNull { it.lastMessage.getApplePodsMarkings() == currentMarkers }
|
||||
?.also { log(tag) { "searchHistory1: Close match based on similarity: $currentMarkers" } }
|
||||
}
|
||||
|
||||
if (recognizedDevice == null) {
|
||||
log(tag, WARN) { "searchHistory1: Didn't recognize: $message" }
|
||||
}
|
||||
|
||||
return recognizedDevice
|
||||
}
|
||||
|
||||
fun updateHistory(device: PodType) {
|
||||
val existing = knownDevices[device.identifier]
|
||||
|
||||
knownDevices[device.identifier] = when {
|
||||
existing != null -> {
|
||||
val history = existing.history.plus(device)
|
||||
existing.copy(
|
||||
seenCounter = existing.seenCounter + 1,
|
||||
history = history,
|
||||
lastCaseBattery = history.determineLatestCaseBattery() ?: existing.lastCaseBattery
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
log(tag) { "searchHistory1: Creating new history for $device" }
|
||||
val history = listOf(device)
|
||||
KnownDevice(
|
||||
id = device.identifier,
|
||||
seenFirstAt = device.seenFirstAt,
|
||||
seenCounter = 1,
|
||||
history = history,
|
||||
lastCaseBattery = history.determineLatestCaseBattery()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ModelInfo(
|
||||
val full: UShort,
|
||||
val dirty: UByte,
|
||||
)
|
||||
|
||||
fun ProximityPairing.Message.getModelInfo(): ModelInfo = ModelInfo(
|
||||
full = (((data[1].toInt() and 255) shl 8) or (data[2].toInt() and 255)).toUShort(),
|
||||
dirty = data[1]
|
||||
)
|
||||
|
||||
abstract fun isResponsible(message: ProximityPairing.Message): Boolean
|
||||
|
||||
abstract fun create(
|
||||
scanResult: BleScanResult,
|
||||
message: ProximityPairing.Message,
|
||||
): ApplePods
|
||||
}
|
||||
@@ -4,14 +4,18 @@ import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.isBitSet
|
||||
import eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.pods.core.*
|
||||
import eu.darken.capod.pods.core.DualPodDevice
|
||||
import eu.darken.capod.pods.core.DualPodDevice.Pod
|
||||
import eu.darken.capod.pods.core.HasCase
|
||||
import eu.darken.capod.pods.core.HasChargeDetectionDual
|
||||
import eu.darken.capod.pods.core.HasDualMicrophone
|
||||
import eu.darken.capod.pods.core.HasEarDetectionDual
|
||||
|
||||
interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasEarDetectionDual, HasCase,
|
||||
HasDualMicrophone, HasAppleColor {
|
||||
|
||||
val primaryPod: Pod
|
||||
get() = when (rawStatus.isBitSet(5)) {
|
||||
get() = when (pubStatus.isBitSet(5)) {
|
||||
true -> Pod.LEFT
|
||||
false -> Pod.RIGHT
|
||||
}
|
||||
@@ -21,13 +25,17 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasE
|
||||
* If the right pod is the primary, the values are flipped.
|
||||
*/
|
||||
val areValuesFlipped: Boolean
|
||||
get() = !rawStatus.isBitSet(5)
|
||||
get() = !pubStatus.isBitSet(5)
|
||||
|
||||
override val batteryLeftPodPercent: Float?
|
||||
get() {
|
||||
payload.private?.asBatteryState(if (areValuesFlipped) 2 else 1)?.let {
|
||||
return it.level
|
||||
}
|
||||
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.upperNibble.toInt()
|
||||
false -> rawPodsBattery.lowerNibble.toInt()
|
||||
true -> pubPodsBattery.upperNibble.toInt()
|
||||
false -> pubPodsBattery.lowerNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
@@ -42,9 +50,13 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasE
|
||||
|
||||
override val batteryRightPodPercent: Float?
|
||||
get() {
|
||||
payload.private?.asBatteryState(if (areValuesFlipped) 1 else 2)?.let {
|
||||
return it.level
|
||||
}
|
||||
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.lowerNibble.toInt()
|
||||
false -> rawPodsBattery.upperNibble.toInt()
|
||||
true -> pubPodsBattery.lowerNibble.toInt()
|
||||
false -> pubPodsBattery.upperNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
@@ -58,24 +70,24 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasE
|
||||
}
|
||||
|
||||
val isThisPodInThecase: Boolean
|
||||
get() = rawStatus.isBitSet(6)
|
||||
get() = pubStatus.isBitSet(6)
|
||||
|
||||
val isOnePodInCase: Boolean
|
||||
get() = rawStatus.isBitSet(4)
|
||||
get() = pubStatus.isBitSet(4)
|
||||
|
||||
val areBothPodsInCase: Boolean
|
||||
get() = rawStatus.isBitSet(2)
|
||||
get() = pubStatus.isBitSet(2)
|
||||
|
||||
override val isLeftPodInEar: Boolean
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(3)
|
||||
false -> rawStatus.isBitSet(1)
|
||||
true -> pubStatus.isBitSet(3)
|
||||
false -> pubStatus.isBitSet(1)
|
||||
}
|
||||
|
||||
override val isRightPodInEar: Boolean
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(1)
|
||||
false -> rawStatus.isBitSet(3)
|
||||
true -> pubStatus.isBitSet(1)
|
||||
false -> pubStatus.isBitSet(3)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,45 +95,63 @@ interface DualApplePods : ApplePods, HasChargeDetectionDual, DualPodDevice, HasE
|
||||
* For the pod that is in the case, this is flipped again though.
|
||||
*/
|
||||
override val isLeftPodMicrophone: Boolean
|
||||
get() = rawStatus.isBitSet(5) xor isThisPodInThecase
|
||||
get() = pubStatus.isBitSet(5) xor isThisPodInThecase
|
||||
|
||||
/**
|
||||
* The data flip bit is UNset if the right pod is primary.
|
||||
* For the pod that is in the case, this is flipped again though.
|
||||
*/
|
||||
override val isRightPodMicrophone: Boolean
|
||||
get() = !rawStatus.isBitSet(5) xor isThisPodInThecase
|
||||
get() = !pubStatus.isBitSet(5) xor isThisPodInThecase
|
||||
|
||||
override val isLeftPodCharging: Boolean
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(0)
|
||||
true -> rawFlags.isBitSet(1)
|
||||
get() {
|
||||
payload.private?.asBatteryState(if (areValuesFlipped) 2 else 1)?.let {
|
||||
return it.isCharging
|
||||
}
|
||||
return when (areValuesFlipped) {
|
||||
false -> pubFlags.isBitSet(0)
|
||||
true -> pubFlags.isBitSet(1)
|
||||
}
|
||||
}
|
||||
|
||||
override val isRightPodCharging: Boolean
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(1)
|
||||
true -> rawFlags.isBitSet(0)
|
||||
get() {
|
||||
payload.private?.asBatteryState(if (areValuesFlipped) 1 else 2)?.let {
|
||||
return it.isCharging
|
||||
}
|
||||
return when (areValuesFlipped) {
|
||||
false -> pubFlags.isBitSet(1)
|
||||
true -> pubFlags.isBitSet(0)
|
||||
}
|
||||
}
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = when (val value = rawCaseBattery.toInt()) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Case: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
get() {
|
||||
payload.private?.asBatteryState(3)?.let { return it.level }
|
||||
|
||||
return when (val value = pubCaseBattery.toInt()) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Case: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val isCaseCharging: Boolean
|
||||
get() = rawFlags.isBitSet(2)
|
||||
get() {
|
||||
payload.private?.asBatteryState(3)?.let { return it.isCharging }
|
||||
|
||||
return pubFlags.isBitSet(2)
|
||||
}
|
||||
|
||||
val caseLidState: LidState
|
||||
get() {
|
||||
val rawstate = rawCaseLidState
|
||||
return LidState.values().firstOrNull { it.rawRange.contains(rawstate.toInt()) } ?: LidState.UNKNOWN
|
||||
val rawstate = pubCaseLidState
|
||||
return LidState.entries.firstOrNull { it.rawRange.contains(rawstate.toInt()) } ?: LidState.UNKNOWN
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
package eu.darken.capod.pods.core.apple
|
||||
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.DEBUG
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
|
||||
abstract class DualApplePodsFactory(private val tag: String) : ApplePodsFactory<DualApplePods>(tag) {
|
||||
|
||||
fun DualApplePods.getCaseMatchMarkings() = SplitPodsMarkings(
|
||||
leftPodBattery = batteryLeftPodPercent,
|
||||
rightPodBattery = batteryRightPodPercent,
|
||||
microPhoneLeft = isLeftPodMicrophone,
|
||||
microPhoneRight = isRightPodMicrophone,
|
||||
chargingLeft = isLeftPodCharging,
|
||||
chargingRight = isRightPodCharging,
|
||||
color = rawDeviceColor,
|
||||
model = model
|
||||
)
|
||||
|
||||
/**
|
||||
* Split pods, one in case, one not.
|
||||
*/
|
||||
data class SplitPodsMarkings(
|
||||
val leftPodBattery: Float?,
|
||||
val rightPodBattery: Float?,
|
||||
val microPhoneLeft: Boolean,
|
||||
val microPhoneRight: Boolean,
|
||||
val chargingLeft: Boolean,
|
||||
val chargingRight: Boolean,
|
||||
val color: UByte,
|
||||
val model: PodDevice.Model,
|
||||
)
|
||||
|
||||
private fun Collection<KnownDevice>.findSplitPodsMatch(device: DualApplePods): Collection<KnownDevice> {
|
||||
val target = device.getCaseMatchMarkings()
|
||||
|
||||
return filter { known ->
|
||||
known.history
|
||||
.filterIsInstance<DualApplePods>()
|
||||
.any { it.getCaseMatchMarkings() == target }
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchHistory(current: DualApplePods): KnownDevice? {
|
||||
val basicResult = super.searchHistory(current)
|
||||
|
||||
val caseIgnored = knownDevices.values.findSplitPodsMatch(current)
|
||||
|
||||
log(tag, DEBUG) { "searchHistory2: Case ignored matches(${caseIgnored.size}): $caseIgnored" }
|
||||
|
||||
return when (caseIgnored.size) {
|
||||
0 -> basicResult
|
||||
1 -> caseIgnored.single()
|
||||
else -> {
|
||||
log(tag) { "searchHistory2: More than one result when ignoring case markers." }
|
||||
val oldest = caseIgnored.maxByOrNull { it.history.size } ?: return null
|
||||
|
||||
caseIgnored.minus(oldest).forEach {
|
||||
log(tag) { "searchHistory2: Removing outlier: $it" }
|
||||
knownDevices.remove(it.id)
|
||||
}
|
||||
|
||||
oldest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,8 +6,8 @@ import eu.darken.capod.pods.core.HasPodStyle
|
||||
interface HasAppleColor : ApplePods, HasPodStyle {
|
||||
|
||||
override val podStyle: HasPodStyle.PodStyle
|
||||
get() = DeviceColor.values()
|
||||
.firstOrNull { it.raw == rawDeviceColor }
|
||||
get() = DeviceColor.entries
|
||||
.firstOrNull { it.raw == pubDeviceColor }
|
||||
?: DeviceColor.UNKNOWN
|
||||
|
||||
enum class DeviceColor(val raw: UByte?) : HasPodStyle.PodStyle {
|
||||
|
||||
@@ -10,13 +10,18 @@ import eu.darken.capod.pods.core.SinglePodDevice
|
||||
interface SingleApplePods : ApplePods, SinglePodDevice, HasAppleColor {
|
||||
|
||||
override val batteryHeadsetPercent: Float?
|
||||
get() = when (val value = rawPodsBattery.lowerNibble.toInt()) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Headset above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
(value / 10f)
|
||||
get() {
|
||||
payload.private?.asBatteryState(1)?.let {
|
||||
return it.level
|
||||
}
|
||||
return when (val value = pubPodsBattery.lowerNibble.toInt()) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Headset above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
(value / 10f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package eu.darken.capod.pods.core.apple
|
||||
|
||||
abstract class SingleApplePodsFactory(private val tag: String) : ApplePodsFactory<SingleApplePods>(tag)
|
||||
@@ -4,19 +4,23 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class AirPodsGen1 constructor(
|
||||
data class AirPodsGen1(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
override val seenLastAt: Instant = Instant.now(),
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
@@ -34,19 +38,28 @@ data class AirPodsGen1 constructor(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super.rssi
|
||||
|
||||
class Factory @Inject constructor() : DualApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = AirPodsGen1(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = AirPodsGen1(
|
||||
scanResult = scanResult,
|
||||
payload = payload,
|
||||
flags = flags,
|
||||
)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -4,19 +4,23 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class AirPodsGen2 constructor(
|
||||
data class AirPodsGen2(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
override val seenLastAt: Instant = Instant.now(),
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
@@ -34,18 +38,28 @@ data class AirPodsGen2 constructor(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<DualApplePods>.rssi
|
||||
|
||||
class Factory @Inject constructor() : DualApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = AirPodsGen2(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = AirPodsGen2(
|
||||
scanResult = scanResult,
|
||||
payload = payload,
|
||||
flags = flags,
|
||||
)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -4,19 +4,23 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class AirPodsGen3 constructor(
|
||||
data class AirPodsGen3(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
override val seenLastAt: Instant = Instant.now(),
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
@@ -34,18 +38,24 @@ data class AirPodsGen3 constructor(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<DualApplePods>.rssi
|
||||
|
||||
class Factory @Inject constructor() : DualApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = AirPodsGen3(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = AirPodsGen3(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -16,7 +19,8 @@ data class AirPodsGen4(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
@@ -34,18 +38,24 @@ data class AirPodsGen4(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<DualApplePods>.rssi
|
||||
|
||||
class Factory @Inject constructor() : DualApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = AirPodsGen4(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = AirPodsGen4(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -16,7 +19,8 @@ data class AirPodsGen4Anc(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
@@ -34,18 +38,24 @@ data class AirPodsGen4Anc(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<DualApplePods>.rssi
|
||||
|
||||
class Factory @Inject constructor() : DualApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = AirPodsGen4Anc(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = AirPodsGen4Anc(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -7,10 +7,13 @@ import eu.darken.capod.pods.core.HasChargeDetection
|
||||
import eu.darken.capod.pods.core.HasEarDetection
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.HasAppleColor
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -20,7 +23,8 @@ data class AirPodsMax(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor {
|
||||
@@ -31,23 +35,34 @@ data class AirPodsMax(
|
||||
get() = rssiAverage ?: super<SingleApplePods>.rssi
|
||||
|
||||
override val isHeadsetBeingCharged: Boolean
|
||||
get() = rawFlags.isBitSet(0)
|
||||
|
||||
override val isBeingWorn: Boolean
|
||||
get() = rawStatus.isBitSet(5)
|
||||
|
||||
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
get() {
|
||||
payload.private?.asBatteryState(1)?.let {
|
||||
return it.isCharging
|
||||
}
|
||||
return pubFlags.isBitSet(0)
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = AirPodsMax(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override val isBeingWorn: Boolean
|
||||
get() = pubStatus.isBitSet(5)
|
||||
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = AirPodsMax(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
@@ -63,7 +78,7 @@ data class AirPodsMax(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DEVICE_CODE_DIRTY = 10.toUByte()
|
||||
private val DEVICE_CODE = 0x0A20.toUShort()
|
||||
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Max")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package eu.darken.capod.pods.core.apple.airpods
|
||||
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.isBitSet
|
||||
import eu.darken.capod.pods.core.HasChargeDetection
|
||||
import eu.darken.capod.pods.core.HasEarDetection
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.HasAppleColor
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class AirPodsMaxUsbc(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
override val seenLastAt: Instant = Instant.now(),
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
) : SingleApplePods, HasEarDetection, HasChargeDetection, HasAppleColor {
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.AIRPODS_MAX_USBC
|
||||
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<SingleApplePods>.rssi
|
||||
|
||||
override val isHeadsetBeingCharged: Boolean
|
||||
get() {
|
||||
payload.private?.asBatteryState(1)?.let {
|
||||
return it.isCharging
|
||||
}
|
||||
return pubFlags.isBitSet(0)
|
||||
}
|
||||
|
||||
override val isBeingWorn: Boolean
|
||||
get() = pubStatus.isBitSet(5)
|
||||
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = AirPodsMaxUsbc(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
return basic.copy(
|
||||
identifier = result.id,
|
||||
seenFirstAt = result.seenFirstAt,
|
||||
seenLastAt = scanResult.receivedAt,
|
||||
seenCounter = result.seenCounter,
|
||||
reliability = result.reliability,
|
||||
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DEVICE_CODE = 0x1F20.toUShort()
|
||||
private val TAG = logTag("PodDevice", "Apple", "AirPods", "Max", "USBC")
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,13 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods.LidState
|
||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -19,7 +22,8 @@ data class AirPodsPro(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
@@ -53,18 +57,28 @@ data class AirPodsPro(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<DualApplePods>.rssi
|
||||
|
||||
class Factory @Inject constructor() : DualApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = AirPodsPro(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = AirPodsPro(
|
||||
scanResult = scanResult,
|
||||
payload = payload,
|
||||
flags = flags,
|
||||
)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
@@ -79,7 +93,6 @@ data class AirPodsPro(
|
||||
cachedCaseState = result.getLatestCaseLidState(basic)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -6,10 +6,13 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods.LidState
|
||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -19,7 +22,8 @@ data class AirPodsPro2(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
@@ -53,18 +57,24 @@ data class AirPodsPro2(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<HasStateDetectionAirPods>.rssi
|
||||
|
||||
class Factory @Inject constructor() : DualApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = AirPodsPro2(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = AirPodsPro2(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
+18
-8
@@ -6,10 +6,13 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods.LidState
|
||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -19,7 +22,8 @@ data class AirPodsPro2Usbc(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
@@ -53,18 +57,24 @@ data class AirPodsPro2Usbc(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<HasStateDetectionAirPods>.rssi
|
||||
|
||||
class Factory @Inject constructor() : DualApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = AirPodsPro2Usbc(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = AirPodsPro2Usbc(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import eu.darken.capod.pods.core.apple.ApplePods
|
||||
interface HasStateDetectionAirPods : HasStateDetection, ApplePods {
|
||||
|
||||
override val state: ConnectionState
|
||||
get() = ConnectionState.values().firstOrNull { rawSuffix == it.raw } ?: ConnectionState.UNKNOWN
|
||||
get() = ConnectionState.entries.firstOrNull { pubSuffix == it.raw } ?: ConnectionState.UNKNOWN
|
||||
|
||||
enum class ConnectionState(val raw: UByte?, @StringRes val labelRes: Int) : HasStateDetection.State {
|
||||
DISCONNECTED(0x00, R.string.pods_connection_state_disconnected_label),
|
||||
|
||||
@@ -4,9 +4,12 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -16,7 +19,8 @@ data class BeatsFitPro(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
@@ -34,18 +38,24 @@ data class BeatsFitPro(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super.rssi
|
||||
|
||||
class Factory @Inject constructor() : DualApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = BeatsFitPro(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = BeatsFitPro(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -16,7 +19,8 @@ data class BeatsFlex(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
) : SingleApplePods {
|
||||
@@ -26,18 +30,24 @@ data class BeatsFlex(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super.rssi
|
||||
|
||||
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = BeatsFlex(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = BeatsFlex(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -16,7 +19,8 @@ data class BeatsSolo3(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
) : SingleApplePods {
|
||||
@@ -25,18 +29,24 @@ data class BeatsSolo3(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super.rssi
|
||||
|
||||
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = BeatsSolo3(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = BeatsSolo3(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -16,25 +19,32 @@ data class BeatsStudio3(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
) : SingleApplePods {
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.BEATS_STUDIO_3
|
||||
|
||||
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = BeatsStudio3(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = BeatsStudio3(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -16,7 +19,8 @@ data class BeatsX(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
) : SingleApplePods {
|
||||
@@ -26,18 +30,24 @@ data class BeatsX(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super.rssi
|
||||
|
||||
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = BeatsX(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = BeatsX(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -16,7 +19,8 @@ data class PowerBeats3(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
) : SingleApplePods {
|
||||
@@ -26,18 +30,24 @@ data class PowerBeats3(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super.rssi
|
||||
|
||||
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = PowerBeats3(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = PowerBeats3(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -4,10 +4,13 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.airpods.HasStateDetectionAirPods
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -17,7 +20,8 @@ data class PowerBeats4(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
) : SingleApplePods, HasStateDetectionAirPods {
|
||||
@@ -27,18 +31,24 @@ data class PowerBeats4(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<SingleApplePods>.rssi
|
||||
|
||||
class Factory @Inject constructor() : SingleApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = PowerBeats4(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = PowerBeats4(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -4,9 +4,12 @@ import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -16,7 +19,8 @@ data class PowerBeatsPro(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
@@ -34,18 +38,24 @@ data class PowerBeatsPro(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super.rssi
|
||||
|
||||
class Factory @Inject constructor() : DualApplePodsFactory(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
getModelInfo().dirty == DEVICE_CODE_DIRTY && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = PowerBeatsPro(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = PowerBeatsPro(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
@@ -64,7 +74,7 @@ data class PowerBeatsPro(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DEVICE_CODE_DIRTY = 11.toUByte()
|
||||
private val DEVICE_CODE = 0x0B20.toUShort()
|
||||
private val TAG = logTag("PodDevice", "Beats", "PowerBeats", "Pro")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package eu.darken.capod.pods.core.apple.beats
|
||||
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
data class PowerBeatsPro2(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
override val seenLastAt: Instant = Instant.now(),
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
private val cachedCaseState: DualApplePods.LidState? = null
|
||||
) : DualApplePods {
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.POWERBEATS_PRO2
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
override val caseLidState: DualApplePods.LidState
|
||||
get() = cachedCaseState ?: super.caseLidState
|
||||
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super.rssi
|
||||
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
getModelInfo().full == DEVICE_CODE && length == ProximityPairing.PAIRING_MESSAGE_LENGTH
|
||||
}
|
||||
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = PowerBeatsPro2(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
return basic.copy(
|
||||
identifier = result.id,
|
||||
seenFirstAt = result.seenFirstAt,
|
||||
seenLastAt = scanResult.receivedAt,
|
||||
seenCounter = result.seenCounter,
|
||||
reliability = result.reliability,
|
||||
cachedBatteryPercentage = result.getLatestCaseBattery(),
|
||||
rssiAverage = result.rssiSmoothed(basic.rssi),
|
||||
cachedCaseState = result.getLatestCaseLidState(basic)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DEVICE_CODE = 0x1D20.toUShort()
|
||||
private val TAG = logTag("PodDevice", "Beats", "PowerBeats", "Pro2")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package eu.darken.capod.pods.core.apple.history
|
||||
|
||||
import eu.darken.capod.common.bluetooth.BluetoothAddress
|
||||
import eu.darken.capod.common.collections.median
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import kotlin.math.max
|
||||
|
||||
data class KnownDevice(
|
||||
val id: PodDevice.Id,
|
||||
val seenFirstAt: Instant,
|
||||
val seenCounter: Int,
|
||||
val history: List<ApplePods>,
|
||||
val lastCaseBattery: Float?,
|
||||
) {
|
||||
val lastPayload: ProximityPayload
|
||||
get() = history.last().payload
|
||||
|
||||
val lastAddress: BluetoothAddress
|
||||
get() = history.last().address
|
||||
|
||||
val reliability: Float
|
||||
get() {
|
||||
if (history.size < 2) return 0f
|
||||
|
||||
val now = Instant.now()
|
||||
|
||||
val pingInterval = List(history.dropLast(1).size) { index ->
|
||||
Duration.between(history[index].seenLastAt, history[index + 1].seenLastAt).toMillis()
|
||||
}.map { it.toInt() }.median()
|
||||
|
||||
val expectedPings: Float = LOOKBACK.toMillis() / pingInterval.toFloat()
|
||||
|
||||
val recentPings = history.filter {
|
||||
Duration.between(it.seenLastAt, now).toMillis() < LOOKBACK.toMillis() + pingInterval
|
||||
}.size
|
||||
|
||||
val reliability: Float = (recentPings / expectedPings)
|
||||
// log("#####") { "interval=$pingInterval, expectedPerMinute=$expectedPings, count=$recentPings" }
|
||||
|
||||
return max(0.0f, reliability).coerceAtMost(1f)
|
||||
}
|
||||
|
||||
fun rssiSmoothed(latest: Int): Int {
|
||||
val now = Instant.now()
|
||||
return history
|
||||
.filter { Duration.between(it.seenLastAt, now) < LOOKBACK }
|
||||
.map { it.rssi }
|
||||
.plus(latest)
|
||||
.median()
|
||||
}
|
||||
|
||||
fun isOlderThan(age: Duration): Boolean {
|
||||
val now = Instant.now()
|
||||
return Duration.between(history.last().seenLastAt, now) > age
|
||||
}
|
||||
|
||||
override fun toString(): String = "KnownDevice(history=${history.size}, last=${history.last()})"
|
||||
|
||||
companion object {
|
||||
// 30 seconds at fastest interval
|
||||
const val MAX_HISTORY = 60
|
||||
val LOOKBACK = Duration.ofSeconds(30)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package eu.darken.capod.pods.core.apple.history
|
||||
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.DEBUG
|
||||
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 eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.main.core.GeneralSettings
|
||||
import eu.darken.capod.pods.core.HasCase
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import eu.darken.capod.pods.core.apple.protocol.RPAChecker
|
||||
import java.time.Duration
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class PodHistoryRepo @Inject constructor(
|
||||
private val generalSettings: GeneralSettings,
|
||||
private val rpaChecker: RPAChecker,
|
||||
) {
|
||||
|
||||
private val knownDevices = mutableMapOf<PodDevice.Id, KnownDevice>()
|
||||
|
||||
data class Identifier(
|
||||
val device: UShort,
|
||||
val podBatteryData: Set<UShort>,
|
||||
val caseBatteryData: UShort,
|
||||
val deviceColor: UByte,
|
||||
)
|
||||
|
||||
private fun ProximityPayload.getFuzzyIdentifier(): Identifier = Identifier(
|
||||
device = (((public.data[1].toInt() and 255) shl 8) or (public.data[2].toInt() and 255)).toUShort(),
|
||||
// Make comparison order independent
|
||||
podBatteryData = setOf(public.data[4].upperNibble, public.data[4].lowerNibble),
|
||||
caseBatteryData = public.data[5].lowerNibble,
|
||||
deviceColor = public.data[7]
|
||||
)
|
||||
|
||||
fun DualApplePods.getCaseMatchMarkings() = SplitPodsMarkings(
|
||||
leftPodBattery = batteryLeftPodPercent,
|
||||
rightPodBattery = batteryRightPodPercent,
|
||||
microPhoneLeft = isLeftPodMicrophone,
|
||||
microPhoneRight = isRightPodMicrophone,
|
||||
chargingLeft = isLeftPodCharging,
|
||||
chargingRight = isRightPodCharging,
|
||||
color = pubDeviceColor,
|
||||
model = model
|
||||
)
|
||||
|
||||
/**
|
||||
* Split pods, one in case, one not.
|
||||
*/
|
||||
data class SplitPodsMarkings(
|
||||
val leftPodBattery: Float?,
|
||||
val rightPodBattery: Float?,
|
||||
val microPhoneLeft: Boolean,
|
||||
val microPhoneRight: Boolean,
|
||||
val chargingLeft: Boolean,
|
||||
val chargingRight: Boolean,
|
||||
val color: UByte,
|
||||
val model: PodDevice.Model,
|
||||
)
|
||||
|
||||
fun search(current: ApplePods): KnownDevice? {
|
||||
val basicResult = baseSearch(current)
|
||||
|
||||
val caseIgnored = if (current is DualApplePods) {
|
||||
val target = current.getCaseMatchMarkings()
|
||||
knownDevices.values.filter { known ->
|
||||
known.history.filterIsInstance<DualApplePods>().any { it.getCaseMatchMarkings() == target }
|
||||
}
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
|
||||
log(TAG, DEBUG) { "search2: Case ignored matches(${caseIgnored.size}): $caseIgnored" }
|
||||
|
||||
return when (caseIgnored.size) {
|
||||
0 -> basicResult
|
||||
1 -> caseIgnored.single()
|
||||
else -> {
|
||||
log(TAG) { "search2: More than one result when ignoring case markers." }
|
||||
val oldest = caseIgnored.maxByOrNull { it.history.size } ?: return null
|
||||
|
||||
caseIgnored.minus(oldest).forEach {
|
||||
log(TAG) { "search2: Removing outlier: $it" }
|
||||
knownDevices.remove(it.id)
|
||||
}
|
||||
|
||||
oldest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun baseSearch(current: ApplePods): KnownDevice? {
|
||||
val scanResult = current.scanResult
|
||||
val payload = current.payload
|
||||
|
||||
// Timeout
|
||||
knownDevices.values.toList()
|
||||
.filter { it.isOlderThan(Duration.ofSeconds(30)) }
|
||||
.forEach { knownDevice ->
|
||||
log(TAG, VERBOSE) { "search1: Removing stale known device: $knownDevice" }
|
||||
knownDevices.remove(knownDevice.id)
|
||||
}
|
||||
|
||||
// Trim history
|
||||
knownDevices.values
|
||||
.filter { it.history.size > KnownDevice.MAX_HISTORY }
|
||||
.toList()
|
||||
.forEach { knownDevices[it.id] = it.copy(history = it.history.takeLast(KnownDevice.MAX_HISTORY)) }
|
||||
|
||||
var recognizedDevice: KnownDevice? = knownDevices.values
|
||||
.firstOrNull { it.lastAddress == scanResult.address }
|
||||
?.also { log(TAG, VERBOSE) { "search1: Recovered previous ID via address: $it" } }
|
||||
|
||||
if (recognizedDevice == null) {
|
||||
generalSettings.mainDeviceIdentityKey.value?.let { key ->
|
||||
if (!rpaChecker.verify(current.address, key)) {
|
||||
log(TAG, VERBOSE) { "search1: Current device does not match IRK" }
|
||||
return@let
|
||||
}
|
||||
|
||||
recognizedDevice = knownDevices.values
|
||||
.firstOrNull { rpaChecker.verify(it.lastAddress, key) }
|
||||
.also { log(TAG, VERBOSE) { "search1: Recovered previous ID via IRK: $it" } }
|
||||
}
|
||||
}
|
||||
|
||||
if (recognizedDevice == null) {
|
||||
val currentMarkers = payload.getFuzzyIdentifier()
|
||||
recognizedDevice = knownDevices.values
|
||||
.firstOrNull { it.lastPayload.getFuzzyIdentifier() == currentMarkers }
|
||||
?.also { log(TAG) { "search1: Close match based on similarity: $currentMarkers" } }
|
||||
}
|
||||
|
||||
if (recognizedDevice == null) log(TAG, WARN) { "search1: Didn't recognize: $current" }
|
||||
|
||||
return recognizedDevice
|
||||
}
|
||||
|
||||
fun updateHistory(device: ApplePods) {
|
||||
val existing = knownDevices[device.identifier]
|
||||
|
||||
fun Collection<ApplePods>.determineLatestCaseBattery(): Float? = this
|
||||
.filterIsInstance<HasCase>()
|
||||
.mapNotNull { it.batteryCasePercent }
|
||||
.lastOrNull()
|
||||
|
||||
knownDevices[device.identifier] = if (existing != null) {
|
||||
val history = existing.history.plus(device)
|
||||
existing.copy(
|
||||
seenCounter = existing.seenCounter + 1,
|
||||
history = history,
|
||||
lastCaseBattery = history.determineLatestCaseBattery() ?: existing.lastCaseBattery
|
||||
)
|
||||
} else {
|
||||
log(TAG) { "Creating new history for $device" }
|
||||
val history = listOf(device)
|
||||
KnownDevice(
|
||||
id = device.identifier,
|
||||
seenFirstAt = device.seenFirstAt,
|
||||
seenCounter = 1,
|
||||
history = history,
|
||||
lastCaseBattery = history.determineLatestCaseBattery()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("Pod", "Apple", "Factory", "History")
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
package eu.darken.capod.pods.core.apple.misc
|
||||
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.isBitSet
|
||||
import eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.pods.core.*
|
||||
import eu.darken.capod.pods.core.HasCase
|
||||
import eu.darken.capod.pods.core.HasChargeDetectionDual
|
||||
import eu.darken.capod.pods.core.HasEarDetectionDual
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -18,118 +20,46 @@ import javax.inject.Inject
|
||||
* Similar data structure but a lot of placeholder values or hardcoded values
|
||||
* Marketed as TWS i99999
|
||||
*/
|
||||
data class FakeAirPodsGen1 constructor(
|
||||
data class FakeAirPodsGen1(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
override val seenLastAt: Instant = Instant.now(),
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
||||
) : DualApplePods, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN1
|
||||
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<ApplePods>.rssi
|
||||
|
||||
/**
|
||||
* Normally values for the left pod are in the lower nibbles, if the left pod is primary (microphone)
|
||||
* If the right pod is the primary, the values are flipped.
|
||||
*/
|
||||
val areValuesFlipped: Boolean
|
||||
get() = !rawStatus.isBitSet(5)
|
||||
|
||||
override val batteryLeftPodPercent: Float?
|
||||
get() {
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.upperNibble.toInt()
|
||||
false -> rawPodsBattery.lowerNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Left pod: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
(value / 10f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val batteryRightPodPercent: Float?
|
||||
get() {
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.lowerNibble.toInt()
|
||||
false -> rawPodsBattery.upperNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Right pod: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val isThisPodInThecase: Boolean
|
||||
get() = rawStatus.isBitSet(6)
|
||||
|
||||
override val isLeftPodInEar: Boolean
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(3)
|
||||
false -> rawStatus.isBitSet(1)
|
||||
}
|
||||
|
||||
override val isRightPodInEar: Boolean
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(1)
|
||||
false -> rawStatus.isBitSet(3)
|
||||
}
|
||||
|
||||
override val isLeftPodCharging: Boolean
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(0)
|
||||
true -> rawFlags.isBitSet(1)
|
||||
}
|
||||
|
||||
override val isRightPodCharging: Boolean
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(1)
|
||||
true -> rawFlags.isBitSet(0)
|
||||
}
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = when (val value = rawCaseBattery.toInt()) {
|
||||
15 -> cachedBatteryPercentage
|
||||
else -> if (value > 10) {
|
||||
log { "Case: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
}
|
||||
}
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
override val isCaseCharging: Boolean
|
||||
get() = rawFlags.isBitSet(2)
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<DualApplePods>.rssi
|
||||
|
||||
class Factory @Inject constructor() : ApplePodsFactory<FakeAirPodsGen1>(TAG) {
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
// Official message length is 19HEX, i.e. binary 25, did they copy this wrong?
|
||||
getModelInfo().full == DEVICE_CODE && length == 19
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = FakeAirPodsGen1(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = FakeAirPodsGen1(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -1,133 +1,63 @@
|
||||
package eu.darken.capod.pods.core.apple.misc
|
||||
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.isBitSet
|
||||
import eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.pods.core.*
|
||||
import eu.darken.capod.pods.core.HasCase
|
||||
import eu.darken.capod.pods.core.HasChargeDetectionDual
|
||||
import eu.darken.capod.pods.core.HasEarDetectionDual
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Similar data structure but a lot of placeholder values or hardcoded values
|
||||
*/
|
||||
data class FakeAirPodsGen2 constructor(
|
||||
data class FakeAirPodsGen2(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
override val seenLastAt: Instant = Instant.now(),
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
||||
) : DualApplePods, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN2
|
||||
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<ApplePods>.rssi
|
||||
|
||||
/**
|
||||
* Normally values for the left pod are in the lower nibbles, if the left pod is primary (microphone)
|
||||
* If the right pod is the primary, the values are flipped.
|
||||
*/
|
||||
val areValuesFlipped: Boolean
|
||||
get() = !rawStatus.isBitSet(5)
|
||||
|
||||
override val batteryLeftPodPercent: Float?
|
||||
get() {
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.upperNibble.toInt()
|
||||
false -> rawPodsBattery.lowerNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Left pod: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
(value / 10f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val batteryRightPodPercent: Float?
|
||||
get() {
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.lowerNibble.toInt()
|
||||
false -> rawPodsBattery.upperNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Right pod: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val isThisPodInThecase: Boolean
|
||||
get() = rawStatus.isBitSet(6)
|
||||
|
||||
override val isLeftPodInEar: Boolean
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(3)
|
||||
false -> rawStatus.isBitSet(1)
|
||||
}
|
||||
|
||||
override val isRightPodInEar: Boolean
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(1)
|
||||
false -> rawStatus.isBitSet(3)
|
||||
}
|
||||
|
||||
override val isLeftPodCharging: Boolean
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(0)
|
||||
true -> rawFlags.isBitSet(1)
|
||||
}
|
||||
|
||||
override val isRightPodCharging: Boolean
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(1)
|
||||
true -> rawFlags.isBitSet(0)
|
||||
}
|
||||
get() = rssiAverage ?: super<DualApplePods>.rssi
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = when (val value = rawCaseBattery.toInt()) {
|
||||
15 -> cachedBatteryPercentage
|
||||
else -> if (value > 10) {
|
||||
log { "Case: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
}
|
||||
}
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
override val isCaseCharging: Boolean
|
||||
get() = rawFlags.isBitSet(2)
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
class Factory @Inject constructor() : ApplePodsFactory<FakeAirPodsGen2>(TAG) {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
// Official message length is 19HEX, i.e. binary 25, did they copy this wrong?
|
||||
getModelInfo().full == DEVICE_CODE && length == 19
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = FakeAirPodsGen2(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = FakeAirPodsGen2(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package eu.darken.capod.pods.core.apple.misc
|
||||
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.isBitSet
|
||||
import eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.pods.core.*
|
||||
import eu.darken.capod.pods.core.HasCase
|
||||
import eu.darken.capod.pods.core.HasChargeDetectionDual
|
||||
import eu.darken.capod.pods.core.HasEarDetectionDual
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -18,118 +20,46 @@ import javax.inject.Inject
|
||||
* Shorter data structure.
|
||||
* https://discord.com/channels/548521543039189022/927235844127993866/1054404630118924308
|
||||
*/
|
||||
data class FakeAirPodsGen3 constructor(
|
||||
data class FakeAirPodsGen3(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
override val seenLastAt: Instant = Instant.now(),
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
) : ApplePods, DualPodDevice, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
||||
) : DualApplePods, HasEarDetectionDual, HasChargeDetectionDual, HasCase {
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_GEN3
|
||||
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<ApplePods>.rssi
|
||||
|
||||
/**
|
||||
* Normally values for the left pod are in the lower nibbles, if the left pod is primary (microphone)
|
||||
* If the right pod is the primary, the values are flipped.
|
||||
*/
|
||||
val areValuesFlipped: Boolean
|
||||
get() = !rawStatus.isBitSet(5)
|
||||
|
||||
override val batteryLeftPodPercent: Float?
|
||||
get() {
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.upperNibble.toInt()
|
||||
false -> rawPodsBattery.lowerNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Left pod: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
(value / 10f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val batteryRightPodPercent: Float?
|
||||
get() {
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.lowerNibble.toInt()
|
||||
false -> rawPodsBattery.upperNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Right pod: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val isLeftPodCharging: Boolean
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(0)
|
||||
true -> rawFlags.isBitSet(1)
|
||||
}
|
||||
|
||||
override val isRightPodCharging: Boolean
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(1)
|
||||
true -> rawFlags.isBitSet(0)
|
||||
}
|
||||
|
||||
private val isThisPodInThecase: Boolean
|
||||
get() = rawStatus.isBitSet(6)
|
||||
|
||||
override val isLeftPodInEar: Boolean
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(3)
|
||||
false -> rawStatus.isBitSet(1)
|
||||
}
|
||||
|
||||
override val isRightPodInEar: Boolean
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(1)
|
||||
false -> rawStatus.isBitSet(3)
|
||||
}
|
||||
get() = rssiAverage ?: super<DualApplePods>.rssi
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = when (val value = rawCaseBattery.toInt()) {
|
||||
15 -> cachedBatteryPercentage
|
||||
else -> if (value > 10) {
|
||||
log { "Case: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
}
|
||||
}
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
override val isCaseCharging: Boolean
|
||||
get() = rawFlags.isBitSet(2)
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
class Factory @Inject constructor() : ApplePodsFactory<FakeAirPodsGen3>(TAG) {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
// Official message length is 19HEX, i.e. binary 25, did they copy this wrong?
|
||||
getModelInfo().full == DEVICE_CODE && length == 19
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = FakeAirPodsGen3(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = FakeAirPodsGen3(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -3,15 +3,17 @@ package eu.darken.capod.pods.core.apple.misc
|
||||
import androidx.annotation.DrawableRes
|
||||
import eu.darken.capod.common.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.isBitSet
|
||||
import eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.pods.core.*
|
||||
import eu.darken.capod.pods.core.HasCase
|
||||
import eu.darken.capod.pods.core.HasChargeDetectionDual
|
||||
import eu.darken.capod.pods.core.HasEarDetectionDual
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -20,17 +22,18 @@ import javax.inject.Inject
|
||||
* Shorter data structure.
|
||||
* https://discord.com/channels/548521543039189022/927235844127993866/962068944196358234
|
||||
*/
|
||||
data class FakeAirPodsPro constructor(
|
||||
data class FakeAirPodsPro(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
override val seenLastAt: Instant = Instant.now(),
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
) : ApplePods, DualPodDevice, HasChargeDetectionDual, HasEarDetectionDual, HasCase {
|
||||
) : DualApplePods, HasChargeDetectionDual, HasEarDetectionDual, HasCase {
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_PRO
|
||||
|
||||
@@ -51,103 +54,30 @@ data class FakeAirPodsPro constructor(
|
||||
get() = R.drawable.devic_airpods_pro2_right
|
||||
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super<ApplePods>.rssi
|
||||
|
||||
/**
|
||||
* Normally values for the left pod are in the lower nibbles, if the left pod is primary (microphone)
|
||||
* If the right pod is the primary, the values are flipped.
|
||||
*/
|
||||
val areValuesFlipped: Boolean
|
||||
get() = !rawStatus.isBitSet(5)
|
||||
|
||||
override val batteryLeftPodPercent: Float?
|
||||
get() {
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.upperNibble.toInt()
|
||||
false -> rawPodsBattery.lowerNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Left pod: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
(value / 10f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val batteryRightPodPercent: Float?
|
||||
get() {
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.lowerNibble.toInt()
|
||||
false -> rawPodsBattery.upperNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Right pod: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val isLeftPodCharging: Boolean
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(0)
|
||||
true -> rawFlags.isBitSet(1)
|
||||
}
|
||||
|
||||
override val isRightPodCharging: Boolean
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(1)
|
||||
true -> rawFlags.isBitSet(0)
|
||||
}
|
||||
|
||||
private val isThisPodInThecase: Boolean
|
||||
get() = rawStatus.isBitSet(6)
|
||||
|
||||
override val isLeftPodInEar: Boolean
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(3)
|
||||
false -> rawStatus.isBitSet(1)
|
||||
}
|
||||
|
||||
override val isRightPodInEar: Boolean
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(1)
|
||||
false -> rawStatus.isBitSet(3)
|
||||
}
|
||||
get() = rssiAverage ?: super<DualApplePods>.rssi
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = when (val value = rawCaseBattery.toInt()) {
|
||||
15 -> cachedBatteryPercentage
|
||||
else -> if (value > 10) {
|
||||
log { "Case: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
}
|
||||
}
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
override val isCaseCharging: Boolean
|
||||
get() = rawFlags.isBitSet(2)
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
class Factory @Inject constructor() : ApplePodsFactory<FakeAirPodsPro>(TAG) {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
// Official message length is 19HEX, i.e. binary 25, did they copy this wrong?
|
||||
getModelInfo().full == DEVICE_CODE && length == 19
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = FakeAirPodsPro(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = FakeAirPodsPro(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
@@ -3,16 +3,19 @@ package eu.darken.capod.pods.core.apple.misc
|
||||
import androidx.annotation.DrawableRes
|
||||
import eu.darken.capod.common.R
|
||||
import eu.darken.capod.common.bluetooth.BleScanResult
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.isBitSet
|
||||
import eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.pods.core.*
|
||||
import eu.darken.capod.pods.core.DualPodDevice
|
||||
import eu.darken.capod.pods.core.HasCase
|
||||
import eu.darken.capod.pods.core.HasChargeDetectionDual
|
||||
import eu.darken.capod.pods.core.HasEarDetectionDual
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods.LidState
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -20,18 +23,19 @@ import javax.inject.Inject
|
||||
* AirPods Pro2 clone
|
||||
* https://discord.com/channels/548521543039189022/927235844127993866/1060911213539774504
|
||||
*/
|
||||
data class FakeAirPodsPro2 constructor(
|
||||
data class FakeAirPodsPro2(
|
||||
override val identifier: PodDevice.Id = PodDevice.Id(),
|
||||
override val seenLastAt: Instant = Instant.now(),
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = PodDevice.BASE_CONFIDENCE,
|
||||
private val rssiAverage: Int? = null,
|
||||
private val cachedBatteryPercentage: Float? = null,
|
||||
private val cachedCaseState: DualApplePods.LidState? = null,
|
||||
) : ApplePods, HasChargeDetectionDual, DualPodDevice, HasEarDetectionDual, HasCase {
|
||||
private val cachedCaseState: LidState? = null,
|
||||
) : DualApplePods, HasChargeDetectionDual, DualPodDevice, HasEarDetectionDual, HasCase {
|
||||
|
||||
override val model: PodDevice.Model = PodDevice.Model.FAKE_AIRPODS_PRO2
|
||||
|
||||
@@ -52,99 +56,32 @@ data class FakeAirPodsPro2 constructor(
|
||||
get() = R.drawable.devic_airpods_pro2_right
|
||||
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: scanResult.rssi
|
||||
|
||||
private val areValuesFlipped: Boolean
|
||||
get() = !rawStatus.isBitSet(5)
|
||||
|
||||
override val batteryLeftPodPercent: Float?
|
||||
get() {
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.upperNibble.toInt()
|
||||
false -> rawPodsBattery.lowerNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Left pod: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
(value / 10f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val batteryRightPodPercent: Float?
|
||||
get() {
|
||||
val value = when (areValuesFlipped) {
|
||||
true -> rawPodsBattery.lowerNibble.toInt()
|
||||
false -> rawPodsBattery.upperNibble.toInt()
|
||||
}
|
||||
return when (value) {
|
||||
15 -> null
|
||||
else -> if (value > 10) {
|
||||
log { "Right pod: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val isThisPodInThecase: Boolean
|
||||
get() = rawStatus.isBitSet(6)
|
||||
|
||||
override val isLeftPodInEar: Boolean
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(3)
|
||||
false -> rawStatus.isBitSet(1)
|
||||
}
|
||||
|
||||
override val isRightPodInEar: Boolean
|
||||
get() = when (areValuesFlipped xor isThisPodInThecase) {
|
||||
true -> rawStatus.isBitSet(1)
|
||||
false -> rawStatus.isBitSet(3)
|
||||
}
|
||||
|
||||
override val isLeftPodCharging: Boolean
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(0)
|
||||
true -> rawFlags.isBitSet(1)
|
||||
}
|
||||
|
||||
override val isRightPodCharging: Boolean
|
||||
get() = when (areValuesFlipped) {
|
||||
false -> rawFlags.isBitSet(1)
|
||||
true -> rawFlags.isBitSet(0)
|
||||
}
|
||||
get() = rssiAverage ?: super<DualApplePods>.rssi
|
||||
override val caseLidState: LidState
|
||||
get() = cachedCaseState ?: super.caseLidState
|
||||
|
||||
override val batteryCasePercent: Float?
|
||||
get() = when (val value = rawCaseBattery.toInt()) {
|
||||
15 -> cachedBatteryPercentage
|
||||
else -> if (value > 10) {
|
||||
log { "Case: Above 100% battery: $value" }
|
||||
1.0f
|
||||
} else {
|
||||
value / 10f
|
||||
}
|
||||
}
|
||||
get() = super.batteryCasePercent ?: cachedBatteryPercentage
|
||||
|
||||
override val isCaseCharging: Boolean
|
||||
get() = rawFlags.isBitSet(2)
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
|
||||
class Factory @Inject constructor() : ApplePodsFactory<FakeAirPodsPro2>(TAG) {
|
||||
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = message.run {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = message.run {
|
||||
// Official message length is 19HEX, i.e. binary 25, did they copy this wrong?
|
||||
getModelInfo().full == DEVICE_CODE && length == 19
|
||||
}
|
||||
|
||||
override fun create(scanResult: BleScanResult, message: ProximityPairing.Message): ApplePods {
|
||||
var basic = FakeAirPodsPro2(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags
|
||||
): ApplePods {
|
||||
var basic = FakeAirPodsPro2(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
+14
-8
@@ -7,7 +7,9 @@ import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.ApplePods
|
||||
import eu.darken.capod.pods.core.apple.ApplePodsFactory
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||
import eu.darken.capod.pods.core.apple.history.PodHistoryRepo
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityMessage
|
||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPayload
|
||||
import java.time.Instant
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -17,7 +19,8 @@ data class UnknownAppleDevice(
|
||||
override val seenFirstAt: Instant = Instant.now(),
|
||||
override val seenCounter: Int = 1,
|
||||
override val scanResult: BleScanResult,
|
||||
override val proximityMessage: ProximityPairing.Message,
|
||||
override val payload: ProximityPayload,
|
||||
override val flags: ApplePods.Flags,
|
||||
override val reliability: Float = 0f,
|
||||
private val rssiAverage: Int? = null,
|
||||
) : ApplePods {
|
||||
@@ -29,18 +32,21 @@ data class UnknownAppleDevice(
|
||||
override val rssi: Int
|
||||
get() = rssiAverage ?: super.rssi
|
||||
|
||||
class Factory @Inject constructor() : ApplePodsFactory<ApplePods>(TAG) {
|
||||
override fun isResponsible(message: ProximityPairing.Message): Boolean = true
|
||||
class Factory @Inject constructor(
|
||||
private val repo: PodHistoryRepo,
|
||||
) : ApplePodsFactory {
|
||||
override fun isResponsible(message: ProximityMessage): Boolean = true
|
||||
|
||||
override fun create(
|
||||
scanResult: BleScanResult,
|
||||
message: ProximityPairing.Message,
|
||||
payload: ProximityPayload,
|
||||
flags: ApplePods.Flags,
|
||||
): ApplePods {
|
||||
var basic = UnknownAppleDevice(scanResult = scanResult, proximityMessage = message)
|
||||
val result = searchHistory(basic)
|
||||
var basic = UnknownAppleDevice(scanResult = scanResult, payload = payload, flags = flags)
|
||||
val result = repo.search(basic)
|
||||
|
||||
if (result != null) basic = basic.copy(identifier = result.id)
|
||||
updateHistory(basic)
|
||||
repo.updateHistory(basic)
|
||||
|
||||
if (result == null) return basic
|
||||
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package eu.darken.capod.pods.core.apple.protocol
|
||||
|
||||
typealias IdentityResolvingKey = ByteArray
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
package eu.darken.capod.pods.core.apple.protocol
|
||||
|
||||
typealias ProximityEncryptionKey = ByteArray
|
||||
@@ -0,0 +1,58 @@
|
||||
package eu.darken.capod.pods.core.apple.protocol
|
||||
|
||||
import android.R.id.message
|
||||
import android.annotation.SuppressLint
|
||||
import dagger.Reusable
|
||||
import eu.darken.capod.common.debug.logging.Logging
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import javax.inject.Inject
|
||||
|
||||
data class ProximityMessage(
|
||||
val type: UByte,
|
||||
val length: Int,
|
||||
val data: UByteArray
|
||||
) {
|
||||
|
||||
override fun toString(): String {
|
||||
val dataHex = data.joinToString(separator = " ") { String.format("%02X", it.toByte()) }
|
||||
return "ProximityPairing.Message(type=$type, length=$length, data=$dataHex)"
|
||||
}
|
||||
|
||||
@Reusable
|
||||
class Decrypter @Inject constructor() {
|
||||
@SuppressLint("GetInstance")
|
||||
fun decrypt(data: ByteArray, key: ProximityEncryptionKey): UByteArray? {
|
||||
val decryptedData = try {
|
||||
val cipher = Cipher.getInstance("AES/ECB/NoPadding").apply {
|
||||
val secretKey = SecretKeySpec(key, "AES")
|
||||
init(Cipher.DECRYPT_MODE, secretKey)
|
||||
}
|
||||
cipher.doFinal(data.copyOfRange(data.size - 16, data.size))
|
||||
} catch (e: Exception) {
|
||||
log(
|
||||
TAG,
|
||||
Logging.Priority.ERROR
|
||||
) { "Failed to decrypt $message with ${key.toByteString()}\n${e.asLog()}" }
|
||||
null
|
||||
}
|
||||
|
||||
log(
|
||||
TAG,
|
||||
Logging.Priority.VERBOSE
|
||||
) { "Decrypted $message with ${key.toByteString()} to ${decryptedData?.toByteString()}" }
|
||||
|
||||
if (decryptedData == null || decryptedData.size != 16) return null
|
||||
|
||||
return decryptedData.toUByteArray()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("Pod", "Factory", "Decrypter")
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-14
@@ -8,27 +8,15 @@ import javax.inject.Inject
|
||||
|
||||
object ProximityPairing {
|
||||
|
||||
data class Message(
|
||||
val type: UByte,
|
||||
val length: Int,
|
||||
val data: UByteArray
|
||||
) {
|
||||
|
||||
override fun toString(): String {
|
||||
val dataHex = data.joinToString(separator = " ") { String.format("%02X", it.toByte()) }
|
||||
return "ProximityPairing.Message(type=$type, length=$length, data=$dataHex)"
|
||||
}
|
||||
}
|
||||
|
||||
@Reusable
|
||||
class Decoder @Inject constructor() {
|
||||
fun decode(message: ContinuityProtocol.Message): Message? {
|
||||
fun decode(message: ContinuityProtocol.Message): ProximityMessage? {
|
||||
if (message.type != CONTINUITY_PROTOCOL_MESSAGE_TYPE_PROXIMITY_PAIRING) {
|
||||
log { "Not a proximity pairing message: $this" }
|
||||
return null
|
||||
}
|
||||
|
||||
return Message(
|
||||
return ProximityMessage(
|
||||
type = message.type,
|
||||
length = message.length,
|
||||
data = message.data
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package eu.darken.capod.pods.core.apple.protocol
|
||||
|
||||
data class ProximityPayload(
|
||||
val public: Public,
|
||||
val private: Private?,
|
||||
) {
|
||||
|
||||
data class Public(
|
||||
val data: UByteArray,
|
||||
)
|
||||
|
||||
data class Private(
|
||||
val data: UByteArray,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package eu.darken.capod.pods.core.apple.protocol
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import eu.darken.capod.common.bluetooth.BluetoothAddress
|
||||
import eu.darken.capod.common.debug.logging.Logging
|
||||
import eu.darken.capod.common.debug.logging.asLog
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import javax.inject.Inject
|
||||
|
||||
class RPAChecker @Inject constructor() {
|
||||
|
||||
// Resolvable-Private-Address
|
||||
fun verify(address: BluetoothAddress, irk: IdentityResolvingKey): Boolean = try {
|
||||
val rpa = address.split(":").map { it.toInt(16).toByte() }.reversed().toByteArray()
|
||||
val prand = rpa.copyOfRange(3, 6)
|
||||
val hash = rpa.copyOfRange(0, 3)
|
||||
val computedHash = ah(irk, prand)
|
||||
hash.contentEquals(computedHash)
|
||||
} catch (e: Exception) {
|
||||
log(
|
||||
TAG,
|
||||
Logging.Priority.ERROR
|
||||
) { "Failed to verify RPA\naddress=${address}\nIRK=${irk.toByteString()}\n${e.asLog()}" }
|
||||
false
|
||||
}
|
||||
|
||||
// E function (Encryption function):
|
||||
@SuppressLint("GetInstance")
|
||||
private fun e(key: ByteArray, data: ByteArray): ByteArray {
|
||||
val cipher = Cipher.getInstance("AES/ECB/NoPadding").apply {
|
||||
val secretKey = SecretKeySpec(key.reversedArray(), "AES")
|
||||
init(Cipher.ENCRYPT_MODE, secretKey)
|
||||
}
|
||||
return cipher.doFinal(data.reversedArray()).reversedArray()
|
||||
}
|
||||
|
||||
// AH function (Address Hashing function):
|
||||
private fun ah(k: ByteArray, r: ByteArray): ByteArray {
|
||||
val rPadded = ByteArray(16).apply {
|
||||
r.copyInto(this, 0, 0, 3)
|
||||
}
|
||||
return e(k, rPadded).copyOfRange(0, 3)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("Monitor", "PodMonitor", "RPAChecker")
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package eu.darken.capod.reaction.core.popup
|
||||
|
||||
import eu.darken.capod.common.bluetooth.BluetoothAddress
|
||||
import eu.darken.capod.common.bluetooth.BluetoothManager2
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.INFO
|
||||
import eu.darken.capod.common.debug.logging.Logging.Priority.VERBOSE
|
||||
@@ -51,8 +52,8 @@ class PopUpReaction @Inject constructor(
|
||||
return@mapNotNull null
|
||||
}
|
||||
log(TAG, VERBOSE) {
|
||||
val prev = previous?.rawCaseLidState?.let { String.format("%02X", it.toByte()) }
|
||||
val cur = current.rawCaseLidState.let { String.format("%02X", it.toByte()) }
|
||||
val prev = previous?.pubCaseLidState?.let { String.format("%02X", it.toByte()) }
|
||||
val cur = current.pubCaseLidState.let { String.format("%02X", it.toByte()) }
|
||||
"previous=$prev (${previous?.caseLidState}), current=$cur (${current.caseLidState})"
|
||||
}
|
||||
log(TAG, VERBOSE) { "previous-id=${previous?.identifier}, current-id=${current.identifier}" }
|
||||
@@ -109,7 +110,7 @@ class PopUpReaction @Inject constructor(
|
||||
else -> null
|
||||
}
|
||||
|
||||
private val connectionCoolDowns = mutableMapOf<String, Instant>()
|
||||
private val connectionCoolDowns = mutableMapOf<BluetoothAddress, Instant>()
|
||||
|
||||
private fun monitorConnection(): Flow<Event> = reactionSettings.showPopUpOnConnection.flow
|
||||
.flatMapLatest { isEnabled ->
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M23 4.5C23 6.43 21.43 8 19.5 8S16 6.43 16 4.5 17.57 1 19.5 1 23 2.57 23 4.5M19.5 10C16.47 10 14 7.53 14 4.5C14 4 14.08 3.5 14.21 3H5C3.89 3 3 3.89 3 5V19C3 20.11 3.9 21 5 21H19C20.11 21 21 20.11 21 19V9.79C20.5 9.92 20 10 19.5 10Z" />
|
||||
</vector>
|
||||
@@ -1,2 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
|
||||
<string name="general_value_not_available_label">N.v.t.</string>
|
||||
<string name="general_error_label">Fout</string>
|
||||
<string name="general_grant_permission_action">Gee toestemming</string>
|
||||
|
||||
<string name="overview_nomaindevice_label">Geen primêre toestel</string>
|
||||
<string name="overview_nomaindevice_description">Alle bespeurde toestelle is waarskynlik nie joune nie. Skakel jou toestel aan en koppel dit of pas die instellings aan.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth is gedeaktiveer</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth is gedeaktiveer, aktiveer dit ;)</string>
|
||||
|
||||
<string name="permission_bluetooth_connect_label">Bluetooth-verbinding</string>
|
||||
<string name="permission_bluetooth_connect_description">Hierdie toepassing vereis die "Bluetooth-verbinding"-toestemming om met gekoppelde toestelle te kommunikeer en verbindings te inisieer.</string>
|
||||
<string name="permission_bluetooth_scan_label">Bluetooth-skandering</string>
|
||||
<string name="permission_bluetooth_scan_description">Die "Bluetooth-skandering"-toestemming laat hierdie toepassing toe om Bluetooth-data van nabygeleë toestelle soos jou AirPods te ontdek en te ontvang.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Hierdie toepassing vereis die "Bluetooth"-toestemming om aan gekoppelde Bluetooth-toestelle te koppel.</string>
|
||||
<string name="permission_access_fine_location_label">Toegang tot presiese ligging</string>
|
||||
<string name="permission_access_fine_location_description">CAPod gebruik die "presiese ligging"-toestemming om Bluetooth Lae Energie-data te ontvang. Jou oorfone gebruik Bluetooth Lae Energie-tegnologie om hul status uit te saai. Hierdie toepassing sal NIE Bluetooth-data gebruik om jou ligging te bepaal NIE.</string>
|
||||
<string name="permission_background_location_label">Toegang tot agtergrondligging</string>
|
||||
<string name="permission_background_location_description">CAPod gebruik "toegang tot agtergrondligging" om funksies soos "Wys opspringer" en "Autokoppel" moontlik te maak terwyl die toepassing gesluit is. Toegang tot agtergrondligging laat die toepassing toe om Bluetooth Lae Energie-data te ontvang terwyl dit in die agtergrond is. Hierdie toepassing sal NIE Bluetooth-data gebruik om jou ligging te bepaal NIE.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Deaktiveer batteryoptimalisering</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Batteryoptimalisering verhoed dat hierdie toepassing betroubaar Bluetooth-data ontvang terwyl die toepassing in die agtergrond is.</string>
|
||||
<string name="permission_required_title">Die volgende toestemming word vereis:</string>
|
||||
<string name="permission_system_alert_window_label">Stelselwaarskuwingsvenster</string>
|
||||
<string name="permission_system_alert_window_description">Laat CAPod toe om bo-oor ander toepassings te teken om die funksie "Wys PopUp" moontlik te maak.</string>
|
||||
|
||||
<string name="settings_scanner_mode_lowpower_label">Lae krag</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Gebalanseerd</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Lae latensie</string>
|
||||
|
||||
<string name="settings_monitor_mode_manual_label">Wanneer toepassing oop is</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Wanneer toestel gekoppel is</string>
|
||||
<string name="settings_monitor_mode_always_label">Altyd</string>
|
||||
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Wanneer gesien</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Houer is oop</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">In oor</string>
|
||||
|
||||
<string name="pods_dual_left_label">Linker pod</string>
|
||||
<string name="pods_dual_right_label">Regter pod</string>
|
||||
<string name="pods_case_label">Houer</string>
|
||||
<string name="pods_case_status_open_label">Oop</string>
|
||||
<string name="pods_case_status_closed_label">Gesluit</string>
|
||||
<string name="pods_connection_state_disconnected_label">Nie aan \'n toestel gekoppel nie</string>
|
||||
<string name="pods_connection_state_idle_label">Gekoppel aan \'n toestel, maar ledig</string>
|
||||
<string name="pods_connection_state_music_label">In musiekmodus</string>
|
||||
<string name="pods_connection_state_call_label">In oproepmodus</string>
|
||||
<string name="pods_connection_state_ringing_label">Lui</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Besig om af te haak</string>
|
||||
<string name="pods_connection_state_unknown_label">Onbekende verbindingstatus</string>
|
||||
<string name="pods_unknown_raw_data_label">Rou data</string>
|
||||
<string name="pods_unknown_label">Onbekende toestel</string>
|
||||
<string name="pods_unknown_contact_dev">Hierdie is \'n onbekende toestel, maar dit gebruik \'n soortgelyke boodskapformaat. Kom ons voeg ondersteuning daarvoor by, kontak my :)</string>
|
||||
<string name="pods_none_label_short">Geen toestel</string>
|
||||
<string name="pods_charging_label">Laai tans</string>
|
||||
<string name="pods_inear_label">In oor</string>
|
||||
<string name="pods_microphone_label">Mikrofoon</string>
|
||||
<string name="pods_yours">Joune</string>
|
||||
<string name="headset_being_worn_label">Word gedra</string>
|
||||
<string name="headset_not_being_worn_label">Word nie gedra nie</string>
|
||||
<string name="pods_case_unknown_state">Onbekende status</string>
|
||||
|
||||
<string name="last_seen_x">Laas gesien: %s</string>
|
||||
<string name="first_seen_x">Eerste gesien: %s</string>
|
||||
<string name="permission_post_notifications_label">Wys kennisgewings</string>
|
||||
<string name="permission_post_notifications_description">Laat CAPod toe om kennisgewings oor jou AirPods te wys, bv. hul huidige status terwyl hulle gekoppel is.</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
<string name="general_value_not_available_label">የለም</string>
|
||||
<string name="general_error_label">ስህተት</string>
|
||||
<string name="general_grant_permission_action">ፍቃድ ስጥ</string>
|
||||
<string name="overview_nomaindevice_label">ዋና መሣሪያ የለም</string>
|
||||
<string name="overview_nomaindevice_description">የተገኙት ሁሉም መሣሪያዎች የእርስዎ ላይሆኑ ይችላሉ። መሣሪያዎን ያብሩና ያገናኙ ወይም ቅንብሮቹን ያስተካክሉ።</string>
|
||||
<string name="overview_bluetooth_disabled_label">ብሉቱዝ ጠፍቷል</string>
|
||||
<string name="overview_bluetooth_disabled_description">ብሉቱዝ ጠፍቷል፣ ያብሩት ;)</string>
|
||||
<string name="permission_bluetooth_connect_label">የብሉቱዝ ግንኙነት</string>
|
||||
<string name="permission_bluetooth_connect_description">ይህ መተግበሪያ ከተጣመሩ መሣሪያዎች ጋር ለመገናኘት እና ግንኙነቶችን ለመጀመር የ \"ብሉቱዝ ግንኙነት\" ፍቃድ ያስፈልገዋል።</string>
|
||||
<string name="permission_bluetooth_scan_label">የብሉቱዝ ቅኝት</string>
|
||||
<string name="permission_bluetooth_scan_description">የ \"ብሉቱዝ ቅኝት\" ፍቃድ ይህ መተግበሪያ እንደ የእርስዎ AirPods ካሉ በአቅራቢያ ካሉ መሣሪያዎች የብሉቱዝ መረጃን እንዲያገኝ እና እንዲቀበል ያስችለዋል።</string>
|
||||
<string name="permission_bluetooth_label">ብሉቱዝ</string>
|
||||
<string name="permission_bluetooth_description">ይህ መተግበሪያ ከተጣመሩ የብሉቱዝ መሣሪያዎች ጋር ለመገናኘት የ \"ብሉቱዝ\" ፍቃድ ያስፈልገዋል።</string>
|
||||
<string name="permission_access_fine_location_label">ትክክለኛ አካባቢን ይድረሱ</string>
|
||||
<string name="permission_access_fine_location_description">CAPod የብሉቱዝ ዝቅተኛ ኃይል መረጃን ለመቀበል የ \"ትክክለኛ አካባቢ\" ፍቃድን ይጠቀማል። የእርስዎ የጆሮ ማዳመጫዎች ሁኔታቸውን ለማሰራጨት የብሉቱዝ ዝቅተኛ ኃይል ቴክኖሎጂን ይጠቀማሉ። ይህ መተግበሪያ አካባቢዎን ለመወሰን የብሉቱዝ መረጃን አይጠቀምም።</string>
|
||||
<string name="permission_background_location_label">የዳራ አካባቢ መዳረሻ</string>
|
||||
<string name="permission_background_location_description">CAPod መተግበሪያው ሲዘጋ እንደ \"ብቅ-ባይ አሳይ\" እና \"ራስ-ሰር ግንኙነት\" ያሉ ባህሪያትን ለማንቃት \"የዳራ አካባቢ መዳረሻ\"ን ይጠቀማል። የዳራ አካባቢ መዳረሻ መተግበሪያው በዳራ ውስጥ እያለ የብሉቱዝ ዝቅተኛ ኃይል መረጃን እንዲቀበል ያስችለዋል። ይህ መተግበሪያ አካባቢዎን ለመወሰን የብሉቱዝ መረጃን አይጠቀምም።</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">የባትሪ ማመቻቸትን አሰናክል</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">የባትሪ ማመቻቸት ይህ መተግበሪያ በዳራ ውስጥ እያለ የብሉቱዝ መረጃን በአስተማማኝ ሁኔታ እንዳይቀበል ይከለክላል።</string>
|
||||
<string name="permission_required_title">የሚከተለው ፍቃድ ያስፈልጋል፦</string>
|
||||
<string name="permission_system_alert_window_label">የسርዓት ማንቂያ መስኮት</string>
|
||||
<string name="permission_system_alert_window_description">CAPod \"ብቅ-ባይ አሳይ\" ባህሪን እንዲቻል ለማድረግ በሌሎች መተግበሪያዎች ላይ እንዲስል ይፍቀዱለት።</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">ዝቅተኛ ኃይል</string>
|
||||
<string name="settings_scanner_mode_balanced_label">ሚዛናዊ</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">ዝቅተኛ መዘግየት</string>
|
||||
<string name="settings_monitor_mode_manual_label">መተግበሪያው ሲከፈት</string>
|
||||
<string name="settings_monitor_mode_automatic_label">መሣሪያው ሲገናኝ</string>
|
||||
<string name="settings_monitor_mode_always_label">ሁልጊዜ</string>
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">ሲታይ</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">ኬዝ ክፍት ነው</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">በጆሮ ውስጥ</string>
|
||||
<string name="pods_dual_left_label">ግራ ፖድ</string>
|
||||
<string name="pods_dual_right_label">ቀኝ ፖድ</string>
|
||||
<string name="pods_case_label">ኬዝ</string>
|
||||
<string name="pods_case_status_open_label">ክፍት</string>
|
||||
<string name="pods_case_status_closed_label">ዝግ</string>
|
||||
<string name="pods_connection_state_disconnected_label">ከመሳሪያ ጋር አልተገናኘም</string>
|
||||
<string name="pods_connection_state_idle_label">ከመሳሪያ ጋር ተገናኝቷል、ግን ስራ ፈት</string>
|
||||
<string name="pods_connection_state_music_label">በሙዚቃ ሁነታ</string>
|
||||
<string name="pods_connection_state_call_label">በጥሪ ሁነታ</string>
|
||||
<string name="pods_connection_state_ringing_label">እየጠራ</string>
|
||||
<string name="pods_connection_state_hanging_up_label">እየዘጋ</string>
|
||||
<string name="pods_connection_state_unknown_label">ያልታወቀ የግንኙነት ሁኔታ</string>
|
||||
<string name="pods_unknown_raw_data_label">ጥሬ መረጃ</string>
|
||||
<string name="pods_unknown_label">ያልታወቀ መሣሪያ</string>
|
||||
<string name="pods_unknown_contact_dev">ይህ ያልታወቀ መሣሪያ ነው、ግን ተመሳሳይ የመልእክት ቅርጸት እየተጠቀመ ነው። ለእሱ ድጋፍ እንጨምርበት、አግኙኝ :)</string>
|
||||
<string name="pods_none_label_short">መሣሪያ የለም</string>
|
||||
<string name="pods_charging_label">ኃይል እየሞላ</string>
|
||||
<string name="pods_inear_label">በጆሮ ውስጥ</string>
|
||||
<string name="pods_microphone_label">ማይክሮፎን</string>
|
||||
<string name="pods_yours">የእርስዎ</string>
|
||||
<string name="headset_being_worn_label">እየተለበሰ</string>
|
||||
<string name="headset_not_being_worn_label">አልተለበሰም</string>
|
||||
<string name="pods_case_unknown_state">ያልታወቀ ሁኔታ</string>
|
||||
<string name="last_seen_x">ለመጨረሻ ጊዜ የታየው፦ %s</string>
|
||||
<string name="first_seen_x">ለመጀመሪያ ጊዜ የታየው፦ %s</string>
|
||||
<string name="permission_post_notifications_label">ማሳወቂያዎችን አሳይ</string>
|
||||
<string name="permission_post_notifications_description">"CAPod ስለ የእርስዎ AirPods ማሳወቂያዎችን እንዲያሳይ ይፍቀዱለት፣ ለምሳሌ ሲገናኙ የአሁኑ ሁኔታቸው።"</string>
|
||||
</resources>
|
||||
|
||||
@@ -55,9 +55,10 @@
|
||||
<string name="pods_microphone_label">ميكروفون</string>
|
||||
<string name="pods_yours">الخاصة بك</string>
|
||||
<string name="headset_being_worn_label">يتم ارتداؤها</string>
|
||||
<string name="headset_not_being_worn_label">لا يتم ارتداؤها</string>
|
||||
<string name="pods_case_unknown_state">حالة غير معروفة</string>
|
||||
<string name="last_seen_x">آخر ظهور: %s</string>
|
||||
<string name="first_seen_x">أول ظهور:%s</string>
|
||||
<string name="permission_post_notifications_label"></string>
|
||||
<string name="permission_post_notifications_label">إظهار الإشعارات</string>
|
||||
<string name="permission_post_notifications_description">"اسمح لـلتطبيق بإظهار الإشعارات المتعلقة بسماعات الأذن الخاصة بك، على سبيل المثال. حالتهم الحالية خلال الاتصال."</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
|
||||
<string name="general_value_not_available_label">Mövcud deyil</string>
|
||||
<string name="general_error_label">Xəta</string>
|
||||
<string name="general_grant_permission_action">İcazə ver</string>
|
||||
@@ -11,6 +15,7 @@
|
||||
<string name="permission_bluetooth_connect_description">Bu tətbiq, cütləşdirilmiş cihazlarla qarşılıqlı əlaqədə olmaq və bağlantıları başlatmaq üçün \"Bluetooth bağlantısı\" icazəsini tələb edir.</string>
|
||||
<string name="permission_bluetooth_scan_label">Bluetooth skan etmə</string>
|
||||
<string name="permission_bluetooth_scan_description">\"Bluetooth skan etmə\" icazəsi, tətbiqə AirPods kimi yaxınlıqda olan cihazlardan Bluetooth verilənlərini almağa və kəşf etməyə icazə verir.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Bu tətbiq, cütləşdirilmiş bluetooth cihazlarına bağlanmaq üçün \"Bluetooth\" icazəsini tələb edir.</string>
|
||||
<string name="permission_access_fine_location_label">Həssas yerləşməyə müraciət</string>
|
||||
<string name="permission_access_fine_location_description">CAPod, Bluetooth Zəif Enerji verilənlərini almaq üçün \"dəqiq yerləşmə\" icazəsini istifadə edir. Qulaqlıqlarınız, öz vəziyyətlərini (qalan batereya faizini) yayımlamaq Bluetooth Zəif Enerji texnologiyasını istifadə edir. Bu tətbiq, Bluetooth verilənlərini yerləşmənizi müəyyənləşdirmək üçün İSTİFADƏ ETMİR.</string>
|
||||
@@ -51,7 +56,10 @@
|
||||
<string name="pods_microphone_label">Mikrofon</string>
|
||||
<string name="pods_yours">Sizin</string>
|
||||
<string name="headset_being_worn_label">Taxılır</string>
|
||||
<string name="headset_not_being_worn_label">Taxılmayıb</string>
|
||||
<string name="pods_case_unknown_state">Bilinməyən vəziyyət</string>
|
||||
<string name="last_seen_x">Son görünmə: %s</string>
|
||||
<string name="first_seen_x">İlk görünmə: %s</string>
|
||||
<string name="permission_post_notifications_label">Bildirişləri göstər</string>
|
||||
<string name="permission_post_notifications_description">CAPod-a AirPods-larınız haqqında bildirişlər göstərməyə icazə verin, məsələn, qoşulmuş vəziyyətdə olduqları zaman onların mövcud statusu.</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
|
||||
<string name="general_value_not_available_label">Н/д</string>
|
||||
<string name="general_error_label">Памылка</string>
|
||||
<string name="general_grant_permission_action">Даць дазвол</string>
|
||||
@@ -13,7 +14,7 @@
|
||||
<string name="permission_bluetooth_connect_label">Злучэнне па Bluetooth</string>
|
||||
<string name="permission_bluetooth_connect_description">Гэтай праграме патрабуецца дазвол на выкарыстанне Bluetooth для ўзаемадзеяння са спалучанымі прыладамі і стварэння новых злучэнняў.</string>
|
||||
<string name="permission_bluetooth_scan_label">Сканіраванне Bluetooth</string>
|
||||
<string name="permission_bluetooth_scan_description">Дазвол на сканіраванне Bluetooth дасць магчымасць гэтай праграме выяўляць і атрымліваць даныя па Bluetooth з навакольных прылад, такіх як вашы AirPods.</string>
|
||||
<string name="permission_bluetooth_scan_description">Дазвол на сканіраванне Bluetooth дасць магчымасць гэтай праграме выяўляць і атрымлівать даныя па Bluetooth з навакольных прылад, такіх як вашы AirPods.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Гэтай праграме патрабуецца дазвол на выкарыстанне Bluetooth для злучэння са спалучанымі прыладамі.</string>
|
||||
<string name="permission_access_fine_location_label">Доступ да дакладнага месцазнаходжання</string>
|
||||
@@ -55,6 +56,7 @@
|
||||
<string name="pods_microphone_label">Мікрафон</string>
|
||||
<string name="pods_yours">Вашы</string>
|
||||
<string name="headset_being_worn_label">Надзеты</string>
|
||||
<string name="headset_not_being_worn_label">Не надзеты</string>
|
||||
<string name="pods_case_unknown_state">Невядомы стан</string>
|
||||
<string name="last_seen_x">Апошні раз быў: %s</string>
|
||||
<string name="first_seen_x">Упершыню быў: %s</string>
|
||||
|
||||
@@ -1,2 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
|
||||
<string name="general_value_not_available_label">Няма данни</string>
|
||||
<string name="general_error_label">Грешка</string>
|
||||
<string name="general_grant_permission_action">Даване на разрешение</string>
|
||||
|
||||
<string name="overview_nomaindevice_label">Няма основно устройство</string>
|
||||
<string name="overview_nomaindevice_description">Всички открити устройства вероятно не са ваши. Включете и свържете вашето устройство или коригирайте настройките.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth е деактивиран</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth е деактивиран, активирайте го ;)</string>
|
||||
|
||||
<string name="permission_bluetooth_connect_label">Bluetooth свързване</string>
|
||||
<string name="permission_bluetooth_connect_description">Това приложение изисква разрешението \"Bluetooth свързване\" за взаимодействие със сдвоени устройства и иницииране на връзки.</string>
|
||||
<string name="permission_bluetooth_scan_label">Bluetooth сканиране</string>
|
||||
<string name="permission_bluetooth_scan_description">Разрешението \"Bluetooth сканиране\" позволява на това приложение да открива и получава Bluetooth данни от близки устройства като вашите AirPods.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Това приложение изисква разрешението \"Bluetooth\" за свързване със сдвоени Bluetooth устройства.</string>
|
||||
<string name="permission_access_fine_location_label">Достъп до точно местоположение</string>
|
||||
<string name="permission_access_fine_location_description">CAPod използва разрешението \"точно местоположение\" за получаване на Bluetooth Low Energy данни. Вашите слушалки използват Bluetooth Low Energy технология за излъчване на състоянието си. Това приложение НЯМА да използва Bluetooth данни за определяне на вашето местоположение.</string>
|
||||
<string name="permission_background_location_label">Достъп до местоположение във фонов режим</string>
|
||||
<string name="permission_background_location_description">CAPod използва \"достъп до местоположение във фонов режим\", за да активира функции като \"Показване на изскачащ прозорец\" и \"Автоматично свързване\", докато приложението е затворено. Достъпът до местоположение във фонов режим позволява на приложението да получава Bluetooth Low Energy данни, докато е във фонов режим. Това приложение НЯМА да използва Bluetooth данни за определяне на вашето местоположение.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Деактивиране на оптимизациите на батерията</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Оптимизациите на батерията пречат на това приложение да получава надеждно Bluetooth данни, докато приложението е във фонов режим.</string>
|
||||
<string name="permission_required_title">Изисква се следното разрешение:</string>
|
||||
<string name="permission_system_alert_window_label">Прозорец за системни предупреждения</string>
|
||||
<string name="permission_system_alert_window_description">Разрешете на CAPod да рисува върху други приложения, за да направи възможна функцията \"Показване на PopUp\".</string>
|
||||
|
||||
<string name="settings_scanner_mode_lowpower_label">Ниска мощност</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Балансиран</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Ниска латентност</string>
|
||||
|
||||
<string name="settings_monitor_mode_manual_label">Когато приложението е отворено</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Когато устройството е свързано</string>
|
||||
<string name="settings_monitor_mode_always_label">Винаги</string>
|
||||
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Когато се види</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Калъфът е отворен</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">В ухото</string>
|
||||
|
||||
<string name="pods_dual_left_label">Лява слушалка</string>
|
||||
<string name="pods_dual_right_label">Дясна слушалка</string>
|
||||
<string name="pods_case_label">Калъф</string>
|
||||
<string name="pods_case_status_open_label">Отворен</string>
|
||||
<string name="pods_case_status_closed_label">Затворен</string>
|
||||
<string name="pods_connection_state_disconnected_label">Не е свързано с устройство</string>
|
||||
<string name="pods_connection_state_idle_label">Свързано с устройство, но неактивно</string>
|
||||
<string name="pods_connection_state_music_label">В музикален режим</string>
|
||||
<string name="pods_connection_state_call_label">В режим на повикване</string>
|
||||
<string name="pods_connection_state_ringing_label">Звънене</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Затваряне</string>
|
||||
<string name="pods_connection_state_unknown_label">Неизвестно състояние на връзката</string>
|
||||
<string name="pods_unknown_raw_data_label">Сурови данни</string>
|
||||
<string name="pods_unknown_label">Неизвестно устройство</string>
|
||||
<string name="pods_unknown_contact_dev">Това е неизвестно устройство, но използва подобен формат на съобщенията. Нека добавим поддръжка за него, свържете се с мен :)</string>
|
||||
<string name="pods_none_label_short">Няма устройство</string>
|
||||
<string name="pods_charging_label">Зареждане</string>
|
||||
<string name="pods_inear_label">В ухото</string>
|
||||
<string name="pods_microphone_label">Микрофон</string>
|
||||
<string name="pods_yours">Ваши</string>
|
||||
<string name="headset_being_worn_label">Носи се</string>
|
||||
<string name="headset_not_being_worn_label">Не се носи</string>
|
||||
<string name="pods_case_unknown_state">Неизвестно състояние</string>
|
||||
|
||||
<string name="last_seen_x">Последно видяно: %s</string>
|
||||
<string name="first_seen_x">Първо видяно: %s</string>
|
||||
<string name="permission_post_notifications_label">Показване на известия</string>
|
||||
<string name="permission_post_notifications_description">Разрешете на CAPod да показва известия за вашите AirPods, напр. текущото им състояние, докато са свързани.</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,4 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
|
||||
<string name="general_value_not_available_label">প্রযোজ্য নয়</string>
|
||||
<string name="general_error_label">ত্রুটি</string>
|
||||
<string name="general_grant_permission_action">অনুমতি প্রদান করুন</string>
|
||||
|
||||
<string name="overview_nomaindevice_label">কোনও প্রাথমিক ডিভাইস নেই</string>
|
||||
<string name="overview_nomaindevice_description">শনাক্ত করা সমস্ত ডিভাইস সম্ভবত আপনার নয়। আপনার ডিভাইস চালু করুন এবং সংযোগ করুন অথবা সেটিংস সামঞ্জস্য করুন।</string>
|
||||
<string name="overview_bluetooth_disabled_label">ব্লুটুথ নিষ্ক্রিয় করা আছে</string>
|
||||
<string name="overview_bluetooth_disabled_description">ব্লুটুথ নিষ্ক্রিয় করা আছে, এটি সক্রিয় করুন ;)</string>
|
||||
|
||||
<string name="permission_bluetooth_connect_label">ব্লুটুথ সংযোগ</string>
|
||||
<string name="permission_bluetooth_connect_description">এই অ্যাপটির পেয়ার করা ডিভাইসগুলির সাথে ইন্টারঅ্যাক্ট করতে এবং সংযোগ শুরু করতে "ব্লুটুথ সংযোগ" অনুমতির প্রয়োজন।</string>
|
||||
<string name="permission_bluetooth_scan_label">ব্লুটুথ স্ক্যানিং</string>
|
||||
<string name="permission_bluetooth_scan_description">"ব্লুটুথ স্ক্যানিং" অনুমতি এই অ্যাপটিকে আপনার AirPods-এর মতো কাছাকাছি ডিভাইসগুলি থেকে ব্লুটুথ ডেটা আবিষ্কার এবং গ্রহণ করতে দেয়।</string>
|
||||
<string name="permission_bluetooth_label">ব্লুটুথ</string>
|
||||
<string name="permission_bluetooth_description">এই অ্যাপটির পেয়ার করা ব্লুটুথ ডিভাইসগুলির সাথে সংযোগ করতে "ব্লুটুথ" অনুমতির প্রয়োজন।</string>
|
||||
<string name="permission_access_fine_location_label">সঠিক অবস্থানে অ্যাক্সেস</string>
|
||||
<string name="permission_access_fine_location_description">CAPod ব্লুটুথ লো এনার্জি ডেটা গ্রহণ করতে "সঠিক অবস্থান" অনুমতি ব্যবহার করে। আপনার হেডফোনগুলি তাদের স্থিতি সম্প্রচার করতে ব্লুটুথ লো এনার্জি প্রযুক্তি ব্যবহার করে। এই অ্যাপটি আপনার অবস্থান নির্ধারণ করতে ব্লুটুথ ডেটা ব্যবহার করবে না।</string>
|
||||
<string name="permission_background_location_label">পটভূমি অবস্থানে অ্যাক্সেস</string>
|
||||
<string name="permission_background_location_description">অ্যাপটি বন্ধ থাকা অবস্থায় "পপআপ দেখান" এবং "স্বয়ংক্রিয় সংযোগ" এর মতো বৈশিষ্ট্যগুলি সক্ষম করতে CAPod "পটভূমি অবস্থানে অ্যাক্সেস" ব্যবহার করে। পটভূমি অবস্থানে অ্যাক্সেস অ্যাপটিকে পটভূমিতে থাকাকালীন ব্লুটুথ লো এনার্জি ডেটা গ্রহণ করতে দেয়। এই অ্যাপটি আপনার অবস্থান নির্ধারণ করতে ব্লুটুথ ডেটা ব্যবহার করবে না।</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">ব্যাটারি অপ্টিমাইজেশন নিষ্ক্রিয় করুন</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">অ্যাপটি পটভূমিতে থাকাকালীন ব্যাটারি অপ্টিমাইজেশন এই অ্যাপটিকে নির্ভরযোগ্যভাবে ব্লুটুথ ডেটা গ্রহণ করতে বাধা দেয়।</string>
|
||||
<string name="permission_required_title">নিম্নলিখিত অনুমতি প্রয়োজন:</string>
|
||||
<string name="permission_system_alert_window_label">সিস্টেম সতর্কতা উইন্ডো</string>
|
||||
<string name="permission_system_alert_window_description">CAPod-কে "পপআপ দেখান" বৈশিষ্ট্যটি সম্ভব করতে অন্যান্য অ্যাপের উপর আঁকতে অনুমতি দিন।</string>
|
||||
|
||||
<string name="settings_scanner_mode_lowpower_label">কম শক্তি</string>
|
||||
<string name="settings_scanner_mode_balanced_label">ভারসাম্যপূর্ণ</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">কম লেটেন্সি</string>
|
||||
|
||||
<string name="settings_monitor_mode_manual_label">অ্যাপ খোলা থাকলে</string>
|
||||
<string name="settings_monitor_mode_automatic_label">ডিভাইস সংযুক্ত থাকলে</string>
|
||||
<string name="settings_monitor_mode_always_label">সবসময়</string>
|
||||
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">দেখলে</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">কেস খোলা আছে</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">কানে</string>
|
||||
|
||||
<string name="pods_dual_left_label">বাম পড</string>
|
||||
<string name="pods_dual_right_label">ডান পড</string>
|
||||
<string name="pods_case_label">কেস</string>
|
||||
<string name="pods_case_status_open_label">খোলা</string>
|
||||
<string name="pods_case_status_closed_label">বন্ধ</string>
|
||||
<string name="pods_connection_state_disconnected_label">কোনও ডিভাইসের সাথে সংযুক্ত নয়</string>
|
||||
<string name="pods_connection_state_idle_label">একটি ডিভাইসের সাথে সংযুক্ত, কিন্তু নিষ্ক্রিয়</string>
|
||||
<string name="pods_connection_state_music_label">মিউজিক মোডে</string>
|
||||
<string name="pods_connection_state_call_label">কল মোডে</string>
|
||||
<string name="pods_connection_state_ringing_label">রিং হচ্ছে</string>
|
||||
<string name="pods_connection_state_hanging_up_label">সংযোগ বিচ্ছিন্ন হচ্ছে</string>
|
||||
<string name="pods_connection_state_unknown_label">অজানা সংযোগ স্থিতি</string>
|
||||
<string name="pods_unknown_raw_data_label">কাঁচা ডেটা</string>
|
||||
<string name="pods_unknown_label">অজানা ডিভাইস</string>
|
||||
<string name="pods_unknown_contact_dev">এটি একটি অজানা ডিভাইস, তবে এটি একই ধরনের বার্তা বিন্যাস ব্যবহার করছে। আসুন এটির জন্য সমর্থন যোগ করি, আমার সাথে যোগাযোগ করুন :)</string>
|
||||
<string name="pods_none_label_short">কোনও ডিভাইস নেই</string>
|
||||
<string name="pods_charging_label">চার্জ হচ্ছে</string>
|
||||
<string name="pods_inear_label">কানে</string>
|
||||
<string name="pods_microphone_label">মাইক্রোফোন</string>
|
||||
<string name="pods_yours">আপনার</string>
|
||||
<string name="headset_being_worn_label">পরিধান করা হচ্ছে</string>
|
||||
<string name="headset_not_being_worn_label">পরিধান করা হয়নি</string>
|
||||
<string name="pods_case_unknown_state">অজানা স্থিতি</string>
|
||||
|
||||
<string name="last_seen_x">শেষ দেখা: %s</string>
|
||||
<string name="first_seen_x">প্রথম দেখা: %s</string>
|
||||
<string name="permission_post_notifications_label">বিজ্ঞপ্তি দেখান</string>
|
||||
<string name="permission_post_notifications_description">CAPod-কে আপনার AirPods সম্পর্কে বিজ্ঞপ্তি দেখাতে অনুমতি দিন, যেমন সংযুক্ত থাকাকালীন তাদের বর্তমান স্থিতি।</string>
|
||||
</resources>
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
<string name="pods_microphone_label">Micròfon</string>
|
||||
<string name="pods_yours">Vostres</string>
|
||||
<string name="headset_being_worn_label">En ús</string>
|
||||
<string name="headset_not_being_worn_label">No s\'utilitzen</string>
|
||||
<string name="pods_case_unknown_state">Estat desconegut</string>
|
||||
<string name="last_seen_x">Últim cop vist: %s</string>
|
||||
<string name="first_seen_x">Primera vegada vist: %s</string>
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
<string name="pods_microphone_label">Mikrofon</string>
|
||||
<string name="pods_yours">Vaše</string>
|
||||
<string name="headset_being_worn_label">Používá se</string>
|
||||
<string name="headset_not_being_worn_label">Nepoužíváno</string>
|
||||
<string name="pods_case_unknown_state">Neznámý stav</string>
|
||||
<string name="last_seen_x">Naposledy spatřeno: %s</string>
|
||||
<string name="first_seen_x">Poprvé spatřeno: %s</string>
|
||||
|
||||
@@ -1,2 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
|
||||
<string name="general_value_not_available_label">I/T</string>
|
||||
<string name="general_error_label">Fejl</string>
|
||||
<string name="general_grant_permission_action">Giv tilladelse</string>
|
||||
|
||||
<string name="overview_nomaindevice_label">Ingen primær enhed</string>
|
||||
<string name="overview_nomaindevice_description">Alle registrerede enheder er sandsynligvis ikke dine. Tænd og tilslut din enhed, eller juster indstillingerne.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth er deaktiveret</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth er deaktiveret, aktiver det ;)</string>
|
||||
|
||||
<string name="permission_bluetooth_connect_label">Bluetooth-forbindelse</string>
|
||||
<string name="permission_bluetooth_connect_description">Denne app kræver tilladelsen \"Bluetooth-forbindelse\" for at interagere med parrede enheder og starte forbindelser.</string>
|
||||
<string name="permission_bluetooth_scan_label">Bluetooth-scanning</string>
|
||||
<string name="permission_bluetooth_scan_description">Tilladelsen \"Bluetooth-scanning\" giver denne app mulighed for at finde og modtage Bluetooth-data fra nærliggende enheder såsom dine AirPods.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Denne app kræver tilladelsen \"Bluetooth\" for at oprette forbindelse til parrede Bluetooth-enheder.</string>
|
||||
<string name="permission_access_fine_location_label">Adgang til fin placering</string>
|
||||
<string name="permission_access_fine_location_description">CAPod bruger tilladelsen \"fin placering\" til at modtage Bluetooth Low Energy-data. Dine hovedtelefoner bruger Bluetooth Low Energy-teknologi til at udsende deres status. Denne app vil IKKE bruge Bluetooth-data til at bestemme din placering.</string>
|
||||
<string name="permission_background_location_label">Adgang til baggrundsplacering</string>
|
||||
<string name="permission_background_location_description">CAPod bruger \"adgang til baggrundsplacering\" til at aktivere funktioner som \"Vis pop op\" og \"AutoConnect\", mens appen er lukket. Adgang til baggrundsplacering giver appen mulighed for at modtage Bluetooth Low Energy-data, mens den er i baggrunden. Denne app vil IKKE bruge Bluetooth-data til at bestemme din placering.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Deaktiver batterioptimeringer</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Batterioptimeringer forhindrer denne app i at modtage Bluetooth-data pålideligt, mens appen er i baggrunden.</string>
|
||||
<string name="permission_required_title">Følgende tilladelse er påkrævet:</string>
|
||||
<string name="permission_system_alert_window_label">Systemadvarselsvindue</string>
|
||||
<string name="permission_system_alert_window_description">Tillad CAPod at tegne oven på andre apps for at gøre funktionen \"Vis PopOp\" mulig.</string>
|
||||
|
||||
<string name="settings_scanner_mode_lowpower_label">Lav effekt</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Balanceret</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Lav latenstid</string>
|
||||
|
||||
<string name="settings_monitor_mode_manual_label">Når appen er åben</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Når enheden er tilsluttet</string>
|
||||
<string name="settings_monitor_mode_always_label">Altid</string>
|
||||
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Når set</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Etui er åbent</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">I øret</string>
|
||||
|
||||
<string name="pods_dual_left_label">Venstre pod</string>
|
||||
<string name="pods_dual_right_label">Højre pod</string>
|
||||
<string name="pods_case_label">Etui</string>
|
||||
<string name="pods_case_status_open_label">Åben</string>
|
||||
<string name="pods_case_status_closed_label">Lukket</string>
|
||||
<string name="pods_connection_state_disconnected_label">Ikke tilsluttet en enhed</string>
|
||||
<string name="pods_connection_state_idle_label">Tilsluttet en enhed, men inaktiv</string>
|
||||
<string name="pods_connection_state_music_label">I musiktilstand</string>
|
||||
<string name="pods_connection_state_call_label">I opkaldstilstand</string>
|
||||
<string name="pods_connection_state_ringing_label">Ringer</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Lægger på</string>
|
||||
<string name="pods_connection_state_unknown_label">Ukendt forbindelsestilstand</string>
|
||||
<string name="pods_unknown_raw_data_label">Rådata</string>
|
||||
<string name="pods_unknown_label">Ukendt enhed</string>
|
||||
<string name="pods_unknown_contact_dev">Dette er en ukendt enhed, men den bruger et lignende meddelelsesformat. Lad os tilføje understøttelse for den, kontakt mig :)</string>
|
||||
<string name="pods_none_label_short">Ingen enhed</string>
|
||||
<string name="pods_charging_label">Oplader</string>
|
||||
<string name="pods_inear_label">I øret</string>
|
||||
<string name="pods_microphone_label">Mikrofon</string>
|
||||
<string name="pods_yours">Dine</string>
|
||||
<string name="headset_being_worn_label">Bæres</string>
|
||||
<string name="headset_not_being_worn_label">Bæres ikke</string>
|
||||
<string name="pods_case_unknown_state">Ukendt tilstand</string>
|
||||
|
||||
<string name="last_seen_x">Sidst set: %s</string>
|
||||
<string name="first_seen_x">Først set: %s</string>
|
||||
<string name="permission_post_notifications_label">Vis notifikationer</string>
|
||||
<string name="permission_post_notifications_description">Tillad CAPod at vise notifikationer om dine AirPods, f.eks. deres aktuelle status, mens de er tilsluttet.</string>
|
||||
</resources>
|
||||
|
||||
@@ -24,15 +24,15 @@
|
||||
<string name="permission_ignore_battery_optimizations_description">Batterie Optimierung hindert die App daran Bluetooth Daten zu erhalten während die App im Hintergrund ist.</string>
|
||||
<string name="permission_required_title">Die folgende Genehmigung ist erforderlich:</string>
|
||||
<string name="permission_system_alert_window_label">Systemwarnungsfenster</string>
|
||||
<string name="permission_system_alert_window_description">Erlauben Sie CAPod, über andere Apps zu zeichnen, um die Funktion \"PopUp anzeigen\" zu ermöglichen.</string>
|
||||
<string name="permission_system_alert_window_description">Erlauben Sie CAPod, PopUp Benachrichtigungen über anderen Apps anzuzeigen.</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">Geringer Strom</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Ausgewogen</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Geringe Latenz</string>
|
||||
<string name="settings_monitor_mode_manual_label">Wenn die App geöffnet ist</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Wenn das Gerät verbunden ist</string>
|
||||
<string name="settings_monitor_mode_always_label">Stets</string>
|
||||
<string name="settings_monitor_mode_always_label">Immer</string>
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Wenn gesehen</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Fall ist offen</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Ladecase ist offen</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">Im Ohr</string>
|
||||
<string name="pods_dual_left_label">Linker Pod</string>
|
||||
<string name="pods_dual_right_label">Rechter Pod</string>
|
||||
@@ -44,7 +44,7 @@
|
||||
<string name="pods_connection_state_music_label">Im Musik Modus</string>
|
||||
<string name="pods_connection_state_call_label">Im Anruf Modus</string>
|
||||
<string name="pods_connection_state_ringing_label">Klingelt</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Legt auf</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Auflegen</string>
|
||||
<string name="pods_connection_state_unknown_label">Unbekannter Verbindungsstatus</string>
|
||||
<string name="pods_unknown_raw_data_label">Rohe Daten</string>
|
||||
<string name="pods_unknown_label">Unbekanntes Gerät</string>
|
||||
@@ -55,6 +55,7 @@
|
||||
<string name="pods_microphone_label">Mikrofon</string>
|
||||
<string name="pods_yours">Dein</string>
|
||||
<string name="headset_being_worn_label">Wird getragen</string>
|
||||
<string name="headset_not_being_worn_label">Wird nicht getragen</string>
|
||||
<string name="pods_case_unknown_state">Unbekannter Zustand</string>
|
||||
<string name="last_seen_x">Zuletzt gesehen: %s</string>
|
||||
<string name="first_seen_x">Zum ersten Mal gesehen: %s</string>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
<string name="general_value_not_available_label">Δε διατίθεται</string>
|
||||
<string name="general_value_not_available_label">Μ/Δ</string>
|
||||
<string name="general_error_label">Σφάλμα</string>
|
||||
<string name="general_grant_permission_action">Παραχώρηση άδειας</string>
|
||||
<string name="overview_nomaindevice_label">Καμία κύρια συσκευή</string>
|
||||
@@ -23,7 +23,7 @@
|
||||
<string name="permission_ignore_battery_optimizations_label">Απενεργοποίηση βελτιστοποιήσεων μπαταρίας</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Οι βελτιστοποιήσεις μπαταρίας εμποδίζουν αυτήν την εφαρμογή να λαμβάνει αξιόπιστα δεδομένα Bluetooth ενώ η εφαρμογή βρίσκεται στο παρασκήνιο.</string>
|
||||
<string name="permission_required_title">Η ακόλουθη άδεια είναι απαραίτητη:</string>
|
||||
<string name="permission_system_alert_window_label">Παράθυρο ειδοποίησης συστήματος</string>
|
||||
<string name="permission_system_alert_window_label">Παράθυρο Ειδοποίησης Συστήματος</string>
|
||||
<string name="permission_system_alert_window_description">Επιτρέψτε στο CAPod να εμφανίζεται πάνω από άλλες εφαρμογές για να είναι δυνατή η λειτουργία \"Εμφάνιση αναδυόμενου\".</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">Χαμηλή ισχύς</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Εξισορρόπηση</string>
|
||||
@@ -46,7 +46,7 @@
|
||||
<string name="pods_connection_state_ringing_label">Κουδούνισμα</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Τερματισμός κλήσης</string>
|
||||
<string name="pods_connection_state_unknown_label">Άγνωστη κατάσταση σύνδεσης</string>
|
||||
<string name="pods_unknown_raw_data_label">Ανεπεξέργαστα δεδομένα</string>
|
||||
<string name="pods_unknown_raw_data_label">Ακατέργαστα δεδομένα</string>
|
||||
<string name="pods_unknown_label">Άγνωστη συσκευή</string>
|
||||
<string name="pods_unknown_contact_dev">Αυτή είναι μια άγνωστη συσκευή, αλλά χρησιμοποιεί παρόμοια μορφή μηνυμάτων. Ας προσθέσουμε υποστήριξη για αυτήν, επικοινωνήστε μαζί μου :)</string>
|
||||
<string name="pods_none_label_short">Καμία συσκευή</string>
|
||||
@@ -55,6 +55,7 @@
|
||||
<string name="pods_microphone_label">Μικρόφωνο</string>
|
||||
<string name="pods_yours">Δικό σας</string>
|
||||
<string name="headset_being_worn_label">Φοριέται</string>
|
||||
<string name="headset_not_being_worn_label">Δε φοριέται</string>
|
||||
<string name="pods_case_unknown_state">Άγνωστη κατάσταση</string>
|
||||
<string name="last_seen_x">Τελευταία εμφάνιση: %s</string>
|
||||
<string name="first_seen_x">Πρώτη εμφάνιση: %s</string>
|
||||
|
||||
@@ -3,8 +3,70 @@
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
|
||||
<string name="general_value_not_available_label">N/D</string>
|
||||
<string name="general_error_label">Error</string>
|
||||
<string name="general_grant_permission_action">Conceder permiso</string>
|
||||
|
||||
<string name="overview_nomaindevice_label">Ningún dispositivo principal</string>
|
||||
<string name="overview_nomaindevice_description">Es poco probable que todos los dispositivos detectados sean tuyos. Encendé y conectá tu dispositivo o ajustá la configuración.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth desactivado</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth está desactivado, activalo ;)</string>
|
||||
|
||||
<string name="permission_bluetooth_connect_label">Conexión Bluetooth</string>
|
||||
<string name="permission_bluetooth_connect_description">Esta aplicación necesita el permiso \"Conexión Bluetooth\" para interactuar con los dispositivos emparejados e iniciar conexiones.</string>
|
||||
<string name="permission_bluetooth_scan_label">Escaneo de Bluetooth</string>
|
||||
<string name="permission_bluetooth_scan_description">El permiso \"Escaneo de Bluetooth\" permite que esta aplicación detecte y reciba datos de Bluetooth de dispositivos cercanos, como tus AirPods.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Esta aplicación requiere el permiso \"Bluetooth\" para conectarse a dispositivos Bluetooth emparejados.</string>
|
||||
<string name="permission_access_fine_location_label">Acceso a ubicación precisa</string>
|
||||
<string name="permission_access_fine_location_description">CAPod utiliza el permiso \"ubicación precisa\" para recibir datos de Bluetooth de baja energía. Tus auriculares utilizan la tecnología Bluetooth de baja energía para transmitir su estado. Esta aplicación NO utilizará datos de Bluetooth para determinar tu ubicación.</string>
|
||||
<string name="permission_background_location_label">Acceso a la ubicación en segundo plano</string>
|
||||
<string name="permission_background_location_description">CAPod utiliza \"acceso a la ubicación en segundo plano\" para habilitar características como \"Mostrar pop-up\" y \"Conexión automática\" mientras la aplicación está cerrada. El acceso a la ubicación en segundo plano permite que la aplicación reciba datos de Bluetooth de baja energía mientras está en segundo plano. Esta aplicación NO utilizará datos de Bluetooth para determinar tu ubicación.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Desactivar optimizaciones de batería</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Las optimizaciones de batería impiden que esta aplicación reciba datos de Bluetooth de forma fiable mientras la aplicación está en segundo plano.</string>
|
||||
<string name="permission_required_title">Se requiere el siguiente permiso:</string>
|
||||
<string name="permission_system_alert_window_label">Ventana de alerta del sistema</string>
|
||||
<string name="permission_system_alert_window_description">Permitir que CAPod se dibuje sobre otras aplicaciones para hacer posible la función \"Mostrar PopUp\".</string>
|
||||
|
||||
<string name="settings_scanner_mode_lowpower_label">Bajo consumo</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Equilibrado</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Baja latencia</string>
|
||||
|
||||
<string name="settings_monitor_mode_manual_label">Cuando la app está abierta</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Cuando el dispositivo está conectado</string>
|
||||
<string name="settings_monitor_mode_always_label">Siempre</string>
|
||||
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Al verse</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Estuche abierto</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">En la oreja</string>
|
||||
|
||||
<string name="pods_dual_left_label">Auricular izquierdo</string>
|
||||
<string name="pods_dual_right_label">Auricular derecho</string>
|
||||
<string name="pods_case_label">Estuche</string>
|
||||
<string name="pods_case_status_open_label">Abierto</string>
|
||||
<string name="pods_case_status_closed_label">Cerrado</string>
|
||||
<string name="pods_connection_state_disconnected_label">No conectado a un dispositivo</string>
|
||||
<string name="pods_connection_state_idle_label">Conectado a un dispositivo, pero inactivo</string>
|
||||
<string name="pods_connection_state_music_label">En modo música</string>
|
||||
<string name="pods_connection_state_call_label">En modo llamada</string>
|
||||
<string name="pods_connection_state_ringing_label">Sonando</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Colgando</string>
|
||||
<string name="pods_connection_state_unknown_label">Estado de conexión desconocido</string>
|
||||
<string name="pods_unknown_raw_data_label">Datos crudos</string>
|
||||
<string name="pods_unknown_label">Dispositivo desconocido</string>
|
||||
<string name="pods_unknown_contact_dev">Este es un dispositivo desconocido, pero está usando un formato de mensaje similar. Agreguemos soporte para él, contactame :)</string>
|
||||
<string name="pods_none_label_short">Sin dispositivo</string>
|
||||
<string name="pods_charging_label">Cargando</string>
|
||||
<string name="pods_inear_label">En la oreja</string>
|
||||
<string name="pods_microphone_label">Micrófono</string>
|
||||
<string name="pods_yours">Tuyos</string>
|
||||
<string name="headset_being_worn_label">En uso</string>
|
||||
<string name="headset_not_being_worn_label">No en uso</string>
|
||||
<string name="pods_case_unknown_state">Estado desconocido</string>
|
||||
|
||||
<string name="last_seen_x">Visto por última vez: %s</string>
|
||||
<string name="first_seen_x">Visto por primera vez: %s</string>
|
||||
<string name="permission_post_notifications_label">Mostrar notificaciones</string>
|
||||
<string name="permission_post_notifications_description">Permitir que CAPod muestre notificaciones sobre tus AirPods, por ejemplo, su estado actual mientras están conectados.</string>
|
||||
</resources>
|
||||
|
||||
@@ -36,4 +36,29 @@
|
||||
<string name="pods_microphone_label">Micrófono</string>
|
||||
<string name="pods_yours">Tuyos</string>
|
||||
<string name="headset_being_worn_label">En uso</string>
|
||||
<string name="general_value_not_available_label">N/D</string>
|
||||
<string name="general_error_label">Error</string>
|
||||
<string name="overview_nomaindevice_label">Ningún dispositivo principal</string>
|
||||
<string name="overview_nomaindevice_description">Es poco probable que todos los dispositivos detectados sean tuyos. Enciende y conecta tu dispositivo o ajusta la configuración.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth desactivado</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth está desactivado, actívalo ;)</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_required_title">Se requiere el siguiente permiso:</string>
|
||||
<string name="permission_system_alert_window_label">Ventana de alerta del sistema</string>
|
||||
<string name="permission_system_alert_window_description">Permitir que CAPod se dibuje sobre otras aplicaciones para hacer posible la función \"Mostrar PopUp\".</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">Bajo consumo</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Equilibrado</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Baja latencia</string>
|
||||
<string name="settings_monitor_mode_manual_label">Cuando la app está abierta</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Cuando el dispositivo está conectado</string>
|
||||
<string name="settings_monitor_mode_always_label">Siempre</string>
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Al verse</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Estuche abierto</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">En el oído</string>
|
||||
<string name="headset_not_being_worn_label">No se está usando</string>
|
||||
<string name="pods_case_unknown_state">Estado desconocido</string>
|
||||
<string name="last_seen_x">Visto por última vez: %s</string>
|
||||
<string name="first_seen_x">Visto por primera vez: %s</string>
|
||||
<string name="permission_post_notifications_label">Mostrar notificaciones</string>
|
||||
<string name="permission_post_notifications_description">Permitir que CAPod muestre notificaciones sobre tus AirPods, p. ej., su estado actual mientras están conectados.</string>
|
||||
</resources>
|
||||
|
||||
@@ -4,27 +4,27 @@
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
<string name="general_value_not_available_label">No disponible</string>
|
||||
<string name="general_error_label">Fallo</string>
|
||||
<string name="general_grant_permission_action">Conceder el permiso</string>
|
||||
<string name="overview_nomaindevice_label">Sin dispositivo principal</string>
|
||||
<string name="overview_nomaindevice_description">Es poco probable que todos los dispositivos detectados sean tuyos. Enciende y conecta tu dispositivo o ajusta la configuración.</string>
|
||||
<string name="general_error_label">Error</string>
|
||||
<string name="general_grant_permission_action">Otorgar el permiso</string>
|
||||
<string name="overview_nomaindevice_label">Ningún dispositivo principal</string>
|
||||
<string name="overview_nomaindevice_description">Es poco probable que todos los dispositivos detectados sean suyos. Enciende y conecta tu dispositivo o ajusta la configuración.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth desactivado</string>
|
||||
<string name="overview_bluetooth_disabled_description">El Bluetooth está desactivado, actívalo ;)</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth está desactivado, actívalo ;)</string>
|
||||
<string name="permission_bluetooth_connect_label">Conexión por Bluetooth</string>
|
||||
<string name="permission_bluetooth_connect_description">Esta aplicación requiere el permiso \"Conexión por Bluetooth\" para interactuar con los dispositivos emparejados e iniciar las conexiones.</string>
|
||||
<string name="permission_bluetooth_connect_description">Esta aplicación necesita el permiso \"Conexión Bluetooth\" para interactuar con los dispositivos emparejados e iniciar conexiones.</string>
|
||||
<string name="permission_bluetooth_scan_label">El Bluetooth está escaneando</string>
|
||||
<string name="permission_bluetooth_scan_description">El permiso \"Escaneo del Bluetooth\" permite que esta aplicación descubra y reciba datos de Bluetooth de dispositivos cercanos, como tus AirPods.</string>
|
||||
<string name="permission_bluetooth_scan_description">El permiso \"Escaneo por Bluetooth\" permite que esta aplicación detecte y reciba datos de Bluetooth de dispositivos cercanos, como los AirPods.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Esta aplicación requiere el permiso \"Bluetooth\" para conectarse a dispositivos bluetooth emparejados.</string>
|
||||
<string name="permission_bluetooth_description">Esta aplicación requiere el permiso \"Bluetooth\" para conectarse a dispositivos emparejados de bluetooth.</string>
|
||||
<string name="permission_access_fine_location_label">Acceso preciso a tu ubicación</string>
|
||||
<string name="permission_access_fine_location_description">CAPod utiliza el permiso \"localización precisa\" para recibir datos Bluetooth Low Energy. Tus auriculares utilizan la tecnología Bluetooth Low Energy para transmitir tu estado. Esta aplicación NO utilizará los datos Bluetooth para determinar tu ubicación.</string>
|
||||
<string name="permission_background_location_label">Ubicación en segundo plano</string>
|
||||
<string name="permission_background_location_description">CAPod utiliza el \"la ubicación en segundo plano\" para activar funciones como \"Mostrar ventana emergente\" y \"Conexión automática\" mientras la aplicación está cerrada. El acceso a la localización en segundo plano permite a la aplicación recibir datos de Bluetooth Low Energy mientras está en segundo plano. Esta aplicación NO utilizará los datos Bluetooth para determinar tu ubicación.</string>
|
||||
<string name="permission_access_fine_location_description">CAPod utiliza el permiso \"ubicación fina\" para recibir datos de Bluetooth de baja energía. Tus auriculares utilizan la tecnología Bluetooth de baja energía para transmitir su estado. Esta aplicación NO utilizará datos Bluetooth para determinar tu ubicación.</string>
|
||||
<string name="permission_background_location_label">Acceso a la ubicación en segundo plano</string>
|
||||
<string name="permission_background_location_description">CAPod utiliza \"acceso a la ubicación en segundo plano\" para habilitar características como \"Mostrar popup\" y \"AutoConnect\" mientras la aplicación está cerrada. El acceso a la ubicación en segundo plano permite que la aplicación reciba datos de Bluetooth de baja energía mientras está en segundo plano. Esta aplicación NO utilizará datos de Bluetooth para determinar su ubicación.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Desactivar las optimizaciones de la batería</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Las optimizaciones de la batería impiden que esta aplicación reciba datos Bluetooth de forma fiable mientras está en segundo plano.</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Las optimizaciones de la batería impiden que esta aplicación reciba datos Bluetooth de forma fiable mientras la aplicación está en segundo plano.</string>
|
||||
<string name="permission_required_title">Se requiere el siguiente permiso:</string>
|
||||
<string name="permission_system_alert_window_label">Ventana de alerta del sistema</string>
|
||||
<string name="permission_system_alert_window_description">Permite a CAPod dibujar sobre otras aplicaciones para hacer posible la función \"Mostrar una ventana emergente\".</string>
|
||||
<string name="permission_system_alert_window_description">Permitir que CAPod funcione sobre otras aplicaciones para hacer posible la función \"Mostrar una ventana emergente\".</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">Bajo consumo</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Equilibrado</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Baja latencia</string>
|
||||
@@ -53,8 +53,9 @@
|
||||
<string name="pods_charging_label">Cargando</string>
|
||||
<string name="pods_inear_label">En el oído</string>
|
||||
<string name="pods_microphone_label">Micrófono</string>
|
||||
<string name="pods_yours">Tuyos</string>
|
||||
<string name="pods_yours">Tus</string>
|
||||
<string name="headset_being_worn_label">Utilizándose</string>
|
||||
<string name="headset_not_being_worn_label">No se usan</string>
|
||||
<string name="pods_case_unknown_state">Estado desconocido</string>
|
||||
<string name="last_seen_x">Visto por última vez: %s</string>
|
||||
<string name="first_seen_x">Visto por primera vez: %s</string>
|
||||
|
||||
@@ -2,10 +2,63 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">Tasuline CAPod</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
<string name="app_name_foss">CAPod – tasuta ja vabavaraline</string>
|
||||
<string name="general_value_not_available_label">Pole saadaval</string>
|
||||
<string name="general_error_label">Viga</string>
|
||||
<string name="general_grant_permission_action">Anna õigus</string>
|
||||
<string name="overview_nomaindevice_label">Puudub peamine seade</string>
|
||||
<string name="overview_nomaindevice_description">Kõik tuvastatud seadmeid ei kuulu tõenäoliselt teile. Lülitage oma seade sisse ja ühendage või muutke seadeid.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth on välja lülitatud</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth on keelatud, lubage see ;)</string>
|
||||
<string name="permission_bluetooth_connect_label">Bluetooth ühendus</string>
|
||||
<string name="permission_bluetooth_connect_description">See rakendus nõuab luba kasutada Bluetooh ühendust, et suhelda seotud seadmetega ja käivitada ühendamist.</string>
|
||||
<string name="permission_bluetooth_scan_label">Bluetoothi skannimine</string>
|
||||
<string name="permission_bluetooth_scan_description">Bluetoothi skannimise luba võimaldab rakendusel avastada ja vastu võtta Bluetooth andmeid lähedal asuvatest seadmetest, nagu AirPods.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">See rakendus nõuab Bluetooth luba, et ühenduda seotud Bluetooth seadmetega.</string>
|
||||
<string name="permission_access_fine_location_label">Luba täpne asukoht</string>
|
||||
<string name="permission_access_fine_location_description">CAPod saab täpse asukoha loaga madalama energiatarbimisega andmeid. Kõrvaklapid edastavad selle tehnoloogiaga oma olekut. See rakendus ei tuvasta Bluetooth andmetega teie asukohta.</string>
|
||||
<string name="permission_background_location_label">Juurdepääs asukohale taustal</string>
|
||||
<string name="permission_background_location_description">CAPod võimaldab sellega hüpikakende näitamist ja automaatset ühendumist, kui rakendus on suletud ning saada Bluetooth madala energiatarbimisega andmeid, kui rakendus töötab taustal. See rakendus ei tuvasta Bluetooth andmetega teie asukohta.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Keela aku optimeerimine</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Aku optimeerimine takistab rakendusel Bluetooth andmete saamist taustal töötades.</string>
|
||||
<string name="permission_required_title">Vaja on järgmist luba:</string>
|
||||
<string name="permission_system_alert_window_label">Süsteemi teate aken</string>
|
||||
<string name="permission_system_alert_window_description">Luba CAPodil toimetada teiste rakenduste kohal, et näha hüpikaknaid.</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">Madal võimsus</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Tasakaalustatud</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Madal latents</string>
|
||||
<string name="settings_monitor_mode_manual_label">Kui rakendus on avatud</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Kui seade on ühendatud</string>
|
||||
<string name="settings_monitor_mode_always_label">Alati</string>
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Kui nähtaval</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Kaas on avatud</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">Kõrvas</string>
|
||||
<string name="pods_dual_left_label">Vasak klapp</string>
|
||||
<string name="pods_dual_right_label">Parem klapp</string>
|
||||
<string name="pods_case_label">Kaas</string>
|
||||
<string name="pods_case_status_open_label">Avatud</string>
|
||||
<string name="pods_case_status_closed_label">Suletud</string>
|
||||
<string name="pods_connection_state_disconnected_label">Puudub ühendus seadmega</string>
|
||||
<string name="pods_connection_state_idle_label">Ühendatud seadmega, ent ooterežiimil</string>
|
||||
<string name="pods_connection_state_music_label">Muusikarežiimis</string>
|
||||
<string name="pods_connection_state_call_label">Kõnerežiimis</string>
|
||||
<string name="pods_connection_state_ringing_label">Heliseb</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Kõne lõpetamine</string>
|
||||
<string name="pods_connection_state_unknown_label">Tundmatu ühenduse olek</string>
|
||||
<string name="pods_unknown_raw_data_label">Algandmed</string>
|
||||
<string name="pods_unknown_label">Tundmatu seade</string>
|
||||
<string name="pods_unknown_contact_dev">See on tundmatu seade, mis kasutab sarnast teatevormingut. Las ma muudan selle ühilduvaks, selleks võta minuga ühendust :)</string>
|
||||
<string name="pods_none_label_short">Puudub seade</string>
|
||||
<string name="pods_charging_label">Laeb</string>
|
||||
<string name="pods_inear_label">Kõrvas</string>
|
||||
<string name="pods_microphone_label">Mikrofon</string>
|
||||
<string name="pods_yours">Sinu</string>
|
||||
<string name="headset_being_worn_label">Kasutusel</string>
|
||||
<string name="headset_not_being_worn_label">Ei kasuta</string>
|
||||
<string name="pods_case_unknown_state">Tundmatu olek</string>
|
||||
<string name="last_seen_x">Viimati kasutusel: %s</string>
|
||||
<string name="first_seen_x">Esimesena kasutusel: %s</string>
|
||||
<string name="permission_post_notifications_label">Näita teateid</string>
|
||||
<string name="permission_post_notifications_description">"Luba CAPodil näidata AirPodsi teateid, nt praegust olekut ühendatud olles."</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
|
||||
<string name="general_value_not_available_label">E/E</string>
|
||||
<string name="general_error_label">Errorea</string>
|
||||
<string name="general_grant_permission_action">Eman baimena</string>
|
||||
|
||||
<string name="overview_nomaindevice_label">Ez dago gailu nagusirik</string>
|
||||
<string name="overview_nomaindevice_description">Detektatutako gailu guztiak nekez izango dira zureak. Piztu eta konektatu zure gailua edo egokitu ezarpenak.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth desgaituta dago</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth desgaituta dago, gaitu ;)</string>
|
||||
|
||||
<string name="permission_bluetooth_connect_label">Bluetooth konexioa</string>
|
||||
<string name="permission_bluetooth_connect_description">Aplikazio honek \"Bluetooth konexioa\" baimena behar du parekatutako gailuekin elkarreragiteko eta konexioak hasteko.</string>
|
||||
<string name="permission_bluetooth_scan_label">Bluetooth eskaneatzea</string>
|
||||
<string name="permission_bluetooth_scan_description">\"Bluetooth eskaneatzea\" baimenak aplikazio honi zure AirPods bezalako inguruko gailuetatik Bluetooth datuak aurkitu eta jasotzeko aukera ematen dio.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Aplikazio honek \"Bluetooth\" baimena behar du parekatutako Bluetooth gailuetara konektatzeko.</string>
|
||||
<string name="permission_access_fine_location_label">Sarbide zehatza kokapenera</string>
|
||||
<string name="permission_access_fine_location_description">CAPod-ek \"kokapen zehatza\" baimena erabiltzen du Bluetooth Energia Baxuko datuak jasotzeko. Zure entzungailuek Bluetooth Energia Baxuko teknologia erabiltzen dute beren egoera igortzeko. Aplikazio honek EZ du Bluetooth datuak erabiliko zure kokapena zehazteko.</string>
|
||||
<string name="permission_background_location_label">Atzeko planoko kokapen sarbidea</string>
|
||||
<string name="permission_background_location_description">CAPod-ek \"atzeko planoko kokapen sarbidea\" erabiltzen du aplikazioa itxita dagoen bitartean \"Erakutsi pop-up\" eta \"Automatikoki konektatu\" bezalako funtzioak gaitzeko. Atzeko planoko kokapen sarbideak aplikazioari Bluetooth Energia Baxuko datuak jasotzeko aukera ematen dio atzeko planoan dagoen bitartean. Aplikazio honek EZ du Bluetooth datuak erabiliko zure kokapena zehazteko.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Desgaitu bateriaren optimizazioak</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Bateriaren optimizazioek aplikazio honek atzeko planoan dagoen bitartean Bluetooth datuak modu fidagarrian jasotzea eragozten dute.</string>
|
||||
<string name="permission_required_title">Baimen hau beharrezkoa da:</string>
|
||||
<string name="permission_system_alert_window_label">Sistemaren Alerta Leihoa</string>
|
||||
<string name="permission_system_alert_window_description">Baimendu CAPod-i beste aplikazio batzuen gainean marrazteko \"Erakutsi PopUp\" funtzioa posible egiteko.</string>
|
||||
|
||||
<string name="settings_scanner_mode_lowpower_label">Energia baxua</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Orekatua</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Latentzia baxua</string>
|
||||
|
||||
<string name="settings_monitor_mode_manual_label">Aplikazioa irekita dagoenean</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Gailua konektatuta dagoenean</string>
|
||||
<string name="settings_monitor_mode_always_label">Beti</string>
|
||||
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Ikustean</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Kaxa irekita dago</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">Belarrian</string>
|
||||
|
||||
<string name="pods_dual_left_label">Ezkerreko poda</string>
|
||||
<string name="pods_dual_right_label">Eskuineko poda</string>
|
||||
<string name="pods_case_label">Kaxa</string>
|
||||
<string name="pods_case_status_open_label">Irekia</string>
|
||||
<string name="pods_case_status_closed_label">Itxita</string>
|
||||
<string name="pods_connection_state_disconnected_label">Ez dago gailu batera konektatuta</string>
|
||||
<string name="pods_connection_state_idle_label">Gailu batera konektatuta, baina inaktibo</string>
|
||||
<string name="pods_connection_state_music_label">Musika moduan</string>
|
||||
<string name="pods_connection_state_call_label">Dei moduan</string>
|
||||
<string name="pods_connection_state_ringing_label">Deitzen ari da</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Eskegitzen ari da</string>
|
||||
<string name="pods_connection_state_unknown_label">Konexio egoera ezezaguna</string>
|
||||
<string name="pods_unknown_raw_data_label">Datu gordinak</string>
|
||||
<string name="pods_unknown_label">Gailu ezezaguna</string>
|
||||
<string name="pods_unknown_contact_dev">Hau gailu ezezagun bat da, baina antzeko mezu formatua erabiltzen ari da. Gehitu diezaiogun euskarria, jarri nirekin harremanetan :)</string>
|
||||
<string name="pods_none_label_short">Gailurik ez</string>
|
||||
<string name="pods_charging_label">Kargatzen</string>
|
||||
<string name="pods_inear_label">Belarrian</string>
|
||||
<string name="pods_microphone_label">Mikrofonoa</string>
|
||||
<string name="pods_yours">Zureak</string>
|
||||
<string name="headset_being_worn_label">Jantzita</string>
|
||||
<string name="headset_not_being_worn_label">Ez dago jantzita</string>
|
||||
<string name="pods_case_unknown_state">Egoera ezezaguna</string>
|
||||
|
||||
<string name="last_seen_x">Azken aldiz ikusia: %s</string>
|
||||
<string name="first_seen_x">Lehen aldiz ikusia: %s</string>
|
||||
<string name="permission_post_notifications_label">Erakutsi jakinarazpenak</string>
|
||||
<string name="permission_post_notifications_description">Baimendu CAPod-i zure AirPods-ei buruzko jakinarazpenak erakustea, adibidez, konektatuta dauden bitartean duten uneko egoera.</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
<string name="general_value_not_available_label">موجود نیست</string>
|
||||
<string name="general_error_label">خطا</string>
|
||||
<string name="general_grant_permission_action">دادن دسترسی</string>
|
||||
<string name="overview_nomaindevice_label">دستگاه اصلی وجود ندارد</string>
|
||||
<string name="overview_nomaindevice_description">همه دستگاههای شناسایی شده به احتمال زیاد متعلق به شما نیستند. دستگاه خود را روشن کنید و متصل کنید یا تنظیمات را تغییر دهید.</string>
|
||||
<string name="overview_bluetooth_disabled_label">بلوتوث غیرفعال است.</string>
|
||||
<string name="overview_bluetooth_disabled_description">بلوتوث غیرفعال است، آن را فعال کنید. ;)</string>
|
||||
<string name="permission_bluetooth_connect_label">اتصال بلوتوث</string>
|
||||
<string name="permission_bluetooth_connect_description">این برنامه برای تعامل با دستگاههای جفتشده و آغاز اتصالات به مجوز «اتصال بلوتوث» نیاز دارد.</string>
|
||||
<string name="permission_bluetooth_scan_label">اسکن کردن بلوتوث</string>
|
||||
<string name="permission_bluetooth_scan_description">مجوز «اسکن کردن بلوتوث» به این برنامه اجازه میدهد دادههای بلوتوث را از دستگاههای نزدیک مانند AirPods شما کشف و دریافت کند.</string>
|
||||
<string name="permission_bluetooth_label">بلوتوث</string>
|
||||
<string name="permission_bluetooth_description">این برنامه برای اتصال به دستگاههای بلوتوث جفتشده به مجوز «بلوتوث» نیاز دارد.</string>
|
||||
<string name="permission_access_fine_location_label">دسترسی به مکان دقیق</string>
|
||||
<string name="permission_access_fine_location_description">CAPod از مجوز «مکان دقیق» برای دریافت دادههای بلوتوث کممصرف استفاده میکند. هدفونهای شما از فناوری بلوتوث کممصرف برای پخش وضعیت خود استفاده میکنند. این برنامه از دادههای بلوتوث برای تعیین موقعیت مکانی شما استفاده نخواهد کرد.</string>
|
||||
<string name="permission_background_location_label">دسترسی به مکان در پسزمینه</string>
|
||||
<string name="permission_background_location_description"> برنامه CAPod از \"دسترسی به مکان در پسزمینه\" برای فعال کردن ویژگیهایی مانند \"نمایش پنجره بازشو\" و \"اتصال خودکار\" در زمانی که برنامه بسته است، استفاده میکند. این دسترسی به برنامه امکان دریافت دادههای بلوتوث کم مصرف در پسزمینه را میدهد. این برنامه از دادههای بلوتوث برای تعیین موقعیت مکانی شما استفاده نخواهد کرد.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">غیرفعال کردن بهینهسازی باتری</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">بهینهسازی باتری باعث میشود این برنامه نتواند بهطور قابلاعتماد دادههای بلوتوث را هنگامی که در پسزمینه فعال است، دریافت کند.</string>
|
||||
<string name="permission_required_title">مجوز زیر مورد نیاز است:</string>
|
||||
<string name="permission_system_alert_window_label">پنجره هشدار سیستمی</string>
|
||||
<string name="permission_system_alert_window_description">اجازه دهید CAPod بتواند روی برنامههای دیگر نمایش داده شود تا قابلیت \"نمایش پنجره بازشو\" امکانپذیر شود.</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">قدرت کم</string>
|
||||
<string name="settings_scanner_mode_balanced_label">متعادل</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">تأخیر کم</string>
|
||||
<string name="settings_monitor_mode_manual_label">وقتی برنامه باز است</string>
|
||||
<string name="settings_monitor_mode_automatic_label">وقتی دستگاه وصل است</string>
|
||||
<string name="settings_monitor_mode_always_label">همیشه</string>
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">موقع دیدن</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">بسته بودن کیس</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">در گوش</string>
|
||||
<string name="pods_dual_left_label">گوش چپ</string>
|
||||
<string name="pods_dual_right_label">گوش راست</string>
|
||||
<string name="pods_case_label">کیس</string>
|
||||
<string name="pods_case_status_open_label">باز</string>
|
||||
<string name="pods_case_status_closed_label">بسته</string>
|
||||
<string name="pods_connection_state_disconnected_label">هیچ دستگاهی متصل نیست</string>
|
||||
<string name="pods_connection_state_idle_label">متصل به یک دستگاه، اما غیرفعال</string>
|
||||
<string name="pods_connection_state_music_label">در حالت موسیقی</string>
|
||||
<string name="pods_connection_state_call_label">در حالت تماس</string>
|
||||
<string name="pods_connection_state_ringing_label">زنگ زدن</string>
|
||||
<string name="pods_connection_state_hanging_up_label">قطع کردن</string>
|
||||
<string name="pods_connection_state_unknown_label">وضعیت اتصال نامشخص</string>
|
||||
<string name="pods_unknown_raw_data_label">داده های خام</string>
|
||||
<string name="pods_unknown_label">دستگاه ناشناخته</string>
|
||||
<string name="pods_unknown_contact_dev">این یک دستگاه ناشناخته است، اما از فرمت پیام مشابهی استفاده میکند. بیایید از آن پشتیبانی کنیم، با من تماس بگیرید. :)</string>
|
||||
<string name="pods_none_label_short">بدون دستگاه</string>
|
||||
<string name="pods_charging_label">درحال شارژ</string>
|
||||
<string name="pods_inear_label">در گوش</string>
|
||||
<string name="pods_microphone_label">میکروفون</string>
|
||||
<string name="pods_yours">مال شما</string>
|
||||
<string name="headset_being_worn_label">در حال استفاده</string>
|
||||
<string name="headset_not_being_worn_label">عدم استفاده</string>
|
||||
<string name="pods_case_unknown_state">وضعیت ناشناخته</string>
|
||||
<string name="last_seen_x">آخرین بازدید: %s</string>
|
||||
<string name="first_seen_x">اولین بازدید: %s</string>
|
||||
<string name="permission_post_notifications_label">نمایش اعلانها</string>
|
||||
<string name="permission_post_notifications_description">"اجازه دهید CAPod اعلانهایی درباره ایرپادهای شما نمایش دهد، مانند وضعیت فعلی آنها هنگام اتصال."</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
|
||||
<string name="general_value_not_available_label">N/A</string>
|
||||
<string name="general_error_label">Virhe</string>
|
||||
<string name="general_grant_permission_action">Myönnä lupa</string>
|
||||
|
||||
<string name="overview_nomaindevice_label">Ei ensisijaista laitetta</string>
|
||||
<string name="overview_nomaindevice_description">Kaikki havaitut laitteet eivät todennäköisesti ole sinun. Kytke laitteeseesi virta ja yhdistä se tai säädä asetuksia.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth on pois käytöstä</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth on pois käytöstä, ota se käyttöön ;)</string>
|
||||
|
||||
<string name="permission_bluetooth_connect_label">Bluetooth-yhteys</string>
|
||||
<string name="permission_bluetooth_connect_description">Tämä sovellus vaatii \"Bluetooth-yhteys\"-luvan ollakseen vuorovaikutuksessa pariliitettyjen laitteiden kanssa ja aloittaakseen yhteyksiä.</string>
|
||||
<string name="permission_bluetooth_scan_label">Bluetooth-skannaus</string>
|
||||
<string name="permission_bluetooth_scan_description">\"Bluetooth-skannaus\"-lupa antaa tämän sovelluksen löytää ja vastaanottaa Bluetooth-tietoja lähellä olevista laitteista, kuten AirPodeistasi.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Tämä sovellus vaatii \"Bluetooth\"-luvan yhdistääkseen pariliitettyihin Bluetooth-laitteisiin.</string>
|
||||
<string name="permission_access_fine_location_label">Tarkka sijainti</string>
|
||||
<string name="permission_access_fine_location_description">CAPod käyttää \"tarkka sijainti\" -lupaa vastaanottaakseen Bluetooth Low Energy -tietoja. Kuulokkeesi käyttävät Bluetooth Low Energy -tekniikkaa lähettääkseen tilansa. Tämä sovellus EI käytä Bluetooth-tietoja sijaintisi määrittämiseen.</string>
|
||||
<string name="permission_background_location_label">Sijainnin käyttö taustalla</string>
|
||||
<string name="permission_background_location_description">CAPod käyttää \"sijainnin käyttö taustalla\" -lupaa mahdollistaakseen ominaisuuksia, kuten \"Näytä ponnahdusikkuna\" ja \"Automaattinen yhdistäminen\", kun sovellus on suljettu. Sijainnin käyttö taustalla antaa sovelluksen vastaanottaa Bluetooth Low Energy -tietoja sen ollessa taustalla. Tämä sovellus EI käytä Bluetooth-tietoja sijaintisi määrittämiseen.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Poista akun optimoinnit käytöstä</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Akun optimoinnit estävät tätä sovellusta vastaanottamasta luotettavasti Bluetooth-tietoja sovelluksen ollessa taustalla.</string>
|
||||
<string name="permission_required_title">Seuraava lupa vaaditaan:</string>
|
||||
<string name="permission_system_alert_window_label">Järjestelmän ilmoitusikkuna</string>
|
||||
<string name="permission_system_alert_window_description">Salli CAPodin piirtää muiden sovellusten päälle mahdollistaaksesi \"Näytä ponnahdusikkuna\" -ominaisuuden.</string>
|
||||
|
||||
<string name="settings_scanner_mode_lowpower_label">Alhainen virrankulutus</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Tasapainotettu</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Matala viive</string>
|
||||
|
||||
<string name="settings_monitor_mode_manual_label">Kun sovellus on auki</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Kun laite on yhdistetty</string>
|
||||
<string name="settings_monitor_mode_always_label">Aina</string>
|
||||
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Kun havaittu</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Kotelo on auki</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">Korvassa</string>
|
||||
|
||||
<string name="pods_dual_left_label">Vasen kuuloke</string>
|
||||
<string name="pods_dual_right_label">Oikea kuuloke</string>
|
||||
<string name="pods_case_label">Kotelo</string>
|
||||
<string name="pods_case_status_open_label">Auki</string>
|
||||
<string name="pods_case_status_closed_label">Suljettu</string>
|
||||
<string name="pods_connection_state_disconnected_label">Ei yhteydessä laitteeseen</string>
|
||||
<string name="pods_connection_state_idle_label">Yhdistetty laitteeseen, mutta käyttämättömänä</string>
|
||||
<string name="pods_connection_state_music_label">Musiikkitilassa</string>
|
||||
<string name="pods_connection_state_call_label">Puhelutilassa</string>
|
||||
<string name="pods_connection_state_ringing_label">Soi</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Lopettaa puhelua</string>
|
||||
<string name="pods_connection_state_unknown_label">Tuntematon yhteystila</string>
|
||||
<string name="pods_unknown_raw_data_label">Raakadata</string>
|
||||
<string name="pods_unknown_label">Tuntematon laite</string>
|
||||
<string name="pods_unknown_contact_dev">Tämä on tuntematon laite, mutta se käyttää samanlaista viestimuotoa. Lisätään tuki sille, ota yhteyttä :)</string>
|
||||
<string name="pods_none_label_short">Ei laitetta</string>
|
||||
<string name="pods_charging_label">Latautuu</string>
|
||||
<string name="pods_inear_label">Korvassa</string>
|
||||
<string name="pods_microphone_label">Mikrofoni</string>
|
||||
<string name="pods_yours">Sinun</string>
|
||||
<string name="headset_being_worn_label">Käytössä</string>
|
||||
<string name="headset_not_being_worn_label">Ei käytössä</string>
|
||||
<string name="pods_case_unknown_state">Tuntematon tila</string>
|
||||
|
||||
<string name="last_seen_x">Viimeksi nähty: %s</string>
|
||||
<string name="first_seen_x">Ensimmäisen kerran nähty: %s</string>
|
||||
<string name="permission_post_notifications_label">Näytä ilmoitukset</string>
|
||||
<string name="permission_post_notifications_description">"Salli CAPodin näyttää ilmoituksia AirPodeistasi, esim. niiden nykyinen tila yhdistettynä."</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,18 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
|
||||
<string name="general_value_not_available_label">Hindi Magagamit</string>
|
||||
<string name="general_error_label">Error</string>
|
||||
<string name="general_grant_permission_action">Bigyan ng permiso</string>
|
||||
<string name="overview_nomaindevice_description">Lahat ng mga device ay malabong saiyo. I-power on at i-connect ang device mo o i-adjust ang settings.</string>
|
||||
<string name="permission_bluetooth_connect_description">Ang app na ito ay kailangan ng \"Bluetooth connect\" permission para makipag interact sa mga na-paired na devices at maka gawa ng connection.</string>
|
||||
<string name="permission_bluetooth_scan_description">Ang \"Bluetooth scanning\" permission ay pag na ibigay, itong app na ito ay puwedeng mag diskubre o maka receive ng data galing sa isang malapit na Bluetooth devices tulad ng iyong AirPods.</string>
|
||||
<string name="permission_bluetooth_description">Ang app na ito ay kailangan ng \"Bluetooth\" permission upang maka connect sa naka-paired na bluetooth device.</string>
|
||||
<string name="permission_access_fine_location_description">Ang CAPod app ay gumagamit ng \"Fine location\" permission para maka receiveng Bluetooth Low Energy Data. Iyong headphones ay gumagamit ng Bluetooth Low Energy technology pa ma-broadcaast ang kanilang Status. Itong app na ito ay HINDI gagamitin ang Bluetooth data para malaman ang iyong lokasyon.</string>
|
||||
|
||||
<string name="overview_nomaindevice_label">Walang pangunahing device</string>
|
||||
<string name="overview_nomaindevice_description">Lahat ng mga natukoyang device ay malamang na hindi sa iyo. I-on at ikonekta ang iyong device o ayusin ang mga setting.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Naka-disable ang Bluetooth</string>
|
||||
<string name="overview_bluetooth_disabled_description">Naka-disable ang Bluetooth, i-enable ito ;)</string>
|
||||
|
||||
<string name="permission_bluetooth_connect_label">Kumonekta sa Bluetooth</string>
|
||||
<string name="permission_bluetooth_connect_description">Ang app na ito ay nangangailangan ng pahintulot na \"Kumonekta sa Bluetooth\" upang makipag-ugnayan sa mga naka-pares na device at simulan ang mga koneksyon.</string>
|
||||
<string name="permission_bluetooth_scan_label">Pag-scan sa Bluetooth</string>
|
||||
<string name="permission_bluetooth_scan_description">Ang pahintulot na \"Pag-scan sa Bluetooth\" ay nagbibigay-daan sa app na ito na matuklasan at makatanggap ng data ng Bluetooth mula sa mga kalapit na device tulad ng iyong AirPods.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Ang app na ito ay nangangailangan ng pahintulot na \"Bluetooth\" upang kumonekta sa mga naka-pares na bluetooth device.</string>
|
||||
<string name="permission_access_fine_location_label">I-access ang eksaktong lokasyon</string>
|
||||
<string name="permission_access_fine_location_description">Ginagamit ng CAPod ang pahintulot na \"eksaktong lokasyon\" upang makatanggap ng data ng Bluetooth Low Energy. Ginagamit ng iyong mga headphone ang teknolohiyang Bluetooth Low Energy upang i-broadcast ang kanilang status. HINDI gagamitin ng app na ito ang data ng Bluetooth upang matukoy ang iyong lokasyon.</string>
|
||||
<string name="permission_background_location_label">Access sa lokasyon sa background</string>
|
||||
<string name="permission_background_location_description">Ginagamit ng CAPod ang \"access sa lokasyon sa background\" upang paganahin ang mga feature tulad ng \"Ipakita ang popup\" at \"AutoConnect\" habang sarado ang app. Ang access sa lokasyon sa background ay nagbibigay-daan sa app na makatanggap ng data ng Bluetooth Low Energy habang nasa background ito. HINDI gagamitin ng app na ito ang data ng Bluetooth upang matukoy ang iyong lokasyon.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">I-disable ang mga pag-optimize ng baterya</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Pinipigilan ng mga pag-optimize ng baterya ang app na ito na mapagkakatiwalaang makatanggap ng data ng Bluetooth habang nasa background ang app.</string>
|
||||
<string name="permission_required_title">Kinakailangan ang sumusunod na pahintulot:</string>
|
||||
<string name="permission_system_alert_window_label">System Alert Window</string>
|
||||
<string name="permission_system_alert_window_description">Payagan ang CAPod na mag-drawing sa ibabaw ng iba pang mga app upang maging posible ang feature na \"Ipakita ang PopUp\".</string>
|
||||
|
||||
<string name="settings_scanner_mode_lowpower_label">Mababang konsumo ng kuryente</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Balanse</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Mababang latency</string>
|
||||
|
||||
<string name="settings_monitor_mode_manual_label">Kapag bukas ang app</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Kapag konektado ang device</string>
|
||||
<string name="settings_monitor_mode_always_label">Palagi</string>
|
||||
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Kapag nakita</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Bukas ang case</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">Nasa tainga</string>
|
||||
|
||||
<string name="pods_dual_left_label">Kaliwang pod</string>
|
||||
<string name="pods_dual_right_label">Kanan na pod</string>
|
||||
<string name="pods_dual_right_label">Kanang pod</string>
|
||||
<string name="pods_case_label">Case</string>
|
||||
<string name="pods_case_status_open_label">Buksan</string>
|
||||
<string name="pods_case_status_closed_label">Sarado</string>
|
||||
<string name="pods_connection_state_disconnected_label">Hindi Konektado sa device</string>
|
||||
<string name="pods_unknown_contact_dev">Ito ay hindi kilalang Device, pero ginagamit ang kilalang message format. lagyan nating ng support para diyan, contact mo ko :)</string>
|
||||
<string name="pods_yours">Sayo</string>
|
||||
<string name="headset_being_worn_label">Ginagamit</string>
|
||||
<string name="pods_case_unknown_state">Hindi alam ang stado</string>
|
||||
<string name="pods_connection_state_disconnected_label">Hindi konektado sa isang device</string>
|
||||
<string name="pods_connection_state_idle_label">Konektado sa isang device, ngunit idle</string>
|
||||
<string name="pods_connection_state_music_label">Nasa music mode</string>
|
||||
<string name="pods_connection_state_call_label">Nasa call mode</string>
|
||||
<string name="pods_connection_state_ringing_label">Nagri-ring</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Binababa ang tawag</string>
|
||||
<string name="pods_connection_state_unknown_label">Hindi kilalang estado ng koneksyon</string>
|
||||
<string name="pods_unknown_raw_data_label">Raw data</string>
|
||||
<string name="pods_unknown_label">Hindi kilalang device</string>
|
||||
<string name="pods_unknown_contact_dev">Ito ay isang hindi kilalang device, ngunit gumagamit ito ng katulad na format ng mensahe. Suportahan natin ito, makipag-ugnayan sa akin :)</string>
|
||||
<string name="pods_none_label_short">Walang device</string>
|
||||
<string name="pods_charging_label">Nagcha-charge</string>
|
||||
<string name="pods_inear_label">Nasa tainga</string>
|
||||
<string name="pods_microphone_label">Mikropono</string>
|
||||
<string name="pods_yours">Sa iyo</string>
|
||||
<string name="headset_being_worn_label">Suot</string>
|
||||
<string name="headset_not_being_worn_label">Hindi suot</string>
|
||||
<string name="pods_case_unknown_state">Hindi kilalang estado</string>
|
||||
|
||||
<string name="last_seen_x">Huling nakita: %s</string>
|
||||
<string name="first_seen_x">Unang nakita: %s</string>
|
||||
<string name="permission_post_notifications_label">Ipakita ang mga notification</string>
|
||||
<string name="permission_post_notifications_description">"Payagan ang CAPod na magpakita ng mga notification tungkol sa iyong AirPods, hal. ang kanilang kasalukuyang status habang konektado."</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS (libre)</string>
|
||||
<string name="general_value_not_available_label">Non disponible</string>
|
||||
<string name="general_value_not_available_label">N.D.</string>
|
||||
<string name="general_error_label">Erreur</string>
|
||||
<string name="general_grant_permission_action">Accorder l’autorisation</string>
|
||||
<string name="overview_nomaindevice_label">Aucun appareil principal</string>
|
||||
@@ -19,12 +19,12 @@
|
||||
<string name="permission_access_fine_location_label">Accéder à la position précise</string>
|
||||
<string name="permission_access_fine_location_description">CAPod utilise l’autorisation « position précise » pour recevoir les données « faible énergie » de Bluetooth. Votre casque-micro utilise la technologie « basse énergie » de Bluetooth pour diffuser son état. Cette appli n’utilisera PAS les données Bluetooth pour déterminer votre position.</string>
|
||||
<string name="permission_background_location_label">Accès à la position en arrière-plan</string>
|
||||
<string name="permission_background_location_description">CAPod utilise « l’accès à la position en arrière-plan » afin de permettre des fonctions telles qu’« Afficher une fenêtre surgissante » et « Connexion automatique » alors que l’appli est fermée. L’accès à la position en arrière-plan permet à l’appli de recevoir les données « basse énergie » de Bluetooth. Elles ne seront PAS utilisées pour déterminer votre position géographie.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Désactiver les optimisations de la pile/batterie</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Les optimisations de la pile/batterie empêchent l’appli de recevoir les données Bluetooth de manière fiable alors que l’appli est en arrière-plan.</string>
|
||||
<string name="permission_background_location_description">CAPod utilise « l’accès à la position en arrière-plan » afin de permettre des fonctions telles qu’« Afficher une fenêtre contextuelle » et « Connexion automatique » alors que l’appli est fermée. L’accès à la position en arrière-plan permet à l’appli de recevoir les données « basse énergie » de Bluetooth. Elles ne seront PAS utilisées pour déterminer votre position géographie.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Désactiver les optimisations de la batterie</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Les optimisations de la batterie empêchent l’appli de recevoir les données Bluetooth de manière fiable alors que l’appli est en arrière-plan.</string>
|
||||
<string name="permission_required_title">L’autorisation suivante est nécessaire :</string>
|
||||
<string name="permission_system_alert_window_label">Fenêtre d’alerte système</string>
|
||||
<string name="permission_system_alert_window_description">Permettre à CAPod d’apparaître par-dessus les autres applis pour rendre la fonction « Afficher une fenêtre surgissante » possible.</string>
|
||||
<string name="permission_system_alert_window_description">Permettre à CAPod d’apparaître par-dessus les autres applis pour rendre la fonction « Afficher une fenêtre contextuelle » possible.</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">Faible puissance</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Équilibré</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Faible latence</string>
|
||||
@@ -55,6 +55,7 @@
|
||||
<string name="pods_microphone_label">Microphone</string>
|
||||
<string name="pods_yours">Les vôtres</string>
|
||||
<string name="headset_being_worn_label">Porté</string>
|
||||
<string name="headset_not_being_worn_label">Non porté</string>
|
||||
<string name="pods_case_unknown_state">État inconnu</string>
|
||||
<string name="last_seen_x">Vu pour la dernière fois : %s</string>
|
||||
<string name="first_seen_x">Vu pour la première fois : %s</string>
|
||||
|
||||
@@ -1,2 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
|
||||
<string name="general_value_not_available_label">N/D</string>
|
||||
<string name="general_error_label">Erro</string>
|
||||
<string name="general_grant_permission_action">Conceder permiso</string>
|
||||
|
||||
<string name="overview_nomaindevice_label">Sen dispositivo principal</string>
|
||||
<string name="overview_nomaindevice_description">É improbable que todos os dispositivos detectados sexan teus. Acende e conecta o teu dispositivo ou axusta a configuración.</string>
|
||||
<string name="overview_bluetooth_disabled_label">O Bluetooth está desactivado</string>
|
||||
<string name="overview_bluetooth_disabled_description">O Bluetooth está desactivado, actívao ;)</string>
|
||||
|
||||
<string name="permission_bluetooth_connect_label">Conexión Bluetooth</string>
|
||||
<string name="permission_bluetooth_connect_description">Esta aplicación require o permiso de \"Conexión Bluetooth\" para interactuar con dispositivos emparellados e iniciar conexións.</string>
|
||||
<string name="permission_bluetooth_scan_label">Escaneo Bluetooth</string>
|
||||
<string name="permission_bluetooth_scan_description">O permiso de \"Escaneo Bluetooth\" permite a esta aplicación descubrir e recibir datos Bluetooth de dispositivos próximos como os teus AirPods.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Esta aplicación require o permiso de \"Bluetooth\" para conectarse a dispositivos bluetooth emparellados.</string>
|
||||
<string name="permission_access_fine_location_label">Acceder á localización precisa</string>
|
||||
<string name="permission_access_fine_location_description">CAPod usa o permiso de \"localización precisa\" para recibir datos de Bluetooth Low Energy. Os teus auriculares usan a tecnoloxía Bluetooth Low Energy para transmitir o seu estado. Esta aplicación NON usará datos Bluetooth para determinar a túa localización.</string>
|
||||
<string name="permission_background_location_label">Acceso á localización en segundo plano</string>
|
||||
<string name="permission_background_location_description">CAPod usa o \"acceso á localización en segundo plano\" para habilitar funcións como \"Mostrar ventá emerxente\" e \"Conexión automática\" mentres a aplicación está pechada. O acceso á localización en segundo plano permite á aplicación recibir datos de Bluetooth Low Energy mentres está en segundo plano. Esta aplicación NON usará datos Bluetooth para determinar a túa localización.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Desactivar optimizacións da batería</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">As optimizacións da batería impiden que esta aplicación reciba datos Bluetooth de forma fiable mentres a aplicación está en segundo plano.</string>
|
||||
<string name="permission_required_title">Requírese o seguinte permiso:</string>
|
||||
<string name="permission_system_alert_window_label">Ventá de alerta do sistema</string>
|
||||
<string name="permission_system_alert_window_description">Permitir a CAPod debuxar sobre outras aplicacións para facer posible a función \"Mostrar ventá emerxente\".</string>
|
||||
|
||||
<string name="settings_scanner_mode_lowpower_label">Baixa potencia</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Equilibrado</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Baixa latencia</string>
|
||||
|
||||
<string name="settings_monitor_mode_manual_label">Cando a aplicación está aberta</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Cando o dispositivo está conectado</string>
|
||||
<string name="settings_monitor_mode_always_label">Sempre</string>
|
||||
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Cando se ve</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">A funda está aberta</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">Na orella</string>
|
||||
|
||||
<string name="pods_dual_left_label">Auricular esquerdo</string>
|
||||
<string name="pods_dual_right_label">Auricular dereito</string>
|
||||
<string name="pods_case_label">Funda</string>
|
||||
<string name="pods_case_status_open_label">Aberta</string>
|
||||
<string name="pods_case_status_closed_label">Pechada</string>
|
||||
<string name="pods_connection_state_disconnected_label">Non conectado a un dispositivo</string>
|
||||
<string name="pods_connection_state_idle_label">Conectado a un dispositivo, pero inactivo</string>
|
||||
<string name="pods_connection_state_music_label">En modo música</string>
|
||||
<string name="pods_connection_state_call_label">En modo chamada</string>
|
||||
<string name="pods_connection_state_ringing_label">Soando</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Colgando</string>
|
||||
<string name="pods_connection_state_unknown_label">Estado de conexión descoñecido</string>
|
||||
<string name="pods_unknown_raw_data_label">Datos brutos</string>
|
||||
<string name="pods_unknown_label">Dispositivo descoñecido</string>
|
||||
<string name="pods_unknown_contact_dev">Este é un dispositivo descoñecido, pero está a usar un formato de mensaxe similar. Imos engadir soporte para el, ponte en contacto comigo :)</string>
|
||||
<string name="pods_none_label_short">Sen dispositivo</string>
|
||||
<string name="pods_charging_label">Cargando</string>
|
||||
<string name="pods_inear_label">Na orella</string>
|
||||
<string name="pods_microphone_label">Micrófono</string>
|
||||
<string name="pods_yours">Teus</string>
|
||||
<string name="headset_being_worn_label">Postos</string>
|
||||
<string name="headset_not_being_worn_label">Non postos</string>
|
||||
<string name="pods_case_unknown_state">Estado descoñecido</string>
|
||||
|
||||
<string name="last_seen_x">Visto por última vez: %s</string>
|
||||
<string name="first_seen_x">Visto por primeira vez: %s</string>
|
||||
<string name="permission_post_notifications_label">Mostrar notificacións</string>
|
||||
<string name="permission_post_notifications_description">"Permitir a CAPod mostrar notificacións sobre os teus AirPods, p. ex., o seu estado actual mentres están conectados."</string>
|
||||
</resources>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">कैपोड</string>
|
||||
<string name="app_name_pro">कैपोड प्रो</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
<string name="general_value_not_available_label">N/A</string>
|
||||
<string name="app_name_foss">कैपॉड फॉस्स</string>
|
||||
<string name="general_value_not_available_label">अनुवाद उपलब्ध नहीं है।</string>
|
||||
<string name="general_error_label">त्रुटि</string>
|
||||
<string name="general_grant_permission_action">अनुदान अनुमति</string>
|
||||
<string name="overview_nomaindevice_label">कोई प्राथमिक उपकरण नहीं</string>
|
||||
@@ -55,7 +55,10 @@
|
||||
<string name="pods_microphone_label">माइक्रोफ़ोन</string>
|
||||
<string name="pods_yours">आपका</string>
|
||||
<string name="headset_being_worn_label">पहना जा रहा है</string>
|
||||
<string name="headset_not_being_worn_label">नहीं पहना गया</string>
|
||||
<string name="pods_case_unknown_state">अज्ञात अवस्था</string>
|
||||
<string name="last_seen_x">अंतिम बार देखे गए: %s</string>
|
||||
<string name="first_seen_x">पहली बार देखा गया: %s</string>
|
||||
<string name="permission_post_notifications_label">नोटिफिकेशन्स सूचनाएँ दिखाएँ</string>
|
||||
<string name="permission_post_notifications_description">"CAPod को आपके AirPods के बारे में नोटिफिकेशन दिखाने दें, जैसे कि कनेक्टेड रहते हुए उनका वर्तमान स्टेटस।"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
<string name="general_value_not_available_label">N/A</string>
|
||||
<string name="general_error_label">Greška</string>
|
||||
<string name="general_grant_permission_action">Daj dopuštenje</string>
|
||||
<string name="overview_nomaindevice_label">Nema primarnog uređaja</string>
|
||||
<string name="overview_nomaindevice_description">Svi otkriveni uređaji vjerojatno nisu vaši. Uključite i povežite svoj uređaj ili prilagodite postavke.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth je onemogućen</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth je onemogućen, omogućite ga ;)</string>
|
||||
<string name="permission_bluetooth_connect_label">Bluetooth povezivanje</string>
|
||||
<string name="permission_bluetooth_connect_description">Ova aplikacija zahtijeva dopuštenje \"Bluetooth povezivanje\" za interakciju s uparenim uređajima i pokretanje veza.</string>
|
||||
<string name="permission_bluetooth_scan_label">Bluetooth skeniranje</string>
|
||||
<string name="permission_bluetooth_scan_description">Dopuštenje \"Bluetooth skeniranje\" omogućuje ovoj aplikaciji otkrivanje i primanje Bluetooth podataka s obližnjih uređaja kao što su vaši AirPods.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Ova aplikacija zahtijeva dopuštenje \"Bluetooth\" za povezivanje s uparenim Bluetooth uređajima.</string>
|
||||
<string name="permission_access_fine_location_label">Pristup preciznoj lokaciji</string>
|
||||
<string name="permission_access_fine_location_description">CAPod koristi dopuštenje \"precizna lokacija\" za primanje podataka Bluetooth Low Energy. Vaše slušalice koriste tehnologiju Bluetooth Low Energy za emitiranje svog statusa. Ova aplikacija NEĆE koristiti Bluetooth podatke za određivanje vaše lokacije.</string>
|
||||
<string name="permission_background_location_label">Pristup lokaciji u pozadini</string>
|
||||
<string name="permission_background_location_description">CAPod koristi \"pristup lokaciji u pozadini\" kako bi omogućio značajke kao što su \"Prikaži skočni prozor\" i \"Automatsko povezivanje\" dok je aplikacija zatvorena. Pristup lokaciji u pozadini omogućuje aplikaciji primanje podataka Bluetooth Low Energy dok je u pozadini. Ova aplikacija NEĆE koristiti Bluetooth podatke za određivanje vaše lokacije.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Onemogući optimizaciju baterije</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Optimizacija baterije sprječava ovu aplikaciju da pouzdano prima Bluetooth podatke dok je aplikacija u pozadini.</string>
|
||||
<string name="permission_required_title">Potrebno je sljedeće dopuštenje:</string>
|
||||
<string name="permission_system_alert_window_label">Prozor sistemskog upozorenja</string>
|
||||
<string name="permission_system_alert_window_description">Dopustite CAPod-u da se prikazuje preko drugih aplikacija kako bi značajka \"Prikaži PopUp\" bila moguća.</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">Niska snaga</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Uravnoteženo</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Niska latencija</string>
|
||||
<string name="settings_monitor_mode_manual_label">Kada je aplikacija otvorena</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Kada je uređaj povezan</string>
|
||||
<string name="settings_monitor_mode_always_label">Uvijek</string>
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Kada se vidi</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Kućište je otvoreno</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">U uhu</string>
|
||||
<string name="pods_dual_left_label">Lijeva slušalica</string>
|
||||
<string name="pods_dual_right_label">Desna slušalica</string>
|
||||
<string name="pods_case_label">Kućište</string>
|
||||
<string name="pods_case_status_open_label">Otvoreno</string>
|
||||
<string name="pods_case_status_closed_label">Zatvoreno</string>
|
||||
<string name="pods_connection_state_disconnected_label">Nije povezan s uređajem</string>
|
||||
<string name="pods_connection_state_idle_label">Povezan s uređajem, ali neaktivan</string>
|
||||
<string name="pods_connection_state_music_label">U glazbenom načinu</string>
|
||||
<string name="pods_connection_state_call_label">U načinu poziva</string>
|
||||
<string name="pods_connection_state_ringing_label">Zvoni</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Prekidanje veze</string>
|
||||
<string name="pods_connection_state_unknown_label">Nepoznato stanje veze</string>
|
||||
<string name="pods_unknown_raw_data_label">Sirovi podaci</string>
|
||||
<string name="pods_unknown_label">Nepoznati uređaj</string>
|
||||
<string name="pods_unknown_contact_dev">Ovo je nepoznati uređaj, ali koristi sličan format poruke. Dodajmo podršku za njega, kontaktirajte me :)</string>
|
||||
<string name="pods_none_label_short">Nema uređaja</string>
|
||||
<string name="pods_charging_label">Punjenje</string>
|
||||
<string name="pods_inear_label">U uhu</string>
|
||||
<string name="pods_microphone_label">Mikrofon</string>
|
||||
<string name="pods_yours">Vaše</string>
|
||||
<string name="headset_being_worn_label">Nosi se</string>
|
||||
<string name="headset_not_being_worn_label">Ne nosi se</string>
|
||||
<string name="pods_case_unknown_state">Nepoznato stanje</string>
|
||||
<string name="last_seen_x">Posljednji put viđen: %s</string>
|
||||
<string name="first_seen_x">Prvi put viđen: %s</string>
|
||||
<string name="permission_post_notifications_label">Prikaži obavijesti</string>
|
||||
<string name="permission_post_notifications_description">Dopustite CAPod-u da prikazuje obavijesti o vašim AirPods slušalicama, npr. njihov trenutni status dok su povezane.</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,57 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="general_value_not_available_label">Nincs_adat</string>
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
<string name="general_value_not_available_label">Nincs adat</string>
|
||||
<string name="general_error_label">Hiba</string>
|
||||
<string name="general_grant_permission_action">Hozzáférés megadva</string>
|
||||
<string name="general_grant_permission_action">Engedély megadása</string>
|
||||
<string name="overview_nomaindevice_label">Nincs elsődleges eszköz</string>
|
||||
<string name="overview_nomaindevice_description">Valószínűleg nem minden észlelt eszköz az Öné. Kapcsolja be és csatlakoztassa a készüléket, vagy módosítsa a beállításokat.</string>
|
||||
<string name="overview_bluetooth_disabled_label">A Bluetooth kikapcsolva</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth kikapcsolva, engedélyezd ;)</string>
|
||||
<string name="permission_bluetooth_connect_label">Bluetooth_kapcsolat</string>
|
||||
<string name="permission_bluetooth_connect_description">Ehhez az alkalmazáshoz a \"Bluetooth kapcsolat\" engedély szükséges a párosított eszközökkel való interakcióhoz és a kapcsolatok kezdeményezéséhez.</string>
|
||||
<string name="permission_bluetooth_scan_label">Bluetooth_keresés</string>
|
||||
<string name="permission_bluetooth_scan_description">A „Bluetooth-keresés” engedély lehetővé teszi az alkalmazás számára, hogy Bluetooth-adatokat keressen és fogadjon a közeli eszközökről, például az Ön AirPodjairól.</string>
|
||||
<string name="overview_bluetooth_disabled_label">A Bluetooth ki van kapcsolva</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth ki van kapcsolva, engedélyezze ;)</string>
|
||||
<string name="permission_bluetooth_connect_label">Bluetooth csatlakozás</string>
|
||||
<string name="permission_bluetooth_connect_description">Ehhez az alkalmazáshoz a \"Közeli eszközök\" engedély szükséges a párosított eszközökkel való interakcióhoz és a kapcsolatok kezdeményezéséhez.</string>
|
||||
<string name="permission_bluetooth_scan_label">Bluetooth keresés</string>
|
||||
<string name="permission_bluetooth_scan_description">A „Bluetooth-keresés” engedély lehetővé teszi az alkalmazás számára, hogy Bluetooth-adatokat keressen és fogadjon a közeli eszközökről, például az Ön AirPods-jairól.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Ehhez az alkalmazáshoz „Bluetooth” engedély szükséges a párosított Bluetooth-eszközökhöz való csatlakozáshoz.</string>
|
||||
<string name="permission_access_fine_location_label">Lokalizálás engedélyezése</string>
|
||||
<string name="permission_access_fine_location_description">A CAPod a „megfelelő hely” engedélyt használja a Bluetooth alacsony töltés adatok fogadására. Fejhallgatója Bluetooth alacsony töltés technológiát használ az állapotának közvetítésére. Ez az alkalmazás NEM használ Bluetooth-adatokat az Ön tartózkodási helyének meghatározásához.</string>
|
||||
<string name="permission_background_location_label">Háttér helyhozzáférés</string>
|
||||
<string name="permission_background_location_description">\"Automatikus_csatlakozás\".</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Az akkumulátor optimalizálás letiltása</string>
|
||||
<string name="permission_access_fine_location_description">A CAPod a „pontos hely” engedélyt használja a Bluetooth Low Energy adatok fogadására. Fülhallgatója Bluetooth Low Energy technológiát használ az állapotának közvetítésére. Ez az alkalmazás NEM használ Bluetooth adatokat az Ön tartózkodási helyének meghatározásához.</string>
|
||||
<string name="permission_background_location_label">Helymeghatározás a háttérben</string>
|
||||
<string name="permission_background_location_description">A CAPod a „háttérben történő helymeghatározást” használja az olyan funkciók engedélyezéséhez, mint az „Előugró ablak” és az „Automatikus csatlakozás”, miközben az alkalmazás zárva van. A háttérben történő helymeghatározás lehetővé teszi, hogy az alkalmazás Bluetooth Low Energy adatokat fogadjon miközben be van zárva. Ez az alkalmazás NEM használ Bluetooth adatokat az Ön tartózkodási helyének meghatározásához.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Akkumulátor optimalizálás letiltása</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Az akkumulátor optimalizálás megakadályozza, hogy ez az alkalmazás megbízhatóan fogadja a Bluetooth-adatokat, miközben az alkalmazás a háttérben működik.</string>
|
||||
<string name="permission_required_title">A következő engedély szükséges:</string>
|
||||
<string name="permission_system_alert_window_label">Rendszer figyelmeztető ablak</string>
|
||||
<string name="permission_system_alert_window_description">Engedélyezze a CAPod számára, hogy más alkalmazások fölé kerüljön, hogy lehetővé tegye a „Előugró ablak” funkciót.</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">Kis fogyasztású</string>
|
||||
<string name="settings_scanner_mode_lowpower_label">Takarékos</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Kiegyensúlyozott</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Alacsony késleltetés</string>
|
||||
<string name="settings_monitor_mode_manual_label">Amikor az alkalmazás nyitva van</string>
|
||||
<string name="settings_monitor_mode_manual_label">Amikor az alkalmazás meg van nyitva</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Amikor az eszköz csatlakoztatva van</string>
|
||||
<string name="settings_monitor_mode_always_label">Mindig</string>
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Amikor látható</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Tok Nyitva</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">Fülhallgató</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Nyitott toknál</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">Viseléskor</string>
|
||||
<string name="pods_dual_left_label">Bal hangszóró</string>
|
||||
<string name="pods_dual_right_label">Jobb hangszóró</string>
|
||||
<string name="pods_case_label">Tok</string>
|
||||
<string name="pods_case_status_open_label">Nyitott</string>
|
||||
<string name="pods_case_status_closed_label">Zárt</string>
|
||||
<string name="pods_connection_state_disconnected_label">Nincs eszközhöz csatlakoztatva</string>
|
||||
<string name="pods_connection_state_idle_label">Eszközhöz csatlakozik, de tétlen</string>
|
||||
<string name="pods_connection_state_music_label">Zenei mód</string>
|
||||
<string name="pods_case_status_open_label">Nyitva</string>
|
||||
<string name="pods_case_status_closed_label">Zárva</string>
|
||||
<string name="pods_connection_state_disconnected_label">Nincs eszköz csatlakoztatva</string>
|
||||
<string name="pods_connection_state_idle_label">Eszközhöz csatlakozva, de az tétlen</string>
|
||||
<string name="pods_connection_state_music_label">Zene üzemmód</string>
|
||||
<string name="pods_connection_state_call_label">Hívás üzemmód</string>
|
||||
<string name="pods_connection_state_ringing_label">Hívás</string>
|
||||
<string name="pods_connection_state_ringing_label">Csengés</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Hívás vége</string>
|
||||
<string name="pods_connection_state_unknown_label">Ismeretlen kapcsolati állapot</string>
|
||||
<string name="pods_unknown_raw_data_label">Rossz adat</string>
|
||||
<string name="pods_unknown_raw_data_label">Nyers adat</string>
|
||||
<string name="pods_unknown_label">Ismeretlen eszköz</string>
|
||||
<string name="pods_unknown_contact_dev">Ez egy ismeretlen eszköz, de hasonló üzenetformátumot használ. Adjunk hozzá támogatást, vedd fel velem a kapcsolatot :)</string>
|
||||
<string name="pods_unknown_contact_dev">Ez egy ismeretlen eszköz, de hasonló üzenetformátumot használ. Vegye fel velem a kapcsolatot, hogy támogatni tudjam :)</string>
|
||||
<string name="pods_none_label_short">Nincs eszköz</string>
|
||||
<string name="pods_charging_label">Töltés</string>
|
||||
<string name="pods_inear_label">Fülhalgató</string>
|
||||
<string name="pods_charging_label">Töltődik</string>
|
||||
<string name="pods_inear_label">Viselve</string>
|
||||
<string name="pods_microphone_label">Mikrofon</string>
|
||||
<string name="pods_yours">A tied</string>
|
||||
<string name="headset_being_worn_label">Használt</string>
|
||||
<string name="pods_yours">Saját</string>
|
||||
<string name="headset_being_worn_label">Viselve</string>
|
||||
<string name="headset_not_being_worn_label">Nincs viselve</string>
|
||||
<string name="pods_case_unknown_state">Ismeretlen állapot</string>
|
||||
<string name="last_seen_x">Utoljára volt látható %s</string>
|
||||
<string name="first_seen_x">Először volt látható: %s</string>
|
||||
<string name="last_seen_x">Utoljára %s volt látható</string>
|
||||
<string name="first_seen_x">Először %s volt látható</string>
|
||||
<string name="permission_post_notifications_label">Értesítések megjelenítése</string>
|
||||
<string name="permission_post_notifications_description">"Engedélyezze a CAPod számára, hogy értesítéseket jelenítsen meg az AirPodsról, pl. aktuális állapotot csatlakozás közben."</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,2 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
|
||||
<string name="general_value_not_available_label">Հ/Չ</string>
|
||||
<string name="general_error_label">Սխալ</string>
|
||||
<string name="general_grant_permission_action">Տրամադրել թույլտվություն</string>
|
||||
|
||||
<string name="overview_nomaindevice_label">Հիմնական սարք չկա</string>
|
||||
<string name="overview_nomaindevice_description">Հայտնաբերված բոլոր սարքերը քիչ հավանական է, որ ձերն են։ Միացրեք և միացրեք ձեր սարքը կամ կարգավորեք կարգավորումները։</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth-ն անջատված է</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth-ն անջատված է, միացրեք այն ;)</string>
|
||||
|
||||
<string name="permission_bluetooth_connect_label">Bluetooth միացում</string>
|
||||
<string name="permission_bluetooth_connect_description">Այս հավելվածին անհրաժեշտ է «Bluetooth միացում» թույլտվությունը՝ զուգակցված սարքերի հետ փոխազդելու և միացումներ սկսելու համար։</string>
|
||||
<string name="permission_bluetooth_scan_label">Bluetooth սկանավորում</string>
|
||||
<string name="permission_bluetooth_scan_description">«Bluetooth սկանավորում» թույլտվությունը թույլ է տալիս այս հավելվածին հայտնաբերել և ստանալ Bluetooth տվյալներ մոտակա սարքերից, ինչպիսիք են ձեր AirPods-ները։</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Այս հավելվածին անհրաժեշտ է «Bluetooth» թույլտվությունը՝ զուգակցված Bluetooth սարքերին միանալու համար։</string>
|
||||
<string name="permission_access_fine_location_label">Մուտք դեպի ճշգրիտ գտնվելու վայր</string>
|
||||
<string name="permission_access_fine_location_description">CAPod-ն օգտագործում է «ճշգրիտ գտնվելու վայր» թույլտվությունը՝ Bluetooth Low Energy տվյալներ ստանալու համար։ Ձեր ականջակալներն օգտագործում են Bluetooth Low Energy տեխնոլոգիան՝ իրենց կարգավիճակը հեռարձակելու համար։ Այս հավելվածը ՉԻ օգտագործի Bluetooth տվյալները՝ ձեր գտնվելու վայրը որոշելու համար։</string>
|
||||
<string name="permission_background_location_label">Ֆոնային գտնվելու վայրի մուտք</string>
|
||||
<string name="permission_background_location_description">CAPod-ն օգտագործում է «ֆոնային գտնվելու վայրի մուտք»՝ հավելվածը փակված ժամանակ «Ցուցադրել թռուցիկ» և «Ինքնամիացում» նման գործառույթներն ενεργացնելու համար։ Ֆոնային գտնվելու վայրի մուտքը թույլ է տալիս հավելվածին ստանալ Bluetooth Low Energy տվյալներ, երբ այն գտնվում է ֆոնային ռեժիմում։ Այս հավելվածը ՉԻ օգտագործի Bluetooth տվյալները՝ ձեր գտնվելու վայրը որոշելու համար։</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Անջատել մարտկոցի օպտիմալացումները</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Մարտկոցի օպտիմալացումները խանգարում են այս հավելվածին հուսալիորեն ստանալ Bluetooth տվյալներ, երբ հավելվածը գտնվում է ֆոնային ռեժիմում։</string>
|
||||
<string name="permission_required_title">Պահանջվում է հետևյալ թույլտվությունը.</string>
|
||||
<string name="permission_system_alert_window_label">Համակարգի ահազանգման պատուհան</string>
|
||||
<string name="permission_system_alert_window_description">Թույլ տվեք CAPod-ին նկարել այլ հավելվածների վրա՝ «Ցուցադրել PopUp» գործառույթը հնարավոր դարձնելու համար։</string>
|
||||
|
||||
<string name="settings_scanner_mode_lowpower_label">Ցածր հզորություն</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Հավասարակշռված</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Ցածր ուշացում</string>
|
||||
|
||||
<string name="settings_monitor_mode_manual_label">Երբ հավելվածը բաց է</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Երբ սարքը միացված է</string>
|
||||
<string name="settings_monitor_mode_always_label">Միշտ</string>
|
||||
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Երբ տեսանելի է</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Պատյանը բաց է</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">Ականջում</string>
|
||||
|
||||
<string name="pods_dual_left_label">Ձախ պարկուճ</string>
|
||||
<string name="pods_dual_right_label">Աջ պարկուճ</string>
|
||||
<string name="pods_case_label">Պատյան</string>
|
||||
<string name="pods_case_status_open_label">Բաց</string>
|
||||
<string name="pods_case_status_closed_label">Փակ</string>
|
||||
<string name="pods_connection_state_disconnected_label">Սարքին միացված չէ</string>
|
||||
<string name="pods_connection_state_idle_label">Սարքին միացված է, բայց անգործուն</string>
|
||||
<string name="pods_connection_state_music_label">Երաժշտության ռեժիմում</string>
|
||||
<string name="pods_connection_state_call_label">Զանգի ռեժիմում</string>
|
||||
<string name="pods_connection_state_ringing_label">Զանգահարում է</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Անջատում է</string>
|
||||
<string name="pods_connection_state_unknown_label">Անհայտ միացման կարգավիճակ</string>
|
||||
<string name="pods_unknown_raw_data_label">Հում տվյալներ</string>
|
||||
<string name="pods_unknown_label">Անհայտ սարք</string>
|
||||
<string name="pods_unknown_contact_dev">Սա անհայտ սարք է, բայց այն օգտագործում է նմանատիپ հաղորդագրության ձևաչափ: Եկեք ավելացնենք դրա համար աջակցություն, կապվեք ինձ հետ :)</string>
|
||||
<string name="pods_none_label_short">Սարք չկա</string>
|
||||
<string name="pods_charging_label">Լիցքավորվում է</string>
|
||||
<string name="pods_inear_label">Ականջում</string>
|
||||
<string name="pods_microphone_label">Խոսափող</string>
|
||||
<string name="pods_yours">Ձերը</string>
|
||||
<string name="headset_being_worn_label">Հագած է</string>
|
||||
<string name="headset_not_being_worn_label">Հագած չէ</string>
|
||||
<string name="pods_case_unknown_state">Անհայտ կարգավիճակ</string>
|
||||
|
||||
<string name="last_seen_x">Վերջին անգամ տեսնվել է՝ %s</string>
|
||||
<string name="first_seen_x">Առաջին անգամ տեսնվել է՝ %s</string>
|
||||
<string name="permission_post_notifications_label">Ցուցադրել ծանուցումներ</string>
|
||||
<string name="permission_post_notifications_description">Թույլ տվեք CAPod-ին ցուցադրել ծանուցումներ ձեր AirPods-ի մասին, օրինակ՝ դրանց ընթացիկ կարգավիճակը միացված ժամանակ։</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,21 +3,21 @@
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
<string name="general_value_not_available_label">T/A</string>
|
||||
<string name="general_error_label">Error</string>
|
||||
<string name="general_value_not_available_label">Tidak Ada</string>
|
||||
<string name="general_error_label">Galat</string>
|
||||
<string name="general_grant_permission_action">Berikan izin</string>
|
||||
<string name="overview_nomaindevice_label">Tidak ada perangkat utama</string>
|
||||
<string name="overview_nomaindevice_description">Semua perangkat yang terdeteksi tidak mungkin menjadi milik anda. Nyalakan & sambungkan perangkat anda atau sesuaikan pengaturan.</string>
|
||||
<string name="overview_nomaindevice_description">Semua perangkat yang terdeteksi kemungkinan bukan milikmu. Nyalakan dan sambungkan perangkatmu, atau sesuaikan pengaturan.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth dinonaktifkan</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth dinonaktifkan, aktifkan terlebih dahulu ;)</string>
|
||||
<string name="permission_bluetooth_connect_label">Bluetooth tersambung</string>
|
||||
<string name="permission_bluetooth_connect_description">Aplikasi ini memerlukan izin \"Koneksi Bluetooth\" untuk berinteraksi dengan perangkat yang akan dipasangkan & memulai koneksi.</string>
|
||||
<string name="permission_bluetooth_connect_label">Koneksi Bluetooth</string>
|
||||
<string name="permission_bluetooth_connect_description">Aplikasi ini memerlukan izin \"Koneksi Bluetooth\" untuk berinteraksi dengan perangkat yang akan dipasangkan dan memulai koneksi.</string>
|
||||
<string name="permission_bluetooth_scan_label">Pemindaian Bluetooth</string>
|
||||
<string name="permission_bluetooth_scan_description">Izin \"Pemindaian Bluetooth\" memungkinkan aplikasi ini menemukan & menerima data Bluetooth dari perangkat terdekat seperti AirPods anda.</string>
|
||||
<string name="permission_bluetooth_scan_description">Izin \"Pemindaian Bluetooth\" memungkinkan aplikasi ini menemukan dan menerima data Bluetooth dari perangkat di sekitar, seperti AirPods Anda.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Aplikasi ini memerlukan izin \"Bluetooth\" untuk terhubung ke perangkat bluetooth yang ingin dipasangkan.</string>
|
||||
<string name="permission_access_fine_location_label">Akses lokasi halus</string>
|
||||
<string name="permission_access_fine_location_description">CAPod menggunakan izin \"lokasi halus\" untuk menerima data Bluetooth Hemat Energi. Headphone anda menggunakan teknologi Bluetooth Hemat Energi untuk menyiarkan statusnya. Aplikasi ini TIDAK akan menggunakan data Bluetooth untuk menentukan lokasi anda.</string>
|
||||
<string name="permission_access_fine_location_label">Akses lokasi akurat</string>
|
||||
<string name="permission_access_fine_location_description">CAPod menggunakan izin \"lokasi akurat\" untuk menerima data Bluetooth Low Energy. Headphone Anda menggunakan teknologi Bluetooth Low Energy untuk menyiarkan statusnya. Aplikasi ini TIDAK akan menggunakan data Bluetooth untuk menentukan lokasi Anda.</string>
|
||||
<string name="permission_background_location_label">Akses lokasi latar belakang</string>
|
||||
<string name="permission_background_location_description">CAPod menggunakan \"akses lokasi latar belakang\" untuk mengaktifkan fitur seperti \"Tampilkan popup\" dan \"Sambungan Otomatis\" saat aplikasi ditutup. Akses lokasi latar belakang memungkinkan aplikasi menerima data Bluetooth Hemat Energi saat berada di latar belakang. Aplikasi ini TIDAK akan menggunakan data Bluetooth untuk menentukan lokasi Anda.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Nonaktifkan pengoptimalan baterai</string>
|
||||
@@ -55,6 +55,7 @@
|
||||
<string name="pods_microphone_label">Mikrofon</string>
|
||||
<string name="pods_yours">Milikmu</string>
|
||||
<string name="headset_being_worn_label">Sedang dipakai</string>
|
||||
<string name="headset_not_being_worn_label">Tidak dipakai</string>
|
||||
<string name="pods_case_unknown_state">Status tidak diketahui</string>
|
||||
<string name="last_seen_x">Terakhir terlihat: %s</string>
|
||||
<string name="first_seen_x">Pertama kali terlihat: %s</string>
|
||||
|
||||
@@ -1,2 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"></resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
|
||||
<string name="app_name">CAPod</string>
|
||||
<string name="app_name_pro">CAPod Pro</string>
|
||||
<string name="app_name_foss">CAPod FOSS</string>
|
||||
|
||||
<string name="general_value_not_available_label">Ekki tiltækt</string>
|
||||
<string name="general_error_label">Villa</string>
|
||||
<string name="general_grant_permission_action">Veita leyfi</string>
|
||||
|
||||
<string name="overview_nomaindevice_label">Ekkert aðaltæki</string>
|
||||
<string name="overview_nomaindevice_description">Öll skynjuð tæki eru ólíklega þín. Kveiktu á og tengdu tækið þitt eða stilltu stillingarnar.</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth er óvirkt</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth er óvirkt, virkjaðu það ;)</string>
|
||||
|
||||
<string name="permission_bluetooth_connect_label">Bluetooth-tenging</string>
|
||||
<string name="permission_bluetooth_connect_description">Þetta forrit krefst \"Bluetooth-tenging\" leyfis til að hafa samskipti við pöruð tæki og hefja tengingar.</string>
|
||||
<string name="permission_bluetooth_scan_label">Bluetooth-skönnun</string>
|
||||
<string name="permission_bluetooth_scan_description">\"Bluetooth-skönnun\" leyfið gerir þessu forriti kleift að finna og taka á móti Bluetooth-gögnum frá nálægum tækjum eins og AirPods þínum.</string>
|
||||
<string name="permission_bluetooth_label">Bluetooth</string>
|
||||
<string name="permission_bluetooth_description">Þetta forrit krefst \"Bluetooth\" leyfis til að tengjast pöruðum Bluetooth-tækjum.</string>
|
||||
<string name="permission_access_fine_location_label">Aðgangur að nákvæmri staðsetningu</string>
|
||||
<string name="permission_access_fine_location_description">CAPod notar \"nákvæma staðsetningu\" leyfið til að taka á móti Bluetooth Low Energy gögnum. Heyrnartólin þín nota Bluetooth Low Energy tækni til að senda út stöðu sína. Þetta forrit mun EKKI nota Bluetooth-gögn til að ákvarða staðsetningu þína.</string>
|
||||
<string name="permission_background_location_label">Aðgangur að staðsetningu í bakgrunni</string>
|
||||
<string name="permission_background_location_description">CAPod notar \"aðgang að staðsetningu í bakgrunni\" til að virkja eiginleika eins og \"Sýna sprettiglugga\" og \"Sjálfvirk tenging\" á meðan forritið er lokað. Aðgangur að staðsetningu í bakgrunni gerir forritinu kleift að taka á móti Bluetooth Low Energy gögnum á meðan það er í bakgrunni. Þetta forrit mun EKKI nota Bluetooth-gögn til að ákvarða staðsetningu þína.</string>
|
||||
<string name="permission_ignore_battery_optimizations_label">Slökkva á rafhlöðubestun</string>
|
||||
<string name="permission_ignore_battery_optimizations_description">Rafhlöðubestun kemur í veg fyrir að þetta forrit geti áreiðanlega tekið á móti Bluetooth-gögnum á meðan forritið er í bakgrunni.</string>
|
||||
<string name="permission_required_title">Eftirfarandi leyfi er krafist:</string>
|
||||
<string name="permission_system_alert_window_label">Kerfisviðvörunargluggi</string>
|
||||
<string name="permission_system_alert_window_description">Leyfðu CAPod að teikna yfir önnur forrit til að gera eiginleikann \"Sýna PopUp\" mögulegan.</string>
|
||||
|
||||
<string name="settings_scanner_mode_lowpower_label">Lítil orka</string>
|
||||
<string name="settings_scanner_mode_balanced_label">Jafnvægi</string>
|
||||
<string name="settings_scanner_mode_lowlatency_label">Lítil biðtími</string>
|
||||
|
||||
<string name="settings_monitor_mode_manual_label">Þegar forrit er opið</string>
|
||||
<string name="settings_monitor_mode_automatic_label">Þegar tæki er tengt</string>
|
||||
<string name="settings_monitor_mode_always_label">Alltaf</string>
|
||||
|
||||
<string name="settings_reaction_autoconnect_whenseen_label">Þegar sést</string>
|
||||
<string name="settings_reaction_autoconnect_caseopen_label">Hulstur er opið</string>
|
||||
<string name="settings_reaction_autoconnect_inear_label">Í eyra</string>
|
||||
|
||||
<string name="pods_dual_left_label">Vinstri hylki</string>
|
||||
<string name="pods_dual_right_label">Hægri hylki</string>
|
||||
<string name="pods_case_label">Hulstur</string>
|
||||
<string name="pods_case_status_open_label">Opið</string>
|
||||
<string name="pods_case_status_closed_label">Lokað</string>
|
||||
<string name="pods_connection_state_disconnected_label">Ekki tengt tæki</string>
|
||||
<string name="pods_connection_state_idle_label">Tengt tæki, en aðgerðalaus</string>
|
||||
<string name="pods_connection_state_music_label">Í tónlistarham</string>
|
||||
<string name="pods_connection_state_call_label">Í símtalsham</string>
|
||||
<string name="pods_connection_state_ringing_label">Hringir</string>
|
||||
<string name="pods_connection_state_hanging_up_label">Leggur á</string>
|
||||
<string name="pods_connection_state_unknown_label">Óþekkt tengistöðu</string>
|
||||
<string name="pods_unknown_raw_data_label">Hrágögn</string>
|
||||
<string name="pods_unknown_label">Óþekkt tæki</string>
|
||||
<string name="pods_unknown_contact_dev">Þetta er óþekkt tæki, en það notar svipað skilaboðasnið. Bætum við stuðningi fyrir það, hafðu samband við mig :)</string>
|
||||
<string name="pods_none_label_short">Ekkert tæki</string>
|
||||
<string name="pods_charging_label">Hleðsla</string>
|
||||
<string name="pods_inear_label">Í eyra</string>
|
||||
<string name="pods_microphone_label">Hljóðnemi</string>
|
||||
<string name="pods_yours">Þín</string>
|
||||
<string name="headset_being_worn_label">Í notkun</string>
|
||||
<string name="headset_not_being_worn_label">Ekki í notkun</string>
|
||||
<string name="pods_case_unknown_state">Óþekkt staða</string>
|
||||
|
||||
<string name="last_seen_x">Síðast séð: %s</string>
|
||||
<string name="first_seen_x">Fyrst séð: %s</string>
|
||||
<string name="permission_post_notifications_label">Sýna tilkynningar</string>
|
||||
<string name="permission_post_notifications_description">Leyfðu CAPod að sýna tilkynningar um AirPods þín, t.d. núverandi stöðu þeirra meðan þau eru tengd.</string>
|
||||
</resources>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user