diff --git a/Tethering/lint-baseline.xml b/Tethering/lint-baseline.xml new file mode 100644 index 0000000000..37511c682e --- /dev/null +++ b/Tethering/lint-baseline.xml @@ -0,0 +1,15 @@ + + + + + + + + \ No newline at end of file diff --git a/Tethering/res/values-af/strings.xml b/Tethering/res/values-af/strings.xml index 37901426ae..056168b12e 100644 --- a/Tethering/res/values-af/strings.xml +++ b/Tethering/res/values-af/strings.xml @@ -16,14 +16,14 @@ - "Verbinding of warmkol is aktief" - "Tik om op te stel." - "Verbinding is gedeaktiveer" - "Kontak jou admin vir besonderhede" - "Warmkol- en verbindingstatus" - - - - - + "Verbinding of warmkol is aktief" + "Tik om op te stel." + "Verbinding is gedeaktiveer" + "Kontak jou administrateur vir besonderhede" + "Warmkol- en verbindingstatus" + + + + + diff --git a/Tethering/res/values-am/strings.xml b/Tethering/res/values-am/strings.xml index bb89d6e81f..ac468dd144 100644 --- a/Tethering/res/values-am/strings.xml +++ b/Tethering/res/values-am/strings.xml @@ -16,14 +16,14 @@ - "እንደ ሞደም መሰካት ወይም መገናኛ ነጥብ ገባሪ" - "ለማዋቀር መታ ያድርጉ።" - "እንደ ሞደም መሰካት ተሰናክሏል" - "ለዝርዝሮች የእርስዎን አስተዳዳሪ ያነጋግሩ" - "መገናኛ ነጥብ እና እንደ ሞደም የመሰካት ሁኔታ" - - - - - + "እንደ ሞደም መሰካት ወይም መገናኛ ነጥብ ገባሪ" + "ለማዋቀር መታ ያድርጉ።" + "እንደ ሞደም መሰካት ተሰናክሏል" + "ለዝርዝሮች የእርስዎን አስተዳዳሪ ያነጋግሩ" + "መገናኛ ነጥብ እና እንደ ሞደም የመሰካት ሁኔታ" + + + + + diff --git a/Tethering/res/values-ar/strings.xml b/Tethering/res/values-ar/strings.xml index ef98a0192a..7d5bad34da 100644 --- a/Tethering/res/values-ar/strings.xml +++ b/Tethering/res/values-ar/strings.xml @@ -16,14 +16,14 @@ - "نقطة التوصيل نشطة أو الاتصال نشط" - "انقر لإعداد التوصيل." - "التوصيل غير مفعَّل" - "تواصَل مع المشرف للحصول على التفاصيل." - "حالة نقطة الاتصال والتوصيل" - - - - - + "النطاق نشط أو نقطة الاتصال نشطة" + "انقر للإعداد." + "التوصيل متوقف." + "تواصَل مع المشرف للحصول على التفاصيل." + "حالة نقطة الاتصال والتوصيل" + + + + + diff --git a/Tethering/res/values-as/strings.xml b/Tethering/res/values-as/strings.xml index 9b9e8d6201..091350455b 100644 --- a/Tethering/res/values-as/strings.xml +++ b/Tethering/res/values-as/strings.xml @@ -16,14 +16,14 @@ - "টে’ডাৰিং অথবা হ’টস্প’ট সক্ৰিয় অৱস্থাত আছে" - "ছেট আপ কৰিবলৈ টিপক।" - "টে’ডাৰিঙৰ সুবিধাটো অক্ষম কৰি থোৱা হৈছে" - "সবিশেষ জানিবলৈ আপোনাৰ প্ৰশাসকৰ সৈতে যোগাযোগ কৰক" - "হ’টস্প’ট আৰু টে’ডাৰিঙৰ স্থিতি" - - - - - + "টে\'ডাৰিং অথবা হ\'টস্প\'ট সক্ৰিয় অৱস্থাত আছে" + "ছেট আপ কৰিবলৈ টিপক।" + "টে\'ডাৰিঙৰ সুবিধাটো অক্ষম কৰি থোৱা হৈছে" + "সবিশেষ জানিবলৈ আপোনাৰ প্ৰশাসকৰ সৈতে যোগাযোগ কৰক" + "হ’টস্প\'ট আৰু টে\'ডাৰিঙৰ স্থিতি" + + + + + diff --git a/Tethering/res/values-az/strings.xml b/Tethering/res/values-az/strings.xml index b091f151fa..dce70da178 100644 --- a/Tethering/res/values-az/strings.xml +++ b/Tethering/res/values-az/strings.xml @@ -16,14 +16,14 @@ - "Modem rejimi və ya hotspot aktivdir" - "Ayarlamaq üçün toxunun." - "Modem rejimi deaktivdir" - "Detallar üçün adminlə əlaqə saxlayın" - "Hotspot və modem rejimi statusu" - - - - - + "Birləşmə və ya hotspot aktivdir" + "Ayarlamaq üçün toxunun." + "Birləşmə deaktivdir" + "Detallar üçün adminlə əlaqə saxlayın" + "Hotspot & birləşmə statusu" + + + + + diff --git a/Tethering/res/values-b+sr+Latn/strings.xml b/Tethering/res/values-b+sr+Latn/strings.xml index aa6c6fda60..b0774ec9a8 100644 --- a/Tethering/res/values-b+sr+Latn/strings.xml +++ b/Tethering/res/values-b+sr+Latn/strings.xml @@ -16,14 +16,14 @@ - "Aktivno je privezivanje ili hotspot" - "Dodirnite da biste podesili." - "Privezivanje je onemogućeno" - "Potražite detalje od administratora" - "Status hotspota i privezivanja" - - - - - + "Privezivanje ili hotspot je aktivan" + "Dodirnite da biste podesili." + "Privezivanje je onemogućeno" + "Potražite detalje od administratora" + "Status hotspota i privezivanja" + + + + + diff --git a/Tethering/res/values-be/strings.xml b/Tethering/res/values-be/strings.xml index 5da882824a..a8acebe2e9 100644 --- a/Tethering/res/values-be/strings.xml +++ b/Tethering/res/values-be/strings.xml @@ -16,14 +16,14 @@ - "Мадэм або хот-спот актыўныя" - "Націсніце, каб наладзіць." - "Рэжым мадэма выключаны" - "Звярніцеся да адміністратара па падрабязную інфармацыю" - "Стан \"Хот-спот і мадэм\"" - - - - - + "Мадэм або хот-спот актыўныя" + "Дакраніцеся, каб наладзіць." + "Рэжым мадэма выключаны" + "Звярніцеся да адміністратара па падрабязную інфармацыю" + "Стан \"Хот-спот і мадэм\"" + + + + + diff --git a/Tethering/res/values-bg/strings.xml b/Tethering/res/values-bg/strings.xml index 0ce2ac77a5..94fb2d8f17 100644 --- a/Tethering/res/values-bg/strings.xml +++ b/Tethering/res/values-bg/strings.xml @@ -16,14 +16,14 @@ - "Има активна споделена връзка или точка за достъп" - "Докоснете, за да настроите." - "Функцията за тетъринг е деактивирана" - "Свържете се с администратора си за подробности" - "Състояние на функцията за точка за достъп и тетъринг" - - - - - + "Има активна споделена връзка или точка за достъп" + "Докоснете, за да настроите." + "Функцията за тетъринг е деактивирана" + "Свържете се с администратора си за подробности" + "Състояние на функцията за точка за достъп и тетъринг" + + + + + diff --git a/Tethering/res/values-bn/strings.xml b/Tethering/res/values-bn/strings.xml index 787a65c2e3..aea02b9ddf 100644 --- a/Tethering/res/values-bn/strings.xml +++ b/Tethering/res/values-bn/strings.xml @@ -16,14 +16,14 @@ - "টেথারিং বা হটস্পট অ্যাক্টিভ আছে" - "সেট-আপ করতে ট্যাপ করুন।" - "টেথারিং বন্ধ করা আছে" - "বিস্তারিত বিবরণ পেতে, অ্যাডমিনের সাথে যোগাযোগ করুন" - "হটস্পট ও টেথারিং সংক্রান্ত স্ট্যাটাস" - - - - - + "টিথারিং বা হটস্পট চালু আছে" + "সেট-আপ করতে ট্যাপ করুন।" + "টিথারিং বন্ধ করা আছে" + "বিশদে জানতে অ্যাডমিনের সাথে যোগাযোগ করুন" + "হটস্পট ও টিথারিং স্ট্যাটাস" + + + + + diff --git a/Tethering/res/values-bs/strings.xml b/Tethering/res/values-bs/strings.xml index b6073fdbef..de232724c5 100644 --- a/Tethering/res/values-bs/strings.xml +++ b/Tethering/res/values-bs/strings.xml @@ -16,14 +16,14 @@ - "Dijeljenje internetske veze ili pristupna tačka su aktivni" - "Dodirnite da postavite." - "Dijeljenje internetske veze je onemogućeno" - "Kontaktirajte administratora za detalje" - "Status pristupne tačke i dijeljenja internetske veze" - - - - - + "Aktivno je povezivanje putem mobitela ili pristupna tačka" + "Dodirnite da postavite." + "Povezivanje putem mobitela je onemogućeno" + "Kontaktirajte svog administratora za detalje" + "Status pristupne tačke i povezivanja putem mobitela" + + + + + diff --git a/Tethering/res/values-ca/strings.xml b/Tethering/res/values-ca/strings.xml index 2513989c92..88b795c1f8 100644 --- a/Tethering/res/values-ca/strings.xml +++ b/Tethering/res/values-ca/strings.xml @@ -16,14 +16,14 @@ - "Compartició de xarxa o punt d\'accés Wi‑Fi actius" - "Toca per configurar." - "La compartició de xarxa està desactivada" - "Contacta amb el teu administrador per obtenir més informació" - "Estat del punt d\'accés Wi‑Fi i de la compartició de xarxa" - - - - - + "Compartició de xarxa o punt d\'accés Wi‑Fi actius" + "Toca per configurar." + "La compartició de xarxa està desactivada" + "Contacta amb el teu administrador per obtenir més informació" + "Estat del punt d\'accés Wi‑Fi i de la compartició de xarxa" + + + + + diff --git a/Tethering/res/values-cs/strings.xml b/Tethering/res/values-cs/strings.xml index a74991562a..8c1b83bf3e 100644 --- a/Tethering/res/values-cs/strings.xml +++ b/Tethering/res/values-cs/strings.xml @@ -16,14 +16,14 @@ - "Tethering nebo hotspot je aktivní" - "Klepnutím ho nastavíte." - "Tethering je zakázán" - "O podrobnosti požádejte administrátora" - "Stav hotspotu a tetheringu" - - - - - + "Tethering nebo hotspot je aktivní" + "Klepnutím zahájíte nastavení." + "Tethering je zakázán" + "O podrobnosti požádejte administrátora" + "Stav hotspotu a tetheringu" + + + + + diff --git a/Tethering/res/values-da/strings.xml b/Tethering/res/values-da/strings.xml index dddf0971ae..f413e70548 100644 --- a/Tethering/res/values-da/strings.xml +++ b/Tethering/res/values-da/strings.xml @@ -16,14 +16,14 @@ - "Netdeling eller hotspot er aktiveret" - "Tryk for at konfigurere." - "Netdeling er deaktiveret" - "Kontakt din administrator for at få oplysninger" - "Status for hotspot og netdeling" - - - - - + "Netdeling eller hotspot er aktivt" + "Tryk for at konfigurere." + "Netdeling er deaktiveret" + "Kontakt din administrator for at få oplysninger" + "Status for hotspot og netdeling" + + + + + diff --git a/Tethering/res/values-de/strings.xml b/Tethering/res/values-de/strings.xml index ab7b8c9471..f057d7824e 100644 --- a/Tethering/res/values-de/strings.xml +++ b/Tethering/res/values-de/strings.xml @@ -16,14 +16,14 @@ - "Tethering oder Hotspot aktiv" - "Zum Einrichten tippen." - "Tethering ist deaktiviert" - "Bitte wende dich für weitere Informationen an den Administrator" - "Hotspot- und Tethering-Status" - - - - - + "Tethering oder Hotspot aktiv" + "Zum Einrichten tippen." + "Tethering ist deaktiviert" + "Bitte wende dich für weitere Informationen an den Administrator" + "Hotspot- und Tethering-Status" + + + + + diff --git a/Tethering/res/values-el/strings.xml b/Tethering/res/values-el/strings.xml index 4ed3ec5802..b3c986bdaf 100644 --- a/Tethering/res/values-el/strings.xml +++ b/Tethering/res/values-el/strings.xml @@ -16,14 +16,14 @@ - "Ενεργή σύνδεση ή ενεργό σημείο πρόσβασης Wi-Fi" - "Πατήστε για ρύθμιση." - "Η σύνδεση είναι απενεργοποιημένη" - "Επικοινωνήστε με τον διαχειριστή για λεπτομέρειες" - "Κατάσταση σημείου πρόσβασης Wi-Fi και σύνδεσης" - - - - - + "Πρόσδεση ή σύνδεση σημείου πρόσβασης ενεργή" + "Πατήστε για ρύθμιση." + "Η σύνδεση είναι απενεργοποιημένη" + "Επικοινωνήστε με τον διαχειριστή σας για λεπτομέρειες" + "Κατάσταση σημείου πρόσβασης Wi-Fi και σύνδεσης" + + + + + diff --git a/Tethering/res/values-en-rAU/strings.xml b/Tethering/res/values-en-rAU/strings.xml index 2dc76892c5..769e01208a 100644 --- a/Tethering/res/values-en-rAU/strings.xml +++ b/Tethering/res/values-en-rAU/strings.xml @@ -16,14 +16,14 @@ - "Tethering or hotspot active" - "Tap to set up." - "Tethering is disabled" - "Contact your admin for details" - "Hotspot and tethering status" - - - - - + "Tethering or hotspot active" + "Tap to set up." + "Tethering is disabled" + "Contact your admin for details" + "Hotspot and tethering status" + + + + + diff --git a/Tethering/res/values-en-rCA/strings.xml b/Tethering/res/values-en-rCA/strings.xml index 066cd82ecf..769e01208a 100644 --- a/Tethering/res/values-en-rCA/strings.xml +++ b/Tethering/res/values-en-rCA/strings.xml @@ -16,14 +16,14 @@ - "Tethering or hotspot active" - "Tap to set up." - "Tethering is disabled" - "Contact your admin for details" - "Hotspot & tethering status" - - - - - + "Tethering or hotspot active" + "Tap to set up." + "Tethering is disabled" + "Contact your admin for details" + "Hotspot and tethering status" + + + + + diff --git a/Tethering/res/values-en-rGB/strings.xml b/Tethering/res/values-en-rGB/strings.xml index 2dc76892c5..769e01208a 100644 --- a/Tethering/res/values-en-rGB/strings.xml +++ b/Tethering/res/values-en-rGB/strings.xml @@ -16,14 +16,14 @@ - "Tethering or hotspot active" - "Tap to set up." - "Tethering is disabled" - "Contact your admin for details" - "Hotspot and tethering status" - - - - - + "Tethering or hotspot active" + "Tap to set up." + "Tethering is disabled" + "Contact your admin for details" + "Hotspot and tethering status" + + + + + diff --git a/Tethering/res/values-en-rIN/strings.xml b/Tethering/res/values-en-rIN/strings.xml index 2dc76892c5..769e01208a 100644 --- a/Tethering/res/values-en-rIN/strings.xml +++ b/Tethering/res/values-en-rIN/strings.xml @@ -16,14 +16,14 @@ - "Tethering or hotspot active" - "Tap to set up." - "Tethering is disabled" - "Contact your admin for details" - "Hotspot and tethering status" - - - - - + "Tethering or hotspot active" + "Tap to set up." + "Tethering is disabled" + "Contact your admin for details" + "Hotspot and tethering status" + + + + + diff --git a/Tethering/res/values-en-rXC/strings.xml b/Tethering/res/values-en-rXC/strings.xml index a83caac4cf..f1674bed4e 100644 --- a/Tethering/res/values-en-rXC/strings.xml +++ b/Tethering/res/values-en-rXC/strings.xml @@ -16,14 +16,14 @@ - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‎‎‎‎‎‎‎‏‏‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‎‎‏‏‏‏‏‏‏‏‎‎Tethering or hotspot active‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‏‎‏‏‏‎‎‎‎‏‎‎Tap to set up.‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‎‏‎‏‏‏‎‎‏‎‏‎‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‏‏‎‎‏‏‏‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‎‏‎‎‏‎‎Tethering is disabled‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‏‏‏‎‎‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‎‏‏‎‎‎‏‏‏‎‏‎‏‎‏‏‏‎‎‎‎‏‏‎‎‏‎‏‎‎‎‏‎Contact your admin for details‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‎‎‏‏‎‎‎‎‏‎‏‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‏‎‎‏‎Hotspot & tethering status‎‏‎‎‏‎" - - - - - + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‏‏‎‎‎‎‏‏‏‎‎‏‎‎‏‎‏‏‎‏‏‎‎‎‎‎Tethering or hotspot active‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‎‎‎‎‎‎‎‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎Tap to set up.‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‎‎‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‎‎‎‎‏‏‏‎‏‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‏‏‎‎‏‏‏‏‏‎Tethering is disabled‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‎‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎‎‏‎‎‏‎‏‎‎‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎Contact your admin for details‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‎‎‏‎‎‏‏‎‎‎‎‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎Hotspot & tethering status‎‏‎‎‏‎" + + + + + diff --git a/Tethering/res/values-es-rUS/strings.xml b/Tethering/res/values-es-rUS/strings.xml index 69bd4e760d..63689f4399 100644 --- a/Tethering/res/values-es-rUS/strings.xml +++ b/Tethering/res/values-es-rUS/strings.xml @@ -16,14 +16,14 @@ - "Conexión mediante dispositivo móvil o hotspot activos" - "Presiona para configurar esta opción." - "Se inhabilitó la conexión mediante dispositivo móvil" - "Para obtener más información, comunícate con el administrador" - "Estado del hotspot y de la conexión mediante dispositivo portátil" - - - - - + "Conexión a red o hotspot conectados" + "Presiona para configurar esta opción." + "Se inhabilitó la conexión mediante dispositivo portátil" + "Para obtener más información, comunícate con el administrador" + "Estado del hotspot y la conexión mediante dispositivo portátil" + + + + + diff --git a/Tethering/res/values-es/strings.xml b/Tethering/res/values-es/strings.xml index 6bef387bab..9a34ed5e38 100644 --- a/Tethering/res/values-es/strings.xml +++ b/Tethering/res/values-es/strings.xml @@ -16,14 +16,14 @@ - "Conexión compartida o punto de acceso activos" - "Toca para configurarla." - "La conexión compartida está inhabilitada" - "Ponte en contacto con el administrador para obtener más información" - "Estado del punto de acceso y la conexión compartida" - - - - - + "Conexión compartida o punto de acceso activos" + "Toca para configurar." + "La conexión compartida está inhabilitada" + "Solicita más información a tu administrador" + "Estado del punto de acceso y de la conexión compartida" + + + + + diff --git a/Tethering/res/values-et/strings.xml b/Tethering/res/values-et/strings.xml index 68088cef25..0970341ab0 100644 --- a/Tethering/res/values-et/strings.xml +++ b/Tethering/res/values-et/strings.xml @@ -16,14 +16,14 @@ - "Jagamine või kuumkoht on aktiivne" - "Puudutage seadistamiseks." - "Jagamine on keelatud" - "Lisateabe saamiseks võtke ühendust oma administraatoriga" - "Kuumkoha ja jagamise olek" - - - - - + "Jagamine või kuumkoht on aktiivne" + "Puudutage seadistamiseks." + "Jagamine on keelatud" + "Lisateabe saamiseks võtke ühendust oma administraatoriga" + "Kuumkoha ja jagamise olek" + + + + + diff --git a/Tethering/res/values-eu/strings.xml b/Tethering/res/values-eu/strings.xml index 37b35a8bfa..632019e2ef 100644 --- a/Tethering/res/values-eu/strings.xml +++ b/Tethering/res/values-eu/strings.xml @@ -16,14 +16,14 @@ - "Konexioa partekatzeko aukera edo wifi-gunea aktibo dago" - "Konfiguratzeko, sakatu hau." - "Konexioa partekatzeko aukera desgaituta dago" - "Xehetasunak lortzeko, jarri administratzailearekin harremanetan" - "Wifi-gunearen eta konexioa partekatzeko aukeraren egoera" - - - - - + "Konexioa partekatzea edo wifi-gunea aktibo dago" + "Sakatu konfiguratzeko." + "Desgaituta dago konexioa partekatzeko aukera" + "Xehetasunak lortzeko, jarri administratzailearekin harremanetan" + "Wifi-gunearen eta konexioa partekatzeko eginbidearen egoera" + + + + + diff --git a/Tethering/res/values-fa/strings.xml b/Tethering/res/values-fa/strings.xml index d7f254359f..2e21c85fa1 100644 --- a/Tethering/res/values-fa/strings.xml +++ b/Tethering/res/values-fa/strings.xml @@ -16,14 +16,14 @@ - "اشتراک‌گذاری اینترنت یا نقطه اتصال فعال است" - "برای راه‌اندازی، ضربه بزنید." - "اشتراک‌گذاری اینترنت غیرفعال است" - "برای جزئیات، با سرپرستتان تماس بگیرید" - "وضعیت نقطه اتصال و اشتراک‌گذاری اینترنت" - - - - - + "اشتراک‌گذاری اینترنت یا نقطه اتصال فعال" + "برای راه‌اندازی ضربه بزنید." + "اشتراک‌گذاری اینترنت غیرفعال است" + "برای جزئیات، با سرپرستتان تماس بگیرید" + "وضعیت نقطه اتصال و اشتراک‌گذاری اینترنت" + + + + + diff --git a/Tethering/res/values-fi/strings.xml b/Tethering/res/values-fi/strings.xml index 4bf09fecef..413db3f0f8 100644 --- a/Tethering/res/values-fi/strings.xml +++ b/Tethering/res/values-fi/strings.xml @@ -16,14 +16,14 @@ - "Puhelimen käyttäminen modeemina tai hotspot käytössä" - "Ota käyttöön napauttamalla." - "Puhelimen käyttäminen modeemina on poistettu käytöstä" - "Pyydä lisätietoa järjestelmänvalvojalta" - "Hotspotin ja modeemina toimivan puhelimen tila" - - - - - + "Yhteyden jakaminen tai hotspot käytössä" + "Ota käyttöön napauttamalla." + "Yhteyden jakaminen on poistettu käytöstä" + "Pyydä lisätietoja järjestelmänvalvojalta" + "Hotspotin ja yhteyden jakamisen tila" + + + + + diff --git a/Tethering/res/values-fr-rCA/strings.xml b/Tethering/res/values-fr-rCA/strings.xml index 66b4684efd..eb2e4ba540 100644 --- a/Tethering/res/values-fr-rCA/strings.xml +++ b/Tethering/res/values-fr-rCA/strings.xml @@ -16,14 +16,14 @@ - "Partage de connexion ou point d\'accès sans fil activé" - "Touchez pour configurer." - "Le partage de connexion est désactivé" - "Communiquez avec votre administrateur pour obtenir plus de détails" - "État du point d\'accès sans fil et du partage de connexion" - - - - - + "Partage de connexion ou point d\'accès sans fil activé" + "Touchez pour configurer." + "Le partage de connexion est désactivé" + "Communiquez avec votre administrateur pour obtenir plus de détails" + "Point d\'accès et partage de connexion" + + + + + diff --git a/Tethering/res/values-fr/strings.xml b/Tethering/res/values-fr/strings.xml index 9440d95b47..22259c52ab 100644 --- a/Tethering/res/values-fr/strings.xml +++ b/Tethering/res/values-fr/strings.xml @@ -16,14 +16,14 @@ - "Partage de connexion ou point d\'accès activé" - "Appuyez pour configurer." - "Le partage de connexion est désactivé" - "Pour en savoir plus, contactez votre administrateur" - "État du point d\'accès et du partage de connexion" - - - - - + "Partage de connexion ou point d\'accès activé" + "Appuyez pour effectuer la configuration." + "Le partage de connexion est désactivé" + "Pour en savoir plus, contactez votre administrateur" + "État du point d\'accès et du partage de connexion" + + + + + diff --git a/Tethering/res/values-gl/strings.xml b/Tethering/res/values-gl/strings.xml index 74bb7f2864..ded82fcd54 100644 --- a/Tethering/res/values-gl/strings.xml +++ b/Tethering/res/values-gl/strings.xml @@ -16,14 +16,14 @@ - "Conexión compartida ou zona wifi activada" - "Toca para configurar." - "A conexión compartida está desactivada" - "Contacta co administrador para obter información" - "Estado da zona wifi e da conexión compartida" - - - - - + "Conexión compartida ou zona wifi activada" + "Toca para configurar." + "A conexión compartida está desactivada" + "Contacta co administrador para obter información" + "Estado da zona wifi e da conexión compartida" + + + + + diff --git a/Tethering/res/values-gu/strings.xml b/Tethering/res/values-gu/strings.xml index c4634999d6..7cbbc2de3d 100644 --- a/Tethering/res/values-gu/strings.xml +++ b/Tethering/res/values-gu/strings.xml @@ -16,14 +16,14 @@ - "ઇન્ટરનેટ શેર કરવાની સુવિધા અથવા હૉટસ્પૉટ સક્રિય છે" - "સેટઅપ કરવા માટે ટૅપ કરો." - "ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરી છે" - "વિગતો માટે તમારા ઍડમિનનો સંપર્ક કરો" - "હૉટસ્પૉટ અને ઇન્ટરનેટ શેર કરવાની સુવિધાનું સ્ટેટસ" - - - - - + "ઇન્ટરનેટ શેર કરવાની સુવિધા અથવા હૉટસ્પૉટ સક્રિય છે" + "સેટઅપ કરવા માટે ટૅપ કરો." + "ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરી છે" + "વિગતો માટે તમારા વ્યવસ્થાપકનો સંપર્ક કરો" + "હૉટસ્પૉટ અને ઇન્ટરનેટ શેર કરવાની સુવિધાનું સ્ટેટસ" + + + + + diff --git a/Tethering/res/values-hi/strings.xml b/Tethering/res/values-hi/strings.xml index 12f7961dc0..08af81b826 100644 --- a/Tethering/res/values-hi/strings.xml +++ b/Tethering/res/values-hi/strings.xml @@ -16,14 +16,14 @@ - "टेदरिंग या हॉटस्पॉट चालू है" - "सेट अप करने के लिए टैप करें." - "टेदरिंग बंद है" - "जानकारी के लिए अपने एडमिन से संपर्क करें" - "हॉटस्पॉट और टेदरिंग की स्थिति" - - - - - + "टेदरिंग या हॉटस्पॉट चालू है" + "सेट अप करने के लिए टैप करें." + "टेदरिंग बंद है" + "जानकारी के लिए अपने एडमिन से संपर्क करें" + "हॉटस्पॉट और टेदरिंग की स्थिति" + + + + + diff --git a/Tethering/res/values-hr/strings.xml b/Tethering/res/values-hr/strings.xml index 19b7b45b07..827c135f20 100644 --- a/Tethering/res/values-hr/strings.xml +++ b/Tethering/res/values-hr/strings.xml @@ -16,14 +16,14 @@ - "Modemsko povezivanje ili žarišna točka aktivni" - "Dodirnite da biste ih postavili." - "Modemsko je povezivanje onemogućeno" - "Obratite se administratoru da biste saznali pojedinosti" - "Status žarišne točke i modemskog povezivanja" - - - - - + "Modemsko povezivanje ili žarišna točka aktivni" + "Dodirnite da biste postavili." + "Modemsko je povezivanje onemogućeno" + "Obratite se administratoru da biste saznali pojedinosti" + "Status žarišne točke i modemskog povezivanja" + + + + + diff --git a/Tethering/res/values-hu/strings.xml b/Tethering/res/values-hu/strings.xml index 419f434ad8..eb68d6babf 100644 --- a/Tethering/res/values-hu/strings.xml +++ b/Tethering/res/values-hu/strings.xml @@ -16,14 +16,14 @@ - "Az internetmegosztás vagy a hotspot aktív" - "Koppintson a beállításhoz." - "Az internetmegosztás le van tiltva" - "A részletekért forduljon rendszergazdájához" - "Hotspot és internetmegosztás állapota" - - - - - + "Megosztás vagy aktív hotspot" + "Koppintson a beállításhoz." + "Az internetmegosztás le van tiltva" + "A részletekért forduljon rendszergazdájához" + "Hotspot és internetmegosztás állapota" + + + + + diff --git a/Tethering/res/values-hy/strings.xml b/Tethering/res/values-hy/strings.xml index c8842b6880..912941e538 100644 --- a/Tethering/res/values-hy/strings.xml +++ b/Tethering/res/values-hy/strings.xml @@ -16,14 +16,14 @@ - "Մոդեմի ռեժիմը միացված է" - "Հպեք՝ կարգավորելու համար։" - "Մոդեմի ռեժիմն անջատված է" - "Մանրամասների համար դիմեք ձեր ադմինիստրատորին" - "Թեժ կետի և մոդեմի ռեժիմի կարգավիճակը" - - - - - + "Մոդեմի ռեժիմը միացված է" + "Հպեք՝ կարգավորելու համար։" + "Մոդեմի ռեժիմն անջատված է" + "Մանրամասների համար դիմեք ձեր ադմինիստրատորին" + "Թեժ կետի և մոդեմի ռեժիմի կարգավիճակը" + + + + + diff --git a/Tethering/res/values-in/strings.xml b/Tethering/res/values-in/strings.xml index 4ae35d43a7..a4e175a439 100644 --- a/Tethering/res/values-in/strings.xml +++ b/Tethering/res/values-in/strings.xml @@ -16,14 +16,14 @@ - "Tethering atau hotspot aktif" - "Ketuk untuk menyiapkan." - "Tethering dinonaktifkan" - "Hubungi admin untuk mengetahui detailnya" - "Status hotspot & tethering" - - - - - + "Tethering atau hotspot aktif" + "Ketuk untuk menyiapkan." + "Tethering dinonaktifkan" + "Hubungi admin untuk mengetahui detailnya" + "Status hotspot & tethering" + + + + + diff --git a/Tethering/res/values-is/strings.xml b/Tethering/res/values-is/strings.xml index df69fb4df4..e9f6670bcd 100644 --- a/Tethering/res/values-is/strings.xml +++ b/Tethering/res/values-is/strings.xml @@ -16,14 +16,14 @@ - "Kveikt á tjóðrun eða heitum reit" - "Ýttu til að setja upp." - "Slökkt er á tjóðrun" - "Hafðu samband við stjórnanda til að fá upplýsingar" - "Staða heits reits og tjóðrunar" - - - - - + "Kveikt á tjóðrun eða aðgangsstað" + "Ýttu til að setja upp." + "Slökkt er á tjóðrun" + "Hafðu samband við kerfisstjórann til að fá upplýsingar" + "Staða heits reits og tjóðrunar" + + + + + diff --git a/Tethering/res/values-it/strings.xml b/Tethering/res/values-it/strings.xml index b13ee9262c..ffb9196f5e 100644 --- a/Tethering/res/values-it/strings.xml +++ b/Tethering/res/values-it/strings.xml @@ -16,14 +16,14 @@ - "Hotspot o tethering attivo" - "Tocca per impostare." - "Tethering disattivato" - "Contatta il tuo amministratore per avere informazioni dettagliate" - "Stato hotspot e tethering" - - - - - + "Hotspot o tethering attivo" + "Tocca per impostare." + "Tethering disattivato" + "Contatta il tuo amministratore per avere informazioni dettagliate" + "Stato hotspot e tethering" + + + + + diff --git a/Tethering/res/values-iw/strings.xml b/Tethering/res/values-iw/strings.xml index f7fb4d51ce..7adcb47350 100644 --- a/Tethering/res/values-iw/strings.xml +++ b/Tethering/res/values-iw/strings.xml @@ -16,14 +16,14 @@ - "שיתוף האינטרנט או הנקודה לשיתוף אינטרנט פעילים" - "יש להקיש כדי להגדיר." - "שיתוף האינטרנט בין מכשירים מושבת" - "לפרטים, יש לפנות לאדמין" - "סטטוס של נקודה לשיתוף אינטרנט ושיתוף אינטרנט בין מכשירים" - - - - - + "נקודה לשיתוף אינטרנט או שיתוף אינטרנט בין מכשירים: בסטטוס פעיל" + "יש להקיש כדי להגדיר." + "שיתוף האינטרנט בין מכשירים מושבת" + "לפרטים, יש לפנות למנהל המערכת" + "סטטוס של נקודה לשיתוף אינטרנט ושיתוף אינטרנט בין מכשירים" + + + + + diff --git a/Tethering/res/values-ja/strings.xml b/Tethering/res/values-ja/strings.xml index 172e7716bf..f68a73010b 100644 --- a/Tethering/res/values-ja/strings.xml +++ b/Tethering/res/values-ja/strings.xml @@ -16,14 +16,14 @@ - "テザリングまたはアクセス ポイントが有効です" - "タップしてセットアップしてください。" - "テザリングは無効に設定されています" - "詳しくは、管理者にお問い合わせください" - "アクセス ポイントとテザリングのステータス" - - - - - + "テザリングまたはアクセス ポイントが有効です" + "タップしてセットアップします。" + "テザリングは無効に設定されています" + "詳しくは、管理者にお問い合わせください" + "アクセス ポイントとテザリングのステータス" + + + + + diff --git a/Tethering/res/values-ka/strings.xml b/Tethering/res/values-ka/strings.xml index b4e119177d..7c22e82bd3 100644 --- a/Tethering/res/values-ka/strings.xml +++ b/Tethering/res/values-ka/strings.xml @@ -16,14 +16,14 @@ - "ტეტერინგი ან უსადენო ქსელი აქტიურია" - "შეეხეთ დასაყენებლად." - "ტეტერინგი გათიშულია" - "დამატებითი ინფორმაციისთვის დაუკავშირდით თქვენს ადმინისტრატორს" - "უსადენო ქსელის და ტეტერინგის სტატუსი" - - - - - + "ტეტერინგი ან უსადენო ქსელი აქტიურია" + "შეეხეთ დასაყენებლად." + "ტეტერინგი გათიშულია" + "დამატებითი ინფორმაციისთვის დაუკავშირდით თქვენს ადმინისტრატორს" + "უსადენო ქსელის და ტეტერინგის სტატუსი" + + + + + diff --git a/Tethering/res/values-kk/strings.xml b/Tethering/res/values-kk/strings.xml index 01163811ab..0857d06de2 100644 --- a/Tethering/res/values-kk/strings.xml +++ b/Tethering/res/values-kk/strings.xml @@ -16,14 +16,14 @@ - "Тетеринг немесе хотспот іске қосылған" - "Реттеу үшін түртіңіз." - "Тетеринг өшірілді." - "Мәлімет алу үшін әкімшіге хабарласыңыз." - "Хотспот және тетеринг күйі" - - - - - + "Тетеринг немесе хотспот қосулы" + "Реттеу үшін түртіңіз." + "Тетеринг өшірілді." + "Мәліметтерді әкімшіден алыңыз." + "Хотспот және тетеринг күйі" + + + + + diff --git a/Tethering/res/values-km/strings.xml b/Tethering/res/values-km/strings.xml index 52667e8260..536e3d1703 100644 --- a/Tethering/res/values-km/strings.xml +++ b/Tethering/res/values-km/strings.xml @@ -16,14 +16,14 @@ - "ការភ្ជាប់ ឬហតស្ប៉ត​កំពុងដំណើរការ" - "ចុច​ដើម្បី​រៀបចំ។" - "ការភ្ជាប់​ត្រូវបានបិទ" - "ទាក់ទងអ្នកគ្រប់គ្រង​របស់អ្នក ដើម្បីទទួលបានព័ត៌មានលម្អិត" - "ស្ថានភាពនៃការភ្ជាប់ និងហតស្ប៉ត" - - - - - + "ការភ្ជាប់ ឬហតស្ប៉ត​កំពុងដំណើរការ" + "ចុច​ដើម្បី​រៀបចំ។" + "ការភ្ជាប់​ត្រូវបានបិទ" + "ទាក់ទងអ្នកគ្រប់គ្រង​របស់អ្នក ដើម្បីទទួលបានព័ត៌មានលម្អិត" + "ស្ថានភាពនៃការភ្ជាប់ និងហតស្ប៉ត" + + + + + diff --git a/Tethering/res/values-kn/strings.xml b/Tethering/res/values-kn/strings.xml index a0a3607f1a..32f54926f4 100644 --- a/Tethering/res/values-kn/strings.xml +++ b/Tethering/res/values-kn/strings.xml @@ -16,14 +16,14 @@ - "ಟೆಥರಿಂಗ್ ಅಥವಾ ಹಾಟ್‌ಸ್ಪಾಟ್ ಸಕ್ರಿಯವಾಗಿದೆ" - "ಸೆಟಪ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ." - "ಟೆಥರಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" - "ವಿವರಗಳಿಗಾಗಿ ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ" - "ಹಾಟ್‌ಸ್ಪಾಟ್ ಮತ್ತು ಟೆಥರಿಂಗ್‌ ಸ್ಥಿತಿ" - - - - - + "ಟೆಥರಿಂಗ್ ಅಥವಾ ಹಾಟ್‌ಸ್ಪಾಟ್ ಸಕ್ರಿಯವಾಗಿದೆ" + "ಸೆಟಪ್ ಮಾಡಲು ಟ್ಯಾಪ್ ಮಾಡಿ." + "ಟೆಥರಿಂಗ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ" + "ವಿವರಗಳಿಗಾಗಿ ನಿಮ್ಮ ನಿರ್ವಾಹಕರನ್ನು ಸಂಪರ್ಕಿಸಿ" + "ಹಾಟ್‌ಸ್ಪಾಟ್ ಮತ್ತು ಟೆಥರಿಂಗ್‌ ಸ್ಥಿತಿ" + + + + + diff --git a/Tethering/res/values-ko/strings.xml b/Tethering/res/values-ko/strings.xml index f7b8da06b7..156b24786d 100644 --- a/Tethering/res/values-ko/strings.xml +++ b/Tethering/res/values-ko/strings.xml @@ -16,14 +16,14 @@ - "테더링 또는 핫스팟 사용 중" - "설정하려면 탭하세요." - "테더링이 사용 중지됨" - "자세한 정보는 관리자에게 문의하세요." - "핫스팟 및 테더링 상태" - - - - - + "테더링 또는 핫스팟 사용" + "설정하려면 탭하세요." + "테더링이 사용 중지됨" + "자세한 정보는 관리자에게 문의하세요." + "핫스팟 및 테더링 상태" + + + + + diff --git a/Tethering/res/values-ky/strings.xml b/Tethering/res/values-ky/strings.xml index 35e645305e..18ee5fd357 100644 --- a/Tethering/res/values-ky/strings.xml +++ b/Tethering/res/values-ky/strings.xml @@ -16,14 +16,14 @@ - "Модем режими күйүп турат" - "Тууралоо үчүн басыңыз." - "Модем режими өчүк" - "Кеңири маалымат үчүн администраторуңузга кайрылыңыз" - "Хотспот жана байланыш түйүнүүн статусу" - - - - - + "Модем режими күйүп турат" + "Жөндөө үчүн таптап коюңуз." + "Телефонду модем катары колдонууга болбойт" + "Кеңири маалымат үчүн администраторуңузга кайрылыңыз" + "Байланыш түйүнүнүн жана модем режиминин статусу" + + + + + diff --git a/Tethering/res/values-lo/strings.xml b/Tethering/res/values-lo/strings.xml index 046551d1a7..b12767018c 100644 --- a/Tethering/res/values-lo/strings.xml +++ b/Tethering/res/values-lo/strings.xml @@ -16,14 +16,14 @@ - "ການປ່ອຍສັນຍານ ຫຼື ຮັອດສະປອດເປີດນຳໃຊ້ຢູ່" - "ແຕະເພື່ອຕັ້ງຄ່າ." - "ການປ່ອຍສັນຍານຖືກປິດໄວ້" - "ຕິດຕໍ່ຜູ້ເບິ່ງແຍງລະບົບຂອງທ່ານສຳລັບລາຍລະອຽດ" - "ສະຖານະຮັອດສະປອດ ແລະ ການປ່ອຍສັນຍານ" - - - - - + "ເປີດການປ່ອຍສັນຍານ ຫຼື ຮັອດສະປອດແລ້ວ" + "ແຕະເພື່ອຕັ້ງຄ່າ." + "ການປ່ອຍສັນຍານຖືກປິດໄວ້" + "ຕິດຕໍ່ຜູ້ເບິ່ງແຍງລະບົບສຳລັບລາຍລະອຽດ" + "ສະຖານະຮັອດສະປອດ ແລະ ການປ່ອຍສັນຍານ" + + + + + diff --git a/Tethering/res/values-lt/strings.xml b/Tethering/res/values-lt/strings.xml index c6853182ff..8427baf39f 100644 --- a/Tethering/res/values-lt/strings.xml +++ b/Tethering/res/values-lt/strings.xml @@ -16,14 +16,14 @@ - "Įrenginys naudojamas kaip modemas arba įjungtas viešosios interneto prieigos taškas" - "Palieskite, kad nustatytumėte." - "Įrenginio kaip modemo naudojimas išjungtas" - "Jei reikia išsamios informacijos, susisiekite su administratoriumi" - "Viešosios interneto prieigos taško ir įrenginio kaip modemo naudojimo būsena" - - - - - + "Įrenginys naudojamas kaip modemas arba įjungtas viešosios interneto prieigos taškas" + "Palieskite, kad nustatytumėte." + "Įrenginio kaip modemo naudojimas išjungtas" + "Jei reikia išsamios informacijos, susisiekite su administratoriumi" + "Viešosios interneto prieigos taško ir įrenginio kaip modemo naudojimo būsena" + + + + + diff --git a/Tethering/res/values-lv/strings.xml b/Tethering/res/values-lv/strings.xml index fd8751c191..aa2d6990e0 100644 --- a/Tethering/res/values-lv/strings.xml +++ b/Tethering/res/values-lv/strings.xml @@ -16,14 +16,14 @@ - "Piesaiste vai tīklājs ir aktīvs" - "Pieskarieties, lai iestatītu." - "Piesaiste ir atspējota" - "Lai iegūtu detalizētu informāciju, sazinieties ar savu administratoru." - "Tīklāja un piesaistes statuss" - - - - - + "Piesaiste vai tīklājs ir aktīvs." + "Pieskarieties, lai to iestatītu." + "Piesaiste ir atspējota" + "Lai iegūtu detalizētu informāciju, sazinieties ar savu administratoru." + "Tīklāja un piesaistes statuss" + + + + + diff --git a/Tethering/res/values-mcc310-mnc004-af/strings.xml b/Tethering/res/values-mcc310-mnc004-af/strings.xml index 216c02ccce..19d659c6ce 100644 --- a/Tethering/res/values-mcc310-mnc004-af/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-af/strings.xml @@ -16,9 +16,9 @@ - "Verbinding het nie internet nie" - "Toestelle kan nie koppel nie" - "Skakel verbinding af" - "Warmkol of verbinding is aan" - "Bykomende heffings kan geld terwyl jy swerf" + "Verbinding het nie internet nie" + "Toestelle kan nie koppel nie" + "Skakel verbinding af" + "Warmkol of verbinding is aan" + "Bykomende heffings kan geld terwyl jy swerf" diff --git a/Tethering/res/values-mcc310-mnc004-am/strings.xml b/Tethering/res/values-mcc310-mnc004-am/strings.xml index 666630ab99..8995430b4f 100644 --- a/Tethering/res/values-mcc310-mnc004-am/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-am/strings.xml @@ -16,9 +16,9 @@ - "እንደ ሞደም መሰካት ምንም በይነመረብ የለውም" - "መሣሪያዎችን ማገናኘት አልተቻልም" - "እንደ ሞደም መሰካትን አጥፋ" - "መገናኛ ነጥብ ወይም እንደ ሞደም መሰካት በርቷል" - "በሚያንዣብብበት ጊዜ ተጨማሪ ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ" + "ማስተሳሰር ምንም በይነመረብ የለውም" + "መሣሪያዎችን ማገናኘት አይቻልም" + "ማስተሳሰርን አጥፋ" + "መገናኛ ነጥብ ወይም ማስተሳሰር በርቷል" + "በሚያንዣብብበት ጊዜ ተጨማሪ ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ" diff --git a/Tethering/res/values-mcc310-mnc004-ar/strings.xml b/Tethering/res/values-mcc310-mnc004-ar/strings.xml index 2859803ed9..54f3b5389a 100644 --- a/Tethering/res/values-mcc310-mnc004-ar/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-ar/strings.xml @@ -16,9 +16,9 @@ - "ما مِن اتصال بالإنترنت خلال التوصيل" - "يتعذّر اتصال الأجهزة" - "إيقاف التوصيل" - "نقطة الاتصال مفعَّلة أو التوصيل مفعَّل" - "قد يتم تحصيل رسوم إضافية أثناء التجوال." + "ما مِن اتصال بالإنترنت خلال التوصيل" + "تعذّر اتصال الأجهزة" + "إيقاف التوصيل" + "نقطة الاتصال أو التوصيل مفعّلان" + "قد يتم تطبيق رسوم إضافية أثناء التجوال." diff --git a/Tethering/res/values-mcc310-mnc004-as/strings.xml b/Tethering/res/values-mcc310-mnc004-as/strings.xml index 360c8caf42..e215141c9e 100644 --- a/Tethering/res/values-mcc310-mnc004-as/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-as/strings.xml @@ -16,9 +16,9 @@ - "টে’ডাৰিঙৰ ইণ্টাৰনেট নাই" - "ডিভাইচসমূহ সংযোগ কৰিব নোৱাৰি" - "টে’ডাৰিং অফ কৰক" - "হ’টস্প’ট অথবা টে’ডাৰিং অন আছে" - "ৰ’মিঙত থাকিলে অতিৰিক্ত মাচুল প্ৰযোজ্য হ’ব পাৰে" + "টে\'ডাৰিঙৰ ইণ্টাৰনেট নাই" + "ডিভাইচসমূহ সংযোগ কৰিব নোৱাৰি" + "টে\'ডাৰিং অফ কৰক" + "হটস্পট অথবা টে\'ডাৰিং অন আছে" + "ৰ\'মিঙত থাকিলে অতিৰিক্ত মাচুল প্ৰযোজ্য হ’ব পাৰে" diff --git a/Tethering/res/values-mcc310-mnc004-az/strings.xml b/Tethering/res/values-mcc310-mnc004-az/strings.xml index b7fdd70fcc..1fd8e4c963 100644 --- a/Tethering/res/values-mcc310-mnc004-az/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-az/strings.xml @@ -16,9 +16,9 @@ - "Modem rejimi internetə qoşulmayıb" - "Cihazları qoşmaq olmur" - "Modem rejimini deaktiv edin" - "Hotspot və ya modem rejimi aktivdir" - "Rouminq zamanı əlavə ödəniş çıxıla bilər" + "Modemin internetə girişi yoxdur" + "Cihazları qoşmaq mümkün deyil" + "Modemi deaktiv edin" + "Hotspot və ya modem aktivdir" + "Rouminq zamanı əlavə ödənişlər tətbiq edilə bilər" diff --git a/Tethering/res/values-mcc310-mnc004-b+sr+Latn/strings.xml b/Tethering/res/values-mcc310-mnc004-b+sr+Latn/strings.xml index a214f938c3..1abe4f3aa3 100644 --- a/Tethering/res/values-mcc310-mnc004-b+sr+Latn/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-b+sr+Latn/strings.xml @@ -16,9 +16,9 @@ - "Privezivanje nema pristup internetu" - "Povezivanje uređaja nije uspelo" - "Isključi privezivanje" - "Uključen je hotspot ili privezivanje" - "Možda važe dodatni troškovi u romingu" + "Privezivanje nema pristup internetu" + "Povezivanje uređaja nije uspelo" + "Isključi privezivanje" + "Uključen je hotspot ili privezivanje" + "Možda važe dodatni troškovi u romingu" diff --git a/Tethering/res/values-mcc310-mnc004-be/strings.xml b/Tethering/res/values-mcc310-mnc004-be/strings.xml index 316e856205..38dbd1e391 100644 --- a/Tethering/res/values-mcc310-mnc004-be/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-be/strings.xml @@ -16,9 +16,9 @@ - "Рэжым мадэма выкарыстоўваецца без доступу да інтэрнэту" - "Не ўдалося падключыць прылады" - "Выключыць рэжым мадэма" - "Хот-спот або рэжым мадэма ўключаны" - "Пры выкарыстанні роўмінгу можа спаганяцца дадатковая плата" + "Рэжым мадэма выкарыстоўваецца без доступу да інтэрнэту" + "Не ўдалося падключыць прылады" + "Выключыць рэжым мадэма" + "Хот-спот або рэжым мадэма ўключаны" + "Пры выкарыстанні роўмінгу можа спаганяцца дадатковая плата" diff --git a/Tethering/res/values-mcc310-mnc004-bg/strings.xml b/Tethering/res/values-mcc310-mnc004-bg/strings.xml index a31c06a6ce..04b44db5c1 100644 --- a/Tethering/res/values-mcc310-mnc004-bg/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-bg/strings.xml @@ -16,9 +16,9 @@ - "Няма връзка с интернет за тетъринг" - "Устройствата не могат да установят връзка" - "Изключване на функцията за тетъринг" - "Точката за достъп или функцията за тетъринг са включени" - "Възможно е да ви бъдат начислени допълнителни такси при роуминг" + "Тетърингът няма връзка с интернет" + "Устройствата не могат да установят връзка" + "Изключване на тетъринга" + "Точката за достъп или тетърингът са включени" + "Възможно е да ви бъдат начислени допълнителни такси при роуминг" diff --git a/Tethering/res/values-mcc310-mnc004-bn/strings.xml b/Tethering/res/values-mcc310-mnc004-bn/strings.xml index f49b14c41c..579d1be1c1 100644 --- a/Tethering/res/values-mcc310-mnc004-bn/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-bn/strings.xml @@ -16,9 +16,9 @@ - "টেথারিং করার জন্য কোনও ইন্টারনেট কানেকশন লাগে না" - "ডিভাইস কানেক্ট করা যাচ্ছে না" - "টেথারিং বন্ধ করুন" - "হটস্পট বা টেথারিং চালু আছে" - "রোমিংয়ে থাকার সময় অতিরিক্ত চার্জ লাগতে পারে" + "টিথারিং করার জন্য কোনও ইন্টারনেট কানেকশন নেই" + "ডিভাইস কানেক্ট করতে পারছে না" + "টিথারিং বন্ধ করুন" + "হটস্পট বা টিথারিং চালু আছে" + "রোমিংয়ের সময় অতিরিক্ত চার্জ করা হতে পারে" diff --git a/Tethering/res/values-mcc310-mnc004-bs/strings.xml b/Tethering/res/values-mcc310-mnc004-bs/strings.xml index ed269c6982..9ce3efe6c3 100644 --- a/Tethering/res/values-mcc310-mnc004-bs/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-bs/strings.xml @@ -16,9 +16,9 @@ - "Dijeljenje internetske veze nema internet" - "Nije moguće povezati uređaje" - "Isključi dijeljenje internetske veze" - "Pristupna tačka ili dijeljenje internetske veze su uključeni" - "Mogu nastati dodatni troškovi u romingu" + "Povezivanje putem mobitela nema internet" + "Uređaji se ne mogu povezati" + "Isključi povezivanje putem mobitela" + "Pristupna tačka ili povezivanje putem mobitela je uključeno" + "Mogu nastati dodatni troškovi u romingu" diff --git a/Tethering/res/values-mcc310-mnc004-ca/strings.xml b/Tethering/res/values-mcc310-mnc004-ca/strings.xml index 0826f4ee35..46d4c35b9b 100644 --- a/Tethering/res/values-mcc310-mnc004-ca/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-ca/strings.xml @@ -16,9 +16,9 @@ - "La compartició de xarxa no té accés a Internet" - "No es poden connectar els dispositius" - "Desactiva la compartició de xarxa" - "El punt d\'accés Wi‑Fi o la compartició de xarxa estan activats" - "És possible que s\'apliquin costos addicionals en itinerància" + "La compartició de xarxa no té accés a Internet" + "No es poden connectar els dispositius" + "Desactiva la compartició de xarxa" + "S\'ha activat el punt d\'accés Wi‑Fi o la compartició de xarxa" + "És possible que s\'apliquin costos addicionals en itinerància" diff --git a/Tethering/res/values-mcc310-mnc004-cs/strings.xml b/Tethering/res/values-mcc310-mnc004-cs/strings.xml index 6899f711b6..cc13860b3d 100644 --- a/Tethering/res/values-mcc310-mnc004-cs/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-cs/strings.xml @@ -16,9 +16,9 @@ - "Tethering nemá připojení k internetu" - "Zařízení se nemůžou připojit" - "Vypnout tethering" - "Je zapnutý hotspot nebo tethering" - "Při roamingu mohou být účtovány dodatečné poplatky" + "Tethering nemá připojení k internetu" + "Zařízení se nemůžou připojit" + "Vypnout tethering" + "Je zapnutý hotspot nebo tethering" + "Při roamingu mohou být účtovány dodatečné poplatky" diff --git a/Tethering/res/values-mcc310-mnc004-da/strings.xml b/Tethering/res/values-mcc310-mnc004-da/strings.xml index dbca93b5cb..92c3ae1156 100644 --- a/Tethering/res/values-mcc310-mnc004-da/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-da/strings.xml @@ -16,9 +16,9 @@ - "Netdeling har ingen internetforbindelse" - "Enheder kan ikke oprette forbindelse" - "Deaktiver netdeling" - "Hotspot eller netdeling er aktiveret" - "Der opkræves muligvis yderligere gebyrer ved roaming" + "Netdeling har ingen internetforbindelse" + "Enheder kan ikke oprette forbindelse" + "Deaktiver netdeling" + "Hotspot eller netdeling er aktiveret" + "Der opkræves muligvis yderligere gebyrer ved roaming" diff --git a/Tethering/res/values-mcc310-mnc004-de/strings.xml b/Tethering/res/values-mcc310-mnc004-de/strings.xml index 139b4e0b0c..967eb4db2e 100644 --- a/Tethering/res/values-mcc310-mnc004-de/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-de/strings.xml @@ -16,9 +16,9 @@ - "Tethering hat keinen Internetzugriff" - "Geräte können sich nicht verbinden" - "Tethering deaktivieren" - "Hotspot oder Tethering ist aktiviert" - "Für das Roaming können zusätzliche Gebühren anfallen" + "Tethering hat keinen Internetzugriff" + "Geräte können sich nicht verbinden" + "Tethering deaktivieren" + "Hotspot oder Tethering ist aktiviert" + "Für das Roaming können zusätzliche Gebühren anfallen" diff --git a/Tethering/res/values-mcc310-mnc004-el/strings.xml b/Tethering/res/values-mcc310-mnc004-el/strings.xml index d778b03aa7..5fb497451f 100644 --- a/Tethering/res/values-mcc310-mnc004-el/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-el/strings.xml @@ -16,9 +16,9 @@ - "Η σύνδεση δεν έχει πρόσβαση στο διαδίκτυο" - "Δεν είναι δυνατή η σύνδεση των συσκευών" - "Απενεργοποίηση σύνδεσης" - "Ενεργό σημείο πρόσβασης Wi-Fi ή ενεργή σύνδεση" - "Ενδέχεται να ισχύουν επιπλέον χρεώσεις κατά την περιαγωγή." + "Η σύνδεση δεν έχει πρόσβαση στο διαδίκτυο" + "Δεν είναι δυνατή η σύνδεση των συσκευών" + "Απενεργοποιήστε τη σύνδεση" + "Ενεργό σημείο πρόσβασης Wi-Fi ή ενεργή σύνδεση" + "Ενδέχεται να ισχύουν επιπλέον χρεώσεις κατά την περιαγωγή." diff --git a/Tethering/res/values-mcc310-mnc004-en-rAU/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rAU/strings.xml index bc68d0088e..45647f93f2 100644 --- a/Tethering/res/values-mcc310-mnc004-en-rAU/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-en-rAU/strings.xml @@ -16,9 +16,9 @@ - "Tethering has no Internet" - "Devices can\'t connect" - "Turn off tethering" - "Hotspot or tethering is on" - "Additional charges may apply while roaming" + "Tethering has no Internet" + "Devices can’t connect" + "Turn off tethering" + "Hotspot or tethering is on" + "Additional charges may apply while roaming" diff --git a/Tethering/res/values-mcc310-mnc004-en-rCA/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rCA/strings.xml index 4f39489aaa..45647f93f2 100644 --- a/Tethering/res/values-mcc310-mnc004-en-rCA/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-en-rCA/strings.xml @@ -16,9 +16,9 @@ - "Tethering has no internet" - "Devices can’t connect" - "Turn off tethering" - "Hotspot or tethering is on" - "Additional charges may apply while roaming" + "Tethering has no Internet" + "Devices can’t connect" + "Turn off tethering" + "Hotspot or tethering is on" + "Additional charges may apply while roaming" diff --git a/Tethering/res/values-mcc310-mnc004-en-rGB/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rGB/strings.xml index bc68d0088e..45647f93f2 100644 --- a/Tethering/res/values-mcc310-mnc004-en-rGB/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-en-rGB/strings.xml @@ -16,9 +16,9 @@ - "Tethering has no Internet" - "Devices can\'t connect" - "Turn off tethering" - "Hotspot or tethering is on" - "Additional charges may apply while roaming" + "Tethering has no Internet" + "Devices can’t connect" + "Turn off tethering" + "Hotspot or tethering is on" + "Additional charges may apply while roaming" diff --git a/Tethering/res/values-mcc310-mnc004-en-rIN/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rIN/strings.xml index bc68d0088e..45647f93f2 100644 --- a/Tethering/res/values-mcc310-mnc004-en-rIN/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-en-rIN/strings.xml @@ -16,9 +16,9 @@ - "Tethering has no Internet" - "Devices can\'t connect" - "Turn off tethering" - "Hotspot or tethering is on" - "Additional charges may apply while roaming" + "Tethering has no Internet" + "Devices can’t connect" + "Turn off tethering" + "Hotspot or tethering is on" + "Additional charges may apply while roaming" diff --git a/Tethering/res/values-mcc310-mnc004-en-rXC/strings.xml b/Tethering/res/values-mcc310-mnc004-en-rXC/strings.xml index be00edfb06..7877074afc 100644 --- a/Tethering/res/values-mcc310-mnc004-en-rXC/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-en-rXC/strings.xml @@ -16,9 +16,9 @@ - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‏‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‎‎Tethering has no internet‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‎‏‎‎‏‎‏‏‎‏‏‏‏‎‏‎‏‏‏‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‎‎‎‎‎‏‎Devices can’t connect‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‎‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‎‏‎‏‏‏‎‏‎‎‏‏‏‎‎‏‎‎Turn off tethering‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‏‎‏‎‏‎‎‎‎‎‏‎‏‏‎‎‏‏‎‏‏‏‎‏‎‎‏‎‎‏‏‏‎‏‎‏‏‎‎‏‎‏‎‏‎Hotspot or tethering is on‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‏‎‎‏‏‏‏‎‎‎‏‎‏‏‎‏‏‎‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‏‏‎‎‎‎‎‏‏‎‏‎‎‎‏‎‎‎‎Additional charges may apply while roaming‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‏‏‎‎‏‏‏‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‎‎‎Tethering has no internet‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‏‎‏‎‏‏‏‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‎‎‎‎‏‏‏‎‎‏‏‎‏‏‎‎‏‎‏‎‎‎‎‏‏‏‎Devices can’t connect‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‏‏‎‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‏‎‎‎‏‏‎‎‏‏‎‏‎‏‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‎Turn off tethering‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‏‎‏‎‏‎‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‎‎‎‎‎‎‏‎‎‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‎‏‏‏‎Hotspot or tethering is on‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‏‎‎‎‏‏‏‏‏‎‏‎‏‎‏‏‏‏‎‎Additional charges may apply while roaming‎‏‎‎‏‎" diff --git a/Tethering/res/values-mcc310-mnc004-es-rUS/strings.xml b/Tethering/res/values-mcc310-mnc004-es-rUS/strings.xml index e00a7a0b90..08edd81a6b 100644 --- a/Tethering/res/values-mcc310-mnc004-es-rUS/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-es-rUS/strings.xml @@ -16,9 +16,9 @@ - "La conexión mediante dispositivo móvil no tiene Internet" - "No se pueden conectar los dispositivos" - "Desactivar conexión mediante dispositivo móvil" - "Se activó el hotspot o la conexión mediante dispositivo móvil" - "Es posible que se apliquen cargos adicionales por roaming" + "La conexión mediante dispositivo móvil no tiene Internet" + "No se pueden conectar los dispositivos" + "Desactivar conexión mediante dispositivo móvil" + "Se activó el hotspot o la conexión mediante dispositivo móvil" + "Es posible que se apliquen cargos adicionales por roaming" diff --git a/Tethering/res/values-mcc310-mnc004-es/strings.xml b/Tethering/res/values-mcc310-mnc004-es/strings.xml index 6c7e9838ae..79f51d00e2 100644 --- a/Tethering/res/values-mcc310-mnc004-es/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-es/strings.xml @@ -16,9 +16,9 @@ - "La conexión no se puede compartir, porque no hay acceso a Internet" - "Los dispositivos no se pueden conectar" - "Desactivar conexión compartida" - "Punto de acceso o conexión compartida activados" - "Pueden aplicarse cargos adicionales en roaming" + "La conexión no se puede compartir, porque no hay acceso a Internet" + "Los dispositivos no se pueden conectar" + "Desactivar conexión compartida" + "Punto de acceso o conexión compartida activados" + "Puede que se apliquen cargos adicionales en itinerancia" diff --git a/Tethering/res/values-mcc310-mnc004-et/strings.xml b/Tethering/res/values-mcc310-mnc004-et/strings.xml index 2f108fc3d9..2da5f8a6d6 100644 --- a/Tethering/res/values-mcc310-mnc004-et/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-et/strings.xml @@ -16,9 +16,9 @@ - "Jagamisel puudub internetiühendus" - "Seadmed ei saa ühendust luua" - "Lülita jagamine välja" - "Kuumkoht või jagamine on sisse lülitatud" - "Rändluse kasutamisega võivad kaasneda lisatasud" + "Jagamisel puudub internetiühendus" + "Seadmed ei saa ühendust luua" + "Lülita jagamine välja" + "Kuumkoht või jagamine on sisse lülitatud" + "Rändluse kasutamisega võivad kaasneda lisatasud" diff --git a/Tethering/res/values-mcc310-mnc004-eu/strings.xml b/Tethering/res/values-mcc310-mnc004-eu/strings.xml index c970dd753f..2073f2806c 100644 --- a/Tethering/res/values-mcc310-mnc004-eu/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-eu/strings.xml @@ -16,9 +16,9 @@ - "Konexioa partekatzeko aukerak ez du Interneteko konexiorik" - "Ezin dira konektatu gailuak" - "Desaktibatu konexioa partekatzeko aukera" - "Wifi-gunea edo konexioa partekatzeko aukera aktibatuta dago" - "Baliteke tarifa gehigarriak ordaindu behar izatea ibiltaritza erabili bitartean" + "Konexioa partekatzeko aukerak ez du Interneteko konexiorik" + "Ezin dira konektatu gailuak" + "Desaktibatu konexioa partekatzeko aukera" + "Wifi-gunea edo konexioa partekatzeko aukera aktibatuta dago" + "Baliteke kostu gehigarriak ordaindu behar izatea ibiltaritzan" diff --git a/Tethering/res/values-mcc310-mnc004-fa/strings.xml b/Tethering/res/values-mcc310-mnc004-fa/strings.xml index 7333e2fc58..e21b2a0852 100644 --- a/Tethering/res/values-mcc310-mnc004-fa/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-fa/strings.xml @@ -16,9 +16,9 @@ - "«اشتراک‌گذاری اینترنت» به اینترنت دسترسی ندارد" - "دستگاه‌ها متصل نمی‌شوند" - "خاموش کردن «اشتراک‌گذاری اینترنت»" - "«نقطه اتصال» یا «اشتراک‌گذاری اینترنت» روشن است" - "هنگام فراگردی ممکن است هزینه‌های اضافی کسر شود" + "«اشتراک‌گذاری اینترنت» به اینترنت دسترسی ندارد" + "دستگاه‌ها متصل نمی‌شوند" + "خاموش کردن «اشتراک‌گذاری اینترنت»" + "«نقطه اتصال» یا «اشتراک‌گذاری اینترنت» روشن است" + "ممکن است درحین فراگردی تغییرات دیگر اعمال شود" diff --git a/Tethering/res/values-mcc310-mnc004-fi/strings.xml b/Tethering/res/values-mcc310-mnc004-fi/strings.xml index 3faed5b1bd..88b0b13eb4 100644 --- a/Tethering/res/values-mcc310-mnc004-fi/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-fi/strings.xml @@ -16,9 +16,9 @@ - "Puhelinta ei voi käyttää modeemina, koska sillä ei ole internet-yhteyttä" - "Laitteet eivät voi muodostaa yhteyttä" - "Laita puhelimen käyttäminen modeemina pois päältä" - "Hotspot tai puhelimen käyttäminen modeemina on päällä" - "Roaming voi aiheuttaa lisämaksuja" + "Ei jaettavaa internetyhteyttä" + "Laitteet eivät voi muodostaa yhteyttä" + "Laita yhteyden jakaminen pois päältä" + "Hotspot tai yhteyden jakaminen on päällä" + "Roaming voi aiheuttaa lisämaksuja" diff --git a/Tethering/res/values-mcc310-mnc004-fr-rCA/strings.xml b/Tethering/res/values-mcc310-mnc004-fr-rCA/strings.xml index 0659c40315..3b781bc8db 100644 --- a/Tethering/res/values-mcc310-mnc004-fr-rCA/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-fr-rCA/strings.xml @@ -16,9 +16,9 @@ - "Le partage de connexion n\'est pas connecté à Internet" - "Impossible de connecter les appareils" - "Désactiver le partage de connexion" - "Le point d\'accès sans fil ou le partage de connexion est activé" - "En itinérance, des frais supplémentaires peuvent s\'appliquer" + "Le partage de connexion n\'est pas connecté à Internet" + "Impossible de connecter les appareils" + "Désactiver le partage de connexion" + "Le point d\'accès ou le partage de connexion est activé" + "En itinérance, des frais supplémentaires peuvent s\'appliquer" diff --git a/Tethering/res/values-mcc310-mnc004-fr/strings.xml b/Tethering/res/values-mcc310-mnc004-fr/strings.xml index 26065f8cf6..51d7203c36 100644 --- a/Tethering/res/values-mcc310-mnc004-fr/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-fr/strings.xml @@ -16,9 +16,9 @@ - "Aucune connexion à Internet disponible pour le partage de connexion" - "Impossible de connecter les appareils" - "Désactiver le partage de connexion" - "Le point d\'accès ou le partage de connexion est activé" - "En itinérance, des frais supplémentaires peuvent s\'appliquer" + "Aucune connexion à Internet n\'est disponible pour le partage de connexion" + "Impossible de connecter les appareils" + "Désactiver le partage de connexion" + "Le point d\'accès ou le partage de connexion est activé" + "En itinérance, des frais supplémentaires peuvent s\'appliquer" diff --git a/Tethering/res/values-mcc310-mnc004-gl/strings.xml b/Tethering/res/values-mcc310-mnc004-gl/strings.xml index 924e71bedd..008ccb475d 100644 --- a/Tethering/res/values-mcc310-mnc004-gl/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-gl/strings.xml @@ -16,9 +16,9 @@ - "A conexión compartida non ten acceso a Internet" - "Non se puideron conectar os dispositivos" - "Desactivar conexión compartida" - "Está activada a zona wifi ou a conexión compartida" - "Pódense aplicar cargos adicionais en itinerancia" + "A conexión compartida non ten Internet" + "Non se puideron conectar os dispositivos" + "Desactivar conexión compartida" + "Está activada a zona wifi ou a conexión compartida" + "Pódense aplicar cargos adicionais en itinerancia" diff --git a/Tethering/res/values-mcc310-mnc004-gu/strings.xml b/Tethering/res/values-mcc310-mnc004-gu/strings.xml index ab446df024..f2e3b4df78 100644 --- a/Tethering/res/values-mcc310-mnc004-gu/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-gu/strings.xml @@ -16,9 +16,9 @@ - "ઇન્ટરનેટ શેર કરવાની સુવિધામાં ઇન્ટરનેટ નથી" - "ડિવાઇસ કનેક્ટ કરી શકાતા નથી" - "ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરો" - "હૉટસ્પૉટ અથવા ઇન્ટરનેટ શેર કરવાની સુવિધા ચાલુ છે" - "રોમિંગ દરમિયાન વધારાના શુલ્ક લાગુ થઈ શકે છે" + "ઇન્ટરનેટ શેર કરવાની સુવિધામાં ઇન્ટરનેટ નથી" + "ડિવાઇસ કનેક્ટ કરી શકાતા નથી" + "ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરો" + "હૉટસ્પૉટ અથવા ઇન્ટરનેટ શેર કરવાની સુવિધા ચાલુ છે" + "રોમિંગમાં વધારાના શુલ્ક લાગી શકે છે" diff --git a/Tethering/res/values-mcc310-mnc004-hi/strings.xml b/Tethering/res/values-mcc310-mnc004-hi/strings.xml index 073a680bf3..b11839d760 100644 --- a/Tethering/res/values-mcc310-mnc004-hi/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-hi/strings.xml @@ -16,9 +16,9 @@ - "टेदरिंग से इंटरनेट नहीं चल रहा है" - "डिवाइस कनेक्ट नहीं हो पा रहे" - "टेदरिंग बंद करें" - "हॉटस्पॉट या टेदरिंग चालू है" - "रोमिंग के दौरान अतिरिक्त शुल्क काटा जा सकता है" + "टेदरिंग से इंटरनेट नहीं चल रहा" + "डिवाइस कनेक्ट नहीं हो पा रहे" + "टेदरिंग बंद करें" + "हॉटस्पॉट या टेदरिंग चालू है" + "रोमिंग के दौरान अतिरिक्त शुल्क लग सकता है" diff --git a/Tethering/res/values-mcc310-mnc004-hr/strings.xml b/Tethering/res/values-mcc310-mnc004-hr/strings.xml index 6cc841538e..0a5aca25b1 100644 --- a/Tethering/res/values-mcc310-mnc004-hr/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-hr/strings.xml @@ -16,9 +16,9 @@ - "Modemsko povezivanje nema internet" - "Uređaji se ne mogu povezati" - "Isključi modemsko povezivanje" - "Uključena je žarišna točka ili modemsko povezivanje" - "U roamingu su mogući dodatni troškovi" + "Modemsko povezivanje nema internet" + "Uređaji se ne mogu povezati" + "Isključivanje modemskog povezivanja" + "Uključena je žarišna točka ili modemsko povezivanje" + "U roamingu su mogući dodatni troškovi" diff --git a/Tethering/res/values-mcc310-mnc004-hu/strings.xml b/Tethering/res/values-mcc310-mnc004-hu/strings.xml index 6ab95651e2..21c689a44e 100644 --- a/Tethering/res/values-mcc310-mnc004-hu/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-hu/strings.xml @@ -16,9 +16,9 @@ - "Nincs internetkapcsolat az internet megosztásához" - "Az eszközök nem tudnak csatlakozni" - "Internetmegosztás kikapcsolása" - "A hotspot vagy az internetmegosztás be van kapcsolva" - "Roaming során további díjak léphetnek fel" + "Nincs internetkapcsolat az internet megosztásához" + "Az eszközök nem tudnak csatlakozni" + "Internetmegosztás kikapcsolása" + "A hotspot vagy az internetmegosztás be van kapcsolva" + "Roaming során további díjak léphetnek fel" diff --git a/Tethering/res/values-mcc310-mnc004-hy/strings.xml b/Tethering/res/values-mcc310-mnc004-hy/strings.xml index 75b1c3e6a7..689d92870e 100644 --- a/Tethering/res/values-mcc310-mnc004-hy/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-hy/strings.xml @@ -16,9 +16,9 @@ - "Մոդեմի ռեժիմի ինտերնետ կապը բացակայում է" - "Չհաջողվեց միացնել սարքերը" - "Անջատել մոդեմի ռեժիմը" - "Թեժ կետը կամ մոդեմի ռեժիմը միացված է" - "Ռոումինգում կարող են լրացուցիչ վճարներ գանձվել" + "Մոդեմի ռեժիմի կապը բացակայում է" + "Չհաջողվեց միացնել սարքը" + "Անջատել մոդեմի ռեժիմը" + "Թեժ կետը կամ մոդեմի ռեժիմը միացված է" + "Ռոումինգում կարող են լրացուցիչ վճարներ գանձվել" diff --git a/Tethering/res/values-mcc310-mnc004-in/strings.xml b/Tethering/res/values-mcc310-mnc004-in/strings.xml index 7289d631f8..a5f4d19abf 100644 --- a/Tethering/res/values-mcc310-mnc004-in/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-in/strings.xml @@ -16,9 +16,9 @@ - "Tidak ada koneksi internet di tethering" - "Perangkat tidak dapat terhubung" - "Nonaktifkan tethering" - "Hotspot atau tethering aktif" - "Biaya tambahan mungkin berlaku saat roaming" + "Tidak ada koneksi internet di tethering" + "Perangkat tidak dapat terhubung" + "Nonaktifkan tethering" + "Hotspot atau tethering aktif" + "Biaya tambahan mungkin berlaku saat roaming" diff --git a/Tethering/res/values-mcc310-mnc004-is/strings.xml b/Tethering/res/values-mcc310-mnc004-is/strings.xml index e2f2f9dff5..fc7e8aaf4e 100644 --- a/Tethering/res/values-mcc310-mnc004-is/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-is/strings.xml @@ -16,9 +16,9 @@ - "Tjóðrun er ekki með internettengingu" - "Tæki geta ekki tengst" - "Slökkva á tjóðrun" - "Kveikt er á heitum reit eða tjóðrun" - "Viðbótargjöld kunna að eiga við í reiki" + "Tjóðrun er ekki með internettengingu" + "Tæki geta ekki tengst" + "Slökkva á tjóðrun" + "Kveikt er á heitum reit eða tjóðrun" + "Viðbótargjöld kunna að eiga við í reiki" diff --git a/Tethering/res/values-mcc310-mnc004-it/strings.xml b/Tethering/res/values-mcc310-mnc004-it/strings.xml index 44805bd151..6456dd1b80 100644 --- a/Tethering/res/values-mcc310-mnc004-it/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-it/strings.xml @@ -16,9 +16,9 @@ - "Nessuna connessione a internet per il tethering" - "Impossibile connettere i dispositivi" - "Disattiva il tethering" - "Hotspot o tethering attivo" - "Potrebbero essere applicati costi aggiuntivi durante il roaming" + "Nessuna connessione a Internet per il tethering" + "Impossibile connettere i dispositivi" + "Disattiva il tethering" + "Hotspot o tethering attivi" + "Potrebbero essere applicati costi aggiuntivi durante il roaming" diff --git a/Tethering/res/values-mcc310-mnc004-iw/strings.xml b/Tethering/res/values-mcc310-mnc004-iw/strings.xml index 061816971e..46b24bd3c5 100644 --- a/Tethering/res/values-mcc310-mnc004-iw/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-iw/strings.xml @@ -16,9 +16,9 @@ - "אי אפשר להפעיל את תכונת שיתוף האינטרנט בין מכשירים כי אין חיבור לאינטרנט" - "למכשירים אין אפשרות להתחבר" - "השבתה של שיתוף האינטרנט בין מכשירים" - "תכונת הנקודה לשיתוף אינטרנט או תכונת שיתוף האינטרנט בין מכשירים פועלת" - "ייתכנו חיובים נוספים במהלך נדידה" + "אי אפשר להפעיל את תכונת שיתוף האינטרנט בין מכשירים כי אין חיבור לאינטרנט" + "למכשירים אין אפשרות להתחבר" + "השבתה של שיתוף האינטרנט בין מכשירים" + "תכונת הנקודה לשיתוף אינטרנט או תכונת שיתוף האינטרנט בין מכשירים פועלת" + "ייתכנו חיובים נוספים בעת נדידה" diff --git a/Tethering/res/values-mcc310-mnc004-ja/strings.xml b/Tethering/res/values-mcc310-mnc004-ja/strings.xml index 344167daff..e6eb277b90 100644 --- a/Tethering/res/values-mcc310-mnc004-ja/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-ja/strings.xml @@ -16,9 +16,9 @@ - "テザリングがインターネットに接続されていません" - "デバイスを接続できません" - "テザリングを OFF にする" - "アクセス ポイントまたはテザリングが ON です" - "ローミング時に追加料金が発生することがあります" + "テザリングがインターネットに接続されていません" + "デバイスを接続できません" + "テザリングを OFF にする" + "アクセス ポイントまたはテザリングが ON です" + "ローミング時に追加料金が発生することがあります" diff --git a/Tethering/res/values-mcc310-mnc004-ka/strings.xml b/Tethering/res/values-mcc310-mnc004-ka/strings.xml index 20db7fc8f2..aeddd7101d 100644 --- a/Tethering/res/values-mcc310-mnc004-ka/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-ka/strings.xml @@ -16,9 +16,9 @@ - "ტეტერინგს არ აქვს ინტერნეტზე წვდომა" - "მოწყობილობები ვერ ახერხებენ დაკავშირებას" - "ტეტერინგის გამორთვა" - "ჩართულია უსადენო ქსელი ან ტეტერინგი" - "როუმინგის გამოყენებისას შეიძლება ჩამოგეჭრათ დამატებითი საფასური" + "ტეტერინგს არ აქვს ინტერნეტზე წვდომა" + "მოწყობილობები ვერ ახერხებენ დაკავშირებას" + "ტეტერინგის გამორთვა" + "ჩართულია უსადენო ქსელი ან ტეტერინგი" + "როუმინგის გამოყენებისას შეიძლება ჩამოგეჭრათ დამატებითი საფასური" diff --git a/Tethering/res/values-mcc310-mnc004-kk/strings.xml b/Tethering/res/values-mcc310-mnc004-kk/strings.xml index 35f1738905..255f0a276f 100644 --- a/Tethering/res/values-mcc310-mnc004-kk/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-kk/strings.xml @@ -16,9 +16,9 @@ - "Тетеринг кезінде интернет байланысы жоқ" - "Құрылғыларды байланыстыру мүмкін емес" - "Тетерингіні өшіру" - "Хотспот немесе тетеринг қосулы" - "Роуминг кезінде қосымша ақы алынуы мүмкін." + "Тетеринг режимі интернет байланысынсыз пайдаланылуда" + "Құрылғыларды байланыстыру мүмкін емес" + "Тетерингіні өшіру" + "Хотспот немесе тетеринг қосулы" + "Роуминг кезінде қосымша ақы алынуы мүмкін." diff --git a/Tethering/res/values-mcc310-mnc004-km/strings.xml b/Tethering/res/values-mcc310-mnc004-km/strings.xml index 2af80b1b3b..2bceb1cf77 100644 --- a/Tethering/res/values-mcc310-mnc004-km/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-km/strings.xml @@ -16,9 +16,9 @@ - "ការភ្ជាប់​មិនមានអ៊ីនធឺណិត​ទេ" - "មិនអាច​ភ្ជាប់ឧបករណ៍​បានទេ" - "បិទការភ្ជាប់" - "ហតស្ប៉ត ឬការភ្ជាប់​ត្រូវបានបើក" - "អាចមាន​ការគិតថ្លៃ​បន្ថែម នៅពេល​រ៉ូមីង" + "ការភ្ជាប់​មិនមានអ៊ីនធឺណិត​ទេ" + "មិនអាច​ភ្ជាប់ឧបករណ៍​បានទេ" + "បិទការភ្ជាប់" + "ហតស្ប៉ត ឬការភ្ជាប់​ត្រូវបានបើក" + "អាចមាន​ការគិតថ្លៃ​បន្ថែម នៅពេល​រ៉ូមីង" diff --git a/Tethering/res/values-mcc310-mnc004-kn/strings.xml b/Tethering/res/values-mcc310-mnc004-kn/strings.xml index 16c55d0373..ed769305a6 100644 --- a/Tethering/res/values-mcc310-mnc004-kn/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-kn/strings.xml @@ -16,9 +16,9 @@ - "ಟೆಥರಿಂಗ್‌ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಕನೆಕ್ಷನ್ ಹೊಂದಿಲ್ಲ" - "ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ" - "ಟೆಥರಿಂಗ್‌ ಆಫ್ ಮಾಡಿ" - "ಹಾಟ್‌ಸ್ಪಾಟ್ ಅಥವಾ ಟೆಥರಿಂಗ್‌ ಆನ್ ಆಗಿದೆ" - "ರೋಮಿಂಗ್‌ನಲ್ಲಿರುವಾಗ ಹೆಚ್ಚುವರಿ ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು" + "ಟೆಥರಿಂಗ್‌ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಕನೆಕ್ಷನ್ ಹೊಂದಿಲ್ಲ" + "ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ" + "ಟೆಥರಿಂಗ್‌ ಆಫ್ ಮಾಡಿ" + "ಹಾಟ್‌ಸ್ಪಾಟ್ ಅಥವಾ ಟೆಥರಿಂಗ್‌ ಆನ್ ಆಗಿದೆ" + "ರೋಮಿಂಗ್‌ನಲ್ಲಿರುವಾಗ ಹೆಚ್ಚುವರಿ ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು" diff --git a/Tethering/res/values-mcc310-mnc004-ko/strings.xml b/Tethering/res/values-mcc310-mnc004-ko/strings.xml index 619504fb6e..6e504941eb 100644 --- a/Tethering/res/values-mcc310-mnc004-ko/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-ko/strings.xml @@ -16,9 +16,9 @@ - "테더링으로 인터넷을 사용할 수 없음" - "기기에서 연결할 수 없음" - "테더링 사용 중지" - "핫스팟 또는 테더링이 켜짐" - "로밍 중에는 추가 요금이 부과될 수 있습니다." + "테더링으로 인터넷을 사용할 수 없음" + "기기에서 연결할 수 없음" + "테더링 사용 중지" + "핫스팟 또는 테더링 켜짐" + "로밍 중에는 추가 요금이 발생할 수 있습니다." diff --git a/Tethering/res/values-mcc310-mnc004-ky/strings.xml b/Tethering/res/values-mcc310-mnc004-ky/strings.xml index f52dd90cdf..d68128b9a5 100644 --- a/Tethering/res/values-mcc310-mnc004-ky/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-ky/strings.xml @@ -16,9 +16,9 @@ - "Модем режими Интернети жок колдонулууда" - "Түзмөктөр туташпай жатат" - "Модем режимин өчүрүү" - "Байланыш түйүнү же модем режими күйүк" - "Роумингде кошумча акы алынышы мүмкүн" + "Модем режими Интернети жок колдонулууда" + "Түзмөктөр туташпай жатат" + "Модем режимин өчүрүү" + "Байланыш түйүнү же модем режими күйүк" + "Роумингде кошумча акы алынышы мүмкүн" diff --git a/Tethering/res/values-mcc310-mnc004-lo/strings.xml b/Tethering/res/values-mcc310-mnc004-lo/strings.xml index d3184f78d7..03e134a0fc 100644 --- a/Tethering/res/values-mcc310-mnc004-lo/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-lo/strings.xml @@ -16,9 +16,9 @@ - "ການປ່ອຍສັນຍານບໍ່ມີອິນເຕີເນັດ" - "ອຸປະກອນບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້" - "ປິດການປ່ອຍສັນຍານ" - "ຮັອດສະປອດ ຫຼື ການປ່ອຍສັນຍານເປີດຢູ່" - "ອາດມີຄ່າບໍລິການເພີ່ມເຕີມໃນລະຫວ່າງການໂຣມມິງ" + "ການປ່ອຍສັນຍານບໍ່ມີອິນເຕີເນັດ" + "ອຸປະກອນບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້" + "ປິດການປ່ອຍສັນຍານ" + "ເປີດໃຊ້ຮັອດສະປອດ ຫຼື ການປ່ອຍສັນຍານຢູ່" + "ອາດມີຄ່າໃຊ້ຈ່າຍເພີ່ມເຕີມໃນລະຫວ່າງການໂຣມມິງ" diff --git a/Tethering/res/values-mcc310-mnc004-lt/strings.xml b/Tethering/res/values-mcc310-mnc004-lt/strings.xml index a07644dc91..652cedc6e6 100644 --- a/Tethering/res/values-mcc310-mnc004-lt/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-lt/strings.xml @@ -16,9 +16,9 @@ - "Nėra įrenginio kaip modemo naudojimo interneto ryšio" - "Nepavyko susieti įrenginių" - "Išjungti įrenginio kaip modemo naudojimą" - "Įjungtas viešosios interneto prieigos taškas arba įrenginio kaip modemo naudojimas" - "Veikiant tarptinkliniam ryšiui gali būti taikomi papildomi mokesčiai" + "Nėra įrenginio kaip modemo naudojimo interneto ryšio" + "Nepavyko susieti įrenginių" + "Išjungti įrenginio kaip modemo naudojimą" + "Įjungtas viešosios interneto prieigos taškas arba įrenginio kaip modemo naudojimas" + "Veikiant tarptinkliniam ryšiui gali būti taikomi papildomi mokesčiai" diff --git a/Tethering/res/values-mcc310-mnc004-lv/strings.xml b/Tethering/res/values-mcc310-mnc004-lv/strings.xml index 5090ecfc05..221972298c 100644 --- a/Tethering/res/values-mcc310-mnc004-lv/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-lv/strings.xml @@ -16,9 +16,9 @@ - "Piesaistei nav interneta savienojuma" - "Nevar savienot ierīces" - "Izslēgt piesaisti" - "Ir ieslēgts tīklājs vai piesaiste" - "Viesabonēšanas laikā var tikt piemērota papildu maksa." + "Piesaistei nav interneta savienojuma" + "Nevar savienot ierīces" + "Izslēgt piesaisti" + "Ir ieslēgts tīklājs vai piesaiste" + "Viesabonēšanas laikā var tikt piemērota papildu samaksa" diff --git a/Tethering/res/values-mcc310-mnc004-mk/strings.xml b/Tethering/res/values-mcc310-mnc004-mk/strings.xml index c95c80e768..227f9e3466 100644 --- a/Tethering/res/values-mcc310-mnc004-mk/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-mk/strings.xml @@ -16,9 +16,9 @@ - "Нема интернет преку мобилен" - "Уредите не може да се поврзат" - "Исклучи интернет преку мобилен" - "Вклучено: точка на пристап или интернет преку мобилен" - "При роаминг може да се наплатат дополнителни трошоци" + "Нема интернет преку мобилен" + "Уредите не може да се поврзат" + "Исклучи интернет преку мобилен" + "Точката на пристап или интернетот преку мобилен е вклучен" + "При роаминг може да се наплатат дополнителни трошоци" diff --git a/Tethering/res/values-mcc310-mnc004-ml/strings.xml b/Tethering/res/values-mcc310-mnc004-ml/strings.xml index 7bad5c1760..ec43885126 100644 --- a/Tethering/res/values-mcc310-mnc004-ml/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-ml/strings.xml @@ -16,9 +16,9 @@ - "ടെതറിംഗിന് ഇന്റർനെറ്റ് ഇല്ല" - "ഉപകരണങ്ങൾ കണക്റ്റ് ചെയ്യാനാവില്ല" - "ടെതറിംഗ് ഓഫാക്കുക" - "ഹോട്ട്‌സ്‌പോട്ട് അല്ലെങ്കിൽ ടെതറിംഗ് ഓണാണ്" - "റോമിംഗ് ചെയ്യുമ്പോൾ അധിക നിരക്കുകൾ ബാധകമായേക്കാം" + "ടെതറിംഗിന് ഇന്റർനെറ്റ് ഇല്ല" + "ഉപകരണങ്ങൾ കണക്റ്റ് ചെയ്യാനാവില്ല" + "ടെതറിംഗ് ഓഫാക്കുക" + "ഹോട്ട്‌സ്‌പോട്ട് അല്ലെങ്കിൽ ടെതറിംഗ് ഓണാണ്" + "റോമിംഗ് ചെയ്യുമ്പോൾ അധിക നിരക്കുകൾ ബാധകമായേക്കാം" diff --git a/Tethering/res/values-mcc310-mnc004-mn/strings.xml b/Tethering/res/values-mcc310-mnc004-mn/strings.xml index ff76236f06..e263573799 100644 --- a/Tethering/res/values-mcc310-mnc004-mn/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-mn/strings.xml @@ -16,9 +16,9 @@ - "Модем болгоход ямар ч интернэт байхгүй байна" - "Төхөөрөмжүүд холбогдох боломжгүй байна" - "Модем болгохыг унтраах" - "Сүлжээний цэг эсвэл модем болгох асаалттай байна" - "Роумингийн үеэр нэмэлт төлбөр тооцогдож магадгүй" + "Модемд интернэт алга байна" + "Төхөөрөмжүүд холбогдох боломжгүй байна" + "Модем болгохыг унтраах" + "Сүлжээний цэг эсвэл модем болгох асаалттай байна" + "Роумингийн үеэр нэмэлт төлбөр нэхэмжилж болзошгүй" diff --git a/Tethering/res/values-mcc310-mnc004-mr/strings.xml b/Tethering/res/values-mcc310-mnc004-mr/strings.xml index 1754dd45d5..adf845d078 100644 --- a/Tethering/res/values-mcc310-mnc004-mr/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-mr/strings.xml @@ -16,9 +16,9 @@ - "टेदरिंगसाठी इंटरनेट नाही" - "डिव्हाइस कनेक्ट होऊ शकत नाहीत" - "टेदरिंग बंद करा" - "हॉटस्पॉट किंवा टेदरिंग सुरू आहे" - "रोमिंगदरम्यान अतिरिक्त शुल्के लागू होऊ शकतात" + "टेदरिंगला इंटरनेट नाही" + "डिव्हाइस कनेक्ट होऊ शकत नाहीत" + "टेदरिंग बंद करा" + "हॉटस्पॉट किंवा टेदरिंग सुरू आहे" + "रोमिंगदरम्यान अतिरिक्त शुल्क लागू होऊ शकतात" diff --git a/Tethering/res/values-mcc310-mnc004-ms/strings.xml b/Tethering/res/values-mcc310-mnc004-ms/strings.xml index 343e6fa347..f65c451e4c 100644 --- a/Tethering/res/values-mcc310-mnc004-ms/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-ms/strings.xml @@ -16,9 +16,9 @@ - "Penambatan tiada Internet" - "Peranti tidak dapat disambungkan" - "Matikan penambatan" - "Tempat liputan atau penambatan dihidupkan" - "Caj tambahan boleh dikenakan semasa perayauan" + "Penambatan tiada Internet" + "Peranti tidak dapat disambungkan" + "Matikan penambatan" + "Tempat liputan atau penambatan dihidupkan" + "Caj tambahan mungkin digunakan semasa perayauan" diff --git a/Tethering/res/values-mcc310-mnc004-my/strings.xml b/Tethering/res/values-mcc310-mnc004-my/strings.xml index 152f468989..4118e775cd 100644 --- a/Tethering/res/values-mcc310-mnc004-my/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-my/strings.xml @@ -16,9 +16,9 @@ - "မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်းတွင် အင်တာနက် မရှိပါ" - "စက်ပစ္စည်းများကို ချိတ်ဆက်၍မရပါ" - "မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း ပိတ်ရန်" - "ဟော့စပေါ့ (သို့) မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း ဖွင့်ထားသည်" - "ပြင်ပကွန်ရက်သုံးနေစဉ် နောက်ထပ်ကျသင့်ငွေ ပေးရနိုင်သည်" + "မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်းတွင် အင်တာနက် မရှိပါ" + "စက်များ ချိတ်ဆက်၍ မရပါ" + "မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း ပိတ်ရန်" + "ဟော့စပေါ့ (သို့) မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း ဖွင့်ထားသည်" + "ပြင်ပကွန်ရက်နှင့် ချိတ်ဆက်သည့်အခါ နောက်ထပ်ကျသင့်မှုများ ရှိနိုင်သည်" diff --git a/Tethering/res/values-mcc310-mnc004-nb/strings.xml b/Tethering/res/values-mcc310-mnc004-nb/strings.xml index 31895d1c8c..36853583ce 100644 --- a/Tethering/res/values-mcc310-mnc004-nb/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-nb/strings.xml @@ -16,9 +16,9 @@ - "Internettdeling har ikke internettilgang" - "Enheter kan ikke koble til" - "Slå av internettdeling" - "Wifi-sone eller internettdeling er på" - "Det kan påløpe flere kostnader ved roaming" + "Internettdeling har ikke internettilgang" + "Enhetene kan ikke koble til" + "Slå av internettdeling" + "Wi-Fi-sone eller internettdeling er på" + "Ytterligere kostnader kan påløpe under roaming" diff --git a/Tethering/res/values-mcc310-mnc004-ne/strings.xml b/Tethering/res/values-mcc310-mnc004-ne/strings.xml index 4b5077387e..d074f15699 100644 --- a/Tethering/res/values-mcc310-mnc004-ne/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-ne/strings.xml @@ -16,9 +16,9 @@ - "टेदरिङमार्फत इन्टरनेट कनेक्ट गर्न सकिएन" - "डिभाइसहरू कनेक्ट गर्न सकिएन" - "टेदरिङ अफ गर्नुहोस्" - "हटस्पट वा टेदरिङ अन छ" - "रोमिङ सेवा प्रयोग गर्दा अतिरिक्त शुल्क लाग्न सक्छ" + "टेदरिङमार्फत इन्टरनेट कनेक्सन प्राप्त हुन सकेन" + "यन्त्रहरू कनेक्ट गर्न सकिएन" + "टेदरिङ निष्क्रिय पार्नुहोस्" + "हटस्पट वा टेदरिङ सक्रिय छ" + "रोमिङ सेवा प्रयोग गर्दा अतिरिक्त शुल्क लाग्न सक्छ" diff --git a/Tethering/res/values-mcc310-mnc004-nl/strings.xml b/Tethering/res/values-mcc310-mnc004-nl/strings.xml index 8af41fde4d..1d888942f4 100644 --- a/Tethering/res/values-mcc310-mnc004-nl/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-nl/strings.xml @@ -16,9 +16,9 @@ - "Tethering heeft geen internet" - "Apparaten kunnen geen verbinding maken" - "Tethering uitzetten" - "Hotspot of tethering staat aan" - "Er kunnen extra kosten voor roaming in rekening worden gebracht" + "Tethering heeft geen internet" + "Apparaten kunnen niet worden verbonden" + "Tethering uitschakelen" + "Hotspot of tethering is ingeschakeld" + "Er kunnen extra kosten voor roaming in rekening worden gebracht." diff --git a/Tethering/res/values-mcc310-mnc004-or/strings.xml b/Tethering/res/values-mcc310-mnc004-or/strings.xml index 6eb0773e9b..8038815fe8 100644 --- a/Tethering/res/values-mcc310-mnc004-or/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-or/strings.xml @@ -16,9 +16,9 @@ - "ଟିଥରିଂ ପାଇଁ କୌଣସି ଇଣ୍ଟରନେଟ କନେକ୍ସନ ନାହିଁ" - "ଡିଭାଇସଗୁଡ଼ିକୁ କନେକ୍ଟ କରାଯାଇପାରିବ ନାହିଁ" - "ଟିଥରିଂକୁ ବନ୍ଦ କରନ୍ତୁ" - "ହଟସ୍ପଟ କିମ୍ବା ଟିଥରିଂ ଚାଲୁ ଅଛି" - "ରୋମିଂ ସମୟରେ ଅତିରିକ୍ତ ଚାର୍ଜ ଲାଗୁ ହୋଇପାରେ" + "ଟିଥରିଂ ପାଇଁ କୌଣସି ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ନାହିଁ" + "ଡିଭାଇସଗୁଡ଼ିକ ସଂଯୋଗ କରାଯାଇପାରିବ ନାହିଁ" + "ଟିଥରିଂ ବନ୍ଦ କରନ୍ତୁ" + "ହଟସ୍ପଟ୍ କିମ୍ବା ଟିଥରିଂ ଚାଲୁ ଅଛି" + "ରୋମିଂରେ ଥିବା ସମୟରେ ଅତିରିକ୍ତ ଶୁଳ୍କ ଲାଗୁ ହୋଇପାରେ" diff --git a/Tethering/res/values-mcc310-mnc004-pa/strings.xml b/Tethering/res/values-mcc310-mnc004-pa/strings.xml index 28181e2ba2..819833eab0 100644 --- a/Tethering/res/values-mcc310-mnc004-pa/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-pa/strings.xml @@ -16,9 +16,9 @@ - "ਟੈਦਰਿੰਗ ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ" - "ਡੀਵਾਈਸ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤੇ ਜਾ ਸਕਦੇ" - "ਟੈਦਰਿੰਗ ਬੰਦ ਕਰੋ" - "ਹੌਟਸਪੌਟ ਜਾਂ ਟੈਦਰਿੰਗ ਚਾਲੂ ਹੈ" - "ਰੋਮਿੰਗ ਦੌਰਾਨ ਵਧੀਕ ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ" + "ਟੈਦਰਿੰਗ ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ" + "ਡੀਵਾਈਸ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤੇ ਜਾ ਸਕਦੇ" + "ਟੈਦਰਿੰਗ ਬੰਦ ਕਰੋ" + "ਹੌਟਸਪੌਟ ਜਾਂ ਟੈਦਰਿੰਗ ਚਾਲੂ ਹੈ" + "ਰੋਮਿੰਗ ਦੌਰਾਨ ਵਧੀਕ ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ" diff --git a/Tethering/res/values-mcc310-mnc004-pl/strings.xml b/Tethering/res/values-mcc310-mnc004-pl/strings.xml index 816302a976..65e4380e39 100644 --- a/Tethering/res/values-mcc310-mnc004-pl/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-pl/strings.xml @@ -16,9 +16,9 @@ - "Tethering nie ma internetu" - "Urządzenia nie mogą się połączyć" - "Wyłącz tethering" - "Hotspot lub tethering jest włączony" - "Podczas korzystania z roamingu mogą zostać naliczone dodatkowe opłaty" + "Tethering nie ma internetu" + "Urządzenia nie mogą się połączyć" + "Wyłącz tethering" + "Hotspot lub tethering jest włączony" + "Podczas korzystania z roamingu mogą zostać naliczone dodatkowe opłaty" diff --git a/Tethering/res/values-mcc310-mnc004-pt-rBR/strings.xml b/Tethering/res/values-mcc310-mnc004-pt-rBR/strings.xml index 55767c224a..d8866170c1 100644 --- a/Tethering/res/values-mcc310-mnc004-pt-rBR/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-pt-rBR/strings.xml @@ -16,9 +16,9 @@ - "O tethering não tem uma conexão de Internet" - "Não é possível conectar os dispositivos" - "Desativar o tethering" - "O ponto de acesso ou tethering está ativado" - "Pode haver cobranças extras durante o roaming" + "O tethering não tem Internet" + "Não é possível conectar os dispositivos" + "Desativar o tethering" + "Ponto de acesso ou tethering ativado" + "Pode haver cobranças extras durante o roaming" diff --git a/Tethering/res/values-mcc310-mnc004-pt-rPT/strings.xml b/Tethering/res/values-mcc310-mnc004-pt-rPT/strings.xml index eccabf8b34..bfd45ca0a3 100644 --- a/Tethering/res/values-mcc310-mnc004-pt-rPT/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-pt-rPT/strings.xml @@ -16,9 +16,9 @@ - "A ligação (à Internet) via telemóvel não tem Internet" - "Não é possível ligar os dispositivos" - "Desativar ligação (à Internet) via telemóvel" - "A zona Wi-Fi ou a ligação (à Internet) via telemóvel está ativada" - "Podem aplicar-se custos adicionais em roaming" + "A ligação (à Internet) via telemóvel não tem Internet" + "Não é possível ligar os dispositivos" + "Desativar ligação (à Internet) via telemóvel" + "A zona Wi-Fi ou a ligação (à Internet) via telemóvel está ativada" + "Podem aplicar-se custos adicionais em roaming." diff --git a/Tethering/res/values-mcc310-mnc004-pt/strings.xml b/Tethering/res/values-mcc310-mnc004-pt/strings.xml index 55767c224a..d8866170c1 100644 --- a/Tethering/res/values-mcc310-mnc004-pt/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-pt/strings.xml @@ -16,9 +16,9 @@ - "O tethering não tem uma conexão de Internet" - "Não é possível conectar os dispositivos" - "Desativar o tethering" - "O ponto de acesso ou tethering está ativado" - "Pode haver cobranças extras durante o roaming" + "O tethering não tem Internet" + "Não é possível conectar os dispositivos" + "Desativar o tethering" + "Ponto de acesso ou tethering ativado" + "Pode haver cobranças extras durante o roaming" diff --git a/Tethering/res/values-mcc310-mnc004-ro/strings.xml b/Tethering/res/values-mcc310-mnc004-ro/strings.xml index 3c3d7bcc39..8d87a9e516 100644 --- a/Tethering/res/values-mcc310-mnc004-ro/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-ro/strings.xml @@ -16,9 +16,9 @@ - "Procesul de tethering nu are internet" - "Dispozitivele nu se pot conecta" - "Dezactivează tetheringul" - "S-a activat hotspotul sau tetheringul" - "Se pot aplica taxe suplimentare pentru roaming" + "Procesul de tethering nu are internet" + "Dispozitivele nu se pot conecta" + "Dezactivați procesul de tethering" + "S-a activat hotspotul sau tethering" + "Se pot aplica taxe suplimentare pentru roaming" diff --git a/Tethering/res/values-mcc310-mnc004-ru/strings.xml b/Tethering/res/values-mcc310-mnc004-ru/strings.xml index 76671801ce..dbdb9ebe49 100644 --- a/Tethering/res/values-mcc310-mnc004-ru/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-ru/strings.xml @@ -16,9 +16,9 @@ - "Режим модема используется без доступа к интернету" - "Невозможно подключить устройства." - "Отключить режим модема" - "Включена точка доступа или режим модема" - "За использование услуг связи в роуминге может взиматься дополнительная плата." + "Режим модема используется без доступа к Интернету" + "Невозможно подключить устройства." + "Отключить режим модема" + "Включены точка доступа или режим модема" + "За использование услуг связи в роуминге может взиматься дополнительная плата." diff --git a/Tethering/res/values-mcc310-mnc004-si/strings.xml b/Tethering/res/values-mcc310-mnc004-si/strings.xml index 0c88a39db0..d8301e41c2 100644 --- a/Tethering/res/values-mcc310-mnc004-si/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-si/strings.xml @@ -16,9 +16,9 @@ - "ටෙදරින් හට අන්තර්ජාලය නැත" - "උපාංගවලට සම්බන්ධ විය නොහැක" - "ටෙදරින් ක්‍රියාවිරහිත කරන්න" - "හොට්ස්පොට් හෝ ටෙදරින් ක්‍රියාත්මකයි" - "රෝමිං අතරේ අතිරේක ගාස්තු අදාළ විය හැක" + "ටෙදරින් හට අන්තර්ජාලය නැත" + "උපාංගවලට සම්බන්ධ විය නොහැකිය" + "ටෙදරින් ක්‍රියාවිරහිත කරන්න" + "හොට්ස්පොට් හෝ ටෙදරින් ක්‍රියාත්මකයි" + "රෝමිං අතරතුර අමතර ගාස්තු අදාළ විය හැකිය" diff --git a/Tethering/res/values-mcc310-mnc004-sk/strings.xml b/Tethering/res/values-mcc310-mnc004-sk/strings.xml index c3b941e7b7..bef71363f4 100644 --- a/Tethering/res/values-mcc310-mnc004-sk/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-sk/strings.xml @@ -16,9 +16,9 @@ - "Tethering nemá internetové pripojenie" - "Zariadenia sa nemôžu pripojiť" - "Vypnúť tethering" - "Je zapnutý hotspot alebo tethering" - "Počas roamingu vám môžu byť účtované ďalšie poplatky" + "Tethering nemá internetové pripojenie" + "Zariadenia sa nemôžu pripojiť" + "Vypnúť tethering" + "Je zapnutý hotspot alebo tethering" + "Počas roamingu vám môžu byť účtované ďalšie poplatky" diff --git a/Tethering/res/values-mcc310-mnc004-sl/strings.xml b/Tethering/res/values-mcc310-mnc004-sl/strings.xml index 6573475d7d..3202c62e8a 100644 --- a/Tethering/res/values-mcc310-mnc004-sl/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-sl/strings.xml @@ -16,9 +16,9 @@ - "Povezava računalnika z internetom prek mobilnega telefona nima internetne povezave" - "Napravi se ne moreta povezati" - "Izklopi povezavo računalnika z internetom prek mobilnega telefona" - "Dostopna točka ali povezava računalnika z internetom prek mobilnega telefona je vklopljena" - "Med gostovanjem lahko nastanejo dodatni stroški." + "Internetna povezava prek mobilnega telefona ni vzpostavljena" + "Napravi se ne moreta povezati" + "Izklopi internetno povezavo prek mobilnega telefona" + "Dostopna točka ali internetna povezava prek mobilnega telefona je vklopljena" + "Med gostovanjem lahko nastanejo dodatni stroški" diff --git a/Tethering/res/values-mcc310-mnc004-sq/strings.xml b/Tethering/res/values-mcc310-mnc004-sq/strings.xml index 93ea2314c9..37f6ad2868 100644 --- a/Tethering/res/values-mcc310-mnc004-sq/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-sq/strings.xml @@ -16,9 +16,9 @@ - "Ndarja e internetit nuk ka internet" - "Pajisjet nuk mund të lidhen" - "Çaktivizo ndarjen e internetit" - "Zona e qasjes për internet ose ndarja e internetit është aktive" - "Mund të zbatohen tarifime shtesë kur je në roaming" + "Ndarja e internetit nuk ka internet" + "Pajisjet nuk mund të lidhen" + "Çaktivizo ndarjen e internetit" + "Zona e qasjes për internet ose ndarja e internetit është aktive" + "Mund të zbatohen tarifime shtesë kur je në roaming" diff --git a/Tethering/res/values-mcc310-mnc004-sr/strings.xml b/Tethering/res/values-mcc310-mnc004-sr/strings.xml index e8fb4784e8..5566d03ed1 100644 --- a/Tethering/res/values-mcc310-mnc004-sr/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-sr/strings.xml @@ -16,9 +16,9 @@ - "Привезивање нема приступ интернету" - "Повезивање уређаја није успело" - "Искључи привезивање" - "Укључен је хотспот или привезивање" - "Можда важе додатни трошкови у ромингу" + "Привезивање нема приступ интернету" + "Повезивање уређаја није успело" + "Искључи привезивање" + "Укључен је хотспот или привезивање" + "Можда важе додатни трошкови у ромингу" diff --git a/Tethering/res/values-mcc310-mnc004-sv/strings.xml b/Tethering/res/values-mcc310-mnc004-sv/strings.xml index 6fc1747f20..9765acd0cf 100644 --- a/Tethering/res/values-mcc310-mnc004-sv/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-sv/strings.xml @@ -16,9 +16,9 @@ - "Det finns ingen internetanslutning för internetdelningen" - "Enheterna kan inte anslutas" - "Inaktivera internetdelning" - "Surfzon eller internetdelning har aktiverats" - "Ytterligare avgifter kan tillkomma vid roaming" + "Det finns ingen internetanslutning för internetdelningen" + "Enheterna kan inte anslutas" + "Inaktivera internetdelning" + "Surfzon eller internetdelning har aktiverats" + "Ytterligare avgifter kan tillkomma vid roaming" diff --git a/Tethering/res/values-mcc310-mnc004-sw/strings.xml b/Tethering/res/values-mcc310-mnc004-sw/strings.xml index 73a7026ae2..cf850c9cd2 100644 --- a/Tethering/res/values-mcc310-mnc004-sw/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-sw/strings.xml @@ -16,9 +16,9 @@ - "Huduma ya kusambaza mtandao haina muunganisho wa intaneti" - "Imeshindwa kuunganisha vifaa" - "Zima kipengele cha kusambaza mtandao" - "Umewasha kipengele cha kusambaza mtandao au mtandao pepe" - "Huenda ukatozwa gharama za ziada ukitumia mitandao ya ng\'ambo" + "Kipengele cha kusambaza mtandao hakina intaneti" + "Imeshindwa kuunganisha vifaa" + "Zima kipengele cha kusambaza mtandao" + "Umewasha kipengele cha kusambaza mtandao au mtandao pepe" + "Huenda ukatozwa gharama za ziada ukitumia mitandao ya ng\'ambo" diff --git a/Tethering/res/values-mcc310-mnc004-ta/strings.xml b/Tethering/res/values-mcc310-mnc004-ta/strings.xml index 436f00b8c2..f4b15aab19 100644 --- a/Tethering/res/values-mcc310-mnc004-ta/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-ta/strings.xml @@ -16,9 +16,9 @@ - "இணைப்பு முறைக்கு இணைய இணைப்பு இல்லை" - "சாதனங்களால் இணைய முடியவில்லை" - "இணைப்பு முறையை முடக்கு" - "ஹாட்ஸ்பாட் அல்லது இணைப்பு முறை இயக்கப்பட்டுள்ளது" - "ரோமிங்கின்போது கூடுதல் கட்டணங்கள் விதிக்கப்படலாம்" + "இணைப்பு முறைக்கு இணைய இணைப்பு இல்லை" + "சாதனங்களால் இணைய முடியவில்லை" + "இணைப்பு முறையை ஆஃப் செய்" + "ஹாட்ஸ்பாட் அல்லது இணைப்பு முறை ஆன் செய்யப்பட்டுள்ளது" + "ரோமிங்கின்போது கூடுதல் கட்டணங்கள் விதிக்கப்படக்கூடும்" diff --git a/Tethering/res/values-mcc310-mnc004-te/strings.xml b/Tethering/res/values-mcc310-mnc004-te/strings.xml index ba8362757e..937d34d520 100644 --- a/Tethering/res/values-mcc310-mnc004-te/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-te/strings.xml @@ -16,9 +16,9 @@ - "టెథరింగ్ చేయడానికి ఇంటర్నెట్ కనెక్షన్ లేదు" - "పరికరాలు కనెక్ట్ అవ్వడం లేదు" - "టెథరింగ్‌ను ఆఫ్ చేయండి" - "హాట్‌స్పాట్ లేదా టెథరింగ్ ఆన్‌లో ఉంది" - "రోమింగ్‌లో ఉన్నప్పుడు అదనపు ఛార్జీలు వర్తించవచ్చు" + "టెథరింగ్ చేయడానికి ఇంటర్నెట్ కనెక్షన్ లేదు" + "పరికరాలు కనెక్ట్ అవ్వడం లేదు" + "టెథరింగ్‌ను ఆఫ్ చేయండి" + "హాట్‌స్పాట్ లేదా టెథరింగ్ ఆన్‌లో ఉంది" + "రోమింగ్‌లో ఉన్నప్పుడు అదనపు ఛార్జీలు వర్తించవచ్చు" diff --git a/Tethering/res/values-mcc310-mnc004-th/strings.xml b/Tethering/res/values-mcc310-mnc004-th/strings.xml index e2ea084a37..f781fae525 100644 --- a/Tethering/res/values-mcc310-mnc004-th/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-th/strings.xml @@ -16,9 +16,9 @@ - "ไม่มีอินเทอร์เน็ตสำหรับการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ" - "อุปกรณ์เชื่อมต่อไม่ได้" - "ปิดการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ" - "ฮอตสปอตหรือการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือเปิดอยู่" - "อาจมีค่าใช้จ่ายเพิ่มเติมขณะโรมมิ่ง" + "การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือไม่มีอินเทอร์เน็ต" + "อุปกรณ์เชื่อมต่อไม่ได้" + "ปิดการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ" + "ฮอตสปอตหรือการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือเปิดอยู่" + "อาจมีค่าใช้จ่ายเพิ่มเติมขณะโรมมิ่ง" diff --git a/Tethering/res/values-mcc310-mnc004-tl/strings.xml b/Tethering/res/values-mcc310-mnc004-tl/strings.xml index 7b4b71c865..8d5d465373 100644 --- a/Tethering/res/values-mcc310-mnc004-tl/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-tl/strings.xml @@ -16,9 +16,9 @@ - "Walang internet ang pag-tether" - "Hindi makakonekta ang mga device" - "I-off ang pag-tether" - "Naka-on ang hotspot o pag-tether" - "Posibleng magkaroon ng mga karagdagang singil habang nagro-roam" + "Walang internet ang pag-tether" + "Hindi makakonekta ang mga device" + "I-off ang pag-tether" + "Naka-on ang Hotspot o pag-tether" + "Posibleng magkaroon ng mga karagdagang singil habang nagro-roam" diff --git a/Tethering/res/values-mcc310-mnc004-tr/strings.xml b/Tethering/res/values-mcc310-mnc004-tr/strings.xml index 066e1d7b95..80cab33ac0 100644 --- a/Tethering/res/values-mcc310-mnc004-tr/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-tr/strings.xml @@ -16,9 +16,9 @@ - "Tethering\'in internet bağlantısı yok" - "Cihazlar bağlanamıyor" - "Tethering\'i kapat" - "Hotspot veya tethering açık" - "Dolaşım sırasında ek ücretler uygulanabilir" + "Tethering\'in internet bağlantısı yok" + "Cihazlar bağlanamıyor" + "Tethering\'i kapat" + "Hotspot veya tethering açık" + "Dolaşım sırasında ek ücretler uygulanabilir" diff --git a/Tethering/res/values-mcc310-mnc004-uk/strings.xml b/Tethering/res/values-mcc310-mnc004-uk/strings.xml index 036746a2ca..c05932a5ae 100644 --- a/Tethering/res/values-mcc310-mnc004-uk/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-uk/strings.xml @@ -16,9 +16,9 @@ - "Телефон, що використовується як модем, не підключений до Інтернету" - "Не вдається підключити пристрої" - "Вимкнути використання телефона як модема" - "Увімкнено точку доступу або використання телефона як модема" - "У роумінгу може стягуватися додаткова плата" + "Телефон, який використовується як модем, не підключений до Інтернету" + "Не вдається підключити пристрої" + "Вимкнути використання телефона як модема" + "Увімкнено точку доступу або використання телефона як модема" + "У роумінгу може стягуватися додаткова плата" diff --git a/Tethering/res/values-mcc310-mnc004-ur/strings.xml b/Tethering/res/values-mcc310-mnc004-ur/strings.xml index 90eadeff6f..d820eee8ba 100644 --- a/Tethering/res/values-mcc310-mnc004-ur/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-ur/strings.xml @@ -16,9 +16,9 @@ - "ٹیدرنگ میں انٹرنیٹ نہیں ہے" - "آلات منسلک نہیں ہو سکتے" - "ٹیدرنگ آف کریں" - "ہاٹ اسپاٹ یا ٹیدرنگ آن ہے" - "رومنگ کے دوران اضافی چارجز لاگو ہو سکتے ہیں" + "ٹیدرنگ میں انٹرنیٹ نہیں ہے" + "آلات منسلک نہیں ہو سکتے" + "ٹیدرنگ آف کریں" + "ہاٹ اسپاٹ یا ٹیدرنگ آن ہے" + "رومنگ کے دوران اضافی چارجز لاگو ہو سکتے ہیں" diff --git a/Tethering/res/values-mcc310-mnc004-uz/strings.xml b/Tethering/res/values-mcc310-mnc004-uz/strings.xml index f647572396..726148aaee 100644 --- a/Tethering/res/values-mcc310-mnc004-uz/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-uz/strings.xml @@ -16,9 +16,9 @@ - "Modem internetga ulanmagan" - "Qurilmalar ulanmadi" - "Modem rejimini faolsizlantirish" - "Hotspot yoki modem rejimi yoniq" - "Rouming vaqtida qoʻshimcha haq olinishi mumkin" + "Modem internetga ulanmagan" + "Qurilmalar ulanmadi" + "Modem rejimini faolsizlantirish" + "Hotspot yoki modem rejimi yoniq" + "Rouming vaqtida qoʻshimcha haq olinishi mumkin" diff --git a/Tethering/res/values-mcc310-mnc004-vi/strings.xml b/Tethering/res/values-mcc310-mnc004-vi/strings.xml index 71db045ad6..b7cb0456b6 100644 --- a/Tethering/res/values-mcc310-mnc004-vi/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-vi/strings.xml @@ -16,9 +16,9 @@ - "Không có Internet để chia sẻ Internet" - "Các thiết bị không thể kết nối" - "Tắt tính năng chia sẻ Internet" - "Điểm phát sóng hoặc tính năng chia sẻ Internet đang bật" - "Bạn có thể mất thêm phí dữ liệu khi chuyển vùng" + "Không có Internet để chia sẻ kết Internet" + "Các thiết bị không thể kết nối" + "Tắt tính năng chia sẻ Internet" + "Điểm phát sóng hoặc tính năng chia sẻ Internet đang bật" + "Bạn có thể mất thêm phí dữ liệu khi chuyển vùng" diff --git a/Tethering/res/values-mcc310-mnc004-zh-rCN/strings.xml b/Tethering/res/values-mcc310-mnc004-zh-rCN/strings.xml index d279fddbc5..af91afff9a 100644 --- a/Tethering/res/values-mcc310-mnc004-zh-rCN/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-zh-rCN/strings.xml @@ -16,9 +16,9 @@ - "共享网络未连接到互联网" - "设备无法连接" - "关闭网络共享" - "热点或网络共享已开启" - "漫游时可能会产生额外的费用" + "共享网络未连接到互联网" + "设备无法连接" + "关闭网络共享" + "热点或网络共享已开启" + "漫游时可能会产生额外的费用" diff --git a/Tethering/res/values-mcc310-mnc004-zh-rHK/strings.xml b/Tethering/res/values-mcc310-mnc004-zh-rHK/strings.xml index 5bad7e459e..28e6b80c01 100644 --- a/Tethering/res/values-mcc310-mnc004-zh-rHK/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-zh-rHK/strings.xml @@ -16,9 +16,9 @@ - "無法透過網絡共享連線至互聯網" - "裝置無法連接" - "關閉網絡共享" - "熱點或網絡共享已開啟" - "漫遊時可能需要支付額外費用" + "無法透過網絡共享連線至互聯網" + "裝置無法連接" + "關閉網絡共享" + "熱點或網絡共享已開啟" + "漫遊時可能需要支付額外費用" diff --git a/Tethering/res/values-mcc310-mnc004-zh-rTW/strings.xml b/Tethering/res/values-mcc310-mnc004-zh-rTW/strings.xml index 8991ff43cf..528a1e5292 100644 --- a/Tethering/res/values-mcc310-mnc004-zh-rTW/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-zh-rTW/strings.xml @@ -16,9 +16,9 @@ - "無法透過網路共用連上網際網路" - "裝置無法連線" - "關閉網路共用" - "無線基地台或網路共用已開啟" - "使用漫遊服務可能須支付額外費用" + "無法透過網路共用連上網際網路" + "裝置無法連線" + "關閉網路共用" + "無線基地台或網路共用已開啟" + "使用漫遊服務可能須支付額外費用" diff --git a/Tethering/res/values-mcc310-mnc004-zu/strings.xml b/Tethering/res/values-mcc310-mnc004-zu/strings.xml index 31db66aeb4..11eb666219 100644 --- a/Tethering/res/values-mcc310-mnc004-zu/strings.xml +++ b/Tethering/res/values-mcc310-mnc004-zu/strings.xml @@ -16,9 +16,9 @@ - "Ukusebenzisa ifoni njengemodemu akunayo i-inthanethi" - "Amadivayisi awakwazi ukuxhuma" - "Vala ukusebenzisa ifoni njengemodemu" - "I-hotspot noma ukusebenzisa ifoni njengemodemu kuvuliwe" - "Kungaba nezinkokhelo ezengeziwe uma uzula" + "Ukusebenzisa ifoni njengemodemu akunayo i-inthanethi" + "Amadivayisi awakwazi ukuxhumeka" + "Vala ukusebenzisa ifoni njengemodemu" + "I-hotspot noma ukusebenzisa ifoni njengemodemu kuvuliwe" + "Kungaba nezinkokhelo ezengeziwe uma uzula" diff --git a/Tethering/res/values-mcc311-mnc480-af/strings.xml b/Tethering/res/values-mcc311-mnc480-af/strings.xml index cc70b66d24..9bfa5317a9 100644 --- a/Tethering/res/values-mcc311-mnc480-af/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-af/strings.xml @@ -16,9 +16,9 @@ - "Verbinding het nie internet nie" - "Toestelle kan nie koppel nie" - "Skakel verbinding af" - "Warmkol of verbinding is aan" - "Bykomende heffings kan geld terwyl jy swerf" + "Verbinding het nie internet nie" + "Toestelle kan nie koppel nie" + "Skakel verbinding af" + "Warmkol of verbinding is aan" + "Bykomende heffings kan geld terwyl jy swerf" diff --git a/Tethering/res/values-mcc311-mnc480-am/strings.xml b/Tethering/res/values-mcc311-mnc480-am/strings.xml index 9808534d17..5949dfa776 100644 --- a/Tethering/res/values-mcc311-mnc480-am/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-am/strings.xml @@ -16,9 +16,9 @@ - "እንደ ሞደም መሰካት ምንም በይነመረብ የለውም" - "መሣሪያዎችን ማገናኘት አልተቻልም" - "እንደ ሞደም መሰካትን አጥፋ" - "መገናኛ ነጥብ ወይም እንደ ሞደም መሰካት በርቷል" - "በሚያንዣብብበት ጊዜ ተጨማሪ ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ" + "ማስተሳሰር ምንም በይነመረብ የለውም" + "መሣሪያዎችን ማገናኘት አይቻልም" + "ማስተሳሰርን አጥፋ" + "መገናኛ ነጥብ ወይም ማስተሳሰር በርቷል" + "በሚያንዣብብበት ጊዜ ተጨማሪ ክፍያዎች ተፈጻሚ ሊሆኑ ይችላሉ" diff --git a/Tethering/res/values-mcc311-mnc480-ar/strings.xml b/Tethering/res/values-mcc311-mnc480-ar/strings.xml index ab84c4aa3e..8467f9b1f5 100644 --- a/Tethering/res/values-mcc311-mnc480-ar/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-ar/strings.xml @@ -16,9 +16,9 @@ - "ما مِن اتصال بالإنترنت خلال التوصيل" - "يتعذّر اتصال الأجهزة" - "إيقاف التوصيل" - "نقطة الاتصال مفعَّلة أو التوصيل مفعَّل" - "قد يتم تحصيل رسوم إضافية أثناء التجوال." + "ما مِن اتصال بالإنترنت خلال التوصيل" + "تعذّر اتصال الأجهزة" + "إيقاف التوصيل" + "نقطة الاتصال أو التوصيل مفعّلان" + "قد يتم تطبيق رسوم إضافية أثناء التجوال." diff --git a/Tethering/res/values-mcc311-mnc480-as/strings.xml b/Tethering/res/values-mcc311-mnc480-as/strings.xml index f7ab7e9345..9776bd89da 100644 --- a/Tethering/res/values-mcc311-mnc480-as/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-as/strings.xml @@ -16,9 +16,9 @@ - "টে’ডাৰিঙৰ ইণ্টাৰনেট নাই" - "ডিভাইচসমূহ সংযোগ কৰিব নোৱাৰি" - "টে’ডাৰিং অফ কৰক" - "হ’টস্প’ট অথবা টে’ডাৰিং অন আছে" - "ৰ’মিঙত থাকিলে অতিৰিক্ত মাচুল প্ৰযোজ্য হ’ব পাৰে" + "টে\'ডাৰিঙৰ ইণ্টাৰনেট নাই" + "ডিভাইচসমূহ সংযোগ কৰিব নোৱাৰি" + "টে\'ডাৰিং অফ কৰক" + "হটস্পট অথবা টে\'ডাৰিং অন আছে" + "ৰ\'মিঙত থাকিলে অতিৰিক্ত মাচুল প্ৰযোজ্য হ’ব পাৰে" diff --git a/Tethering/res/values-mcc311-mnc480-az/strings.xml b/Tethering/res/values-mcc311-mnc480-az/strings.xml index 6e36df1719..e6d3eaf9f0 100644 --- a/Tethering/res/values-mcc311-mnc480-az/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-az/strings.xml @@ -16,9 +16,9 @@ - "Modem rejimi internetə qoşulmayıb" - "Cihazları qoşmaq olmur" - "Modem rejimini deaktiv edin" - "Hotspot və ya modem rejimi aktivdir" - "Rouminq zamanı əlavə ödəniş çıxıla bilər" + "Modemin internetə girişi yoxdur" + "Cihazları qoşmaq mümkün deyil" + "Modemi deaktiv edin" + "Hotspot və ya modem aktivdir" + "Rouminq zamanı əlavə ödənişlər tətbiq edilə bilər" diff --git a/Tethering/res/values-mcc311-mnc480-b+sr+Latn/strings.xml b/Tethering/res/values-mcc311-mnc480-b+sr+Latn/strings.xml index 173007554b..4c8a1df8ee 100644 --- a/Tethering/res/values-mcc311-mnc480-b+sr+Latn/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-b+sr+Latn/strings.xml @@ -16,9 +16,9 @@ - "Privezivanje nema pristup internetu" - "Povezivanje uređaja nije uspelo" - "Isključi privezivanje" - "Uključen je hotspot ili privezivanje" - "Možda važe dodatni troškovi u romingu" + "Privezivanje nema pristup internetu" + "Povezivanje uređaja nije uspelo" + "Isključi privezivanje" + "Uključen je hotspot ili privezivanje" + "Možda važe dodatni troškovi u romingu" diff --git a/Tethering/res/values-mcc311-mnc480-be/strings.xml b/Tethering/res/values-mcc311-mnc480-be/strings.xml index 88577d4121..edfa41e1ff 100644 --- a/Tethering/res/values-mcc311-mnc480-be/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-be/strings.xml @@ -16,9 +16,9 @@ - "Рэжым мадэма выкарыстоўваецца без доступу да інтэрнэту" - "Не ўдалося падключыць прылады" - "Выключыць рэжым мадэма" - "Хот-спот або рэжым мадэма ўключаны" - "Пры выкарыстанні роўмінгу можа спаганяцца дадатковая плата" + "Рэжым мадэма выкарыстоўваецца без доступу да інтэрнэту" + "Не ўдалося падключыць прылады" + "Выключыць рэжым мадэма" + "Хот-спот або рэжым мадэма ўключаны" + "Пры выкарыстанні роўмінгу можа спаганяцца дадатковая плата" diff --git a/Tethering/res/values-mcc311-mnc480-bg/strings.xml b/Tethering/res/values-mcc311-mnc480-bg/strings.xml index d549997b0f..f56398196f 100644 --- a/Tethering/res/values-mcc311-mnc480-bg/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-bg/strings.xml @@ -16,9 +16,9 @@ - "Няма връзка с интернет за тетъринг" - "Устройствата не могат да установят връзка" - "Изключване на функцията за тетъринг" - "Точката за достъп или функцията за тетъринг са включени" - "Възможно е да ви бъдат начислени допълнителни такси при роуминг" + "Тетърингът няма връзка с интернет" + "Устройствата не могат да установят връзка" + "Изключване на тетъринга" + "Точката за достъп или тетърингът са включени" + "Възможно е да ви бъдат начислени допълнителни такси при роуминг" diff --git a/Tethering/res/values-mcc311-mnc480-bn/strings.xml b/Tethering/res/values-mcc311-mnc480-bn/strings.xml index 93e316a947..d8ecd2e988 100644 --- a/Tethering/res/values-mcc311-mnc480-bn/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-bn/strings.xml @@ -16,9 +16,9 @@ - "টেথারিং করার জন্য কোনও ইন্টারনেট কানেকশন লাগে না" - "ডিভাইস কানেক্ট করা যাচ্ছে না" - "টেথারিং বন্ধ করুন" - "হটস্পট বা টেথারিং চালু আছে" - "রোমিংয়ে থাকার সময় অতিরিক্ত চার্জ লাগতে পারে" + "টিথারিং করার জন্য কোনও ইন্টারনেট কানেকশন নেই" + "ডিভাইস কানেক্ট করতে পারছে না" + "টিথারিং বন্ধ করুন" + "হটস্পট বা টিথারিং চালু আছে" + "রোমিংয়ের সময় অতিরিক্ত চার্জ করা হতে পারে" diff --git a/Tethering/res/values-mcc311-mnc480-bs/strings.xml b/Tethering/res/values-mcc311-mnc480-bs/strings.xml index 27777c266a..b85fd5e285 100644 --- a/Tethering/res/values-mcc311-mnc480-bs/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-bs/strings.xml @@ -16,9 +16,9 @@ - "Dijeljenje internetske veze nema internet" - "Nije moguće povezati uređaje" - "Isključi dijeljenje internetske veze" - "Pristupna tačka ili dijeljenje internetske veze su uključeni" - "Mogu nastati dodatni troškovi u romingu" + "Povezivanje putem mobitela nema internet" + "Uređaji se ne mogu povezati" + "Isključi povezivanje putem mobitela" + "Pristupna tačka ili povezivanje putem mobitela je uključeno" + "Mogu nastati dodatni troškovi u romingu" diff --git a/Tethering/res/values-mcc311-mnc480-ca/strings.xml b/Tethering/res/values-mcc311-mnc480-ca/strings.xml index dad35f884c..a3572151be 100644 --- a/Tethering/res/values-mcc311-mnc480-ca/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-ca/strings.xml @@ -16,9 +16,9 @@ - "La compartició de xarxa no té accés a Internet" - "No es poden connectar els dispositius" - "Desactiva la compartició de xarxa" - "El punt d\'accés Wi‑Fi o la compartició de xarxa estan activats" - "És possible que s\'apliquin costos addicionals en itinerància" + "La compartició de xarxa no té accés a Internet" + "No es poden connectar els dispositius" + "Desactiva la compartició de xarxa" + "S\'ha activat el punt d\'accés Wi‑Fi o la compartició de xarxa" + "És possible que s\'apliquin costos addicionals en itinerància" diff --git a/Tethering/res/values-mcc311-mnc480-cs/strings.xml b/Tethering/res/values-mcc311-mnc480-cs/strings.xml index cbcabe1043..91196be9e5 100644 --- a/Tethering/res/values-mcc311-mnc480-cs/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-cs/strings.xml @@ -16,9 +16,9 @@ - "Tethering nemá připojení k internetu" - "Zařízení se nemůžou připojit" - "Vypnout tethering" - "Je zapnutý hotspot nebo tethering" - "Při roamingu mohou být účtovány dodatečné poplatky" + "Tethering nemá připojení k internetu" + "Zařízení se nemůžou připojit" + "Vypnout tethering" + "Je zapnutý hotspot nebo tethering" + "Při roamingu mohou být účtovány dodatečné poplatky" diff --git a/Tethering/res/values-mcc311-mnc480-da/strings.xml b/Tethering/res/values-mcc311-mnc480-da/strings.xml index 917670965c..196890011d 100644 --- a/Tethering/res/values-mcc311-mnc480-da/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-da/strings.xml @@ -16,9 +16,9 @@ - "Netdeling har ingen internetforbindelse" - "Enheder kan ikke oprette forbindelse" - "Deaktiver netdeling" - "Hotspot eller netdeling er aktiveret" - "Der opkræves muligvis yderligere gebyrer ved roaming" + "Netdeling har ingen internetforbindelse" + "Enheder kan ikke oprette forbindelse" + "Deaktiver netdeling" + "Hotspot eller netdeling er aktiveret" + "Der opkræves muligvis yderligere gebyrer ved roaming" diff --git a/Tethering/res/values-mcc311-mnc480-de/strings.xml b/Tethering/res/values-mcc311-mnc480-de/strings.xml index b3bc7d83d8..eb3f8c52c0 100644 --- a/Tethering/res/values-mcc311-mnc480-de/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-de/strings.xml @@ -16,9 +16,9 @@ - "Tethering hat keinen Internetzugriff" - "Geräte können sich nicht verbinden" - "Tethering deaktivieren" - "Hotspot oder Tethering ist aktiviert" - "Für das Roaming können zusätzliche Gebühren anfallen" + "Tethering hat keinen Internetzugriff" + "Geräte können sich nicht verbinden" + "Tethering deaktivieren" + "Hotspot oder Tethering ist aktiviert" + "Für das Roaming können zusätzliche Gebühren anfallen" diff --git a/Tethering/res/values-mcc311-mnc480-el/strings.xml b/Tethering/res/values-mcc311-mnc480-el/strings.xml index babd62ce91..56c3d81b63 100644 --- a/Tethering/res/values-mcc311-mnc480-el/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-el/strings.xml @@ -16,9 +16,9 @@ - "Η σύνδεση δεν έχει πρόσβαση στο διαδίκτυο" - "Δεν είναι δυνατή η σύνδεση των συσκευών" - "Απενεργοποίηση σύνδεσης" - "Ενεργό σημείο πρόσβασης Wi-Fi ή ενεργή σύνδεση" - "Ενδέχεται να ισχύουν επιπλέον χρεώσεις κατά την περιαγωγή." + "Η σύνδεση δεν έχει πρόσβαση στο διαδίκτυο" + "Δεν είναι δυνατή η σύνδεση των συσκευών" + "Απενεργοποιήστε τη σύνδεση" + "Ενεργό σημείο πρόσβασης Wi-Fi ή ενεργή σύνδεση" + "Ενδέχεται να ισχύουν επιπλέον χρεώσεις κατά την περιαγωγή." diff --git a/Tethering/res/values-mcc311-mnc480-en-rAU/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rAU/strings.xml index afa4467a0f..dd1a1971cd 100644 --- a/Tethering/res/values-mcc311-mnc480-en-rAU/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-en-rAU/strings.xml @@ -16,9 +16,9 @@ - "Tethering has no Internet" - "Devices can\'t connect" - "Turn off tethering" - "Hotspot or tethering is on" - "Additional charges may apply while roaming" + "Tethering has no Internet" + "Devices can’t connect" + "Turn off tethering" + "Hotspot or tethering is on" + "Additional charges may apply while roaming" diff --git a/Tethering/res/values-mcc311-mnc480-en-rCA/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rCA/strings.xml index 251cad6339..dd1a1971cd 100644 --- a/Tethering/res/values-mcc311-mnc480-en-rCA/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-en-rCA/strings.xml @@ -16,9 +16,9 @@ - "Tethering has no internet" - "Devices can’t connect" - "Turn off tethering" - "Hotspot or tethering is on" - "Additional charges may apply while roaming" + "Tethering has no Internet" + "Devices can’t connect" + "Turn off tethering" + "Hotspot or tethering is on" + "Additional charges may apply while roaming" diff --git a/Tethering/res/values-mcc311-mnc480-en-rGB/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rGB/strings.xml index afa4467a0f..dd1a1971cd 100644 --- a/Tethering/res/values-mcc311-mnc480-en-rGB/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-en-rGB/strings.xml @@ -16,9 +16,9 @@ - "Tethering has no Internet" - "Devices can\'t connect" - "Turn off tethering" - "Hotspot or tethering is on" - "Additional charges may apply while roaming" + "Tethering has no Internet" + "Devices can’t connect" + "Turn off tethering" + "Hotspot or tethering is on" + "Additional charges may apply while roaming" diff --git a/Tethering/res/values-mcc311-mnc480-en-rIN/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rIN/strings.xml index afa4467a0f..dd1a1971cd 100644 --- a/Tethering/res/values-mcc311-mnc480-en-rIN/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-en-rIN/strings.xml @@ -16,9 +16,9 @@ - "Tethering has no Internet" - "Devices can\'t connect" - "Turn off tethering" - "Hotspot or tethering is on" - "Additional charges may apply while roaming" + "Tethering has no Internet" + "Devices can’t connect" + "Turn off tethering" + "Hotspot or tethering is on" + "Additional charges may apply while roaming" diff --git a/Tethering/res/values-mcc311-mnc480-en-rXC/strings.xml b/Tethering/res/values-mcc311-mnc480-en-rXC/strings.xml index 766a0e8950..d3347aae20 100644 --- a/Tethering/res/values-mcc311-mnc480-en-rXC/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-en-rXC/strings.xml @@ -16,9 +16,9 @@ - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‏‏‎‎‏‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‎‎‎‏‏‎‎‎‏‏‎‎‎‎‎‎‎‎‏‎‏‎‏‎‏‎‎‎‏‎‎Tethering has no internet‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‎‏‎‏‏‏‎‏‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‏‏‎‎‎‏‎‎Devices can’t connect‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‏‎‎‏‏‎‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎Turn off tethering‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‎‏‏‎‎‏‏‎‎‏‎‏‏‏‎‏‏‏‎‏‏‏‏‏‏‎‎Hotspot or tethering is on‎‏‎‎‏‎" - "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‎‎‎‎‏‏‎‏‏‎‏‏‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎Additional charges may apply while roaming‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‎‎‎‎‎‏‎‎‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‎‏‏‎‏‏‏‎‎‏‎‏‎‎‎‏‎‎‎Tethering has no internet‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‏‎‏‎‎‏‎‎‏‏‏‎‏‏‎‏‎‏‎‏‎‎‎‏‎‎‎‎‎‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‏‏‎‏‎‎‏‏‏‏‏‎Devices can’t connect‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‎‏‏‎‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‏‎‎‎‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‎‎Turn off tethering‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‏‎‎‏‎‎‏‎‏‎‏‏‏‎‏‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‎Hotspot or tethering is on‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‎‏‎‏‎‏‎‎‎‏‎‏‎‏‏‎‏‎‎‎‏‏‏‏‎‎‏‏‏‏‎‎‎‏‎‎‎‎‏‏‎‏‎‏‎‎‏‏‎‎‏‏‎Additional charges may apply while roaming‎‏‎‎‏‎" diff --git a/Tethering/res/values-mcc311-mnc480-es-rUS/strings.xml b/Tethering/res/values-mcc311-mnc480-es-rUS/strings.xml index 16c6059aa2..2f0504f07d 100644 --- a/Tethering/res/values-mcc311-mnc480-es-rUS/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-es-rUS/strings.xml @@ -16,9 +16,9 @@ - "La conexión mediante dispositivo móvil no tiene Internet" - "No se pueden conectar los dispositivos" - "Desactivar conexión mediante dispositivo móvil" - "Se activó el hotspot o la conexión mediante dispositivo móvil" - "Es posible que se apliquen cargos adicionales por roaming" + "La conexión mediante dispositivo móvil no tiene Internet" + "No se pueden conectar los dispositivos" + "Desactivar conexión mediante dispositivo móvil" + "Se activó el hotspot o la conexión mediante dispositivo móvil" + "Es posible que se apliquen cargos adicionales por roaming" diff --git a/Tethering/res/values-mcc311-mnc480-es/strings.xml b/Tethering/res/values-mcc311-mnc480-es/strings.xml index 952e0562fa..2d8f882425 100644 --- a/Tethering/res/values-mcc311-mnc480-es/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-es/strings.xml @@ -16,9 +16,9 @@ - "La conexión no se puede compartir, porque no hay acceso a Internet" - "Los dispositivos no se pueden conectar" - "Desactivar conexión compartida" - "Punto de acceso o conexión compartida activados" - "Pueden aplicarse cargos adicionales en roaming" + "La conexión no se puede compartir, porque no hay acceso a Internet" + "Los dispositivos no se pueden conectar" + "Desactivar conexión compartida" + "Punto de acceso o conexión compartida activados" + "Puede que se apliquen cargos adicionales en itinerancia" diff --git a/Tethering/res/values-mcc311-mnc480-et/strings.xml b/Tethering/res/values-mcc311-mnc480-et/strings.xml index c9cae1f4db..8493c47071 100644 --- a/Tethering/res/values-mcc311-mnc480-et/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-et/strings.xml @@ -16,9 +16,9 @@ - "Jagamisel puudub internetiühendus" - "Seadmed ei saa ühendust luua" - "Lülita jagamine välja" - "Kuumkoht või jagamine on sisse lülitatud" - "Rändluse kasutamisega võivad kaasneda lisatasud" + "Jagamisel puudub internetiühendus" + "Seadmed ei saa ühendust luua" + "Lülita jagamine välja" + "Kuumkoht või jagamine on sisse lülitatud" + "Rändluse kasutamisega võivad kaasneda lisatasud" diff --git a/Tethering/res/values-mcc311-mnc480-eu/strings.xml b/Tethering/res/values-mcc311-mnc480-eu/strings.xml index 7abb4b0404..33bccab3e8 100644 --- a/Tethering/res/values-mcc311-mnc480-eu/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-eu/strings.xml @@ -16,9 +16,9 @@ - "Konexioa partekatzeko aukerak ez du Interneteko konexiorik" - "Ezin dira konektatu gailuak" - "Desaktibatu konexioa partekatzeko aukera" - "Wifi-gunea edo konexioa partekatzeko aukera aktibatuta dago" - "Baliteke tarifa gehigarriak ordaindu behar izatea ibiltaritza erabili bitartean" + "Konexioa partekatzeko aukerak ez du Interneteko konexiorik" + "Ezin dira konektatu gailuak" + "Desaktibatu konexioa partekatzeko aukera" + "Wifi-gunea edo konexioa partekatzeko aukera aktibatuta dago" + "Baliteke kostu gehigarriak ordaindu behar izatea ibiltaritzan" diff --git a/Tethering/res/values-mcc311-mnc480-fa/strings.xml b/Tethering/res/values-mcc311-mnc480-fa/strings.xml index 6bdf38743f..cf8a0cc277 100644 --- a/Tethering/res/values-mcc311-mnc480-fa/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-fa/strings.xml @@ -16,9 +16,9 @@ - "«اشتراک‌گذاری اینترنت» به اینترنت دسترسی ندارد" - "دستگاه‌ها متصل نمی‌شوند" - "خاموش کردن «اشتراک‌گذاری اینترنت»" - "«نقطه اتصال» یا «اشتراک‌گذاری اینترنت» روشن است" - "هنگام فراگردی ممکن است هزینه‌های اضافی کسر شود" + "«اشتراک‌گذاری اینترنت» به اینترنت دسترسی ندارد" + "دستگاه‌ها متصل نمی‌شوند" + "خاموش کردن «اشتراک‌گذاری اینترنت»" + "«نقطه اتصال» یا «اشتراک‌گذاری اینترنت» روشن است" + "ممکن است درحین فراگردی تغییرات دیگر اعمال شود" diff --git a/Tethering/res/values-mcc311-mnc480-fi/strings.xml b/Tethering/res/values-mcc311-mnc480-fi/strings.xml index 57f16bb52c..6a3ab806db 100644 --- a/Tethering/res/values-mcc311-mnc480-fi/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-fi/strings.xml @@ -16,9 +16,9 @@ - "Puhelinta ei voi käyttää modeemina, koska sillä ei ole internet-yhteyttä" - "Laitteet eivät voi muodostaa yhteyttä" - "Laita puhelimen käyttäminen modeemina pois päältä" - "Hotspot tai puhelimen käyttäminen modeemina on päällä" - "Roaming voi aiheuttaa lisämaksuja" + "Ei jaettavaa internetyhteyttä" + "Laitteet eivät voi muodostaa yhteyttä" + "Laita yhteyden jakaminen pois päältä" + "Hotspot tai yhteyden jakaminen on päällä" + "Roaming voi aiheuttaa lisämaksuja" diff --git a/Tethering/res/values-mcc311-mnc480-fr-rCA/strings.xml b/Tethering/res/values-mcc311-mnc480-fr-rCA/strings.xml index bf3d634c8b..ffb9bf6047 100644 --- a/Tethering/res/values-mcc311-mnc480-fr-rCA/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-fr-rCA/strings.xml @@ -16,9 +16,9 @@ - "Le partage de connexion n\'est pas connecté à Internet" - "Impossible de connecter les appareils" - "Désactiver le partage de connexion" - "Le point d\'accès sans fil ou le partage de connexion est activé" - "En itinérance, des frais supplémentaires peuvent s\'appliquer" + "Le partage de connexion n\'est pas connecté à Internet" + "Impossible de connecter les appareils" + "Désactiver le partage de connexion" + "Le point d\'accès ou le partage de connexion est activé" + "En itinérance, des frais supplémentaires peuvent s\'appliquer" diff --git a/Tethering/res/values-mcc311-mnc480-fr/strings.xml b/Tethering/res/values-mcc311-mnc480-fr/strings.xml index 6faa61e96b..768bce3f0a 100644 --- a/Tethering/res/values-mcc311-mnc480-fr/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-fr/strings.xml @@ -16,9 +16,9 @@ - "Aucune connexion à Internet disponible pour le partage de connexion" - "Impossible de connecter les appareils" - "Désactiver le partage de connexion" - "Le point d\'accès ou le partage de connexion est activé" - "En itinérance, des frais supplémentaires peuvent s\'appliquer" + "Aucune connexion à Internet n\'est disponible pour le partage de connexion" + "Impossible de connecter les appareils" + "Désactiver le partage de connexion" + "Le point d\'accès ou le partage de connexion est activé" + "En itinérance, des frais supplémentaires peuvent s\'appliquer" diff --git a/Tethering/res/values-mcc311-mnc480-gl/strings.xml b/Tethering/res/values-mcc311-mnc480-gl/strings.xml index 446d706cec..0c4195a7ca 100644 --- a/Tethering/res/values-mcc311-mnc480-gl/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-gl/strings.xml @@ -16,9 +16,9 @@ - "A conexión compartida non ten acceso a Internet" - "Non se puideron conectar os dispositivos" - "Desactivar conexión compartida" - "Está activada a zona wifi ou a conexión compartida" - "Pódense aplicar cargos adicionais en itinerancia" + "A conexión compartida non ten Internet" + "Non se puideron conectar os dispositivos" + "Desactivar conexión compartida" + "Está activada a zona wifi ou a conexión compartida" + "Pódense aplicar cargos adicionais en itinerancia" diff --git a/Tethering/res/values-mcc311-mnc480-gu/strings.xml b/Tethering/res/values-mcc311-mnc480-gu/strings.xml index 577874d202..e9d33a7db2 100644 --- a/Tethering/res/values-mcc311-mnc480-gu/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-gu/strings.xml @@ -16,9 +16,9 @@ - "ઇન્ટરનેટ શેર કરવાની સુવિધામાં ઇન્ટરનેટ નથી" - "ડિવાઇસ કનેક્ટ કરી શકાતા નથી" - "ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરો" - "હૉટસ્પૉટ અથવા ઇન્ટરનેટ શેર કરવાની સુવિધા ચાલુ છે" - "રોમિંગ દરમિયાન વધારાના શુલ્ક લાગુ થઈ શકે છે" + "ઇન્ટરનેટ શેર કરવાની સુવિધામાં ઇન્ટરનેટ નથી" + "ડિવાઇસ કનેક્ટ કરી શકાતા નથી" + "ઇન્ટરનેટ શેર કરવાની સુવિધા બંધ કરો" + "હૉટસ્પૉટ અથવા ઇન્ટરનેટ શેર કરવાની સુવિધા ચાલુ છે" + "રોમિંગમાં વધારાના શુલ્ક લાગી શકે છે" diff --git a/Tethering/res/values-mcc311-mnc480-hi/strings.xml b/Tethering/res/values-mcc311-mnc480-hi/strings.xml index f2a477378d..aa418ac5d3 100644 --- a/Tethering/res/values-mcc311-mnc480-hi/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-hi/strings.xml @@ -16,9 +16,9 @@ - "टेदरिंग से इंटरनेट नहीं चल रहा है" - "डिवाइस कनेक्ट नहीं हो पा रहे" - "टेदरिंग बंद करें" - "हॉटस्पॉट या टेदरिंग चालू है" - "रोमिंग के दौरान अतिरिक्त शुल्क काटा जा सकता है" + "टेदरिंग से इंटरनेट नहीं चल रहा" + "डिवाइस कनेक्ट नहीं हो पा रहे" + "टेदरिंग बंद करें" + "हॉटस्पॉट या टेदरिंग चालू है" + "रोमिंग के दौरान अतिरिक्त शुल्क लग सकता है" diff --git a/Tethering/res/values-mcc311-mnc480-hr/strings.xml b/Tethering/res/values-mcc311-mnc480-hr/strings.xml index a08f8220db..51c524afbc 100644 --- a/Tethering/res/values-mcc311-mnc480-hr/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-hr/strings.xml @@ -16,9 +16,9 @@ - "Modemsko povezivanje nema internet" - "Uređaji se ne mogu povezati" - "Isključi modemsko povezivanje" - "Uključena je žarišna točka ili modemsko povezivanje" - "U roamingu su mogući dodatni troškovi" + "Modemsko povezivanje nema internet" + "Uređaji se ne mogu povezati" + "Isključivanje modemskog povezivanja" + "Uključena je žarišna točka ili modemsko povezivanje" + "U roamingu su mogući dodatni troškovi" diff --git a/Tethering/res/values-mcc311-mnc480-hu/strings.xml b/Tethering/res/values-mcc311-mnc480-hu/strings.xml index 61a399a9cb..164e45edd1 100644 --- a/Tethering/res/values-mcc311-mnc480-hu/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-hu/strings.xml @@ -16,9 +16,9 @@ - "Nincs internetkapcsolat az internet megosztásához" - "Az eszközök nem tudnak csatlakozni" - "Internetmegosztás kikapcsolása" - "A hotspot vagy az internetmegosztás be van kapcsolva" - "Roaming során további díjak léphetnek fel" + "Nincs internetkapcsolat az internet megosztásához" + "Az eszközök nem tudnak csatlakozni" + "Internetmegosztás kikapcsolása" + "A hotspot vagy az internetmegosztás be van kapcsolva" + "Roaming során további díjak léphetnek fel" diff --git a/Tethering/res/values-mcc311-mnc480-hy/strings.xml b/Tethering/res/values-mcc311-mnc480-hy/strings.xml index f4d63c9a6b..e76c0a4c80 100644 --- a/Tethering/res/values-mcc311-mnc480-hy/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-hy/strings.xml @@ -16,9 +16,9 @@ - "Մոդեմի ռեժիմի ինտերնետ կապը բացակայում է" - "Չհաջողվեց միացնել սարքերը" - "Անջատել մոդեմի ռեժիմը" - "Թեժ կետը կամ մոդեմի ռեժիմը միացված է" - "Ռոումինգում կարող են լրացուցիչ վճարներ գանձվել" + "Մոդեմի ռեժիմի կապը բացակայում է" + "Չհաջողվեց միացնել սարքը" + "Անջատել մոդեմի ռեժիմը" + "Թեժ կետը կամ մոդեմի ռեժիմը միացված է" + "Ռոումինգում կարող են լրացուցիչ վճարներ գանձվել" diff --git a/Tethering/res/values-mcc311-mnc480-in/strings.xml b/Tethering/res/values-mcc311-mnc480-in/strings.xml index 98c6d71872..2b817f8abd 100644 --- a/Tethering/res/values-mcc311-mnc480-in/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-in/strings.xml @@ -16,9 +16,9 @@ - "Tidak ada koneksi internet di tethering" - "Perangkat tidak dapat terhubung" - "Nonaktifkan tethering" - "Hotspot atau tethering aktif" - "Biaya tambahan mungkin berlaku saat roaming" + "Tidak ada koneksi internet di tethering" + "Perangkat tidak dapat terhubung" + "Nonaktifkan tethering" + "Hotspot atau tethering aktif" + "Biaya tambahan mungkin berlaku saat roaming" diff --git a/Tethering/res/values-mcc311-mnc480-is/strings.xml b/Tethering/res/values-mcc311-mnc480-is/strings.xml index ade1b0110b..a338d9c7ca 100644 --- a/Tethering/res/values-mcc311-mnc480-is/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-is/strings.xml @@ -16,9 +16,9 @@ - "Tjóðrun er ekki með internettengingu" - "Tæki geta ekki tengst" - "Slökkva á tjóðrun" - "Kveikt er á heitum reit eða tjóðrun" - "Viðbótargjöld kunna að eiga við í reiki" + "Tjóðrun er ekki með internettengingu" + "Tæki geta ekki tengst" + "Slökkva á tjóðrun" + "Kveikt er á heitum reit eða tjóðrun" + "Viðbótargjöld kunna að eiga við í reiki" diff --git a/Tethering/res/values-mcc311-mnc480-it/strings.xml b/Tethering/res/values-mcc311-mnc480-it/strings.xml index 07e1526409..77769c2ac5 100644 --- a/Tethering/res/values-mcc311-mnc480-it/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-it/strings.xml @@ -16,9 +16,9 @@ - "Nessuna connessione a internet per il tethering" - "Impossibile connettere i dispositivi" - "Disattiva il tethering" - "Hotspot o tethering attivo" - "Potrebbero essere applicati costi aggiuntivi durante il roaming" + "Nessuna connessione a Internet per il tethering" + "Impossibile connettere i dispositivi" + "Disattiva il tethering" + "Hotspot o tethering attivi" + "Potrebbero essere applicati costi aggiuntivi durante il roaming" diff --git a/Tethering/res/values-mcc311-mnc480-iw/strings.xml b/Tethering/res/values-mcc311-mnc480-iw/strings.xml index ebebae8faf..5267b51264 100644 --- a/Tethering/res/values-mcc311-mnc480-iw/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-iw/strings.xml @@ -16,9 +16,9 @@ - "אי אפשר להפעיל את תכונת שיתוף האינטרנט בין מכשירים כי אין חיבור לאינטרנט" - "למכשירים אין אפשרות להתחבר" - "השבתה של שיתוף האינטרנט בין מכשירים" - "תכונת הנקודה לשיתוף אינטרנט או תכונת שיתוף האינטרנט בין מכשירים פועלת" - "ייתכנו חיובים נוספים במהלך נדידה" + "אי אפשר להפעיל את תכונת שיתוף האינטרנט בין מכשירים כי אין חיבור לאינטרנט" + "למכשירים אין אפשרות להתחבר" + "השבתה של שיתוף האינטרנט בין מכשירים" + "תכונת הנקודה לשיתוף אינטרנט או תכונת שיתוף האינטרנט בין מכשירים פועלת" + "ייתכנו חיובים נוספים בעת נדידה" diff --git a/Tethering/res/values-mcc311-mnc480-ja/strings.xml b/Tethering/res/values-mcc311-mnc480-ja/strings.xml index 334d3628a8..66a9a6dd35 100644 --- a/Tethering/res/values-mcc311-mnc480-ja/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-ja/strings.xml @@ -16,9 +16,9 @@ - "テザリングがインターネットに接続されていません" - "デバイスを接続できません" - "テザリングを OFF にする" - "アクセス ポイントまたはテザリングが ON です" - "ローミング時に追加料金が発生することがあります" + "テザリングがインターネットに接続されていません" + "デバイスを接続できません" + "テザリングを OFF にする" + "アクセス ポイントまたはテザリングが ON です" + "ローミング時に追加料金が発生することがあります" diff --git a/Tethering/res/values-mcc311-mnc480-ka/strings.xml b/Tethering/res/values-mcc311-mnc480-ka/strings.xml index d369d20267..d8ad880849 100644 --- a/Tethering/res/values-mcc311-mnc480-ka/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-ka/strings.xml @@ -16,9 +16,9 @@ - "ტეტერინგს არ აქვს ინტერნეტზე წვდომა" - "მოწყობილობები ვერ ახერხებენ დაკავშირებას" - "ტეტერინგის გამორთვა" - "ჩართულია უსადენო ქსელი ან ტეტერინგი" - "როუმინგის გამოყენებისას შეიძლება ჩამოგეჭრათ დამატებითი საფასური" + "ტეტერინგს არ აქვს ინტერნეტზე წვდომა" + "მოწყობილობები ვერ ახერხებენ დაკავშირებას" + "ტეტერინგის გამორთვა" + "ჩართულია უსადენო ქსელი ან ტეტერინგი" + "როუმინგის გამოყენებისას შეიძლება ჩამოგეჭრათ დამატებითი საფასური" diff --git a/Tethering/res/values-mcc311-mnc480-kk/strings.xml b/Tethering/res/values-mcc311-mnc480-kk/strings.xml index c16c106162..1ddd6b419b 100644 --- a/Tethering/res/values-mcc311-mnc480-kk/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-kk/strings.xml @@ -16,9 +16,9 @@ - "Тетеринг кезінде интернет байланысы жоқ" - "Құрылғыларды байланыстыру мүмкін емес" - "Тетерингіні өшіру" - "Хотспот немесе тетеринг қосулы" - "Роуминг кезінде қосымша ақы алынуы мүмкін." + "Тетеринг режимі интернет байланысынсыз пайдаланылуда" + "Құрылғыларды байланыстыру мүмкін емес" + "Тетерингіні өшіру" + "Хотспот немесе тетеринг қосулы" + "Роуминг кезінде қосымша ақы алынуы мүмкін." diff --git a/Tethering/res/values-mcc311-mnc480-km/strings.xml b/Tethering/res/values-mcc311-mnc480-km/strings.xml index 8084b877fd..cf5a1379cc 100644 --- a/Tethering/res/values-mcc311-mnc480-km/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-km/strings.xml @@ -16,9 +16,9 @@ - "ការភ្ជាប់​មិនមានអ៊ីនធឺណិត​ទេ" - "មិនអាច​ភ្ជាប់ឧបករណ៍​បានទេ" - "បិទការភ្ជាប់" - "ហតស្ប៉ត ឬការភ្ជាប់​ត្រូវបានបើក" - "អាចមាន​ការគិតថ្លៃ​បន្ថែម នៅពេល​រ៉ូមីង" + "ការភ្ជាប់​មិនមានអ៊ីនធឺណិត​ទេ" + "មិនអាច​ភ្ជាប់ឧបករណ៍​បានទេ" + "បិទការភ្ជាប់" + "ហតស្ប៉ត ឬការភ្ជាប់​ត្រូវបានបើក" + "អាចមាន​ការគិតថ្លៃ​បន្ថែម នៅពេល​រ៉ូមីង" diff --git a/Tethering/res/values-mcc311-mnc480-kn/strings.xml b/Tethering/res/values-mcc311-mnc480-kn/strings.xml index 528cdbf18a..68ae68bc19 100644 --- a/Tethering/res/values-mcc311-mnc480-kn/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-kn/strings.xml @@ -16,9 +16,9 @@ - "ಟೆಥರಿಂಗ್‌ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಕನೆಕ್ಷನ್ ಹೊಂದಿಲ್ಲ" - "ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ" - "ಟೆಥರಿಂಗ್‌ ಆಫ್ ಮಾಡಿ" - "ಹಾಟ್‌ಸ್ಪಾಟ್ ಅಥವಾ ಟೆಥರಿಂಗ್‌ ಆನ್ ಆಗಿದೆ" - "ರೋಮಿಂಗ್‌ನಲ್ಲಿರುವಾಗ ಹೆಚ್ಚುವರಿ ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು" + "ಟೆಥರಿಂಗ್‌ ಯಾವುದೇ ಇಂಟರ್ನೆಟ್ ಕನೆಕ್ಷನ್ ಹೊಂದಿಲ್ಲ" + "ಸಾಧನಗಳನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ" + "ಟೆಥರಿಂಗ್‌ ಆಫ್ ಮಾಡಿ" + "ಹಾಟ್‌ಸ್ಪಾಟ್ ಅಥವಾ ಟೆಥರಿಂಗ್‌ ಆನ್ ಆಗಿದೆ" + "ರೋಮಿಂಗ್‌ನಲ್ಲಿರುವಾಗ ಹೆಚ್ಚುವರಿ ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು" diff --git a/Tethering/res/values-mcc311-mnc480-ko/strings.xml b/Tethering/res/values-mcc311-mnc480-ko/strings.xml index f195c82daa..17185ba2d0 100644 --- a/Tethering/res/values-mcc311-mnc480-ko/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-ko/strings.xml @@ -16,9 +16,9 @@ - "테더링으로 인터넷을 사용할 수 없음" - "기기에서 연결할 수 없음" - "테더링 사용 중지" - "핫스팟 또는 테더링이 켜짐" - "로밍 중에는 추가 요금이 부과될 수 있습니다." + "테더링으로 인터넷을 사용할 수 없음" + "기기에서 연결할 수 없음" + "테더링 사용 중지" + "핫스팟 또는 테더링 켜짐" + "로밍 중에는 추가 요금이 발생할 수 있습니다." diff --git a/Tethering/res/values-mcc311-mnc480-ky/strings.xml b/Tethering/res/values-mcc311-mnc480-ky/strings.xml index f8ca53149e..6a9fb9810c 100644 --- a/Tethering/res/values-mcc311-mnc480-ky/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-ky/strings.xml @@ -16,9 +16,9 @@ - "Модем режими Интернети жок колдонулууда" - "Түзмөктөр туташпай жатат" - "Модем режимин өчүрүү" - "Байланыш түйүнү же модем режими күйүк" - "Роумингде кошумча акы алынышы мүмкүн" + "Модем режими Интернети жок колдонулууда" + "Түзмөктөр туташпай жатат" + "Модем режимин өчүрүү" + "Байланыш түйүнү же модем режими күйүк" + "Роумингде кошумча акы алынышы мүмкүн" diff --git a/Tethering/res/values-mcc311-mnc480-lo/strings.xml b/Tethering/res/values-mcc311-mnc480-lo/strings.xml index 6258bc0d9c..bcc4b57626 100644 --- a/Tethering/res/values-mcc311-mnc480-lo/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-lo/strings.xml @@ -16,9 +16,9 @@ - "ການປ່ອຍສັນຍານບໍ່ມີອິນເຕີເນັດ" - "ອຸປະກອນບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້" - "ປິດການປ່ອຍສັນຍານ" - "ຮັອດສະປອດ ຫຼື ການປ່ອຍສັນຍານເປີດຢູ່" - "ອາດມີຄ່າບໍລິການເພີ່ມເຕີມໃນລະຫວ່າງການໂຣມມິງ" + "ການປ່ອຍສັນຍານບໍ່ມີອິນເຕີເນັດ" + "ອຸປະກອນບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້" + "ປິດການປ່ອຍສັນຍານ" + "ເປີດໃຊ້ຮັອດສະປອດ ຫຼື ການປ່ອຍສັນຍານຢູ່" + "ອາດມີຄ່າໃຊ້ຈ່າຍເພີ່ມເຕີມໃນລະຫວ່າງການໂຣມມິງ" diff --git a/Tethering/res/values-mcc311-mnc480-lt/strings.xml b/Tethering/res/values-mcc311-mnc480-lt/strings.xml index 267c7f6666..011c2c11fb 100644 --- a/Tethering/res/values-mcc311-mnc480-lt/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-lt/strings.xml @@ -16,9 +16,9 @@ - "Nėra įrenginio kaip modemo naudojimo interneto ryšio" - "Nepavyko susieti įrenginių" - "Išjungti įrenginio kaip modemo naudojimą" - "Įjungtas viešosios interneto prieigos taškas arba įrenginio kaip modemo naudojimas" - "Veikiant tarptinkliniam ryšiui gali būti taikomi papildomi mokesčiai" + "Nėra įrenginio kaip modemo naudojimo interneto ryšio" + "Nepavyko susieti įrenginių" + "Išjungti įrenginio kaip modemo naudojimą" + "Įjungtas viešosios interneto prieigos taškas arba įrenginio kaip modemo naudojimas" + "Veikiant tarptinkliniam ryšiui gali būti taikomi papildomi mokesčiai" diff --git a/Tethering/res/values-mcc311-mnc480-lv/strings.xml b/Tethering/res/values-mcc311-mnc480-lv/strings.xml index ca54195c7d..5cb2f3b7aa 100644 --- a/Tethering/res/values-mcc311-mnc480-lv/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-lv/strings.xml @@ -16,9 +16,9 @@ - "Piesaistei nav interneta savienojuma" - "Nevar savienot ierīces" - "Izslēgt piesaisti" - "Ir ieslēgts tīklājs vai piesaiste" - "Viesabonēšanas laikā var tikt piemērota papildu maksa." + "Piesaistei nav interneta savienojuma" + "Nevar savienot ierīces" + "Izslēgt piesaisti" + "Ir ieslēgts tīklājs vai piesaiste" + "Viesabonēšanas laikā var tikt piemērota papildu samaksa" diff --git a/Tethering/res/values-mcc311-mnc480-mk/strings.xml b/Tethering/res/values-mcc311-mnc480-mk/strings.xml index 8c2b8aa8aa..4cbfd887c5 100644 --- a/Tethering/res/values-mcc311-mnc480-mk/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-mk/strings.xml @@ -16,9 +16,9 @@ - "Нема интернет преку мобилен" - "Уредите не може да се поврзат" - "Исклучи интернет преку мобилен" - "Вклучено: точка на пристап или интернет преку мобилен" - "При роаминг може да се наплатат дополнителни трошоци" + "Нема интернет преку мобилен" + "Уредите не може да се поврзат" + "Исклучи интернет преку мобилен" + "Точката на пристап или интернетот преку мобилен е вклучен" + "При роаминг може да се наплатат дополнителни трошоци" diff --git a/Tethering/res/values-mcc311-mnc480-ml/strings.xml b/Tethering/res/values-mcc311-mnc480-ml/strings.xml index 9a722c564f..9cf4eaf34a 100644 --- a/Tethering/res/values-mcc311-mnc480-ml/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-ml/strings.xml @@ -16,9 +16,9 @@ - "ടെതറിംഗിന് ഇന്റർനെറ്റ് ഇല്ല" - "ഉപകരണങ്ങൾ കണക്റ്റ് ചെയ്യാനാവില്ല" - "ടെതറിംഗ് ഓഫാക്കുക" - "ഹോട്ട്‌സ്‌പോട്ട് അല്ലെങ്കിൽ ടെതറിംഗ് ഓണാണ്" - "റോമിംഗ് ചെയ്യുമ്പോൾ അധിക നിരക്കുകൾ ബാധകമായേക്കാം" + "ടെതറിംഗിന് ഇന്റർനെറ്റ് ഇല്ല" + "ഉപകരണങ്ങൾ കണക്റ്റ് ചെയ്യാനാവില്ല" + "ടെതറിംഗ് ഓഫാക്കുക" + "ഹോട്ട്‌സ്‌പോട്ട് അല്ലെങ്കിൽ ടെതറിംഗ് ഓണാണ്" + "റോമിംഗ് ചെയ്യുമ്പോൾ അധിക നിരക്കുകൾ ബാധകമായേക്കാം" diff --git a/Tethering/res/values-mcc311-mnc480-mn/strings.xml b/Tethering/res/values-mcc311-mnc480-mn/strings.xml index f6fcf01e14..47c82c14d9 100644 --- a/Tethering/res/values-mcc311-mnc480-mn/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-mn/strings.xml @@ -16,9 +16,9 @@ - "Модем болгоход ямар ч интернэт байхгүй байна" - "Төхөөрөмжүүд холбогдох боломжгүй байна" - "Модем болгохыг унтраах" - "Сүлжээний цэг эсвэл модем болгох асаалттай байна" - "Роумингийн үеэр нэмэлт төлбөр тооцогдож магадгүй" + "Модемд интернэт алга байна" + "Төхөөрөмжүүд холбогдох боломжгүй байна" + "Модем болгохыг унтраах" + "Сүлжээний цэг эсвэл модем болгох асаалттай байна" + "Роумингийн үеэр нэмэлт төлбөр нэхэмжилж болзошгүй" diff --git a/Tethering/res/values-mcc311-mnc480-mr/strings.xml b/Tethering/res/values-mcc311-mnc480-mr/strings.xml index 2563e15f29..ad9e809ab2 100644 --- a/Tethering/res/values-mcc311-mnc480-mr/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-mr/strings.xml @@ -16,9 +16,9 @@ - "टेदरिंगसाठी इंटरनेट नाही" - "डिव्हाइस कनेक्ट होऊ शकत नाहीत" - "टेदरिंग बंद करा" - "हॉटस्पॉट किंवा टेदरिंग सुरू आहे" - "रोमिंगदरम्यान अतिरिक्त शुल्के लागू होऊ शकतात" + "टेदरिंगला इंटरनेट नाही" + "डिव्हाइस कनेक्ट होऊ शकत नाहीत" + "टेदरिंग बंद करा" + "हॉटस्पॉट किंवा टेदरिंग सुरू आहे" + "रोमिंगदरम्यान अतिरिक्त शुल्क लागू होऊ शकतात" diff --git a/Tethering/res/values-mcc311-mnc480-ms/strings.xml b/Tethering/res/values-mcc311-mnc480-ms/strings.xml index b2ccbbbf53..e708cb8717 100644 --- a/Tethering/res/values-mcc311-mnc480-ms/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-ms/strings.xml @@ -16,9 +16,9 @@ - "Penambatan tiada Internet" - "Peranti tidak dapat disambungkan" - "Matikan penambatan" - "Tempat liputan atau penambatan dihidupkan" - "Caj tambahan boleh dikenakan semasa perayauan" + "Penambatan tiada Internet" + "Peranti tidak dapat disambungkan" + "Matikan penambatan" + "Tempat liputan atau penambatan dihidupkan" + "Caj tambahan mungkin digunakan semasa perayauan" diff --git a/Tethering/res/values-mcc311-mnc480-my/strings.xml b/Tethering/res/values-mcc311-mnc480-my/strings.xml index 2281b7beec..ba5462250b 100644 --- a/Tethering/res/values-mcc311-mnc480-my/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-my/strings.xml @@ -16,9 +16,9 @@ - "မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်းတွင် အင်တာနက် မရှိပါ" - "စက်ပစ္စည်းများကို ချိတ်ဆက်၍မရပါ" - "မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း ပိတ်ရန်" - "ဟော့စပေါ့ (သို့) မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း ဖွင့်ထားသည်" - "ပြင်ပကွန်ရက်သုံးနေစဉ် နောက်ထပ်ကျသင့်ငွေ ပေးရနိုင်သည်" + "မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်းတွင် အင်တာနက် မရှိပါ" + "စက်များ ချိတ်ဆက်၍ မရပါ" + "မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း ပိတ်ရန်" + "ဟော့စပေါ့ (သို့) မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း ဖွင့်ထားသည်" + "ပြင်ပကွန်ရက်နှင့် ချိတ်ဆက်သည့်အခါ နောက်ထပ်ကျသင့်မှုများ ရှိနိုင်သည်" diff --git a/Tethering/res/values-mcc311-mnc480-nb/strings.xml b/Tethering/res/values-mcc311-mnc480-nb/strings.xml index 92e630017a..57db484a25 100644 --- a/Tethering/res/values-mcc311-mnc480-nb/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-nb/strings.xml @@ -16,9 +16,9 @@ - "Internettdeling har ikke internettilgang" - "Enheter kan ikke koble til" - "Slå av internettdeling" - "Wifi-sone eller internettdeling er på" - "Det kan påløpe flere kostnader ved roaming" + "Internettdeling har ikke internettilgang" + "Enhetene kan ikke koble til" + "Slå av internettdeling" + "Wi-Fi-sone eller internettdeling er på" + "Ytterligere kostnader kan påløpe under roaming" diff --git a/Tethering/res/values-mcc311-mnc480-ne/strings.xml b/Tethering/res/values-mcc311-mnc480-ne/strings.xml index bfd6108aaf..1503244f50 100644 --- a/Tethering/res/values-mcc311-mnc480-ne/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-ne/strings.xml @@ -16,9 +16,9 @@ - "टेदरिङमार्फत इन्टरनेट कनेक्ट गर्न सकिएन" - "डिभाइसहरू कनेक्ट गर्न सकिएन" - "टेदरिङ अफ गर्नुहोस्" - "हटस्पट वा टेदरिङ अन छ" - "रोमिङ सेवा प्रयोग गर्दा अतिरिक्त शुल्क लाग्न सक्छ" + "टेदरिङमार्फत इन्टरनेट कनेक्सन प्राप्त हुन सकेन" + "यन्त्रहरू कनेक्ट गर्न सकिएन" + "टेदरिङ निष्क्रिय पार्नुहोस्" + "हटस्पट वा टेदरिङ सक्रिय छ" + "रोमिङ सेवा प्रयोग गर्दा अतिरिक्त शुल्क लाग्न सक्छ" diff --git a/Tethering/res/values-mcc311-mnc480-nl/strings.xml b/Tethering/res/values-mcc311-mnc480-nl/strings.xml index 7533b6ffeb..b08133f4e5 100644 --- a/Tethering/res/values-mcc311-mnc480-nl/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-nl/strings.xml @@ -16,9 +16,9 @@ - "Tethering heeft geen internet" - "Apparaten kunnen geen verbinding maken" - "Tethering uitzetten" - "Hotspot of tethering staat aan" - "Er kunnen extra kosten voor roaming in rekening worden gebracht" + "Tethering heeft geen internet" + "Apparaten kunnen niet worden verbonden" + "Tethering uitschakelen" + "Hotspot of tethering is ingeschakeld" + "Er kunnen extra kosten voor roaming in rekening worden gebracht." diff --git a/Tethering/res/values-mcc311-mnc480-or/strings.xml b/Tethering/res/values-mcc311-mnc480-or/strings.xml index 7324de1a00..1ad4ca354a 100644 --- a/Tethering/res/values-mcc311-mnc480-or/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-or/strings.xml @@ -16,9 +16,9 @@ - "ଟିଥରିଂ ପାଇଁ କୌଣସି ଇଣ୍ଟରନେଟ କନେକ୍ସନ ନାହିଁ" - "ଡିଭାଇସଗୁଡ଼ିକୁ କନେକ୍ଟ କରାଯାଇପାରିବ ନାହିଁ" - "ଟିଥରିଂକୁ ବନ୍ଦ କରନ୍ତୁ" - "ହଟସ୍ପଟ କିମ୍ବା ଟିଥରିଂ ଚାଲୁ ଅଛି" - "ରୋମିଂ ସମୟରେ ଅତିରିକ୍ତ ଚାର୍ଜ ଲାଗୁ ହୋଇପାରେ" + "ଟିଥରିଂ ପାଇଁ କୌଣସି ଇଣ୍ଟର୍ନେଟ୍ ସଂଯୋଗ ନାହିଁ" + "ଡିଭାଇସଗୁଡ଼ିକ ସଂଯୋଗ କରାଯାଇପାରିବ ନାହିଁ" + "ଟିଥରିଂ ବନ୍ଦ କରନ୍ତୁ" + "ହଟସ୍ପଟ୍ କିମ୍ବା ଟିଥରିଂ ଚାଲୁ ଅଛି" + "ରୋମିଂରେ ଥିବା ସମୟରେ ଅତିରିକ୍ତ ଶୁଳ୍କ ଲାଗୁ ହୋଇପାରେ" diff --git a/Tethering/res/values-mcc311-mnc480-pa/strings.xml b/Tethering/res/values-mcc311-mnc480-pa/strings.xml index 0ca0af5ebd..88def563d8 100644 --- a/Tethering/res/values-mcc311-mnc480-pa/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-pa/strings.xml @@ -16,9 +16,9 @@ - "ਟੈਦਰਿੰਗ ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ" - "ਡੀਵਾਈਸ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤੇ ਜਾ ਸਕਦੇ" - "ਟੈਦਰਿੰਗ ਬੰਦ ਕਰੋ" - "ਹੌਟਸਪੌਟ ਜਾਂ ਟੈਦਰਿੰਗ ਚਾਲੂ ਹੈ" - "ਰੋਮਿੰਗ ਦੌਰਾਨ ਵਧੀਕ ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ" + "ਟੈਦਰਿੰਗ ਕੋਲ ਇੰਟਰਨੈੱਟ ਪਹੁੰਚ ਨਹੀਂ ਹੈ" + "ਡੀਵਾਈਸ ਕਨੈਕਟ ਨਹੀਂ ਕੀਤੇ ਜਾ ਸਕਦੇ" + "ਟੈਦਰਿੰਗ ਬੰਦ ਕਰੋ" + "ਹੌਟਸਪੌਟ ਜਾਂ ਟੈਦਰਿੰਗ ਚਾਲੂ ਹੈ" + "ਰੋਮਿੰਗ ਦੌਰਾਨ ਵਧੀਕ ਖਰਚੇ ਲਾਗੂ ਹੋ ਸਕਦੇ ਹਨ" diff --git a/Tethering/res/values-mcc311-mnc480-pl/strings.xml b/Tethering/res/values-mcc311-mnc480-pl/strings.xml index 62bb68c64f..f9890abdc2 100644 --- a/Tethering/res/values-mcc311-mnc480-pl/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-pl/strings.xml @@ -16,9 +16,9 @@ - "Tethering nie ma internetu" - "Urządzenia nie mogą się połączyć" - "Wyłącz tethering" - "Hotspot lub tethering jest włączony" - "Podczas korzystania z roamingu mogą zostać naliczone dodatkowe opłaty" + "Tethering nie ma internetu" + "Urządzenia nie mogą się połączyć" + "Wyłącz tethering" + "Hotspot lub tethering jest włączony" + "Podczas korzystania z roamingu mogą zostać naliczone dodatkowe opłaty" diff --git a/Tethering/res/values-mcc311-mnc480-pt-rBR/strings.xml b/Tethering/res/values-mcc311-mnc480-pt-rBR/strings.xml index ae033fa98d..ce3b88479f 100644 --- a/Tethering/res/values-mcc311-mnc480-pt-rBR/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-pt-rBR/strings.xml @@ -16,9 +16,9 @@ - "O tethering não tem uma conexão de Internet" - "Não é possível conectar os dispositivos" - "Desativar o tethering" - "O ponto de acesso ou tethering está ativado" - "Pode haver cobranças extras durante o roaming" + "O tethering não tem Internet" + "Não é possível conectar os dispositivos" + "Desativar o tethering" + "Ponto de acesso ou tethering ativado" + "Pode haver cobranças extras durante o roaming" diff --git a/Tethering/res/values-mcc311-mnc480-pt-rPT/strings.xml b/Tethering/res/values-mcc311-mnc480-pt-rPT/strings.xml index c54486425d..7e883ea576 100644 --- a/Tethering/res/values-mcc311-mnc480-pt-rPT/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-pt-rPT/strings.xml @@ -16,9 +16,9 @@ - "A ligação (à Internet) via telemóvel não tem Internet" - "Não é possível ligar os dispositivos" - "Desativar ligação (à Internet) via telemóvel" - "A zona Wi-Fi ou a ligação (à Internet) via telemóvel está ativada" - "Podem aplicar-se custos adicionais em roaming" + "A ligação (à Internet) via telemóvel não tem Internet" + "Não é possível ligar os dispositivos" + "Desativar ligação (à Internet) via telemóvel" + "A zona Wi-Fi ou a ligação (à Internet) via telemóvel está ativada" + "Podem aplicar-se custos adicionais em roaming." diff --git a/Tethering/res/values-mcc311-mnc480-pt/strings.xml b/Tethering/res/values-mcc311-mnc480-pt/strings.xml index ae033fa98d..ce3b88479f 100644 --- a/Tethering/res/values-mcc311-mnc480-pt/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-pt/strings.xml @@ -16,9 +16,9 @@ - "O tethering não tem uma conexão de Internet" - "Não é possível conectar os dispositivos" - "Desativar o tethering" - "O ponto de acesso ou tethering está ativado" - "Pode haver cobranças extras durante o roaming" + "O tethering não tem Internet" + "Não é possível conectar os dispositivos" + "Desativar o tethering" + "Ponto de acesso ou tethering ativado" + "Pode haver cobranças extras durante o roaming" diff --git a/Tethering/res/values-mcc311-mnc480-ro/strings.xml b/Tethering/res/values-mcc311-mnc480-ro/strings.xml index 484b8b39da..1009417316 100644 --- a/Tethering/res/values-mcc311-mnc480-ro/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-ro/strings.xml @@ -16,9 +16,9 @@ - "Procesul de tethering nu are internet" - "Dispozitivele nu se pot conecta" - "Dezactivează tetheringul" - "S-a activat hotspotul sau tetheringul" - "Se pot aplica taxe suplimentare pentru roaming" + "Procesul de tethering nu are internet" + "Dispozitivele nu se pot conecta" + "Dezactivați procesul de tethering" + "S-a activat hotspotul sau tethering" + "Se pot aplica taxe suplimentare pentru roaming" diff --git a/Tethering/res/values-mcc311-mnc480-ru/strings.xml b/Tethering/res/values-mcc311-mnc480-ru/strings.xml index ef387033eb..88683bed95 100644 --- a/Tethering/res/values-mcc311-mnc480-ru/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-ru/strings.xml @@ -16,9 +16,9 @@ - "Режим модема используется без доступа к интернету" - "Невозможно подключить устройства." - "Отключить режим модема" - "Включена точка доступа или режим модема" - "За использование услуг связи в роуминге может взиматься дополнительная плата." + "Режим модема используется без доступа к Интернету" + "Невозможно подключить устройства." + "Отключить режим модема" + "Включены точка доступа или режим модема" + "За использование услуг связи в роуминге может взиматься дополнительная плата." diff --git a/Tethering/res/values-mcc311-mnc480-si/strings.xml b/Tethering/res/values-mcc311-mnc480-si/strings.xml index 3069085dba..176bcdb797 100644 --- a/Tethering/res/values-mcc311-mnc480-si/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-si/strings.xml @@ -16,9 +16,9 @@ - "ටෙදරින් හට අන්තර්ජාලය නැත" - "උපාංගවලට සම්බන්ධ විය නොහැක" - "ටෙදරින් ක්‍රියාවිරහිත කරන්න" - "හොට්ස්පොට් හෝ ටෙදරින් ක්‍රියාත්මකයි" - "රෝමිං අතරේ අතිරේක ගාස්තු අදාළ විය හැක" + "ටෙදරින් හට අන්තර්ජාලය නැත" + "උපාංගවලට සම්බන්ධ විය නොහැකිය" + "ටෙදරින් ක්‍රියාවිරහිත කරන්න" + "හොට්ස්පොට් හෝ ටෙදරින් ක්‍රියාත්මකයි" + "රෝමිං අතරතුර අමතර ගාස්තු අදාළ විය හැකිය" diff --git a/Tethering/res/values-mcc311-mnc480-sk/strings.xml b/Tethering/res/values-mcc311-mnc480-sk/strings.xml index 9f70311343..b9e2127fa8 100644 --- a/Tethering/res/values-mcc311-mnc480-sk/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-sk/strings.xml @@ -16,9 +16,9 @@ - "Tethering nemá internetové pripojenie" - "Zariadenia sa nemôžu pripojiť" - "Vypnúť tethering" - "Je zapnutý hotspot alebo tethering" - "Počas roamingu vám môžu byť účtované ďalšie poplatky" + "Tethering nemá internetové pripojenie" + "Zariadenia sa nemôžu pripojiť" + "Vypnúť tethering" + "Je zapnutý hotspot alebo tethering" + "Počas roamingu vám môžu byť účtované ďalšie poplatky" diff --git a/Tethering/res/values-mcc311-mnc480-sl/strings.xml b/Tethering/res/values-mcc311-mnc480-sl/strings.xml index 613d7a887d..e8140e686a 100644 --- a/Tethering/res/values-mcc311-mnc480-sl/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-sl/strings.xml @@ -16,9 +16,9 @@ - "Povezava računalnika z internetom prek mobilnega telefona nima internetne povezave" - "Napravi se ne moreta povezati" - "Izklopi povezavo računalnika z internetom prek mobilnega telefona" - "Dostopna točka ali povezava računalnika z internetom prek mobilnega telefona je vklopljena" - "Med gostovanjem lahko nastanejo dodatni stroški." + "Internetna povezava prek mobilnega telefona ni vzpostavljena" + "Napravi se ne moreta povezati" + "Izklopi internetno povezavo prek mobilnega telefona" + "Dostopna točka ali internetna povezava prek mobilnega telefona je vklopljena" + "Med gostovanjem lahko nastanejo dodatni stroški" diff --git a/Tethering/res/values-mcc311-mnc480-sq/strings.xml b/Tethering/res/values-mcc311-mnc480-sq/strings.xml index 0472d4ddbd..61e698d6e8 100644 --- a/Tethering/res/values-mcc311-mnc480-sq/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-sq/strings.xml @@ -16,9 +16,9 @@ - "Ndarja e internetit nuk ka internet" - "Pajisjet nuk mund të lidhen" - "Çaktivizo ndarjen e internetit" - "Zona e qasjes për internet ose ndarja e internetit është aktive" - "Mund të zbatohen tarifime shtesë kur je në roaming" + "Ndarja e internetit nuk ka internet" + "Pajisjet nuk mund të lidhen" + "Çaktivizo ndarjen e internetit" + "Zona e qasjes për internet ose ndarja e internetit është aktive" + "Mund të zbatohen tarifime shtesë kur je në roaming" diff --git a/Tethering/res/values-mcc311-mnc480-sr/strings.xml b/Tethering/res/values-mcc311-mnc480-sr/strings.xml index bc70cf6762..b4c411c354 100644 --- a/Tethering/res/values-mcc311-mnc480-sr/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-sr/strings.xml @@ -16,9 +16,9 @@ - "Привезивање нема приступ интернету" - "Повезивање уређаја није успело" - "Искључи привезивање" - "Укључен је хотспот или привезивање" - "Можда важе додатни трошкови у ромингу" + "Привезивање нема приступ интернету" + "Повезивање уређаја није успело" + "Искључи привезивање" + "Укључен је хотспот или привезивање" + "Можда важе додатни трошкови у ромингу" diff --git a/Tethering/res/values-mcc311-mnc480-sv/strings.xml b/Tethering/res/values-mcc311-mnc480-sv/strings.xml index 507acc82b9..4f543e47b9 100644 --- a/Tethering/res/values-mcc311-mnc480-sv/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-sv/strings.xml @@ -16,9 +16,9 @@ - "Det finns ingen internetanslutning för internetdelningen" - "Enheterna kan inte anslutas" - "Inaktivera internetdelning" - "Surfzon eller internetdelning har aktiverats" - "Ytterligare avgifter kan tillkomma vid roaming" + "Det finns ingen internetanslutning för internetdelningen" + "Enheterna kan inte anslutas" + "Inaktivera internetdelning" + "Surfzon eller internetdelning har aktiverats" + "Ytterligare avgifter kan tillkomma vid roaming" diff --git a/Tethering/res/values-mcc311-mnc480-sw/strings.xml b/Tethering/res/values-mcc311-mnc480-sw/strings.xml index 865b0e38a5..ac347ab485 100644 --- a/Tethering/res/values-mcc311-mnc480-sw/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-sw/strings.xml @@ -16,9 +16,9 @@ - "Huduma ya kusambaza mtandao haina muunganisho wa intaneti" - "Imeshindwa kuunganisha vifaa" - "Zima kipengele cha kusambaza mtandao" - "Umewasha kipengele cha kusambaza mtandao au mtandao pepe" - "Huenda ukatozwa gharama za ziada ukitumia mitandao ya ng\'ambo" + "Kipengele cha kusambaza mtandao hakina intaneti" + "Imeshindwa kuunganisha vifaa" + "Zima kipengele cha kusambaza mtandao" + "Umewasha kipengele cha kusambaza mtandao au mtandao pepe" + "Huenda ukatozwa gharama za ziada ukitumia mitandao ya ng\'ambo" diff --git a/Tethering/res/values-mcc311-mnc480-ta/strings.xml b/Tethering/res/values-mcc311-mnc480-ta/strings.xml index e5f33ee9bb..2ea2467e58 100644 --- a/Tethering/res/values-mcc311-mnc480-ta/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-ta/strings.xml @@ -16,9 +16,9 @@ - "இணைப்பு முறைக்கு இணைய இணைப்பு இல்லை" - "சாதனங்களால் இணைய முடியவில்லை" - "இணைப்பு முறையை முடக்கு" - "ஹாட்ஸ்பாட் அல்லது இணைப்பு முறை இயக்கப்பட்டுள்ளது" - "ரோமிங்கின்போது கூடுதல் கட்டணங்கள் விதிக்கப்படலாம்" + "இணைப்பு முறைக்கு இணைய இணைப்பு இல்லை" + "சாதனங்களால் இணைய முடியவில்லை" + "இணைப்பு முறையை ஆஃப் செய்" + "ஹாட்ஸ்பாட் அல்லது இணைப்பு முறை ஆன் செய்யப்பட்டுள்ளது" + "ரோமிங்கின்போது கூடுதல் கட்டணங்கள் விதிக்கப்படக்கூடும்" diff --git a/Tethering/res/values-mcc311-mnc480-te/strings.xml b/Tethering/res/values-mcc311-mnc480-te/strings.xml index 1bb4786ed7..9360297dd8 100644 --- a/Tethering/res/values-mcc311-mnc480-te/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-te/strings.xml @@ -16,9 +16,9 @@ - "టెథరింగ్ చేయడానికి ఇంటర్నెట్ కనెక్షన్ లేదు" - "పరికరాలు కనెక్ట్ అవ్వడం లేదు" - "టెథరింగ్‌ను ఆఫ్ చేయండి" - "హాట్‌స్పాట్ లేదా టెథరింగ్ ఆన్‌లో ఉంది" - "రోమింగ్‌లో ఉన్నప్పుడు అదనపు ఛార్జీలు వర్తించవచ్చు" + "టెథరింగ్ చేయడానికి ఇంటర్నెట్ కనెక్షన్ లేదు" + "పరికరాలు కనెక్ట్ అవ్వడం లేదు" + "టెథరింగ్‌ను ఆఫ్ చేయండి" + "హాట్‌స్పాట్ లేదా టెథరింగ్ ఆన్‌లో ఉంది" + "రోమింగ్‌లో ఉన్నప్పుడు అదనపు ఛార్జీలు వర్తించవచ్చు" diff --git a/Tethering/res/values-mcc311-mnc480-th/strings.xml b/Tethering/res/values-mcc311-mnc480-th/strings.xml index e76f735b45..9c4d7e08f2 100644 --- a/Tethering/res/values-mcc311-mnc480-th/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-th/strings.xml @@ -16,9 +16,9 @@ - "ไม่มีอินเทอร์เน็ตสำหรับการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ" - "อุปกรณ์เชื่อมต่อไม่ได้" - "ปิดการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ" - "ฮอตสปอตหรือการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือเปิดอยู่" - "อาจมีค่าใช้จ่ายเพิ่มเติมขณะโรมมิ่ง" + "การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือไม่มีอินเทอร์เน็ต" + "อุปกรณ์เชื่อมต่อไม่ได้" + "ปิดการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ" + "ฮอตสปอตหรือการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือเปิดอยู่" + "อาจมีค่าใช้จ่ายเพิ่มเติมขณะโรมมิ่ง" diff --git a/Tethering/res/values-mcc311-mnc480-tl/strings.xml b/Tethering/res/values-mcc311-mnc480-tl/strings.xml index cccc8c4308..a7c78a5932 100644 --- a/Tethering/res/values-mcc311-mnc480-tl/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-tl/strings.xml @@ -16,9 +16,9 @@ - "Walang internet ang pag-tether" - "Hindi makakonekta ang mga device" - "I-off ang pag-tether" - "Naka-on ang hotspot o pag-tether" - "Posibleng magkaroon ng mga karagdagang singil habang nagro-roam" + "Walang internet ang pag-tether" + "Hindi makakonekta ang mga device" + "I-off ang pag-tether" + "Naka-on ang Hotspot o pag-tether" + "Posibleng magkaroon ng mga karagdagang singil habang nagro-roam" diff --git a/Tethering/res/values-mcc311-mnc480-tr/strings.xml b/Tethering/res/values-mcc311-mnc480-tr/strings.xml index 93bef125eb..93da2c3f79 100644 --- a/Tethering/res/values-mcc311-mnc480-tr/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-tr/strings.xml @@ -16,9 +16,9 @@ - "Tethering\'in internet bağlantısı yok" - "Cihazlar bağlanamıyor" - "Tethering\'i kapat" - "Hotspot veya tethering açık" - "Dolaşım sırasında ek ücretler uygulanabilir" + "Tethering\'in internet bağlantısı yok" + "Cihazlar bağlanamıyor" + "Tethering\'i kapat" + "Hotspot veya tethering açık" + "Dolaşım sırasında ek ücretler uygulanabilir" diff --git a/Tethering/res/values-mcc311-mnc480-uk/strings.xml b/Tethering/res/values-mcc311-mnc480-uk/strings.xml index 1bc2c06dd4..ee0dcd2c4b 100644 --- a/Tethering/res/values-mcc311-mnc480-uk/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-uk/strings.xml @@ -16,9 +16,9 @@ - "Телефон, що використовується як модем, не підключений до Інтернету" - "Не вдається підключити пристрої" - "Вимкнути використання телефона як модема" - "Увімкнено точку доступу або використання телефона як модема" - "У роумінгу може стягуватися додаткова плата" + "Телефон, який використовується як модем, не підключений до Інтернету" + "Не вдається підключити пристрої" + "Вимкнути використання телефона як модема" + "Увімкнено точку доступу або використання телефона як модема" + "У роумінгу може стягуватися додаткова плата" diff --git a/Tethering/res/values-mcc311-mnc480-ur/strings.xml b/Tethering/res/values-mcc311-mnc480-ur/strings.xml index 63d8e1bf7d..41cd28eef9 100644 --- a/Tethering/res/values-mcc311-mnc480-ur/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-ur/strings.xml @@ -16,9 +16,9 @@ - "ٹیدرنگ میں انٹرنیٹ نہیں ہے" - "آلات منسلک نہیں ہو سکتے" - "ٹیدرنگ آف کریں" - "ہاٹ اسپاٹ یا ٹیدرنگ آن ہے" - "رومنگ کے دوران اضافی چارجز لاگو ہو سکتے ہیں" + "ٹیدرنگ میں انٹرنیٹ نہیں ہے" + "آلات منسلک نہیں ہو سکتے" + "ٹیدرنگ آف کریں" + "ہاٹ اسپاٹ یا ٹیدرنگ آن ہے" + "رومنگ کے دوران اضافی چارجز لاگو ہو سکتے ہیں" diff --git a/Tethering/res/values-mcc311-mnc480-uz/strings.xml b/Tethering/res/values-mcc311-mnc480-uz/strings.xml index 4d655d97ae..c847bc943b 100644 --- a/Tethering/res/values-mcc311-mnc480-uz/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-uz/strings.xml @@ -16,9 +16,9 @@ - "Modem internetga ulanmagan" - "Qurilmalar ulanmadi" - "Modem rejimini faolsizlantirish" - "Hotspot yoki modem rejimi yoniq" - "Rouming vaqtida qoʻshimcha haq olinishi mumkin" + "Modem internetga ulanmagan" + "Qurilmalar ulanmadi" + "Modem rejimini faolsizlantirish" + "Hotspot yoki modem rejimi yoniq" + "Rouming vaqtida qoʻshimcha haq olinishi mumkin" diff --git a/Tethering/res/values-mcc311-mnc480-vi/strings.xml b/Tethering/res/values-mcc311-mnc480-vi/strings.xml index 15e7a01553..a74326f09e 100644 --- a/Tethering/res/values-mcc311-mnc480-vi/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-vi/strings.xml @@ -16,9 +16,9 @@ - "Không có Internet để chia sẻ Internet" - "Các thiết bị không thể kết nối" - "Tắt tính năng chia sẻ Internet" - "Điểm phát sóng hoặc tính năng chia sẻ Internet đang bật" - "Bạn có thể mất thêm phí dữ liệu khi chuyển vùng" + "Không có Internet để chia sẻ kết Internet" + "Các thiết bị không thể kết nối" + "Tắt tính năng chia sẻ Internet" + "Điểm phát sóng hoặc tính năng chia sẻ Internet đang bật" + "Bạn có thể mất thêm phí dữ liệu khi chuyển vùng" diff --git a/Tethering/res/values-mcc311-mnc480-zh-rCN/strings.xml b/Tethering/res/values-mcc311-mnc480-zh-rCN/strings.xml index 8a200aa262..d7370036e3 100644 --- a/Tethering/res/values-mcc311-mnc480-zh-rCN/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-zh-rCN/strings.xml @@ -16,9 +16,9 @@ - "共享网络未连接到互联网" - "设备无法连接" - "关闭网络共享" - "热点或网络共享已开启" - "漫游时可能会产生额外的费用" + "共享网络未连接到互联网" + "设备无法连接" + "关闭网络共享" + "热点或网络共享已开启" + "漫游时可能会产生额外的费用" diff --git a/Tethering/res/values-mcc311-mnc480-zh-rHK/strings.xml b/Tethering/res/values-mcc311-mnc480-zh-rHK/strings.xml index b2e64d1825..f378a9dc2c 100644 --- a/Tethering/res/values-mcc311-mnc480-zh-rHK/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-zh-rHK/strings.xml @@ -16,9 +16,9 @@ - "無法透過網絡共享連線至互聯網" - "裝置無法連接" - "關閉網絡共享" - "熱點或網絡共享已開啟" - "漫遊時可能需要支付額外費用" + "無法透過網絡共享連線至互聯網" + "裝置無法連接" + "關閉網絡共享" + "熱點或網絡共享已開啟" + "漫遊時可能需要支付額外費用" diff --git a/Tethering/res/values-mcc311-mnc480-zh-rTW/strings.xml b/Tethering/res/values-mcc311-mnc480-zh-rTW/strings.xml index 0d7ddf2b76..cd653df1da 100644 --- a/Tethering/res/values-mcc311-mnc480-zh-rTW/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-zh-rTW/strings.xml @@ -16,9 +16,9 @@ - "無法透過網路共用連上網際網路" - "裝置無法連線" - "關閉網路共用" - "無線基地台或網路共用已開啟" - "使用漫遊服務可能須支付額外費用" + "無法透過網路共用連上網際網路" + "裝置無法連線" + "關閉網路共用" + "無線基地台或網路共用已開啟" + "使用漫遊服務可能須支付額外費用" diff --git a/Tethering/res/values-mcc311-mnc480-zu/strings.xml b/Tethering/res/values-mcc311-mnc480-zu/strings.xml index d18f079599..32f6df56f1 100644 --- a/Tethering/res/values-mcc311-mnc480-zu/strings.xml +++ b/Tethering/res/values-mcc311-mnc480-zu/strings.xml @@ -16,9 +16,9 @@ - "Ukusebenzisa ifoni njengemodemu akunayo i-inthanethi" - "Amadivayisi awakwazi ukuxhuma" - "Vala ukusebenzisa ifoni njengemodemu" - "I-hotspot noma ukusebenzisa ifoni njengemodemu kuvuliwe" - "Kungaba nezinkokhelo ezengeziwe uma uzula" + "Ukusebenzisa ifoni njengemodemu akunayo i-inthanethi" + "Amadivayisi awakwazi ukuxhumeka" + "Vala ukusebenzisa ifoni njengemodemu" + "I-hotspot noma ukusebenzisa ifoni njengemodemu kuvuliwe" + "Kungaba nezinkokhelo ezengeziwe uma uzula" diff --git a/Tethering/res/values-mk/strings.xml b/Tethering/res/values-mk/strings.xml index f1b15e69c1..9ad9b9a589 100644 --- a/Tethering/res/values-mk/strings.xml +++ b/Tethering/res/values-mk/strings.xml @@ -16,14 +16,14 @@ - "Активно: интернет преку мобилен или точка на пристап" - "Допрете за поставување." - "Интернетот преку мобилен е оневозможен" - "За детали, контактирајте со администраторот" - "Статус на точка на пристап и интернет преку мобилен" - - - - - + "Активно е врзување или точка на пристап" + "Допрете за поставување." + "Врзувањето е оневозможено" + "Контактирајте со администраторот за детали" + "Статус на точката на пристап и врзувањето" + + + + + diff --git a/Tethering/res/values-ml/strings.xml b/Tethering/res/values-ml/strings.xml index 8182b11f94..9db79ce220 100644 --- a/Tethering/res/values-ml/strings.xml +++ b/Tethering/res/values-ml/strings.xml @@ -16,14 +16,14 @@ - "ടെതറിംഗ് അല്ലെങ്കിൽ ഹോട്ട്സ്‌പോട്ട് സജീവമാണ്" - "സജ്ജീകരിക്കാൻ ടാപ്പ് ചെയ്യുക." - "ടെതറിംഗ് പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു" - "വിശദാംശങ്ങൾക്ക് നിങ്ങളുടെ അഡ്മിനെ ബന്ധപ്പെടുക" - "ഹോട്ട്‌സ്പോട്ടിന്റെയും ടെതറിംഗിന്റെയും നില" - - - - - + "ടെതറിംഗ് അല്ലെങ്കിൽ ഹോട്ട്സ്‌പോട്ട് സജീവമാണ്" + "സജ്ജീകരിക്കാൻ ടാപ്പ് ചെയ്യുക." + "ടെതറിംഗ് പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു" + "വിശദാംശങ്ങൾക്ക് നിങ്ങളുടെ അഡ്മിനെ ബന്ധപ്പെടുക" + "ഹോട്ട്‌സ്പോട്ടിന്റെയും ടെതറിംഗിന്റെയും നില" + + + + + diff --git a/Tethering/res/values-mn/strings.xml b/Tethering/res/values-mn/strings.xml index a9aef5c563..42d1edbace 100644 --- a/Tethering/res/values-mn/strings.xml +++ b/Tethering/res/values-mn/strings.xml @@ -16,14 +16,14 @@ - "Модем болгох эсвэл сүлжээний цэг идэвхтэй байна" - "Тохируулахын тулд товшино уу." - "Модем болгохыг идэвхгүй болгосон" - "Дэлгэрэнгүй мэдээлэл авах бол админтайгаа холбогдоно уу" - "Сүлжээний цэг болон модем болгохын төлөв" - - - - - + "Модем болгох эсвэл сүлжээний цэг идэвхтэй байна" + "Тохируулахын тулд товшино уу." + "Модем болгохыг идэвхгүй болгосон" + "Дэлгэрэнгүй мэдээлэл авахын тулд админтайгаа холбогдоно уу" + "Сүлжээний цэг болон модем болгох төлөв" + + + + + diff --git a/Tethering/res/values-mr/strings.xml b/Tethering/res/values-mr/strings.xml index d49cc61dd4..13995b6b8a 100644 --- a/Tethering/res/values-mr/strings.xml +++ b/Tethering/res/values-mr/strings.xml @@ -16,14 +16,14 @@ - "टेदरिंग किंवा हॉटस्पॉट अ‍ॅक्टिव्ह आहे" - "सेट करण्यासाठी टॅप करा." - "टेदरिंग बंद केले आहे" - "तपशिलांसाठी तुमच्या ॲडमिनशी संपर्क साधा" - "हॉटस्पॉट & टेदरिंग स्टेटस" - - - - - + "टेदरिंग किंवा हॉटस्पॉट अ‍ॅक्टिव्ह आहे" + "सेट करण्यासाठी टॅप करा." + "टेदरिंग बंद केले आहे" + "तपशीलांसाठी तुमच्या ॲडमिनशी संपर्क साधा" + "हॉटस्पॉट आणि टेदरिंगची स्थिती" + + + + + diff --git a/Tethering/res/values-ms/strings.xml b/Tethering/res/values-ms/strings.xml index bc7aab35a6..d6a67f37b1 100644 --- a/Tethering/res/values-ms/strings.xml +++ b/Tethering/res/values-ms/strings.xml @@ -16,14 +16,14 @@ - "Penambatan atau tempat liputan aktif" - "Ketik untuk membuat persediaan." - "Penambatan dilumpuhkan" - "Hubungi pentadbir anda untuk mendapatkan maklumat lanjut" - "Status tempat liputan & penambatan" - - - - - + "Penambatan atau tempat liputan aktif" + "Ketik untuk membuat persediaan." + "Penambatan dilumpuhkan" + "Hubungi pentadbir anda untuk mendapatkan maklumat lanjut" + "Status tempat liputan & penambatan" + + + + + diff --git a/Tethering/res/values-my/strings.xml b/Tethering/res/values-my/strings.xml index 4f4042336b..49f6b88a75 100644 --- a/Tethering/res/values-my/strings.xml +++ b/Tethering/res/values-my/strings.xml @@ -16,14 +16,14 @@ - "မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း (သို့) ဟော့စပေါ့ ဖွင့်ထားသည်" - "စနစ်ထည့်သွင်းရန် တို့ပါ။" - "မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း ပိတ်ထားသည်" - "အသေးစိတ်သိရန် သင့်စီမံခန့်ခွဲသူထံ ဆက်သွယ်ပါ" - "ဟော့စပေါ့နှင့် မိုဘိုင်းသုံး၍ ချိတ်ဆက်ခြင်း အခြေအနေ" - - - - - + "မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း သို့မဟုတ် ဟော့စပေါ့ ဖွင့်ထားသည်" + "စနစ်ထည့်သွင်းရန် တို့ပါ။" + "မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်းကို ပိတ်ထားသည်" + "အသေးစိတ်အတွက် သင့်စီမံခန့်ခွဲသူကို ဆက်သွယ်ပါ" + "ဟော့စပေါ့နှင့် မိုဘိုင်းဖုန်းသုံး ချိတ်ဆက်မျှဝေခြင်း အခြေအနေ" + + + + + diff --git a/Tethering/res/values-nb/strings.xml b/Tethering/res/values-nb/strings.xml index e9024c03d9..9594e0a70a 100644 --- a/Tethering/res/values-nb/strings.xml +++ b/Tethering/res/values-nb/strings.xml @@ -16,14 +16,14 @@ - "Internettdeling eller wifi-sone er aktiv" - "Trykk for å konfigurere." - "Internettdeling er slått av" - "Kontakt administratoren din for å få mer informasjon" - "Status for wifi-sone og internettdeling" - - - - - + "Internettdeling eller Wi-Fi-sone er aktiv" + "Trykk for å konfigurere." + "Internettdeling er slått av" + "Ta kontakt med administratoren din for å få mer informasjon" + "Status for Wi-Fi-sone og internettdeling" + + + + + diff --git a/Tethering/res/values-ne/strings.xml b/Tethering/res/values-ne/strings.xml index 988d5c20da..72ae3a80a9 100644 --- a/Tethering/res/values-ne/strings.xml +++ b/Tethering/res/values-ne/strings.xml @@ -16,14 +16,14 @@ - "टेदरिङ वा हटस्पट अन छ" - "सेटअप गर्न ट्याप गर्नुहोस्।" - "टेदरिङ सुविधा अफ गरिएको छ" - "विस्तृत जानकारीका लागि एड्मिनलाई सम्पर्क गर्नुहोस्" - "हटस्पट तथा टेदरिङको स्थिति" - - - - - + "टेदरिङ वा हटस्पट सक्रिय छ" + "सेटअप गर्न ट्याप गर्नुहोस्।" + "टेदरिङ सुविधा असक्षम पारिएको छ" + "विवरणहरूका लागि आफ्ना प्रशासकलाई सम्पर्क गर्नुहोस्" + "हटस्पट तथा टेदरिङको स्थिति" + + + + + diff --git a/Tethering/res/values-nl/strings.xml b/Tethering/res/values-nl/strings.xml index d6a0a1ac3c..18b2bbfc76 100644 --- a/Tethering/res/values-nl/strings.xml +++ b/Tethering/res/values-nl/strings.xml @@ -16,14 +16,14 @@ - "Tethering of hotspot actief" - "Tik om in te stellen." - "Tethering staat uit" - "Neem contact op met je beheerder voor meer informatie" - "Status van hotspot en tethering" - - - - - + "Tethering of hotspot actief" + "Tik om in te stellen." + "Tethering is uitgeschakeld" + "Neem contact op met je beheerder voor meer informatie" + "Status van hotspot en tethering" + + + + + diff --git a/Tethering/res/values-or/strings.xml b/Tethering/res/values-or/strings.xml index 9abca6c4b2..a15a6db42a 100644 --- a/Tethering/res/values-or/strings.xml +++ b/Tethering/res/values-or/strings.xml @@ -16,14 +16,14 @@ - "ଟିଥରିଂ କିମ୍ବା ହଟସ୍ପଟ ସକ୍ରିୟ ଅଛି" - "ସେଟ ଅପ କରିବାକୁ ଟାପ କରନ୍ତୁ।" - "ଟିଥରିଂକୁ ଅକ୍ଷମ କରାଯାଇଛି" - "ବିବରଣୀ ପାଇଁ ଆପଣଙ୍କ ଆଡମିନଙ୍କ ସହ କଣ୍ଟାକ୍ଟ କରନ୍ତୁ" - "ହଟସ୍ପଟ ଏବଂ ଟିଥରିଂ ସ୍ଥିତି" - - - - - + "ଟିଥେରିଂ କିମ୍ୱା ହଟସ୍ପଟ୍ ସକ୍ରିୟ ଅଛି" + "ସେଟ୍ ଅପ୍ କରିବାକୁ ଟାପ୍ କରନ୍ତୁ।" + "ଟିଥେରିଂ ଅକ୍ଷମ କରାଯାଇଛି" + "ବିବରଣୀଗୁଡ଼ିକ ପାଇଁ ଆପଣଙ୍କ ଆଡମିନଙ୍କ ସହ ଯୋଗାଯୋଗ କରନ୍ତୁ" + "ହଟସ୍ପଟ୍ ଓ ଟିଥେରିଂ ସ୍ଥିତି" + + + + + diff --git a/Tethering/res/values-pa/strings.xml b/Tethering/res/values-pa/strings.xml index bcd1c1461e..a8235e423e 100644 --- a/Tethering/res/values-pa/strings.xml +++ b/Tethering/res/values-pa/strings.xml @@ -16,14 +16,14 @@ - "ਟੈਦਰਿੰਗ ਜਾਂ ਹੌਟਸਪੌਟ ਕਿਰਿਆਸ਼ੀਲ" - "ਸੈੱਟਅੱਪ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।" - "ਟੈਦਰਿੰਗ ਨੂੰ ਬੰਦ ਕੀਤਾ ਗਿਆ ਹੈ" - "ਵੇਰਵਿਆਂ ਲਈ ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ" - "ਹੌਟਸਪੌਟ ਅਤੇ ਟੈਦਰਿੰਗ ਦੀ ਸਥਿਤੀ" - - - - - + "ਟੈਦਰਿੰਗ ਜਾਂ ਹੌਟਸਪੌਟ ਕਿਰਿਆਸ਼ੀਲ" + "ਸੈੱਟਅੱਪ ਕਰਨ ਲਈ ਟੈਪ ਕਰੋ।" + "ਟੈਦਰਿੰਗ ਨੂੰ ਬੰਦ ਕੀਤਾ ਗਿਆ ਹੈ" + "ਵੇਰਵਿਆਂ ਲਈ ਆਪਣੇ ਪ੍ਰਸ਼ਾਸਕ ਨਾਲ ਸੰਪਰਕ ਕਰੋ" + "ਹੌਟਸਪੌਟ ਅਤੇ ਟੈਦਰਿੰਗ ਦੀ ਸਥਿਤੀ" + + + + + diff --git a/Tethering/res/values-pl/strings.xml b/Tethering/res/values-pl/strings.xml index 855afb4e10..ccb017d43f 100644 --- a/Tethering/res/values-pl/strings.xml +++ b/Tethering/res/values-pl/strings.xml @@ -16,14 +16,14 @@ - "Aktywny tethering lub hotspot" - "Kliknij, aby skonfigurować." - "Tethering jest wyłączony" - "Aby uzyskać szczegółowe informacje, skontaktuj się z administratorem" - "Stan hotspotu i tetheringu" - - - - - + "Aktywny tethering lub punkt dostępu" + "Kliknij, by skonfigurować" + "Tethering został wyłączony" + "Aby uzyskać szczegółowe informacje, skontaktuj się z administratorem" + "Hotspot i tethering – stan" + + + + + diff --git a/Tethering/res/values-pt-rBR/strings.xml b/Tethering/res/values-pt-rBR/strings.xml index 7e19f0e7e4..a0a4745f93 100644 --- a/Tethering/res/values-pt-rBR/strings.xml +++ b/Tethering/res/values-pt-rBR/strings.xml @@ -16,14 +16,14 @@ - "Ponto de acesso ou tethering ativo" - "Toque para configurar." - "O tethering está desativado" - "Entre em contato com seu administrador para saber detalhes" - "Status do ponto de acesso e do tethering" - - - - - + "Ponto de acesso ou tethering ativo" + "Toque para configurar." + "Tethering desativado" + "Fale com seu administrador para saber detalhes" + "Status de ponto de acesso e tethering" + + + + + diff --git a/Tethering/res/values-pt-rPT/strings.xml b/Tethering/res/values-pt-rPT/strings.xml index ac8ea5cf89..e3f03fcc69 100644 --- a/Tethering/res/values-pt-rPT/strings.xml +++ b/Tethering/res/values-pt-rPT/strings.xml @@ -16,14 +16,14 @@ - "Ligação (à Internet) via telemóvel ou zona Wi-Fi ativa" - "Toque para configurar." - "A ligação (à Internet) via telemóvel está desativada" - "Contacte o administrador para obter detalhes" - "Estado da zona Wi-Fi e da ligação (à Internet) via telemóvel" - - - - - + "Ligação (à Internet) via telemóvel ou zona Wi-Fi ativas" + "Toque para configurar." + "A ligação (à Internet) via telemóvel está desativada." + "Contacte o administrador para obter detalhes." + "Estado da zona Wi-Fi e da ligação (à Internet) via telemóvel" + + + + + diff --git a/Tethering/res/values-pt/strings.xml b/Tethering/res/values-pt/strings.xml index 7e19f0e7e4..a0a4745f93 100644 --- a/Tethering/res/values-pt/strings.xml +++ b/Tethering/res/values-pt/strings.xml @@ -16,14 +16,14 @@ - "Ponto de acesso ou tethering ativo" - "Toque para configurar." - "O tethering está desativado" - "Entre em contato com seu administrador para saber detalhes" - "Status do ponto de acesso e do tethering" - - - - - + "Ponto de acesso ou tethering ativo" + "Toque para configurar." + "Tethering desativado" + "Fale com seu administrador para saber detalhes" + "Status de ponto de acesso e tethering" + + + + + diff --git a/Tethering/res/values-ro/strings.xml b/Tethering/res/values-ro/strings.xml index e022504db3..5706a4a69c 100644 --- a/Tethering/res/values-ro/strings.xml +++ b/Tethering/res/values-ro/strings.xml @@ -16,14 +16,14 @@ - "Tethering sau hotspot activ" - "Atinge pentru a configura." - "Tetheringul este dezactivat" - "Contactează administratorul pentru detalii" - "Starea hotspotului și a tetheringului" - - - - - + "Tethering sau hotspot activ" + "Atingeți ca să configurați." + "Tetheringul este dezactivat" + "Contactați administratorul pentru detalii" + "Starea hotspotului și a tetheringului" + + + + + diff --git a/Tethering/res/values-ru/strings.xml b/Tethering/res/values-ru/strings.xml index 4361d70712..7cb6f7db3f 100644 --- a/Tethering/res/values-ru/strings.xml +++ b/Tethering/res/values-ru/strings.xml @@ -16,14 +16,14 @@ - "Включен режим модема или точка доступа" - "Нажмите, чтобы настроить." - "Включить режим модема нельзя" - "Чтобы узнать больше, обратитесь к администратору." - "Статус точки доступа и режима модема" - - - - - + "Включен режим модема или точка доступа" + "Нажмите, чтобы настроить." + "Использование телефона в качестве модема запрещено" + "Чтобы узнать подробности, обратитесь к администратору." + "Статус хот-спота и режима модема" + + + + + diff --git a/Tethering/res/values-si/strings.xml b/Tethering/res/values-si/strings.xml index 14f30e918a..ec34c22de7 100644 --- a/Tethering/res/values-si/strings.xml +++ b/Tethering/res/values-si/strings.xml @@ -16,14 +16,14 @@ - "ටෙදරින් හෝ හොට්ස්පොට් සක්‍රියයි" - "පිහිටුවීමට තට්ටු කරන්න." - "ටෙදරින් අබල කර ඇත" - "විස්තර සඳහා ඔබේ පරිපාලක අමතන්න" - "හොට්ස්පොට් සහ ටෙදරින් තත්ත්වය" - - - - - + "ටෙදරින් හෝ හොට්ස්පොට් සක්‍රීයයි" + "පිහිටුවීමට තට්ටු කරන්න." + "ටෙදරින් අබල කර ඇත" + "විස්තර සඳහා ඔබගේ පරිපාලක අමතන්න" + "හොට්ස්පොට් & ටෙදරින් තත්ත්වය" + + + + + diff --git a/Tethering/res/values-sk/strings.xml b/Tethering/res/values-sk/strings.xml index 15845e72a8..43e787c84f 100644 --- a/Tethering/res/values-sk/strings.xml +++ b/Tethering/res/values-sk/strings.xml @@ -16,14 +16,14 @@ - "Tethering alebo hotspot je aktívny" - "Nastavíte ho klepnutím." - "Tethering je deaktivovaný" - "O podrobnosti požiadajte svojho správcu" - "Stav hotspotu a tetheringu" - - - - - + "Tethering alebo prístupový bod je aktívny" + "Klepnutím prejdete na nastavenie." + "Tethering je deaktivovaný" + "O podrobnosti požiadajte svojho správcu" + "Stav hotspotu a tetheringu" + + + + + diff --git a/Tethering/res/values-sl/strings.xml b/Tethering/res/values-sl/strings.xml index 4c9bd3cdfb..59433626a1 100644 --- a/Tethering/res/values-sl/strings.xml +++ b/Tethering/res/values-sl/strings.xml @@ -16,14 +16,14 @@ - "Povezava računalnika z internetom prek mobilnega telefona ali dostopna točka je aktivna." - "Dotaknite se za nastavitev." - "Povezava računalnika z internetom prek mobilnega telefona je onemogočena." - "Za podrobnosti se obrnite na skrbnika." - "Stanje dostopne točke in povezave računalnika z internetom prek mobilnega telefona" - - - - - + "Povezava z internetom prek mobilnega telefona ali dostopna točka je aktivna" + "Dotaknite se, če želite nastaviti." + "Povezava z internetom prek mobilnega telefona je onemogočena" + "Za podrobnosti se obrnite na skrbnika" + "Stanje dostopne točke in povezave z internetom prek mobilnega telefona" + + + + + diff --git a/Tethering/res/values-sq/strings.xml b/Tethering/res/values-sq/strings.xml index e39e98d61d..21e11558bb 100644 --- a/Tethering/res/values-sq/strings.xml +++ b/Tethering/res/values-sq/strings.xml @@ -16,14 +16,14 @@ - "Lidhja e çiftimit ose ajo e qasjes në zona publike interneti është aktive" - "Trokit për ta konfiguruar." - "Ndarja e internetit është çaktivizuar" - "Kontakto me administratorin për detaje" - "Statusi i zonës së qasjes dhe ndarjes së internetit" - - - - - + "Ndarja e internetit ose zona e qasjes së internetit është aktive" + "Trokit për ta konfiguruar." + "Ndarja e internetit është çaktivizuar" + "Kontakto me administratorin për detaje" + "Statusi i zonës së qasjes dhe ndarjes së internetit" + + + + + diff --git a/Tethering/res/values-sr/strings.xml b/Tethering/res/values-sr/strings.xml index ca3ba5943a..e2e4dc6361 100644 --- a/Tethering/res/values-sr/strings.xml +++ b/Tethering/res/values-sr/strings.xml @@ -16,14 +16,14 @@ - "Активно је привезивање или хотспот" - "Додирните да бисте подесили." - "Привезивање је онемогућено" - "Потражите детаље од администратора" - "Статус хотспота и привезивања" - - - - - + "Привезивање или хотспот је активан" + "Додирните да бисте подесили." + "Привезивање је онемогућено" + "Потражите детаље од администратора" + "Статус хотспота и привезивања" + + + + + diff --git a/Tethering/res/values-sv/strings.xml b/Tethering/res/values-sv/strings.xml index da5e1044b9..72702c2858 100644 --- a/Tethering/res/values-sv/strings.xml +++ b/Tethering/res/values-sv/strings.xml @@ -16,14 +16,14 @@ - "Internetdelning eller surfzon är aktiv" - "Tryck om du vill konfigurera." - "Internetdelning har inaktiverats" - "Kontakta administratören om du vill veta mer" - "Status för surfzon och internetdelning" - - - - - + "Internetdelning eller surfzon har aktiverats" + "Tryck om du vill konfigurera." + "Internetdelning har inaktiverats" + "Kontakta administratören om du vill veta mer" + "Trådlös surfzon och internetdelning har inaktiverats" + + + + + diff --git a/Tethering/res/values-sw/strings.xml b/Tethering/res/values-sw/strings.xml index 3e58667263..65e4aa8ceb 100644 --- a/Tethering/res/values-sw/strings.xml +++ b/Tethering/res/values-sw/strings.xml @@ -16,14 +16,14 @@ - "Umewasha kipengele cha kusambaza mtandao au mtandao pepe" - "Gusa ili uweke mipangilio." - "Umezima kipengele cha kusambaza mtandao" - "Wasiliana na msimamizi wako ili upate maelezo zaidi" - "Mtandaopepe na hali ya kusambaza mtandao" - - - - - + "Kusambaza mtandao au mtandaopepe umewashwa" + "Gusa ili uweke mipangilio." + "Umezima kipengele cha kusambaza mtandao" + "Wasiliana na msimamizi wako ili upate maelezo zaidi" + "Mtandaopepe na hali ya kusambaza mtandao" + + + + + diff --git a/Tethering/res/values-ta/strings.xml b/Tethering/res/values-ta/strings.xml index a811e67ece..4aba62d4ab 100644 --- a/Tethering/res/values-ta/strings.xml +++ b/Tethering/res/values-ta/strings.xml @@ -16,14 +16,14 @@ - "இணைப்பு முறை அல்லது ஹாட்ஸ்பாட் செயல்பாட்டில் உள்ளது" - "அமைக்க தட்டவும்." - "இணைப்பு முறை முடக்கப்பட்டுள்ளது" - "விவரங்களுக்கு உங்கள் நிர்வாகியைத் தொடர்புகொள்ளவும்" - "ஹாட்ஸ்பாட் & இணைப்பு முறை நிலை" - - - - - + "டெதெரிங் அல்லது ஹாட்ஸ்பாட் இயங்குகிறது" + "அமைக்க, தட்டவும்." + "டெதெரிங் முடக்கப்பட்டுள்ளது" + "விவரங்களுக்கு உங்கள் நிர்வாகியைத் தொடர்புகொள்ளவும்" + "ஹாட்ஸ்பாட் & டெதெரிங் நிலை" + + + + + diff --git a/Tethering/res/values-te/strings.xml b/Tethering/res/values-te/strings.xml index a92208d1b2..1f91791341 100644 --- a/Tethering/res/values-te/strings.xml +++ b/Tethering/res/values-te/strings.xml @@ -16,14 +16,14 @@ - "టెథరింగ్ లేదా హాట్‌స్పాట్ యాక్టివ్‌గా ఉంది" - "సెటప్ చేయడానికి ట్యాప్ చేయండి." - "టెథరింగ్ డిజేబుల్ చేయబడింది" - "వివరాల కోసం మీ అడ్మిన్‌ను కాంటాక్ట్ చేయండి" - "హాట్‌స్పాట్ & టెథరింగ్ స్టేటస్" - - - - - + "టెథరింగ్ లేదా హాట్‌స్పాట్ యాక్టివ్‌గా ఉంది" + "సెటప్ చేయడానికి ట్యాప్ చేయండి." + "టెథరింగ్ డిజేబుల్ చేయబడింది" + "వివరాల కోసం మీ అడ్మిన్‌ని సంప్రదించండి" + "హాట్‌స్పాట్ & టెథరింగ్ స్థితి" + + + + + diff --git a/Tethering/res/values-th/strings.xml b/Tethering/res/values-th/strings.xml index 5ebbc802e7..44171c0db8 100644 --- a/Tethering/res/values-th/strings.xml +++ b/Tethering/res/values-th/strings.xml @@ -16,14 +16,14 @@ - "การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือหรือฮอตสปอตทำงานอยู่" - "แตะเพื่อตั้งค่า" - "การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือปิดอยู่" - "ติดต่อผู้ดูแลระบบเพื่อขอรายละเอียด" - "สถานะฮอตสปอตและการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ" - - - - - + "การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือหรือฮอตสปอตทำงานอยู่" + "แตะเพื่อตั้งค่า" + "ปิดใช้การเชื่อมต่ออินเทอร์เน็ตผ่านมือถือแล้ว" + "ติดต่อผู้ดูแลระบบเพื่อขอรายละเอียด" + "สถานะฮอตสปอตและการเชื่อมต่ออินเทอร์เน็ตผ่านมือถือ" + + + + + diff --git a/Tethering/res/values-tl/strings.xml b/Tethering/res/values-tl/strings.xml index 3364e521fc..7347dd3e62 100644 --- a/Tethering/res/values-tl/strings.xml +++ b/Tethering/res/values-tl/strings.xml @@ -16,14 +16,14 @@ - "Aktibo ang pag-tether o hotspot" - "I-tap para i-set up." - "Naka-disable ang pag-tether" - "Makipag-ugnayan sa iyong admin para sa mga detalye" - "Status ng hotspot at pag-tether" - - - - - + "Aktibo ang pag-tether o hotspot" + "I-tap para i-set up." + "Naka-disable ang pag-tether" + "Makipag-ugnayan sa iyong admin para sa mga detalye" + "Status ng hotspot at pag-tether" + + + + + diff --git a/Tethering/res/values-tr/strings.xml b/Tethering/res/values-tr/strings.xml index 0bb273c107..32030f1765 100644 --- a/Tethering/res/values-tr/strings.xml +++ b/Tethering/res/values-tr/strings.xml @@ -16,14 +16,14 @@ - "Tethering veya hotspot etkin" - "Ayarlamak için dokunun." - "Tethering devre dışı bırakıldı" - "Ayrıntılı bilgi için yöneticinize başvurun" - "Hotspot ve tethering durumu" - - - - - + "Tethering veya hotspot etkin" + "Ayarlamak için dokunun." + "Tethering devre dışı bırakıldı" + "Ayrıntılı bilgi için yöneticinize başvurun" + "Hotspot ve tethering durumu" + + + + + diff --git a/Tethering/res/values-uk/strings.xml b/Tethering/res/values-uk/strings.xml index 11962e5c11..1ca89b3f78 100644 --- a/Tethering/res/values-uk/strings.xml +++ b/Tethering/res/values-uk/strings.xml @@ -16,14 +16,14 @@ - "Модем чи точка доступу активні" - "Натисніть, щоб налаштувати." - "Використання телефона як модема вимкнено" - "Щоб дізнатися більше, зверніться до адміністратора" - "Статус точки доступу й модема" - - - - - + "Модем чи точка доступу активні" + "Натисніть, щоб налаштувати." + "Використання телефона як модема вимкнено" + "Щоб дізнатися більше, зв\'яжіться з адміністратором" + "Статус точки доступу та модема" + + + + + diff --git a/Tethering/res/values-ur/strings.xml b/Tethering/res/values-ur/strings.xml index c70e44fd20..d72c7d4195 100644 --- a/Tethering/res/values-ur/strings.xml +++ b/Tethering/res/values-ur/strings.xml @@ -16,14 +16,14 @@ - "ٹیدرنگ یا ہاٹ اسپاٹ فعال ہے" - "سیٹ اپ کرنے کیلئے تھپتھپائیں۔" - "ٹیدرنگ غیر فعال ہے" - "تفصیلات کیلئے اپنے منتظم سے رابطہ کریں" - "ہاٹ اسپاٹ اور ٹیتھرنگ کا اسٹیٹس" - - - - - + "ٹیدرنگ یا ہاٹ اسپاٹ فعال" + "سیٹ اپ کرنے کیلئے تھپتھپائیں۔" + "ٹیدرنگ غیر فعال ہے" + "تفصیلات کے لئے اپنے منتظم سے رابطہ کریں" + "ہاٹ اسپاٹ اور ٹیتھرنگ کا اسٹیٹس" + + + + + diff --git a/Tethering/res/values-uz/strings.xml b/Tethering/res/values-uz/strings.xml index b315901a04..af3b2ebb35 100644 --- a/Tethering/res/values-uz/strings.xml +++ b/Tethering/res/values-uz/strings.xml @@ -16,14 +16,14 @@ - "Modem rejimi yoki hotspot yoniq" - "Sozlash uchun bosing." - "Modem rejimi faolsizlantirildi" - "Tafsilotlari uchun administratoringizga murojaat qiling" - "Hotspot va modem rejimi holati" - - - - - + "Modem rejimi yoki hotspot yoniq" + "Sozlash uchun bosing." + "Modem rejimi faolsizlantirildi" + "Tafsilotlari uchun administratoringizga murojaat qiling" + "Hotspot va modem rejimi holati" + + + + + diff --git a/Tethering/res/values-vi/strings.xml b/Tethering/res/values-vi/strings.xml index 8e1b91ef2c..21a0735922 100644 --- a/Tethering/res/values-vi/strings.xml +++ b/Tethering/res/values-vi/strings.xml @@ -16,14 +16,14 @@ - "Tính năng chia sẻ Internet hoặc điểm phát sóng đang hoạt động" - "Hãy nhấn để thiết lập." - "Tính năng chia sẻ Internet đã bị tắt" - "Hãy liên hệ với quản trị viên của bạn để biết thông tin chi tiết" - "Trạng thái của chế độ cài đặt \"Điểm phát sóng và chia sẻ Internet\"" - - - - - + "Tính năng chia sẻ Internet hoặc điểm phát sóng đang hoạt động" + "Hãy nhấn để thiết lập." + "Đã tắt tính năng chia sẻ Internet" + "Hãy liên hệ với quản trị viên của bạn để biết chi tiết" + "Trạng thái điểm phát sóng và chia sẻ Internet" + + + + + diff --git a/Tethering/res/values-zh-rCN/strings.xml b/Tethering/res/values-zh-rCN/strings.xml index 054344eae2..98e3b4b46f 100644 --- a/Tethering/res/values-zh-rCN/strings.xml +++ b/Tethering/res/values-zh-rCN/strings.xml @@ -16,14 +16,14 @@ - "网络共享或热点已启用" - "点按即可设置。" - "网络共享已停用" - "如需了解详情,请与您的管理员联系" - "热点和网络共享状态" - - - - - + "网络共享或热点已启用" + "点按即可设置。" + "网络共享已停用" + "如需了解详情,请与您的管理员联系" + "热点和网络共享状态" + + + + + diff --git a/Tethering/res/values-zh-rHK/strings.xml b/Tethering/res/values-zh-rHK/strings.xml index 790d40a300..9cafd42dd4 100644 --- a/Tethering/res/values-zh-rHK/strings.xml +++ b/Tethering/res/values-zh-rHK/strings.xml @@ -16,14 +16,14 @@ - "網絡共享或熱點已啟用" - "輕按即可設定。" - "網絡共享已停用" - "請聯絡你的管理員以瞭解詳情" - "熱點和網絡共享狀態" - - - - - + "網絡共享或熱點已啟用" + "輕按即可設定。" + "網絡共享已停用" + "請聯絡您的管理員以瞭解詳情" + "熱點和網絡共享狀態" + + + + + diff --git a/Tethering/res/values-zh-rTW/strings.xml b/Tethering/res/values-zh-rTW/strings.xml index 65a689e809..50a50bf7a9 100644 --- a/Tethering/res/values-zh-rTW/strings.xml +++ b/Tethering/res/values-zh-rTW/strings.xml @@ -16,14 +16,14 @@ - "網路共用或無線基地台已啟用" - "輕觸即可設定。" - "網路共用已停用" - "詳情請洽你的管理員" - "無線基地台與網路共用狀態" - - - - - + "網路共用或無線基地台已啟用" + "輕觸即可進行設定。" + "網路共用已停用" + "詳情請洽你的管理員" + "無線基地台與網路共用狀態" + + + + + diff --git a/Tethering/res/values-zu/strings.xml b/Tethering/res/values-zu/strings.xml index e9651dd9b9..f210f8726e 100644 --- a/Tethering/res/values-zu/strings.xml +++ b/Tethering/res/values-zu/strings.xml @@ -16,14 +16,14 @@ - "Ukusebenzisa njengemodemu noma i-hotspot ephathekayo kuvuliwe" - "Thepha ukuze usethe." - "Ukusebenzisa ifoni njengemodemu kukhutshaziwe" - "Xhumana nomphathi wakho ukuze uthole imininingwane" - "I-Hotspot nesimo sokusebenzisa ifoni njengemodemu" - - - - - + "Ukusebenzisa njengemodemu noma i-hotspot ephathekayo kuvuliwe" + "Thepha ukuze usethe." + "Ukusebenzisa ifoni njengemodemu kukhutshaziwe" + "Xhumana nomphathi wakho ukuze uthole imininingwane" + "I-Hotspot nesimo sokusebenzisa ifoni njengemodemu" + + + + + diff --git a/framework-t/api/system-current.txt b/framework-t/api/system-current.txt index 64762b4050..1549089218 100644 --- a/framework-t/api/system-current.txt +++ b/framework-t/api/system-current.txt @@ -59,12 +59,30 @@ package android.nearby { } public class NearbyManager { + method public void queryOffloadCapability(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void startBroadcast(@NonNull android.nearby.BroadcastRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.BroadcastCallback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int startScan(@NonNull android.nearby.ScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.nearby.ScanCallback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void stopBroadcast(@NonNull android.nearby.BroadcastCallback); method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void stopScan(@NonNull android.nearby.ScanCallback); } + public final class OffloadCapability implements android.os.Parcelable { + method public int describeContents(); + method public long getVersion(); + method public boolean isFastPairSupported(); + method public boolean isNearbyShareSupported(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + } + + public static final class OffloadCapability.Builder { + ctor public OffloadCapability.Builder(); + method @NonNull public android.nearby.OffloadCapability build(); + method @NonNull public android.nearby.OffloadCapability.Builder setFastPairSupported(boolean); + method @NonNull public android.nearby.OffloadCapability.Builder setNearbyShareSupported(boolean); + method @NonNull public android.nearby.OffloadCapability.Builder setVersion(long); + } + public final class PresenceBroadcastRequest extends android.nearby.BroadcastRequest implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.List getActions(); @@ -175,8 +193,14 @@ package android.nearby { public interface ScanCallback { method public void onDiscovered(@NonNull android.nearby.NearbyDevice); + method public default void onError(int); method public void onLost(@NonNull android.nearby.NearbyDevice); method public void onUpdated(@NonNull android.nearby.NearbyDevice); + field public static final int ERROR_INVALID_ARGUMENT = 2; // 0x2 + field public static final int ERROR_PERMISSION_DENIED = 3; // 0x3 + field public static final int ERROR_RESOURCE_EXHAUSTED = 4; // 0x4 + field public static final int ERROR_UNKNOWN = 0; // 0x0 + field public static final int ERROR_UNSUPPORTED = 1; // 0x1 } public abstract class ScanFilter { @@ -191,6 +215,7 @@ package android.nearby { method public int getScanType(); method @NonNull public android.os.WorkSource getWorkSource(); method public boolean isBleEnabled(); + method public boolean isOffloadOnly(); method public static boolean isValidScanMode(int); method public static boolean isValidScanType(int); method @NonNull public static String scanModeToString(int); @@ -209,6 +234,7 @@ package android.nearby { method @NonNull public android.nearby.ScanRequest.Builder addScanFilter(@NonNull android.nearby.ScanFilter); method @NonNull public android.nearby.ScanRequest build(); method @NonNull public android.nearby.ScanRequest.Builder setBleEnabled(boolean); + method @NonNull public android.nearby.ScanRequest.Builder setOffloadOnly(boolean); method @NonNull public android.nearby.ScanRequest.Builder setScanMode(int); method @NonNull public android.nearby.ScanRequest.Builder setScanType(int); method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.nearby.ScanRequest.Builder setWorkSource(@Nullable android.os.WorkSource); diff --git a/framework/lint-baseline.xml b/framework/lint-baseline.xml new file mode 100644 index 0000000000..f68aad734e --- /dev/null +++ b/framework/lint-baseline.xml @@ -0,0 +1,367 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nearby/README.md b/nearby/README.md index 6925dc4be9..845188245b 100644 --- a/nearby/README.md +++ b/nearby/README.md @@ -29,6 +29,20 @@ $ cd packages/modules/Nearby $ aidegen . # This will launch Intellij project for Nearby module. ``` +Note, the setup above may fail to index classes defined in proto, such +that all classes defined in proto shows red in IDE and cannot be auto-completed. +To fix, you can mannually add jar files generated from proto to the class path +as below. First, find the jar file of presence proto with +```sh +ls $ANDROID_BUILD_TOP/out/soong/.intermediates/packages/modules/Connectivity/nearby/service/proto/presence-lite-protos/android_common/combined/presence-lite-protos.jar +``` +Then, add the jar in IDE as below. +1. Menu: File > Project Structure +2. Select Modules at the left panel and select the Dependencies tab. +3. Select the + icon and select 1 JARs or Directories option. +4. Select the JAR file found above, make sure it is checked in the beginning square. +5. Click the OK button. +6. Restart the IDE to re-index. ## Build and Install @@ -40,3 +54,23 @@ $ $ANDROID_BUILD_TOP/out/host/linux-x86/bin/deapexer decompress --input \ --output /tmp/tethering.apex $ adb install -r /tmp/tethering.apex ``` + +## Build and Install from tm-mainline-prod branch +When build and flash the APEX from tm-mainline-prod, you may see the error below. +``` +[INSTALL_FAILED_VERSION_DOWNGRADE: Downgrade of APEX package com.google.android.tethering is not allowed. Active version: 990090000 attempted: 339990000]) +``` +This is because the device is flashed with AOSP built from master or other branches, which has +prebuilt APEX with higher version. We can use root access to replace the prebuilt APEX with the APEX +built from tm-mainline-prod as below. +1. adb root && adb remount; adb shell mount -orw,remount /system/apex +2. cp tethering.next.apex com.google.android.tethering.apex +3. adb push com.google.android.tethering.apex /system/apex/ +4. adb reboot +After the steps above, the APEX can be reinstalled with adb install -r. +(More APEX background can be found in https://source.android.com/docs/core/ota/apex#using-an-apex.) + +## Build APEX to support multiple platforms +If you need to flash the APEX to different devices, Pixel 6, Pixel 7, or even devices from OEM, you +can share the APEX by ```source build/envsetup.sh && lunch aosp_arm64-userdebug```. This can avoid + re-compiling for different targets. diff --git a/nearby/TEST_MAPPING b/nearby/TEST_MAPPING index d68bcc9d7e..dbaca33b81 100644 --- a/nearby/TEST_MAPPING +++ b/nearby/TEST_MAPPING @@ -8,9 +8,6 @@ }, { "name": "NearbyIntegrationUntrustedTests" - }, - { - "name": "NearbyIntegrationUiTests" } ], "postsubmit": [ diff --git a/nearby/framework/Android.bp b/nearby/framework/Android.bp index 278f823125..a8a6eaa9f5 100644 --- a/nearby/framework/Android.bp +++ b/nearby/framework/Android.bp @@ -52,5 +52,7 @@ java_library { static_libs: [ "modules-utils-preconditions", ], - visibility: ["//packages/modules/Connectivity/nearby/tests:__subpackages__"], + visibility: [ + "//packages/modules/Connectivity/nearby/tests:__subpackages__", + "//packages/modules/Connectivity/nearby/halfsheet:__subpackages__"], } diff --git a/nearby/framework/java/android/nearby/DataElement.java b/nearby/framework/java/android/nearby/DataElement.java index 6fa5fb58f2..10c11322ba 100644 --- a/nearby/framework/java/android/nearby/DataElement.java +++ b/nearby/framework/java/android/nearby/DataElement.java @@ -16,13 +16,17 @@ package android.nearby; +import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.util.Preconditions; +import java.util.Arrays; +import java.util.Objects; /** * Represents a data element in Nearby Presence. @@ -35,11 +39,95 @@ public final class DataElement implements Parcelable { private final int mKey; private final byte[] mValue; + /** @hide */ + @IntDef({ + DataType.BLE_SERVICE_DATA, + DataType.BLE_ADDRESS, + DataType.SALT, + DataType.PRIVATE_IDENTITY, + DataType.TRUSTED_IDENTITY, + DataType.PUBLIC_IDENTITY, + DataType.PROVISIONED_IDENTITY, + DataType.TX_POWER, + DataType.ACTION, + DataType.MODEL_ID, + DataType.EDDYSTONE_EPHEMERAL_IDENTIFIER, + DataType.ACCOUNT_KEY_DATA, + DataType.CONNECTION_STATUS, + DataType.BATTERY, + DataType.SCAN_MODE, + DataType.TEST_DE_BEGIN, + DataType.TEST_DE_END + }) + public @interface DataType { + int BLE_SERVICE_DATA = 100; + int BLE_ADDRESS = 101; + // This is to indicate if the scan is offload only + int SCAN_MODE = 102; + int SALT = 0; + int PRIVATE_IDENTITY = 1; + int TRUSTED_IDENTITY = 2; + int PUBLIC_IDENTITY = 3; + int PROVISIONED_IDENTITY = 4; + int TX_POWER = 5; + int ACTION = 6; + int MODEL_ID = 7; + int EDDYSTONE_EPHEMERAL_IDENTIFIER = 8; + int ACCOUNT_KEY_DATA = 9; + int CONNECTION_STATUS = 10; + int BATTERY = 11; + // Reserves test DE ranges from {@link DataElement.DataType#TEST_DE_BEGIN} + // to {@link DataElement.DataType#TEST_DE_END}, inclusive. + // Reserves 128 Test DEs. + int TEST_DE_BEGIN = Integer.MAX_VALUE - 127; // 2147483520 + int TEST_DE_END = Integer.MAX_VALUE; // 2147483647 + } + + /** + * @hide + */ + public static boolean isValidType(int type) { + return type == DataType.BLE_SERVICE_DATA + || type == DataType.ACCOUNT_KEY_DATA + || type == DataType.BLE_ADDRESS + || type == DataType.SCAN_MODE + || type == DataType.SALT + || type == DataType.PRIVATE_IDENTITY + || type == DataType.TRUSTED_IDENTITY + || type == DataType.PUBLIC_IDENTITY + || type == DataType.PROVISIONED_IDENTITY + || type == DataType.TX_POWER + || type == DataType.ACTION + || type == DataType.MODEL_ID + || type == DataType.EDDYSTONE_EPHEMERAL_IDENTIFIER + || type == DataType.CONNECTION_STATUS + || type == DataType.BATTERY; + } + + /** + * @return {@code true} if this is identity type. + * @hide + */ + public boolean isIdentityDataType() { + return mKey == DataType.PRIVATE_IDENTITY + || mKey == DataType.TRUSTED_IDENTITY + || mKey == DataType.PUBLIC_IDENTITY + || mKey == DataType.PROVISIONED_IDENTITY; + } + + /** + * @return {@code true} if this is test data element type. + * @hide + */ + public static boolean isTestDeType(int type) { + return type >= DataType.TEST_DE_BEGIN && type <= DataType.TEST_DE_END; + } + /** * Constructs a {@link DataElement}. */ public DataElement(int key, @NonNull byte[] value) { - Preconditions.checkState(value != null, "value cannot be null"); + Preconditions.checkArgument(value != null, "value cannot be null"); mKey = key; mValue = value; } @@ -60,6 +148,20 @@ public final class DataElement implements Parcelable { } }; + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof DataElement) { + return mKey == ((DataElement) obj).mKey + && Arrays.equals(mValue, ((DataElement) obj).mValue); + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(mKey, Arrays.hashCode(mValue)); + } + @Override public int describeContents() { return 0; diff --git a/nearby/framework/java/android/nearby/INearbyManager.aidl b/nearby/framework/java/android/nearby/INearbyManager.aidl index 0291fff5cb..7af271ecf2 100644 --- a/nearby/framework/java/android/nearby/INearbyManager.aidl +++ b/nearby/framework/java/android/nearby/INearbyManager.aidl @@ -20,6 +20,7 @@ import android.nearby.IBroadcastListener; import android.nearby.IScanListener; import android.nearby.BroadcastRequestParcelable; import android.nearby.ScanRequest; +import android.nearby.aidl.IOffloadCallback; /** * Interface for communicating with the nearby services. @@ -37,4 +38,6 @@ interface INearbyManager { in IBroadcastListener callback, String packageName, @nullable String attributionTag); void stopBroadcast(in IBroadcastListener callback, String packageName, @nullable String attributionTag); + + void queryOffloadCapability(in IOffloadCallback callback) ; } \ No newline at end of file diff --git a/nearby/framework/java/android/nearby/IScanListener.aidl b/nearby/framework/java/android/nearby/IScanListener.aidl index 3e3b107c0d..80563b7e8c 100644 --- a/nearby/framework/java/android/nearby/IScanListener.aidl +++ b/nearby/framework/java/android/nearby/IScanListener.aidl @@ -34,5 +34,5 @@ oneway interface IScanListener { void onLost(in NearbyDeviceParcelable nearbyDeviceParcelable); /** Reports when there is an error during scanning. */ - void onError(); + void onError(in int errorCode); } diff --git a/nearby/framework/java/android/nearby/NearbyDevice.java b/nearby/framework/java/android/nearby/NearbyDevice.java index 538940cd3e..e8fcc28d89 100644 --- a/nearby/framework/java/android/nearby/NearbyDevice.java +++ b/nearby/framework/java/android/nearby/NearbyDevice.java @@ -21,11 +21,13 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.util.ArraySet; import com.android.internal.util.Preconditions; import java.util.List; import java.util.Objects; +import java.util.Set; /** * A class represents a device that can be discovered by multiple mediums. @@ -123,13 +125,17 @@ public abstract class NearbyDevice { @Override public boolean equals(Object other) { - if (other instanceof NearbyDevice) { - NearbyDevice otherDevice = (NearbyDevice) other; - return Objects.equals(mName, otherDevice.mName) - && mMediums == otherDevice.mMediums - && mRssi == otherDevice.mRssi; + if (!(other instanceof NearbyDevice)) { + return false; } - return false; + NearbyDevice otherDevice = (NearbyDevice) other; + Set mediumSet = new ArraySet<>(mMediums); + Set otherMediumSet = new ArraySet<>(otherDevice.mMediums); + if (!mediumSet.equals(otherMediumSet)) { + return false; + } + + return Objects.equals(mName, otherDevice.mName) && mRssi == otherDevice.mRssi; } @Override diff --git a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java index 8f44091a41..8fb96501f3 100644 --- a/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java +++ b/nearby/framework/java/android/nearby/NearbyDeviceParcelable.java @@ -46,6 +46,7 @@ public final class NearbyDeviceParcelable implements Parcelable { @Override public NearbyDeviceParcelable createFromParcel(Parcel in) { Builder builder = new Builder(); + builder.setDeviceId(in.readLong()); builder.setScanType(in.readInt()); if (in.readInt() == 1) { builder.setName(in.readString()); @@ -76,6 +77,17 @@ public final class NearbyDeviceParcelable implements Parcelable { in.readByteArray(salt); builder.setData(salt); } + if (in.readInt() == 1) { + builder.setPresenceDevice(in.readParcelable( + PresenceDevice.class.getClassLoader(), + PresenceDevice.class)); + } + if (in.readInt() == 1) { + int encryptionKeyTagLength = in.readInt(); + byte[] keyTag = new byte[encryptionKeyTagLength]; + in.readByteArray(keyTag); + builder.setData(keyTag); + } return builder.build(); } @@ -85,6 +97,7 @@ public final class NearbyDeviceParcelable implements Parcelable { } }; + private final long mDeviceId; @ScanRequest.ScanType int mScanType; @Nullable private final String mName; @NearbyDevice.Medium private final int mMedium; @@ -96,8 +109,11 @@ public final class NearbyDeviceParcelable implements Parcelable { @Nullable private final String mFastPairModelId; @Nullable private final byte[] mData; @Nullable private final byte[] mSalt; + @Nullable private final PresenceDevice mPresenceDevice; + @Nullable private final byte[] mEncryptionKeyTag; private NearbyDeviceParcelable( + long deviceId, @ScanRequest.ScanType int scanType, @Nullable String name, int medium, @@ -108,7 +124,10 @@ public final class NearbyDeviceParcelable implements Parcelable { @Nullable String fastPairModelId, @Nullable String bluetoothAddress, @Nullable byte[] data, - @Nullable byte[] salt) { + @Nullable byte[] salt, + @Nullable PresenceDevice presenceDevice, + @Nullable byte[] encryptionKeyTag) { + mDeviceId = deviceId; mScanType = scanType; mName = name; mMedium = medium; @@ -120,6 +139,8 @@ public final class NearbyDeviceParcelable implements Parcelable { mBluetoothAddress = bluetoothAddress; mData = data; mSalt = salt; + mPresenceDevice = presenceDevice; + mEncryptionKeyTag = encryptionKeyTag; } /** No special parcel contents. */ @@ -136,6 +157,7 @@ public final class NearbyDeviceParcelable implements Parcelable { */ @Override public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeLong(mDeviceId); dest.writeInt(mScanType); dest.writeInt(mName == null ? 0 : 1); if (mName != null) { @@ -164,13 +186,24 @@ public final class NearbyDeviceParcelable implements Parcelable { dest.writeInt(mSalt.length); dest.writeByteArray(mSalt); } + dest.writeInt(mPresenceDevice == null ? 0 : 1); + if (mPresenceDevice != null) { + dest.writeParcelable(mPresenceDevice, /* parcelableFlags= */ 0); + } + dest.writeInt(mEncryptionKeyTag == null ? 0 : 1); + if (mEncryptionKeyTag != null) { + dest.writeInt(mEncryptionKeyTag.length); + dest.writeByteArray(mEncryptionKeyTag); + } } /** Returns a string representation of this ScanRequest. */ @Override public String toString() { return "NearbyDeviceParcelable[" - + "scanType=" + + "deviceId=" + + mDeviceId + + ", scanType=" + mScanType + ", name=" + mName @@ -197,20 +230,25 @@ public final class NearbyDeviceParcelable implements Parcelable { public boolean equals(Object other) { if (other instanceof NearbyDeviceParcelable) { NearbyDeviceParcelable otherNearbyDeviceParcelable = (NearbyDeviceParcelable) other; - return mScanType == otherNearbyDeviceParcelable.mScanType + return mDeviceId == otherNearbyDeviceParcelable.mDeviceId + && mScanType == otherNearbyDeviceParcelable.mScanType && (Objects.equals(mName, otherNearbyDeviceParcelable.mName)) && (mMedium == otherNearbyDeviceParcelable.mMedium) && (mTxPower == otherNearbyDeviceParcelable.mTxPower) && (mRssi == otherNearbyDeviceParcelable.mRssi) && (mAction == otherNearbyDeviceParcelable.mAction) && (Objects.equals( - mPublicCredential, otherNearbyDeviceParcelable.mPublicCredential)) + mPublicCredential, otherNearbyDeviceParcelable.mPublicCredential)) && (Objects.equals( - mBluetoothAddress, otherNearbyDeviceParcelable.mBluetoothAddress)) + mBluetoothAddress, otherNearbyDeviceParcelable.mBluetoothAddress)) && (Objects.equals( - mFastPairModelId, otherNearbyDeviceParcelable.mFastPairModelId)) + mFastPairModelId, otherNearbyDeviceParcelable.mFastPairModelId)) && (Arrays.equals(mData, otherNearbyDeviceParcelable.mData)) - && (Arrays.equals(mSalt, otherNearbyDeviceParcelable.mSalt)); + && (Arrays.equals(mSalt, otherNearbyDeviceParcelable.mSalt)) + && (Objects.equals( + mPresenceDevice, otherNearbyDeviceParcelable.mPresenceDevice)) + && (Arrays.equals( + mEncryptionKeyTag, otherNearbyDeviceParcelable.mEncryptionKeyTag)); } return false; } @@ -218,6 +256,7 @@ public final class NearbyDeviceParcelable implements Parcelable { @Override public int hashCode() { return Objects.hash( + mDeviceId, mScanType, mName, mMedium, @@ -227,7 +266,19 @@ public final class NearbyDeviceParcelable implements Parcelable { mBluetoothAddress, mFastPairModelId, Arrays.hashCode(mData), - Arrays.hashCode(mSalt)); + Arrays.hashCode(mSalt), + mPresenceDevice, + Arrays.hashCode(mEncryptionKeyTag)); + } + + /** + * The id of the device. + *

This id is not a hardware id. It may rotate based on the remote device's broadcasts. + * + * @hide + */ + public long getDeviceId() { + return mDeviceId; } /** @@ -351,8 +402,29 @@ public final class NearbyDeviceParcelable implements Parcelable { return mSalt; } + /** + * Gets the {@link PresenceDevice} Nearby Presence device. This field is + * for Fast Pair client only. + */ + @Nullable + public PresenceDevice getPresenceDevice() { + return mPresenceDevice; + } + + /** + * Gets the encryption key tag calculated from advertisement + * Returns {@code null} if the data is not encrypted or this is not a Presence device. + * + * Used in Presence. + */ + @Nullable + public byte[] getEncryptionKeyTag() { + return mEncryptionKeyTag; + } + /** Builder class for {@link NearbyDeviceParcelable}. */ public static final class Builder { + private long mDeviceId = -1; @Nullable private String mName; @NearbyDevice.Medium private int mMedium; private int mTxPower; @@ -364,6 +436,14 @@ public final class NearbyDeviceParcelable implements Parcelable { @Nullable private String mBluetoothAddress; @Nullable private byte[] mData; @Nullable private byte[] mSalt; + @Nullable private PresenceDevice mPresenceDevice; + @Nullable private byte[] mEncryptionKeyTag; + + /** Sets the id of the device. */ + public Builder setDeviceId(long deviceId) { + this.mDeviceId = deviceId; + return this; + } /** * Sets the scan type of the NearbyDeviceParcelable. @@ -469,7 +549,7 @@ public final class NearbyDeviceParcelable implements Parcelable { /** * Sets the scanned raw data. * - * @param data Data the scan. For example, {@link ScanRecord#getServiceData()} if scanned by + * @param data raw data scanned, like {@link ScanRecord#getServiceData()} if scanned by * Bluetooth. */ @NonNull @@ -478,6 +558,17 @@ public final class NearbyDeviceParcelable implements Parcelable { return this; } + /** + * Sets the encryption key tag calculated from the advertisement. + * + * @param encryptionKeyTag calculated from identity scanned from the advertisement + */ + @NonNull + public Builder setEncryptionKeyTag(@Nullable byte[] encryptionKeyTag) { + mEncryptionKeyTag = encryptionKeyTag; + return this; + } + /** * Sets the slat in the advertisement from the Nearby Presence device. * @@ -489,10 +580,22 @@ public final class NearbyDeviceParcelable implements Parcelable { return this; } + /** + * Sets the {@link PresenceDevice} if there is any. + * The current {@link NearbyDeviceParcelable} can be seen as the wrapper of the + * {@link PresenceDevice}. + */ + @Nullable + public Builder setPresenceDevice(@Nullable PresenceDevice presenceDevice) { + mPresenceDevice = presenceDevice; + return this; + } + /** Builds a ScanResult. */ @NonNull public NearbyDeviceParcelable build() { return new NearbyDeviceParcelable( + mDeviceId, mScanType, mName, mMedium, @@ -503,7 +606,9 @@ public final class NearbyDeviceParcelable implements Parcelable { mFastPairModelId, mBluetoothAddress, mData, - mSalt); + mSalt, + mPresenceDevice, + mEncryptionKeyTag); } } } diff --git a/nearby/framework/java/android/nearby/NearbyManager.java b/nearby/framework/java/android/nearby/NearbyManager.java index 106c290992..a70b303dca 100644 --- a/nearby/framework/java/android/nearby/NearbyManager.java +++ b/nearby/framework/java/android/nearby/NearbyManager.java @@ -26,6 +26,7 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; +import android.nearby.aidl.IOffloadCallback; import android.os.RemoteException; import android.provider.Settings; import android.util.Log; @@ -37,6 +38,7 @@ import java.lang.ref.WeakReference; import java.util.Objects; import java.util.WeakHashMap; import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * This class provides a way to perform Nearby related operations such as scanning, broadcasting @@ -62,7 +64,7 @@ public class NearbyManager { ScanStatus.ERROR, }) public @interface ScanStatus { - // Default, invalid state. + // The undetermined status, some modules may be initializing. Retry is suggested. int UNKNOWN = 0; // The successful state. int SUCCESS = 1; @@ -73,6 +75,7 @@ public class NearbyManager { private static final String TAG = "NearbyManager"; /** + * TODO(b/286137024): Remove this when CTS R5 is rolled out. * Whether allows Fast Pair to scan. * * (0 = disabled, 1 = enabled) @@ -103,6 +106,9 @@ public class NearbyManager { mService = service; } + // This can be null when NearbyDeviceParcelable field not set for Presence device + // or the scan type is not recognized. + @Nullable private static NearbyDevice toClientNearbyDevice( NearbyDeviceParcelable nearbyDeviceParcelable, @ScanRequest.ScanType int scanType) { @@ -118,23 +124,12 @@ public class NearbyManager { } if (scanType == ScanRequest.SCAN_TYPE_NEARBY_PRESENCE) { - PublicCredential publicCredential = nearbyDeviceParcelable.getPublicCredential(); - if (publicCredential == null) { - return null; + PresenceDevice presenceDevice = nearbyDeviceParcelable.getPresenceDevice(); + if (presenceDevice == null) { + Log.e(TAG, + "Cannot find any Presence device in discovered NearbyDeviceParcelable"); } - byte[] salt = nearbyDeviceParcelable.getSalt(); - if (salt == null) { - salt = new byte[0]; - } - return new PresenceDevice.Builder( - // Use the public credential hash as the device Id. - String.valueOf(publicCredential.hashCode()), - salt, - publicCredential.getSecretId(), - publicCredential.getEncryptedMetadata()) - .setRssi(nearbyDeviceParcelable.getRssi()) - .addMedium(nearbyDeviceParcelable.getMedium()) - .build(); + return presenceDevice; } return null; } @@ -278,29 +273,42 @@ public class NearbyManager { } /** - * Read from {@link Settings} whether Fast Pair scan is enabled. + * Query offload capability in a device. The query is asynchronous and result is called back + * in {@link Consumer}, which is set to true if offload is supported. * - * @param context the {@link Context} to query the setting - * @return whether the Fast Pair is enabled - * @hide + * @param executor the callback will take place on this {@link Executor} + * @param callback the callback invoked with {@link OffloadCapability} */ - public static boolean getFastPairScanEnabled(@NonNull Context context) { - final int enabled = Settings.Secure.getInt( - context.getContentResolver(), FAST_PAIR_SCAN_ENABLED, 0); - return enabled != 0; + public void queryOffloadCapability(@NonNull @CallbackExecutor Executor executor, + @NonNull Consumer callback) { + try { + mService.queryOffloadCapability(new OffloadTransport(executor, callback)); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } - /** - * Write into {@link Settings} whether Fast Pair scan is enabled - * - * @param context the {@link Context} to set the setting - * @param enable whether the Fast Pair scan should be enabled - * @hide - */ - @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) - public static void setFastPairScanEnabled(@NonNull Context context, boolean enable) { - Settings.Secure.putInt( - context.getContentResolver(), FAST_PAIR_SCAN_ENABLED, enable ? 1 : 0); + private static class OffloadTransport extends IOffloadCallback.Stub { + + private final Executor mExecutor; + // Null when cancelled + volatile @Nullable Consumer mConsumer; + + OffloadTransport(Executor executor, Consumer consumer) { + Preconditions.checkArgument(executor != null, "illegal null executor"); + Preconditions.checkArgument(consumer != null, "illegal null consumer"); + mExecutor = executor; + mConsumer = consumer; + } + + @Override + public void onQueryComplete(OffloadCapability capability) { + mExecutor.execute(() -> { + if (mConsumer != null) { + mConsumer.accept(capability); + } + }); + } } private static class ScanListenerTransport extends IScanListener.Stub { @@ -339,9 +347,9 @@ public class NearbyManager { public void onDiscovered(NearbyDeviceParcelable nearbyDeviceParcelable) throws RemoteException { mExecutor.execute(() -> { - if (mScanCallback != null) { - mScanCallback.onDiscovered( - toClientNearbyDevice(nearbyDeviceParcelable, mScanType)); + NearbyDevice nearbyDevice = toClientNearbyDevice(nearbyDeviceParcelable, mScanType); + if (mScanCallback != null && nearbyDevice != null) { + mScanCallback.onDiscovered(nearbyDevice); } }); } @@ -350,7 +358,8 @@ public class NearbyManager { public void onUpdated(NearbyDeviceParcelable nearbyDeviceParcelable) throws RemoteException { mExecutor.execute(() -> { - if (mScanCallback != null) { + NearbyDevice nearbyDevice = toClientNearbyDevice(nearbyDeviceParcelable, mScanType); + if (mScanCallback != null && nearbyDevice != null) { mScanCallback.onUpdated( toClientNearbyDevice(nearbyDeviceParcelable, mScanType)); } @@ -360,7 +369,8 @@ public class NearbyManager { @Override public void onLost(NearbyDeviceParcelable nearbyDeviceParcelable) throws RemoteException { mExecutor.execute(() -> { - if (mScanCallback != null) { + NearbyDevice nearbyDevice = toClientNearbyDevice(nearbyDeviceParcelable, mScanType); + if (mScanCallback != null && nearbyDevice != null) { mScanCallback.onLost( toClientNearbyDevice(nearbyDeviceParcelable, mScanType)); } @@ -368,10 +378,10 @@ public class NearbyManager { } @Override - public void onError() { + public void onError(int errorCode) { mExecutor.execute(() -> { if (mScanCallback != null) { - Log.e("NearbyManager", "onError: There is an error in scan."); + mScanCallback.onError(errorCode); } }); } @@ -410,4 +420,35 @@ public class NearbyManager { }); } } + + /** + * TODO(b/286137024): Remove this when CTS R5 is rolled out. + * Read from {@link Settings} whether Fast Pair scan is enabled. + * + * @param context the {@link Context} to query the setting + * @return whether the Fast Pair is enabled + * @hide + */ + public static boolean getFastPairScanEnabled(@NonNull Context context) { + final int enabled = Settings.Secure.getInt( + context.getContentResolver(), FAST_PAIR_SCAN_ENABLED, 0); + return enabled != 0; + } + + /** + * TODO(b/286137024): Remove this when CTS R5 is rolled out. + * Write into {@link Settings} whether Fast Pair scan is enabled + * + * @param context the {@link Context} to set the setting + * @param enable whether the Fast Pair scan should be enabled + * @hide + */ + @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) + public static void setFastPairScanEnabled(@NonNull Context context, boolean enable) { + Settings.Secure.putInt( + context.getContentResolver(), FAST_PAIR_SCAN_ENABLED, enable ? 1 : 0); + Log.v(TAG, String.format( + "successfully %s Fast Pair scan", enable ? "enables" : "disables")); + } + } diff --git a/nearby/framework/java/android/nearby/OffloadCapability.aidl b/nearby/framework/java/android/nearby/OffloadCapability.aidl new file mode 100644 index 0000000000..fe1c45e66b --- /dev/null +++ b/nearby/framework/java/android/nearby/OffloadCapability.aidl @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nearby; + +/** + * A class that can describe what offload functions are available. + * + * {@hide} + */ +parcelable OffloadCapability; + diff --git a/nearby/framework/java/android/nearby/OffloadCapability.java b/nearby/framework/java/android/nearby/OffloadCapability.java new file mode 100644 index 0000000000..9071c1cd0e --- /dev/null +++ b/nearby/framework/java/android/nearby/OffloadCapability.java @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nearby; + +import android.annotation.NonNull; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * A class that can describe what offload functions are available. + * + * @hide + */ +@SystemApi +public final class OffloadCapability implements Parcelable { + private final boolean mFastPairSupported; + private final boolean mNearbyShareSupported; + private final long mVersion; + + public boolean isFastPairSupported() { + return mFastPairSupported; + } + + public boolean isNearbyShareSupported() { + return mNearbyShareSupported; + } + + public long getVersion() { + return mVersion; + } + + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeBoolean(mFastPairSupported); + dest.writeBoolean(mNearbyShareSupported); + dest.writeLong(mVersion); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + public static final Creator CREATOR = new Creator() { + @Override + public OffloadCapability createFromParcel(Parcel in) { + boolean isFastPairSupported = in.readBoolean(); + boolean isNearbyShareSupported = in.readBoolean(); + long version = in.readLong(); + return new Builder() + .setFastPairSupported(isFastPairSupported) + .setNearbyShareSupported(isNearbyShareSupported) + .setVersion(version) + .build(); + } + + @Override + public OffloadCapability[] newArray(int size) { + return new OffloadCapability[size]; + } + }; + + private OffloadCapability(boolean fastPairSupported, boolean nearbyShareSupported, + long version) { + mFastPairSupported = fastPairSupported; + mNearbyShareSupported = nearbyShareSupported; + mVersion = version; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof OffloadCapability)) return false; + OffloadCapability that = (OffloadCapability) o; + return isFastPairSupported() == that.isFastPairSupported() + && isNearbyShareSupported() == that.isNearbyShareSupported() + && getVersion() == that.getVersion(); + } + + @Override + public int hashCode() { + return Objects.hash(isFastPairSupported(), isNearbyShareSupported(), getVersion()); + } + + @Override + public String toString() { + return "OffloadCapability{" + + "fastPairSupported=" + mFastPairSupported + + ", nearbyShareSupported=" + mNearbyShareSupported + + ", version=" + mVersion + + '}'; + } + + /** + * Builder class for {@link OffloadCapability}. + */ + public static final class Builder { + private boolean mFastPairSupported; + private boolean mNearbyShareSupported; + private long mVersion; + + /** + * Sets if the Nearby Share feature is supported + * + * @param fastPairSupported {@code true} if the Fast Pair feature is supported + */ + @NonNull + public Builder setFastPairSupported(boolean fastPairSupported) { + mFastPairSupported = fastPairSupported; + return this; + } + + /** + * Sets if the Nearby Share feature is supported. + * + * @param nearbyShareSupported {@code true} if the Nearby Share feature is supported + */ + @NonNull + public Builder setNearbyShareSupported(boolean nearbyShareSupported) { + mNearbyShareSupported = nearbyShareSupported; + return this; + } + + /** + * Sets the version number of Nearby Offload. + * + * @param version Nearby Offload version number + */ + @NonNull + public Builder setVersion(long version) { + mVersion = version; + return this; + } + + /** + * Builds an OffloadCapability object. + */ + @NonNull + public OffloadCapability build() { + return new OffloadCapability(mFastPairSupported, mNearbyShareSupported, mVersion); + } + } +} diff --git a/nearby/framework/java/android/nearby/PresenceDevice.java b/nearby/framework/java/android/nearby/PresenceDevice.java index cb406e49b6..b5d9ad4f0d 100644 --- a/nearby/framework/java/android/nearby/PresenceDevice.java +++ b/nearby/framework/java/android/nearby/PresenceDevice.java @@ -26,6 +26,7 @@ import android.os.Parcelable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -134,6 +135,54 @@ public final class PresenceDevice extends NearbyDevice implements Parcelable { return mExtendedProperties; } + /** + * This can only be hidden because this is the System API, + * which cannot be changed in T timeline. + * + * @hide + */ + @Override + public boolean equals(Object other) { + if (other instanceof PresenceDevice) { + PresenceDevice otherDevice = (PresenceDevice) other; + if (super.equals(otherDevice)) { + return Arrays.equals(mSalt, otherDevice.mSalt) + && Arrays.equals(mSecretId, otherDevice.mSecretId) + && Arrays.equals(mEncryptedIdentity, otherDevice.mEncryptedIdentity) + && Objects.equals(mDeviceId, otherDevice.mDeviceId) + && mDeviceType == otherDevice.mDeviceType + && Objects.equals(mDeviceImageUrl, otherDevice.mDeviceImageUrl) + && mDiscoveryTimestampMillis == otherDevice.mDiscoveryTimestampMillis + && Objects.equals(mExtendedProperties, otherDevice.mExtendedProperties); + } + } + return false; + } + + /** + * This can only be hidden because this is the System API, + * which cannot be changed in T timeline. + * + * @hide + * + * @return The unique hash value of the {@link PresenceDevice} + */ + @Override + public int hashCode() { + return Objects.hash( + getName(), + getMediums(), + getRssi(), + Arrays.hashCode(mSalt), + Arrays.hashCode(mSecretId), + Arrays.hashCode(mEncryptedIdentity), + mDeviceId, + mDeviceType, + mDeviceImageUrl, + mDiscoveryTimestampMillis, + mExtendedProperties); + } + private PresenceDevice(String deviceName, List mMediums, int rssi, String deviceId, byte[] salt, byte[] secretId, byte[] encryptedIdentity, int deviceType, String deviceImageUrl, long discoveryTimestampMillis, @@ -326,7 +375,6 @@ public final class PresenceDevice extends NearbyDevice implements Parcelable { return this; } - /** * Sets the image url of the discovered Presence device. * @@ -338,7 +386,6 @@ public final class PresenceDevice extends NearbyDevice implements Parcelable { return this; } - /** * Sets discovery timestamp, the clock is based on elapsed time. * @@ -350,7 +397,6 @@ public final class PresenceDevice extends NearbyDevice implements Parcelable { return this; } - /** * Adds an extended property of the discovered presence device. * diff --git a/nearby/framework/java/android/nearby/PresenceScanFilter.java b/nearby/framework/java/android/nearby/PresenceScanFilter.java index f0c3c0672f..50e97b4282 100644 --- a/nearby/framework/java/android/nearby/PresenceScanFilter.java +++ b/nearby/framework/java/android/nearby/PresenceScanFilter.java @@ -71,7 +71,7 @@ public final class PresenceScanFilter extends ScanFilter implements Parcelable { super(ScanRequest.SCAN_TYPE_NEARBY_PRESENCE, rssiThreshold); mCredentials = new ArrayList<>(credentials); mPresenceActions = new ArrayList<>(presenceActions); - mExtendedProperties = extendedProperties; + mExtendedProperties = new ArrayList<>(extendedProperties); } private PresenceScanFilter(Parcel in) { @@ -132,7 +132,7 @@ public final class PresenceScanFilter extends ScanFilter implements Parcelable { } dest.writeInt(mExtendedProperties.size()); if (!mExtendedProperties.isEmpty()) { - dest.writeList(mExtendedProperties); + dest.writeParcelableList(mExtendedProperties, 0); } } diff --git a/nearby/framework/java/android/nearby/ScanCallback.java b/nearby/framework/java/android/nearby/ScanCallback.java index 1b1b4bca2d..7b66607e9d 100644 --- a/nearby/framework/java/android/nearby/ScanCallback.java +++ b/nearby/framework/java/android/nearby/ScanCallback.java @@ -16,9 +16,13 @@ package android.nearby; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Reports newly discovered devices. * Note: The frequency of the callback is dependent on whether the caller @@ -31,6 +35,37 @@ import android.annotation.SystemApi; */ @SystemApi public interface ScanCallback { + + /** General error code for scan. */ + int ERROR_UNKNOWN = 0; + + /** + * Scan failed as the request is not supported. + */ + int ERROR_UNSUPPORTED = 1; + + /** + * Invalid argument such as out-of-range, illegal format etc. + */ + int ERROR_INVALID_ARGUMENT = 2; + + /** + * Request from clients who do not have permissions. + */ + int ERROR_PERMISSION_DENIED = 3; + + /** + * Request cannot be fulfilled due to limited resource. + */ + int ERROR_RESOURCE_EXHAUSTED = 4; + + /** @hide **/ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ERROR_UNKNOWN, ERROR_UNSUPPORTED, ERROR_INVALID_ARGUMENT, ERROR_PERMISSION_DENIED, + ERROR_RESOURCE_EXHAUSTED}) + @interface ErrorCode { + } + /** * Reports a {@link NearbyDevice} being discovered. * @@ -51,4 +86,11 @@ public interface ScanCallback { * @param device {@link NearbyDevice} that is lost. */ void onLost(@NonNull NearbyDevice device); + + /** + * Notifies clients of error from the scan. + * + * @param errorCode defined by Nearby + */ + default void onError(@ErrorCode int errorCode) {} } diff --git a/nearby/framework/java/android/nearby/ScanRequest.java b/nearby/framework/java/android/nearby/ScanRequest.java index c717ac7850..61cbf39391 100644 --- a/nearby/framework/java/android/nearby/ScanRequest.java +++ b/nearby/framework/java/android/nearby/ScanRequest.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.os.WorkSource; @@ -33,6 +34,8 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Consumer; /** * An encapsulation of various parameters for requesting nearby scans. @@ -61,6 +64,12 @@ public final class ScanRequest implements Parcelable { * results without starting BLE scans themselves. */ public static final int SCAN_MODE_NO_POWER = -1; + /** + * A special scan mode to indicate that client only wants to use CHRE to scan. + * + * @hide + */ + public static final int SCAN_MODE_CHRE_ONLY = 3; /** * Used to read a ScanRequest from a Parcel. */ @@ -72,6 +81,7 @@ public final class ScanRequest implements Parcelable { .setScanType(in.readInt()) .setScanMode(in.readInt()) .setBleEnabled(in.readBoolean()) + .setOffloadOnly(in.readBoolean()) .setWorkSource(in.readTypedObject(WorkSource.CREATOR)); final int size = in.readInt(); for (int i = 0; i < size; i++) { @@ -89,14 +99,16 @@ public final class ScanRequest implements Parcelable { private final @ScanType int mScanType; private final @ScanMode int mScanMode; private final boolean mBleEnabled; + private final boolean mOffloadOnly; private final @NonNull WorkSource mWorkSource; private final List mScanFilters; private ScanRequest(@ScanType int scanType, @ScanMode int scanMode, boolean bleEnabled, - @NonNull WorkSource workSource, List scanFilters) { + boolean offloadOnly, @NonNull WorkSource workSource, List scanFilters) { mScanType = scanType; mScanMode = scanMode; mBleEnabled = bleEnabled; + mOffloadOnly = offloadOnly; mWorkSource = workSource; mScanFilters = scanFilters; } @@ -161,6 +173,13 @@ public final class ScanRequest implements Parcelable { return mBleEnabled; } + /** + * Returns if CHRE enabled for scanning. + */ + public boolean isOffloadOnly() { + return mOffloadOnly; + } + /** * Returns Scan Filters for this request. */ @@ -197,7 +216,13 @@ public final class ScanRequest implements Parcelable { stringBuilder.append("Request[") .append("scanType=").append(mScanType); stringBuilder.append(", scanMode=").append(scanModeToString(mScanMode)); - stringBuilder.append(", enableBle=").append(mBleEnabled); + // TODO(b/286137024): Remove this when CTS R5 is rolled out. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + stringBuilder.append(", bleEnabled=").append(mBleEnabled); + stringBuilder.append(", offloadOnly=").append(mOffloadOnly); + } else { + stringBuilder.append(", enableBle=").append(mBleEnabled); + } stringBuilder.append(", workSource=").append(mWorkSource); stringBuilder.append(", scanFilters=").append(mScanFilters); stringBuilder.append("]"); @@ -209,6 +234,7 @@ public final class ScanRequest implements Parcelable { dest.writeInt(mScanType); dest.writeInt(mScanMode); dest.writeBoolean(mBleEnabled); + dest.writeBoolean(mOffloadOnly); dest.writeTypedObject(mWorkSource, /* parcelableFlags= */0); final int size = mScanFilters.size(); dest.writeInt(size); @@ -224,6 +250,7 @@ public final class ScanRequest implements Parcelable { return mScanType == otherRequest.mScanType && (mScanMode == otherRequest.mScanMode) && (mBleEnabled == otherRequest.mBleEnabled) + && (mOffloadOnly == otherRequest.mOffloadOnly) && (Objects.equals(mWorkSource, otherRequest.mWorkSource)); } return false; @@ -231,7 +258,7 @@ public final class ScanRequest implements Parcelable { @Override public int hashCode() { - return Objects.hash(mScanType, mScanMode, mBleEnabled, mWorkSource); + return Objects.hash(mScanType, mScanMode, mBleEnabled, mOffloadOnly, mWorkSource); } /** @hide **/ @@ -254,6 +281,7 @@ public final class ScanRequest implements Parcelable { private @ScanMode int mScanMode; private boolean mBleEnabled; + private boolean mOffloadOnly; private WorkSource mWorkSource; private List mScanFilters; @@ -261,6 +289,7 @@ public final class ScanRequest implements Parcelable { public Builder() { mScanType = INVALID_SCAN_TYPE; mBleEnabled = true; + mOffloadOnly = false; mWorkSource = new WorkSource(); mScanFilters = new ArrayList<>(); } @@ -300,6 +329,22 @@ public final class ScanRequest implements Parcelable { return this; } + /** + * By default, a scan request can be served by either offload or + * non-offload implementation, depending on the resource available in the device. + * + * A client can explicitly request a scan to be served by offload only. + * Before the request, the client should query the offload capability by + * using {@link NearbyManager#queryOffloadCapability(Executor, Consumer)}}. Otherwise, + * {@link ScanCallback#ERROR_UNSUPPORTED} will be returned on devices without + * offload capability. + */ + @NonNull + public Builder setOffloadOnly(boolean offloadOnly) { + mOffloadOnly = offloadOnly; + return this; + } + /** * Sets the work source to use for power attribution for this scan request. Defaults to * empty work source, which implies the caller that sends the scan request will be used @@ -355,7 +400,8 @@ public final class ScanRequest implements Parcelable { Preconditions.checkState(isValidScanMode(mScanMode), "invalid scan mode : " + mScanMode + ", scan mode must be one of ScanMode#SCAN_MODE_"); - return new ScanRequest(mScanType, mScanMode, mBleEnabled, mWorkSource, mScanFilters); + return new ScanRequest( + mScanType, mScanMode, mBleEnabled, mOffloadOnly, mWorkSource, mScanFilters); } } } diff --git a/nearby/framework/java/android/nearby/aidl/IOffloadCallback.aidl b/nearby/framework/java/android/nearby/aidl/IOffloadCallback.aidl new file mode 100644 index 0000000000..8bef8177bb --- /dev/null +++ b/nearby/framework/java/android/nearby/aidl/IOffloadCallback.aidl @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.nearby.aidl; + +import android.nearby.OffloadCapability; + +/** + * Listener for offload queries. + * + * {@hide} + */ +oneway interface IOffloadCallback { + /** Invokes when ContextHub transaction completes. */ + void onQueryComplete(in OffloadCapability capability); +} diff --git a/nearby/halfsheet/Android.bp b/nearby/halfsheet/Android.bp index 8011dc64e3..6190d45975 100644 --- a/nearby/halfsheet/Android.bp +++ b/nearby/halfsheet/Android.bp @@ -20,6 +20,7 @@ android_app { name: "HalfSheetUX", defaults: ["platform_app_defaults"], srcs: ["src/**/*.java"], + resource_dirs: ["res"], sdk_version: "module_current", // This is included in tethering apex, which uses min SDK 30 min_sdk_version: "30", @@ -28,7 +29,7 @@ android_app { libs: [ "framework-bluetooth", "framework-connectivity-t.impl", - "nearby-service-string", + "framework-nearby-static", ], static_libs: [ "androidx.annotation_annotation", diff --git a/nearby/halfsheet/res/drawable/quantum_ic_devices_other_vd_theme_24.xml b/nearby/halfsheet/res/drawable/quantum_ic_devices_other_vd_theme_24.xml new file mode 100644 index 0000000000..3dcfdee413 --- /dev/null +++ b/nearby/halfsheet/res/drawable/quantum_ic_devices_other_vd_theme_24.xml @@ -0,0 +1,25 @@ + + + + diff --git a/nearby/halfsheet/res/layout-land/fast_pair_device_pairing_fragment.xml b/nearby/halfsheet/res/layout-land/fast_pair_device_pairing_fragment.xml new file mode 100644 index 0000000000..545f7fa472 --- /dev/null +++ b/nearby/halfsheet/res/layout-land/fast_pair_device_pairing_fragment.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nearby/halfsheet/res/layout-land/fast_pair_half_sheet.xml b/nearby/halfsheet/res/layout-land/fast_pair_half_sheet.xml new file mode 100644 index 0000000000..e993536fa0 --- /dev/null +++ b/nearby/halfsheet/res/layout-land/fast_pair_half_sheet.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + diff --git a/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml b/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml index 7fbe229cfd..77cd1ea56c 100644 --- a/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml +++ b/nearby/halfsheet/res/layout/fast_pair_device_pairing_fragment.xml @@ -5,7 +5,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" tools:ignore="RtlCompat" - android:layout_width="match_parent" android:layout_height="match_parent"> + android:layout_width="match_parent" + android:layout_height="match_parent"> + "Begin tans opstelling …" "Stel toestel op" "Toestel is gekoppel" + "Gekoppel aan “%s”" "Kon nie koppel nie" + "Kan nie koppel nie" + "Probeer die toestel self saambind" + "Probeer om die toestel in saambindmodus te sit" + "Toestelle binne bereik" + "Toestelle met jou rekening" + "Jou gestoorde toestel is reg" + "Naby" + "Toestelle" + "Koppel tans …" + "Battery: %d%%" "Klaar" "Stoor" "Koppel" diff --git a/nearby/halfsheet/res/values-am/strings.xml b/nearby/halfsheet/res/values-am/strings.xml index da3b14494b..7c0aed47d8 100644 --- a/nearby/halfsheet/res/values-am/strings.xml +++ b/nearby/halfsheet/res/values-am/strings.xml @@ -20,7 +20,18 @@ "ማዋቀርን በመጀመር ላይ…" "መሣሪያ አዋቅር" "መሣሪያ ተገናኝቷል" + "ከ«%s» ጋር ተገናኝቷል" "መገናኘት አልተቻለም" + "መገናኘት አልተቻለም" + "በእጅ ከመሣሪያው ጋር ለማጣመር ይሞክሩ" + "መሣሪያውን ወደ ማጣመር ሁነታ ለማስገባት ይሞክሩ" + "በቅርብ ያሉ መሣሪያዎች" + "ከመለያዎ ጋር መሣሪያዎች" + "የተቀመጠው መሣሪያዎ ይገኛል" + "በአቅራቢያ" + "መሣሪያዎች" + "በማገናኘት ላይ…" + "ባትሪ፦ %d%%" "ተጠናቅቋል" "አስቀምጥ" "አገናኝ" diff --git a/nearby/halfsheet/res/values-ar/strings.xml b/nearby/halfsheet/res/values-ar/strings.xml index d0bfce4c3b..089faaa95b 100644 --- a/nearby/halfsheet/res/values-ar/strings.xml +++ b/nearby/halfsheet/res/values-ar/strings.xml @@ -20,7 +20,18 @@ "جارٍ الإعداد…" "إعداد جهاز" "تمّ إقران الجهاز" + "‏تم الربط بـ \"%s\"" "تعذّر الربط" + "يتعذَّر الاتصال" + "جرِّب الإقران يدويًا بالجهاز." + "جرِّب تشغيل الجهاز في وضع الإقران." + "الأجهزة القريبة" + "الأجهزة المرتبطة بحسابك" + "جهازك المحفوظ متاح" + "مشاركة عن قرب" + "الأجهزة" + "جارٍ الاتصال…" + "‏البطارية: %d%%" "تم" "حفظ" "ربط" diff --git a/nearby/halfsheet/res/values-as/strings.xml b/nearby/halfsheet/res/values-as/strings.xml index 8ff49462b4..bb9dfccc09 100644 --- a/nearby/halfsheet/res/values-as/strings.xml +++ b/nearby/halfsheet/res/values-as/strings.xml @@ -20,7 +20,18 @@ "ছেটআপ আৰম্ভ কৰি থকা হৈছে…" "ডিভাইচ ছেট আপ কৰক" "ডিভাইচ সংযোগ কৰা হ’ল" + "“%s”ৰ সৈতে সংযোগ কৰা হ’ল" "সংযোগ কৰিব পৰা নগ’ল" + "সংযোগ কৰিব পৰা নাই" + "ডিভাইচটোৰ সৈতে মেনুৱেলী পেয়াৰ কৰক" + "ডিভাইচক পেয়াৰ কৰা ম’ডত ৰাখি চাওক" + "সংযোগ সীমাত থকা ডিভাইচসমূহ" + "আপোনাৰ একাউণ্টত সংযোগ হোৱা ডিভাইচবোৰ" + "আপুনি ছেভ কৰা ডিভাইচ উপলব্ধ" + "নিকটৱৰ্তী" + "ডিভাইচ" + "সংযোগ কৰি থকা হৈছে…" + "বেটাৰী: %d%%" "হ’ল" "ছেভ কৰক" "সংযোগ কৰক" diff --git a/nearby/halfsheet/res/values-az/strings.xml b/nearby/halfsheet/res/values-az/strings.xml index af499ef6f9..844963b46d 100644 --- a/nearby/halfsheet/res/values-az/strings.xml +++ b/nearby/halfsheet/res/values-az/strings.xml @@ -20,7 +20,18 @@ "Ayarlama başladılır…" "Cihazı quraşdırın" "Cihaz qoşulub" + "“%s” şəbəkəsinə qoşulub" "Qoşulmaq mümkün olmadı" + "Qoşulmaq olmur" + "Cihazı manual olaraq birləşdirin" + "Cihazı qoşalaşdırma rejiminə qoymağa çalışın" + "Əl altında olan cihazlar" + "Hesabınızdakı cihazlar" + "Yadda saxlanmış cihazınız əlçatandır" + "Yaxınlıqda" + "Cihazlar" + "Qoşulur…" + "Batareya: %d%%" "Oldu" "Saxlayın" "Qoşun" diff --git a/nearby/halfsheet/res/values-b+sr+Latn/strings.xml b/nearby/halfsheet/res/values-b+sr+Latn/strings.xml index eea6b64755..fcd1dc613b 100644 --- a/nearby/halfsheet/res/values-b+sr+Latn/strings.xml +++ b/nearby/halfsheet/res/values-b+sr+Latn/strings.xml @@ -20,7 +20,18 @@ "Podešavanje se pokreće…" "Podesite uređaj" "Uređaj je povezan" + "Povezani ste sa uređajem %s" "Povezivanje nije uspelo" + "Povezivanje nije uspelo" + "Probajte da uparite ručno sa uređajem" + "Probajte da prebacite uređaj u režim uparivanja" + "Uređaji u dometu" + "Uređaji povezani sa nalogom" + "Sačuvani uređaj je dostupan" + "U blizini" + "Uređaji" + "Povezuje se…" + "Baterija: %d%%" "Gotovo" "Sačuvaj" "Poveži" diff --git a/nearby/halfsheet/res/values-be/strings.xml b/nearby/halfsheet/res/values-be/strings.xml index a5c1ef6b7d..f469922486 100644 --- a/nearby/halfsheet/res/values-be/strings.xml +++ b/nearby/halfsheet/res/values-be/strings.xml @@ -20,7 +20,18 @@ "Пачынаецца наладжванне…" "Наладзьце прыладу" "Прылада падключана" + "Падключана да прылады \"%s\"" "Не ўдалося падключыцца" + "Не ўдалося падключыцца" + "Паспрабуйце спалучыць прыладу ўручную" + "Перавядзіце прыладу ў рэжым спалучэння" + "Прылады ў межах дасягальнасці" + "Прылады з вашым уліковым запісам" + "Ёсць захаваная вамі прылада" + "Паблізу" + "Прылады" + "Падключэнне…" + "Узровень зараду: %d%%" "Гатова" "Захаваць" "Падключыць" diff --git a/nearby/halfsheet/res/values-bg/strings.xml b/nearby/halfsheet/res/values-bg/strings.xml index 0ee7aef11f..a0c5103cd8 100644 --- a/nearby/halfsheet/res/values-bg/strings.xml +++ b/nearby/halfsheet/res/values-bg/strings.xml @@ -20,7 +20,18 @@ "Настройването се стартира…" "Настройване на устройството" "Устройството е свързано" + "Установена е връзка с(ъс) „%s“" "Свързването не бе успешно" + "Не може да се установи връзка" + "Опитайте да сдвоите устройството ръчно" + "Опитайте да зададете режим на сдвояване за устройството" + "Устройства в обхват" + "Устройства с профила ви" + "Запазеното ви у-во е налице" + "В близост" + "Устройства" + "Свързва се…" + "Батерия: %d%%" "Готово" "Запазване" "Свързване" diff --git a/nearby/halfsheet/res/values-bn/strings.xml b/nearby/halfsheet/res/values-bn/strings.xml index 484e35b5ab..4d6afc015b 100644 --- a/nearby/halfsheet/res/values-bn/strings.xml +++ b/nearby/halfsheet/res/values-bn/strings.xml @@ -20,7 +20,18 @@ "সেট-আপ করা শুরু হচ্ছে…" "ডিভাইস সেট-আপ করুন" "ডিভাইস কানেক্ট হয়েছে" + "“%s”-এ কানেক্ট করা হয়েছে" "কানেক্ট করা যায়নি" + "কানেক্ট করা যায়নি" + "ডিভাইসে ম্যানুয়ালি পেয়ার করার চেষ্টা করুন" + "ডিভাইস \'যোগ করার\' মোডে রাখার চেষ্টা করুন" + "কাছে রয়েছে এমন ডিভাইস" + "আপনার অ্যাকাউন্টের সাথে কানেক্ট থাকা ডিভাইস" + "আপনার সেভ করা ডিভাইস উপলভ্য আছে" + "নিয়ারবাই" + "ডিভাইস" + "কানেক্ট করা হচ্ছে…" + "ব্যাটারি: %d%%" "হয়ে গেছে" "সেভ করুন" "কানেক্ট করুন" diff --git a/nearby/halfsheet/res/values-bs/strings.xml b/nearby/halfsheet/res/values-bs/strings.xml index 2fc8644b96..47f13c3ac9 100644 --- a/nearby/halfsheet/res/values-bs/strings.xml +++ b/nearby/halfsheet/res/values-bs/strings.xml @@ -20,7 +20,18 @@ "Pokretanje postavljanja…" "Postavi uređaj" "Uređaj je povezan" + "Povezano s uređajem “%s”" "Povezivanje nije uspjelo" + "Nije moguće povezati" + "Pokušajte ručno upariti uređaj" + "Pokušajte staviti uređaj u način rada za uparivanje" + "Uređaji u dometu" + "Uređaji s vašim računom" + "Sačuvani uređaj je dostupan" + "U blizini" + "Uređaji" + "Povezivanje…" + "Baterija: %d%%" "Gotovo" "Sačuvaj" "Poveži" diff --git a/nearby/halfsheet/res/values-ca/strings.xml b/nearby/halfsheet/res/values-ca/strings.xml index 8912792499..44ebc3e005 100644 --- a/nearby/halfsheet/res/values-ca/strings.xml +++ b/nearby/halfsheet/res/values-ca/strings.xml @@ -20,7 +20,18 @@ "Iniciant la configuració…" "Configura el dispositiu" "El dispositiu s\'ha connectat" + "Connectat a %s" "No s\'ha pogut connectar" + "No es pot establir la connexió" + "Prova de vincular el dispositiu manualment" + "Prova d\'activar el mode de vinculació al dispositiu" + "Dispositius a l\'abast" + "Dispositius amb el teu compte" + "Dispositiu desat disponible" + "A prop" + "Dispositius" + "S\'està connectant…" + "Bateria: %d%%" "Fet" "Desa" "Connecta" diff --git a/nearby/halfsheet/res/values-cs/strings.xml b/nearby/halfsheet/res/values-cs/strings.xml index 7e7ea3cc4f..53d27ab91f 100644 --- a/nearby/halfsheet/res/values-cs/strings.xml +++ b/nearby/halfsheet/res/values-cs/strings.xml @@ -20,7 +20,18 @@ "Zahajování nastavení…" "Nastavení zařízení" "Zařízení je připojeno" + "Připojeno k zařízení %s" "Nelze se připojit" + "Nepodařilo se připojit" + "Zkuste zařízení spárovat ručně" + "Přepněte zařízení do režimu párování" + "Zařízení v dosahu" + "Zařízení s vaším účtem" + "Je dostupné uložené zařízení" + "V okolí" + "Zařízení" + "Připojování…" + "Baterie: %d %%" "Hotovo" "Uložit" "Připojit" diff --git a/nearby/halfsheet/res/values-da/strings.xml b/nearby/halfsheet/res/values-da/strings.xml index 1d937e2963..89b221f8f9 100644 --- a/nearby/halfsheet/res/values-da/strings.xml +++ b/nearby/halfsheet/res/values-da/strings.xml @@ -20,7 +20,18 @@ "Begynder konfiguration…" "Konfigurer enhed" "Enheden er forbundet" + "Der er oprettet forbindelse til \"%s\"" "Forbindelsen kan ikke oprettes" + "Kunne ikke forbindes" + "Prøv at parre med enheden manuelt" + "Prøv at sætte enheden i parringstilstand" + "Enheder inden for rækkevidde" + "Enheder med din konto" + "Din gemte enhed er tilgængelig" + "Tæt på" + "Enheder" + "Forbinder…" + "Batteri: %d %%" "Luk" "Gem" "Opret forbindelse" diff --git a/nearby/halfsheet/res/values-de/strings.xml b/nearby/halfsheet/res/values-de/strings.xml index 9186a4473d..de54114e23 100644 --- a/nearby/halfsheet/res/values-de/strings.xml +++ b/nearby/halfsheet/res/values-de/strings.xml @@ -20,7 +20,18 @@ "Einrichtung wird gestartet..." "Gerät einrichten" "Gerät verbunden" + "Mit „%s“ verbunden" "Verbindung nicht möglich" + "Kein Verbindungsaufbau möglich" + "Versuche, das Gerät manuell zu koppeln" + "Versuche, das Gerät in den Kopplungsmodus zu versetzen" + "Geräte in Reichweite" + "Geräte mit deinem Konto" + "Gespeichertes Gerät verfügbar" + "Nearby" + "Geräte" + "Wird verbunden…" + "Akkustand: %d %%" "Fertig" "Speichern" "Verbinden" diff --git a/nearby/halfsheet/res/values-el/strings.xml b/nearby/halfsheet/res/values-el/strings.xml index 3e18a93a60..1ea467a51e 100644 --- a/nearby/halfsheet/res/values-el/strings.xml +++ b/nearby/halfsheet/res/values-el/strings.xml @@ -20,7 +20,18 @@ "Έναρξη ρύθμισης…" "Ρύθμιση συσκευής" "Η συσκευή συνδέθηκε" + "Συνδέθηκε με τη συσκευή %s" "Αδυναμία σύνδεσης" + "Δεν είναι δυνατή η σύνδεση" + "Δοκιμάστε να κάνετε μη αυτόματη σύζευξη στη συσκευή" + "Δοκιμάστε να θέσετε τη συσκευή σε λειτουργία σύζευξης" + "Συσκευές εντός εμβέλειας" + "Συσκευές με τον λογαριασμό σας" + "Η αποθ. συσκ. είναι διαθέσιμη" + "Κοντά" + "Συσκευές" + "Σύνδεση…" + "Μπαταρία: %d%%" "Τέλος" "Αποθήκευση" "Σύνδεση" diff --git a/nearby/halfsheet/res/values-en-rAU/strings.xml b/nearby/halfsheet/res/values-en-rAU/strings.xml index d4ed675b30..b7039a13cb 100644 --- a/nearby/halfsheet/res/values-en-rAU/strings.xml +++ b/nearby/halfsheet/res/values-en-rAU/strings.xml @@ -20,7 +20,18 @@ "Starting setup…" "Set up device" "Device connected" + "Connected to \'%s\'" "Couldn\'t connect" + "Unable to connect" + "Try pairing to the device manually" + "Try putting the device into pairing mode" + "Devices within reach" + "Devices with your account" + "Your saved device is available" + "Nearby" + "Devices" + "Connecting…" + "Battery: %d%%" "Done" "Save" "Connect" diff --git a/nearby/halfsheet/res/values-en-rCA/strings.xml b/nearby/halfsheet/res/values-en-rCA/strings.xml index 6094199209..06b3a5eff2 100644 --- a/nearby/halfsheet/res/values-en-rCA/strings.xml +++ b/nearby/halfsheet/res/values-en-rCA/strings.xml @@ -20,7 +20,18 @@ "Starting Setup…" "Set up device" "Device connected" + "Connected to “%s”" "Couldn\'t connect" + "Unable to connect" + "Try manually pairing to the device" + "Try putting the device into pairing mode" + "Devices within reach" + "Devices with your account" + "Your saved device is available" + "Nearby" + "Devices" + "Connecting…" + "Battery: %d%%" "Done" "Save" "Connect" diff --git a/nearby/halfsheet/res/values-en-rGB/strings.xml b/nearby/halfsheet/res/values-en-rGB/strings.xml index d4ed675b30..b7039a13cb 100644 --- a/nearby/halfsheet/res/values-en-rGB/strings.xml +++ b/nearby/halfsheet/res/values-en-rGB/strings.xml @@ -20,7 +20,18 @@ "Starting setup…" "Set up device" "Device connected" + "Connected to \'%s\'" "Couldn\'t connect" + "Unable to connect" + "Try pairing to the device manually" + "Try putting the device into pairing mode" + "Devices within reach" + "Devices with your account" + "Your saved device is available" + "Nearby" + "Devices" + "Connecting…" + "Battery: %d%%" "Done" "Save" "Connect" diff --git a/nearby/halfsheet/res/values-en-rIN/strings.xml b/nearby/halfsheet/res/values-en-rIN/strings.xml index d4ed675b30..b7039a13cb 100644 --- a/nearby/halfsheet/res/values-en-rIN/strings.xml +++ b/nearby/halfsheet/res/values-en-rIN/strings.xml @@ -20,7 +20,18 @@ "Starting setup…" "Set up device" "Device connected" + "Connected to \'%s\'" "Couldn\'t connect" + "Unable to connect" + "Try pairing to the device manually" + "Try putting the device into pairing mode" + "Devices within reach" + "Devices with your account" + "Your saved device is available" + "Nearby" + "Devices" + "Connecting…" + "Battery: %d%%" "Done" "Save" "Connect" diff --git a/nearby/halfsheet/res/values-en-rXC/strings.xml b/nearby/halfsheet/res/values-en-rXC/strings.xml index 460cc1bbdb..c71272ef30 100644 --- a/nearby/halfsheet/res/values-en-rXC/strings.xml +++ b/nearby/halfsheet/res/values-en-rXC/strings.xml @@ -20,7 +20,18 @@ "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‏‏‏‎‎‎‏‏‏‏‎‎‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‎‏‎‎‏‏‎‏‏‏‏‎Starting Setup…‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‏‎‏‎‏‏‎‏‎‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‎‎Set up device‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‎‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎‏‎‎‏‎‏‏‎‎‏‏‎‏‎Device connected‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‏‎‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‏‎‏‎‎‎‎‏‏‎‏‏‏‏‎‎Connected to “%s”‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‎‎‏‏‏‏‏‏‎‎‎‎Couldn\'t connect‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‎‎‎‎‏‎‏‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‎‎‏‎‏‎‏‎‏‎‏‏‎‏‏‎‎‎‏‎‏‎‎‏‎Unable to connect‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎‎‎‎‏‏‏‎‏‎‏‎‎‎‎‏‎‏‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‏‏‎Try manually pairing to the device‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‎‏‎‏‎‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎Try putting the device into pairing mode‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‏‎‎‏‎‏‏‎‎‏‎‎‎‎‏‎‎‏‏‎‏‎‎‎‎‏‏‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‏‏‎‏‎‎‎‎Devices within reach‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‎‎‎‎‏‎‎‎‏‏‎‏‎‏‎‎‏‏‏‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‏‎‏‎‏‏‎‎Devices with your account‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‎‎‏‏‏‎‎‎‏‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎‏‎‎‏‏‎‏‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‎Your saved device is available‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‎‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‏‎‏‎‎‏‏‎‏‏‎‏‏‎‎‏‏‏‎Nearby‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‏‎‎‏‏‏‎‎‎‏‎‏‏‎‎‎‏‎‏‎‎‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‎Devices‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‎‏‏‏‎‏‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‎‎‎‏‎‎‏‎‏‏‎‎‎‏‏‏‏‎‎‏‏‎‎‏‎‎‎‏‏‎‏‏‏‎Connecting…‎‏‎‎‏‎" + "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‎‏‎‎‎‏‏‏‎‎‏‏‎‏‎‎‏‏‎‎‏‎‏‏‏‏‎‏‏‏‎‏‏‏‎‏‏‎‏‏‏‎‏‏‏‏‎‎‎‎‏‎‏‎Battery: %d%%‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‏‎‏‎‏‎‏‎‏‏‏‎‎‏‎‎‏‏‎‏‏‎‏‎‏‏‏‎‎‎‏‎‎‏‎‏‏‎Done‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‏‎‏‎‎‎‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‏‎‏‎‎‏‏‏‎‏‎‏‏‎‎‎‏‏‎‎‏‎‎‎‎Save‎‏‎‎‏‎" "‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‎‏‎‏‏‏‏‏‎‏‎‏‏‎‏‎‏‏‎‏‏‎‏‎‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‎Connect‎‏‎‎‏‎" diff --git a/nearby/halfsheet/res/values-es-rUS/strings.xml b/nearby/halfsheet/res/values-es-rUS/strings.xml index d8fb2836d5..05eb75dc7b 100644 --- a/nearby/halfsheet/res/values-es-rUS/strings.xml +++ b/nearby/halfsheet/res/values-es-rUS/strings.xml @@ -20,7 +20,18 @@ "Iniciando la configuración…" "Configuración del dispositivo" "Se conectó el dispositivo" + "Se estableció conexión con \"%s\"" "No se pudo establecer conexión" + "No se pudo establecer conexión" + "Intenta vincular el dispositivo manualmente" + "Prueba poner el dispositivo en el modo de vinculación" + "Dispositivos al alcance" + "Dispositivos con tu cuenta" + "El dispositivo está disponible" + "Nearby" + "Dispositivos" + "Conectando…" + "Batería: %d%%" "Listo" "Guardar" "Conectar" diff --git a/nearby/halfsheet/res/values-es/strings.xml b/nearby/halfsheet/res/values-es/strings.xml index 4b8340a2f7..7142a1a65c 100644 --- a/nearby/halfsheet/res/values-es/strings.xml +++ b/nearby/halfsheet/res/values-es/strings.xml @@ -20,7 +20,18 @@ "Iniciando configuración…" "Configurar el dispositivo" "Dispositivo conectado" + "Conectado a \"%s\"" "No se ha podido conectar" + "No se ha podido conectar" + "Prueba a emparejar el dispositivo manualmente" + "Prueba a poner el dispositivo en modo Emparejamiento" + "Dispositivos al alcance" + "Dispositivos conectados con tu cuenta" + "Dispositivo guardado disponible" + "Nearby" + "Dispositivos" + "Conectando…" + "Batería: %d %%" "Hecho" "Guardar" "Conectar" diff --git a/nearby/halfsheet/res/values-et/strings.xml b/nearby/halfsheet/res/values-et/strings.xml index e6abc6453f..20a46a5fd4 100644 --- a/nearby/halfsheet/res/values-et/strings.xml +++ b/nearby/halfsheet/res/values-et/strings.xml @@ -20,7 +20,18 @@ "Seadistuse käivitamine …" "Seadistage seade" "Seade on ühendatud" + "Ühendatud seadmega „%s“" "Ühendamine ebaõnnestus" + "Ühendust ei õnnestu luua" + "Proovige seadmega käsitsi siduda" + "Lülitage seade sidumisrežiimi" + "Ühendusulatuses olevad seadmed" + "Teie kontoga ühendatud seadmed" + "Salvestatud seade on saadaval" + "Läheduses" + "Seadmed" + "Ühendamine …" + "Aku: %d%%" "Valmis" "Salvesta" "Ühenda" diff --git a/nearby/halfsheet/res/values-eu/strings.xml b/nearby/halfsheet/res/values-eu/strings.xml index 4243fd55ac..cd6eb34be1 100644 --- a/nearby/halfsheet/res/values-eu/strings.xml +++ b/nearby/halfsheet/res/values-eu/strings.xml @@ -20,7 +20,18 @@ "Konfigurazio-prozesua abiarazten…" "Konfiguratu gailua" "Konektatu da gailua" + "%s gailura konektatuta" "Ezin izan da konektatu" + "Ezin da konektatu" + "Saiatu gailua eskuz parekatzen" + "Jarri gailua parekatzeko moduan" + "Estaldura-eremuko gailuak" + "Kontura konektatutako gailuak" + "Gordetako gailua erabilgarri dago" + "Nearby" + "Gailuak" + "Konektatzen…" + "Bateria: %% %d" "Eginda" "Gorde" "Konektatu" diff --git a/nearby/halfsheet/res/values-fa/strings.xml b/nearby/halfsheet/res/values-fa/strings.xml index 3585f9518d..7490e0f961 100644 --- a/nearby/halfsheet/res/values-fa/strings.xml +++ b/nearby/halfsheet/res/values-fa/strings.xml @@ -20,7 +20,18 @@ "درحال شروع راه‌اندازی…" "راه‌اندازی دستگاه" "دستگاه متصل شد" + "‏به «%s» متصل شد" "متصل نشد" + "اتصال برقرار نشد" + "مرتبط‌سازی با دستگاه را به‌صورت دستی امتحان کنید" + "دستگاه را در حالت مرتبط‌سازی قرار دهید" + "دستگاه‌های دردسترس" + "دستگاه‌های متصل به حسابتان" + "دستگاه ذخیره‌شده‌تان دردسترس است" + "اطراف" + "دستگاه‌ها" + "درحال اتصال…" + "‏باتری: %d%%" "تمام" "ذخیره" "متصل کردن" diff --git a/nearby/halfsheet/res/values-fi/strings.xml b/nearby/halfsheet/res/values-fi/strings.xml index e8d47de878..b488b3e2c3 100644 --- a/nearby/halfsheet/res/values-fi/strings.xml +++ b/nearby/halfsheet/res/values-fi/strings.xml @@ -20,7 +20,18 @@ "Aloitetaan käyttöönottoa…" "Määritä laite" "Laite on yhdistetty" + "%s yhdistetty" "Ei yhteyttä" + "Yhdistäminen epäonnistui" + "Yritä yhdistää laitteet manuaalisesti" + "Kokeile asettaa laite parinmuodostustilaan" + "Kantoalueella olevat laitteet" + "Tiliisi liitetyt laitteet" + "Laitteesi on käytettävissä" + "Lähellä" + "Laitteet" + "Yhdistetään…" + "Akku: %d %%" "Valmis" "Tallenna" "Yhdistä" diff --git a/nearby/halfsheet/res/values-fr-rCA/strings.xml b/nearby/halfsheet/res/values-fr-rCA/strings.xml index 64dd107b8f..9a48890861 100644 --- a/nearby/halfsheet/res/values-fr-rCA/strings.xml +++ b/nearby/halfsheet/res/values-fr-rCA/strings.xml @@ -20,7 +20,18 @@ "Démarrage de la configuration…" "Configurer l\'appareil" "Appareil associé" + "Connecté à l\'appareil suivant : « %s »" "Impossible d\'associer" + "Échec de la connexion" + "Essayez d\'associer manuellement l\'appareil" + "Essayez de mettre l\'appareil en mode d\'association" + "Appareils à portée" + "Appareils connectés à votre compte" + "Appareil enregistré accessible" + "À proximité" + "Appareils" + "Connexion en cours…" + "Pile : %d%%" "OK" "Enregistrer" "Associer" diff --git a/nearby/halfsheet/res/values-fr/strings.xml b/nearby/halfsheet/res/values-fr/strings.xml index 484c57bdea..f1263ab22d 100644 --- a/nearby/halfsheet/res/values-fr/strings.xml +++ b/nearby/halfsheet/res/values-fr/strings.xml @@ -20,7 +20,18 @@ "Début de la configuration…" "Configurer un appareil" "Appareil associé" + "Connecté à \"%s\"" "Impossible de se connecter" + "Impossible de se connecter" + "Essayez d\'associer manuellement l\'appareil" + "Essayez de mettre l\'appareil en mode association" + "Appareils à portée de main" + "Appareils connectés à votre compte" + "Appareil enregistré disponible" + "À proximité" + "Appareils" + "Connexion…" + "Batterie : %d %%" "OK" "Enregistrer" "Connecter" diff --git a/nearby/halfsheet/res/values-gl/strings.xml b/nearby/halfsheet/res/values-gl/strings.xml index 30393ff8fc..91eac4f892 100644 --- a/nearby/halfsheet/res/values-gl/strings.xml +++ b/nearby/halfsheet/res/values-gl/strings.xml @@ -20,7 +20,18 @@ "Iniciando configuración…" "Configura o dispositivo" "Conectouse o dispositivo" + "O dispositivo conectouse a %s" "Non se puido conectar" + "Non se puido conectar" + "Proba a vincular o dispositivo manualmente" + "Proba a poñer o dispositivo no modo de vinculación" + "Dispositivos dentro do alcance" + "Dispositivos conectados á túa conta" + "Dispositivo gardado dispoñible" + "Nearby" + "Dispositivos" + "Conectando…" + "Batería: %d %%" "Feito" "Gardar" "Conectar" diff --git a/nearby/halfsheet/res/values-gu/strings.xml b/nearby/halfsheet/res/values-gu/strings.xml index 03b057d37a..a7a7a2bb0c 100644 --- a/nearby/halfsheet/res/values-gu/strings.xml +++ b/nearby/halfsheet/res/values-gu/strings.xml @@ -20,7 +20,18 @@ "સેટઅપ શરૂ કરી રહ્યાં છીએ…" "ડિવાઇસનું સેટઅપ કરો" "ડિવાઇસ કનેક્ટ કર્યું" + "“%s” સાથે કનેક્ટ કરેલું" "કનેક્ટ કરી શક્યા નથી" + "કનેક્ટ કરી શકાયું નથી" + "ડિવાઇસથી મેન્યૂઅલી જોડાણ બનાવવાનો પ્રયાસ કરો" + "ડિવાઇસને જોડાણ બનાવવાના મોડમાં રાખવાનો પ્રયાસ કરો" + "પહોંચની અંદરના ડિવાઇસ" + "તમારા એકાઉન્ટ સાથેના ડિવાઇસ" + "તમારું સાચવેલું ડિવાઇસ ઉપલબ્ધ છે" + "શેરિંગ" + "ડિવાઇસ" + "કનેક્ટ કરી રહ્યાં છીએ…" + "બૅટરી: %d%%" "થઈ ગયું" "સાચવો" "કનેક્ટ કરો" diff --git a/nearby/halfsheet/res/values-hi/strings.xml b/nearby/halfsheet/res/values-hi/strings.xml index ecd420e3bf..dff9496c12 100644 --- a/nearby/halfsheet/res/values-hi/strings.xml +++ b/nearby/halfsheet/res/values-hi/strings.xml @@ -20,7 +20,18 @@ "सेट अप शुरू किया जा रहा है…" "डिवाइस सेट अप करें" "डिवाइस कनेक्ट हो गया" + "“%s” से कनेक्ट हो गया है" "कनेक्ट नहीं किया जा सका" + "कनेक्ट नहीं किया जा सका" + "डिवाइस को मैन्युअल तरीके से दूसरे डिवाइस से जोड़ने की कोशिश करें" + "डिवाइस को दूसरे डिवाइस से जोड़ने वाले मोड में रखें" + "ऐसे डिवाइस जो रेंज में हैं" + "आपके खाते से जुड़े डिवाइस" + "सेव किया गया डिवाइस उपलब्ध है" + "आस-पास शेयरिंग" + "डिवाइस" + "कनेक्ट हो रहा है…" + "बैटरी: %d%%" "हो गया" "सेव करें" "कनेक्ट करें" diff --git a/nearby/halfsheet/res/values-hr/strings.xml b/nearby/halfsheet/res/values-hr/strings.xml index 5a3de8f11c..13952b895c 100644 --- a/nearby/halfsheet/res/values-hr/strings.xml +++ b/nearby/halfsheet/res/values-hr/strings.xml @@ -20,7 +20,18 @@ "Pokretanje postavljanja…" "Postavi uređaj" "Uređaj je povezan" + "Povezano s uređajem %s" "Povezivanje nije uspjelo" + "Povezivanje nije uspjelo" + "Pokušajte ručno upariti s uređajem" + "Pokušajte staviti uređaj u način uparivanja" + "Uređaji u dometu" + "Uređaji s vašim računom" + "Spremljeni je uređaj dostupan" + "U blizini" + "Uređaji" + "Povezivanje…" + "Baterija: %d%%" "Gotovo" "Spremi" "Poveži" diff --git a/nearby/halfsheet/res/values-hu/strings.xml b/nearby/halfsheet/res/values-hu/strings.xml index ba3d2e0b86..3d810d46a0 100644 --- a/nearby/halfsheet/res/values-hu/strings.xml +++ b/nearby/halfsheet/res/values-hu/strings.xml @@ -20,7 +20,18 @@ "Beállítás megkezdése…" "Eszköz beállítása" "Eszköz csatlakoztatva" + "Csatlakoztatva a következőhöz: %s" "Nem sikerült csatlakozni" + "Nem sikerült csatlakozni" + "Próbálkozzon az eszköz kézi párosításával" + "Próbálja meg bekapcsolni az eszközön a párosítási módot" + "Elérhető eszközök" + "A fiókjával társított eszközök" + "A mentett eszköze elérhető" + "Közeli" + "Eszközök" + "Csatlakozás…" + "Akkumulátor: %d%%" "Kész" "Mentés" "Csatlakozás" diff --git a/nearby/halfsheet/res/values-hy/strings.xml b/nearby/halfsheet/res/values-hy/strings.xml index ecabd16c29..57b925609f 100644 --- a/nearby/halfsheet/res/values-hy/strings.xml +++ b/nearby/halfsheet/res/values-hy/strings.xml @@ -20,7 +20,18 @@ "Կարգավորում…" "Կարգավորեք սարքը" "Սարքը զուգակցվեց" + "«%s» սարքի հետ կապը հաստատվեց" "Չհաջողվեց միանալ" + "Չհաջողվեց կապ հաստատել" + "Փորձեք ձեռքով զուգակցել սարքը" + "Փորձեք սարքում միացնել զուգակցման ռեժիմը" + "Սարքեր հասանելիության տիրույթում" + "Ձեր հաշվի հետ կապված սարքեր" + "Պահված սարքը հասանելի է" + "Մոտակայքում" + "Սարքեր" + "Միացում…" + "Մարտկոցի լիցքը՝ %d%%" "Պատրաստ է" "Պահել" "Միանալ" diff --git a/nearby/halfsheet/res/values-in/strings.xml b/nearby/halfsheet/res/values-in/strings.xml index dc777b2193..c665572005 100644 --- a/nearby/halfsheet/res/values-in/strings.xml +++ b/nearby/halfsheet/res/values-in/strings.xml @@ -20,7 +20,18 @@ "Memulai Penyiapan …" "Siapkan perangkat" "Perangkat terhubung" + "Terhubung ke “%s”" "Tidak dapat terhubung" + "Tidak dapat terhubung" + "Coba sambungkan ke perangkat secara manual" + "Coba masukkan perangkat ke dalam mode penyambungan" + "Perangkat dalam jangkauan" + "Perangkat dengan akun Anda" + "Perangkat tersimpan tersedia" + "Berbagi Langsung" + "Perangkat" + "Menghubungkan …" + "Daya baterai: %d%%" "Selesai" "Simpan" "Hubungkan" diff --git a/nearby/halfsheet/res/values-is/strings.xml b/nearby/halfsheet/res/values-is/strings.xml index ee094d9fe5..04a4de4d4e 100644 --- a/nearby/halfsheet/res/values-is/strings.xml +++ b/nearby/halfsheet/res/values-is/strings.xml @@ -20,7 +20,18 @@ "Ræsir uppsetningu…" "Uppsetning tækis" "Tækið er tengt" + "Tengt við „%s“" "Tenging mistókst" + "Tenging tókst ekki" + "Prófaðu að para tækið handvirkt" + "Prófaðu að kveikja á pörunarstillingu í tækinu" + "Nálæg tæki" + "Tæki tengd reikningnum þínum" + "Vistaða tækið er tiltækt" + "Nálægt" + "Tæki" + "Tengist…" + "Rafhlaða: %d%%" "Lokið" "Vista" "Tengja" diff --git a/nearby/halfsheet/res/values-it/strings.xml b/nearby/halfsheet/res/values-it/strings.xml index 700dd77b55..2ffe268013 100644 --- a/nearby/halfsheet/res/values-it/strings.xml +++ b/nearby/halfsheet/res/values-it/strings.xml @@ -20,7 +20,18 @@ "Avvio della configurazione…" "Configura dispositivo" "Dispositivo connesso" + "Connesso a \"%s\"" "Impossibile connettere" + "Impossibile connettersi" + "Prova a eseguire l\'accoppiamento manuale con il dispositivo" + "Prova a impostare il dispositivo sulla modalità di accoppiamento" + "Dispositivi nelle vicinanze" + "Dispositivi collegati all\'account" + "Disposit. salvato disponibile" + "Nelle vicinanze" + "Dispositivi" + "Connessione in corso" + "Batteria: %d%%" "Fine" "Salva" "Connetti" diff --git a/nearby/halfsheet/res/values-iw/strings.xml b/nearby/halfsheet/res/values-iw/strings.xml index e6ff9b949c..61724f098c 100644 --- a/nearby/halfsheet/res/values-iw/strings.xml +++ b/nearby/halfsheet/res/values-iw/strings.xml @@ -20,7 +20,18 @@ "ההגדרה מתבצעת…" "הגדרת המכשיר" "המכשיר מחובר" + "‏יש חיבור אל \'%s\'" "לא ניתן להתחבר" + "לא ניתן להתחבר" + "כדאי לנסות לבצע התאמה ידנית למכשיר" + "כדאי לנסות להעביר את המכשיר למצב התאמה" + "מכשירים בהישג יד" + "מכשירים המחוברים לחשבון שלך" + "המכשיר ששמרת זמין" + "בקרבת מקום" + "מכשירים" + "מתבצעת התחברות…" + "‏טעינת הסוללה: %d%%" "סיום" "שמירה" "התחברות" diff --git a/nearby/halfsheet/res/values-ja/strings.xml b/nearby/halfsheet/res/values-ja/strings.xml index a429b7e5a7..3168bdad5d 100644 --- a/nearby/halfsheet/res/values-ja/strings.xml +++ b/nearby/halfsheet/res/values-ja/strings.xml @@ -20,7 +20,18 @@ "セットアップを開始中…" "デバイスのセットアップ" "デバイス接続完了" + "「%s」に接続しました" "接続エラー" + "接続できません" + "手動でデバイスとペア設定してみてください" + "デバイスをペア設定モードにしてください" + "近接するデバイス" + "アカウントと接続済みのデバイス" + "保存済みのデバイスがあります" + "周辺ユーザーとの共有" + "デバイス" + "接続しています…" + "バッテリー: %d%%" "完了" "保存" "接続" diff --git a/nearby/halfsheet/res/values-ka/strings.xml b/nearby/halfsheet/res/values-ka/strings.xml index 4353ae9758..a9ee648f6c 100644 --- a/nearby/halfsheet/res/values-ka/strings.xml +++ b/nearby/halfsheet/res/values-ka/strings.xml @@ -20,7 +20,18 @@ "დაყენება იწყება…" "მოწყობილობის დაყენება" "მოწყობილობა დაკავშირებულია" + "დაკავშირებულია „%s“-თან" "დაკავშირება ვერ მოხერხდა" + "დაკავშირება ვერ ხერხდება" + "ცადეთ მოწყობილობასთან ხელით დაწყვილება" + "ცადეთ მოწყობილობის გადაყვანა დაწყვილების რეჟიმზე" + "ხელმისაწვდომი მოწყობილობები" + "მოწყობილობები თქვენი ანგარიშით" + "შენახული მოწყობილობა ხელმისაწვდომია" + "ახლომახლო" + "მოწყობილობები" + "მიმდინარეობს დაკავშირება…" + "ბატარეა: %d%%" "მზადაა" "შენახვა" "დაკავშირება" diff --git a/nearby/halfsheet/res/values-kk/strings.xml b/nearby/halfsheet/res/values-kk/strings.xml index 98d8073cda..6e1a0bd63f 100644 --- a/nearby/halfsheet/res/values-kk/strings.xml +++ b/nearby/halfsheet/res/values-kk/strings.xml @@ -20,7 +20,18 @@ "Реттеу басталуда…" "Құрылғыны реттеу" "Құрылғы байланыстырылды" + "\"%s\" құрылғысымен байланыстырылды" "Қосылмады" + "Қосылу мүмкін емес" + "Құрылғыны қолмен жұптап көріңіз." + "Құрылғыны жұптау режиміне қойып көріңіз." + "Қолжетімді құрылғылар" + "Аккаунтпен байланыстырылған құрылғылар" + "Сақталған құрылғы қолжетімді" + "Маңайдағы" + "Құрылғылар" + "Жалғанып жатыр…" + "Батарея: %d%%" "Дайын" "Сақтау" "Қосу" diff --git a/nearby/halfsheet/res/values-km/strings.xml b/nearby/halfsheet/res/values-km/strings.xml index 85e39dbd8f..deb65045fb 100644 --- a/nearby/halfsheet/res/values-km/strings.xml +++ b/nearby/halfsheet/res/values-km/strings.xml @@ -20,7 +20,18 @@ "កំពុងចាប់ផ្ដើម​រៀបចំ…" "រៀបចំ​ឧបករណ៍" "បានភ្ជាប់ឧបករណ៍" + "បានភ្ជាប់ជាមួយ “%s”" "មិន​អាចភ្ជាប់​បានទេ" + "មិនអាច​ភ្ជាប់​បានទេ" + "សាកល្បង​ផ្គូផ្គង​ដោយ​ផ្ទាល់​ទៅឧបករណ៍" + "សាកល្បង​កំណត់​ឧបករណ៍​ឱ្យ​ប្រើ​មុខងារ​​ផ្គូផ្គង" + "ឧបករណ៍​នៅ​ក្បែរដៃ" + "ឧបករណ៍​ភ្ជាប់​ជាមួយ​គណនី​របស់អ្នក​" + "ឧបករណ៍ដែលអ្នកបានរក្សាទុកអាចប្រើបានហើយ" + "នៅ​ជិត" + "ឧបករណ៍" + "កំពុងភ្ជាប់…" + "ថ្ម៖ %d%%" "រួចរាល់" "រក្សាទុក" "ភ្ជាប់" diff --git a/nearby/halfsheet/res/values-kn/strings.xml b/nearby/halfsheet/res/values-kn/strings.xml index fb62bb1d2d..87b4fe33f7 100644 --- a/nearby/halfsheet/res/values-kn/strings.xml +++ b/nearby/halfsheet/res/values-kn/strings.xml @@ -20,7 +20,18 @@ "ಸೆಟಪ್ ಪ್ರಾರಂಭಿಸಲಾಗುತ್ತಿದೆ…" "ಸಾಧನವನ್ನು ಸೆಟಪ್ ಮಾಡಿ" "ಸಾಧನವನ್ನು ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ" + "“%s” ಗೆ ಕನೆಕ್ಟ್ ಮಾಡಲಾಗಿದೆ" "ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ" + "ಕನೆಕ್ಟ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ" + "ಸಾಧನಕ್ಕೆ ಹಸ್ತಚಾಲಿತವಾಗಿ ಜೋಡಿಸಲು ಪ್ರಯತ್ನಿಸಿ" + "ಜೋಡಿಸುವಿಕೆ ಮೋಡ್‌ನಲ್ಲಿ ಸಾಧನವನ್ನು ಇರಿಸಲು ಪ್ರಯತ್ನಿಸಿ" + "ವ್ಯಾಪ್ತಿಯಲ್ಲಿರುವ ಸಾಧನಗಳು" + "ನಿಮ್ಮ ಖಾತೆಗೆ ಸಂಪರ್ಕಿತವಾಗಿರುವ ಸಾಧನಗಳು" + "ಉಳಿಸಲಾದ ನಿಮ್ಮ ಸಾಧನವು ಲಭ್ಯವಿದೆ" + "ಸಮೀಪದಲ್ಲಿರುವುದು" + "ಸಾಧನಗಳು" + "ಕನೆಕ್ಟ್ ಆಗುತ್ತಿದೆ…" + "ಬ್ಯಾಟರಿ: %d%%" "ಮುಗಿದಿದೆ" "ಉಳಿಸಿ" "ಕನೆಕ್ಟ್ ಮಾಡಿ" diff --git a/nearby/halfsheet/res/values-ko/strings.xml b/nearby/halfsheet/res/values-ko/strings.xml index c94ff7650c..37d50da4ec 100644 --- a/nearby/halfsheet/res/values-ko/strings.xml +++ b/nearby/halfsheet/res/values-ko/strings.xml @@ -20,7 +20,18 @@ "설정을 시작하는 중…" "기기 설정" "기기 연결됨" + "\'%s\'에 연결됨" "연결할 수 없음" + "연결할 수 없음" + "기기에 수동으로 페어링을 시도해 보세요." + "기기를 페어링 모드로 전환하세요." + "연결 가능 기기" + "내 계정에 연결된 기기" + "저장된 기기 사용 가능" + "Nearby" + "기기" + "연결 중…" + "배터리: %d%%" "완료" "저장" "연결" diff --git a/nearby/halfsheet/res/values-ky/strings.xml b/nearby/halfsheet/res/values-ky/strings.xml index b0dfe2097c..b6aa409861 100644 --- a/nearby/halfsheet/res/values-ky/strings.xml +++ b/nearby/halfsheet/res/values-ky/strings.xml @@ -20,7 +20,18 @@ "Жөндөлүп баштады…" "Түзмөктү жөндөө" "Түзмөк туташты" + "“%s” түзмөгүнө туташты" "Туташпай койду" + "Туташпай жатат" + "Түзмөккө кол менен жупташтырып көрүңүз" + "Түзмөктү Байланыштыруу режимине коюп көрүңүз" + "Жеткиликтүү түзмөктөр" + "Аккаунтуңузга кирип турган түзмөктөр" + "Сакталган түзмөгүңүз жеткиликтүү" + "Жакын жерде" + "Түзмөктөр" + "Туташууда…" + "Батарея: %d%%" "Бүттү" "Сактоо" "Туташуу" diff --git a/nearby/halfsheet/res/values-lo/strings.xml b/nearby/halfsheet/res/values-lo/strings.xml index 9c945b247e..cbc7601898 100644 --- a/nearby/halfsheet/res/values-lo/strings.xml +++ b/nearby/halfsheet/res/values-lo/strings.xml @@ -20,7 +20,18 @@ "ກຳລັງເລີ່ມການຕັ້ງຄ່າ…" "ຕັ້ງຄ່າອຸປະກອນ" "ເຊື່ອມຕໍ່ອຸປະກອນແລ້ວ" + "ເຊື່ອມຕໍ່ກັບ “%s” ແລ້ວ" "ບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້" + "ບໍ່ສາມາດເຊື່ອມຕໍ່ໄດ້" + "ລອງຈັບຄູ່ອຸປະກອນດ້ວຍຕົນເອງ" + "ໃຫ້ລອງຕັ້ງອຸປະກອນເປັນໂໝດຈັບຄູ່" + "ອຸປະກອນພ້ອມໃຫ້ເຂົ້າເຖິງ" + "ອຸປະກອນທີ່ມີບັນຊີຂອງທ່ານ" + "ອຸປະກອນທີ່ບັນທຶກໄວ້ຂອງທ່ານສາມາດໃຊ້ໄດ້" + "ໃກ້ຄຽງ" + "ອຸປະກອນ" + "ກຳລັງເຊື່ອມຕໍ່…" + "ແບັດເຕີຣີ: %d%%" "ແລ້ວໆ" "ບັນທຶກ" "ເຊື່ອມຕໍ່" diff --git a/nearby/halfsheet/res/values-lt/strings.xml b/nearby/halfsheet/res/values-lt/strings.xml index 5dbad0ad33..29a9bc5ab8 100644 --- a/nearby/halfsheet/res/values-lt/strings.xml +++ b/nearby/halfsheet/res/values-lt/strings.xml @@ -20,7 +20,18 @@ "Pradedama sąranka…" "Įrenginio nustatymas" "Įrenginys prijungtas" + "Prisijungta prie „%s“" "Prisijungti nepavyko" + "Nepavyko prisijungti" + "Pabandykite neautomatiškai susieti įrenginį" + "Pabandykite vėl įgalinti įrenginio susiejimo režimą" + "Lengvai pasiekiami įrenginiai" + "Įrenginiai su jūsų paskyra" + "Išsaugotas įrenginys pasiekiamas" + "Netoliese" + "Įrenginiai" + "Prisijungiama…" + "Akumuliatorius: %d %%" "Atlikta" "Išsaugoti" "Prisijungti" diff --git a/nearby/halfsheet/res/values-lv/strings.xml b/nearby/halfsheet/res/values-lv/strings.xml index a9e1bf9c6f..9573357cd9 100644 --- a/nearby/halfsheet/res/values-lv/strings.xml +++ b/nearby/halfsheet/res/values-lv/strings.xml @@ -20,7 +20,18 @@ "Tiek sākta iestatīšana…" "Iestatiet ierīci" "Ierīce ir pievienota" + "Izveidots savienojums ar ierīci “%s”" "Nevarēja izveidot savienojumu" + "Nevar izveidot savienojumu." + "Mēģiniet manuāli izveidot savienojumu pārī ar ierīci." + "Ieslēdziet ierīcē režīmu savienošanai pārī" + "Sasniedzamas ierīces" + "Ierīces ar jūsu kontu" + "Jūsu saglabātā ierīce pieejama" + "Tuvumā" + "Ierīces" + "Savienojuma izveide…" + "Akumulatora uzlādes līmenis: %d%%" "Gatavs" "Saglabāt" "Izveidot savienojumu" diff --git a/nearby/halfsheet/res/values-mk/strings.xml b/nearby/halfsheet/res/values-mk/strings.xml index e29dfa146b..693f1123ce 100644 --- a/nearby/halfsheet/res/values-mk/strings.xml +++ b/nearby/halfsheet/res/values-mk/strings.xml @@ -20,7 +20,18 @@ "Се започнува со поставување…" "Поставете го уредот" "Уредот е поврзан" + "Поврзан со „%s“" "Не може да се поврзе" + "Не може да се поврзе" + "Обидете се рачно да се спарите со уредот" + "Пробајте да го ставите уредот во режим на спарување" + "Уреди на дофат" + "Уреди поврзани со вашата сметка" + "Вашиот зачуван уред е достапен" + "Во близина" + "Уреди" + "Се поврзува…" + "Батерија: %d %%" "Готово" "Зачувај" "Поврзи" diff --git a/nearby/halfsheet/res/values-ml/strings.xml b/nearby/halfsheet/res/values-ml/strings.xml index cbc171bf17..56a2db2a75 100644 --- a/nearby/halfsheet/res/values-ml/strings.xml +++ b/nearby/halfsheet/res/values-ml/strings.xml @@ -20,7 +20,18 @@ "സജ്ജീകരിക്കൽ ആരംഭിക്കുന്നു…" "ഉപകരണം സജ്ജീകരിക്കുക" "ഉപകരണം കണക്റ്റ് ചെയ്‌തു" + "“%s” എന്നതിലേക്ക് കണക്റ്റ് ചെയ്‌തു" "കണക്റ്റ് ചെയ്യാനായില്ല" + "കണക്റ്റ് ചെയ്യാനാകുന്നില്ല" + "ഉപകരണം ജോടിയാക്കാൻ നേരിട്ട് ശ്രമിക്കുക" + "ഉപകരണം ജോഡിയാക്കൽ മോഡിലേക്ക് മാറ്റിയ ശേഷം ശ്രമിക്കുക" + "പരിധിയിലുള്ള ഉപകരണങ്ങൾ" + "അക്കൗണ്ട് ഉപയോഗിക്കുന്ന ഉപകരണങ്ങൾ" + "സംരക്ഷിച്ച ഉപകരണം ലഭ്യമാണ്" + "സമീപമുള്ളവ" + "ഉപകരണങ്ങൾ" + "കണക്റ്റ് ചെയ്യുന്നു…" + "ബാറ്ററി: %d%%" "പൂർത്തിയായി" "സംരക്ഷിക്കുക" "കണക്റ്റ് ചെയ്യുക" diff --git a/nearby/halfsheet/res/values-mn/strings.xml b/nearby/halfsheet/res/values-mn/strings.xml index 6d21effb4a..5a72ce3846 100644 --- a/nearby/halfsheet/res/values-mn/strings.xml +++ b/nearby/halfsheet/res/values-mn/strings.xml @@ -20,7 +20,18 @@ "Тохируулгыг эхлүүлж байна…" "Төхөөрөмж тохируулах" "Төхөөрөмж холбогдсон" + "“%s”-д холбогдсон" "Холбогдож чадсангүй" + "Холбогдох боломжгүй байна" + "Төхөөрөмжийг гар аргаар хослуулна уу" + "Төхөөрөмжийг хослуулах горимд оруулахаар оролдоно уу" + "Хүрээн дэх төхөөрөмжүүд" + "Таны бүртгэлтэй холбогдсон төхөөрөмж" + "Таны хадгалсан төхөөрөмж бэлэн байна" + "Ойролцоо" + "Төхөөрөмж" + "Холбогдож байна…" + "Батарей: %d%%" "Болсон" "Хадгалах" "Холбох" diff --git a/nearby/halfsheet/res/values-mr/strings.xml b/nearby/halfsheet/res/values-mr/strings.xml index a3e1d7a843..3eeb0ec587 100644 --- a/nearby/halfsheet/res/values-mr/strings.xml +++ b/nearby/halfsheet/res/values-mr/strings.xml @@ -20,7 +20,18 @@ "सेटअप सुरू करत आहे…" "डिव्हाइस सेट करा" "डिव्हाइस कनेक्ट केले आहे" + "\"%s\" शी कनेक्ट केले आहे" "कनेक्ट करता आले नाही" + "कनेक्ट करता आले नाही" + "डिव्हाइसशी मॅन्युअली पेअर करण्याचा प्रयत्न करा" + "डिव्हाइसला पेअरिंग मोडमध्ये ठेवण्याचा प्रयत्न करा" + "पुरेसे जवळ असलेले डिव्हाइस" + "तुमच्या खात्‍यासह असलेली डिव्‍हाइस" + "सेव्ह केलेले डिव्हाइस उपलब्ध" + "जवळपास" + "डिव्हाइस" + "कनेक्ट करत आहे…" + "बॅटरी: %d%%" "पूर्ण झाले" "सेव्ह करा" "कनेक्ट करा" diff --git a/nearby/halfsheet/res/values-ms/strings.xml b/nearby/halfsheet/res/values-ms/strings.xml index 4835c1b335..0af903db13 100644 --- a/nearby/halfsheet/res/values-ms/strings.xml +++ b/nearby/halfsheet/res/values-ms/strings.xml @@ -20,7 +20,18 @@ "Memulakan Persediaan…" "Sediakan peranti" "Peranti disambungkan" + "Disambungkan kepada “%s”" "Tidak dapat menyambung" + "Tidak dapat menyambung" + "Cuba gandingkan dengan peranti secara manual" + "Cuba letakkan peranti dalam mod penggandingan" + "Peranti dalam capaian" + "Peranti dengan akaun anda" + "Peranti disimpan anda tersedia" + "Berdekatan" + "Peranti" + "Menyambung…" + "Bateri: %d%%" "Selesai" "Simpan" "Sambung" diff --git a/nearby/halfsheet/res/values-my/strings.xml b/nearby/halfsheet/res/values-my/strings.xml index 32c3105080..306538f597 100644 --- a/nearby/halfsheet/res/values-my/strings.xml +++ b/nearby/halfsheet/res/values-my/strings.xml @@ -20,7 +20,18 @@ "စနစ်ထည့်သွင်းခြင်း စတင်နေသည်…" "စက်ကို စနစ်ထည့်သွင်းရန်" "စက်ကို ချိတ်ဆက်လိုက်ပြီ" + "“%s” သို့ ချိတ်ဆက်ထားသည်" "ချိတ်ဆက်၍မရပါ" + "ချိတ်ဆက်၍ မရပါ" + "စက်ကို ကိုယ်တိုင်တွဲချိတ်ကြည့်ပါ" + "စက်ကို တွဲချိတ်ခြင်းမုဒ်သို့ ထားကြည့်ပါ" + "လက်လှမ်းမီသည့် စက်များ" + "သင့်အကောင့်ရှိသည့် စက်များ" + "သင်သိမ်းထားသောစက် ရပါပြီ" + "အနီးတစ်ဝိုက်" + "စက်များ" + "ချိတ်ဆက်နေသည်…" + "ဘက်ထရီ− %d%%" "ပြီးပြီ" "သိမ်းရန်" "ချိတ်ဆက်ရန်" diff --git a/nearby/halfsheet/res/values-nb/strings.xml b/nearby/halfsheet/res/values-nb/strings.xml index 9d725659a1..72a2ab7565 100644 --- a/nearby/halfsheet/res/values-nb/strings.xml +++ b/nearby/halfsheet/res/values-nb/strings.xml @@ -20,7 +20,18 @@ "Starter konfigureringen …" "Konfigurer enheten" "Enheten er tilkoblet" + "Koblet til «%s»" "Kunne ikke koble til" + "Kan ikke koble til" + "Prøv manuell tilkobling til enheten" + "Prøv å sette enheten i tilkoblingsmodus" + "Enheter innen rekkevidde" + "Enheter med kontoen din" + "Lagret enhet tilgjengelig" + "I nærheten" + "Enheter" + "Kobler til …" + "Batteri: %d %%" "Ferdig" "Lagre" "Koble til" diff --git a/nearby/halfsheet/res/values-ne/strings.xml b/nearby/halfsheet/res/values-ne/strings.xml index 1370412b1e..2956768f43 100644 --- a/nearby/halfsheet/res/values-ne/strings.xml +++ b/nearby/halfsheet/res/values-ne/strings.xml @@ -20,7 +20,18 @@ "सेटअप प्रक्रिया सुरु गरिँदै छ…" "डिभाइस सेटअप गर्नुहोस्" "डिभाइस कनेक्ट गरियो" + "\"%s\" मा कनेक्ट गरिएको छ" "कनेक्ट गर्न सकिएन" + "कनेक्ट गर्न सकिएन" + "म्यानुअल तरिकाले डिभाइस कनेक्ट गरी हेर्नुहोस्" + "डिभाइस कनेक्ट गर्ने मोडमा राखी हेर्नुहोस्" + "कनेक्ट गर्न सकिने नजिकैका डिभाइसहरू" + "तपाईंको खातामा कनेक्ट गरिएका डिभाइस" + "तपाईंले सेभ गरेको डिभाइस उपलब्ध छ" + "नजिकै" + "डिभाइसहरू" + "कनेक्ट गरिँदै छ…" + "ब्याट्री: %d%%" "सम्पन्न भयो" "सेभ गर्नुहोस्" "कनेक्ट गर्नुहोस्" diff --git a/nearby/halfsheet/res/values-night/colors.xml b/nearby/halfsheet/res/values-night/colors.xml new file mode 100644 index 0000000000..69b832a605 --- /dev/null +++ b/nearby/halfsheet/res/values-night/colors.xml @@ -0,0 +1,30 @@ + + + + #00000000 + + + @android:color/system_accent1_100 + + @android:color/system_neutral1_50 + + @android:color/system_neutral1_900 + + @android:color/system_accent1_600 + + @android:color/system_neutral2_200 + + @android:color/system_neutral1_50 + + @android:color/system_neutral1_800 + + + #FFFFFF + #24FFFFFF + #F6AEA9 + + + diff --git a/nearby/halfsheet/res/values-nl/strings.xml b/nearby/halfsheet/res/values-nl/strings.xml index 4eb7624b57..e956116a2e 100644 --- a/nearby/halfsheet/res/values-nl/strings.xml +++ b/nearby/halfsheet/res/values-nl/strings.xml @@ -20,7 +20,18 @@ "Instellen starten…" "Apparaat instellen" "Apparaat verbonden" + "Verbonden met %s" "Kan geen verbinding maken" + "Kan geen verbinding maken" + "Probeer handmatig met het apparaat te koppelen" + "Activeer de koppelingsstand van het apparaat" + "Apparaten binnen bereik" + "Apparaten met je account" + "Opgeslagen apparaat beschikbaar" + "In de buurt" + "Apparaten" + "Verbinden…" + "Batterij: %d%%" "Klaar" "Opslaan" "Verbinden" diff --git a/nearby/halfsheet/res/values-or/strings.xml b/nearby/halfsheet/res/values-or/strings.xml index c5e8cfcc73..0ec472ca31 100644 --- a/nearby/halfsheet/res/values-or/strings.xml +++ b/nearby/halfsheet/res/values-or/strings.xml @@ -20,7 +20,18 @@ "ସେଟଅପ ଆରମ୍ଭ କରାଯାଉଛି…" "ଡିଭାଇସ ସେଟ ଅପ କରନ୍ତୁ" "ଡିଭାଇସ ସଂଯୁକ୍ତ ହୋଇଛି" + "“%s” ସହ କନେକ୍ଟ କରାଯାଇଛି" "ସଂଯୋଗ କରାଯାଇପାରିଲା ନାହିଁ" + "କନେକ୍ଟ କରିବାରେ ଅସମର୍ଥ" + "ଡିଭାଇସ ସହ ମାନୁଆଲୀ ପେୟାର କରିବା ପାଇଁ ଚେଷ୍ଟା କରନ୍ତୁ" + "ଡିଭାଇସକୁ ପେୟାରିଂ ମୋଡରେ ରଖିବାକୁ ଚେଷ୍ଟା କରନ୍ତୁ" + "ପହଞ୍ଚ ଭିତରେ ଥିବା ଡିଭାଇସଗୁଡ଼ିକ" + "ଆପଣଙ୍କ ଆକାଉଣ୍ଟ ସହ ଥିବା ଡିଭାଇସଗୁଡ଼ିକ" + "ସେଭ ଥିବା ଆପଣଙ୍କ ଡିଭାଇସ ଉପଲବ୍ଧ" + "ଆଖପାଖର" + "ଡିଭାଇସଗୁଡ଼ିକ" + "କନେକ୍ଟ କରାଯାଉଛି…" + "ବେଟେରୀ: %d%%" "ହୋଇଗଲା" "ସେଭ କରନ୍ତୁ" "ସଂଯୋଗ କରନ୍ତୁ" diff --git a/nearby/halfsheet/res/values-pa/strings.xml b/nearby/halfsheet/res/values-pa/strings.xml index f0523a3a39..4eb0553ea1 100644 --- a/nearby/halfsheet/res/values-pa/strings.xml +++ b/nearby/halfsheet/res/values-pa/strings.xml @@ -20,7 +20,18 @@ "ਸੈੱਟਅੱਪ ਸ਼ੁਰੂ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…" "ਡੀਵਾਈਸ ਸੈੱਟਅੱਪ ਕਰੋ" "ਡੀਵਾਈਸ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ" + "“%s” ਨਾਲ ਕਨੈਕਟ ਹੈ" "ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ" + "ਕਨੈਕਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ" + "ਡੀਵਾਈਸ ਨਾਲ ਹੱਥੀਂ ਜੋੜਾਬੱਧ ਕਰਨ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ" + "ਡੀਵਾਈਸ ਨੂੰ ਜੋੜਾਬੱਧਕਰਨ ਮੋਡ ਵਿੱਚ ਰੱਖਣ ਦੀ ਕੋਸ਼ਿਸ਼ ਕਰੋ" + "ਡੀਵਾਈਸ ਜੋ ਪਹੁੰਚ ਅੰਦਰ ਹਨ" + "ਤੁਹਾਡੇ ਖਾਤੇ ਵਾਲੇ ਡੀਵਾਈਸ" + "ਰੱਖਿਅਤ ਕੀਤਾ ਡੀਵਾਈਸ ਉਪਲਬਧ ਹੈ" + "ਨਜ਼ਦੀਕੀ" + "ਡੀਵਾਈਸ" + "ਕਨੈਕਟ ਹੋ ਰਿਹਾ ਹੈ…" + "ਬੈਟਰੀ: %d%%" "ਹੋ ਗਿਆ" "ਰੱਖਿਅਤ ਕਰੋ" "ਕਨੈਕਟ ਕਰੋ" diff --git a/nearby/halfsheet/res/values-pl/strings.xml b/nearby/halfsheet/res/values-pl/strings.xml index 5abf5fd34b..5082e18159 100644 --- a/nearby/halfsheet/res/values-pl/strings.xml +++ b/nearby/halfsheet/res/values-pl/strings.xml @@ -20,7 +20,18 @@ "Rozpoczynam konfigurowanie…" "Skonfiguruj urządzenie" "Urządzenie połączone" + "Połączono z: „%s”" "Nie udało się połączyć" + "Nie udało się połączyć" + "Spróbuj ręcznie sparować urządzenie" + "Włącz na urządzeniu tryb parowania" + "Urządzenia w zasięgu" + "Urządzenia z Twoim kontem" + "Zapisane urządzenie dostępne" + "W pobliżu" + "Urządzenia" + "Łączę…" + "Bateria: %d%%" "Gotowe" "Zapisz" "Połącz" diff --git a/nearby/halfsheet/res/values-pt-rBR/strings.xml b/nearby/halfsheet/res/values-pt-rBR/strings.xml index b021b3924f..15d29d2cc5 100644 --- a/nearby/halfsheet/res/values-pt-rBR/strings.xml +++ b/nearby/halfsheet/res/values-pt-rBR/strings.xml @@ -20,7 +20,18 @@ "Iniciando a configuração…" "Configurar dispositivo" "Dispositivo conectado" + "Conectado ao dispositivo \"%s\"" "Erro ao conectar" + "Não foi possível conectar" + "Tente parear o dispositivo manualmente" + "Coloque o dispositivo no modo de pareamento" + "Dispositivos ao alcance" + "Dispositivos conectados à sua conta" + "Dispositivo salvo disponível" + "Por perto" + "Dispositivos" + "Conectando…" + "Bateria: %d%%" "Concluído" "Salvar" "Conectar" diff --git a/nearby/halfsheet/res/values-pt-rPT/strings.xml b/nearby/halfsheet/res/values-pt-rPT/strings.xml index 3285c73e9c..ab8decf299 100644 --- a/nearby/halfsheet/res/values-pt-rPT/strings.xml +++ b/nearby/halfsheet/res/values-pt-rPT/strings.xml @@ -20,7 +20,18 @@ "A iniciar a configuração…" "Configure o dispositivo" "Dispositivo ligado" + "Ligado a \"%s\"" "Não foi possível ligar" + "Impossível ligar" + "Experimente sincronizar manualmente com o dispositivo" + "Experimente ativar o modo de sincronização no dispositivo" + "Dispositivos ao alcance" + "Dispositivos com a sua conta" + "Disposit. guardado disponível" + "Partilha" + "Dispositivos" + "A ligar…" + "Bateria: %d%%" "Concluir" "Guardar" "Ligar" diff --git a/nearby/halfsheet/res/values-pt/strings.xml b/nearby/halfsheet/res/values-pt/strings.xml index b021b3924f..15d29d2cc5 100644 --- a/nearby/halfsheet/res/values-pt/strings.xml +++ b/nearby/halfsheet/res/values-pt/strings.xml @@ -20,7 +20,18 @@ "Iniciando a configuração…" "Configurar dispositivo" "Dispositivo conectado" + "Conectado ao dispositivo \"%s\"" "Erro ao conectar" + "Não foi possível conectar" + "Tente parear o dispositivo manualmente" + "Coloque o dispositivo no modo de pareamento" + "Dispositivos ao alcance" + "Dispositivos conectados à sua conta" + "Dispositivo salvo disponível" + "Por perto" + "Dispositivos" + "Conectando…" + "Bateria: %d%%" "Concluído" "Salvar" "Conectar" diff --git a/nearby/halfsheet/res/values-ro/strings.xml b/nearby/halfsheet/res/values-ro/strings.xml index 189f698dd9..0335d01ef3 100644 --- a/nearby/halfsheet/res/values-ro/strings.xml +++ b/nearby/halfsheet/res/values-ro/strings.xml @@ -20,7 +20,18 @@ "Începe configurarea…" "Configurează dispozitivul" "Dispozitivul s-a conectat" + "Conectat la %s" "Nu s-a putut conecta" + "Nu se poate conecta" + "Încearcă asocierea manuală cu dispozitivul" + "Încearcă să pui dispozitivul în modul de asociere" + "Dispozitive în vecinătate" + "Dispozitive conectate cu contul" + "Dispozitivul este disponibil" + "În apropiere" + "Dispozitive" + "Se conectează…" + "Baterie: %d%%" "Gata" "Salvează" "Conectează" diff --git a/nearby/halfsheet/res/values-ru/strings.xml b/nearby/halfsheet/res/values-ru/strings.xml index ee869dff91..d90b6447e5 100644 --- a/nearby/halfsheet/res/values-ru/strings.xml +++ b/nearby/halfsheet/res/values-ru/strings.xml @@ -20,7 +20,18 @@ "Начинаем настройку…" "Настройка устройства" "Устройство подключено" + "Подключено к устройству \"%s\"" "Ошибка подключения" + "Не удалось подключиться" + "Попробуйте подключиться к устройству вручную." + "Переведите устройство в режим подключения." + "Устройства в зоне охвата" + "Устройства с вашим аккаунтом" + "Доступно сохранен. устройство" + "Мое окружение" + "Устройства" + "Подключение…" + "Батарея: %d %%" "Готово" "Сохранить" "Подключить" diff --git a/nearby/halfsheet/res/values-si/strings.xml b/nearby/halfsheet/res/values-si/strings.xml index f4274c27aa..c9b96bb23f 100644 --- a/nearby/halfsheet/res/values-si/strings.xml +++ b/nearby/halfsheet/res/values-si/strings.xml @@ -20,7 +20,18 @@ "පිහිටුවීම ආරම්භ කරමින්…" "උපාංගය පිහිටුවන්න" "උපාංගය සම්බන්ධිතයි" + "“%s” වෙත සම්බන්ධ විය" "සම්බන්ධ කළ නොහැකි විය" + "සම්බන්ධ වීමට නොහැකි වේ" + "උපාංගය වෙත හස්තීයව යුගල කිරීමට උත්සාහ කරන්න" + "උපාංගය යුගල කිරීමේ ප්‍රකාරයට දැමීමට උත්සාහ කරන්න" + "ළඟා විය හැකි උපාංග" + "ඔබේ ගිණුම සමග උපාංග" + "ඔබේ සුරැකි උපාංගය ලබා ගත හැක" + "අවට" + "උපාංග" + "සම්බන්ධ වෙමින්…" + "බැටරිය: %d%%" "නිමයි" "සුරකින්න" "සම්බන්ධ කරන්න" diff --git a/nearby/halfsheet/res/values-sk/strings.xml b/nearby/halfsheet/res/values-sk/strings.xml index 46c45af3e2..f7ab21fc65 100644 --- a/nearby/halfsheet/res/values-sk/strings.xml +++ b/nearby/halfsheet/res/values-sk/strings.xml @@ -20,7 +20,18 @@ "Spúšťa sa nastavenie…" "Nastavte zariadenie" "Zariadenie bolo pripojené" + "Pripojené k zariadeniu %s" "Nepodarilo sa pripojiť" + "Nepodarilo sa pripojiť" + "Skúste zariadenie spárovať ručne" + "Prepnite zariadenie do párovacieho režimu" + "Zariadenia v dosahu" + "Zariadenia s vaším účtom" + "Uložené zariadenie je dostupné" + "Nablízku" + "Zariadenia" + "Pripája sa…" + "Batéria: %d %%" "Hotovo" "Uložiť" "Pripojiť" diff --git a/nearby/halfsheet/res/values-sl/strings.xml b/nearby/halfsheet/res/values-sl/strings.xml index e4f3c91a3a..9e9357c75f 100644 --- a/nearby/halfsheet/res/values-sl/strings.xml +++ b/nearby/halfsheet/res/values-sl/strings.xml @@ -20,7 +20,18 @@ "Začetek nastavitve …" "Nastavitev naprave" "Naprava je povezana" + "Povezano z napravo »%s«" "Povezava ni mogoča" + "Povezave ni mogoče vzpostaviti" + "Poskusite ročno seznaniti napravo." + "Poskusite napravo preklopiti v način za seznanjanje." + "Naprave znotraj dosega" + "Naprave z vašim računom" + "Shranjena naprava je na voljo" + "Bližina" + "Naprave" + "Povezovanje …" + "Baterija: %d %%" "Končano" "Shrani" "Poveži" diff --git a/nearby/halfsheet/res/values-sq/strings.xml b/nearby/halfsheet/res/values-sq/strings.xml index 9265d1f63d..538e9d6ec0 100644 --- a/nearby/halfsheet/res/values-sq/strings.xml +++ b/nearby/halfsheet/res/values-sq/strings.xml @@ -20,7 +20,18 @@ "Po nis konfigurimin…" "Konfiguro pajisjen" "Pajisja u lidh" + "U lidh me “%s”" "Nuk mund të lidhej" + "Nuk mund të lidhet" + "Provo të çiftosh me pajisjen manualisht" + "Provo ta vendosësh pajisjen në modalitetin e çiftimit" + "Pajisjet që mund të arrish" + "Pajisjet me llogarinë tënde" + "Pajisja jote e ruajtur ofrohet" + "Në afërsi" + "Pajisjet" + "Po lidhet…" + "Bateria: %d%%" "U krye" "Ruaj" "Lidh" diff --git a/nearby/halfsheet/res/values-sr/strings.xml b/nearby/halfsheet/res/values-sr/strings.xml index 094be03a6f..c4bcd19d93 100644 --- a/nearby/halfsheet/res/values-sr/strings.xml +++ b/nearby/halfsheet/res/values-sr/strings.xml @@ -20,7 +20,18 @@ "Подешавање се покреће…" "Подесите уређај" "Уређај је повезан" + "Повезани сте са уређајем %s" "Повезивање није успело" + "Повезивање није успело" + "Пробајте да упарите ручно са уређајем" + "Пробајте да пребаците уређај у режим упаривања" + "Уређаји у домету" + "Уређаји повезани са налогом" + "Сачувани уређај је доступан" + "У близини" + "Уређаји" + "Повезује се…" + "Батерија: %d%%" "Готово" "Сачувај" "Повежи" diff --git a/nearby/halfsheet/res/values-sv/strings.xml b/nearby/halfsheet/res/values-sv/strings.xml index 297b7bc562..b00091ccb0 100644 --- a/nearby/halfsheet/res/values-sv/strings.xml +++ b/nearby/halfsheet/res/values-sv/strings.xml @@ -20,7 +20,18 @@ "Konfigureringen startas …" "Konfigurera enheten" "Enheten är ansluten" + "Ansluten till %s" "Det gick inte att ansluta" + "Det går inte att ansluta" + "Testa att parkoppla enheten manuellt" + "Testa att aktivera parkopplingsläget på enheten" + "Enheter inom räckvidd" + "Enheter med ditt konto" + "En sparad enhet är tillgänglig" + "Närdelning" + "Enheter" + "Ansluter …" + "Batteri: %d %%" "Klar" "Spara" "Anslut" diff --git a/nearby/halfsheet/res/values-sw/strings.xml b/nearby/halfsheet/res/values-sw/strings.xml index bf0bfebc87..238a288963 100644 --- a/nearby/halfsheet/res/values-sw/strings.xml +++ b/nearby/halfsheet/res/values-sw/strings.xml @@ -20,7 +20,18 @@ "Inaanza Kuweka Mipangilio…" "Weka mipangilio ya kifaa" "Kifaa kimeunganishwa" + "Imeunganishwa kwenye “%s”" "Imeshindwa kuunganisha" + "Imeshindwa kuunganisha" + "Jaribu kuoanisha mwenyewe kwenye kifaa" + "Jaribu kuweka kifaa katika hali ya kuoanisha" + "Vifaa vilivyo karibu nawe" + "Vifaa vilivyounganishwa kwenye akaunti yako" + "Kifaa ulichohifadhi kinapatikana" + "Uhamishaji wa Karibu" + "Vifaa" + "Inaunganisha…" + "Betri: %d%%" "Imemaliza" "Hifadhi" "Unganisha" diff --git a/nearby/halfsheet/res/values-ta/strings.xml b/nearby/halfsheet/res/values-ta/strings.xml index dfd67a6fb2..baadcc2d59 100644 --- a/nearby/halfsheet/res/values-ta/strings.xml +++ b/nearby/halfsheet/res/values-ta/strings.xml @@ -20,7 +20,18 @@ "அமைவைத் தொடங்குகிறது…" "சாதனத்தை அமையுங்கள்" "சாதனம் இணைக்கப்பட்டது" + "“%s” உடன் இணைக்கப்பட்டது" "இணைக்க முடியவில்லை" + "இணைக்க முடியவில்லை" + "சாதனத்துடன் நீங்களாகவே இணைக்க முயலவும்" + "சாதனத்தை \'இணைத்தல் பயன்முறையில்\' வைக்கவும்" + "தொடர்பு வரம்பிலுள்ள சாதனங்கள்" + "உங்கள் கணக்குடன் இணைந்துள்ள சாதனங்கள்" + "நீங்கள் சேமித்த சாதனம் உள்ளது" + "அருகில் பகிர்தல்" + "சாதனங்கள்" + "இணைக்கிறது…" + "பேட்டரி: %d%%" "முடிந்தது" "சேமி" "இணை" diff --git a/nearby/halfsheet/res/values-te/strings.xml b/nearby/halfsheet/res/values-te/strings.xml index 87be14563f..cb8f91b6db 100644 --- a/nearby/halfsheet/res/values-te/strings.xml +++ b/nearby/halfsheet/res/values-te/strings.xml @@ -20,7 +20,18 @@ "సెటప్ ప్రారంభమవుతోంది…" "పరికరాన్ని సెటప్ చేయండి" "పరికరం కనెక్ట్ చేయబడింది" + "“%s”కు కనెక్ట్ చేయబడింది" "కనెక్ట్ చేయడం సాధ్యపడలేదు" + "కనెక్ట్ చేయలేకపోయింది" + "పరికరానికి మాన్యువల్‌గా పెయిరింగ్ చేయడానికి ట్రై చేయండి" + "పరికరాన్ని పెయిరింగ్ మోడ్‌లో ఉంచడానికి ట్రై చేయండి" + "అందుబాటులో ఉన్న పరికరాలు" + "మీ ఖాతా ఉన్న పరికరాలు" + "మీ సేవ్ చేసిన పరికరం అందుబాటులో ఉంది" + "సమీపం" + "పరికరాలు" + "కనెక్ట్ అవుతోంది…" + "బ్యాటరీ: %d%%" "పూర్తయింది" "సేవ్ చేయండి" "కనెక్ట్ చేయండి" diff --git a/nearby/halfsheet/res/values-th/strings.xml b/nearby/halfsheet/res/values-th/strings.xml index bc4296bd22..f5c5c2e2ef 100644 --- a/nearby/halfsheet/res/values-th/strings.xml +++ b/nearby/halfsheet/res/values-th/strings.xml @@ -20,7 +20,18 @@ "กำลังเริ่มการตั้งค่า…" "ตั้งค่าอุปกรณ์" "เชื่อมต่ออุปกรณ์แล้ว" + "เชื่อมต่อกับ \"%s\" แล้ว" "เชื่อมต่อไม่ได้" + "เชื่อมต่อไม่ได้" + "ลองจับคู่อุปกรณ์ด้วยตนเอง" + "พยายามนำอุปกรณ์เข้าสู่โหมดการจับคู่" + "อุปกรณ์ที่อยู่ติดกัน" + "อุปกรณ์ที่มีบัญชีของคุณ" + "อุปกรณ์ที่บันทึกพร้อมใช้แล้ว" + "ใกล้เคียง" + "อุปกรณ์" + "กำลังเชื่อมต่อ…" + "แบตเตอรี่: %d%%" "เสร็จสิ้น" "บันทึก" "เชื่อมต่อ" diff --git a/nearby/halfsheet/res/values-tl/strings.xml b/nearby/halfsheet/res/values-tl/strings.xml index a6de0e8f9a..a546da67c2 100644 --- a/nearby/halfsheet/res/values-tl/strings.xml +++ b/nearby/halfsheet/res/values-tl/strings.xml @@ -20,7 +20,18 @@ "Sinisimulan ang Pag-set Up…" "I-set up ang device" "Naikonekta na ang device" + "Nakakonekta sa “%s”" "Hindi makakonekta" + "Hindi makakonekta" + "Subukang manual na magpares sa device" + "Subukang ilagay sa pairing mode ang device" + "Mga naaabot na device" + "Mga device sa iyong account" + "Available ang iyong na-save na device" + "Kalapit" + "Mga Device" + "Kumokonekta…" + "Baterya: %d%%" "Tapos na" "I-save" "Kumonekta" diff --git a/nearby/halfsheet/res/values-tr/strings.xml b/nearby/halfsheet/res/values-tr/strings.xml index cd5a6ea9b0..a54c5e7658 100644 --- a/nearby/halfsheet/res/values-tr/strings.xml +++ b/nearby/halfsheet/res/values-tr/strings.xml @@ -20,7 +20,18 @@ "Kurulum Başlatılıyor…" "Cihazı kur" "Cihaz bağlandı" + "\"%s\" cihazına bağlanıldı" "Bağlanamadı" + "Bağlantı kurulamadı" + "Cihazla manuel olarak eşlemeyi deneyin" + "Cihazı eşleme moduna geçirmeyi deneyin" + "Erişilebilecek cihazlar" + "Hesabınıza bağlı cihazlar" + "Kayıtlı cihazınız kullanılabilir" + "Yakındaki" + "Cihazlar" + "Bağlanıyor…" + "Pil seviyesi: %%%d" "Bitti" "Kaydet" "Bağlan" diff --git a/nearby/halfsheet/res/values-uk/strings.xml b/nearby/halfsheet/res/values-uk/strings.xml index 242ca07a06..ab73c1fb5b 100644 --- a/nearby/halfsheet/res/values-uk/strings.xml +++ b/nearby/halfsheet/res/values-uk/strings.xml @@ -20,7 +20,18 @@ "Запуск налаштування…" "Налаштуйте пристрій" "Пристрій підключено" + "Підключено до пристрою \"%s\"" "Не вдалося підключити" + "Не вдалося підключитися" + "Спробуйте підключитися до пристрою вручну" + "Спробуйте ввімкнути на пристрої режим підключення" + "Пристрої в радіусі дії" + "Пристрої з вашим обліковим записом" + "Збережений пристрій доступний" + "Поблизу" + "Пристрої" + "Підключення…" + "Акумулятор: %d%%" "Готово" "Зберегти" "Підключити" diff --git a/nearby/halfsheet/res/values-ur/strings.xml b/nearby/halfsheet/res/values-ur/strings.xml index 4a4a59caa6..a2b2038edf 100644 --- a/nearby/halfsheet/res/values-ur/strings.xml +++ b/nearby/halfsheet/res/values-ur/strings.xml @@ -20,7 +20,18 @@ "سیٹ اپ شروع ہو رہا ہے…" "آلہ سیٹ اپ کریں" "آلہ منسلک ہے" + "‏\"‎\"%s سے منسلک ہے" "منسلک نہیں ہو سکا" + "منسلک ہونے سے قاصر" + "دستی طور پر آلے کے ساتھ جوڑا بنانا آزمائیں" + "آلے کو جوڑا بنانے والے موڈ میں رکھ کر آزمائیں" + "رسائی کے اندر آلات" + "آپ کے اکاؤنٹ سے منسلک آلات" + "آپ کا محفوظ کردہ آلہ دستیاب ہے" + "قریبی" + "آلات" + "منسلک ہو رہا ہے…" + "‏بیٹری: ‎‎%d%%‎" "ہو گیا" "محفوظ کریں" "منسلک کریں" diff --git a/nearby/halfsheet/res/values-uz/strings.xml b/nearby/halfsheet/res/values-uz/strings.xml index 420512daa0..70c190ae7c 100644 --- a/nearby/halfsheet/res/values-uz/strings.xml +++ b/nearby/halfsheet/res/values-uz/strings.xml @@ -20,7 +20,18 @@ "Sozlash boshlandi…" "Qurilmani sozlash" "Qurilma ulandi" + "Bunga ulandi: “%s”" "Ulanmadi" + "Ulanmadi" + "Qurilmangizga odatiy usulda ulaning" + "Qurilmada ulanish rejimini yoqing" + "Atrofdagi qurilmalar" + "Hisobingizga ulangan qurilmalar" + "Saqlangan qurilmangiz mavjud" + "Yaqin-atrofda" + "Qurilmalar" + "Ulanmoqda…" + "Batareya: %d%%" "Tayyor" "Saqlash" "Ulanish" diff --git a/nearby/halfsheet/res/values-vi/strings.xml b/nearby/halfsheet/res/values-vi/strings.xml index 9c1e052981..e2ea4673b5 100644 --- a/nearby/halfsheet/res/values-vi/strings.xml +++ b/nearby/halfsheet/res/values-vi/strings.xml @@ -20,7 +20,18 @@ "Đang bắt đầu thiết lập…" "Thiết lập thiết bị" "Đã kết nối thiết bị" + "Đã kết nối với “%s”" "Không kết nối được" + "Không thể kết nối" + "Hãy thử ghép nối thủ công với thiết bị" + "Hãy thử đưa thiết bị này vào chế độ ghép nối" + "Thiết bị trong tầm tay" + "Các thiết bị có tài khoản của bạn" + "Thiết bị đã lưu của bạn có sẵn" + "Lân cận" + "Thiết bị" + "Đang kết nối…" + "Pin: %d%%" "Xong" "Lưu" "Kết nối" diff --git a/nearby/halfsheet/res/values-zh-rCN/strings.xml b/nearby/halfsheet/res/values-zh-rCN/strings.xml index 482b5c4f92..8117bac93c 100644 --- a/nearby/halfsheet/res/values-zh-rCN/strings.xml +++ b/nearby/halfsheet/res/values-zh-rCN/strings.xml @@ -20,7 +20,18 @@ "正在启动设置…" "设置设备" "设备已连接" + "已连接到“%s”" "无法连接" + "无法连接" + "请尝试手动与该设备配对" + "请尝试让设备进入配对模式" + "附近的设备" + "与您的帐号相关联的设备" + "您保存的设备已可供使用" + "附近" + "设备" + "正在连接…" + "电量:%d%%" "完成" "保存" "连接" diff --git a/nearby/halfsheet/res/values-zh-rHK/strings.xml b/nearby/halfsheet/res/values-zh-rHK/strings.xml index 3ca73e6aa8..d934f883b6 100644 --- a/nearby/halfsheet/res/values-zh-rHK/strings.xml +++ b/nearby/halfsheet/res/values-zh-rHK/strings.xml @@ -20,7 +20,18 @@ "開始設定…" "設定裝置" "已連接裝置" + "已連線至「%s」" "無法連接" + "無法連線" + "嘗試手動配對裝置" + "嘗試讓裝置進入配對模式" + "附近的裝置" + "已連結您帳戶的裝置" + "您儲存的裝置已可使用" + "咫尺共享" + "裝置" + "正在連接…" + "電量:%d%%" "完成" "儲存" "連接" diff --git a/nearby/halfsheet/res/values-zh-rTW/strings.xml b/nearby/halfsheet/res/values-zh-rTW/strings.xml index b4e680d866..0c90ebb8aa 100644 --- a/nearby/halfsheet/res/values-zh-rTW/strings.xml +++ b/nearby/halfsheet/res/values-zh-rTW/strings.xml @@ -20,7 +20,18 @@ "正在啟動設定程序…" "設定裝置" "裝置已連線" + "已連線到「%s」" "無法連線" + "無法連線" + "嘗試手動配對裝置" + "嘗試讓裝置進入配對模式" + "鄰近裝置" + "與你帳戶連結的裝置" + "你儲存的裝置已可使用" + "鄰近分享" + "裝置" + "連線中…" + "電池電量:%d%%" "完成" "儲存" "連線" diff --git a/nearby/halfsheet/res/values-zu/strings.xml b/nearby/halfsheet/res/values-zu/strings.xml index 33fb405837..3f26469d78 100644 --- a/nearby/halfsheet/res/values-zu/strings.xml +++ b/nearby/halfsheet/res/values-zu/strings.xml @@ -20,7 +20,18 @@ "Iqalisa Ukusetha…" "Setha idivayisi" "Idivayisi ixhunyiwe" + "Ixhunywe ku-\"%s\"" "Ayikwazanga ukuxhuma" + "Ayikwazanga ukuxhuma" + "Zama ukubhangqa kule divayisi" + "Zama ukubeka le divayisi kumodi yokubhangqa" + "Amadivayisi afinyelelekayo" + "Amadivayisi ane-akhawunti yakho" + "Idivayisi yakho elondoloziwe ikhona" + "Eduze" + "Amadivayisi" + "Iyaxhuma…" + "Ibhethri: %d%%" "Kwenziwe" "Londoloza" "Xhuma" diff --git a/nearby/halfsheet/res/values/strings.xml b/nearby/halfsheet/res/values/strings.xml index 01a82e4c35..c1f53d439c 100644 --- a/nearby/halfsheet/res/values/strings.xml +++ b/nearby/halfsheet/res/values/strings.xml @@ -35,8 +35,22 @@ [CHAR LIMIT=30] --> Device connected + Connected to \u201c%s\u201d Couldn\'t connect + Unable to connect + Try manually pairing to the device + Try putting the device into pairing mode + + + + Devices within reach + Devices with your account + Your saved device is available + + Nearby + + Devices + + Connecting… + + Battery: %d%% "); + builder.append(byteString(entry.getValue())); + } + return builder.toString(); + } + + private static String byteString(SparseArray bytesArray) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < bytesArray.size(); i++) { + builder.append(builder.toString().isEmpty() ? " " : "\n "); + builder.append(byteString(bytesArray.valueAt(i))); + } + return builder.toString(); + } + + private static BleFilter createTestFilter() { + BleFilter.Builder builder = new BleFilter.Builder(); + builder + .setServiceUuid( + new ParcelUuid(UUID.fromString("0000FEA0-0000-1000-8000-00805F9B34FB")), + new ParcelUuid(UUID.fromString("FFFFFFF-FFFF-FFF-FFFF-FFFFFFFFFFFF"))) + .setDeviceAddress("00:11:22:33:AA:BB") + .setDeviceName("BERT") + .setManufacturerData( + BluetoothAssignedNumbers.GOOGLE, + new byte[] {0x00, 0x10}, + new byte[] {0x00, (byte) 0xFF}) + .setServiceData( + ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB"), + new byte[] {0x51, 0x64}, + new byte[] {0x00, (byte) 0xFF}); + return builder.build(); + } + + // ref to beacon.decode.BeaconFilterBuilder.eddystoneFilter() + private static BleFilter createEddystoneFilter() { + return new BleFilter.Builder().setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID).build(); + } + // ref to beacon.decode.BeaconFilterBuilder.eddystoneUidFilter() + private static BleFilter createEddystoneUidFilter() { + return new BleFilter.Builder() + .setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID) + .setServiceData( + EDDYSTONE_SERVICE_DATA_PARCELUUID, new byte[] {(short) 0x00}, + new byte[] {(byte) 0xf0}) + .build(); + } + + // ref to beacon.decode.BeaconFilterBuilder.eddystoneUrlFilter() + private static BleFilter createEddystoneUrlFilter() { + return new BleFilter.Builder() + .setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID) + .setServiceData( + EDDYSTONE_SERVICE_DATA_PARCELUUID, + new byte[] {(short) 0x10}, new byte[] {(byte) 0xf0}) + .build(); + } + + // ref to beacon.decode.BeaconFilterBuilder.eddystoneEidFilter() + private static BleFilter createEddystoneEidFilter() { + return new BleFilter.Builder() + .setServiceUuid(EDDYSTONE_SERVICE_DATA_PARCELUUID) + .setServiceData( + EDDYSTONE_SERVICE_DATA_PARCELUUID, + new byte[] {(short) 0x30}, new byte[] {(byte) 0xf0}) + .build(); + } + + // ref to beacon.decode.BeaconFilterBuilder.iBeaconWithoutUuidFilter() + private static BleFilter createIBeaconWithoutUuidFilter() { + byte[] data = {(byte) 0x02, (byte) 0x15}; + byte[] mask = {(byte) 0xff, (byte) 0xff}; + + return new BleFilter.Builder().setManufacturerData((short) 0x004C, data, mask).build(); + } + + // ref to beacon.decode.BeaconFilterBuilder.iBeaconWithUuidFilter() + private static BleFilter createIBeaconWithUuidFilter() { + byte[] data = getFilterData(ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB")); + byte[] mask = getFilterMask(ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805F9B34FB")); + + return new BleFilter.Builder().setManufacturerData((short) 0x004C, data, mask).build(); + } + // Ref to beacon.decode.AppleBeaconDecoder.getFilterData private static byte[] getFilterData(ParcelUuid uuid) { byte[] data = new byte[18]; @@ -418,6 +839,20 @@ public class BleFilterTest { return data; } + // Ref to beacon.decode.AppleBeaconDecoder.getFilterMask + private static byte[] getFilterMask(ParcelUuid uuid) { + byte[] mask = new byte[18]; + mask[0] = (byte) 0xff; + mask[1] = (byte) 0xff; + // Check if UUID is needed in data + if (uuid != null) { + for (int i = 0; i < 16; i++) { + mask[i + 2] = (byte) 0xff; + } + } + return mask; + } + // Ref to beacon.decode.AppleBeaconDecoder.uuidToByteArray private static byte[] uuidToByteArray(ParcelUuid uuid) { ByteBuffer bb = ByteBuffer.wrap(new byte[16]); @@ -453,24 +888,4 @@ public class BleFilterTest { return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits()) == (data.getMostSignificantBits() & mask.getMostSignificantBits())); } - - private static String byteString(Map bytesMap) { - StringBuilder builder = new StringBuilder(); - for (Map.Entry entry : bytesMap.entrySet()) { - builder.append(builder.toString().isEmpty() ? " " : "\n "); - builder.append(entry.getKey().toString()); - builder.append(" --> "); - builder.append(byteString(entry.getValue())); - } - return builder.toString(); - } - - private static String byteString(SparseArray bytesArray) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < bytesArray.size(); i++) { - builder.append(builder.toString().isEmpty() ? " " : "\n "); - builder.append(byteString(bytesArray.valueAt(i))); - } - return builder.toString(); - } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java index 5da98e25b8..3f9a2599d6 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleRecordTest.java @@ -34,6 +34,9 @@ package com.android.server.nearby.common.ble; import static com.google.common.truth.Truth.assertThat; +import android.os.ParcelUuid; +import android.util.SparseArray; + import androidx.test.filters.SdkSuppress; import org.junit.Test; @@ -238,7 +241,6 @@ public class BleRecordTest { BleRecord record = BleRecord.parseFromBytes(BEACON); BleRecord record2 = BleRecord.parseFromBytes(SAME_BEACON); - assertThat(record).isEqualTo(record2); // Different items. @@ -246,5 +248,48 @@ public class BleRecordTest { assertThat(record).isNotEqualTo(record2); assertThat(record.hashCode()).isNotEqualTo(record2.hashCode()); } + + @Test + public void testFields() { + BleRecord record = BleRecord.parseFromBytes(BEACON); + assertThat(byteString(record.getManufacturerSpecificData())) + .isEqualTo(" 0215F7826DA64FA24E988024BC5B71E0893E44D02522B3"); + assertThat( + byteString(record.getServiceData( + ParcelUuid.fromString("000000E0-0000-1000-8000-00805F9B34FB")))) + .isEqualTo("[null]"); + assertThat(record.getTxPowerLevel()).isEqualTo(-12); + assertThat(record.toString()).isEqualTo( + "BleRecord [advertiseFlags=6, serviceUuids=[], " + + "manufacturerSpecificData={76=[2, 21, -9, -126, 109, -90, 79, -94, 78," + + " -104, -128, 36, -68, 91, 113, -32, -119, 62, 68, -48, 37, 34, -77]}," + + " serviceData={0000d00d-0000-1000-8000-00805f9b34fb" + + "=[116, 109, 77, 107, 50, 54, 100]}," + + " txPowerLevel=-12, deviceName=Kontakt]"); + } + + private static String byteString(SparseArray bytesArray) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < bytesArray.size(); i++) { + builder.append(builder.toString().isEmpty() ? " " : "\n "); + builder.append(byteString(bytesArray.valueAt(i))); + } + return builder.toString(); + } + + private static String byteString(byte[] bytes) { + if (bytes == null) { + return "[null]"; + } else { + final char[] hexArray = "0123456789ABCDEF".toCharArray(); + char[] hexChars = new char[bytes.length * 2]; + for (int i = 0; i < bytes.length; i++) { + int v = bytes[i] & 0xFF; + hexChars[i * 2] = hexArray[v >>> 4]; + hexChars[i * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleSightingTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleSightingTest.java new file mode 100644 index 0000000000..3443b53885 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/BleSightingTest.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.ble; + +import static com.google.common.truth.Truth.assertThat; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.os.Parcel; + +import org.junit.Test; + +import java.util.concurrent.TimeUnit; + +/** Test for Bluetooth LE {@link BleSighting}. */ +public class BleSightingTest { + private static final String DEVICE_NAME = "device1"; + private static final String OTHER_DEVICE_NAME = "device2"; + private static final long TIME_EPOCH_MILLIS = 123456; + private static final long OTHER_TIME_EPOCH_MILLIS = 456789; + private static final int RSSI = 1; + private static final int OTHER_RSSI = 2; + + private final BluetoothDevice mBluetoothDevice1 = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:11:22:33:44:55"); + private final BluetoothDevice mBluetoothDevice2 = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice("AA:BB:CC:DD:EE:FF"); + + + @Test + public void testEquals() { + BleSighting sighting = + buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI); + BleSighting sighting2 = + buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI); + assertThat(sighting.equals(sighting2)).isTrue(); + assertThat(sighting2.equals(sighting)).isTrue(); + assertThat(sighting.hashCode()).isEqualTo(sighting2.hashCode()); + + // Transitive property. + BleSighting sighting3 = + buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI); + assertThat(sighting2.equals(sighting3)).isTrue(); + assertThat(sighting.equals(sighting3)).isTrue(); + + // Set different values for each field, one at a time. + sighting2 = buildBleSighting(mBluetoothDevice2, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI); + assertSightingsNotEquals(sighting, sighting2); + + sighting2 = buildBleSighting(mBluetoothDevice1, OTHER_DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI); + assertSightingsNotEquals(sighting, sighting2); + + sighting2 = buildBleSighting(mBluetoothDevice1, DEVICE_NAME, OTHER_TIME_EPOCH_MILLIS, RSSI); + assertSightingsNotEquals(sighting, sighting2); + + sighting2 = buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, OTHER_RSSI); + assertSightingsNotEquals(sighting, sighting2); + } + + @Test + public void getNormalizedRSSI_usingNearbyRssiOffset_getCorrectValue() { + BleSighting sighting = + buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI); + + int defaultRssiOffset = 3; + assertThat(sighting.getNormalizedRSSI()).isEqualTo(RSSI + defaultRssiOffset); + } + + @Test + public void testFields() { + BleSighting sighting = + buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI); + assertThat(byteString(sighting.getBleRecordBytes())) + .isEqualTo("080964657669636531"); + assertThat(sighting.getRssi()).isEqualTo(RSSI); + assertThat(sighting.getTimestampMillis()).isEqualTo(TIME_EPOCH_MILLIS); + assertThat(sighting.getTimestampNanos()) + .isEqualTo(TimeUnit.MILLISECONDS.toNanos(TIME_EPOCH_MILLIS)); + assertThat(sighting.toString()).isEqualTo( + "BleSighting{device=" + mBluetoothDevice1 + "," + + " bleRecord=BleRecord [advertiseFlags=-1," + + " serviceUuids=[]," + + " manufacturerSpecificData={}, serviceData={}," + + " txPowerLevel=-2147483648," + + " deviceName=device1]," + + " rssi=1," + + " timestampNanos=123456000000}"); + } + + @Test + public void testParcelable() { + BleSighting sighting = + buildBleSighting(mBluetoothDevice1, DEVICE_NAME, TIME_EPOCH_MILLIS, RSSI); + Parcel dest = Parcel.obtain(); + sighting.writeToParcel(dest, 0); + dest.setDataPosition(0); + assertThat(sighting.getRssi()).isEqualTo(RSSI); + } + + @Test + public void testCreatorNewArray() { + BleSighting[] sightings = + BleSighting.CREATOR.newArray(2); + assertThat(sightings.length).isEqualTo(2); + } + + private static String byteString(byte[] bytes) { + if (bytes == null) { + return "[null]"; + } else { + final char[] hexArray = "0123456789ABCDEF".toCharArray(); + char[] hexChars = new char[bytes.length * 2]; + for (int i = 0; i < bytes.length; i++) { + int v = bytes[i] & 0xFF; + hexChars[i * 2] = hexArray[v >>> 4]; + hexChars[i * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + } + + /** Builds a BleSighting instance which will correctly match filters by device name. */ + private static BleSighting buildBleSighting( + BluetoothDevice bluetoothDevice, String deviceName, long timeEpochMillis, int rssi) { + byte[] nameBytes = deviceName.getBytes(UTF_8); + byte[] bleRecordBytes = new byte[nameBytes.length + 2]; + bleRecordBytes[0] = (byte) (nameBytes.length + 1); + bleRecordBytes[1] = 0x09; // Value of private BleRecord.DATA_TYPE_LOCAL_NAME_COMPLETE; + System.arraycopy(nameBytes, 0, bleRecordBytes, 2, nameBytes.length); + + return new BleSighting(bluetoothDevice, bleRecordBytes, + rssi, TimeUnit.MILLISECONDS.toNanos(timeEpochMillis)); + } + + private static void assertSightingsNotEquals(BleSighting sighting1, BleSighting sighting2) { + assertThat(sighting1.equals(sighting2)).isFalse(); + assertThat(sighting1.hashCode()).isNotEqualTo(sighting2.hashCode()); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/BeaconDecoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/BeaconDecoderTest.java new file mode 100644 index 0000000000..9a9181d6c1 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/BeaconDecoderTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.ble.decode; + +import static com.android.server.nearby.common.ble.BleRecord.parseFromBytes; +import static com.android.server.nearby.common.ble.testing.FastPairTestData.getFastPairRecord; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; + +public class BeaconDecoderTest { + private BeaconDecoder mBeaconDecoder = new FastPairDecoder();; + + @Test + public void testFields() { + assertThat(mBeaconDecoder.getTelemetry(parseFromBytes(getFastPairRecord()))).isNull(); + assertThat(mBeaconDecoder.getUrl(parseFromBytes(getFastPairRecord()))).isNull(); + assertThat(mBeaconDecoder.supportsBeaconIdAndTxPower(parseFromBytes(getFastPairRecord()))) + .isTrue(); + assertThat(mBeaconDecoder.supportsTxPower()).isTrue(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java index 1ad04f8067..6552699db9 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/decode/FastPairDecoderTest.java @@ -17,15 +17,23 @@ package com.android.server.nearby.common.ble.decode; import static com.android.server.nearby.common.ble.BleRecord.parseFromBytes; +import static com.android.server.nearby.common.ble.testing.FastPairTestData.DEVICE_ADDRESS; import static com.android.server.nearby.common.ble.testing.FastPairTestData.FAST_PAIR_MODEL_ID; +import static com.android.server.nearby.common.ble.testing.FastPairTestData.FAST_PAIR_SHARED_ACCOUNT_KEY_RECORD; +import static com.android.server.nearby.common.ble.testing.FastPairTestData.RSSI; import static com.android.server.nearby.common.ble.testing.FastPairTestData.getFastPairRecord; import static com.android.server.nearby.common.ble.testing.FastPairTestData.newFastPairRecord; import static com.google.common.truth.Truth.assertThat; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; + import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.server.nearby.common.ble.BleRecord; +import com.android.server.nearby.common.ble.BleSighting; +import com.android.server.nearby.common.ble.testing.FastPairTestData; import com.android.server.nearby.util.Hex; import com.google.common.primitives.Bytes; @@ -34,12 +42,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.concurrent.TimeUnit; @RunWith(AndroidJUnit4.class) public class FastPairDecoderTest { private static final String LONG_MODEL_ID = "1122334455667788"; - private final FastPairDecoder mDecoder = new FastPairDecoder(); // Bits 3-6 are model ID length bits = 0b1000 = 8 private static final byte LONG_MODEL_ID_HEADER = 0b00010000; private static final String PADDED_LONG_MODEL_ID = "00001111"; @@ -49,74 +58,239 @@ public class FastPairDecoderTest { private static final byte MODEL_ID_HEADER = 0b00000110; private static final String MODEL_ID = "112233"; private static final byte BLOOM_FILTER_HEADER = 0b01100000; + private static final byte BLOOM_FILTER_NO_NOTIFICATION_HEADER = 0b01100010; private static final String BLOOM_FILTER = "112233445566"; + private static final byte LONG_BLOOM_FILTER_HEADER = (byte) 0b10100000; + private static final String LONG_BLOOM_FILTER = "00112233445566778899"; private static final byte BLOOM_FILTER_SALT_HEADER = 0b00010001; private static final String BLOOM_FILTER_SALT = "01"; + private static final byte BATTERY_HEADER = 0b00110011; + private static final byte BATTERY_NO_NOTIFICATION_HEADER = 0b00110100; + private static final String BATTERY = "01048F"; private static final byte RANDOM_RESOLVABLE_DATA_HEADER = 0b01000110; private static final String RANDOM_RESOLVABLE_DATA = "11223344"; - private static final byte BLOOM_FILTER_NO_NOTIFICATION_HEADER = 0b01100010; + private final FastPairDecoder mDecoder = new FastPairDecoder(); + private final BluetoothDevice mBluetoothDevice = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice(DEVICE_ADDRESS); + + @Test + public void filter() { + assertThat(FastPairDecoder.FILTER.matches(bleSighting(getFastPairRecord()))).isTrue(); + assertThat(FastPairDecoder.FILTER.matches(bleSighting(FAST_PAIR_SHARED_ACCOUNT_KEY_RECORD))) + .isTrue(); + + // Any ID is a valid frame. + assertThat(FastPairDecoder.FILTER.matches( + bleSighting(newFastPairRecord(Hex.stringToBytes("000001"))))).isTrue(); + assertThat(FastPairDecoder.FILTER.matches( + bleSighting(newFastPairRecord(Hex.stringToBytes("098FEC"))))).isTrue(); + assertThat(FastPairDecoder.FILTER.matches( + bleSighting(FastPairTestData.newFastPairRecord( + LONG_MODEL_ID_HEADER, Hex.stringToBytes(LONG_MODEL_ID))))).isTrue(); + } @Test public void getModelId() { assertThat(mDecoder.getBeaconIdBytes(parseFromBytes(getFastPairRecord()))) .isEqualTo(FAST_PAIR_MODEL_ID); - FastPairServiceData fastPairServiceData1 = - new FastPairServiceData(LONG_MODEL_ID_HEADER, - LONG_MODEL_ID); - assertThat( - mDecoder.getBeaconIdBytes( - newBleRecord(fastPairServiceData1.createServiceData()))) - .isEqualTo(Hex.stringToBytes(LONG_MODEL_ID)); FastPairServiceData fastPairServiceData = - new FastPairServiceData(PADDED_LONG_MODEL_ID_HEADER, - PADDED_LONG_MODEL_ID); + new FastPairServiceData(LONG_MODEL_ID_HEADER, LONG_MODEL_ID); assertThat( mDecoder.getBeaconIdBytes( newBleRecord(fastPairServiceData.createServiceData()))) + .isEqualTo(Hex.stringToBytes(LONG_MODEL_ID)); + + FastPairServiceData fastPairServiceData1 = + new FastPairServiceData(PADDED_LONG_MODEL_ID_HEADER, PADDED_LONG_MODEL_ID); + assertThat( + mDecoder.getBeaconIdBytes( + newBleRecord(fastPairServiceData1.createServiceData()))) .isEqualTo(Hex.stringToBytes(TRIMMED_LONG_MODEL_ID)); } @Test - public void getBloomFilter() { - FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER, - MODEL_ID); - fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); - fastPairServiceData.mExtraFields.add(BLOOM_FILTER); - assertThat(FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) - .isEqualTo(Hex.stringToBytes(BLOOM_FILTER)); + public void getBeaconIdType() { + assertThat(mDecoder.getBeaconIdType()).isEqualTo(1); } @Test - public void getBloomFilter_smallModelId() { - FastPairServiceData fastPairServiceData = new FastPairServiceData(null, MODEL_ID); - assertThat(FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) + public void getCalibratedBeaconTxPower() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(LONG_MODEL_ID_HEADER, LONG_MODEL_ID); + assertThat( + mDecoder.getCalibratedBeaconTxPower( + newBleRecord(fastPairServiceData.createServiceData()))) .isNull(); } @Test - public void getBloomFilterSalt_modelIdAndMultipleExtraFields() { - FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER, - MODEL_ID); - fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); - fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_SALT_HEADER); - fastPairServiceData.mExtraFields.add(BLOOM_FILTER); - fastPairServiceData.mExtraFields.add(BLOOM_FILTER_SALT); + public void getServiceDataArray() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(LONG_MODEL_ID_HEADER, LONG_MODEL_ID); assertThat( - FastPairDecoder.getBloomFilterSalt(fastPairServiceData.createServiceData())) - .isEqualTo(Hex.stringToBytes(BLOOM_FILTER_SALT)); + mDecoder.getServiceDataArray( + newBleRecord(fastPairServiceData.createServiceData()))) + .isEqualTo(Hex.stringToBytes("101122334455667788")); } @Test - public void getRandomResolvableData_whenContainConnectionState() { - FastPairServiceData fastPairServiceData = new FastPairServiceData(MODEL_ID_HEADER, - MODEL_ID); - fastPairServiceData.mExtraFieldHeaders.add(RANDOM_RESOLVABLE_DATA_HEADER); - fastPairServiceData.mExtraFields.add(RANDOM_RESOLVABLE_DATA); + public void hasBloomFilter() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(LONG_MODEL_ID_HEADER, LONG_MODEL_ID); assertThat( - FastPairDecoder.getRandomResolvableData(fastPairServiceData - .createServiceData())) - .isEqualTo(Hex.stringToBytes(RANDOM_RESOLVABLE_DATA)); + mDecoder.hasBloomFilter( + newBleRecord(fastPairServiceData.createServiceData()))) + .isFalse(); + } + + @Test + public void hasModelId_allCases() { + // One type of the format is just the 3-byte model ID. This format has no header byte (all 3 + // service data bytes are the model ID in little endian). + assertThat(hasModelId("112233", mDecoder)).isTrue(); + + // If the model ID is shorter than 3 bytes, then return null. + assertThat(hasModelId("11", mDecoder)).isFalse(); + + // If the data is longer than 3 bytes, + // byte 0 must be 0bVVVLLLLR (version, ID length, reserved). + FastPairServiceData fastPairServiceData = + new FastPairServiceData((byte) 0b00001000, "11223344"); + assertThat( + FastPairDecoder.hasBeaconIdBytes( + newBleRecord(fastPairServiceData.createServiceData()))).isTrue(); + + FastPairServiceData fastPairServiceData1 = + new FastPairServiceData((byte) 0b00001010, "1122334455"); + assertThat( + FastPairDecoder.hasBeaconIdBytes( + newBleRecord(fastPairServiceData1.createServiceData()))).isTrue(); + + // Length bits correct, but version bits != version 0 (only supported version). + FastPairServiceData fastPairServiceData2 = + new FastPairServiceData((byte) 0b00101000, "11223344"); + assertThat( + FastPairDecoder.hasBeaconIdBytes( + newBleRecord(fastPairServiceData2.createServiceData()))).isFalse(); + + // Version bits correct, but length bits incorrect (too big, too small). + FastPairServiceData fastPairServiceData3 = + new FastPairServiceData((byte) 0b00001010, "11223344"); + assertThat( + FastPairDecoder.hasBeaconIdBytes( + newBleRecord(fastPairServiceData3.createServiceData()))).isFalse(); + + FastPairServiceData fastPairServiceData4 = + new FastPairServiceData((byte) 0b00000010, "11223344"); + assertThat( + FastPairDecoder.hasBeaconIdBytes( + newBleRecord(fastPairServiceData4.createServiceData()))).isFalse(); + } + + @Test + public void getBatteryLevel() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(BATTERY_HEADER); + fastPairServiceData.mExtraFields.add(BATTERY); + assertThat( + FastPairDecoder.getBatteryLevel(fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BATTERY)); + } + + @Test + public void getBatteryLevel_notIncludedInPacket() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER); + assertThat( + FastPairDecoder.getBatteryLevel(fastPairServiceData.createServiceData())).isNull(); + } + + @Test + public void getBatteryLevel_noModelId() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData((byte) 0b00000000, null); + fastPairServiceData.mExtraFieldHeaders.add(BATTERY_HEADER); + fastPairServiceData.mExtraFields.add(BATTERY); + assertThat( + FastPairDecoder.getBatteryLevel(fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BATTERY)); + } + + @Test + public void getBatteryLevel_multipelExtraField() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(BATTERY_HEADER); + fastPairServiceData.mExtraFields.add(BATTERY); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER); + assertThat( + FastPairDecoder.getBatteryLevel(fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BATTERY)); + } + + @Test + public void getBatteryLevelNoNotification() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(BATTERY_NO_NOTIFICATION_HEADER); + fastPairServiceData.mExtraFields.add(BATTERY); + assertThat( + FastPairDecoder.getBatteryLevelNoNotification( + fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BATTERY)); + } + + @Test + public void getBatteryLevelNoNotification_notIncludedInPacket() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER); + assertThat( + FastPairDecoder.getBatteryLevelNoNotification( + fastPairServiceData.createServiceData())).isNull(); + } + + @Test + public void getBatteryLevelNoNotification_noModelId() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData((byte) 0b00000000, null); + fastPairServiceData.mExtraFieldHeaders.add(BATTERY_NO_NOTIFICATION_HEADER); + fastPairServiceData.mExtraFields.add(BATTERY); + assertThat( + FastPairDecoder.getBatteryLevelNoNotification( + fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BATTERY)); + } + + @Test + public void getBatteryLevelNoNotification_multipleExtraField() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(BATTERY_NO_NOTIFICATION_HEADER); + fastPairServiceData.mExtraFields.add(BATTERY); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER); + assertThat( + FastPairDecoder.getBatteryLevelNoNotification( + fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BATTERY)); + } + + @Test + public void getBloomFilter() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER); + assertThat( + FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BLOOM_FILTER)); } @Test @@ -125,14 +299,222 @@ public class FastPairDecoderTest { new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_NO_NOTIFICATION_HEADER); fastPairServiceData.mExtraFields.add(BLOOM_FILTER); - assertThat(FastPairDecoder.getBloomFilterNoNotification(fastPairServiceData - .createServiceData())).isEqualTo(Hex.stringToBytes(BLOOM_FILTER)); + assertThat( + FastPairDecoder.getBloomFilterNoNotification( + fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BLOOM_FILTER)); + } + + @Test + public void getBloomFilter_smallModelId() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(null, MODEL_ID); + assertThat( + FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) + .isNull(); + } + + @Test + public void getBloomFilter_headerVersionBitsNotZero() { + // Header bits are defined as 0bVVVLLLLR (V=version, L=length, R=reserved), must be zero. + FastPairServiceData fastPairServiceData = + new FastPairServiceData((byte) 0b00100000, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER); + assertThat( + FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) + .isNull(); + } + + @Test + public void getBloomFilter_noExtraFieldBytesIncluded() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(null); + fastPairServiceData.mExtraFields.add(null); + assertThat( + FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) + .isNull(); + } + + @Test + public void getBloomFilter_extraFieldLengthIsZero() { + // The extra field header is formatted as 0bLLLLTTTT (L=length, T=type). + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00000000); + fastPairServiceData.mExtraFields.add(null); + assertThat( + FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) + .hasLength(0); + } + + @Test + public void getBloomFilter_extraFieldLengthLongerThanPacket() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add((byte) 0b11110000); + fastPairServiceData.mExtraFields.add("1122"); + assertThat( + FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) + .isNull(); + } + + @Test + public void getBloomFilter_secondExtraFieldLengthLongerThanPacket() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00100000); + fastPairServiceData.mExtraFields.add("1122"); + fastPairServiceData.mExtraFieldHeaders.add((byte) 0b11110001); + fastPairServiceData.mExtraFields.add("3344"); + assertThat( + FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())).isNull(); + } + + @Test + public void getBloomFilter_typeIsWrong() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add((byte) 0b01100001); + fastPairServiceData.mExtraFields.add("112233445566"); + assertThat( + FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) + .isNull(); + } + + @Test + public void getBloomFilter_noModelId() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData((byte) 0b00000000, null); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER); + assertThat( + FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BLOOM_FILTER)); + } + + @Test + public void getBloomFilter_noModelIdAndMultipleExtraFields() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData((byte) 0b00000000, null); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER); + fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00010001); + fastPairServiceData.mExtraFields.add("00"); + assertThat( + FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BLOOM_FILTER)); + } + + @Test + public void getBloomFilter_modelIdAndMultipleExtraFields() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_SALT_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER_SALT); + assertThat( + FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BLOOM_FILTER)); + } + + @Test + public void getBloomFilterSalt_modelIdAndMultipleExtraFields() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_SALT_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER_SALT); + assertThat( + FastPairDecoder.getBloomFilterSalt(fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BLOOM_FILTER_SALT)); + } + + @Test + public void getBloomFilter_modelIdAndMultipleExtraFieldsWithBloomFilterLast() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00010001); + fastPairServiceData.mExtraFields.add("1A"); + fastPairServiceData.mExtraFieldHeaders.add((byte) 0b00100010); + fastPairServiceData.mExtraFields.add("2CFE"); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER); + assertThat( + FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BLOOM_FILTER)); + } + + @Test + public void getBloomFilter_modelIdAndMultipleExtraFieldsWithSameType() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add(BLOOM_FILTER); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add("000000000000"); + assertThat( + FastPairDecoder.getBloomFilter(fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(BLOOM_FILTER)); + } + + @Test + public void getBloomFilter_longExtraField() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(LONG_BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add(LONG_BLOOM_FILTER); + fastPairServiceData.mExtraFieldHeaders.add(BLOOM_FILTER_HEADER); + fastPairServiceData.mExtraFields.add("000000000000"); + assertThat( + FastPairDecoder.getBloomFilter( + fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(LONG_BLOOM_FILTER)); + } + + @Test + public void getRandomResolvableData_whenNoConnectionState() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + assertThat( + FastPairDecoder.getRandomResolvableData( + fastPairServiceData.createServiceData())) + .isEqualTo(null); + } + + @Test + public void getRandomResolvableData_whenContainConnectionState() { + FastPairServiceData fastPairServiceData = + new FastPairServiceData(MODEL_ID_HEADER, MODEL_ID); + fastPairServiceData.mExtraFieldHeaders.add(RANDOM_RESOLVABLE_DATA_HEADER); + fastPairServiceData.mExtraFields.add(RANDOM_RESOLVABLE_DATA); + assertThat( + FastPairDecoder.getRandomResolvableData( + fastPairServiceData.createServiceData())) + .isEqualTo(Hex.stringToBytes(RANDOM_RESOLVABLE_DATA)); } private static BleRecord newBleRecord(byte[] serviceDataBytes) { return parseFromBytes(newFastPairRecord(serviceDataBytes)); } - class FastPairServiceData { + + private static boolean hasModelId(String modelId, FastPairDecoder decoder) { + byte[] modelIdBytes = Hex.stringToBytes(modelId); + BleRecord bleRecord = + parseFromBytes(FastPairTestData.newFastPairRecord((byte) 0, modelIdBytes)); + return FastPairDecoder.hasBeaconIdBytes(bleRecord) + && Arrays.equals(decoder.getBeaconIdBytes(bleRecord), modelIdBytes); + } + + private BleSighting bleSighting(byte[] frame) { + return new BleSighting(mBluetoothDevice, frame, RSSI, + TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis())); + } + + static class FastPairServiceData { private Byte mHeader; private String mModelId; List mExtraFieldHeaders = new ArrayList<>(); @@ -164,6 +546,4 @@ public class FastPairDecoderTest { return serviceData; } } - - } diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java index ebe72b39c1..29f8d4e6c2 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/RangingUtilsTest.java @@ -48,8 +48,10 @@ public class RangingUtilsTest { // RSSI expected at 1 meter based on the calibrated tx field of -41dBm // Using params: distance (m), int calibratedTxPower (dBm), int rssi = RangingUtils.rssiFromTargetDistance(1.0, -41); + int rssi1 = RangingUtils.rssiFromTargetDistance(0.0, -41); assertThat(rssi).isEqualTo(-82); + assertThat(rssi1).isEqualTo(-41); } @Test diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/StringUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/StringUtilsTest.java new file mode 100644 index 0000000000..6b20fdd161 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/ble/util/StringUtilsTest.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.ble.util; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.ParcelUuid; +import android.util.SparseArray; + +import com.android.server.nearby.common.ble.BleRecord; + +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class StringUtilsTest { + // iBeacon (Apple) Packet 1 + private static final byte[] BEACON = { + // Flags + (byte) 0x02, + (byte) 0x01, + (byte) 0x06, + // Manufacturer-specific data header + (byte) 0x1a, + (byte) 0xff, + (byte) 0x4c, + (byte) 0x00, + // iBeacon Type + (byte) 0x02, + // Frame length + (byte) 0x15, + // iBeacon Proximity UUID + (byte) 0xf7, + (byte) 0x82, + (byte) 0x6d, + (byte) 0xa6, + (byte) 0x4f, + (byte) 0xa2, + (byte) 0x4e, + (byte) 0x98, + (byte) 0x80, + (byte) 0x24, + (byte) 0xbc, + (byte) 0x5b, + (byte) 0x71, + (byte) 0xe0, + (byte) 0x89, + (byte) 0x3e, + // iBeacon Instance ID (Major/Minor) + (byte) 0x44, + (byte) 0xd0, + (byte) 0x25, + (byte) 0x22, + // Tx Power + (byte) 0xb3, + // RSP + (byte) 0x08, + (byte) 0x09, + (byte) 0x4b, + (byte) 0x6f, + (byte) 0x6e, + (byte) 0x74, + (byte) 0x61, + (byte) 0x6b, + (byte) 0x74, + (byte) 0x02, + (byte) 0x0a, + (byte) 0xf4, + (byte) 0x0a, + (byte) 0x16, + (byte) 0x0d, + (byte) 0xd0, + (byte) 0x74, + (byte) 0x6d, + (byte) 0x4d, + (byte) 0x6b, + (byte) 0x32, + (byte) 0x36, + (byte) 0x64, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00, + (byte) 0x00 + }; + + @Test + public void testToString() { + BleRecord record = BleRecord.parseFromBytes(BEACON); + assertThat(StringUtils.toString((SparseArray) null)).isEqualTo("null"); + assertThat(StringUtils.toString(new SparseArray())).isEqualTo("{}"); + assertThat(StringUtils + .toString((Map) null)).isEqualTo("null"); + assertThat(StringUtils + .toString(new HashMap())).isEqualTo("{}"); + assertThat(StringUtils.toString(record.getManufacturerSpecificData())) + .isEqualTo("{76=[2, 21, -9, -126, 109, -90, 79, -94, 78, -104, -128," + + " 36, -68, 91, 113, -32, -119, 62, 68, -48, 37, 34, -77]}"); + assertThat(StringUtils.toString(record.getServiceData())) + .isEqualTo("{0000d00d-0000-1000-8000-00805f9b34fb=" + + "[116, 109, 77, 107, 50, 54, 100]}"); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/BloomFilterTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/BloomFilterTest.java new file mode 100644 index 0000000000..30df81f27c --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/BloomFilterTest.java @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bloomfilter; + +import static com.google.common.io.BaseEncoding.base16; +import static com.google.common.primitives.Bytes.concat; +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * Unit-tests for the {@link BloomFilter} class. + */ +public class BloomFilterTest { + private static final int BYTE_ARRAY_LENGTH = 100; + + private final BloomFilter mBloomFilter = + new BloomFilter(new byte[BYTE_ARRAY_LENGTH], newHasher()); + + public BloomFilter.Hasher newHasher() { + return new FastPairBloomFilterHasher(); + } + + @Test + public void emptyFilter_returnsEmptyArray() throws Exception { + assertThat(mBloomFilter.asBytes()).isEqualTo(new byte[BYTE_ARRAY_LENGTH]); + } + + @Test + public void emptyFilter_neverContains() throws Exception { + assertThat(mBloomFilter.possiblyContains(element(1))).isFalse(); + assertThat(mBloomFilter.possiblyContains(element(2))).isFalse(); + assertThat(mBloomFilter.possiblyContains(element(3))).isFalse(); + } + + @Test + public void add() throws Exception { + assertThat(mBloomFilter.possiblyContains(element(1))).isFalse(); + mBloomFilter.add(element(1)); + assertThat(mBloomFilter.possiblyContains(element(1))).isTrue(); + } + + @Test + public void add_onlyGivenArgAdded() throws Exception { + mBloomFilter.add(element(1)); + assertThat(mBloomFilter.possiblyContains(element(1))).isTrue(); + assertThat(mBloomFilter.possiblyContains(element(2))).isFalse(); + assertThat(mBloomFilter.possiblyContains(element(3))).isFalse(); + } + + @Test + public void add_multipleArgs() throws Exception { + mBloomFilter.add(element(1)); + mBloomFilter.add(element(2)); + assertThat(mBloomFilter.possiblyContains(element(1))).isTrue(); + assertThat(mBloomFilter.possiblyContains(element(2))).isTrue(); + assertThat(mBloomFilter.possiblyContains(element(3))).isFalse(); + } + + /** + * This test was added because of a bug where the BloomFilter doesn't utilize all bits given. + * Functionally, the filter still works, but we just have a much higher false positive rate. The + * bug was caused by confusing bit length and byte length, which made our BloomFilter only set + * bits on the first byteLength (bitLength / 8) bits rather than the whole bitLength bits. + * + *

Here, we're verifying that the bits set are somewhat scattered. So instead of something + * like [ 0, 1, 1, 0, 0, 0, 0, ..., 0 ], we should be getting something like + * [ 0, 1, 0, 0, 1, 1, 0, 0,0, 1, ..., 1, 0]. + */ + @Test + public void randomness_noEndBias() throws Exception { + // Add one element to our BloomFilter. + mBloomFilter.add(element(1)); + + // Record the amount of non-zero bytes and the longest streak of zero bytes in the resulting + // BloomFilter. This is an approximation of reasonable distribution since we're recording by + // bytes instead of bits. + int nonZeroCount = 0; + int longestZeroStreak = 0; + int currentZeroStreak = 0; + for (byte b : mBloomFilter.asBytes()) { + if (b == 0) { + currentZeroStreak++; + } else { + // Increment the number of non-zero bytes we've seen, update the longest zero + // streak, and then reset the current zero streak. + nonZeroCount++; + longestZeroStreak = Math.max(longestZeroStreak, currentZeroStreak); + currentZeroStreak = 0; + } + } + // Update the longest zero streak again for the tail case. + longestZeroStreak = Math.max(longestZeroStreak, currentZeroStreak); + + // Since randomness is hard to measure within one unit test, we instead do a valid check. + // All non-zero bytes should not be packed into one end of the array. + // + // In this case, the size of one end is approximated to be: + // BYTE_ARRAY_LENGTH / nonZeroCount. + // Therefore, the longest zero streak should be less than: + // BYTE_ARRAY_LENGTH - one end of the array. + int longestAcceptableZeroStreak = BYTE_ARRAY_LENGTH - (BYTE_ARRAY_LENGTH / nonZeroCount); + assertThat(longestZeroStreak).isAtMost(longestAcceptableZeroStreak); + } + + @Test + public void randomness_falsePositiveRate() throws Exception { + // Create a new BloomFilter with a length of only 10 bytes. + BloomFilter bloomFilter = new BloomFilter(new byte[10], newHasher()); + + // Add 5 distinct elements to the BloomFilter. + for (int i = 0; i < 5; i++) { + bloomFilter.add(element(i)); + } + + // Now test 100 other elements and record the number of false positives. + int falsePositives = 0; + for (int i = 5; i < 105; i++) { + falsePositives += bloomFilter.possiblyContains(element(i)) ? 1 : 0; + } + + // We expect the false positive rate to be 3% with 5 elements in a 10 byte filter. Thus, + // we give a little leeway and verify that the false positive rate is no more than 5%. + assertWithMessage( + String.format( + "False positive rate too large. Expected <= 5%%, but got %d%%.", + falsePositives)) + .that(falsePositives <= 5) + .isTrue(); + System.out.printf("False positive rate: %d%%%n", falsePositives); + } + + + private String element(int index) { + return "ELEMENT_" + index; + } + + @Test + public void specificBitPattern() throws Exception { + // Create a new BloomFilter along with a fixed set of elements + // and bit patterns to verify with. + BloomFilter bloomFilter = new BloomFilter(new byte[6], newHasher()); + // Combine an account key and mac address. + byte[] element = + concat( + base16().decode("11223344556677889900AABBCCDDEEFF"), + base16().withSeparator(":", 2).decode("84:68:3E:00:02:11")); + byte[] expectedBitPattern = new byte[] {0x50, 0x00, 0x04, 0x15, 0x08, 0x01}; + + // Add the fixed elements to the filter. + bloomFilter.add(element); + + // Verify that the resulting bytes match the expected one. + byte[] bloomFilterBytes = bloomFilter.asBytes(); + assertWithMessage( + "Unexpected bit pattern. Expected %s, but got %s.", + base16().encode(expectedBitPattern), base16().encode(bloomFilterBytes)) + .that(Arrays.equals(expectedBitPattern, bloomFilterBytes)) + .isTrue(); + + // Verify that the expected bit pattern creates a BloomFilter containing all fixed elements. + bloomFilter = new BloomFilter(expectedBitPattern, newHasher()); + assertThat(bloomFilter.possiblyContains(element)).isTrue(); + } + + // This test case has been on the spec, + // https://devsite.googleplex.com/nearby/fast-pair/spec#test_cases. + // Explicitly adds it here, and we can easily change the parameters (e.g. account key, ble + // address) to clarify test results with partners. + @Test + public void specificBitPattern_hasOneAccountKey() { + BloomFilter bloomFilter1 = new BloomFilter(new byte[4], newHasher()); + BloomFilter bloomFilter2 = new BloomFilter(new byte[4], newHasher()); + byte[] accountKey = base16().decode("11223344556677889900AABBCCDDEEFF"); + byte[] salt1 = base16().decode("C7"); + byte[] salt2 = base16().decode("C7C8"); + + // Add the fixed elements to the filter. + bloomFilter1.add(concat(accountKey, salt1)); + bloomFilter2.add(concat(accountKey, salt2)); + + assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("0A428810")); + assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("020C802A")); + } + + // Adds this test case to spec. We can easily change the parameters (e.g. account key, ble + // address, battery data) to clarify test results with partners. + @Test + public void specificBitPattern_hasOneAccountKey_withBatteryData() { + BloomFilter bloomFilter1 = new BloomFilter(new byte[4], newHasher()); + BloomFilter bloomFilter2 = new BloomFilter(new byte[4], newHasher()); + byte[] accountKey = base16().decode("11223344556677889900AABBCCDDEEFF"); + byte[] salt1 = base16().decode("C7"); + byte[] salt2 = base16().decode("C7C8"); + byte[] batteryData = { + 0b00110011, // length = 3, show UI indication. + 0b01000000, // Left bud: not charging, battery level = 64. + 0b01000000, // Right bud: not charging, battery level = 64. + 0b01000000 // Case: not charging, battery level = 64. + }; + + // Adds battery data to build bloom filter. + bloomFilter1.add(concat(accountKey, salt1, batteryData)); + bloomFilter2.add(concat(accountKey, salt2, batteryData)); + + assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("4A00F000")); + assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("0101460A")); + } + + // This test case has been on the spec, + // https://devsite.googleplex.com/nearby/fast-pair/spec#test_cases. + // Explicitly adds it here, and we can easily change the parameters (e.g. account key, ble + // address) to clarify test results with partners. + @Test + public void specificBitPattern_hasTwoAccountKeys() { + BloomFilter bloomFilter1 = new BloomFilter(new byte[5], newHasher()); + BloomFilter bloomFilter2 = new BloomFilter(new byte[5], newHasher()); + byte[] accountKey1 = base16().decode("11223344556677889900AABBCCDDEEFF"); + byte[] accountKey2 = base16().decode("11112222333344445555666677778888"); + byte[] salt1 = base16().decode("C7"); + byte[] salt2 = base16().decode("C7C8"); + + // Adds the fixed elements to the filter. + bloomFilter1.add(concat(accountKey1, salt1)); + bloomFilter1.add(concat(accountKey2, salt1)); + bloomFilter2.add(concat(accountKey1, salt2)); + bloomFilter2.add(concat(accountKey2, salt2)); + + assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("2FBA064200")); + assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("844A62208B")); + } + + // Adds this test case to spec. We can easily change the parameters (e.g. account keys, ble + // address, battery data) to clarify test results with partners. + @Test + public void specificBitPattern_hasTwoAccountKeys_withBatteryData() { + BloomFilter bloomFilter1 = new BloomFilter(new byte[5], newHasher()); + BloomFilter bloomFilter2 = new BloomFilter(new byte[5], newHasher()); + byte[] accountKey1 = base16().decode("11223344556677889900AABBCCDDEEFF"); + byte[] accountKey2 = base16().decode("11112222333344445555666677778888"); + byte[] salt1 = base16().decode("C7"); + byte[] salt2 = base16().decode("C7C8"); + byte[] batteryData = { + 0b00110011, // length = 3, show UI indication. + 0b01000000, // Left bud: not charging, battery level = 64. + 0b01000000, // Right bud: not charging, battery level = 64. + 0b01000000 // Case: not charging, battery level = 64. + }; + + // Adds battery data to build bloom filter. + bloomFilter1.add(concat(accountKey1, salt1, batteryData)); + bloomFilter1.add(concat(accountKey2, salt1, batteryData)); + bloomFilter2.add(concat(accountKey1, salt2, batteryData)); + bloomFilter2.add(concat(accountKey2, salt2, batteryData)); + + assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("102256C04D")); + assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("461524D008")); + } + + // Adds this test case to spec. We can easily change the parameters (e.g. account keys, ble + // address, battery data and battery remaining time) to clarify test results with partners. + @Test + public void specificBitPattern_hasTwoAccountKeys_withBatteryLevelAndRemainingTime() { + BloomFilter bloomFilter1 = new BloomFilter(new byte[5], newHasher()); + BloomFilter bloomFilter2 = new BloomFilter(new byte[5], newHasher()); + byte[] accountKey1 = base16().decode("11223344556677889900AABBCCDDEEFF"); + byte[] accountKey2 = base16().decode("11112222333344445555666677778888"); + byte[] salt1 = base16().decode("C7"); + byte[] salt2 = base16().decode("C7C8"); + byte[] batteryData = { + 0b00110011, // length = 3, show UI indication. + 0b01000000, // Left bud: not charging, battery level = 64. + 0b01000000, // Right bud: not charging, battery level = 64. + 0b01000000 // Case: not charging, battery level = 64. + }; + byte[] batteryRemainingTime = { + 0b00010101, // length = 1, type = 0b0101 (remaining battery time). + 0x1E, // remaining battery time (in minutes) = 30 minutes. + }; + + // Adds battery data to build bloom filter. + bloomFilter1.add(concat(accountKey1, salt1, batteryData, batteryRemainingTime)); + bloomFilter1.add(concat(accountKey2, salt1, batteryData, batteryRemainingTime)); + bloomFilter2.add(concat(accountKey1, salt2, batteryData, batteryRemainingTime)); + bloomFilter2.add(concat(accountKey2, salt2, batteryData, batteryRemainingTime)); + + assertThat(bloomFilter1.asBytes()).isEqualTo(base16().decode("32A086B41A")); + assertThat(bloomFilter2.asBytes()).isEqualTo(base16().decode("C2A042043E")); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasherTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasherTest.java new file mode 100644 index 0000000000..92c3b1a727 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bloomfilter/FastPairBloomFilterHasherTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bloomfilter; + +import static com.google.common.truth.Truth.assertThat; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import org.junit.Test; + +import java.nio.charset.Charset; + +public class FastPairBloomFilterHasherTest { + private static final Charset CHARSET = UTF_8; + private static FastPairBloomFilterHasher sFastPairBloomFilterHasher = + new FastPairBloomFilterHasher(); + @Test + public void getHashes() { + int[] hashe1 = sFastPairBloomFilterHasher.getHashes(element(1).getBytes(CHARSET)); + int[] hashe2 = sFastPairBloomFilterHasher.getHashes(element(1).getBytes(CHARSET)); + int[] hashe3 = sFastPairBloomFilterHasher.getHashes(element(2).getBytes(CHARSET)); + assertThat(hashe1).isEqualTo(hashe2); + assertThat(hashe1).isNotEqualTo(hashe3); + } + + private String element(int index) { + return "ELEMENT_" + index; + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java index 28d2fca58e..cf40fc6f6b 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/AdditionalDataEncoderTest.java @@ -61,6 +61,21 @@ public class AdditionalDataEncoderTest { assertThat(additionalData).isEqualTo(rawData); } + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void inputNullSecretKeyToEncode_mustThrowException() { + byte[] rawData = base16().decode("00112233445566778899AABBCCDDEEFF"); + + GeneralSecurityException exception = + assertThrows( + GeneralSecurityException.class, + () -> AdditionalDataEncoder.encodeAdditionalDataPacket(null, rawData)); + + assertThat(exception) + .hasMessageThat() + .contains("Incorrect secret for encoding additional data packet"); + } + @Test @SdkSuppress(minSdkVersion = 32, codeName = "T") public void inputIncorrectKeySizeToEncode_mustThrowException() { @@ -77,6 +92,54 @@ public class AdditionalDataEncoderTest { .contains("Incorrect secret for encoding additional data packet"); } + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void inputNullAdditionalDataToEncode_mustThrowException() + throws GeneralSecurityException { + byte[] secret = AesCtrMultipleBlockEncryption.generateKey(); + + GeneralSecurityException exception = + assertThrows( + GeneralSecurityException.class, + () -> AdditionalDataEncoder.encodeAdditionalDataPacket(secret, null)); + + assertThat(exception) + .hasMessageThat() + .contains("Invalid data for encoding additional data packet"); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void inputInvalidAdditionalDataSizeToEncode_mustThrowException() + throws GeneralSecurityException { + byte[] secret = AesCtrMultipleBlockEncryption.generateKey(); + byte[] rawData = base16().decode(""); + GeneralSecurityException exception = + assertThrows( + GeneralSecurityException.class, + () -> AdditionalDataEncoder.encodeAdditionalDataPacket(secret, rawData)); + + assertThat(exception) + .hasMessageThat() + .contains("Invalid data for encoding additional data packet"); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void inputTooLargeAdditionalDataSizeToEncode_mustThrowException() + throws GeneralSecurityException { + byte[] secret = AesCtrMultipleBlockEncryption.generateKey(); + byte[] rawData = new byte[MAX_LENGTH_OF_DATA + 1]; + GeneralSecurityException exception = + assertThrows( + GeneralSecurityException.class, + () -> AdditionalDataEncoder.encodeAdditionalDataPacket(secret, rawData)); + + assertThat(exception) + .hasMessageThat() + .contains("Invalid data for encoding additional data packet"); + } + @Test @SdkSuppress(minSdkVersion = 32, codeName = "T") public void inputIncorrectKeySizeToDecode_mustThrowException() { diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java index 0a56f2f94e..6871024913 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothAudioPairerTest.java @@ -83,6 +83,23 @@ public class BluetoothAudioPairerTest extends TestCase { } } + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void getDevice() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + try { + assertThat(new BluetoothAudioPairer( + context, + BLUETOOTH_DEVICE, + Preferences.builder().build(), + new EventLoggerWrapper(new TestEventLogger()), + null /* KeyBasePairingInfo */, + null /*PasskeyConfirmationHandler */, + new TimingLogger(EVENT_NAME, Preferences.builder().build())).getDevice()) + .isEqualTo(BLUETOOTH_DEVICE); + } catch (PairingException e) { + } + } + @SdkSuppress(minSdkVersion = 32, codeName = "T") public void testBluetoothAudioPairerUnpairNoCrash() { Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairerTest.java new file mode 100644 index 0000000000..48f4b9b503 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BluetoothClassicPairerTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.fastpair; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +public class BluetoothClassicPairerTest { + @Mock + PasskeyConfirmationHandler mPasskeyConfirmationHandler; + private static final BluetoothDevice BLUETOOTH_DEVICE = BluetoothAdapter.getDefaultAdapter() + .getRemoteDevice("11:22:33:44:55:66"); + private BluetoothClassicPairer mBluetoothClassicPairer; + + @Before + public void setUp() { + mBluetoothClassicPairer = new BluetoothClassicPairer( + ApplicationProvider.getApplicationContext(), + BLUETOOTH_DEVICE, + Preferences.builder().build(), + mPasskeyConfirmationHandler); + } + + @Test + public void pair() throws PairingException { + PairingException exception = + assertThrows( + PairingException.class, + () -> mBluetoothClassicPairer.pair()); + + assertThat(exception) + .hasMessageThat() + .contains("BluetoothClassicPairer, createBond"); + assertThat(mBluetoothClassicPairer.isPaired()).isFalse(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BytesTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BytesTest.java new file mode 100644 index 0000000000..9450d60d32 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/BytesTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.fastpair; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.filters.SdkSuppress; + +import junit.framework.TestCase; + +import java.nio.ByteOrder; +import java.util.Arrays; + +/** + * Unit tests for {@link Bytes}. + */ +public class BytesTest extends TestCase { + + private static final Bytes.Value VALUE1 = + new Bytes.Value(new byte[]{1, 2}, ByteOrder.BIG_ENDIAN); + private static final Bytes.Value VALUE2 = + new Bytes.Value(new byte[]{1, 2}, ByteOrder.BIG_ENDIAN); + private static final Bytes.Value VALUE3 = + new Bytes.Value(new byte[]{1, 3}, ByteOrder.BIG_ENDIAN); + + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testEquals_asExpected() { + assertThat(VALUE1.equals(VALUE2)).isTrue(); + } + + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testNotEquals_asExpected() { + assertThat(VALUE1.equals(VALUE3)).isFalse(); + } + + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetBytes_asExpected() { + assertThat(Arrays.equals(VALUE1.getBytes(ByteOrder.BIG_ENDIAN), new byte[]{1, 2})).isTrue(); + } + + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testToString() { + assertThat(VALUE1.toString()).isEqualTo("0102"); + } + + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testReverse() { + assertThat(VALUE1.reverse(new byte[]{1, 2})).isEqualTo(new byte[]{2, 1}); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java index f7ffa2498d..6684fbc419 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/ConstantsTest.java @@ -33,6 +33,8 @@ import com.android.server.nearby.common.bluetooth.BluetoothException; import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic; import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection; +import com.google.common.collect.ImmutableList; + import junit.framework.TestCase; import org.mockito.Mock; @@ -44,6 +46,8 @@ import java.util.UUID; */ public class ConstantsTest extends TestCase { + private static final int PASSKEY = 32689; + @Mock private BluetoothGattConnection mMockGattConnection; @@ -78,4 +82,62 @@ public class ConstantsTest extends TestCase { assertThat(KeyBasedPairingCharacteristic.getId(mMockGattConnection)) .isEqualTo(OLD_KEY_BASE_PAIRING_CHARACTERISTICS); } + + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void test_accountKeyCharacteristic_notCrash() throws BluetoothException { + Constants.FastPairService.AccountKeyCharacteristic.getId(mMockGattConnection); + } + + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void test_additionalDataCharacteristic_notCrash() throws BluetoothException { + Constants.FastPairService.AdditionalDataCharacteristic.getId(mMockGattConnection); + } + + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void test_nameCharacteristic_notCrash() throws BluetoothException { + Constants.FastPairService.NameCharacteristic.getId(mMockGattConnection); + } + + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void test_passKeyCharacteristic_encryptDecryptSuccessfully() + throws java.security.GeneralSecurityException { + byte[] secret = AesEcbSingleBlockEncryption.generateKey(); + + Constants.FastPairService.PasskeyCharacteristic.getId(mMockGattConnection); + assertThat( + Constants.FastPairService.PasskeyCharacteristic.decrypt( + Constants.FastPairService.PasskeyCharacteristic.Type.SEEKER, + secret, + Constants.FastPairService.PasskeyCharacteristic.encrypt( + Constants.FastPairService.PasskeyCharacteristic.Type.SEEKER, + secret, + PASSKEY)) + ).isEqualTo(PASSKEY); + } + + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void test_beaconActionsCharacteristic_notCrash() throws BluetoothException { + Constants.FastPairService.BeaconActionsCharacteristic.getId(mMockGattConnection); + for (byte b : ImmutableList.of( + (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType + .READ_BEACON_PARAMETERS, + (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType + .READ_PROVISIONING_STATE, + (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType + .SET_EPHEMERAL_IDENTITY_KEY, + (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType + .CLEAR_EPHEMERAL_IDENTITY_KEY, + (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType + .READ_EPHEMERAL_IDENTITY_KEY, + (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType + .RING, + (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType + .READ_RINGING_STATE, + (byte) Constants.FastPairService.BeaconActionsCharacteristic.BeaconActionType + .UNKNOWN + )) { + assertThat(Constants.FastPairService.BeaconActionsCharacteristic + .valueOf(b)).isEqualTo(b); + } + } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/CreateBondExceptionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/CreateBondExceptionTest.java new file mode 100644 index 0000000000..052e696f04 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/CreateBondExceptionTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.fastpair; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.filters.SdkSuppress; + +import com.android.server.nearby.common.bluetooth.BluetoothException; +import com.android.server.nearby.intdefs.FastPairEventIntDefs; + +import junit.framework.TestCase; + +/** + * Unit tests for {@link CreateBondException}. + */ +public class CreateBondExceptionTest extends TestCase { + + private static final String FORMAT = "FORMAT"; + private static final int REASON = 0; + private static final CreateBondException EXCEPTION = new CreateBondException( + FastPairEventIntDefs.CreateBondErrorCode.INCORRECT_VARIANT, REASON, FORMAT); + + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void test_getter_asExpected() throws BluetoothException { + assertThat(EXCEPTION.getErrorCode()).isEqualTo( + FastPairEventIntDefs.CreateBondErrorCode.INCORRECT_VARIANT); + assertThat(EXCEPTION.getReason()).isSameInstanceAs(REASON); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiverTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiverTest.java new file mode 100644 index 0000000000..94bf111fd3 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/DeviceIntentReceiverTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.fastpair; + +import static org.mockito.MockitoAnnotations.initMocks; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.Intent; + +import androidx.test.filters.SdkSuppress; +import androidx.test.platform.app.InstrumentationRegistry; + +import junit.framework.TestCase; + +import org.mockito.Mock; + +/** + * Unit tests for {@link DeviceIntentReceiver}. + */ +public class DeviceIntentReceiverTest extends TestCase { + @Mock Preferences mPreferences; + @Mock BluetoothDevice mBluetoothDevice; + + private DeviceIntentReceiver mDeviceIntentReceiver; + private Intent mIntent; + + @Override + protected void setUp() throws Exception { + super.setUp(); + initMocks(this); + + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + mDeviceIntentReceiver = DeviceIntentReceiver.oneShotReceiver( + context, mPreferences, mBluetoothDevice); + + mIntent = new Intent().putExtra(BluetoothDevice.EXTRA_DEVICE, mBluetoothDevice); + } + + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void test_onReceive_notCrash() throws Exception { + mDeviceIntentReceiver.onReceive(mIntent); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java index 28e925fb8b..f10f66dd66 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/EventTest.java @@ -58,6 +58,13 @@ public class EventTest { .setBluetoothDevice(BLUETOOTH_DEVICE) .setProfile(PROFILE) .build(); + assertThat(event.hasBluetoothDevice()).isTrue(); + assertThat(event.hasProfile()).isTrue(); + assertThat(event.isFailure()).isTrue(); + assertThat(event.toString()).isEqualTo( + "Event{eventCode=1120, timestamp=1234, profile=1, " + + "bluetoothDevice=" + BLUETOOTH_DEVICE + ", " + + "exception=java.lang.Exception: Test exception}"); Parcel parcel = Parcel.obtain(); event.writeToParcel(parcel, event.describeContents()); @@ -70,5 +77,8 @@ public class EventTest { assertThat(result.getEventCode()).isEqualTo(event.getEventCode()); assertThat(result.getBluetoothDevice()).isEqualTo(event.getBluetoothDevice()); assertThat(result.getProfile()).isEqualTo(event.getProfile()); + assertThat(result.equals(event)).isTrue(); + + assertThat(Event.CREATOR.newArray(10)).isNotEmpty(); } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java index a103a72c78..63c39b2423 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/FastPairDualConnectionTest.java @@ -23,9 +23,11 @@ import static com.android.server.nearby.common.bluetooth.fastpair.FastPairDualCo import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyShort; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; @@ -45,6 +47,7 @@ import com.google.protobuf.ByteString; import junit.framework.TestCase; +import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -75,6 +78,8 @@ public class FastPairDualConnectionTest extends TestCase { private TestEventLogger mEventLogger; @Mock private TimingLogger mTimingLogger; @Mock private BluetoothAudioPairer mBluetoothAudioPairer; + @Mock private android.bluetooth.BluetoothAdapter mBluetoothAdapter; + @Mock FastPairDualConnection mFastPairDualConnection; @Override public void setUp() throws Exception { @@ -287,6 +292,23 @@ public class FastPairDualConnectionTest extends TestCase { } } + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testReflectionException() + throws BluetoothException, ReflectionException, ExecutionException, + InterruptedException, PairingException, TimeoutException { + when(mFastPairDualConnection.pair()) + .thenThrow(new ReflectionException( + new NoSuchMethodException("testReflectionException"))); + ReflectionException exception = + assertThrows( + ReflectionException.class, + () -> mFastPairDualConnection.pair()); + assertThat(exception) + .hasMessageThat() + .contains("testReflectionException"); + } + @SdkSuppress(minSdkVersion = 32, codeName = "T") public void testHistoryItem() { FastPairDualConnection connection = new FastPairDualConnection( @@ -307,6 +329,11 @@ public class FastPairDualConnectionTest extends TestCase { .isFalse(); } + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetLeState() throws ReflectionException { + FastPairDualConnection.getLeState(mBluetoothAdapter); + } + static class TestEventLogger implements EventLogger { private List mLogs = new ArrayList<>(); diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandlerTest.java new file mode 100644 index 0000000000..e53e60f389 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HandshakeHandlerTest.java @@ -0,0 +1,524 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.fastpair; + +import static com.android.server.nearby.common.bluetooth.fastpair.Constants.BLUETOOTH_ADDRESS_LENGTH; +import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.ADDITIONAL_DATA_CHARACTERISTIC; +import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.ActionOverBleFlag.DEVICE_ACTION; +import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.PROVIDER_INITIATES_BONDING; +import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_DEVICE_NAME; +import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_DISCOVERABLE; +import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.KeyBasedPairingRequestFlag.REQUEST_RETROACTIVE_PAIR; +import static com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request.ADDITIONAL_DATA_TYPE_INDEX; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.when; + +import android.platform.test.annotations.Presubmit; + +import androidx.annotation.Nullable; +import androidx.core.util.Consumer; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SdkSuppress; +import androidx.test.filters.SmallTest; + +import com.android.server.nearby.common.bluetooth.BluetoothException; +import com.android.server.nearby.common.bluetooth.BluetoothGattException; +import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.AdditionalDataCharacteristic.AdditionalDataType; +import com.android.server.nearby.common.bluetooth.fastpair.Constants.FastPairService.KeyBasedPairingCharacteristic.Request; +import com.android.server.nearby.common.bluetooth.gatt.BluetoothGattConnection; +import com.android.server.nearby.common.bluetooth.testability.android.bluetooth.BluetoothAdapter; +import com.android.server.nearby.common.bluetooth.util.BluetoothOperationExecutor; +import com.android.server.nearby.intdefs.NearbyEventIntDefs; + +import com.google.common.collect.ImmutableSet; +import com.google.common.io.BaseEncoding; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.security.GeneralSecurityException; +import java.time.Duration; +import java.util.Arrays; + +/** + * Unit tests for {@link HandshakeHandler}. + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class HandshakeHandlerTest { + + public static final byte[] PUBLIC_KEY = + BaseEncoding.base64().decode( + "d2JTfvfdS6u7LmGfMOmco3C7ra3lW1k17AOly0LrBydDZURacfTY" + + "IMmo5K1ejfD9e8b6qHsDTNzselhifi10kQ=="); + private static final String SEEKER_ADDRESS = "A1:A2:A3:A4:A5:A6"; + private static final String PROVIDER_BLE_ADDRESS = "11:22:33:44:55:66"; + /** + * The random-resolvable private address (RPA) is sometimes used when advertising over BLE, to + * hide the static public address (otherwise, the Fast Pair device would b + * identifiable/trackable whenever it's BLE advertising). + */ + private static final String RANDOM_PRIVATE_ADDRESS = "BB:BB:BB:BB:BB:1E"; + private static final byte[] SHARED_SECRET = + BaseEncoding.base16().decode("0123456789ABCDEF0123456789ABCDEF"); + + @Mock EventLoggerWrapper mEventLoggerWrapper; + @Mock BluetoothGattConnection mBluetoothGattConnection; + @Mock BluetoothGattConnection.ChangeObserver mChangeObserver; + @Mock private Consumer mRescueFromError; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void handshakeGattError_noRetryError_failed() throws BluetoothException { + HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest = + new HandshakeHandler.KeyBasedPairingRequest.Builder() + .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS)) + .build(); + BluetoothGattException exception = + new BluetoothGattException("Exception for no retry", 257); + when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception); + GattConnectionManager gattConnectionManager = + createGattConnectionManager(Preferences.builder(), () -> {}); + gattConnectionManager.setGattConnection(mBluetoothGattConnection); + when(mBluetoothGattConnection.enableNotification(any(), any())) + .thenReturn(mChangeObserver); + InOrder inOrder = inOrder(mEventLoggerWrapper); + + assertThrows( + BluetoothGattException.class, + () -> + getHandshakeHandler(gattConnectionManager, address -> address) + .doHandshakeWithRetryAndSignalLostCheck( + PUBLIC_KEY, + keyBasedPairingRequest, + mRescueFromError)); + + inOrder.verify(mEventLoggerWrapper).setCurrentEvent( + NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION); + inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception); + inOrder.verifyNoMoreInteractions(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void handshakeGattError_retryAndNoCount_throwException() throws BluetoothException { + HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest = + new HandshakeHandler.KeyBasedPairingRequest.Builder() + .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS)) + .build(); + BluetoothGattException exception = new BluetoothGattException("Exception for retry", 133); + when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception); + GattConnectionManager gattConnectionManager = + createGattConnectionManager(Preferences.builder(), () -> {}); + gattConnectionManager.setGattConnection(mBluetoothGattConnection); + when(mBluetoothGattConnection.enableNotification(any(), any())) + .thenReturn(mChangeObserver); + InOrder inOrder = inOrder(mEventLoggerWrapper); + + HandshakeHandler.HandshakeException handshakeException = + assertThrows( + HandshakeHandler.HandshakeException.class, + () -> getHandshakeHandler(gattConnectionManager, address -> address) + .doHandshakeWithRetryAndSignalLostCheck( + PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError)); + + inOrder.verify(mEventLoggerWrapper) + .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION); + inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception); + inOrder.verify(mEventLoggerWrapper) + .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION); + inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception); + inOrder.verify(mEventLoggerWrapper) + .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION); + inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception); + inOrder.verify(mEventLoggerWrapper) + .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION); + inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception); + inOrder.verifyNoMoreInteractions(); + assertThat(handshakeException.getOriginalException()).isEqualTo(exception); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void handshakeGattError_noRetryOnTimeout_throwException() throws BluetoothException { + HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest = + new HandshakeHandler.KeyBasedPairingRequest.Builder() + .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS)) + .build(); + BluetoothOperationExecutor.BluetoothOperationTimeoutException exception = + new BluetoothOperationExecutor.BluetoothOperationTimeoutException("Test timeout"); + when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception); + GattConnectionManager gattConnectionManager = + createGattConnectionManager(Preferences.builder(), () -> {}); + gattConnectionManager.setGattConnection(mBluetoothGattConnection); + when(mBluetoothGattConnection.enableNotification(any(), any())) + .thenReturn(mChangeObserver); + InOrder inOrder = inOrder(mEventLoggerWrapper); + + assertThrows( + HandshakeHandler.HandshakeException.class, + () -> + new HandshakeHandler( + gattConnectionManager, + PROVIDER_BLE_ADDRESS, + Preferences.builder().setRetrySecretHandshakeTimeout(false).build(), + mEventLoggerWrapper, + address -> address) + .doHandshakeWithRetryAndSignalLostCheck( + PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError)); + + inOrder.verify(mEventLoggerWrapper) + .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION); + inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception); + inOrder.verifyNoMoreInteractions(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void handshakeGattError_signalLost() throws BluetoothException { + HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest = + new HandshakeHandler.KeyBasedPairingRequest.Builder() + .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS)) + .build(); + BluetoothGattException exception = new BluetoothGattException("Exception for retry", 133); + when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception); + GattConnectionManager gattConnectionManager = + createGattConnectionManager(Preferences.builder(), () -> {}); + gattConnectionManager.setGattConnection(mBluetoothGattConnection); + when(mBluetoothGattConnection.enableNotification(any(), any())) + .thenReturn(mChangeObserver); + InOrder inOrder = inOrder(mEventLoggerWrapper); + + SignalLostException signalLostException = + assertThrows( + SignalLostException.class, + () -> getHandshakeHandler(gattConnectionManager, address -> null) + .doHandshakeWithRetryAndSignalLostCheck( + PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError)); + + inOrder.verify(mEventLoggerWrapper) + .setCurrentEvent(NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION); + inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception); + assertThat(signalLostException).hasCauseThat().isEqualTo(exception); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void handshakeGattError_addressRotate() throws BluetoothException { + HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest = + new HandshakeHandler.KeyBasedPairingRequest.Builder() + .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS)) + .build(); + BluetoothGattException exception = new BluetoothGattException("Exception for retry", 133); + when(mChangeObserver.waitForUpdate(anyLong())).thenThrow(exception); + GattConnectionManager gattConnectionManager = + createGattConnectionManager(Preferences.builder(), () -> {}); + gattConnectionManager.setGattConnection(mBluetoothGattConnection); + when(mBluetoothGattConnection.enableNotification(any(), any())) + .thenReturn(mChangeObserver); + InOrder inOrder = inOrder(mEventLoggerWrapper); + + SignalRotatedException signalRotatedException = + assertThrows( + SignalRotatedException.class, + () -> getHandshakeHandler( + gattConnectionManager, address -> "AA:BB:CC:DD:EE:FF") + .doHandshakeWithRetryAndSignalLostCheck( + PUBLIC_KEY, keyBasedPairingRequest, mRescueFromError)); + + inOrder.verify(mEventLoggerWrapper).setCurrentEvent( + NearbyEventIntDefs.EventCode.SECRET_HANDSHAKE_GATT_COMMUNICATION); + inOrder.verify(mEventLoggerWrapper).logCurrentEventFailed(exception); + assertThat(signalRotatedException.getNewAddress()).isEqualTo("AA:BB:CC:DD:EE:FF"); + assertThat(signalRotatedException).hasCauseThat().isEqualTo(exception); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void constructBytes_setRetroactiveFlag_decodeCorrectly() throws + GeneralSecurityException { + HandshakeHandler.KeyBasedPairingRequest keyBasedPairingRequest = + new HandshakeHandler.KeyBasedPairingRequest.Builder() + .setVerificationData(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS)) + .addFlag(REQUEST_RETROACTIVE_PAIR) + .setSeekerPublicAddress(BluetoothAddress.decode(SEEKER_ADDRESS)) + .build(); + + byte[] encryptedRawMessage = + AesEcbSingleBlockEncryption.encrypt( + SHARED_SECRET, keyBasedPairingRequest.getBytes()); + HandshakeRequest handshakeRequest = + new HandshakeRequest(SHARED_SECRET, encryptedRawMessage); + + assertThat(handshakeRequest.getType()) + .isEqualTo(HandshakeRequest.Type.KEY_BASED_PAIRING_REQUEST); + assertThat(handshakeRequest.requestRetroactivePair()).isTrue(); + assertThat(handshakeRequest.getVerificationData()) + .isEqualTo(BluetoothAddress.decode(PROVIDER_BLE_ADDRESS)); + assertThat(handshakeRequest.getSeekerPublicAddress()) + .isEqualTo(BluetoothAddress.decode(SEEKER_ADDRESS)); + assertThat(handshakeRequest.requestDeviceName()).isFalse(); + assertThat(handshakeRequest.requestDiscoverable()).isFalse(); + assertThat(handshakeRequest.requestProviderInitialBonding()).isFalse(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void getTimeout_notOverShortRetryMaxSpentTime_getShort() { + Preferences preferences = Preferences.builder().build(); + + assertThat(getHandshakeHandler(/* getEnable128BitCustomGattCharacteristicsId= */ true) + .getTimeoutMs( + preferences.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs() + - 1)) + .isEqualTo(preferences.getSecretHandshakeShortTimeoutMs()); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void getTimeout_overShortRetryMaxSpentTime_getLong() { + Preferences preferences = Preferences.builder().build(); + + assertThat(getHandshakeHandler(/* getEnable128BitCustomGattCharacteristicsId= */ true) + .getTimeoutMs( + preferences.getSecretHandshakeShortTimeoutRetryMaxSpentTimeMs() + + 1)) + .isEqualTo(preferences.getSecretHandshakeLongTimeoutMs()); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void getTimeout_retryNotEnabled_getOrigin() { + Preferences preferences = Preferences.builder().build(); + + assertThat( + new HandshakeHandler( + createGattConnectionManager(Preferences.builder(), () -> {}), + PROVIDER_BLE_ADDRESS, + Preferences.builder() + .setRetryGattConnectionAndSecretHandshake(false).build(), + mEventLoggerWrapper, + /* fastPairSignalChecker= */ null) + .getTimeoutMs(0)) + .isEqualTo(Duration.ofSeconds( + preferences.getGattOperationTimeoutSeconds()).toMillis()); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void triggersActionOverBle_notCrash() { + HandshakeHandler.ActionOverBle.Builder actionOverBleBuilder = + new HandshakeHandler.ActionOverBle.Builder() + .addFlag( + Constants.FastPairService.KeyBasedPairingCharacteristic + .ActionOverBleFlag.ADDITIONAL_DATA_CHARACTERISTIC) + .setVerificationData(BluetoothAddress.decode(RANDOM_PRIVATE_ADDRESS)) + .setAdditionalDataType(AdditionalDataType.PERSONALIZED_NAME) + .setEvent(0, 0) + .setEventAdditionalData(new byte[]{1}) + .getThis(); + HandshakeHandler.ActionOverBle actionOverBle = actionOverBleBuilder.build(); + assertThat(actionOverBle.getBytes().length).isEqualTo(16); + assertThat( + Arrays.equals( + Arrays.copyOfRange(actionOverBle.getBytes(), 0, 12), + new byte[]{ + (byte) 16, (byte) -64, (byte) -69, (byte) -69, + (byte) -69, (byte) -69, (byte) -69, (byte) 30, + (byte) 0, (byte) 0, (byte) 1, (byte) 1})) + .isTrue(); + } + + private GattConnectionManager createGattConnectionManager( + Preferences.Builder prefs, ToggleBluetoothTask toggleBluetooth) { + return new GattConnectionManager( + ApplicationProvider.getApplicationContext(), + prefs.build(), + new EventLoggerWrapper(null), + BluetoothAdapter.getDefaultAdapter(), + toggleBluetooth, + PROVIDER_BLE_ADDRESS, + new TimingLogger("GattConnectionManager", prefs.build()), + /* fastPairSignalChecker= */ null, + /* setMtu= */ false); + } + + private HandshakeHandler getHandshakeHandler( + GattConnectionManager gattConnectionManager, + @Nullable FastPairConnection.FastPairSignalChecker fastPairSignalChecker) { + return new HandshakeHandler( + gattConnectionManager, + PROVIDER_BLE_ADDRESS, + Preferences.builder() + .setGattConnectionAndSecretHandshakeNoRetryGattError(ImmutableSet.of(257)) + .setRetrySecretHandshakeTimeout(true) + .build(), + mEventLoggerWrapper, + fastPairSignalChecker); + } + + private HandshakeHandler getHandshakeHandler( + boolean getEnable128BitCustomGattCharacteristicsId) { + return new HandshakeHandler( + createGattConnectionManager(Preferences.builder(), () -> {}), + PROVIDER_BLE_ADDRESS, + Preferences.builder() + .setGattOperationTimeoutSeconds(5) + .setEnable128BitCustomGattCharacteristicsId( + getEnable128BitCustomGattCharacteristicsId) + .build(), + mEventLoggerWrapper, + /* fastPairSignalChecker= */ null); + } + + private static class HandshakeRequest { + + /** + * 16 bytes data: 1-byte for type, 1-byte for flags, 6-bytes for provider's BLE address, 8 + * bytes optional data. + * + * @see {go/fast-pair-spec-handshake-message1} + */ + private final byte[] mDecryptedMessage; + + HandshakeRequest(byte[] key, byte[] encryptedPairingRequest) + throws GeneralSecurityException { + mDecryptedMessage = AesEcbSingleBlockEncryption.decrypt(key, encryptedPairingRequest); + } + + /** + * Gets the type of this handshake request. Currently, we have 2 types: 0x00 for Key-based + * Pairing Request and 0x10 for Action Request. + */ + public Type getType() { + return Type.valueOf(mDecryptedMessage[Request.TYPE_INDEX]); + } + + /** + * Gets verification data of this handshake request. + * Currently, we use Provider's BLE address. + */ + public byte[] getVerificationData() { + return Arrays.copyOfRange( + mDecryptedMessage, + Request.VERIFICATION_DATA_INDEX, + Request.VERIFICATION_DATA_INDEX + Request.VERIFICATION_DATA_LENGTH); + } + + /** Gets Seeker's public address of the handshake request. */ + public byte[] getSeekerPublicAddress() { + return Arrays.copyOfRange( + mDecryptedMessage, + Request.SEEKER_PUBLIC_ADDRESS_INDEX, + Request.SEEKER_PUBLIC_ADDRESS_INDEX + BLUETOOTH_ADDRESS_LENGTH); + } + + /** Checks whether the Seeker request discoverability from flags byte. */ + public boolean requestDiscoverable() { + return (getFlags() & REQUEST_DISCOVERABLE) != 0; + } + + /** + * Checks whether the Seeker requests that the Provider shall initiate bonding from + * flags byte. + */ + public boolean requestProviderInitialBonding() { + return (getFlags() & PROVIDER_INITIATES_BONDING) != 0; + } + + /** Checks whether the Seeker requests that the Provider shall notify the existing name. */ + public boolean requestDeviceName() { + return (getFlags() & REQUEST_DEVICE_NAME) != 0; + } + + /** Checks whether this is for retroactively writing account key. */ + public boolean requestRetroactivePair() { + return (getFlags() & REQUEST_RETROACTIVE_PAIR) != 0; + } + + /** Gets the flags of this handshake request. */ + private byte getFlags() { + return mDecryptedMessage[Request.FLAGS_INDEX]; + } + + /** Checks whether the Seeker requests a device action. */ + public boolean requestDeviceAction() { + return (getFlags() & DEVICE_ACTION) != 0; + } + + /** + * Checks whether the Seeker requests an action which will be followed by an additional + * data. + */ + public boolean requestFollowedByAdditionalData() { + return (getFlags() & ADDITIONAL_DATA_CHARACTERISTIC) != 0; + } + + /** Gets the {@link AdditionalDataType} of this handshake request. */ + @AdditionalDataType + public int getAdditionalDataType() { + if (!requestFollowedByAdditionalData() + || mDecryptedMessage.length <= ADDITIONAL_DATA_TYPE_INDEX) { + return AdditionalDataType.UNKNOWN; + } + return mDecryptedMessage[ADDITIONAL_DATA_TYPE_INDEX]; + } + + /** Enumerates the handshake message types. */ + public enum Type { + KEY_BASED_PAIRING_REQUEST(Request.TYPE_KEY_BASED_PAIRING_REQUEST), + ACTION_OVER_BLE(Request.TYPE_ACTION_OVER_BLE), + UNKNOWN((byte) 0xFF); + + private final byte mValue; + + Type(byte type) { + mValue = type; + } + + public static Type valueOf(byte value) { + for (Type type : Type.values()) { + if (type.getValue() == value) { + return type; + } + } + return UNKNOWN; + } + + public byte getValue() { + return mValue; + } + } + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java index 670b2ca058..32e62a3ff3 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/HeadsetPieceTest.java @@ -196,5 +196,63 @@ public class HeadsetPieceTest { assertThat(headsetPiece.isBatteryLow()).isFalse(); } + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void describeContents() { + HeadsetPiece headsetPiece = + HeadsetPiece.builder() + .setLowLevelThreshold(30) + .setBatteryLevel(18) + .setImageUrl("http://fake.image.path/image.png") + .setCharging(true) + .build(); + assertThat(headsetPiece.describeContents()).isEqualTo(0); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void hashcode() { + HeadsetPiece headsetPiece = + HeadsetPiece.builder() + .setLowLevelThreshold(30) + .setBatteryLevel(18) + .setImageUrl("http://fake.image.path/image.png") + .setCharging(true) + .build(); + HeadsetPiece headsetPiece1 = + HeadsetPiece.builder() + .setLowLevelThreshold(30) + .setBatteryLevel(18) + .setImageUrl("http://fake.image.path/image.png") + .setCharging(true) + .build(); + assertThat(headsetPiece.hashCode()).isEqualTo(headsetPiece1.hashCode()); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testToString() { + HeadsetPiece headsetPiece = + HeadsetPiece.builder() + .setLowLevelThreshold(30) + .setBatteryLevel(18) + .setImageUrl("http://fake.image.path/image.png") + .setCharging(true) + .build(); + assertThat(headsetPiece.toString()) + .isEqualTo("HeadsetPiece{lowLevelThreshold=30," + + " batteryLevel=18," + + " imageUrl=http://fake.image.path/image.png," + + " charging=true," + + " imageContentUri=null}"); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testCreatorNewArray() { + HeadsetPiece[] headsetPieces = + HeadsetPiece.CREATOR.newArray(2); + assertThat(headsetPieces.length).isEqualTo(2); + } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/LtvTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/LtvTest.java new file mode 100644 index 0000000000..81a5d9263c --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/LtvTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.fastpair; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SdkSuppress; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Unit tests for {@link Ltv}. + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class LtvTest { + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testParseEmpty_throwsException() throws Ltv.ParseException { + assertThrows(Ltv.ParseException.class, + () -> Ltv.parse(new byte[]{0})); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testParse_finishesSuccessfully() throws Ltv.ParseException { + assertThat(Ltv.parse(new byte[]{3, 4, 5, 6})).isNotEmpty(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PairingProgressListenerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PairingProgressListenerTest.java new file mode 100644 index 0000000000..a58a92d10d --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PairingProgressListenerTest.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.fastpair; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; + +public class PairingProgressListenerTest { + + @Test + public void testFromOrdinal() { + assertThat(PairingProgressListener.fromOrdinal(0)).isEqualTo( + PairingProgressListener.PairingEvent.START); + assertThat(PairingProgressListener.fromOrdinal(1)).isEqualTo( + PairingProgressListener.PairingEvent.SUCCESS); + assertThat(PairingProgressListener.fromOrdinal(2)).isEqualTo( + PairingProgressListener.PairingEvent.FAILED); + assertThat(PairingProgressListener.fromOrdinal(3)).isEqualTo( + PairingProgressListener.PairingEvent.UNKNOWN); + assertThat(PairingProgressListener.fromOrdinal(4)).isEqualTo( + PairingProgressListener.PairingEvent.UNKNOWN); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PreferencesTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PreferencesTest.java index b40a5a5a85..378e3b9003 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PreferencesTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/PreferencesTest.java @@ -1266,12 +1266,38 @@ public class PreferencesTest { public void testExtraLoggingInformation() { Preferences prefs = Preferences.builder().setExtraLoggingInformation(FIRST_EXTRA_LOGGING_INFO).build(); - assertThat(prefs.getExtraLoggingInformation()).isEqualTo(FIRST_EXTRA_LOGGING_INFO); - assertThat(prefs.toBuilder().build().getExtraLoggingInformation()) - .isEqualTo(FIRST_EXTRA_LOGGING_INFO); - Preferences prefs2 = Preferences.builder().setExtraLoggingInformation(SECOND_EXTRA_LOGGING_INFO).build(); + Preferences prefs3 = + Preferences.builder().setExtraLoggingInformation(FIRST_EXTRA_LOGGING_INFO).build(); + + assertThat(prefs.getExtraLoggingInformation()).isEqualTo(FIRST_EXTRA_LOGGING_INFO); assertThat(prefs2.getExtraLoggingInformation()).isEqualTo(SECOND_EXTRA_LOGGING_INFO); + assertThat(prefs.toBuilder().build().getExtraLoggingInformation()) + .isEqualTo(FIRST_EXTRA_LOGGING_INFO); + // Test equal() + assertThat(prefs.getExtraLoggingInformation().equals(null)).isFalse(); + assertThat(prefs.getExtraLoggingInformation() + .equals(prefs2.getExtraLoggingInformation())).isFalse(); + assertThat(prefs.getExtraLoggingInformation() + .equals(prefs3.getExtraLoggingInformation())).isTrue(); + // Test getModelId() + assertThat(prefs.getExtraLoggingInformation().getModelId()) + .isEqualTo(prefs3.getExtraLoggingInformation().getModelId()); + assertThat(prefs.getExtraLoggingInformation().getModelId()) + .isNotEqualTo(prefs2.getExtraLoggingInformation().getModelId()); + // Test hashCode() + assertThat(prefs.getExtraLoggingInformation().hashCode()) + .isEqualTo(prefs3.getExtraLoggingInformation().hashCode()); + assertThat(prefs.getExtraLoggingInformation().hashCode()) + .isNotEqualTo(prefs2.getExtraLoggingInformation().hashCode()); + // Test toBuilder() + + assertThat(prefs.getExtraLoggingInformation() + .toBuilder().setModelId("000007").build().getModelId()) + .isEqualTo("000007"); + // Test toString() + assertThat(prefs.getExtraLoggingInformation().toString()) + .isEqualTo("ExtraLoggingInformation{modelId=000006}"); } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/TdsExceptionTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/TdsExceptionTest.java new file mode 100644 index 0000000000..e8b94802bf --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/fastpair/TdsExceptionTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.fastpair; + +import static com.google.common.truth.Truth.assertThat; + +import com.android.server.nearby.intdefs.FastPairEventIntDefs; + +import org.junit.Test; + +public class TdsExceptionTest { + @Test + public void testGetErrorCode() { + TdsException tdsException = new TdsException( + FastPairEventIntDefs.BrEdrHandoverErrorCode.BLUETOOTH_MAC_INVALID, "format"); + assertThat(tdsException.getErrorCode()) + .isEqualTo(FastPairEventIntDefs.BrEdrHandoverErrorCode.BLUETOOTH_MAC_INVALID); + + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapterTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapterTest.java new file mode 100644 index 0000000000..a9ab7daf04 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothAdapterTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.testability.android.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SdkSuppress; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link BluetoothAdapter}. + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BluetoothAdapterTest { + + private static final byte[] BYTES = new byte[]{0, 1, 2, 3, 4, 5}; + private static final String ADDRESS = "00:11:22:33:AA:BB"; + + @Mock private android.bluetooth.BluetoothAdapter mBluetoothAdapter; + @Mock private android.bluetooth.BluetoothDevice mBluetoothDevice; + @Mock private android.bluetooth.le.BluetoothLeAdvertiser mBluetoothLeAdvertiser; + @Mock private android.bluetooth.le.BluetoothLeScanner mBluetoothLeScanner; + + BluetoothAdapter mTestabilityBluetoothAdapter; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mTestabilityBluetoothAdapter = BluetoothAdapter.wrap(mBluetoothAdapter); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testWrapNullAdapter_isNull() { + assertThat(BluetoothAdapter.wrap(null)).isNull(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testWrapNonNullAdapter_isNotNull_unWrapSame() { + assertThat(mTestabilityBluetoothAdapter).isNotNull(); + assertThat(mTestabilityBluetoothAdapter.unwrap()).isSameInstanceAs(mBluetoothAdapter); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testDisable_callsWrapped() { + when(mBluetoothAdapter.disable()).thenReturn(true); + assertThat(mTestabilityBluetoothAdapter.disable()).isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testEnable_callsWrapped() { + when(mBluetoothAdapter.enable()).thenReturn(true); + assertThat(mTestabilityBluetoothAdapter.enable()).isTrue(); + when(mBluetoothAdapter.isEnabled()).thenReturn(true); + assertThat(mTestabilityBluetoothAdapter.isEnabled()).isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetBluetoothLeAdvertiser_callsWrapped() { + when(mBluetoothAdapter.getBluetoothLeAdvertiser()).thenReturn(mBluetoothLeAdvertiser); + assertThat(mTestabilityBluetoothAdapter.getBluetoothLeAdvertiser().unwrap()) + .isSameInstanceAs(mBluetoothLeAdvertiser); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetBluetoothLeScanner_callsWrapped() { + when(mBluetoothAdapter.getBluetoothLeScanner()).thenReturn(mBluetoothLeScanner); + assertThat(mTestabilityBluetoothAdapter.getBluetoothLeScanner().unwrap()) + .isSameInstanceAs(mBluetoothLeScanner); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetBondedDevices_callsWrapped() { + when(mBluetoothAdapter.getBondedDevices()).thenReturn(null); + assertThat(mTestabilityBluetoothAdapter.getBondedDevices()).isNull(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testIsDiscovering_pcallsWrapped() { + when(mBluetoothAdapter.isDiscovering()).thenReturn(true); + assertThat(mTestabilityBluetoothAdapter.isDiscovering()).isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testStartDiscovery_callsWrapped() { + when(mBluetoothAdapter.startDiscovery()).thenReturn(true); + assertThat(mTestabilityBluetoothAdapter.startDiscovery()).isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testCancelDiscovery_callsWrapped() { + when(mBluetoothAdapter.cancelDiscovery()).thenReturn(true); + assertThat(mTestabilityBluetoothAdapter.cancelDiscovery()).isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetRemoteDeviceBytes_callsWrapped() { + when(mBluetoothAdapter.getRemoteDevice(BYTES)).thenReturn(mBluetoothDevice); + assertThat(mTestabilityBluetoothAdapter.getRemoteDevice(BYTES).unwrap()) + .isSameInstanceAs(mBluetoothDevice); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetRemoteDeviceString_callsWrapped() { + when(mBluetoothAdapter.getRemoteDevice(ADDRESS)).thenReturn(mBluetoothDevice); + assertThat(mTestabilityBluetoothAdapter.getRemoteDevice(ADDRESS).unwrap()) + .isSameInstanceAs(mBluetoothDevice); + + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDeviceTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDeviceTest.java new file mode 100644 index 0000000000..d494024ed7 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDeviceTest.java @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.testability.android.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SdkSuppress; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.IOException; +import java.util.UUID; + +/** + * Unit tests for {@link BluetoothDevice}. + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BluetoothDeviceTest { + private static final UUID UUID_CONST = UUID.randomUUID(); + private static final String ADDRESS = "ADDRESS"; + private static final String STRING = "STRING"; + + @Mock private android.bluetooth.BluetoothDevice mBluetoothDevice; + @Mock private android.bluetooth.BluetoothGatt mBluetoothGatt; + @Mock private android.bluetooth.BluetoothSocket mBluetoothSocket; + @Mock private android.bluetooth.BluetoothClass mBluetoothClass; + + BluetoothDevice mTestabilityBluetoothDevice; + BluetoothGattCallback mTestBluetoothGattCallback; + Context mContext; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mTestabilityBluetoothDevice = BluetoothDevice.wrap(mBluetoothDevice); + mTestBluetoothGattCallback = new TestBluetoothGattCallback(); + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testWrapNonNullAdapter_isNotNull_unWrapSame() { + assertThat(mTestabilityBluetoothDevice).isNotNull(); + assertThat(mTestabilityBluetoothDevice.unwrap()).isSameInstanceAs(mBluetoothDevice); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testEquality_asExpected() { + assertThat(mTestabilityBluetoothDevice.equals(null)).isFalse(); + assertThat(mTestabilityBluetoothDevice.equals(mTestabilityBluetoothDevice)).isTrue(); + assertThat(mTestabilityBluetoothDevice.equals(BluetoothDevice.wrap(mBluetoothDevice))) + .isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testHashCode_asExpected() { + assertThat(mTestabilityBluetoothDevice.hashCode()) + .isEqualTo(BluetoothDevice.wrap(mBluetoothDevice).hashCode()); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testConnectGattWithThreeParameters_callsWrapped() { + when(mBluetoothDevice + .connectGatt(mContext, true, mTestBluetoothGattCallback.unwrap())) + .thenReturn(mBluetoothGatt); + assertThat(mTestabilityBluetoothDevice + .connectGatt(mContext, true, mTestBluetoothGattCallback) + .unwrap()) + .isSameInstanceAs(mBluetoothGatt); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testConnectGattWithFourParameters_callsWrapped() { + when(mBluetoothDevice + .connectGatt(mContext, true, mTestBluetoothGattCallback.unwrap(), 1)) + .thenReturn(mBluetoothGatt); + assertThat(mTestabilityBluetoothDevice + .connectGatt(mContext, true, mTestBluetoothGattCallback, 1) + .unwrap()) + .isSameInstanceAs(mBluetoothGatt); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testCreateRfcommSocketToServiceRecord_callsWrapped() throws IOException { + when(mBluetoothDevice.createRfcommSocketToServiceRecord(UUID_CONST)) + .thenReturn(mBluetoothSocket); + assertThat(mTestabilityBluetoothDevice.createRfcommSocketToServiceRecord(UUID_CONST)) + .isSameInstanceAs(mBluetoothSocket); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testCreateInsecureRfcommSocketToServiceRecord_callsWrapped() throws IOException { + when(mBluetoothDevice.createInsecureRfcommSocketToServiceRecord(UUID_CONST)) + .thenReturn(mBluetoothSocket); + assertThat(mTestabilityBluetoothDevice + .createInsecureRfcommSocketToServiceRecord(UUID_CONST)) + .isSameInstanceAs(mBluetoothSocket); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testSetPairingConfirmation_callsWrapped() throws IOException { + when(mBluetoothDevice.setPairingConfirmation(true)).thenReturn(true); + assertThat(mTestabilityBluetoothDevice.setPairingConfirmation(true)).isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testFetchUuidsWithSdp_callsWrapped() throws IOException { + when(mBluetoothDevice.fetchUuidsWithSdp()).thenReturn(true); + assertThat(mTestabilityBluetoothDevice.fetchUuidsWithSdp()).isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testCreateBond_callsWrapped() throws IOException { + when(mBluetoothDevice.createBond()).thenReturn(true); + assertThat(mTestabilityBluetoothDevice.createBond()).isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetUuids_callsWrapped() throws IOException { + when(mBluetoothDevice.getUuids()).thenReturn(null); + assertThat(mTestabilityBluetoothDevice.getUuids()).isNull(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetBondState_callsWrapped() throws IOException { + when(mBluetoothDevice.getBondState()).thenReturn(1); + assertThat(mTestabilityBluetoothDevice.getBondState()).isEqualTo(1); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetAddress_callsWrapped() throws IOException { + when(mBluetoothDevice.getAddress()).thenReturn(ADDRESS); + assertThat(mTestabilityBluetoothDevice.getAddress()).isSameInstanceAs(ADDRESS); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetBluetoothClass_callsWrapped() throws IOException { + when(mBluetoothDevice.getBluetoothClass()).thenReturn(mBluetoothClass); + assertThat(mTestabilityBluetoothDevice.getBluetoothClass()) + .isSameInstanceAs(mBluetoothClass); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetType_callsWrapped() throws IOException { + when(mBluetoothDevice.getType()).thenReturn(1); + assertThat(mTestabilityBluetoothDevice.getType()).isEqualTo(1); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetName_callsWrapped() throws IOException { + when(mBluetoothDevice.getName()).thenReturn(STRING); + assertThat(mTestabilityBluetoothDevice.getName()).isSameInstanceAs(STRING); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testToString_callsWrapped() { + when(mBluetoothDevice.toString()).thenReturn(STRING); + assertThat(mTestabilityBluetoothDevice.toString()).isSameInstanceAs(STRING); + } + + private static class TestBluetoothGattCallback extends BluetoothGattCallback {} +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallbackTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallbackTest.java new file mode 100644 index 0000000000..26ae6d7076 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallbackTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.testability.android.bluetooth; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +/** + * Unit tests for {@link BluetoothGattCallback}. + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BluetoothGattCallbackTest { + @Mock private android.bluetooth.BluetoothGatt mBluetoothGatt; + @Mock private android.bluetooth.BluetoothGattCharacteristic mBluetoothGattCharacteristic; + @Mock private android.bluetooth.BluetoothGattDescriptor mBluetoothGattDescriptor; + + TestBluetoothGattCallback mTestBluetoothGattCallback = new TestBluetoothGattCallback(); + + @Test + public void testOnConnectionStateChange_notCrash() { + mTestBluetoothGattCallback.unwrap() + .onConnectionStateChange(mBluetoothGatt, 1, 1); + } + + @Test + public void testOnServiceDiscovered_notCrash() { + mTestBluetoothGattCallback.unwrap().onServicesDiscovered(mBluetoothGatt, 1); + } + + @Test + public void testOnCharacteristicRead_notCrash() { + mTestBluetoothGattCallback.unwrap().onCharacteristicRead(mBluetoothGatt, + mBluetoothGattCharacteristic, 1); + } + + @Test + public void testOnCharacteristicWrite_notCrash() { + mTestBluetoothGattCallback.unwrap().onCharacteristicWrite(mBluetoothGatt, + mBluetoothGattCharacteristic, 1); + } + + @Test + public void testOnDescriptionRead_notCrash() { + mTestBluetoothGattCallback.unwrap().onDescriptorRead(mBluetoothGatt, + mBluetoothGattDescriptor, 1); + } + + @Test + public void testOnDescriptionWrite_notCrash() { + mTestBluetoothGattCallback.unwrap().onDescriptorWrite(mBluetoothGatt, + mBluetoothGattDescriptor, 1); + } + + @Test + public void testOnReadRemoteRssi_notCrash() { + mTestBluetoothGattCallback.unwrap().onReadRemoteRssi(mBluetoothGatt, 1, 1); + } + + @Test + public void testOnReliableWriteCompleted_notCrash() { + mTestBluetoothGattCallback.unwrap().onReliableWriteCompleted(mBluetoothGatt, 1); + } + + @Test + public void testOnMtuChanged_notCrash() { + mTestBluetoothGattCallback.unwrap().onMtuChanged(mBluetoothGatt, 1, 1); + } + + @Test + public void testOnCharacteristicChanged_notCrash() { + mTestBluetoothGattCallback.unwrap() + .onCharacteristicChanged(mBluetoothGatt, mBluetoothGattCharacteristic); + } + + private static class TestBluetoothGattCallback extends BluetoothGattCallback { } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallbackTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallbackTest.java new file mode 100644 index 0000000000..fb99317f05 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerCallbackTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.testability.android.bluetooth; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; + +/** + * Unit tests for {@link BluetoothGattServerCallback}. + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BluetoothGattServerCallbackTest { + @Mock + private android.bluetooth.BluetoothDevice mBluetoothDevice; + @Mock + private android.bluetooth.BluetoothGattService mBluetoothGattService; + @Mock + private android.bluetooth.BluetoothGattCharacteristic mBluetoothGattCharacteristic; + @Mock + private android.bluetooth.BluetoothGattDescriptor mBluetoothGattDescriptor; + + TestBluetoothGattServerCallback + mTestBluetoothGattServerCallback = new TestBluetoothGattServerCallback(); + + @Test + public void testOnCharacteristicReadRequest_notCrash() { + mTestBluetoothGattServerCallback.unwrap().onCharacteristicReadRequest( + mBluetoothDevice, 1, 1, mBluetoothGattCharacteristic); + } + + @Test + public void testOnCharacteristicWriteRequest_notCrash() { + mTestBluetoothGattServerCallback.unwrap().onCharacteristicWriteRequest( + mBluetoothDevice, + 1, + mBluetoothGattCharacteristic, + false, + true, + 1, + new byte[]{1}); + } + + @Test + public void testOnConnectionStateChange_notCrash() { + mTestBluetoothGattServerCallback.unwrap().onConnectionStateChange( + mBluetoothDevice, + 1, + 2); + } + + @Test + public void testOnDescriptorReadRequest_notCrash() { + mTestBluetoothGattServerCallback.unwrap().onDescriptorReadRequest( + mBluetoothDevice, + 1, + 2, mBluetoothGattDescriptor); + } + + @Test + public void testOnDescriptorWriteRequest_notCrash() { + mTestBluetoothGattServerCallback.unwrap().onDescriptorWriteRequest( + mBluetoothDevice, + 1, + mBluetoothGattDescriptor, + false, + true, + 2, + new byte[]{1}); + } + + @Test + public void testOnExecuteWrite_notCrash() { + mTestBluetoothGattServerCallback.unwrap().onExecuteWrite( + mBluetoothDevice, + 1, + false); + } + + @Test + public void testOnMtuChanged_notCrash() { + mTestBluetoothGattServerCallback.unwrap().onMtuChanged( + mBluetoothDevice, + 1); + } + + @Test + public void testOnNotificationSent_notCrash() { + mTestBluetoothGattServerCallback.unwrap().onNotificationSent( + mBluetoothDevice, + 1); + } + + @Test + public void testOnServiceAdded_notCrash() { + mTestBluetoothGattServerCallback.unwrap().onServiceAdded(1, mBluetoothGattService); + } + + private static class TestBluetoothGattServerCallback extends BluetoothGattServerCallback { } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerTest.java new file mode 100644 index 0000000000..48283d1b60 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattServerTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.testability.android.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SdkSuppress; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.UUID; + +/** + * Unit tests for {@link BluetoothGattServer}. + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BluetoothGattServerTest { + private static final UUID UUID_CONST = UUID.randomUUID(); + private static final byte[] BYTES = new byte[]{1, 2, 3}; + + @Mock private android.bluetooth.BluetoothDevice mBluetoothDevice; + @Mock private android.bluetooth.BluetoothGattServer mBluetoothGattServer; + @Mock private android.bluetooth.BluetoothGattService mBluetoothGattService; + @Mock private android.bluetooth.BluetoothGattCharacteristic mBluetoothGattCharacteristic; + + BluetoothGattServer mTestabilityBluetoothGattServer; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mTestabilityBluetoothGattServer = BluetoothGattServer.wrap(mBluetoothGattServer); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testWrapNonNullAdapter_isNotNull_unWrapSame() { + assertThat(mTestabilityBluetoothGattServer).isNotNull(); + assertThat(mTestabilityBluetoothGattServer.unwrap()).isSameInstanceAs(mBluetoothGattServer); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testConnect_callsWrapped() { + when(mBluetoothGattServer + .connect(mBluetoothDevice, true)) + .thenReturn(true); + assertThat(mTestabilityBluetoothGattServer + .connect(BluetoothDevice.wrap(mBluetoothDevice), true)) + .isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testAddService_callsWrapped() { + when(mBluetoothGattServer + .addService(mBluetoothGattService)) + .thenReturn(true); + assertThat(mTestabilityBluetoothGattServer + .addService(mBluetoothGattService)) + .isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testClearServices_callsWrapped() { + doNothing().when(mBluetoothGattServer).clearServices(); + mTestabilityBluetoothGattServer.clearServices(); + verify(mBluetoothGattServer).clearServices(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testClose_callsWrapped() { + doNothing().when(mBluetoothGattServer).close(); + mTestabilityBluetoothGattServer.close(); + verify(mBluetoothGattServer).close(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testNotifyCharacteristicChanged_callsWrapped() { + when(mBluetoothGattServer + .notifyCharacteristicChanged( + mBluetoothDevice, + mBluetoothGattCharacteristic, + true)) + .thenReturn(true); + assertThat(mTestabilityBluetoothGattServer + .notifyCharacteristicChanged( + BluetoothDevice.wrap(mBluetoothDevice), + mBluetoothGattCharacteristic, + true)) + .isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testSendResponse_callsWrapped() { + when(mBluetoothGattServer.sendResponse( + mBluetoothDevice, 1, 1, 1, BYTES)).thenReturn(true); + mTestabilityBluetoothGattServer.sendResponse( + BluetoothDevice.wrap(mBluetoothDevice), 1, 1, 1, BYTES); + verify(mBluetoothGattServer).sendResponse( + mBluetoothDevice, 1, 1, 1, BYTES); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testCancelConnection_callsWrapped() { + doNothing().when(mBluetoothGattServer).cancelConnection(mBluetoothDevice); + mTestabilityBluetoothGattServer.cancelConnection(BluetoothDevice.wrap(mBluetoothDevice)); + verify(mBluetoothGattServer).cancelConnection(mBluetoothDevice); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetService_callsWrapped() { + when(mBluetoothGattServer.getService(UUID_CONST)).thenReturn(null); + assertThat(mTestabilityBluetoothGattServer.getService(UUID_CONST)).isNull(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapperTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapperTest.java new file mode 100644 index 0000000000..a03a2559c6 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattWrapperTest.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.testability.android.bluetooth; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SdkSuppress; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.UUID; + +/** + * Unit tests for {@link BluetoothGattWrapper}. + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BluetoothGattWrapperTest { + private static final UUID UUID_CONST = UUID.randomUUID(); + private static final byte[] BYTES = new byte[]{1, 2, 3}; + + @Mock private android.bluetooth.BluetoothDevice mBluetoothDevice; + @Mock private android.bluetooth.BluetoothGatt mBluetoothGatt; + @Mock private android.bluetooth.BluetoothGattService mBluetoothGattService; + @Mock private android.bluetooth.BluetoothGattCharacteristic mBluetoothGattCharacteristic; + @Mock private android.bluetooth.BluetoothGattDescriptor mBluetoothGattDescriptor; + + BluetoothGattWrapper mBluetoothGattWrapper; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mBluetoothGattWrapper = BluetoothGattWrapper.wrap(mBluetoothGatt); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testWrapNonNullAdapter_isNotNull_unWrapSame() { + assertThat(mBluetoothGattWrapper).isNotNull(); + assertThat(mBluetoothGattWrapper.unwrap()).isSameInstanceAs(mBluetoothGatt); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testEquality_asExpected() { + assertThat(mBluetoothGattWrapper.equals(null)).isFalse(); + assertThat(mBluetoothGattWrapper.equals(mBluetoothGattWrapper)).isTrue(); + assertThat(mBluetoothGattWrapper.equals(BluetoothGattWrapper.wrap(mBluetoothGatt))) + .isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetDevice_callsWrapped() { + when(mBluetoothGatt.getDevice()).thenReturn(mBluetoothDevice); + assertThat(mBluetoothGattWrapper.getDevice().unwrap()).isSameInstanceAs(mBluetoothDevice); + } + + @Test + public void testHashCode_asExpected() { + assertThat(mBluetoothGattWrapper.hashCode()) + .isEqualTo(BluetoothGattWrapper.wrap(mBluetoothGatt).hashCode()); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetServices_callsWrapped() { + when(mBluetoothGatt.getServices()).thenReturn(null); + assertThat(mBluetoothGattWrapper.getServices()).isNull(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testGetService_callsWrapped() { + when(mBluetoothGatt.getService(UUID_CONST)).thenReturn(mBluetoothGattService); + assertThat(mBluetoothGattWrapper.getService(UUID_CONST)) + .isSameInstanceAs(mBluetoothGattService); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testDiscoverServices_callsWrapped() { + when(mBluetoothGatt.discoverServices()).thenReturn(true); + assertThat(mBluetoothGattWrapper.discoverServices()).isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testReadCharacteristic_callsWrapped() { + when(mBluetoothGatt.readCharacteristic(mBluetoothGattCharacteristic)).thenReturn(true); + assertThat(mBluetoothGattWrapper.readCharacteristic(mBluetoothGattCharacteristic)).isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testWriteCharacteristic_callsWrapped() { + when(mBluetoothGatt.writeCharacteristic(mBluetoothGattCharacteristic, BYTES, 1)) + .thenReturn(1); + assertThat(mBluetoothGattWrapper.writeCharacteristic( + mBluetoothGattCharacteristic, BYTES, 1)).isEqualTo(1); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testReadDescriptor_callsWrapped() { + when(mBluetoothGatt.readDescriptor(mBluetoothGattDescriptor)).thenReturn(false); + assertThat(mBluetoothGattWrapper.readDescriptor(mBluetoothGattDescriptor)).isFalse(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testWriteDescriptor_callsWrapped() { + when(mBluetoothGatt.writeDescriptor(mBluetoothGattDescriptor, BYTES)).thenReturn(5); + assertThat(mBluetoothGattWrapper.writeDescriptor(mBluetoothGattDescriptor, BYTES)) + .isEqualTo(5); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testReadRemoteRssi_callsWrapped() { + when(mBluetoothGatt.readRemoteRssi()).thenReturn(false); + assertThat(mBluetoothGattWrapper.readRemoteRssi()).isFalse(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testRequestConnectionPriority_callsWrapped() { + when(mBluetoothGatt.requestConnectionPriority(5)).thenReturn(false); + assertThat(mBluetoothGattWrapper.requestConnectionPriority(5)).isFalse(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testRequestMtu_callsWrapped() { + when(mBluetoothGatt.requestMtu(5)).thenReturn(false); + assertThat(mBluetoothGattWrapper.requestMtu(5)).isFalse(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testSetCharacteristicNotification_callsWrapped() { + when(mBluetoothGatt.setCharacteristicNotification(mBluetoothGattCharacteristic, true)) + .thenReturn(false); + assertThat(mBluetoothGattWrapper + .setCharacteristicNotification(mBluetoothGattCharacteristic, true)).isFalse(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testDisconnect_callsWrapped() { + doNothing().when(mBluetoothGatt).disconnect(); + mBluetoothGattWrapper.disconnect(); + verify(mBluetoothGatt).disconnect(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testClose_callsWrapped() { + doNothing().when(mBluetoothGatt).close(); + mBluetoothGattWrapper.close(); + verify(mBluetoothGatt).close(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothAdvertiserTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothAdvertiserTest.java new file mode 100644 index 0000000000..8468ed162e --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothAdvertiserTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; + +import android.bluetooth.le.AdvertiseCallback; +import android.bluetooth.le.AdvertiseData; +import android.bluetooth.le.AdvertiseSettings; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SdkSuppress; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link BluetoothLeAdvertiser}. + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BluetoothAdvertiserTest { + @Mock android.bluetooth.le.BluetoothLeAdvertiser mWrappedBluetoothLeAdvertiser; + @Mock AdvertiseSettings mAdvertiseSettings; + @Mock AdvertiseData mAdvertiseData; + @Mock AdvertiseCallback mAdvertiseCallback; + + BluetoothLeAdvertiser mBluetoothLeAdvertiser; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mBluetoothLeAdvertiser = BluetoothLeAdvertiser.wrap(mWrappedBluetoothLeAdvertiser); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testWrapNullAdapter_isNull() { + assertThat(BluetoothLeAdvertiser.wrap(null)).isNull(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testWrapNonNullAdapter_isNotNull_unWrapSame() { + assertThat(mWrappedBluetoothLeAdvertiser).isNotNull(); + assertThat(mBluetoothLeAdvertiser.unwrap()).isSameInstanceAs(mWrappedBluetoothLeAdvertiser); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testStartAdvertisingThreeParameters_callsWrapped() { + doNothing().when(mWrappedBluetoothLeAdvertiser) + .startAdvertising(mAdvertiseSettings, mAdvertiseData, mAdvertiseCallback); + mBluetoothLeAdvertiser + .startAdvertising(mAdvertiseSettings, mAdvertiseData, mAdvertiseCallback); + verify(mWrappedBluetoothLeAdvertiser).startAdvertising( + mAdvertiseSettings, mAdvertiseData, mAdvertiseCallback); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testStartAdvertisingFourParameters_callsWrapped() { + doNothing().when(mWrappedBluetoothLeAdvertiser).startAdvertising( + mAdvertiseSettings, mAdvertiseData, mAdvertiseData, mAdvertiseCallback); + mBluetoothLeAdvertiser.startAdvertising( + mAdvertiseSettings, mAdvertiseData, mAdvertiseData, mAdvertiseCallback); + verify(mWrappedBluetoothLeAdvertiser).startAdvertising( + mAdvertiseSettings, mAdvertiseData, mAdvertiseData, mAdvertiseCallback); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testStopAdvertising_callsWrapped() { + doNothing().when(mWrappedBluetoothLeAdvertiser).stopAdvertising(mAdvertiseCallback); + mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback); + verify(mWrappedBluetoothLeAdvertiser).stopAdvertising(mAdvertiseCallback); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScannerTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScannerTest.java new file mode 100644 index 0000000000..3fce54f3c2 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/BluetoothLeScannerTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.PendingIntent; +import android.bluetooth.le.ScanFilter; +import android.bluetooth.le.ScanSettings; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SdkSuppress; +import androidx.test.filters.SmallTest; + +import com.google.common.collect.ImmutableList; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link BluetoothLeScanner}. + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class BluetoothLeScannerTest { + @Mock android.bluetooth.le.BluetoothLeScanner mWrappedBluetoothLeScanner; + @Mock PendingIntent mPendingIntent; + @Mock ScanSettings mScanSettings; + @Mock ScanFilter mScanFilter; + + TestScanCallback mTestScanCallback = new TestScanCallback(); + BluetoothLeScanner mBluetoothLeScanner; + ImmutableList mImmutableScanFilterList; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mBluetoothLeScanner = BluetoothLeScanner.wrap(mWrappedBluetoothLeScanner); + mImmutableScanFilterList = ImmutableList.of(mScanFilter); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testWrapNullAdapter_isNull() { + assertThat(BluetoothLeAdvertiser.wrap(null)).isNull(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testWrapNonNullAdapter_isNotNull_unWrapSame() { + assertThat(mWrappedBluetoothLeScanner).isNotNull(); + assertThat(mBluetoothLeScanner.unwrap()).isSameInstanceAs(mWrappedBluetoothLeScanner); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testStartScan_callsWrapped() { + doNothing().when(mWrappedBluetoothLeScanner).startScan(mTestScanCallback.unwrap()); + mBluetoothLeScanner.startScan(mTestScanCallback); + verify(mWrappedBluetoothLeScanner).startScan(mTestScanCallback.unwrap()); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testStartScanWithFiltersCallback_callsWrapped() { + doNothing().when(mWrappedBluetoothLeScanner) + .startScan(mImmutableScanFilterList, mScanSettings, mTestScanCallback.unwrap()); + mBluetoothLeScanner.startScan(mImmutableScanFilterList, mScanSettings, mTestScanCallback); + verify(mWrappedBluetoothLeScanner) + .startScan(mImmutableScanFilterList, mScanSettings, mTestScanCallback.unwrap()); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testStartScanWithFiltersCallbackIntent_callsWrapped() { + when(mWrappedBluetoothLeScanner.startScan( + mImmutableScanFilterList, mScanSettings, mPendingIntent)).thenReturn(1); + mBluetoothLeScanner.startScan(mImmutableScanFilterList, mScanSettings, mPendingIntent); + verify(mWrappedBluetoothLeScanner) + .startScan(mImmutableScanFilterList, mScanSettings, mPendingIntent); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testStopScan_callsWrapped() { + doNothing().when(mWrappedBluetoothLeScanner).stopScan(mTestScanCallback.unwrap()); + mBluetoothLeScanner.stopScan(mTestScanCallback); + verify(mWrappedBluetoothLeScanner).stopScan(mTestScanCallback.unwrap()); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testStopScanPendingIntent_callsWrapped() { + doNothing().when(mWrappedBluetoothLeScanner).stopScan(mPendingIntent); + mBluetoothLeScanner.stopScan(mPendingIntent); + verify(mWrappedBluetoothLeScanner).stopScan(mPendingIntent); + } + + private static class TestScanCallback extends ScanCallback {}; +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallbackTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallbackTest.java new file mode 100644 index 0000000000..6d6848610a --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanCallbackTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.google.common.collect.ImmutableList; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link ScanCallback}. + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ScanCallbackTest { + @Mock android.bluetooth.le.ScanResult mScanResult; + + TestScanCallback mTestScanCallback = new TestScanCallback(); + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void testOnScanFailed_notCrash() { + mTestScanCallback.unwrap().onScanFailed(1); + } + + @Test + public void testOnScanResult_notCrash() { + mTestScanCallback.unwrap().onScanResult(1, mScanResult); + } + + @Test + public void testOnBatchScanResult_notCrash() { + mTestScanCallback.unwrap().onBatchScanResults(ImmutableList.of(mScanResult)); + } + + private static class TestScanCallback extends ScanCallback { } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResultTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResultTest.java new file mode 100644 index 0000000000..255c178e5d --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/le/ScanResultTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.bluetooth.testability.android.bluetooth.le; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link ScanResult}. + */ +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class ScanResultTest { + + @Mock android.bluetooth.le.ScanResult mWrappedScanResult; + @Mock android.bluetooth.le.ScanRecord mScanRecord; + @Mock android.bluetooth.BluetoothDevice mBluetoothDevice; + ScanResult mScanResult; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mScanResult = ScanResult.wrap(mWrappedScanResult); + } + + @Test + public void testGetScanRecord_calledWrapped() { + when(mWrappedScanResult.getScanRecord()).thenReturn(mScanRecord); + assertThat(mScanResult.getScanRecord()).isSameInstanceAs(mScanRecord); + } + + @Test + public void testGetRssi_calledWrapped() { + when(mWrappedScanResult.getRssi()).thenReturn(3); + assertThat(mScanResult.getRssi()).isEqualTo(3); + } + + @Test + public void testGetTimestampNanos_calledWrapped() { + when(mWrappedScanResult.getTimestampNanos()).thenReturn(4L); + assertThat(mScanResult.getTimestampNanos()).isEqualTo(4L); + } + + @Test + public void testGetDevice_calledWrapped() { + when(mWrappedScanResult.getDevice()).thenReturn(mBluetoothDevice); + assertThat(mScanResult.getDevice().unwrap()).isSameInstanceAs(mBluetoothDevice); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java index 47182c35c2..7535c06818 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/common/bluetooth/util/BluetoothGattUtilsTest.java @@ -34,14 +34,12 @@ import org.junit.runner.RunWith; import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import java.util.UUID; /** Unit tests for {@link BluetoothGattUtils}. */ @Presubmit @SmallTest @RunWith(AndroidJUnit4.class) public class BluetoothGattUtilsTest { - private static final UUID TEST_UUID = UUID.randomUUID(); private static final ImmutableSet GATT_HIDDEN_CONSTANTS = ImmutableSet.of( "GATT_WRITE_REQUEST_BUSY", "GATT_WRITE_REQUEST_FAIL", "GATT_WRITE_REQUEST_SUCCESS"); diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java index 70dcec8e9e..bebb2f211b 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/EventLoopTest.java @@ -36,7 +36,6 @@ public class EventLoopTest { @Rule public ExpectedException thrown = ExpectedException.none(); - /* @Test public void remove() { mEventLoop.postRunnable(new NumberedRunnable(0)); @@ -44,10 +43,8 @@ public class EventLoopTest { mEventLoop.postRunnable(runnableToAddAndRemove); mEventLoop.removeRunnable(runnableToAddAndRemove); mEventLoop.postRunnable(new NumberedRunnable(2)); - - assertThat(mExecutedRunnables).containsExactly(0, 2); + assertThat(mExecutedRunnables).doesNotContain(1); } - */ @Test @SdkSuppress(minSdkVersion = 32, codeName = "T") diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/HandlerEventLoopImplTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/HandlerEventLoopImplTest.java new file mode 100644 index 0000000000..4775456166 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/HandlerEventLoopImplTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.eventloop; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.filters.SdkSuppress; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.ArrayList; +import java.util.List; + +public class HandlerEventLoopImplTest { + private static final String TAG = "HandlerEventLoopImplTest"; + private final HandlerEventLoopImpl mHandlerEventLoopImpl = + new HandlerEventLoopImpl(TAG); + private final List mExecutedRunnables = new ArrayList<>(); + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void remove() { + mHandlerEventLoopImpl.postRunnable(new NumberedRunnable(0)); + NumberedRunnable runnableToAddAndRemove = new NumberedRunnable(1); + mHandlerEventLoopImpl.postRunnable(runnableToAddAndRemove); + mHandlerEventLoopImpl.removeRunnable(runnableToAddAndRemove); + mHandlerEventLoopImpl.postRunnable(new NumberedRunnable(2)); + assertThat(mExecutedRunnables).doesNotContain(1); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void isPosted() { + NumberedRunnable runnable = new HandlerEventLoopImplTest.NumberedRunnable(0); + mHandlerEventLoopImpl.postRunnableDelayed(runnable, 10 * 1000L); + assertThat(mHandlerEventLoopImpl.isPosted(runnable)).isTrue(); + mHandlerEventLoopImpl.removeRunnable(runnable); + assertThat(mHandlerEventLoopImpl.isPosted(runnable)).isFalse(); + + // Let a runnable execute, then verify that it's not posted. + mHandlerEventLoopImpl.postRunnable(runnable); + assertThat(mHandlerEventLoopImpl.isPosted(runnable)).isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void postAndWaitAfterDestroy() throws InterruptedException { + mHandlerEventLoopImpl.destroy(); + mHandlerEventLoopImpl.postAndWait(new HandlerEventLoopImplTest.NumberedRunnable(0)); + assertThat(mExecutedRunnables).isEmpty(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void postEmptyQueueRunnable() { + mHandlerEventLoopImpl.postEmptyQueueRunnable( + new HandlerEventLoopImplTest.NumberedRunnable(0)); + } + + private class NumberedRunnable extends NamedRunnable { + private final int mId; + + private NumberedRunnable(int id) { + super("NumberedRunnable:" + id); + this.mId = id; + } + + @Override + public void run() { + // Note: when running in robolectric, this is not actually executed on a different + // thread, it's executed in the same thread the test runs in, so this is safe. + mExecutedRunnables.add(mId); + } + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/NamedRunnableTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/NamedRunnableTest.java new file mode 100644 index 0000000000..7005da1a74 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/eventloop/NamedRunnableTest.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.eventloop; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; + +public class NamedRunnableTest { + private static final String TAG = "NamedRunnableTest"; + + @Test + public void testToString() { + assertThat(mNamedRunnable.toString()).isEqualTo("Runnable[" + TAG + "]"); + } + + private final NamedRunnable mNamedRunnable = new NamedRunnable(TAG) { + @Override + public void run() { + } + }; +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/fastpair/IconUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/fastpair/IconUtilsTest.java new file mode 100644 index 0000000000..d39d9cc1bc --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/fastpair/IconUtilsTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.fastpair; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.graphics.Bitmap; + +import org.junit.Test; +import org.mockito.Mock; + +public class IconUtilsTest { + private static final int MIN_ICON_SIZE = 16; + private static final int DESIRED_ICON_SIZE = 32; + @Mock + Context mContext; + + @Test + public void isIconSizedCorrectly() { + // Null bitmap is not sized correctly + assertThat(IconUtils.isIconSizeCorrect(null)).isFalse(); + + int minIconSize = MIN_ICON_SIZE; + int desiredIconSize = DESIRED_ICON_SIZE; + + // Bitmap that is 1x1 pixels is not sized correctly + Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); + assertThat(IconUtils.isIconSizeCorrect(icon)).isFalse(); + + // Bitmap is categorized as small, and not regular + icon = Bitmap.createBitmap(minIconSize + 1, + minIconSize + 1, Bitmap.Config.ALPHA_8); + assertThat(IconUtils.isIconSizeCorrect(icon)).isTrue(); + assertThat(IconUtils.isIconSizedSmall(icon)).isTrue(); + assertThat(IconUtils.isIconSizedRegular(icon)).isFalse(); + + // Bitmap is categorized as regular, but not small + icon = Bitmap.createBitmap(desiredIconSize + 1, + desiredIconSize + 1, Bitmap.Config.ALPHA_8); + assertThat(IconUtils.isIconSizeCorrect(icon)).isTrue(); + assertThat(IconUtils.isIconSizedSmall(icon)).isFalse(); + assertThat(IconUtils.isIconSizedRegular(icon)).isTrue(); + } + + @Test + public void testAddWhiteCircleBackground() { + int minIconSize = MIN_ICON_SIZE; + Bitmap icon = Bitmap.createBitmap(minIconSize + 1, minIconSize + 1, + Bitmap.Config.ALPHA_8); + + assertThat( + IconUtils.isIconSizeCorrect(IconUtils.addWhiteCircleBackground(mContext, icon))) + .isTrue(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/locator/LocatorTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/locator/LocatorTest.java new file mode 100644 index 0000000000..c3a4e5532f --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/locator/LocatorTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.locator; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.server.nearby.common.eventloop.EventLoop; +import com.android.server.nearby.fastpair.FastPairAdvHandler; +import com.android.server.nearby.fastpair.FastPairModule; +import com.android.server.nearby.fastpair.cache.FastPairCacheManager; +import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager; +import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockitoAnnotations; + +import java.time.Clock; + +import src.com.android.server.nearby.fastpair.testing.MockingLocator; + +public class LocatorTest { + private Locator mLocator; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mLocator = src.com.android.server.nearby.fastpair.testing.MockingLocator.withMocksOnly( + ApplicationProvider.getApplicationContext()); + mLocator.bind(new FastPairModule()); + } + + @Test + public void genericConstructor() { + assertThat(mLocator.get(FastPairCacheManager.class)).isNotNull(); + assertThat(mLocator.get(FootprintsDeviceManager.class)).isNotNull(); + assertThat(mLocator.get(EventLoop.class)).isNotNull(); + assertThat(mLocator.get(FastPairHalfSheetManager.class)).isNotNull(); + assertThat(mLocator.get(FastPairAdvHandler.class)).isNotNull(); + assertThat(mLocator.get(Clock.class)).isNotNull(); + } + + @Test + public void genericDestroy() { + mLocator.destroy(); + } + + @Test + public void getOptional() { + assertThat(mLocator.getOptional(FastPairModule.class)).isNotNull(); + mLocator.removeBindingForTest(FastPairModule.class); + assertThat(mLocator.getOptional(FastPairModule.class)).isNull(); + } + + @Test + public void getParent() { + assertThat(mLocator.getParent()).isNotNull(); + } + + @Test + public void getUnboundErrorMessage() { + assertThat(mLocator.getUnboundErrorMessage(FastPairModule.class)) + .isEqualTo( + "Unbound type: com.android.server.nearby.fastpair.FastPairModule\n" + + "Searched locators:\n" + "android.app.Application ->\n" + + "android.app.Application ->\n" + "android.app.Application"); + } + + @Test + public void getContextForTest() { + src.com.android.server.nearby.fastpair.testing.MockingLocator mockingLocator = + new MockingLocator(ApplicationProvider.getApplicationContext(), mLocator); + assertThat(mockingLocator.getContextForTest()).isNotNull(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/common/servicemonitor/PackageWatcherTest.java b/nearby/tests/unit/src/com/android/server/nearby/common/servicemonitor/PackageWatcherTest.java new file mode 100644 index 0000000000..eafc7db37b --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/common/servicemonitor/PackageWatcherTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.common.servicemonitor; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Intent; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Test; + +public class PackageWatcherTest { + private PackageWatcher mPackageWatcher = new PackageWatcher() { + @Override + public void onSomePackagesChanged() { + } + }; + + @Test + public void getPackageName() { + Intent intent = new Intent("Action", null); + assertThat(mPackageWatcher.getPackageName(intent)).isNull(); + } + + @Test + public void onReceive() { + Intent intent = new Intent(Intent.ACTION_PACKAGES_UNSUSPENDED, null); + mPackageWatcher.onReceive(ApplicationProvider.getApplicationContext(), intent); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java index 346a9614c9..900b618e3a 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairAdvHandlerTest.java @@ -16,24 +16,39 @@ package com.android.server.nearby.fastpair; +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.accounts.Account; import android.content.Context; import android.nearby.FastPairDevice; +import com.android.server.nearby.common.bloomfilter.BloomFilter; import com.android.server.nearby.common.locator.LocatorContextWrapper; +import com.android.server.nearby.fastpair.cache.DiscoveryItem; +import com.android.server.nearby.fastpair.cache.FastPairCacheManager; import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager; import com.android.server.nearby.fastpair.notification.FastPairNotificationManager; import com.android.server.nearby.provider.FastPairDataProvider; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.ByteString; + import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.time.Clock; +import java.util.List; + +import service.proto.Cache; +import service.proto.Data; import service.proto.Rpcs; public class FastPairAdvHandlerTest { @@ -45,11 +60,39 @@ public class FastPairAdvHandlerTest { private FastPairHalfSheetManager mFastPairHalfSheetManager; @Mock private FastPairNotificationManager mFastPairNotificationManager; + @Mock + private FastPairCacheManager mFastPairCacheManager; + @Mock + private FastPairController mFastPairController; + @Mock + private Data.FastPairDeviceWithAccountKey mFastPairDeviceWithAccountKey; + @Mock + private BloomFilter mBloomFilter; + @Mock + Cache.StoredFastPairItem mStoredFastPairItem; + @Mock private Clock mClock; + + private final Account mAccount = new Account("test1@gmail.com", "com.google"); + private static final byte[] ACCOUNT_KEY = + new byte[] {4, 65, 90, -26, -5, -38, -128, 40, -103, 101, 95, 55, 8, -42, -120, 78}; + private static final byte[] ACCOUNT_KEY_2 = new byte[] {0, 1, 2}; private static final String BLUETOOTH_ADDRESS = "AA:BB:CC:DD"; + private static final String MODEL_ID = "MODEL_ID"; private static final int CLOSE_RSSI = -80; private static final int FAR_AWAY_RSSI = -120; private static final int TX_POWER = -70; private static final byte[] INITIAL_BYTE_ARRAY = new byte[]{0x01, 0x02, 0x03}; + private static final byte[] SUBSEQUENT_DATA_BYTES = new byte[]{ + 0, -112, -63, 32, 37, -20, 36, 0, -60, 0, -96, 17, -10, 51, -28, -28, 100}; + private static final byte[] SUBSEQUENT_DATA_BYTES_INVALID = new byte[]{ + 0, -112, -63, 32, 37, -20, 48, 0, -60, 0, 90, 17, -10, 51, -28, -28, 100}; + private static final byte[] SALT = new byte[]{0x01}; + private static final Cache.StoredDiscoveryItem STORED_DISCOVERY_ITEM = + Cache.StoredDiscoveryItem.newBuilder() + .setDeviceName("Device Name") + .setTxPower(TX_POWER) + .setMacAddress(BLUETOOTH_ADDRESS) + .build(); LocatorContextWrapper mLocatorContextWrapper; FastPairAdvHandler mFastPairAdvHandler; @@ -65,8 +108,29 @@ public class FastPairAdvHandlerTest { mLocatorContextWrapper.getLocator().overrideBindingForTest( FastPairNotificationManager.class, mFastPairNotificationManager ); + mLocatorContextWrapper.getLocator().overrideBindingForTest( + FastPairCacheManager.class, mFastPairCacheManager + ); + mLocatorContextWrapper.getLocator().overrideBindingForTest( + FastPairController.class, mFastPairController); + mLocatorContextWrapper.getLocator().overrideBindingForTest(Clock.class, mClock); + when(mFastPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(any())) .thenReturn(Rpcs.GetObservedDeviceResponse.getDefaultInstance()); + when(mFastPairDataProvider.loadFastPairEligibleAccounts()).thenReturn(List.of(mAccount)); + when(mFastPairDataProvider.loadFastPairDeviceWithAccountKey(mAccount)) + .thenReturn(List.of(mFastPairDeviceWithAccountKey)); + when(mFastPairDataProvider.loadFastPairDeviceWithAccountKey(eq(mAccount), any())) + .thenReturn(List.of(mFastPairDeviceWithAccountKey)); + when(mFastPairDeviceWithAccountKey.getAccountKey()) + .thenReturn(ByteString.copyFrom(ACCOUNT_KEY)); + when(mFastPairDeviceWithAccountKey.getDiscoveryItem()) + .thenReturn(STORED_DISCOVERY_ITEM); + when(mStoredFastPairItem.getAccountKey()) + .thenReturn(ByteString.copyFrom(ACCOUNT_KEY_2), ByteString.copyFrom(ACCOUNT_KEY_2)); + when(mFastPairCacheManager.getAllSavedStoredFastPairItem()) + .thenReturn(List.of(mStoredFastPairItem)); + mFastPairAdvHandler = new FastPairAdvHandler(mLocatorContextWrapper, mFastPairDataProvider); } @@ -75,6 +139,7 @@ public class FastPairAdvHandlerTest { FastPairDevice fastPairDevice = new FastPairDevice.Builder() .setData(INITIAL_BYTE_ARRAY) .setBluetoothAddress(BLUETOOTH_ADDRESS) + .setModelId(MODEL_ID) .setRssi(CLOSE_RSSI) .setTxPower(TX_POWER) .build(); @@ -89,6 +154,7 @@ public class FastPairAdvHandlerTest { FastPairDevice fastPairDevice = new FastPairDevice.Builder() .setData(INITIAL_BYTE_ARRAY) .setBluetoothAddress(BLUETOOTH_ADDRESS) + .setModelId(MODEL_ID) .setRssi(FAR_AWAY_RSSI) .setTxPower(TX_POWER) .build(); @@ -99,37 +165,116 @@ public class FastPairAdvHandlerTest { } @Test - public void testSubsequentBroadcast() { - byte[] fastPairRecordWithBloomFilter = - new byte[]{ - (byte) 0x02, - (byte) 0x01, - (byte) 0x02, // Flags - (byte) 0x02, - (byte) 0x0A, - (byte) 0xEB, // Tx Power (-20) - (byte) 0x0B, - (byte) 0x16, - (byte) 0x2C, - (byte) 0xFE, // FastPair Service Data - (byte) 0x00, // Flags (model ID length = 3) - (byte) 0x40, // Account key hash flags (length = 4, type = 0) - (byte) 0x11, - (byte) 0x22, - (byte) 0x33, - (byte) 0x44, // Account key hash (0x11223344) - (byte) 0x11, // Account key salt flags (length = 1, type = 1) - (byte) 0x55, // Account key salt - }; + public void testSubsequentBroadcast_showNotification() { FastPairDevice fastPairDevice = new FastPairDevice.Builder() - .setData(fastPairRecordWithBloomFilter) + .setData(SUBSEQUENT_DATA_BYTES) .setBluetoothAddress(BLUETOOTH_ADDRESS) + .setModelId(MODEL_ID) .setRssi(CLOSE_RSSI) .setTxPower(TX_POWER) .build(); - mFastPairAdvHandler.handleBroadcast(fastPairDevice); + DiscoveryItem discoveryItem = + new DiscoveryItem(mLocatorContextWrapper, STORED_DISCOVERY_ITEM); + verify(mFastPairNotificationManager).showDiscoveryNotification(eq(discoveryItem), + eq(ACCOUNT_KEY)); verify(mFastPairHalfSheetManager, never()).showHalfSheet(any()); } + + @Test + public void testSubsequentBroadcast_tooFar_notShowNotification() { + FastPairDevice fastPairDevice = new FastPairDevice.Builder() + .setData(SUBSEQUENT_DATA_BYTES) + .setBluetoothAddress(BLUETOOTH_ADDRESS) + .setModelId(MODEL_ID) + .setRssi(FAR_AWAY_RSSI) + .setTxPower(TX_POWER) + .build(); + mFastPairAdvHandler.handleBroadcast(fastPairDevice); + + verify(mFastPairController, never()).pair(any(), any(), any()); + verify(mFastPairHalfSheetManager, never()).showHalfSheet(any()); + } + + @Test + public void testSubsequentBroadcast_notRecognize_notShowNotification() { + FastPairDevice fastPairDevice = new FastPairDevice.Builder() + .setData(SUBSEQUENT_DATA_BYTES_INVALID) + .setBluetoothAddress(BLUETOOTH_ADDRESS) + .setModelId(MODEL_ID) + .setRssi(FAR_AWAY_RSSI) + .setTxPower(TX_POWER) + .build(); + mFastPairAdvHandler.handleBroadcast(fastPairDevice); + + verify(mFastPairController, never()).pair(any(), any(), any()); + verify(mFastPairHalfSheetManager, never()).showHalfSheet(any()); + } + + @Test + public void testSubsequentBroadcast_cached_notShowNotification() { + when(mStoredFastPairItem.getAccountKey()) + .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY)); + + FastPairDevice fastPairDevice = new FastPairDevice.Builder() + .setData(SUBSEQUENT_DATA_BYTES_INVALID) + .setBluetoothAddress(BLUETOOTH_ADDRESS) + .setModelId(MODEL_ID) + .setRssi(FAR_AWAY_RSSI) + .setTxPower(TX_POWER) + .build(); + mFastPairAdvHandler.handleBroadcast(fastPairDevice); + + verify(mFastPairController, never()).pair(any(), any(), any()); + verify(mFastPairHalfSheetManager, never()).showHalfSheet(any()); + } + + @Test + public void testFindRecognizedDevice_bloomFilterNotContains_notFound() { + when(mFastPairDeviceWithAccountKey.getAccountKey()) + .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY)); + when(mBloomFilter.possiblyContains(any(byte[].class))).thenReturn(false); + + assertThat(FastPairAdvHandler.findRecognizedDevice( + ImmutableList.of(mFastPairDeviceWithAccountKey), mBloomFilter, SALT)).isNull(); + } + + @Test + public void testFindRecognizedDevice_bloomFilterContains_found() { + when(mFastPairDeviceWithAccountKey.getAccountKey()) + .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY)); + when(mBloomFilter.possiblyContains(any(byte[].class))).thenReturn(true); + + assertThat(FastPairAdvHandler.findRecognizedDevice( + ImmutableList.of(mFastPairDeviceWithAccountKey), mBloomFilter, SALT)).isNotNull(); + } + + @Test + public void testFindRecognizedDeviceFromCachedItem_bloomFilterNotContains_notFound() { + when(mStoredFastPairItem.getAccountKey()) + .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY)); + when(mBloomFilter.possiblyContains(any(byte[].class))).thenReturn(false); + + assertThat(FastPairAdvHandler.findRecognizedDeviceFromCachedItem( + ImmutableList.of(mStoredFastPairItem), mBloomFilter, SALT)).isNull(); + } + + @Test + public void testFindRecognizedDeviceFromCachedItem_bloomFilterContains_found() { + when(mStoredFastPairItem.getAccountKey()) + .thenReturn(ByteString.copyFrom(ACCOUNT_KEY), ByteString.copyFrom(ACCOUNT_KEY)); + when(mBloomFilter.possiblyContains(any(byte[].class))).thenReturn(true); + + assertThat(FastPairAdvHandler.findRecognizedDeviceFromCachedItem( + ImmutableList.of(mStoredFastPairItem), mBloomFilter, SALT)).isNotNull(); + } + + @Test + public void testGenerateBatteryData_correct() { + byte[] data = new byte[] + {0, -112, 96, 5, -125, 45, 35, 98, 98, 81, 13, 17, 3, 51, -28, -28, -28}; + assertThat(FastPairAdvHandler.generateBatteryData(data)) + .isEqualTo(new byte[]{51, -28, -28, -28}); + } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairManagerTest.java index 26d184770c..00df1b93e8 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairManagerTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FastPairManagerTest.java @@ -17,6 +17,7 @@ package com.android.server.nearby.fastpair; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -54,7 +55,7 @@ public class FastPairManagerTest { public void testFastPairInit() { mFastPairManager.initiate(); - verify(mContext, times(1)).registerReceiver(any(), any()); + verify(mContext, times(1)).registerReceiver(any(), any(), anyInt()); } @Test diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/FlagUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FlagUtilsTest.java new file mode 100644 index 0000000000..9cf65f46d2 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/FlagUtilsTest.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.fastpair; + +import org.junit.Test; + +public class FlagUtilsTest { + + @Test + public void testGetPreferencesBuilder_notCrash() { + FlagUtils.getPreferencesBuilder().build(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/DiscoveryItemTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/DiscoveryItemTest.java new file mode 100644 index 0000000000..5d4ea22963 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/DiscoveryItemTest.java @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.fastpair.cache; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.nearby.common.locator.LocatorContextWrapper; +import com.android.server.nearby.fastpair.FastPairManager; +import com.android.server.nearby.fastpair.testing.FakeDiscoveryItems; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import service.proto.Cache; + +/** Unit tests for {@link DiscoveryItem} */ +public class DiscoveryItemTest { + private static final String DEFAULT_MAC_ADDRESS = "00:11:22:33:44:55"; + private static final String DEFAULT_DESCRIPITON = "description"; + private static final long DEFAULT_TIMESTAMP = 1000000000L; + private static final String DEFAULT_TITLE = "title"; + private static final String APP_NAME = "app_name"; + private static final String ACTION_URL = + "intent:#Intent;action=com.android.server.nearby:ACTION_FAST_PAIR;" + + "package=com.google.android.gms;" + + "component=com.google.android.gms/" + + ".nearby.discovery.service.DiscoveryService;end"; + private static final String DISPLAY_URL = "DISPLAY_URL"; + private static final String TRIGGER_ID = "trigger.id"; + private static final String FAST_PAIR_ID = "id"; + private static final int RSSI = -80; + private static final int TX_POWER = -10; + + @Mock private Context mContext; + private LocatorContextWrapper mLocatorContextWrapper; + private FastPairCacheManager mFastPairCacheManager; + private FastPairManager mFastPairManager; + private DiscoveryItem mDiscoveryItem; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mLocatorContextWrapper = new LocatorContextWrapper(mContext); + mFastPairManager = new FastPairManager(mLocatorContextWrapper); + mFastPairCacheManager = mLocatorContextWrapper.getLocator().get(FastPairCacheManager.class); + when(mContext.getContentResolver()).thenReturn( + InstrumentationRegistry.getInstrumentation().getContext().getContentResolver()); + mDiscoveryItem = + FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper); + } + + @Test + public void testMultipleFields() { + assertThat(mDiscoveryItem.getId()).isEqualTo(FAST_PAIR_ID); + assertThat(mDiscoveryItem.getDescription()).isEqualTo(DEFAULT_DESCRIPITON); + assertThat(mDiscoveryItem.getDisplayUrl()).isEqualTo(DISPLAY_URL); + assertThat(mDiscoveryItem.getTriggerId()).isEqualTo(TRIGGER_ID); + assertThat(mDiscoveryItem.getMacAddress()).isEqualTo(DEFAULT_MAC_ADDRESS); + assertThat( + mDiscoveryItem.getFirstObservationTimestampMillis()).isEqualTo(DEFAULT_TIMESTAMP); + assertThat( + mDiscoveryItem.getLastObservationTimestampMillis()).isEqualTo(DEFAULT_TIMESTAMP); + assertThat(mDiscoveryItem.getActionUrl()).isEqualTo(ACTION_URL); + assertThat(mDiscoveryItem.getAppName()).isEqualTo(APP_NAME); + assertThat(mDiscoveryItem.getRssi()).isEqualTo(RSSI); + assertThat(mDiscoveryItem.getTxPower()).isEqualTo(TX_POWER); + assertThat(mDiscoveryItem.getFastPairInformation()).isNull(); + assertThat(mDiscoveryItem.getFastPairSecretKey()).isNull(); + assertThat(mDiscoveryItem.getIcon()).isNull(); + assertThat(mDiscoveryItem.getIconFifeUrl()).isNotNull(); + assertThat(mDiscoveryItem.getState()).isNotNull(); + assertThat(mDiscoveryItem.getTitle()).isNotNull(); + assertThat(mDiscoveryItem.isApp()).isFalse(); + assertThat(mDiscoveryItem.isDeletable( + 100000L, 0L)).isTrue(); + assertThat(mDiscoveryItem.isDeviceType(Cache.NearbyType.NEARBY_CHROMECAST)).isTrue(); + assertThat(mDiscoveryItem.isExpired( + 100000L, 0L)).isTrue(); + assertThat(mDiscoveryItem.isFastPair()).isTrue(); + assertThat(mDiscoveryItem.isPendingAppInstallValid(5)).isTrue(); + assertThat(mDiscoveryItem.isPendingAppInstallValid(5, + FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, null, + TRIGGER_ID, DEFAULT_MAC_ADDRESS, "", RSSI, TX_POWER))).isTrue(); + assertThat(mDiscoveryItem.isTypeEnabled(Cache.NearbyType.NEARBY_CHROMECAST)).isTrue(); + assertThat(mDiscoveryItem.toString()).isNotNull(); + } + + @Test + public void isMuted() { + assertThat(mDiscoveryItem.isMuted()).isFalse(); + } + + @Test + public void itemWithDefaultDescription_shouldShowUp() { + assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse(); + + // Null description should not show up. + mDiscoveryItem.setStoredItemForTest(DiscoveryItem.newStoredDiscoveryItem()); + mDiscoveryItem.setStoredItemForTest( + FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, null, + TRIGGER_ID, DEFAULT_MAC_ADDRESS, "", RSSI, TX_POWER)); + assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse(); + + // Empty description should not show up. + mDiscoveryItem.setStoredItemForTest( + FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, "", + TRIGGER_ID, DEFAULT_MAC_ADDRESS, DEFAULT_TITLE, RSSI, TX_POWER)); + assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse(); + } + + @Test + public void itemWithEmptyTitle_shouldNotShowUp() { + // Null title should not show up. + assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse(); + // Empty title should not show up. + mDiscoveryItem.setStoredItemForTest( + FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON, + TRIGGER_ID, DEFAULT_MAC_ADDRESS, "", RSSI, TX_POWER)); + assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse(); + + // Null title should not show up. + mDiscoveryItem.setStoredItemForTest( + FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON, + TRIGGER_ID, DEFAULT_MAC_ADDRESS, null, RSSI, TX_POWER)); + assertThat(mDiscoveryItem.isReadyForDisplay()).isFalse(); + } + + @Test + public void itemWithRssiAndTxPower_shouldHaveCorrectEstimatedDistance() { + assertThat(mDiscoveryItem.getEstimatedDistance()).isWithin(0.01).of(28.18); + } + + @Test + public void itemWithoutRssiOrTxPower_shouldNotHaveEstimatedDistance() { + mDiscoveryItem.setStoredItemForTest( + FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON, + TRIGGER_ID, DEFAULT_MAC_ADDRESS, "", 0, 0)); + assertThat(mDiscoveryItem.getEstimatedDistance()).isWithin(0.01).of(0); + } + + @Test + public void getUiHashCode_differentAddress_differentHash() { + mDiscoveryItem.setStoredItemForTest( + FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON, + TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER)); + DiscoveryItem compareTo = + FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper); + compareTo.setStoredItemForTest( + FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON, + TRIGGER_ID, "55:44:33:22:11:00", "", RSSI, TX_POWER)); + assertThat(mDiscoveryItem.getUiHashCode()).isNotEqualTo(compareTo.getUiHashCode()); + } + + @Test + public void getUiHashCode_sameAddress_sameHash() { + mDiscoveryItem.setStoredItemForTest( + FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON, + TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER)); + DiscoveryItem compareTo = + FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper); + compareTo.setStoredItemForTest( + FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON, + TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER)); + assertThat(mDiscoveryItem.getUiHashCode()).isEqualTo(compareTo.getUiHashCode()); + } + + @Test + public void isFastPair() { + DiscoveryItem fastPairItem = + FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper); + assertThat(fastPairItem.isFastPair()).isTrue(); + } + + @Test + public void testEqual() { + DiscoveryItem fastPairItem = + FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper); + assertThat(mDiscoveryItem.equals(fastPairItem)).isTrue(); + } + + @Test + public void testCompareTo() { + DiscoveryItem fastPairItem = + FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper); + assertThat(mDiscoveryItem.compareTo(fastPairItem)).isEqualTo(0); + } + + + @Test + public void testCopyOfStoredItem() { + DiscoveryItem fastPairItem = + FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper); + fastPairItem.setStoredItemForTest( + FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON, + TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER)); + assertThat(mDiscoveryItem.equals(fastPairItem)).isFalse(); + fastPairItem.setStoredItemForTest(mDiscoveryItem.getCopyOfStoredItem()); + assertThat(mDiscoveryItem.equals(fastPairItem)).isTrue(); + } + + @Test + public void testStoredItemForTest() { + DiscoveryItem fastPairItem = + FakeDiscoveryItems.newFastPairDiscoveryItem(mLocatorContextWrapper); + fastPairItem.setStoredItemForTest( + FakeDiscoveryItems.newFastPairDeviceStoredItem(FAST_PAIR_ID, DEFAULT_DESCRIPITON, + TRIGGER_ID, "00:11:22:33:44:55", "", RSSI, TX_POWER)); + assertThat(mDiscoveryItem.equals(fastPairItem)).isFalse(); + fastPairItem.setStoredItemForTest(mDiscoveryItem.getStoredItemForTest()); + assertThat(mDiscoveryItem.equals(fastPairItem)).isTrue(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java index fdda6f70ec..18f2cf695c 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairCacheManagerTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; +import android.bluetooth.le.ScanResult; import android.content.Context; import androidx.test.core.app.ApplicationProvider; @@ -50,6 +51,10 @@ public class FastPairCacheManagerTest { DiscoveryItem mDiscoveryItem2; @Mock Cache.StoredFastPairItem mStoredFastPairItem; + @Mock + ScanResult mScanResult; + + Context mContext; Cache.StoredDiscoveryItem mStoredDiscoveryItem = Cache.StoredDiscoveryItem.newBuilder() .setTriggerId(MODEL_ID) .setAppName(APP_NAME).build(); @@ -60,12 +65,12 @@ public class FastPairCacheManagerTest { @Before public void setup() { MockitoAnnotations.initMocks(this); + mContext = ApplicationProvider.getApplicationContext(); } @Test @SdkSuppress(minSdkVersion = 32, codeName = "T") public void notSaveRetrieveInfo() { - Context mContext = ApplicationProvider.getApplicationContext(); when(mDiscoveryItem.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem); when(mDiscoveryItem.getTriggerId()).thenReturn(MODEL_ID); @@ -78,7 +83,6 @@ public class FastPairCacheManagerTest { @Test @SdkSuppress(minSdkVersion = 32, codeName = "T") public void saveRetrieveInfo() { - Context mContext = ApplicationProvider.getApplicationContext(); when(mDiscoveryItem.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem); when(mDiscoveryItem.getTriggerId()).thenReturn(MODEL_ID); when(mDiscoveryItem2.getCopyOfStoredItem()).thenReturn(mStoredDiscoveryItem2); @@ -100,7 +104,6 @@ public class FastPairCacheManagerTest { @Test @SdkSuppress(minSdkVersion = 32, codeName = "T") public void saveRetrieveInfoStoredFastPairItem() { - Context mContext = ApplicationProvider.getApplicationContext(); Cache.StoredFastPairItem storedFastPairItem = Cache.StoredFastPairItem.newBuilder() .setMacAddress(MAC_ADDRESS) .setAccountKey(ACCOUNT_KEY) @@ -118,7 +121,6 @@ public class FastPairCacheManagerTest { @Test @SdkSuppress(minSdkVersion = 32, codeName = "T") public void checkGetAllFastPairItems() { - Context mContext = ApplicationProvider.getApplicationContext(); Cache.StoredFastPairItem storedFastPairItem = Cache.StoredFastPairItem.newBuilder() .setMacAddress(MAC_ADDRESS) .setAccountKey(ACCOUNT_KEY) @@ -139,5 +141,15 @@ public class FastPairCacheManagerTest { assertThat(fastPairCacheManager.getAllSavedStoredFastPairItem().size()) .isEqualTo(1); + + fastPairCacheManager.cleanUp(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void getDeviceFromScanResult_notCrash() { + FastPairCacheManager fastPairCacheManager = new FastPairCacheManager(mContext); + fastPairCacheManager.getDeviceFromScanResult(mScanResult); + } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairDbHelperTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairDbHelperTest.java new file mode 100644 index 0000000000..c5428f588a --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/cache/FastPairDbHelperTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.fastpair.cache; + +import static org.junit.Assert.assertThrows; + +import android.content.Context; +import android.database.sqlite.SQLiteException; + +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockitoAnnotations; + +public class FastPairDbHelperTest { + + Context mContext; + FastPairDbHelper mFastPairDbHelper; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContext = InstrumentationRegistry.getInstrumentation().getContext(); + mFastPairDbHelper = new FastPairDbHelper(mContext); + } + + @After + public void teardown() { + mFastPairDbHelper.close(); + } + + @Test + public void testUpgrade_notCrash() { + mFastPairDbHelper + .onUpgrade(mFastPairDbHelper.getWritableDatabase(), 1, 2); + } + + @Test + public void testDowngrade_throwsException() { + assertThrows( + SQLiteException.class, + () -> mFastPairDbHelper.onDowngrade( + mFastPairDbHelper.getWritableDatabase(), 2, 1)); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetBlocklistTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetBlocklistTest.java new file mode 100644 index 0000000000..f3afbe76b8 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetBlocklistTest.java @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.fastpair.halfsheet; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import com.android.server.nearby.fastpair.blocklist.Blocklist; +import com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState; +import com.android.server.nearby.fastpair.blocklist.BlocklistElement; +import com.android.server.nearby.util.DefaultClock; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class FastPairHalfSheetBlocklistTest { + + @Mock + private DefaultClock mClock; + private FastPairHalfSheetBlocklist mFastPairHalfSheetBlocklist; + private static final int SIZE_OF_BLOCKLIST = 2; + private static final long CURRENT_TIME = 1000000L; + private static final long BLOCKLIST_CANCEL_TIMEOUT_MILLIS = 30000L; + private static final long SUPPRESS_ALL_DURATION_MILLIS = 60000L; + private static final long DURATION_RESURFACE_DISMISS_HALF_SHEET_MILLISECOND = 86400000; + private static final long STATE_EXPIRATION_MILLISECOND = 86400000; + private static final int HALFSHEET_ID = 1; + private static final long DURATION_MILLI_SECONDS_LONG = 86400000; + private static final int DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS = 1; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mClock.elapsedRealtime()).thenReturn(CURRENT_TIME); + mFastPairHalfSheetBlocklist = new FastPairHalfSheetBlocklist(SIZE_OF_BLOCKLIST, mClock); + } + + @Test + public void testUpdateState() { + mFastPairHalfSheetBlocklist.put(HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.ACTIVE, CURRENT_TIME)); + + boolean initiallyBlocklisted = + mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS); + + mFastPairHalfSheetBlocklist.updateState(HALFSHEET_ID, Blocklist.BlocklistState.ACTIVE); + boolean isBlockListedWhenActive = + mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS); + + mFastPairHalfSheetBlocklist.updateState(HALFSHEET_ID, Blocklist.BlocklistState.DISMISSED); + boolean isBlockListedAfterDismissed = + mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS); + + mFastPairHalfSheetBlocklist.updateState(HALFSHEET_ID, + Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN); + boolean isBlockListedAfterDoNotShowAgain = + mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS); + + mFastPairHalfSheetBlocklist.updateState(HALFSHEET_ID, + Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN_LONG); + boolean isBlockListedAfterDoNotShowAgainLong = + mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS); + + assertThat(initiallyBlocklisted).isFalse(); + assertThat(isBlockListedWhenActive).isFalse(); + assertThat(isBlockListedAfterDismissed).isTrue(); + assertThat(isBlockListedAfterDoNotShowAgain).isTrue(); + assertThat(isBlockListedAfterDoNotShowAgainLong).isTrue(); + } + + @Test + public void testBlocklist_overflow() { + mFastPairHalfSheetBlocklist.put(HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.DISMISSED, CURRENT_TIME)); + mFastPairHalfSheetBlocklist.put(HALFSHEET_ID + 1, + new BlocklistElement(Blocklist.BlocklistState.UNKNOWN, CURRENT_TIME)); + mFastPairHalfSheetBlocklist.put(HALFSHEET_ID + 2, + new BlocklistElement(Blocklist.BlocklistState.UNKNOWN, CURRENT_TIME)); + + // blocklist should have evicted HALFSHEET_ID making it no longer blocklisted, this is + // because for the test we initialize the size of the blocklist cache to be max = 2 + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse(); + } + + @Test + public void removeHalfSheetDismissState() { + mFastPairHalfSheetBlocklist.put(HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.DISMISSED, CURRENT_TIME)); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue(); + + mFastPairHalfSheetBlocklist.put( + HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME)); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue(); + assertThat(mFastPairHalfSheetBlocklist.removeBlocklist(HALFSHEET_ID)).isTrue(); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse(); + assertThat(mFastPairHalfSheetBlocklist.removeBlocklist(HALFSHEET_ID + 1)).isFalse(); + } + + @Test + public void removeHalfSheetBanState() { + mFastPairHalfSheetBlocklist.put( + HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME)); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue(); + assertThat(mFastPairHalfSheetBlocklist.removeBlocklist(HALFSHEET_ID)).isTrue(); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse(); + assertThat(mFastPairHalfSheetBlocklist.removeBlocklist(HALFSHEET_ID + 1)).isFalse(); + } + + @Test + public void testHalfSheetTimeOutReleaseBan() { + mFastPairHalfSheetBlocklist.put( + HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME)); + when(mClock.elapsedRealtime()) + .thenReturn(CURRENT_TIME + BLOCKLIST_CANCEL_TIMEOUT_MILLIS + 1); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse(); + } + + @Test + public void testHalfSheetDoNotShowAgainLong() { + mFastPairHalfSheetBlocklist.put( + HALFSHEET_ID, + new BlocklistElement( + Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN_LONG, CURRENT_TIME)); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue(); + assertThat(mFastPairHalfSheetBlocklist.removeBlocklist(HALFSHEET_ID)).isTrue(); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse(); + assertThat(mFastPairHalfSheetBlocklist.removeBlocklist(HALFSHEET_ID + 1)).isFalse(); + } + + @Test + public void testHalfSheetDoNotShowAgainLongTimeout() { + mFastPairHalfSheetBlocklist.put( + HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME)); + when(mClock.elapsedRealtime()).thenReturn(CURRENT_TIME + DURATION_MILLI_SECONDS_LONG + 1); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse(); + } + + @Test + public void banAllItem_blockHalfSheet() { + mFastPairHalfSheetBlocklist.put(HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.ACTIVE, CURRENT_TIME)); + + mFastPairHalfSheetBlocklist.banAllItem(SUPPRESS_ALL_DURATION_MILLIS); + when(mClock.elapsedRealtime()).thenReturn(CURRENT_TIME + SUPPRESS_ALL_DURATION_MILLIS - 1); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue(); + } + + @Test + public void banAllItem_invokeAgainWithShorterDurationTime_blockHalfSheet() { + mFastPairHalfSheetBlocklist.put(HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.ACTIVE, CURRENT_TIME)); + + mFastPairHalfSheetBlocklist.banAllItem(SUPPRESS_ALL_DURATION_MILLIS); + // The 2nd invocation time is shorter than the original one so it's ignored. + mFastPairHalfSheetBlocklist.banAllItem(SUPPRESS_ALL_DURATION_MILLIS - 1); + when(mClock.elapsedRealtime()).thenReturn(CURRENT_TIME + SUPPRESS_ALL_DURATION_MILLIS - 1); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue(); + } + + @Test + public void banAllItem_releaseHalfSheet() { + mFastPairHalfSheetBlocklist.put(HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.ACTIVE, CURRENT_TIME)); + + mFastPairHalfSheetBlocklist.banAllItem(SUPPRESS_ALL_DURATION_MILLIS); + when(mClock.elapsedRealtime()).thenReturn(CURRENT_TIME + SUPPRESS_ALL_DURATION_MILLIS); + + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse(); + } + + @Test + public void banAllItem_extendEndTime_blockHalfSheet() { + mFastPairHalfSheetBlocklist.put(HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.ACTIVE, CURRENT_TIME)); + + mFastPairHalfSheetBlocklist.banAllItem(SUPPRESS_ALL_DURATION_MILLIS); + when(mClock.elapsedRealtime()).thenReturn(CURRENT_TIME + SUPPRESS_ALL_DURATION_MILLIS); + // Another banAllItem comes so the end time is extended. + mFastPairHalfSheetBlocklist.banAllItem(/* banDurationTimeMillis= */ 1); + + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue(); + } + + @Test + public void testHalfSheetTimeOutFirstDismissWithInDuration() { + mFastPairHalfSheetBlocklist.put( + HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME)); + when(mClock.elapsedRealtime()) + .thenReturn(CURRENT_TIME + DURATION_RESURFACE_DISMISS_HALF_SHEET_MILLISECOND - 1); + + assertThat( + mFastPairHalfSheetBlocklist.isBlocklisted( + HALFSHEET_ID, (int) DURATION_RESURFACE_DISMISS_HALF_SHEET_MILLISECOND)) + .isTrue(); + } + + @Test + public void testHalfSheetTimeOutFirstDismissOutOfDuration() { + mFastPairHalfSheetBlocklist.put( + HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME)); + when(mClock.elapsedRealtime()) + .thenReturn(CURRENT_TIME + DURATION_RESURFACE_DISMISS_HALF_SHEET_MILLISECOND + 1); + + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse(); + } + + @Test + public void testHalfSheetReset() { + mFastPairHalfSheetBlocklist.put( + HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME)); + mFastPairHalfSheetBlocklist.resetBlockState(HALFSHEET_ID); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse(); + } + + @Test + public void testIsStateExpired() { + mFastPairHalfSheetBlocklist.put( + HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME)); + when(mClock.elapsedRealtime()) + .thenReturn(CURRENT_TIME + 1); + assertThat(mFastPairHalfSheetBlocklist.isStateExpired(HALFSHEET_ID)).isFalse(); + when(mClock.elapsedRealtime()) + .thenReturn(CURRENT_TIME + STATE_EXPIRATION_MILLISECOND + 1); + assertThat(mFastPairHalfSheetBlocklist.isStateExpired(HALFSHEET_ID)).isTrue(); + } + + @Test + public void testForceUpdateState() { + mFastPairHalfSheetBlocklist.put(HALFSHEET_ID, + new BlocklistElement(Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN, CURRENT_TIME)); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isTrue(); + mFastPairHalfSheetBlocklist.forceUpdateState(HALFSHEET_ID, BlocklistState.ACTIVE); + assertThat(mFastPairHalfSheetBlocklist.isBlocklisted(HALFSHEET_ID, + DURATION_RESURFACE_HALFSHEET_FIRST_DISMISS_MILLI_SECONDS)).isFalse(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java index 58e4c47e4c..82b9070a57 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/halfsheet/FastPairHalfSheetManagerTest.java @@ -16,22 +16,40 @@ package com.android.server.nearby.fastpair.halfsheet; +import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.ACTIVE; +import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DISMISSED; +import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN; +import static com.android.server.nearby.fastpair.blocklist.Blocklist.BlocklistState.DO_NOT_SHOW_AGAIN_LONG; +import static com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager.DISMISS_HALFSHEET_RUNNABLE_NAME; +import static com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager.SHOW_TOAST_RUNNABLE_NAME; + +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.nearby.FastPairStatusCallback; +import android.nearby.PairStatusMetadata; import android.os.UserHandle; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.nearby.common.eventloop.EventLoop; +import com.android.server.nearby.common.eventloop.NamedRunnable; import com.android.server.nearby.common.locator.Locator; import com.android.server.nearby.common.locator.LocatorContextWrapper; import com.android.server.nearby.fastpair.FastPairController; @@ -48,53 +66,67 @@ import java.util.List; import service.proto.Cache; public class FastPairHalfSheetManagerTest { - private static final String BLEADDRESS = "11:22:44:66"; + private static final String MODEL_ID = "model_id"; + private static final String BLE_ADDRESS = "11:22:44:66"; + private static final String MODEL_ID_1 = "model_id_1"; + private static final String BLE_ADDRESS_1 = "99:99:99:99"; private static final String NAME = "device_name"; + private static final int PASSKEY = 1234; + private static final int SUCCESS = 1001; + private static final int FAIL = 1002; + private static final String EXTRA_HALF_SHEET_CONTENT = + "com.android.nearby.halfsheet.HALF_SHEET_CONTENT"; + private static final String RESULT_FAIL = "RESULT_FAIL"; private FastPairHalfSheetManager mFastPairHalfSheetManager; private Cache.ScanFastPairStoreItem mScanFastPairStoreItem; + private ResolveInfo mResolveInfo; + private List mResolveInfoList; + private ApplicationInfo mApplicationInfo; + @Mock private Context mContext; @Mock LocatorContextWrapper mContextWrapper; @Mock - ResolveInfo mResolveInfo; - @Mock PackageManager mPackageManager; @Mock Locator mLocator; @Mock FastPairController mFastPairController; + @Mock + EventLoop mEventLoop; + @Mock + FastPairStatusCallback mFastPairStatusCallback; @Before public void setup() { MockitoAnnotations.initMocks(this); + mLocator.overrideBindingForTest(FastPairController.class, mFastPairController); + mLocator.overrideBindingForTest(EventLoop.class, mEventLoop); + + mResolveInfo = new ResolveInfo(); + mResolveInfoList = new ArrayList<>(); + mResolveInfo.activityInfo = new ActivityInfo(); + mApplicationInfo = new ApplicationInfo(); + mPackageManager = mock(PackageManager.class); + + when(mContext.getContentResolver()).thenReturn( + InstrumentationRegistry.getInstrumentation().getContext().getContentResolver()); + when(mContextWrapper.getPackageManager()).thenReturn(mPackageManager); + when(mContextWrapper.getLocator()).thenReturn(mLocator); + when(mLocator.get(EventLoop.class)).thenReturn(mEventLoop); + when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(mResolveInfoList); + when(mPackageManager.canRequestPackageInstalls()).thenReturn(false); mScanFastPairStoreItem = Cache.ScanFastPairStoreItem.newBuilder() - .setAddress(BLEADDRESS) + .setModelId(MODEL_ID) + .setAddress(BLE_ADDRESS) .setDeviceName(NAME) .build(); } @Test public void verifyFastPairHalfSheetManagerBehavior() { - mLocator.overrideBindingForTest(FastPairController.class, mFastPairController); - ResolveInfo resolveInfo = new ResolveInfo(); - List resolveInfoList = new ArrayList<>(); - - mPackageManager = mock(PackageManager.class); - when(mContextWrapper.getPackageManager()).thenReturn(mPackageManager); - resolveInfo.activityInfo = new ActivityInfo(); - ApplicationInfo applicationInfo = new ApplicationInfo(); - applicationInfo.sourceDir = "/apex/com.android.tethering"; - applicationInfo.packageName = "test.package"; - resolveInfo.activityInfo.applicationInfo = applicationInfo; - resolveInfoList.add(resolveInfo); - when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(resolveInfoList); - when(mPackageManager.canRequestPackageInstalls()).thenReturn(false); - - mFastPairHalfSheetManager = - new FastPairHalfSheetManager(mContextWrapper); - - when(mContextWrapper.getLocator()).thenReturn(mLocator); - + configResolveInfoList(); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); @@ -105,27 +137,12 @@ public class FastPairHalfSheetManagerTest { @Test public void verifyFastPairHalfSheetManagerHalfSheetApkNotValidBehavior() { - mLocator.overrideBindingForTest(FastPairController.class, mFastPairController); - ResolveInfo resolveInfo = new ResolveInfo(); - List resolveInfoList = new ArrayList<>(); - - mPackageManager = mock(PackageManager.class); - when(mContextWrapper.getPackageManager()).thenReturn(mPackageManager); - resolveInfo.activityInfo = new ActivityInfo(); - ApplicationInfo applicationInfo = new ApplicationInfo(); // application directory is wrong - applicationInfo.sourceDir = "/apex/com.android.nearby"; - applicationInfo.packageName = "test.package"; - resolveInfo.activityInfo.applicationInfo = applicationInfo; - resolveInfoList.add(resolveInfo); - when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(resolveInfoList); - when(mPackageManager.canRequestPackageInstalls()).thenReturn(false); - - mFastPairHalfSheetManager = - new FastPairHalfSheetManager(mContextWrapper); - - when(mContextWrapper.getLocator()).thenReturn(mLocator); - + mApplicationInfo.sourceDir = "/apex/com.android.nearby"; + mApplicationInfo.packageName = "test.package"; + mResolveInfo.activityInfo.applicationInfo = mApplicationInfo; + mResolveInfoList.add(mResolveInfo); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); ArgumentCaptor intentArgumentCaptor = ArgumentCaptor.forClass(Intent.class); mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); @@ -133,4 +150,420 @@ public class FastPairHalfSheetManagerTest { verify(mContextWrapper, never()) .startActivityAsUser(intentArgumentCaptor.capture(), eq(UserHandle.CURRENT)); } + + @Test + public void testHalfSheetForegroundState() { + configResolveInfoList(); + mFastPairHalfSheetManager = + new FastPairHalfSheetManager(mContextWrapper); + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + assertThat(mFastPairHalfSheetManager.getHalfSheetForeground()).isTrue(); + mFastPairHalfSheetManager.dismiss(MODEL_ID); + assertThat(mFastPairHalfSheetManager.getHalfSheetForeground()).isFalse(); + } + + @Test + public void testEmptyMethods() { + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + mFastPairHalfSheetManager.destroyBluetoothPairController(); + mFastPairHalfSheetManager.notifyPairingProcessDone(true, BLE_ADDRESS, null); + mFastPairHalfSheetManager.showPairingFailed(); + mFastPairHalfSheetManager.showPairingHalfSheet(null); + mFastPairHalfSheetManager.showPairingSuccessHalfSheet(BLE_ADDRESS); + mFastPairHalfSheetManager.showPasskeyConfirmation(null, PASSKEY); + } + + @Test + public void showInitialPairingHalfSheetThenDismissOnce_stateDISMISSED() { + configResolveInfoList(); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + FastPairHalfSheetBlocklist mHalfSheetBlocklist = + mFastPairHalfSheetManager.getHalfSheetBlocklist(); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + mFastPairHalfSheetManager.dismiss(MODEL_ID); + + Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID); + + //First time dismiss -> state: DISMISSED + assertThat(mHalfSheetBlocklist.get(halfSheetId).getState()).isEqualTo(DISMISSED); + } + + @Test + public void showInitialPairingHalfSheetThenBan_stateDO_NOT_SHOW_AGAIN() { + configResolveInfoList(); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + FastPairHalfSheetBlocklist mHalfSheetBlocklist = + mFastPairHalfSheetManager.getHalfSheetBlocklist(); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + mFastPairHalfSheetManager.dismiss(MODEL_ID); + mFastPairHalfSheetManager.dismiss(MODEL_ID); + + Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID); + + //First time ban -> state: DO_NOT_SHOW_AGAIN + assertThat(mHalfSheetBlocklist.get(halfSheetId).getState()).isEqualTo(DO_NOT_SHOW_AGAIN); + } + + @Test + public void showInitialPairingHalfSheetThenBanTwice_stateDO_NOT_SHOW_AGAIN_LONG() { + configResolveInfoList(); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + FastPairHalfSheetBlocklist mHalfSheetBlocklist = + mFastPairHalfSheetManager.getHalfSheetBlocklist(); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + mFastPairHalfSheetManager.dismiss(MODEL_ID); + mFastPairHalfSheetManager.dismiss(MODEL_ID); + mFastPairHalfSheetManager.dismiss(MODEL_ID); + + Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID); + + //Second time ban -> state: DO_NOT_SHOW_AGAIN + assertThat(mHalfSheetBlocklist.get(halfSheetId).getState()) + .isEqualTo(DO_NOT_SHOW_AGAIN_LONG); + } + + @Test + public void testResetBanSate_resetDISMISSEDtoACTIVE() { + configResolveInfoList(); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + FastPairHalfSheetBlocklist mHalfSheetBlocklist = + mFastPairHalfSheetManager.getHalfSheetBlocklist(); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + + Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID); + + mHalfSheetBlocklist.updateState(halfSheetId, DISMISSED); + mFastPairHalfSheetManager.resetBanState(MODEL_ID); + + assertThat(mHalfSheetBlocklist.get(halfSheetId).getState()).isEqualTo(ACTIVE); + } + + @Test + public void testResetBanSate_resetDO_NOT_SHOW_AGAINtoACTIVE() { + configResolveInfoList(); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + FastPairHalfSheetBlocklist mHalfSheetBlocklist = + mFastPairHalfSheetManager.getHalfSheetBlocklist(); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + + Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID); + + mHalfSheetBlocklist.updateState(halfSheetId, DO_NOT_SHOW_AGAIN); + mFastPairHalfSheetManager.resetBanState(MODEL_ID); + + assertThat(mHalfSheetBlocklist.get(halfSheetId).getState()).isEqualTo(ACTIVE); + } + + @Test + public void testResetBanSate_resetDO_NOT_SHOW_AGAIN_LONGtoACTIVE() { + configResolveInfoList(); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + FastPairHalfSheetBlocklist mHalfSheetBlocklist = + mFastPairHalfSheetManager.getHalfSheetBlocklist(); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + + Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID); + + mHalfSheetBlocklist.updateState(halfSheetId, DO_NOT_SHOW_AGAIN_LONG + ); + mFastPairHalfSheetManager.resetBanState(MODEL_ID); + + assertThat(mHalfSheetBlocklist.get(halfSheetId).getState()).isEqualTo(ACTIVE); + } + + @Test + public void testReportDonePairing() { + configResolveInfoList(); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + + assertThat(mFastPairHalfSheetManager.getHalfSheetBlocklist().size()).isEqualTo(1); + + mFastPairHalfSheetManager + .reportDonePairing(mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID)); + + assertThat(mFastPairHalfSheetManager.getHalfSheetBlocklist().size()).isEqualTo(0); + } + + @Test + public void showInitialPairingHalfSheet_AutoDismiss() throws InterruptedException { + configResolveInfoList(); + mFastPairHalfSheetManager = + new FastPairHalfSheetManager(mContextWrapper); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + + verifyInitialPairingNameRunnablePostedTimes(1); + } + + @Test + public void showInitialPairingHalfSheet_whenUiShownAndItemWithTheSameAddress() { + Cache.ScanFastPairStoreItem testItem = Cache.ScanFastPairStoreItem.newBuilder() + .setModelId(MODEL_ID) + .setAddress(BLE_ADDRESS) + .setDeviceName(NAME) + .build(); + configResolveInfoList(); + mFastPairHalfSheetManager = + new FastPairHalfSheetManager(mContextWrapper); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + + verifyHalfSheetActivityIntent(1); + verifyInitialPairingNameRunnablePostedTimes(1); + + mFastPairHalfSheetManager.showHalfSheet(testItem); + // When half sheet shown and receives broadcast from the same address, + // DO NOT request start-activity to avoid unnecessary memory usage, + // Just reset the auto dismiss timeout for the new request + verifyHalfSheetActivityIntent(1); + verifyInitialPairingNameRunnablePostedTimes(2); + } + + @Test + public void showInitialPairingHalfSheet_whenUiShowAndItemWithDifferentAddressSameModelId() { + Cache.ScanFastPairStoreItem testItem = Cache.ScanFastPairStoreItem.newBuilder() + .setModelId(MODEL_ID) + .setAddress(BLE_ADDRESS_1) + .setDeviceName(NAME) + .build(); + configResolveInfoList(); + mFastPairHalfSheetManager = + new FastPairHalfSheetManager(mContextWrapper); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + + verifyHalfSheetActivityIntent(1); + verifyInitialPairingNameRunnablePostedTimes(1); + + mFastPairHalfSheetManager.showHalfSheet(testItem); + // When half sheet shown and receives broadcast from the same model id + // but with different address, DO NOT rest the auto dismiss timeout. No action is required. + verifyHalfSheetActivityIntent(1); + verifyInitialPairingNameRunnablePostedTimes(1); + } + + @Test + public void showInitialPairingHalfSheet_whenUiShowAndItemWithDifferentModelId() { + Cache.ScanFastPairStoreItem testItem = Cache.ScanFastPairStoreItem.newBuilder() + .setModelId(MODEL_ID_1) + .setAddress(BLE_ADDRESS_1) + .setDeviceName(NAME) + .build(); + configResolveInfoList(); + mFastPairHalfSheetManager = + new FastPairHalfSheetManager(mContextWrapper); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + + verifyInitialPairingNameRunnablePostedTimes(1); + verifyHalfSheetActivityIntent(1); + + mFastPairHalfSheetManager.showHalfSheet(testItem); + // When half sheet shown and receives broadcast from a different model id, + // the new request should be ignored. No action is required. + verifyHalfSheetActivityIntent(1); + verifyInitialPairingNameRunnablePostedTimes(1); + } + + @Test + public void showInitialPairingHalfSheet_whenUiNotShownAndIsPairingWithTheSameAddress() { + Cache.ScanFastPairStoreItem testItem = Cache.ScanFastPairStoreItem.newBuilder() + .setModelId(MODEL_ID) + .setAddress(BLE_ADDRESS) + .setDeviceName(NAME) + .build(); + configResolveInfoList(); + mFastPairHalfSheetManager = + new FastPairHalfSheetManager(mContextWrapper); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + mFastPairHalfSheetManager.setHalfSheetForeground(/* state= */ false); + mFastPairHalfSheetManager.setIsActivePairing(true); + mFastPairHalfSheetManager.showHalfSheet(testItem); + + // If the half sheet is not in foreground but the system is still pairing the same device, + // mark as duplicate request and skip. + verifyHalfSheetActivityIntent(1); + verifyInitialPairingNameRunnablePostedTimes(1); + } + + @Test + public void showInitialPairingHalfSheet_whenUiNotShownAndIsPairingWithADifferentAddress() { + Cache.ScanFastPairStoreItem testItem = Cache.ScanFastPairStoreItem.newBuilder() + .setModelId(MODEL_ID_1) + .setAddress(BLE_ADDRESS_1) + .setDeviceName(NAME) + .build(); + configResolveInfoList(); + mFastPairHalfSheetManager = + new FastPairHalfSheetManager(mContextWrapper); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + mFastPairHalfSheetManager.setHalfSheetForeground(/* state= */ false); + mFastPairHalfSheetManager.setIsActivePairing(true); + mFastPairHalfSheetManager.showHalfSheet(testItem); + + // shouldShowHalfSheet + verifyHalfSheetActivityIntent(2); + verifyInitialPairingNameRunnablePostedTimes(2); + } + + @Test + public void showInitialPairingHalfSheet_whenUiNotShownAndIsNotPairingWithTheSameAddress() { + Cache.ScanFastPairStoreItem testItem = Cache.ScanFastPairStoreItem.newBuilder() + .setModelId(MODEL_ID) + .setAddress(BLE_ADDRESS) + .setDeviceName(NAME) + .build(); + configResolveInfoList(); + mFastPairHalfSheetManager = + new FastPairHalfSheetManager(mContextWrapper); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + mFastPairHalfSheetManager.setHalfSheetForeground(/* state= */ false); + mFastPairHalfSheetManager.setIsActivePairing(false); + mFastPairHalfSheetManager.showHalfSheet(testItem); + + // shouldShowHalfSheet + verifyHalfSheetActivityIntent(2); + verifyInitialPairingNameRunnablePostedTimes(2); + } + + @Test + public void testReportActivelyPairing() { + configResolveInfoList(); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + + assertThat(mFastPairHalfSheetManager.isActivePairing()).isFalse(); + + mFastPairHalfSheetManager.reportActivelyPairing(); + + assertThat(mFastPairHalfSheetManager.isActivePairing()).isTrue(); + } + + @Test + public void showPairingSuccessHalfSheetHalfSheetActivityActive_ChangeUIToShowSuccessInfo() { + configResolveInfoList(); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + mFastPairHalfSheetManager.mFastPairUiService + .setFastPairStatusCallback(mFastPairStatusCallback); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + mFastPairHalfSheetManager.showPairingSuccessHalfSheet(BLE_ADDRESS); + + verifyFastPairStatusCallback(1, SUCCESS); + assertThat(mFastPairHalfSheetManager.isActivePairing()).isFalse(); + } + + @Test + public void showPairingSuccessHalfSheetHalfSheetActivityNotActive_showToast() { + configResolveInfoList(); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + mFastPairHalfSheetManager.setHalfSheetForeground(false); + mFastPairHalfSheetManager.showPairingSuccessHalfSheet(BLE_ADDRESS); + + ArgumentCaptor captor = ArgumentCaptor.forClass(NamedRunnable.class); + + verify(mEventLoop).postRunnable(captor.capture()); + assertThat( + captor.getAllValues().stream() + .filter(r -> r.name.equals(SHOW_TOAST_RUNNABLE_NAME)) + .count()) + .isEqualTo(1); + assertThat(mFastPairHalfSheetManager.isActivePairing()).isFalse(); + } + + @Test + public void showPairingFailedHalfSheetHalfSheetActivityActive_ChangeUIToShowFailedInfo() { + configResolveInfoList(); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + mFastPairHalfSheetManager.mFastPairUiService + .setFastPairStatusCallback(mFastPairStatusCallback); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + mFastPairHalfSheetManager.showPairingFailed(); + + verifyFastPairStatusCallback(1, FAIL); + assertThat(mFastPairHalfSheetManager.isActivePairing()).isFalse(); + } + + @Test + public void showPairingFailedHalfSheetActivityNotActive_StartHalfSheetToShowFailedInfo() { + configResolveInfoList(); + mFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + FastPairHalfSheetBlocklist mHalfSheetBlocklist = + mFastPairHalfSheetManager.getHalfSheetBlocklist(); + + mFastPairHalfSheetManager.showHalfSheet(mScanFastPairStoreItem); + mFastPairHalfSheetManager.setHalfSheetForeground(false); + mFastPairHalfSheetManager.showPairingFailed(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + Integer halfSheetId = mFastPairHalfSheetManager.mModelIdMap.get(MODEL_ID); + + verify(mContextWrapper, times(2)) + .startActivityAsUser(captor.capture(), eq(UserHandle.CURRENT)); + assertThat( + captor.getAllValues().stream() + .filter(r -> + r.getStringExtra(EXTRA_HALF_SHEET_CONTENT) != null + && r.getStringExtra(EXTRA_HALF_SHEET_CONTENT) + .equals(RESULT_FAIL)) + + .count()) + .isEqualTo(1); + assertThat(mFastPairHalfSheetManager.isActivePairing()).isFalse(); + assertThat(mHalfSheetBlocklist.get(halfSheetId).getState()).isEqualTo(ACTIVE); + } + + private void verifyInitialPairingNameRunnablePostedTimes(int times) { + ArgumentCaptor captor = ArgumentCaptor.forClass(NamedRunnable.class); + + verify(mEventLoop, times(times)).postRunnableDelayed(captor.capture(), anyLong()); + assertThat( + captor.getAllValues().stream() + .filter(r -> r.name.equals(DISMISS_HALFSHEET_RUNNABLE_NAME)) + .count()) + .isEqualTo(times); + } + + private void verifyHalfSheetActivityIntent(int times) { + ArgumentCaptor captor = ArgumentCaptor.forClass(Intent.class); + + verify(mContextWrapper, times(times)) + .startActivityAsUser(captor.capture(), eq(UserHandle.CURRENT)); + assertThat( + captor.getAllValues().stream() + .filter(r -> r.getAction().equals("android.nearby.SHOW_HALFSHEET")) + .count()) + .isEqualTo(times); + } + + private void verifyFastPairStatusCallback(int times, int status) { + ArgumentCaptor captor = + ArgumentCaptor.forClass(PairStatusMetadata.class); + verify(mFastPairStatusCallback, times(times)).onPairUpdate(any(), captor.capture()); + assertThat( + captor.getAllValues().stream() + .filter(r -> r.getStatus() == status) + .count()) + .isEqualTo(times); + } + + private void configResolveInfoList() { + mApplicationInfo.sourceDir = "/apex/com.android.tethering"; + mApplicationInfo.packageName = "test.package"; + mResolveInfo.activityInfo.applicationInfo = mApplicationInfo; + mResolveInfoList.add(mResolveInfo); + } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationBuilderTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationBuilderTest.java new file mode 100644 index 0000000000..d99596954d --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationBuilderTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.fastpair.notification; + +import static com.android.server.nearby.fastpair.notification.FastPairNotificationBuilder.NOTIFICATION_OVERRIDE_NAME_EXTRA; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Resources; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.nearby.halfsheet.R; +import com.android.server.nearby.fastpair.HalfSheetResources; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +public class FastPairNotificationBuilderTest { + + private static final String STRING_DEVICE = "Devices"; + private static final String STRING_NEARBY = "Nearby"; + + @Mock private Context mContext; + @Mock private PackageManager mPackageManager; + @Mock private Resources mResources; + + private ResolveInfo mResolveInfo; + private List mResolveInfoList; + private ApplicationInfo mApplicationInfo; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + HalfSheetResources.setResourcesContextForTest(mContext); + + mResolveInfo = new ResolveInfo(); + mResolveInfoList = new ArrayList<>(); + mResolveInfo.activityInfo = new ActivityInfo(); + mApplicationInfo = new ApplicationInfo(); + + when(mContext.getResources()).thenReturn(mResources); + when(mContext.getApplicationInfo()) + .thenReturn(InstrumentationRegistry + .getInstrumentation().getContext().getApplicationInfo()); + when(mContext.getContentResolver()).thenReturn( + InstrumentationRegistry.getInstrumentation().getContext().getContentResolver()); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + when(mPackageManager.queryIntentActivities(any(), anyInt())).thenReturn(mResolveInfoList); + when(mPackageManager.canRequestPackageInstalls()).thenReturn(false); + mApplicationInfo.sourceDir = "/apex/com.android.nearby"; + mApplicationInfo.packageName = "test.package"; + mResolveInfo.activityInfo.applicationInfo = mApplicationInfo; + mResolveInfoList.add(mResolveInfo); + + when(mResources.getString(eq(R.string.common_devices))).thenReturn(STRING_DEVICE); + when(mResources.getString(eq(R.string.common_nearby_title))).thenReturn(STRING_NEARBY); + } + + @Test + public void setIsDevice_true() { + Notification notification = + new FastPairNotificationBuilder(mContext, "channelId") + .setIsDevice(true).build(); + assertThat(notification.extras.getString(NOTIFICATION_OVERRIDE_NAME_EXTRA)) + .isEqualTo(STRING_DEVICE); + } + + @Test + public void setIsDevice_false() { + Notification notification = + new FastPairNotificationBuilder(mContext, "channelId") + .setIsDevice(false).build(); + assertThat(notification.extras.getString(NOTIFICATION_OVERRIDE_NAME_EXTRA)) + .isEqualTo(STRING_NEARBY); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationManagerTest.java new file mode 100644 index 0000000000..9670a3f57b --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationManagerTest.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.fastpair.notification; + +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.content.Context; +import android.content.res.Resources; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.nearby.common.locator.Locator; +import com.android.server.nearby.common.locator.LocatorContextWrapper; +import com.android.server.nearby.fastpair.HalfSheetResources; +import com.android.server.nearby.fastpair.cache.DiscoveryItem; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import service.proto.Cache; + +public class FastPairNotificationManagerTest { + + @Mock + private Context mContext; + @Mock + NotificationManager mNotificationManager; + @Mock + Resources mResources; + @Mock + private LocatorContextWrapper mLocatorContextWrapper; + @Mock + private Locator mLocator; + + private static final int NOTIFICATION_ID = 1; + private static final int BATTERY_LEVEL = 1; + private static final String DEVICE_NAME = "deviceName"; + private FastPairNotificationManager mFastPairNotificationManager; + private DiscoveryItem mItem; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mContext.getContentResolver()).thenReturn( + InstrumentationRegistry.getInstrumentation().getContext().getContentResolver()); + when(mLocatorContextWrapper.getResources()).thenReturn(mResources); + when(mLocatorContextWrapper.getLocator()).thenReturn(mLocator); + HalfSheetResources.setResourcesContextForTest(mLocatorContextWrapper); + // Real context is needed + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + FastPairNotificationManager fastPairNotificationManager = + mFastPairNotificationManager = + new FastPairNotificationManager(context, NOTIFICATION_ID, mNotificationManager, + new HalfSheetResources(mLocatorContextWrapper)); + mLocator.overrideBindingForTest(FastPairNotificationManager.class, + fastPairNotificationManager); + + mItem = new DiscoveryItem(mLocatorContextWrapper, + Cache.StoredDiscoveryItem.newBuilder().setTitle("Device Name").build()); + } + + @Test + public void notifyPairingProcessDone() { + mFastPairNotificationManager.notifyPairingProcessDone(true, true, + "privateAddress", "publicAddress"); + } + + @Test + public void showConnectingNotification() { + mFastPairNotificationManager.showConnectingNotification(mItem); + } + + @Test + public void showPairingFailedNotification() { + mFastPairNotificationManager + .showPairingFailedNotification(mItem, new byte[]{1}); + } + + @Test + public void showPairingSucceededNotification() { + mFastPairNotificationManager + .showPairingSucceededNotification(mItem, BATTERY_LEVEL, DEVICE_NAME); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationsTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationsTest.java new file mode 100644 index 0000000000..cfebbded2d --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/notification/FastPairNotificationsTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.fastpair.notification; + +import static com.android.server.nearby.fastpair.notification.FastPairNotificationManager.DEVICES_WITHIN_REACH_CHANNEL_ID; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import android.app.Notification; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; + +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.nearby.halfsheet.R; +import com.android.server.nearby.common.locator.Locator; +import com.android.server.nearby.common.locator.LocatorContextWrapper; +import com.android.server.nearby.fastpair.HalfSheetResources; +import com.android.server.nearby.fastpair.cache.DiscoveryItem; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import service.proto.Cache; + +public class FastPairNotificationsTest { + private static final Cache.StoredDiscoveryItem SCAN_FAST_PAIR_ITEM = + Cache.StoredDiscoveryItem.newBuilder() + .setDeviceName("TestName") + .build(); + private static final String STRING_DEVICE = "Devices"; + private static final String STRING_NEARBY = "Nearby"; + private static final String STRING_YOUR_DEVICE = "Your saved device is available"; + private static final String STRING_CONNECTING = "Connecting"; + private static final String STRING_DEVICE_READY = "Device connected"; + + private static final byte[] ACCOUNT_KEY = new byte[]{0x01, 0x02}; + @Mock + LocatorContextWrapper mContextWrapper; + @Mock + Locator mLocator; + @Mock + private Context mContext; + @Mock + private Resources mResources; + @Mock + private Drawable mDrawable; + + private DiscoveryItem mItem; + private HalfSheetResources mHalfSheetResources; + private FastPairNotifications mFastPairNotifications; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mHalfSheetResources = new HalfSheetResources(mContext); + Context realContext = InstrumentationRegistry.getInstrumentation().getContext(); + mFastPairNotifications = + new FastPairNotifications(realContext, mHalfSheetResources); + HalfSheetResources.setResourcesContextForTest(mContext); + + when(mContextWrapper.getLocator()).thenReturn(mLocator); + when(mContext.getResources()).thenReturn(mResources); + + when(mResources.getString(eq(R.string.common_devices))).thenReturn(STRING_DEVICE); + when(mResources.getString(eq(R.string.common_nearby_title))).thenReturn(STRING_NEARBY); + when(mResources.getString(eq(R.string.fast_pair_your_device))) + .thenReturn(STRING_YOUR_DEVICE); + when(mResources.getString(eq(R.string.common_connecting))).thenReturn(STRING_CONNECTING); + when(mResources.getString(eq(R.string.fast_pair_device_ready))) + .thenReturn(STRING_DEVICE_READY); + when(mResources.getDrawable(eq(R.drawable.quantum_ic_devices_other_vd_theme_24), any())) + .thenReturn(mDrawable); + + mItem = new DiscoveryItem(mContextWrapper, SCAN_FAST_PAIR_ITEM); + } + + @Test + public void verify_progressNotification() { + Notification notification = mFastPairNotifications.progressNotification(mItem); + + assertThat(notification.getChannelId()).isEqualTo(DEVICES_WITHIN_REACH_CHANNEL_ID); + assertThat(notification.getSmallIcon().getResId()) + .isEqualTo(R.drawable.quantum_ic_devices_other_vd_theme_24); + assertThat(notification.category).isEqualTo(Notification.CATEGORY_PROGRESS); + assertThat(notification.tickerText.toString()).isEqualTo(STRING_CONNECTING); + } + + @Test + public void verify_discoveryNotification() { + Notification notification = + mFastPairNotifications.discoveryNotification(mItem, ACCOUNT_KEY); + + assertThat(notification.getChannelId()).isEqualTo(DEVICES_WITHIN_REACH_CHANNEL_ID); + assertThat(notification.getSmallIcon().getResId()) + .isEqualTo(R.drawable.quantum_ic_devices_other_vd_theme_24); + assertThat(notification.category).isEqualTo(Notification.CATEGORY_RECOMMENDATION); + } + + @Test + public void verify_succeededNotification() { + Notification notification = mFastPairNotifications + .pairingSucceededNotification(101, null, "model name", mItem); + + assertThat(notification.getChannelId()).isEqualTo(DEVICES_WITHIN_REACH_CHANNEL_ID); + assertThat(notification.getSmallIcon().getResId()) + .isEqualTo(R.drawable.quantum_ic_devices_other_vd_theme_24); + assertThat(notification.tickerText.toString()).isEqualTo(STRING_DEVICE_READY); + assertThat(notification.category).isEqualTo(Notification.CATEGORY_STATUS); + } + + @Test + public void verify_failedNotification() { + Notification notification = + mFastPairNotifications.showPairingFailedNotification(mItem, ACCOUNT_KEY); + + assertThat(notification.getChannelId()).isEqualTo(DEVICES_WITHIN_REACH_CHANNEL_ID); + assertThat(notification.getSmallIcon().getResId()) + .isEqualTo(R.drawable.quantum_ic_devices_other_vd_theme_24); + assertThat(notification.category).isEqualTo(Notification.CATEGORY_ERROR); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandlerTest.java new file mode 100644 index 0000000000..2d496fdb5d --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/HalfSheetPairingProgressHandlerTest.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.fastpair.pairinghandler; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; + +import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection; +import com.android.server.nearby.common.eventloop.EventLoop; +import com.android.server.nearby.common.locator.Locator; +import com.android.server.nearby.common.locator.LocatorContextWrapper; +import com.android.server.nearby.fastpair.cache.DiscoveryItem; +import com.android.server.nearby.fastpair.cache.FastPairCacheManager; +import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager; +import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager; +import com.android.server.nearby.fastpair.testing.FakeDiscoveryItems; + +import com.google.protobuf.ByteString; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.time.Clock; + +import service.proto.Cache; +import service.proto.Rpcs; + +public class HalfSheetPairingProgressHandlerTest { + @Mock + Locator mLocator; + @Mock + LocatorContextWrapper mContextWrapper; + @Mock + Clock mClock; + @Mock + FastPairCacheManager mFastPairCacheManager; + @Mock + FastPairConnection mFastPairConnection; + @Mock + FootprintsDeviceManager mFootprintsDeviceManager; + @Mock + EventLoop mEventLoop; + + private static final String MAC_ADDRESS = "00:11:22:33:44:55"; + private static final byte[] ACCOUNT_KEY = new byte[]{0x01, 0x02}; + private static final int SUBSEQUENT_PAIR_START = 1310; + private static final int SUBSEQUENT_PAIR_END = 1320; + private static final int PASSKEY = 1234; + private static HalfSheetPairingProgressHandler sHalfSheetPairingProgressHandler; + private static DiscoveryItem sDiscoveryItem; + private static BluetoothDevice sBluetoothDevice; + private static FastPairHalfSheetManager sFastPairHalfSheetManager; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mContextWrapper.getLocator()).thenReturn(mLocator); + mLocator.overrideBindingForTest(FastPairCacheManager.class, mFastPairCacheManager); + mLocator.overrideBindingForTest(Clock.class, mClock); + sFastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + mLocator.bind(FastPairHalfSheetManager.class, sFastPairHalfSheetManager); + when(mLocator.get(FastPairHalfSheetManager.class)).thenReturn(sFastPairHalfSheetManager); + when(mLocator.get(EventLoop.class)).thenReturn(mEventLoop); + sDiscoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper); + sDiscoveryItem.setStoredItemForTest( + sDiscoveryItem.getStoredItemForTest().toBuilder() + .setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY)) + .setMacAddress(MAC_ADDRESS) + .setFastPairInformation( + Cache.FastPairInformation.newBuilder() + .setDeviceType(Rpcs.DeviceType.HEADPHONES).build()) + .build()); + sHalfSheetPairingProgressHandler = + new HalfSheetPairingProgressHandler(mContextWrapper, sDiscoveryItem, + sDiscoveryItem.getAppPackageName(), ACCOUNT_KEY); + + sBluetoothDevice = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:11:22:33:44:55"); + } + + @Test + public void getPairEndEventCode() { + assertThat(sHalfSheetPairingProgressHandler + .getPairEndEventCode()).isEqualTo(SUBSEQUENT_PAIR_END); + } + + @Test + public void getPairStartEventCode() { + assertThat(sHalfSheetPairingProgressHandler + .getPairStartEventCode()).isEqualTo(SUBSEQUENT_PAIR_START); + } + + @Test + public void testOnHandlePasskeyConfirmation() { + sHalfSheetPairingProgressHandler.onHandlePasskeyConfirmation(sBluetoothDevice, PASSKEY); + } + + @Test + public void testOnPairedCallbackCalled() { + sHalfSheetPairingProgressHandler.onPairedCallbackCalled(mFastPairConnection, ACCOUNT_KEY, + mFootprintsDeviceManager, MAC_ADDRESS); + } + + @Test + public void testonPairingFailed() { + Throwable e = new Throwable("onPairingFailed"); + sHalfSheetPairingProgressHandler.onPairingFailed(e); + } + + @Test + public void testonPairingStarted() { + sHalfSheetPairingProgressHandler.onPairingStarted(); + assertThat(sFastPairHalfSheetManager.isActivePairing()).isTrue(); + } + + @Test + public void testonPairingSuccess() { + sHalfSheetPairingProgressHandler.onPairingSuccess(MAC_ADDRESS); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandlerTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandlerTest.java new file mode 100644 index 0000000000..5c61ddb569 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/NotificationPairingProgressHandlerTest.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.fastpair.pairinghandler; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.when; + +import android.app.NotificationManager; +import android.bluetooth.BluetoothManager; +import android.content.Context; +import android.content.res.Resources; + +import androidx.annotation.Nullable; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection; +import com.android.server.nearby.common.locator.Locator; +import com.android.server.nearby.common.locator.LocatorContextWrapper; +import com.android.server.nearby.fastpair.HalfSheetResources; +import com.android.server.nearby.fastpair.cache.DiscoveryItem; +import com.android.server.nearby.fastpair.cache.FastPairCacheManager; +import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager; +import com.android.server.nearby.fastpair.halfsheet.FastPairHalfSheetManager; +import com.android.server.nearby.fastpair.notification.FastPairNotificationManager; +import com.android.server.nearby.fastpair.testing.FakeDiscoveryItems; + +import com.google.protobuf.ByteString; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.time.Clock; + +import service.proto.Cache; +import service.proto.Rpcs; + +public class NotificationPairingProgressHandlerTest { + + @Mock + Locator mLocator; + @Mock + LocatorContextWrapper mContextWrapper; + @Mock + Clock mClock; + @Mock + FastPairCacheManager mFastPairCacheManager; + @Mock + FastPairConnection mFastPairConnection; + @Mock + FootprintsDeviceManager mFootprintsDeviceManager; + @Mock + android.bluetooth.BluetoothManager mBluetoothManager; + @Mock + NotificationManager mNotificationManager; + @Mock + Resources mResources; + + private static final String MAC_ADDRESS = "00:11:22:33:44:55"; + private static final byte[] ACCOUNT_KEY = new byte[]{0x01, 0x02}; + private static final int SUBSEQUENT_PAIR_START = 1310; + private static final int SUBSEQUENT_PAIR_END = 1320; + private static DiscoveryItem sDiscoveryItem; + private static NotificationPairingProgressHandler sNotificationPairingProgressHandler; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mContextWrapper.getSystemService(BluetoothManager.class)) + .thenReturn(mBluetoothManager); + when(mContextWrapper.getLocator()).thenReturn(mLocator); + when(mContextWrapper.getResources()).thenReturn(mResources); + HalfSheetResources.setResourcesContextForTest(mContextWrapper); + + mLocator.overrideBindingForTest(FastPairCacheManager.class, + mFastPairCacheManager); + mLocator.overrideBindingForTest(Clock.class, mClock); + sDiscoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper); + sDiscoveryItem.setStoredItemForTest( + sDiscoveryItem.getStoredItemForTest().toBuilder() + .setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY)) + .setFastPairInformation( + Cache.FastPairInformation.newBuilder() + .setDeviceType(Rpcs.DeviceType.HEADPHONES).build()) + .build()); + sNotificationPairingProgressHandler = createProgressHandler(ACCOUNT_KEY, sDiscoveryItem); + } + + @Test + public void getPairEndEventCode() { + assertThat(sNotificationPairingProgressHandler + .getPairEndEventCode()).isEqualTo(SUBSEQUENT_PAIR_END); + } + + @Test + public void getPairStartEventCode() { + assertThat(sNotificationPairingProgressHandler + .getPairStartEventCode()).isEqualTo(SUBSEQUENT_PAIR_START); + } + + @Test + public void onReadyToPair() { + sNotificationPairingProgressHandler.onReadyToPair(); + } + + @Test + public void onPairedCallbackCalled() { + sNotificationPairingProgressHandler.onPairedCallbackCalled(mFastPairConnection, + ACCOUNT_KEY, mFootprintsDeviceManager, MAC_ADDRESS); + } + + @Test + public void onPairingFailed() { + Throwable e = new Throwable("Pairing Failed"); + sNotificationPairingProgressHandler.onPairingFailed(e); + } + + @Test + public void onPairingSuccess() { + sNotificationPairingProgressHandler.onPairingSuccess(sDiscoveryItem.getMacAddress()); + } + + private NotificationPairingProgressHandler createProgressHandler( + @Nullable byte[] accountKey, DiscoveryItem fastPairItem) { + FastPairHalfSheetManager fastPairHalfSheetManager = + new FastPairHalfSheetManager(mContextWrapper); + // Real context is needed + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + FastPairNotificationManager fastPairNotificationManager = + new FastPairNotificationManager(context, 1234, mNotificationManager, + new HalfSheetResources(mContextWrapper)); + mLocator.overrideBindingForTest(FastPairHalfSheetManager.class, fastPairHalfSheetManager); + mLocator.overrideBindingForTest(FastPairNotificationManager.class, + fastPairNotificationManager); + + NotificationPairingProgressHandler mNotificationPairingProgressHandler = + new NotificationPairingProgressHandler( + mContextWrapper, + fastPairItem, + fastPairItem.getAppPackageName(), + accountKey, + fastPairNotificationManager); + return mNotificationPairingProgressHandler; + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java index 2ade5f2ea5..6d769df538 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/pairinghandler/PairingProgressHandlerBaseTest.java @@ -20,10 +20,21 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.when; -import androidx.annotation.Nullable; +import android.app.NotificationManager; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothManager; +import android.content.Context; +import android.content.res.Resources; +import androidx.annotation.Nullable; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.nearby.common.bluetooth.fastpair.FastPairConnection; +import com.android.server.nearby.common.bluetooth.fastpair.Preferences; import com.android.server.nearby.common.locator.Locator; import com.android.server.nearby.common.locator.LocatorContextWrapper; +import com.android.server.nearby.fastpair.HalfSheetResources; import com.android.server.nearby.fastpair.cache.DiscoveryItem; import com.android.server.nearby.fastpair.cache.FastPairCacheManager; import com.android.server.nearby.fastpair.footprint.FootprintsDeviceManager; @@ -42,8 +53,8 @@ import java.time.Clock; import service.proto.Cache; import service.proto.Rpcs; - public class PairingProgressHandlerBaseTest { + @Mock Locator mLocator; @Mock @@ -54,24 +65,53 @@ public class PairingProgressHandlerBaseTest { FastPairCacheManager mFastPairCacheManager; @Mock FootprintsDeviceManager mFootprintsDeviceManager; + @Mock + FastPairConnection mFastPairConnection; + @Mock + BluetoothManager mBluetoothManager; + @Mock + Resources mResources; + @Mock + NotificationManager mNotificationManager; + + private static final String MAC_ADDRESS = "00:11:22:33:44:55"; private static final byte[] ACCOUNT_KEY = new byte[]{0x01, 0x02}; + private static final int PASSKEY = 1234; + private static DiscoveryItem sDiscoveryItem; + private static PairingProgressHandlerBase sPairingProgressHandlerBase; + private static BluetoothDevice sBluetoothDevice; @Before public void setup() { - MockitoAnnotations.initMocks(this); + when(mContextWrapper.getSystemService(BluetoothManager.class)) + .thenReturn(mBluetoothManager); when(mContextWrapper.getLocator()).thenReturn(mLocator); mLocator.overrideBindingForTest(FastPairCacheManager.class, mFastPairCacheManager); mLocator.overrideBindingForTest(Clock.class, mClock); + when(mContextWrapper.getResources()).thenReturn(mResources); + HalfSheetResources.setResourcesContextForTest(mContextWrapper); + + sBluetoothDevice = + BluetoothAdapter.getDefaultAdapter().getRemoteDevice("00:11:22:33:44:55"); + sDiscoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper); + sDiscoveryItem.setStoredItemForTest( + sDiscoveryItem.getStoredItemForTest().toBuilder() + .setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY)) + .setFastPairInformation( + Cache.FastPairInformation.newBuilder() + .setDeviceType(Rpcs.DeviceType.HEADPHONES).build()) + .build()); + + sPairingProgressHandlerBase = + createProgressHandler(ACCOUNT_KEY, sDiscoveryItem, /* isRetroactivePair= */ false); } @Test public void createHandler_halfSheetSubsequentPairing_notificationPairingHandlerCreated() { - - DiscoveryItem discoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper); - discoveryItem.setStoredItemForTest( - discoveryItem.getStoredItemForTest().toBuilder() + sDiscoveryItem.setStoredItemForTest( + sDiscoveryItem.getStoredItemForTest().toBuilder() .setAuthenticationPublicKeySecp256R1(ByteString.copyFrom(ACCOUNT_KEY)) .setFastPairInformation( Cache.FastPairInformation.newBuilder() @@ -79,7 +119,7 @@ public class PairingProgressHandlerBaseTest { .build()); PairingProgressHandlerBase progressHandler = - createProgressHandler(ACCOUNT_KEY, discoveryItem, /* isRetroactivePair= */ false); + createProgressHandler(ACCOUNT_KEY, sDiscoveryItem, /* isRetroactivePair= */ false); assertThat(progressHandler).isInstanceOf(NotificationPairingProgressHandler.class); } @@ -87,26 +127,107 @@ public class PairingProgressHandlerBaseTest { @Test public void createHandler_halfSheetInitialPairing_halfSheetPairingHandlerCreated() { // No account key - DiscoveryItem discoveryItem = FakeDiscoveryItems.newFastPairDiscoveryItem(mContextWrapper); - discoveryItem.setStoredItemForTest( - discoveryItem.getStoredItemForTest().toBuilder() + sDiscoveryItem.setStoredItemForTest( + sDiscoveryItem.getStoredItemForTest().toBuilder() .setFastPairInformation( Cache.FastPairInformation.newBuilder() .setDeviceType(Rpcs.DeviceType.HEADPHONES).build()) .build()); PairingProgressHandlerBase progressHandler = - createProgressHandler(null, discoveryItem, /* isRetroactivePair= */ false); + createProgressHandler(null, sDiscoveryItem, /* isRetroactivePair= */ false); assertThat(progressHandler).isInstanceOf(HalfSheetPairingProgressHandler.class); } + @Test + public void onPairingStarted() { + sPairingProgressHandlerBase.onPairingStarted(); + } + + @Test + public void onWaitForScreenUnlock() { + sPairingProgressHandlerBase.onWaitForScreenUnlock(); + } + + @Test + public void onScreenUnlocked() { + sPairingProgressHandlerBase.onScreenUnlocked(); + } + + @Test + public void onReadyToPair() { + sPairingProgressHandlerBase.onReadyToPair(); + } + + @Test + public void onSetupPreferencesBuilder() { + Preferences.Builder prefsBuilder = + Preferences.builder() + .setEnableBrEdrHandover(false) + .setIgnoreDiscoveryError(true); + sPairingProgressHandlerBase.onSetupPreferencesBuilder(prefsBuilder); + } + + @Test + public void onPairingSetupCompleted() { + sPairingProgressHandlerBase.onPairingSetupCompleted(); + } + + @Test + public void onHandlePasskeyConfirmation() { + sPairingProgressHandlerBase.onHandlePasskeyConfirmation(sBluetoothDevice, PASSKEY); + } + + @Test + public void getKeyForLocalCache() { + FastPairConnection.SharedSecret sharedSecret = + FastPairConnection.SharedSecret.create(ACCOUNT_KEY, sDiscoveryItem.getMacAddress()); + sPairingProgressHandlerBase + .getKeyForLocalCache(ACCOUNT_KEY, mFastPairConnection, sharedSecret); + } + + @Test + public void onPairedCallbackCalled() { + sPairingProgressHandlerBase.onPairedCallbackCalled(mFastPairConnection, + ACCOUNT_KEY, mFootprintsDeviceManager, MAC_ADDRESS); + } + + @Test + public void onPairingFailed() { + Throwable e = new Throwable("Pairing Failed"); + sPairingProgressHandlerBase.onPairingFailed(e); + } + + @Test + public void onPairingSuccess() { + sPairingProgressHandlerBase.onPairingSuccess(sDiscoveryItem.getMacAddress()); + } + + @Test + public void optInFootprintsForInitialPairing() { + sPairingProgressHandlerBase.optInFootprintsForInitialPairing( + mFootprintsDeviceManager, sDiscoveryItem, ACCOUNT_KEY, null); + } + + @Test + public void skipWaitingScreenUnlock() { + assertThat(sPairingProgressHandlerBase.skipWaitingScreenUnlock()).isFalse(); + } + private PairingProgressHandlerBase createProgressHandler( @Nullable byte[] accountKey, DiscoveryItem fastPairItem, boolean isRetroactivePair) { - FastPairNotificationManager fastPairNotificationManager = - new FastPairNotificationManager(mContextWrapper, fastPairItem, true); + FastPairHalfSheetManager fastPairHalfSheetManager = new FastPairHalfSheetManager(mContextWrapper); + // Real context is needed + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + FastPairNotificationManager fastPairNotificationManager = + new FastPairNotificationManager(context, 1234, mNotificationManager, + new HalfSheetResources(mContextWrapper)); + + mLocator.overrideBindingForTest(FastPairNotificationManager.class, + fastPairNotificationManager); mLocator.overrideBindingForTest(FastPairHalfSheetManager.class, fastPairHalfSheetManager); PairingProgressHandlerBase pairingProgressHandlerBase = PairingProgressHandlerBase.create( diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java index c406e47e73..cdec04d48c 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/FakeDiscoveryItems.java @@ -22,10 +22,17 @@ import com.android.server.nearby.fastpair.cache.DiscoveryItem; import service.proto.Cache; public class FakeDiscoveryItems { - public static final String DEFAULT_MAC_ADDRESS = "00:11:22:33:44:55"; - public static final long DEFAULT_TIMESTAMP = 1000000000L; - public static final String DEFAULT_DESCRIPITON = "description"; - public static final String TRIGGER_ID = "trigger.id"; + private static final String DEFAULT_MAC_ADDRESS = "00:11:22:33:44:55"; + private static final long DEFAULT_TIMESTAMP = 1000000000L; + private static final String DEFAULT_DESCRIPITON = "description"; + private static final String APP_NAME = "app_name"; + private static final String ACTION_URL = + "intent:#Intent;action=com.android.server.nearby:ACTION_FAST_PAIR;" + + "package=com.google.android.gms;" + + "component=com.google.android.gms/" + + ".nearby.discovery.service.DiscoveryService;end"; + private static final String DISPLAY_URL = "DISPLAY_URL"; + private static final String TRIGGER_ID = "trigger.id"; private static final String FAST_PAIR_ID = "id"; private static final int RSSI = -80; private static final int TX_POWER = -10; @@ -46,9 +53,36 @@ public class FakeDiscoveryItems { item.setMacAddress(DEFAULT_MAC_ADDRESS); item.setFirstObservationTimestampMillis(DEFAULT_TIMESTAMP); item.setLastObservationTimestampMillis(DEFAULT_TIMESTAMP); + item.setActionUrl(ACTION_URL); + item.setAppName(APP_NAME); item.setRssi(RSSI); item.setTxPower(TX_POWER); + item.setDisplayUrl(DISPLAY_URL); return item.build(); } + public static Cache.StoredDiscoveryItem newFastPairDeviceStoredItem(String id, + String description, String triggerId, String macAddress, String title, + int rssi, int txPower) { + Cache.StoredDiscoveryItem.Builder item = Cache.StoredDiscoveryItem.newBuilder(); + item.setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED); + if (id != null) { + item.setId(id); + } + if (description != null) { + item.setDescription(description); + } + if (triggerId != null) { + item.setTriggerId(triggerId); + } + if (macAddress != null) { + item.setMacAddress(macAddress); + } + if (title != null) { + item.setTitle(title); + } + item.setRssi(rssi); + item.setTxPower(txPower); + return item.build(); + } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java index b261b2639f..c9a453380a 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java +++ b/nearby/tests/unit/src/com/android/server/nearby/fastpair/testing/MockingLocator.java @@ -41,7 +41,7 @@ public class MockingLocator extends Locator { } @SuppressWarnings("nullness") // due to passing in this before initialized. - private MockingLocator(Context context, Locator locator) { + public MockingLocator(Context context, Locator locator) { super(context, locator); this.mLocatorContextWrapper = new LocatorContextWrapper(context, this); } diff --git a/nearby/tests/unit/src/com/android/server/nearby/injector/ContextHubManagerAdapterTest.java b/nearby/tests/unit/src/com/android/server/nearby/injector/ContextHubManagerAdapterTest.java new file mode 100644 index 0000000000..b5770643dd --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/injector/ContextHubManagerAdapterTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.injector; + +import android.hardware.location.ContextHubInfo; +import android.hardware.location.ContextHubManager; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class ContextHubManagerAdapterTest { + private ContextHubManagerAdapter mContextHubManagerAdapter; + + @Mock + ContextHubManager mContextHubManager; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mContextHubManagerAdapter = new ContextHubManagerAdapter(mContextHubManager); + } + + @Test + public void getContextHubs() { + mContextHubManagerAdapter.getContextHubs(); + } + + @Test + public void queryNanoApps() { + mContextHubManagerAdapter.queryNanoApps(new ContextHubInfo()); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/BroadcastProviderManagerTest.java similarity index 79% rename from nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java rename to nearby/tests/unit/src/com/android/server/nearby/managers/BroadcastProviderManagerTest.java index d45d570448..bc3821040a 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/provider/BroadcastProviderManagerTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/managers/BroadcastProviderManagerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 The Android Open Source Project + * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,13 @@ * limitations under the License. */ -package com.android.server.nearby.provider; +package com.android.server.nearby.managers; import static android.Manifest.permission.READ_DEVICE_CONFIG; import static android.Manifest.permission.WRITE_DEVICE_CONFIG; -import static android.provider.DeviceConfig.NAMESPACE_TETHERING; import static com.android.server.nearby.NearbyConfiguration.NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY; +import static com.android.server.nearby.NearbyConfiguration.NEARBY_SUPPORT_TEST_APP; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.eq; @@ -39,6 +39,9 @@ import android.provider.DeviceConfig; import androidx.test.core.app.ApplicationProvider; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.server.nearby.NearbyConfiguration; +import com.android.server.nearby.provider.BleBroadcastProvider; + import com.google.common.util.concurrent.MoreExecutors; import org.junit.Before; @@ -51,9 +54,10 @@ import org.mockito.junit.MockitoRule; import java.util.Collections; /** - * Unit test for {@link BroadcastProviderManager}. + * Unit test for {@link com.android.server.nearby.managers.BroadcastProviderManager}. */ public class BroadcastProviderManagerTest { + private static final String NAMESPACE = NearbyConfiguration.getNamespace(); private static final byte[] IDENTITY = new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; private static final int MEDIUM_TYPE_BLE = 0; private static final byte[] SALT = {2, 3}; @@ -79,11 +83,12 @@ public class BroadcastProviderManagerTest { @Before public void setUp() { mUiAutomation.adoptShellPermissionIdentity(WRITE_DEVICE_CONFIG, READ_DEVICE_CONFIG); - DeviceConfig.setProperty(NAMESPACE_TETHERING, NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY, - "true", false); + DeviceConfig.setProperty( + NAMESPACE, NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY, "true", false); mContext = ApplicationProvider.getApplicationContext(); - mBroadcastProviderManager = new BroadcastProviderManager(MoreExecutors.directExecutor(), + mBroadcastProviderManager = new BroadcastProviderManager( + MoreExecutors.directExecutor(), mBleBroadcastProvider); PrivateCredential privateCredential = @@ -101,14 +106,22 @@ public class BroadcastProviderManagerTest { @Test public void testStartAdvertising() { mBroadcastProviderManager.startBroadcast(mBroadcastRequest, mBroadcastListener); - verify(mBleBroadcastProvider).start(any(byte[].class), any( - BleBroadcastProvider.BroadcastListener.class)); + verify(mBleBroadcastProvider).start(eq(BroadcastRequest.PRESENCE_VERSION_V0), + any(byte[].class), any(BleBroadcastProvider.BroadcastListener.class)); + } + + @Test + public void testStopAdvertising() { + mBroadcastProviderManager.stopBroadcast(mBroadcastListener); } @Test public void testStartAdvertising_featureDisabled() throws Exception { - DeviceConfig.setProperty(NAMESPACE_TETHERING, NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY, - "false", false); + DeviceConfig.setProperty( + NAMESPACE, NEARBY_ENABLE_PRESENCE_BROADCAST_LEGACY, "false", false); + DeviceConfig.setProperty( + NAMESPACE, NEARBY_SUPPORT_TEST_APP, "false", false); + mBroadcastProviderManager = new BroadcastProviderManager(MoreExecutors.directExecutor(), mBleBroadcastProvider); mBroadcastProviderManager.startBroadcast(mBroadcastRequest, mBroadcastListener); diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerLegacyTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerLegacyTest.java new file mode 100644 index 0000000000..aa0dad328c --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerLegacyTest.java @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.managers; + +import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE; +import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AppOpsManager; +import android.content.Context; +import android.nearby.DataElement; +import android.nearby.IScanListener; +import android.nearby.NearbyDeviceParcelable; +import android.nearby.PresenceScanFilter; +import android.nearby.PublicCredential; +import android.nearby.ScanRequest; +import android.os.IBinder; + +import com.android.server.nearby.injector.Injector; +import com.android.server.nearby.provider.BleDiscoveryProvider; +import com.android.server.nearby.provider.ChreCommunication; +import com.android.server.nearby.provider.ChreDiscoveryProvider; +import com.android.server.nearby.provider.DiscoveryProviderController; +import com.android.server.nearby.util.identity.CallerIdentity; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Unit test for {@link DiscoveryProviderManagerLegacy} class. + */ +public class DiscoveryProviderManagerLegacyTest { + private static final int SCAN_MODE_CHRE_ONLY = 3; + private static final int DATA_TYPE_SCAN_MODE = 102; + private static final int UID = 1234; + private static final int PID = 5678; + private static final String PACKAGE_NAME = "android.nearby.test"; + private static final int RSSI = -60; + @Mock + Injector mInjector; + @Mock + Context mContext; + @Mock + AppOpsManager mAppOpsManager; + @Mock + BleDiscoveryProvider mBleDiscoveryProvider; + @Mock + ChreDiscoveryProvider mChreDiscoveryProvider; + @Mock + DiscoveryProviderController mBluetoothController; + @Mock + DiscoveryProviderController mChreController; + @Mock + IScanListener mScanListener; + @Mock + CallerIdentity mCallerIdentity; + @Mock + DiscoveryProviderManagerLegacy.ScanListenerDeathRecipient mScanListenerDeathRecipient; + @Mock + IBinder mIBinder; + private DiscoveryProviderManagerLegacy mDiscoveryProviderManager; + private Map + mScanTypeScanListenerRecordMap; + + private static PresenceScanFilter getPresenceScanFilter() { + final byte[] secretId = new byte[]{1, 2, 3, 4}; + final byte[] authenticityKey = new byte[]{0, 1, 1, 1}; + final byte[] publicKey = new byte[]{1, 1, 2, 2}; + final byte[] encryptedMetadata = new byte[]{1, 2, 3, 4, 5}; + final byte[] metadataEncryptionKeyTag = new byte[]{1, 1, 3, 4, 5}; + + PublicCredential credential = new PublicCredential.Builder( + secretId, authenticityKey, publicKey, encryptedMetadata, metadataEncryptionKeyTag) + .setIdentityType(IDENTITY_TYPE_PRIVATE) + .build(); + + final int action = 123; + return new PresenceScanFilter.Builder() + .addCredential(credential) + .setMaxPathLoss(RSSI) + .addPresenceAction(action) + .build(); + } + + private static PresenceScanFilter getChreOnlyPresenceScanFilter() { + final byte[] secretId = new byte[]{1, 2, 3, 4}; + final byte[] authenticityKey = new byte[]{0, 1, 1, 1}; + final byte[] publicKey = new byte[]{1, 1, 2, 2}; + final byte[] encryptedMetadata = new byte[]{1, 2, 3, 4, 5}; + final byte[] metadataEncryptionKeyTag = new byte[]{1, 1, 3, 4, 5}; + + PublicCredential credential = new PublicCredential.Builder( + secretId, authenticityKey, publicKey, encryptedMetadata, metadataEncryptionKeyTag) + .setIdentityType(IDENTITY_TYPE_PRIVATE) + .build(); + + final int action = 123; + DataElement scanModeElement = new DataElement(DATA_TYPE_SCAN_MODE, + new byte[]{SCAN_MODE_CHRE_ONLY}); + return new PresenceScanFilter.Builder() + .addCredential(credential) + .setMaxPathLoss(RSSI) + .addPresenceAction(action) + .addExtendedProperty(scanModeElement) + .build(); + } + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + when(mInjector.getAppOpsManager()).thenReturn(mAppOpsManager); + when(mBleDiscoveryProvider.getController()).thenReturn(mBluetoothController); + when(mChreDiscoveryProvider.getController()).thenReturn(mChreController); + + mScanTypeScanListenerRecordMap = new HashMap<>(); + mDiscoveryProviderManager = + new DiscoveryProviderManagerLegacy(mContext, mInjector, + mBleDiscoveryProvider, + mChreDiscoveryProvider, + mScanTypeScanListenerRecordMap); + mCallerIdentity = CallerIdentity + .forTest(UID, PID, PACKAGE_NAME, /* attributionTag= */ null); + } + + @Test + public void testOnNearbyDeviceDiscovered() { + NearbyDeviceParcelable nearbyDeviceParcelable = new NearbyDeviceParcelable.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .build(); + mDiscoveryProviderManager.onNearbyDeviceDiscovered(nearbyDeviceParcelable); + } + + @Test + public void testInvalidateProviderScanMode() { + mDiscoveryProviderManager.invalidateProviderScanMode(); + } + + @Test + public void testStartProviders_chreOnlyChreAvailable_bleProviderNotStarted() { + when(mChreDiscoveryProvider.available()).thenReturn(true); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getChreOnlyPresenceScanFilter()).build(); + DiscoveryProviderManagerLegacy.ScanListenerRecord record = + new DiscoveryProviderManagerLegacy.ScanListenerRecord( + scanRequest, mScanListener, + mCallerIdentity, mScanListenerDeathRecipient); + mScanTypeScanListenerRecordMap.put(mIBinder, record); + + Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + verify(mBluetoothController, never()).start(); + assertThat(start).isTrue(); + } + + @Test + public void testStartProviders_chreOnlyChreAvailable_multipleFilters_bleProviderNotStarted() { + when(mChreDiscoveryProvider.available()).thenReturn(true); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getChreOnlyPresenceScanFilter()) + .addScanFilter(getPresenceScanFilter()).build(); + DiscoveryProviderManagerLegacy.ScanListenerRecord record = + new DiscoveryProviderManagerLegacy.ScanListenerRecord( + scanRequest, mScanListener, + mCallerIdentity, mScanListenerDeathRecipient); + mScanTypeScanListenerRecordMap.put(mIBinder, record); + + Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + verify(mBluetoothController, never()).start(); + assertThat(start).isTrue(); + } + + @Test + public void testStartProviders_chreOnlyChreUnavailable_bleProviderNotStarted() { + when(mChreDiscoveryProvider.available()).thenReturn(false); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getChreOnlyPresenceScanFilter()).build(); + DiscoveryProviderManagerLegacy.ScanListenerRecord record = + new DiscoveryProviderManagerLegacy.ScanListenerRecord( + scanRequest, mScanListener, + mCallerIdentity, mScanListenerDeathRecipient); + mScanTypeScanListenerRecordMap.put(mIBinder, record); + + Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + verify(mBluetoothController, never()).start(); + assertThat(start).isFalse(); + } + + @Test + public void testStartProviders_notChreOnlyChreAvailable_bleProviderNotStarted() { + when(mChreDiscoveryProvider.available()).thenReturn(true); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getPresenceScanFilter()).build(); + DiscoveryProviderManagerLegacy.ScanListenerRecord record = + new DiscoveryProviderManagerLegacy.ScanListenerRecord( + scanRequest, mScanListener, + mCallerIdentity, mScanListenerDeathRecipient); + mScanTypeScanListenerRecordMap.put(mIBinder, record); + + Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + verify(mBluetoothController, never()).start(); + assertThat(start).isTrue(); + } + + @Test + public void testStartProviders_notChreOnlyChreUnavailable_bleProviderStarted() { + when(mChreDiscoveryProvider.available()).thenReturn(false); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getPresenceScanFilter()).build(); + DiscoveryProviderManagerLegacy.ScanListenerRecord record = + new DiscoveryProviderManagerLegacy.ScanListenerRecord( + scanRequest, mScanListener, + mCallerIdentity, mScanListenerDeathRecipient); + mScanTypeScanListenerRecordMap.put(mIBinder, record); + + Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + verify(mBluetoothController, atLeastOnce()).start(); + assertThat(start).isTrue(); + } + + @Test + public void testStartProviders_chreOnlyChreUndetermined_bleProviderNotStarted() { + when(mChreDiscoveryProvider.available()).thenReturn(null); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getChreOnlyPresenceScanFilter()).build(); + DiscoveryProviderManagerLegacy.ScanListenerRecord record = + new DiscoveryProviderManagerLegacy.ScanListenerRecord( + scanRequest, mScanListener, + mCallerIdentity, mScanListenerDeathRecipient); + mScanTypeScanListenerRecordMap.put(mIBinder, record); + + Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + verify(mBluetoothController, never()).start(); + assertThat(start).isNull(); + } + + @Test + public void testStartProviders_notChreOnlyChreUndetermined_bleProviderStarted() { + when(mChreDiscoveryProvider.available()).thenReturn(null); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getPresenceScanFilter()).build(); + DiscoveryProviderManagerLegacy.ScanListenerRecord record = + new DiscoveryProviderManagerLegacy.ScanListenerRecord( + scanRequest, mScanListener, + mCallerIdentity, mScanListenerDeathRecipient); + mScanTypeScanListenerRecordMap.put(mIBinder, record); + + Boolean start = mDiscoveryProviderManager.startProviders(scanRequest); + verify(mBluetoothController, atLeastOnce()).start(); + assertThat(start).isTrue(); + } + + @Test + public void test_stopChreProvider_clearFilters() throws Exception { + // Cannot use mocked ChreDiscoveryProvider, + // so we cannot use class variable mDiscoveryProviderManager + ExecutorService executor = Executors.newSingleThreadExecutor(); + DiscoveryProviderManagerLegacy manager = + new DiscoveryProviderManagerLegacy(mContext, mInjector, + mBleDiscoveryProvider, + new ChreDiscoveryProvider( + mContext, + new ChreCommunication(mInjector, mContext, executor), executor), + mScanTypeScanListenerRecordMap); + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getPresenceScanFilter()).build(); + DiscoveryProviderManagerLegacy.ScanListenerRecord record = + new DiscoveryProviderManagerLegacy.ScanListenerRecord( + scanRequest, mScanListener, + mCallerIdentity, mScanListenerDeathRecipient); + mScanTypeScanListenerRecordMap.put(mIBinder, record); + manager.startChreProvider(List.of(getPresenceScanFilter())); + // This is an asynchronized process. The filters will be set in executor thread. So we need + // to wait for some time to get the correct result. + Thread.sleep(200); + + assertThat(manager.mChreDiscoveryProvider.getController().isStarted()) + .isTrue(); + assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull(); + + manager.stopChreProvider(); + Thread.sleep(200); + // The filters should be cleared right after. + assertThat(manager.mChreDiscoveryProvider.getController().isStarted()) + .isFalse(); + assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isEmpty(); + } + + @Test + public void test_restartChreProvider() throws Exception { + // Cannot use mocked ChreDiscoveryProvider, + // so we cannot use class variable mDiscoveryProviderManager + ExecutorService executor = Executors.newSingleThreadExecutor(); + DiscoveryProviderManagerLegacy manager = + new DiscoveryProviderManagerLegacy(mContext, mInjector, + mBleDiscoveryProvider, + new ChreDiscoveryProvider( + mContext, + new ChreCommunication(mInjector, mContext, executor), executor), + mScanTypeScanListenerRecordMap); + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getPresenceScanFilter()).build(); + DiscoveryProviderManagerLegacy.ScanListenerRecord record = + new DiscoveryProviderManagerLegacy.ScanListenerRecord(scanRequest, mScanListener, + mCallerIdentity, mScanListenerDeathRecipient); + mScanTypeScanListenerRecordMap.put(mIBinder, record); + manager.startChreProvider(List.of(getPresenceScanFilter())); + // This is an asynchronized process. The filters will be set in executor thread. So we need + // to wait for some time to get the correct result. + Thread.sleep(200); + + assertThat(manager.mChreDiscoveryProvider.getController().isStarted()) + .isTrue(); + assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull(); + + // We want to make sure quickly restart the provider the filters should + // be reset correctly. + // See b/255922206, there can be a race condition that filters get cleared because onStop() + // get executed after onStart() if they are called from different threads. + manager.stopChreProvider(); + manager.mChreDiscoveryProvider.getController().setProviderScanFilters( + List.of(getPresenceScanFilter())); + manager.startChreProvider(List.of(getPresenceScanFilter())); + Thread.sleep(200); + assertThat(manager.mChreDiscoveryProvider.getController().isStarted()) + .isTrue(); + assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull(); + + // Wait for enough time + Thread.sleep(1000); + + assertThat(manager.mChreDiscoveryProvider.getController().isStarted()) + .isTrue(); + assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerTest.java new file mode 100644 index 0000000000..7ecf631df3 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/managers/DiscoveryProviderManagerTest.java @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.managers; + +import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE; +import static android.nearby.ScanRequest.SCAN_TYPE_NEARBY_PRESENCE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.AppOpsManager; +import android.content.Context; +import android.nearby.DataElement; +import android.nearby.IScanListener; +import android.nearby.NearbyDeviceParcelable; +import android.nearby.PresenceScanFilter; +import android.nearby.PublicCredential; +import android.nearby.ScanRequest; +import android.os.IBinder; + +import com.android.server.nearby.injector.Injector; +import com.android.server.nearby.provider.BleDiscoveryProvider; +import com.android.server.nearby.provider.ChreCommunication; +import com.android.server.nearby.provider.ChreDiscoveryProvider; +import com.android.server.nearby.provider.DiscoveryProviderController; +import com.android.server.nearby.util.identity.CallerIdentity; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +public class DiscoveryProviderManagerTest { + private static final int SCAN_MODE_CHRE_ONLY = 3; + private static final int DATA_TYPE_SCAN_MODE = 102; + private static final int UID = 1234; + private static final int PID = 5678; + private static final String PACKAGE_NAME = "android.nearby.test"; + private static final int RSSI = -60; + @Mock + Injector mInjector; + @Mock + Context mContext; + @Mock + AppOpsManager mAppOpsManager; + @Mock + BleDiscoveryProvider mBleDiscoveryProvider; + @Mock + ChreDiscoveryProvider mChreDiscoveryProvider; + @Mock + DiscoveryProviderController mBluetoothController; + @Mock + DiscoveryProviderController mChreController; + @Mock + IScanListener mScanListener; + @Mock + CallerIdentity mCallerIdentity; + @Mock + IBinder mIBinder; + private Executor mExecutor; + private DiscoveryProviderManager mDiscoveryProviderManager; + + private static PresenceScanFilter getPresenceScanFilter() { + final byte[] secretId = new byte[]{1, 2, 3, 4}; + final byte[] authenticityKey = new byte[]{0, 1, 1, 1}; + final byte[] publicKey = new byte[]{1, 1, 2, 2}; + final byte[] encryptedMetadata = new byte[]{1, 2, 3, 4, 5}; + final byte[] metadataEncryptionKeyTag = new byte[]{1, 1, 3, 4, 5}; + + PublicCredential credential = new PublicCredential.Builder( + secretId, authenticityKey, publicKey, encryptedMetadata, metadataEncryptionKeyTag) + .setIdentityType(IDENTITY_TYPE_PRIVATE) + .build(); + + final int action = 123; + return new PresenceScanFilter.Builder() + .addCredential(credential) + .setMaxPathLoss(RSSI) + .addPresenceAction(action) + .build(); + } + + private static PresenceScanFilter getChreOnlyPresenceScanFilter() { + final byte[] secretId = new byte[]{1, 2, 3, 4}; + final byte[] authenticityKey = new byte[]{0, 1, 1, 1}; + final byte[] publicKey = new byte[]{1, 1, 2, 2}; + final byte[] encryptedMetadata = new byte[]{1, 2, 3, 4, 5}; + final byte[] metadataEncryptionKeyTag = new byte[]{1, 1, 3, 4, 5}; + + PublicCredential credential = new PublicCredential.Builder( + secretId, authenticityKey, publicKey, encryptedMetadata, metadataEncryptionKeyTag) + .setIdentityType(IDENTITY_TYPE_PRIVATE) + .build(); + + final int action = 123; + DataElement scanModeElement = new DataElement(DATA_TYPE_SCAN_MODE, + new byte[]{SCAN_MODE_CHRE_ONLY}); + return new PresenceScanFilter.Builder() + .addCredential(credential) + .setMaxPathLoss(RSSI) + .addPresenceAction(action) + .addExtendedProperty(scanModeElement) + .build(); + } + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + mExecutor = Executors.newSingleThreadExecutor(); + when(mInjector.getAppOpsManager()).thenReturn(mAppOpsManager); + when(mBleDiscoveryProvider.getController()).thenReturn(mBluetoothController); + when(mChreDiscoveryProvider.getController()).thenReturn(mChreController); + when(mScanListener.asBinder()).thenReturn(mIBinder); + + mDiscoveryProviderManager = + new DiscoveryProviderManager(mContext, mExecutor, mInjector, + mBleDiscoveryProvider, + mChreDiscoveryProvider); + mCallerIdentity = CallerIdentity + .forTest(UID, PID, PACKAGE_NAME, /* attributionTag= */ null); + } + + @Test + public void testOnNearbyDeviceDiscovered() { + NearbyDeviceParcelable nearbyDeviceParcelable = new NearbyDeviceParcelable.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .build(); + mDiscoveryProviderManager.onNearbyDeviceDiscovered(nearbyDeviceParcelable); + } + + @Test + public void testInvalidateProviderScanMode() { + mDiscoveryProviderManager.invalidateProviderScanMode(); + } + + @Test + public void testStartProviders_chreOnlyChreAvailable_bleProviderNotStarted() { + reset(mBluetoothController); + when(mChreDiscoveryProvider.available()).thenReturn(true); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getChreOnlyPresenceScanFilter()).build(); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); + + Boolean start = mDiscoveryProviderManager.startProviders(); + verify(mBluetoothController, never()).start(); + assertThat(start).isTrue(); + } + + @Test + public void testStartProviders_chreOnlyChreAvailable_multipleFilters_bleProviderNotStarted() { + reset(mBluetoothController); + when(mChreDiscoveryProvider.available()).thenReturn(true); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getChreOnlyPresenceScanFilter()).build(); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); + + Boolean start = mDiscoveryProviderManager.startProviders(); + verify(mBluetoothController, never()).start(); + assertThat(start).isTrue(); + } + + @Test + public void testStartProviders_chreOnlyChreUnavailable_bleProviderNotStarted() { + reset(mBluetoothController); + when(mChreDiscoveryProvider.available()).thenReturn(false); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getChreOnlyPresenceScanFilter()).build(); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); + + Boolean start = mDiscoveryProviderManager.startProviders(); + verify(mBluetoothController, never()).start(); + assertThat(start).isFalse(); + } + + @Test + public void testStartProviders_notChreOnlyChreAvailable_bleProviderNotStarted() { + when(mChreDiscoveryProvider.available()).thenReturn(true); + reset(mBluetoothController); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getPresenceScanFilter()).build(); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); + + Boolean start = mDiscoveryProviderManager.startProviders(); + verify(mBluetoothController, never()).start(); + assertThat(start).isTrue(); + } + + @Test + public void testStartProviders_notChreOnlyChreUnavailable_bleProviderStarted() { + when(mChreDiscoveryProvider.available()).thenReturn(false); + reset(mBluetoothController); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getPresenceScanFilter()).build(); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); + + Boolean start = mDiscoveryProviderManager.startProviders(); + verify(mBluetoothController, atLeastOnce()).start(); + assertThat(start).isTrue(); + } + + @Test + public void testStartProviders_chreOnlyChreUndetermined_bleProviderNotStarted() { + when(mChreDiscoveryProvider.available()).thenReturn(null); + reset(mBluetoothController); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getChreOnlyPresenceScanFilter()).build(); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); + + Boolean start = mDiscoveryProviderManager.startProviders(); + verify(mBluetoothController, never()).start(); + assertThat(start).isNull(); + } + + @Test + public void testStartProviders_notChreOnlyChreUndetermined_bleProviderStarted() { + when(mChreDiscoveryProvider.available()).thenReturn(null); + reset(mBluetoothController); + + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getPresenceScanFilter()).build(); + mDiscoveryProviderManager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); + + Boolean start = mDiscoveryProviderManager.startProviders(); + verify(mBluetoothController, atLeastOnce()).start(); + assertThat(start).isTrue(); + } + + @Test + public void test_stopChreProvider_clearFilters() throws Exception { + // Cannot use mocked ChreDiscoveryProvider, + // so we cannot use class variable mDiscoveryProviderManager + DiscoveryProviderManager manager = + new DiscoveryProviderManager(mContext, mExecutor, mInjector, + mBleDiscoveryProvider, + new ChreDiscoveryProvider( + mContext, + new ChreCommunication(mInjector, mContext, mExecutor), mExecutor)); + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getPresenceScanFilter()).build(); + manager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); + manager.startChreProvider(List.of(getPresenceScanFilter())); + // This is an asynchronized process. The filters will be set in executor thread. So we need + // to wait for some time to get the correct result. + Thread.sleep(200); + + assertThat(manager.mChreDiscoveryProvider.getController().isStarted()) + .isTrue(); + assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull(); + + manager.stopChreProvider(); + Thread.sleep(200); + // The filters should be cleared right after. + assertThat(manager.mChreDiscoveryProvider.getController().isStarted()) + .isFalse(); + assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isEmpty(); + } + + @Test + public void test_restartChreProvider() throws Exception { + // Cannot use mocked ChreDiscoveryProvider, + // so we cannot use class variable mDiscoveryProviderManager + DiscoveryProviderManager manager = + new DiscoveryProviderManager(mContext, mExecutor, mInjector, + mBleDiscoveryProvider, + new ChreDiscoveryProvider( + mContext, + new ChreCommunication(mInjector, mContext, mExecutor), mExecutor)); + ScanRequest scanRequest = new ScanRequest.Builder() + .setScanType(SCAN_TYPE_NEARBY_PRESENCE) + .addScanFilter(getPresenceScanFilter()).build(); + manager.registerScanListener(scanRequest, mScanListener, mCallerIdentity); + + manager.startChreProvider(List.of(getPresenceScanFilter())); + // This is an asynchronized process. The filters will be set in executor thread. So we need + // to wait for some time to get the correct result. + Thread.sleep(200); + + assertThat(manager.mChreDiscoveryProvider.getController().isStarted()) + .isTrue(); + assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull(); + + // We want to make sure quickly restart the provider the filters should + // be reset correctly. + // See b/255922206, there can be a race condition that filters get cleared because onStop() + // get executed after onStart() if they are called from different threads. + manager.stopChreProvider(); + manager.mChreDiscoveryProvider.getController().setProviderScanFilters( + List.of(getPresenceScanFilter())); + manager.startChreProvider(List.of(getPresenceScanFilter())); + Thread.sleep(200); + assertThat(manager.mChreDiscoveryProvider.getController().isStarted()) + .isTrue(); + assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull(); + + // Wait for enough time + Thread.sleep(1000); + + assertThat(manager.mChreDiscoveryProvider.getController().isStarted()) + .isTrue(); + assertThat(manager.mChreDiscoveryProvider.getFiltersLocked()).isNotNull(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/ListenerMultiplexerTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/ListenerMultiplexerTest.java new file mode 100644 index 0000000000..104d7627f4 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/managers/ListenerMultiplexerTest.java @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.managers; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.os.IBinder; + +import androidx.annotation.NonNull; + +import com.android.server.nearby.managers.registration.BinderListenerRegistration; + +import com.google.common.util.concurrent.MoreExecutors; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Collection; + +public class ListenerMultiplexerTest { + + @Before + public void setUp() { + initMocks(this); + } + + @Test + public void testAdd() { + TestMultiplexer multiplexer = new TestMultiplexer(); + + Runnable listener = mock(Runnable.class); + IBinder binder = mock(IBinder.class); + int value = 2; + multiplexer.addListener(binder, listener, value); + + synchronized (multiplexer.mMultiplexerLock) { + assertThat(multiplexer.mRegistered).isTrue(); + assertThat(multiplexer.mOnRegisterCalledCount).isEqualTo(1); + assertThat(multiplexer.mMergeOperationCount).isEqualTo(1); + assertThat(multiplexer.mMergeUpdatedCount).isEqualTo(1); + assertThat(multiplexer.mMerged).isEqualTo(value); + } + Runnable listener2 = mock(Runnable.class); + IBinder binder2 = mock(IBinder.class); + int value2 = 1; + multiplexer.addListener(binder2, listener2, value2); + synchronized (multiplexer.mMultiplexerLock) { + assertThat(multiplexer.mRegistered).isTrue(); + assertThat(multiplexer.mOnRegisterCalledCount).isEqualTo(1); + assertThat(multiplexer.mMergeOperationCount).isEqualTo(2); + assertThat(multiplexer.mMergeUpdatedCount).isEqualTo(1); + assertThat(multiplexer.mMerged).isEqualTo(value); + } + } + + @Test + public void testReplace() { + TestMultiplexer multiplexer = new TestMultiplexer(); + Runnable listener = mock(Runnable.class); + IBinder binder = mock(IBinder.class); + int value = 2; + multiplexer.addListener(binder, listener, value); + synchronized (multiplexer.mMultiplexerLock) { + assertThat(multiplexer.mRegistered).isTrue(); + assertThat(multiplexer.mOnRegisterCalledCount).isEqualTo(1); + assertThat(multiplexer.mMerged).isEqualTo(value); + } + multiplexer.notifyListeners(); + verify(listener, times(1)).run(); + reset(listener); + + // Same key, different value + Runnable listener2 = mock(Runnable.class); + int value2 = 1; + multiplexer.addListener(binder, listener2, value2); + synchronized (multiplexer.mMultiplexerLock) { + assertThat(multiplexer.mRegistered).isTrue(); + // Should not be called again + assertThat(multiplexer.mOnRegisterCalledCount).isEqualTo(1); + assertThat(multiplexer.mOnUnregisterCalledCount).isEqualTo(0); + assertThat(multiplexer.mMerged).isEqualTo(value2); + } + // Run on the new listener + multiplexer.notifyListeners(); + verify(listener, never()).run(); + verify(listener2, times(1)).run(); + + multiplexer.removeRegistration(binder); + + synchronized (multiplexer.mMultiplexerLock) { + assertThat(multiplexer.mRegistered).isFalse(); + assertThat(multiplexer.mOnRegisterCalledCount).isEqualTo(1); + assertThat(multiplexer.mOnUnregisterCalledCount).isEqualTo(1); + assertThat(multiplexer.mMerged).isEqualTo(Integer.MIN_VALUE); + } + } + + @Test + public void testRemove() { + TestMultiplexer multiplexer = new TestMultiplexer(); + Runnable listener = mock(Runnable.class); + IBinder binder = mock(IBinder.class); + int value = 2; + multiplexer.addListener(binder, listener, value); + synchronized (multiplexer.mMultiplexerLock) { + assertThat(multiplexer.mRegistered).isTrue(); + assertThat(multiplexer.mMerged).isEqualTo(value); + } + multiplexer.notifyListeners(); + verify(listener, times(1)).run(); + reset(listener); + + multiplexer.removeRegistration(binder); + synchronized (multiplexer.mMultiplexerLock) { + assertThat(multiplexer.mRegistered).isFalse(); + assertThat(multiplexer.mMerged).isEqualTo(Integer.MIN_VALUE); + } + multiplexer.notifyListeners(); + verify(listener, never()).run(); + } + + @Test + public void testMergeMultiple() { + TestMultiplexer multiplexer = new TestMultiplexer(); + + Runnable listener = mock(Runnable.class); + IBinder binder = mock(IBinder.class); + int value = 2; + + Runnable listener2 = mock(Runnable.class); + IBinder binder2 = mock(IBinder.class); + int value2 = 1; + + Runnable listener3 = mock(Runnable.class); + IBinder binder3 = mock(IBinder.class); + int value3 = 5; + + multiplexer.addListener(binder, listener, value); + synchronized (multiplexer.mMultiplexerLock) { + assertThat(multiplexer.mRegistered).isTrue(); + assertThat(multiplexer.mOnRegisterCalledCount).isEqualTo(1); + assertThat(multiplexer.mMergeOperationCount).isEqualTo(1); + assertThat(multiplexer.mMergeUpdatedCount).isEqualTo(1); + assertThat(multiplexer.mMerged).isEqualTo(value); + } + multiplexer.notifyListeners(); + verify(listener, times(1)).run(); + verify(listener2, never()).run(); + verify(listener3, never()).run(); + + multiplexer.addListener(binder2, listener2, value2); + synchronized (multiplexer.mMultiplexerLock) { + assertThat(multiplexer.mRegistered).isTrue(); + assertThat(multiplexer.mOnRegisterCalledCount).isEqualTo(1); + assertThat(multiplexer.mMergeOperationCount).isEqualTo(2); + assertThat(multiplexer.mMergeUpdatedCount).isEqualTo(1); + assertThat(multiplexer.mMerged).isEqualTo(value); + } + multiplexer.notifyListeners(); + verify(listener, times(2)).run(); + verify(listener2, times(1)).run(); + verify(listener3, never()).run(); + + multiplexer.addListener(binder3, listener3, value3); + synchronized (multiplexer.mMultiplexerLock) { + assertThat(multiplexer.mRegistered).isTrue(); + assertThat(multiplexer.mOnRegisterCalledCount).isEqualTo(1); + assertThat(multiplexer.mMergeOperationCount).isEqualTo(3); + assertThat(multiplexer.mMergeUpdatedCount).isEqualTo(2); + assertThat(multiplexer.mMerged).isEqualTo(value3); + } + multiplexer.notifyListeners(); + verify(listener, times(3)).run(); + verify(listener2, times(2)).run(); + verify(listener3, times(1)).run(); + + multiplexer.removeRegistration(binder); + synchronized (multiplexer.mMultiplexerLock) { + assertThat(multiplexer.mRegistered).isTrue(); + assertThat(multiplexer.mOnRegisterCalledCount).isEqualTo(1); + assertThat(multiplexer.mMergeOperationCount).isEqualTo(4); + assertThat(multiplexer.mMergeUpdatedCount).isEqualTo(2); + assertThat(multiplexer.mMerged).isEqualTo(value3); + } + multiplexer.notifyListeners(); + verify(listener, times(3)).run(); + verify(listener2, times(3)).run(); + verify(listener3, times(2)).run(); + + multiplexer.removeRegistration(binder3); + synchronized (multiplexer.mMultiplexerLock) { + assertThat(multiplexer.mRegistered).isTrue(); + assertThat(multiplexer.mOnRegisterCalledCount).isEqualTo(1); + assertThat(multiplexer.mMergeOperationCount).isEqualTo(5); + assertThat(multiplexer.mMergeUpdatedCount).isEqualTo(3); + assertThat(multiplexer.mMerged).isEqualTo(value2); + } + multiplexer.notifyListeners(); + verify(listener, times(3)).run(); + verify(listener2, times(4)).run(); + verify(listener3, times(2)).run(); + + multiplexer.removeRegistration(binder2); + synchronized (multiplexer.mMultiplexerLock) { + assertThat(multiplexer.mRegistered).isFalse(); + assertThat(multiplexer.mOnRegisterCalledCount).isEqualTo(1); + assertThat(multiplexer.mMergeOperationCount).isEqualTo(6); + assertThat(multiplexer.mMergeUpdatedCount).isEqualTo(4); + assertThat(multiplexer.mMerged).isEqualTo(Integer.MIN_VALUE); + } + multiplexer.notifyListeners(); + verify(listener, times(3)).run(); + verify(listener2, times(4)).run(); + verify(listener3, times(2)).run(); + } + + private class TestMultiplexer extends + ListenerMultiplexer { + int mOnRegisterCalledCount; + int mOnUnregisterCalledCount; + boolean mRegistered; + private int mMergeOperationCount; + private int mMergeUpdatedCount; + + @Override + public void onRegister() { + mOnRegisterCalledCount++; + mRegistered = true; + } + + @Override + public void onUnregister() { + mOnUnregisterCalledCount++; + mRegistered = false; + } + + @Override + public Integer mergeRegistrations( + @NonNull Collection testListenerRegistrations) { + mMergeOperationCount++; + int max = Integer.MIN_VALUE; + for (TestListenerRegistration registration : testListenerRegistrations) { + max = Math.max(max, registration.getValue()); + } + return max; + } + + @Override + public void onMergedRegistrationsUpdated() { + mMergeUpdatedCount++; + } + + public void addListener(IBinder binder, Runnable runnable, int value) { + TestListenerRegistration registration = new TestListenerRegistration(binder, runnable, + value); + putRegistration(binder, registration); + } + + public void notifyListeners() { + deliverToListeners(registration -> Runnable::run); + } + + private class TestListenerRegistration extends BinderListenerRegistration { + private final int mValue; + + protected TestListenerRegistration(IBinder binder, Runnable runnable, int value) { + super(binder, MoreExecutors.directExecutor(), runnable); + mValue = value; + } + + @Override + public TestMultiplexer getOwner() { + return TestMultiplexer.this; + } + + public int getValue() { + return mValue; + } + } + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/MergedDiscoveryRequestTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/MergedDiscoveryRequestTest.java new file mode 100644 index 0000000000..9281e42acc --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/managers/MergedDiscoveryRequestTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.managers; + +import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE; + +import static com.google.common.truth.Truth.assertThat; + +import android.nearby.DataElement; +import android.nearby.PresenceScanFilter; +import android.nearby.PublicCredential; +import android.nearby.ScanFilter; +import android.nearby.ScanRequest; +import android.util.ArraySet; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +/** + * Unit test for {@link MergedDiscoveryRequest} class. + */ +public class MergedDiscoveryRequestTest { + + @Test + public void test_addScanType() { + MergedDiscoveryRequest.Builder builder = new MergedDiscoveryRequest.Builder(); + builder.addScanType(ScanRequest.SCAN_TYPE_FAST_PAIR); + builder.addScanType(ScanRequest.SCAN_TYPE_NEARBY_PRESENCE); + MergedDiscoveryRequest request = builder.build(); + + assertThat(request.getScanTypes()).isEqualTo(new ArraySet<>( + Arrays.asList(ScanRequest.SCAN_TYPE_FAST_PAIR, + ScanRequest.SCAN_TYPE_NEARBY_PRESENCE))); + } + + @Test + public void test_addActions() { + MergedDiscoveryRequest.Builder builder = new MergedDiscoveryRequest.Builder(); + builder.addActions(new ArrayList<>(Arrays.asList(1, 2, 3))); + builder.addActions(new ArraySet<>(Arrays.asList(2, 3, 4))); + builder.addActions(new ArraySet<>(Collections.singletonList(5))); + + MergedDiscoveryRequest request = builder.build(); + assertThat(request.getActions()).isEqualTo(new ArraySet<>(new Integer[]{1, 2, 3, 4, 5})); + } + + @Test + public void test_addFilters() { + final int rssi = -40; + final int action = 123; + final byte[] secreteId = new byte[]{1, 2, 3, 4}; + final byte[] authenticityKey = new byte[]{0, 1, 1, 1}; + final byte[] publicKey = new byte[]{1, 1, 2, 2}; + final byte[] encryptedMetadata = new byte[]{1, 2, 3, 4, 5}; + final byte[] metadataEncryptionKeyTag = new byte[]{1, 1, 3, 4, 5}; + final int key = 3; + final byte[] value = new byte[]{1, 1, 1, 1}; + + PublicCredential mPublicCredential = new PublicCredential.Builder(secreteId, + authenticityKey, publicKey, encryptedMetadata, + metadataEncryptionKeyTag).setIdentityType(IDENTITY_TYPE_PRIVATE).build(); + PresenceScanFilter scanFilterBuilder = new PresenceScanFilter.Builder().setMaxPathLoss( + rssi).addCredential(mPublicCredential).addPresenceAction( + action).addExtendedProperty(new DataElement(key, value)).build(); + + MergedDiscoveryRequest.Builder builder = new MergedDiscoveryRequest.Builder(); + builder.addScanFilters(Collections.singleton(scanFilterBuilder)); + MergedDiscoveryRequest request = builder.build(); + + Set expectedResult = new ArraySet<>(); + expectedResult.add(scanFilterBuilder); + assertThat(request.getScanFilters()).isEqualTo(expectedResult); + } + + @Test + public void test_addMedium() { + MergedDiscoveryRequest.Builder builder = new MergedDiscoveryRequest.Builder(); + builder.addMedium(MergedDiscoveryRequest.Medium.BLE); + builder.addMedium(MergedDiscoveryRequest.Medium.BLE); + MergedDiscoveryRequest request = builder.build(); + + Set expectedResult = new ArraySet<>(); + expectedResult.add(MergedDiscoveryRequest.Medium.BLE); + assertThat(request.getMediums()).isEqualTo(expectedResult); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/registration/BinderListenerRegistrationTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/registration/BinderListenerRegistrationTest.java new file mode 100644 index 0000000000..88141908c0 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/managers/registration/BinderListenerRegistrationTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.managers.registration; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.os.IBinder; +import android.os.RemoteException; + +import androidx.annotation.NonNull; + +import com.android.server.nearby.managers.ListenerMultiplexer; + +import com.google.common.util.concurrent.MoreExecutors; + +import org.junit.Before; +import org.junit.Test; + +import java.util.Collection; + +/** + * Unit test for {@link BinderListenerRegistration} class. + */ +public class BinderListenerRegistrationTest { + private TestMultiplexer mMultiplexer; + private boolean mOnRegisterCalled; + private boolean mOnUnRegisterCalled; + + @Before + public void setUp() { + mMultiplexer = new TestMultiplexer(); + } + + @Test + public void test_addAndRemove() throws RemoteException { + Runnable listener = mock(Runnable.class); + IBinder binder = mock(IBinder.class); + int value = 2; + BinderListenerRegistration registration = mMultiplexer.addListener(binder, + listener, value); + // First element, onRegister should be called + assertThat(mOnRegisterCalled).isTrue(); + verify(binder, times(1)).linkToDeath(any(), anyInt()); + mMultiplexer.notifyListeners(); + verify(listener, times(1)).run(); + synchronized (mMultiplexer.mMultiplexerLock) { + assertThat(mMultiplexer.mMerged).isEqualTo(value); + } + reset(listener); + + Runnable listener2 = mock(Runnable.class); + IBinder binder2 = mock(IBinder.class); + int value2 = 1; + BinderListenerRegistration registration2 = mMultiplexer.addListener(binder2, + listener2, value2); + verify(binder2, times(1)).linkToDeath(any(), anyInt()); + mMultiplexer.notifyListeners(); + verify(listener2, times(1)).run(); + synchronized (mMultiplexer.mMultiplexerLock) { + assertThat(mMultiplexer.mMerged).isEqualTo(value); + } + reset(listener); + reset(listener2); + + registration2.remove(); + verify(binder2, times(1)).unlinkToDeath(any(), anyInt()); + // Remove one element, onUnregister should NOT be called + assertThat(mOnUnRegisterCalled).isFalse(); + mMultiplexer.notifyListeners(); + verify(listener, times(1)).run(); + synchronized (mMultiplexer.mMultiplexerLock) { + assertThat(mMultiplexer.mMerged).isEqualTo(value); + } + reset(listener); + reset(listener2); + + registration.remove(); + verify(binder, times(1)).unlinkToDeath(any(), anyInt()); + // Remove all elements, onUnregister should NOT be called + assertThat(mOnUnRegisterCalled).isTrue(); + synchronized (mMultiplexer.mMultiplexerLock) { + assertThat(mMultiplexer.mMerged).isEqualTo(Integer.MIN_VALUE); + } + } + + private class TestMultiplexer extends + ListenerMultiplexer { + @Override + public void onRegister() { + mOnRegisterCalled = true; + } + + @Override + public void onUnregister() { + mOnUnRegisterCalled = true; + } + + @Override + public Integer mergeRegistrations( + @NonNull Collection testListenerRegistrations) { + int max = Integer.MIN_VALUE; + for (TestListenerRegistration registration : testListenerRegistrations) { + max = Math.max(max, registration.getValue()); + } + return max; + } + + @Override + public void onMergedRegistrationsUpdated() { + } + + public BinderListenerRegistration addListener(IBinder binder, Runnable runnable, + int value) { + TestListenerRegistration registration = new TestListenerRegistration(binder, runnable, + value); + putRegistration(binder, registration); + return registration; + } + + public void notifyListeners() { + deliverToListeners(registration -> Runnable::run); + } + + private class TestListenerRegistration extends BinderListenerRegistration { + private final int mValue; + + protected TestListenerRegistration(IBinder binder, Runnable runnable, int value) { + super(binder, MoreExecutors.directExecutor(), runnable); + mValue = value; + } + + @Override + public TestMultiplexer getOwner() { + return TestMultiplexer.this; + } + + public int getValue() { + return mValue; + } + } + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/managers/registration/DiscoveryRegistrationTest.java b/nearby/tests/unit/src/com/android/server/nearby/managers/registration/DiscoveryRegistrationTest.java new file mode 100644 index 0000000000..03c4f75c43 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/managers/registration/DiscoveryRegistrationTest.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.managers.registration; + +import static android.nearby.PresenceCredential.IDENTITY_TYPE_PRIVATE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.mockito.MockitoAnnotations.initMocks; + +import android.app.AppOpsManager; +import android.nearby.DataElement; +import android.nearby.IScanListener; +import android.nearby.NearbyDeviceParcelable; +import android.nearby.PresenceScanFilter; +import android.nearby.PublicCredential; +import android.nearby.ScanCallback; +import android.nearby.ScanFilter; +import android.nearby.ScanRequest; +import android.os.IBinder; +import android.util.ArraySet; + +import androidx.annotation.NonNull; + +import com.android.server.nearby.managers.ListenerMultiplexer; +import com.android.server.nearby.managers.MergedDiscoveryRequest; +import com.android.server.nearby.util.identity.CallerIdentity; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.concurrent.Executors; + +/** + * Unit test for {@link DiscoveryRegistration} class. + */ +public class DiscoveryRegistrationTest { + private static final int RSSI = -40; + private static final int ACTION = 123; + private static final byte[] SECRETE_ID = new byte[]{1, 2, 3, 4}; + private static final byte[] AUTHENTICITY_KEY = new byte[]{0, 1, 1, 1}; + private static final byte[] PUBLIC_KEY = new byte[]{1, 1, 2, 2}; + private static final byte[] ENCRYPTED_METADATA = new byte[]{1, 2, 3, 4, 5}; + private static final byte[] METADATA_ENCRYPTION_KEY_TAG = new byte[]{1, 1, 3, 4, 5}; + private static final int KEY = 3; + private static final byte[] VALUE = new byte[]{1, 1, 1, 1}; + private final PublicCredential mPublicCredential = new PublicCredential.Builder(SECRETE_ID, + AUTHENTICITY_KEY, PUBLIC_KEY, ENCRYPTED_METADATA, + METADATA_ENCRYPTION_KEY_TAG).setIdentityType(IDENTITY_TYPE_PRIVATE).build(); + private final PresenceScanFilter mFilter = new PresenceScanFilter.Builder().setMaxPathLoss( + 50).addCredential(mPublicCredential).addPresenceAction(ACTION).addExtendedProperty( + new DataElement(KEY, VALUE)).build(); + private DiscoveryRegistration mDiscoveryRegistration; + private ScanRequest mScanRequest; + private TestDiscoveryManager mOwner; + private Object mMultiplexLock; + @Mock + private IScanListener mCallback; + @Mock + private CallerIdentity mIdentity; + @Mock + private AppOpsManager mAppOpsManager; + @Mock + private IBinder mBinder; + + @Before + public void setUp() { + initMocks(this); + when(mCallback.asBinder()).thenReturn(mBinder); + when(mAppOpsManager.noteOp(eq("android:bluetooth_scan"), eq(0), eq(null), eq(null), + eq(null))).thenReturn(AppOpsManager.MODE_ALLOWED); + + mOwner = new TestDiscoveryManager(); + mMultiplexLock = new Object(); + mScanRequest = new ScanRequest.Builder().setScanType( + ScanRequest.SCAN_TYPE_NEARBY_PRESENCE).addScanFilter(mFilter).build(); + mDiscoveryRegistration = new DiscoveryRegistration(mOwner, mScanRequest, mCallback, + Executors.newSingleThreadExecutor(), mIdentity, mMultiplexLock, mAppOpsManager); + } + + @Test + public void test_getScanRequest() { + assertThat(mDiscoveryRegistration.getScanRequest()).isEqualTo(mScanRequest); + } + + @Test + public void test_getActions() { + Set result = new ArraySet<>(); + result.add(ACTION); + assertThat(mDiscoveryRegistration.getActions()).isEqualTo(result); + } + + @Test + public void test_getOwner() { + assertThat(mDiscoveryRegistration.getOwner()).isEqualTo(mOwner); + } + + @Test + public void test_getPresenceScanFilters() { + Set result = new ArraySet<>(); + result.add(mFilter); + assertThat(mDiscoveryRegistration.getPresenceScanFilters()).isEqualTo(result); + } + + @Test + public void test_presenceFilterMatches_match() { + NearbyDeviceParcelable device = new NearbyDeviceParcelable.Builder().setDeviceId( + 123).setName("test").setTxPower(RSSI + 1).setRssi(RSSI).setScanType( + ScanRequest.SCAN_TYPE_NEARBY_PRESENCE).setAction(ACTION).setEncryptionKeyTag( + METADATA_ENCRYPTION_KEY_TAG).build(); + assertThat(DiscoveryRegistration.presenceFilterMatches(device, List.of(mFilter))).isTrue(); + } + + @Test + public void test_presenceFilterMatches_emptyFilter() { + NearbyDeviceParcelable device = new NearbyDeviceParcelable.Builder().setDeviceId( + 123).setName("test").setScanType(ScanRequest.SCAN_TYPE_NEARBY_PRESENCE).build(); + assertThat(DiscoveryRegistration.presenceFilterMatches(device, List.of())).isTrue(); + } + + @Test + public void test_presenceFilterMatches_actionNotMatch() { + NearbyDeviceParcelable device = new NearbyDeviceParcelable.Builder().setDeviceId( + 12).setName("test").setRssi(RSSI).setScanType( + ScanRequest.SCAN_TYPE_NEARBY_PRESENCE).setAction(5).setEncryptionKeyTag( + METADATA_ENCRYPTION_KEY_TAG).build(); + assertThat(DiscoveryRegistration.presenceFilterMatches(device, List.of(mFilter))).isFalse(); + } + + @Test + public void test_onDiscoveredOnUpdatedCalled() throws Exception { + final long deviceId = 122; + NearbyDeviceParcelable.Builder builder = new NearbyDeviceParcelable.Builder().setDeviceId( + deviceId).setName("test").setTxPower(RSSI + 1).setRssi(RSSI).setScanType( + ScanRequest.SCAN_TYPE_NEARBY_PRESENCE).setAction(ACTION).setEncryptionKeyTag( + METADATA_ENCRYPTION_KEY_TAG); + runOperation(mDiscoveryRegistration.onNearbyDeviceDiscovered(builder.build())); + + verify(mCallback, times(1)).onDiscovered(eq(builder.build())); + verify(mCallback, never()).onUpdated(any()); + verify(mCallback, never()).onLost(any()); + verify(mCallback, never()).onError(anyInt()); + assertThat(mDiscoveryRegistration.getDiscoveryOnLostAlarms().get(deviceId)).isNotNull(); + reset(mCallback); + + // Update RSSI + runOperation( + mDiscoveryRegistration.onNearbyDeviceDiscovered(builder.setRssi(RSSI - 1).build())); + verify(mCallback, never()).onDiscovered(any()); + verify(mCallback, times(1)).onUpdated(eq(builder.build())); + verify(mCallback, never()).onLost(any()); + verify(mCallback, never()).onError(anyInt()); + assertThat(mDiscoveryRegistration.getDiscoveryOnLostAlarms().get(deviceId)).isNotNull(); + } + + @Test + public void test_onLost() throws Exception { + final long deviceId = 123; + NearbyDeviceParcelable device = new NearbyDeviceParcelable.Builder().setDeviceId( + deviceId).setName("test").setTxPower(RSSI + 1).setRssi(RSSI).setScanType( + ScanRequest.SCAN_TYPE_NEARBY_PRESENCE).setAction(ACTION).setEncryptionKeyTag( + METADATA_ENCRYPTION_KEY_TAG).build(); + runOperation(mDiscoveryRegistration.onNearbyDeviceDiscovered(device)); + assertThat(mDiscoveryRegistration.getDiscoveryOnLostAlarms().get(deviceId)).isNotNull(); + verify(mCallback, times(1)).onDiscovered(eq(device)); + verify(mCallback, never()).onUpdated(any()); + verify(mCallback, never()).onError(anyInt()); + verify(mCallback, never()).onLost(any()); + reset(mCallback); + + runOperation(mDiscoveryRegistration.reportDeviceLost(device)); + + assertThat(mDiscoveryRegistration.getDiscoveryOnLostAlarms().get(deviceId)).isNull(); + verify(mCallback, never()).onDiscovered(eq(device)); + verify(mCallback, never()).onUpdated(any()); + verify(mCallback, never()).onError(anyInt()); + verify(mCallback, times(1)).onLost(eq(device)); + } + + @Test + public void test_onError() throws Exception { + AppOpsManager manager = mock(AppOpsManager.class); + when(manager.noteOp(eq("android:bluetooth_scan"), eq(0), eq(null), eq(null), + eq(null))).thenReturn(AppOpsManager.MODE_IGNORED); + + DiscoveryRegistration r = new DiscoveryRegistration(mOwner, mScanRequest, mCallback, + Executors.newSingleThreadExecutor(), mIdentity, mMultiplexLock, manager); + + NearbyDeviceParcelable device = new NearbyDeviceParcelable.Builder().setDeviceId( + 123).setName("test").setTxPower(RSSI + 1).setRssi(RSSI).setScanType( + ScanRequest.SCAN_TYPE_NEARBY_PRESENCE).setAction(ACTION).setEncryptionKeyTag( + METADATA_ENCRYPTION_KEY_TAG).build(); + runOperation(r.onNearbyDeviceDiscovered(device)); + + verify(mCallback, never()).onDiscovered(any()); + verify(mCallback, never()).onUpdated(any()); + verify(mCallback, never()).onLost(any()); + verify(mCallback, times(1)).onError(eq(ScanCallback.ERROR_PERMISSION_DENIED)); + } + + private void runOperation(BinderListenerRegistration.ListenerOperation operation) + throws Exception { + if (operation == null) { + return; + } + operation.onScheduled(false); + operation.operate(mCallback); + operation.onComplete(/* success= */ true); + } + + private static class TestDiscoveryManager extends + ListenerMultiplexer { + + @Override + public MergedDiscoveryRequest mergeRegistrations( + @NonNull Collection discoveryRegistrations) { + return null; + } + + @Override + public void onMergedRegistrationsUpdated() { + + } + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/DataElementHeaderTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/DataElementHeaderTest.java new file mode 100644 index 0000000000..e186709f96 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/presence/DataElementHeaderTest.java @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.presence; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import android.nearby.BroadcastRequest; + +import org.junit.Test; + +import java.util.List; + +/** + * Unit test for {@link DataElementHeader}. + */ +public class DataElementHeaderTest { + + private static final int VERSION = BroadcastRequest.PRESENCE_VERSION_V1; + + @Test + public void test_illegalLength() { + assertThrows(IllegalArgumentException.class, + () -> new DataElementHeader(VERSION, 12, 128)); + } + + @Test + public void test_singeByteConversion() { + DataElementHeader header = new DataElementHeader(VERSION, 12, 3); + byte[] bytes = header.toBytes(); + assertThat(bytes).isEqualTo(new byte[]{(byte) 0b00111100}); + + DataElementHeader afterConversionHeader = DataElementHeader.fromBytes(VERSION, bytes); + assertThat(afterConversionHeader.getDataLength()).isEqualTo(3); + assertThat(afterConversionHeader.getDataType()).isEqualTo(12); + } + + @Test + public void test_multipleBytesConversion() { + DataElementHeader header = new DataElementHeader(VERSION, 6, 100); + DataElementHeader afterConversionHeader = + DataElementHeader.fromBytes(VERSION, header.toBytes()); + assertThat(afterConversionHeader.getDataLength()).isEqualTo(100); + assertThat(afterConversionHeader.getDataType()).isEqualTo(6); + } + + @Test + public void test_fromBytes() { + // Single byte case. + byte[] singleByte = new byte[]{(byte) 0b01011101}; + DataElementHeader singeByteHeader = DataElementHeader.fromBytes(VERSION, singleByte); + assertThat(singeByteHeader.getDataLength()).isEqualTo(5); + assertThat(singeByteHeader.getDataType()).isEqualTo(13); + + // Two bytes case. + byte[] twoBytes = new byte[]{(byte) 0b11011101, (byte) 0b01011101}; + DataElementHeader twoBytesHeader = DataElementHeader.fromBytes(VERSION, twoBytes); + assertThat(twoBytesHeader.getDataLength()).isEqualTo(93); + assertThat(twoBytesHeader.getDataType()).isEqualTo(93); + + // Three bytes case. + byte[] threeBytes = new byte[]{(byte) 0b11011101, (byte) 0b11111111, (byte) 0b01011101}; + DataElementHeader threeBytesHeader = DataElementHeader.fromBytes(VERSION, threeBytes); + assertThat(threeBytesHeader.getDataLength()).isEqualTo(93); + assertThat(threeBytesHeader.getDataType()).isEqualTo(16349); + + // Four bytes case. + byte[] fourBytes = new byte[]{ + (byte) 0b11011101, (byte) 0b11111111, (byte) 0b11111111, (byte) 0b01011101}; + + DataElementHeader fourBytesHeader = DataElementHeader.fromBytes(VERSION, fourBytes); + assertThat(fourBytesHeader.getDataLength()).isEqualTo(93); + assertThat(fourBytesHeader.getDataType()).isEqualTo(2097117); + } + + @Test + public void test_fromBytesIllegal_singleByte() { + assertThrows(IllegalArgumentException.class, + () -> DataElementHeader.fromBytes(VERSION, new byte[]{(byte) 0b11011101})); + } + + @Test + public void test_fromBytesIllegal_twoBytes_wrongFirstByte() { + assertThrows(IllegalArgumentException.class, + () -> DataElementHeader.fromBytes(VERSION, + new byte[]{(byte) 0b01011101, (byte) 0b01011101})); + } + + @Test + public void test_fromBytesIllegal_twoBytes_wrongLastByte() { + assertThrows(IllegalArgumentException.class, + () -> DataElementHeader.fromBytes(VERSION, + new byte[]{(byte) 0b11011101, (byte) 0b11011101})); + } + + @Test + public void test_fromBytesIllegal_threeBytes() { + assertThrows(IllegalArgumentException.class, + () -> DataElementHeader.fromBytes(VERSION, + new byte[]{(byte) 0b11011101, (byte) 0b11011101, (byte) 0b11011101})); + } + + @Test + public void test_multipleBytesConversion_largeNumber() { + DataElementHeader header = new DataElementHeader(VERSION, 22213546, 66); + DataElementHeader afterConversionHeader = + DataElementHeader.fromBytes(VERSION, header.toBytes()); + assertThat(afterConversionHeader.getDataLength()).isEqualTo(66); + assertThat(afterConversionHeader.getDataType()).isEqualTo(22213546); + } + + @Test + public void test_isExtending() { + assertThat(DataElementHeader.isExtending((byte) 0b10000100)).isTrue(); + assertThat(DataElementHeader.isExtending((byte) 0b01110100)).isFalse(); + assertThat(DataElementHeader.isExtending((byte) 0b00000000)).isFalse(); + } + + @Test + public void test_convertTag() { + assertThat(DataElementHeader.convertTag(true)).isEqualTo((byte) 128); + assertThat(DataElementHeader.convertTag(false)).isEqualTo(0); + } + + @Test + public void test_getHeaderValue() { + assertThat(DataElementHeader.getHeaderValue((byte) 0b10000100)).isEqualTo(4); + assertThat(DataElementHeader.getHeaderValue((byte) 0b00000100)).isEqualTo(4); + assertThat(DataElementHeader.getHeaderValue((byte) 0b11010100)).isEqualTo(84); + assertThat(DataElementHeader.getHeaderValue((byte) 0b01010100)).isEqualTo(84); + } + + @Test + public void test_convertTypeMultipleIntList() { + List list = DataElementHeader.convertTypeMultipleBytes(128); + assertThat(list.size()).isEqualTo(2); + assertThat(list.get(0)).isEqualTo((byte) 0b10000001); + assertThat(list.get(1)).isEqualTo((byte) 0b00000000); + + List list2 = DataElementHeader.convertTypeMultipleBytes(10); + assertThat(list2.size()).isEqualTo(1); + assertThat(list2.get(0)).isEqualTo((byte) 0b00001010); + + List list3 = DataElementHeader.convertTypeMultipleBytes(5242398); + assertThat(list3.size()).isEqualTo(4); + assertThat(list3.get(0)).isEqualTo((byte) 0b10000010); + assertThat(list3.get(1)).isEqualTo((byte) 0b10111111); + assertThat(list3.get(2)).isEqualTo((byte) 0b11111100); + assertThat(list3.get(3)).isEqualTo((byte) 0b00011110); + } + + @Test + public void test_getTypeMultipleBytes() { + byte[] inputBytes = new byte[]{(byte) 0b11011000, (byte) 0b10000000, (byte) 0b00001001}; + // 0b101100000000000001001 + assertThat(DataElementHeader.getTypeMultipleBytes(inputBytes)).isEqualTo(1441801); + + byte[] inputBytes2 = new byte[]{(byte) 0b00010010}; + assertThat(DataElementHeader.getTypeMultipleBytes(inputBytes2)).isEqualTo(18); + + byte[] inputBytes3 = new byte[]{(byte) 0b10000001, (byte) 0b00000000}; + assertThat(DataElementHeader.getTypeMultipleBytes(inputBytes3)).isEqualTo(128); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementTest.java new file mode 100644 index 0000000000..895df69356 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementTest.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.presence; + +import static com.google.common.truth.Truth.assertThat; + +import android.nearby.BroadcastRequest; +import android.nearby.DataElement; +import android.nearby.PresenceBroadcastRequest; +import android.nearby.PresenceCredential; +import android.nearby.PrivateCredential; +import android.nearby.PublicCredential; + +import com.android.server.nearby.util.encryption.CryptorImpIdentityV1; +import com.android.server.nearby.util.encryption.CryptorImpV1; + +import org.junit.Before; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ExtendedAdvertisementTest { + private static final int IDENTITY_TYPE = PresenceCredential.IDENTITY_TYPE_PRIVATE; + private static final int DATA_TYPE_MODEL_ID = 7; + private static final int DATA_TYPE_BLE_ADDRESS = 101; + private static final int DATA_TYPE_PUBLIC_IDENTITY = 3; + private static final byte[] MODE_ID_DATA = + new byte[]{2, 1, 30, 2, 10, -16, 6, 22, 44, -2, -86, -69, -52}; + private static final byte[] BLE_ADDRESS = new byte[]{124, 4, 56, 60, 120, -29, -90}; + private static final DataElement MODE_ID_ADDRESS_ELEMENT = + new DataElement(DATA_TYPE_MODEL_ID, MODE_ID_DATA); + private static final DataElement BLE_ADDRESS_ELEMENT = + new DataElement(DATA_TYPE_BLE_ADDRESS, BLE_ADDRESS); + + private static final byte[] IDENTITY = + new byte[]{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}; + private static final int MEDIUM_TYPE_BLE = 0; + private static final byte[] SALT = {2, 3}; + private static final int PRESENCE_ACTION_1 = 1; + private static final int PRESENCE_ACTION_2 = 2; + + private static final byte[] SECRET_ID = new byte[]{1, 2, 3, 4}; + private static final byte[] AUTHENTICITY_KEY = + new byte[]{-97, 10, 107, -86, 25, 65, -54, -95, -72, 59, 54, 93, 9, 3, -24, -88}; + private static final byte[] PUBLIC_KEY = + new byte[] { + 48, 89, 48, 19, 6, 7, 42, -122, 72, -50, 61, 2, 1, 6, 8, 42, -122, 72, -50, 61, + 66, 0, 4, -56, -39, -92, 69, 0, 52, 23, 67, 83, -14, 75, 52, -14, -5, -41, 48, + -83, 31, 42, -39, 102, -13, 22, -73, -73, 86, 30, -96, -84, -13, 4, 122, 104, + -65, 64, 91, -109, -45, -35, -56, 55, -79, 47, -85, 27, -96, -119, -82, -80, + 123, 41, -119, -25, 1, -112, 112 + }; + private static final byte[] ENCRYPTED_METADATA_BYTES = + new byte[] { + -44, -25, -95, -124, -7, 90, 116, -8, 7, -120, -23, -22, -106, -44, -19, 61, + -18, 39, 29, 78, 108, -11, -39, 85, -30, 64, -99, 102, 65, 37, -42, 114, -37, + 88, -112, 8, -75, -53, 23, -16, -104, 67, 49, 48, -53, 73, -109, 44, -23, -11, + -118, -61, -37, -104, 60, 105, 115, 1, 56, -89, -107, -45, -116, -1, -25, 84, + -19, -128, 81, 11, 92, 77, -58, 82, 122, 123, 31, -87, -57, 70, 23, -81, 7, 2, + -114, -83, 74, 124, -68, -98, 47, 91, 9, 48, -67, 41, -7, -97, 78, 66, -65, 58, + -4, -46, -30, -85, -50, 100, 46, -66, -128, 7, 66, 9, 88, 95, 12, -13, 81, -91, + }; + private static final byte[] METADATA_ENCRYPTION_KEY_TAG = + new byte[] {-126, -104, 1, -1, 26, -46, -68, -86}; + private static final String DEVICE_NAME = "test_device"; + + private PresenceBroadcastRequest.Builder mBuilder; + private PrivateCredential mPrivateCredential; + private PublicCredential mPublicCredential; + + @Before + public void setUp() { + mPrivateCredential = + new PrivateCredential.Builder(SECRET_ID, AUTHENTICITY_KEY, IDENTITY, DEVICE_NAME) + .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE) + .build(); + mPublicCredential = + new PublicCredential.Builder(SECRET_ID, AUTHENTICITY_KEY, PUBLIC_KEY, + ENCRYPTED_METADATA_BYTES, METADATA_ENCRYPTION_KEY_TAG) + .build(); + mBuilder = + new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE), + SALT, mPrivateCredential) + .setVersion(BroadcastRequest.PRESENCE_VERSION_V1) + .addAction(PRESENCE_ACTION_1) + .addAction(PRESENCE_ACTION_2) + .addExtendedProperty(new DataElement(DATA_TYPE_BLE_ADDRESS, BLE_ADDRESS)) + .addExtendedProperty(new DataElement(DATA_TYPE_MODEL_ID, MODE_ID_DATA)); + } + + @Test + public void test_createFromRequest() { + ExtendedAdvertisement originalAdvertisement = ExtendedAdvertisement.createFromRequest( + mBuilder.build()); + + assertThat(originalAdvertisement.getActions()) + .containsExactly(PRESENCE_ACTION_1, PRESENCE_ACTION_2); + assertThat(originalAdvertisement.getIdentity()).isEqualTo(IDENTITY); + assertThat(originalAdvertisement.getIdentityType()).isEqualTo(IDENTITY_TYPE); + assertThat(originalAdvertisement.getLength()).isEqualTo(66); + assertThat(originalAdvertisement.getVersion()).isEqualTo( + BroadcastRequest.PRESENCE_VERSION_V1); + assertThat(originalAdvertisement.getSalt()).isEqualTo(SALT); + assertThat(originalAdvertisement.getDataElements()) + .containsExactly(MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT); + } + + @Test + public void test_createFromRequest_encodeAndDecode() { + ExtendedAdvertisement originalAdvertisement = ExtendedAdvertisement.createFromRequest( + mBuilder.build()); + + byte[] generatedBytes = originalAdvertisement.toBytes(); + + ExtendedAdvertisement newAdvertisement = + ExtendedAdvertisement.fromBytes(generatedBytes, mPublicCredential); + + assertThat(newAdvertisement.getActions()) + .containsExactly(PRESENCE_ACTION_1, PRESENCE_ACTION_2); + assertThat(newAdvertisement.getIdentity()).isEqualTo(IDENTITY); + assertThat(newAdvertisement.getIdentityType()).isEqualTo(IDENTITY_TYPE); + assertThat(newAdvertisement.getLength()).isEqualTo(66); + assertThat(newAdvertisement.getVersion()).isEqualTo( + BroadcastRequest.PRESENCE_VERSION_V1); + assertThat(newAdvertisement.getSalt()).isEqualTo(SALT); + assertThat(newAdvertisement.getDataElements()) + .containsExactly(MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT); + } + + @Test + public void test_createFromRequest_invalidParameter() { + // invalid version + mBuilder.setVersion(BroadcastRequest.PRESENCE_VERSION_V0); + assertThat(ExtendedAdvertisement.createFromRequest(mBuilder.build())).isNull(); + + // invalid salt + PresenceBroadcastRequest.Builder builder = + new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE), + new byte[]{1, 2, 3}, mPrivateCredential) + .setVersion(BroadcastRequest.PRESENCE_VERSION_V1) + .addAction(PRESENCE_ACTION_1); + assertThat(ExtendedAdvertisement.createFromRequest(builder.build())).isNull(); + + // invalid identity + PrivateCredential privateCredential = + new PrivateCredential.Builder(SECRET_ID, + AUTHENTICITY_KEY, new byte[]{1, 2, 3, 4}, DEVICE_NAME) + .setIdentityType(PresenceCredential.IDENTITY_TYPE_PRIVATE) + .build(); + PresenceBroadcastRequest.Builder builder2 = + new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE), + new byte[]{1, 2, 3}, privateCredential) + .setVersion(BroadcastRequest.PRESENCE_VERSION_V1) + .addAction(PRESENCE_ACTION_1); + assertThat(ExtendedAdvertisement.createFromRequest(builder2.build())).isNull(); + + // empty action + PresenceBroadcastRequest.Builder builder3 = + new PresenceBroadcastRequest.Builder(Collections.singletonList(MEDIUM_TYPE_BLE), + SALT, mPrivateCredential) + .setVersion(BroadcastRequest.PRESENCE_VERSION_V1); + assertThat(ExtendedAdvertisement.createFromRequest(builder3.build())).isNull(); + } + + @Test + public void test_toBytes() { + ExtendedAdvertisement adv = ExtendedAdvertisement.createFromRequest(mBuilder.build()); + assertThat(adv.toBytes()).isEqualTo(getExtendedAdvertisementByteArray()); + } + + @Test + public void test_fromBytes() { + byte[] originalBytes = getExtendedAdvertisementByteArray(); + ExtendedAdvertisement adv = + ExtendedAdvertisement.fromBytes(originalBytes, mPublicCredential); + + assertThat(adv.getActions()) + .containsExactly(PRESENCE_ACTION_1, PRESENCE_ACTION_2); + assertThat(adv.getIdentity()).isEqualTo(IDENTITY); + assertThat(adv.getIdentityType()).isEqualTo(IDENTITY_TYPE); + assertThat(adv.getLength()).isEqualTo(66); + assertThat(adv.getVersion()).isEqualTo( + BroadcastRequest.PRESENCE_VERSION_V1); + assertThat(adv.getSalt()).isEqualTo(SALT); + assertThat(adv.getDataElements()) + .containsExactly(MODE_ID_ADDRESS_ELEMENT, BLE_ADDRESS_ELEMENT); + } + + @Test + public void test_toString() { + ExtendedAdvertisement adv = ExtendedAdvertisement.createFromRequest(mBuilder.build()); + assertThat(adv.toString()).isEqualTo("ExtendedAdvertisement:" + + ""); + } + + @Test + public void test_getDataElements_accordingToType() { + ExtendedAdvertisement adv = ExtendedAdvertisement.createFromRequest(mBuilder.build()); + List dataElements = new ArrayList<>(); + + dataElements.add(BLE_ADDRESS_ELEMENT); + assertThat(adv.getDataElements(DATA_TYPE_BLE_ADDRESS)).isEqualTo(dataElements); + assertThat(adv.getDataElements(DATA_TYPE_PUBLIC_IDENTITY)).isEmpty(); + } + + private static byte[] getExtendedAdvertisementByteArray() { + ByteBuffer buffer = ByteBuffer.allocate(66); + buffer.put((byte) 0b00100000); // Header V1 + buffer.put((byte) 0b00100000); // Salt header: length 2, type 0 + // Salt data + buffer.put(SALT); + // Identity header: length 16, type 1 (private identity) + buffer.put(new byte[]{(byte) 0b10010000, (byte) 0b00000001}); + // Identity data + buffer.put(CryptorImpIdentityV1.getInstance().encrypt(IDENTITY, SALT, AUTHENTICITY_KEY)); + + ByteBuffer deBuffer = ByteBuffer.allocate(28); + // Action1 header: length 1, type 6 + deBuffer.put(new byte[]{(byte) 0b00010110}); + // Action1 data + deBuffer.put((byte) PRESENCE_ACTION_1); + // Action2 header: length 1, type 6 + deBuffer.put(new byte[]{(byte) 0b00010110}); + // Action2 data + deBuffer.put((byte) PRESENCE_ACTION_2); + // Ble address header: length 7, type 102 + deBuffer.put(new byte[]{(byte) 0b10000111, (byte) 0b01100101}); + // Ble address data + deBuffer.put(BLE_ADDRESS); + // model id header: length 13, type 7 + deBuffer.put(new byte[]{(byte) 0b10001101, (byte) 0b00000111}); + // model id data + deBuffer.put(MODE_ID_DATA); + + byte[] data = deBuffer.array(); + CryptorImpV1 cryptor = CryptorImpV1.getInstance(); + buffer.put(cryptor.encrypt(data, SALT, AUTHENTICITY_KEY)); + buffer.put(cryptor.sign(data, AUTHENTICITY_KEY)); + + return buffer.array(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementUtilsTest.java new file mode 100644 index 0000000000..c4fccf7d3e --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/presence/ExtendedAdvertisementUtilsTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.presence; + +import static com.google.common.truth.Truth.assertThat; + +import android.nearby.BroadcastRequest; +import android.nearby.DataElement; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * Unit test for {@link ExtendedAdvertisementUtils}. + */ +public class ExtendedAdvertisementUtilsTest { + private static final byte[] ADVERTISEMENT1 = new byte[]{0b00100000, 12, 34, 78, 10}; + private static final byte[] ADVERTISEMENT2 = new byte[]{0b00100000, 0b00100011, 34, 78, + (byte) 0b10010000, (byte) 0b00000100, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + + private static final int DATA_TYPE_SALT = 3; + private static final int DATA_TYPE_PRIVATE_IDENTITY = 4; + + @Test + public void test_constructHeader() { + assertThat(ExtendedAdvertisementUtils.constructHeader(1)).isEqualTo(0b100000); + assertThat(ExtendedAdvertisementUtils.constructHeader(0)).isEqualTo(0); + assertThat(ExtendedAdvertisementUtils.constructHeader(6)).isEqualTo((byte) 0b11000000); + } + + @Test + public void test_getVersion() { + assertThat(ExtendedAdvertisementUtils.getVersion(ADVERTISEMENT1)).isEqualTo(1); + byte[] adv = new byte[]{(byte) 0b10111100, 9, 19, 90, 23}; + assertThat(ExtendedAdvertisementUtils.getVersion(adv)).isEqualTo(5); + byte[] adv2 = new byte[]{(byte) 0b10011111, 9, 19, 90, 23}; + assertThat(ExtendedAdvertisementUtils.getVersion(adv2)).isEqualTo(4); + } + + @Test + public void test_getDataElementHeader_salt() { + byte[] saltHeaderArray = ExtendedAdvertisementUtils.getDataElementHeader(ADVERTISEMENT2, 1); + DataElementHeader header = DataElementHeader.fromBytes( + BroadcastRequest.PRESENCE_VERSION_V1, saltHeaderArray); + assertThat(header.getDataType()).isEqualTo(DATA_TYPE_SALT); + assertThat(header.getDataLength()).isEqualTo(ExtendedAdvertisement.SALT_DATA_LENGTH); + } + + @Test + public void test_getDataElementHeader_identity() { + byte[] identityHeaderArray = + ExtendedAdvertisementUtils.getDataElementHeader(ADVERTISEMENT2, 4); + DataElementHeader header = DataElementHeader.fromBytes(BroadcastRequest.PRESENCE_VERSION_V1, + identityHeaderArray); + assertThat(header.getDataType()).isEqualTo(DATA_TYPE_PRIVATE_IDENTITY); + assertThat(header.getDataLength()).isEqualTo(ExtendedAdvertisement.IDENTITY_DATA_LENGTH); + } + + @Test + public void test_constructDataElement_salt() { + DataElement salt = new DataElement(DATA_TYPE_SALT, new byte[]{13, 14}); + byte[] saltArray = ExtendedAdvertisementUtils.convertDataElementToBytes(salt); + // Data length and salt header length. + assertThat(saltArray.length).isEqualTo(ExtendedAdvertisement.SALT_DATA_LENGTH + 1); + // Header + assertThat(saltArray[0]).isEqualTo((byte) 0b00100011); + // Data + assertThat(saltArray[1]).isEqualTo((byte) 13); + assertThat(saltArray[2]).isEqualTo((byte) 14); + } + + @Test + public void test_constructDataElement_privateIdentity() { + byte[] identityData = new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}; + DataElement identity = new DataElement(DATA_TYPE_PRIVATE_IDENTITY, identityData); + byte[] identityArray = ExtendedAdvertisementUtils.convertDataElementToBytes(identity); + // Data length and identity header length. + assertThat(identityArray.length).isEqualTo(ExtendedAdvertisement.IDENTITY_DATA_LENGTH + 2); + // 1st header byte + assertThat(identityArray[0]).isEqualTo((byte) 0b10010000); + // 2st header byte + assertThat(identityArray[1]).isEqualTo((byte) 0b00000100); + // Data + assertThat(Arrays.copyOfRange(identityArray, 2, identityArray.length)) + .isEqualTo(identityData); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/FastAdvertisementTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/FastAdvertisementTest.java index 5e0ccbe96e..8e3e0681c6 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/presence/FastAdvertisementTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/presence/FastAdvertisementTest.java @@ -75,6 +75,15 @@ public class FastAdvertisementTest { assertThat(originalAdvertisement.getVersion()).isEqualTo( BroadcastRequest.PRESENCE_VERSION_V0); assertThat(originalAdvertisement.getSalt()).isEqualTo(SALT); + assertThat(originalAdvertisement.getTxPower()).isEqualTo(TX_POWER); + assertThat(originalAdvertisement.toString()) + .isEqualTo("FastAdvertisement: extendedProperties = new ArrayList<>(); + extendedProperties.add(new DataElement(DATA_TYPE_ACCOUNT_KEY, new byte[]{1, 2, 3, 4})); + extendedProperties.add(new DataElement(DATA_TYPE_INTENT, + new byte[]{(byte) PRESENCE_ACTION})); + assertThat(discoveryResult.accountKeyMatches(extendedProperties)).isTrue(); } @Test @@ -86,4 +143,24 @@ public class PresenceDiscoveryResultTest { assertThat(discoveryResult.matches(scanFilter)).isTrue(); } + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void test_notMatches() { + PresenceDiscoveryResult.Builder builder = new PresenceDiscoveryResult.Builder() + .setPublicCredential(mCredential) + .setSalt(SALT) + .setTxPower(TX_POWER) + .setRssi(RSSI) + .setEncryptedIdentityTag(new byte[]{5, 4, 3, 2, 1}) + .addPresenceAction(PRESENCE_ACTION); + + PresenceScanFilter scanFilter = new PresenceScanFilter.Builder() + .setMaxPathLoss(80) + .addPresenceAction(PRESENCE_ACTION) + .addCredential(mCredential) + .build(); + + PresenceDiscoveryResult discoveryResult = builder.build(); + assertThat(discoveryResult.matches(scanFilter)).isFalse(); + } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java new file mode 100644 index 0000000000..9deb1ebbe4 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/presence/PresenceManagerTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.presence; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.nearby.DataElement; +import android.nearby.PresenceDevice; + +import androidx.test.filters.SdkSuppress; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.nearby.common.locator.LocatorContextWrapper; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class PresenceManagerTest { + private static final byte[] IDENTITY = + new byte[] {1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}; + private static final byte[] SALT = {2, 3}; + private static final byte[] SECRET_ID = + new byte[] {-97, 10, 107, -86, 25, 65, -54, -95, -72, 59, 54, 93, 9, 3, -24, -88}; + + @Mock private Context mContext; + private LocatorContextWrapper mLocatorContextWrapper; + private PresenceManager mPresenceManager; + + @Before + public void setup() { + MockitoAnnotations.initMocks(this); + + mLocatorContextWrapper = new LocatorContextWrapper(mContext); + mPresenceManager = new PresenceManager(mLocatorContextWrapper); + when(mContext.getContentResolver()) + .thenReturn(InstrumentationRegistry.getInstrumentation() + .getContext().getContentResolver()); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testInit() { + mPresenceManager.initiate(); + + verify(mContext, times(1)).registerReceiver(any(), any()); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testDeviceStatusUpdated() { + DataElement dataElement1 = new DataElement(1, new byte[] {1, 2}); + DataElement dataElement2 = new DataElement(2, new byte[] {-1, -2, 3, 4, 5, 6, 7, 8, 9}); + + PresenceDevice presenceDevice = + new PresenceDevice.Builder(/* deviceId= */ "deviceId", SALT, SECRET_ID, IDENTITY) + .addExtendedProperty(dataElement1) + .addExtendedProperty(dataElement2) + .build(); + + mPresenceManager.mScanCallback.onDiscovered(presenceDevice); + mPresenceManager.mScanCallback.onUpdated(presenceDevice); + mPresenceManager.mScanCallback.onLost(presenceDevice); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java index d06a785ffe..05b556b098 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BleBroadcastProviderTest.java @@ -16,6 +16,7 @@ package com.android.server.nearby.provider; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -24,12 +25,14 @@ import android.app.AppOpsManager; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; import android.bluetooth.le.AdvertiseSettings; +import android.bluetooth.le.AdvertisingSetCallback; import android.content.Context; +import android.hardware.location.ContextHubManager; import android.nearby.BroadcastCallback; +import android.nearby.BroadcastRequest; import androidx.test.core.app.ApplicationProvider; -import com.android.server.nearby.injector.ContextHubManagerAdapter; import com.android.server.nearby.injector.Injector; import com.google.common.util.concurrent.MoreExecutors; @@ -59,9 +62,10 @@ public class BleBroadcastProviderTest { } @Test - public void testOnStatus_success() { + public void testOnStatus_success_fastAdv() { byte[] advertiseBytes = new byte[]{1, 2, 3, 4}; - mBleBroadcastProvider.start(advertiseBytes, mBroadcastListener); + mBleBroadcastProvider.start(BroadcastRequest.PRESENCE_VERSION_V0, + advertiseBytes, mBroadcastListener); AdvertiseSettings settings = new AdvertiseSettings.Builder().build(); mBleBroadcastProvider.onStartSuccess(settings); @@ -69,15 +73,47 @@ public class BleBroadcastProviderTest { } @Test - public void testOnStatus_failure() { + public void testOnStatus_success_extendedAdv() { byte[] advertiseBytes = new byte[]{1, 2, 3, 4}; - mBleBroadcastProvider.start(advertiseBytes, mBroadcastListener); + mBleBroadcastProvider.start(BroadcastRequest.PRESENCE_VERSION_V1, + advertiseBytes, mBroadcastListener); + + // advertising set can not be mocked, so we will allow nulls + mBleBroadcastProvider.mAdvertisingSetCallback.onAdvertisingSetStarted(null, -30, + AdvertisingSetCallback.ADVERTISE_SUCCESS); + verify(mBroadcastListener).onStatusChanged(eq(BroadcastCallback.STATUS_OK)); + } + + @Test + public void testOnStatus_failure_fastAdv() { + byte[] advertiseBytes = new byte[]{1, 2, 3, 4}; + mBleBroadcastProvider.start(BroadcastRequest.PRESENCE_VERSION_V0, + advertiseBytes, mBroadcastListener); mBleBroadcastProvider.onStartFailure(BroadcastCallback.STATUS_FAILURE); verify(mBroadcastListener, times(1)) .onStatusChanged(eq(BroadcastCallback.STATUS_FAILURE)); } + @Test + public void testOnStatus_failure_extendedAdv() { + byte[] advertiseBytes = new byte[]{1, 2, 3, 4}; + mBleBroadcastProvider.start(BroadcastRequest.PRESENCE_VERSION_V1, + advertiseBytes, mBroadcastListener); + + // advertising set can not be mocked, so we will allow nulls + mBleBroadcastProvider.mAdvertisingSetCallback.onAdvertisingSetStarted(null, -30, + AdvertisingSetCallback.ADVERTISE_FAILED_INTERNAL_ERROR); + // Can be additional failure if the test device does not support LE Extended Advertising. + verify(mBroadcastListener, atLeastOnce()) + .onStatusChanged(eq(BroadcastCallback.STATUS_FAILURE)); + } + + @Test + public void testStop() { + mBleBroadcastProvider.stop(); + } + private static class TestInjector implements Injector { @Override @@ -88,7 +124,7 @@ public class BleBroadcastProviderTest { } @Override - public ContextHubManagerAdapter getContextHubManagerAdapter() { + public ContextHubManager getContextHubManager() { return null; } diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java index 902cc33719..2d8bd632d9 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/provider/BleDiscoveryProviderTest.java @@ -17,6 +17,9 @@ package com.android.server.nearby.provider; import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES; +import static android.nearby.ScanCallback.ERROR_UNKNOWN; + +import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; @@ -30,10 +33,13 @@ import android.bluetooth.BluetoothManager; import android.bluetooth.le.ScanRecord; import android.bluetooth.le.ScanResult; import android.content.Context; +import android.hardware.location.ContextHubManager; +import android.nearby.PresenceScanFilter; +import android.nearby.PublicCredential; +import android.nearby.ScanFilter; import androidx.test.platform.app.InstrumentationRegistry; -import com.android.server.nearby.injector.ContextHubManagerAdapter; import com.android.server.nearby.injector.Injector; import org.junit.Before; @@ -42,6 +48,8 @@ import org.mockito.Mock; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; public final class BleDiscoveryProviderTest { @@ -49,6 +57,8 @@ public final class BleDiscoveryProviderTest { private BleDiscoveryProvider mBleDiscoveryProvider; @Mock private AbstractDiscoveryProvider.Listener mListener; +// @Mock +// private BluetoothAdapter mBluetoothAdapter; @Before public void setup() { @@ -61,7 +71,7 @@ public final class BleDiscoveryProviderTest { } @Test - public void test_callback() throws InterruptedException { + public void test_callback_found() throws InterruptedException { mBleDiscoveryProvider.getController().setListener(mListener); mBleDiscoveryProvider.onStart(); mBleDiscoveryProvider.getScanCallback() @@ -72,12 +82,40 @@ public final class BleDiscoveryProviderTest { verify(mListener, times(1)).onNearbyDeviceDiscovered(any()); } + @Test + public void test_callback_failed() throws InterruptedException { + mBleDiscoveryProvider.getController().setListener(mListener); + mBleDiscoveryProvider.onStart(); + mBleDiscoveryProvider.getScanCallback().onScanFailed(1); + + + // Wait for callback to be invoked + Thread.sleep(500); + verify(mListener, times(1)).onError(ERROR_UNKNOWN); + } + @Test public void test_stopScan() { mBleDiscoveryProvider.onStart(); mBleDiscoveryProvider.onStop(); } + @Test + public void test_stopScan_filersReset() { + List filterList = new ArrayList<>(); + filterList.add(getSanFilter()); + + mBleDiscoveryProvider.getController().setProviderScanFilters(filterList); + mBleDiscoveryProvider.onStart(); + mBleDiscoveryProvider.onStop(); + assertThat(mBleDiscoveryProvider.getFiltersLocked()).isNull(); + } + + @Test + public void testInvalidateScanMode() { + mBleDiscoveryProvider.invalidateScanMode(); + } + private class TestInjector implements Injector { @Override public BluetoothAdapter getBluetoothAdapter() { @@ -85,7 +123,7 @@ public final class BleDiscoveryProviderTest { } @Override - public ContextHubManagerAdapter getContextHubManagerAdapter() { + public ContextHubManager getContextHubManager() { return null; } @@ -125,4 +163,22 @@ public final class BleDiscoveryProviderTest { return null; } } + + private static PresenceScanFilter getSanFilter() { + return new PresenceScanFilter.Builder() + .setMaxPathLoss(70) + .addCredential(getPublicCredential()) + .addPresenceAction(124) + .build(); + } + + private static PublicCredential getPublicCredential() { + return new PublicCredential.Builder( + new byte[]{1, 2}, + new byte[]{1, 2}, + new byte[]{1, 2}, + new byte[]{1, 2}, + new byte[]{1, 2}) + .build(); + } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java index 1b29b52216..ce479c86da 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreCommunicationTest.java @@ -16,18 +16,33 @@ package com.android.server.nearby.provider; +import static android.Manifest.permission.READ_DEVICE_CONFIG; +import static android.Manifest.permission.WRITE_DEVICE_CONFIG; + +import static com.android.server.nearby.NearbyConfiguration.NEARBY_MAINLINE_NANO_APP_MIN_VERSION; +import static com.android.server.nearby.provider.ChreCommunication.INVALID_NANO_APP_VERSION; + +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.Context; import android.hardware.location.ContextHubClient; import android.hardware.location.ContextHubInfo; +import android.hardware.location.ContextHubManager; import android.hardware.location.ContextHubTransaction; import android.hardware.location.NanoAppMessage; import android.hardware.location.NanoAppState; +import android.os.Build; +import android.provider.DeviceConfig; -import com.android.server.nearby.injector.ContextHubManagerAdapter; +import androidx.test.filters.SdkSuppress; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.nearby.NearbyConfiguration; import com.android.server.nearby.injector.Injector; import org.junit.Before; @@ -42,8 +57,12 @@ import java.util.List; import java.util.concurrent.Executor; public class ChreCommunicationTest { + private static final String NAMESPACE = NearbyConfiguration.getNamespace(); + private static final int APP_VERSION = 1; + @Mock Injector mInjector; - @Mock ContextHubManagerAdapter mManager; + @Mock Context mContext; + @Mock ContextHubManager mManager; @Mock ContextHubTransaction> mTransaction; @Mock ContextHubTransaction.Response> mTransactionResponse; @Mock ContextHubClient mClient; @@ -56,38 +75,78 @@ public class ChreCommunicationTest { @Before public void setUp() { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .adoptShellPermissionIdentity(WRITE_DEVICE_CONFIG, READ_DEVICE_CONFIG); + DeviceConfig.setProperty( + NAMESPACE, NEARBY_MAINLINE_NANO_APP_MIN_VERSION, "1", false); + MockitoAnnotations.initMocks(this); - when(mInjector.getContextHubManagerAdapter()).thenReturn(mManager); + when(mInjector.getContextHubManager()).thenReturn(mManager); when(mManager.getContextHubs()).thenReturn(Collections.singletonList(new ContextHubInfo())); when(mManager.queryNanoApps(any())).thenReturn(mTransaction); - when(mManager.createClient(any(), any(), any())).thenReturn(mClient); + when(mManager.createClient(any(), any(), any(), any())).thenReturn(mClient); when(mTransactionResponse.getResult()).thenReturn(ContextHubTransaction.RESULT_SUCCESS); when(mTransactionResponse.getContents()) .thenReturn( Collections.singletonList( - new NanoAppState(ChreDiscoveryProvider.NANOAPP_ID, 1, true))); + new NanoAppState( + ChreDiscoveryProvider.NANOAPP_ID, + APP_VERSION, + true))); - mChreCommunication = new ChreCommunication(mInjector, new InlineExecutor()); + mChreCommunication = new ChreCommunication(mInjector, mContext, new InlineExecutor()); + } + + @Test + public void testStart() { mChreCommunication.start( mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID)); verify(mTransaction).setOnCompleteListener(mOnQueryCompleteListenerCaptor.capture(), any()); mOnQueryCompleteListenerCaptor.getValue().onComplete(mTransaction, mTransactionResponse); - } - - @Test - public void testStart() { verify(mChreCallback).started(true); } @Test public void testStop() { + mChreCommunication.start( + mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID)); + + verify(mTransaction).setOnCompleteListener(mOnQueryCompleteListenerCaptor.capture(), any()); + mOnQueryCompleteListenerCaptor.getValue().onComplete(mTransaction, mTransactionResponse); mChreCommunication.stop(); verify(mClient).close(); } + @Test + public void testNotReachMinVersion() { + DeviceConfig.setProperty(NAMESPACE, NEARBY_MAINLINE_NANO_APP_MIN_VERSION, "3", false); + mChreCommunication.start( + mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID)); + verify(mTransaction).setOnCompleteListener(mOnQueryCompleteListenerCaptor.capture(), any()); + mOnQueryCompleteListenerCaptor.getValue().onComplete(mTransaction, mTransactionResponse); + verify(mChreCallback).started(false); + } + + @Test + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void test_getNanoVersion() { + assertThat(mChreCommunication.queryNanoAppVersion()).isEqualTo(INVALID_NANO_APP_VERSION); + + mChreCommunication.start( + mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID)); + verify(mTransaction).setOnCompleteListener(mOnQueryCompleteListenerCaptor.capture(), any()); + mOnQueryCompleteListenerCaptor.getValue().onComplete(mTransaction, mTransactionResponse); + + assertThat(mChreCommunication.queryNanoAppVersion()).isEqualTo(APP_VERSION); + } + @Test public void testSendMessageToNanApp() { + mChreCommunication.start( + mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID)); + verify(mTransaction).setOnCompleteListener(mOnQueryCompleteListenerCaptor.capture(), any()); + mOnQueryCompleteListenerCaptor.getValue().onComplete(mTransaction, mTransactionResponse); NanoAppMessage message = NanoAppMessage.createMessageToNanoApp( ChreDiscoveryProvider.NANOAPP_ID, @@ -99,6 +158,8 @@ public class ChreCommunicationTest { @Test public void testOnMessageFromNanoApp() { + mChreCommunication.start( + mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID)); NanoAppMessage message = NanoAppMessage.createMessageToNanoApp( ChreDiscoveryProvider.NANOAPP_ID, @@ -108,14 +169,61 @@ public class ChreCommunicationTest { verify(mChreCallback).onMessageFromNanoApp(eq(message)); } + @Test + public void testContextHubTransactionResultToString() { + assertThat( + mChreCommunication.contextHubTransactionResultToString( + ContextHubTransaction.RESULT_SUCCESS)) + .isEqualTo("RESULT_SUCCESS"); + assertThat( + mChreCommunication.contextHubTransactionResultToString( + ContextHubTransaction.RESULT_FAILED_UNKNOWN)) + .isEqualTo("RESULT_FAILED_UNKNOWN"); + assertThat( + mChreCommunication.contextHubTransactionResultToString( + ContextHubTransaction.RESULT_FAILED_BAD_PARAMS)) + .isEqualTo("RESULT_FAILED_BAD_PARAMS"); + assertThat( + mChreCommunication.contextHubTransactionResultToString( + ContextHubTransaction.RESULT_FAILED_UNINITIALIZED)) + .isEqualTo("RESULT_FAILED_UNINITIALIZED"); + assertThat( + mChreCommunication.contextHubTransactionResultToString( + ContextHubTransaction.RESULT_FAILED_BUSY)) + .isEqualTo("RESULT_FAILED_BUSY"); + assertThat( + mChreCommunication.contextHubTransactionResultToString( + ContextHubTransaction.RESULT_FAILED_AT_HUB)) + .isEqualTo("RESULT_FAILED_AT_HUB"); + assertThat( + mChreCommunication.contextHubTransactionResultToString( + ContextHubTransaction.RESULT_FAILED_TIMEOUT)) + .isEqualTo("RESULT_FAILED_TIMEOUT"); + assertThat( + mChreCommunication.contextHubTransactionResultToString( + ContextHubTransaction.RESULT_FAILED_SERVICE_INTERNAL_FAILURE)) + .isEqualTo("RESULT_FAILED_SERVICE_INTERNAL_FAILURE"); + assertThat( + mChreCommunication.contextHubTransactionResultToString( + ContextHubTransaction.RESULT_FAILED_HAL_UNAVAILABLE)) + .isEqualTo("RESULT_FAILED_HAL_UNAVAILABLE"); + assertThat( + mChreCommunication.contextHubTransactionResultToString(9)) + .isEqualTo("UNKNOWN_RESULT value=9"); + } + @Test public void testOnHubReset() { + mChreCommunication.start( + mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID)); mChreCommunication.onHubReset(mClient); verify(mChreCallback).onHubReset(); } @Test public void testOnNanoAppLoaded() { + mChreCommunication.start( + mChreCallback, Collections.singleton(ChreDiscoveryProvider.NANOAPP_ID)); mChreCommunication.onNanoAppLoaded(mClient, ChreDiscoveryProvider.NANOAPP_ID); verify(mChreCallback).onNanoAppRestart(eq(ChreDiscoveryProvider.NANOAPP_ID)); } diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java index 7c0dd926e2..154441b537 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/provider/ChreDiscoveryProviderTest.java @@ -16,16 +16,34 @@ package com.android.server.nearby.provider; +import static android.Manifest.permission.READ_DEVICE_CONFIG; +import static android.Manifest.permission.WRITE_DEVICE_CONFIG; + +import static com.android.server.nearby.NearbyConfiguration.NEARBY_SUPPORT_TEST_APP; + +import static com.google.common.truth.Truth.assertThat; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.location.NanoAppMessage; -import android.nearby.ScanFilter; +import android.nearby.DataElement; +import android.nearby.NearbyDeviceParcelable; +import android.nearby.OffloadCapability; +import android.nearby.aidl.IOffloadCallback; +import android.os.Build; +import android.os.IBinder; +import android.os.RemoteException; +import android.provider.DeviceConfig; import androidx.test.filters.SdkSuppress; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.server.nearby.NearbyConfiguration; +import com.android.server.nearby.presence.PresenceDiscoveryResult; + import com.google.protobuf.ByteString; import org.junit.Before; @@ -35,22 +53,39 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import service.proto.Blefilter; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; +import service.proto.Blefilter; + public class ChreDiscoveryProviderTest { @Mock AbstractDiscoveryProvider.Listener mListener; @Mock ChreCommunication mChreCommunication; + @Mock IBinder mIBinder; @Captor ArgumentCaptor mChreCallbackCaptor; + @Captor ArgumentCaptor mNearbyDevice; + private static final String NAMESPACE = NearbyConfiguration.getNamespace(); + private static final int DATA_TYPE_CONNECTION_STATUS_KEY = 10; + private static final int DATA_TYPE_BATTERY_KEY = 11; + private static final int DATA_TYPE_TX_POWER_KEY = 5; + private static final int DATA_TYPE_BLUETOOTH_ADDR_KEY = 101; + private static final int DATA_TYPE_FP_ACCOUNT_KEY = 9; + private static final int DATA_TYPE_BLE_SERVICE_DATA_KEY = 100; + private static final int DATA_TYPE_TEST_DE_BEGIN_KEY = 2147483520; + private static final int DATA_TYPE_TEST_DE_END_KEY = 2147483647; + + private final Object mLock = new Object(); private ChreDiscoveryProvider mChreDiscoveryProvider; + private OffloadCapability mOffloadCapability; @Before public void setUp() { + InstrumentationRegistry.getInstrumentation().getUiAutomation() + .adoptShellPermissionIdentity(WRITE_DEVICE_CONFIG, READ_DEVICE_CONFIG); + MockitoAnnotations.initMocks(this); Context context = InstrumentationRegistry.getInstrumentation().getContext(); mChreDiscoveryProvider = @@ -59,13 +94,68 @@ public class ChreDiscoveryProviderTest { @Test @SdkSuppress(minSdkVersion = 32, codeName = "T") - public void testOnStart() { - List scanFilters = new ArrayList<>(); - mChreDiscoveryProvider.getController().setProviderScanFilters(scanFilters); - mChreDiscoveryProvider.onStart(); + public void testInit() { + mChreDiscoveryProvider.init(); verify(mChreCommunication).start(mChreCallbackCaptor.capture(), any()); mChreCallbackCaptor.getValue().started(true); - verify(mChreCommunication).sendMessageToNanoApp(any()); + } + + @Test + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void test_queryInvalidVersion() { + when(mChreCommunication.queryNanoAppVersion()).thenReturn( + (long) ChreCommunication.INVALID_NANO_APP_VERSION); + IOffloadCallback callback = new IOffloadCallback() { + @Override + public void onQueryComplete(OffloadCapability capability) throws RemoteException { + synchronized (mLock) { + mOffloadCapability = capability; + mLock.notify(); + } + } + + @Override + public IBinder asBinder() { + return mIBinder; + } + }; + mChreDiscoveryProvider.queryOffloadCapability(callback); + OffloadCapability capability = + new OffloadCapability + .Builder() + .setFastPairSupported(false) + .setVersion(ChreCommunication.INVALID_NANO_APP_VERSION) + .build(); + assertThat(mOffloadCapability).isEqualTo(capability); + } + + @Test + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public void test_queryVersion() { + long version = 500L; + when(mChreCommunication.queryNanoAppVersion()).thenReturn(version); + IOffloadCallback callback = new IOffloadCallback() { + @Override + public void onQueryComplete(OffloadCapability capability) throws RemoteException { + synchronized (mLock) { + mOffloadCapability = capability; + mLock.notify(); + } + } + + @Override + public IBinder asBinder() { + return mIBinder; + } + }; + mChreDiscoveryProvider.queryOffloadCapability(callback); + OffloadCapability capability = + new OffloadCapability + .Builder() + .setFastPairSupported(true) + .setVersion(version) + .build(); + assertThat(mOffloadCapability).isEqualTo(capability); } @Test @@ -93,12 +183,220 @@ public class ChreDiscoveryProviderTest { ChreDiscoveryProvider.NANOAPP_MESSAGE_TYPE_FILTER_RESULT, results.toByteArray()); mChreDiscoveryProvider.getController().setListener(mListener); + mChreDiscoveryProvider.init(); mChreDiscoveryProvider.onStart(); verify(mChreCommunication).start(mChreCallbackCaptor.capture(), any()); mChreCallbackCaptor.getValue().onMessageFromNanoApp(chre_message); verify(mListener).onNearbyDeviceDiscovered(any()); } + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testOnNearbyDeviceDiscoveredWithDataElements() { + // Disables the setting of test app support + boolean isSupportedTestApp = getDeviceConfigBoolean( + NEARBY_SUPPORT_TEST_APP, false /* defaultValue */); + if (isSupportedTestApp) { + DeviceConfig.setProperty(NAMESPACE, NEARBY_SUPPORT_TEST_APP, "false", false); + } + assertThat(new NearbyConfiguration().isTestAppSupported()).isFalse(); + + final byte [] connectionStatus = new byte[] {1, 2, 3}; + final byte [] batteryStatus = new byte[] {4, 5, 6}; + final byte [] txPower = new byte[] {2}; + final byte [] bluetoothAddr = new byte[] {1, 2, 3, 4, 5, 6}; + final byte [] fastPairAccountKey = new byte[16]; + // First byte is length of service data, padding zeros should be thrown away. + final byte [] bleServiceData = new byte[] {5, 1, 2, 3, 4, 5, 0, 0, 0, 0}; + final byte [] testData = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + + final List expectedExtendedProperties = new ArrayList<>(); + expectedExtendedProperties.add(new DataElement(DATA_TYPE_CONNECTION_STATUS_KEY, + connectionStatus)); + expectedExtendedProperties.add(new DataElement(DATA_TYPE_BATTERY_KEY, batteryStatus)); + expectedExtendedProperties.add(new DataElement(DATA_TYPE_TX_POWER_KEY, txPower)); + expectedExtendedProperties.add( + new DataElement(DATA_TYPE_BLUETOOTH_ADDR_KEY, bluetoothAddr)); + expectedExtendedProperties.add( + new DataElement(DATA_TYPE_FP_ACCOUNT_KEY, fastPairAccountKey)); + expectedExtendedProperties.add( + new DataElement(DATA_TYPE_BLE_SERVICE_DATA_KEY, new byte[] {1, 2, 3, 4, 5})); + + Blefilter.PublicCredential credential = + Blefilter.PublicCredential.newBuilder() + .setSecretId(ByteString.copyFrom(new byte[] {1})) + .setAuthenticityKey(ByteString.copyFrom(new byte[2])) + .setPublicKey(ByteString.copyFrom(new byte[3])) + .setEncryptedMetadata(ByteString.copyFrom(new byte[4])) + .setEncryptedMetadataTag(ByteString.copyFrom(new byte[5])) + .build(); + Blefilter.BleFilterResult result = + Blefilter.BleFilterResult.newBuilder() + .setTxPower(2) + .setRssi(1) + .setBluetoothAddress(ByteString.copyFrom(bluetoothAddr)) + .setBleServiceData(ByteString.copyFrom(bleServiceData)) + .setPublicCredential(credential) + .addDataElement(Blefilter.DataElement.newBuilder() + .setKey(DATA_TYPE_CONNECTION_STATUS_KEY) + .setValue(ByteString.copyFrom(connectionStatus)) + .setValueLength(connectionStatus.length) + ) + .addDataElement(Blefilter.DataElement.newBuilder() + .setKey(DATA_TYPE_BATTERY_KEY) + .setValue(ByteString.copyFrom(batteryStatus)) + .setValueLength(batteryStatus.length) + ) + .addDataElement(Blefilter.DataElement.newBuilder() + .setKey(DATA_TYPE_FP_ACCOUNT_KEY) + .setValue(ByteString.copyFrom(fastPairAccountKey)) + .setValueLength(fastPairAccountKey.length) + ) + .addDataElement(Blefilter.DataElement.newBuilder() + .setKey(DATA_TYPE_TEST_DE_BEGIN_KEY) + .setValue(ByteString.copyFrom(testData)) + .setValueLength(testData.length) + ) + .addDataElement(Blefilter.DataElement.newBuilder() + .setKey(DATA_TYPE_TEST_DE_END_KEY) + .setValue(ByteString.copyFrom(testData)) + .setValueLength(testData.length) + ) + .build(); + Blefilter.BleFilterResults results = + Blefilter.BleFilterResults.newBuilder().addResult(result).build(); + NanoAppMessage chre_message = + NanoAppMessage.createMessageToNanoApp( + ChreDiscoveryProvider.NANOAPP_ID, + ChreDiscoveryProvider.NANOAPP_MESSAGE_TYPE_FILTER_RESULT, + results.toByteArray()); + mChreDiscoveryProvider.getController().setListener(mListener); + mChreDiscoveryProvider.init(); + mChreDiscoveryProvider.onStart(); + verify(mChreCommunication).start(mChreCallbackCaptor.capture(), any()); + mChreCallbackCaptor.getValue().onMessageFromNanoApp(chre_message); + verify(mListener).onNearbyDeviceDiscovered(mNearbyDevice.capture()); + + List extendedProperties = PresenceDiscoveryResult + .fromDevice(mNearbyDevice.getValue()).getExtendedProperties(); + assertThat(extendedProperties).containsExactlyElementsIn(expectedExtendedProperties); + // Reverts the setting of test app support + if (isSupportedTestApp) { + DeviceConfig.setProperty(NAMESPACE, NEARBY_SUPPORT_TEST_APP, "true", false); + assertThat(new NearbyConfiguration().isTestAppSupported()).isTrue(); + } + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testOnNearbyDeviceDiscoveredWithTestDataElements() { + // Enables the setting of test app support + boolean isSupportedTestApp = getDeviceConfigBoolean( + NEARBY_SUPPORT_TEST_APP, false /* defaultValue */); + if (!isSupportedTestApp) { + DeviceConfig.setProperty(NAMESPACE, NEARBY_SUPPORT_TEST_APP, "true", false); + } + assertThat(new NearbyConfiguration().isTestAppSupported()).isTrue(); + + final byte [] connectionStatus = new byte[] {1, 2, 3}; + final byte [] batteryStatus = new byte[] {4, 5, 6}; + final byte [] txPower = new byte[] {2}; + final byte [] bluetoothAddr = new byte[] {1, 2, 3, 4, 5, 6}; + final byte [] fastPairAccountKey = new byte[16]; + // First byte is length of service data, padding zeros should be thrown away. + final byte [] bleServiceData = new byte[] {5, 1, 2, 3, 4, 5, 0, 0, 0, 0}; + final byte [] testData = new byte[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + + final List expectedExtendedProperties = new ArrayList<>(); + expectedExtendedProperties.add(new DataElement(DATA_TYPE_CONNECTION_STATUS_KEY, + connectionStatus)); + expectedExtendedProperties.add(new DataElement(DATA_TYPE_BATTERY_KEY, batteryStatus)); + expectedExtendedProperties.add(new DataElement(DATA_TYPE_TX_POWER_KEY, txPower)); + expectedExtendedProperties.add( + new DataElement(DATA_TYPE_BLUETOOTH_ADDR_KEY, bluetoothAddr)); + expectedExtendedProperties.add( + new DataElement(DATA_TYPE_FP_ACCOUNT_KEY, fastPairAccountKey)); + expectedExtendedProperties.add( + new DataElement(DATA_TYPE_BLE_SERVICE_DATA_KEY, new byte[] {1, 2, 3, 4, 5})); + expectedExtendedProperties.add( + new DataElement(DATA_TYPE_TEST_DE_BEGIN_KEY, testData)); + expectedExtendedProperties.add( + new DataElement(DATA_TYPE_TEST_DE_END_KEY, testData)); + + Blefilter.PublicCredential credential = + Blefilter.PublicCredential.newBuilder() + .setSecretId(ByteString.copyFrom(new byte[] {1})) + .setAuthenticityKey(ByteString.copyFrom(new byte[2])) + .setPublicKey(ByteString.copyFrom(new byte[3])) + .setEncryptedMetadata(ByteString.copyFrom(new byte[4])) + .setEncryptedMetadataTag(ByteString.copyFrom(new byte[5])) + .build(); + Blefilter.BleFilterResult result = + Blefilter.BleFilterResult.newBuilder() + .setTxPower(2) + .setRssi(1) + .setBluetoothAddress(ByteString.copyFrom(bluetoothAddr)) + .setBleServiceData(ByteString.copyFrom(bleServiceData)) + .setPublicCredential(credential) + .addDataElement(Blefilter.DataElement.newBuilder() + .setKey(DATA_TYPE_CONNECTION_STATUS_KEY) + .setValue(ByteString.copyFrom(connectionStatus)) + .setValueLength(connectionStatus.length) + ) + .addDataElement(Blefilter.DataElement.newBuilder() + .setKey(DATA_TYPE_BATTERY_KEY) + .setValue(ByteString.copyFrom(batteryStatus)) + .setValueLength(batteryStatus.length) + ) + .addDataElement(Blefilter.DataElement.newBuilder() + .setKey(DATA_TYPE_FP_ACCOUNT_KEY) + .setValue(ByteString.copyFrom(fastPairAccountKey)) + .setValueLength(fastPairAccountKey.length) + ) + .addDataElement(Blefilter.DataElement.newBuilder() + .setKey(DATA_TYPE_TEST_DE_BEGIN_KEY) + .setValue(ByteString.copyFrom(testData)) + .setValueLength(testData.length) + ) + .addDataElement(Blefilter.DataElement.newBuilder() + .setKey(DATA_TYPE_TEST_DE_END_KEY) + .setValue(ByteString.copyFrom(testData)) + .setValueLength(testData.length) + ) + .build(); + Blefilter.BleFilterResults results = + Blefilter.BleFilterResults.newBuilder().addResult(result).build(); + NanoAppMessage chre_message = + NanoAppMessage.createMessageToNanoApp( + ChreDiscoveryProvider.NANOAPP_ID, + ChreDiscoveryProvider.NANOAPP_MESSAGE_TYPE_FILTER_RESULT, + results.toByteArray()); + mChreDiscoveryProvider.getController().setListener(mListener); + mChreDiscoveryProvider.init(); + mChreDiscoveryProvider.onStart(); + verify(mChreCommunication).start(mChreCallbackCaptor.capture(), any()); + mChreCallbackCaptor.getValue().onMessageFromNanoApp(chre_message); + verify(mListener).onNearbyDeviceDiscovered(mNearbyDevice.capture()); + + List extendedProperties = PresenceDiscoveryResult + .fromDevice(mNearbyDevice.getValue()).getExtendedProperties(); + assertThat(extendedProperties).containsExactlyElementsIn(expectedExtendedProperties); + // Reverts the setting of test app support + if (!isSupportedTestApp) { + DeviceConfig.setProperty(NAMESPACE, NEARBY_SUPPORT_TEST_APP, "false", false); + assertThat(new NearbyConfiguration().isTestAppSupported()).isFalse(); + } + } + + private boolean getDeviceConfigBoolean(final String name, final boolean defaultValue) { + final String value = getDeviceConfigProperty(name); + return value != null ? Boolean.parseBoolean(value) : defaultValue; + } + + private String getDeviceConfigProperty(String name) { + return DeviceConfig.getProperty(NAMESPACE, name); + } + private static class InLineExecutor implements Executor { @Override public void execute(Runnable command) { diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/FastPairDataProviderTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/FastPairDataProviderTest.java new file mode 100644 index 0000000000..300efbdee3 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/provider/FastPairDataProviderTest.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.provider; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.accounts.Account; +import android.content.Context; +import android.nearby.FastPairDataProviderService; +import android.nearby.aidl.FastPairAccountDevicesMetadataRequestParcel; +import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataParcel; +import android.nearby.aidl.FastPairAntispoofKeyDeviceMetadataRequestParcel; +import android.nearby.aidl.FastPairDeviceMetadataParcel; +import android.nearby.aidl.FastPairEligibleAccountParcel; +import android.nearby.aidl.FastPairEligibleAccountsRequestParcel; +import android.nearby.aidl.FastPairManageAccountDeviceRequestParcel; +import android.nearby.aidl.FastPairManageAccountRequestParcel; + +import androidx.test.filters.SdkSuppress; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.server.nearby.fastpair.footprint.FastPairUploadInfo; + +import com.google.common.collect.ImmutableList; +import com.google.protobuf.ByteString; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import service.proto.Cache; +import service.proto.FastPairString; +import service.proto.Rpcs; + +public class FastPairDataProviderTest { + + private static final Account ACCOUNT = new Account("abc@google.com", "type1"); + private static final byte[] MODEL_ID = new byte[]{7, 9}; + private static final int BLE_TX_POWER = 5; + private static final String CONNECT_SUCCESS_COMPANION_APP_INSTALLED = + "CONNECT_SUCCESS_COMPANION_APP_INSTALLED"; + private static final String CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED = + "CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED"; + private static final int DEVICE_TYPE = 1; + private static final String DOWNLOAD_COMPANION_APP_DESCRIPTION = + "DOWNLOAD_COMPANION_APP_DESCRIPTION"; + private static final String FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION = + "FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION"; + private static final byte[] IMAGE = new byte[]{7, 9}; + private static final String IMAGE_URL = "IMAGE_URL"; + private static final String INITIAL_NOTIFICATION_DESCRIPTION = + "INITIAL_NOTIFICATION_DESCRIPTION"; + private static final String INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT = + "INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT"; + private static final String INITIAL_PAIRING_DESCRIPTION = "INITIAL_PAIRING_DESCRIPTION"; + private static final String INTENT_URI = "INTENT_URI"; + private static final String OPEN_COMPANION_APP_DESCRIPTION = "OPEN_COMPANION_APP_DESCRIPTION"; + private static final String RETRO_ACTIVE_PAIRING_DESCRIPTION = + "RETRO_ACTIVE_PAIRING_DESCRIPTION"; + private static final String SUBSEQUENT_PAIRING_DESCRIPTION = "SUBSEQUENT_PAIRING_DESCRIPTION"; + private static final float TRIGGER_DISTANCE = 111; + private static final String TRUE_WIRELESS_IMAGE_URL_CASE = "TRUE_WIRELESS_IMAGE_URL_CASE"; + private static final String TRUE_WIRELESS_IMAGE_URL_LEFT_BUD = + "TRUE_WIRELESS_IMAGE_URL_LEFT_BUD"; + private static final String TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD = + "TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD"; + private static final String UNABLE_TO_CONNECT_DESCRIPTION = "UNABLE_TO_CONNECT_DESCRIPTION"; + private static final String UNABLE_TO_CONNECT_TITLE = "UNABLE_TO_CONNECT_TITLE"; + private static final String UPDATE_COMPANION_APP_DESCRIPTION = + "UPDATE_COMPANION_APP_DESCRIPTION"; + private static final String WAIT_LAUNCH_COMPANION_APP_DESCRIPTION = + "WAIT_LAUNCH_COMPANION_APP_DESCRIPTION"; + private static final byte[] ACCOUNT_KEY = new byte[]{3}; + private static final byte[] SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS = new byte[]{2, 8}; + private static final byte[] ANTI_SPOOFING_KEY = new byte[]{4, 5, 6}; + private static final String ACTION_URL = "ACTION_URL"; + private static final String APP_NAME = "APP_NAME"; + private static final byte[] AUTHENTICATION_PUBLIC_KEY_SEC_P256R1 = new byte[]{5, 7}; + private static final String DESCRIPTION = "DESCRIPTION"; + private static final String DEVICE_NAME = "DEVICE_NAME"; + private static final String DISPLAY_URL = "DISPLAY_URL"; + private static final long FIRST_OBSERVATION_TIMESTAMP_MILLIS = 8393L; + private static final String ICON_FIFE_URL = "ICON_FIFE_URL"; + private static final byte[] ICON_PNG = new byte[]{2, 5}; + private static final String ID = "ID"; + private static final long LAST_OBSERVATION_TIMESTAMP_MILLIS = 934234L; + private static final String MAC_ADDRESS = "MAC_ADDRESS"; + private static final String NAME = "NAME"; + private static final String PACKAGE_NAME = "PACKAGE_NAME"; + private static final long PENDING_APP_INSTALL_TIMESTAMP_MILLIS = 832393L; + private static final int RSSI = 9; + private static final String TITLE = "TITLE"; + private static final String TRIGGER_ID = "TRIGGER_ID"; + private static final int TX_POWER = 63; + + @Mock ProxyFastPairDataProvider mProxyFastPairDataProvider; + + FastPairDataProvider mFastPairDataProvider; + FastPairEligibleAccountParcel[] mFastPairEligibleAccountParcels = + { genHappyPathFastPairEligibleAccountParcel() }; + FastPairAntispoofKeyDeviceMetadataParcel mFastPairAntispoofKeyDeviceMetadataParcel = + genHappyPathFastPairAntispoofKeyDeviceMetadataParcel(); + FastPairUploadInfo mFastPairUploadInfo = genHappyPathFastPairUploadInfo(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + Context context = InstrumentationRegistry.getInstrumentation().getContext(); + mFastPairDataProvider = FastPairDataProvider.init(context); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testFailurePath_throwsException() throws IllegalStateException { + mFastPairDataProvider = FastPairDataProvider.getInstance(); + assertThrows( + IllegalStateException.class, + () -> { + mFastPairDataProvider.loadFastPairEligibleAccounts(); }); + assertThrows( + IllegalStateException.class, + () -> { + mFastPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(MODEL_ID); }); + assertThrows( + IllegalStateException.class, + () -> { + mFastPairDataProvider.loadFastPairDeviceWithAccountKey(ACCOUNT); }); + assertThrows( + IllegalStateException.class, + () -> { + mFastPairDataProvider.loadFastPairDeviceWithAccountKey( + ACCOUNT, ImmutableList.of()); }); + assertThrows( + IllegalStateException.class, + () -> { + mFastPairDataProvider.optIn(ACCOUNT); }); + assertThrows( + IllegalStateException.class, + () -> { + mFastPairDataProvider.upload(ACCOUNT, mFastPairUploadInfo); }); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testLoadFastPairAntispoofKeyDeviceMetadata_receivesResponse() { + mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider); + when(mProxyFastPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(any())) + .thenReturn(mFastPairAntispoofKeyDeviceMetadataParcel); + + mFastPairDataProvider.loadFastPairAntispoofKeyDeviceMetadata(MODEL_ID); + ArgumentCaptor captor = + ArgumentCaptor.forClass(FastPairAntispoofKeyDeviceMetadataRequestParcel.class); + verify(mProxyFastPairDataProvider).loadFastPairAntispoofKeyDeviceMetadata(captor.capture()); + assertThat(captor.getValue().modelId).isSameInstanceAs(MODEL_ID); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testOptIn_finishesSuccessfully() { + mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider); + doNothing().when(mProxyFastPairDataProvider).manageFastPairAccount(any()); + mFastPairDataProvider.optIn(ACCOUNT); + ArgumentCaptor captor = + ArgumentCaptor.forClass(FastPairManageAccountRequestParcel.class); + verify(mProxyFastPairDataProvider).manageFastPairAccount(captor.capture()); + assertThat(captor.getValue().account).isSameInstanceAs(ACCOUNT); + assertThat(captor.getValue().requestType).isEqualTo( + FastPairDataProviderService.MANAGE_REQUEST_ADD); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testUpload_finishesSuccessfully() { + mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider); + doNothing().when(mProxyFastPairDataProvider).manageFastPairAccountDevice(any()); + mFastPairDataProvider.upload(ACCOUNT, mFastPairUploadInfo); + ArgumentCaptor captor = + ArgumentCaptor.forClass(FastPairManageAccountDeviceRequestParcel.class); + verify(mProxyFastPairDataProvider).manageFastPairAccountDevice(captor.capture()); + assertThat(captor.getValue().account).isSameInstanceAs(ACCOUNT); + assertThat(captor.getValue().requestType).isEqualTo( + FastPairDataProviderService.MANAGE_REQUEST_ADD); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testLoadFastPairEligibleAccounts_receivesOneAccount() { + mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider); + when(mProxyFastPairDataProvider.loadFastPairEligibleAccounts(any())) + .thenReturn(mFastPairEligibleAccountParcels); + assertThat(mFastPairDataProvider.loadFastPairEligibleAccounts().size()) + .isEqualTo(1); + ArgumentCaptor captor = + ArgumentCaptor.forClass(FastPairEligibleAccountsRequestParcel.class); + verify(mProxyFastPairDataProvider).loadFastPairEligibleAccounts(captor.capture()); + assertThat(captor.getValue()).isNotNull(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testLoadFastPairDeviceWithAccountKey_finishesSuccessfully() { + mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider); + when(mProxyFastPairDataProvider.loadFastPairAccountDevicesMetadata(any())) + .thenReturn(null); + + mFastPairDataProvider.loadFastPairDeviceWithAccountKey(ACCOUNT); + ArgumentCaptor captor = + ArgumentCaptor.forClass(FastPairAccountDevicesMetadataRequestParcel.class); + verify(mProxyFastPairDataProvider).loadFastPairAccountDevicesMetadata(captor.capture()); + assertThat(captor.getValue().account).isSameInstanceAs(ACCOUNT); + assertThat(captor.getValue().deviceAccountKeys).isEmpty(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testLoadFastPairDeviceWithAccountKeyDeviceAccountKeys_finishesSuccessfully() { + mFastPairDataProvider.setProxyDataProvider(mProxyFastPairDataProvider); + when(mProxyFastPairDataProvider.loadFastPairAccountDevicesMetadata(any())) + .thenReturn(null); + + mFastPairDataProvider.loadFastPairDeviceWithAccountKey( + ACCOUNT, ImmutableList.of(ACCOUNT_KEY)); + ArgumentCaptor captor = + ArgumentCaptor.forClass(FastPairAccountDevicesMetadataRequestParcel.class); + verify(mProxyFastPairDataProvider).loadFastPairAccountDevicesMetadata(captor.capture()); + assertThat(captor.getValue().account).isSameInstanceAs(ACCOUNT); + assertThat(captor.getValue().deviceAccountKeys.length).isEqualTo(1); + assertThat(captor.getValue().deviceAccountKeys[0].byteArray).isSameInstanceAs(ACCOUNT_KEY); + } + + private static FastPairEligibleAccountParcel genHappyPathFastPairEligibleAccountParcel() { + FastPairEligibleAccountParcel parcel = new FastPairEligibleAccountParcel(); + parcel.account = ACCOUNT; + parcel.optIn = true; + + return parcel; + } + + private static FastPairAntispoofKeyDeviceMetadataParcel + genHappyPathFastPairAntispoofKeyDeviceMetadataParcel() { + FastPairAntispoofKeyDeviceMetadataParcel parcel = + new FastPairAntispoofKeyDeviceMetadataParcel(); + parcel.antispoofPublicKey = ANTI_SPOOFING_KEY; + parcel.deviceMetadata = genHappyPathFastPairDeviceMetadataParcel(); + + return parcel; + } + + private static FastPairDeviceMetadataParcel genHappyPathFastPairDeviceMetadataParcel() { + FastPairDeviceMetadataParcel parcel = new FastPairDeviceMetadataParcel(); + + parcel.bleTxPower = BLE_TX_POWER; + parcel.connectSuccessCompanionAppInstalled = CONNECT_SUCCESS_COMPANION_APP_INSTALLED; + parcel.connectSuccessCompanionAppNotInstalled = + CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED; + parcel.deviceType = DEVICE_TYPE; + parcel.downloadCompanionAppDescription = DOWNLOAD_COMPANION_APP_DESCRIPTION; + parcel.failConnectGoToSettingsDescription = FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION; + parcel.image = IMAGE; + parcel.imageUrl = IMAGE_URL; + parcel.initialNotificationDescription = INITIAL_NOTIFICATION_DESCRIPTION; + parcel.initialNotificationDescriptionNoAccount = + INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT; + parcel.initialPairingDescription = INITIAL_PAIRING_DESCRIPTION; + parcel.intentUri = INTENT_URI; + parcel.name = NAME; + parcel.openCompanionAppDescription = OPEN_COMPANION_APP_DESCRIPTION; + parcel.retroactivePairingDescription = RETRO_ACTIVE_PAIRING_DESCRIPTION; + parcel.subsequentPairingDescription = SUBSEQUENT_PAIRING_DESCRIPTION; + parcel.triggerDistance = TRIGGER_DISTANCE; + parcel.trueWirelessImageUrlCase = TRUE_WIRELESS_IMAGE_URL_CASE; + parcel.trueWirelessImageUrlLeftBud = TRUE_WIRELESS_IMAGE_URL_LEFT_BUD; + parcel.trueWirelessImageUrlRightBud = TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD; + parcel.unableToConnectDescription = UNABLE_TO_CONNECT_DESCRIPTION; + parcel.unableToConnectTitle = UNABLE_TO_CONNECT_TITLE; + parcel.updateCompanionAppDescription = UPDATE_COMPANION_APP_DESCRIPTION; + parcel.waitLaunchCompanionAppDescription = WAIT_LAUNCH_COMPANION_APP_DESCRIPTION; + + return parcel; + } + + private static Cache.StoredDiscoveryItem genHappyPathStoredDiscoveryItem() { + Cache.StoredDiscoveryItem.Builder storedDiscoveryItemBuilder = + Cache.StoredDiscoveryItem.newBuilder(); + storedDiscoveryItemBuilder.setActionUrl(ACTION_URL); + storedDiscoveryItemBuilder.setActionUrlType(Cache.ResolvedUrlType.WEBPAGE); + storedDiscoveryItemBuilder.setAppName(APP_NAME); + storedDiscoveryItemBuilder.setAuthenticationPublicKeySecp256R1( + ByteString.copyFrom(AUTHENTICATION_PUBLIC_KEY_SEC_P256R1)); + storedDiscoveryItemBuilder.setDescription(DESCRIPTION); + storedDiscoveryItemBuilder.setDeviceName(DEVICE_NAME); + storedDiscoveryItemBuilder.setDisplayUrl(DISPLAY_URL); + storedDiscoveryItemBuilder.setFirstObservationTimestampMillis( + FIRST_OBSERVATION_TIMESTAMP_MILLIS); + storedDiscoveryItemBuilder.setIconFifeUrl(ICON_FIFE_URL); + storedDiscoveryItemBuilder.setIconPng(ByteString.copyFrom(ICON_PNG)); + storedDiscoveryItemBuilder.setId(ID); + storedDiscoveryItemBuilder.setLastObservationTimestampMillis( + LAST_OBSERVATION_TIMESTAMP_MILLIS); + storedDiscoveryItemBuilder.setMacAddress(MAC_ADDRESS); + storedDiscoveryItemBuilder.setPackageName(PACKAGE_NAME); + storedDiscoveryItemBuilder.setPendingAppInstallTimestampMillis( + PENDING_APP_INSTALL_TIMESTAMP_MILLIS); + storedDiscoveryItemBuilder.setRssi(RSSI); + storedDiscoveryItemBuilder.setState(Cache.StoredDiscoveryItem.State.STATE_ENABLED); + storedDiscoveryItemBuilder.setTitle(TITLE); + storedDiscoveryItemBuilder.setTriggerId(TRIGGER_ID); + storedDiscoveryItemBuilder.setTxPower(TX_POWER); + + FastPairString.FastPairStrings.Builder stringsBuilder = + FastPairString.FastPairStrings.newBuilder(); + stringsBuilder.setPairingFinishedCompanionAppInstalled( + CONNECT_SUCCESS_COMPANION_APP_INSTALLED); + stringsBuilder.setPairingFinishedCompanionAppNotInstalled( + CONNECT_SUCCESS_COMPANION_APP_NOT_INSTALLED); + stringsBuilder.setPairingFailDescription( + FAIL_CONNECT_GOTO_SETTINGS_DESCRIPTION); + stringsBuilder.setTapToPairWithAccount( + INITIAL_NOTIFICATION_DESCRIPTION); + stringsBuilder.setTapToPairWithoutAccount( + INITIAL_NOTIFICATION_DESCRIPTION_NO_ACCOUNT); + stringsBuilder.setInitialPairingDescription(INITIAL_PAIRING_DESCRIPTION); + stringsBuilder.setRetroactivePairingDescription(RETRO_ACTIVE_PAIRING_DESCRIPTION); + stringsBuilder.setSubsequentPairingDescription(SUBSEQUENT_PAIRING_DESCRIPTION); + stringsBuilder.setWaitAppLaunchDescription(WAIT_LAUNCH_COMPANION_APP_DESCRIPTION); + storedDiscoveryItemBuilder.setFastPairStrings(stringsBuilder.build()); + + Cache.FastPairInformation.Builder fpInformationBuilder = + Cache.FastPairInformation.newBuilder(); + Rpcs.TrueWirelessHeadsetImages.Builder imagesBuilder = + Rpcs.TrueWirelessHeadsetImages.newBuilder(); + imagesBuilder.setCaseUrl(TRUE_WIRELESS_IMAGE_URL_CASE); + imagesBuilder.setLeftBudUrl(TRUE_WIRELESS_IMAGE_URL_LEFT_BUD); + imagesBuilder.setRightBudUrl(TRUE_WIRELESS_IMAGE_URL_RIGHT_BUD); + fpInformationBuilder.setTrueWirelessImages(imagesBuilder.build()); + fpInformationBuilder.setDeviceType(Rpcs.DeviceType.HEADPHONES); + + storedDiscoveryItemBuilder.setFastPairInformation(fpInformationBuilder.build()); + storedDiscoveryItemBuilder.setTxPower(TX_POWER); + + storedDiscoveryItemBuilder.setIconPng(ByteString.copyFrom(ICON_PNG)); + storedDiscoveryItemBuilder.setIconFifeUrl(ICON_FIFE_URL); + storedDiscoveryItemBuilder.setActionUrl(ACTION_URL); + + return storedDiscoveryItemBuilder.build(); + } + + private static FastPairUploadInfo genHappyPathFastPairUploadInfo() { + return new FastPairUploadInfo( + genHappyPathStoredDiscoveryItem(), + ByteString.copyFrom(ACCOUNT_KEY), + ByteString.copyFrom(SHA256_ACCOUNT_KEY_PUBLIC_ADDRESS)); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/provider/UtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/provider/UtilsTest.java index eeea319cde..35f87f1513 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/provider/UtilsTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/provider/UtilsTest.java @@ -110,8 +110,6 @@ public class UtilsTest { private static final byte[] ICON_PNG = new byte[]{2, 5}; private static final String ID = "ID"; private static final long LAST_OBSERVATION_TIMESTAMP_MILLIS = 934234L; - private static final int LAST_USER_EXPERIENCE = 1; - private static final long LOST_MILLIS = 393284L; private static final String MAC_ADDRESS = "MAC_ADDRESS"; private static final String NAME = "NAME"; private static final String PACKAGE_NAME = "PACKAGE_NAME"; @@ -121,7 +119,6 @@ public class UtilsTest { private static final String TITLE = "TITLE"; private static final String TRIGGER_ID = "TRIGGER_ID"; private static final int TX_POWER = 63; - private static final int TYPE = 1; @Test @SdkSuppress(minSdkVersion = 32, codeName = "T") diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/ArrayUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/ArrayUtilsTest.java new file mode 100644 index 0000000000..a759baf2c9 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/util/ArrayUtilsTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.util; + +import static com.google.common.truth.Truth.assertThat; + +import androidx.test.filters.SdkSuppress; + +import org.junit.Test; + +public final class ArrayUtilsTest { + + private static final byte[] BYTES_ONE = new byte[] {7, 9}; + private static final byte[] BYTES_TWO = new byte[] {8}; + private static final byte[] BYTES_EMPTY = new byte[] {}; + private static final byte[] BYTES_ALL = new byte[] {7, 9, 8}; + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testConcatByteArraysNoInput() { + assertThat(ArrayUtils.concatByteArrays().length).isEqualTo(0); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testConcatByteArraysOneEmptyArray() { + assertThat(ArrayUtils.concatByteArrays(BYTES_EMPTY).length).isEqualTo(0); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testConcatByteArraysOneNonEmptyArray() { + assertThat(ArrayUtils.concatByteArrays(BYTES_ONE)).isEqualTo(BYTES_ONE); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testConcatByteArraysMultipleNonEmptyArrays() { + assertThat(ArrayUtils.concatByteArrays(BYTES_ONE, BYTES_TWO)).isEqualTo(BYTES_ALL); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testConcatByteArraysMultipleArrays() { + assertThat(ArrayUtils.concatByteArrays(BYTES_ONE, BYTES_EMPTY, BYTES_TWO)) + .isEqualTo(BYTES_ALL); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testIsEmptyNull_returnsTrue() { + assertThat(ArrayUtils.isEmpty(null)).isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testIsEmpty_returnsTrue() { + assertThat(ArrayUtils.isEmpty(new byte[]{})).isTrue(); + } + + @Test + @SdkSuppress(minSdkVersion = 32, codeName = "T") + public void testIsEmpty_returnsFalse() { + assertThat(ArrayUtils.isEmpty(BYTES_ALL)).isFalse(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/BroadcastPermissionsTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/BroadcastPermissionsTest.java index 1a224124dc..71ade2ac2d 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/util/BroadcastPermissionsTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/util/BroadcastPermissionsTest.java @@ -93,4 +93,9 @@ public final class BroadcastPermissionsTest { assertThat(BroadcastPermissions.getPermissionLevel(mMockContext, UID, PID)) .isEqualTo(PERMISSION_BLUETOOTH_ADVERTISE); } + + @Test + public void test_enforceBroadcastPermission() { + BroadcastPermissions.enforceBroadcastPermission(mMockContext, mCallerIdentity); + } } diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java index f098600956..ac90b9fb86 100644 --- a/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java +++ b/nearby/tests/unit/src/com/android/server/nearby/util/DataUtilsTest.java @@ -30,6 +30,7 @@ import service.proto.Rpcs.GetObservedDeviceResponse; public final class DataUtilsTest { private static final String BLUETOOTH_ADDRESS = "00:11:22:33:FF:EE"; + private static final String MODEL_ID = "model_id"; private static final String APP_PACKAGE = "test_package"; private static final String APP_ACTION_URL = "intent:#Intent;action=cto_be_set%3AACTION_MAGIC_PAIR;" @@ -57,15 +58,11 @@ public final class DataUtilsTest { private static final String MESSAGE_RETROACTIVE_PAIR_DESCRIPTION = "message 7"; private static final String MESSAGE_WAIT_LAUNCH_COMPANION_APP_DESCRIPTION = "message 8"; private static final String MESSAGE_FAIL_CONNECT_DESCRIPTION = "message 9"; - private static final String MESSAGE_FAST_PAIR_TV_CONNECT_DEVICE_NO_ACCOUNT_DESCRIPTION = - "message 10"; - private static final String MESSAGE_ASSISTANT_HALF_SHEET_DESCRIPTION = "message 11"; - private static final String MESSAGE_ASSISTANT_NOTIFICATION_DESCRIPTION = "message 12"; @Test public void test_toScanFastPairStoreItem_withAccount() { Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem( - createObservedDeviceResponse(), BLUETOOTH_ADDRESS, ACCOUNT); + createObservedDeviceResponse(), BLUETOOTH_ADDRESS, MODEL_ID, ACCOUNT); assertThat(item.getAddress()).isEqualTo(BLUETOOTH_ADDRESS); assertThat(item.getActionUrl()).isEqualTo(APP_ACTION_URL); assertThat(item.getDeviceName()).isEqualTo(DEVICE_NAME); @@ -97,7 +94,7 @@ public final class DataUtilsTest { @Test public void test_toScanFastPairStoreItem_withoutAccount() { Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem( - createObservedDeviceResponse(), BLUETOOTH_ADDRESS, /* account= */ null); + createObservedDeviceResponse(), BLUETOOTH_ADDRESS, MODEL_ID, /* account= */ null); FastPairStrings strings = item.getFastPairStrings(); assertThat(strings.getInitialPairingDescription()) .isEqualTo(MESSAGE_INIT_NOTIFY_DESCRIPTION_NO_ACCOUNT); @@ -106,7 +103,25 @@ public final class DataUtilsTest { @Test public void test_toString() { Cache.ScanFastPairStoreItem item = DataUtils.toScanFastPairStoreItem( - createObservedDeviceResponse(), BLUETOOTH_ADDRESS, ACCOUNT); + createObservedDeviceResponse(), BLUETOOTH_ADDRESS, MODEL_ID, ACCOUNT); + + assertThat(DataUtils.toString(item)) + .isEqualTo("ScanFastPairStoreItem=[address:00:11:22:33:FF:EE, " + + "actionUrl:intent:#Intent;action=cto_be_set%3AACTION_MAGIC_PAIR;" + + "package=to_be_set;component=to_be_set;" + + "to_be_set%3AEXTRA_COMPANION_APP=test_package;" + + "end, deviceName:My device, " + + "iconFifeUrl:device_image_url, " + + "fastPairStrings:FastPairStrings[tapToPairWithAccount=message 1, " + + "tapToPairWithoutAccount=message 2, " + + "initialPairingDescription=message 3 My device, " + + "pairingFinishedCompanionAppInstalled=message 4, " + + "pairingFinishedCompanionAppNotInstalled=message 5, " + + "subsequentPairingDescription=message 6, " + + "retroactivePairingDescription=message 7, " + + "waitAppLaunchDescription=message 8, " + + "pairingFailDescription=message 9]]"); + FastPairStrings strings = item.getFastPairStrings(); assertThat(DataUtils.toString(strings)) diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/EnvironmentTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/EnvironmentTest.java new file mode 100644 index 0000000000..e167cf44dd --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/util/EnvironmentTest.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.util; + +import org.junit.Test; + +public class EnvironmentTest { + + @Test + public void getNearbyDirectory() { + Environment.getNearbyDirectory(); + Environment.getNearbyDirectory(1); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpIdentityV1Test.java b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpIdentityV1Test.java new file mode 100644 index 0000000000..f0294fc014 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpIdentityV1Test.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.util.encryption; + +import static com.google.common.truth.Truth.assertThat; + +import android.util.Log; + +import org.junit.Test; + +import java.util.Arrays; + +public class CryptorImpIdentityV1Test { + private static final String TAG = "CryptorImpIdentityV1Test"; + private static final byte[] SALT = new byte[] {102, 22}; + private static final byte[] DATA = + new byte[] {107, -102, 101, 107, 20, 62, 2, 73, 113, 59, 8, -14, -58, 122}; + private static final byte[] AUTHENTICITY_KEY = + new byte[] {-89, 88, -50, -42, -99, 57, 84, -24, 121, 1, -104, -8, -26, -73, -36, 100}; + + @Test + public void test_encrypt_decrypt() { + Cryptor identityCryptor = CryptorImpIdentityV1.getInstance(); + byte[] encryptedData = identityCryptor.encrypt(DATA, SALT, AUTHENTICITY_KEY); + + assertThat(identityCryptor.decrypt(encryptedData, SALT, AUTHENTICITY_KEY)).isEqualTo(DATA); + } + + @Test + public void test_encryption() { + Cryptor identityCryptor = CryptorImpIdentityV1.getInstance(); + byte[] encryptedData = identityCryptor.encrypt(DATA, SALT, AUTHENTICITY_KEY); + + // for debugging + Log.d(TAG, "encrypted data is: " + Arrays.toString(encryptedData)); + + assertThat(encryptedData).isEqualTo(getEncryptedData()); + } + + @Test + public void test_decryption() { + Cryptor identityCryptor = CryptorImpIdentityV1.getInstance(); + byte[] decryptedData = + identityCryptor.decrypt(getEncryptedData(), SALT, AUTHENTICITY_KEY); + // for debugging + Log.d(TAG, "decrypted data is: " + Arrays.toString(decryptedData)); + + assertThat(decryptedData).isEqualTo(DATA); + } + + @Test + public void generateHmacTag() { + CryptorImpIdentityV1 identityCryptor = CryptorImpIdentityV1.getInstance(); + byte[] generatedTag = identityCryptor.sign(DATA); + byte[] expectedTag = new byte[]{50, 116, 95, -87, 63, 123, -79, -43}; + assertThat(generatedTag).isEqualTo(expectedTag); + } + + private static byte[] getEncryptedData() { + return new byte[]{6, -31, -32, -123, 43, -92, -47, -110, -65, 126, -15, -51, -19, -43}; + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpV1Test.java b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpV1Test.java new file mode 100644 index 0000000000..3ca25752b6 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorImpV1Test.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.util.encryption; + +import static com.google.common.truth.Truth.assertThat; + +import android.util.Log; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * Unit test for {@link CryptorImpV1} + */ +public final class CryptorImpV1Test { + private static final String TAG = "CryptorImpV1Test"; + private static final byte[] SALT = new byte[] {102, 22}; + private static final byte[] DATA = + new byte[] {107, -102, 101, 107, 20, 62, 2, 73, 113, 59, 8, -14, -58, 122}; + private static final byte[] AUTHENTICITY_KEY = + new byte[] {-89, 88, -50, -42, -99, 57, 84, -24, 121, 1, -104, -8, -26, -73, -36, 100}; + + @Test + public void test_encryption() { + Cryptor v1Cryptor = CryptorImpV1.getInstance(); + byte[] encryptedData = v1Cryptor.encrypt(DATA, SALT, AUTHENTICITY_KEY); + + // for debugging + Log.d(TAG, "encrypted data is: " + Arrays.toString(encryptedData)); + + assertThat(encryptedData).isEqualTo(getEncryptedData()); + } + + @Test + public void test_encryption_invalidInput() { + Cryptor v1Cryptor = CryptorImpV1.getInstance(); + assertThat(v1Cryptor.encrypt(DATA, SALT, new byte[]{1, 2, 3, 4, 6})).isNull(); + } + + @Test + public void test_decryption() { + Cryptor v1Cryptor = CryptorImpV1.getInstance(); + byte[] decryptedData = + v1Cryptor.decrypt(getEncryptedData(), SALT, AUTHENTICITY_KEY); + // for debugging + Log.d(TAG, "decrypted data is: " + Arrays.toString(decryptedData)); + + assertThat(decryptedData).isEqualTo(DATA); + } + + @Test + public void test_decryption_invalidInput() { + Cryptor v1Cryptor = CryptorImpV1.getInstance(); + assertThat(v1Cryptor.decrypt(getEncryptedData(), SALT, new byte[]{1, 2, 3, 4, 6})).isNull(); + } + + @Test + public void generateSign() { + CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance(); + byte[] generatedTag = v1Cryptor.sign(DATA, AUTHENTICITY_KEY); + byte[] expectedTag = new byte[]{ + 100, 88, -104, 80, -66, 107, -38, 95, 34, 40, -56, -23, -90, 90, -87, 12}; + assertThat(generatedTag).isEqualTo(expectedTag); + } + + @Test + public void test_verify() { + CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance(); + byte[] expectedTag = new byte[]{ + 100, 88, -104, 80, -66, 107, -38, 95, 34, 40, -56, -23, -90, 90, -87, 12}; + + assertThat(v1Cryptor.verify(DATA, AUTHENTICITY_KEY, expectedTag)).isTrue(); + assertThat(v1Cryptor.verify(DATA, AUTHENTICITY_KEY, DATA)).isFalse(); + } + + @Test + public void test_generateHmacTag_sameResult() { + CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance(); + byte[] res1 = v1Cryptor.generateHmacTag(DATA, AUTHENTICITY_KEY); + assertThat(res1) + .isEqualTo(v1Cryptor.generateHmacTag(DATA, AUTHENTICITY_KEY)); + } + + @Test + public void test_generateHmacTag_nullData() { + CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance(); + assertThat(v1Cryptor.generateHmacTag(/* data= */ null, AUTHENTICITY_KEY)).isNull(); + } + + @Test + public void test_generateHmacTag_nullKey() { + CryptorImpV1 v1Cryptor = CryptorImpV1.getInstance(); + assertThat(v1Cryptor.generateHmacTag(DATA, /* authenticityKey= */ null)).isNull(); + } + + private static byte[] getEncryptedData() { + return new byte[]{-92, 94, -99, -97, 81, -48, -7, 119, -64, -22, 45, -49, -50, 92}; + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorTest.java new file mode 100644 index 0000000000..ca612e3ec8 --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/util/encryption/CryptorTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.util.encryption; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; + +/** + * Unit test for {@link Cryptor} + */ +public final class CryptorTest { + + private static final byte[] DATA = + new byte[] {107, -102, 101, 107, 20, 62, 2, 73, 113, 59, 8, -14, -58, 122}; + private static final byte[] AUTHENTICITY_KEY = + new byte[] {-89, 88, -50, -42, -99, 57, 84, -24, 121, 1, -104, -8, -26, -73, -36, 100}; + + @Test + public void test_computeHkdf() { + int outputSize = 16; + byte[] res1 = Cryptor.computeHkdf(DATA, AUTHENTICITY_KEY, outputSize); + byte[] res2 = Cryptor.computeHkdf(DATA, + new byte[] {-89, 88, -50, -42, -99, 57, 84, -24, 121, 1, -104, -8, -26}, + outputSize); + + assertThat(res1).hasLength(outputSize); + assertThat(res2).hasLength(outputSize); + assertThat(res1).isNotEqualTo(res2); + assertThat(res1) + .isEqualTo(CryptorImpV1.computeHkdf(DATA, AUTHENTICITY_KEY, outputSize)); + } + + @Test + public void test_computeHkdf_invalidInput() { + assertThat(Cryptor.computeHkdf(DATA, AUTHENTICITY_KEY, /* size= */ 256000)) + .isNull(); + assertThat(Cryptor.computeHkdf(DATA, new byte[0], /* size= */ 255)) + .isNull(); + } +} diff --git a/nearby/tests/unit/src/com/android/server/nearby/util/identity/CallerIdentityTest.java b/nearby/tests/unit/src/com/android/server/nearby/util/identity/CallerIdentityTest.java new file mode 100644 index 0000000000..c29cb92fdc --- /dev/null +++ b/nearby/tests/unit/src/com/android/server/nearby/util/identity/CallerIdentityTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.nearby.util.identity; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; + +public class CallerIdentityTest { + private static final int UID = 100; + private static final int PID = 10002; + private static final String PACKAGE_NAME = "package_name"; + private static final String ATTRIBUTION_TAG = "attribution_tag"; + + @Test + public void testToString() { + CallerIdentity callerIdentity = + CallerIdentity.forTest(UID, PID, PACKAGE_NAME, ATTRIBUTION_TAG); + assertThat(callerIdentity.toString()).isEqualTo("100/package_name[attribution_tag]"); + assertThat(callerIdentity.isSystemServer()).isFalse(); + } + + @Test + public void testHashCode() { + CallerIdentity callerIdentity = + CallerIdentity.forTest(UID, PID, PACKAGE_NAME, ATTRIBUTION_TAG); + CallerIdentity callerIdentity1 = + CallerIdentity.forTest(UID, PID, PACKAGE_NAME, ATTRIBUTION_TAG); + assertThat(callerIdentity.hashCode()).isEqualTo(callerIdentity1.hashCode()); + } +} diff --git a/service-t/lint-baseline.xml b/service-t/lint-baseline.xml new file mode 100644 index 0000000000..38d3ab0ce9 --- /dev/null +++ b/service-t/lint-baseline.xml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/service/ServiceConnectivityResources/res/values-kn/strings.xml b/service/ServiceConnectivityResources/res/values-kn/strings.xml index 8046d0eb8a..98a2d9c825 100644 --- a/service/ServiceConnectivityResources/res/values-kn/strings.xml +++ b/service/ServiceConnectivityResources/res/values-kn/strings.xml @@ -30,7 +30,7 @@ "%1$s ಸೀಮಿತ ಸಂಪರ್ಕ ಕಲ್ಪಿಸುವಿಕೆಯನ್ನು ಹೊಂದಿದೆ" "ಹೇಗಾದರೂ ಸಂಪರ್ಕಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ" "%1$s ಗೆ ಬದಲಾಯಿಸಲಾಗಿದೆ" - "%2$s ಇಂಟರ್ನೆಟ್ ಆ್ಯಕ್ಸೆಸ್ ಹೊಂದಿಲ್ಲದಿರುವಾಗ, ಸಾಧನವು %1$s ಬಳಸುತ್ತದೆ. ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು." + "%2$s ಇಂಟರ್ನೆಟ್ ಪ್ರವೇಶ ಹೊಂದಿಲ್ಲದಿರುವಾಗ, ಸಾಧನವು %1$s ಬಳಸುತ್ತದೆ. ಶುಲ್ಕಗಳು ಅನ್ವಯವಾಗಬಹುದು." "%1$s ರಿಂದ %2$s ಗೆ ಬದಲಾಯಿಸಲಾಗಿದೆ" "ಮೊಬೈಲ್ ಡೇಟಾ" diff --git a/service/lint-baseline.xml b/service/lint-baseline.xml new file mode 100644 index 0000000000..5149e6d5d7 --- /dev/null +++ b/service/lint-baseline.xml @@ -0,0 +1,510 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 04d0b93073..0a651f829b 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -110,6 +110,7 @@ import static java.util.Map.Entry; import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.ActivityManager; import android.app.ActivityManager.UidFrozenStateChangedCallback; @@ -3245,6 +3246,8 @@ public class ConnectivityService extends IConnectivityManager.Stub sendStickyBroadcast(makeGeneralIntent(info, bcastType)); } + // TODO(b/193460475): Remove when tooling supports SystemApi to public API. + @SuppressLint("NewApi") // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed. @TargetApi(Build.VERSION_CODES.S) private void sendStickyBroadcast(Intent intent) { @@ -8888,6 +8891,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // else not handled } + // TODO(b/193460475): Remove when tooling supports SystemApi to public API. + @SuppressLint("NewApi") private void sendIntent(PendingIntent pendingIntent, Intent intent) { mPendingIntentWakeLock.acquire(); try { diff --git a/tests/cts/hostside/AndroidTest.xml b/tests/cts/hostside/AndroidTest.xml index e83e36afdb..90b7875894 100644 --- a/tests/cts/hostside/AndroidTest.xml +++ b/tests/cts/hostside/AndroidTest.xml @@ -33,6 +33,11 @@