├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ ├── config.yml
│ ├── feature-request.md
│ └── question.md
└── workflows
│ └── build.yml
├── .gitignore
├── COPYING
├── Gemfile
├── Gemfile.lock
├── README.md
├── app
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ └── dev
│ │ └── patri9ck
│ │ └── a2ln
│ │ ├── app
│ │ ├── App.java
│ │ ├── AppsAdapter.java
│ │ └── AppsFragment.java
│ │ ├── log
│ │ ├── KeptLog.java
│ │ └── LogDialogBuilder.java
│ │ ├── main
│ │ ├── Application.java
│ │ └── MainActivity.java
│ │ ├── notification
│ │ ├── NotificationReceiver.java
│ │ ├── NotificationSender.java
│ │ ├── ParsedNotification.java
│ │ └── spam
│ │ │ ├── NotificationSpamHandler.java
│ │ │ └── StrippedNotification.java
│ │ ├── pairing
│ │ ├── Pairing.java
│ │ └── PairingResult.java
│ │ ├── server
│ │ ├── Destination.java
│ │ ├── DragAndDropCallback.java
│ │ ├── Server.java
│ │ ├── ServersAdapter.java
│ │ ├── ServersFragment.java
│ │ └── SwipeToDeleteCallback.java
│ │ ├── settings
│ │ └── SettingsFragment.java
│ │ └── util
│ │ ├── Storage.java
│ │ └── Util.java
│ └── res
│ ├── drawable
│ ├── ic_apps.xml
│ ├── ic_help.xml
│ ├── ic_notification.xml
│ ├── ic_pair.xml
│ ├── ic_permission.xml
│ ├── ic_qr_code.xml
│ ├── ic_servers.xml
│ └── ic_settings.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── dialog_edit_server.xml
│ ├── dialog_pair.xml
│ ├── dialog_paired.xml
│ ├── dialog_pairing.xml
│ ├── fragment_apps.xml
│ ├── fragment_servers.xml
│ ├── fragment_settings.xml
│ ├── item_app.xml
│ └── item_server.xml
│ ├── menu
│ └── menu_bottom_navigation.xml
│ ├── mipmap-anydpi-v26
│ ├── ic_launcher.xml
│ └── ic_launcher_round.xml
│ ├── mipmap-hdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-mdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── mipmap-xxxhdpi
│ ├── ic_launcher.png
│ ├── ic_launcher_foreground.png
│ └── ic_launcher_round.png
│ ├── navigation
│ └── navigation_mobile.xml
│ ├── values-de-rDE
│ ├── logs.xml
│ └── strings.xml
│ ├── values-night
│ ├── colors.xml
│ └── themes.xml
│ ├── values-ru
│ ├── logs.xml
│ └── strings.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── ic_launcher_background.xml
│ ├── logs.xml
│ ├── preferences.xml
│ ├── static.xml
│ ├── strings.xml
│ ├── styles.xml
│ └── themes.xml
├── build.gradle.kts
├── fastlane
├── Appfile
├── Fastfile
└── metadata
│ └── android
│ ├── de-DE
│ ├── changelogs
│ │ ├── 15.txt
│ │ ├── 16.txt
│ │ ├── 17.txt
│ │ ├── 18.txt
│ │ ├── 19.txt
│ │ └── 20.txt
│ ├── full_description.txt
│ ├── images
│ │ └── phoneScreenshots
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ └── 4.png
│ ├── short_description.txt
│ └── title.txt
│ ├── en-US
│ ├── changelogs
│ │ ├── 1.txt
│ │ ├── 10.txt
│ │ ├── 11.txt
│ │ ├── 12.txt
│ │ ├── 13.txt
│ │ ├── 14.txt
│ │ ├── 15.txt
│ │ ├── 16.txt
│ │ ├── 17.txt
│ │ ├── 18.txt
│ │ ├── 19.txt
│ │ ├── 2.txt
│ │ ├── 20.txt
│ │ ├── 3.txt
│ │ ├── 4.txt
│ │ ├── 5.txt
│ │ ├── 6.txt
│ │ ├── 7.txt
│ │ ├── 8.txt
│ │ └── 9.txt
│ ├── full_description.txt
│ ├── images
│ │ ├── featureGraphic.png
│ │ ├── icon.png
│ │ └── phoneScreenshots
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ └── 4.png
│ ├── short_description.txt
│ └── title.txt
│ └── ru
│ ├── full_description.txt
│ ├── short_description.txt
│ └── title.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── images
├── banner.png
├── icon-filled.png
└── icon-transparent.png
└── settings.gradle.kts
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: patri9ck
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report
3 | about: Report a bug
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | # Description
11 | A clear and concise description of what the bug is.
12 |
13 | # Reproduction
14 | Steps to reproduce the behavior.
15 |
16 | # Screenshots
17 | If applicable, add screenshots to help explain your problem.
18 |
19 | # Smartphone
20 | - Device:
21 | - Android version:
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Installation Help
4 | url: https://patri9ck.dev/a2ln/app.html
5 | about: Everything you will need to install the app
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature Request
3 | about: Request a feature
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | # Is your feature request related to a problem?
11 | A clear and concise description of what the problem is.
12 |
13 | # Description
14 | A clear and concise description of what you want to happen.
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Question
3 | about: Ask a question
4 | title: ''
5 | labels: question
6 | assignees: ''
7 |
8 | ---
9 |
10 | # Ask your question, whatever it may be
11 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build APK
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout
12 | uses: actions/checkout@v3
13 | - name: Set up JDK
14 | uses: actions/setup-java@v3
15 | with:
16 | distribution: 'temurin'
17 | java-version: '17'
18 | cache: 'gradle'
19 | - name: Build APK
20 | run: ./gradlew clean assembleDebug
21 | - name: Upload artifact
22 | uses: actions/upload-artifact@v3
23 | with:
24 | name: apk
25 | path: app/build/outputs/apk/debug/app-debug.apk
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .gradle
3 | local.properties
4 | app/build/
5 | keystore.properties
6 | fastlane/README.md
7 | fastlane/report.xml
8 | fastlane/metadata/android/**/screenshots.html
9 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gem "fastlane"
4 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | CFPropertyList (3.0.5)
5 | rexml
6 | addressable (2.8.0)
7 | public_suffix (>= 2.0.2, < 5.0)
8 | artifactory (3.0.15)
9 | atomos (0.1.3)
10 | aws-eventstream (1.2.0)
11 | aws-partitions (1.602.0)
12 | aws-sdk-core (3.131.2)
13 | aws-eventstream (~> 1, >= 1.0.2)
14 | aws-partitions (~> 1, >= 1.525.0)
15 | aws-sigv4 (~> 1.1)
16 | jmespath (~> 1, >= 1.6.1)
17 | aws-sdk-kms (1.57.0)
18 | aws-sdk-core (~> 3, >= 3.127.0)
19 | aws-sigv4 (~> 1.1)
20 | aws-sdk-s3 (1.114.0)
21 | aws-sdk-core (~> 3, >= 3.127.0)
22 | aws-sdk-kms (~> 1)
23 | aws-sigv4 (~> 1.4)
24 | aws-sigv4 (1.5.0)
25 | aws-eventstream (~> 1, >= 1.0.2)
26 | babosa (1.0.4)
27 | claide (1.1.0)
28 | colored (1.2)
29 | colored2 (3.1.2)
30 | commander (4.6.0)
31 | highline (~> 2.0.0)
32 | declarative (0.0.20)
33 | digest-crc (0.6.4)
34 | rake (>= 12.0.0, < 14.0.0)
35 | domain_name (0.5.20190701)
36 | unf (>= 0.0.5, < 1.0.0)
37 | dotenv (2.7.6)
38 | emoji_regex (3.2.3)
39 | excon (0.92.3)
40 | faraday (1.10.0)
41 | faraday-em_http (~> 1.0)
42 | faraday-em_synchrony (~> 1.0)
43 | faraday-excon (~> 1.1)
44 | faraday-httpclient (~> 1.0)
45 | faraday-multipart (~> 1.0)
46 | faraday-net_http (~> 1.0)
47 | faraday-net_http_persistent (~> 1.0)
48 | faraday-patron (~> 1.0)
49 | faraday-rack (~> 1.0)
50 | faraday-retry (~> 1.0)
51 | ruby2_keywords (>= 0.0.4)
52 | faraday-cookie_jar (0.0.7)
53 | faraday (>= 0.8.0)
54 | http-cookie (~> 1.0.0)
55 | faraday-em_http (1.0.0)
56 | faraday-em_synchrony (1.0.0)
57 | faraday-excon (1.1.0)
58 | faraday-httpclient (1.0.1)
59 | faraday-multipart (1.0.4)
60 | multipart-post (~> 2)
61 | faraday-net_http (1.0.1)
62 | faraday-net_http_persistent (1.2.0)
63 | faraday-patron (1.0.0)
64 | faraday-rack (1.0.0)
65 | faraday-retry (1.0.3)
66 | faraday_middleware (1.2.0)
67 | faraday (~> 1.0)
68 | fastimage (2.2.6)
69 | fastlane (2.207.0)
70 | CFPropertyList (>= 2.3, < 4.0.0)
71 | addressable (>= 2.8, < 3.0.0)
72 | artifactory (~> 3.0)
73 | aws-sdk-s3 (~> 1.0)
74 | babosa (>= 1.0.3, < 2.0.0)
75 | bundler (>= 1.12.0, < 3.0.0)
76 | colored
77 | commander (~> 4.6)
78 | dotenv (>= 2.1.1, < 3.0.0)
79 | emoji_regex (>= 0.1, < 4.0)
80 | excon (>= 0.71.0, < 1.0.0)
81 | faraday (~> 1.0)
82 | faraday-cookie_jar (~> 0.0.6)
83 | faraday_middleware (~> 1.0)
84 | fastimage (>= 2.1.0, < 3.0.0)
85 | gh_inspector (>= 1.1.2, < 2.0.0)
86 | google-apis-androidpublisher_v3 (~> 0.3)
87 | google-apis-playcustomapp_v1 (~> 0.1)
88 | google-cloud-storage (~> 1.31)
89 | highline (~> 2.0)
90 | json (< 3.0.0)
91 | jwt (>= 2.1.0, < 3)
92 | mini_magick (>= 4.9.4, < 5.0.0)
93 | multipart-post (~> 2.0.0)
94 | naturally (~> 2.2)
95 | optparse (~> 0.1.1)
96 | plist (>= 3.1.0, < 4.0.0)
97 | rubyzip (>= 2.0.0, < 3.0.0)
98 | security (= 0.1.3)
99 | simctl (~> 1.6.3)
100 | terminal-notifier (>= 2.0.0, < 3.0.0)
101 | terminal-table (>= 1.4.5, < 2.0.0)
102 | tty-screen (>= 0.6.3, < 1.0.0)
103 | tty-spinner (>= 0.8.0, < 1.0.0)
104 | word_wrap (~> 1.0.0)
105 | xcodeproj (>= 1.13.0, < 2.0.0)
106 | xcpretty (~> 0.3.0)
107 | xcpretty-travis-formatter (>= 0.0.3)
108 | gh_inspector (1.1.3)
109 | google-apis-androidpublisher_v3 (0.23.0)
110 | google-apis-core (>= 0.6, < 2.a)
111 | google-apis-core (0.7.0)
112 | addressable (~> 2.5, >= 2.5.1)
113 | googleauth (>= 0.16.2, < 2.a)
114 | httpclient (>= 2.8.1, < 3.a)
115 | mini_mime (~> 1.0)
116 | representable (~> 3.0)
117 | retriable (>= 2.0, < 4.a)
118 | rexml
119 | webrick
120 | google-apis-iamcredentials_v1 (0.12.0)
121 | google-apis-core (>= 0.6, < 2.a)
122 | google-apis-playcustomapp_v1 (0.9.0)
123 | google-apis-core (>= 0.6, < 2.a)
124 | google-apis-storage_v1 (0.16.0)
125 | google-apis-core (>= 0.6, < 2.a)
126 | google-cloud-core (1.6.0)
127 | google-cloud-env (~> 1.0)
128 | google-cloud-errors (~> 1.0)
129 | google-cloud-env (1.6.0)
130 | faraday (>= 0.17.3, < 3.0)
131 | google-cloud-errors (1.2.0)
132 | google-cloud-storage (1.37.0)
133 | addressable (~> 2.8)
134 | digest-crc (~> 0.4)
135 | google-apis-iamcredentials_v1 (~> 0.1)
136 | google-apis-storage_v1 (~> 0.1)
137 | google-cloud-core (~> 1.6)
138 | googleauth (>= 0.16.2, < 2.a)
139 | mini_mime (~> 1.0)
140 | googleauth (1.2.0)
141 | faraday (>= 0.17.3, < 3.a)
142 | jwt (>= 1.4, < 3.0)
143 | memoist (~> 0.16)
144 | multi_json (~> 1.11)
145 | os (>= 0.9, < 2.0)
146 | signet (>= 0.16, < 2.a)
147 | highline (2.0.3)
148 | http-cookie (1.0.5)
149 | domain_name (~> 0.5)
150 | httpclient (2.8.3)
151 | jmespath (1.6.1)
152 | json (2.6.2)
153 | jwt (2.4.1)
154 | memoist (0.16.2)
155 | mini_magick (4.11.0)
156 | mini_mime (1.1.2)
157 | multi_json (1.15.0)
158 | multipart-post (2.0.0)
159 | nanaimo (0.3.0)
160 | naturally (2.2.1)
161 | optparse (0.1.1)
162 | os (1.1.4)
163 | plist (3.6.0)
164 | public_suffix (4.0.7)
165 | rake (13.0.6)
166 | representable (3.2.0)
167 | declarative (< 0.1.0)
168 | trailblazer-option (>= 0.1.1, < 0.2.0)
169 | uber (< 0.2.0)
170 | retriable (3.1.2)
171 | rexml (3.2.5)
172 | rouge (2.0.7)
173 | ruby2_keywords (0.0.5)
174 | rubyzip (2.3.2)
175 | security (0.1.3)
176 | signet (0.17.0)
177 | addressable (~> 2.8)
178 | faraday (>= 0.17.5, < 3.a)
179 | jwt (>= 1.5, < 3.0)
180 | multi_json (~> 1.10)
181 | simctl (1.6.8)
182 | CFPropertyList
183 | naturally
184 | terminal-notifier (2.0.0)
185 | terminal-table (1.8.0)
186 | unicode-display_width (~> 1.1, >= 1.1.1)
187 | trailblazer-option (0.1.2)
188 | tty-cursor (0.7.1)
189 | tty-screen (0.8.1)
190 | tty-spinner (0.9.3)
191 | tty-cursor (~> 0.7)
192 | uber (0.1.0)
193 | unf (0.1.4)
194 | unf_ext
195 | unf_ext (0.0.8.2)
196 | unicode-display_width (1.8.0)
197 | webrick (1.7.0)
198 | word_wrap (1.0.0)
199 | xcodeproj (1.22.0)
200 | CFPropertyList (>= 2.3.3, < 4.0)
201 | atomos (~> 0.1.3)
202 | claide (>= 1.0.2, < 2.0)
203 | colored2 (~> 3.1)
204 | nanaimo (~> 0.3.0)
205 | rexml (~> 3.2.4)
206 | xcpretty (0.3.0)
207 | rouge (~> 2.0.7)
208 | xcpretty-travis-formatter (1.0.1)
209 | xcpretty (~> 0.2, >= 0.0.7)
210 |
211 | PLATFORMS
212 | x86_64-linux
213 |
214 | DEPENDENCIES
215 | fastlane
216 |
217 | BUNDLED WITH
218 | 2.3.17
219 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android 2 Linux Notifications App
2 | **Android 2 Linux Notifications** (**A2LN**) is a way to display your Android phone notifications on your Linux computer. This repository contains the app part of A2LN.
3 |
4 | More information can be found at [patri9ck.dev/a2ln](https://patri9ck.dev/a2ln/).
5 |
6 | ## License
7 | A2LN is licensed under the [GNU General Public License Version 3 or later](COPYING).
8 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | import java.io.FileInputStream
19 | import java.util.Properties
20 |
21 | plugins {
22 | id("com.android.application")
23 | }
24 |
25 | android {
26 | namespace = "dev.patri9ck.a2ln"
27 |
28 | compileSdk = 34
29 |
30 | defaultConfig {
31 | applicationId = "dev.patri9ck.a2ln"
32 | minSdk = 27
33 | targetSdk = 34
34 |
35 | versionCode = 20
36 | versionName = "1.4.0"
37 | }
38 |
39 | compileOptions {
40 | sourceCompatibility(JavaVersion.VERSION_17)
41 | targetCompatibility(JavaVersion.VERSION_17)
42 | }
43 |
44 | buildFeatures {
45 | viewBinding = true
46 | buildConfig = true
47 | }
48 |
49 | val keystoreFile = rootProject.file("keystore.properties")
50 |
51 | if (keystoreFile.exists()) {
52 | val keystoreProperties = Properties()
53 |
54 | keystoreProperties.load(FileInputStream(keystoreFile))
55 |
56 | signingConfigs {
57 | create("release") {
58 | storeFile = file(keystoreProperties.getProperty("storeFile"))
59 | storePassword = keystoreProperties.getProperty("storePassword")
60 |
61 | keyAlias = keystoreProperties.getProperty("keyAlias")
62 | keyPassword = keystoreProperties.getProperty("keyPassword")
63 | }
64 | }
65 |
66 | buildTypes {
67 | getByName("release") {
68 | signingConfig = signingConfigs.getByName("release")
69 | }
70 | }
71 | }
72 | }
73 |
74 | dependencies {
75 | implementation("com.google.code.gson:gson:2.10")
76 | implementation("com.google.android.material:material:1.11.0")
77 | implementation("org.zeromq:jeromq:0.5.2")
78 | implementation("androidx.appcompat:appcompat:1.6.1")
79 | implementation("androidx.navigation:navigation-fragment:2.7.7")
80 | implementation("androidx.navigation:navigation-ui:2.7.7")
81 | implementation("com.journeyapps:zxing-android-embedded:4.3.0")
82 | implementation("me.xdrop:fuzzywuzzy:1.4.0")
83 | implementation("net.jodah:expiringmap:0.5.11")
84 | }
85 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
18 |
20 |
21 |
22 |
25 |
26 |
27 |
28 |
37 |
42 |
43 |
44 |
45 |
46 |
47 |
51 |
52 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/app/App.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.app;
19 |
20 | import android.graphics.drawable.Drawable;
21 |
22 | public class App {
23 |
24 | private final String name;
25 | private final String packageName;
26 | private final Drawable icon;
27 | private boolean enabled;
28 |
29 | public App(String name, String packageName, Drawable icon, boolean enabled) {
30 | this.name = name;
31 | this.packageName = packageName;
32 | this.icon = icon;
33 | this.enabled = enabled;
34 | }
35 |
36 | public String getName() {
37 | return name;
38 | }
39 |
40 | public String getPackageName() {
41 | return packageName;
42 | }
43 |
44 | public Drawable getIcon() {
45 | return icon;
46 | }
47 |
48 | public boolean isEnabled() {
49 | return enabled;
50 | }
51 |
52 | public void setEnabled(boolean enabled) {
53 | this.enabled = enabled;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/app/AppsAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.app;
19 |
20 | import android.view.LayoutInflater;
21 | import android.view.ViewGroup;
22 | import android.widget.CheckBox;
23 | import android.widget.ImageView;
24 | import android.widget.TextView;
25 |
26 | import androidx.annotation.NonNull;
27 | import androidx.recyclerview.widget.RecyclerView;
28 |
29 | import java.util.List;
30 |
31 | import dev.patri9ck.a2ln.databinding.ItemAppBinding;
32 | import dev.patri9ck.a2ln.util.Storage;
33 |
34 | public class AppsAdapter extends RecyclerView.Adapter {
35 |
36 | private final List disabledApps;
37 | private final Storage storage;
38 | private final List apps;
39 |
40 | public AppsAdapter(List disabledApps, List apps, Storage storage) {
41 | this.disabledApps = disabledApps;
42 | this.apps = apps;
43 | this.storage = storage;
44 | }
45 |
46 | @NonNull
47 | @Override
48 | public AppViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
49 | return new AppViewHolder(ItemAppBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
50 | }
51 |
52 | @Override
53 | public void onBindViewHolder(AppViewHolder holder, int position) {
54 | App app = apps.get(position);
55 |
56 | holder.appCheckBox.setOnCheckedChangeListener((appCheckBoxView, isChecked) -> {
57 | app.setEnabled(isChecked);
58 |
59 | String packageName = app.getPackageName();
60 |
61 | if (isChecked) {
62 | disabledApps.remove(packageName);
63 | } else if (!disabledApps.contains(packageName)) {
64 | disabledApps.add(packageName);
65 | }
66 |
67 | storage.saveDisabledApps(disabledApps);
68 | });
69 |
70 | holder.appCheckBox.setChecked(app.isEnabled());
71 | holder.nameTextView.setText(app.getName());
72 | holder.iconImageView.setImageDrawable(app.getIcon());
73 | }
74 |
75 | @Override
76 | public int getItemCount() {
77 | return apps.size();
78 | }
79 |
80 | protected static class AppViewHolder extends RecyclerView.ViewHolder {
81 |
82 | private final TextView nameTextView;
83 | private final CheckBox appCheckBox;
84 | private final ImageView iconImageView;
85 |
86 | public AppViewHolder(ItemAppBinding itemAppBinding) {
87 | super(itemAppBinding.getRoot());
88 |
89 | nameTextView = itemAppBinding.nameTextView;
90 | appCheckBox = itemAppBinding.appCheckBox;
91 | iconImageView = itemAppBinding.iconImageView;
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/app/AppsFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.app;
19 |
20 | import android.content.Context;
21 | import android.content.pm.PackageManager;
22 | import android.os.Bundle;
23 | import android.view.LayoutInflater;
24 | import android.view.View;
25 | import android.view.ViewGroup;
26 |
27 | import androidx.annotation.NonNull;
28 | import androidx.fragment.app.Fragment;
29 | import androidx.recyclerview.widget.LinearLayoutManager;
30 |
31 | import java.util.Comparator;
32 | import java.util.List;
33 | import java.util.concurrent.CompletableFuture;
34 | import java.util.stream.Collectors;
35 |
36 | import dev.patri9ck.a2ln.R;
37 | import dev.patri9ck.a2ln.databinding.FragmentAppsBinding;
38 | import dev.patri9ck.a2ln.util.Storage;
39 |
40 | public class AppsFragment extends Fragment {
41 |
42 | private Storage storage;
43 | private List disabledApps;
44 | private FragmentAppsBinding fragmentAppsBinding;
45 |
46 | @Override
47 | public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
48 | storage = new Storage(requireContext(), requireContext().getSharedPreferences(getString(R.string.preferences), Context.MODE_PRIVATE));
49 | disabledApps = storage.loadDisabledApps();
50 | fragmentAppsBinding = FragmentAppsBinding.inflate(inflater, container, false);
51 |
52 | loadAppsRecyclerView();
53 |
54 | return fragmentAppsBinding.getRoot();
55 | }
56 |
57 | @Override
58 | public void onDestroyView() {
59 | super.onDestroyView();
60 |
61 | fragmentAppsBinding = null;
62 | }
63 |
64 | private void loadAppsRecyclerView() {
65 | fragmentAppsBinding.loadingProgressIndicator.setVisibility(View.VISIBLE);
66 |
67 | PackageManager packageManager = requireContext().getPackageManager();
68 |
69 | CompletableFuture.supplyAsync(() -> packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
70 | .stream()
71 | .filter(applicationInfo -> packageManager.getLaunchIntentForPackage(applicationInfo.packageName) != null)
72 | .map(applicationInfo -> new App(applicationInfo.loadLabel(packageManager).toString(), applicationInfo.packageName, applicationInfo.loadIcon(packageManager), !disabledApps.contains(applicationInfo.packageName)))
73 | .sorted(Comparator.comparing(App::isEnabled).thenComparing(App::getName))
74 | .collect(Collectors.toList()))
75 | .thenAccept(apps -> requireActivity().runOnUiThread(() -> {
76 | fragmentAppsBinding.loadingProgressIndicator.setVisibility(View.INVISIBLE);
77 |
78 | fragmentAppsBinding.appsRecyclerView.setAdapter(new AppsAdapter(disabledApps, apps, storage));
79 | fragmentAppsBinding.appsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
80 | }));
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/log/KeptLog.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.log;
19 |
20 | import android.content.Context;
21 |
22 | import java.text.DateFormat;
23 | import java.util.ArrayList;
24 | import java.util.Date;
25 | import java.util.List;
26 |
27 | public class KeptLog {
28 |
29 | private final List messages = new ArrayList<>();
30 |
31 | private final Context context;
32 |
33 | public KeptLog(Context context) {
34 | this.context = context;
35 | }
36 |
37 | public void log(int id, Object... arguments) {
38 | String message = context.getString(id, arguments);
39 |
40 | messages.add(DateFormat.getTimeInstance().format(new Date()) + ": " + message);
41 | }
42 |
43 | public String format() {
44 | return String.join("\n", messages);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/log/LogDialogBuilder.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.log;
19 |
20 | import android.view.LayoutInflater;
21 |
22 | import com.google.android.material.dialog.MaterialAlertDialogBuilder;
23 |
24 | import dev.patri9ck.a2ln.R;
25 |
26 | public class LogDialogBuilder extends MaterialAlertDialogBuilder {
27 |
28 | public LogDialogBuilder(String log, LayoutInflater layoutInflater) {
29 | super(layoutInflater.getContext(), R.style.Dialog);
30 |
31 | setTitle(R.string.log_dialog_title);
32 | setMessage(log);
33 | setNegativeButton(R.string.cancel, null);
34 | }
35 |
36 | public LogDialogBuilder(KeptLog keptLog, LayoutInflater layoutInflater) {
37 | this(keptLog.format(), layoutInflater);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/main/Application.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2024 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.main;
19 |
20 | import com.google.android.material.color.DynamicColors;
21 |
22 | public class Application extends android.app.Application {
23 | @Override
24 | public void onCreate() {
25 | super.onCreate();
26 | DynamicColors.applyToActivitiesIfAvailable(this);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/main/MainActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.main;
19 |
20 | import android.app.NotificationChannel;
21 | import android.app.NotificationManager;
22 | import android.content.Context;
23 | import android.content.Intent;
24 | import android.content.SharedPreferences;
25 | import android.os.Bundle;
26 | import android.provider.Settings;
27 |
28 | import androidx.appcompat.app.AppCompatActivity;
29 | import androidx.core.app.NotificationManagerCompat;
30 | import androidx.core.view.WindowCompat;
31 | import androidx.navigation.NavController;
32 | import androidx.navigation.fragment.NavHostFragment;
33 | import androidx.navigation.ui.AppBarConfiguration;
34 | import androidx.navigation.ui.NavigationUI;
35 |
36 | import com.google.android.material.dialog.MaterialAlertDialogBuilder;
37 |
38 | import org.zeromq.ZCert;
39 |
40 | import dev.patri9ck.a2ln.R;
41 | import dev.patri9ck.a2ln.databinding.ActivityMainBinding;
42 |
43 | public class MainActivity extends AppCompatActivity {
44 |
45 | private ActivityMainBinding activityMainBinding;
46 |
47 | @Override
48 | protected void onCreate(Bundle savedInstanceState) {
49 | super.onCreate(savedInstanceState);
50 |
51 | activityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());
52 |
53 | setSupportActionBar(activityMainBinding.topAppBar);
54 | setContentView(activityMainBinding.getRoot());
55 |
56 | generateKeys();
57 | loadNavigationBar();
58 | requestPermission();
59 | createNotificationChannel();
60 |
61 | WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
62 | }
63 |
64 | private void generateKeys() {
65 | SharedPreferences sharedPreferences = getSharedPreferences(getString(R.string.preferences), Context.MODE_PRIVATE);
66 |
67 | if (sharedPreferences.contains(getString(R.string.preferences_public_key)) && sharedPreferences.contains(getString(R.string.preferences_secret_key))) {
68 | return;
69 | }
70 |
71 | ZCert zCert = new ZCert();
72 |
73 | sharedPreferences.edit()
74 | .putString(getString(R.string.preferences_public_key), zCert.getPublicKeyAsZ85())
75 | .putString(getString(R.string.preferences_secret_key), zCert.getSecretKeyAsZ85())
76 | .apply();
77 | }
78 |
79 | private void loadNavigationBar() {
80 | NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.main_fragment_container_view);
81 |
82 | if (navHostFragment == null) {
83 | return;
84 | }
85 |
86 | NavController navController = navHostFragment.getNavController();
87 |
88 | NavigationUI.setupActionBarWithNavController(this, navController, new AppBarConfiguration.Builder(R.id.navigation_servers, R.id.navigation_apps, R.id.navigation_settings)
89 | .build());
90 | NavigationUI.setupWithNavController(activityMainBinding.mainBottomNavigationView, navController);
91 | }
92 |
93 | private void requestPermission() {
94 | if (NotificationManagerCompat.getEnabledListenerPackages(this).contains(getPackageName())) {
95 | return;
96 | }
97 | new MaterialAlertDialogBuilder(this, R.style.Dialog)
98 | .setTitle(R.string.permission_request_dialog_title)
99 | .setMessage(R.string.permission_request_dialog_listener_information)
100 | .setPositiveButton(R.string.grant, (requestPermissionDialog, which) -> startActivity(new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS)))
101 | .setNegativeButton(R.string.cancel, null)
102 | .show();
103 | }
104 |
105 | private void createNotificationChannel() {
106 | NotificationChannel notificationChannel = new NotificationChannel(getString(R.string.channel_id), getString(R.string.channel_name), NotificationManager.IMPORTANCE_NONE);
107 |
108 | notificationChannel.setDescription(getString(R.string.channel_description));
109 |
110 | NotificationManagerCompat.from(this).createNotificationChannel(notificationChannel);
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/notification/NotificationReceiver.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.notification;
19 |
20 | import android.content.Context;
21 | import android.content.SharedPreferences;
22 | import android.content.pm.PackageManager;
23 | import android.hardware.display.DisplayManager;
24 | import android.service.notification.NotificationListenerService;
25 | import android.service.notification.StatusBarNotification;
26 | import android.util.Log;
27 | import android.view.Display;
28 |
29 | import java.util.List;
30 | import java.util.Optional;
31 | import java.util.concurrent.CompletableFuture;
32 |
33 | import dev.patri9ck.a2ln.R;
34 | import dev.patri9ck.a2ln.notification.spam.NotificationSpamHandler;
35 | import dev.patri9ck.a2ln.util.Storage;
36 |
37 | public class NotificationReceiver extends NotificationListenerService {
38 |
39 | private static final String TAG = "A2LNNR";
40 |
41 | private boolean initialized;
42 |
43 | private SharedPreferences sharedPreferences;
44 | private Storage storage;
45 |
46 | private NotificationSender notificationSender;
47 |
48 | private NotificationSpamHandler notificationSpamHandler;
49 |
50 | private List disabledApps;
51 |
52 | private boolean display;
53 | private boolean noApp;
54 |
55 | private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, key) -> {
56 | if (getString(R.string.preferences_servers).equals(key)) {
57 | notificationSender.setServers(storage.loadServers());
58 |
59 | return;
60 | }
61 |
62 | if (getString(R.string.preferences_similarity).equals(key)) {
63 | notificationSpamHandler.setSimilarity(storage.loadSimilarityOrDefault());
64 |
65 | return;
66 | }
67 |
68 | if (getString(R.string.preferences_duration).equals(key)) {
69 | notificationSpamHandler.setDuration(storage.loadDurationOrDefault());
70 |
71 | return;
72 | }
73 |
74 | if (getString(R.string.preferences_disabled_apps).equals(key)) {
75 | disabledApps = storage.loadDisabledApps();
76 |
77 | return;
78 | }
79 |
80 | if (getString(R.string.preferences_display).equals(key)) {
81 | display = storage.loadDisplay();
82 |
83 | return;
84 | }
85 |
86 | if (getString(R.string.preferences_no_app).equals(key)) {
87 | noApp = storage.loadNoApp();
88 | }
89 | };
90 |
91 | @Override
92 | public synchronized void onNotificationPosted(StatusBarNotification statusBarNotification) {
93 | if (!initialized) {
94 | return;
95 | }
96 |
97 | PackageManager packageManager = getPackageManager();
98 |
99 | String packageName = statusBarNotification.getPackageName();
100 |
101 | Log.v(TAG, "Notification posted (" + packageName + ")");
102 |
103 | boolean test = getPackageName().equals(packageName);
104 |
105 | if (test) {
106 | Log.v(TAG, "Test notification detected");
107 | } else {
108 | if (!noApp && packageManager.getLaunchIntentForPackage(packageName) == null) {
109 | Log.v(TAG, "Not from an actual app");
110 |
111 | return;
112 | }
113 |
114 | if (disabledApps.contains(packageName)) {
115 | Log.v(TAG, "App is disabled");
116 |
117 | return;
118 | }
119 |
120 | if (display && isDisplayEnabled()) {
121 | Log.v(TAG, "Display is on");
122 |
123 | return;
124 | }
125 | }
126 |
127 | Optional optionalParsedNotification = ParsedNotification.parseNotification(statusBarNotification, this);
128 |
129 | if (!optionalParsedNotification.isPresent()) {
130 | Log.v(TAG, "Notification cannot be parsed");
131 |
132 | return;
133 | }
134 |
135 | ParsedNotification parsedNotification = optionalParsedNotification.get();
136 |
137 | if (notificationSpamHandler.isSpammed(parsedNotification, test)) {
138 | Log.v(TAG, "Notification is spammed");
139 |
140 | return;
141 | }
142 |
143 | CompletableFuture.supplyAsync(() -> notificationSender.sendParsedNotification(parsedNotification)).thenAccept(keptLog -> {
144 | if (test) {
145 | storage.saveLog(keptLog.format());
146 | }
147 | });
148 |
149 | Log.v(TAG, "Notification given to NotificationSender");
150 | }
151 |
152 | @Override
153 | public void onListenerConnected() {
154 | Log.v(TAG, "NotificationReceiver connected");
155 |
156 | initialize();
157 | }
158 |
159 | @Override
160 | public void onListenerDisconnected() {
161 | Log.v(TAG, "NotificationReceiver disconnected");
162 |
163 | uninitialize();
164 | }
165 |
166 | private synchronized void initialize() {
167 | if (initialized) {
168 | return;
169 | }
170 |
171 | sharedPreferences = getSharedPreferences(getString(R.string.preferences), Context.MODE_PRIVATE);
172 | storage = new Storage(this, sharedPreferences);
173 |
174 | NotificationSender.fromStorage(this, storage).ifPresent(notificationSender -> {
175 | this.notificationSender = notificationSender;
176 |
177 | notificationSpamHandler = new NotificationSpamHandler(storage.loadSimilarityOrDefault(), storage.loadDurationOrDefault());
178 | disabledApps = storage.loadDisabledApps();
179 | display = storage.loadDisplay();
180 |
181 | sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
182 |
183 | initialized = true;
184 | });
185 | }
186 |
187 | private synchronized void uninitialize() {
188 | if (!initialized) {
189 | return;
190 | }
191 |
192 | sharedPreferences.unregisterOnSharedPreferenceChangeListener(listener);
193 | }
194 |
195 | private boolean isDisplayEnabled() {
196 | for (Display display : ((DisplayManager) getSystemService(Context.DISPLAY_SERVICE)).getDisplays()) {
197 | if (display.getState() == Display.STATE_ON) {
198 | return true;
199 | }
200 | }
201 |
202 | return false;
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/notification/NotificationSender.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.notification;
19 |
20 | import android.content.Context;
21 | import android.util.Log;
22 |
23 | import org.zeromq.SocketType;
24 | import org.zeromq.ZContext;
25 | import org.zeromq.ZMQ;
26 | import org.zeromq.ZMsg;
27 |
28 | import java.util.List;
29 | import java.util.Optional;
30 | import java.util.concurrent.CompletableFuture;
31 | import java.util.concurrent.CountDownLatch;
32 | import java.util.concurrent.TimeUnit;
33 | import java.util.stream.Collectors;
34 |
35 | import dev.patri9ck.a2ln.R;
36 | import dev.patri9ck.a2ln.log.KeptLog;
37 | import dev.patri9ck.a2ln.server.Server;
38 | import dev.patri9ck.a2ln.util.Storage;
39 | import zmq.util.Z85;
40 |
41 | public class NotificationSender {
42 |
43 | private static final String TAG = "A2LNNS";
44 |
45 | private static final int TIMEOUT_SECONDS = 3;
46 |
47 | private final Context context;
48 | private final byte[] publicKey;
49 | private final byte[] secretKey;
50 | private List servers;
51 |
52 | public NotificationSender(Context context, byte[] publicKey, byte[] secretKey, List servers) {
53 | this.context = context;
54 | this.publicKey = publicKey;
55 | this.secretKey = secretKey;
56 | this.servers = filterServers(servers);
57 | }
58 |
59 | public static Optional fromStorage(Context context, Storage storage) {
60 | String rawPublicKey = storage.loadRawPublicKey().orElse(null);
61 |
62 | if (rawPublicKey == null) {
63 | Log.e(TAG, "Own public key does not exist");
64 |
65 | return Optional.empty();
66 | }
67 |
68 | String rawSecretKey = storage.loadRawSecretKey().orElse(null);
69 |
70 | if (rawSecretKey == null) {
71 | Log.e(TAG, "Own secret key does not exist");
72 |
73 | return Optional.empty();
74 | }
75 |
76 | byte[] publicKey = Z85.decode(rawPublicKey);
77 |
78 | if (publicKey == null) {
79 | Log.e(TAG, "Cannot decode own public key");
80 |
81 | return Optional.empty();
82 | }
83 |
84 | byte[] secretKey = Z85.decode(rawSecretKey);
85 |
86 | if (secretKey == null) {
87 | Log.e(TAG, "Cannot decode own secret key");
88 |
89 | return Optional.empty();
90 | }
91 |
92 | return Optional.of(new NotificationSender(context, publicKey, secretKey, storage.loadServers()));
93 | }
94 |
95 | public void setServers(List servers) {
96 | this.servers = filterServers(servers);
97 | }
98 |
99 | public KeptLog sendParsedNotification(ParsedNotification parsedNotification) {
100 | KeptLog keptLog = new KeptLog(context);
101 |
102 | if (servers.isEmpty()) {
103 | keptLog.log(R.string.log_notification_no_servers);
104 |
105 | return keptLog;
106 | }
107 |
108 | keptLog.log(R.string.log_notification_trying);
109 |
110 | ZMsg zMsg = new ZMsg();
111 |
112 | zMsg.add(parsedNotification.getAppName());
113 | zMsg.add(parsedNotification.getTitle());
114 | zMsg.add(parsedNotification.getText());
115 |
116 | parsedNotification.getIcon().ifPresent(zMsg::add);
117 |
118 | CountDownLatch countDownLatch = new CountDownLatch(servers.size());
119 |
120 | try (ZContext zContext = new ZContext()) {
121 | servers.forEach(server -> CompletableFuture.runAsync(() -> {
122 | try (ZMQ.Socket client = zContext.createSocket(SocketType.PUSH)) {
123 | client.setSendTimeOut(TIMEOUT_SECONDS * 1000);
124 | client.setImmediate(false);
125 | client.setCurvePublicKey(publicKey);
126 | client.setCurveSecretKey(secretKey);
127 | client.setCurveServerKey(server.getPublicKey());
128 |
129 | String address = server.getAddress();
130 |
131 | if (!client.connect("tcp://" + address)) {
132 | keptLog.log(R.string.log_failed_connection, address);
133 | } else if (!zMsg.send(client, false)) {
134 | keptLog.log(R.string.log_notification_failed_sending, address);
135 | } else {
136 | keptLog.log(R.string.log_notification_success, address);
137 | }
138 |
139 | countDownLatch.countDown();
140 | }
141 | }));
142 | } finally {
143 | try {
144 | if (!countDownLatch.await(TIMEOUT_SECONDS + 1, TimeUnit.SECONDS)) {
145 | keptLog.log(R.string.log_notification_timed_out);
146 | }
147 | } catch (InterruptedException ignored) {
148 | // Ignored
149 | }
150 |
151 | zMsg.destroy();
152 | }
153 |
154 | return keptLog;
155 | }
156 |
157 | private List filterServers(List servers) {
158 | return servers.stream().filter(Server::isEnabled).collect(Collectors.toList());
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/notification/ParsedNotification.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.notification;
19 |
20 | import android.app.Notification;
21 | import android.content.Context;
22 | import android.graphics.Bitmap;
23 | import android.graphics.drawable.BitmapDrawable;
24 | import android.graphics.drawable.Drawable;
25 | import android.graphics.drawable.Icon;
26 | import android.service.notification.StatusBarNotification;
27 | import android.util.Log;
28 |
29 | import java.io.ByteArrayOutputStream;
30 | import java.io.IOException;
31 | import java.util.Optional;
32 |
33 | import dev.patri9ck.a2ln.util.Util;
34 |
35 | public class ParsedNotification {
36 |
37 | private static final String TAG = "A2LN";
38 |
39 | private final String appName;
40 | private final String title;
41 | private final String text;
42 | private final byte[] icon;
43 |
44 | public ParsedNotification(String appName, String title, String text, byte[] icon) {
45 | this.appName = appName;
46 | this.title = title;
47 | this.text = text;
48 | this.icon = icon;
49 | }
50 |
51 | public ParsedNotification(String appName, String title, String text) {
52 | this(appName, title, text, null);
53 | }
54 |
55 | public static Optional parseNotification(StatusBarNotification statusBarNotification, Context context) {
56 | Notification notification = statusBarNotification.getNotification();
57 |
58 | String title = notification.extras.getString(Notification.EXTRA_TITLE);
59 | String text = notification.extras.getString(Notification.EXTRA_TEXT);
60 |
61 | if (title == null || text == null) {
62 | return Optional.empty();
63 | }
64 |
65 | String appName = Util.getAppName(context.getPackageManager(), statusBarNotification.getPackageName()).orElse("");
66 |
67 | Icon largeIcon = notification.getLargeIcon();
68 |
69 | if (largeIcon != null) {
70 | try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {
71 | Drawable drawable = largeIcon.loadDrawable(context);
72 |
73 | if (drawable instanceof BitmapDrawable) {
74 | ((BitmapDrawable) drawable).getBitmap().compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
75 |
76 | return Optional.of(new ParsedNotification(appName, title, text, byteArrayOutputStream.toByteArray()));
77 | }
78 | } catch (IOException exception) {
79 | Log.e(TAG, "Failed to convert picture to bytes", exception);
80 | }
81 | }
82 |
83 | return Optional.of(new ParsedNotification(appName, title, text));
84 | }
85 |
86 | public String getAppName() {
87 | return appName;
88 | }
89 |
90 | public String getTitle() {
91 | return title;
92 | }
93 |
94 | public String getText() {
95 | return text;
96 | }
97 |
98 | public Optional getIcon() {
99 | return Optional.ofNullable(icon);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/notification/spam/NotificationSpamHandler.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.notification.spam;
19 |
20 | import net.jodah.expiringmap.ExpirationPolicy;
21 | import net.jodah.expiringmap.ExpiringMap;
22 |
23 | import java.util.concurrent.TimeUnit;
24 |
25 | import dev.patri9ck.a2ln.notification.ParsedNotification;
26 | import dev.patri9ck.a2ln.util.Storage;
27 | import dev.patri9ck.a2ln.util.Util;
28 |
29 | public class NotificationSpamHandler {
30 |
31 | private final ExpiringMap strippedNotifications = ExpiringMap.builder()
32 | .variableExpiration()
33 | .expirationPolicy(ExpirationPolicy.CREATED)
34 | .build();
35 |
36 | private float similarity;
37 | private int duration;
38 |
39 | public NotificationSpamHandler(float similarity, int duration) {
40 | this.similarity = similarity;
41 | this.duration = duration;
42 | }
43 |
44 | public void setSimilarity(float similarity) {
45 | this.similarity = similarity;
46 | }
47 |
48 | public void setDuration(int duration) {
49 | this.duration = duration;
50 | }
51 |
52 | public boolean isSpammed(ParsedNotification parsedNotification, boolean simple) {
53 | StrippedNotification strippedNotification = new StrippedNotification(parsedNotification);
54 |
55 | if (strippedNotifications.containsKey(strippedNotification)) {
56 | return true;
57 | }
58 |
59 | if (!simple && similarity < Storage.DEFAULT_SIMILARITY) {
60 | for (StrippedNotification spammedStrippedNotification : strippedNotifications.keySet()) {
61 | if (spammedStrippedNotification.getAppName().equals(strippedNotification.getAppName())
62 | && spammedStrippedNotification.getTitle().equals(strippedNotification.getTitle())
63 | && Util.getSimilarity(spammedStrippedNotification.getText(), strippedNotification.getText()) >= similarity) {
64 | strippedNotifications.put(spammedStrippedNotification, new Object(), duration, TimeUnit.SECONDS);
65 |
66 | return true;
67 | }
68 | }
69 | }
70 |
71 | strippedNotifications.put(strippedNotification, new Object(), simple ? Storage.DEFAULT_DURATION : duration, TimeUnit.SECONDS);
72 |
73 | return false;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/notification/spam/StrippedNotification.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package dev.patri9ck.a2ln.notification.spam;
20 |
21 | import java.util.Objects;
22 |
23 | import dev.patri9ck.a2ln.notification.ParsedNotification;
24 |
25 | public class StrippedNotification {
26 |
27 | private final String appName;
28 | private final String title;
29 | private final String text;
30 |
31 | public StrippedNotification(ParsedNotification parsedNotification) {
32 | this.appName = parsedNotification.getAppName();
33 | this.title = parsedNotification.getTitle();
34 | this.text = parsedNotification.getText();
35 | }
36 |
37 | public String getAppName() {
38 | return appName;
39 | }
40 |
41 | public String getTitle() {
42 | return title;
43 | }
44 |
45 | public String getText() {
46 | return text;
47 | }
48 |
49 | @Override
50 | public boolean equals(Object object) {
51 | if (this == object) {
52 | return true;
53 | }
54 |
55 | if (object == null || getClass() != object.getClass()) {
56 | return false;
57 | }
58 |
59 | StrippedNotification strippedNotification = (StrippedNotification) object;
60 |
61 | return appName.equals(strippedNotification.appName) && title.equals(strippedNotification.title) && text.equals(strippedNotification.text);
62 | }
63 |
64 | @Override
65 | public int hashCode() {
66 | return Objects.hash(appName, title, text);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/pairing/Pairing.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.pairing;
19 |
20 | import android.content.Context;
21 |
22 | import org.zeromq.SocketType;
23 | import org.zeromq.ZContext;
24 | import org.zeromq.ZMQ;
25 | import org.zeromq.ZMsg;
26 |
27 | import dev.patri9ck.a2ln.R;
28 | import dev.patri9ck.a2ln.log.KeptLog;
29 | import dev.patri9ck.a2ln.server.Destination;
30 |
31 | public class Pairing {
32 |
33 | private static final int TIMEOUT_SECONDS = 20;
34 |
35 | private final Context context;
36 | private final Destination destination;
37 | private final String ip;
38 | private final String rawPublicKey;
39 |
40 | public Pairing(Context context, Destination destination, String ip, String rawPublicKey) {
41 | this.context = context;
42 | this.destination = destination;
43 | this.ip = ip;
44 | this.rawPublicKey = rawPublicKey;
45 | }
46 |
47 | public PairingResult pair() {
48 | KeptLog keptLog = new KeptLog(context);
49 |
50 | String address = destination.getAddress();
51 |
52 | keptLog.log(R.string.log_pairing_trying, address);
53 |
54 | try (ZContext zContext = new ZContext(); ZMQ.Socket client = zContext.createSocket(SocketType.REQ)) {
55 | client.setSendTimeOut(TIMEOUT_SECONDS * 1000);
56 | client.setReceiveTimeOut(TIMEOUT_SECONDS * 1000);
57 | client.setImmediate(false);
58 |
59 | if (!client.connect("tcp://" + address)) {
60 | keptLog.log(R.string.log_failed_connection, address);
61 |
62 | return new PairingResult(keptLog);
63 | }
64 |
65 | ZMsg zMsg = new ZMsg();
66 |
67 | zMsg.add(ip);
68 | zMsg.add(rawPublicKey);
69 |
70 | if (!zMsg.send(client)) {
71 | keptLog.log(R.string.log_pairing_failed_sending, address);
72 |
73 | return new PairingResult(keptLog);
74 | }
75 |
76 | byte[] publicKey = client.recv();
77 |
78 | if (publicKey == null) {
79 | keptLog.log(R.string.log_pairing_failed_receiving, address);
80 |
81 | return new PairingResult(keptLog);
82 | }
83 |
84 | return new PairingResult(keptLog, publicKey);
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/pairing/PairingResult.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.pairing;
19 |
20 | import java.util.Optional;
21 |
22 | import dev.patri9ck.a2ln.log.KeptLog;
23 |
24 | public class PairingResult {
25 |
26 | private final KeptLog keptLog;
27 | private final byte[] publicKey;
28 |
29 | public PairingResult(KeptLog keptLog) {
30 | this(keptLog, null);
31 | }
32 |
33 | public PairingResult(KeptLog keptLog, byte[] publicKey) {
34 | this.keptLog = keptLog;
35 | this.publicKey = publicKey;
36 | }
37 |
38 | public KeptLog getKeptLog() {
39 | return keptLog;
40 | }
41 |
42 | public Optional getPublicKey() {
43 | return Optional.ofNullable(publicKey);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/server/Destination.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package dev.patri9ck.a2ln.server;
20 |
21 | public class Destination {
22 |
23 | private String ip;
24 | private int port;
25 |
26 | public Destination() {
27 | // Gson
28 | }
29 |
30 | public Destination(String ip, int port) {
31 | this.ip = ip;
32 | this.port = port;
33 | }
34 |
35 | public String getIp() {
36 | return ip;
37 | }
38 |
39 | public void setIp(String ip) {
40 | this.ip = ip;
41 | }
42 |
43 | public int getPort() {
44 | return port;
45 | }
46 |
47 | public void setPort(int port) {
48 | this.port = port;
49 | }
50 |
51 | public String getAddress() {
52 | return ip + ":" + port;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/server/DragAndDropCallback.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.server;
19 |
20 | import androidx.annotation.NonNull;
21 | import androidx.recyclerview.widget.ItemTouchHelper;
22 | import androidx.recyclerview.widget.RecyclerView;
23 |
24 | import java.util.Collections;
25 | import java.util.List;
26 |
27 | import dev.patri9ck.a2ln.util.Storage;
28 |
29 | public class DragAndDropCallback extends ItemTouchHelper.SimpleCallback {
30 |
31 | private final List servers;
32 | private final Storage storage;
33 | private final ServersAdapter serversAdapter;
34 |
35 | public DragAndDropCallback(List servers, Storage storage, ServersAdapter serversAdapter) {
36 | super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
37 |
38 | this.servers = servers;
39 | this.storage = storage;
40 | this.serversAdapter = serversAdapter;
41 | }
42 |
43 | @Override
44 | public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
45 | int from = viewHolder.getAdapterPosition();
46 | int to = target.getAdapterPosition();
47 |
48 | Collections.swap(servers, from, to);
49 |
50 | serversAdapter.notifyItemMoved(from, to);
51 |
52 | storage.saveServers(servers);
53 |
54 | return true;
55 | }
56 |
57 | @Override
58 | public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
59 | // Ignored
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/server/Server.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.server;
19 |
20 | import java.util.Optional;
21 |
22 | public class Server extends Destination {
23 |
24 | private byte[] publicKey;
25 | private String alias;
26 | private boolean enabled;
27 |
28 | public Server() {
29 | // Gson
30 | }
31 |
32 | public Server(String ip, int port, byte[] publicKey, String alias, boolean enabled) {
33 | super(ip, port);
34 |
35 | this.publicKey = publicKey;
36 | this.alias = alias;
37 | this.enabled = enabled;
38 | }
39 |
40 | public Server(String ip, int port, byte[] publicKey) {
41 | this(ip, port, publicKey, null, true);
42 | }
43 |
44 | public byte[] getPublicKey() {
45 | return publicKey;
46 | }
47 |
48 | public Optional getAlias() {
49 | return Optional.ofNullable(alias);
50 | }
51 |
52 | public void setAlias(String alias) {
53 | this.alias = alias;
54 | }
55 |
56 | public boolean isEnabled() {
57 | return enabled;
58 | }
59 |
60 | public void setEnabled(boolean enabled) {
61 | this.enabled = enabled;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/server/ServersAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.server;
19 |
20 | import android.annotation.SuppressLint;
21 | import android.view.LayoutInflater;
22 | import android.view.ViewGroup;
23 | import android.widget.CheckBox;
24 | import android.widget.TextView;
25 |
26 | import androidx.annotation.NonNull;
27 | import androidx.recyclerview.widget.RecyclerView;
28 |
29 | import com.google.android.material.dialog.MaterialAlertDialogBuilder;
30 |
31 | import java.util.List;
32 |
33 | import dev.patri9ck.a2ln.R;
34 | import dev.patri9ck.a2ln.databinding.DialogEditServerBinding;
35 | import dev.patri9ck.a2ln.databinding.ItemServerBinding;
36 | import dev.patri9ck.a2ln.util.Storage;
37 |
38 | public class ServersAdapter extends RecyclerView.Adapter {
39 |
40 | private final ServersFragment serversFragment;
41 | private final Storage storage;
42 | private final List servers;
43 |
44 | public ServersAdapter(ServersFragment serversFragment, Storage storage, List servers) {
45 | this.serversFragment = serversFragment;
46 | this.storage = storage;
47 | this.servers = servers;
48 | }
49 |
50 | @NonNull
51 | @Override
52 | public ServerViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
53 | return new ServerViewHolder(ItemServerBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
54 | }
55 |
56 | @SuppressLint("SetTextI18n")
57 | @Override
58 | public void onBindViewHolder(ServerViewHolder holder, int position) {
59 | Server server = servers.get(position);
60 |
61 | holder.addressTextView.setText(server.getAlias().orElse(server.getAddress()));
62 |
63 | holder.addressTextView.setOnClickListener(view -> {
64 | DialogEditServerBinding dialogEditServerBinding = DialogEditServerBinding.inflate(serversFragment.getLayoutInflater());
65 |
66 | dialogEditServerBinding.editServerIpEditText.setText(server.getIp());
67 | dialogEditServerBinding.editServerPortEditText.setText(Integer.toString(server.getPort()));
68 |
69 | server.getAlias().ifPresent(dialogEditServerBinding.editServerAliasEditText::setText);
70 |
71 | new MaterialAlertDialogBuilder(serversFragment.requireContext(), R.style.Dialog)
72 | .setTitle(R.string.edit_server_dialog_title)
73 | .setView(dialogEditServerBinding.getRoot())
74 | .setPositiveButton(R.string.apply, (editPortDialog, which) -> {
75 | String ip = dialogEditServerBinding.editServerIpEditText.getText().toString();
76 |
77 | serversFragment.validate(ip, dialogEditServerBinding.editServerPortEditText.getText().toString(), !ip.equals(server.getIp())).ifPresent(destination -> {
78 | String alias = dialogEditServerBinding.editServerAliasEditText.getText().toString();
79 |
80 | server.setAlias(alias.trim().isEmpty() ? null : alias);
81 | server.setIp(destination.getIp());
82 | server.setPort(destination.getPort());
83 |
84 | notifyItemChanged(position);
85 |
86 | storage.saveServers(servers);
87 | });
88 | })
89 | .setNegativeButton(R.string.cancel, null)
90 | .show();
91 | });
92 |
93 | holder.serverCheckBox.setOnCheckedChangeListener((serverCheckBoxView, isChecked) -> {
94 | server.setEnabled(isChecked);
95 |
96 | storage.saveServers(servers);
97 | });
98 |
99 | holder.serverCheckBox.setChecked(server.isEnabled());
100 | }
101 |
102 | @Override
103 | public int getItemCount() {
104 | return servers.size();
105 | }
106 |
107 | protected static class ServerViewHolder extends RecyclerView.ViewHolder {
108 |
109 | private final TextView addressTextView;
110 | private final CheckBox serverCheckBox;
111 |
112 | public ServerViewHolder(ItemServerBinding itemServerBinding) {
113 | super(itemServerBinding.getRoot());
114 |
115 | addressTextView = itemServerBinding.addressTextView;
116 | serverCheckBox = itemServerBinding.serverCheckBox;
117 | }
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/server/SwipeToDeleteCallback.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.server;
19 |
20 | import android.view.View;
21 |
22 | import androidx.annotation.NonNull;
23 | import androidx.recyclerview.widget.ItemTouchHelper;
24 | import androidx.recyclerview.widget.RecyclerView;
25 |
26 | import com.google.android.material.snackbar.Snackbar;
27 |
28 | import java.util.List;
29 |
30 | import dev.patri9ck.a2ln.R;
31 | import dev.patri9ck.a2ln.util.Storage;
32 |
33 | public class SwipeToDeleteCallback extends ItemTouchHelper.SimpleCallback {
34 |
35 | private final View rootView;
36 | private final Storage storage;
37 | private final List servers;
38 | private final ServersAdapter serversAdapter;
39 |
40 | public SwipeToDeleteCallback(View rootView, Storage storage, List servers, ServersAdapter serversAdapter) {
41 | super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
42 |
43 | this.rootView = rootView;
44 | this.storage = storage;
45 | this.servers = servers;
46 | this.serversAdapter = serversAdapter;
47 | }
48 |
49 | @Override
50 | public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
51 | return false;
52 | }
53 |
54 | @Override
55 | public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
56 | int position = viewHolder.getAdapterPosition();
57 |
58 | Server server = servers.remove(position);
59 |
60 | serversAdapter.notifyItemRemoved(position);
61 |
62 | storage.saveServers(servers);
63 |
64 | Snackbar.make(rootView, R.string.removed_server, Snackbar.LENGTH_LONG)
65 | .setAction(R.string.removed_server_undo, view -> {
66 | servers.add(position, server);
67 | serversAdapter.notifyItemInserted(position);
68 |
69 | storage.saveServers(servers);
70 | }).show();
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/util/Storage.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 |
19 | package dev.patri9ck.a2ln.util;
20 |
21 | import android.content.Context;
22 | import android.content.SharedPreferences;
23 |
24 | import java.util.List;
25 | import java.util.Optional;
26 |
27 | import dev.patri9ck.a2ln.R;
28 | import dev.patri9ck.a2ln.server.Server;
29 |
30 | public class Storage {
31 |
32 | public static final float DEFAULT_SIMILARITY = 1F;
33 | public static final int DEFAULT_DURATION = 1;
34 |
35 | public static final int DEFAULT_PORT = 23045;
36 |
37 | private final Context context;
38 | private final SharedPreferences sharedPreferences;
39 |
40 | public Storage(Context context, SharedPreferences sharedPreferences) {
41 | this.context = context;
42 | this.sharedPreferences = sharedPreferences;
43 | }
44 |
45 | public List loadServers() {
46 | return Util.fromJson(sharedPreferences.getString(context.getString(R.string.preferences_servers), null), Server.class);
47 | }
48 |
49 | public void saveServers(List servers) {
50 | sharedPreferences.edit().putString(context.getString(R.string.preferences_servers), Util.toJson(servers)).apply();
51 | }
52 |
53 | public Optional loadRawPublicKey() {
54 | return Optional.ofNullable(sharedPreferences.getString(context.getString(R.string.preferences_public_key), null));
55 | }
56 |
57 | public Optional loadRawSecretKey() {
58 | return Optional.ofNullable(sharedPreferences.getString(context.getString(R.string.preferences_secret_key), null));
59 | }
60 |
61 | public List loadDisabledApps() {
62 | return Util.fromJson(sharedPreferences.getString(context.getString(R.string.preferences_disabled_apps), null), String.class);
63 | }
64 |
65 | public void saveDisabledApps(List disabledApps) {
66 | sharedPreferences.edit().putString(context.getString(R.string.preferences_disabled_apps), Util.toJson(disabledApps)).apply();
67 | }
68 |
69 | public Optional loadSimilarity() {
70 | float similarity = sharedPreferences.getFloat(context.getString(R.string.preferences_similarity), Float.MIN_VALUE);
71 |
72 | return Optional.ofNullable(similarity == Float.MIN_VALUE ? null : similarity);
73 | }
74 |
75 | public float loadSimilarityOrDefault() {
76 | return loadSimilarity().orElse(DEFAULT_SIMILARITY);
77 | }
78 |
79 | public void saveSimilarity(float similarity) {
80 | sharedPreferences.edit().putFloat(context.getString(R.string.preferences_similarity), similarity).apply();
81 | }
82 |
83 | public void removeSimilarity() {
84 | sharedPreferences.edit().remove(context.getString(R.string.preferences_similarity)).apply();
85 | }
86 |
87 | public Optional loadDuration() {
88 | int duration = sharedPreferences.getInt(context.getString(R.string.preferences_duration), Integer.MIN_VALUE);
89 |
90 | return Optional.ofNullable(duration == Integer.MIN_VALUE ? null : duration);
91 | }
92 |
93 | public int loadDurationOrDefault() {
94 | return loadDuration().orElse(DEFAULT_DURATION);
95 | }
96 |
97 | public void saveDuration(int duration) {
98 | sharedPreferences.edit().putInt(context.getString(R.string.preferences_duration), duration).apply();
99 | }
100 |
101 | public void removeDuration() {
102 | sharedPreferences.edit().remove(context.getString(R.string.preferences_duration)).apply();
103 | }
104 |
105 | public boolean loadDisplay() {
106 | return sharedPreferences.getBoolean(context.getString(R.string.preferences_display), false);
107 | }
108 |
109 | public void saveDisplay(boolean display) {
110 | sharedPreferences.edit().putBoolean(context.getString(R.string.preferences_display), display).apply();
111 | }
112 |
113 | public boolean loadNoApp() {
114 | return sharedPreferences.getBoolean(context.getString(R.string.preferences_no_app), false);
115 | }
116 |
117 | public void saveNoApp(boolean noApp) {
118 | sharedPreferences.edit().putBoolean(context.getString(R.string.preferences_no_app), noApp).apply();
119 | }
120 |
121 | public Optional loadLog() {
122 | return Optional.ofNullable(sharedPreferences.getString(context.getString(R.string.preferences_log), null));
123 | }
124 |
125 | public void saveLog(String log) {
126 | sharedPreferences.edit().putString(context.getString(R.string.preferences_log), log).apply();
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/app/src/main/java/dev/patri9ck/a2ln/util/Util.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | package dev.patri9ck.a2ln.util;
19 |
20 | import android.content.pm.PackageManager;
21 |
22 | import com.google.gson.Gson;
23 | import com.google.gson.reflect.TypeToken;
24 |
25 | import java.util.ArrayList;
26 | import java.util.List;
27 | import java.util.Optional;
28 |
29 | import me.xdrop.fuzzywuzzy.FuzzySearch;
30 |
31 | public class Util {
32 |
33 | private static final int MINIMUM_PORT = 1;
34 | private static final int MAXIMUM_PORT = 65535;
35 |
36 | private static final Gson GSON = new Gson();
37 |
38 | private Util() {
39 | }
40 |
41 | public static String toJson(List> raw) {
42 | return GSON.toJson(raw);
43 | }
44 |
45 | public static List fromJson(String json, Class type) {
46 | if (json == null) {
47 | return new ArrayList<>();
48 | }
49 |
50 | return GSON.fromJson(json, TypeToken.getParameterized(ArrayList.class, type).getType());
51 | }
52 |
53 | public static Optional parseInteger(String rawInteger) {
54 | try {
55 | return Optional.of(Integer.parseInt(rawInteger));
56 | } catch (NumberFormatException ignored) {
57 | return Optional.empty();
58 | }
59 | }
60 |
61 | public static Optional parsePort(String rawPort) {
62 | return parseInteger(rawPort).filter(port -> port >= MINIMUM_PORT && port <= MAXIMUM_PORT);
63 | }
64 |
65 | public static Optional getAppName(PackageManager packageManager, String packageName) {
66 | try {
67 | return Optional.of((String) packageManager.getApplicationLabel(packageManager.getApplicationInfo(packageName, 0)));
68 | } catch (PackageManager.NameNotFoundException ignored) {
69 | return Optional.empty();
70 | }
71 | }
72 |
73 | public static float getSimilarity(String first, String second) {
74 | return (float) FuzzySearch.ratio(first, second) / 100F;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_apps.xml:
--------------------------------------------------------------------------------
1 |
18 |
23 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_help.xml:
--------------------------------------------------------------------------------
1 |
18 |
23 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_notification.xml:
--------------------------------------------------------------------------------
1 |
18 |
23 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_pair.xml:
--------------------------------------------------------------------------------
1 |
18 |
23 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_permission.xml:
--------------------------------------------------------------------------------
1 |
18 |
23 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_qr_code.xml:
--------------------------------------------------------------------------------
1 |
18 |
23 |
26 |
29 |
32 |
35 |
38 |
41 |
44 |
47 |
50 |
53 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_servers.xml:
--------------------------------------------------------------------------------
1 |
18 |
23 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings.xml:
--------------------------------------------------------------------------------
1 |
18 |
24 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
18 |
24 |
25 |
29 |
30 |
35 |
36 |
37 |
38 |
46 |
47 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_edit_server.xml:
--------------------------------------------------------------------------------
1 |
18 |
23 |
24 |
28 |
29 |
38 |
39 |
40 |
41 |
45 |
46 |
54 |
55 |
56 |
57 |
61 |
62 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_pair.xml:
--------------------------------------------------------------------------------
1 |
18 |
22 |
23 |
28 |
29 |
37 |
38 |
43 |
44 |
51 |
52 |
53 |
54 |
59 |
60 |
67 |
68 |
69 |
70 |
75 |
76 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_paired.xml:
--------------------------------------------------------------------------------
1 |
18 |
24 |
25 |
30 |
31 |
38 |
39 |
44 |
45 |
51 |
52 |
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_pairing.xml:
--------------------------------------------------------------------------------
1 |
18 |
24 |
25 |
32 |
33 |
38 |
39 |
46 |
47 |
52 |
53 |
59 |
60 |
65 |
66 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_apps.xml:
--------------------------------------------------------------------------------
1 |
18 |
23 |
24 |
31 |
32 |
36 |
37 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_servers.xml:
--------------------------------------------------------------------------------
1 |
18 |
23 |
24 |
28 |
29 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_settings.xml:
--------------------------------------------------------------------------------
1 |
18 |
26 |
27 |
31 |
32 |
36 |
37 |
45 |
46 |
54 |
55 |
61 |
62 |
67 |
68 |
75 |
76 |
77 |
78 |
83 |
84 |
91 |
92 |
93 |
94 |
99 |
100 |
108 |
109 |
114 |
115 |
116 |
121 |
122 |
130 |
131 |
136 |
137 |
138 |
144 |
145 |
152 |
153 |
160 |
161 |
162 |
163 |
164 |
171 |
172 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_app.xml:
--------------------------------------------------------------------------------
1 |
18 |
24 |
25 |
33 |
34 |
44 |
45 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_server.xml:
--------------------------------------------------------------------------------
1 |
18 |
24 |
25 |
36 |
37 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_bottom_navigation.xml:
--------------------------------------------------------------------------------
1 |
18 |
32 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/navigation/navigation_mobile.xml:
--------------------------------------------------------------------------------
1 |
18 |
23 |
28 |
33 |
38 |
39 |
--------------------------------------------------------------------------------
/app/src/main/res/values-de-rDE/logs.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 | Verbindung zu %s konnte nicht hergestellt werden.
20 | Versuche, mit %s zu koppeln…
21 | Eigene IP und öffentlicher Schlüssel konnten nicht zu %s gesendet werden.
22 | öffentlicher Schlüssel konnte nicht von %s empfangen werden.
23 | Keine Server angegeben.
24 | Versuche Benachrichtigung zu Servern zu senden.
25 | Benachrichtigung konnte nicht zu %s gesendet werden.
26 | Benachrichtigung erfolgreich zu %s gesendet.
27 | Zeit wurde überschreitet.
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/values-de-rDE/strings.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 | IP
20 | Port
21 | Alias
22 | Öffentlicher Schlüssel
23 | Koppeln
24 | Anwenden
25 | Abbrechen
26 | Erlauben
27 | Bereits gekoppelt
28 | Ungültige IP
29 | Ungültiger Port
30 | Ungültiger QR-Code
31 | Kopplung fehlgeschlagen
32 | Server entfernt
33 | Wiederherstellen
34 | Berechtigung
35 | QR-Code scannen
36 | Benachrichtigung
37 | Hilfe
38 | Log ansehen
39 | Test-Benachrichtigung
40 | Eine Test-Benachrichtigung wurde zu jedem laufenden Server gesendet.
41 | Server
42 | Apps
43 | Einstellungen
44 | Neuen Server koppeln
45 | Alternativ können Sie hier die IP und den Port des Servers manuell eintragen.
46 | Koppeln…
47 | Akzeptieren Sie die Kopplungs-Anfrage beim Server und verifizieren Sie die IP und den öffentlichen Schlüssel.
48 | Erfolgreich gekoppelt
49 | Verifizieren Sie die IP und den öffentlichen Schlüssel des Servers.
50 | Berechtigung benötigt
51 | Es wird eine Berechtigung benötigt, um Benachrichtigungen abfangen zu können.
52 | Es wird eine Berechtigung benötigt, um eine Test-Benachrichtigung zu senden, welche anschließend abgefangen und zum Server gesendet wird.
53 | Es konnte keine Test-Benachrichtung abgefangen werden.
54 | Server bearbeiten
55 | Log
56 | Eine Benachrichtigung kann für eine bestimmte Zeit blockiert werden, falls sie eine gewisse Ähnlichkeit mit einer vorherigen überschreitet.
57 | Ähnlichkeit in %
58 | Dauer in Sekunden
59 | Keine Benachrichtigungen senden, falls das Display angeschaltet ist
60 | Benachrichtigungen senden, welche nicht von Apps kommen. Diese können nicht deaktiviert werden.
61 | Test-Benachrichtigungen
62 | Wird für Test-Benachrichtigungen genutzt.
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 | #a1a1a1
20 | #80D5D4
21 | #003737
22 | #004F4F
23 | #9CF1F0
24 | #B0CCCB
25 | #1B3534
26 | #324B4B
27 | #CCE8E7
28 | #B3C8E8
29 | #1C314B
30 | #334863
31 | #D3E4FF
32 | #FFB4AB
33 | #690005
34 | #93000A
35 | #FFDAD6
36 | #0E1514
37 | #DDE4E3
38 | #0E1514
39 | #DDE4E3
40 | #3F4948
41 | #BEC9C8
42 | #889392
43 | #3F4948
44 | #000000
45 | #DDE4E3
46 | #2B3231
47 | #006A6A
48 | #9CF1F0
49 | #002020
50 | #80D5D4
51 | #004F4F
52 | #CCE8E7
53 | #051F1F
54 | #B0CCCB
55 | #324B4B
56 | #D3E4FF
57 | #041C35
58 | #B3C8E8
59 | #334863
60 | #0E1514
61 | #343A3A
62 | #090F0F
63 | #161D1D
64 | #1A2121
65 | #252B2B
66 | #2F3636
67 | #84D9D8
68 | #001A1A
69 | #479E9D
70 | #000000
71 | #B5D0CF
72 | #001A1A
73 | #7B9695
74 | #000000
75 | #B7CCED
76 | #00172F
77 | #7D92B1
78 | #000000
79 | #FFBAB1
80 | #370001
81 | #FF5449
82 | #000000
83 | #0E1514
84 | #DDE4E3
85 | #0E1514
86 | #F6FCFB
87 | #3F4948
88 | #C2CDCC
89 | #9BA5A4
90 | #7B8585
91 | #000000
92 | #DDE4E3
93 | #252B2B
94 | #005151
95 | #9CF1F0
96 | #001414
97 | #80D5D4
98 | #003D3D
99 | #CCE8E7
100 | #001414
101 | #B0CCCB
102 | #213A3A
103 | #D3E4FF
104 | #001226
105 | #B3C8E8
106 | #223751
107 | #0E1514
108 | #343A3A
109 | #090F0F
110 | #161D1D
111 | #1A2121
112 | #252B2B
113 | #2F3636
114 | #EAFFFE
115 | #000000
116 | #84D9D8
117 | #000000
118 | #EAFFFE
119 | #000000
120 | #B5D0CF
121 | #000000
122 | #FAFAFF
123 | #000000
124 | #B7CCED
125 | #000000
126 | #FFF9F9
127 | #000000
128 | #FFBAB1
129 | #000000
130 | #0E1514
131 | #DDE4E3
132 | #0E1514
133 | #FFFFFF
134 | #3F4948
135 | #F3FDFC
136 | #C2CDCC
137 | #C2CDCC
138 | #000000
139 | #DDE4E3
140 | #000000
141 | #003030
142 | #A0F5F5
143 | #000000
144 | #84D9D8
145 | #001A1A
146 | #D0ECEB
147 | #000000
148 | #B5D0CF
149 | #001A1A
150 | #DAE8FF
151 | #000000
152 | #B7CCED
153 | #00172F
154 | #0E1514
155 | #343A3A
156 | #090F0F
157 | #161D1D
158 | #1A2121
159 | #252B2B
160 | #2F3636
161 |
162 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ru/logs.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 | Не удалось подключиться к %s.
20 | Пытаюсь соединиться с %s…
21 | Не удалось отправить собственный IP-адрес и публичный ключ%s.
22 | Не удалось получить публичный ключ от %s.
23 | Серверы не указаны.
24 | Попытка отправить уведомление на серверы.
25 | Не удалось отправить уведомление на %s.
26 | Успешно отправлено уведомление на %s.
27 | Время ожидания истекло.
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/values-ru/strings.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 | IP-адрес
20 | Порт
21 | Алиас
22 | Публичный ключ
23 | Спряжить
24 | Принять
25 | Cancel
26 | Разрешить
27 | Уже сопряжено
28 | Неверный IP
29 | Неверный Порт
30 | Неверный QR Код
31 | Сопряжение не удалось
32 | Сервер Удалён
33 | Отменить
34 | Разрешение
35 | Сканировать QR код
36 | Уведомление
37 | Помощь
38 | Просмотреть журнал
39 | Тестовое уведомление
40 | Тестовое уведомление было послано на все запущенные сервера.
41 | Серверы
42 | Приложения
43 | Настройки
44 | Сопряжить с Новым Сервером
45 | Как альтернатива, введите IP и порт сервера вручную.
46 | Сопряжение…
47 | Примите запрос на сопряжение на сервере и проверьте IP-адрес и публичный ключ.
48 | Успешно сопряжено
49 | Проверьте IP и публичный ключ сервера.
50 | Требуется разрешение
51 | Чтобы иметь возможность прослушивать уведомления необходимо разрешение.
52 | Требуется разрешение для отправки тестового уведомления которое будет подхвачено слушателем уведомлений и отправлено на сервер.
53 | Слушатель уведомлений не получил ни одного уведомления.
54 | Редактировать Сервер
55 | Журнал
56 | Уведомление может быть заблокировано на определенный срок, если оно превышает определенное сходство с предыдущим..
57 | Сходство в %
58 | Продолжительность в секундах
59 | Не отправлять уведомления, если отображение включено.
60 | Отправляйте уведомления, которые не приходят из приложений. Их нельзя отключить.
61 | Проверочные уведомления
62 | Используется для тестовых уведомлений.
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 | #404040
20 | #006A6A
21 | #FFFFFF
22 | #9CF1F0
23 | #002020
24 | #4A6363
25 | #FFFFFF
26 | #CCE8E7
27 | #051F1F
28 | #4B607C
29 | #FFFFFF
30 | #D3E4FF
31 | #041C35
32 | #BA1A1A
33 | #FFFFFF
34 | #FFDAD6
35 | #410002
36 | #F4FBFA
37 | #161D1D
38 | #F4FBFA
39 | #161D1D
40 | #DAE5E4
41 | #3F4948
42 | #6F7979
43 | #BEC9C8
44 | #000000
45 | #2B3231
46 | #ECF2F1
47 | #80D5D4
48 | #9CF1F0
49 | #002020
50 | #80D5D4
51 | #004F4F
52 | #CCE8E7
53 | #051F1F
54 | #B0CCCB
55 | #324B4B
56 | #D3E4FF
57 | #041C35
58 | #B3C8E8
59 | #334863
60 | #D5DBDA
61 | #F4FBFA
62 | #FFFFFF
63 | #EFF5F4
64 | #E9EFEE
65 | #E3E9E9
66 | #DDE4E3
67 | #004B4B
68 | #FFFFFF
69 | #238181
70 | #FFFFFF
71 | #2E4747
72 | #FFFFFF
73 | #5F7979
74 | #FFFFFF
75 | #2F445F
76 | #FFFFFF
77 | #617693
78 | #FFFFFF
79 | #8C0009
80 | #FFFFFF
81 | #DA342E
82 | #FFFFFF
83 | #F4FBFA
84 | #161D1D
85 | #F4FBFA
86 | #161D1D
87 | #DAE5E4
88 | #3B4544
89 | #576161
90 | #737D7C
91 | #000000
92 | #2B3231
93 | #ECF2F1
94 | #80D5D4
95 | #238181
96 | #FFFFFF
97 | #006767
98 | #FFFFFF
99 | #5F7979
100 | #FFFFFF
101 | #476160
102 | #FFFFFF
103 | #617693
104 | #FFFFFF
105 | #495D79
106 | #FFFFFF
107 | #D5DBDA
108 | #F4FBFA
109 | #FFFFFF
110 | #EFF5F4
111 | #E9EFEE
112 | #E3E9E9
113 | #DDE4E3
114 | #002727
115 | #FFFFFF
116 | #004B4B
117 | #FFFFFF
118 | #0C2626
119 | #FFFFFF
120 | #2E4747
121 | #FFFFFF
122 | #0C233C
123 | #FFFFFF
124 | #2F445F
125 | #FFFFFF
126 | #4E0002
127 | #FFFFFF
128 | #8C0009
129 | #FFFFFF
130 | #F4FBFA
131 | #161D1D
132 | #F4FBFA
133 | #000000
134 | #DAE5E4
135 | #1C2625
136 | #3B4544
137 | #3B4544
138 | #000000
139 | #2B3231
140 | #FFFFFF
141 | #A6FBFA
142 | #004B4B
143 | #FFFFFF
144 | #003333
145 | #FFFFFF
146 | #2E4747
147 | #FFFFFF
148 | #173131
149 | #FFFFFF
150 | #2F445F
151 | #FFFFFF
152 | #182E47
153 | #FFFFFF
154 | #D5DBDA
155 | #F4FBFA
156 | #FFFFFF
157 | #EFF5F4
158 | #E9EFEE
159 | #E3E9E9
160 | #DDE4E3
161 |
162 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 | 10dp
20 | 16dp
21 | 6dp
22 | 15dp
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 | #009999
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/values/logs.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 | Failed to connect to %s.
20 | Trying to pair with %s…
21 | Failed to send own IP and public key to %s.
22 | Failed to receive public key from %s.
23 | No servers given.
24 | Trying to send notification to servers.
25 | Failed to send notification to %s.
26 | Successfully sent notification to %s.
27 | Timed out.
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/values/preferences.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 | dev.patri9ck.a2ln
20 | servers
21 | disabled_apps
22 | public_key
23 | secret_key
24 | similarity
25 | duration
26 | display
27 | no_app
28 | log
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/values/static.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 | Android 2 Linux Notifications
20 | %s
21 | A2LN
22 | test_notifications
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 | IP
20 | Port
21 | Alias
22 | Public Key
23 | Pair
24 | Apply
25 | Cancel
26 | Grant
27 | Already Paired
28 | Invalid IP
29 | Invalid Port
30 | Invalid QR Code
31 | Pairing Failed
32 | Server Removed
33 | Undo
34 | Permission
35 | Scan a QR Code
36 | Notification
37 | Help
38 | View Log
39 | Test Notification
40 | A test notification was sent to every running server.
41 | Servers
42 | Apps
43 | Settings
44 | Pair a New Server
45 | Alternatively, enter the IP and port of the server manually.
46 | Pairing…
47 | Accept the pairing request on the server and verify the IP and public key.
48 | Paired Successfully
49 | Verify the IP and public key of the server.
50 | Permission Needed
51 | To be able to listen to notifications, a permission is needed.
52 | A permission is required to send a test notification which will be picked up by the notification listener and sent to the server.
53 | No notification was picked up by the notification listener.
54 | Edit Server
55 | Log
56 | A notification can be blocked for a certain duration if it exceeds a certain similarity with a previous one.
57 | Similarity in %
58 | Duration in Seconds
59 | Do not send notifications if the display is enabled.
60 | Send notifications which do not come from apps. They cannot be disabled.
61 | Test Notifications
62 | Used for test notifications.
63 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
23 |
24 |
27 |
28 |
29 |
30 |
33 |
34 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
69 |
70 |
75 |
76 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | buildscript {
19 | repositories {
20 | google()
21 | mavenCentral()
22 | }
23 |
24 | dependencies {
25 | classpath("com.android.tools.build:gradle:8.2.1")
26 | }
27 | }
28 |
29 | tasks.register("clean", Delete::class) {
30 | delete(rootProject.buildDir)
31 | }
32 |
--------------------------------------------------------------------------------
/fastlane/Appfile:
--------------------------------------------------------------------------------
1 | package_name("dev.patri9ck.a2ln")
2 |
--------------------------------------------------------------------------------
/fastlane/Fastfile:
--------------------------------------------------------------------------------
1 | default_platform(:android)
2 |
3 | platform :android do
4 | lane :google_play_store do |options|
5 | aab = options[:aab] || true
6 |
7 | if aab
8 | gradle(task: "clean bundleRelease")
9 | end
10 |
11 | upload_to_play_store(
12 | json_key: options[:json_key],
13 | version_code: options[:version_code],
14 | skip_upload_aab: !aab
15 | )
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/15.txt:
--------------------------------------------------------------------------------
1 | - Option, ähnliche Benachrichtigungen zu blockieren
2 | - Um eine Test-Benachrichtigung zu senden wird nun eine richtige Benachrichtigung erstellt
3 | - Möglichkeit, keine Benachrichtigungen senden zu lassen, falls das Display eingeschaltet ist
4 | - Deutsche Übersetzung
5 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/16.txt:
--------------------------------------------------------------------------------
1 | - Korrekte Darstellung der Einstellungen im Querformat
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/17.txt:
--------------------------------------------------------------------------------
1 | - Test-Benachrichtigungen ignorieren nun alle Einstellungen
2 | - Option, auch Benachrichtigungen zu senden, welche nicht von Apps kommen
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/18.txt:
--------------------------------------------------------------------------------
1 | - Standard-Port (23045) nutzen, anstatt einen zu empfangen
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/19.txt:
--------------------------------------------------------------------------------
1 | - Material 3 Design hinzugefügt
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/changelogs/20.txt:
--------------------------------------------------------------------------------
1 | - Material 3 Design hinzugefügt
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/full_description.txt:
--------------------------------------------------------------------------------
1 | Android 2 Linux Notifications (A2LN) ist eine Möglichkeit, Deine Benachrichtigungen auf Deinem Linux-Computer anzeigen zu lassen.
2 |
3 | Es besteht aus einer App auf Deinem Telefon und einem Server auf Deinem Computer, welche über LAN sicher miteinander kommunizieren.
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/images/phoneScreenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/fastlane/metadata/android/de-DE/images/phoneScreenshots/1.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/images/phoneScreenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/fastlane/metadata/android/de-DE/images/phoneScreenshots/2.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/images/phoneScreenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/fastlane/metadata/android/de-DE/images/phoneScreenshots/3.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/images/phoneScreenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/fastlane/metadata/android/de-DE/images/phoneScreenshots/4.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/short_description.txt:
--------------------------------------------------------------------------------
1 | Eine Möglichkeit, Benachrichtigungen auf Linux anzeigen zu lassen
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/de-DE/title.txt:
--------------------------------------------------------------------------------
1 | Android 2 Linux Notifications
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/1.txt:
--------------------------------------------------------------------------------
1 | - Initial release
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/10.txt:
--------------------------------------------------------------------------------
1 | - Fixed app crashes
2 | - Added progress indicator when sending a test notification
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/11.txt:
--------------------------------------------------------------------------------
1 | - Fixed app crashes when logs are viewed in another fragment
2 | - Sorted app list
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/12.txt:
--------------------------------------------------------------------------------
1 | - Alias support
2 | - Small improvements
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/13.txt:
--------------------------------------------------------------------------------
1 | - Drag and drop server order swapping
2 | - Toggling of servers
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/14.txt:
--------------------------------------------------------------------------------
1 | - Allow domains as IPs
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/15.txt:
--------------------------------------------------------------------------------
1 | - Option to block similar notifications
2 | - Create an actual notification to send a test notification
3 | - Ability to not send notifications if the display is enabled
4 | - German translation
5 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/16.txt:
--------------------------------------------------------------------------------
1 | - Fix settings tab in landscape mode
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/17.txt:
--------------------------------------------------------------------------------
1 | - Let test notifications bypass all settings
2 | - Option to send notifications which do not come from apps
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/18.txt:
--------------------------------------------------------------------------------
1 | - Use default port (23045) instead of receiving one
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/19.txt:
--------------------------------------------------------------------------------
1 | - Added Material 3 Design
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/2.txt:
--------------------------------------------------------------------------------
1 | - Added Fastlane structure
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/20.txt:
--------------------------------------------------------------------------------
1 | - Added Material 3 Design
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/3.txt:
--------------------------------------------------------------------------------
1 | - Removed automatic versionCode and versionName resolving
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/4.txt:
--------------------------------------------------------------------------------
1 | - A whole new UI
2 | - New configuration system which means that you have to setup the app again
3 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/5.txt:
--------------------------------------------------------------------------------
1 | - New logo
2 | - "Help" button in "Settings" tab
3 | - New accent color
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/6.txt:
--------------------------------------------------------------------------------
1 | - Important: Pairing is now required which allows encryption and authentication, requires newest server version
2 | - Prettier UI
3 | - Test notification button
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/7.txt:
--------------------------------------------------------------------------------
1 | - QR code pairing
2 | - GUI improvements
3 | - Bug fixes
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/8.txt:
--------------------------------------------------------------------------------
1 | - FOSS QR code pairing with ZXing
2 | - GUI improvements
3 | - IP editing
4 | - Sending the notification's app name to the server
5 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/changelogs/9.txt:
--------------------------------------------------------------------------------
1 | - Viewable logs
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/full_description.txt:
--------------------------------------------------------------------------------
1 | Android 2 Linux Notifications (A2LN) is a way to display your Android phone notifications on your Linux computer.
2 |
3 | It consists of an app on your phone and a server on your computer which communicate securely over LAN.
4 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/featureGraphic.png:
--------------------------------------------------------------------------------
1 | ../../../../../images/banner.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/icon.png:
--------------------------------------------------------------------------------
1 | ../../../../../images/icon-filled.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/short_description.txt:
--------------------------------------------------------------------------------
1 | A way to display Android phone notifications on Linux
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/en-US/title.txt:
--------------------------------------------------------------------------------
1 | Android 2 Linux Notifications
2 |
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/full_description.txt:
--------------------------------------------------------------------------------
1 | Android 2 Linux Notifications (A2LN) это способ отображения уведомлений телефона Android на компьютере Linux.
2 |
3 | Он состоит из приложения на вашем телефоне и сервера на вашем компьютере которые безопасно обмениваются данными через локальную сеть.
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/short_description.txt:
--------------------------------------------------------------------------------
1 | Способ отображения уведомлений из телефона Android в Linux
--------------------------------------------------------------------------------
/fastlane/metadata/android/ru/title.txt:
--------------------------------------------------------------------------------
1 | Уведомления из Android в Linux
2 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | # Copyright (C) 2023 patri9ck and contributors
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 | #
18 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
19 | android.useAndroidX=true
20 | android.enableJetifier=false
21 | org.gradle.unsafe.configuration-cache=true
22 | android.nonTransitiveRClass=false
23 | android.nonFinalResIds=false
24 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionSha256Sum=9d926787066a081739e8200858338b4a69e837c3a821a33aca9db09dd4a41026
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
5 | networkTimeout=10000
6 | validateDistributionUrl=true
7 | zipStoreBase=GRADLE_USER_HOME
8 | zipStorePath=wrapper/dists
9 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | # This is normally unused
84 | # shellcheck disable=SC2034
85 | APP_BASE_NAME=${0##*/}
86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
88 |
89 | # Use the maximum available, or set MAX_FD != -1 to use that value.
90 | MAX_FD=maximum
91 |
92 | warn () {
93 | echo "$*"
94 | } >&2
95 |
96 | die () {
97 | echo
98 | echo "$*"
99 | echo
100 | exit 1
101 | } >&2
102 |
103 | # OS specific support (must be 'true' or 'false').
104 | cygwin=false
105 | msys=false
106 | darwin=false
107 | nonstop=false
108 | case "$( uname )" in #(
109 | CYGWIN* ) cygwin=true ;; #(
110 | Darwin* ) darwin=true ;; #(
111 | MSYS* | MINGW* ) msys=true ;; #(
112 | NONSTOP* ) nonstop=true ;;
113 | esac
114 |
115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
116 |
117 |
118 | # Determine the Java command to use to start the JVM.
119 | if [ -n "$JAVA_HOME" ] ; then
120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
121 | # IBM's JDK on AIX uses strange locations for the executables
122 | JAVACMD=$JAVA_HOME/jre/sh/java
123 | else
124 | JAVACMD=$JAVA_HOME/bin/java
125 | fi
126 | if [ ! -x "$JAVACMD" ] ; then
127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
128 |
129 | Please set the JAVA_HOME variable in your environment to match the
130 | location of your Java installation."
131 | fi
132 | else
133 | JAVACMD=java
134 | if ! command -v java >/dev/null 2>&1
135 | then
136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 | fi
142 |
143 | # Increase the maximum file descriptors if we can.
144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
145 | case $MAX_FD in #(
146 | max*)
147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
148 | # shellcheck disable=SC2039,SC3045
149 | MAX_FD=$( ulimit -H -n ) ||
150 | warn "Could not query maximum file descriptor limit"
151 | esac
152 | case $MAX_FD in #(
153 | '' | soft) :;; #(
154 | *)
155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
156 | # shellcheck disable=SC2039,SC3045
157 | ulimit -n "$MAX_FD" ||
158 | warn "Could not set maximum file descriptor limit to $MAX_FD"
159 | esac
160 | fi
161 |
162 | # Collect all arguments for the java command, stacking in reverse order:
163 | # * args from the command line
164 | # * the main class name
165 | # * -classpath
166 | # * -D...appname settings
167 | # * --module-path (only if needed)
168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
169 |
170 | # For Cygwin or MSYS, switch paths to Windows format before running java
171 | if "$cygwin" || "$msys" ; then
172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
174 |
175 | JAVACMD=$( cygpath --unix "$JAVACMD" )
176 |
177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
178 | for arg do
179 | if
180 | case $arg in #(
181 | -*) false ;; # don't mess with options #(
182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
183 | [ -e "$t" ] ;; #(
184 | *) false ;;
185 | esac
186 | then
187 | arg=$( cygpath --path --ignore --mixed "$arg" )
188 | fi
189 | # Roll the args list around exactly as many times as the number of
190 | # args, so each arg winds up back in the position where it started, but
191 | # possibly modified.
192 | #
193 | # NB: a `for` loop captures its iteration list before it begins, so
194 | # changing the positional parameters here affects neither the number of
195 | # iterations, nor the values presented in `arg`.
196 | shift # remove old arg
197 | set -- "$@" "$arg" # push replacement arg
198 | done
199 | fi
200 |
201 |
202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
204 |
205 | # Collect all arguments for the java command:
206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
207 | # and any embedded shellness will be escaped.
208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
209 | # treated as '${Hostname}' itself on the command line.
210 |
211 | set -- \
212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
213 | -classpath "$CLASSPATH" \
214 | org.gradle.wrapper.GradleWrapperMain \
215 | "$@"
216 |
217 | # Stop when "xargs" is not available.
218 | if ! command -v xargs >/dev/null 2>&1
219 | then
220 | die "xargs is not available"
221 | fi
222 |
223 | # Use "xargs" to parse quoted args.
224 | #
225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
226 | #
227 | # In Bash we could simply go:
228 | #
229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
230 | # set -- "${ARGS[@]}" "$@"
231 | #
232 | # but POSIX shell has neither arrays nor command substitution, so instead we
233 | # post-process each arg (as a line of input to sed) to backslash-escape any
234 | # character that might be a shell metacharacter, then use eval to reverse
235 | # that process (while maintaining the separation between arguments), and wrap
236 | # the whole thing up as a single "set" statement.
237 | #
238 | # This will of course break if any of these variables contains a newline or
239 | # an unmatched quote.
240 | #
241 |
242 | eval "set -- $(
243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
244 | xargs -n1 |
245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
246 | tr '\n' ' '
247 | )" '"$@"'
248 |
249 | exec "$JAVACMD" "$@"
250 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%"=="" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%"=="" set DIRNAME=.
29 | @rem This is normally unused
30 | set APP_BASE_NAME=%~n0
31 | set APP_HOME=%DIRNAME%
32 |
33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
35 |
36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
38 |
39 | @rem Find java.exe
40 | if defined JAVA_HOME goto findJavaFromJavaHome
41 |
42 | set JAVA_EXE=java.exe
43 | %JAVA_EXE% -version >NUL 2>&1
44 | if %ERRORLEVEL% equ 0 goto execute
45 |
46 | echo.
47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
48 | echo.
49 | echo Please set the JAVA_HOME variable in your environment to match the
50 | echo location of your Java installation.
51 |
52 | goto fail
53 |
54 | :findJavaFromJavaHome
55 | set JAVA_HOME=%JAVA_HOME:"=%
56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
57 |
58 | if exist "%JAVA_EXE%" goto execute
59 |
60 | echo.
61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
62 | echo.
63 | echo Please set the JAVA_HOME variable in your environment to match the
64 | echo location of your Java installation.
65 |
66 | goto fail
67 |
68 | :execute
69 | @rem Setup the command line
70 |
71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
72 |
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if %ERRORLEVEL% equ 0 goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | set EXIT_CODE=%ERRORLEVEL%
85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
87 | exit /b %EXIT_CODE%
88 |
89 | :mainEnd
90 | if "%OS%"=="Windows_NT" endlocal
91 |
92 | :omega
93 |
--------------------------------------------------------------------------------
/images/banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/images/banner.png
--------------------------------------------------------------------------------
/images/icon-filled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/images/icon-filled.png
--------------------------------------------------------------------------------
/images/icon-transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/patri9ck/a2ln-app/2c37b25f1adde602fb30a42b86bc7a4e23672876/images/icon-transparent.png
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Android 2 Linux Notifications - A way to display Android phone notifications on Linux
3 | * Copyright (C) 2023 patri9ck and contributors
4 | *
5 | * This program is free software: you can redistribute it and/or modify
6 | * it under the terms of the GNU General Public License as published by
7 | * the Free Software Foundation, either version 3 of the License, or
8 | * (at your option) any later version.
9 | *
10 | * This program is distributed in the hope that it will be useful,
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | * GNU General Public License for more details.
14 | *
15 | * You should have received a copy of the GNU General Public License
16 | * along with this program. If not, see .
17 | */
18 | @file:Suppress("UnstableApiUsage")
19 |
20 | dependencyResolutionManagement {
21 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
22 | repositories {
23 | google()
24 | mavenCentral()
25 | }
26 | }
27 |
28 | rootProject.name = "a2ln"
29 |
30 | include(":app")
31 |
--------------------------------------------------------------------------------