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\u201dCouldn\'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 @@
+
+
+
+
+
diff --git a/tests/cts/hostside/app/Android.bp b/tests/cts/hostside/app/Android.bp
index 224538206a..470bb17a02 100644
--- a/tests/cts/hostside/app/Android.bp
+++ b/tests/cts/hostside/app/Android.bp
@@ -38,7 +38,6 @@ java_defaults {
srcs: ["src/**/*.java"],
// Tag this module as a cts test artifact
test_suites: [
- "cts",
"general-tests",
"sts",
],
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 1c997228e6..606ba08991 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -61,6 +61,7 @@ import android.service.notification.NotificationListenerService;
import android.util.Log;
import android.util.Pair;
+import com.android.compatibility.common.util.AmUtils;
import com.android.compatibility.common.util.BatteryUtils;
import com.android.compatibility.common.util.DeviceConfigStateHelper;
@@ -719,10 +720,12 @@ public abstract class AbstractRestrictBackgroundNetworkTestCase {
Log.i(TAG, "Setting Battery Saver Mode to " + enabled);
if (enabled) {
turnBatteryOn();
+ AmUtils.waitForBroadcastBarrier();
executeSilentShellCommand("cmd power set-mode 1");
} else {
executeSilentShellCommand("cmd power set-mode 0");
turnBatteryOff();
+ AmUtils.waitForBroadcastBarrier();
}
}
diff --git a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index 8c38b44d34..5331601e04 100644
--- a/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/tests/cts/hostside/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -449,13 +449,19 @@ public class NetworkPolicyTestUtils {
// this function and using PollingCheck to try to make sure the uid has updated and reduce the
// flaky rate.
public static void assertNetworkingBlockedStatusForUid(int uid, boolean metered,
- boolean expectedResult) throws Exception {
- PollingCheck.waitFor(() -> (expectedResult == isUidNetworkingBlocked(uid, metered)));
+ boolean expectedResult) {
+ final String errMsg = String.format("Unexpected result from isUidNetworkingBlocked; "
+ + "uid= " + uid + ", metered=" + metered + ", expected=" + expectedResult);
+ PollingCheck.waitFor(() -> (expectedResult == isUidNetworkingBlocked(uid, metered)),
+ errMsg);
}
- public static void assertIsUidRestrictedOnMeteredNetworks(int uid, boolean expectedResult)
- throws Exception {
- PollingCheck.waitFor(() -> (expectedResult == isUidRestrictedOnMeteredNetworks(uid)));
+ public static void assertIsUidRestrictedOnMeteredNetworks(int uid, boolean expectedResult) {
+ final String errMsg = String.format(
+ "Unexpected result from isUidRestrictedOnMeteredNetworks; "
+ + "uid= " + uid + ", expected=" + expectedResult);
+ PollingCheck.waitFor(() -> (expectedResult == isUidRestrictedOnMeteredNetworks(uid)),
+ errMsg);
}
public static boolean isUidNetworkingBlocked(int uid, boolean meteredNetwork) {
diff --git a/tests/cts/hostside/app2/AndroidManifest.xml b/tests/cts/hostside/app2/AndroidManifest.xml
index ff7240dba1..2c2d957d1a 100644
--- a/tests/cts/hostside/app2/AndroidManifest.xml
+++ b/tests/cts/hostside/app2/AndroidManifest.xml
@@ -20,6 +20,7 @@
+
@@ -45,7 +46,11 @@
+ android:foregroundServiceType="specialUse"
+ android:exported="true">
+
+