diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 1f055691..757cbec1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -5,7 +5,7 @@
android:versionName="1.6.5">
+ android:targetSdkVersion="25" />
+
+
@@ -42,8 +44,6 @@
android:enabled="true" >
-
-
+
22 means we have to support the new permissions model
+ targetSdkVersion 25
//multiDexEnabled true
//testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner"
}
@@ -57,7 +57,7 @@ android {
minifyEnabled false
useProguard false
}
- release { //keep on 'releae', set to 'all' when testing to make sure proguard is not deleting important stuff
+ release { //keep on 'release', set to 'all' when testing to make sure proguard is not deleting important stuff
minifyEnabled true
useProguard true
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
@@ -83,5 +83,6 @@ dependencies {
androidTestCompile 'org.mockito:mockito-core:1.10.19'
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.1'// Because mockito has some problems with dex environment
androidTestCompile 'org.skyscreamer:jsonassert:1.3.0'
+ testCompile 'junit:junit:4.12'
}
diff --git a/res/layout/activity_refresh_list.xml b/res/layout/activity_refresh_list.xml
new file mode 100644
index 00000000..bdc9f5ef
--- /dev/null
+++ b/res/layout/activity_refresh_list.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/menu/pairing.xml b/res/menu/pairing.xml
index ffc8c144..1589a7ab 100644
--- a/res/menu/pairing.xml
+++ b/res/menu/pairing.xml
@@ -9,14 +9,6 @@
android:title="@string/refresh"
/>
-
-
-
-
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 3d5c6bf7..8ac25088 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -5,13 +5,23 @@
تقرير البطّاريّة
بلّغ عن حالة البطّاريّة دوريًّا
اكشف نظام الملفّات
+ تسمح بتصفّح نظام ملفّات هذا الهاتف عن بعد
مزامنة الحافظة
شارك محتوى الحافظة
- الدَّخل البعيد
+ الدّخل البعيد
+ استخدم الهاتف أو اللوحيّ كفأرة ولوحة مفاتيح
+ تحكّمات الوسائط المتعدّدة
+ توفّر تحكّمًا بعيدًا لمشغّل الوسائط
+ شغّل أمرًا
+ تحفّز أوامر عن بعد من الهاتف أو اللوحيّ
وخزة
أرسل واستقبل وخزات
مزامنة الإخطارات
انفذ إلى إخطاراتك من أجهزة أخرى
+ استقبل الإخطارات
+ استقبل الإخطارات من الجهاز الآخر واعرضها على أندرويد
+ شارك واستقبل
+ شارك الملفّات والعناوين بين الجهازين
هذه الميزة غير متوفّرة في إصدار أندرويد لديك
لا أجهزة
حسنًا
@@ -19,24 +29,33 @@
افتح الإعدادات
عليك إعطاء التّطبيق صلاحيّات للنّفاذ إلى الإخطارات
أرسل وخزة
+ تحكّمات الوسائط المتعدّدة
+ الدّخل البعيد
+ حرّك اصبعًا على الشّاشة لتحريك مؤشّر الفأرة. المس للنّقر واستخدم إصبعين أو ثلاث للزّرّين الأيمن والأوسط. انقر مطوّلًا للسّحب والإفلات.
+ اضبط إجراء اللمس بإصبعين
+ اضبط إجراء اللمس بثلاث أصابع
+ اضبط حساسيّة لوحة اللمس
+ اعكس اتّجاه التّمرير
-
-
- - Nothing
+ - النّقر باليمين
+ - النّقر بالوسط
+ - لا شيء
- - Slowest
- - Above Slowest
- - Default
- - Above Default
- - Fastest
+ - الأبطأ
+ - الأقل بطئًا
+ - الافتراضيّ
+ - الأسرع قليلًا
+ - الأسرع
الأجهزة المقترن بها
الأجهزة المتوفّرة
الأجهزة المتذكَّرة
فشل تحميل الملحقات (المس لمعلومات اكثر):
- أزل الاقتران
+ إعدادات الملحقة
+ ألغِ الاقتران
الجهاز المقترن غير قابل الوصول
+ اقرن جهازًا جديدًا
جهاز مجهول
الجهاز غير قابل الوصول
طُلب الاقتران بالفعل
@@ -46,14 +65,24 @@
ألغاه المستخدم
ألغاه ندّ آخر
استُقبل مفتاح غير صالح
+ معلومات التّعمية
+ لا يستخدم الجهاز الآخر إصدارة حديثة من «كدي المتّصل»، ستُستخدم طريقة التّعمية القديمة.
+ بصمة SHA1 لشهادة جهازك هي:
+ بصمة SHA1 لشهادة الجهاز البعيد هي:
طُلب الاقتران
طلب اقتران من %1s
استُلمت وصلة من %1s
المس لفتح \'%1s\'
ملفّ وارد من %1s
%1s
- استُلم ملفّ من %1s
+ يرسل ملفًّا إلى %1s
+ %1s
+ استُقبل ملفّ من %1s
+ فشل استقبال الملفّ من %1s
المس لفتح \'%1s\'
+ أرسل ملفًّا إلى %1s
+ %1s
+ %1s
المس للإجابة
أعد الاتّصال
أرسل نقرة باليمين
@@ -74,6 +103,8 @@
التّالي
المستوى
إعدادات الوسائط المتعدّدة
+ زرّا التّقدّم والإرجاع
+ اضبط الوقت عند نقر زرّيّ التّقديم أو الإرجاع.
- 10 ثوان
- 20 ثانية
@@ -92,9 +123,13 @@
اسم جهاز غير صالح
استُقبل نصّ، حُفظ إلى الحافظة
قائمة أجهزة مخصّصة
+ اقرن جهازًا جديدًا
+ ألغِ اقتران %s
أضف أجهزة بميفاق الإنترنت مإ
إخطارات مزعجة
اهتزّ وشغّل صوتًا عند استقبال ملفّ
+ مرشّح الإخطارات
+ ستُزامن الإخطارات من التّطبيقات المحدّدة.
التّخزين الدّاخليّ
كلّ الملفّات
بطاقة SD %d
@@ -102,7 +137,26 @@
(للقراءة فقط)
صور الكاميرا
أضف مضيفًا/مإ
+ اسم المضيف أو عنوان IP
لم يُعثر على مشغّلات
استخدم هذا الخيار فقط إن لم يُكتَشف جهازك آليًّا. أدخِل عنوان مإ أو اسم المضيف أدناه والمس الزرّ لإضافته إلى القائمة. المس عنصرًا موجودًا لإزالته من القائمة.
%1$s على %2$s
+ أرسل ملفّات
+ أجهزة «كدي المتّصل»
+ الأجهزة الأخرى التي تشغّل «كدي المتّصل» وعلى نفس الشّبكة ستظهر هنا.
+ اقتُرن الجهاز
+ أعد تسمية الجهاز
+ أعد التّسمية
+ أنعش
+ الجهاز المقترن هذا لا يمكن الوصول إليه. تأكّد من اتّصاله بنفس الشّبكة.
+ لا متصفّحات ملفّات مثبّتة.
+ أرسل SMS
+ أرسل رسائل نصّيّة من سطح المكتب
+ لا يدعم جهازك هذه الملحقة
+ جِد جهازي
+ جِد جهازي اللوحيّ
+ يرّن هذا الجهاز لتجده
+ وُجد
+ افتح
+ أغلق
diff --git a/res/values-ast/strings.xml b/res/values-ast/strings.xml
index a21a67b6..a35388af 100644
--- a/res/values-ast/strings.xml
+++ b/res/values-ast/strings.xml
@@ -1,33 +1,39 @@
- Notificador telefónicu
+ Avisador telefónicu
Unvia avisos pa SMS y llamaes
Informe de batería
- Infoma davezu del estáu de la batería
+ Informe periódicu del estáu de la batería
+ Permite restolar remotamente a esti preséu
Sincronización del cartafueyu
Comparte\'l conteníu del cartafueyu
Entrada remota
- Controles remotos multimedia
+ Usa\'l to teléfonu o tableta como panel táutil y tecláu
+ Pulsaciones remotes
+ Controles multimedia
+ Forne un control remotu pal to reproductor multimedia
+ Execución de comandos
+ Aiciona comandos remotos del to teléfonu o tableta
Ping
- Unvia y recibi pings
+ Unvia y recibe pings
Sincronización d\'avisos
- Accede a los tos avisos dende otros preseos
- Unvia y recibi pings
+ Accede a los tos avisos d\'otros preseos
+ Recibir avisos
+ Recibe avisos d\'otros preseos y amuésalos n\'Android
+ Compartir y recibir
Comparte ficheros y URLs ente preseos
Esta carauterística nun ta disponible na to versión d\'Android
Ensin preseos
Aceutar
Encaboxar
Abrir axustes
- Necesites garantizar l\'accesu a los avisos
+ ¡
Unviar ping
- Controles multimedia
- Entrada remota
- Muevi un deu enriba la pantalla pa mover el mur. calca pa un clic y usa dos/tres deos pa los motones de drecha y mediu. Usa un primíu llargu pa arrastrar y soltar.
+ Control multimedia
- - Clic drechu
- - Clic d\'en mediu
- - Nada
+ - Right click
+ - Middle click
+ - Nothing
- Slowest
@@ -36,101 +42,16 @@
- Above Default
- Fastest
- Preseos coneutaos
- Preseos disponibles
- Preseos recordaos
- El preséu empareyáu nun ye agamable
- Preséu desconocíu
- Nun ye algamable\'l preséu
- Empareyamientu yá solicitáu
- El preséu yá ta empareyáu
- Nun pudo unviase\'l paquete
- Tiempu escosao
- Encaboxáu pol usuariu
- Encaboxáu pola otra parte
- Recibióse una clave non válida
- Solicitóse l\'empareyamientu
- Solicitú d\'empareyamientu de %1s
- Recibióse l\'enllaz de %1s
- Calca p\'abrir «%1s»
- Ficheru entrante de %1s
- %1s
- Unviando ficheru a %1s
- %1s
- Recibióse\'l ficheru de %1s
- Fallu al recibir el ficheru de %1s
- Tap to open \'%1s\'
- Unvióse\'l ficheru a %1s
- %1s
- %1s
- Calca pa responder
- Reconeutar
- Amosar tecláu
- Preséu non empareyáu
- Solicitar empareyamientu
- Aceutar
- Refugar
- Preséu
- Empareyar preséu
- Control remotu
- Axustes KDE Connect
- Reproducir
- Anterior
- Rebobinar
- Avance rápidu
- Siguiente
- Volume
- Axustes multimedia
- Botones d\'avanzar/rebobinar
- Axusta\'l tiempu p\'avanzar/rebobinar al primise
+ Escosó\'l tiempu
- - 10 segundos
- - 20 segundos
- - 30 segundos
- - 1 minutu
- - 2 minutos
+ - 10 seconds
+ - 20 seconds
+ - 30 seconds
+ - 1 minute
+ - 2 minutes
- Esti preséu usa una versión vieya del protocolu
- Esti preséu usa una versión anovada del protocolu
- Axustes xenerales
- Axustes
- Axustes de %s
- Nome de preséu
- %s
- Nome de preséu non válidu
- Recibióse\'l testu y guardóse nel cartafueyu
- Llista de preseos personalizada
- Empareyar con un preséu nuevu
- Desempareyar %s
- Amestar preseos pola IP
- Avisos sonoros
- Fai vibrar y reproduz un soníu al recibir un ficheru
- Peñera d\'avisos
- Los avisos sincronizaránse coles aplicaciones esbillaes.
- Almacenamientu internu
- Tolos ficheros
- Tarxeta SD %d
- Tarxeta SD
- (namai llectura)
- Semeyes de cámara
- Amestar agospiu/IP
- Nome d\'agospiu o IP
- Nun s\'alcontraron reproductores
- Usa esta opción namái si\'l to preséu nun se deteuta automáticamente. Introduz la direición o\'l nome d\'agospiu y toca\'l botón p\'amestalu a la llista. Toca un elementu esistente pa desanicialu de la llista.
- %1$s en %2$s
- Unviar ficheros
- Preseos KDE Connect
- Deberíen apaecer equí otros preseos executando KDE Connect.
- Preséu empareyáu
- Renomar preséu
- Renomar
- Refrescar
- Esti preséu empareyáu nun ye algamable. Asegúrate que ta coneutáu a la to mesma rede.
- Nun hai restoladores de ficheros instalaos.
- Unviar SMS
+ Nun hai restoladores de ficheros instalaos
Unvia mensaxes de testu dende\'l to escritoriu
- Esti complementu nun ta sofitáu pol preséu
- Alcontrar
- Abrir
- Zarrar
+ Esti complementu nun lu sofita\'l preséu
+ Fai sonar el teléfonu pa qu\'asina pueas alcontralu
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index dad48dd2..ec035e67 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -27,8 +27,6 @@
- Srednji klik
- Ništa
- desno
- Srednje
- Slowest
- Above Slowest
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 4c60746e..6312ea16 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -10,8 +10,8 @@
Comparteix el contingut del porta-retalls
Entrada remota
Usa el vostre telèfon o tauleta com un ratolí i un teclat
- Rep les pulsacions de tecla remotes
- Rep els esdeveniments de pulsacions de tecla des de dispositius remots
+ S\'estan rebent pulsacions de tecla remotes
+ S\'estan rebent esdeveniments de pulsacions de tecla des de dispositius remots
Controls multimèdia
Proporciona un comandament a distància pel reproductor multimèdia
Executa una ordre
@@ -33,7 +33,7 @@
Envia un ping
Control multimèdia
Fes servir les tecles remotes només en editar
- No hi ha cap connexió activa amb el teclat remot, establiu-ne una al «kdeconnect»
+ No hi ha cap connexió activa amb el teclat remot, establiu-ne una al kde-connect
La connexió amb el teclat remot està activa
Hi ha més d\'una connexió amb un teclat remot, seleccioneu el dispositiu per configurar-lo
Entrada remota
@@ -47,9 +47,6 @@
- Clic del mig
- No fer res
- dret
- mig
- Predeterminada
- La més lenta
- Lenta
@@ -176,4 +173,7 @@
L\'he trobat
Obre
Tanca
+ Us caldrà concedir permís per accedir a l\'emmagatzematge
+ Alguns connectors necessiten permisos per a funcionar (puntegeu per a més informació):
+ Aquest connector necessita permisos per a funcionar
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index f5944c9b..f0c10f52 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -47,9 +47,6 @@
- Kliknutí prostředním tlačítkem myši
- Nic
- pravé
- prostřední
- výchozí
- Nejpomalejší
- Méně pomalý
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index ca665a25..e49cef4b 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -47,15 +47,12 @@
- Midterklik
- Intet
- højre
- midter
- standard
- Mest langsom
- Over mest langsom
- Standard
- Over standard
- - Hurtigste
+ - Hurtigst
Forbundne enheder
Tilgængelig enheder
@@ -85,12 +82,15 @@
Indkommende fil fra %1s
%1s
Sender fil til %1s
+ Sender filer til %1s
%1s
+ Sendte %1$d ud af %2$d filer
Modtog fil fra %1s
Kunne ikke modtage fil fra %1s
Tap for at åbne \"%1s\"
Fil sendt til %1s
%1s
+ Kunne ikke sende filen til %1s
%1s
Tap for at svare
Forbind igen
@@ -137,6 +137,10 @@
Tilføj enheder via IP
Støjende bekendtgørelser
Vibrér og afspil en lyd når en fil modtages
+ Tilpas destinationsmappe
+ Modtagne filer vil dukke op i Downloads
+ Filer vil blive gemt i mappen nedenfor
+ Destinationsmappe
Bekendtgørelsesfilter
Bekendtgørelser vil blive synkroniseret for de valgte apps.
Intern lagring
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index bd7d7587..b650ac64 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -13,7 +13,7 @@
Multimedia-Bedienung
Eine Fernbedienung für Ihre Medienwiedergabe
Befehl ausführen
- Von Ihrem Telefon oder Tablett Befehle auf anderen Geräten ausführen
+ Von Ihrem Telefon oder Tablet Befehle auf anderen Geräten ausführen
Ping
Senden und Empfangen von Pings
Benachrichtigungs-Abgleich
@@ -41,9 +41,6 @@
- Mittelklick
- Nichts
- Rechts
- Mitte
- Standard
- Langsamste
- Langsam
@@ -95,7 +92,7 @@
Mittelklick senden
Tastatur anzeigen
Das Gerät ist nicht verbunden
- Verbindung angefordert
+ Verbindung anfordern
Annehmen
Ablehnen
Gerät
@@ -164,8 +161,8 @@
Text-Nachrichten von Ihrer Arbeitsfläche senden
Dieses Modul wird durch das Gerät nicht unterstützt
Mein Telefon suchen
- Mein Tablett suchen
- Ruft dieses Gerät an, um es zu suchen.
+ Mein Tablet suchen
+ Ruft dieses Gerät an, damit sie es finden können
Gefunden
Öffnen
Schließen
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index 0f4e75f5..086b790c 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -41,9 +41,6 @@
- Μεσαίο κλικ
- Τίποτα
- δεξί
- μεσαίο
- προκαθορισμένο
- Το πιο αργό
- Πάνω από το πιο αργό
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index de3f74d7..ccd6ff51 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -47,9 +47,6 @@
- Middle click
- Nothing
- right
- middle
- default
- Slowest
- Above Slowest
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 1240e079..987c62cd 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -11,7 +11,7 @@
Entrada remota
Usar su teléfono o tableta como teclado y teclado táctil
Recibir pulsaciones de teclas remotas
- Reciba eventos de pulsación de teclas desde dispositivos remotos
+ Recibir eventos de pulsación de teclas desde dispositivos remotos
Controles multimedia
Proporciona un control remoto para su reproductor de medios
Ejecutar orden
@@ -47,9 +47,6 @@
- Clic del botón central
- Nada
- derecho
- medio
- por defecto
- Muy poco sensible
- Poco sensible
@@ -176,4 +173,7 @@
Encontrado
Abrir
Cerrar
+ Debe otorgar permisos para acceder al almacenamiento
+ Algunos complementos necesitan permisos para funcionar (pulse para más información):
+ Este complemento necesita permisos para funcionar
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 18bd240d..849539a6 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -41,9 +41,6 @@
- Keskklõps
- Ei tee midagi
- parem
- keskmine
- vaikimisi
- Kõige aeglasem
- Kõige aeglasemast kiirem
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index ac9b3e10..a70d95e3 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -41,9 +41,6 @@
- Keskinapsautus
- Ei toimintoa
- Oikea painike
- Keskipainike
- oletus
- Hitain
- Hitainta suurempi
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index 5c83f175..28c97681 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -28,25 +28,25 @@
Mova un dedo na pantalla para mover o cursor. Toque para facer clic, e use dous ou tres dedos para os botóns secundario e central. Prema durante un tempo para arrastrar e soltar.
Definir a acción de tocar con dous dedos
Definir a acción de tocar con tres dedos
+ Definir a sensibilidade do punteiro táctil
+ Inverter a dirección de desprazamento
- Clic dereito
- Clic central
- Nada
- dereita
- medio
- - Slowest
- - Above Slowest
- - Default
- - Above Default
- - Fastest
+ - O máis lento
+ - Lento
+ - Predeterminado
+ - Rápido
+ - O máis rápido
Dispositivos conectados
Dispositivos dispoñíbeis
Dispositivos coñecidos
Non foi posíbel cargar os seguintes complementos (toque para obter máis información):
- Configuración do engadido
+ Configuración do complemento
Desemparellarse
O dispositivo emparellado está fóra do alcance.
Emparellar cun novo dispositivo
@@ -136,7 +136,7 @@
Outros dispositivos que estean a executar KDE Connect na mesma rede deberían aparecer aquí.
Emparellouse co dispositivo
Cambiar o nome do dispositivo
- Mudar o nome
+ Cambiar o nome
Actualizar
Este dispositivo emparellado está fóra do alcance. Asegúrese de que está conectado á mesma rede.
Non hai navegadores de ficheiros instalados.
diff --git a/res/values-he/strings.xml b/res/values-he/strings.xml
index 63c91c95..82726e88 100644
--- a/res/values-he/strings.xml
+++ b/res/values-he/strings.xml
@@ -5,15 +5,23 @@
דיווח סוללה
מדווח על אחוז הסוללה למחשב
גישה לקבצים
- סנכנרון לוח העתקה
- שתף בין המחשבים את מה שמועתק
+ אפשר להתקן המרוחק לדפדף בקבצים של הפלאפון מרחוק
+ סנכרון לוח העתקה
+ שתף בין המחשבים את כל מה שמועתק
שליטה מרחוק
+ השתמש בפלאפון כדי לשלוט בעכבר ובמקלדת של ההתקן המרוחק
+ קבלת לחיצות מרחוק
+ קבלת אירועי מקלדת מהתקן מרוחק
שליטה במדיה
+ מספק שליטה מרוחקת על נגן המדיה שלך
הרץ פקודה
+ הרץ פקודה במחשב מהמכשיר שלך
פינג
שלח וקבל פינגים
סנכרון התראות
הראה את ההתראות מהפלאפון בהתקן אחר
+ קבלת התראות
+ קבל התראות מהתקן אחר והצג אותם במכשיר שלך
שתף וקבל קבצים וכתובות
שתף וקבל קבצים וכתובת אינטרנט בין התקנים
אפשרות זו אינה זמינה בגרסת האנדרואיד שלך
@@ -24,7 +32,11 @@
אתה צריך לתת הרשאות לגישה להתראות
שלח פינג
שליטה על המדיה
- שלוט על המחשב
+ השתמש במקשים מרוחקים רק בעת עריכה
+ אין מקלדת מרוחקת מופעלת, הוסף אחת ל־kdeconnect
+ חיבור המקדלת המרוחקת פעיל
+ ישנם כמה מקלדות מרוחקות מחוברות, בחר את ההתקן להגדרה
+ שליטה מרחוק
הזז את האצבע על המסך כדי להזיז את סמן העכבר במחשב. לחץ כדי ללחוץ במחשב, השתמש בשנים או שלוש אצבעות כדי ללחוץ על המקש הימני או האמצעי. השתמש בליחצה ארוכה לגרירה ושחרור.
הגדר פעולה ללחיצת שתי אצבעות
הגדר פעולה ללחיצת שלוש אצבעות
@@ -35,9 +47,6 @@
- לחיצה אצמעית (גלגלת)
- שום דבר
- ימין
- אמצע
- ברירת מחדל
- הכי איטי
- יותר מההכי איטי
@@ -59,9 +68,9 @@
ההתקן כבר מותאם
לא יכול לשלוח חבילה
נגמר הזמן
- בוטל ע\"י המשתמש
- בוטל ע\"י מישהו אחר
- התקבל מפתח לא חוקי
+ בוטל על ידי המשתמש
+ בוטל על ידי מישהו אחר
+ התקבל מפתח לא תקין
פרטי הצפנה
ההתקן השני אינו משתמש בגרסה האחרונה של KDE Connect, משתמש בשיטת ההצפנה הישנה.
טביעת האצבע SHA1 של ההתקן היא:
@@ -69,16 +78,19 @@
בקשת התאמה
בוקשה התאמה מ־%1s
התקבל קישור מ־%1s
- לחץ כדי לפתוח את \'%1s\'
- התקבל קובץ ־%1s
+ לחץ כדי לפתוח את \"%1s\"
+ התקבל קובץ מאת %1s
%1s
- שולח קובץ ל־%1s
+ שולח קובץ אל %1s
+ שולח קובצים אל %1s
%1s
- התקבל קובץ מ־%1s
- נכשל בקבלת קובץ מ־%1s
- לחץ כדי לפתוח את %1s
- הקובץ נשלח ל־%1s
+ "שולח %1$d מתוך %2$d קבצים "
+ התקבל קובץ מאת %1s
+ נכשל בקבלת קובץ מאת %1s
+ לחץ כדי לפתוח את \"%1s\"
+ הקובץ נשלח אל %1s
%1s
+ נכשל בשליחת הקובץ אל %1s
%1s
לחץ כדי לענות
התחבר מחדש
@@ -109,22 +121,26 @@
- דקה
- שתי דקות
- שתף ל...
+ שתף אל...
ההתקן משתמש בגרסה ישנה יותר
ההתקן משתמש בגרסה חדשה יותר
הגדרות כלליות
הגדרות
הגדרות %s
- שם המכשיר
+ שם ההתקן
%s
- שם המכשיר לא תקין
+ שם ההתקן לא תקין
התקבל טקסט, נשמר ללוח העתקה
רשימת התקנים מותאמת אישית
התאם התקן חדש
בטל את ההתאמה עם %s
- הוסף התקן ע\"י כתובת IP
+ הוסף התקן על ידי כתובת IP
התראות רועשות
רטוט ונגן צליל בעת קבלת קובץ
+ שנה תקיית יעד
+ קבצים שהתקבלו יהיו בהורדות
+ קבצים יאוכסנו בתיקיה למטה
+ תיקית יעד
סנן התראות
התראות יסונכרנו רק לאפליקציות נבחרות
זיכרון פנימי
@@ -133,11 +149,11 @@
כרטיס זיכרון
(לקריאה בלבד)
תמונות מצלמה
- הוסף כתובת שרת או IP
- כתבות שרת או IP
+ הוסף כתובת או IP
+ כתובת או IP
לא נמצא נגן
השתמש באפשרות זו רק אם המכשיר שלך לא מזוהה באופן אוטומטי. הקלד את כתובת הIP או את כינוי ההתקן למטה ולחץ על הכפתור כדי להוסיף לרשימה. לחץ על פריט קיים כדי להסיר אותו מהרשימה.
- %1$s ב־%2$s
+ %1$s אצל %2$s
שלח קובץ
מכשירי KDE Connect
התקנים אחרים המריצים KDE Connect ברשת הנוכחית צריכים להופיע פה.
@@ -145,12 +161,15 @@
שנה שם התקן
שנה שם
רענן
- ההתקן המתואם לא זמין, וודא שהוא מחובר לאותה רשת אליה התקן זה מחובר.
+ ההתקן המתואם לא זמין, וודא שהוא מחובר לאותה רשת אליה אתה מחובר.
+ נראה שאתה מחובר דרך הרשת הסלולרית. KDE Connect עובד רק עם רשתות מקומיות.
לא נמצאו מנהלי קבצים מותקנים במכשיר זה.
- שלח SMS
+ שליחת הודעת SMS
שלח הודעות מהמחשב שלך
- התוסף הזה לא נתמך ע\"י המכשיר שלך
- מצא את המכשיר שלי
+ תוסף הזה לא נתמך על ידי המכשיר שלך
+ מצא את הפלאפון שלי
+ מצא את הטבלט שלי
+ מפעיל רעש במכשיר כדי שתוכל למצוא אותו.
נמצא
פתח
סגור
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index c3298781..fbc2f613 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -47,9 +47,6 @@
- Clic centrale
- Niente
- destra
- centro
- predefinita
- Minima
- Più veloce
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index 55b661aa..2a89b9ea 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -41,9 +41,6 @@
- 가운데 단추 누름
- 아무것도 안 함
- 오른쪽
- 가운데
- 기본값
- 느리게
- 느리게 이상
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 307b9292..603c07a0 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -41,14 +41,11 @@
Jau paprašyta suporuoti
Įrenginys jau suporuotas
Nepavyksta išsiųsti paketo
+ Skirtasis laikas baigėsi
Naudotojas atšaukė užduotį
Porininkas atšaukė užduotį
Gautas netinkamas raktas
Paprašyta suporuoti
- %1s
- %1s
- %1s
- %1s
Norėdami atsakyti, palieskite
Prisijungti iš naujo
Rodyti klaviatūrą
@@ -86,7 +83,6 @@
Suporuoti naują įrenginį
Atrišti %s
Pridėti įrenginį pagal IP
- Vidinė saugykla
Visi failai
SD kortelė %d
SD kortelė
@@ -100,9 +96,12 @@
Čia turėtų pasirodyti to kiti paties tinklo įrenginiai, kuriuose veikia „KDE Connect“
Įrenginys suporuotas
Pervadinti įrenginį
+ Pervadinti
Atnaujinti
Šis suporuotas įrenginys nepasiekiamas. Patikrinkite, ar jis prisijungęs prie to paties tinklo.
Siųsti SMS
Telefonas nepalaiko šio papildinio
Radau
+ Atverti
+ Užverti
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index ef92ae6c..61f1fce6 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -47,9 +47,6 @@
- Middelste muisklik
- Niets
- rechts
- midden
- standaard
- Langzaamst
- Langzaam
@@ -78,22 +75,22 @@
Het andere apparaat gebruikt geen recente versie van KDE Connect, de verouderde versleutelingsmethode zal worden gebruikt.
De SHA1 vingerafdruk van het certificaat van uw apparaat is:
De SHA1 vingerafdruk van het certificaat van het apparaat op afstand is:
- Paar gevraagd
+ Paarvorming gevraagd
Verzoek om een paar te maken van %1s
Ontvangen koppeling van %1s
Tap om \'%1s\' te openen
Inkomend bestand van %1s
%1s
Bezig bestand te verzenden naar %1s
- Bestanden verzenden naar %1s
+ Bezig bestanden te verzenden naar %1s
%1s
- Verzonden %1$d van %2$d bestanden
+ Verzonden %1$d uit %2$d bestanden
Bestand ontvangen van %1s
Bestand ontvangen van %1s is mislukt
Tap om \'%1s\' te openen
Bestand verzonden naar %1s
%1s
- Verzenden van bestanden naar %1s is mislukt
+ Verzenden van bestand naar %1s is mislukt
%1s
Tap om te antwoorden
Opnieuw verbinden
@@ -140,7 +137,7 @@
Voeg apparaten toe per IP-adres
Luidruchtige meldingen
Vibreer en speel een geluidje bij ontvangen van een bestand
- Pas bestemmingsmap aan
+ De bestemmingsmap aanpassen
Ontvangen bestanden zullen in Downloads verschijnen
Bestanden zullen opgeslagen worden in de onderstaande map
Bestemmingsmap
@@ -176,4 +173,7 @@
Gevonden
Openen
Sluiten
+ U moet toestemming geven voor toegang tot de opslag
+ Sommige plug-ins hebben toestemming nodig om te werken (tik voor meer informatie):
+ Deze plug-in heeft toestemming nodig om te werken
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 5e6630c0..84682a04 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -47,9 +47,6 @@
- Kliknięcie środkowym
- Nic
- prawo
- środek
- domyślne
- Najmniejsza
- Ponad najmniejszą
@@ -176,4 +173,7 @@
Znaleziony
Otwórz
Zamknij
+ Musisz nadać uprawnienia, aby uzyskać dostęp do pamięci masowej
+ Niektóre z wtyczek wymagają uprawnień do działania (stuknij po więcej informacji)
+ Ta wtyczka wymaga uprawnień do działania
diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml
index c688b029..816dc563 100644
--- a/res/values-pt-rBR/strings.xml
+++ b/res/values-pt-rBR/strings.xml
@@ -39,9 +39,6 @@
- Botão do meio
- Nada
- direita
- meio
- padrão
- Mais lento
- Ainda mais lento
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 193d21a1..9c053dee 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -47,9 +47,6 @@
- Botão do meio
- Nada
- direita
- meio
- predefinição
- Mais Lento
- Ainda Mais Lento
@@ -176,4 +173,7 @@
Encontrado
Abrir
Fechar
+ Precisa de dar permissões de acesso ao armazenamento
+ Alguns \'plugins\' precisam de permissões para funcionar (toque para mais informações):
+ Este \'plugin\' precisa de permissões para funcionar
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index b158da24..54222404 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -10,8 +10,6 @@
Zdieľať obsah schránky
Vzdialený vstup
Použiť váš telefón alebo tablet ako touchpad a klávesnicu
- Prijímať vzdialené stlačenia klávesov
- Prijímať udalosti stlačení klávesov od vzdialených zariadení
Multimediálne ovládače
Poskytuje vzdialené ovládanie pre váš prehrávač médií
Spustiť príkaz
@@ -32,10 +30,6 @@
Musíte povoliť oprávnenia na prístup k pripomienkam
Poslať ping
Multimediálny ovládač
- Spracúva vzdialené klávesy len pri editácii
- Nie sú žiadne aktívne pripojenia vzdialenej klávesnice, vytvorte nejaké v Kdeconnect
- Vzdialené pripojenie klávesnice je aktívne
- Je viac ako jedno vzdialené pripojenie klávesnice, vyberte zariadenie na nastavenie
Vzdialený vstup
Posúvajte prst na obrazovke na posun kurzora. Ťuknutie vyvolá klik a použite dva/tri prsty pre pravé a stredné tlačidlo. Použite dlhé stlačenie pre drag and drop.
Nastaviť akciu dvoch prstov
@@ -47,9 +41,6 @@
- Stredný klik
- Nič
- vpravo
- stred
- predvolené
- Najpomalšie
- Nad najpomalším
@@ -85,15 +76,12 @@
Prichádzajúci súbor od %s
%1s
Posielam súbor pre %1s
- Posielam súbor pre %1s
%1s
- Poslať %1$d z %2$d súborov
Prijatý súbor od %1s
Zlyhalo prijatie súboru od %1s
Ťuknite na otvorenie \'%1s\'
Poslať súbor pre %1s
%1s
- Zlyhalo poslanie súboru %1s
%1s
Tapnite na odpoveď
Znovu pripojiť
@@ -140,10 +128,6 @@
Pridať zariadenia podľa IP
Hlučné pripomienky
Vibrovať a prehrať zvuk pri prijatí súboru
- Prispôsobiť cieľový adresár
- Prijaté súbory sa objavia v Preberaniach
- Súbory sa uložia v adresári dolu
- Cieľový adresár
Filter upozornení
Upozornenia budú synchronizované pre vybrané aplikácie.
Interné úložisko
@@ -165,7 +149,6 @@
Premenovať
Obnoviť
Toto spárované zariadenie nie je dosiahnuteľné. Prosím, uistite sa, že je pripojené do rovnakej siete.
- Zdá sa, že ste na mobilom dátovom pripojení. KDE Connect funguje iba na lokálnej sieti.
Nie sú nainštalované žiadne prehliadače.
Poslať SMS
Posielať textové správy z vášho počítača
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 423fac73..c05ba613 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -176,4 +176,7 @@
Нађен
Отвори
Затвори
+ Морате дати дозволе за приступ унутрашњој меморији
+ Неки прикључци траже дозволе да би радили (тапните за више информација):
+ Овај прикључак тражи дозволе да би радио
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 4e614b96..8ceaee0b 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -8,10 +8,10 @@
Gör det möjligt att bläddra i apparatens filsystem från annan apparat
Synkronisera klippbord
Dela klippbordets innehåll
- Fjärrinmatning
+ Extern inmatning
Använd telefonen eller surfplattan som mus och tangentbord
- Ta emot fjärrtangentnedtryckningar
- Ta emot tangentnedtryckningar från fjärrenheter
+ Ta emot externa tangentnedtryckningar
+ Ta emot tangentnedtryckningar från externa enheter
Multimediakontroller
Tillhandahåller en fjärrkontroll för mediaspelaren
Kör kommando
@@ -32,11 +32,11 @@
Du måste ge rättighet att komma åt underrättelser
Skicka ping
Kontroll av multimedia
- Hantera bara fjärrtangenter vid redigering
- Det finns ingen aktiv fjärrtangentbordsanslutning, upprätta en i KDE-anslut
- Fjärrtangentbordsanslutning är aktiv
- Det finns mer än en fjärrtangentbordsanslutning, välj enhet att anpassa
- Fjärrinmatning
+ Hantera bara externa tangenter vid redigering
+ Det finns ingen aktiv anslutning till externt tangentbord, upprätta en i KDE-anslut
+ Anslutning till externt tangentbord är aktiv
+ Det finns mer än en anslutning till externt tangentbord, välj enhet att anpassa
+ Extern inmatning
Flytta fingret på skärmen för att röra muspekaren. Rör för att klicka, och använd två eller tre fingrar för höger- och mittenknapparna. Använd en längre beröring för drag och släpp.
Ställ in åtgärd vid två fingerberöringar
Ställ in åtgärd vid tre fingerberöringar
@@ -47,9 +47,6 @@
- Mittenklick
- Ingenting
- höger
- mitten
- normal
- Långsammaste
- Ovanför långsammaste
@@ -176,4 +173,7 @@
Hittade den
Öppna
Stäng
+ Du måste ge rättighet att komma åt lagringen
+ Vissa insticksprogram kräver rättigheter för att fungera (rör för mer information):
+ Insticksprogrammet behöver rättigheter för att fungera
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
new file mode 100644
index 00000000..ab4776d8
--- /dev/null
+++ b/res/values-tr/strings.xml
@@ -0,0 +1,179 @@
+
+
+ Telefon bildiricisi
+ SMS ve çağrılar için bildirimler yolla
+ Batarya raporu
+ Batarya durumunu belirli aralıklarla raporla
+ Dosya sistemi gösterme
+ Bu aıygıtın dosya sistemine uzaktan gözatılmasına izin verir
+ Pano eşitleme
+ Pano içeriğini paylaş
+ Uzak girdi
+ Telefonunuzu veya tabletinizi, dokunmatik veya klavye olarak kullanın
+ Uzak tuşa basmaları getir
+ Tuş basma eylemlerini, uzak aygıtlardan getir
+ Çoklu ortam denetimleri
+ Ortam oynatıcınız için uzak denetim sağlar
+ Komut Çalıştır
+ Uzak komutları, telefon veya tabletinizden tetikler
+ Ping
+ Ping gönder ve al
+ Bildirim eşitleme
+ Bildirimlerinize, diğer aygıtlardan erişin
+ Bildirimleri al
+ Bildirimleri diğer aygıtlardan al ve Android üzerinde göster
+ Paylaş ve al
+ Dosyaları ve URL\'leri aygıtlar arasında paylaş
+ Bu özellik, sahip olduğunuz Android sürümünde kullanılabilir değil
+ Aygıt yok
+ Tamam
+ İptal
+ Ayarları aç
+ Bildirimler erişebilmek için izine ihtiyacınız var
+ Ping gönder
+ Çoklu ortam denetimi
+ Uzak tuşları, sadece düzenleme yaparken işle
+ Etkin bir uzak klavye bağlantısı yok, kdeconnect ile bir bağlantı kurun
+ Uzak klavye bağlantısı etkin
+ Birden çok uzak klavye bağlantısı mevcut, yapılandırmak istediğiniz aygıtı seçin
+ Girdi sil
+ Fare imlecini hareket ettirmek için ekran üzerinde parmaklarınızı hareket ettirin. Tek tıklama için dokunun, sağ ve orta düğmeler için iki/üç parmak kullanın. Sürüklemek için uzun basın.
+ İki parmak dokunma eylemini ayarla
+ Üç parmak dokunma eylemini ayarla
+ Dokunmatik yüzey hassasiyetini ayarla
+ Ters Kaydırma Yönü
+
+ - Sağ tık
+ - Orta tık
+ - Hiçbiri
+
+
+ - En Yavaş
+ - En Yavaşın Üstü
+ - Varsayıla
+ - Varsayılan Üstü
+ - En Hızlı
+
+ Bağlı aygıtlar
+ Kullanılabilir aygıtlar
+ Hatırlanan aygıtlar
+ Eklentiler yüklenemedi (daha fazla bilgi için dokunun):
+ Eklenti ayarları
+ Ayır
+ Eşleşmiş aygıt ulaşılabilir değil
+ Yeni bir aygıt eşleştir
+ Bİlinmeyen aygıt
+ Aygıt ulaşılabilir değil
+ Eşleşme zaten talep edilmiş
+ Aygıt zaten eşleşmiş
+ Paket gönderilemedi
+ Zaman aşımı
+ Kullanıcı tarafından iptal edildi
+ Diğer eş tarafından iptal edildi
+ Geçersiz anahtar alındı
+ Şifreleme Bilgisi
+ Diğer aygıt, KDE Connect\'in son sürümünü kullanmıyor, eski şifreleme yöntemini kullanıyor.
+ Aygıt sertifikanızın SHA1 parmak izi:
+ Uzak aygıt sertifikanızın SHA1 parmak izi:
+ Eşleşme talep edildi
+ %1s için eşleşme talebi
+ %1s üzerinden bağlantı alındı
+ \'%1s\' açmak için dokunun
+ %1s üzerinden gelen dosya
+ %1s
+ Dosya şuraya gönderiliyor, %1s
+ Dosyalar şuraya gönderiliyor, %1s
+ %1s
+ %2$d dosyadan %1$d dosya gönderildi
+ Şuradan dosya alındı, %1s
+ Şuradan dosya alma başarısız, %1s
+ \'%1s\' açmak için dokunun
+ Dosyayı şuraya gönder, %1s
+ %1s
+ Dosyayı şuraya gönderme başarısız, %1s
+ %1s
+ Cevap için dokunun
+ Yeniden Bağlan
+ Sağ Tık Gönder
+ Orta Tık Gönder
+ Klavyeyi Göster
+ Aygıt eşleşmemiş
+ Eşleşme isteği
+ Onayla
+ Reddet
+ Aygıt
+ Aygıt eşleştir
+ Uzak denetim
+ KDE Connect Ayarları
+ Oynat
+ Önceki
+ Geri Sar
+ Hızlı İleri Sar
+ Sonraki
+ Ses
+ Çoklu Ortam Ayarları
+ İleri/geri düğmeleri
+ Basıldığında kullanılacak ileri/geri zamanını ayarlayın.
+
+ - 10 saniye
+ - 20 saniye
+ - 30 saniye
+ - 1 dakika
+ - 2 dakika
+
+ Paylaş...
+ Bu aygıt, eski bir protokol sürümü kullanıyor
+ Bu aygıt, daha yeni bir protokol sürümü kullanıyor
+ Genel Ayarlar
+ Ayarlar
+ %s ayarları
+ Aygıt adı
+ %s
+ Geçersiz aygıt adı
+ Gelen ileti, panoya kaydet
+ Özel aygıt listesi
+ Yeni bir aygıt eşleştir
+ Ayır %s
+ IP\'ye göre aygıtları ekle
+ Sesli bildirimler
+ Bir dosya alırken, ses çıkar ve titret
+ Hedef dizini özelleştir
+ Gelen dosyalar İndirilenler\'de gözükecektir
+ Dosyalar aşağıdaki dizinden depolanacaktır
+ Hedef dizin
+ Bildirim süzgeci
+ Bildirimler, seçili uygulamalar için eşitlenecektir.
+ Harici depolama
+ Tüm dosyalar
+ SD kart %d
+ SD kart
+ (salt okunur)
+ Kamera resimleri
+ Makine/IP ekle
+ Makine adı veya IP
+ Onatıcı bulunamadı
+ Bu seçeneği, sadece aygıtınız otomatik bulunamadıysa kullanın. Aşağıya IP adresini veya makine adının girin ve listeye eklemek için düğmeye dokunun. Listeden bir ögeyi silmek için, mevcut ögeye tıklayın.
+ %2$s üzerindeki %1$s
+ Dosyaları gönder
+ KDE Connect Aygıtları
+ KDE Connect\'te çalışan, aynı ağdaki diğer aygıtlar burada gözükmelidir.
+ Aygıt eşleştirildi
+ Aygıtı yeniden adlandır
+ Yeniden adlandır
+ Tazele
+ Eşleştirilmiş aygıt ulaşılabilir değil. Aynı ağa bağlı olduğundan emin olun.
+ Mobil veri bağlantısında olduğunuz gözüküyor. KDE Connect sadece yerel ağlarda çalışır.
+ Yüklü bir dosya tarayıcısı yok.
+ SMS Gönder
+ Masaüstünden metin iletisi gönder
+ Eklenti, aygıt tarafından desteklenmiyor
+ Telefonumu bul
+ Tabletimi bul
+ Aygıtı bulmak için onu çaldır
+ Bulundu
+ Aç
+ Kapat
+ Depolamaya erişim için izne ihtiyacınız var
+ Bazı Eklentiler çalışmak için izne ihtiyaç duyar (daha fazla bilgi için dokunun):
+ Bu eklenti, çalışmak için izne ihtiyaç duyuyor
+
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 70e55b1e..99648101 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -47,9 +47,6 @@
- Клацання середньою
- Нічого
- права
- середня
- типва
- Найповільніший
- Швидший за найповільніший
@@ -176,4 +173,7 @@
Знайдено
Відкрити
Закрити
+ Для доступу до сховища даних вам слід надати програмі права доступу
+ Для роботи деяких додатків потрібні додаткові права доступу (натисніть, щоб дізнатися більше):
+ Для роботи цього додатка потрібні додаткові права доступу
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 894e7bdc..78bd446e 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -47,9 +47,6 @@
- 中键点击
- 无
- 右
- 中
- 默认
- 最慢
- 高于最慢
@@ -170,6 +167,9 @@
从桌面发送短消息
设备不支持此插件
找到我的手机
+ 找到我的平板电脑
让设备响铃从而找到它
找到
+ 打开
+ 关闭
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index abcc34a6..715ae130 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -41,9 +41,6 @@
- 中鍵點擊
- 無
- 右
- 中
- 預設
- 慢
- 最慢
diff --git a/res/values/strings.xml b/res/values/strings.xml
index f78801be..8934a81d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -53,9 +53,9 @@
- Middle click
- Nothing
- right
- middle
- default
+ right
+ middle
+ default
- right
- middle
@@ -206,4 +206,8 @@
Open
Close
+ You need to grant permissions to access the storage
+ Some Plugins need permissions to work (tap for more info):
+ This plugin needs permissions to work
+
diff --git a/res/xml/mousepadplugin_preferences.xml b/res/xml/mousepadplugin_preferences.xml
index f5b8f5d6..dbafa886 100644
--- a/res/xml/mousepadplugin_preferences.xml
+++ b/res/xml/mousepadplugin_preferences.xml
@@ -5,30 +5,30 @@
+ android:key="@string/mousepad_double_tap_key"
+ android:summary="%s"
+ android:title="@string/mousepad_double_tap_settings_title"/>
+ android:key="@string/mousepad_triple_tap_key"
+ android:summary="%s"
+ android:title="@string/mousepad_triple_tap_settings_title"/>
+ android:key="@string/mousepad_sensitivity_key"
+ android:summary="%s"
+ android:title="@string/mousepad_sensitivity_settings_title"/>
+ *
+ * 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 .
+*/
+
+package org.kde.kdeconnect.Backends.BluetoothBackend;
+
+import android.annotation.TargetApi;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothServerSocket;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+
+import org.json.JSONException;
+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.NetworkPackage;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.security.PublicKey;
+import java.util.UUID;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class BluetoothLink extends BaseLink {
+ private final BluetoothSocket socket;
+ private final BluetoothLinkProvider linkProvider;
+
+ private boolean continueAccepting = true;
+
+ private Thread receivingThread = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ StringBuilder sb = new StringBuilder();
+ try {
+ Reader reader = new InputStreamReader(socket.getInputStream(), "UTF-8");
+ char[] buf = new char[512];
+ while (continueAccepting) {
+ while (sb.indexOf("\n") == -1 && continueAccepting) {
+ int charsRead;
+ if ((charsRead = reader.read(buf)) > 0) {
+ sb.append(buf, 0, charsRead);
+ }
+ }
+
+ int endIndex = sb.indexOf("\n");
+ if (endIndex != -1) {
+ String message = sb.substring(0, endIndex + 1);
+ sb.delete(0, endIndex + 1);
+ processMessage(message);
+ }
+ }
+ } catch (IOException e) {
+ Log.e("BluetoothLink/receiving", "Connection to " + socket.getRemoteDevice().getAddress() + " likely broken.", e);
+ disconnect();
+ }
+ }
+
+ private void processMessage(String message) {
+ NetworkPackage np;
+ try {
+ np = NetworkPackage.unserialize(message);
+ } catch (JSONException e) {
+ Log.e("BluetoothLink/receiving", "Unable to parse message.", e);
+ return;
+ }
+
+ if (np.getType().equals(NetworkPackage.PACKAGE_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 {
+ UUID transferUuid = UUID.fromString(np.getPayloadTransferInfo().getString("uuid"));
+ transferSocket = socket.getRemoteDevice().createRfcommSocketToServiceRecord(transferUuid);
+ transferSocket.connect();
+ np.setPayload(transferSocket.getInputStream(), np.getPayloadSize());
+ } catch (Exception e) {
+ if (transferSocket != null) {
+ try { transferSocket.close(); } catch(IOException ignored) { }
+ }
+ Log.e("BluetoothLink/receiving", "Unable to get payload", e);
+ }
+ }
+
+ packageReceived(np);
+ }
+ });
+
+ public BluetoothLink(Context context, BluetoothSocket socket, String deviceId, BluetoothLinkProvider linkProvider) {
+ super(context, deviceId, linkProvider);
+ this.socket = socket;
+ this.linkProvider = linkProvider;
+ }
+
+ public void startListening() {
+ this.receivingThread.start();
+ }
+
+ @Override
+ public String getName() {
+ return "BluetoothLink";
+ }
+
+ @Override
+ public BasePairingHandler getPairingHandler(Device device, BasePairingHandler.PairingHandlerCallback callback) {
+ return new BluetoothPairingHandler(device, callback);
+ }
+
+ public void disconnect() {
+ if (socket == null) {
+ return;
+ }
+ continueAccepting = false;
+ try {
+ socket.close();
+ } catch (IOException e) {
+ }
+ linkProvider.disconnectedLink(this, getDeviceId(), socket);
+ }
+
+ private void sendMessage(NetworkPackage np) throws JSONException, IOException {
+ byte[] message = np.serialize().getBytes(Charset.forName("UTF-8"));
+ OutputStream socket = this.socket.getOutputStream();
+ Log.i("BluetoothLink","Beginning to send message");
+ socket.write(message);
+ Log.i("BluetoothLink","Finished sending message");
+ }
+
+ @Override
+ public boolean sendPackage(NetworkPackage np, Device.SendPackageStatusCallback callback) {
+ return sendPackageInternal(np, callback, null);
+ }
+
+ @Override
+ public boolean sendPackageEncrypted(NetworkPackage np, Device.SendPackageStatusCallback callback, PublicKey key) {
+ return sendPackageInternal(np, callback, key);
+ }
+
+ private boolean sendPackageInternal(NetworkPackage np, final Device.SendPackageStatusCallback callback, PublicKey key) {
+
+ /*if (!isConnected()) {
+ Log.e("BluetoothLink", "sendPackageEncrypted failed: not connected");
+ callback.sendFailure(new Exception("Not connected"));
+ return;
+ }*/
+
+ try {
+ BluetoothServerSocket serverSocket = null;
+ if (np.hasPayload()) {
+ UUID transferUuid = UUID.randomUUID();
+ serverSocket = BluetoothAdapter.getDefaultAdapter()
+ .listenUsingRfcommWithServiceRecord("KDE Connect Transfer", transferUuid);
+ JSONObject payloadTransferInfo = new JSONObject();
+ payloadTransferInfo.put("uuid", transferUuid.toString());
+ 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) {
+ BluetoothSocket transferSocket = serverSocket.accept();
+ try {
+ serverSocket.close();
+
+ int idealBufferLength = 4096;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && transferSocket.getMaxReceivePacketSize() > 0) {
+ idealBufferLength = transferSocket.getMaxReceivePacketSize();
+ }
+ byte[] buffer = new byte[idealBufferLength];
+ int bytesRead;
+ long progress = 0;
+ InputStream stream = np.getPayload();
+ while ((bytesRead = stream.read(buffer)) != -1) {
+ progress += bytesRead;
+ transferSocket.getOutputStream().write(buffer, 0, bytesRead);
+ if (np.getPayloadSize() > 0) {
+ callback.onProgressChanged((int) (100 * progress / np.getPayloadSize()));
+ }
+ }
+ transferSocket.getOutputStream().flush();
+ stream.close();
+ } catch (Exception e) {
+ callback.onFailure(e);
+ return false;
+ } finally {
+ try { transferSocket.close(); } catch (IOException ignored) { }
+ }
+ }
+
+ callback.onSuccess();
+ return true;
+ } catch (Exception e) {
+ callback.onFailure(e);
+ return false;
+ }
+ }
+
+ @Override
+ public boolean linkShouldBeKeptAlive() {
+ return receivingThread.isAlive();
+ }
+
+ /*
+ public boolean isConnected() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
+ return socket.isConnected();
+ } else {
+ return true;
+ }
+ }
+*/
+}
diff --git a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java
new file mode 100644
index 00000000..629b1c6e
--- /dev/null
+++ b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2016 Saikrishna Arcot
+ *
+ * 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 .
+*/
+
+package org.kde.kdeconnect.Backends.BluetoothBackend;
+
+import android.annotation.TargetApi;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothSocket;
+import android.bluetooth.BluetoothServerSocket;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.os.Parcelable;
+import android.util.Log;
+
+import org.kde.kdeconnect.Backends.BaseLinkProvider;
+import org.kde.kdeconnect.Device;
+import org.kde.kdeconnect.NetworkPackage;
+
+import java.io.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.Set;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class BluetoothLinkProvider extends BaseLinkProvider {
+
+ private static final UUID SERVICE_UUID = UUID.fromString("185f3df4-3268-4e3f-9fca-d4d5059915bd");
+ private static final int REQUEST_ENABLE_BT = 48;
+
+ private final Context context;
+ private final Map visibleComputers = new HashMap<>();
+ private final Map sockets = new HashMap<>();
+
+ private BluetoothAdapter bluetoothAdapter = null;
+
+ private ServerRunnable serverRunnable;
+ private ClientRunnable clientRunnable;
+
+ private void addLink(NetworkPackage identityPackage, BluetoothLink link) {
+ String deviceId = identityPackage.getString("deviceId");
+ Log.i("BluetoothLinkProvider","addLink to "+deviceId);
+ BluetoothLink oldLink = visibleComputers.get(deviceId);
+ if (oldLink == link) {
+ Log.e("BluetoothLinkProvider", "oldLink == link. This should not happen!");
+ return;
+ }
+ visibleComputers.put(deviceId, link);
+ connectionAccepted(identityPackage, link);
+ link.startListening();
+ if (oldLink != null) {
+ Log.i("BluetoothLinkProvider","Removing old connection to same device");
+ oldLink.disconnect();
+ }
+ }
+
+ public BluetoothLinkProvider(Context context) {
+ this.context = context;
+
+ bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (bluetoothAdapter == null) {
+ Log.e("BluetoothLinkProvider","No bluetooth adapter found.");
+ }
+ }
+
+ @Override
+ public void onStart() {
+ if (bluetoothAdapter == null) {
+ return;
+ }
+
+ if (!bluetoothAdapter.isEnabled()) {
+ Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ Log.e("BluetoothLinkProvider","Bluetooth adapter not enabled.");
+ // TODO: next line needs to be called from an existing activity, so move it?
+ // startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
+ // TODO: Check result of the previous command, whether the user allowed bluetooth or not.
+ return;
+ }
+
+ //This handles the case when I'm the existing device in the network and receive a hello package
+ clientRunnable = new ClientRunnable();
+ new Thread(clientRunnable).start();
+
+ // I'm on a new network, let's be polite and introduce myself
+ serverRunnable = new ServerRunnable();
+ new Thread(serverRunnable).start();
+ }
+
+ @Override
+ public void onNetworkChange() {
+ onStop();
+ onStart();
+ }
+
+ @Override
+ public void onStop() {
+ if (bluetoothAdapter == null || clientRunnable == null || serverRunnable == null) {
+ return;
+ }
+
+ clientRunnable.stopProcessing();
+ serverRunnable.stopProcessing();
+ }
+
+ @Override
+ public String getName() {
+ return "BluetoothLinkProvider";
+ }
+
+ public void disconnectedLink(BluetoothLink link, String deviceId, BluetoothSocket socket) {
+ sockets.remove(socket.getRemoteDevice());
+ visibleComputers.remove(deviceId);
+ connectionLost(link);
+ }
+
+ private class ServerRunnable implements Runnable {
+
+ private boolean continueProcessing = true;
+ private BluetoothServerSocket serverSocket;
+
+ void stopProcessing() {
+ continueProcessing = false;
+ if (serverSocket != null) {
+ try {
+ serverSocket.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @Override
+ public void run() {
+ try {
+ serverSocket = bluetoothAdapter
+ .listenUsingRfcommWithServiceRecord("KDE Connect", SERVICE_UUID);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ if (continueProcessing) {
+ try {
+ BluetoothSocket socket = serverSocket.accept();
+ connect(socket);
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ private void connect(BluetoothSocket socket) throws Exception {
+ //socket.connect();
+ OutputStream outputStream = socket.getOutputStream();
+ if (sockets.containsKey(socket.getRemoteDevice())) {
+ Log.i("BTLinkProvider/Server", "Received duplicate connection from " + socket.getRemoteDevice().getAddress());
+ socket.close();
+ return;
+ } else {
+ sockets.put(socket.getRemoteDevice(), socket);
+ }
+
+ Log.i("BTLinkProvider/Server", "Received connection from " + socket.getRemoteDevice().getAddress());
+
+ NetworkPackage np = NetworkPackage.createIdentityPackage(context);
+ byte[] message = np.serialize().getBytes("UTF-8");
+ outputStream.write(message);
+
+ Log.i("BTLinkProvider/Server", "Sent identity package");
+
+ // Listen for the response
+ StringBuilder sb = new StringBuilder();
+ Reader reader = new InputStreamReader(socket.getInputStream(), "UTF-8");
+ int charsRead;
+ char[] buf = new char[512];
+ while(sb.lastIndexOf("\n") == -1 && (charsRead = reader.read(buf)) != -1) {
+ sb.append(buf, 0, charsRead);
+ }
+
+ String response = sb.toString();
+ final NetworkPackage identityPackage = NetworkPackage.unserialize(response);
+
+ if (!identityPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
+ Log.e("BTLinkProvider/Server", "2 Expecting an identity package");
+ return;
+ }
+
+ Log.i("BTLinkProvider/Server", "Received identity package");
+
+ BluetoothLink link = new BluetoothLink(context, socket,
+ identityPackage.getString("deviceId"), BluetoothLinkProvider.this);
+
+ addLink(identityPackage, link);
+ }
+ }
+
+ private class ClientRunnable extends BroadcastReceiver implements Runnable {
+
+ private boolean continueProcessing = true;
+ private Map connectionThreads = new HashMap<>();
+
+ void stopProcessing() {
+ continueProcessing = false;
+ }
+
+ @Override
+ public void run() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+ IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_UUID);
+ context.registerReceiver(this, filter);
+ }
+
+ while (continueProcessing) {
+ connectToDevices();
+ try {
+ Thread.sleep(15000);
+ } catch (InterruptedException ignored) {
+ }
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+ context.unregisterReceiver(this);
+ }
+ }
+
+ private void connectToDevices() {
+ Set pairedDevices = bluetoothAdapter.getBondedDevices();
+ Log.i("BluetoothLinkProvider", "Bluetooth adapter paired devices: " + pairedDevices.size());
+
+ // Loop through paired devices
+ for (BluetoothDevice device : pairedDevices) {
+ if (sockets.containsKey(device)) {
+ continue;
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
+ device.fetchUuidsWithSdp();
+ } else {
+ connectToDevice(device);
+ }
+ }
+ }
+
+ @Override
+ @TargetApi(value=Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1)
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(BluetoothDevice.ACTION_UUID)) {
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ Parcelable[] activeUuids = intent.getParcelableArrayExtra(BluetoothDevice.EXTRA_UUID);
+
+ if (sockets.containsKey(device)) {
+ return;
+ }
+
+ if (activeUuids == null) {
+ return;
+ }
+
+ for (Parcelable uuid: activeUuids) {
+ if (uuid.toString().equals(SERVICE_UUID.toString())) {
+ connectToDevice(device);
+ return;
+ }
+ }
+ }
+ }
+
+ private void connectToDevice(BluetoothDevice device) {
+ if (!connectionThreads.containsKey(device) || !connectionThreads.get(device).isAlive()) {
+ Thread connectionThread = new Thread(new ClientConnect(device));
+ connectionThread.start();
+ connectionThreads.put(device, connectionThread);
+ }
+ }
+
+
+ }
+
+ private class ClientConnect implements Runnable {
+
+ private final BluetoothDevice device;
+
+ public ClientConnect(BluetoothDevice device) {
+ this.device = device;
+ }
+
+ @Override
+ public void run() {
+ connectToDevice();
+ }
+
+ private void connectToDevice() {
+ BluetoothSocket socket;
+ try {
+ socket = device.createRfcommSocketToServiceRecord(SERVICE_UUID);
+ socket.connect();
+ sockets.put(device, socket);
+ } catch (IOException e) {
+ Log.e("BTLinkProvider/Client", "Could not connect to KDE Connect service on " + device.getAddress(), e);
+ return;
+ }
+
+ Log.i("BTLinkProvider/Client", "Connected to " + device.getAddress());
+
+ try {
+ int character;
+ StringBuilder sb = new StringBuilder();
+ while(sb.lastIndexOf("\n") == -1 && (character = socket.getInputStream().read()) != -1) {
+ sb.append((char)character);
+ }
+
+ String message = sb.toString();
+ final NetworkPackage identityPackage = NetworkPackage.unserialize(message);
+
+ if (!identityPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
+ Log.e("BTLinkProvider/Client", "1 Expecting an identity package");
+ socket.close();
+ return;
+ }
+
+ Log.i("BTLinkProvider/Client", "Received identity package");
+
+ String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId");
+ if (identityPackage.getString("deviceId").equals(myId)) {
+ // Probably won't happen, but just to be safe
+ socket.close();
+ return;
+ }
+
+ if (visibleComputers.containsKey(identityPackage.getString("deviceId"))) {
+ return;
+ }
+
+ Log.i("BTLinkProvider/Client", "Identity package received, creating link");
+
+ final BluetoothLink link = new BluetoothLink(context, socket,
+ identityPackage.getString("deviceId"), BluetoothLinkProvider.this);
+
+ NetworkPackage np2 = NetworkPackage.createIdentityPackage(context);
+ link.sendPackage(np2,new Device.SendPackageStatusCallback() {
+ @Override
+ public void onSuccess() {
+ addLink(identityPackage, link);
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+
+ }
+ });
+ } catch (Exception e) {
+ Log.e("BTLinkProvider/Client", "Connection lost/disconnected on " + device.getAddress(), e);
+ }
+ }
+ }
+}
diff --git a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothPairingHandler.java b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothPairingHandler.java
new file mode 100644
index 00000000..1d380e05
--- /dev/null
+++ b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothPairingHandler.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2015 Vineet Garg
+ *
+ * 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 .
+*/
+
+package org.kde.kdeconnect.Backends.BluetoothBackend;
+
+import android.util.Log;
+import org.kde.kdeconnect.Backends.BasePairingHandler;
+import org.kde.kdeconnect.Device;
+import org.kde.kdeconnect.NetworkPackage;
+import org.kde.kdeconnect_tp.R;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+public class BluetoothPairingHandler extends BasePairingHandler {
+
+ Timer mPairingTimer;
+ public BluetoothPairingHandler(Device device, final PairingHandlerCallback callback) {
+ super(device, callback);
+
+ if (device.isPaired()) {
+ mPairStatus = PairStatus.Paired;
+ } else {
+ mPairStatus = PairStatus.NotPaired;
+ }
+ }
+
+// @Override
+ public NetworkPackage createPairPackage() {
+ NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
+ np.set("pair", true);
+ return np;
+ }
+
+ @Override
+ public void packageReceived(NetworkPackage np) throws Exception{
+
+ boolean wantsPair = np.getBoolean("pair");
+
+ if (wantsPair == isPaired()) {
+ if (mPairStatus == PairStatus.Requested) {
+ //Log.e("Device","Unpairing (pair rejected)");
+ mPairStatus = PairStatus.NotPaired;
+ hidePairingNotification();
+ mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer));
+ }
+ return;
+ }
+
+ if (wantsPair) {
+
+ if (mPairStatus == PairStatus.Requested) { //We started pairing
+ hidePairingNotification();
+ pairingDone();
+ } else {
+
+ // If device is already paired, accept pairing silently
+ if (mDevice.isPaired()) {
+ acceptPairing();
+ return;
+ }
+
+ // Pairing notifications are still managed by device as there is no other way to
+ // know about notificationId to cancel notification when PairActivity is started
+ // Even putting notificationId in intent does not work because PairActivity can be
+ // started from MainActivity too, so then notificationId cannot be set
+ hidePairingNotification();
+ mDevice.displayPairingNotification();
+
+ mPairingTimer = new Timer();
+
+ mPairingTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ Log.w("KDE/Device","Unpairing (timeout B)");
+ mPairStatus = PairStatus.NotPaired;
+ hidePairingNotification();
+ }
+ }, 25*1000); //Time to show notification, waiting for user to accept (peer will timeout in 30 seconds)
+ mPairStatus = PairStatus.RequestedByPeer;
+ mCallback.incomingRequest();
+
+ }
+ } else {
+ Log.i("KDE/Pairing", "Unpair request");
+
+ if (mPairStatus == PairStatus.Requested) {
+ hidePairingNotification();
+ mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_canceled_by_other_peer));
+ } else if (mPairStatus == PairStatus.Paired) {
+ mCallback.unpaired();
+ }
+
+ mPairStatus = PairStatus.NotPaired;
+
+ }
+
+ }
+
+ @Override
+ public void requestPairing() {
+
+ Device.SendPackageStatusCallback statusCallback = new Device.SendPackageStatusCallback() {
+ @Override
+ public void onSuccess() {
+ hidePairingNotification(); //Will stop the pairingTimer if it was running
+ mPairingTimer = new Timer();
+ mPairingTimer.schedule(new TimerTask() {
+ @Override
+ public void run() {
+ mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_timed_out));
+ Log.w("KDE/Device","Unpairing (timeout A)");
+ mPairStatus = PairStatus.NotPaired;
+ }
+ }, 30*1000); //Time to wait for the other to accept
+ mPairStatus = PairStatus.Requested;
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_could_not_send_package));
+ }
+ };
+ mDevice.sendPackage(createPairPackage(), statusCallback);
+ }
+
+ public void hidePairingNotification() {
+ mDevice.hidePairingNotification();
+ if (mPairingTimer != null) {
+ mPairingTimer .cancel();
+ }
+ }
+
+ @Override
+ public void acceptPairing() {
+ hidePairingNotification();
+ Device.SendPackageStatusCallback statusCallback = new Device.SendPackageStatusCallback() {
+ @Override
+ public void onSuccess() {
+ pairingDone();
+ }
+
+ @Override
+ public void onFailure(Throwable e) {
+ mCallback.pairingFailed(mDevice.getContext().getString(R.string.error_not_reachable));
+ }
+ };
+ mDevice.sendPackage(createPairPackage(), statusCallback);
+ }
+
+ @Override
+ public void rejectPairing() {
+ hidePairingNotification();
+ mPairStatus = PairStatus.NotPaired;
+ NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
+ np.set("pair", false);
+ mDevice.sendPackage(np);
+ }
+
+ //@Override
+ public void pairingDone() {
+ // Store device information needed to create a Device object in a future
+ //Log.e("KDE/PairingDone", "Pairing Done");
+ mPairStatus = PairStatus.Paired;
+ mCallback.pairingDone();
+
+ }
+
+ @Override
+ public void unpair() {
+ mPairStatus = PairStatus.NotPaired;
+ NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_PAIR);
+ np.set("pair", false);
+ mDevice.sendPackage(np);
+ }
+}
diff --git a/src/org/kde/kdeconnect/BackgroundService.java b/src/org/kde/kdeconnect/BackgroundService.java
index ca6c2cca..16c5efc1 100644
--- a/src/org/kde/kdeconnect/BackgroundService.java
+++ b/src/org/kde/kdeconnect/BackgroundService.java
@@ -26,11 +26,13 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Binder;
+import android.os.Build;
import android.os.IBinder;
import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
+//import org.kde.kdeconnect.Backends.BluetoothBackend.BluetoothLinkProvider;
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
@@ -140,11 +142,11 @@ public class BackgroundService extends Service {
}
private void registerLinkProviders() {
-
- //linkProviders.add(new LoopbackLinkProvider(this));
-
linkProviders.add(new LanLinkProvider(this));
-
+// linkProviders.add(new LoopbackLinkProvider(this));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+// linkProviders.add(new BluetoothLinkProvider(this));
+ }
}
public ArrayList getLinkProviders() {
diff --git a/src/org/kde/kdeconnect/Device.java b/src/org/kde/kdeconnect/Device.java
index 3da45204..cb234c8e 100644
--- a/src/org/kde/kdeconnect/Device.java
+++ b/src/org/kde/kdeconnect/Device.java
@@ -82,7 +82,8 @@ public class Device implements BaseLink.PackageReceiver {
private List m_supportedPlugins = new ArrayList<>();
private final ConcurrentHashMap plugins = new ConcurrentHashMap<>();
private final ConcurrentHashMap failedPlugins = new ConcurrentHashMap<>();
- private Map> pluginsByIncomingInterface;
+ private final ConcurrentHashMap pluginsWithoutPermissions = new ConcurrentHashMap<>();
+ private Map> pluginsByIncomingInterface = new HashMap<>();
private final SharedPreferences settings;
@@ -722,6 +723,16 @@ public class Device implements BaseLink.PackageReceiver {
failedPlugins.put(pluginKey, plugin);
}
+ if(!plugin.checkRequiredPermissions()){
+ Log.e("KDE/addPlugin", "No permission " + pluginKey);
+ plugins.remove(pluginKey);
+ pluginsWithoutPermissions.put(pluginKey, plugin);
+ success = false;
+ } else {
+ Log.i("KDE/addPlugin", "Permission OK " + pluginKey);
+ pluginsWithoutPermissions.remove(pluginKey);
+ }
+
return success;
}
@@ -812,6 +823,10 @@ public class Device implements BaseLink.PackageReceiver {
return failedPlugins;
}
+ public ConcurrentHashMap getPluginsWithoutPermissions() {
+ return pluginsWithoutPermissions;
+ }
+
public void addPluginsChangedListener(PluginsChangedListener listener) {
pluginsChangedListeners.add(listener);
}
diff --git a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java
index cfb17715..fa0c2b26 100644
--- a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java
+++ b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/MousePadActivity.java
@@ -97,11 +97,11 @@ public class MousePadActivity extends ActionBarActivity implements GestureDetect
scrollDirection = 1;
}
String doubleTapSetting = prefs.getString(getString(R.string.mousepad_double_tap_key),
- getString(R.string.mousepad_double_default));
+ getString(R.string.mousepad_default_double));
String tripleTapSetting = prefs.getString(getString(R.string.mousepad_triple_tap_key),
- getString(R.string.mousepad_triple_default));
+ getString(R.string.mousepad_default_triple));
String sensitivitySetting = prefs.getString(getString(R.string.mousepad_sensitivity_key),
- getString(R.string.mousepad_sensitivity_default));
+ getString(R.string.mousepad_default_sensitivity));
doubleTapAction = ClickType.fromString(doubleTapSetting);
tripleTapAction = ClickType.fromString(tripleTapSetting);
diff --git a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java
index 740cb2c5..3fb320f5 100644
--- a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java
@@ -24,14 +24,18 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
import android.content.DialogInterface;
import android.content.Intent;
+import android.graphics.Bitmap;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Log;
+
import org.kde.kdeconnect.Helpers.AppsHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
@@ -39,15 +43,27 @@ import org.kde.kdeconnect.UserInterface.MaterialActivity;
import org.kde.kdeconnect.UserInterface.SettingsActivity;
import org.kde.kdeconnect_tp.R;
+import java.io.ByteArrayOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class NotificationsPlugin extends Plugin implements NotificationReceiver.NotificationListener {
public final static String PACKAGE_TYPE_NOTIFICATION = "kdeconnect.notification";
public final static String PACKAGE_TYPE_NOTIFICATION_REQUEST = "kdeconnect.notification.request";
+ public final static String PACKAGE_TYPE_NOTIFICATION_REPLY = "kdeconnect.notification.reply";
+
+
+ private boolean sendIcons = true;
+
+ private Map pendingIntents;
-/*
- private boolean sendIcons = false;
-*/
@Override
public String getDisplayName() {
@@ -81,6 +97,8 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
@Override
public boolean onCreate() {
+ pendingIntents = new HashMap();
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
if (hasPermission()) {
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
@@ -159,6 +177,8 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
String packageName = statusBarNotification.getPackageName();
String appName = AppsHelper.appNameLookup(context, packageName);
+
+
if ("com.facebook.orca".equals(packageName) &&
(statusBarNotification.getId() == 10012) &&
"Messenger".equals(appName) &&
@@ -182,28 +202,41 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
np.set("silent", true);
np.set("requestAnswer", true); //For compatibility with old desktop versions of KDE Connect that don't support "silent"
}
-/*
+
if (sendIcons) {
try {
- Drawable drawableAppIcon = AppsHelper.appIconLookup(context, packageName);
- Bitmap appIcon = ImagesHelper.drawableToBitmap(drawableAppIcon);
- ByteArrayOutputStream outStream = new ByteArrayOutputStream();
- if (appIcon.getWidth() > 128) {
- appIcon = Bitmap.createScaledBitmap(appIcon, 96, 96, true);
+ Bitmap appIcon = notification.largeIcon;
+
+ if (appIcon != null) {
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ if (appIcon.getWidth() > 128) {
+ appIcon = Bitmap.createScaledBitmap(appIcon, 96, 96, true);
+ }
+ appIcon.compress(Bitmap.CompressFormat.PNG, 90, outStream);
+ byte[] bitmapData = outStream.toByteArray();
+
+ np.setPayload(bitmapData);
+
+ np.set("payloadHash", getChecksum(bitmapData));
}
- appIcon.compress(Bitmap.CompressFormat.PNG, 90, outStream);
- byte[] bitmapData = outStream.toByteArray();
- np.setPayload(bitmapData);
- } catch (Exception e) {
+ } catch(Exception e){
e.printStackTrace();
Log.e("NotificationsPlugin", "Error retrieving icon");
}
}
-*/
+
+ RepliableNotification rn = extractRepliableNotification(statusBarNotification);
+ if(rn.pendingIntent != null) {
+ np.set("requestReplyId", rn.id);
+ pendingIntents.put(rn.id, rn);
+ }
+
np.set("id", key);
np.set("appName", appName == null? packageName : appName);
np.set("isClearable", statusBarNotification.isClearable());
np.set("ticker", getTickerText(notification));
+ np.set("title", getNotificationTitle(notification));
+ np.set("text", getNotificationText(notification));
np.set("time", Long.toString(statusBarNotification.getPostTime()));
if (requestAnswer) {
np.set("requestAnswer", true);
@@ -213,6 +246,127 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
device.sendPackage(np);
}
+ void replyToNotification(String id, String message){
+ if(pendingIntents.isEmpty() || !pendingIntents.containsKey(id)){
+ Log.e("NotificationsPlugin", "No such notification");
+ return;
+ }
+
+ RepliableNotification repliableNotification = pendingIntents.get(id);
+ if(repliableNotification == null) {
+ Log.e("NotificationsPlugin", "No such notification");
+ return;
+ }
+ RemoteInput[] remoteInputs = new RemoteInput[repliableNotification.remoteInputs.size()];
+
+ Intent localIntent = new Intent();
+ localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Bundle localBundle = new Bundle();
+ int i = 0;
+ for(RemoteInput remoteIn : repliableNotification.remoteInputs){
+ getDetailsOfNotification(remoteIn);
+ remoteInputs[i] = remoteIn;
+ localBundle.putCharSequence(remoteInputs[i].getResultKey(), message);
+ i++;
+ }
+ RemoteInput.addResultsToIntent(remoteInputs, localIntent, localBundle);
+
+ try {
+ repliableNotification.pendingIntent.send(context, 0, localIntent);
+ } catch (PendingIntent.CanceledException e) {
+ Log.e("NotificationPlugin", "replyToNotification error: " + e.getMessage());
+ }
+ pendingIntents.remove(id);
+ }
+
+ private void getDetailsOfNotification(RemoteInput remoteInput) {
+ //Some more details of RemoteInput... no idea what for but maybe it will be useful at some point
+ String resultKey = remoteInput.getResultKey();
+ String label = remoteInput.getLabel().toString();
+ Boolean canFreeForm = remoteInput.getAllowFreeFormInput();
+ if(remoteInput.getChoices() != null && remoteInput.getChoices().length > 0) {
+ String[] possibleChoices = new String[remoteInput.getChoices().length];
+ for(int i = 0; i < remoteInput.getChoices().length; i++){
+ possibleChoices[i] = remoteInput.getChoices()[i].toString();
+ }
+ }
+ }
+
+ private String getNotificationTitle(Notification notification) {
+ final String TITLE_KEY = "android.title";
+ final String TEXT_KEY = "android.text";
+ String title = "";
+
+ if(notification != null) {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ try {
+ Bundle extras = notification.extras;
+ title = extras.getCharSequence(TITLE_KEY).toString();
+ } catch(Exception e) {
+ Log.w("NotificationPlugin","problem parsing notification extras for " + notification.tickerText);
+ e.printStackTrace();
+ }
+ }
+ }
+
+ //TODO Add compat for under Kitkat devices
+
+ return title;
+ }
+
+ private RepliableNotification extractRepliableNotification(StatusBarNotification statusBarNotification) {
+ RepliableNotification repliableNotification = new RepliableNotification();
+
+ if(statusBarNotification != null) {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ try {
+ Boolean reply = false;
+
+ //works for WhatsApp, but not for Telegram
+ for(Notification.Action act : statusBarNotification.getNotification().actions) {
+ if(act != null && act.getRemoteInputs() != null) {
+ repliableNotification.remoteInputs.addAll(Arrays.asList(act.getRemoteInputs()));
+ repliableNotification.pendingIntent = act.actionIntent;
+ reply = true;
+ break;
+ }
+ }
+
+ repliableNotification.packageName = statusBarNotification.getPackageName();
+
+ 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();
+ }
+ }
+ }
+
+ return repliableNotification;
+ }
+
+ private String getNotificationText(Notification notification) {
+ final String TEXT_KEY = "android.text";
+ String text = "";
+
+ if(notification != null) {
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ try {
+ Bundle extras = notification.extras;
+ 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();
+ }
+ }
+ }
+
+ //TODO Add compat for under Kitkat devices
+
+ return text;
+ }
+
/**
* Returns the ticker text of the notification.
@@ -234,7 +388,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
if (extraTextExtra != null) extraText = extraTextExtra.toString();
if (extraTitle != null && extraText != null && !extraText.isEmpty()) {
- ticker = extraTitle + " ‐ " + extraText;
+ ticker = extraTitle + ": " + extraText;
} else if (extraTitle != null) {
ticker = extraTitle;
} else if (extraText != null) {
@@ -309,6 +463,10 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
}
});
+ } else if (np.has("requestReplyId") && np.has("message")) {
+
+ replyToNotification(np.getString("requestReplyId"), np.getString("message"));
+
}
return true;
@@ -352,7 +510,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
@Override
public String[] getSupportedPackageTypes() {
- return new String[]{PACKAGE_TYPE_NOTIFICATION_REQUEST};
+ return new String[]{PACKAGE_TYPE_NOTIFICATION_REQUEST,PACKAGE_TYPE_NOTIFICATION_REPLY};
}
@Override
@@ -402,4 +560,29 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
}
return result;
}
+
+ public String getChecksum(byte[] data){
+
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ md.update(data);
+ return bytesToHex(md.digest());
+ } catch (NoSuchAlgorithmException e) {
+ Log.e("KDEConenct", "Error while generating checksum", e);
+ }
+ return null;
+ }
+
+
+ public static String bytesToHex(byte[] bytes) {
+ char[] hexArray = "0123456789ABCDEF".toCharArray();
+ char[] hexChars = new char[bytes.length * 2];
+ for ( int j = 0; j < bytes.length; j++ ) {
+ int v = bytes[j] & 0xFF;
+ hexChars[j * 2] = hexArray[v >>> 4];
+ hexChars[j * 2 + 1] = hexArray[v & 0x0F];
+ }
+ return new String(hexChars).toLowerCase();
+ }
+
}
diff --git a/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/RepliableNotification.java b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/RepliableNotification.java
new file mode 100644
index 00000000..f0dd1e56
--- /dev/null
+++ b/src/org/kde/kdeconnect/Plugins/NotificationsPlugin/RepliableNotification.java
@@ -0,0 +1,16 @@
+package org.kde.kdeconnect.Plugins.NotificationsPlugin;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.UUID;
+
+public class RepliableNotification {
+ public String id = UUID.randomUUID().toString();
+ public PendingIntent pendingIntent;
+ public ArrayList remoteInputs = new ArrayList<>();
+ public String packageName;
+ public String tag;
+}
diff --git a/src/org/kde/kdeconnect/Plugins/Plugin.java b/src/org/kde/kdeconnect/Plugins/Plugin.java
index 67650e7f..00b0c68b 100644
--- a/src/org/kde/kdeconnect/Plugins/Plugin.java
+++ b/src/org/kde/kdeconnect/Plugins/Plugin.java
@@ -20,23 +20,33 @@
package org.kde.kdeconnect.Plugins;
+import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.support.annotation.StringRes;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
import android.view.View;
import android.widget.Button;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPackage;
+import org.kde.kdeconnect.UserInterface.MaterialActivity;
import org.kde.kdeconnect.UserInterface.PluginSettingsActivity;
import org.kde.kdeconnect.UserInterface.SettingsActivity;
+import org.kde.kdeconnect_tp.R;
public abstract class Plugin {
protected Device device;
protected Context context;
+ protected int permissionExplanation = R.string.permission_explanation;
public final void setContext(Context context, Device device) {
this.device = device;
@@ -167,14 +177,6 @@ public abstract class Plugin {
*/
public boolean onPackageReceived(NetworkPackage np) { return false; }
- /**
- * If onCreate returns false, should create a dialog explaining
- * the problem (and how to fix it, if possible) to the user.
- */
- public AlertDialog getErrorDialog(Activity deviceActivity) {
- return null;
- }
-
/**
* Should return the list of NetworkPackage types that this plugin can handle
*/
@@ -205,4 +207,70 @@ public abstract class Plugin {
return b;
}
+ public String[] getRequiredPermissions() {
+ return new String[0];
+ }
+
+ public String[] getOptionalPermissions() {
+ return new String[0];
+ }
+
+ //Permission from Manifest.permission.*
+ protected boolean isPermissionGranted(String permission) {
+ int result = ContextCompat.checkSelfPermission(context, permission);
+ return (result == PackageManager.PERMISSION_GRANTED);
+ }
+
+ protected boolean arePermissionsGranted(String[] permissions) {
+ for(String permission: permissions){
+ if(!isPermissionGranted(permission)){
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected AlertDialog requestPermissionDialog(Activity activity, String permissions, @StringRes int reason) {
+ return requestPermissionDialog(activity, new String[]{permissions}, reason);
+ }
+
+ protected AlertDialog requestPermissionDialog(final Activity activity, final String[] permissions, @StringRes int reason){
+ return new AlertDialog.Builder(activity)
+ .setTitle(getDisplayName())
+ .setMessage(reason)
+ .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ ActivityCompat.requestPermissions(activity, permissions, 0);
+ }
+ })
+ .setNegativeButton(R.string.cancel,new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ //Do nothing
+ }
+ })
+ .create();
+ }
+
+ /**
+ * If onCreate returns false, should create a dialog explaining
+ * the problem (and how to fix it, if possible) to the user.
+ */
+
+ public AlertDialog getErrorDialog(Activity deviceActivity) {
+ return null;
+ }
+
+ public AlertDialog getPermissionExplanationDialog(Activity deviceActivity) {
+ return requestPermissionDialog(deviceActivity,getRequiredPermissions(), permissionExplanation);
+ }
+
+ public boolean checkRequiredPermissions(){
+ if (!arePermissionsGranted(getRequiredPermissions())) {
+ return false;
+ }
+ return true;
+ }
+
}
diff --git a/src/org/kde/kdeconnect/Plugins/PluginFactory.java b/src/org/kde/kdeconnect/Plugins/PluginFactory.java
index 826a5857..b514634a 100644
--- a/src/org/kde/kdeconnect/Plugins/PluginFactory.java
+++ b/src/org/kde/kdeconnect/Plugins/PluginFactory.java
@@ -125,8 +125,7 @@ public class PluginFactory {
PluginFactory.registerPlugin(TelepathyPlugin.class);
PluginFactory.registerPlugin(FindMyPhonePlugin.class);
PluginFactory.registerPlugin(RunCommandPlugin.class);
- //Commented here and in AndroidManifest until we release a desktop version with this feature, so we don't get bad "feature not working" reviews
- //PluginFactory.registerPlugin(RemoteKeyboardPlugin.class);
+ PluginFactory.registerPlugin(RemoteKeyboardPlugin.class);
}
public static PluginInfo getPluginInfo(Context context, String pluginKey) {
diff --git a/src/org/kde/kdeconnect/Plugins/SftpPlugin/SftpPlugin.java b/src/org/kde/kdeconnect/Plugins/SftpPlugin/SftpPlugin.java
index 59d33f63..65bad277 100644
--- a/src/org/kde/kdeconnect/Plugins/SftpPlugin/SftpPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/SftpPlugin/SftpPlugin.java
@@ -20,8 +20,16 @@
package org.kde.kdeconnect.Plugins.SftpPlugin;
+import android.Manifest;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.pm.PackageManager;
import android.os.Environment;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import org.json.JSONException;
import org.kde.kdeconnect.Helpers.StorageHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
@@ -38,6 +46,9 @@ public class SftpPlugin extends Plugin {
private static final SimpleSftpServer server = new SimpleSftpServer();
+
+
+
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_sftp);
@@ -94,6 +105,7 @@ public class SftpPlugin extends Plugin {
} else {
res.append(context.getString(R.string.sftp_all_files));
}
+
String pathName = res.toString();
if (storage.readonly) {
res.append(" ");
@@ -127,6 +139,12 @@ public class SftpPlugin extends Plugin {
return false;
}
+ @Override
+ public String[] getRequiredPermissions() {
+ String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE};
+ return perms;
+ }
+
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_SFTP_REQUEST};
diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/NotificationUpdateCallback.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/NotificationUpdateCallback.java
index 78219de1..2b0a987e 100644
--- a/src/org/kde/kdeconnect/Plugins/SharePlugin/NotificationUpdateCallback.java
+++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/NotificationUpdateCallback.java
@@ -74,6 +74,9 @@ class NotificationUpdateCallback extends Device.SendPackageStatusCallback {
public void onFailure(Throwable e) {
updateDone(false);
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
+ if (e != null) {
+ e.printStackTrace();
+ }
}
private void updateText() {
diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java
index c2fe9c79..ae94b465 100644
--- a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java
+++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java
@@ -23,8 +23,9 @@ package org.kde.kdeconnect.Plugins.SharePlugin;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
+import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBar;
-import android.support.v7.app.ActionBarActivity;
+import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
@@ -32,7 +33,6 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
-
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPackage;
@@ -46,15 +46,14 @@ import java.util.ArrayList;
import java.util.Collection;
-public class ShareActivity extends ActionBarActivity {
+public class ShareActivity extends AppCompatActivity {
- private MenuItem menuProgress;
+ private SwipeRefreshLayout mSwipeRefreshLayout;
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.refresh, menu);
- menuProgress = menu.findItem(R.id.menu_progress);
return true;
}
@@ -62,28 +61,7 @@ public class ShareActivity extends ActionBarActivity {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
- updateComputerList();
- BackgroundService.RunCommand(ShareActivity.this, new BackgroundService.InstanceCallback() {
- @Override
- public void onServiceStart(BackgroundService service) {
- service.onNetworkChange();
- }
- });
- item.setVisible(false);
- menuProgress.setVisible(true);
- new Thread(new Runnable() {
- @Override
- public void run() {
- try { Thread.sleep(1500); } catch (InterruptedException e) { }
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- menuProgress.setVisible(false);
- item.setVisible(true);
- }
- });
- }
- }).start();
+ updateComputerListAction();
break;
default:
break;
@@ -91,6 +69,30 @@ public class ShareActivity extends ActionBarActivity {
return true;
}
+ private void updateComputerListAction() {
+ updateComputerList();
+ BackgroundService.RunCommand(ShareActivity.this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ service.onNetworkChange();
+ }
+ });
+
+ mSwipeRefreshLayout.setRefreshing(true);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try { Thread.sleep(1500); } catch (InterruptedException ignored) { }
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mSwipeRefreshLayout.setRefreshing(false);
+ }
+ });
+ }
+ }).start();
+ }
+
private void updateComputerList() {
final Intent intent = getIntent();
@@ -179,7 +181,6 @@ public class ShareActivity extends ActionBarActivity {
device.sendPackage(np);
}
}
-
finish();
}
});
@@ -193,13 +194,21 @@ public class ShareActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_list);
+ setContentView(R.layout.activity_refresh_list);
ActionBar actionBar = getSupportActionBar();
- actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
-
-
- setContentView(R.layout.activity_list);
+ mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh_list_layout);
+ mSwipeRefreshLayout.setOnRefreshListener(
+ new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ updateComputerListAction();
+ }
+ }
+ );
+ if (actionBar != null) {
+ actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
+ }
}
diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java
index fc2042e0..8be61db0 100644
--- a/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java
@@ -20,7 +20,9 @@
package org.kde.kdeconnect.Plugins.SharePlugin;
+import android.Manifest;
import android.app.Activity;
+import android.app.AlertDialog;
import android.app.DownloadManager;
import android.app.Notification;
import android.app.NotificationManager;
@@ -30,6 +32,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
@@ -110,6 +113,17 @@ public class SharePlugin extends Plugin {
Log.i("SharePlugin", "hasPayload");
+ int permissionCheck = ContextCompat.checkSelfPermission(context,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE);
+
+ if(permissionCheck == PackageManager.PERMISSION_GRANTED) {
+
+ } else if(permissionCheck == PackageManager.PERMISSION_DENIED){
+ // TODO Request Permission for storage
+ Log.i("SharePlugin", "no Permission for Storage");
+ return false;
+ }
+
final InputStream input = np.getPayload();
final long fileLength = np.getPayloadSize();
final String originalFilename = np.getString("filename", Long.toString(System.currentTimeMillis()));
@@ -132,6 +146,8 @@ public class SharePlugin extends Plugin {
final OutputStream destinationOutput = context.getContentResolver().openOutputStream(destinationDocument.getUri());
final Uri destinationUri = destinationDocument.getUri();
+
+
final int notificationId = (int)System.currentTimeMillis();
Resources res = context.getResources();
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
@@ -159,10 +175,10 @@ public class SharePlugin extends Plugin {
destinationOutput.write(data, 0, count);
if (fileLength > 0) {
if (progress >= fileLength) break;
- long progressPercentage = (progress * 100 / fileLength);
+ long progressPercentage = (progress * 10 / fileLength);
if (progressPercentage != prevProgressPercentage) {
prevProgressPercentage = progressPercentage;
- builder.setProgress(100, (int) progressPercentage, false);
+ builder.setProgress(100, (int) progressPercentage*10, false);
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
}
}
@@ -186,25 +202,33 @@ public class SharePlugin extends Plugin {
//Update the notification and allow to open the file from it
Resources res = context.getResources();
String message = successful? res.getString(R.string.received_file_title, device.getName()) : res.getString(R.string.received_file_fail_title, device.getName());
- NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
- .setContentTitle(message)
+ builder.setContentTitle(message)
.setTicker(message)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
- .setAutoCancel(true);
- if (successful) {
- Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(destinationUri, mimeType);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
- stackBuilder.addNextIntent(intent);
- PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
- builder.setContentText(res.getString(R.string.received_file_text, destinationDocument.getName()))
- .setContentIntent(resultPendingIntent);
+ .setAutoCancel(true)
+ .setProgress(100,100,false)
+ .setOngoing(false);
+
+ // Nougat requires share:// URIs instead of file:// URIs
+ // TODO use FileProvider for >Nougat
+ if(Build.VERSION.SDK_INT < 24) {
+ if (successful) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(destinationUri, mimeType);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
+ stackBuilder.addNextIntent(intent);
+ PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
+ builder.setContentText(res.getString(R.string.received_file_text, destinationDocument.getName()))
+ .setContentIntent(resultPendingIntent);
+ }
}
+
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean("share_notification_preference", true)) {
builder.setDefaults(Notification.DEFAULT_ALL);
}
+
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
if (successful) {
@@ -408,5 +432,10 @@ public class SharePlugin extends Plugin {
return new String[]{PACKAGE_TYPE_SHARE_REQUEST};
}
+ @Override
+ public String[] getRequiredPermissions() {
+ String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE};
+ return perms;
+ }
}
diff --git a/src/org/kde/kdeconnect/Plugins/TelepathyPlugin/TelepathyPlugin.java b/src/org/kde/kdeconnect/Plugins/TelepathyPlugin/TelepathyPlugin.java
index 67a5ce86..55f5b88c 100644
--- a/src/org/kde/kdeconnect/Plugins/TelepathyPlugin/TelepathyPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/TelepathyPlugin/TelepathyPlugin.java
@@ -20,6 +20,10 @@
package org.kde.kdeconnect.Plugins.TelepathyPlugin;
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
import android.telephony.SmsManager;
import android.util.Log;
@@ -45,11 +49,6 @@ public class TelepathyPlugin extends Plugin {
return context.getResources().getString(R.string.pref_plugin_telepathy_desc);
}
- @Override
- public boolean onCreate() {
- return true;
- }
-
@Override
public void onDestroy() {
}
@@ -65,6 +64,10 @@ public class TelepathyPlugin extends Plugin {
String phoneNo = np.getString("phoneNumber");
String sms = np.getString("messageBody");
try {
+ int permissionCheck = ContextCompat.checkSelfPermission(context,
+ Manifest.permission.SEND_SMS);
+
+ if(permissionCheck == PackageManager.PERMISSION_GRANTED) {
SmsManager smsManager = SmsManager.getDefault();
ArrayList parts = smsManager.divideMessage(sms);
@@ -72,7 +75,9 @@ public class TelepathyPlugin extends Plugin {
// If this message turns out to fit in a single SMS, sendMultpartTextMessage
// properly handles that case
smsManager.sendMultipartTextMessage(phoneNo, null, parts, null, null);
-
+ } else if(permissionCheck == PackageManager.PERMISSION_DENIED){
+ // TODO Request Permission SEND_SMS
+ }
//TODO: Notify other end
} catch (Exception e) {
//TODO: Notify other end
@@ -184,4 +189,8 @@ public class TelepathyPlugin extends Plugin {
return new String[]{};
}
+ @Override
+ public String[] getRequiredPermissions() {
+ return new String[]{Manifest.permission.SEND_SMS/*, Manifest.permission.READ_CONTACTS*/};
+ }
}
diff --git a/src/org/kde/kdeconnect/Plugins/TelephonyPlugin/TelephonyPlugin.java b/src/org/kde/kdeconnect/Plugins/TelephonyPlugin/TelephonyPlugin.java
index 27d3af74..33a55cde 100644
--- a/src/org/kde/kdeconnect/Plugins/TelephonyPlugin/TelephonyPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/TelephonyPlugin/TelephonyPlugin.java
@@ -20,13 +20,16 @@
package org.kde.kdeconnect.Plugins.TelephonyPlugin;
+import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
+import android.support.v4.content.ContextCompat;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -112,32 +115,40 @@ public class TelephonyPlugin extends Plugin {
//Log.e("TelephonyPlugin", "callBroadcastReceived");
- Map contactInfo = ContactsHelper.phoneNumberLookup(context, phoneNumber);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_TELEPHONY);
- if (phoneNumber != null) {
- np.set("phoneNumber", phoneNumber);
- }
+ int permissionCheck = ContextCompat.checkSelfPermission(context,
+ Manifest.permission.READ_CONTACTS);
+
+ if(permissionCheck==PackageManager.PERMISSION_GRANTED) {
+
+ Map contactInfo = ContactsHelper.phoneNumberLookup(context, phoneNumber);
+
+ if (contactInfo.containsKey("name")) {
+ np.set("contactName", contactInfo.get("name"));
+ }
+
+ if (contactInfo.containsKey("photoID")) {
+ String photoUri = contactInfo.get("photoID");
+ if (photoUri != null) {
+ try {
+ String base64photo = ContactsHelper.photoId64Encoded(context, photoUri);
+ if (base64photo != null && !base64photo.isEmpty()) {
+ np.set("phoneThumbnail", base64photo);
+ }
+ } catch (Exception e) {
+ Log.e("TelephonyPlugin", "Failed to get contact photo");
+ }
+ }
+
+ }
- if (contactInfo.containsKey("name")) {
- np.set("contactName", contactInfo.get("name"));
} else {
np.set("contactName", phoneNumber);
}
- if (contactInfo.containsKey("photoID")) {
- String photoUri = contactInfo.get("photoID");
- if (photoUri != null) {
- try {
- String base64photo = ContactsHelper.photoId64Encoded(context, photoUri);
- if (base64photo != null && !base64photo.isEmpty()) {
- np.set("phoneThumbnail", base64photo);
- }
- } catch (Exception e) {
- Log.e("TelephonyPlugin", "Failed to get contact photo");
- }
- }
-
+ if (phoneNumber != null) {
+ np.set("phoneNumber", phoneNumber);
}
switch (state) {
@@ -231,18 +242,26 @@ public class TelephonyPlugin extends Plugin {
}
String phoneNumber = messages.get(0).getOriginatingAddress();
- Map contactInfo = ContactsHelper.phoneNumberLookup(context, phoneNumber);
+
+ int permissionCheck = ContextCompat.checkSelfPermission(context,
+ Manifest.permission.READ_CONTACTS);
+
+ if(permissionCheck==PackageManager.PERMISSION_GRANTED) {
+ Map contactInfo = ContactsHelper.phoneNumberLookup(context, phoneNumber);
+
+ if (contactInfo.containsKey("name")) {
+ np.set("contactName", contactInfo.get("name"));
+ }
+
+ if (contactInfo.containsKey("photoID")) {
+ np.set("phoneThumbnail", ContactsHelper.photoId64Encoded(context, contactInfo.get("photoID")));
+ }
+ }
if (phoneNumber != null) {
np.set("phoneNumber", phoneNumber);
}
- if (contactInfo.containsKey("name")) {
- np.set("contactName", contactInfo.get("name"));
- }
- if (contactInfo.containsKey("photoID")) {
- np.set("phoneThumbnail", ContactsHelper.photoId64Encoded(context, contactInfo.get("photoID")));
- }
device.sendPackage(np);
}
@@ -290,4 +309,9 @@ public class TelephonyPlugin extends Plugin {
return new String[]{PACKAGE_TYPE_TELEPHONY};
}
+ @Override
+ public String[] getRequiredPermissions() {
+ return new String[]{Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_CONTACTS, Manifest.permission.SEND_SMS};
+ }
+
}
diff --git a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java
index 56d2c8b8..a5435f7f 100644
--- a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java
+++ b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java
@@ -68,6 +68,7 @@ public class DeviceFragment extends Fragment {
Device device;
TextView errorHeader;
+ TextView noPermissionsHeader;
MaterialActivity mActivity;
@@ -389,6 +390,38 @@ public class DeviceFragment extends Fragment {
}
}
+ //Plugins without permissions List
+ final ConcurrentHashMap permissionsNeeded = device.getPluginsWithoutPermissions();
+ if (!permissionsNeeded.isEmpty()) {
+ if (noPermissionsHeader == null) {
+ noPermissionsHeader = new TextView(mActivity);
+ noPermissionsHeader.setPadding(
+ 0,
+ ((int) (28 * getResources().getDisplayMetrics().density)),
+ 0,
+ ((int) (8 * getResources().getDisplayMetrics().density))
+ );
+ noPermissionsHeader.setOnClickListener(null);
+ noPermissionsHeader.setOnLongClickListener(null);
+ noPermissionsHeader.setText(getResources().getString(R.string.plugins_need_permission));
+ }
+ items.add(new CustomItem(noPermissionsHeader));
+ for (Map.Entry entry : permissionsNeeded.entrySet()) {
+ String pluginKey = entry.getKey();
+ final Plugin plugin = entry.getValue();
+ if (plugin == null) {
+ items.add(new SmallEntryItem(pluginKey));
+ } else {
+ items.add(new SmallEntryItem(plugin.getDisplayName(), new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ plugin.getPermissionExplanationDialog(mActivity).show();
+ }
+ }));
+ }
+ }
+ }
+
ListView buttonsList = (ListView) rootView.findViewById(R.id.buttons_list);
ListAdapter adapter = new ListAdapter(mActivity, items);
buttonsList.setAdapter(adapter);
diff --git a/src/org/kde/kdeconnect/UserInterface/MaterialActivity.java b/src/org/kde/kdeconnect/UserInterface/MaterialActivity.java
index 4eeed193..d8201032 100644
--- a/src/org/kde/kdeconnect/UserInterface/MaterialActivity.java
+++ b/src/org/kde/kdeconnect/UserInterface/MaterialActivity.java
@@ -1,14 +1,17 @@
package org.kde.kdeconnect.UserInterface;
+import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
+import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
@@ -294,6 +297,22 @@ public class MaterialActivity extends AppCompatActivity {
}
}
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ for (int result : grantResults) {
+ if (result == PackageManager.PERMISSION_GRANTED) {
+ //New permission granted, reload plugins
+ BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ Device device = service.getDevice(mCurrentDevice);
+ device.reloadPluginsFromSettings();
+ }
+ });
+ }
+ }
+ }
+
public void renameDevice() {
final TextView nameView = (TextView) mNavigationView.findViewById(R.id.device_name);
final EditText deviceNameEdit = new EditText(MaterialActivity.this);
diff --git a/src/org/kde/kdeconnect/UserInterface/PairingFragment.java b/src/org/kde/kdeconnect/UserInterface/PairingFragment.java
index b848baf7..67c37543 100644
--- a/src/org/kde/kdeconnect/UserInterface/PairingFragment.java
+++ b/src/org/kde/kdeconnect/UserInterface/PairingFragment.java
@@ -24,6 +24,7 @@ import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
+import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.Menu;
@@ -55,10 +56,10 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
private static final int RESULT_PAIRING_SUCCESFUL = Activity.RESULT_FIRST_USER;
private View rootView;
+ private View listRootView;
+ private SwipeRefreshLayout mSwipeRefreshLayout;
private MaterialActivity mActivity;
- private MenuItem menuProgress;
-
boolean listRefreshCalledThisFrame = false;
TextView headerText;
@@ -75,12 +76,21 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
setHasOptionsMenu(true);
- rootView = inflater.inflate(R.layout.activity_list, container, false);
-
+ rootView = inflater.inflate(R.layout.activity_refresh_list, container, false);
+ listRootView = rootView.findViewById(R.id.listView1);
+ mSwipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.refresh_list_layout);
+ mSwipeRefreshLayout.setOnRefreshListener(
+ new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ updateComputerListAction();
+ }
+ }
+ );
headerText = new TextView(inflater.getContext());
headerText.setText(getString(R.string.pairing_description));
headerText.setPadding(0, (int) (16 * getResources().getDisplayMetrics().density), 0, (int) (12 * getResources().getDisplayMetrics().density));
- ((ListView) rootView).addHeaderView(headerText);
+ ((ListView) listRootView).addHeaderView(headerText);
return rootView;
}
@@ -91,7 +101,30 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
mActivity = ((MaterialActivity) getActivity());
}
- void updateComputerList() {
+ private void updateComputerListAction() {
+ updateComputerList();
+ BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
+ @Override
+ public void onServiceStart(BackgroundService service) {
+ service.onNetworkChange();
+ }
+ });
+ mSwipeRefreshLayout.setRefreshing(true);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try { Thread.sleep(1500); } catch (InterruptedException ignored) { }
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mSwipeRefreshLayout.setRefreshing(false);
+ }
+ });
+ }
+ }).start();
+ }
+
+ private void updateComputerList() {
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(final BackgroundService service) {
@@ -232,33 +265,13 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.pairing, menu);
- menuProgress = menu.findItem(R.id.menu_progress);
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
- updateComputerList();
- BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
- @Override
- public void onServiceStart(BackgroundService service) {
- service.onNetworkChange();
- }
- });
- menuProgress.setVisible(true);
- new Thread(new Runnable() {
- @Override
- public void run() {
- try { Thread.sleep(1500); } catch (InterruptedException e) { }
- mActivity.runOnUiThread(new Runnable() {
- @Override
- public void run() {
- menuProgress.setVisible(false);
- }
- });
- }
- }).start();
+ updateComputerListAction();
break;
case R.id.menu_rename:
mActivity.renameDevice();