mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-09-02 07:05:09 +00:00
Compare commits
101 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2bfd9c645f | ||
|
87ddf47999 | ||
|
c7a4f76f2b | ||
|
d950266431 | ||
|
9f005b508a | ||
|
470eab525a | ||
|
9ff223e2b8 | ||
|
b9a0f310c7 | ||
|
2564586127 | ||
|
d1c0a9763d | ||
|
d163a57af6 | ||
|
e882bebe4e | ||
|
d1565702cc | ||
|
1b8763db61 | ||
|
f4a4046b3b | ||
|
882afbbc43 | ||
|
8e60359496 | ||
|
ba5a925075 | ||
|
d7c8f61c80 | ||
|
7da6310926 | ||
|
ccd92aca03 | ||
|
c5a8406928 | ||
|
a4afc84911 | ||
|
6d70d0dc50 | ||
|
2a36194273 | ||
|
edc04f41a6 | ||
|
7f215b8cd5 | ||
|
cf526953f3 | ||
|
dc5f10c073 | ||
|
21b97d335c | ||
|
f735520aba | ||
|
0072911c5b | ||
|
96a83ddcd3 | ||
|
794f1d4706 | ||
|
e5c8160ee2 | ||
|
25a0972606 | ||
|
6bcb77f65f | ||
|
235e93be5e | ||
|
aa776739b7 | ||
|
e8e01c9a51 | ||
|
168b16527c | ||
|
9a8ae36ef6 | ||
|
b80d7dcf50 | ||
|
b1e8a66a8c | ||
|
b375d79653 | ||
|
6f0ed846b8 | ||
|
2a7f953bd4 | ||
|
728bfeefea | ||
|
2ab76c6e90 | ||
|
3f2b8495b3 | ||
|
b9f3c8c7b1 | ||
|
a73c95094c | ||
|
64c20f5e63 | ||
|
f30462bafa | ||
|
7ce2130d12 | ||
|
77b28fe9c4 | ||
|
27a2c030d6 | ||
|
95f866ac98 | ||
|
177c18e0f4 | ||
|
a3fb423dd3 | ||
|
3c97e1c067 | ||
|
50dc239d64 | ||
|
f278bd17e1 | ||
|
9c51f1e898 | ||
|
5063f3d0f4 | ||
|
f1194e88c2 | ||
|
386a9f3f40 | ||
|
0510167c4e | ||
|
1867150ebb | ||
|
3022e3d835 | ||
|
6e1fe3edbd | ||
|
71609f144d | ||
|
102ce97a19 | ||
|
3a36a4a675 | ||
|
655a9805e4 | ||
|
f88f2da7b6 | ||
|
d46a3e49c5 | ||
|
9f6a1c70aa | ||
|
2a61f431d6 | ||
|
8b5734db43 | ||
|
d3beb45b79 | ||
|
601cafa84b | ||
|
210b7b7ed0 | ||
|
5bf0118bf7 | ||
|
c263e996fa | ||
|
8ff45db1f3 | ||
|
a146767580 | ||
|
315d9657dd | ||
|
5f70db7568 | ||
|
c2cd60773b | ||
|
ca90b64094 | ||
|
7b6d8a4c35 | ||
|
b725e81c37 | ||
|
17e9892c80 | ||
|
a8bc4f24c7 | ||
|
3b82cd44e4 | ||
|
79272a6042 | ||
|
12fb67a7aa | ||
|
d03520ce70 | ||
|
0453728407 | ||
|
7dc023385e |
32
.gitlab/merge_request_templates/Bugfix.md
Normal file
32
.gitlab/merge_request_templates/Bugfix.md
Normal file
@@ -0,0 +1,32 @@
|
||||
## Summary
|
||||
|
||||
Add a description of your merge request here. What does your new feature do?
|
||||
|
||||
Describe in detail what your patch does, why it does that, etc. Merge requests
|
||||
without an adequate description are difficult to review, and probably we will
|
||||
ask for more information!
|
||||
|
||||
Please also keep this description up-to-date with any discussion that takes
|
||||
place so that reviewers can understand your intent. This is especially
|
||||
important if they didn't participate in the discussion.
|
||||
|
||||
Make sure to remove this comment when you are done.
|
||||
|
||||
Fill in the following lines as appropriate to automatically close GitLab issue or Bugzilla bugs
|
||||
Fixes <!-- Gitlab Issue Number -->
|
||||
BUG: <!-- bugzilla bug -->
|
||||
|
||||
## Test Plan
|
||||
|
||||
### Before:
|
||||
Add a quick discription of the (buggy) behavior of the app before this fix
|
||||
This section does not need to be too detailed because it should mostly be
|
||||
covered by the bug report and the summary. Just share the steps for how to
|
||||
reproduce the bug.
|
||||
|
||||
### After:
|
||||
Add a more detailed description of how to exercise the new behavior, showing
|
||||
that the bug has been fixed. If any other behavior has been changed, share
|
||||
the steps to verify that the new behavior doesn't have any regressions.
|
||||
|
||||
/label ~bugfix
|
27
.gitlab/merge_request_templates/Feature.md
Normal file
27
.gitlab/merge_request_templates/Feature.md
Normal file
@@ -0,0 +1,27 @@
|
||||
## Summary
|
||||
|
||||
Add a description of your merge request here. What does your new feature do?
|
||||
|
||||
Describe in detail what your patch does, why it does that, etc. Merge requests
|
||||
without an adequate description are difficult to review, and probably we will
|
||||
ask for more information!
|
||||
|
||||
Please also keep this description up-to-date with any discussion that takes
|
||||
place so that reviewers can understand your intent. This is especially
|
||||
important if they didn't participate in the discussion.
|
||||
|
||||
Make sure to remove this comment when you are done.
|
||||
|
||||
Implements <!-- GitLab Issue Number -->
|
||||
|
||||
## Test Plan
|
||||
|
||||
Add a description of how to test your patch here. Tell us how to use the new
|
||||
feature and what we should be seeing. If applicable, it is great to include
|
||||
screenshots, either here or in the Summary section.
|
||||
|
||||
It can be difficult to understand a new feature from the text description in
|
||||
the summary, so put enough detail here that so that we can understand how to run
|
||||
the new feature and we can play with it ourselves to understand it.
|
||||
|
||||
/label ~feature
|
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.kde.kdeconnect_tp"
|
||||
android:versionCode="11240"
|
||||
android:versionName="1.12.4">
|
||||
android:versionCode="11270"
|
||||
android:versionName="1.12.7">
|
||||
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
|
11
build.gradle
11
build.gradle
@@ -6,7 +6,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.2'
|
||||
classpath 'com.android.tools.build:gradle:3.4.0'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,12 +36,9 @@ android {
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
pickFirst "META-INF/DEPENDENCIES"
|
||||
pickFirst "META-INF/LICENSE"
|
||||
pickFirst "META-INF/NOTICE"
|
||||
pickFirst "META-INF/BCKEY.SF"
|
||||
pickFirst "META-INF/BCKEY.DSA"
|
||||
pickFirst "META-INF/INDEX.LIST"
|
||||
merge "META-INF/DEPENDENCIES"
|
||||
merge "META-INF/LICENSE"
|
||||
merge "META-INF/NOTICE"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Tue Jan 15 13:04:46 CET 2019
|
||||
#Wed May 01 14:24:13 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 231 B |
Binary file not shown.
Before Width: | Height: | Size: 163 B |
Binary file not shown.
Before Width: | Height: | Size: 304 B |
Binary file not shown.
Before Width: | Height: | Size: 537 B |
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<bitmap
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/drawer_header"
|
||||
android:gravity="center|clip_vertical|clip_horizontal"
|
||||
/>
|
@@ -12,12 +12,8 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:drawableEnd="@drawable/ic_delete"
|
||||
android:drawableLeft="@drawable/ic_delete"
|
||||
android:drawableRight="@drawable/ic_delete"
|
||||
android:drawableStart="@drawable/ic_delete"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingRight"
|
||||
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
|
||||
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingLeft"/>
|
||||
|
||||
<FrameLayout
|
||||
@@ -34,8 +30,6 @@
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingRight"
|
||||
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
|
||||
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingLeft"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall"
|
||||
android:visibility="visible"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/mpris_control_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -10,13 +10,15 @@
|
||||
<Space
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.25" />
|
||||
android:id="@+id/top_space"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dip"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_gravity="center"
|
||||
android:orientation="horizontal">
|
||||
android:orientation="horizontal"
|
||||
android:layout_below="@id/top_space"
|
||||
android:layout_above="@id/textView">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/previous_button"
|
||||
@@ -44,8 +46,7 @@
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.25"
|
||||
android:gravity="center"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:text="@string/presenter_lock_tip" />
|
||||
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/refresh_list_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/refresh_list_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ListView
|
||||
android:id="@+id/devices_list"
|
||||
|
@@ -4,7 +4,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<TextView
|
||||
android:id="@+id/list_item_category_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@@ -32,7 +32,7 @@
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/systemvolume_seek"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1"
|
||||
|
@@ -122,7 +122,7 @@
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/positionSeek"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_weight="1" />
|
||||
|
@@ -1,18 +0,0 @@
|
||||
<androidx.appcompat.widget.SwitchCompat
|
||||
android:id="@+id/dark_theme"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingEnd="48dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="48dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="4dp"
|
||||
android:text="@string/dark_theme"
|
||||
android:textColor="@android:color/white"
|
||||
app:switchPadding="12dp"
|
||||
tools:background="@drawable/drawer_header"
|
||||
/>
|
@@ -78,11 +78,7 @@
|
||||
<string name="pairing_request_from">طلب اقتران من %1s</string>
|
||||
<string name="received_url_title">استُلمت وصلة من %1s</string>
|
||||
<string name="received_url_text">المس لفتح \'%1s\'</string>
|
||||
<string name="outgoing_file_title">يرسل ملفًّا إلى %1s</string>
|
||||
<string name="received_file_text">المس لفتح \'%1s\'</string>
|
||||
<string name="sent_file_title">أرسل ملفًّا إلى %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">المس للإجابة</string>
|
||||
<string name="reconnect">أعد الاتّصال</string>
|
||||
<string name="right_click">أرسل نقرة باليمين</string>
|
||||
|
@@ -59,11 +59,7 @@
|
||||
<string name="pairing_request_from">Uparivanje zatraženo od %1s</string>
|
||||
<string name="received_url_title">Primljena veza od %1s</string>
|
||||
<string name="received_url_text">Kucni za otvaranje \'%1s\'</string>
|
||||
<string name="outgoing_file_title">Slanje datoteke na %1s</string>
|
||||
<string name="received_file_text">Kucni za otvaranje \'%1s\'</string>
|
||||
<string name="sent_file_title">Poslana datoteka na %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Kucni za odgovor</string>
|
||||
<string name="reconnect">Ponovo uspostavi vezu</string>
|
||||
<string name="right_click">Pošalji Desni Klik</string>
|
||||
|
@@ -104,11 +104,13 @@
|
||||
<item quantity="one">Fitxer: %1s</item>
|
||||
<item quantity="other">(Fitxer %2$d de %3$d): %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">S\'està enviant el fitxer a %1s</string>
|
||||
<string name="outgoing_files_title">S\'estan enviant fitxers a %1s</string>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="one">S\'està enviant el fitxer %1$d a %2$s</item>
|
||||
<item quantity="other">S\'estan enviant els fitxers %1$d a %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">S\'ha enviat %1$d fitxer</item>
|
||||
<item quantity="other">S\'ha enviat %1$d de %2$d fitxers</item>
|
||||
<item quantity="one">Fitxer: %1$s</item>
|
||||
<item quantity="other">(Fitxer %2$d de %3$d): %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">S\'ha rebut un fitxer des de %1$s</item>
|
||||
@@ -118,12 +120,16 @@
|
||||
<item quantity="one">Ha fallat en rebre un fitxer des de %1$s</item>
|
||||
<item quantity="other">Ha fallat en rebre %2$d de %3$d fitxers des de %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="sent_files_title">
|
||||
<item quantity="one">Fitxer enviat a %1$s</item>
|
||||
<item quantity="other">%2$d fitxers enviats a %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="send_files_fail_title">
|
||||
<item quantity="one">Ha fallat en enviar el fitxer a %1$s</item>
|
||||
<item quantity="other">Ha fallat en enviar %2$d de %3$d fitxers a %1$s</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Puntegeu per obrir «%1s»</string>
|
||||
<string name="cannot_create_file">No s\'ha pogut crear el fitxer %s</string>
|
||||
<string name="sent_file_title">Fitxer enviat a %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Ha fallat en enviar el fitxer a %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Puntegeu per a respondre</string>
|
||||
<string name="reconnect">Reconnecta</string>
|
||||
<string name="right_click">Envia un clic del botó dret</string>
|
||||
@@ -240,7 +246,7 @@
|
||||
<string name="plugins_need_optional_permission">Alguns connectors tenen característiques desactivades per la falta de permís (puntegeu per a més informació):</string>
|
||||
<string name="share_optional_permission_explanation">Per a compartir fitxers entre el telèfon i l\'escriptori, haureu de donar accés a l\'emmagatzematge del telèfon</string>
|
||||
<string name="telepathy_permission_explanation">Per a llegir i escriure SMS des de l\'escriptori, haureu de donar permís als SMS</string>
|
||||
<string name="telephony_permission_explanation">Per a veure les trucades telefòniques i SMS des de l\'escriptori, haureu de donar permís a les trucades telefòniques i SMS</string>
|
||||
<string name="telephony_permission_explanation">Per a veure les trucades telefòniques des de l\'escriptori, haureu de donar permís d\'accés al registre de trucades telefòniques i a l\'estat del telèfon</string>
|
||||
<string name="telephony_optional_permission_explanation">Per a veure un nom de contacte en comptes d\'un número de telèfon, haureu de donar permís als contactes del telèfon</string>
|
||||
<string name="contacts_permission_explanation">Per a compartir els vostres contactes amb l\'escriptori, caldrà que els hi doneu permís</string>
|
||||
<string name="select_ringtone">Seleccioneu un to de la trucada</string>
|
||||
@@ -294,4 +300,5 @@
|
||||
<string name="notification_channel_receivenotification">Notificacions des d\'altres dispositius</string>
|
||||
<string name="take_picture">Llança la càmera</string>
|
||||
<string name="plugin_photo_desc">Llança l\'aplicació de la càmera per facilitar la presa i la transferència de fotografies</string>
|
||||
<string name="no_app_for_opening">No s\'ha trobat cap aplicació adequada per obrir aquest fitxer</string>
|
||||
</resources>
|
||||
|
@@ -106,14 +106,6 @@
|
||||
<item quantity="many">(Soubor %2$d of %3$d) : %1$s</item>
|
||||
<item quantity="other"/>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Odesílám soubor do %1s</string>
|
||||
<string name="outgoing_files_title">Odesílám soubory do %1s</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Odeslán %1$d soubor</item>
|
||||
<item quantity="few">Odeslány %1$d ze %2$d souborů</item>
|
||||
<item quantity="many">Odesláno %1$d ze %2$d souborů</item>
|
||||
<item quantity="other"/>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Přijat soubor z %1$s</item>
|
||||
<item quantity="few">Přijaty %2$d soubory z %1$s</item>
|
||||
@@ -128,10 +120,6 @@
|
||||
</plurals>
|
||||
<string name="received_file_text">Ťukněte pro otevření \'%1s\'</string>
|
||||
<string name="cannot_create_file">Nelze vytvořit soubor %s</string>
|
||||
<string name="sent_file_title">Soubor byl odeslán do %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Odesílání souborů na %1s selhalo</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Ťukněte pro odpovězení</string>
|
||||
<string name="reconnect">Znovu připojit</string>
|
||||
<string name="right_click">Poslat kliknutí pravým tlačítkem</string>
|
||||
@@ -224,7 +212,6 @@
|
||||
<string name="plugins_need_optional_permission">Některé moduly mají vypnuté vlastnosti, kvůli nedostatečným oprávněním (ťukněte pro více informací):</string>
|
||||
<string name="share_optional_permission_explanation">Pro sdílení souborů mezi telefonem a počítačem potřebujete udělit oprávnění k úložišti telefonu</string>
|
||||
<string name="telepathy_permission_explanation">Pro čtení a psaní SMS z počítače musíte udělit oprávnění k SMS</string>
|
||||
<string name="telephony_permission_explanation">Pro zobrazení telefonátů a SMS v počítači musíte udělit oprávnění k telefonování a SMS</string>
|
||||
<string name="telephony_optional_permission_explanation">Pro zobrazení jména kontaktu u telefonního čísla je potřeba udělit oprávnění ke kontaktům v telefonu</string>
|
||||
<string name="contacts_permission_explanation">Pro sdílení knihy kontaktů s pracovním prostředím, musíte udělit přístup ke kontaktům</string>
|
||||
<string name="select_ringtone">Vybrat vyzváněcí tón</string>
|
||||
|
@@ -84,13 +84,7 @@
|
||||
<string name="pairing_request_from">Parringsanmodning fra %1s</string>
|
||||
<string name="received_url_title">Modtog link fra %1s</string>
|
||||
<string name="received_url_text">Tap for at åbne \"%1s\"</string>
|
||||
<string name="outgoing_file_title">Sender fil til %1s</string>
|
||||
<string name="outgoing_files_title">Sender filer til %1s</string>
|
||||
<string name="received_file_text">Tap for at åbne \"%1s\"</string>
|
||||
<string name="sent_file_title">Fil sendt til %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Kunne ikke sende filen til %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Tap for at svare</string>
|
||||
<string name="reconnect">Forbind igen</string>
|
||||
<string name="right_click">Send højreklik</string>
|
||||
@@ -172,6 +166,5 @@
|
||||
<string name="plugins_need_optional_permission">Nogle plugins har deaktiverede funktioner pga. manglende tilladelser (tap for mere info):</string>
|
||||
<string name="share_optional_permission_explanation">For at dele filer mellem din telefon og din desktop skal du give adgang til telefonens datalager.</string>
|
||||
<string name="telepathy_permission_explanation">For at læse og skrive sms\'er fra din desktop, skal du give tilladelse til sms</string>
|
||||
<string name="telephony_permission_explanation">For at se telefonopkald og sms\'er fra desktoppen, skal du give tilladelse til telefonopkald og sms.</string>
|
||||
<string name="telephony_optional_permission_explanation">For at se et kontaktnavn i stedet for et telefonnummer, skal du give adgang til telefonens kontakter</string>
|
||||
</resources>
|
||||
|
@@ -61,7 +61,7 @@
|
||||
<item>Strongest</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Verbundene Geräte</string>
|
||||
<string name="category_not_paired_devices">Verfügbare Gerät</string>
|
||||
<string name="category_not_paired_devices">Verfügbare Geräte</string>
|
||||
<string name="category_remembered_devices">Gemerkte Geräte</string>
|
||||
<string name="device_menu_plugins">Modul-Einstellungen</string>
|
||||
<string name="device_menu_unpair">Verbindung trennen</string>
|
||||
@@ -84,17 +84,7 @@
|
||||
<string name="pairing_request_from">Verbindungsanfrage von %1s</string>
|
||||
<string name="received_url_title">Verknüpfung von %1s erhalten</string>
|
||||
<string name="received_url_text">Tippen um „%1s“ zu öffnen</string>
|
||||
<string name="outgoing_file_title">Datei wird an %1s gesendet</string>
|
||||
<string name="outgoing_files_title">Datei wird an %1s gesendet</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">%1$d Datei gesendet</item>
|
||||
<item quantity="other">%1$d von %2$d Dateien gesendet</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Tippen um „%1s“ zu öffnen</string>
|
||||
<string name="sent_file_title">Datei an %1s gesendet</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Das Senden der Datei an %1s ist fehlgeschlagen</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Tippen zum Antworten</string>
|
||||
<string name="reconnect">Erneut verbinden</string>
|
||||
<string name="right_click">Rechtsklick senden</string>
|
||||
@@ -184,7 +174,6 @@
|
||||
<string name="plugins_need_optional_permission">Einige Module haben eingeschränkte Funktionen wegen fehlender Berechtigungen, tippen Sie für weitere Informationen:</string>
|
||||
<string name="share_optional_permission_explanation">m Dateien zwischen Rechner und Telefon auszutauschen, muss der Zugriff auf den Telefonspeicher gewährt werden</string>
|
||||
<string name="telepathy_permission_explanation">Um SMS vom Rechner aus zu lesen und zu versenden, muss der Zugriff auf die SMS-Funktion gewährt werden</string>
|
||||
<string name="telephony_permission_explanation">Um Telefonate und SMS auf dem Rechner zu sehen, müssen Berechtigungen für Anrufe und SMS erteilt werden</string>
|
||||
<string name="telephony_optional_permission_explanation">Um einen Namen anstelle einer Telefonnummer zu sehen, muss der Zugriff auf das Adressbuch gewährt werden</string>
|
||||
<string name="select_ringtone">Einen Klingelton auswählen</string>
|
||||
<string name="telephony_pref_blocked_title">Unterdrückte Nummern</string>
|
||||
|
@@ -84,13 +84,7 @@
|
||||
<string name="pairing_request_from">Αίτημα σύζευξης από %1s</string>
|
||||
<string name="received_url_title">Ελήφθη σύνδεσμος από %1s</string>
|
||||
<string name="received_url_text">Χτυπήστε για άνοιγμα \'%1s\'</string>
|
||||
<string name="outgoing_file_title">Αποστολή αρχείου σε %1s</string>
|
||||
<string name="outgoing_files_title">Αποστολή αρχείων σε %1s</string>
|
||||
<string name="received_file_text">Χτυπήστε για άνοιγμα \'%1s\'</string>
|
||||
<string name="sent_file_title">Εστάλη αρχείο σε %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Αποτυχία αποστολής αρχείου σε %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Χτυπήστε για να απαντήσετε</string>
|
||||
<string name="reconnect">Επανασύνδεση</string>
|
||||
<string name="right_click">Αποστολή δεξιού κλικ</string>
|
||||
@@ -172,6 +166,5 @@
|
||||
<string name="plugins_need_optional_permission">Κάποια πρόσθετα έχουν λειτουργίες ανενεργές εξαιτίας της απουσίας δικαιωμάτων (χτυπήστε για περισσότερες πληροφορίες):</string>
|
||||
<string name="share_optional_permission_explanation">Για το διαμοιρασμό αρχείων ανάμεσα στο τηλέφωνο και τον υπολογιστή σας χρειάζεται να παραχωρήσετε πρόσβαση στον αποθηκευτικό χώρο του τηλεφώνου σας</string>
|
||||
<string name="telepathy_permission_explanation">Για να διαβάσετε και να γράψετε SMS από την επιφάνεια εργασίας, χρειάζεται να δώσετε δικαιώματα στο SMS</string>
|
||||
<string name="telephony_permission_explanation">Για να δείτε τηλεφωνικές κλήσεις και SMS από την επιφάνεια εργασίας, χρειάζεται να παραχωρήσετε δικαιώματα σε τηλεφωνικές κλήσεις και SMS</string>
|
||||
<string name="telephony_optional_permission_explanation">Για να δείτε το όνομα επαφής αντί για τον αριθμό κλήσης χρειάζεται να παραχωρήσετε πρόσβαση στις επαφές στο τηλέφωνο</string>
|
||||
</resources>
|
||||
|
@@ -13,6 +13,7 @@
|
||||
<string name="pref_plugin_clipboard_desc">Share the clipboard content</string>
|
||||
<string name="pref_plugin_mousepad">Remote input</string>
|
||||
<string name="pref_plugin_mousepad_desc">Use your phone or tablet as a touchpad and keyboard</string>
|
||||
<string name="pref_plugin_presenter">Slideshow remote</string>
|
||||
<string name="pref_plugin_presenter_desc">Use your device to change slides in a presentation</string>
|
||||
<string name="pref_plugin_remotekeyboard">Receive remote keypresses</string>
|
||||
<string name="pref_plugin_remotekeyboard_desc">Receive keypress events from remote devices</string>
|
||||
@@ -37,6 +38,7 @@
|
||||
<string name="open_settings">Open settings</string>
|
||||
<string name="no_permissions">You need to grant permission to access notifications</string>
|
||||
<string name="no_permission_mprisreceiver">To be able to control your media players you need to grant access to the notifications</string>
|
||||
<string name="no_permissions_remotekeyboard">To receive keypresses you need to activate the KDE Connect Remote Keyboard</string>
|
||||
<string name="send_ping">Send ping</string>
|
||||
<string name="open_mpris_controls">Multimedia control</string>
|
||||
<string name="remotekeyboard_editing_only_title">Handle remote keys only when editing</string>
|
||||
@@ -94,17 +96,40 @@
|
||||
<string name="pairing_request_from">Pairing request from %1s</string>
|
||||
<string name="received_url_title">Received link from %1s</string>
|
||||
<string name="received_url_text">Tap to open \'%1s\'</string>
|
||||
<string name="outgoing_file_title">Sending file to %1s</string>
|
||||
<string name="outgoing_files_title">Sending files to %1s</string>
|
||||
<plurals name="incoming_file_title">
|
||||
<item quantity="one">Receiving %1$d file from %2$s</item>
|
||||
<item quantity="other">Receiving %1$d files from %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="incoming_files_text">
|
||||
<item quantity="one">File: %1s</item>
|
||||
<item quantity="other">(File %2$d of %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="one">Sending %1$d file to %2$s</item>
|
||||
<item quantity="other">Sending %1$d files to %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Send files</item>
|
||||
<item quantity="other">Sent %1$d out of %2$d files</item>
|
||||
<item quantity="one">File: %1$s</item>
|
||||
<item quantity="other">(File %2$d of %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Received file from %1$s</item>
|
||||
<item quantity="other">Received %2$d files from %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_fail_title">
|
||||
<item quantity="one">Failed receiving file from %1$s</item>
|
||||
<item quantity="other">Failed receiving %2$d of %3$d files from %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="sent_files_title">
|
||||
<item quantity="one">Sent file to %1$s</item>
|
||||
<item quantity="other">Sent %2$d files to %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="send_files_fail_title">
|
||||
<item quantity="one">Failed sending file to %1$s</item>
|
||||
<item quantity="other">Failed sending %2$d of %3$d files to %1$s</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Tap to open \'%1s\'</string>
|
||||
<string name="sent_file_title">Sent file to %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Failed to send file to %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="cannot_create_file">Cannot create file %s</string>
|
||||
<string name="tap_to_answer">Tap to answer</string>
|
||||
<string name="reconnect">Reconnect</string>
|
||||
<string name="right_click">Send Right Click</string>
|
||||
@@ -150,6 +175,11 @@
|
||||
<string name="pair_device_action">Pair a new device</string>
|
||||
<string name="unpair_device_action">Unpair %s</string>
|
||||
<string name="custom_device_list">Add devices by IP</string>
|
||||
<string name="delete_custom_device">Delete %s?</string>
|
||||
<string name="custom_device_deleted">Custom device deleted</string>
|
||||
<string name="custom_device_list_help">If your device is not automatically detected you can add its IP address or hostname by clicking on the Floating Action Button</string>
|
||||
<string name="custom_device_fab_hint">Add a device</string>
|
||||
<string name="undo">Undo</string>
|
||||
<string name="share_notification_preference">Noisy notifications</string>
|
||||
<string name="share_notification_preference_summary">Vibrate and play a sound when receiving a file</string>
|
||||
<string name="share_destination_customize">Customise destination directory</string>
|
||||
@@ -165,7 +195,28 @@
|
||||
<string name="sftp_sdcard">SD card</string>
|
||||
<string name="sftp_readonly">(read only)</string>
|
||||
<string name="sftp_camera">Camera pictures</string>
|
||||
<string name="add_device_dialog_title">Add device</string>
|
||||
<string name="add_device_hint">Hostname or IP address</string>
|
||||
<string name="sftp_preference_detected_sdcards">Detected SD cards</string>
|
||||
<string name="sftp_preference_edit_sdcard_title">Edit SD card</string>
|
||||
<string name="sftp_preference_configured_storage_locations">Configured storage locations</string>
|
||||
<string name="sftp_preference_add_storage_location_title">Add storage location</string>
|
||||
<string name="sftp_preference_edit_storage_location">Edit storage location</string>
|
||||
<string name="sftp_preference_add_camera_shortcut">Add camera folder shortcut</string>
|
||||
<string name="sftp_preference_add_camera_shortcut_summary_on">Add a shortcut to the camera folder</string>
|
||||
<string name="sftp_preference_add_camera_shortcut_summary_off">Do not add a shortcut to the camera folder</string>
|
||||
<string name="sftp_storage_preference_storage_location">Storage location</string>
|
||||
<string name="sftp_storage_preference_storage_location_already_configured">This location has already been configured</string>
|
||||
<string name="sftp_storage_preference_click_to_select">click to select</string>
|
||||
<string name="sftp_storage_preference_display_name">Display name</string>
|
||||
<string name="sftp_storage_preference_display_name_already_used">This display name is already used</string>
|
||||
<string name="sftp_storage_preference_display_name_cannot_be_empty">Display name cannot be empty</string>
|
||||
<string name="sftp_action_mode_menu_delete">Delete</string>
|
||||
<string name="sftp_no_sdcard_detected">No SD card detected</string>
|
||||
<string name="sftp_no_storage_locations_configured">No storage locations configured</string>
|
||||
<string name="sftp_saf_permission_explanation">To access files remotely you have to configure storage locations</string>
|
||||
<string name="add_host">Add host/IP</string>
|
||||
<string name="add_host_hint">Hostname or IP</string>
|
||||
<string name="no_players_connected">No players found</string>
|
||||
<string name="mpris_player_on_device">%1$s on %2$s</string>
|
||||
<string name="send_files">Send files</string>
|
||||
@@ -195,7 +246,7 @@
|
||||
<string name="plugins_need_optional_permission">Some plugins have features disabled because of lack of permission (tap for more info):</string>
|
||||
<string name="share_optional_permission_explanation">To share files between your phone and your desktop you need to give access to the phone\'s storage</string>
|
||||
<string name="telepathy_permission_explanation">To read and write SMS from your desktop you need to give permission to SMS</string>
|
||||
<string name="telephony_permission_explanation">To see phone calls and SMS from the desktop you need to give permission to phone calls and SMS</string>
|
||||
<string name="telephony_permission_explanation">To see phone calls on the desktop you need to give permission to phone call logs and phone state</string>
|
||||
<string name="telephony_optional_permission_explanation">To see a contact name instead of a phone number you need to give access to the phone\'s contacts</string>
|
||||
<string name="contacts_permission_explanation">To share your contacts book with the desktop, you need to give contacts permission</string>
|
||||
<string name="select_ringtone">Select a ringtone</string>
|
||||
@@ -206,6 +257,7 @@
|
||||
<string name="settings_icon_description">Settings icon</string>
|
||||
<string name="presenter_fullscreen">Full-screen</string>
|
||||
<string name="presenter_exit">Exit presentation</string>
|
||||
<string name="presenter_lock_tip">You can lock your device and use the volume keys to go to the previous/next slide</string>
|
||||
<string name="add_command">Add a command</string>
|
||||
<string name="addcommand_explanation">There are no commands registered</string>
|
||||
<string name="addcommand_explanation2">You can add new commands in the KDE Connect System Settings</string>
|
||||
@@ -237,4 +289,16 @@
|
||||
<string name="settings_more_settings_title">More settings</string>
|
||||
<string name="settings_more_settings_text">Per-device settings can be found under \'Plugin settings\' from within a device.</string>
|
||||
<string name="setting_persistent_notification">Show persistent notification</string>
|
||||
<string name="setting_persistent_notification_oreo">Persistent notification</string>
|
||||
<string name="setting_persistent_notification_description">Tap to enable/disable in Notification settings</string>
|
||||
<string name="extra_options">Extra options</string>
|
||||
<string name="privacy_options">Privacy options</string>
|
||||
<string name="set_privacy_options">Set your privacy options</string>
|
||||
<string name="new_notification">New notification</string>
|
||||
<string name="block_contents">Block contents of notifications</string>
|
||||
<string name="block_images">Block images in notifications</string>
|
||||
<string name="notification_channel_receivenotification">Notifications from other devices</string>
|
||||
<string name="take_picture">Launch camera</string>
|
||||
<string name="plugin_photo_desc">Launch the camera app to ease taking and transferring pictures</string>
|
||||
<string name="no_app_for_opening">No suitable app found to open this file</string>
|
||||
</resources>
|
||||
|
@@ -38,6 +38,7 @@
|
||||
<string name="open_settings">Abrir preferencias</string>
|
||||
<string name="no_permissions">Debe otorgar permisos para acceder a las notificaciones</string>
|
||||
<string name="no_permission_mprisreceiver">Para poder controlar sus reproductores de medios, necesita dar acceso a las notificaciones</string>
|
||||
<string name="no_permissions_remotekeyboard">Para recibir pulsaciones de teclado debe activar el teclado remoto de KDE Connect</string>
|
||||
<string name="send_ping">Enviar ping</string>
|
||||
<string name="open_mpris_controls">Control multimedia</string>
|
||||
<string name="remotekeyboard_editing_only_title">Manejar teclas remotas solo al editar</string>
|
||||
@@ -103,11 +104,13 @@
|
||||
<item quantity="one">Archivo: %1s</item>
|
||||
<item quantity="other">(Archivo %2$d de %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Enviando archivo a %1s</string>
|
||||
<string name="outgoing_files_title">Enviando archivos a %1s</string>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="one">Enviando %1$d archivo a %2$s</item>
|
||||
<item quantity="other">Enviando %1$d archivos a %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Enviado %1$d archivo</item>
|
||||
<item quantity="other">Enviados %1$d de %2$d archivos</item>
|
||||
<item quantity="one">Archivo: %1$s</item>
|
||||
<item quantity="other">(Archivo %2$d de %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Recibido archivo desde %1$s</item>
|
||||
@@ -117,12 +120,16 @@
|
||||
<item quantity="one">Fallo recibiendo archivo desde %1$s</item>
|
||||
<item quantity="other">Fallo recibiendo %2$d de %3$d archivos desde %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="sent_files_title">
|
||||
<item quantity="one">Archivo enviado a %1$s</item>
|
||||
<item quantity="other">Enviados %2$d archivos a %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="send_files_fail_title">
|
||||
<item quantity="one">Fallo al enviar el archivo a %1$s</item>
|
||||
<item quantity="other">Fallo al enviar %2$d de %3$d archivos a %1$s</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Pulse para abrir «%1s»</string>
|
||||
<string name="cannot_create_file">No se pudo crear el archivo %s</string>
|
||||
<string name="sent_file_title">Archivo enviado a %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Fallo al enviar el archivo a %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Pulse para responder</string>
|
||||
<string name="reconnect">Reconectar</string>
|
||||
<string name="right_click">Enviar clic derecho</string>
|
||||
@@ -239,7 +246,7 @@
|
||||
<string name="plugins_need_optional_permission">Algunos complementos tienen funcionalidades desactivadas por falta de permisos (pulse para más información):</string>
|
||||
<string name="share_optional_permission_explanation">Para compartir archivos entre su teléfono y su escritorio, necesita dar acceso al almacenamiento de su teléfono</string>
|
||||
<string name="telepathy_permission_explanation">Para leer y escribir SMS desde su escritorio, necesita dar permisos para SMS</string>
|
||||
<string name="telephony_permission_explanation">Para ver las llamadas telefónicas y SMS desde su escritorio, necesita dar permisos para llamadas telefónicas y SMS</string>
|
||||
<string name="telephony_permission_explanation">Para ver las llamadas telefónicas en el escritorio, necesita dar permisos al registro de llamadas telefónicas y al estado del teléfono</string>
|
||||
<string name="telephony_optional_permission_explanation">Para ver el nombre de un contacto en lugar de un número telefónico, necesita dar acceso a los contactos de su teléfono</string>
|
||||
<string name="contacts_permission_explanation">Para compartir sus contactos con el escritorio, necesita dar permisos de acceso a los mismos</string>
|
||||
<string name="select_ringtone">Seleccionar tono</string>
|
||||
@@ -293,4 +300,5 @@
|
||||
<string name="notification_channel_receivenotification">Notificaciones desde otros dispositivos</string>
|
||||
<string name="take_picture">Lanzar cámara</string>
|
||||
<string name="plugin_photo_desc">Lanzar la aplicación de la cámara para facilitar tomar y transferir imágenes</string>
|
||||
<string name="no_app_for_opening">No se encontró ninguna aplicación adecuada para abrir este archivo</string>
|
||||
</resources>
|
||||
|
@@ -78,13 +78,7 @@
|
||||
<string name="pairing_request_from">Paardumise soov seadmest %1s</string>
|
||||
<string name="received_url_title">Lingi saamine seadmest %1s</string>
|
||||
<string name="received_url_text">Koputa \"%1s\" avamiseks</string>
|
||||
<string name="outgoing_file_title">Faili saatmine seadmesse %1s</string>
|
||||
<string name="outgoing_files_title">Failide saatmine seadmesse %1s</string>
|
||||
<string name="received_file_text">Koputa \"%1s\" avamiseks</string>
|
||||
<string name="sent_file_title">Fail saadeti seadmesse %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Faili saatmine seadmesse %1s nurjus</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Koputa vastamiseks</string>
|
||||
<string name="reconnect">Ühenda uuesti</string>
|
||||
<string name="right_click">Saada paremklõps</string>
|
||||
|
@@ -102,12 +102,6 @@
|
||||
<item quantity="one">Fitxategia: %1s</item>
|
||||
<item quantity="other">(%3$d(e)tik %2$d fitxategia) : %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Fitxategia bidaltzen %1s-ra</string>
|
||||
<string name="outgoing_files_title">Fitxategiak bidaltzen %1s-ra</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Bidali %1$d fitxategia</item>
|
||||
<item quantity="other">Bidali %2$d fitxategitik %1$d</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Fitxategi bat jaso da %1$s-tik</item>
|
||||
<item quantity="other">%2$d fitxategi jaso dira %1$s-tik</item>
|
||||
@@ -118,10 +112,6 @@
|
||||
</plurals>
|
||||
<string name="received_file_text">Tak egin \'%1s\' irekitzeko</string>
|
||||
<string name="cannot_create_file">Ezin da sortu %s fitxategia</string>
|
||||
<string name="sent_file_title">Fitxategia bidalita %1s-ra</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Huts egin du fitxategia %1s(e)ra bidaltzea</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Tak egin erantzuteko</string>
|
||||
<string name="reconnect">Birkonektatu</string>
|
||||
<string name="right_click">Bidali eskumako klik</string>
|
||||
@@ -213,7 +203,6 @@
|
||||
<string name="plugins_need_optional_permission">Plugin batzuek desgaitutako eginbideak dituzte baimenak faltan dituztelako (tak egin informazio gehiagorako):</string>
|
||||
<string name="share_optional_permission_explanation">Zure telefonoa eta mahaigainaren artean fitxategiak partekatzeko telefonoaren biltegiratzea atzitzeko baimena eman behar duzu</string>
|
||||
<string name="telepathy_permission_explanation">SMSak zure mahaigainetik bidali ahal izateko, SMSak erabiltzeko baimena eman behar duzu</string>
|
||||
<string name="telephony_permission_explanation">Telefono deiak eta SMSak zure mahaigainetik ikusteko, telefono deiak eta SMSak erabiltzeko baimena eman behar duzu</string>
|
||||
<string name="telephony_optional_permission_explanation">Telefono zenbakiaren ordez kontaktuaren izena ikusteko telefonoko kontaktuak atzitzeko baimena eman behar duzu</string>
|
||||
<string name="contacts_permission_explanation">Zure kontaktuen liburuak mahaigainarekin partekatzeko, kontaktuetara baimena eman behar duzu</string>
|
||||
<string name="select_ringtone">Hautatu dei-tonu bat</string>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="kde_connect">KDE Connect</string>
|
||||
<string name="foreground_notification_no_devices">Ei laiteyhteyksiä</string>
|
||||
<string name="foreground_notification_no_devices">Ei laiteyhteyttä</string>
|
||||
<string name="foreground_notification_devices">Yhdistetty laitteeseen: %s</string>
|
||||
<string name="pref_plugin_telephony">Puhelinilmoitukset</string>
|
||||
<string name="pref_plugin_telephony_desc">Lähetä ilmoitukset saapuvista puheluista</string>
|
||||
@@ -13,6 +13,7 @@
|
||||
<string name="pref_plugin_clipboard_desc">Jaa leikepöydän sisältö</string>
|
||||
<string name="pref_plugin_mousepad">Kauko-ohjaus</string>
|
||||
<string name="pref_plugin_mousepad_desc">Käytä puhelinta tai tablettia hiirenä ja näppäimistönä</string>
|
||||
<string name="pref_plugin_presenter">Etädiaesitys</string>
|
||||
<string name="pref_plugin_presenter_desc">Käytä laitettasi esitysdiojen vaihtamiseen</string>
|
||||
<string name="pref_plugin_remotekeyboard">Vastaanota etänäppäinpainallukset</string>
|
||||
<string name="pref_plugin_remotekeyboard_desc">Vastaanottaa etälaitteiden näppäinpainallustapahtumat</string>
|
||||
@@ -37,6 +38,7 @@
|
||||
<string name="open_settings">Avaa asetukset</string>
|
||||
<string name="no_permissions">Ilmoitusten näkemiseksi sinun on annettava käyttöoikeus ilmoituksiin</string>
|
||||
<string name="no_permission_mprisreceiver">Hallitaksesi mediasoittimiasi sinun on annettava käyttöoikeudet ilmoituksiin</string>
|
||||
<string name="no_permissions_remotekeyboard">Näppäinpainallusten vastaanottamiseksi KDE Connectin etänäppäimistö on aktivoitava</string>
|
||||
<string name="send_ping">Lähetä tiedustelupaketti</string>
|
||||
<string name="open_mpris_controls">Multimedian ohjaus</string>
|
||||
<string name="remotekeyboard_editing_only_title">Käsittele etänäppäimet vain muokattaessa</string>
|
||||
@@ -102,11 +104,13 @@
|
||||
<item quantity="one">Tiedosto: %1s</item>
|
||||
<item quantity="other">(Tiedosto %2$d / %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Lähetetään tiedostoa laitteeseen %1s</string>
|
||||
<string name="outgoing_files_title">Lähetetään tiedostoa laitteeseen %1s</string>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="one">Lähetetään %1$d tiedosto laitteeseen %2$s</item>
|
||||
<item quantity="other">Lähetetään %1$d tiedostoa laitteeseen %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Lähetetty %1$d tiedosto</item>
|
||||
<item quantity="other">Lähetetty %1$d/%2$d tiedostoa</item>
|
||||
<item quantity="one">Tiedosto: %1$s</item>
|
||||
<item quantity="other">(Tiedosto %2$d/%3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Vastaanotettiin tiedosto lähettäjältä %1$s</item>
|
||||
@@ -116,12 +120,16 @@
|
||||
<item quantity="one">Ei voitu vastaanottaa tiedostoa lähettäjältä %1$s</item>
|
||||
<item quantity="other">Ei voitu vastaanottaa %2$d/%3$d tiedostoa lähettäjältä %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="sent_files_title">
|
||||
<item quantity="one">Tiedosto lähetetty laitteeseen %1$s</item>
|
||||
<item quantity="other">%2$d tiedostoa lähetetty laitteeseen %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="send_files_fail_title">
|
||||
<item quantity="one">Tiedoston lähetys laitteelle %1$s epäonnistui</item>
|
||||
<item quantity="other">%2$d/%3$d tiedoston lähetys laitteelle %1$s epäonnistui</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Avaa ”%1s” napauttamalla</string>
|
||||
<string name="cannot_create_file">Ei voida luoda tiedostoa %s</string>
|
||||
<string name="sent_file_title">Tiedosto lähetetty laitteeseen %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Tiedoston lähetys laitteelle %1s epäonnistui</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Vastaa napauttamalla</string>
|
||||
<string name="reconnect">Yhdistä uudelleen</string>
|
||||
<string name="right_click">Lähetä oikean painikkeen napsautus</string>
|
||||
@@ -168,6 +176,10 @@
|
||||
<string name="unpair_device_action">Poista laitepari %s</string>
|
||||
<string name="custom_device_list">Lisää laitteita IP:llä</string>
|
||||
<string name="delete_custom_device">Poistetaanko %s?</string>
|
||||
<string name="custom_device_deleted">Poistettiin mukautettu laite</string>
|
||||
<string name="custom_device_list_help">Ellei laitetta tunnisteta automaattisesti, sen IP-osoitteen tai konenimen voi lisätä napsauttamalla kelluvaa toimintopainiketta</string>
|
||||
<string name="custom_device_fab_hint">Lisää laite</string>
|
||||
<string name="undo">Kumoa</string>
|
||||
<string name="share_notification_preference">Äänekkäät ilmoitukset</string>
|
||||
<string name="share_notification_preference_summary">Värise ja soita ääni tiedoston saapuessa</string>
|
||||
<string name="share_destination_customize">Vaihda kohdekansio</string>
|
||||
@@ -183,7 +195,28 @@
|
||||
<string name="sftp_sdcard">SD-kortti</string>
|
||||
<string name="sftp_readonly">(vain luku)</string>
|
||||
<string name="sftp_camera">Kamerakuvat</string>
|
||||
<string name="add_device_dialog_title">Lisää laite</string>
|
||||
<string name="add_device_hint">Konenimi tai IP-osoite</string>
|
||||
<string name="sftp_preference_detected_sdcards">Havaitut SD-kortit</string>
|
||||
<string name="sftp_preference_edit_sdcard_title">Muokkaa SD-korttia</string>
|
||||
<string name="sftp_preference_configured_storage_locations">Asetetut tallennustilat</string>
|
||||
<string name="sftp_preference_add_storage_location_title">Lisää tallennustila</string>
|
||||
<string name="sftp_preference_edit_storage_location">Muokkaa tallennustilaa</string>
|
||||
<string name="sftp_preference_add_camera_shortcut">Lisää oikopolku kamerakansioon</string>
|
||||
<string name="sftp_preference_add_camera_shortcut_summary_on">Lisää oikopolku kamerakansioon</string>
|
||||
<string name="sftp_preference_add_camera_shortcut_summary_off">Älä lisää oikopolkua kamerakansioon</string>
|
||||
<string name="sftp_storage_preference_storage_location">Tallennustila</string>
|
||||
<string name="sftp_storage_preference_storage_location_already_configured">Tämä sijainti on jo asetettu</string>
|
||||
<string name="sftp_storage_preference_click_to_select">valitse napsauttamalla</string>
|
||||
<string name="sftp_storage_preference_display_name">Näyttönimi</string>
|
||||
<string name="sftp_storage_preference_display_name_already_used">Tämä näyttönimi on jo käytössä</string>
|
||||
<string name="sftp_storage_preference_display_name_cannot_be_empty">Näyttönimi ei voi olla tyhjä</string>
|
||||
<string name="sftp_action_mode_menu_delete">Poista</string>
|
||||
<string name="sftp_no_sdcard_detected">SD-kortteja ei havaittu</string>
|
||||
<string name="sftp_no_storage_locations_configured">Tallennustiloja ei ole asetettu</string>
|
||||
<string name="sftp_saf_permission_explanation">Tallennustilat on asetettava etätiedostojen käyttämiseksi</string>
|
||||
<string name="add_host">Lisää kone/IP</string>
|
||||
<string name="add_host_hint">Konenimi tai IP</string>
|
||||
<string name="no_players_connected">Soittimia ei löytynyt</string>
|
||||
<string name="mpris_player_on_device">%1$s laitteella %2$s</string>
|
||||
<string name="send_files">Lähetä tiedostoja</string>
|
||||
@@ -213,7 +246,7 @@
|
||||
<string name="plugins_need_optional_permission">Jotkin liitännäisten ominaisuudet eivät ole käytössä puuttuvien käyttöoikeuksien takia (lisätietoa napsauttamalla):</string>
|
||||
<string name="share_optional_permission_explanation">Jakaaksesi tiedostoja puhelimen ja työpöydän välillä sinun on annettava käyttöoikeudet puhelimen tallennustilaan</string>
|
||||
<string name="telepathy_permission_explanation">Lukeaksesi ja lähettääksesi tekstiviestejä työpöydältä sinun on annettava käyttöoikeudet tekstiviesteihin</string>
|
||||
<string name="telephony_permission_explanation">Nähdäksesi soitot ja tekstiviestit työpöydältä sinun on annettava käyttöoikeudet puheluihin ja tekstiviesteihin</string>
|
||||
<string name="telephony_permission_explanation">Puhelujen näyttäminen työpöydällä vaatii käyttöoikeuden puhelulokiin ja puhelimen tilaan</string>
|
||||
<string name="telephony_optional_permission_explanation">Puhelimen yhteystietoihin on annettava käyttöoikeudet, jotta voit nähdä yhteystiedoissa nimet puhelinnumerojen sijaan</string>
|
||||
<string name="contacts_permission_explanation">Käyttääksesi yhteystietoja työpöydältä sinun on annettava yhteystietojen käyttöoikeudet</string>
|
||||
<string name="select_ringtone">Valitse soittoääni</string>
|
||||
@@ -224,6 +257,7 @@
|
||||
<string name="settings_icon_description">Asetuskuvake</string>
|
||||
<string name="presenter_fullscreen">Koko näyttö</string>
|
||||
<string name="presenter_exit">Poistu esitystilasta</string>
|
||||
<string name="presenter_lock_tip">Laitteen voi lukita ja käyttää äänenvoimakkuuspainikkeita edellinen/seuraava dia -painikkeina</string>
|
||||
<string name="add_command">Lisää komento</string>
|
||||
<string name="addcommand_explanation">Komentoja ei ole rekisteröity</string>
|
||||
<string name="addcommand_explanation2">Voit lisätä uusia komentoja KDE Connectin järjestelmäasetuksissa</string>
|
||||
@@ -255,6 +289,7 @@
|
||||
<string name="settings_more_settings_title">Lisää asetuksia</string>
|
||||
<string name="settings_more_settings_text">Laitekohtaiset asetukset löytyvät laitteen ”Liitännäisasetuksista”.</string>
|
||||
<string name="setting_persistent_notification">Näytä pysyvä ilmoitus</string>
|
||||
<string name="setting_persistent_notification_oreo">Pysyvä ilmoitus</string>
|
||||
<string name="extra_options">Lisäasetukset</string>
|
||||
<string name="privacy_options">Yksityisyysasetukset</string>
|
||||
<string name="set_privacy_options">Aseta yksityisyysasetukset</string>
|
||||
@@ -262,4 +297,7 @@
|
||||
<string name="block_contents">Estä ilmoitusten sisältö</string>
|
||||
<string name="block_images">Estä ilmoitusten kuvat</string>
|
||||
<string name="notification_channel_receivenotification">Muiden laitteiden ilmoitukset</string>
|
||||
<string name="take_picture">Käynnistä kamera</string>
|
||||
<string name="plugin_photo_desc">Helpota kuvien ottamista ja siirtämistä käynnistämällä kamerasovellus</string>
|
||||
<string name="no_app_for_opening">Tämän tiedoston avaamiseen sopivaa sovellusta ei löytynyt</string>
|
||||
</resources>
|
||||
|
@@ -13,6 +13,7 @@
|
||||
<string name="pref_plugin_clipboard_desc">Partage le contenu du presse-papiers</string>
|
||||
<string name="pref_plugin_mousepad">Contrôle distant</string>
|
||||
<string name="pref_plugin_mousepad_desc">Utilisez votre téléphone ou tablette comme un pavé tactile et un clavier</string>
|
||||
<string name="pref_plugin_presenter">Télécommande de présentation</string>
|
||||
<string name="pref_plugin_presenter_desc">Utilisez votre appareil pour changer les diapositives d\'une présentation</string>
|
||||
<string name="pref_plugin_remotekeyboard">Recevoir les appuis de touches distants</string>
|
||||
<string name="pref_plugin_remotekeyboard_desc">Recevoir les appuis de touches des périphériques distants</string>
|
||||
@@ -37,10 +38,11 @@
|
||||
<string name="open_settings">Accéder aux paramètres</string>
|
||||
<string name="no_permissions">Vous devez accorder la permission d\'accéder aux notifications</string>
|
||||
<string name="no_permission_mprisreceiver">Pour pouvoir contrôler vos lecteurs multimédia, veuillez permettre l\'accès aux notifications</string>
|
||||
<string name="no_permissions_remotekeyboard">Vous devez activer le Clavier à distance KDE Connect pour recevoir les appuis sur les touches</string>
|
||||
<string name="send_ping">Envoyer un « Ping »</string>
|
||||
<string name="open_mpris_controls">Contrôles multimédia</string>
|
||||
<string name="remotekeyboard_editing_only_title">Gérer les appuis de touches à distance uniquement lors de l\'édition</string>
|
||||
<string name="remotekeyboard_not_connected">Aucune connexion active d\'un clavier sans fil disponible, établissez-en une dans KDE Connect</string>
|
||||
<string name="remotekeyboard_not_connected">Aucune connexion active d\'un clavier à distance disponible, établissez-en une dans KDE Connect</string>
|
||||
<string name="remotekeyboard_connected">La connexion au clavier sans fil est active</string>
|
||||
<string name="remotekeyboard_multiple_connections">Plusieurs connexions à des claviers sans fil sont disponibles, sélectionnez le périphérique à configurer</string>
|
||||
<string name="open_mousepad">Contrôle distant</string>
|
||||
@@ -102,12 +104,6 @@
|
||||
<item quantity="one">Fichier : %1s</item>
|
||||
<item quantity="other">(Fichier %2$d sur %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Envoi d\'un fichier à %1s</string>
|
||||
<string name="outgoing_files_title">Envoi de fichiers à %1s</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">%1$d fichier envoyé</item>
|
||||
<item quantity="other">%1$d fichiers envoyés sur %2$d</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Fichier reçu de %1$s</item>
|
||||
<item quantity="other">%2$d fichiers reçus de %1$s</item>
|
||||
@@ -118,10 +114,6 @@
|
||||
</plurals>
|
||||
<string name="received_file_text">Appuyez pour ouvrir %1s</string>
|
||||
<string name="cannot_create_file">Impossible de créer le fichier %s</string>
|
||||
<string name="sent_file_title">Fichier envoyé à %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Impossible d\'envoyer le fichier à %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Cliquer pour répondre</string>
|
||||
<string name="reconnect">Reconnecter</string>
|
||||
<string name="right_click">Envoyer un clic droit</string>
|
||||
@@ -168,6 +160,10 @@
|
||||
<string name="unpair_device_action">Dissocier %s</string>
|
||||
<string name="custom_device_list">Ajouter des périphériques par IP</string>
|
||||
<string name="delete_custom_device">Supprimer %s ?</string>
|
||||
<string name="custom_device_deleted">Périphérique personnalisé supprimé</string>
|
||||
<string name="custom_device_list_help">Si votre périphérique n\'est pas détecté automatiquement, vous pouvez ajouter son adresse IP ou son nom d\'hôte en cliquant sur le bouton d\'action flottant</string>
|
||||
<string name="custom_device_fab_hint">Ajouter un périphérique</string>
|
||||
<string name="undo">Annuler</string>
|
||||
<string name="share_notification_preference">Notifications sonores</string>
|
||||
<string name="share_notification_preference_summary">Vibrer et jouer un son quand un fichier est reçu</string>
|
||||
<string name="share_destination_customize">Personnaliser le dossier de destination</string>
|
||||
@@ -183,6 +179,26 @@
|
||||
<string name="sftp_sdcard">Carte SD</string>
|
||||
<string name="sftp_readonly">(lecture seule)</string>
|
||||
<string name="sftp_camera">Images de l\'appareil photo</string>
|
||||
<string name="add_device_dialog_title">Ajouter un périphérique</string>
|
||||
<string name="add_device_hint">Nom d\'hôte ou adresse IP</string>
|
||||
<string name="sftp_preference_detected_sdcards">Cartes SD détectées</string>
|
||||
<string name="sftp_preference_edit_sdcard_title">Modifier la carte SD</string>
|
||||
<string name="sftp_preference_configured_storage_locations">Emplacements de stockage configurés</string>
|
||||
<string name="sftp_preference_add_storage_location_title">Ajouter un emplacement de stockage</string>
|
||||
<string name="sftp_preference_edit_storage_location">Modifier un emplacement de stockage</string>
|
||||
<string name="sftp_preference_add_camera_shortcut">Ajouter un raccourci pour le dossier de l\'appareil photo</string>
|
||||
<string name="sftp_preference_add_camera_shortcut_summary_on">Ajouter un raccourci vers le dossier de l\'appareil photo</string>
|
||||
<string name="sftp_preference_add_camera_shortcut_summary_off">Ne pas ajouter de raccourci vers le dossier de l\'appareil photo</string>
|
||||
<string name="sftp_storage_preference_storage_location">Emplacement de stockage</string>
|
||||
<string name="sftp_storage_preference_storage_location_already_configured">Cet emplacement est déjà configuré</string>
|
||||
<string name="sftp_storage_preference_click_to_select">cliquez pour sélectionner</string>
|
||||
<string name="sftp_storage_preference_display_name">Nom d\'affichage</string>
|
||||
<string name="sftp_storage_preference_display_name_already_used">Ce nom d\'affichage est déjà utilisé</string>
|
||||
<string name="sftp_storage_preference_display_name_cannot_be_empty">Impossible de configurer un nom d\'affichage vide</string>
|
||||
<string name="sftp_action_mode_menu_delete">Supprimer</string>
|
||||
<string name="sftp_no_sdcard_detected">Aucune carte SD détectée</string>
|
||||
<string name="sftp_no_storage_locations_configured">Aucun emplacement stockage n\'est configuré</string>
|
||||
<string name="sftp_saf_permission_explanation">Vous devez configurer des emplacements de stockage pour accéder aux fichiers à distance</string>
|
||||
<string name="add_host">Ajouter hôte/IP</string>
|
||||
<string name="add_host_hint">"Nom d\'hôte ou adresse IP "</string>
|
||||
<string name="no_players_connected">Aucun lecteur trouvé</string>
|
||||
@@ -214,7 +230,6 @@
|
||||
<string name="plugins_need_optional_permission">Certaines fonctionnalités de modules externes sont désactivées faute de permissions suffisantes (tapez pour plus d\'informations) :</string>
|
||||
<string name="share_optional_permission_explanation">Pour partager des fichiers entre votre téléphone et votre ordinateur, veuillez permettre l\'accès à la mémoire de stockage du téléphone</string>
|
||||
<string name="telepathy_permission_explanation">Pour lire et écrire des SMS depuis votre ordinateur, veuillez permettre l\'accès aux SMS</string>
|
||||
<string name="telephony_permission_explanation">Pour voir les appels et les SMS depuis votre ordinateur, veuillez permettre l\'accès aux appels et aux SMS</string>
|
||||
<string name="telephony_optional_permission_explanation">Pour voir le nom du contact au lieu du numéro de téléphone, veuillez permettre l\'accès aux contacts du téléphone</string>
|
||||
<string name="contacts_permission_explanation">Pour partager votre carnet de contacts avec votre ordinateur, veuillez permettre l\'accès aux contacts du téléphone</string>
|
||||
<string name="select_ringtone">Sélectionnez une sonnerie</string>
|
||||
@@ -225,6 +240,7 @@
|
||||
<string name="settings_icon_description">Icône des paramètres</string>
|
||||
<string name="presenter_fullscreen">Plein écran</string>
|
||||
<string name="presenter_exit">Quitter la présentation</string>
|
||||
<string name="presenter_lock_tip">Vous pouvez verrouiller votre appareil et utiliser les touches de volume pour passer d\'une diapositive à l\'autre</string>
|
||||
<string name="add_command">Ajouter une commande</string>
|
||||
<string name="addcommand_explanation">Aucune commande enregistrée</string>
|
||||
<string name="addcommand_explanation2">Vous pouvez ajouter de nouvelles commandes dans la configuration système de KDE Connect</string>
|
||||
@@ -265,4 +281,7 @@
|
||||
<string name="block_contents">Bloquer les contenus des notifications</string>
|
||||
<string name="block_images">Bloquer les images des notifications</string>
|
||||
<string name="notification_channel_receivenotification">Notifications provenant d\'autres périphériques</string>
|
||||
<string name="take_picture">Lancer l\'appareil photo</string>
|
||||
<string name="plugin_photo_desc">Lancer l\'application appareil photo pour prendre et transférer des photos</string>
|
||||
<string name="no_app_for_opening">Aucune application adaptée trouvée pour ouvrir ce fichier.</string>
|
||||
</resources>
|
||||
|
@@ -104,11 +104,13 @@
|
||||
<item quantity="one">Ficheiro: %1s</item>
|
||||
<item quantity="other">(Ficheiro %2$d de %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Enviando un ficheiro a %1s</string>
|
||||
<string name="outgoing_files_title">Enviando os ficheiros a %1s</string>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="one">Enviando %1$d ficheiro a %2$s</item>
|
||||
<item quantity="other">Enviando %1$d ficheiros a %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Enviouse %1$d ficheiro.</item>
|
||||
<item quantity="other">Enviáronse %1$d de %2$d ficheiros.</item>
|
||||
<item quantity="one">Ficheiro: %1$s</item>
|
||||
<item quantity="other">(Ficheiro %2$d de %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Recibiuse un ficheiro de %1$s</item>
|
||||
@@ -118,12 +120,16 @@
|
||||
<item quantity="one">A recepción do ficheiro de %1$s fallou</item>
|
||||
<item quantity="other">A recepción de %2$d de %3$d ficheiros de %1$s fallou</item>
|
||||
</plurals>
|
||||
<plurals name="sent_files_title">
|
||||
<item quantity="one">Enviouse o ficheiro a %1$s</item>
|
||||
<item quantity="other">Enviáronse os %2$d ficheiros a %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="send_files_fail_title">
|
||||
<item quantity="one">Non se puido enviar o ficheiro a %1$s</item>
|
||||
<item quantity="other">Non se puideron enviar %2$d dos %3$d ficheiros a %1$s</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Toque para abrir «%1s».</string>
|
||||
<string name="cannot_create_file">Non se pode crear o ficheiro %s</string>
|
||||
<string name="sent_file_title">Enviouse o ficheiro a %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Non se puido enviar o ficheiro a %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Toque para contestar</string>
|
||||
<string name="reconnect">Conectar de novo</string>
|
||||
<string name="right_click">Enviar un clic secundario</string>
|
||||
@@ -240,7 +246,7 @@
|
||||
<string name="plugins_need_optional_permission">Algúns complementos teñen funcionalidades desactivadas por mor dunha falta de permisos (toque para máis información):</string>
|
||||
<string name="share_optional_permission_explanation">Para compartir ficheiros entre o teléfono e o escritorio ten que dar acceso ao almacenamento do teléfono.</string>
|
||||
<string name="telepathy_permission_explanation">Para ler e escribir SMS desde o escritorio ten que dar permiso de SMS.</string>
|
||||
<string name="telephony_permission_explanation">Para ver as chamadas de teléfono e os SMS desde o escritorio ten que dar permiso a chamadas de teléfono e a SMS.</string>
|
||||
<string name="telephony_permission_explanation">Para ver as chamadas de teléfono no escritorio ten que dar permiso aos rexistros de chamadas telefónicas e ao estado do teléfono</string>
|
||||
<string name="telephony_optional_permission_explanation">Para ver o nome dun contacto en vez dun número de teléfono ten que dar acceso aos contactos do teléfono.</string>
|
||||
<string name="contacts_permission_explanation">Para compartir o caderno de contactos co escritorio ten que dar permiso de contactos</string>
|
||||
<string name="select_ringtone">Seleccione un son de chamada</string>
|
||||
@@ -294,4 +300,5 @@
|
||||
<string name="notification_channel_receivenotification">Notificacións desde outros dispositivos</string>
|
||||
<string name="take_picture">Iniciar a cámara</string>
|
||||
<string name="plugin_photo_desc">Iniciar o aplicativo da cámara para facilitar sacar e transferir imaxes</string>
|
||||
<string name="no_app_for_opening">Non se atopou ningún aplicativo axeitado para abrir este ficheiro</string>
|
||||
</resources>
|
||||
|
@@ -84,13 +84,7 @@
|
||||
<string name="pairing_request_from">בוקשה התאמה מ־%1s</string>
|
||||
<string name="received_url_title">התקבל קישור מ־%1s</string>
|
||||
<string name="received_url_text">לחץ כדי לפתוח את \"%1s\"</string>
|
||||
<string name="outgoing_file_title">שולח קובץ אל %1s</string>
|
||||
<string name="outgoing_files_title">שולח קובצים אל %1s</string>
|
||||
<string name="received_file_text">לחץ כדי לפתוח את \"%1s\"</string>
|
||||
<string name="sent_file_title">הקובץ נשלח אל %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">נכשל בשליחת הקובץ אל %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">לחץ כדי לענות</string>
|
||||
<string name="reconnect">התחבר מחדש</string>
|
||||
<string name="right_click">שלח לחיצה ימנית</string>
|
||||
|
@@ -10,7 +10,7 @@
|
||||
<string name="pref_plugin_sftp">Menyingkap sistem file</string>
|
||||
<string name="pref_plugin_sftp_desc">Membolehkan menelusuri sistem file perangkat ini secara jarak jauh</string>
|
||||
<string name="pref_plugin_clipboard">Sinkron clipboard</string>
|
||||
<string name="pref_plugin_clipboard_desc">Berbagi isi clipboard</string>
|
||||
<string name="pref_plugin_clipboard_desc">Berbagi konten clipboard</string>
|
||||
<string name="pref_plugin_mousepad">Input jarak jauh</string>
|
||||
<string name="pref_plugin_mousepad_desc">Gunakan telepon atau tabletmu sebagai touchpad dan keyboard</string>
|
||||
<string name="pref_plugin_presenter_desc">Gunakan perangkatmu untuk mengubah slide dalam sebuah presentasi</string>
|
||||
@@ -99,11 +99,6 @@
|
||||
<plurals name="incoming_files_text">
|
||||
<item quantity="other">File: %1s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Mengirim file ke %1s</string>
|
||||
<string name="outgoing_files_title">Mengirim file ke %1s</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="other">Kirimkan file %1$d</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="other">File yang diterima dari %1$s</item>
|
||||
</plurals>
|
||||
@@ -112,10 +107,6 @@
|
||||
</plurals>
|
||||
<string name="received_file_text">Ketuk untuk membuka \'%1s\'</string>
|
||||
<string name="cannot_create_file">Gak bisa menciptakan file %s</string>
|
||||
<string name="sent_file_title">Kirim file ke %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Gagal mengirim file ke %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Ketuk untuk menjawab</string>
|
||||
<string name="reconnect">Sambung-ulang</string>
|
||||
<string name="right_click">Kirim Klik Kanan</string>
|
||||
@@ -206,7 +197,6 @@
|
||||
<string name="plugins_need_optional_permission">Beberapa plugin yang memiliki fitur dinonfungsikan karena kurangnya perizinan (ketuk untuk info selebihnya):</string>
|
||||
<string name="share_optional_permission_explanation">Untuk membagikan file antara teleponmu dan desktopmu kamu harus memberikan akses ke penyimpanan teleponmu</string>
|
||||
<string name="telepathy_permission_explanation">Untuk membaca dan menulis SMS dari desktopmu kamu harus memberikan perizinan untuk SMS</string>
|
||||
<string name="telephony_permission_explanation">Untuk melihat paggian telepon dan SMS dari desktopmu kamu harus memberikan perizinan untuk panggilan telepon dan SMS</string>
|
||||
<string name="telephony_optional_permission_explanation">Untuk melihat nama kontak alih-alih nomor telepon, kamu harus memberikan akses ke kontak telepon</string>
|
||||
<string name="contacts_permission_explanation">Untuk membagikan buku kontak dengan desktopmu, kamu harus memberikan perizinan kontak</string>
|
||||
<string name="select_ringtone">Pilih sebuah ringtone</string>
|
||||
|
@@ -38,6 +38,7 @@
|
||||
<string name="open_settings">Apri impostazioni</string>
|
||||
<string name="no_permissions">Devi concedere i permessi per l\'accesso alle notifiche</string>
|
||||
<string name="no_permission_mprisreceiver">Per poter controllare i tuoi lettori multimediali devi accordare l\'accesso alle notifiche</string>
|
||||
<string name="no_permissions_remotekeyboard">Per ricevere pressioni di tasti, devi attivare la tastiera remota di KDE Connect</string>
|
||||
<string name="send_ping">Invia ping</string>
|
||||
<string name="open_mpris_controls">Controllo multimediale</string>
|
||||
<string name="remotekeyboard_editing_only_title">Gestisci i tasti remoti solo durante la modifica</string>
|
||||
@@ -103,12 +104,6 @@
|
||||
<item quantity="one">File: %1s</item>
|
||||
<item quantity="other">(File %2$d di %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Invio file a %1s</string>
|
||||
<string name="outgoing_files_title">Invio file a %1s</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Inviato %1$d file</item>
|
||||
<item quantity="other">Inviati %1$d di %2$d file</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">File ricevuto da %1$s</item>
|
||||
<item quantity="other">Ricevuti %2$d file da %1$s</item>
|
||||
@@ -119,10 +114,6 @@
|
||||
</plurals>
|
||||
<string name="received_file_text">Tocca per aprire «%1s»</string>
|
||||
<string name="cannot_create_file">Impossibile creare il file %s</string>
|
||||
<string name="sent_file_title">File inviato a %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Invio del file a %1s non riuscito</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Tocca per rispondere</string>
|
||||
<string name="reconnect">Riconnetti</string>
|
||||
<string name="right_click">Invia clic tasto destro</string>
|
||||
@@ -190,7 +181,26 @@
|
||||
<string name="sftp_camera">Immagini fotocamera</string>
|
||||
<string name="add_device_dialog_title">Aggiungi dispositivo</string>
|
||||
<string name="add_device_hint">Nome host o indirizzo IP</string>
|
||||
<string name="sftp_preference_detected_sdcards">Schede SD rilevate</string>
|
||||
<string name="sftp_preference_edit_sdcard_title">Modifica scheda SD</string>
|
||||
<string name="sftp_preference_configured_storage_locations">Posizioni di archiviazione configurate</string>
|
||||
<string name="sftp_preference_add_storage_location_title">Aggiungi posizione di archiviazione</string>
|
||||
<string name="sftp_preference_edit_storage_location">Modifica posizione di archiviazione</string>
|
||||
<string name="sftp_preference_add_camera_shortcut">Aggiungi scorciatoia alla cartella della fotocamera</string>
|
||||
<string name="sftp_preference_add_camera_shortcut_summary_on">Aggiungi una scorciatoia alla cartella della fotocamera</string>
|
||||
<string name="sftp_preference_add_camera_shortcut_summary_off">Non aggiungere una scorciatoia alla cartella della fotocamera</string>
|
||||
<string name="sftp_storage_preference_storage_location">Posizione di archiviazione</string>
|
||||
<string name="sftp_storage_preference_storage_location_already_configured">Questa posizione è già stata configurata</string>
|
||||
<string name="sftp_storage_preference_click_to_select">clic per selezionare</string>
|
||||
<string name="sftp_storage_preference_display_name">Nome visualizzato</string>
|
||||
<string name="sftp_storage_preference_display_name_already_used">Il nome visualizzato è già in uso</string>
|
||||
<string name="sftp_storage_preference_display_name_cannot_be_empty">Il nome visualizzato non può essere vuoto</string>
|
||||
<string name="sftp_action_mode_menu_delete">Elimina</string>
|
||||
<string name="sftp_no_sdcard_detected">Nessuna scheda SD rilevata</string>
|
||||
<string name="sftp_no_storage_locations_configured">Nessuna posizione di archiviazione configurata</string>
|
||||
<string name="sftp_saf_permission_explanation">Per accedere da remoto ai file, devi configurare posizioni di archiviazione</string>
|
||||
<string name="add_host">Aggiungi host/IP</string>
|
||||
<string name="add_host_hint">Nome host o IP</string>
|
||||
<string name="no_players_connected">Nessun lettore trovato</string>
|
||||
<string name="mpris_player_on_device">%1$s su %2$s</string>
|
||||
<string name="send_files">Invia file</string>
|
||||
@@ -220,7 +230,6 @@
|
||||
<string name="plugins_need_optional_permission">Alcune estensioni hanno funzioni disabilitate per una mancanza di permessi (tocca per maggiori informazioni):</string>
|
||||
<string name="share_optional_permission_explanation">Per condividere i file tra il telefono e il tuo desktop devi dare accesso alla memoria del telefono</string>
|
||||
<string name="telepathy_permission_explanation">Per leggere e scrivere SMS dal tuo desktop, devi concedere l\'autorizzazione per SMS</string>
|
||||
<string name="telephony_permission_explanation">Per vedere le chiamate telefoniche e gli SMS dal desktop devi dare l\'autorizzazione per telefonate e SMS</string>
|
||||
<string name="telephony_optional_permission_explanation">Per vedere il nome di un contatto invece del numero di telefono devi dare accesso alla rubrica del telefono</string>
|
||||
<string name="contacts_permission_explanation">Per condividere la tua rubrica con il desktop, devi concedere l\'autorizzazione per i contatti</string>
|
||||
<string name="select_ringtone">Seleziona una suoneria</string>
|
||||
@@ -272,4 +281,7 @@
|
||||
<string name="block_contents">Blocca i contenuti delle notifiche</string>
|
||||
<string name="block_images">Blocca le immagini nelle notifiche</string>
|
||||
<string name="notification_channel_receivenotification">Notifiche da altri dispositivi</string>
|
||||
<string name="take_picture">Avvia fotocamera</string>
|
||||
<string name="plugin_photo_desc">Avvia l\'applicazione della fotocamera per scattare e trasferire foto con facilità</string>
|
||||
<string name="no_app_for_opening">Nessuna applicazione appropriata trovata per aprire questo file</string>
|
||||
</resources>
|
||||
|
@@ -90,16 +90,7 @@
|
||||
<string name="pairing_request_from">%1s에서 연결 요청</string>
|
||||
<string name="received_url_title">%1s에서 링크 받음</string>
|
||||
<string name="received_url_text">\'%1s\'을(를) 열려면 누르십시오</string>
|
||||
<string name="outgoing_file_title">%1s(으)로 파일 보내는 중</string>
|
||||
<string name="outgoing_files_title">%1s(으)로 파일 보내는 중</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="other">파일 %2$d개 중 %1$d개 보냄</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">\'%1s\'을(를) 열려면 누르십시오</string>
|
||||
<string name="sent_file_title">%1s(으)로 파일 보냄</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">파일을 %1s(으)로 보낼 수 없음</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">눌러서 응답하기</string>
|
||||
<string name="reconnect">다시 연결</string>
|
||||
<string name="right_click">오른쪽 단추 누름 신호 보내기</string>
|
||||
@@ -186,7 +177,6 @@
|
||||
<string name="plugins_need_optional_permission">일부 플러그인은 권한이 없어서 비활성화되었습니다(정보를 보려면 누르기):</string>
|
||||
<string name="share_optional_permission_explanation">휴대폰과 데스크톱간 파일을 공유하려면 휴대폰 저장소 접근 권한이 필요합니다</string>
|
||||
<string name="telepathy_permission_explanation">데스크톱에서 문자 메시지를 읽고 보내려면 문자 메시지 접근 권한이 필요합니다</string>
|
||||
<string name="telephony_permission_explanation">데스크톱에서 통화와 문자 메시지를 보려면 통화 및 문자 메시지 접근 권한이 필요합니다</string>
|
||||
<string name="telephony_optional_permission_explanation">전화번호 대신 연락처에 등록된 이름을 보려면 주소록 접근 권한이 필요합니다</string>
|
||||
<string name="contacts_permission_explanation">데스크톱과 주소록을 공유하려면 주소록 접근 권한이 필요합니다</string>
|
||||
<string name="select_ringtone">벨소리 선택</string>
|
||||
|
@@ -104,11 +104,13 @@
|
||||
<item quantity="one">Bestand: %1s</item>
|
||||
<item quantity="other">(Bestand %2$d van %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Bestand wordt verzonden naar %1s</string>
|
||||
<string name="outgoing_files_title">Bezig bestanden te verzenden naar %1s</string>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="one">%1$d bestand wordt naar %2$s verzonden</item>
|
||||
<item quantity="other">%1$d bestanden worden naar %2$s verzonden</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Bestand %1$d verzenden</item>
|
||||
<item quantity="other">%1$d bestanden uit %2$d verzenden</item>
|
||||
<item quantity="one">Bestand: %1$s</item>
|
||||
<item quantity="other">(Bestand %2$d van %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Ontvangen van bestand vanaf %1$s</item>
|
||||
@@ -118,12 +120,16 @@
|
||||
<item quantity="one">Ontvangen van bestand vanaf %1$s is mislukt</item>
|
||||
<item quantity="other">Ontvangen van %2$d van %3$d bestanden vanaf %1$s is mislukt</item>
|
||||
</plurals>
|
||||
<plurals name="sent_files_title">
|
||||
<item quantity="one">Bestand verzonden naar %1$s</item>
|
||||
<item quantity="other">%2$d bestanden verzonden naar %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="send_files_fail_title">
|
||||
<item quantity="one">Verzenden van bestand naar %1$s is mislukt</item>
|
||||
<item quantity="other">Verzenden van %2$d van %3$d bestanden naar %1$s is mislukt</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Tap om \'%1s\' te openen</string>
|
||||
<string name="cannot_create_file">Kan bestand %s niet aanmaken</string>
|
||||
<string name="sent_file_title">Bestand verzonden naar %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Verzenden van bestand naar %1s is mislukt</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Tap om te antwoorden</string>
|
||||
<string name="reconnect">Opnieuw verbinden</string>
|
||||
<string name="right_click">Verstuur een rechter muisklik</string>
|
||||
@@ -240,7 +246,7 @@
|
||||
<string name="plugins_need_optional_permission">Sommige plug-ins hebben functies uitgeschakeld vanwege ontbrekende toestemming (tik voor meer informatie):</string>
|
||||
<string name="share_optional_permission_explanation">Om bestanden tussen uw telefoon en uw bureaublad te delen moet u toegang geven tot de opslag van uw telefoon</string>
|
||||
<string name="telepathy_permission_explanation">Om een SMS te lezen of te schrijven vanaf uw bureaublad moet u toestemming geven tot SMS</string>
|
||||
<string name="telephony_permission_explanation">Om telefoonoproepen en SMS te zien vanaf het bureaublad moet u toestemming geven tot telefoonoproepen en SMS</string>
|
||||
<string name="telephony_permission_explanation">Om telefoonoproepen te zien op het bureaublad moet u toestemming geven tot telefoonoproeplogs en telefoonstatus</string>
|
||||
<string name="telephony_optional_permission_explanation">Om een naam van een contactpersoon te zien in plaats van een telefoonnummer moet u toegang geven tot de contacten in uw telefoon</string>
|
||||
<string name="contacts_permission_explanation">Om uw contactpersoennboek te delen met het bureaublad moet u contacten rechten geven</string>
|
||||
<string name="select_ringtone">Selecteer een ringtone</string>
|
||||
@@ -294,4 +300,5 @@
|
||||
<string name="notification_channel_receivenotification">Meldingen van andere apparaten</string>
|
||||
<string name="take_picture">Start camera</string>
|
||||
<string name="plugin_photo_desc">Start de camera-app om nemen en overdragen van afbeeldingen te vergemakkelijken</string>
|
||||
<string name="no_app_for_opening">Geen geschikte toepassing gevonden om dit bestand te openen</string>
|
||||
</resources>
|
||||
|
@@ -13,6 +13,7 @@
|
||||
<string name="pref_plugin_clipboard_desc">Del innhaldet på utklippstavla</string>
|
||||
<string name="pref_plugin_mousepad">Fjernstyring</string>
|
||||
<string name="pref_plugin_mousepad_desc">Bruk telefonen eller nettbrettet som styreplate og tastatur</string>
|
||||
<string name="pref_plugin_presenter">Fjernstyring av lysbiletvising</string>
|
||||
<string name="pref_plugin_presenter_desc">Bruk eininga til å byta lysbilete i presentasjonar</string>
|
||||
<string name="pref_plugin_remotekeyboard">Ta imot eksterne tastetrykk</string>
|
||||
<string name="pref_plugin_remotekeyboard_desc">Ta imot tastetrykk frå eksterne einingar</string>
|
||||
@@ -24,10 +25,10 @@
|
||||
<string name="pref_plugin_contacts_desc">Tillat synkronisering av adresseboka på eininga</string>
|
||||
<string name="pref_plugin_ping">Ping</string>
|
||||
<string name="pref_plugin_ping_desc">Send og ta imot pingsignal</string>
|
||||
<string name="pref_plugin_notifications">Varslingssynkronisering</string>
|
||||
<string name="pref_plugin_notifications">Synkroniser varslingar</string>
|
||||
<string name="pref_plugin_notifications_desc">Få tilgang til varslingar frå andre einingar</string>
|
||||
<string name="pref_plugin_receive_notifications">Få varslingar</string>
|
||||
<string name="pref_plugin_receive_notifications_desc">Få varslingar frå den andre eininga og vis dei i Android</string>
|
||||
<string name="pref_plugin_receive_notifications">Ta imot varslingar</string>
|
||||
<string name="pref_plugin_receive_notifications_desc">Ta imot varslingar frå den andre eininga og vis dei i Android</string>
|
||||
<string name="pref_plugin_sharereceiver">Del og ta imot</string>
|
||||
<string name="pref_plugin_sharereceiver_desc">Del filer og nettadresser mellom einingar</string>
|
||||
<string name="plugin_not_available">Denne funksjonen er ikkje tilgjengeleg i din Android-versjon</string>
|
||||
@@ -37,6 +38,7 @@
|
||||
<string name="open_settings">Opna innstillingar</string>
|
||||
<string name="no_permissions">Du må gje tilgang til lesing av varslingar</string>
|
||||
<string name="no_permission_mprisreceiver">Du må gje tilgang til varslingar for å kunne kontrollera mediespelarar</string>
|
||||
<string name="no_permissions_remotekeyboard">For å ta imot tastetrykk må du slå på fjerntastaturet i KDE Connect</string>
|
||||
<string name="send_ping">Send pingsignal</string>
|
||||
<string name="open_mpris_controls">Mediekontroll</string>
|
||||
<string name="remotekeyboard_editing_only_title">Handter eksterne tastetrykk berre ved redigering</string>
|
||||
@@ -72,7 +74,7 @@
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Tilkopla einingar</string>
|
||||
<string name="category_not_paired_devices">Tilgjengelege einingar</string>
|
||||
<string name="category_remembered_devices">Hugs einingar</string>
|
||||
<string name="category_remembered_devices">Hugsa einingar</string>
|
||||
<string name="device_menu_plugins">Programtillegg-oppsett</string>
|
||||
<string name="device_menu_unpair">Løys paring</string>
|
||||
<string name="device_not_reachable">Får ikkje kontakt med para eining</string>
|
||||
@@ -102,11 +104,13 @@
|
||||
<item quantity="one">Fil: %1s</item>
|
||||
<item quantity="other">(Fil %2$d av %3$d): %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Sender fil til %1s</string>
|
||||
<string name="outgoing_files_title">Sender filer til %1s</string>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="one">Sender %1$d fil til %2$s</item>
|
||||
<item quantity="other">Sender %1$d filer til %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Sende %1$d fil</item>
|
||||
<item quantity="other">Sende %1$d av %2$d filer</item>
|
||||
<item quantity="one">Fil: %1$s</item>
|
||||
<item quantity="other">(Fil %2$d av %3$d): %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Fekk fil frå %1$s</item>
|
||||
@@ -116,12 +120,16 @@
|
||||
<item quantity="one">Klarte ikkje ta imot fil frå %1$s</item>
|
||||
<item quantity="other">Klarte ikkje ta imot %2$d av %3$d filer frå %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="sent_files_title">
|
||||
<item quantity="one">Sende fil til %1$s</item>
|
||||
<item quantity="other">Sende %2$d filer til %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="send_files_fail_title">
|
||||
<item quantity="one">Klarte ikkje senda fil til %1$s</item>
|
||||
<item quantity="other">Klarte ikkje senda %2$d av %3$d filer til %1$s</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Trykk for å opna «%1s»</string>
|
||||
<string name="cannot_create_file">Klarte ikkje oppretta fila %s</string>
|
||||
<string name="sent_file_title">Send fil til %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Klarte ikkje senda fil til %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Trykk for å svara</string>
|
||||
<string name="reconnect">Kopla til på nytt</string>
|
||||
<string name="right_click">Send høgreklikk</string>
|
||||
@@ -143,7 +151,7 @@
|
||||
<string name="mpris_volume">Lydstyrke</string>
|
||||
<string name="mpris_settings">Medieinnstillingar</string>
|
||||
<string name="mpris_time_settings_title">Spoleknappar</string>
|
||||
<string name="mpris_time_settings_summary">Juster tida for spoling ved trykking</string>
|
||||
<string name="mpris_time_settings_summary">Juster kor langt spoleknappane skal spola</string>
|
||||
<string-array name="mpris_time_entries">
|
||||
<item>10 sekund</item>
|
||||
<item>20 sekund</item>
|
||||
@@ -168,8 +176,12 @@
|
||||
<string name="unpair_device_action">Løys paring for %s</string>
|
||||
<string name="custom_device_list">Legg til eining basert på IP</string>
|
||||
<string name="delete_custom_device">Vil du sletta %s?</string>
|
||||
<string name="custom_device_deleted">Tilpassa eining er sletta</string>
|
||||
<string name="custom_device_list_help">Viss eininga ikkje vert funnen automatisk, kan du leggja til IP-adressa eller vertsnamnet til eininga ved å trykka på handlingsknappen</string>
|
||||
<string name="custom_device_fab_hint">Legg til eining</string>
|
||||
<string name="undo">Angra</string>
|
||||
<string name="share_notification_preference">Lydvarsling</string>
|
||||
<string name="share_notification_preference_summary">Vibrer og spel ein lyd ved mottak av fil</string>
|
||||
<string name="share_notification_preference_summary">Vibrer og spel ein lyd ved mottak av filer</string>
|
||||
<string name="share_destination_customize">Sjølvvald målmappe</string>
|
||||
<string name="share_destination_customize_summary_disabled">Mottekne filer vert lagra i nedlastingsmappa</string>
|
||||
<string name="share_destination_customize_summary_enabled">Mottekne filer vert lagra i mappa nedanfor</string>
|
||||
@@ -183,7 +195,28 @@
|
||||
<string name="sftp_sdcard">SD-kort</string>
|
||||
<string name="sftp_readonly">(skriveverna)</string>
|
||||
<string name="sftp_camera">Kamerabilete</string>
|
||||
<string name="add_device_dialog_title">Legg til eining</string>
|
||||
<string name="add_device_hint">Vertsnamn/IP-adresse</string>
|
||||
<string name="sftp_preference_detected_sdcards">Oppdaga SD-kort</string>
|
||||
<string name="sftp_preference_edit_sdcard_title">Rediger SD-kort</string>
|
||||
<string name="sftp_preference_configured_storage_locations">Oppsette lagringsområde</string>
|
||||
<string name="sftp_preference_add_storage_location_title">Legg til lagringsområde</string>
|
||||
<string name="sftp_preference_edit_storage_location">Rediger lagringsområde</string>
|
||||
<string name="sftp_preference_add_camera_shortcut">Legg til snarveg til kameramappe</string>
|
||||
<string name="sftp_preference_add_camera_shortcut_summary_on">Legg til ein snarveg til kameramappa</string>
|
||||
<string name="sftp_preference_add_camera_shortcut_summary_off">Ikkje legg til ein snarveg til kameramappa</string>
|
||||
<string name="sftp_storage_preference_storage_location">Lagringsområde</string>
|
||||
<string name="sftp_storage_preference_storage_location_already_configured">Dette lagringsområdet er alt sett opp</string>
|
||||
<string name="sftp_storage_preference_click_to_select">trykk for å velja</string>
|
||||
<string name="sftp_storage_preference_display_name">Visingsnamn</string>
|
||||
<string name="sftp_storage_preference_display_name_already_used">Dette visingsnamnet er alt i bruk</string>
|
||||
<string name="sftp_storage_preference_display_name_cannot_be_empty">Visiningsnamnet kan ikkje vera tomt</string>
|
||||
<string name="sftp_action_mode_menu_delete">Slett</string>
|
||||
<string name="sftp_no_sdcard_detected">Fann ikkje noko SD-kort</string>
|
||||
<string name="sftp_no_storage_locations_configured">Ingen lagringsområde er sette opp</string>
|
||||
<string name="sftp_saf_permission_explanation">For å få tilgang til over nettet må du setja opp lagringsområde</string>
|
||||
<string name="add_host">Legg til vert/IP</string>
|
||||
<string name="add_host_hint">Vertsnamn/IP-adresse</string>
|
||||
<string name="no_players_connected">Fann ingen spelarar</string>
|
||||
<string name="mpris_player_on_device">%1$s på %2$s</string>
|
||||
<string name="send_files">Send filer</string>
|
||||
@@ -210,20 +243,21 @@
|
||||
<string name="plugins_need_permission">Nokre av tillegga treng utvida løyva for å fungera (trykk på dei for meir informasjon):</string>
|
||||
<string name="permission_explanation">Dette tillegget treng utvida løyve for å fungera</string>
|
||||
<string name="optional_permission_explanation">Du må gje utvida løyve for at alle funksjonane skal fungera</string>
|
||||
<string name="plugins_need_optional_permission">På grunn av manglande løyve har nokre av tillegga funksjonar slåtte av (trykk på dei for meir informasjon):</string>
|
||||
<string name="plugins_need_optional_permission">På grunn av manglande løyve vil desse funksjonane ikkje verka (trykk på dei for meir informasjon):</string>
|
||||
<string name="share_optional_permission_explanation">For å kunna dela filer mellom telefonen og datamaskina må du gje appen lese- og skriveløyve til lagringsområdet på telefonen</string>
|
||||
<string name="telepathy_permission_explanation">For å kunna lesa og skriva tekstmeldingar frå datamaskina må du gje appen tilgang til SMS</string>
|
||||
<string name="telephony_permission_explanation">For å kunna sjå telefonsamtalar og tekstmeldingar frå datamaskina må du gje appen tilgang til telefon- og SMS-funksjonar</string>
|
||||
<string name="telephony_permission_explanation">For å kunna sjå telefonsamtalar på datamaskina må du gje appen tilgang til samtaleloggar og telefonstatus</string>
|
||||
<string name="telephony_optional_permission_explanation">For å kunna sjå namn på kontaktar i staden for berre telefonnummeret må du gje appen tilgang til kontaktlista di</string>
|
||||
<string name="contacts_permission_explanation">For å kunna dela adresseboka di med datamaskina må du gje appen lese- og skriveløyve til adresseboka</string>
|
||||
<string name="select_ringtone">Vel ringjetone</string>
|
||||
<string name="telephony_pref_blocked_title">Blokkerte nummer</string>
|
||||
<string name="telephony_pref_blocked_dialog_desc">Ikkje vis oppringingar og SMS-ar frå desse telefonnummera. Skriv inn eitt telefonnummer per linje.</string>
|
||||
<string name="telephony_pref_blocked_dialog_desc">Ikkje vis oppringingar eller SMS-ar frå desse telefonnummera. Skriv inn eitt telefonnummer per linje.</string>
|
||||
<string name="mpris_coverart_description">Omslagsbilete til gjeldande mediefil</string>
|
||||
<string name="device_icon_description">Einingsikon</string>
|
||||
<string name="settings_icon_description">Innstillingsikon</string>
|
||||
<string name="presenter_fullscreen">Fullskjerm</string>
|
||||
<string name="presenter_exit">Avslutt presentasjon</string>
|
||||
<string name="presenter_lock_tip">Du kan låsa eininga og bruka lydstyrke-knappane som førre/neste-knappar</string>
|
||||
<string name="add_command">Legg til kommando</string>
|
||||
<string name="addcommand_explanation">Ingen kommandoar er registrerte</string>
|
||||
<string name="addcommand_explanation2">Du kan leggja til nye kommandoar i systeminnstillingane til KDE Connect</string>
|
||||
@@ -264,4 +298,7 @@
|
||||
<string name="block_contents">Blokker varslingsinnhald</string>
|
||||
<string name="block_images">Blokker bilete i varslingar</string>
|
||||
<string name="notification_channel_receivenotification">Varslingar frå andre einingar</string>
|
||||
<string name="take_picture">Opna kamera</string>
|
||||
<string name="plugin_photo_desc">Opna kamera-appen for å gjera det lettare å ta og overføra bilete</string>
|
||||
<string name="no_app_for_opening">Fann ikkje nokon app som kan opna denne fila</string>
|
||||
</resources>
|
||||
|
@@ -106,14 +106,6 @@
|
||||
<item quantity="many">(Plik %2$d z %3$d) : %1$s</item>
|
||||
<item quantity="other"/>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Wysyłanie pliku do %1s</string>
|
||||
<string name="outgoing_files_title">Wysyłanie pliku do %1s</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Wysłano %1$d plik</item>
|
||||
<item quantity="few">Wysłano %1$d z %2$d plików</item>
|
||||
<item quantity="many">Wysłano %1$d z %2$d plików</item>
|
||||
<item quantity="other"/>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Odebrano plik od %1$s</item>
|
||||
<item quantity="few">Odebrano %2$d pliki od %1$s</item>
|
||||
@@ -128,10 +120,6 @@
|
||||
</plurals>
|
||||
<string name="received_file_text">Stuknij, aby otworzyć \'%1s\'</string>
|
||||
<string name="cannot_create_file">Nie można utworzyć pliku %s</string>
|
||||
<string name="sent_file_title">Plik wysłano do %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Nie udało się wysyłanie pliku do %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Stuknij, aby odpowiedzieć</string>
|
||||
<string name="reconnect">Połącz ponownie</string>
|
||||
<string name="right_click">Wyślij naciskając prawym</string>
|
||||
@@ -223,7 +211,6 @@
|
||||
<string name="plugins_need_optional_permission">Niektóre z wtyczek mają ograniczone możliwości ze względu na ograniczone uprawnienia (stuknij po więcej informacji)</string>
|
||||
<string name="share_optional_permission_explanation">Aby udostępniać pliki z twojego telefonu na twoim komputerze musisz pozowolić na dostęp do pamięci telefonu</string>
|
||||
<string name="telepathy_permission_explanation">Aby odczytywać i pisać SMSy z twojego komputera musisz nadać uprawnienia do SMSów</string>
|
||||
<string name="telephony_permission_explanation">Aby widzieć rozmowy telefoniczne i SMSy z twojego komputera musisz nadać uprawnienia na rozmowy telefoniczne i SMSy</string>
|
||||
<string name="telephony_optional_permission_explanation">Aby widzieć nazwę kontaktu zamiast numeru telefonu musisz pozwolić na dostęp do kontaktów telefonu</string>
|
||||
<string name="contacts_permission_explanation">Aby współdzielić swoją książkę adresową z komputerem musisz udzielić uprawnień do kontaktów</string>
|
||||
<string name="select_ringtone">Wybierz dzwonek</string>
|
||||
|
@@ -38,6 +38,7 @@
|
||||
<string name="open_settings">Abrir configurações</string>
|
||||
<string name="no_permissions">Você precisa conceder permissão para acessar as notificações</string>
|
||||
<string name="no_permission_mprisreceiver">Para ser possível controlar os seus leitores multimídia é necessário conceder acesso às notificações</string>
|
||||
<string name="no_permissions_remotekeyboard">Para receber eventos de teclado é preciso ativar o Teclado Remoto do KDE Connect</string>
|
||||
<string name="send_ping">Enviar ping</string>
|
||||
<string name="open_mpris_controls">Controle multimídia</string>
|
||||
<string name="remotekeyboard_editing_only_title">Lidar com as teclas remotas apenas na edição</string>
|
||||
@@ -103,11 +104,13 @@
|
||||
<item quantity="one">Arquivo: %1s</item>
|
||||
<item quantity="other">(Arquivo %2$d de %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Enviando arquivo para %1s</string>
|
||||
<string name="outgoing_files_title">Enviando arquivos para %1s</string>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="one">Enviando %1$d arquivo para %2$s</item>
|
||||
<item quantity="other">Enviando %1$d arquivos para %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Enviado %1$d arquivo</item>
|
||||
<item quantity="other">Enviados %1$d de %2$d arquivos</item>
|
||||
<item quantity="one">Arquivo: %1$s</item>
|
||||
<item quantity="other">(Arquivo %2$d de %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Arquivo recebido de %1$s</item>
|
||||
@@ -117,12 +120,16 @@
|
||||
<item quantity="one">Falha na recepção do arquivo de %1$s</item>
|
||||
<item quantity="other">Falha na recepção de %2$d de %3$d arquivos de %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="sent_files_title">
|
||||
<item quantity="one">Enviar arquivo para %1$s</item>
|
||||
<item quantity="other">Enviar %2$d arquivos para %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="send_files_fail_title">
|
||||
<item quantity="one">Não foi possível enviar o arquivo para %1$s</item>
|
||||
<item quantity="other">Não foi possível enviar %2$d de %3$d arquivos para o %1$s</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Toque para abrir o \'%1s\'</string>
|
||||
<string name="cannot_create_file">Não foi possível criar o arquivo %s</string>
|
||||
<string name="sent_file_title">Enviar arquivo para %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Não foi possível enviar o arquivo para o %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Toque para responder</string>
|
||||
<string name="reconnect">Reconectar</string>
|
||||
<string name="right_click">Enviar um Botão Direito</string>
|
||||
@@ -239,7 +246,7 @@
|
||||
<string name="plugins_need_optional_permission">Alguns plugins possuem recursos desativados devido à falta de permissões (toque para obter mais informações):</string>
|
||||
<string name="share_optional_permission_explanation">Para compartilhar arquivos entre o seu celular e o seu ambiente de trabalho é necessário permissão para acessar o armazenamento do seu celular</string>
|
||||
<string name="telepathy_permission_explanation">Para ler e gravar SMS a partir do seu ambiente de trabalho é necessário conceder permissão para SMS</string>
|
||||
<string name="telephony_permission_explanation">Para ver as chamadas e SMS do celular a partir do seu ambiente de trabalho é necessário conceder permissão para as chamadas telefônicas e SMS</string>
|
||||
<string name="telephony_permission_explanation">Para ver as chamadas telefônicas no seu ambiente de trabalho é preciso dar permissões para registro de chamadas telefônicas e do estado do telefone</string>
|
||||
<string name="telephony_optional_permission_explanation">Para ver o nome de um contato em vez do seu número de telefone é necessário conceder acesso aos contatos do celular</string>
|
||||
<string name="contacts_permission_explanation">Para compartilhar o seu livro de endereços com o ambiente de trabalho é necessário conceder permissão para os contatos</string>
|
||||
<string name="select_ringtone">Selecionar um toque de chamada</string>
|
||||
@@ -293,4 +300,5 @@
|
||||
<string name="notification_channel_receivenotification">Notificações dos outros dispositivos</string>
|
||||
<string name="take_picture">Iniciar câmera</string>
|
||||
<string name="plugin_photo_desc">Iniciar o aplicativo da câmera para facilitar a captura e transferência de fotos</string>
|
||||
<string name="no_app_for_opening">Não foi encontrado nenhum aplicativo adequado para abrir este arquivo</string>
|
||||
</resources>
|
||||
|
@@ -38,6 +38,7 @@
|
||||
<string name="open_settings">Abrir a configuração</string>
|
||||
<string name="no_permissions">Precisa de dar permissões de acesso às notificações</string>
|
||||
<string name="no_permission_mprisreceiver">Para poder controlar os seus leitores multimédia, terá de dar acesso às notificações</string>
|
||||
<string name="no_permissions_remotekeyboard">Para receber eventos de teclado, tem de activar o Teclado Remoto do KDE Connect</string>
|
||||
<string name="send_ping">Enviar um pedido de contacto</string>
|
||||
<string name="open_mpris_controls">Comando multimédia</string>
|
||||
<string name="remotekeyboard_editing_only_title">Lidar com as teclas remotas apenas na edição</string>
|
||||
@@ -103,11 +104,13 @@
|
||||
<item quantity="one">Ficheiro: %1$s</item>
|
||||
<item quantity="other">(Ficheiro %2$d de %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">A enviar o ficheiro para o %1s</string>
|
||||
<string name="outgoing_files_title">A enviar os ficheiros para o %1s</string>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="one">A enviar %1$d ficheiro para o %2$s</item>
|
||||
<item quantity="other">A enviar %1$d ficheiros para o %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Foi enviado %1$d ficheiro</item>
|
||||
<item quantity="other">Foram enviados %1$d de %2$d ficheiros</item>
|
||||
<item quantity="one">Ficheiro: %1$s</item>
|
||||
<item quantity="other">(Ficheiro %2$d e %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Ficheiro recebido de %1$s</item>
|
||||
@@ -117,12 +120,16 @@
|
||||
<item quantity="one">Falhou a recepção do ficheiro de %1$s</item>
|
||||
<item quantity="other">Falhou a recepção de %2$d de %3$d ficheiros de %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="sent_files_title">
|
||||
<item quantity="one">Foi enviado um ficheiro para %1$s</item>
|
||||
<item quantity="other">Foram enviados %2$d ficheiros para %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="send_files_fail_title">
|
||||
<item quantity="one">Não foi possível enviar o ficheiro para o %1$s</item>
|
||||
<item quantity="other">Não foi possível enviar %2$d de %3$d ficheiros para o %1$s</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Toque para abrir o \'%1s\'</string>
|
||||
<string name="cannot_create_file">Não é possível criar o ficheiro %s</string>
|
||||
<string name="sent_file_title">O ficheiro foi enviado para %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Não foi possível enviar o ficheiro para o %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Toque para responder</string>
|
||||
<string name="reconnect">Ligar de Novo</string>
|
||||
<string name="right_click">Enviar um Botão Direito</string>
|
||||
@@ -190,6 +197,26 @@
|
||||
<string name="sftp_camera">Fotografias</string>
|
||||
<string name="add_device_dialog_title">Adicionar um dispositivo</string>
|
||||
<string name="add_device_hint">Nome ou IP da máquina</string>
|
||||
<string name="sftp_preference_detected_sdcards">Cartões SD detectados</string>
|
||||
<string name="sftp_preference_edit_sdcard_title">Editar o cartão SD</string>
|
||||
<string name="sftp_preference_configured_storage_locations">Locais de armazenamento configurados</string>
|
||||
<string name="sftp_preference_add_storage_location_title">Adicionar um local de armazenamento</string>
|
||||
<string name="sftp_preference_edit_storage_location">Editar o local de armazenamento</string>
|
||||
<string name="sftp_preference_add_camera_shortcut">Adicionar um atalho para a pasta da câmara</string>
|
||||
<string name="sftp_preference_add_camera_shortcut_summary_on">Adicionar um atalho para a pasta da câmara</string>
|
||||
<string name="sftp_preference_add_camera_shortcut_summary_off">Não adicionar um atalho para a pasta da câmara</string>
|
||||
<string name="sftp_storage_preference_storage_location">Local de armazenamento</string>
|
||||
<string name="sftp_storage_preference_storage_location_already_configured">Este local já foi configurado</string>
|
||||
<string name="sftp_storage_preference_click_to_select">carregar para seleccionar</string>
|
||||
<string name="sftp_storage_preference_display_name">Nome visível</string>
|
||||
<string name="sftp_storage_preference_display_name_already_used">Este nome visível já foi usado</string>
|
||||
<string name="sftp_storage_preference_display_name_cannot_be_empty">O nome visível não pode estar vazio</string>
|
||||
<string name="sftp_action_mode_menu_delete">Apagar</string>
|
||||
<string name="sftp_no_sdcard_detected">Nenhum cartão SD detectado</string>
|
||||
<string name="sftp_no_storage_locations_configured">Não estão configurados locais de armazenamento</string>
|
||||
<string name="sftp_saf_permission_explanation">Para aceder a ficheiros remotos, tem de configurar os locais de armazenamento</string>
|
||||
<string name="add_host">Adicionar uma máquina/IP</string>
|
||||
<string name="add_host_hint">Nome ou IP da máquina</string>
|
||||
<string name="no_players_connected">Não foram encontrados leitores</string>
|
||||
<string name="mpris_player_on_device">%1$s em %2$s</string>
|
||||
<string name="send_files">Enviar os ficheiros</string>
|
||||
@@ -219,7 +246,7 @@
|
||||
<string name="plugins_need_optional_permission">Alguns \'plugins\' têm funcionalidades desactivadas devido à falta de permissões (toque para obter mais informações):</string>
|
||||
<string name="share_optional_permission_explanation">Para partilhar ficheiros entre o seu telemóvel e o seu ambiente de trabalho, precisa de permissão para aceder ao armazenamento do seu telemóvel</string>
|
||||
<string name="telepathy_permission_explanation">Para ler e escrever SMS\'s a partir do seu ambiente de trabalho, precisa de dar permissões para os SMS\'s</string>
|
||||
<string name="telephony_permission_explanation">Para ver as chamadas e os SMS\'s a partir do seu ambiente de trabalho, precisa de dar permissões para as chamadas telefónicas e SMS\'s</string>
|
||||
<string name="telephony_permission_explanation">Para ver as chamadas telefónicas a partir do seu ambiente de trabalho, precisa de dar permissões para o registo de chamadas telefónicas e do estado do telemóvel</string>
|
||||
<string name="telephony_optional_permission_explanation">Para ver o nome de um contacto em vez do seu número de telefone, precisa de dar acesso aos contactos do telemóvel</string>
|
||||
<string name="contacts_permission_explanation">Para partilhar o seu livro de contactos com o ambiente de trabalho, precisa de dar permissões para os contactos</string>
|
||||
<string name="select_ringtone">Seleccione um toque de chamada</string>
|
||||
@@ -271,4 +298,7 @@
|
||||
<string name="block_contents">Bloquear o conteúdo das notificações</string>
|
||||
<string name="block_images">Bloquear as imagens das notificações</string>
|
||||
<string name="notification_channel_receivenotification">Notificações dos outros dispositivos</string>
|
||||
<string name="take_picture">Lançar a câmara</string>
|
||||
<string name="plugin_photo_desc">Lança a aplicação da câmara para facilitar a tirada de fotografias e sua transferência</string>
|
||||
<string name="no_app_for_opening">Não existe nenhuma aplicação adequada para abrir este ficheiro</string>
|
||||
</resources>
|
||||
|
@@ -90,8 +90,6 @@
|
||||
<string name="pairing_request_from">Запрос на сопряжение от %1s</string>
|
||||
<string name="received_url_title">Получена ссылка от %1s</string>
|
||||
<string name="received_url_text">Нажмите, чтобы открыть «%1s»</string>
|
||||
<string name="outgoing_file_title">Отправка файла на %1s</string>
|
||||
<string name="outgoing_files_title">Отправка файлов на %1s</string>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Получен %2$d файл с %1$s</item>
|
||||
<item quantity="few">Получены %2$d файла с %1$s</item>
|
||||
@@ -105,10 +103,6 @@
|
||||
<item quantity="other">Не удалось получить файл с %1$s</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Нажмите, чтобы открыть «%1s»</string>
|
||||
<string name="sent_file_title">Файл отправлен на %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Не удалось отправить файл на %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Нажмите, чтобы ответить</string>
|
||||
<string name="reconnect">Подключить заново</string>
|
||||
<string name="right_click">Нажатие правой кнопки</string>
|
||||
@@ -191,7 +185,6 @@
|
||||
<string name="plugins_need_optional_permission">Некоторые функции модулей отключены из-за отсутствия необходимых разрешений (нажмите для просмотра подробностей):</string>
|
||||
<string name="share_optional_permission_explanation">Чтобы обмениваться файлами между телефоном и компьютером, необходимо предоставить доступ к встроенной памяти телефона</string>
|
||||
<string name="telepathy_permission_explanation">Чтобы читать и писать SMS с компьютера, вам необходимо дать разрешение на доступ к SMS</string>
|
||||
<string name="telephony_permission_explanation">Чтобы видеть телефонные звонки и SMS на компьютере, необходимо дать разрешение на телефонные звонки и SMS</string>
|
||||
<string name="telephony_optional_permission_explanation">Чтобы видеть имя контакта вместо номера телефона, необходимо предоставить доступ к контактам</string>
|
||||
<string name="dark_theme">Тёмное оформление</string>
|
||||
<string name="devices">Устройства</string>
|
||||
|
@@ -106,14 +106,6 @@
|
||||
<item quantity="many">(Súbor %2$d z %3$d) : %1$s</item>
|
||||
<item quantity="other"/>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Posielam súbor pre %1s</string>
|
||||
<string name="outgoing_files_title">Posielam súbor do %1s</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Odoslaný %1$d súbor</item>
|
||||
<item quantity="few">Odoslané súbory %1$d z %2$d</item>
|
||||
<item quantity="many">Odoslaných súborov %1$d z %2$d</item>
|
||||
<item quantity="other"/>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Prijatý súbor od %1$s</item>
|
||||
<item quantity="few">Prijaté %2$d súbory od %1$s</item>
|
||||
@@ -128,10 +120,6 @@
|
||||
</plurals>
|
||||
<string name="received_file_text">Ťuknite na otvorenie \'%1s\'</string>
|
||||
<string name="cannot_create_file">Nomožno vytvoriť súbor %s</string>
|
||||
<string name="sent_file_title">Poslať súbor pre %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Zlyhalo poslanie súboru do %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Tapnite na odpoveď</string>
|
||||
<string name="reconnect">Znovu pripojiť</string>
|
||||
<string name="right_click">Poslať kliknutie pravým</string>
|
||||
@@ -193,7 +181,10 @@
|
||||
<string name="sftp_sdcard">SD karta</string>
|
||||
<string name="sftp_readonly">(iba na čítanie)</string>
|
||||
<string name="sftp_camera">Obrázky fotoaparátu</string>
|
||||
<string name="add_device_dialog_title">Pridať zariadenie</string>
|
||||
<string name="sftp_storage_preference_display_name">Zobraziť meno</string>
|
||||
<string name="add_host">Pridať hostiteľa/IP</string>
|
||||
<string name="add_host_hint">Názov hostiteľa alebo IP</string>
|
||||
<string name="no_players_connected">Nenašli sa žiadne prehrávače</string>
|
||||
<string name="mpris_player_on_device">%1$s na %2$s</string>
|
||||
<string name="send_files">Odoslať súbory</string>
|
||||
@@ -223,7 +214,6 @@
|
||||
<string name="plugins_need_optional_permission">Niektoré pluginy majú zakázané funkcie pre nedostatok opránení (ťuknite pre viac info):</string>
|
||||
<string name="share_optional_permission_explanation">Na zdieľanie súborov medzi vašim telefónom a počítačom potrebujete dať prístup k úložisku telefónu</string>
|
||||
<string name="telepathy_permission_explanation">Na čítanie a písanie SMS z vašeho počítača, potrebujete dať oprávnienie na SMS</string>
|
||||
<string name="telephony_permission_explanation">Aby ste videli telefónne hovory a SMS z počítača, potrebujete dať oprávnenie na hovory a SMS</string>
|
||||
<string name="telephony_optional_permission_explanation">Aby ste videli meno kontaktu namiesto čísla, potrebujete dať oprávnenie na telefónne kontakty</string>
|
||||
<string name="contacts_permission_explanation">Na zdieľanie kontaktov s počítačom, potrebujete dať oprávnenie na kontakty</string>
|
||||
<string name="select_ringtone">Nastaviť tón zvonenia</string>
|
||||
|
@@ -63,7 +63,6 @@
|
||||
<string name="category_connected_devices">Повезани уређаји</string>
|
||||
<string name="category_not_paired_devices">Доступни уређаји</string>
|
||||
<string name="category_remembered_devices">Упамћени уређаји</string>
|
||||
<string name="plugins_failed_to_load">Неуспело учитавање прикључка (тапните за више информација):</string>
|
||||
<string name="device_menu_plugins">Поставке прикључака</string>
|
||||
<string name="device_menu_unpair">Распари</string>
|
||||
<string name="device_not_reachable">Упарени уређај није доступан</string>
|
||||
@@ -85,13 +84,7 @@
|
||||
<string name="pairing_request_from">Захтев за упаривање са %1s</string>
|
||||
<string name="received_url_title">Примљена веза са %1s</string>
|
||||
<string name="received_url_text">Тапните да отворите „%1s“</string>
|
||||
<string name="outgoing_file_title">Шаљем фајл на %1s</string>
|
||||
<string name="outgoing_files_title">Шаљем фајлове на %1s</string>
|
||||
<string name="received_file_text">Тапните да отворите „%1s“</string>
|
||||
<string name="sent_file_title">Послат фајл на %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Неуспело слање фајла на %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Тапните да одговорите</string>
|
||||
<string name="reconnect">Поново повежи</string>
|
||||
<string name="right_click">Пошаљи десни клик</string>
|
||||
@@ -140,7 +133,6 @@
|
||||
<string name="title_activity_notification_filter">Филтер обавештења</string>
|
||||
<string name="filter_apps_info">Обавештења ће се синхронизовати за изабране апликације.</string>
|
||||
<string name="sftp_internal_storage">Унутрашња меморија</string>
|
||||
<string name="sftp_all_files">сви фајлови</string>
|
||||
<string name="sftp_sdcard_num">СД картица %d</string>
|
||||
<string name="sftp_sdcard">СД картица</string>
|
||||
<string name="sftp_readonly">(само за читање)</string>
|
||||
@@ -148,7 +140,6 @@
|
||||
<string name="add_host">Додај домаћина/ИП</string>
|
||||
<string name="add_host_hint">Име домаћина или ИП</string>
|
||||
<string name="no_players_connected">Нисам нашао плејере</string>
|
||||
<string name="custom_dev_list_help">Користите ову опцију само ако се ваши уређаји не приказују аутоматски. Унесите ИП адресу или име домаћина и додирните дугме да га додате на листу. Додирните постојећу ставку да бисте је уклонили.</string>
|
||||
<string name="mpris_player_on_device">%1$s на %2$s</string>
|
||||
<string name="send_files">Пошаљи фајлове</string>
|
||||
<string name="pairing_title">Уређаји КДЕ‑конекције</string>
|
||||
@@ -174,9 +165,7 @@
|
||||
<string name="permission_explanation">Овај прикључак тражи дозволе да би радио.</string>
|
||||
<string name="optional_permission_explanation">Морате дати допунске дозволе за активирање свих функција.</string>
|
||||
<string name="plugins_need_optional_permission">Неки прикључци имају деактивиране могућности због недостатка дозвола (тапните за више информација):</string>
|
||||
<string name="sftp_permission_explanation">Програм захтева дозволе да би са рачунара приступио фајловима на телефону.</string>
|
||||
<string name="share_optional_permission_explanation">За дељење фајлова између телефона и рачунара морате дати приступ складишту телефона.</string>
|
||||
<string name="telepathy_permission_explanation">За читање и писање СМС‑ова на рачунару морате дати дозволу за СМС.</string>
|
||||
<string name="telephony_permission_explanation">Да бисте са рачунара видели телефонске позиве и СМС‑ове морате дати дозволу за позиве и СМС‑ове.</string>
|
||||
<string name="telephony_optional_permission_explanation">Да бисте видели име контакта уместо броја телефона морате дати приступ за контакте на телефону.</string>
|
||||
</resources>
|
||||
|
@@ -97,18 +97,20 @@
|
||||
<string name="received_url_title">Tog emot länk från %1s</string>
|
||||
<string name="received_url_text">Rör för att öppna \'%1s\'</string>
|
||||
<plurals name="incoming_file_title">
|
||||
<item quantity="one">Tog emot %1$d fil från %2$s</item>
|
||||
<item quantity="other">Tog emot %1$d filer från %2$s</item>
|
||||
<item quantity="one">Tar emot %1$d fil från %2$s</item>
|
||||
<item quantity="other">Tar emot %1$d filer från %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="incoming_files_text">
|
||||
<item quantity="one">Fil: %1s</item>
|
||||
<item quantity="other">(Fil %2$d av %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Skickar fil till %1s</string>
|
||||
<string name="outgoing_files_title">Skickar filer till %1s</string>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="one">Skickar %1$d fil till %2$s</item>
|
||||
<item quantity="other">Skickar %1$d filer till %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Skickade %1$d fil</item>
|
||||
<item quantity="other">Skickade %1$d av %2$d filer</item>
|
||||
<item quantity="one">Fil: %1$s</item>
|
||||
<item quantity="other">(Fil %2$d av %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Tog emot fil från %1$s</item>
|
||||
@@ -118,12 +120,16 @@
|
||||
<item quantity="one">Misslyckades ta emot fil från %1$s</item>
|
||||
<item quantity="other">Misslyckades ta emot %2$d av %3$d filer från %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="sent_files_title">
|
||||
<item quantity="one">Skickade fil till %1$s</item>
|
||||
<item quantity="other">Skickade %2$d filer till %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="send_files_fail_title">
|
||||
<item quantity="one">Misslyckades skicka fil till %1$s</item>
|
||||
<item quantity="other">Misslyckades skicka %2$d av %3$d filer till %1$s</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Rör för att öppna \'%1s\'</string>
|
||||
<string name="cannot_create_file">Kan inte skapa filen %s</string>
|
||||
<string name="sent_file_title">Skickade fil till %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Misslyckades skicka fil till %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Rör för att svara</string>
|
||||
<string name="reconnect">Anslut igen</string>
|
||||
<string name="right_click">Skicka högerklick</string>
|
||||
@@ -240,7 +246,7 @@
|
||||
<string name="plugins_need_optional_permission">Vissa insticksprogram har inaktiverade funktioner på grund av att rättigheter saknas (rör för mer information):</string>
|
||||
<string name="share_optional_permission_explanation">För att dela filer mellan telefonen och skrivbordet behöver du ge tillgång till telefonens lagringsutrymme</string>
|
||||
<string name="telepathy_permission_explanation">För att läsa och skriva SMS från skrivbordet måste du ge rättigheter för SMS</string>
|
||||
<string name="telephony_permission_explanation">För att se telefonsamtal och SMS från skrivbordet måste du ge rättigheter för telefonsamtal och SMS</string>
|
||||
<string name="telephony_permission_explanation">För att se telefonsamtal på skrivbordet måste du ge rättigheter för telefonsamtalsloggar och telefontillstånd</string>
|
||||
<string name="telephony_optional_permission_explanation">För att se ett kontaktnamn istället för ett telefonnummer måste du ge tillgång till telefonens kontakter</string>
|
||||
<string name="contacts_permission_explanation">För att dela kontaktlistan med skrivbordet måste du ge rättigheter för kontakter</string>
|
||||
<string name="select_ringtone">Välj en ringsignal</string>
|
||||
@@ -294,4 +300,5 @@
|
||||
<string name="notification_channel_receivenotification">Underrättelser från andra apparater</string>
|
||||
<string name="take_picture">Starta kamera</string>
|
||||
<string name="plugin_photo_desc">Starta kameraprogrammet för att förenkla att ta och överföra bilder</string>
|
||||
<string name="no_app_for_opening">Inget lämpligt program hittades för att öppna filen</string>
|
||||
</resources>
|
||||
|
@@ -84,13 +84,7 @@
|
||||
<string name="pairing_request_from">%1s için eşleşme talebi</string>
|
||||
<string name="received_url_title">%1s üzerinden bağlantı alındı</string>
|
||||
<string name="received_url_text">\'%1s\' açmak için dokunun</string>
|
||||
<string name="outgoing_file_title">Dosya şuraya gönderiliyor, %1s</string>
|
||||
<string name="outgoing_files_title">Dosyalar şuraya gönderiliyor, %1s</string>
|
||||
<string name="received_file_text">\'%1s\' açmak için dokunun</string>
|
||||
<string name="sent_file_title">Dosyayı şuraya gönder, %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Dosyayı şuraya gönderme başarısız, %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Cevap için dokunun</string>
|
||||
<string name="reconnect">Yeniden Bağlan</string>
|
||||
<string name="right_click">Sağ Tık Gönder</string>
|
||||
@@ -172,6 +166,5 @@
|
||||
<string name="plugins_need_optional_permission">Bazı eklentilerin özellikleri, izin yetersizliğinden kapalı gelmektedir (daha fazla bilgi için dokunun):</string>
|
||||
<string name="share_optional_permission_explanation">Telefon ve masaüstünüz arasında dosya paylaşılabilmesi için, telefonun depolama alanına erişim izni olmalıdır</string>
|
||||
<string name="telepathy_permission_explanation">Masaüstünde SMS yazma ve okuma yapmak için SMS izni gereklidir</string>
|
||||
<string name="telephony_permission_explanation">Masaüstünden telefon çağrılarını ve SMS görebilmek için izin gereklidir</string>
|
||||
<string name="telephony_optional_permission_explanation">Telefon numarası yerine kişi ismi görebilmek için telefonun kişilerine erişim gereklidir</string>
|
||||
</resources>
|
||||
|
@@ -108,13 +108,17 @@
|
||||
<item quantity="many">(Файл %2$d з %3$d): %1$s</item>
|
||||
<item quantity="other">Файл: %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Надсилаємо файл до %1s</string>
|
||||
<string name="outgoing_files_title">Надсилаємо файли на %1s</string>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="one">Надсилаємо %1$d файл до %2$s</item>
|
||||
<item quantity="few">Надсилаємо %1$d файли до %2$s</item>
|
||||
<item quantity="many">Надсилаємо %1$d файлів до %2$s</item>
|
||||
<item quantity="other">Надсилаємо %1$d файл до %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Надіслано %1$d з %2$d файла</item>
|
||||
<item quantity="few">Надіслано %1$d з %2$d файлів</item>
|
||||
<item quantity="many">Надіслано %1$d з %2$d файлів</item>
|
||||
<item quantity="other">Надіслано %1$d файл</item>
|
||||
<item quantity="one">(Файл %2$d з %3$d) : %1$s</item>
|
||||
<item quantity="few">(Файл %2$d з %3$d) : %1$s</item>
|
||||
<item quantity="many">(Файл %2$d з %3$d) : %1$s</item>
|
||||
<item quantity="other">Файл: %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Отримано %2$d файл з %1$s</item>
|
||||
@@ -128,12 +132,20 @@
|
||||
<item quantity="many">Не вдалося отримати %2$d з %3$d файлів з %1$s</item>
|
||||
<item quantity="other">Не вдалося отримати файл з %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="sent_files_title">
|
||||
<item quantity="one">До %1$s надіслано %2$d файл</item>
|
||||
<item quantity="few">До %1$s надіслано %2$d файли</item>
|
||||
<item quantity="many">До %1$s надіслано %2$d файлів</item>
|
||||
<item quantity="other">Файл надіслано до %1s</item>
|
||||
</plurals>
|
||||
<plurals name="send_files_fail_title">
|
||||
<item quantity="one">Не вдалося надіслати %2$d з %3$d файлів до %1$s</item>
|
||||
<item quantity="few">Не вдалося надіслати %2$d з %3$d файлів до %1$s</item>
|
||||
<item quantity="many">Не вдалося надіслати %2$d з %3$d файлів до %1$s</item>
|
||||
<item quantity="other">Не вдалося надіслати файл до %1$s</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Натисніть, щоб відкрити «%1s»</string>
|
||||
<string name="cannot_create_file">Не вдалося створити файл %s</string>
|
||||
<string name="sent_file_title">Файл надіслано до %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Не вдалося надіслати файл на %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Натисніть, щоб відповісти</string>
|
||||
<string name="reconnect">З\'єднати знову</string>
|
||||
<string name="right_click">Надіслати клацання правою кнопкою</string>
|
||||
@@ -250,7 +262,7 @@
|
||||
<string name="plugins_need_optional_permission">Можливості деяких додатків вимкнено, оскільки програмі не вистачає прав доступу (натисніть, щоб дізнатися більше):</string>
|
||||
<string name="share_optional_permission_explanation">Щоб спільного використовувати файли на вашому телефоні і робочому комп’ютері, вам слід надати програмі доступ до сховища даних вашого телефону</string>
|
||||
<string name="telepathy_permission_explanation">Щоб читати і писати SMS з вашого робочого комп’ютера, вам слід надати програмі доступ до SMS</string>
|
||||
<string name="telephony_permission_explanation">"Щоб переглядати дзвінки і SMS з робочого комп’ютера, вам слід надати програмі доступ до дзвінків і SMS"</string>
|
||||
<string name="telephony_permission_explanation">Щоб переглядати дзвінки з робочого комп’ютера, вам слід надати програмі доступ до журналу дзвінків та стану телефону</string>
|
||||
<string name="telephony_optional_permission_explanation">Щоб бачити ім’я контакту замість номеру телефону, вам слід надати програмі доступ до записів контактів на телефоні</string>
|
||||
<string name="contacts_permission_explanation">Щоб мати змогу спільно використовувати ваші записи контактів на пристрої і на комп\'ютері, вам слід надати програмі доступ до контактів</string>
|
||||
<string name="select_ringtone">Виберіть мелодію дзвінка</string>
|
||||
@@ -304,4 +316,5 @@
|
||||
<string name="notification_channel_receivenotification">Сповіщення з інших пристроїв</string>
|
||||
<string name="take_picture">Запустити камеру</string>
|
||||
<string name="plugin_photo_desc">Запустити додаток камери для спрощення знімання та передавання фотографій</string>
|
||||
<string name="no_app_for_opening">Не знайдено відповідної програми для відкриття цього файла</string>
|
||||
</resources>
|
||||
|
@@ -100,11 +100,6 @@
|
||||
<plurals name="incoming_files_text">
|
||||
<item quantity="other">(%3$d 个文件中的第 %2$d 个):%1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">正在向%1s发送文件</string>
|
||||
<string name="outgoing_files_title">正在向 %1s 发送文件</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="other">已发送 %2$d 个文件中的 %1$d 个</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="other">已从 %1$s 接收了 %2$d 个文件</item>
|
||||
</plurals>
|
||||
@@ -113,10 +108,6 @@
|
||||
</plurals>
|
||||
<string name="received_file_text">点击以打开“%1s”</string>
|
||||
<string name="cannot_create_file">无法创建文件 %s</string>
|
||||
<string name="sent_file_title">发送文件到%1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">向 %1s 发送文件失败</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">点击以应答</string>
|
||||
<string name="reconnect">重新连接</string>
|
||||
<string name="right_click">发送右键点击</string>
|
||||
@@ -207,7 +198,6 @@
|
||||
<string name="plugins_need_optional_permission">因缺少权限,某些插件的一些功能已禁用(点击以查看更多信息):</string>
|
||||
<string name="share_optional_permission_explanation">您需要给予访问手机存储的权限才能在手机和桌面计算机之间分享文件</string>
|
||||
<string name="telepathy_permission_explanation">从计算机桌面读取、写入短消息需要向应用程序授予 SMS 权限</string>
|
||||
<string name="telephony_permission_explanation">您必须给予访问手机通话和短信的权限才能从桌面计算机查看通话记录和短信</string>
|
||||
<string name="telephony_optional_permission_explanation">要查看联系人姓名而非电话号码,您需要授予访问手机通讯录的权限</string>
|
||||
<string name="contacts_permission_explanation">要与桌面共享通讯薄,您需要给予联系人权限</string>
|
||||
<string name="select_ringtone">选择铃声</string>
|
||||
|
@@ -102,10 +102,11 @@
|
||||
<plurals name="incoming_files_text">
|
||||
<item quantity="other">(檔案 %2$d/%3$d):%1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">正在將檔案發送到 %1s</string>
|
||||
<string name="outgoing_files_title">正在將檔案發送到 %1s</string>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="other">正在將 %1$d 個檔案傳送至 %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="other">傳送 %1$d 個檔案,共 %2$d 個檔案。</item>
|
||||
<item quantity="other">(第 %2$d(共 %3$d)個檔案):%1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="other">已從 %1$s 接收 %2$d 個檔案</item>
|
||||
@@ -113,12 +114,14 @@
|
||||
<plurals name="received_files_fail_title">
|
||||
<item quantity="other">無法從 %1$s 接收到 %2$d/%3$d 個檔案</item>
|
||||
</plurals>
|
||||
<plurals name="sent_files_title">
|
||||
<item quantity="other">已將 %2$d 傳送至 %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="send_files_fail_title">
|
||||
<item quantity="other">無法將第 %2$d (共 %3$d) 個檔案傳送至 %1$s</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">點擊開啟 \'%1s\'</string>
|
||||
<string name="cannot_create_file">無法建立 %s 檔案</string>
|
||||
<string name="sent_file_title">將檔案傳送到 %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">傳送到 %1s 的檔案失敗</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">點擊即可應答</string>
|
||||
<string name="reconnect">重新連線</string>
|
||||
<string name="right_click">傳送右鍵點擊</string>
|
||||
@@ -235,7 +238,7 @@
|
||||
<string name="plugins_need_optional_permission">部份的附加元件因為缺乏權限,而導致功能被停用。(點擊以了解更多資訊):</string>
|
||||
<string name="share_optional_permission_explanation">為了要在您的手機與電腦之間分享檔案,你需要同意存取手機的儲存空間。</string>
|
||||
<string name="telepathy_permission_explanation">為了要在您的個人電腦上讀取與撰寫簡訊,你需要提供簡訊的權限。</string>
|
||||
<string name="telephony_permission_explanation">為了要在您的電腦上檢視手機通話與簡訊,你需要提供手機通話與簡訊的權限。</string>
|
||||
<string name="telephony_permission_explanation">為了要在桌面上檢視手機通話,您需要提供手機通話記錄及手機狀態的權限。</string>
|
||||
<string name="telephony_optional_permission_explanation">為了要讓聯絡人名稱取代手機號碼,您需要提供手機通訊錄的權限。</string>
|
||||
<string name="contacts_permission_explanation">為了要與電腦分享您的通訊錄,您必須提供「聯絡人」的權限</string>
|
||||
<string name="select_ringtone">選擇一個鈴聲</string>
|
||||
@@ -289,4 +292,5 @@
|
||||
<string name="notification_channel_receivenotification">其他裝置上的通知</string>
|
||||
<string name="take_picture">啟動相機</string>
|
||||
<string name="plugin_photo_desc">開啟相機應用程式以輕鬆拍攝並傳輸相片</string>
|
||||
<string name="no_app_for_opening">找不到適合用來開啟此檔案的應用程式</string>
|
||||
</resources>
|
||||
|
@@ -135,11 +135,13 @@
|
||||
<item quantity="one">File: %1s</item>
|
||||
<item quantity="other">(File %2$d of %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<string name="outgoing_file_title">Sending file to %1s</string>
|
||||
<string name="outgoing_files_title">Sending files to %1s</string>
|
||||
<plurals name="outgoing_file_title">
|
||||
<item quantity="one">Sending %1$d file to %2$s</item>
|
||||
<item quantity="other">Sending %1$d files to %2$s</item>
|
||||
</plurals>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Sent %1$d file</item>
|
||||
<item quantity="other">Sent %1$d out of %2$d files</item>
|
||||
<item quantity="one">File: %1$s</item>
|
||||
<item quantity="other">(File %2$d of %3$d) : %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="received_files_title">
|
||||
<item quantity="one">Received file from %1$s</item>
|
||||
@@ -149,12 +151,16 @@
|
||||
<item quantity="one">Failed receiving file from %1$s</item>
|
||||
<item quantity="other">Failed receiving %2$d of %3$d files from %1$s</item>
|
||||
</plurals>
|
||||
<plurals name="sent_files_title">
|
||||
<item quantity="one">Sent file to %1$s</item>
|
||||
<item quantity="other">Sent %2$d files to %1$s"</item>
|
||||
</plurals>
|
||||
<plurals name="send_files_fail_title">
|
||||
<item quantity="one">Failed sending file to %1$s</item>
|
||||
<item quantity="other">Failed sending %2$d of %3$d files to %1$s</item>
|
||||
</plurals>
|
||||
<string name="received_file_text">Tap to open \'%1s\'</string>
|
||||
<string name="cannot_create_file">Cannot create file %s</string>
|
||||
<string name="sent_file_title">Sent file to %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">Failed to send file to %1s</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">Tap to answer</string>
|
||||
<string name="reconnect">Reconnect</string>
|
||||
<string name="right_click">Send Right Click</string>
|
||||
@@ -289,7 +295,7 @@
|
||||
<string name="plugins_need_optional_permission">Some plugins have features disabled because of lack of permission (tap for more info):</string>
|
||||
<string name="share_optional_permission_explanation">To share files between your phone and your desktop you need to give access to the phone\'s storage</string>
|
||||
<string name="telepathy_permission_explanation">To read and write SMS from your desktop you need to give permission to SMS</string>
|
||||
<string name="telephony_permission_explanation">To see phone calls and SMS from the desktop you need to give permission to phone calls and SMS</string>
|
||||
<string name="telephony_permission_explanation">To see phone calls on the desktop you need to give permission to phone call logs and phone state</string>
|
||||
<string name="telephony_optional_permission_explanation">To see a contact name instead of a phone number you need to give access to the phone\'s contacts</string>
|
||||
<string name="contacts_permission_explanation">To share your contacts book with the desktop, you need to give contacts permission</string>
|
||||
<string name="select_ringtone">Select a ringtone</string>
|
||||
@@ -354,4 +360,5 @@
|
||||
<string name="plugin_photo_desc">Launch the camera app to ease taking and transferring pictures</string>
|
||||
|
||||
<string name="findmyphone_preference_key_ringtone" translatable="false">findmyphone_ringtone</string>
|
||||
<string name="no_app_for_opening">No suitable app found to open this file</string>
|
||||
</resources>
|
||||
|
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:keep="@xml/findmyphoneplugin_preferences">
|
||||
|
||||
<Preference
|
||||
android:key="@string/findmyphone_preference_key_ringtone"
|
||||
|
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:keep="@xml/mousepadplugin_preferences">
|
||||
|
||||
<ListPreference
|
||||
android:id="@+id/mousepad_double_tap_preference"
|
||||
|
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:keep="@xml/mprisplugin_preferences">
|
||||
|
||||
<ListPreference
|
||||
android:id="@+id/mpris_time_preference"
|
||||
|
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:keep="@xml/remotekeyboardplugin_preferences">
|
||||
|
||||
<CheckBoxPreference
|
||||
android:id="@+id/remotekeyboard_editing_only"
|
||||
|
@@ -1,5 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceScreen xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
tools:keep="@xml/sftpplugin_preferences">
|
||||
<PreferenceCategory
|
||||
android:key="@string/sftp_preference_key_preference_category"
|
||||
android:title="@string/sftp_preference_detected_sdcards"
|
||||
|
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:keep="@xml/shareplugin_preferences">
|
||||
|
||||
<CheckBoxPreference
|
||||
android:id="@+id/share_destination_customize"
|
||||
|
@@ -1,5 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceScreen xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
tools:keep="@xml/telephonyplugin_preferences">
|
||||
|
||||
<EditTextPreference
|
||||
android:dialogMessage="@string/telephony_pref_blocked_dialog_desc"
|
||||
|
@@ -26,7 +26,6 @@ import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
@@ -90,6 +89,4 @@ public abstract class BaseLink {
|
||||
|
||||
//TO OVERRIDE, should be sync
|
||||
public abstract boolean sendPacket(NetworkPacket np, Device.SendPacketStatusCallback callback);
|
||||
@Deprecated
|
||||
public abstract boolean sendPacketEncrypted(NetworkPacket np, Device.SendPacketStatusCallback callback, PublicKey key);
|
||||
}
|
||||
|
@@ -32,7 +32,6 @@ import org.json.JSONObject;
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BasePairingHandler;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -41,7 +40,6 @@ import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.PublicKey;
|
||||
import java.util.UUID;
|
||||
|
||||
public class BluetoothLink extends BaseLink {
|
||||
@@ -87,14 +85,6 @@ public class BluetoothLink extends BaseLink {
|
||||
return;
|
||||
}
|
||||
|
||||
if (np.getType().equals(NetworkPacket.PACKET_TYPE_ENCRYPTED)) {
|
||||
try {
|
||||
np = RsaHelper.decrypt(np, privateKey);
|
||||
} catch (Exception e) {
|
||||
Log.e("BluetoothLink/receiving", "Exception decrypting the package", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (np.hasPayloadTransferInfo()) {
|
||||
BluetoothSocket transferSocket = null;
|
||||
try {
|
||||
@@ -144,7 +134,7 @@ public class BluetoothLink extends BaseLink {
|
||||
continueAccepting = false;
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
linkProvider.disconnectedLink(this, getDeviceId(), socket);
|
||||
}
|
||||
@@ -158,16 +148,7 @@ public class BluetoothLink extends BaseLink {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendPacket(NetworkPacket np, Device.SendPacketStatusCallback callback) {
|
||||
return sendPacketInternal(np, callback, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendPacketEncrypted(NetworkPacket np, Device.SendPacketStatusCallback callback, PublicKey key) {
|
||||
return sendPacketInternal(np, callback, key);
|
||||
}
|
||||
|
||||
private boolean sendPacketInternal(NetworkPacket np, final Device.SendPacketStatusCallback callback, PublicKey key) {
|
||||
public boolean sendPacket(NetworkPacket np, final Device.SendPacketStatusCallback callback) {
|
||||
|
||||
/*if (!isConnected()) {
|
||||
Log.e("BluetoothLink", "sendPacketEncrypted failed: not connected");
|
||||
@@ -186,15 +167,6 @@ public class BluetoothLink extends BaseLink {
|
||||
np.setPayloadTransferInfo(payloadTransferInfo);
|
||||
}
|
||||
|
||||
if (key != null) {
|
||||
try {
|
||||
np = RsaHelper.encrypt(np, key);
|
||||
} catch (Exception e) {
|
||||
callback.onFailure(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage(np);
|
||||
|
||||
if (serverSocket != null) {
|
||||
|
@@ -149,7 +149,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDEConnect", "Exception", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,7 +160,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
||||
serverSocket = bluetoothAdapter
|
||||
.listenUsingRfcommWithServiceRecord("KDE Connect", SERVICE_UUID);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDEConnect", "Exception", e);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -52,7 +52,7 @@ public class BluetoothPairingHandler extends BasePairingHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packageReceived(NetworkPacket np) throws Exception {
|
||||
public void packageReceived(NetworkPacket np) {
|
||||
|
||||
boolean wantsPair = np.getBoolean("pair");
|
||||
|
||||
|
@@ -27,7 +27,6 @@ import org.json.JSONObject;
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BasePairingHandler;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||
import org.kde.kdeconnect.Helpers.StringsHelper;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
@@ -42,7 +41,6 @@ import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.nio.channels.NotYetConnectedException;
|
||||
import java.security.PublicKey;
|
||||
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
@@ -61,7 +59,7 @@ public class LanLink extends BaseLink {
|
||||
// because it's probably trying to find me and
|
||||
// potentially ask for pairing.
|
||||
|
||||
private volatile Socket socket = null;
|
||||
private volatile SSLSocket socket = null;
|
||||
|
||||
private final LinkDisconnectedCallback callback;
|
||||
|
||||
@@ -71,14 +69,14 @@ public class LanLink extends BaseLink {
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLink", "Error", e);
|
||||
}
|
||||
}
|
||||
|
||||
//Returns the old socket
|
||||
public Socket reset(final Socket newSocket, ConnectionStarted connectionSource) throws IOException {
|
||||
public SSLSocket reset(final SSLSocket newSocket, ConnectionStarted connectionSource) throws IOException {
|
||||
|
||||
Socket oldSocket = socket;
|
||||
SSLSocket oldSocket = socket;
|
||||
socket = newSocket;
|
||||
|
||||
this.connectionSource = connectionSource;
|
||||
@@ -121,7 +119,7 @@ public class LanLink extends BaseLink {
|
||||
return oldSocket;
|
||||
}
|
||||
|
||||
public LanLink(Context context, String deviceId, LanLinkProvider linkProvider, Socket socket, ConnectionStarted connectionSource) throws IOException {
|
||||
public LanLink(Context context, String deviceId, LanLinkProvider linkProvider, SSLSocket socket, ConnectionStarted connectionSource) throws IOException {
|
||||
super(context, deviceId, linkProvider);
|
||||
callback = linkProvider;
|
||||
reset(socket, connectionSource);
|
||||
@@ -139,7 +137,8 @@ public class LanLink extends BaseLink {
|
||||
}
|
||||
|
||||
//Blocking, do not call from main thread
|
||||
private boolean sendPacketInternal(NetworkPacket np, final Device.SendPacketStatusCallback callback, PublicKey key) {
|
||||
@Override
|
||||
public boolean sendPacket(NetworkPacket np, final Device.SendPacketStatusCallback callback) {
|
||||
if (socket == null) {
|
||||
Log.e("KDE/sendPacket", "Not yet connected");
|
||||
callback.onFailure(new NotYetConnectedException());
|
||||
@@ -159,11 +158,6 @@ public class LanLink extends BaseLink {
|
||||
server = null;
|
||||
}
|
||||
|
||||
//Encrypt if key provided
|
||||
if (key != null) {
|
||||
np = RsaHelper.encrypt(np, key);
|
||||
}
|
||||
|
||||
//Log.e("LanLink/sendPacket", np.getType());
|
||||
|
||||
//Send body of the network package
|
||||
@@ -188,9 +182,7 @@ public class LanLink extends BaseLink {
|
||||
payloadSocket = server.accept();
|
||||
|
||||
//Convert to SSL if needed
|
||||
if (socket instanceof SSLSocket) {
|
||||
payloadSocket = SslHelper.convertToSslSocket(context, payloadSocket, getDeviceId(), true, false);
|
||||
}
|
||||
payloadSocket = SslHelper.convertToSslSocket(context, payloadSocket, getDeviceId(), true, false);
|
||||
|
||||
outputStream = payloadSocket.getOutputStream();
|
||||
inputStream = np.getPayload().getInputStream();
|
||||
@@ -201,7 +193,7 @@ public class LanLink extends BaseLink {
|
||||
long size = np.getPayloadSize();
|
||||
long progress = 0;
|
||||
long timeSinceLastUpdate = -1;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
while (!np.isCanceled() && (bytesRead = inputStream.read(buffer)) != -1) {
|
||||
//Log.e("ok",""+bytesRead);
|
||||
progress += bytesRead;
|
||||
outputStream.write(buffer, 0, bytesRead);
|
||||
@@ -216,14 +208,16 @@ public class LanLink extends BaseLink {
|
||||
outputStream.flush();
|
||||
Log.i("KDE/LanLink", "Finished sending payload ("+progress+" bytes written)");
|
||||
} finally {
|
||||
try { server.close(); } catch (Exception e) { }
|
||||
try { payloadSocket.close(); } catch (Exception e) { }
|
||||
try { server.close(); } catch (Exception ignored) { }
|
||||
try { payloadSocket.close(); } catch (Exception ignored) { }
|
||||
np.getPayload().close();
|
||||
try { outputStream.close(); } catch (Exception e) { }
|
||||
try { outputStream.close(); } catch (Exception ignored) { }
|
||||
}
|
||||
}
|
||||
|
||||
callback.onSuccess();
|
||||
if (!np.isCanceled()) {
|
||||
callback.onSuccess();
|
||||
}
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
if (callback != null) {
|
||||
@@ -238,45 +232,19 @@ public class LanLink extends BaseLink {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Blocking, do not call from main thread
|
||||
@Override
|
||||
public boolean sendPacket(NetworkPacket np, Device.SendPacketStatusCallback callback) {
|
||||
return sendPacketInternal(np, callback, null);
|
||||
}
|
||||
|
||||
//Blocking, do not call from main thread
|
||||
@Override
|
||||
public boolean sendPacketEncrypted(NetworkPacket np, Device.SendPacketStatusCallback callback, PublicKey key) {
|
||||
return sendPacketInternal(np, callback, key);
|
||||
}
|
||||
|
||||
private void receivedNetworkPacket(NetworkPacket np) {
|
||||
|
||||
if (np.getType().equals(NetworkPacket.PACKET_TYPE_ENCRYPTED)) {
|
||||
try {
|
||||
np = RsaHelper.decrypt(np, privateKey);
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/onPacketReceived","Exception decrypting the package");
|
||||
}
|
||||
}
|
||||
|
||||
if (np.hasPayloadTransferInfo()) {
|
||||
Socket payloadSocket = new Socket();
|
||||
try {
|
||||
int tcpPort = np.getPayloadTransferInfo().getInt("port");
|
||||
InetSocketAddress deviceAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
|
||||
payloadSocket.connect(new InetSocketAddress(deviceAddress.getAddress(), tcpPort));
|
||||
// Use ssl if existing link is on ssl
|
||||
if (socket instanceof SSLSocket) {
|
||||
payloadSocket = SslHelper.convertToSslSocket(context, payloadSocket, getDeviceId(), true, true);
|
||||
}
|
||||
payloadSocket = SslHelper.convertToSslSocket(context, payloadSocket, getDeviceId(), true, true);
|
||||
np.setPayload(new NetworkPacket.Payload(payloadSocket, np.getPayloadSize()));
|
||||
} catch (Exception e) {
|
||||
try { payloadSocket.close(); } catch(Exception ignored) { }
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLink", "Exception connecting to payload remote socket");
|
||||
Log.e("KDE/LanLink", "Exception connecting to payload remote socket", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -66,9 +66,6 @@ import javax.net.ssl.SSLSocket;
|
||||
*/
|
||||
public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDisconnectedCallback {
|
||||
|
||||
public static final int MIN_VERSION_WITH_SSL_SUPPORT = 6;
|
||||
private static final int MIN_VERSION_WITH_NEW_PORT_SUPPORT = 7;
|
||||
|
||||
private final static int MIN_PORT = 1716;
|
||||
private final static int MAX_PORT = 1764;
|
||||
final static int PAYLOAD_TRANSFER_MIN_PORT = 1739;
|
||||
@@ -93,7 +90,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
}
|
||||
|
||||
//They received my UDP broadcast and are connecting to me. The first thing they sned should be their identity.
|
||||
private void tcpPacketReceived(Socket socket) throws Exception {
|
||||
private void tcpPacketReceived(Socket socket) {
|
||||
|
||||
NetworkPacket networkPacket;
|
||||
try {
|
||||
@@ -102,7 +99,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
networkPacket = NetworkPacket.unserialize(message);
|
||||
//Log.e("TcpListener","Received TCP package: "+networkPacket.serialize());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider", "Exception while receiving TCP packet", e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -116,7 +113,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
}
|
||||
|
||||
//I've received their broadcast and should connect to their TCP socket and send my identity.
|
||||
private void udpPacketReceived(DatagramPacket packet) throws Exception {
|
||||
private void udpPacketReceived(DatagramPacket packet) {
|
||||
|
||||
final InetAddress address = packet.getAddress();
|
||||
|
||||
@@ -136,11 +133,6 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
}
|
||||
}
|
||||
|
||||
if (identityPacket.getInt("protocolVersion") >= MIN_VERSION_WITH_NEW_PORT_SUPPORT && identityPacket.getInt("tcpPort") < MIN_PORT) {
|
||||
Log.w("KDE/LanLinkProvider", "Ignoring a udp broadcast from legacy port because it comes from a device which knows about the new port.");
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i("KDE/LanLinkProvider", "Broadcast identity package received from " + identityPacket.getString("deviceName"));
|
||||
|
||||
int tcpPort = identityPacket.getInt("tcpPort", MIN_PORT);
|
||||
@@ -157,8 +149,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
identityPacketReceived(identityPacket, socket, LanLink.ConnectionStarted.Remotely);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider", "Cannot connect to " + address);
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider", "Cannot connect to " + address, e);
|
||||
if (!reverseConnectionBlackList.contains(address)) {
|
||||
Log.w("KDE/LanLinkProvider", "Blacklisting " + address);
|
||||
reverseConnectionBlackList.add(address);
|
||||
@@ -179,7 +170,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
try {
|
||||
socket.setKeepAlive(true);
|
||||
} catch (SocketException e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLink", "Exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,65 +198,58 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
// If I'm the TCP server I will be the SSL client and viceversa.
|
||||
final boolean clientMode = (connectionStarted == LanLink.ConnectionStarted.Locally);
|
||||
|
||||
// Add ssl handler if device uses new protocol
|
||||
// Do the SSL handshake
|
||||
try {
|
||||
if (identityPacket.getInt("protocolVersion") >= MIN_VERSION_WITH_SSL_SUPPORT) {
|
||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||
boolean isDeviceTrusted = preferences.getBoolean(deviceId, false);
|
||||
|
||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||
boolean isDeviceTrusted = preferences.getBoolean(deviceId, false);
|
||||
if (isDeviceTrusted && !SslHelper.isCertificateStored(context, deviceId)) {
|
||||
//Device paired with and old version, we can't use it as we lack the certificate
|
||||
BackgroundService.RunCommand(context, service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
device.unpair();
|
||||
//Retry as unpaired
|
||||
identityPacketReceived(identityPacket, socket, connectionStarted);
|
||||
});
|
||||
}
|
||||
|
||||
if (isDeviceTrusted && !SslHelper.isCertificateStored(context, deviceId)) {
|
||||
//Device paired with and old version, we can't use it as we lack the certificate
|
||||
Log.i("KDE/LanLinkProvider", "Starting SSL handshake with " + identityPacket.getString("deviceName") + " trusted:" + isDeviceTrusted);
|
||||
|
||||
final SSLSocket sslsocket = SslHelper.convertToSslSocket(context, socket, deviceId, isDeviceTrusted, clientMode);
|
||||
sslsocket.addHandshakeCompletedListener(event -> {
|
||||
String mode = clientMode ? "client" : "server";
|
||||
try {
|
||||
Certificate certificate = event.getPeerCertificates()[0];
|
||||
identityPacket.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
|
||||
Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + identityPacket.getString("deviceName") + " secured with " + event.getCipherSuite());
|
||||
addLink(identityPacket, sslsocket, connectionStarted);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"), e);
|
||||
BackgroundService.RunCommand(context, service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
device.unpair();
|
||||
//Retry as unpaired
|
||||
identityPacketReceived(identityPacket, socket, connectionStarted);
|
||||
});
|
||||
}
|
||||
|
||||
Log.i("KDE/LanLinkProvider", "Starting SSL handshake with " + identityPacket.getString("deviceName") + " trusted:" + isDeviceTrusted);
|
||||
|
||||
final SSLSocket sslsocket = SslHelper.convertToSslSocket(context, socket, deviceId, isDeviceTrusted, clientMode);
|
||||
sslsocket.addHandshakeCompletedListener(event -> {
|
||||
String mode = clientMode ? "client" : "server";
|
||||
try {
|
||||
Certificate certificate = event.getPeerCertificates()[0];
|
||||
identityPacket.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
|
||||
Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + identityPacket.getString("deviceName") + " secured with " + event.getCipherSuite());
|
||||
addLink(identityPacket, sslsocket, connectionStarted);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"));
|
||||
e.printStackTrace();
|
||||
BackgroundService.RunCommand(context, service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
device.unpair();
|
||||
});
|
||||
});
|
||||
//Handshake is blocking, so do it on another thread and free this thread to keep receiving new connection
|
||||
new Thread(() -> {
|
||||
try {
|
||||
synchronized (this) {
|
||||
sslsocket.startHandshake();
|
||||
}
|
||||
});
|
||||
//Handshake is blocking, so do it on another thread and free this thread to keep receiving new connection
|
||||
new Thread(() -> {
|
||||
try {
|
||||
synchronized (this) {
|
||||
sslsocket.startHandshake();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider", "Handshake failed with " + identityPacket.getString("deviceName"));
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider", "Handshake failed with " + identityPacket.getString("deviceName"), e);
|
||||
|
||||
//String[] ciphers = sslsocket.getSupportedCipherSuites();
|
||||
//for (String cipher : ciphers) {
|
||||
// Log.i("SupportedCiphers","cipher: " + cipher);
|
||||
//}
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
addLink(identityPacket, socket, connectionStarted);
|
||||
}
|
||||
//String[] ciphers = sslsocket.getSupportedCipherSuites();
|
||||
//for (String cipher : ciphers) {
|
||||
// Log.i("SupportedCiphers","cipher: " + cipher);
|
||||
//}
|
||||
}
|
||||
}).start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLink", "Exception", e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -281,9 +265,9 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
* @param identityPacket representation of remote device
|
||||
* @param socket a new Socket, which should be used to receive packets from the remote device
|
||||
* @param connectionOrigin which side started this connection
|
||||
* @throws IOException if an exception is thrown by {@link LanLink#reset(Socket, LanLink.ConnectionStarted)}
|
||||
* @throws IOException if an exception is thrown by {@link LanLink#reset(SSLSocket, LanLink.ConnectionStarted)}
|
||||
*/
|
||||
private void addLink(final NetworkPacket identityPacket, Socket socket, LanLink.ConnectionStarted connectionOrigin) throws IOException {
|
||||
private void addLink(final NetworkPacket identityPacket, SSLSocket socket, LanLink.ConnectionStarted connectionOrigin) throws IOException {
|
||||
|
||||
String deviceId = identityPacket.getString("deviceId");
|
||||
LanLink currentLink = visibleComputers.get(deviceId);
|
||||
@@ -311,8 +295,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
udpServer.setReuseAddress(true);
|
||||
udpServer.setBroadcast(true);
|
||||
} catch (SocketException e) {
|
||||
Log.e("LanLinkProvider", "Error creating udp server");
|
||||
e.printStackTrace();
|
||||
Log.e("LanLinkProvider", "Error creating udp server", e);
|
||||
return;
|
||||
}
|
||||
new Thread(() -> {
|
||||
@@ -324,8 +307,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
udpServer.receive(packet);
|
||||
udpPacketReceived(packet);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLinkProvider", "UdpReceive exception");
|
||||
Log.e("LanLinkProvider", "UdpReceive exception", e);
|
||||
}
|
||||
}
|
||||
Log.w("UdpListener", "Stopping UDP listener");
|
||||
@@ -336,8 +318,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
try {
|
||||
tcpServer = openServerSocketOnFreePort(MIN_PORT);
|
||||
} catch (Exception e) {
|
||||
Log.e("LanLinkProvider", "Error creating tcp server");
|
||||
e.printStackTrace();
|
||||
Log.e("LanLinkProvider", "Error creating tcp server", e);
|
||||
return;
|
||||
}
|
||||
new Thread(() -> {
|
||||
@@ -347,8 +328,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
configureSocket(socket);
|
||||
tcpPacketReceived(socket);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLinkProvider", "TcpReceive exception");
|
||||
Log.e("LanLinkProvider", "TcpReceive exception", e);
|
||||
}
|
||||
}
|
||||
Log.w("TcpListener", "Stopping TCP listener");
|
||||
@@ -398,8 +378,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
socket.setBroadcast(true);
|
||||
bytes = identity.serialize().getBytes(StringsHelper.UTF8);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider", "Failed to create DatagramSocket");
|
||||
Log.e("KDE/LanLinkProvider", "Failed to create DatagramSocket", e);
|
||||
}
|
||||
|
||||
if (bytes != null) {
|
||||
@@ -410,8 +389,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
socket.send(new DatagramPacket(bytes, bytes.length, client, MIN_PORT));
|
||||
//Log.i("KDE/LanLinkProvider","Udp identity package sent to address "+client);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider", "Sending udp identity package failed. Invalid address? (" + ipstr + ")");
|
||||
Log.e("KDE/LanLinkProvider", "Sending udp identity package failed. Invalid address? (" + ipstr + ")", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -449,12 +427,12 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
try {
|
||||
tcpServer.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLink", "Exception", e);
|
||||
}
|
||||
try {
|
||||
udpServer.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLink", "Exception", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -22,7 +22,6 @@ package org.kde.kdeconnect.Backends.LanBackend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -31,9 +30,7 @@ import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.security.KeyFactory;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
||||
@@ -54,14 +51,11 @@ public class LanPairingHandler extends BasePairingHandler {
|
||||
private NetworkPacket createPairPacket() {
|
||||
NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_PAIR);
|
||||
np.set("pair", true);
|
||||
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(mDevice.getContext());
|
||||
String publicKey = "-----BEGIN PUBLIC KEY-----\n" + globalSettings.getString("publicKey", "").trim()+ "\n-----END PUBLIC KEY-----\n";
|
||||
np.set("publicKey", publicKey);
|
||||
return np;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packageReceived(NetworkPacket np) throws Exception{
|
||||
public void packageReceived(NetworkPacket np) {
|
||||
|
||||
boolean wantsPair = np.getBoolean("pair");
|
||||
|
||||
@@ -77,15 +71,6 @@ public class LanPairingHandler extends BasePairingHandler {
|
||||
|
||||
if (wantsPair) {
|
||||
|
||||
//Retrieve their public key
|
||||
try {
|
||||
String publicKeyContent = np.getString("publicKey").replace("-----BEGIN PUBLIC KEY-----\n","").replace("-----END PUBLIC KEY-----\n", "");
|
||||
byte[] publicKeyBytes = Base64.decode(publicKeyContent, 0);
|
||||
mDevice.publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
|
||||
} catch (Exception e) {
|
||||
//IGNORE
|
||||
}
|
||||
|
||||
if (mPairStatus == PairStatus.Requested) { //We started pairing
|
||||
|
||||
hidePairingNotification();
|
||||
@@ -158,11 +143,7 @@ public class LanPairingHandler extends BasePairingHandler {
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
if (e != null) {
|
||||
e.printStackTrace();
|
||||
} else {
|
||||
Log.e("LanPairing/onFailure", "Unknown (null) exception");
|
||||
}
|
||||
Log.e("LanPairing/onFailure", "Exception", e);
|
||||
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_could_not_send_package));
|
||||
}
|
||||
};
|
||||
@@ -187,11 +168,7 @@ public class LanPairingHandler extends BasePairingHandler {
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
if (e != null) {
|
||||
e.printStackTrace();
|
||||
} else {
|
||||
Log.e("LanPairing/onFailure", "Unknown (null) exception");
|
||||
}
|
||||
Log.e("LanPairing/onFailure", "Exception", e);
|
||||
mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_not_reachable));
|
||||
}
|
||||
};
|
||||
@@ -212,25 +189,15 @@ public class LanPairingHandler extends BasePairingHandler {
|
||||
//Log.e("KDE/PairingDone", "Pairing Done");
|
||||
SharedPreferences.Editor editor = mDevice.getContext().getSharedPreferences(mDevice.getDeviceId(), Context.MODE_PRIVATE).edit();
|
||||
|
||||
if (mDevice.publicKey != null) {
|
||||
try {
|
||||
String encodedPublicKey = Base64.encodeToString(mDevice.publicKey.getEncoded(), 0);
|
||||
editor.putString("publicKey", encodedPublicKey);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/PairingDone", "Error encoding public key");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
String encodedCertificate = Base64.encodeToString(mDevice.certificate.getEncoded(), 0);
|
||||
editor.putString("certificate", encodedCertificate);
|
||||
} catch (NullPointerException n) {
|
||||
Log.w("KDE/PairingDone", "Certificate is null, remote device does not support ssl");
|
||||
Log.w("KDE/PairingDone", "Certificate is null, remote device does not support ssl", n);
|
||||
} catch (CertificateEncodingException c) {
|
||||
Log.e("KDE/PairingDOne", "Error encoding certificate");
|
||||
Log.e("KDE/PairingDOne", "Error encoding certificate", c);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Pairng", "Exception");
|
||||
Log.e("KDE/Pairng", "Exception", e);
|
||||
}
|
||||
editor.apply();
|
||||
|
||||
|
@@ -28,8 +28,6 @@ import org.kde.kdeconnect.Backends.BasePairingHandler;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
||||
public class LoopbackLink extends BaseLink {
|
||||
|
||||
public LoopbackLink(Context context, BaseLinkProvider linkProvider) {
|
||||
@@ -58,8 +56,4 @@ public class LoopbackLink extends BaseLink {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendPacketEncrypted(NetworkPacket np, Device.SendPacketStatusCallback callback, PublicKey key) {
|
||||
return sendPacket(np, callback);
|
||||
}
|
||||
}
|
||||
|
@@ -33,7 +33,7 @@ public class LoopbackPairingHandler extends BasePairingHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void packageReceived(NetworkPacket np) throws Exception {
|
||||
public void packageReceived(NetworkPacket np) {
|
||||
|
||||
}
|
||||
|
||||
|
@@ -35,7 +35,6 @@ import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BasePairingHandler;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
@@ -45,10 +44,8 @@ import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@@ -68,7 +65,6 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
|
||||
private final String deviceId;
|
||||
private String name;
|
||||
public PublicKey publicKey;
|
||||
public Certificate certificate;
|
||||
private int notificationId;
|
||||
private int protocolVersion;
|
||||
@@ -81,7 +77,7 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
|
||||
private final CopyOnWriteArrayList<BaseLink> links = new CopyOnWriteArrayList<>();
|
||||
|
||||
private List<String> m_supportedPlugins = new ArrayList<>();
|
||||
private List<String> supportedPlugins = new ArrayList<>();
|
||||
private final ConcurrentHashMap<String, Plugin> plugins = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Plugin> pluginsWithoutPermissions = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<String, Plugin> pluginsWithoutOptionalPermissions = new ConcurrentHashMap<>();
|
||||
@@ -150,19 +146,8 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
this.protocolVersion = NetworkPacket.ProtocolVersion; //We don't know it yet
|
||||
this.deviceType = DeviceType.FromString(settings.getString("deviceType", "desktop"));
|
||||
|
||||
try {
|
||||
String publicKeyStr = settings.getString("publicKey", null);
|
||||
if (publicKeyStr != null) {
|
||||
byte[] publicKeyBytes = Base64.decode(publicKeyStr, 0);
|
||||
publicKey = KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Device", "Exception deserializing stored public key for device");
|
||||
}
|
||||
|
||||
//Assume every plugin is supported until addLink is called and we can get the actual list
|
||||
m_supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
|
||||
supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
|
||||
|
||||
//Do not load plugins yet, the device is not present
|
||||
//reloadPluginsFromSettings();
|
||||
@@ -179,7 +164,6 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
this.pairStatus = PairStatus.NotPaired;
|
||||
this.protocolVersion = 0;
|
||||
this.deviceType = DeviceType.Computer;
|
||||
this.publicKey = null;
|
||||
|
||||
settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
||||
|
||||
@@ -459,8 +443,7 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
certificate = SslHelper.parseCertificate(certificateBytes);
|
||||
Log.i("KDE/Device", "Got certificate ");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Device", "Error getting certificate");
|
||||
Log.e("KDE/Device", "Error getting certificate", e);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -474,8 +457,7 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
|
||||
link.setPrivateKey(privateKey);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Device", "Exception reading our own private key"); //Should not happen
|
||||
Log.e("KDE/Device", "Exception reading our own private key", e); //Should not happen
|
||||
}
|
||||
|
||||
Log.i("KDE/Device", "addLink " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size());
|
||||
@@ -512,9 +494,9 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
Set<String> outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities", null);
|
||||
Set<String> incomingCapabilities = identityPacket.getStringSet("incomingCapabilities", null);
|
||||
if (incomingCapabilities != null && outgoingCapabilities != null) {
|
||||
m_supportedPlugins = new Vector<>(PluginFactory.pluginsForCapabilities(incomingCapabilities, outgoingCapabilities));
|
||||
supportedPlugins = new Vector<>(PluginFactory.pluginsForCapabilities(incomingCapabilities, outgoingCapabilities));
|
||||
} else {
|
||||
m_supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
|
||||
supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
|
||||
}
|
||||
|
||||
link.addPacketReceiver(this);
|
||||
@@ -549,8 +531,6 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
@Override
|
||||
public void onPacketReceived(NetworkPacket np) {
|
||||
|
||||
hackToMakeRetrocompatiblePacketTypes(np);
|
||||
|
||||
if (NetworkPacket.PACKET_TYPE_PAIR.equals(np.getType())) {
|
||||
|
||||
Log.i("KDE/Device", "Pair package");
|
||||
@@ -559,12 +539,16 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
try {
|
||||
ph.packageReceived(np);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("PairingPacketReceived", "Exception");
|
||||
Log.e("PairingPacketReceived", "Exception", e);
|
||||
}
|
||||
}
|
||||
} else if (isPaired()) {
|
||||
|
||||
// pluginsByIncomingInterface may not be built yet
|
||||
if(pluginsByIncomingInterface.isEmpty()) {
|
||||
reloadPluginsFromSettings();
|
||||
}
|
||||
|
||||
//If capabilities are not supported, iterate all plugins
|
||||
Collection<String> targetPlugins = pluginsByIncomingInterface.get(np.getType());
|
||||
if (targetPlugins != null && !targetPlugins.isEmpty()) {
|
||||
@@ -573,8 +557,7 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
try {
|
||||
plugin.onPacketReceived(np);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Device", "Exception in " + plugin.getPluginKey() + "'s onPacketReceived()");
|
||||
Log.e("KDE/Device", "Exception in " + plugin.getPluginKey() + "'s onPacketReceived()", e);
|
||||
//try { Log.e("KDE/Device", "NetworkPacket:" + np.serialize()); } catch (Exception _) { }
|
||||
}
|
||||
}
|
||||
@@ -599,8 +582,7 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
try {
|
||||
plugin.onUnpairedDevicePacketReceived(np);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/Device", "Exception in " + plugin.getDisplayName() + "'s onPacketReceived() in unPairedPacketListeners");
|
||||
Log.e("KDE/Device", "Exception in " + plugin.getDisplayName() + "'s onPacketReceived() in unPairedPacketListeners", e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -626,11 +608,7 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
if (e != null) {
|
||||
e.printStackTrace();
|
||||
} else {
|
||||
Log.e("KDE/sendPacket", "Unknown (null) exception");
|
||||
}
|
||||
Log.e("KDE/sendPacket", "Exception", e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -656,20 +634,12 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
}
|
||||
*/
|
||||
|
||||
hackToMakeRetrocompatiblePacketTypes(np);
|
||||
|
||||
boolean useEncryption = (protocolVersion < LanLinkProvider.MIN_VERSION_WITH_SSL_SUPPORT && (!np.getType().equals(NetworkPacket.PACKET_TYPE_PAIR) && isPaired()));
|
||||
|
||||
boolean success = false;
|
||||
//Make a copy to avoid concurrent modification exception if the original list changes
|
||||
for (final BaseLink link : links) {
|
||||
if (link == null)
|
||||
continue; //Since we made a copy, maybe somebody destroyed the link in the meanwhile
|
||||
if (useEncryption) {
|
||||
success = link.sendPacketEncrypted(np, callback, publicKey);
|
||||
} else {
|
||||
success = link.sendPacket(np, callback);
|
||||
}
|
||||
success = link.sendPacket(np, callback);
|
||||
if (success) break; //If the link didn't call sendSuccess(), try the next one
|
||||
}
|
||||
|
||||
@@ -729,10 +699,7 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
success = plugin.onCreate();
|
||||
} catch (Exception e) {
|
||||
success = false;
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (!success) {
|
||||
Log.e("KDE/addPlugin", "plugin failed to load " + pluginKey);
|
||||
Log.e("KDE/addPlugin", "plugin failed to load " + pluginKey, e);
|
||||
}
|
||||
|
||||
plugins.put(pluginKey, plugin);
|
||||
@@ -770,8 +737,7 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
plugin.onDestroy();
|
||||
//Log.e("removePlugin","removed " + pluginKey);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/removePlugin", "Exception calling onDestroy for plugin " + pluginKey);
|
||||
Log.e("KDE/removePlugin", "Exception calling onDestroy for plugin " + pluginKey, e);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -791,7 +757,7 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
|
||||
HashMap<String, ArrayList<String>> newPluginsByIncomingInterface = new HashMap<>();
|
||||
|
||||
for (String pluginKey : m_supportedPlugins) {
|
||||
for (String pluginKey : supportedPlugins) {
|
||||
|
||||
PluginFactory.PluginInfo pluginInfo = PluginFactory.getPluginInfo(pluginKey);
|
||||
|
||||
@@ -805,7 +771,6 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
boolean success = addPlugin(pluginKey);
|
||||
if (success) {
|
||||
for (String packageType : pluginInfo.getSupportedPacketTypes()) {
|
||||
packageType = hackToMakeRetrocompatiblePacketTypes(packageType);
|
||||
ArrayList<String> plugins = newPluginsByIncomingInterface.get(packageType);
|
||||
if (plugins == null) plugins = new ArrayList<>();
|
||||
plugins.add(pluginKey);
|
||||
@@ -872,17 +837,7 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
}
|
||||
|
||||
public List<String> getSupportedPlugins() {
|
||||
return m_supportedPlugins;
|
||||
}
|
||||
|
||||
private void hackToMakeRetrocompatiblePacketTypes(NetworkPacket np) {
|
||||
if (protocolVersion >= 6) return;
|
||||
np.mType = np.getType().replace(".request", "");
|
||||
}
|
||||
|
||||
private String hackToMakeRetrocompatiblePacketTypes(String type) {
|
||||
if (protocolVersion >= 6) return type;
|
||||
return type.replace(".request", "");
|
||||
return supportedPlugins;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -39,8 +39,7 @@ public class AppsHelper {
|
||||
|
||||
} catch (final PackageManager.NameNotFoundException e) {
|
||||
|
||||
e.printStackTrace();
|
||||
Log.e("AppsHelper", "Could not resolve name " + packageName);
|
||||
Log.e("AppsHelper", "Could not resolve name " + packageName, e);
|
||||
|
||||
return null;
|
||||
|
||||
@@ -57,15 +56,8 @@ public class AppsHelper {
|
||||
return pm.getApplicationIcon(ai);
|
||||
|
||||
} catch (final PackageManager.NameNotFoundException e) {
|
||||
|
||||
e.printStackTrace();
|
||||
Log.e("AppsHelper", "Could not find icon for " + packageName);
|
||||
|
||||
Log.e("AppsHelper", "Could not find icon for " + packageName, e);
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -46,6 +46,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.collection.LongSparseArray;
|
||||
|
||||
@@ -191,7 +192,7 @@ public class ContactsHelper {
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// If you are experiencing this, please open a bug report indicating how you got here
|
||||
e.printStackTrace();
|
||||
Log.e("Contacts", "Exception while fetching vcards", e);
|
||||
}
|
||||
|
||||
// At this point we are screwed:
|
||||
@@ -213,7 +214,6 @@ public class ContactsHelper {
|
||||
* @param IDs collection of uIDs to look up
|
||||
* @return Mapping of uIDs to the corresponding VCard
|
||||
*/
|
||||
@SuppressWarnings("UnnecessaryContinue")
|
||||
private static Map<uID, VCardBuilder> getVCardsSlow(Context context, Collection<uID> IDs) {
|
||||
Map<uID, VCardBuilder> toReturn = new HashMap<>();
|
||||
|
||||
@@ -239,10 +239,10 @@ public class ContactsHelper {
|
||||
toReturn.put(ID, new VCardBuilder(vcard.toString()));
|
||||
} catch (IOException e) {
|
||||
// If you are experiencing this, please open a bug report indicating how you got here
|
||||
e.printStackTrace();
|
||||
Log.e("Contacts", "Exception while fetching vcards", e);
|
||||
} catch (NullPointerException e) {
|
||||
// If you are experiencing this, please open a bug report indicating how you got here
|
||||
e.printStackTrace();
|
||||
Log.e("Contacts", "Exception while fetching vcards", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -394,6 +394,7 @@ public class ContactsHelper {
|
||||
.append("\n");
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String toString() {
|
||||
return vcardBody.toString() + VCARD_END;
|
||||
}
|
||||
@@ -415,9 +416,14 @@ public class ContactsHelper {
|
||||
static final String COLUMN = ContactsContract.Contacts.LOOKUP_KEY;
|
||||
|
||||
public uID(String lookupKey) {
|
||||
|
||||
if (lookupKey == null)
|
||||
throw new IllegalArgumentException("lookUpKey should not be null");
|
||||
|
||||
contactLookupKey = lookupKey;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String toString() {
|
||||
return this.contactLookupKey;
|
||||
}
|
||||
|
@@ -496,8 +496,7 @@ public class DeviceHelper {
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//Some phones might not define BRAND or MODEL, ignore exceptions
|
||||
Log.e("Exception", e.getMessage());
|
||||
e.printStackTrace();
|
||||
Log.e("Exception", e.getMessage(), e);
|
||||
}
|
||||
if (deviceName == null || deviceName.isEmpty()) {
|
||||
return "Android"; //Could not find a name
|
||||
|
@@ -20,11 +20,14 @@
|
||||
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
import android.webkit.MimeTypeMap;
|
||||
|
||||
@@ -32,9 +35,12 @@ import org.kde.kdeconnect.NetworkPacket;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class FilesHelper {
|
||||
|
||||
public static final String LOG_TAG = "SendFileActivity";
|
||||
|
||||
private static String getFileExt(String filename) {
|
||||
//return MimeTypeMap.getFileExtensionFromUrl(filename);
|
||||
return filename.substring((filename.lastIndexOf(".") + 1));
|
||||
@@ -119,71 +125,152 @@ public class FilesHelper {
|
||||
InputStream inputStream = cr.openInputStream(uri);
|
||||
|
||||
NetworkPacket np = new NetworkPacket(type);
|
||||
|
||||
String filename = null;
|
||||
long size = -1;
|
||||
Long lastModified = null;
|
||||
|
||||
if (uri.getScheme().equals("file")) {
|
||||
// file:// is a non media uri, so we cannot query the ContentProvider
|
||||
|
||||
np.set("filename", uri.getLastPathSegment());
|
||||
|
||||
try {
|
||||
size = new File(uri.getPath()).length();
|
||||
} catch (Exception e) {
|
||||
Log.e("SendFileActivity", "Could not obtain file size");
|
||||
e.printStackTrace();
|
||||
File mFile = new File(uri.getPath());
|
||||
|
||||
filename = mFile.getName();
|
||||
size = mFile.length();
|
||||
lastModified = mFile.lastModified();
|
||||
} catch (NullPointerException e) {
|
||||
Log.e(LOG_TAG, "Received bad file URI", e);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Probably a content:// uri, so we query the Media content provider
|
||||
// Since we used Intent.CATEGORY_OPENABLE, these two columns are the only ones we are
|
||||
// guaranteed to have: https://developer.android.com/reference/android/provider/OpenableColumns
|
||||
String[] proj = {
|
||||
OpenableColumns.SIZE,
|
||||
OpenableColumns.DISPLAY_NAME,
|
||||
};
|
||||
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
String[] proj = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.DISPLAY_NAME};
|
||||
cursor = cr.query(uri, proj, null, null, null);
|
||||
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
|
||||
try (Cursor cursor = cr.query(uri, proj, null, null, null)) {
|
||||
int nameColumnIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);
|
||||
int sizeColumnIndex = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE);
|
||||
cursor.moveToFirst();
|
||||
String path = cursor.getString(column_index);
|
||||
np.set("filename", Uri.parse(path).getLastPathSegment());
|
||||
size = new File(path).length();
|
||||
} catch (Exception unused) {
|
||||
|
||||
Log.w("SendFileActivity", "Could not resolve media to a file, trying to get info as media");
|
||||
filename = cursor.getString(nameColumnIndex);
|
||||
|
||||
try {
|
||||
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);
|
||||
cursor.moveToFirst();
|
||||
String name = cursor.getString(column_index);
|
||||
np.set("filename", name);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("SendFileActivity", "Could not obtain file name");
|
||||
// It is recommended to check for the value to be null because there are
|
||||
// situations were we don't know the size (for instance, if the file is
|
||||
// not local to the device)
|
||||
if (!cursor.isNull(sizeColumnIndex)) {
|
||||
size = cursor.getInt(sizeColumnIndex);
|
||||
}
|
||||
|
||||
try {
|
||||
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE);
|
||||
cursor.moveToFirst();
|
||||
//For some reason this size can differ from the actual file size!
|
||||
size = cursor.getInt(column_index);
|
||||
} catch (Exception e) {
|
||||
Log.e("SendFileActivity", "Could not obtain file size");
|
||||
e.printStackTrace();
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
cursor.close();
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
lastModified = getLastModifiedTime(context, uri);
|
||||
} catch (Exception e) {
|
||||
Log.e(LOG_TAG, "Problem getting file information", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (filename != null) {
|
||||
np.set("filename", filename);
|
||||
} else {
|
||||
// It would be very surprising if this happens
|
||||
Log.e(LOG_TAG, "Unable to read filename");
|
||||
}
|
||||
|
||||
if (lastModified != null) {
|
||||
np.set("lastModified", lastModified);
|
||||
} else {
|
||||
// This would not be too surprising, and probably means we need to improve
|
||||
// FilesHelper.getLastModifiedTime
|
||||
Log.w(LOG_TAG, "Unable to read file last modified time");
|
||||
}
|
||||
|
||||
np.setPayload(new NetworkPacket.Payload(inputStream, size));
|
||||
|
||||
return np;
|
||||
} catch (Exception e) {
|
||||
Log.e("SendFileActivity", "Exception creating network packet", e);
|
||||
e.printStackTrace();
|
||||
Log.e(LOG_TAG, "Exception creating network packet", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* By hook or by crook, get the last modified time of the passed content:// URI
|
||||
*
|
||||
* This is a challenge because different content sources have different columns defined, and
|
||||
* I don't know how to tell what the source of the content is.
|
||||
*
|
||||
* Therefore, my brilliant solution is to just try everything until something works.
|
||||
*
|
||||
* Will return null if nothing worked.
|
||||
*/
|
||||
public static Long getLastModifiedTime(final Context context, final Uri uri) {
|
||||
ContentResolver cr = context.getContentResolver();
|
||||
|
||||
Long lastModifiedTime = null;
|
||||
|
||||
// Open a cursor without a column because we do not yet know what columns are defined
|
||||
try (Cursor cursor = cr.query(uri, null, null, null, null)) {
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
String[] allColumns = cursor.getColumnNames();
|
||||
|
||||
// MediaStore.MediaColumns.DATE_MODIFIED resolves to "date_modified"
|
||||
// I see this column defined in case we used the Gallery app to select the file to transfer
|
||||
// This can occur both for devices running Storage Access Framework (SAF) if we select
|
||||
// the Gallery to provide the file to transfer, as well as for older devices by doing the same
|
||||
int mediaDataModifiedColumnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED);
|
||||
|
||||
// DocumentsContract.Document.COLUMN_LAST_MODIFIED resolves to "last_modified"
|
||||
// I see this column defined when, on a device using SAF we select a file using the
|
||||
// file browser
|
||||
// According to https://developer.android.com/reference/kotlin/android/provider/DocumentsContract
|
||||
// all "document providers" must provide certain columns. Do we actually have a DocumentProvider here?
|
||||
// I do not think this code path will ever happen for a non-media file is selected on
|
||||
// an API < KitKat device, since those will be delivered as a file:// URI and handled
|
||||
// accordingly. Therefore, it is safe to ignore the warning that this field requires
|
||||
// API 19
|
||||
@SuppressLint("InlinedApi")
|
||||
int documentLastModifiedColumnIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED);
|
||||
|
||||
// If we have an image, it may be the case that MediaStore.MediaColumns.DATE_MODIFIED
|
||||
// catches the modification date, but if not, here is another column we can look for.
|
||||
// This should be checked *after* DATE_MODIFIED since I think that column might give
|
||||
// better information
|
||||
int imageDateTakenColumnIndex = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATE_TAKEN);
|
||||
|
||||
// Report whether the captured timestamp is in milliseconds or seconds
|
||||
// The truthy-ness of this value for each different type of column is known from either
|
||||
// experimentation or the docs (when docs exist...)
|
||||
boolean milliseconds;
|
||||
|
||||
int properColumnIndex;
|
||||
if (mediaDataModifiedColumnIndex >= 0) {
|
||||
properColumnIndex = mediaDataModifiedColumnIndex;
|
||||
milliseconds = false;
|
||||
} else if (documentLastModifiedColumnIndex >= 0) {
|
||||
properColumnIndex = documentLastModifiedColumnIndex;
|
||||
milliseconds = true;
|
||||
} else if (imageDateTakenColumnIndex >= 0) {
|
||||
properColumnIndex = imageDateTakenColumnIndex;
|
||||
milliseconds = true;
|
||||
} else {
|
||||
// Nothing worked :(
|
||||
String formattedColumns = Arrays.toString(allColumns);
|
||||
Log.w("SendFileActivity", "Unable to get file modification time. Available columns were: " + formattedColumns);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!cursor.isNull(properColumnIndex)) {
|
||||
lastModifiedTime = cursor.getLong(properColumnIndex);
|
||||
}
|
||||
|
||||
if (!milliseconds) {
|
||||
lastModifiedTime *= 1000;
|
||||
milliseconds = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return lastModifiedTime;
|
||||
}
|
||||
}
|
||||
|
@@ -36,23 +36,20 @@ public class NetworkHelper {
|
||||
return false; //We are connected to at least one non-mobile network
|
||||
}
|
||||
if (mobile) { //We suspect we are on a mobile net
|
||||
try {
|
||||
try (LineNumberReader is = new LineNumberReader(new FileReader("/proc/net/arp"))) {
|
||||
//Check the number of network neighbours, on data it should be 0
|
||||
LineNumberReader is = new LineNumberReader(new FileReader("/proc/net/arp"));
|
||||
is.skip(Long.MAX_VALUE);
|
||||
//Log.e("NetworkHelper", "procnetarp has " + is.getLineNumber() + " lines");
|
||||
if (is.getLineNumber() > 1) { //The first line are the headers
|
||||
return false; //I have neighbours, so this doesn't look like a mobile network
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.e("NetworkHelper", "Exception reading procnetarp");
|
||||
e.printStackTrace();
|
||||
Log.e("NetworkHelper", "Exception reading procnetarp", e);
|
||||
}
|
||||
}
|
||||
return mobile;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.d("isOnMobileNetwork", "Something went wrong, but this is non-critical.");
|
||||
Log.e("isOnMobileNetwork", "Something went wrong, but this is non-critical.", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@@ -1,7 +0,0 @@
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
class ObjectsHelper {
|
||||
public static boolean equals(Object a, Object b) {
|
||||
return (a == null) ? (b == null) : a.equals(b);
|
||||
}
|
||||
}
|
@@ -40,6 +40,7 @@ import java.util.concurrent.locks.Condition;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
public class SMSHelper {
|
||||
@@ -93,6 +94,7 @@ public class SMSHelper {
|
||||
* @param threadID Thread to look up
|
||||
* @return List of all messages in the thread
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
public static List<Message> getMessagesInThread(Context context, ThreadID threadID) {
|
||||
final String selection = ThreadID.lookupColumn + " == ?";
|
||||
final String[] selectionArgs = new String[] { threadID.toString() };
|
||||
@@ -106,6 +108,7 @@ public class SMSHelper {
|
||||
* @param timestamp epoch in millis matching the timestamp to return
|
||||
* @return null if no matching message is found, otherwise return a Message
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
public static List<Message> getMessagesSinceTimestamp(Context context, long timestamp) {
|
||||
final String selection = Message.DATE + " > ?";
|
||||
final String[] selectionArgs = new String[] {Long.toString(timestamp)};
|
||||
@@ -122,10 +125,11 @@ public class SMSHelper {
|
||||
* @param selectionArgs Parameters for selection. May be null.
|
||||
* @return Returns HashMap<ThreadID, List<Message>>, which is transformed in caller functions into other classes.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
private static HashMap<ThreadID, List<Message>> getMessages(Uri Uri,
|
||||
Context context,
|
||||
String selection,
|
||||
String[] selectionArgs) {
|
||||
Context context,
|
||||
String selection,
|
||||
String[] selectionArgs) {
|
||||
HashMap<ThreadID, List<Message>> toReturn = new HashMap<>();
|
||||
try (Cursor myCursor = context.getContentResolver().query(
|
||||
Uri,
|
||||
@@ -145,7 +149,7 @@ public class SMSHelper {
|
||||
}
|
||||
|
||||
Message message = new Message(messageInfo);
|
||||
ThreadID threadID = new ThreadID(message.m_threadID);
|
||||
ThreadID threadID = new ThreadID(message.threadID);
|
||||
|
||||
if (!toReturn.containsKey(threadID)) {
|
||||
toReturn.put(threadID, new ArrayList<>());
|
||||
@@ -167,6 +171,7 @@ public class SMSHelper {
|
||||
* @param selectionArgs Parameters for selection. May be null.
|
||||
* @return List of messages matching the filter
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
private static List<Message> getMessagesWithFilter(Context context, String selection, String[] selectionArgs) {
|
||||
HashMap<ThreadID, List<Message>> result = getMessages(SMSHelper.getSMSUri(), context, selection, selectionArgs);
|
||||
List<Message> toReturn = new ArrayList<>();
|
||||
@@ -184,6 +189,7 @@ public class SMSHelper {
|
||||
* @param context android.content.Context running the request
|
||||
* @return Mapping of thread_id to the first message in each thread
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
public static Map<ThreadID, Message> getConversations(Context context) {
|
||||
HashMap<ThreadID, List<Message>> result = getMessages(SMSHelper.getConversationUri(), context, null, null);
|
||||
HashMap<ThreadID, Message> toReturn = new HashMap<>();
|
||||
@@ -213,6 +219,7 @@ public class SMSHelper {
|
||||
/**
|
||||
* Represent an ID used to uniquely identify a message thread
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
public static class ThreadID {
|
||||
final Long threadID;
|
||||
static final String lookupColumn = Telephony.Sms.THREAD_ID;
|
||||
@@ -221,13 +228,14 @@ public class SMSHelper {
|
||||
this.threadID = threadID;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public String toString() {
|
||||
return this.threadID.toString();
|
||||
return threadID.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.threadID.hashCode();
|
||||
return threadID.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -239,15 +247,16 @@ public class SMSHelper {
|
||||
/**
|
||||
* Represent a message and all of its interesting data columns
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
public static class Message {
|
||||
|
||||
final String m_address;
|
||||
final String m_body;
|
||||
public final long m_date;
|
||||
final int m_type;
|
||||
final int m_read;
|
||||
final long m_threadID; // ThreadID is *int* for SMS messages but *long* for MMS
|
||||
final int m_uID;
|
||||
final String address;
|
||||
final String body;
|
||||
public final long date;
|
||||
final int type;
|
||||
final int read;
|
||||
final long threadID; // ThreadID is *int* for SMS messages but *long* for MMS
|
||||
final int uID;
|
||||
|
||||
/**
|
||||
* Named constants which are used to construct a Message
|
||||
@@ -283,40 +292,40 @@ public class SMSHelper {
|
||||
};
|
||||
|
||||
Message(final HashMap<String, String> messageInfo) {
|
||||
m_address = messageInfo.get(Message.ADDRESS);
|
||||
m_body = messageInfo.get(Message.BODY);
|
||||
m_date = Long.parseLong(messageInfo.get(Message.DATE));
|
||||
address = messageInfo.get(Message.ADDRESS);
|
||||
body = messageInfo.get(Message.BODY);
|
||||
date = Long.parseLong(messageInfo.get(Message.DATE));
|
||||
if (messageInfo.get(Message.TYPE) == null)
|
||||
{
|
||||
// To be honest, I have no idea why this happens. The docs say the TYPE field is mandatory.
|
||||
// Just stick some junk in here and hope we can figure it out later.
|
||||
// Quick investigation suggests that these are multi-target MMSes
|
||||
m_type = -1;
|
||||
type = -1;
|
||||
} else {
|
||||
m_type = Integer.parseInt(messageInfo.get(Message.TYPE));
|
||||
type = Integer.parseInt(messageInfo.get(Message.TYPE));
|
||||
}
|
||||
m_read = Integer.parseInt(messageInfo.get(Message.READ));
|
||||
m_threadID = Long.parseLong(messageInfo.get(Message.THREAD_ID));
|
||||
m_uID = Integer.parseInt(messageInfo.get(Message.U_ID));
|
||||
read = Integer.parseInt(messageInfo.get(Message.READ));
|
||||
threadID = Long.parseLong(messageInfo.get(Message.THREAD_ID));
|
||||
uID = Integer.parseInt(messageInfo.get(Message.U_ID));
|
||||
}
|
||||
|
||||
public JSONObject toJSONObject() throws JSONException {
|
||||
JSONObject json = new JSONObject();
|
||||
|
||||
json.put(Message.ADDRESS, m_address);
|
||||
json.put(Message.BODY, m_body);
|
||||
json.put(Message.DATE, m_date);
|
||||
json.put(Message.TYPE, m_type);
|
||||
json.put(Message.READ, m_read);
|
||||
json.put(Message.THREAD_ID, m_threadID);
|
||||
json.put(Message.U_ID, m_uID);
|
||||
json.put(Message.ADDRESS, address);
|
||||
json.put(Message.BODY, body);
|
||||
json.put(Message.DATE, date);
|
||||
json.put(Message.TYPE, type);
|
||||
json.put(Message.READ, read);
|
||||
json.put(Message.THREAD_ID, threadID);
|
||||
json.put(Message.U_ID, uID);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.m_body;
|
||||
return body;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -26,11 +26,6 @@ import android.preference.PreferenceManager;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
@@ -40,8 +35,6 @@ import java.security.PublicKey;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
public class RsaHelper {
|
||||
|
||||
public static void initialiseRsaKeys(Context context) {
|
||||
@@ -55,8 +48,7 @@ public class RsaHelper {
|
||||
keyGen.initialize(2048);
|
||||
keyPair = keyGen.genKeyPair();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/initializeRsaKeys", "Exception");
|
||||
Log.e("KDE/initializeRsaKeys", "Exception", e);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -78,67 +70,12 @@ public class RsaHelper {
|
||||
return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
|
||||
}
|
||||
|
||||
public static PublicKey getPublicKey(Context context, String deviceId) throws GeneralSecurityException {
|
||||
SharedPreferences settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
||||
byte[] publicKeyBytes = Base64.decode(settings.getString("publicKey", ""), 0);
|
||||
return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKeyBytes));
|
||||
}
|
||||
|
||||
public static PrivateKey getPrivateKey(Context context) throws GeneralSecurityException {
|
||||
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
byte[] privateKeyBytes = Base64.decode(globalSettings.getString("privateKey", ""), 0);
|
||||
return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
|
||||
}
|
||||
|
||||
public static NetworkPacket encrypt(NetworkPacket np, PublicKey publicKey) throws GeneralSecurityException, JSONException {
|
||||
|
||||
String serialized = np.serialize();
|
||||
|
||||
int chunkSize = 128;
|
||||
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
|
||||
JSONArray chunks = new JSONArray();
|
||||
while (serialized.length() > 0) {
|
||||
if (serialized.length() < chunkSize) {
|
||||
chunkSize = serialized.length();
|
||||
}
|
||||
String chunk = serialized.substring(0, chunkSize);
|
||||
serialized = serialized.substring(chunkSize);
|
||||
byte[] chunkBytes = chunk.getBytes(Charset.defaultCharset());
|
||||
byte[] encryptedChunk;
|
||||
encryptedChunk = cipher.doFinal(chunkBytes);
|
||||
chunks.put(Base64.encodeToString(encryptedChunk, Base64.NO_WRAP));
|
||||
}
|
||||
|
||||
//Log.i("NetworkPacket", "Encrypted " + chunks.length()+" chunks");
|
||||
|
||||
NetworkPacket encrypted = new NetworkPacket(NetworkPacket.PACKET_TYPE_ENCRYPTED);
|
||||
encrypted.set("data", chunks);
|
||||
encrypted.setPayload(np.getPayload());
|
||||
return encrypted;
|
||||
|
||||
}
|
||||
|
||||
public static NetworkPacket decrypt(NetworkPacket np, PrivateKey privateKey) throws GeneralSecurityException, JSONException {
|
||||
|
||||
JSONArray chunks = np.getJSONArray("data");
|
||||
|
||||
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
|
||||
StringBuilder decryptedJson = new StringBuilder();
|
||||
for (int i = 0; i < chunks.length(); i++) {
|
||||
byte[] encryptedChunk = Base64.decode(chunks.getString(i), Base64.NO_WRAP);
|
||||
String decryptedChunk = new String(cipher.doFinal(encryptedChunk));
|
||||
decryptedJson.append(decryptedChunk);
|
||||
}
|
||||
|
||||
NetworkPacket decrypted = NetworkPacket.unserialize(decryptedJson.toString());
|
||||
decrypted.setPayload(np.getPayload());
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -65,11 +65,6 @@ import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public class SslHelper {
|
||||
|
||||
public enum SslMode {
|
||||
Client,
|
||||
Server
|
||||
}
|
||||
|
||||
public static X509Certificate certificate; //my device's certificate
|
||||
|
||||
public static final BouncyCastleProvider BC = new BouncyCastleProvider();
|
||||
@@ -114,8 +109,7 @@ public class SslHelper {
|
||||
edit.apply();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/initialiseCert", "Exception");
|
||||
Log.e("KDE/initialiseCert", "Exception", e);
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -125,8 +119,7 @@ public class SslHelper {
|
||||
X509CertificateHolder certificateHolder = new X509CertificateHolder(certificateBytes);
|
||||
certificate = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certificateHolder);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/SslHelper", "Exception reading own certificate");
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/SslHelper", "Exception reading own certificate", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,8 +188,7 @@ public class SslHelper {
|
||||
}
|
||||
return tlsContext;
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/SslHelper", "Error creating tls context");
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/SslHelper", "Error creating tls context", e);
|
||||
}
|
||||
return null;
|
||||
|
||||
|
@@ -26,6 +26,7 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
@@ -89,10 +90,10 @@ public class StorageHelper {
|
||||
try (Scanner scanner = new Scanner(new File("/proc/mounts"))) {
|
||||
mounts = scanner.useDelimiter("\\A").next();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("StorageHelper", "Exception while getting storageList", e);
|
||||
}
|
||||
|
||||
File dirs[] = storage.listFiles();
|
||||
File[] dirs = storage.listFiles();
|
||||
for (File dir : dirs) {
|
||||
//Log.e("getStorageList", "path: "+dir.getAbsolutePath());
|
||||
if (dir.isDirectory() && dir.canRead() && dir.canExecute()) {
|
||||
@@ -129,7 +130,7 @@ public class StorageHelper {
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("StorageHelper", "Exception", e);
|
||||
}
|
||||
|
||||
for (String line : entries) {
|
||||
|
@@ -44,12 +44,10 @@ public class NetworkPacket {
|
||||
|
||||
public final static String PACKET_TYPE_IDENTITY = "kdeconnect.identity";
|
||||
public final static String PACKET_TYPE_PAIR = "kdeconnect.pair";
|
||||
public final static String PACKET_TYPE_ENCRYPTED = "kdeconnect.encrypted";
|
||||
|
||||
public static Set<String> protocolPacketTypes = new HashSet<String>() {{
|
||||
add(PACKET_TYPE_IDENTITY);
|
||||
add(PACKET_TYPE_PAIR);
|
||||
add(PACKET_TYPE_ENCRYPTED);
|
||||
}};
|
||||
|
||||
private long mId;
|
||||
@@ -57,6 +55,7 @@ public class NetworkPacket {
|
||||
private JSONObject mBody;
|
||||
private Payload mPayload;
|
||||
private JSONObject mPayloadTransferInfo;
|
||||
private volatile boolean canceled;
|
||||
|
||||
private NetworkPacket() {
|
||||
|
||||
@@ -70,6 +69,9 @@ public class NetworkPacket {
|
||||
mPayloadTransferInfo = new JSONObject();
|
||||
}
|
||||
|
||||
public boolean isCanceled() { return canceled; }
|
||||
public void cancel() { canceled = true; }
|
||||
|
||||
public String getType() {
|
||||
return mType;
|
||||
}
|
||||
@@ -91,7 +93,7 @@ public class NetworkPacket {
|
||||
if (value == null) return;
|
||||
try {
|
||||
mBody.put(key, value);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +116,7 @@ public class NetworkPacket {
|
||||
public void set(String key, int value) {
|
||||
try {
|
||||
mBody.put(key, value);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +131,7 @@ public class NetworkPacket {
|
||||
public void set(String key, boolean value) {
|
||||
try {
|
||||
mBody.put(key, value);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +146,7 @@ public class NetworkPacket {
|
||||
public void set(String key, double value) {
|
||||
try {
|
||||
mBody.put(key, value);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +157,7 @@ public class NetworkPacket {
|
||||
public void set(String key, JSONArray value) {
|
||||
try {
|
||||
mBody.put(key, value);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +168,7 @@ public class NetworkPacket {
|
||||
public void set(String key, JSONObject value) {
|
||||
try {
|
||||
mBody.put(key, value);
|
||||
} catch (JSONException e) {
|
||||
} catch (JSONException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +181,7 @@ public class NetworkPacket {
|
||||
try {
|
||||
String str = jsonArray.getString(i);
|
||||
list.add(str);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return list;
|
||||
@@ -197,7 +199,7 @@ public class NetworkPacket {
|
||||
jsonArray.put(str);
|
||||
}
|
||||
mBody.put(key, jsonArray);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,7 +212,7 @@ public class NetworkPacket {
|
||||
try {
|
||||
String str = jsonArray.getString(i);
|
||||
list.add(str);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
return list;
|
||||
@@ -228,7 +230,7 @@ public class NetworkPacket {
|
||||
jsonArray.put(str);
|
||||
}
|
||||
mBody.put(key, jsonArray);
|
||||
} catch (Exception e) {
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,8 +281,7 @@ public class NetworkPacket {
|
||||
np.mBody.put("incomingCapabilities", new JSONArray(PluginFactory.getIncomingCapabilities()));
|
||||
np.mBody.put("outgoingCapabilities", new JSONArray(PluginFactory.getOutgoingCapabilities()));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("NetworkPacakge", "Exception on createIdentityPacket");
|
||||
Log.e("NetworkPackage", "Exception on createIdentityPacket", e);
|
||||
}
|
||||
|
||||
return np;
|
||||
@@ -318,7 +319,7 @@ public class NetworkPacket {
|
||||
private Socket inputSocket;
|
||||
private long payloadSize;
|
||||
|
||||
Payload(long payloadSize) {
|
||||
public Payload(long payloadSize) {
|
||||
this((InputStream)null, payloadSize);
|
||||
}
|
||||
|
||||
|
@@ -29,7 +29,6 @@ import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
|
||||
public class KeyListenerView extends View {
|
||||
@@ -104,12 +103,7 @@ public class KeyListenerView extends View {
|
||||
}
|
||||
|
||||
private void sendKeyPressPacket(final NetworkPacket np) {
|
||||
BackgroundService.RunCommand(getContext(), service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendKeyboardPacket(np);
|
||||
});
|
||||
BackgroundService.RunWithPlugin(getContext(), deviceId, MousePadPlugin.class, plugin -> plugin.sendKeyboardPacket(np));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -27,13 +27,13 @@ import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
@@ -41,7 +41,6 @@ import android.widget.TextView;
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.Plugins.SystemvolumePlugin.SystemvolumeFragment;
|
||||
import org.kde.kdeconnect.UserInterface.ThemeUtil;
|
||||
@@ -53,6 +52,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.graphics.drawable.DrawableCompat;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import butterknife.BindView;
|
||||
import butterknife.ButterKnife;
|
||||
|
||||
public class MprisActivity extends AppCompatActivity {
|
||||
|
||||
@@ -61,6 +62,55 @@ public class MprisActivity extends AppCompatActivity {
|
||||
private Runnable positionSeekUpdateRunnable = null;
|
||||
private MprisPlugin.MprisPlayer targetPlayer = null;
|
||||
|
||||
@BindView(R.id.play_button)
|
||||
ImageButton playButton;
|
||||
|
||||
@BindView(R.id.prev_button)
|
||||
ImageButton prevButton;
|
||||
|
||||
@BindView(R.id.next_button)
|
||||
ImageButton nextButton;
|
||||
|
||||
@BindView(R.id.rew_button)
|
||||
ImageButton rewButton;
|
||||
|
||||
@BindView(R.id.ff_button)
|
||||
ImageButton ffButton;
|
||||
|
||||
@BindView(R.id.time_textview)
|
||||
TextView timeText;
|
||||
|
||||
@BindView(R.id.album_art)
|
||||
ImageView albumArtView;
|
||||
|
||||
@BindView(R.id.player_spinner)
|
||||
Spinner playerSpinner;
|
||||
|
||||
@BindView(R.id.no_players)
|
||||
TextView noPlayers;
|
||||
|
||||
@BindView(R.id.now_playing_textview)
|
||||
TextView nowPlayingText;
|
||||
|
||||
@BindView(R.id.positionSeek)
|
||||
SeekBar positionBar;
|
||||
|
||||
@BindView(R.id.progress_slider)
|
||||
LinearLayout progressSlider;
|
||||
|
||||
@BindView(R.id.volume_seek)
|
||||
SeekBar volumeSeek;
|
||||
|
||||
@BindView(R.id.volume_layout)
|
||||
LinearLayout volumeLayout;
|
||||
|
||||
@BindView(R.id.stop_button)
|
||||
ImageButton stopButton;
|
||||
|
||||
@BindView(R.id.progress_textview)
|
||||
TextView progressText;
|
||||
|
||||
|
||||
private static String milisToProgress(long milis) {
|
||||
int length = (int) (milis / 1000); //From milis to seconds
|
||||
StringBuilder text = new StringBuilder();
|
||||
@@ -81,14 +131,7 @@ public class MprisActivity extends AppCompatActivity {
|
||||
|
||||
private void connectToPlugin(final String targetPlayerName) {
|
||||
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
|
||||
final Device device = service.getDevice(deviceId);
|
||||
final MprisPlugin mpris = device.getPlugin(MprisPlugin.class);
|
||||
if (mpris == null) {
|
||||
Log.e("MprisActivity", "device has no mpris plugin!");
|
||||
return;
|
||||
}
|
||||
BackgroundService.RunWithPlugin(this, deviceId, MprisPlugin.class, mpris -> {
|
||||
targetPlayer = mpris.getPlayerStatus(targetPlayerName);
|
||||
|
||||
addSytemvolumeFragment();
|
||||
@@ -111,20 +154,19 @@ public class MprisActivity extends AppCompatActivity {
|
||||
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
runOnUiThread(() -> {
|
||||
Spinner spinner = findViewById(R.id.player_spinner);
|
||||
//String prevPlayer = (String)spinner.getSelectedItem();
|
||||
spinner.setAdapter(adapter);
|
||||
|
||||
playerSpinner.setAdapter(adapter);
|
||||
|
||||
if (playerList.isEmpty()) {
|
||||
findViewById(R.id.no_players).setVisibility(View.VISIBLE);
|
||||
spinner.setVisibility(View.GONE);
|
||||
((TextView) findViewById(R.id.now_playing_textview)).setText("");
|
||||
noPlayers.setVisibility(View.VISIBLE);
|
||||
playerSpinner.setVisibility(View.GONE);
|
||||
nowPlayingText.setText("");
|
||||
} else {
|
||||
findViewById(R.id.no_players).setVisibility(View.GONE);
|
||||
spinner.setVisibility(View.VISIBLE);
|
||||
noPlayers.setVisibility(View.GONE);
|
||||
playerSpinner.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
playerSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> arg0, View arg1, int pos, long id) {
|
||||
|
||||
@@ -156,7 +198,7 @@ public class MprisActivity extends AppCompatActivity {
|
||||
if (targetPlayer != null) {
|
||||
int targetIndex = adapter.getPosition(targetPlayer.getPlayer());
|
||||
if (targetIndex >= 0) {
|
||||
spinner.setSelection(targetIndex);
|
||||
playerSpinner.setSelection(targetIndex);
|
||||
} else {
|
||||
targetPlayer = null;
|
||||
}
|
||||
@@ -164,7 +206,7 @@ public class MprisActivity extends AppCompatActivity {
|
||||
//If no player selected, select the first one (if any)
|
||||
if (targetPlayer == null && !playerList.isEmpty()) {
|
||||
targetPlayer = mpris.getPlayerStatus(playerList.get(0));
|
||||
spinner.setSelection(0);
|
||||
playerSpinner.setSelection(0);
|
||||
}
|
||||
updatePlayerStatus(mpris);
|
||||
});
|
||||
@@ -172,7 +214,6 @@ public class MprisActivity extends AppCompatActivity {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void addSytemvolumeFragment() {
|
||||
@@ -210,55 +251,53 @@ public class MprisActivity extends AppCompatActivity {
|
||||
}
|
||||
String song = playerStatus.getCurrentSong();
|
||||
|
||||
TextView nowPlaying = findViewById(R.id.now_playing_textview);
|
||||
if (!nowPlaying.getText().toString().equals(song)) {
|
||||
nowPlaying.setText(song);
|
||||
if (!nowPlayingText.getText().toString().equals(song)) {
|
||||
nowPlayingText.setText(song);
|
||||
}
|
||||
|
||||
Bitmap albumArt = playerStatus.getAlbumArt();
|
||||
if (albumArt == null) {
|
||||
Drawable placeholder_art = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_album_art_placeholder));
|
||||
DrawableCompat.setTint(placeholder_art, getResources().getColor(R.color.primary));
|
||||
((ImageView) findViewById(R.id.album_art)).setImageDrawable(placeholder_art);
|
||||
albumArtView.setImageDrawable(placeholder_art);
|
||||
} else {
|
||||
((ImageView) findViewById(R.id.album_art)).setImageBitmap(albumArt);
|
||||
albumArtView.setImageBitmap(albumArt);
|
||||
}
|
||||
|
||||
if (playerStatus.isSeekAllowed()) {
|
||||
((TextView) findViewById(R.id.time_textview)).setText(milisToProgress(playerStatus.getLength()));
|
||||
SeekBar positionSeek = findViewById(R.id.positionSeek);
|
||||
positionSeek.setMax((int) (playerStatus.getLength()));
|
||||
positionSeek.setProgress((int) (playerStatus.getPosition()));
|
||||
findViewById(R.id.progress_slider).setVisibility(View.VISIBLE);
|
||||
timeText.setText(milisToProgress(playerStatus.getLength()));
|
||||
positionBar.setMax((int) (playerStatus.getLength()));
|
||||
positionBar.setProgress((int) (playerStatus.getPosition()));
|
||||
progressSlider.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
findViewById(R.id.progress_slider).setVisibility(View.GONE);
|
||||
progressSlider.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
int volume = playerStatus.getVolume();
|
||||
((SeekBar) findViewById(R.id.volume_seek)).setProgress(volume);
|
||||
volumeSeek.setProgress(volume);
|
||||
|
||||
boolean isPlaying = playerStatus.isPlaying();
|
||||
if (isPlaying) {
|
||||
((ImageButton) findViewById(R.id.play_button)).setImageResource(R.drawable.ic_pause_black);
|
||||
findViewById(R.id.play_button).setEnabled(playerStatus.isPauseAllowed());
|
||||
playButton.setImageResource(R.drawable.ic_pause_black);
|
||||
playButton.setEnabled(playerStatus.isPauseAllowed());
|
||||
} else {
|
||||
((ImageButton) findViewById(R.id.play_button)).setImageResource(R.drawable.ic_play_black);
|
||||
findViewById(R.id.play_button).setEnabled(playerStatus.isPlayAllowed());
|
||||
playButton.setImageResource(R.drawable.ic_play_black);
|
||||
playButton.setEnabled(playerStatus.isPlayAllowed());
|
||||
}
|
||||
|
||||
findViewById(R.id.volume_layout).setVisibility(playerStatus.isSetVolumeAllowed() ? View.VISIBLE : View.GONE);
|
||||
findViewById(R.id.rew_button).setVisibility(playerStatus.isSeekAllowed() ? View.VISIBLE : View.GONE);
|
||||
findViewById(R.id.ff_button).setVisibility(playerStatus.isSeekAllowed() ? View.VISIBLE : View.GONE);
|
||||
volumeLayout.setVisibility(playerStatus.isSetVolumeAllowed() ? View.VISIBLE : View.GONE);
|
||||
rewButton.setVisibility(playerStatus.isSeekAllowed() ? View.VISIBLE : View.GONE);
|
||||
ffButton.setVisibility(playerStatus.isSeekAllowed() ? View.VISIBLE : View.GONE);
|
||||
|
||||
//Show and hide previous/next buttons simultaneously
|
||||
if (playerStatus.isGoPreviousAllowed() || playerStatus.isGoNextAllowed()) {
|
||||
findViewById(R.id.prev_button).setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.prev_button).setEnabled(playerStatus.isGoPreviousAllowed());
|
||||
findViewById(R.id.next_button).setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.next_button).setEnabled(playerStatus.isGoNextAllowed());
|
||||
prevButton.setVisibility(View.VISIBLE);
|
||||
prevButton.setEnabled(playerStatus.isGoPreviousAllowed());
|
||||
nextButton.setVisibility(View.VISIBLE);
|
||||
nextButton.setEnabled(playerStatus.isGoNextAllowed());
|
||||
} else {
|
||||
findViewById(R.id.prev_button).setVisibility(View.GONE);
|
||||
findViewById(R.id.next_button).setVisibility(View.GONE);
|
||||
prevButton.setVisibility(View.GONE);
|
||||
nextButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +312,7 @@ public class MprisActivity extends AppCompatActivity {
|
||||
}
|
||||
final int currentVolume = targetPlayer.getVolume();
|
||||
|
||||
if (currentVolume < 100 || currentVolume > 0) {
|
||||
if (currentVolume <= 100 && currentVolume >= 0) {
|
||||
int newVolume = currentVolume + step;
|
||||
if (newVolume > 100) {
|
||||
newVolume = 100;
|
||||
@@ -310,11 +349,23 @@ public class MprisActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private interface MprisPlayerCallback {
|
||||
void performAction(MprisPlugin.MprisPlayer player);
|
||||
}
|
||||
|
||||
private void performActionOnClick(View v, MprisPlayerCallback l) {
|
||||
v.setOnClickListener(view -> BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer == null) return;
|
||||
l.performAction(targetPlayer);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeUtil.setUserPreferredTheme(this);
|
||||
setContentView(R.layout.activity_mpris);
|
||||
ButterKnife.bind(this);
|
||||
|
||||
final String targetPlayerName = getIntent().getStringExtra("player");
|
||||
getIntent().removeExtra("player");
|
||||
@@ -328,37 +379,19 @@ public class MprisActivity extends AppCompatActivity {
|
||||
BackgroundService.RunCommand(MprisActivity.this, service -> service.addConnectionListener(connectionReceiver));
|
||||
connectToPlugin(targetPlayerName);
|
||||
|
||||
findViewById(R.id.play_button).setOnClickListener(view -> BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.playPause();
|
||||
}));
|
||||
performActionOnClick(playButton, MprisPlugin.MprisPlayer::playPause);
|
||||
|
||||
findViewById(R.id.prev_button).setOnClickListener(view -> BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.previous();
|
||||
}));
|
||||
performActionOnClick(prevButton, MprisPlugin.MprisPlayer::previous);
|
||||
|
||||
findViewById(R.id.rew_button).setOnClickListener(view -> BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.seek(interval_time * -1);
|
||||
}));
|
||||
performActionOnClick(rewButton, p -> targetPlayer.seek(interval_time * -1));
|
||||
|
||||
findViewById(R.id.ff_button).setOnClickListener(view -> BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.seek(interval_time);
|
||||
}));
|
||||
performActionOnClick(ffButton, p -> p.seek(interval_time));
|
||||
|
||||
findViewById(R.id.next_button).setOnClickListener(view -> BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.next();
|
||||
}));
|
||||
performActionOnClick(nextButton, MprisPlugin.MprisPlayer::next);
|
||||
|
||||
findViewById(R.id.stop_button).setOnClickListener(view -> BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.stop();
|
||||
}));
|
||||
performActionOnClick(stopButton, MprisPlugin.MprisPlayer::stop);
|
||||
|
||||
((SeekBar) findViewById(R.id.volume_seek)).setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
volumeSeek.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
|
||||
}
|
||||
@@ -377,22 +410,19 @@ public class MprisActivity extends AppCompatActivity {
|
||||
|
||||
});
|
||||
|
||||
positionSeekUpdateRunnable = () -> {
|
||||
final SeekBar positionSeek = findViewById(R.id.positionSeek);
|
||||
BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer != null) {
|
||||
positionSeek.setProgress((int) (targetPlayer.getPosition()));
|
||||
}
|
||||
positionSeekUpdateHandler.removeCallbacks(positionSeekUpdateRunnable);
|
||||
positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 1000);
|
||||
});
|
||||
};
|
||||
positionSeekUpdateRunnable = () -> BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer != null) {
|
||||
positionBar.setProgress((int) (targetPlayer.getPosition()));
|
||||
}
|
||||
positionSeekUpdateHandler.removeCallbacks(positionSeekUpdateRunnable);
|
||||
positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 1000);
|
||||
});
|
||||
positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 200);
|
||||
|
||||
((SeekBar) findViewById(R.id.positionSeek)).setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
positionBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
public void onProgressChanged(SeekBar seekBar, int progress, boolean byUser) {
|
||||
((TextView) findViewById(R.id.progress_textview)).setText(milisToProgress(progress));
|
||||
progressText.setText(milisToProgress(progress));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -412,7 +442,7 @@ public class MprisActivity extends AppCompatActivity {
|
||||
|
||||
});
|
||||
|
||||
findViewById(R.id.now_playing_textview).setSelected(true);
|
||||
nowPlayingText.setSelected(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -16,7 +16,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.MprisPlugin;
|
||||
|
||||
@@ -31,17 +31,21 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||
import org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationReceiver;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.TaskStackBuilder;
|
||||
import androidx.media.app.NotificationCompat.MediaStyle;
|
||||
@@ -54,12 +58,14 @@ import androidx.media.app.NotificationCompat.MediaStyle;
|
||||
* - The media session (via MediaSessionCompat; for lock screen control on
|
||||
* older Android version. And in the future for lock screen album covers)
|
||||
*/
|
||||
public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceChangeListener, NotificationReceiver.NotificationListener {
|
||||
private final static int MPRIS_MEDIA_NOTIFICATION_ID = 0x91b70463; // echo MprisNotification | md5sum | head -c 8
|
||||
private final static String MPRIS_MEDIA_SESSION_TAG = "org.kde.kdeconnect_tp.media_session";
|
||||
|
||||
private static final MprisMediaSession instance = new MprisMediaSession();
|
||||
|
||||
private boolean spotifyRunning;
|
||||
|
||||
public static MprisMediaSession getInstance() {
|
||||
return instance;
|
||||
}
|
||||
@@ -132,6 +138,19 @@ public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceCh
|
||||
mpris.setPlayerListUpdatedHandler("media_notification", mediaNotificationHandler);
|
||||
mpris.setPlayerStatusUpdatedHandler("media_notification", mediaNotificationHandler);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
||||
NotificationReceiver.RunCommand(context, service -> {
|
||||
|
||||
service.addListener(MprisMediaSession.this);
|
||||
|
||||
boolean serviceReady = service.isConnected();
|
||||
|
||||
if (serviceReady) {
|
||||
onListenerConnected(service);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
updateMediaNotification();
|
||||
}
|
||||
|
||||
@@ -164,52 +183,62 @@ public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceCh
|
||||
* @param service The background service
|
||||
*/
|
||||
private void updateCurrentPlayer(BackgroundService service) {
|
||||
Device device = null;
|
||||
MprisPlugin.MprisPlayer playing = null;
|
||||
Pair<Device, MprisPlugin.MprisPlayer> player = findPlayer(service);
|
||||
|
||||
//Update the last-displayed device and player
|
||||
notificationDevice = player.first == null ? null : player.first.getDeviceId();
|
||||
notificationPlayer = player.second;
|
||||
}
|
||||
|
||||
private Pair<Device, MprisPlugin.MprisPlayer> findPlayer(BackgroundService service) {
|
||||
//First try the previously displayed player
|
||||
if (notificationDevice != null && mprisDevices.contains(notificationDevice) && notificationPlayer != null) {
|
||||
device = service.getDevice(notificationDevice);
|
||||
}
|
||||
MprisPlugin mpris = null;
|
||||
if (device != null) {
|
||||
mpris = device.getPlugin(MprisPlugin.class);
|
||||
}
|
||||
if (mpris != null) {
|
||||
playing = mpris.getPlayerStatus(notificationPlayer.getPlayer());
|
||||
}
|
||||
if (notificationDevice != null && mprisDevices.contains(notificationDevice)) {
|
||||
Device device = service.getDevice(notificationDevice);
|
||||
|
||||
//If nonexistant or not playing, try a different player for the same device
|
||||
if ((playing == null || !playing.isPlaying()) && mpris != null) {
|
||||
MprisPlugin.MprisPlayer playingPlayer = mpris.getPlayingPlayer();
|
||||
if (device != null && device.isPluginEnabled("MprisPlugin")) {
|
||||
if (shouldShowPlayer(notificationPlayer)){
|
||||
return new Pair<>(device, notificationPlayer);
|
||||
}
|
||||
|
||||
//Only replace the previously found player if we really found one
|
||||
if (playingPlayer != null) {
|
||||
playing = playingPlayer;
|
||||
}
|
||||
}
|
||||
|
||||
//If nonexistant or not playing, try a different player for another device
|
||||
if (playing == null || !playing.isPlaying()) {
|
||||
for (Device otherDevice : service.getDevices().values()) {
|
||||
//First, check if we actually display notification for this device
|
||||
if (!mprisDevices.contains(otherDevice.getDeviceId())) continue;
|
||||
mpris = otherDevice.getPlugin(MprisPlugin.class);
|
||||
if (mpris == null) continue;
|
||||
|
||||
MprisPlugin.MprisPlayer playingPlayer = mpris.getPlayingPlayer();
|
||||
//Only replace the previously found player if we really found one
|
||||
if (playingPlayer != null) {
|
||||
playing = playingPlayer;
|
||||
device = otherDevice;
|
||||
break;
|
||||
// Try a different player for the same device
|
||||
MprisPlugin.MprisPlayer player = getPlayerFromDevice(device);
|
||||
if (player != null) {
|
||||
return new Pair<>(device, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Update the last-displayed device and player
|
||||
notificationDevice = device == null ? null : device.getDeviceId();
|
||||
notificationPlayer = playing;
|
||||
// Try a different player from another device
|
||||
for (Device otherDevice : service.getDevices().values()) {
|
||||
MprisPlugin.MprisPlayer player = getPlayerFromDevice(otherDevice);
|
||||
if (player != null) {
|
||||
return new Pair<>(otherDevice, player);
|
||||
}
|
||||
}
|
||||
return new Pair<>(null, null);
|
||||
}
|
||||
|
||||
private MprisPlugin.MprisPlayer getPlayerFromDevice(Device device) {
|
||||
|
||||
if (!mprisDevices.contains(device.getDeviceId()))
|
||||
return null;
|
||||
|
||||
MprisPlugin plugin = device.getPlugin(MprisPlugin.class);
|
||||
|
||||
if (plugin == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MprisPlugin.MprisPlayer player = plugin.getPlayingPlayer();
|
||||
if (shouldShowPlayer(player)) {
|
||||
return player;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean shouldShowPlayer(MprisPlugin.MprisPlayer player) {
|
||||
return player != null && !(player.isSpotify() && spotifyRunning);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -316,7 +345,7 @@ public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceCh
|
||||
*/
|
||||
PendingIntent piOpenActivity = TaskStackBuilder.create(context)
|
||||
.addNextIntentWithParentStack(iOpenActivity)
|
||||
.getPendingIntent(Build.VERSION.SDK_INT > 15 ? 0 : (int)System.currentTimeMillis(), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
.getPendingIntent(Build.VERSION.SDK_INT > 15 ? 0 : (int) System.currentTimeMillis(), PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NotificationHelper.Channels.MEDIA_CONTROL);
|
||||
|
||||
@@ -433,4 +462,33 @@ public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceCh
|
||||
notificationPlayer = player;
|
||||
updateMediaNotification();
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
@Override
|
||||
public void onNotificationPosted(StatusBarNotification n) {
|
||||
if (n.getPackageName().equals("com.spotify.music")) {
|
||||
spotifyRunning = true;
|
||||
updateMediaNotification();
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
@Override
|
||||
public void onNotificationRemoved(StatusBarNotification n) {
|
||||
if (n.getPackageName().equals("com.spotify.music")) {
|
||||
spotifyRunning = false;
|
||||
updateMediaNotification();
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
||||
@Override
|
||||
public void onListenerConnected(NotificationReceiver service) {
|
||||
for (StatusBarNotification n : service.getActiveNotifications()) {
|
||||
if (n.getPackageName().equals("com.spotify.music")) {
|
||||
spotifyRunning = true;
|
||||
updateMediaNotification();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -87,7 +87,7 @@ public class MprisPlugin extends Plugin {
|
||||
return player;
|
||||
}
|
||||
|
||||
private boolean isSpotify() {
|
||||
boolean isSpotify() {
|
||||
return getPlayer().toLowerCase().equals("spotify");
|
||||
}
|
||||
|
||||
@@ -307,8 +307,7 @@ public class MprisPlugin extends Plugin {
|
||||
try {
|
||||
playerStatusUpdated.get(key).dispatchMessage(new Message());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("MprisControl", "Exception");
|
||||
Log.e("MprisControl", "Exception", e);
|
||||
playerStatusUpdated.remove(key);
|
||||
}
|
||||
}
|
||||
@@ -355,8 +354,7 @@ public class MprisPlugin extends Plugin {
|
||||
try {
|
||||
playerListUpdated.get(key).dispatchMessage(new Message());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("MprisControl", "Exception");
|
||||
Log.e("MprisControl", "Exception", e);
|
||||
playerListUpdated.remove(key);
|
||||
}
|
||||
}
|
||||
@@ -468,8 +466,7 @@ public class MprisPlugin extends Plugin {
|
||||
try {
|
||||
playerStatusUpdated.get(key).dispatchMessage(new Message());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("MprisControl", "Exception");
|
||||
Log.e("MprisControl", "Exception", e);
|
||||
playerStatusUpdated.remove(key);
|
||||
}
|
||||
}
|
||||
|
@@ -45,7 +45,7 @@ class MprisReceiverCallback extends MediaController.Callback {
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void onPlaybackStateChanged(@NonNull PlaybackState state) {
|
||||
public void onPlaybackStateChanged(PlaybackState state) {
|
||||
plugin.sendMetadata(player);
|
||||
}
|
||||
|
||||
|
@@ -16,7 +16,7 @@
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.NotificationsPlugin;
|
||||
|
||||
@@ -40,6 +40,7 @@ import android.service.notification.StatusBarNotification;
|
||||
import android.text.SpannableString;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.kde.kdeconnect.Helpers.AppsHelper;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
@@ -55,6 +56,8 @@ import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -68,11 +71,15 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
private final static String PACKET_TYPE_NOTIFICATION = "kdeconnect.notification";
|
||||
private final static String PACKET_TYPE_NOTIFICATION_REQUEST = "kdeconnect.notification.request";
|
||||
private final static String PACKET_TYPE_NOTIFICATION_REPLY = "kdeconnect.notification.reply";
|
||||
private final static String PACKET_TYPE_NOTIFICATION_ACTION = "kdeconnect.notification.action";
|
||||
|
||||
private final static String TAG = "NotificationsPlugin";
|
||||
|
||||
private AppDatabase appDatabase;
|
||||
|
||||
private Set<String> currentNotifications;
|
||||
private Map<String, RepliableNotification> pendingIntents;
|
||||
private Map<String, List<Notification.Action>> actions;
|
||||
private boolean serviceReady;
|
||||
|
||||
@Override
|
||||
@@ -117,6 +124,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
|
||||
pendingIntents = new HashMap<>();
|
||||
currentNotifications = new HashSet<>();
|
||||
actions = new HashMap<>();
|
||||
|
||||
appDatabase = new AppDatabase(context, true);
|
||||
|
||||
@@ -153,6 +161,9 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
return;
|
||||
}
|
||||
String id = getNotificationKeyCompat(statusBarNotification);
|
||||
|
||||
actions.remove(id);
|
||||
|
||||
NetworkPacket np = new NetworkPacket(PACKET_TYPE_NOTIFICATION);
|
||||
np.set("id", id);
|
||||
np.set("isCancel", true);
|
||||
@@ -245,13 +256,32 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
np.set("payloadHash", getChecksum(bitmapData));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("NotificationsPlugin", "Error retrieving icon");
|
||||
Log.e("NotificationsPlugin", "Error retrieving icon", e);
|
||||
}
|
||||
} else {
|
||||
currentNotifications.add(key);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
if (notification.actions != null && notification.actions.length > 0) {
|
||||
actions.put(key, new LinkedList<>());
|
||||
JSONArray jsonArray = new JSONArray();
|
||||
for (Notification.Action action : notification.actions) {
|
||||
|
||||
if (null == action.title)
|
||||
continue;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH)
|
||||
if (action.getRemoteInputs() != null && action.getRemoteInputs().length > 0)
|
||||
continue;
|
||||
|
||||
jsonArray.put(action.title.toString());
|
||||
actions.get(key).add(action);
|
||||
}
|
||||
np.set("actions", jsonArray);
|
||||
}
|
||||
}
|
||||
|
||||
np.set("id", key);
|
||||
np.set("isClearable", statusBarNotification.isClearable());
|
||||
np.set("appName", appName == null ? packageName : appName);
|
||||
@@ -354,8 +384,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
Bundle extras = notification.extras;
|
||||
title = extractStringFromExtra(extras, TITLE_KEY);
|
||||
} catch (Exception e) {
|
||||
Log.w("NotificationPlugin", "problem parsing notification extras for " + notification.tickerText);
|
||||
e.printStackTrace();
|
||||
Log.e("NotificationPlugin", "problem parsing notification extras for " + notification.tickerText, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,8 +411,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
repliableNotification.tag = statusBarNotification.getTag();//TODO find how to pass Tag with sending PendingIntent, might fix Hangout problem
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w("NotificationPlugin", "problem extracting notification wear for " + statusBarNotification.getNotification().tickerText);
|
||||
e.printStackTrace();
|
||||
Log.e("NotificationPlugin", "problem extracting notification wear for " + statusBarNotification.getNotification().tickerText, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,8 +430,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
Object extraTextExtra = extras.get(TEXT_KEY);
|
||||
if (extraTextExtra != null) text = extraTextExtra.toString();
|
||||
} catch (Exception e) {
|
||||
Log.w("NotificationPlugin", "problem parsing notification extras for " + notification.tickerText);
|
||||
e.printStackTrace();
|
||||
Log.e("NotificationPlugin", "problem parsing notification extras for " + notification.tickerText, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -451,8 +478,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
ticker = extraText;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w("NotificationPlugin", "problem parsing notification extras for " + notification.tickerText);
|
||||
e.printStackTrace();
|
||||
Log.e("NotificationPlugin", "problem parsing notification extras for " + notification.tickerText, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -466,15 +492,37 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
|
||||
private void sendCurrentNotifications(NotificationReceiver service) {
|
||||
StatusBarNotification[] notifications = service.getActiveNotifications();
|
||||
for (StatusBarNotification notification : notifications) {
|
||||
sendNotification(notification);
|
||||
if (notifications != null) { //Can happen only on API 23 and lower
|
||||
for (StatusBarNotification notification : notifications) {
|
||||
sendNotification(notification);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPacketReceived(final NetworkPacket np) {
|
||||
|
||||
if (np.getBoolean("request")) {
|
||||
if (np.getType().equals(PACKET_TYPE_NOTIFICATION_ACTION) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
|
||||
String key = np.getString("key");
|
||||
String title = np.getString("action");
|
||||
PendingIntent intent = null;
|
||||
|
||||
for (Notification.Action a : actions.get(key)) {
|
||||
if (a.title.equals(title)) {
|
||||
intent = a.actionIntent;
|
||||
}
|
||||
}
|
||||
|
||||
if (intent != null) {
|
||||
try {
|
||||
intent.send();
|
||||
} catch (PendingIntent.CanceledException e) {
|
||||
Log.e(TAG, "Triggering action failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
} else if (np.getBoolean("request")) {
|
||||
|
||||
if (serviceReady) {
|
||||
NotificationReceiver.RunCommand(context, this::sendCurrentNotifications);
|
||||
@@ -510,7 +558,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPacketTypes() {
|
||||
return new String[]{PACKET_TYPE_NOTIFICATION_REQUEST, PACKET_TYPE_NOTIFICATION_REPLY};
|
||||
return new String[]{PACKET_TYPE_NOTIFICATION_REQUEST, PACKET_TYPE_NOTIFICATION_REPLY, PACKET_TYPE_NOTIFICATION_ACTION};
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -25,9 +25,7 @@ public class PhotoActivity extends AppCompatActivity {
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
|
||||
BackgroundService.RunWithPlugin(this, getIntent().getStringExtra("deviceId"), PhotoPlugin.class, plugin -> {
|
||||
this.plugin = plugin;
|
||||
});
|
||||
BackgroundService.RunWithPlugin(this, getIntent().getStringExtra("deviceId"), PhotoPlugin.class, plugin -> this.plugin = plugin);
|
||||
|
||||
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
|
||||
|
@@ -25,7 +25,6 @@ import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
@@ -185,21 +184,6 @@ public abstract class Plugin {
|
||||
*/
|
||||
public abstract String[] getOutgoingPacketTypes();
|
||||
|
||||
/**
|
||||
* Creates a button that will be displayed in the user interface
|
||||
* It can open an activity or perform any other action that the
|
||||
* plugin would wants to expose to the user. Return null if no
|
||||
* button should be displayed.
|
||||
*/
|
||||
@Deprecated
|
||||
public Button getInterfaceButton(final Activity activity) {
|
||||
if (!hasMainActivity()) return null;
|
||||
Button b = new Button(activity);
|
||||
b.setText(getActionName());
|
||||
b.setOnClickListener(view -> startMainActivity(activity));
|
||||
return b;
|
||||
}
|
||||
|
||||
protected String[] getRequiredPermissions() {
|
||||
return new String[0];
|
||||
}
|
||||
|
@@ -141,8 +141,7 @@ public class PluginFactory {
|
||||
plugin.setContext(context, device);
|
||||
return plugin;
|
||||
} catch (Exception e) {
|
||||
Log.e("PluginFactory", "Could not instantiate plugin: " + pluginKey);
|
||||
e.printStackTrace();
|
||||
Log.e("PluginFactory", "Could not instantiate plugin: " + pluginKey, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -29,6 +29,7 @@ import android.inputmethodservice.KeyboardView.OnKeyboardActionListener;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Toast;
|
||||
@@ -119,6 +120,8 @@ public class RemoteKeyboardService
|
||||
} finally {
|
||||
RemoteKeyboardPlugin.releaseInstances();
|
||||
}
|
||||
|
||||
getWindow().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -133,6 +136,8 @@ public class RemoteKeyboardService
|
||||
} finally {
|
||||
RemoteKeyboardPlugin.releaseInstances();
|
||||
}
|
||||
|
||||
getWindow().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -25,6 +25,7 @@ import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -58,46 +59,44 @@ public class RunCommandActivity extends AppCompatActivity {
|
||||
|
||||
private void updateView() {
|
||||
|
||||
BackgroundService.RunWithPlugin(this, deviceId, RunCommandPlugin.class, plugin -> {
|
||||
runOnUiThread(() -> {
|
||||
ListView view = findViewById(R.id.runcommandslist);
|
||||
BackgroundService.RunWithPlugin(this, deviceId, RunCommandPlugin.class, plugin -> runOnUiThread(() -> {
|
||||
ListView view = findViewById(R.id.runcommandslist);
|
||||
|
||||
registerForContextMenu(view);
|
||||
registerForContextMenu(view);
|
||||
|
||||
commandItems = new ArrayList<>();
|
||||
for (JSONObject obj : plugin.getCommandList()) {
|
||||
try {
|
||||
commandItems.add(new CommandEntry(obj.getString("name"),
|
||||
obj.getString("command"), obj.getString("key")));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
commandItems = new ArrayList<>();
|
||||
for (JSONObject obj : plugin.getCommandList()) {
|
||||
try {
|
||||
commandItems.add(new CommandEntry(obj.getString("name"),
|
||||
obj.getString("command"), obj.getString("key")));
|
||||
} catch (JSONException e) {
|
||||
Log.e("RunCommand", "Error parsing JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(commandItems, (lhs, rhs) -> {
|
||||
String lName = ((CommandEntry) lhs).getName();
|
||||
String rName = ((CommandEntry) rhs).getName();
|
||||
return lName.compareTo(rName);
|
||||
});
|
||||
|
||||
ListAdapter adapter = new ListAdapter(RunCommandActivity.this, commandItems);
|
||||
|
||||
view.setAdapter(adapter);
|
||||
view.setOnItemClickListener((adapterView, view1, i, l) -> {
|
||||
CommandEntry entry = (CommandEntry) commandItems.get(i);
|
||||
plugin.runCommand(entry.getKey());
|
||||
});
|
||||
|
||||
|
||||
TextView explanation = findViewById(R.id.addcomand_explanation);
|
||||
String text = getString(R.string.addcommand_explanation);
|
||||
if (!plugin.canAddCommand()) {
|
||||
text += "\n" + getString(R.string.addcommand_explanation2);
|
||||
}
|
||||
explanation.setText(text);
|
||||
explanation.setVisibility(commandItems.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
Collections.sort(commandItems, (lhs, rhs) -> {
|
||||
String lName = ((CommandEntry) lhs).getName();
|
||||
String rName = ((CommandEntry) rhs).getName();
|
||||
return lName.compareTo(rName);
|
||||
});
|
||||
});
|
||||
|
||||
ListAdapter adapter = new ListAdapter(RunCommandActivity.this, commandItems);
|
||||
|
||||
view.setAdapter(adapter);
|
||||
view.setOnItemClickListener((adapterView, view1, i, l) -> {
|
||||
CommandEntry entry = (CommandEntry) commandItems.get(i);
|
||||
plugin.runCommand(entry.getKey());
|
||||
});
|
||||
|
||||
|
||||
TextView explanation = findViewById(R.id.addcomand_explanation);
|
||||
String text = getString(R.string.addcommand_explanation);
|
||||
if (!plugin.canAddCommand()) {
|
||||
text += "\n" + getString(R.string.addcommand_explanation2);
|
||||
}
|
||||
explanation.setText(text);
|
||||
explanation.setVisibility(commandItems.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,19 +116,15 @@ public class RunCommandActivity extends AppCompatActivity {
|
||||
addCommandButton.hide();
|
||||
}
|
||||
|
||||
addCommandButton.setOnClickListener(v -> {
|
||||
|
||||
BackgroundService.RunWithPlugin(RunCommandActivity.this, deviceId, RunCommandPlugin.class, plugin -> {
|
||||
plugin.sendSetupPacket();
|
||||
AlertDialog dialog = new AlertDialog.Builder(RunCommandActivity.this)
|
||||
.setTitle(R.string.add_command)
|
||||
.setMessage(R.string.add_command_description)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.create();
|
||||
dialog.show();
|
||||
});
|
||||
|
||||
});
|
||||
addCommandButton.setOnClickListener(v -> BackgroundService.RunWithPlugin(RunCommandActivity.this, deviceId, RunCommandPlugin.class, plugin -> {
|
||||
plugin.sendSetupPacket();
|
||||
AlertDialog dialog = new AlertDialog.Builder(RunCommandActivity.this)
|
||||
.setTitle(R.string.add_command)
|
||||
.setMessage(R.string.add_command_description)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.create();
|
||||
dialog.show();
|
||||
}));
|
||||
|
||||
updateView();
|
||||
}
|
||||
@@ -160,17 +155,13 @@ public class RunCommandActivity extends AppCompatActivity {
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
BackgroundService.RunWithPlugin(this, deviceId, RunCommandPlugin.class, plugin -> {
|
||||
plugin.addCommandsUpdatedCallback(commandsChangedCallback);
|
||||
});
|
||||
BackgroundService.RunWithPlugin(this, deviceId, RunCommandPlugin.class, plugin -> plugin.addCommandsUpdatedCallback(commandsChangedCallback));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
BackgroundService.RunWithPlugin(this, deviceId, RunCommandPlugin.class, plugin -> {
|
||||
plugin.removeCommandsUpdatedCallback(commandsChangedCallback);
|
||||
});
|
||||
BackgroundService.RunWithPlugin(this, deviceId, RunCommandPlugin.class, plugin -> plugin.removeCommandsUpdatedCallback(commandsChangedCallback));
|
||||
}
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@ package org.kde.kdeconnect.Plugins.RunCommandPlugin;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@@ -115,7 +116,7 @@ public class RunCommandPlugin extends Plugin {
|
||||
)
|
||||
);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
Log.e("RunCommand", "Error parsing JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +126,7 @@ public class RunCommandPlugin extends Plugin {
|
||||
context.sendBroadcast(updateWidget);
|
||||
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
Log.e("RunCommand", "Error parsing JSON", e);
|
||||
}
|
||||
|
||||
for (CommandsChangedCallback aCallback : callbacks) {
|
||||
|
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||
* Copyright 2019 Simon Redman <simon@ergotech.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
@@ -28,10 +29,12 @@ import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.database.ContentObserver;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Telephony;
|
||||
import android.telephony.PhoneNumberUtils;
|
||||
import android.telephony.SmsManager;
|
||||
import android.telephony.SmsMessage;
|
||||
@@ -56,6 +59,7 @@ import java.util.Map;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import static org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin.PACKET_TYPE_TELEPHONY;
|
||||
@@ -127,7 +131,7 @@ public class SMSPlugin extends Plugin {
|
||||
|
||||
//Log.e("TelephonyPlugin","Telephony event: " + action);
|
||||
|
||||
if ("android.provider.Telephony.SMS_RECEIVED".equals(action)) {
|
||||
if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(action)) {
|
||||
|
||||
final Bundle bundle = intent.getExtras();
|
||||
if (bundle == null) return;
|
||||
@@ -177,6 +181,7 @@ public class SMSPlugin extends Plugin {
|
||||
* In this case, this onChange expects to be called whenever *anything* in the Messages
|
||||
* database changes and simply reports those updated messages to anyone who might be listening
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
if (mPlugin.mostRecentTimestamp == 0) {
|
||||
@@ -202,8 +207,8 @@ public class SMSPlugin extends Plugin {
|
||||
// Update the most recent counter
|
||||
mostRecentTimestampLock.lock();
|
||||
for (SMSHelper.Message message : messages) {
|
||||
if (message.m_date > mostRecentTimestamp) {
|
||||
mPlugin.mostRecentTimestamp = message.m_date;
|
||||
if (message.date > mostRecentTimestamp) {
|
||||
mPlugin.mostRecentTimestamp = message.date;
|
||||
}
|
||||
}
|
||||
mostRecentTimestampLock.unlock();
|
||||
@@ -270,11 +275,12 @@ public class SMSPlugin extends Plugin {
|
||||
device.sendPacket(np);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
permissionExplanation = R.string.telepathy_permission_explanation;
|
||||
|
||||
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
|
||||
IntentFilter filter = new IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION);
|
||||
filter.setPriority(500);
|
||||
context.registerReceiver(receiver, filter);
|
||||
|
||||
@@ -295,6 +301,7 @@ public class SMSPlugin extends Plugin {
|
||||
return context.getResources().getString(R.string.pref_plugin_telepathy_desc);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
@Override
|
||||
public boolean onPacketReceived(NetworkPacket np) {
|
||||
|
||||
@@ -322,8 +329,7 @@ public class SMSPlugin extends Plugin {
|
||||
//TODO: Notify other end
|
||||
} catch (Exception e) {
|
||||
//TODO: Notify other end
|
||||
Log.e("SMSPlugin", e.getMessage());
|
||||
e.printStackTrace();
|
||||
Log.e("SMSPlugin", "Exception", e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -338,6 +344,7 @@ public class SMSPlugin extends Plugin {
|
||||
* @param messages Messages to include in the packet
|
||||
* @return NetworkPacket of type PACKET_TYPE_SMS_MESSAGE
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
private static NetworkPacket constructBulkMessagePacket(Collection<SMSHelper.Message> messages) {
|
||||
NetworkPacket reply = new NetworkPacket(PACKET_TYPE_SMS_MESSAGE);
|
||||
|
||||
@@ -366,6 +373,7 @@ public class SMSPlugin extends Plugin {
|
||||
* <p>
|
||||
* Send one packet of type PACKET_TYPE_SMS_MESSAGE with the first message in all conversations
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
private boolean handleRequestConversations(NetworkPacket packet) {
|
||||
Map<SMSHelper.ThreadID, SMSHelper.Message> conversations = SMSHelper.getConversations(this.context);
|
||||
|
||||
@@ -373,8 +381,8 @@ public class SMSPlugin extends Plugin {
|
||||
// recent in every conversation
|
||||
mostRecentTimestampLock.lock();
|
||||
for (SMSHelper.Message message : conversations.values()) {
|
||||
if (message.m_date > mostRecentTimestamp) {
|
||||
mostRecentTimestamp = message.m_date;
|
||||
if (message.date > mostRecentTimestamp) {
|
||||
mostRecentTimestamp = message.date;
|
||||
}
|
||||
}
|
||||
mostRecentTimestampLock.unlock();
|
||||
@@ -386,6 +394,7 @@ public class SMSPlugin extends Plugin {
|
||||
return true;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
|
||||
private boolean handleRequestConversation(NetworkPacket packet) {
|
||||
SMSHelper.ThreadID threadID = new SMSHelper.ThreadID(packet.getLong("threadID"));
|
||||
|
||||
@@ -428,6 +437,14 @@ public class SMSPlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public String[] getRequiredPermissions() {
|
||||
return new String[]{Manifest.permission.SEND_SMS};
|
||||
return new String[]{
|
||||
Manifest.permission.SEND_SMS,
|
||||
Manifest.permission.READ_SMS,
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinSdk() {
|
||||
return Build.VERSION_CODES.KITKAT;
|
||||
}
|
||||
}
|
||||
|
@@ -99,14 +99,26 @@ public class AndroidSafFileSystemView implements FileSystemView {
|
||||
|
||||
return createAndroidSafSshFile(documentUri, documentUri, filename);
|
||||
} else {
|
||||
//ChildDocument, strip the leading / from nameWithoutRoot and append that to the treeDocumentId
|
||||
/*
|
||||
When sharing a root document tree like "Internal Storage" documentUri looks like:
|
||||
content://com.android.externalstorage.documents/tree/primary:/document/primary:
|
||||
For a file or folder beneath that the uri looks like:
|
||||
content://com.android.externalstorage.documents/tree/primary:/document/primary:Folder/file.txt
|
||||
|
||||
Sharing a non root document tree the documentUri looks like:
|
||||
content://com.android.externalstorage.documents/tree/primary:Download/document/primary:Download
|
||||
For a file or folder beneath that the uri looks like:
|
||||
content://com.android.externalstorage.documents/tree/primary:Download/document/primary:Download/Folder/file.txt
|
||||
*/
|
||||
String treeDocumentId = DocumentsContract.getTreeDocumentId(treeUri);
|
||||
File nameWithoutRootFile = new File(nameWithoutRoot);
|
||||
String parentSuffix = nameWithoutRootFile.getParent();
|
||||
String parentDocumentId = treeDocumentId + (parentSuffix.equals("/") ? "" : parentSuffix.substring(1));
|
||||
String parentDocumentId = treeDocumentId + (parentSuffix.equals("/") ? "" : parentSuffix);
|
||||
|
||||
Uri parentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, parentDocumentId);
|
||||
Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, treeDocumentId + nameWithoutRoot.substring(1));
|
||||
|
||||
String documentId = treeDocumentId + (treeDocumentId.endsWith(":") ? nameWithoutRoot.substring(1) : nameWithoutRoot);
|
||||
Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId);
|
||||
|
||||
return createAndroidSafSshFile(parentUri, documentUri, filename);
|
||||
}
|
||||
|
@@ -32,6 +32,8 @@ import android.provider.DocumentsContract;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.apache.sshd.common.file.SshFile;
|
||||
import org.kde.kdeconnect.Helpers.FilesHelper;
|
||||
|
||||
@@ -49,8 +51,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
@TargetApi(21)
|
||||
public class AndroidSafSshFile implements SshFile {
|
||||
private static final String TAG = AndroidSafSshFile.class.getSimpleName();
|
||||
@@ -189,6 +189,10 @@ public class AndroidSafSshFile implements SshFile {
|
||||
|
||||
if (uri != null) {
|
||||
documentInfo = new DocumentInfo(fileSystemView.context, uri);
|
||||
if (!name.equals(documentInfo.displayName)) {
|
||||
delete();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (FileNotFoundException ignored) {}
|
||||
|
||||
@@ -196,7 +200,7 @@ public class AndroidSafSshFile implements SshFile {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void truncate() throws IOException {
|
||||
public void truncate() {
|
||||
if (documentInfo.length > 0) {
|
||||
delete();
|
||||
create();
|
||||
@@ -231,8 +235,8 @@ public class AndroidSafSshFile implements SshFile {
|
||||
parentUri = destParentUri;
|
||||
documentInfo.uri = newUri;
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
Log.e(TAG,"DocumentsContract.moveDocument() threw an exception: " + ignored.getMessage());
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG,"DocumentsContract.moveDocument() threw an exception", e);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
@@ -310,7 +314,7 @@ public class AndroidSafSshFile implements SshFile {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Attribute, Object> getAttributes(boolean followLinks) throws IOException {
|
||||
public Map<Attribute, Object> getAttributes(boolean followLinks) {
|
||||
Map<SshFile.Attribute, Object> attributes = new HashMap<>();
|
||||
for (SshFile.Attribute attr : SshFile.Attribute.values()) {
|
||||
switch (attr) {
|
||||
@@ -326,7 +330,7 @@ public class AndroidSafSshFile implements SshFile {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(Attribute attribute, boolean followLinks) throws IOException {
|
||||
public Object getAttribute(Attribute attribute, boolean followLinks) {
|
||||
Object ret;
|
||||
|
||||
switch (attribute) {
|
||||
@@ -398,13 +402,10 @@ public class AndroidSafSshFile implements SshFile {
|
||||
@Override
|
||||
public void setAttributes(Map<Attribute, Object> attributes) {
|
||||
//TODO: Using Java 7 NIO it should be possible to implement setting a number of attributes but does SaF allow that?
|
||||
Log.d(TAG, "setAttributes()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(Attribute attribute, Object value) throws IOException {
|
||||
Log.d(TAG, "setAttribute()");
|
||||
}
|
||||
public void setAttribute(Attribute attribute, Object value) {}
|
||||
|
||||
@Override
|
||||
public String readSymbolicLink() throws IOException {
|
||||
|
@@ -53,7 +53,7 @@ class RootFile implements SshFile {
|
||||
return "/";
|
||||
}
|
||||
|
||||
public Map<Attribute, Object> getAttributes(boolean followLinks) throws IOException {
|
||||
public Map<Attribute, Object> getAttributes(boolean followLinks) {
|
||||
Map<Attribute, Object> attrs = new HashMap<>();
|
||||
|
||||
attrs.put(Attribute.Size, 0);
|
||||
@@ -88,7 +88,7 @@ class RootFile implements SshFile {
|
||||
}
|
||||
|
||||
public String readSymbolicLink() {
|
||||
return null;
|
||||
return "";
|
||||
}
|
||||
|
||||
public void createSymbolicLink(SshFile destination) {
|
||||
|
@@ -25,6 +25,7 @@ import android.content.ContentResolver;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
@@ -80,7 +81,7 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("SFTP", "Exception in server.init()", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.util.Log;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -276,7 +277,7 @@ public class SftpSettingsFragment
|
||||
storageInfoList.add(SftpPlugin.StorageInfo.fromJSON(jsonArray.getJSONObject(i)));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
Log.e("SFTPSettings", "Couldn't load storage info", e);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT <= 19) {
|
||||
@@ -473,7 +474,13 @@ public class SftpSettingsFragment
|
||||
SftpPlugin.StorageInfo info = storageInfoList.remove(i);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
requireContext().getContentResolver().releasePersistableUriPermission(info.uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
try {
|
||||
// This throws when trying to release a URI we don't have access to
|
||||
requireContext().getContentResolver().releasePersistableUriPermission(info.uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
} catch (SecurityException e) {
|
||||
// Usually safe to ignore, but who knows?
|
||||
Log.e("SFTP Settings", "Exception", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -95,11 +95,8 @@ class SimpleSftpServer {
|
||||
sshd.setCommandFactory(new ScpCommandFactory());
|
||||
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystem.Factory()));
|
||||
|
||||
if (device.publicKey != null) {
|
||||
keyAuth.deviceKey = device.publicKey;
|
||||
} else {
|
||||
keyAuth.deviceKey = device.certificate.getPublicKey();
|
||||
}
|
||||
keyAuth.deviceKey = device.certificate.getPublicKey();
|
||||
|
||||
sshd.setPublickeyAuthenticator(keyAuth);
|
||||
sshd.setPasswordAuthenticator(passwordAuth);
|
||||
}
|
||||
@@ -116,7 +113,6 @@ class SimpleSftpServer {
|
||||
sshd.start();
|
||||
started = true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
port++;
|
||||
if (port >= ENDPORT) {
|
||||
port = -1;
|
||||
@@ -135,7 +131,7 @@ class SimpleSftpServer {
|
||||
started = false;
|
||||
sshd.stop(true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("SFTP", "Exception while stopping the server", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -21,6 +21,7 @@
|
||||
package org.kde.kdeconnect.Plugins.SharePlugin;
|
||||
|
||||
import android.app.DownloadManager;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
@@ -46,7 +47,7 @@ import androidx.core.content.FileProvider;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
||||
private final ShareNotification shareNotification;
|
||||
private final ReceiveNotification receiveNotification;
|
||||
private NetworkPacket currentNetworkPacket;
|
||||
private String currentFileName;
|
||||
private int currentFileNum;
|
||||
@@ -65,8 +66,8 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
||||
|
||||
lock = new Object();
|
||||
networkPacketList = new ArrayList<>();
|
||||
shareNotification = new ShareNotification(device);
|
||||
shareNotification.addCancelAction(getId());
|
||||
receiveNotification = new ReceiveNotification(device);
|
||||
receiveNotification.addCancelAction(getId());
|
||||
currentFileNum = 0;
|
||||
totalNumFiles = 0;
|
||||
totalPayloadSize = 0;
|
||||
@@ -86,7 +87,7 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
||||
this.totalNumFiles = numberOfFiles;
|
||||
this.totalPayloadSize = totalPayloadSize;
|
||||
|
||||
shareNotification.setTitle(getDevice().getContext().getResources()
|
||||
receiveNotification.setTitle(getDevice().getContext().getResources()
|
||||
.getQuantityString(R.plurals.incoming_file_title, totalNumFiles, totalNumFiles, getDevice().getName()));
|
||||
}
|
||||
}
|
||||
@@ -99,7 +100,7 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
||||
totalNumFiles = networkPacket.getInt(SharePlugin.KEY_NUMBER_OF_FILES, 1);
|
||||
totalPayloadSize = networkPacket.getLong(SharePlugin.KEY_TOTAL_PAYLOAD_SIZE);
|
||||
|
||||
shareNotification.setTitle(getDevice().getContext().getResources()
|
||||
receiveNotification.setTitle(getDevice().getContext().getResources()
|
||||
.getQuantityString(R.plurals.incoming_file_title, totalNumFiles, totalNumFiles, getDevice().getName()));
|
||||
}
|
||||
}
|
||||
@@ -148,6 +149,7 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
||||
publishFile(fileDocument, received);
|
||||
}
|
||||
} else {
|
||||
//TODO: Only set progress to 100 if this is the only file/packet to send
|
||||
setProgress(100);
|
||||
publishFile(fileDocument, 0);
|
||||
}
|
||||
@@ -179,7 +181,7 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
||||
isRunning = false;
|
||||
|
||||
if (canceled) {
|
||||
shareNotification.cancel();
|
||||
receiveNotification.cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -189,29 +191,35 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
||||
}
|
||||
|
||||
if (numFiles == 1 && currentNetworkPacket.has("open")) {
|
||||
shareNotification.cancel();
|
||||
receiveNotification.cancel();
|
||||
openFile(fileDocument);
|
||||
} else {
|
||||
//Update the notification and allow to open the file from it
|
||||
shareNotification.setFinished(getDevice().getContext().getResources().getQuantityString(R.plurals.received_files_title, numFiles, getDevice().getName(), numFiles));
|
||||
receiveNotification.setFinished(getDevice().getContext().getResources().getQuantityString(R.plurals.received_files_title, numFiles, getDevice().getName(), numFiles));
|
||||
|
||||
if (totalNumFiles == 1 && fileDocument != null) {
|
||||
shareNotification.setURI(fileDocument.getUri(), fileDocument.getType(), fileDocument.getName());
|
||||
receiveNotification.setURI(fileDocument.getUri(), fileDocument.getType(), fileDocument.getName());
|
||||
}
|
||||
|
||||
shareNotification.show();
|
||||
receiveNotification.show();
|
||||
}
|
||||
reportResult(null);
|
||||
|
||||
} catch (ActivityNotFoundException e) {
|
||||
receiveNotification.setFinished(getDevice().getContext().getString(R.string.no_app_for_opening));
|
||||
receiveNotification.show();
|
||||
} catch (Exception e) {
|
||||
isRunning = false;
|
||||
|
||||
Log.e("Shareplugin", "Error receiving file", e);
|
||||
|
||||
int failedFiles;
|
||||
synchronized (lock) {
|
||||
failedFiles = (totalNumFiles - currentFileNum + 1);
|
||||
}
|
||||
|
||||
shareNotification.setFinished(getDevice().getContext().getResources().getQuantityString(R.plurals.received_files_fail_title, failedFiles, getDevice().getName(), failedFiles, totalNumFiles));
|
||||
shareNotification.show();
|
||||
receiveNotification.setFinished(getDevice().getContext().getResources().getQuantityString(R.plurals.received_files_fail_title, failedFiles, getDevice().getName(), failedFiles, totalNumFiles));
|
||||
receiveNotification.show();
|
||||
reportError(e);
|
||||
} finally {
|
||||
closeAllInputStreams();
|
||||
@@ -231,7 +239,7 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
||||
|
||||
//We need to check for already existing files only when storing in the default path.
|
||||
//User-defined paths use the new Storage Access Framework that already handles this.
|
||||
//If the file should be opened immediately store it in the standard location to avoid the FileProvider trouble (See ShareNotification::setURI)
|
||||
//If the file should be opened immediately store it in the standard location to avoid the FileProvider trouble (See ReceiveNotification::setURI)
|
||||
if (open || !ShareSettingsFragment.isCustomDestinationEnabled(getDevice().getContext())) {
|
||||
final String defaultPath = ShareSettingsFragment.getDefaultDestinationDirectory().getAbsolutePath();
|
||||
filenameToUse = FilesHelper.findNonExistingNameForNewFile(defaultPath, filenameToUse);
|
||||
@@ -256,7 +264,7 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
||||
}
|
||||
|
||||
private long receiveFile(InputStream input, OutputStream output) throws IOException {
|
||||
byte data[] = new byte[4096];
|
||||
byte[] data = new byte[4096];
|
||||
int count;
|
||||
long received = 0;
|
||||
|
||||
@@ -293,10 +301,10 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
||||
|
||||
private void setProgress(int progress) {
|
||||
synchronized (lock) {
|
||||
shareNotification.setProgress(progress, getDevice().getContext().getResources()
|
||||
receiveNotification.setProgress(progress, getDevice().getContext().getResources()
|
||||
.getQuantityString(R.plurals.incoming_files_text, totalNumFiles, currentFileName, currentFileNum, totalNumFiles));
|
||||
}
|
||||
shareNotification.show();
|
||||
receiveNotification.show();
|
||||
}
|
||||
|
||||
private void publishFile(DocumentFile fileDocument, long size) {
|
||||
@@ -319,7 +327,7 @@ public class CompositeReceiveFileJob extends BackgroundJob<Device, Void> {
|
||||
File file = new File(fileDocument.getUri().getPath());
|
||||
Uri contentUri = FileProvider.getUriForFile(getDevice().getContext(), "org.kde.kdeconnect_tp.fileprovider", file);
|
||||
intent.setDataAndType(contentUri, mimeType);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
} else {
|
||||
intent.setDataAndType(fileDocument.getUri(), mimeType);
|
||||
}
|
||||
|
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* Copyright 2019 Erik Duisters <e.duisters1@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.SharePlugin;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.async.BackgroundJob;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class CompositeUploadFileJob extends BackgroundJob<Device, Void> {
|
||||
private boolean isRunning;
|
||||
private Handler handler;
|
||||
private String currentFileName;
|
||||
private int currentFileNum;
|
||||
private boolean updatePacketPending;
|
||||
private long totalSend;
|
||||
private int prevProgressPercentage;
|
||||
private UploadNotification uploadNotification;
|
||||
|
||||
private final Object lock; //Use to protect concurrent access to the variables below
|
||||
private final List<NetworkPacket> networkPacketList;
|
||||
private NetworkPacket currentNetworkPacket;
|
||||
private final Device.SendPacketStatusCallback sendPacketStatusCallback;
|
||||
private int totalNumFiles;
|
||||
private long totalPayloadSize;
|
||||
|
||||
CompositeUploadFileJob(@NonNull Device device, @NonNull Callback<Void> callback) {
|
||||
super(device, callback);
|
||||
|
||||
isRunning = false;
|
||||
handler = new Handler(Looper.getMainLooper());
|
||||
currentFileNum = 0;
|
||||
currentFileName = "";
|
||||
updatePacketPending = false;
|
||||
|
||||
lock = new Object();
|
||||
networkPacketList = new ArrayList<>();
|
||||
totalNumFiles = 0;
|
||||
totalPayloadSize = 0;
|
||||
totalSend = 0;
|
||||
prevProgressPercentage = 0;
|
||||
uploadNotification = new UploadNotification(getDevice());
|
||||
uploadNotification.addCancelAction(getId());
|
||||
|
||||
sendPacketStatusCallback = new SendPacketStatusCallback();
|
||||
}
|
||||
|
||||
private Device getDevice() { return requestInfo; }
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
boolean done;
|
||||
|
||||
isRunning = true;
|
||||
|
||||
synchronized (lock) {
|
||||
done = networkPacketList.isEmpty();
|
||||
}
|
||||
|
||||
try {
|
||||
while (!done && !canceled) {
|
||||
synchronized (lock) {
|
||||
currentNetworkPacket = networkPacketList.remove(0);
|
||||
}
|
||||
|
||||
currentFileName = currentNetworkPacket.getString("filename");
|
||||
currentFileNum++;
|
||||
|
||||
setProgress(prevProgressPercentage);
|
||||
|
||||
addTotalsToNetworkPacket(currentNetworkPacket);
|
||||
|
||||
if (!getDevice().sendPacketBlocking(currentNetworkPacket, sendPacketStatusCallback)) {
|
||||
throw new RuntimeException("Sending packet failed");
|
||||
}
|
||||
|
||||
synchronized (lock) {
|
||||
done = networkPacketList.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
if (canceled) {
|
||||
uploadNotification.cancel();
|
||||
} else {
|
||||
uploadNotification.setFinished(getDevice().getContext().getResources().getQuantityString(R.plurals.sent_files_title, currentFileNum, getDevice().getName(), currentFileNum));
|
||||
uploadNotification.show();
|
||||
|
||||
reportResult(null);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
int failedFiles;
|
||||
synchronized (lock) {
|
||||
failedFiles = (totalNumFiles - currentFileNum + 1);
|
||||
uploadNotification.setFinished(getDevice().getContext().getResources()
|
||||
.getQuantityString(R.plurals.send_files_fail_title, failedFiles, getDevice().getName(),
|
||||
failedFiles, totalNumFiles));
|
||||
}
|
||||
|
||||
uploadNotification.show();
|
||||
reportError(e);
|
||||
} finally {
|
||||
isRunning = false;
|
||||
|
||||
for (NetworkPacket networkPacket : networkPacketList) {
|
||||
networkPacket.getPayload().close();
|
||||
}
|
||||
networkPacketList.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void addTotalsToNetworkPacket(NetworkPacket networkPacket) {
|
||||
synchronized (lock) {
|
||||
networkPacket.set(SharePlugin.KEY_NUMBER_OF_FILES, totalNumFiles);
|
||||
networkPacket.set(SharePlugin.KEY_TOTAL_PAYLOAD_SIZE, totalPayloadSize);
|
||||
}
|
||||
}
|
||||
|
||||
private void setProgress(int progress) {
|
||||
synchronized (lock) {
|
||||
uploadNotification.setProgress(progress, getDevice().getContext().getResources()
|
||||
.getQuantityString(R.plurals.outgoing_files_text, totalNumFiles, currentFileName, currentFileNum, totalNumFiles));
|
||||
}
|
||||
uploadNotification.show();
|
||||
}
|
||||
|
||||
void addNetworkPacket(@NonNull NetworkPacket networkPacket) {
|
||||
synchronized (lock) {
|
||||
networkPacketList.add(networkPacket);
|
||||
|
||||
totalNumFiles++;
|
||||
|
||||
if (networkPacket.getPayloadSize() >= 0) {
|
||||
totalPayloadSize += networkPacket.getPayloadSize();
|
||||
}
|
||||
|
||||
uploadNotification.setTitle(getDevice().getContext().getResources()
|
||||
.getQuantityString(R.plurals.outgoing_file_title, totalNumFiles, totalNumFiles, getDevice().getName()));
|
||||
|
||||
//Give SharePlugin some time to add more NetworkPackets
|
||||
if (isRunning && !updatePacketPending) {
|
||||
updatePacketPending = true;
|
||||
handler.post(this::sendUpdatePacket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendUpdatePacket() {
|
||||
NetworkPacket np = new NetworkPacket(SharePlugin.PACKET_TYPE_SHARE_REQUEST_UPDATE);
|
||||
|
||||
synchronized (lock) {
|
||||
np.set("numberOfFiles", totalNumFiles);
|
||||
np.set("totalPayloadSize", totalPayloadSize);
|
||||
updatePacketPending = false;
|
||||
}
|
||||
|
||||
getDevice().sendPacket(np);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
super.cancel();
|
||||
|
||||
currentNetworkPacket.cancel();
|
||||
}
|
||||
|
||||
private class SendPacketStatusCallback extends Device.SendPacketStatusCallback {
|
||||
@Override
|
||||
public void onProgressChanged(int percent) {
|
||||
float send = totalSend + (currentNetworkPacket.getPayloadSize() * ((float)percent / 100));
|
||||
int progress = (int)((send * 100) / totalPayloadSize);
|
||||
|
||||
if (progress != prevProgressPercentage) {
|
||||
setProgress(progress);
|
||||
prevProgressPercentage = progress;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
if (currentNetworkPacket.getPayloadSize() == 0) {
|
||||
synchronized (lock) {
|
||||
if (networkPacketList.isEmpty()) {
|
||||
setProgress(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
totalSend += currentNetworkPacket.getPayloadSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
//Ignored
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,119 +0,0 @@
|
||||
package org.kde.kdeconnect.Plugins.SharePlugin;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
class NotificationUpdateCallback extends Device.SendPacketStatusCallback {
|
||||
|
||||
private final Resources res;
|
||||
private final Device device;
|
||||
private final NotificationManager notificationManager;
|
||||
private final NotificationCompat.Builder builder;
|
||||
|
||||
private final ArrayList<NetworkPacket> toSend;
|
||||
|
||||
private final int notificationId;
|
||||
|
||||
private int sentFiles = 0;
|
||||
private final int numFiles;
|
||||
|
||||
NotificationUpdateCallback(Context context, Device device, ArrayList<NetworkPacket> toSend) {
|
||||
this.toSend = toSend;
|
||||
this.device = device;
|
||||
this.res = context.getResources();
|
||||
|
||||
String title;
|
||||
if (toSend.size() > 1) {
|
||||
title = res.getString(R.string.outgoing_files_title, device.getName());
|
||||
} else {
|
||||
title = res.getString(R.string.outgoing_file_title, device.getName());
|
||||
}
|
||||
|
||||
notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
builder = new NotificationCompat.Builder(context, NotificationHelper.Channels.FILETRANSFER)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_upload)
|
||||
.setAutoCancel(true)
|
||||
.setOngoing(true)
|
||||
.setProgress(100, 0, false)
|
||||
.setContentTitle(title)
|
||||
.setTicker(title);
|
||||
|
||||
notificationId = (int) System.currentTimeMillis();
|
||||
|
||||
numFiles = toSend.size();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(int progress) {
|
||||
builder.setProgress(100 * numFiles, (100 * sentFiles) + progress, false);
|
||||
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
sentFiles++;
|
||||
if (sentFiles == numFiles) {
|
||||
updateDone(true);
|
||||
} else {
|
||||
updateText();
|
||||
}
|
||||
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable e) {
|
||||
updateDone(false);
|
||||
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
|
||||
if (e != null) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateText() {
|
||||
String text;
|
||||
text = res.getQuantityString(R.plurals.outgoing_files_text, numFiles, sentFiles, numFiles);
|
||||
builder.setContentText(text);
|
||||
}
|
||||
|
||||
private void updateDone(boolean successful) {
|
||||
int icon;
|
||||
String title;
|
||||
String text;
|
||||
|
||||
if (successful) {
|
||||
if (numFiles > 1) {
|
||||
text = res.getQuantityString(R.plurals.outgoing_files_text, numFiles, sentFiles, numFiles);
|
||||
} else {
|
||||
final String filename = toSend.get(0).getString("filename");
|
||||
text = res.getString(R.string.sent_file_text, filename);
|
||||
}
|
||||
title = res.getString(R.string.sent_file_title, device.getName());
|
||||
icon = android.R.drawable.stat_sys_upload_done;
|
||||
} else {
|
||||
final String filename = toSend.get(sentFiles).getString("filename");
|
||||
title = res.getString(R.string.sent_file_failed_title, device.getName());
|
||||
text = res.getString(R.string.sent_file_failed_text, filename);
|
||||
icon = android.R.drawable.stat_notify_error;
|
||||
}
|
||||
|
||||
builder.setOngoing(false)
|
||||
.setTicker(title)
|
||||
.setContentTitle(title)
|
||||
.setContentText(text)
|
||||
.setSmallIcon(icon)
|
||||
.setProgress(0, 0, false); //setting progress to 0 out of 0 remove the progress bar
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -43,7 +43,7 @@ import java.io.InputStream;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
class ShareNotification {
|
||||
class ReceiveNotification {
|
||||
private final NotificationManager notificationManager;
|
||||
private final int notificationId;
|
||||
private NotificationCompat.Builder builder;
|
||||
@@ -54,7 +54,7 @@ class ShareNotification {
|
||||
private static final int bigImageWidth = 1440;
|
||||
private static final int bigImageHeight = 720;
|
||||
|
||||
public ShareNotification(Device device) {
|
||||
public ReceiveNotification(Device device) {
|
||||
this.device = device;
|
||||
|
||||
notificationId = (int) System.currentTimeMillis();
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user