diff --git a/BoardConfig.mk b/BoardConfig.mk index 0122674..424383a 100644 --- a/BoardConfig.mk +++ b/BoardConfig.mk @@ -81,8 +81,8 @@ USE_DEVICE_SPECIFIC_GPS := true # Legacy BLOB Support TARGET_NEEDS_PLATFORM_TEXT_RELOCATIONS := true -# NFC -#BOARD_NFC_HAL_SUFFIX := msm8974 +# SONY NFC +BOARD_NFC_CHIPSET := sony # Partitions BOARD_FLASH_BLOCK_SIZE := 131072 diff --git a/NfcSony/Android.mk b/NfcSony/Android.mk new file mode 100644 index 0000000..d66b394 --- /dev/null +++ b/NfcSony/Android.mk @@ -0,0 +1,24 @@ +LOCAL_PATH:= $(call my-dir) + +##################################### +# Sony Configuration +##################################### +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_SRC_FILES := \ + $(call all-java-files-under, src) + +LOCAL_SRC_FILES += \ + $(call all-java-files-under, sony) + +LOCAL_PACKAGE_NAME := NfcSony +LOCAL_OVERRIDES_PACKAGES := Nfc +LOCAL_CERTIFICATE := platform + +LOCAL_PROGUARD_ENABLED := disabled + +include $(BUILD_PACKAGE) + +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/NfcSony/AndroidManifest.xml b/NfcSony/AndroidManifest.xml new file mode 100755 index 0000000..942a9f0 --- /dev/null +++ b/NfcSony/AndroidManifest.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/CleanSpec.mk b/NfcSony/CleanSpec.mk new file mode 100644 index 0000000..fc12f05 --- /dev/null +++ b/NfcSony/CleanSpec.mk @@ -0,0 +1,53 @@ +# Copyright (C) 2007 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. +# + +# If you don't need to do a full clean build but would like to touch +# a file or delete some intermediate files, add a clean step to the end +# of the list. These steps will only be run once, if they haven't been +# run before. +# +# E.g.: +# $(call add-clean-step, touch -c external/sqlite/sqlite3.h) +# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates) +# +# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with +# files that are missing or have been moved. +# +# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory. +# Use $(OUT_DIR) to refer to the "out" directory. +# +# If you need to re-do something that's already mentioned, just copy +# the command and add it to the bottom of the list. E.g., if a change +# that you made last week required touching a file and a change you +# made today requires touching the same file, just copy the old +# touch step and add it to the end of the list. +# +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ + +# For example: +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates) +#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates) +#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f) +#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*) + +# ************************************************ +# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST +# ************************************************ + +$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/app/Nfc.apk) +$(call add-clean-step, rm -f $(PRODUCT_OUT)/system/app/NfcGoogle.apk) +$(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/APPS/NfcGoogle_intermediates) diff --git a/NfcSony/assets/star.png b/NfcSony/assets/star.png new file mode 100644 index 0000000..7e52d03 Binary files /dev/null and b/NfcSony/assets/star.png differ diff --git a/NfcSony/etc/sample_nfcee_access.xml b/NfcSony/etc/sample_nfcee_access.xml new file mode 100644 index 0000000..d6c8707 --- /dev/null +++ b/NfcSony/etc/sample_nfcee_access.xml @@ -0,0 +1,45 @@ + + + + diff --git a/NfcSony/migrate_nfc.txt b/NfcSony/migrate_nfc.txt new file mode 100644 index 0000000..00e0d12 --- /dev/null +++ b/NfcSony/migrate_nfc.txt @@ -0,0 +1,3 @@ +# Migrate the prefs from the old package name +com.android.nfc:com.android.nfc3 + shared_prefs diff --git a/NfcSony/res/drawable-hdpi/ic_menu_cancel_holo_dark.png b/NfcSony/res/drawable-hdpi/ic_menu_cancel_holo_dark.png new file mode 100644 index 0000000..2956109 Binary files /dev/null and b/NfcSony/res/drawable-hdpi/ic_menu_cancel_holo_dark.png differ diff --git a/NfcSony/res/drawable-hdpi/icon.png b/NfcSony/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..f732349 Binary files /dev/null and b/NfcSony/res/drawable-hdpi/icon.png differ diff --git a/NfcSony/res/drawable-hdpi/nfc.png b/NfcSony/res/drawable-hdpi/nfc.png new file mode 100644 index 0000000..7ff0ab1 Binary files /dev/null and b/NfcSony/res/drawable-hdpi/nfc.png differ diff --git a/NfcSony/res/drawable-hdpi/tap_again.png b/NfcSony/res/drawable-hdpi/tap_again.png new file mode 100644 index 0000000..37542b9 Binary files /dev/null and b/NfcSony/res/drawable-hdpi/tap_again.png differ diff --git a/NfcSony/res/drawable-mdpi/back.png b/NfcSony/res/drawable-mdpi/back.png new file mode 100644 index 0000000..dcad41d Binary files /dev/null and b/NfcSony/res/drawable-mdpi/back.png differ diff --git a/NfcSony/res/drawable-mdpi/ic_menu_cancel_holo_dark.png b/NfcSony/res/drawable-mdpi/ic_menu_cancel_holo_dark.png new file mode 100644 index 0000000..6ed1327 Binary files /dev/null and b/NfcSony/res/drawable-mdpi/ic_menu_cancel_holo_dark.png differ diff --git a/NfcSony/res/drawable-mdpi/icon.png b/NfcSony/res/drawable-mdpi/icon.png new file mode 100644 index 0000000..a27df56 Binary files /dev/null and b/NfcSony/res/drawable-mdpi/icon.png differ diff --git a/NfcSony/res/drawable-mdpi/tap_again.png b/NfcSony/res/drawable-mdpi/tap_again.png new file mode 100644 index 0000000..284761d Binary files /dev/null and b/NfcSony/res/drawable-mdpi/tap_again.png differ diff --git a/NfcSony/res/drawable-xhdpi/icon.png b/NfcSony/res/drawable-xhdpi/icon.png new file mode 100644 index 0000000..3bfed64 Binary files /dev/null and b/NfcSony/res/drawable-xhdpi/icon.png differ diff --git a/NfcSony/res/drawable-xhdpi/tap_again.png b/NfcSony/res/drawable-xhdpi/tap_again.png new file mode 100644 index 0000000..6e4b87c Binary files /dev/null and b/NfcSony/res/drawable-xhdpi/tap_again.png differ diff --git a/NfcSony/res/drawable-xxhdpi/icon.png b/NfcSony/res/drawable-xxhdpi/icon.png new file mode 100644 index 0000000..ca1bce3 Binary files /dev/null and b/NfcSony/res/drawable-xxhdpi/icon.png differ diff --git a/NfcSony/res/drawable-xxhdpi/tap_again.png b/NfcSony/res/drawable-xxhdpi/tap_again.png new file mode 100644 index 0000000..ae696fd Binary files /dev/null and b/NfcSony/res/drawable-xxhdpi/tap_again.png differ diff --git a/NfcSony/res/drawable-xxxhdpi/icon.png b/NfcSony/res/drawable-xxxhdpi/icon.png new file mode 100644 index 0000000..9b0c96b Binary files /dev/null and b/NfcSony/res/drawable-xxxhdpi/icon.png differ diff --git a/NfcSony/res/layout/cardemu_item.xml b/NfcSony/res/layout/cardemu_item.xml new file mode 100644 index 0000000..1f01de1 --- /dev/null +++ b/NfcSony/res/layout/cardemu_item.xml @@ -0,0 +1,43 @@ + + + + + + + + + + diff --git a/NfcSony/res/layout/cardemu_payment_item.xml b/NfcSony/res/layout/cardemu_payment_item.xml new file mode 100644 index 0000000..ff8e933 --- /dev/null +++ b/NfcSony/res/layout/cardemu_payment_item.xml @@ -0,0 +1,29 @@ + + + + + diff --git a/NfcSony/res/layout/cardemu_resolver.xml b/NfcSony/res/layout/cardemu_resolver.xml new file mode 100644 index 0000000..34b141e --- /dev/null +++ b/NfcSony/res/layout/cardemu_resolver.xml @@ -0,0 +1,36 @@ + + + + + + + diff --git a/NfcSony/res/layout/screenshot.xml b/NfcSony/res/layout/screenshot.xml new file mode 100644 index 0000000..6c5bfc3 --- /dev/null +++ b/NfcSony/res/layout/screenshot.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/layout/tapagain.xml b/NfcSony/res/layout/tapagain.xml new file mode 100644 index 0000000..cb1d02b --- /dev/null +++ b/NfcSony/res/layout/tapagain.xml @@ -0,0 +1,45 @@ + + + + + + diff --git a/NfcSony/res/raw/end.ogg b/NfcSony/res/raw/end.ogg new file mode 100644 index 0000000..7199153 Binary files /dev/null and b/NfcSony/res/raw/end.ogg differ diff --git a/NfcSony/res/raw/error.ogg b/NfcSony/res/raw/error.ogg new file mode 100644 index 0000000..71d14b4 Binary files /dev/null and b/NfcSony/res/raw/error.ogg differ diff --git a/NfcSony/res/raw/start.ogg b/NfcSony/res/raw/start.ogg new file mode 100644 index 0000000..3c4d8c5 Binary files /dev/null and b/NfcSony/res/raw/start.ogg differ diff --git a/NfcSony/res/values-af/provisioning.xml b/NfcSony/res/values-af/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-af/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-af/strings.xml b/NfcSony/res/values-af/strings.xml new file mode 100644 index 0000000..12139c7 --- /dev/null +++ b/NfcSony/res/values-af/strings.xml @@ -0,0 +1,49 @@ + + + "NFC-diens" + "NFC" + "Kontak ontvang via NFC" + "Raak om hierdie persoon as \'n kontak by te voeg." + "NFC-interaksie voltooi" + "Raak om aan hierdie persoon jou kontakinligting te gee." + "NFC geaktiveer." + "Raak en straal" + "Inkomende straal..." + "Stralend…" + "Straling voltooi" + "Straal het nie voltooi nie" + "Straal gekanselleer" + "Kanselleer" + "Raak om te bekyk" + "Die ontvanger se toestel ondersteun nie groot lêeroordrag via \'n straal nie." + "Bring toestelle weer bymekaar" + "Straal is tans besig. Probeer weer wanneer die vorige oordrag voltooid is." + "toestel" + "Koppel tans %1$s" + "Het %1$s gekoppel" + "Kon nie %1$s koppel nie" + "Ontkoppel tans %1$s" + "Het %1$s ontkoppel" + "Bind tans %1$s saam" + "Kon nie %1$s saambind nie" + "Kon nie Bluetooth aktiveer nie" + "Is jy seker jy wil die Bluetooth-toestel, %1$s, saambind?" + "Ja" + "Nee" + "Tik weer om met %1$s te betaal" + "Tik weer om met %1$s te voltooi" + "Hierdie transaksie kon nie met %1$s voltooi word nie." + "Kon nie %1$s gebruik nie." + "Betaal met" + "Voltooi met" + "Jou voorkeurdiens vir tik en betaal is verwyder. Wil jy \'n ander een kies?" + "Tik \'n ander toestel om te voltooi" + "Koppel" + "Kon nie aan netwerk koppel nie" + "Gekoppel" + "Koppel aan netwerk" + "Koppel aan die netwerk %1$s?" + "Android Straal vereis dat NFC geaktiveer moet wees. Wil jy dit aktiveer?" + "Android Straal" + diff --git a/NfcSony/res/values-am/provisioning.xml b/NfcSony/res/values-am/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-am/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-am/strings.xml b/NfcSony/res/values-am/strings.xml new file mode 100644 index 0000000..49cd2af --- /dev/null +++ b/NfcSony/res/values-am/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc አገልግሎት" + "NFC" + "NFC የተቀበላቸው ዕውቂያዎች" + "ይሄን ሰው እንደ ዕውቅያ ለማከል ነካ አድርግ፡፡" + "NFC ልውውጥ ተጠናቋል" + "ለእዚህ ሰው የአንተን ዕውቅያ መረጃ ለመስጠት መታ አድርግ" + "NFC ነቅቷል፡፡" + "ለማብራት ንካ" + "ገቢ ሞገድ..." + "ሞገድ በማመንጨት ላይ..." + "አመልማሎ ተጠናቅቋል" + "ሞገድ አልተጠናቀቀም" + "ሞገድ ተሰርዧል" + "ይቅር" + "ለማየት ንካ" + "የተቀባዩ መሣሪያ ትልቅ ፋይል በሞገድ በኩል ማስተላለፍ አይደግፍም።" + "መሣሪያዎችን ዳግም አንድ ላይ አሰባስብ" + "Beam አሁን ላይ ባተሌ ነው። ቀዳሚው ዝውውር ሲጠናቀቅ እንደገና ይሞክሩ።" + "መሣሪያ" + "%1$sን በማገናኘት ላይ" + "%1$s ተገናኝቷል" + "%1$sን ማገናኘት አልተቻለም" + "የ%1$sን ግንኙነት በማቋረጥ ላይ" + "የ%1$s ግንኙነት ተቋርጧል" + "%1$sን በማጣመር ላይ" + "%1$sን ማጣመር አልተቻለም" + "ብሉቱዝ ማንቃት አልተቻለም" + "እርግጠኛ ንዎት የብሉቱዝ መሣሪያው %1$sን ማጣመር ይፈልጋሉ?" + "አዎ" + "የለም" + "በ%1$s ለመክፈል በድጋሚ መታ ያድርጉ" + "በ%1$s ለማጠናቀቅ በድጋሚ መታ ያድርጉ" + "ይህ ግብይት በ%1$s መጠናቀቅ አልቻለም።" + "%1$sን መጠቀም አልተቻለም።" + "በዚህ ይክፈሉ" + "በሚከተለው ያጠናቅቁ" + "እርስዎ የመረጡት መታ አድርጎ መክፈያ አገልግሎት ተወግዷል። ሌላ ይመርጡ?" + "ለማጠናቀቅ ሌማ መሣሪያ መታ ያድርጉ" + "አገናኝ" + "ከአውታረ መረብ ጋር መገናኘት አልተቻለም" + "ተገናኝቷል" + "ከአውታረ መረብ ጋር አገናኝ" + "ከ%1$s አውታረ መረብ ጋር ይገናኙ?" + "Android Beam ኤንኤፍሲ እንዲነቃ ይጠይቃል። ሊያነቁት ይፈልጋሉ?" + "Android Beam" + diff --git a/NfcSony/res/values-ar/provisioning.xml b/NfcSony/res/values-ar/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-ar/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-ar/strings.xml b/NfcSony/res/values-ar/strings.xml new file mode 100644 index 0000000..1e5db50 --- /dev/null +++ b/NfcSony/res/values-ar/strings.xml @@ -0,0 +1,49 @@ + + + "‏خدمة NFC" + "NFC" + "‏تم استلام جهة اتصال عبر NFC" + "المس لإضافة هذا الشخص كجهة اتصال." + "‏اكتمل تفاعل NFC" + "المس لمنح هذا الشخص معلومات اتصالك." + "‏تم تمكين NFC." + "المس لتبادل البيانات" + "بيانات واردة..." + "إرسال لاسلكي..." + "اكتمل تبادل البيانات" + "لم يكتمل تبادل البيانات" + "تم إلغاء تبادل البيانات" + "إلغاء" + "المس ليتم العرض" + "جهاز المستلم لا يوفر نقل ملفات كبيرة عبر تبادل البيانات." + "تجميع الأجهزة معًا من جديد" + "الشعاع مشغول حاليًا. يمكنك إعادة المحاولة عند اكتمال عملية النقل السابقة." + "جهاز" + "جارٍ الاتصال بـ %1$s" + "تم الاتصال بـ %1$s" + "تعذر الاتصال بـ %1$s" + "جارٍ إلغاء الاتصال بـ %1$s" + "تم إلغاء الاتصال بـ %1$s" + "جارٍ إقران %1$s" + "تعذر إقران %1$s" + "تعذر تمكين بلوتوث" + "هل تريد بالتأكيد إقران جهاز بلوتوث %1$s؟" + "نعم" + "لا" + "انقر مرة أخرى للدفع باستخدام %1$s" + "انقر مرة أخرى للإكمال باستخدام %1$s" + "تعذر إكمال هذه المعاملة باستخدام %1$s." + "تعذر استخدام %1$s." + "الدفع بواسطة" + "إكمال باستخدام" + "تمت إزالة خدمتك المفضلة للتوصيل والدفع. هل تريد اختيار خدمة أخرى؟" + "انقر على جهاز آخر للإكمال" + "اتصال" + "تعذر الاتصال بالشبكة" + "تم الاتصال" + "الاتصال بالشبكة" + "هل تريد الاتصال بالشبكة %1$s؟" + "‏يتطلب شعاع Android تمكين ميزة NFC. هل تريد تمكينها؟" + "‏شعاع Android" + diff --git a/NfcSony/res/values-as-rIN/strings.xml b/NfcSony/res/values-as-rIN/strings.xml new file mode 100644 index 0000000..f787488 --- /dev/null +++ b/NfcSony/res/values-as-rIN/strings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/values-ast-rES/strings.xml b/NfcSony/res/values-ast-rES/strings.xml new file mode 100644 index 0000000..2a02fa0 --- /dev/null +++ b/NfcSony/res/values-ast-rES/strings.xml @@ -0,0 +1,76 @@ + + + + Serviciu Nfc + Nfc + + Contautu recibíu per NFC + + Toca p\'amestar a esta persona como contautu. + + Interaición de NFC completa + + Toca pa compartir la to información de contautu con esta persona. + + NFC habilitada. + Toca pa compartir + Tresferencia entrante... + Compartiendo... + Tresferencia completada + Nun se completó la tresferencia + Tresferencia encaboxada + Encaboxar + Toca pa ver + El preséu del receptor nun almite la tresferencia de ficheros grandes. + Vuelvi a xuntar los preseos + + + + + + + + + + Nun pudo activase Bluetooth + + Daveres que quies empareyar el preséu Bluetooth %1$s? + + Non + + Vuelvi a tocar pa pagar con %1$s + + Toca otra vegada pa completar con %1$s + + Nun pudo completase la tresaición con %1$s. + + Nun pudo usase %1$s. + + Pagar con + + Completar con + + Desanicióse\'l to serviciu preferíu pa la función Tocar & Pagar. Esbillar otru?\" + + Toca otru preséu pa completar + + + Coneutar + + Nun pue coneutase a la rede + + Coneutada + + Afitar conexón con rede + + Coneutar a la rede %1$s? + + Android Beam necesita NFC p\'habilitase. Quies habilitalo? + + Android Beam + diff --git a/NfcSony/res/values-az-rAZ/strings.xml b/NfcSony/res/values-az-rAZ/strings.xml new file mode 100644 index 0000000..b1071ae --- /dev/null +++ b/NfcSony/res/values-az-rAZ/strings.xml @@ -0,0 +1,32 @@ + + + "Nfc Xidməti" + "Nfc" + "Kontakt NFC üzərindən alınıb" + "Bu şəxsi kontakt kimi əlavə etmək üçün toxunun." + "NFC qarşılıqlı əlaqə tamamlandı" + "Bu şəxsə kontakt məlumatınızı vermək üçün toxunun." + "NFC aktivləşdirilib." + "Beam üçün toxunun" + "Gələn beam..." + "Beaming..." + "Beam tamamlandı" + "Beam tamamlanmadı" + "Beam ləğv edilib" + "Ləğv et" + "Görüntüləmək üçün toxunun" + "Qəbul edənin cihazı beam vasitəsilə böyük fayl ötürülməsini dəstəkləmir." + "Cihazları yenidən bir yerə gətir" + "Qoşulur..." + "Qoşulu" + "Qoşula bilmədi" + "Əlaqə kəsilir" + "Əlaqə kəsilib" + "Qoşalama" + "Qoşalaya bilmədi" + "Bluetooth aktiv edilə bilmədi" + "%1$s Bluetooth cihazını qoşalamaq istədiyinizdən əminsiniz?" + "Bəli" + "Xeyr" + diff --git a/NfcSony/res/values-be/strings.xml b/NfcSony/res/values-be/strings.xml new file mode 100644 index 0000000..bd2bca9 --- /dev/null +++ b/NfcSony/res/values-be/strings.xml @@ -0,0 +1,41 @@ + + + + Nfc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/values-bg/provisioning.xml b/NfcSony/res/values-bg/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-bg/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-bg/strings.xml b/NfcSony/res/values-bg/strings.xml new file mode 100644 index 0000000..1b4cd27 --- /dev/null +++ b/NfcSony/res/values-bg/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc Service" + "КБП" + "Контактът е получен чрез КБП" + "Докоснете, за да добавите този човек като контакт." + "Взаимодействието чрез КБП завърши" + "Докоснете, за да дадете на този човек информацията си за връзка." + "КБП е активирана." + "Докоснете, за да излъчите" + "Входящо излъчване..." + "Излъчва се..." + "Излъчването завърши" + "Излъчването не завърши" + "Излъчването бе анулирано" + "Отказ" + "Докоснете, за да видите" + "Устройството на получателя не поддържа прехвърляне на големи файлове чрез излъчване." + "Приближете отново устройствата едно до друго" + "Понастоящем Android Лъч работи. Опитайте отново, когато предишното прехвърляне завърши." + "устройството" + "Установява се връзка с/ъс %1$s" + "Установена е връзка с/ъс %1$s" + "Не можа да се установи връзка с/ъс %1$s" + "Връзката с/ъс %1$s се прекратява" + "Връзката с/ъс %1$s е прекратена" + "Извършва се сдвояване с/ъс %1$s" + "Неуспешно сдвояване с/ъс %1$s" + "Bluetooth не можа да се активира" + "Наистина ли искате да сдвоите устройството с Bluetooth %1$s?" + "Да" + "Не" + "Докоснете отново за плащане със: %1$s" + "Докоснете отново за извършване със: %1$s" + "Тази транзакция не можа да бъде извършена със: %1$s." + "%1$s не можа да се използва." + "Плащане със:" + "Извършване със:" + "Предпочитаната от вас услуга за функцията „докоснете и платете“ бе премахната. Ще изберете ли друга?" + "Докоснете друго устройство, за да завършите" + "Свързване" + "Не може да се установи връзка с мрежата" + "Установена е връзка" + "Установяване на връзка с мрежата" + "Да се установи ли връзка с мрежата „%1$s“?" + "Функцията „Android Лъч“ изисква да активирате КБП. Искате ли да го направите?" + "Android Лъч" + diff --git a/NfcSony/res/values-bn-rBD/provisioning.xml b/NfcSony/res/values-bn-rBD/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-bn-rBD/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-bn-rBD/strings.xml b/NfcSony/res/values-bn-rBD/strings.xml new file mode 100644 index 0000000..9b5a7f5 --- /dev/null +++ b/NfcSony/res/values-bn-rBD/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc পরিষেবা" + "Nfc" + "NFC এর মাধ্যমে পরিচিতি প্রাপ্ত হয়েছে" + "এই ব্যক্তিকে একটি পরিচিতি হিসাবে জুড়তে স্পর্শ করুন।" + "NFC পারস্পরিক ক্রিয়া সম্পূর্ণ" + "এই ব্যক্তিকে আপনার পরিচিতি তথ্য দিতে স্পর্শ করুন।" + "NFC সক্ষম হয়েছে।" + "বীম করতে স্পর্শ করুন" + "আগত বীম..." + "বীম হচ্ছে..." + "বীম সম্পূর্ণ" + "বীম সস্পূর্ণ হয়নি" + "বীম বাতিল হয়েছে" + "বাতিল" + "দেখতে স্পর্শ করুন" + "রিসিভার এর ডিভাইস বীমের মাধ্যমে বড় ফাইল স্থানান্তর সমর্থন করে না।" + "ডিভাইসগুলিকে আবার একত্র করুন" + "বীম বর্তমানে ব্যস্ত রয়েছে৷ পূর্ববর্তী স্থানন্তরণটি সম্পূর্ণ হলে আবার চেষ্টা করুন৷" + "ডিভাইস" + "%1$s এর সাথে সংযোগ করা হচ্ছে" + "%1$s সংযুক্ত হয়েছে" + "%1$s সংযোগ করা যায়নি" + "%1$s থেকে সংযোগ বিচ্ছিন্ন করা হচ্ছে" + "%1$s থেকে সংযোগ বিচ্ছিন্ন হয়েছে" + "%1$s যুক্ত করা হচ্ছে" + "%1$s যুক্ত করা যায়নি" + "Bluetooth সক্ষম করা যায়নি" + "আপনি কি %1$s Bluetooth ডিভাইসটি যুক্ত করার ব্যাপারে নিশ্চিত?" + "হ্যাঁ" + "না" + "%1$s এর মাধ্যমে অর্থ প্রদানের জন্য আবার আলতো চাপুন" + "%1$s এর মাধ্যমে সম্পূর্ণ করতে আবার আলতো চাপুন" + "%1$s এর সঙ্গে লেনদেন সম্পন্ন করা যায়নি।" + "%1$s ব্যবহার করা যায়নি।" + "এর মাধ্যমে অর্থ পরিশোধ করুন" + "এর সঙ্গে সম্পূর্ণ করুন" + "আপনার পছন্দের “আলতো চাপুন ও অর্থ প্রদান করুন” পরিষেবা সরানো হয়েছে। অন্য কিছু চয়ন করবেন?" + "সম্পূর্ণ করতে অন্য ডিভাইস আলতো চাপুন" + "সংযুক্ত করুন" + "নেটওয়ার্কের সাথে সংযুক্ত হওয়া যায়নি" + "সংযুক্ত হয়েছে" + "নেটওয়ার্কের সাথে সংযোগ করুন" + "%1$s নেটওয়ার্কে সংযোগ করবেন?" + "Android Beam সক্ষম করতে NFC আবশ্যক। আপনি কি এটি সক্ষম করতে চান?" + "Android বীম" + diff --git a/NfcSony/res/values-br-rFR/strings.xml b/NfcSony/res/values-br-rFR/strings.xml new file mode 100644 index 0000000..f787488 --- /dev/null +++ b/NfcSony/res/values-br-rFR/strings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/values-ca/provisioning.xml b/NfcSony/res/values-ca/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-ca/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-ca/strings.xml b/NfcSony/res/values-ca/strings.xml new file mode 100644 index 0000000..ce34a07 --- /dev/null +++ b/NfcSony/res/values-ca/strings.xml @@ -0,0 +1,49 @@ + + + "Servei Nfc" + "Nfc" + "Contacte per NFC" + "Toca aquesta opció per afegir aquesta persona com a contacte." + "Interacció completa de NFC" + "Toca per donar la teva informació de contacte a aquesta persona." + "NFC activat." + "Toca per fer brillar" + "Transferència entrant..." + "S\'està compartint..." + "Sensor completat" + "El sensor no s\'ha executat" + "S\'ha desactivat el sensor" + "Cancel·la" + "Toca per visualitzar" + "El dispositiu del receptor no és compatible amb la transferència de fitxers grans mitjançant sensors." + "Torna a apropar els dispositius" + "En aquest moment Beam està ocupat. Prova-ho de nou quan acabi la transferència anterior." + "dispositiu" + "S\'està connectant %1$s." + "S\'ha connectat %1$s." + "No s\'ha pogut connectar %1$s." + "S\'està desconnectant %1$s." + "S\'ha desconnectat %1$s." + "S\'està vinculant %1$s." + "No s\'ha pogut vincular %1$s." + "No es pot activar el Bluetooth" + "Estàs segur que vols vincular el dispositiu Bluetooth %1$s?" + "Sí" + "No" + "Torna a tocar per pagar amb %1$s" + "Torna a tocar per completar-ho amb %1$s" + "No s\'ha pogut completar aquesta transacció amb %1$s." + "No s\'ha pogut utilitzar %1$s." + "Pagament amb" + "Completa amb" + "S\'ha suprimit el teu servei preferit per pagar i tocar. En vols triar un altre?" + "Toca un altre dispositiu pe completar el procés." + "Connecta" + "No es pot connectar a la xarxa." + "Connexió correcta" + "Connexió a la xarxa" + "Vols connectar-te a la xarxa %1$s?" + "Android Beam requereix que s\'activi NFC. Vols activar-lo?" + "Android Beam" + diff --git a/NfcSony/res/values-cs/provisioning.xml b/NfcSony/res/values-cs/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-cs/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-cs/strings.xml b/NfcSony/res/values-cs/strings.xml new file mode 100644 index 0000000..0b35ed8 --- /dev/null +++ b/NfcSony/res/values-cs/strings.xml @@ -0,0 +1,49 @@ + + + "Služba Nfc" + "Nfc" + "Kontakt přijatý prostřednictvím komunikace NFC" + "Klepnutím přidáte tuto osobu mezi kontakty" + "Interakce NFC byla dokončena" + "Klepnutím této osobě předáte své kontaktní údaje." + "Funkce NFC povolena." + "Přenos zahájíte dotykem" + "Příchozí přenos..." + "Přenášení..." + "Přenos dokončen" + "Přenos nebyl dokončen" + "Přenos byl zrušen" + "Zrušit" + "Zobrazte dotykem" + "Zařízení příjemce nepodporuje přenos velkých souborů prostřednictvím funkce Beam." + "Znovu přibližte zařízení" + "Aplikace Beam je momentálně zaneprázdněná. Opakujte akci po dokončení předchozího přenosu." + "zařízení" + "Připojování k zařízení %1$s" + "Připojeno k zařízení %1$s" + "Zařízení %1$s se nepodařilo připojit" + "Odpojování od zařízení %1$s" + "Odpojeno od zařízení %1$s" + "Párování se zařízením %1$s" + "Zařízení %1$s nelze spárovat" + "Bluetooth nelze povolit." + "Opravdu chcete spárovat zařízení Bluetooth %1$s?" + "Ano" + "Ne" + "Dotykem zaplatíte pomocí aplikace %1$s" + "Dotykem akci dokončíte pomocí aplikace %1$s" + "Tuto transakci nelze dokončit pomocí aplikace %1$s." + "Aplikaci %1$s nelze použít." + "Platební metoda" + "Dokončit pomocí" + "Preferovaná služba pro platbu mobilem byla odstraněna. Chcete vybrat jinou?" + "Akci dokončíte tím, že se zařízením dotknete jiného zařízení" + "Připojit" + "K síti se nelze připojit." + "Připojeno" + "Připojit k síti" + "Připojit k síti %1$s?" + "Funkce Android Beam k aktivaci vyžaduje technologii NFC. Chcete ji aktivovat?" + "Teleport Android" + diff --git a/NfcSony/res/values-csb-rPL/strings.xml b/NfcSony/res/values-csb-rPL/strings.xml new file mode 100644 index 0000000..f787488 --- /dev/null +++ b/NfcSony/res/values-csb-rPL/strings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/values-cy/strings.xml b/NfcSony/res/values-cy/strings.xml new file mode 100644 index 0000000..f787488 --- /dev/null +++ b/NfcSony/res/values-cy/strings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/values-da/provisioning.xml b/NfcSony/res/values-da/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-da/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-da/strings.xml b/NfcSony/res/values-da/strings.xml new file mode 100644 index 0000000..5b811b7 --- /dev/null +++ b/NfcSony/res/values-da/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc Service" + "NFC" + "Kontakt via NFC" + "Tryk for at tilføje denne person som kontaktperson." + "NFC-interaktion er komplet" + "Tryk for at give denne person dine kontaktoplysninger." + "NFC er aktiveret." + "Tryk for at overføre" + "Indgående overførsel..." + "Overfører..." + "Overførsel fuldført" + "Overførslen blev ikke fuldført" + "Overførslen blev annulleret" + "Annuller" + "Tryk for at se" + "Modtagerens enhed understøtter ikke overførsel af store filer via Beam." + "Før enhederne sammen igen" + "Beam er optaget i øjeblikket. Prøv igen, når den forrige overførsel er gennemført." + "enhed" + "Opretter forbindelse til %1$s" + "Forbundet med %1$s" + "Kunne ikke oprette forbindelse til %1$s" + "Forbindelsen til %1$s afbrydes" + "Forbindelsen til %1$s er afbrudt" + "Parrer med %1$s" + "Parring med %1$s mislykkedes" + "Bluetooth kunne ikke aktiveres" + "Er du sikker på du vil parre Bluetooth-enheden %1$s?" + "Ja" + "Nej" + "Tryk igen for at betale med %1$s" + "Tryk igen for at fuldføre med %1$s" + "Denne transaktion kunne ikke fuldføres med %1$s." + "Kunne ikke fuldføres med %1$s." + "Betal med" + "Fuldfør med" + "Din foretrukne tjeneste til Tryk og betal blev fjernet. Vil du vælge en anden?" + "Tryk på en anden enhed for at sende" + "Forbind" + "Der kunne ikke oprettes forbindelse til netværket" + "Der er oprettet forbindelse" + "Opret forbindelse til netværket" + "Vil du oprette forbindelse til netværket %1$s?" + "Android Beam kræver, at NFC er aktiveret. Vil du at aktivere NFC?" + "Android Beam" + diff --git a/NfcSony/res/values-de/provisioning.xml b/NfcSony/res/values-de/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-de/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-de/strings.xml b/NfcSony/res/values-de/strings.xml new file mode 100644 index 0000000..beb17b7 --- /dev/null +++ b/NfcSony/res/values-de/strings.xml @@ -0,0 +1,49 @@ + + + "NFC-Dienst" + "NFC" + "Kontakt per NFC erhalten" + "Zum Hinzufügen dieser Person als Kontakt tippen" + "NFC-Interaktion abgeschlossen" + "Zum Weitergeben Ihrer Kontaktinformationen an diese Person tippen" + "NFC aktiviert" + "Zum Beamen berühren" + "Beam wird empfangen..." + "Beamen..." + "Beamen abgeschlossen" + "Beam wurde nicht abgeschlossen." + "Beam abgebrochen" + "Abbrechen" + "Zum Anzeigen berühren" + "Das Gerät des Empfängers unterstützt die Übertragung großer Dateien durch Beamen nicht." + "Geräte erneut nebeneinander platzieren" + "Android Beam ist momentan ausgelastet. Bitte versuchen Sie es erneut, wenn die vorherige Übertragung abgeschlossen ist." + "Gerät" + "Verbindung zu %1$s wird hergestellt" + "Mit %1$s verbunden" + "Verbindung zu %1$s nicht möglich" + "Verbindung zu %1$s wird getrennt" + "Verbindung zu %1$s ist getrennt" + "Pairing mit %1$s wird durchgeführt" + "Pairing mit %1$s nicht möglich" + "Bluetooth konnte nicht aktiviert werden." + "Möchten Sie das Bluetooth-Gerät %1$s wirklich koppeln?" + "Ja" + "Nein" + "Halten Sie Ihr Gerät erneut ans Terminal, um mit %1$s zu bezahlen." + "Halten Sie Ihr Gerät erneut ans Terminal, um den Vorgang mit %1$s durchzuführen." + "Diese Transaktion konnte nicht mit %1$s durchgeführt werden." + "Fehler bei der Verwendung von %1$s" + "Zahlen mit" + "Durchführen mit" + "Der von Ihnen bevorzugte Dienst für das mobile Bezahlen wurde entfernt. Möchten Sie einen anderen auswählen?" + "Zum Abschluss auf ein anderes Gerät tippen" + "Verbinden" + "Verbindung zum Netzwerk konnte nicht hergestellt werden." + "Verbunden" + "Mit Netzwerk verbinden" + "Verbindung zum Netzwerk %1$s herstellen?" + "Android Beam erfordert zur Aktivierung die Nahfeldkommunikation (NFC). Möchten Sie sie aktivieren?" + "Android Beam" + diff --git a/NfcSony/res/values-el/provisioning.xml b/NfcSony/res/values-el/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-el/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-el/strings.xml b/NfcSony/res/values-el/strings.xml new file mode 100644 index 0000000..ce1fc72 --- /dev/null +++ b/NfcSony/res/values-el/strings.xml @@ -0,0 +1,49 @@ + + + "Υπηρεσία ΕΚΠ" + "ΕΚΠ" + "Λήψη επαφής μέσω ΕΚΠ" + "Αγγίξτε για να προσθέσετε αυτό το άτομο ως επαφή." + "Η αλληλεπίδραση ΕΚΠ ολοκληρώθηκε" + "Αγγίξτε για να δώσετε σε αυτό το άτομο τα στοιχεία επικοινωνίας σας." + "Ενεργοποίηση ΕΚΠ." + "Αγγίξτε για μετάδοση" + "Εισερχόμενη ζεύξη…" + "Αποστολή…" + "Ολοκλήρωση ζεύξης" + "Η ζεύξη δεν ολοκληρώθηκε" + "Ακύρωση ζεύξης" + "Άκυρο" + "Αγγίξτε για προβολή" + "Η συσκευή του παραλήπτη δεν υποστηρίζει τη μεταφορά μεγάλων αρχείων μέσω ζεύξης." + "Φέρτε ξανά τις συσκευές σας σε επαφή" + "Η λειτουργία ζεύξης είναι απασχολημένη αυτήν τη στιγμή. Δοκιμάστε ξανά μετά την ολοκλήρωση της προηγούμενης μεταφοράς." + "συσκευή" + "Σύνδεση σε %1$s" + "Συνδέθηκε %1$s" + "Δεν ήταν δυνατή η σύνδεση σε %1$s" + "Αποσύνδεση %1$s" + "Αποσυνδέθηκε %1$s" + "Σύζευξη %1$s" + "Δεν ήταν δυνατή η σύζευξη %1$s" + "Δεν ήταν δυνατή η ενεργοποίηση του Bluetooth" + "Είστε βέβαιοι ότι θέλετε να γίνει σύζευξη της συσκευής Bluetooth %1$s;" + "Ναι" + "Όχι" + "Πατήστε ξανά για να πληρώσετε με την εφαρμογή %1$s" + "Πατήστε ξανά για να ολοκληρώσετε αυτήν τη συναλλαγή με την εφαρμογή %1$s" + "Δεν ήταν δυνατή η ολοκλήρωση αυτής της συναλλαγής με την εφαρμογή %1$s." + "Δεν ήταν δυνατή η χρήση της εφαρμογής %1$s." + "Πληρωμή με" + "Ολοκλήρωση συναλλαγής με" + "Η προτιμώμενη υπηρεσία σας για τη λειτουργία \"Πατήστε και πληρώστε\" καταργήθηκε. Επιλογή άλλης;" + "Πατήστε μια άλλη συσκευή για ολοκλήρωση" + "Σύνδεση" + "Δεν είναι δυνατή η σύνδεση στο δίκτυο" + "Συνδέθηκε" + "Σύνδεση στο δίκτυο" + "Σύνδεση στο δίκτυο %1$s;" + "Το Android Beam απαιτεί την ενεργοποίηση της ΕΚΠ (Επικοινωνία κοντινού πεδίου). Θέλετε να την ενεργοποιήσετε;" + "Android Beam" + diff --git a/NfcSony/res/values-en-rAU/provisioning.xml b/NfcSony/res/values-en-rAU/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-en-rAU/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-en-rAU/strings.xml b/NfcSony/res/values-en-rAU/strings.xml new file mode 100644 index 0000000..307e92c --- /dev/null +++ b/NfcSony/res/values-en-rAU/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc Service" + "Nfc" + "Contact received over NFC" + "Touch to add this person as a contact." + "NFC interaction complete" + "Touch to give this person your contact info." + "NFC enabled." + "Touch to beam" + "Incoming beam..." + "Beaming..." + "Beam complete" + "Beam did not complete" + "Beam cancelled" + "Cancel" + "Touch to view" + "The receiver\'s device doesn\'t support large file transfer via beam." + "Bring devices together again" + "Beam is currently busy. Try again when the previous transfer completes." + "device" + "Connecting %1$s" + "Connected %1$s" + "Could not connect %1$s" + "Disconnecting %1$s" + "Disconnected %1$s" + "Pairing %1$s" + "Could not pair %1$s" + "Could not enable Bluetooth" + "Are you sure you want to pair the Bluetooth device %1$s?" + "Yes" + "No" + "Tap again to pay with %1$s" + "Tap again to complete with %1$s" + "This transaction couldn\'t be completed with %1$s." + "Couldn\'t use %1$s." + "Pay with:" + "Complete with" + "Your preferred service for tap & pay was removed. Choose another?" + "Tap another device to complete" + "Connect" + "Unable to connect to network" + "Connected" + "Connect to network" + "Connect to network %1$s?" + "Android Beam requires NFC to be enabled. Do you want to enable it?" + "Android Beam" + diff --git a/NfcSony/res/values-en-rGB/provisioning.xml b/NfcSony/res/values-en-rGB/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-en-rGB/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-en-rGB/strings.xml b/NfcSony/res/values-en-rGB/strings.xml new file mode 100644 index 0000000..307e92c --- /dev/null +++ b/NfcSony/res/values-en-rGB/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc Service" + "Nfc" + "Contact received over NFC" + "Touch to add this person as a contact." + "NFC interaction complete" + "Touch to give this person your contact info." + "NFC enabled." + "Touch to beam" + "Incoming beam..." + "Beaming..." + "Beam complete" + "Beam did not complete" + "Beam cancelled" + "Cancel" + "Touch to view" + "The receiver\'s device doesn\'t support large file transfer via beam." + "Bring devices together again" + "Beam is currently busy. Try again when the previous transfer completes." + "device" + "Connecting %1$s" + "Connected %1$s" + "Could not connect %1$s" + "Disconnecting %1$s" + "Disconnected %1$s" + "Pairing %1$s" + "Could not pair %1$s" + "Could not enable Bluetooth" + "Are you sure you want to pair the Bluetooth device %1$s?" + "Yes" + "No" + "Tap again to pay with %1$s" + "Tap again to complete with %1$s" + "This transaction couldn\'t be completed with %1$s." + "Couldn\'t use %1$s." + "Pay with:" + "Complete with" + "Your preferred service for tap & pay was removed. Choose another?" + "Tap another device to complete" + "Connect" + "Unable to connect to network" + "Connected" + "Connect to network" + "Connect to network %1$s?" + "Android Beam requires NFC to be enabled. Do you want to enable it?" + "Android Beam" + diff --git a/NfcSony/res/values-en-rIN/provisioning.xml b/NfcSony/res/values-en-rIN/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-en-rIN/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-en-rIN/strings.xml b/NfcSony/res/values-en-rIN/strings.xml new file mode 100644 index 0000000..307e92c --- /dev/null +++ b/NfcSony/res/values-en-rIN/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc Service" + "Nfc" + "Contact received over NFC" + "Touch to add this person as a contact." + "NFC interaction complete" + "Touch to give this person your contact info." + "NFC enabled." + "Touch to beam" + "Incoming beam..." + "Beaming..." + "Beam complete" + "Beam did not complete" + "Beam cancelled" + "Cancel" + "Touch to view" + "The receiver\'s device doesn\'t support large file transfer via beam." + "Bring devices together again" + "Beam is currently busy. Try again when the previous transfer completes." + "device" + "Connecting %1$s" + "Connected %1$s" + "Could not connect %1$s" + "Disconnecting %1$s" + "Disconnected %1$s" + "Pairing %1$s" + "Could not pair %1$s" + "Could not enable Bluetooth" + "Are you sure you want to pair the Bluetooth device %1$s?" + "Yes" + "No" + "Tap again to pay with %1$s" + "Tap again to complete with %1$s" + "This transaction couldn\'t be completed with %1$s." + "Couldn\'t use %1$s." + "Pay with:" + "Complete with" + "Your preferred service for tap & pay was removed. Choose another?" + "Tap another device to complete" + "Connect" + "Unable to connect to network" + "Connected" + "Connect to network" + "Connect to network %1$s?" + "Android Beam requires NFC to be enabled. Do you want to enable it?" + "Android Beam" + diff --git a/NfcSony/res/values-en-rPT/strings.xml b/NfcSony/res/values-en-rPT/strings.xml new file mode 100644 index 0000000..f787488 --- /dev/null +++ b/NfcSony/res/values-en-rPT/strings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/values-eo/strings.xml b/NfcSony/res/values-eo/strings.xml new file mode 100644 index 0000000..f787488 --- /dev/null +++ b/NfcSony/res/values-eo/strings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/values-es-rMX/strings.xml b/NfcSony/res/values-es-rMX/strings.xml new file mode 100644 index 0000000..f787488 --- /dev/null +++ b/NfcSony/res/values-es-rMX/strings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/values-es-rUS/provisioning.xml b/NfcSony/res/values-es-rUS/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-es-rUS/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-es-rUS/strings.xml b/NfcSony/res/values-es-rUS/strings.xml new file mode 100644 index 0000000..aa564b0 --- /dev/null +++ b/NfcSony/res/values-es-rUS/strings.xml @@ -0,0 +1,49 @@ + + + "Servicio NFC" + "NFC" + "Se recibió el contacto por NFC." + "Toca para agregar a esta persona como un contacto." + "Interacción NFC completa" + "Toca para que esta persona obtenga tu información de contacto." + "Se habilitó la NFC." + "Tocar para transmitir" + "Transmisión entrante..." + "Transmitiendo..." + "Transmisión completa" + "La transmisión no se completó" + "Transmisión cancelada" + "Cancelar" + "Toca para ver" + "El dispositivo del receptor no admite la transferencia de archivos de gran tamaño." + "Volver a unir los dispositivos" + "Beam está ocupado en este momento. Vuelve a intentarlo cuando finalice la transferencia anterior." + "dispositivo" + "Conectando %1$s" + "%1$s con conexión" + "Error al conectar %1$s" + "Desconectando %1$s" + "%1$s sin conexión" + "Sincronizando %1$s" + "Error al sincronizar %1$s" + "Error al activar Bluetooth" + "¿Realmente deseas sincronizar el dispositivo Bluetooth %1$s?" + "Sí" + "No" + "Presionar de nuevo para pagar con %1$s" + "Presionar de nuevo para completar con %1$s" + "No se pudo completar la transacción con %1$s." + "No se pudo utilizar %1$s." + "Pagar con" + "Completar con" + "Se eliminó tu servicio preferido para usar la función Presionar y pagar. ¿Quieres seleccionar otro?" + "Presiona otro dispositivo para completar." + "Conectar" + "No se pudo establecer la conexión con la red." + "Conexión establecida" + "Conectar a la red" + "¿Conectarse a la red %1$s?" + "Para usar Android Beam, debes habilitar la función NFC. ¿Quieres habilitarla?" + "Android Beam" + diff --git a/NfcSony/res/values-es/provisioning.xml b/NfcSony/res/values-es/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-es/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-es/strings.xml b/NfcSony/res/values-es/strings.xml new file mode 100644 index 0000000..b05c6cb --- /dev/null +++ b/NfcSony/res/values-es/strings.xml @@ -0,0 +1,49 @@ + + + "Servicio NFC" + "NFC" + "Contacto recibido por NFC" + "Toca para añadir a esta persona como contacto." + "Interacción de NFC completa" + "Toca para compartir tu información de contacto con esta persona." + "NFC habilitada" + "Toca para compartir" + "Transferencia entrante..." + "Compartiendo..." + "Transferencia completada" + "Transferencia no completada" + "Transferencia cancelada" + "Cancelar" + "Tocar para ver" + "El dispositivo del receptor no admite la transferencia de archivos de gran tamaño." + "Vuelve a juntar los dispositivos" + "Android Beam está ocupado en este momento. Vuelve a intentarlo cuando se complete la transferencia anterior." + "dispositivo" + "Conectando %1$s" + "%1$s conectado" + "No se ha podido conectar %1$s" + "Desconectando %1$s" + "%1$s desconectado" + "Vinculando %1$s" + "No se ha podido vincular %1$s" + "Error al habilitar Bluetooth" + "¿Seguro que quieres vincular el dispositivo Bluetooth %1$s?" + "Sí" + "No" + "Vuelve a tocar para pagar con %1$s" + "Vuelve a tocar para completar con %1$s" + "No se ha podido completar esta transacción con %1$s." + "No se ha podido usar %1$s." + "Pagar con" + "Completar con" + "Se ha retirado tu servicio preferido para usar la función Tocar y pagar. ¿Quieres seleccionar otro?" + "Toca otro dispositivo para completar el proceso" + "Conectar" + "Error al conectarse a la red" + "Conectado" + "Conectar a la red" + "¿Quieres conectarte a la red %1$s?" + "Para usar Android Beam, NFC debe estar habilitado. ¿Quieres habilitarlo?" + "Android Beam" + diff --git a/NfcSony/res/values-et-rEE/provisioning.xml b/NfcSony/res/values-et-rEE/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-et-rEE/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-et-rEE/strings.xml b/NfcSony/res/values-et-rEE/strings.xml new file mode 100644 index 0000000..f9b8ac8 --- /dev/null +++ b/NfcSony/res/values-et-rEE/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc teenus" + "Nfc" + "Kontakt saadud NFC kaudu" + "Puudutage, et lisada see inimene kontaktisikute hulka." + "NFC koostoime lõpetatud" + "Puudutage, et anda sellele inimesele oma kontaktandmed." + "NFC lubatud." + "Puudutage kiire kasutamiseks" + "Sissetulev kiir ..." + "Kiirega edastamine ..." + "Kiir on valmis" + "Kiir ei lõpetanud toimingut" + "Kiir on tühistatud" + "Tühista" + "Puudutage vaatamiseks" + "Vastuvõtja seade ei toeta suurte failide edastamist kiire kaudu." + "Ühendage seadmed uuesti" + "Kiirega edastamise funktsioon on praegu hõivatud. Proovige uuesti, kui ülekandmine on lõppenud." + "seade" + "Ühendamine seadmega %1$s" + "Ühendatud seadmega %1$s" + "Ei õnnestunud ühendada seadmega %1$s" + "Ühenduse katkestamine seadmega %1$s" + "Ühendus seadmega %1$s on katkestatud" + "Sidumine seadmega %1$s" + "Ei saa siduda seadmega %1$s" + "Bluetoothi ei saanud lubada" + "Kas soovite kindlasti ühildada Bluetoothi seadme %1$s?" + "Jah" + "Ei" + "Puudutage uuesti, et kasutada maksmiseks rakendust %1$s" + "Puudutage uuesti, et viia toiming lõpule rakendusega %1$s" + "Tehingut ei saanud teostada rakendusega %1$s." + "Rakendust %1$s ei saanud kasutada." + "Maksmine rakendusega" + "Teostamine rakendusega" + "Funktsiooni Puuduta ja maksa eelistatud teenus on eemaldatud. Kas soovite valida teise teenuse?" + "Lõpuleviimiseks puudutage teist seadet" + "Ühenda" + "Võrguga ei saa ühendust" + "Ühendatud" + "Võrguühenduse loomine" + "Kas luua ühendus võrguga %1$s?" + "Android Beam nõuab NFC lubamist. Kas soovite selle lubada?" + "Android Beam" + diff --git a/NfcSony/res/values-eu-rES/provisioning.xml b/NfcSony/res/values-eu-rES/provisioning.xml new file mode 100644 index 0000000..0c79a06 --- /dev/null +++ b/NfcSony/res/values-eu-rES/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "aplikazioa/com.android.kudeatutakohorniketa" + "aplikazioa/com.android.kudeatutakohorniketa.v2" + + diff --git a/NfcSony/res/values-eu-rES/strings.xml b/NfcSony/res/values-eu-rES/strings.xml new file mode 100644 index 0000000..7c3243a --- /dev/null +++ b/NfcSony/res/values-eu-rES/strings.xml @@ -0,0 +1,49 @@ + + + "NFC zerbitzua" + "NFC" + "Kontaktua NFC bidez jaso da" + "Pertsona hau kontaktu gisa gehitzeko, ukitu hau." + "NFC elkarrekintza osatu da" + "Zurekin harremanetan jartzeko informazioa emateko, ukitu hau." + "NFC gaituta." + "NFC bidez partekatzeko, ukitu hau" + "Sarrerako NFC bidez partekatzea…" + "NFC bidez partekatzen" + "NFC bidezko partekatzea osatu da" + "NFC bidezko partekatzea ez da osatu" + "NFC bidezko partekatzea bertan behera utzi da" + "Utzi" + "Ukitu ikusteko" + "Hartzailearen gailuak ez du onartzen fitxategi handiak NFC bidez transferitzea." + "Elkartu gailuak berriro" + "Beam lanpetuta dago. Saiatu berriro abian den transferentzia amaitutakoan." + "gailua" + "%1$s konektatzen" + "%1$s konektatu da" + "Ezin izan da %1$s konektatu" + "%1$s deskonektatzen" + "%1$s deskonektatu da" + "%1$s gailuarekin parekatzen" + "Ezin izan da %1$s gailuarekin parekatu" + "Ezin izan da Bluetootha gaitu" + "Ziur %1$s Bluetooth gailuarekin parekatu nahi duzula?" + "Bai" + "Ez" + "%1$s aplikazioarekin ordaintzeko, sakatu berriro" + "Ekintza %1$s aplikazioarekin osatzeko, sakatu berriro" + "Ezin izan da transakzioa osatu %1$s aplikazioarekin." + "Ezin izan da %1$s erabili." + "Ordaindu honekin" + "Ekintza osatzeko aplikazioak" + "Sakatu eta ordaindu eginbidea erabiltzeko zerbitzu hobetsia kendu egin da. Beste bat aukeratu nahi duzu?" + "Datuak bidaltzeko, ukitu beste gailu bat" + "Konektatu" + "Ezin da sarera konektatu" + "Konektatuta" + "Konektatu sarera" + "%1$s sarera konektatu nahi duzu?" + "Android Beam erabiltzeko NFC gaitu behar da. Gaitu egin nahi duzu?" + "Android Beam" + diff --git a/NfcSony/res/values-fa/provisioning.xml b/NfcSony/res/values-fa/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-fa/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-fa/strings.xml b/NfcSony/res/values-fa/strings.xml new file mode 100644 index 0000000..0cf63bf --- /dev/null +++ b/NfcSony/res/values-fa/strings.xml @@ -0,0 +1,49 @@ + + + "‏سرویس Nfc" + "Nfc" + "‏مخاطب از طریق NFC دریافت شد" + "برای افزودن این فرد به‌عنوان یک مخاطب، لمس کنید." + "‏تعامل NFC کامل شد" + "لمس کنید تا اطلاعات تماس خود را به این فرد بدهید." + "‏با NFC فعال شده." + "برای پخش، لمس کنید" + "در حال دریافت پرتو..." + "پرتو…" + "پرتو ارسال شد" + "پرتو کامل نشد" + "پرتو لغو شد" + "لغو" + "برای مشاهده لمس کنید" + "دستگاه گیرنده انتقال فایل‌های بزرگ از طریق پرتو را پشتیبانی نمی‌کند." + "مرتبط‌کردن دوباره دستگاه‌ها" + "«پرتو» در حال حاضر مشغول است. هنگامی که انتقال قبلی کامل شد دوباره امتحان کنید." + "دستگاه" + "در حال اتصال %1$s" + "%1$s متصل شد" + "%1$s متصل نشد" + "در حال قطع ارتباط %1$s" + "ارتباط %1$s قطع شد" + "در حال مرتبط‌سازی %1$s" + "%1$s مرتبط‌سازی نشد" + "فعال کردن بلوتوث امکانپذیر نیست" + "آیا مطمئنید که می‌خواهید با دستگاه بلوتوث %1$s مرتبط‌سازی کنید؟" + "بله" + "خیر" + "برای پرداخت با %1$s، دوباره ضربه بزنید" + "برای تکمیل از طریق %1$s، دوباره ضربه بزنید" + "این تراکنش با %1$s تکمیل نشد." + "نتوانست از %1$s استفاده کند." + "پرداخت با" + "تکمیل با" + "سرویس ترجیحی شما برای ضربه و پرداخت حذف شده است. سرویس دیگری را انتخاب می‌کنید؟" + "برای تکمیل، روی دستگاه دیگری ضربه بزنید" + "اتصال" + "اتصال به شبکه ممکن نیست" + "مرتبط شد" + "اتصال به شبکه" + "به شبکه %1$s متصل می‌شوید؟" + "‏پرتوی Android برای فعال شدن به NFC نیاز دارد. می‌خواهید آن را فعال کنید؟" + "‏پرتو Android" + diff --git a/NfcSony/res/values-fi/provisioning.xml b/NfcSony/res/values-fi/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-fi/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-fi/strings.xml b/NfcSony/res/values-fi/strings.xml new file mode 100644 index 0000000..609c19d --- /dev/null +++ b/NfcSony/res/values-fi/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc Service" + "NFC" + "Kontakti saatu NFC:n välityksellä" + "Lisää tämä henkilö yhteystietoihin koskettamalla." + "NFC-vuorovaikutus suoritettu" + "Anna tälle henkilölle yhteystietosi koskettamalla." + "NFC otettu käyttöön." + "Muodosta yhteys" + "Saapuva sisällönjakopyyntö..." + "Jaetaan sisältöä…" + "Sisältö jaettu" + "Jakoa ei suoritettu valmiiksi" + "Jako peruutettu" + "Peruuta" + "Kosketa ja näytä" + "Vastaanottajan laite ei tue suurten tiedostojen siirtoa sisällön jakamisen kautta." + "Yhdistä laitteet uudelleen" + "Beam on tällä hetkellä varattu. Yritä uudelleen, kun edellinen siirto on valmis." + "laite" + "Yhdistetään %1$s" + "%1$s on yhdistetty" + "%1$s ei voinut muodostaa yhteyttä" + "Katkaistaan yhteys: %1$s" + "%1$s ei ole enää yhteydessä" + "Muodostetaan laiteparia: %1$s" + "Laiteparia ei voitu muodostaa: %1$s" + "Bluetooth-yhteyttä ei voi ottaa käyttöön" + "Oletko varma, että haluat muodostaa laiteparin Bluetooth-laitteeseen %1$s?" + "Kyllä" + "Ei" + "Maksa sovelluksella %1$s napauttamalla uudelleen." + "Maksa sovelluksella %1$s napauttamalla uudelleen." + "Tapahtuman suorittaminen sovelluksella %1$s epäonnistui." + "Sovelluksen %1$s käyttö epäonnistui." + "Maksusovellus:" + "Käytettävä sovellus:" + "Napauttamalla maksamiseen valitsemasi palvelu poistettiin. Haluatko valita toisen?" + "Suorita toiminto loppuun napauttamalla toista laitetta" + "Yhdistä" + "Verkkoyhteyttä ei voi muodostaa" + "Yhdistetty" + "Yhdistä verkkoon" + "Yhdistetäänkö verkkoon %1$s?" + "Android Beam edellyttää, että NFC on käytössä. Haluatko ottaa sen käyttöön?" + "Android Beam" + diff --git a/NfcSony/res/values-fr-rCA/provisioning.xml b/NfcSony/res/values-fr-rCA/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-fr-rCA/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-fr-rCA/strings.xml b/NfcSony/res/values-fr-rCA/strings.xml new file mode 100644 index 0000000..6d29ed9 --- /dev/null +++ b/NfcSony/res/values-fr-rCA/strings.xml @@ -0,0 +1,49 @@ + + + "Service NFC" + "NFC" + "Contact reçu via NFC" + "Appuyez pour ajouter cette personne à vos contacts." + "Communication NFC terminée." + "Appuyez pour communiquer vos coordonnées à cette personne." + "NFC activée" + "Appuyer pour partager" + "Partage entrant..." + "Partage en cours…" + "Partage terminé." + "Échec du partage." + "Partage annulé." + "Annuler" + "Appuyer pour afficher" + "Le récepteur n\'est pas compatible avec le transfert de fichiers volumineux via le partage." + "Mettre de nouveau les appareils côte à côte" + "Beam est actuellement occupé. Essayez de nouveau lorsque le transfert précédent sera terminé." + "appareil" + "Connexion de « %1$s » en cours" + "Connecté à « %1$s »" + "Impossible de connecter « %1$s »" + "Déconnexion de « %1$s » en cours" + "Déconnecté de « %1$s »" + "Association de « %1$s » en cours" + "Impossible d\'associer « %1$s »" + "Impossible d\'activer le Bluetooth." + "Voulez-vous vraiment associer l\'appareil Bluetooth %1$s?" + "Oui" + "Non" + "Touchez à nouveau pour payer avec %1$s" + "Touchez à nouveau pour terminer la transaction avec %1$s" + "Cette transaction n\'a pas pu être effectuée avec %1$s." + "Impossible d\'utiliser %1$s." + "Payer avec" + "Compatible avec :" + "Votre service préféré pour toucher et payer a été supprimé. En choisir un autre?" + "Touchez un autre appareil pour terminer" + "Associer" + "Impossible de se connecter au réseau" + "Connecté" + "Se connecter au réseau" + "Se connecter au réseau %1$s?" + "L\'activation de la connectivité NFC est nécessaire pour utiliser Android Beam. Voulez-vous l\'activer?" + "Android Beam" + diff --git a/NfcSony/res/values-fr/provisioning.xml b/NfcSony/res/values-fr/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-fr/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-fr/strings.xml b/NfcSony/res/values-fr/strings.xml new file mode 100644 index 0000000..5f2de3d --- /dev/null +++ b/NfcSony/res/values-fr/strings.xml @@ -0,0 +1,49 @@ + + + "Service NFC" + "NFC" + "Contact reçu via NFC" + "Appuyez pour ajouter cette personne à vos contacts." + "Communication NFC terminée." + "Appuyez pour communiquer vos coordonnées à cette personne." + "NFC activée" + "Appuyer pour partager" + "Partage entrant..." + "Partage en cours…" + "Partage terminé." + "Échec du partage." + "Partage annulé." + "Annuler" + "Appuyer pour afficher" + "Le récepteur n\'est pas compatible avec le transfert de fichiers volumineux via le partage." + "Mettre de nouveau les appareils côte à côte" + "Android Beam est actuellement occupé. Veuillez réessayer une fois le transfert précédent terminé." + "appareil" + "Connexion de l\'appareil %1$s en cours…" + "L\'appareil %1$s a bien été connecté." + "Impossible de connecter %1$s." + "Déconnexion de l\'appareil %1$s en cours…" + "L\'appareil %1$s a bien été déconnecté." + "Association de l\'appareil %1$s en cours…" + "Impossible d\'associer l\'appareil %1$s." + "Impossible d\'activer le Bluetooth." + "Voulez-vous vraiment associer l\'appareil Bluetooth %1$s ?" + "Oui" + "Non" + "Appuyez à nouveau pour payer avec %1$s" + "Appuyez à nouveau pour terminer la transaction avec %1$s" + "Impossible d\'effectuer cette transaction avec %1$s." + "Impossible d\'utiliser %1$s." + "Payer avec" + "Effectuer avec" + "Le service par défaut de paiement sans contact a été supprimé. Voulez-vous en sélectionner un autre ?" + "Approchez votre téléphone d\'un autre appareil pour finaliser l\'envoi." + "Se connecter" + "Impossible de se connecter au réseau." + "Connecté" + "Se connecter au réseau" + "Se connecter au réseau %1$s ?" + "L\'activation de la NFC est nécessaire pour utiliser Android Beam. Voulez-vous l\'activer ?" + "Android Beam" + diff --git a/NfcSony/res/values-frp-rIT/strings.xml b/NfcSony/res/values-frp-rIT/strings.xml new file mode 100644 index 0000000..f787488 --- /dev/null +++ b/NfcSony/res/values-frp-rIT/strings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/values-fy-rNL/strings.xml b/NfcSony/res/values-fy-rNL/strings.xml new file mode 100644 index 0000000..f787488 --- /dev/null +++ b/NfcSony/res/values-fy-rNL/strings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/values-gd-rGB/strings.xml b/NfcSony/res/values-gd-rGB/strings.xml new file mode 100644 index 0000000..f787488 --- /dev/null +++ b/NfcSony/res/values-gd-rGB/strings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/values-gl-rES/provisioning.xml b/NfcSony/res/values-gl-rES/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-gl-rES/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-gl-rES/strings.xml b/NfcSony/res/values-gl-rES/strings.xml new file mode 100644 index 0000000..845ee42 --- /dev/null +++ b/NfcSony/res/values-gl-rES/strings.xml @@ -0,0 +1,49 @@ + + + "Servizo NFC" + "NFC" + "Contacto recibido por NFC" + "Toca para engadir esta persoa aos teus contactos." + "Completouse a interacción por NFC" + "Toca para pasarlle a esta persoa a túa información de contacto." + "NFC activado" + "Toca para transferir" + "Transferencia entrante..." + "Transferindo..." + "Completouse a transferencia" + "Non se completou a transferencia" + "Cancelouse a transferencia" + "Cancelar" + "Toca para ver" + "O dispositivo do destinatario non é compatible coa transferencia de ficheiros grandes." + "Achega os dispositivos de novo" + "Beam está ocupado neste momento. Téntao de novo cando se complete a transferencia anterior." + "dispositivo" + "Conectando %1$s" + "%1$s conectado" + "Non se puido conectar %1$s" + "Desconectando %1$s" + "%1$s desconectado" + "Sincronizando %1$s" + "Non se puido sincronizar %1$s" + "Non se puido activar o Bluetooth" + "Seguro que queres sincronizar o dispositivo Bluetooth %1$s?" + "Si" + "Non" + "Toca de novo para pagar con %1$s" + "Toca de novo para completar con %1$s" + "Esta transacción non se puido completar con %1$s." + "Non se puido usar %1$s." + "Pagar con" + "Completar con" + "Eliminouse o teu servizo preferido para tocar e pagar. Queres escoller outro?" + "Toca outro dispositivo para completar a acción" + "Conectar" + "Non se pode conectar á rede" + "Conectado" + "Conectar coa rede" + "Queres conectarte á rede %1$s?" + "Android Beam necesita NFC para activarse. Queres activalo?" + "Android Beam" + diff --git a/NfcSony/res/values-gu-rIN/provisioning.xml b/NfcSony/res/values-gu-rIN/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-gu-rIN/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-gu-rIN/strings.xml b/NfcSony/res/values-gu-rIN/strings.xml new file mode 100644 index 0000000..de7d901 --- /dev/null +++ b/NfcSony/res/values-gu-rIN/strings.xml @@ -0,0 +1,49 @@ + + + "NFC સેવા" + "NFC" + "NFC પર સંપર્ક પ્રાપ્ત" + "આ વ્યક્તિને એક સંપર્ક તરીકે ઉમેરવા માટે ટચ કરો." + "NFC ક્રિયાપ્રતિક્રિયા પૂર્ણ" + "આ વ્યક્તિને તમારી સંપર્ક માહિતી આપવા માટે ટચ કરો." + "NFC સક્ષમ." + "બીમ કરવા ટચ કરો" + "ઇનકમિંગ બીમ ..." + "બીમિંગ..." + "બીમ પૂર્ણ" + "બીમ પૂર્ણ થયું નહોતું" + "બીમ રદ કર્યું" + "રદ કરો" + "જોવા માટે ટચ કરો" + "પ્રાપ્તિકર્તાનું ઉપકરણ બીમ દ્વારા મોટી ફાઇલનાં સ્થાનાંતરણનું સમર્થન કરતું નથી." + "ઉપકરણોને ફરીથી એકસાથે લાવો" + "બીમ હાલમાં વ્યસ્ત છે. પહેલાંના સ્થાનાંતરણ પૂર્ણ થાય ત્યારે ફરીથી પ્રયાસ કરો." + "ઉપકરણ" + "%1$s કનેક્ટ થઈ રહ્યું છે" + "%1$s કનેક્ટ કર્યું" + "%1$s થી કનેક્ટ કરી શકાયું નથી" + "%1$s ડિસ્કનેક્ટ થઈ રહ્યું છે" + "%1$s ડિસ્કનેક્ટ કર્યું" + "%1$s ની જોડી કરી રહ્યું છે" + "%1$s ની જોડી કરી શકાઈ નથી" + "Bluetooth સક્ષમ કરી શકાયું નથી" + "શું તમે ખરેખર Bluetooth ની ઉપકરણ %1$s સાથે જોડી કરવા માંગો છો?" + "હા" + "નહીં" + "%1$s થી ચૂકવણી કરવા માટે ફરીથી ટેપ કરો" + "%1$s સાથે પૂર્ણ કરવા માટે ટેપ કરો" + "આ વ્યવહાર %1$s સાથે પૂર્ણ કરી શકાયો નથી." + "%1$s નો ઉપયોગ કરી શકાયો નથી." + "આનાથી ચૂકવો" + "આની સાથે પૂર્ણ કરો" + "ટેપ કરો અને ચૂકવો માટેની તમારી પસંદ કરેલ સેવા દૂર કરવામાં આવી હતી. બીજી પસંદ કરીએ?" + "પૂર્ણ કરવા માટે બીજા ઉપકરણને ટેપ કરો" + "કનેક્ટ" + "નેટવર્ક સાથે જોડાવવામાં અસમર્થ" + "કનેક્ટ કર્યું" + "નેટવર્ક સાથે કનેક્ટ કરો" + "%1$s નેટવર્કથી કનેક્ટ કરીએ?" + "Android બીમને NFC સક્ષમ કરવાની જરૂર છે. શું તમે તેને સક્ષમ કરવા માંગો છો?" + "Android બીમ" + diff --git a/NfcSony/res/values-hi/provisioning.xml b/NfcSony/res/values-hi/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-hi/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-hi/strings.xml b/NfcSony/res/values-hi/strings.xml new file mode 100644 index 0000000..d17db31 --- /dev/null +++ b/NfcSony/res/values-hi/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc सेवा" + "NFC" + "NFC पर प्राप्त संपर्क" + "इस व्‍यक्ति को संपर्क के रूप में जोड़ने के लिए स्‍पर्श करें." + "NFC सहभागिता पूर्ण" + "इस व्‍यक्ति को अपनी संपर्क जानकारी देने के लिए स्‍पर्श करें." + "NFC सक्षम है." + "बीम करने के लिए स्पर्श करें" + "इनकमिंग बीम..." + "बीम हो रहा है..." + "बीम करना पूर्ण" + "बीम पूर्ण नहीं हुआ" + "बीम रद्द" + "अभी नहीं" + "देखने के लिए स्‍पर्श करें" + "प्राप्तकर्ता का डिवाइस, बीम द्वारा बड़ी फ़ाइल के स्थानांतरण का समर्थन नहीं करता." + "डिवाइस को पुन: एक साथ लाएं" + "Beam वर्तमान में व्‍यस्‍त है. पिछला ट्रांसफर पूर्ण हो जाने पर पुन: प्रयास करें." + "डिवाइस" + "%1$s को कनेक्‍ट किया जा रहा है" + "%1$s को कनेक्‍ट किया गया" + "%1$s को कनेक्‍ट नहीं किया जा सका" + "%1$s को डिस्‍कनेक्‍ट किया जा रहा है" + "%1$s को डिस्‍कनेक्‍ट किया गया" + "%1$s को युग्‍मित किया जा रहा है" + "%1$s को युग्‍मित नहीं किया जा सका" + "ब्लूटूथ को सक्षम नहीं किया जा सका" + "क्‍या आप वाकई ब्लूटूथ डिवाइस %1$s को युग्मित करना चाहते हैं?" + "हां" + "नहीं" + "%1$s के साथ पेमेंट करने के लिए पुन: टैप करें" + "%1$s के साथ पूर्ण करने के लिए पुन: टैप करें" + "यह लेन-देन %1$s के साथ पूर्ण नहीं किया जा सकता." + "%1$s का उपयोग नहीं किया जा सका." + "इनके द्वारा पेमेंट करें" + "इनके द्वारा पूर्ण करें" + "टैप करके पेमेंट करने के लिए आपकी पसंदीदा सेवा को निकाल दिया गया था. कोई अन्‍य चुनें?" + "पूर्ण करने के लिए अन्य डिवाइस टैप करें" + "कनेक्ट करें" + "नेटवर्क से कनेक्‍ट करने में असमर्थ" + "कनेक्ट किया गया" + "नेटवर्क से कनेक्‍ट करें" + "%1$s नेटवर्क से कनेक्ट करें?" + "Android Beam के लिए NFC को सक्षम किया जाना आवश्यक है. क्या आप उसे सक्षम करना चाहते हैं?" + "Android Beam" + diff --git a/NfcSony/res/values-hr/provisioning.xml b/NfcSony/res/values-hr/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-hr/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-hr/strings.xml b/NfcSony/res/values-hr/strings.xml new file mode 100644 index 0000000..863d4b1 --- /dev/null +++ b/NfcSony/res/values-hr/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc Service" + "NFC" + "Kontakt primljen NFC-om" + "Dodirnite kako biste ovu osobu dodali kao kontakt" + "NFC interakcija dovršena" + "Dodirnite kako biste ovoj osobi dali svoje podatke za kontakt." + "NFC omogućen." + "Dodirnite za emitiranje" + "Dolazno emitiranje..." + "Emitiranje..." + "Emitiranje je završeno" + "Emitiranje nije dovršeno" + "Emitiranje otkazano" + "Odustani" + "Dodirnite za prikaz" + "Uređaj prijamnika ne podržava prijenos velikih datoteka emitiranjem." + "Opet povežite uređaje" + "Beam je trenutačno zauzet. Pokušajte ponovo po dovršetku prethodnog prijenosa." + "uređaj" + "Povezivanje s uređajem %1$s" + "Uređaj %1$s povezan je" + "Uređaj %1$s nije povezan" + "Prekidanje veze s uređajem %1$s" + "Prekinuta je veza s uređajem %1$s" + "Uparivanje uređaja %1$s" + "Uređaj %1$s nije uparen" + "Omogućavanje Bluetootha nije bilo moguće" + "Jeste li sigurni da želite upariti Bluetooth uređaj %1$s?" + "Da" + "Ne" + "Dodirnite ponovo da biste platili pomoću aplikacije %1$s" + "Dodirnite ponovo da biste dovršili transakciju pomoću aplikacije %1$s" + "Ta transakcija nije mogla biti dovršena pomoću aplikacije %1$s." + "Upotreba aplikacije %1$s nije uspjela." + "Plati pomoću aplikacije" + "Dovrši pomoću aplikacije" + "Vaša je odabrana usluga za opciju Dodirni i plati uklonjena. Želite li odabrati drugu?" + "Dodirnite neki drugi uređaj da biste završili" + "Poveži" + "Povezivanje s mrežom nije moguće" + "Povezano" + "Povezivanje s mrežom" + "Želite li se povezati s mrežom %1$s?" + "Android Beam zahtijeva da bude omogućena funkcija NFC. Želite li je omogućiti?" + "Android Beam" + diff --git a/NfcSony/res/values-hu/provisioning.xml b/NfcSony/res/values-hu/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-hu/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-hu/strings.xml b/NfcSony/res/values-hu/strings.xml new file mode 100644 index 0000000..28b02f5 --- /dev/null +++ b/NfcSony/res/values-hu/strings.xml @@ -0,0 +1,49 @@ + + + "NFC-szolgáltatás" + "NFC" + "NFC-n kapott névjegy" + "Érintse meg, ha hozzá szeretné adni ezt a személyt ismerősként." + "Az NFC-kapcsolat befejeződött" + "Érintse meg kapcsolatfelvételi adatok megosztásához." + "NFC bekapcsolva" + "Érintse meg a sugárzáshoz" + "Bejövő sugár..." + "Sugárzás..." + "Átvitel befejezve." + "A sugárzás nem fejeződött be" + "Sugárzás megszakítva" + "Mégse" + "A megtekintéshez érintse meg." + "A fogadó eszköz nem támogatja a nagyméretű fájlok átvitelét adatsugárzás útján." + "Eszközök újbóli összekapcsolása" + "A Beam jelenleg elfoglalt. Próbálja újra, ha az előző átvitel már befejeződött." + "eszköz" + "Csatlakozás a következőhöz: %1$s" + "Csatlakozva a következőhöz: %1$s" + "Nem sikerült csatlakozni ehhez: %1$s" + "Leválasztás a következőről: %1$s" + "Leválasztva a következőről: %1$s" + "Párosítás a következővel: %1$s" + "Nem sikerült a párosítás ezzel: %1$s" + "Nem lehet aktiválni a Bluetootht" + "Biztosan párosítani szeretné a(z) %1$s Bluetooth-eszközt?" + "Igen" + "Nem" + "Koppintson ismét a(z) %1$s alkalmazással való fizetéshez" + "Koppintson ismét, hogy a(z) %1$s alkalmazással fejezze be a műveletet" + "A tranzakciót nem lehetett végrehajtani a(z) %1$s alkalmazással." + "Nem lehetett használni a(z) %1$s alkalmazást." + "Fizetőeszköz" + "Befejezés a következővel:" + "Az érintéssel való fizetésnél előnyben részesített szolgáltatást eltávolították. Választ egy másikat?" + "A befejezéshez koppintson egy másik eszközre" + "Csatlakozás" + "Nem lehet csatlakozni a hálózathoz" + "Csatlakozva" + "Csatlakozás hálózathoz" + "Csatlakozik a következő hálózathoz: %1$s?" + "Az Android Beam használatához engedélyezni kell az NFC-t. Engedélyezi?" + "Android Beam" + diff --git a/NfcSony/res/values-hy-rAM/provisioning.xml b/NfcSony/res/values-hy-rAM/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-hy-rAM/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-hy-rAM/strings.xml b/NfcSony/res/values-hy-rAM/strings.xml new file mode 100644 index 0000000..2b32b45 --- /dev/null +++ b/NfcSony/res/values-hy-rAM/strings.xml @@ -0,0 +1,49 @@ + + + "NFC ծառայություն" + "Nfc" + "NFC-ով ստացված կոնտակտ" + "Հպեք՝ այս անձին որպես կոնտակտ ավելացնելու համար:" + "NFC-ով տվյալների փոխանցման գործընթացն ավարտված է" + "Հպեք՝ այս անձին ձեր կոնտակտային տվյալները փոխանցելու համար:" + "NFC-ն միացված է:" + "Սեղմեք՝ տվյալները փոխանցելու համար" + "Մուտքային փոխանցում..." + "Փոխանցվում է..." + "Ռադիոհաղորդմամբ գործընթացն ավարտված է" + "Ռադիոհաղորդմամբ գործընթացն ավարտված չէ" + "Ռադիոհաղորդմամբ գործընթացը չեղարկված է" + "Չեղարկել" + "Հպեք՝ դիտելու համար" + "Ընդունողի սարքը չի աջակցում ռադիոհաղորդմամբ մեծ ֆայլերի փոխանցումը:" + "Սարքերը կրկին միմյանց հետ կցել" + "Այս պահին տվյալների փոխանցումը զբաղված է: Փորձեք կրկին նախորդ փոխանցումը ավարտելուց հետո:" + "սարք" + "%1$s-ի կապակցում" + "%1$s սարքը կապակցված է" + "Չհաջողվեց կապակցել %1$s սարքը" + "%1$s-ի կապախզում" + "%1$s սարքը կապախզված է" + "%1$s-ի զուգավորում" + "Չհաջողվեց զուգավորել %1$s սարքը" + "Չհաջողվեց միացնել Bluetooth-ը" + "Վստա՞հ եք, որ ցանկանում եք զուգավորել %1$s Bluetooth սարքը:" + "Այո" + "Ոչ" + "Կրկին հպեք՝ %1$s հավելվածով վճարելու համար" + "Կրկին հպեք՝ %1$s հավելվածով ավարտելու համար:" + "Այս գործարքը չի կարող ավարտվել %1$s հավելվածով:" + "Չկարողացանք օգտագործել %1$s հավելվածը:" + "Վճարել" + "Ավարտել" + "Հպելու և վճարելու համար ձեր նախընտրած ծառայությունը հեռացվել է: Ընտրե՞լ մեկ ուրիշը:" + "Ավարտելու համար հպեք մեկ այլ սարքի էկրանին" + "Միանալ" + "Չհաջողվեց միանալ ցանցին" + "Միացված է" + "Միանալ ցանցին" + "Միանա՞լ %1$s ցանցին:" + "Որպեսզի Android Beam-ն աշխատի, անհրաժեշտ է միացնել NFC-ն: Ցանկանո՞ւմ եք միացնել այն:" + "Android Beam" + diff --git a/NfcSony/res/values-in/provisioning.xml b/NfcSony/res/values-in/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-in/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-in/strings.xml b/NfcSony/res/values-in/strings.xml new file mode 100644 index 0000000..f1cf98c --- /dev/null +++ b/NfcSony/res/values-in/strings.xml @@ -0,0 +1,49 @@ + + + "Layanan Nfc" + "Nfc" + "Kontak yang diterima lewat NFC" + "Sentuh untuk menambahkan orang ini sebagai kontak." + "Interaksi NFC lengkap" + "Sentuh untuk memberikan orang ini info kontak Anda." + "NFC diaktifkan." + "Sentuh untuk menyorot" + "Pancaran yang masuk..." + "Memancarkan..." + "Beam selesai" + "Beam tidak lengkap" + "Beam dibatalkan" + "Batal" + "Sentuh untuk melihat" + "Perangkat penerima tidak mendukung transfer file besar melalui beam." + "Kumpulkan lagi perangkatnya" + "Beam sedang sibuk. Coba lagi jika transfer sebelumnya telah selesai." + "perangkat" + "Menghubungkan %1$s" + "%1$s terhubung" + "Tidak dapat menghubungkan %1$s" + "Memutuskan hubungan dengan %1$s" + "Hubungan dengan %1$s terputus" + "Menyandingkan %1$s" + "Tidak dapat menyandingkan %1$s" + "Tidak dapat mengaktifkan Bluetooth" + "Yakin ingin menyandingkan perangkat Bluetooth %1$s?" + "Ya" + "Tidak" + "Ketuk lagi untuk membayar dengan %1$s" + "Ketuk lagi untuk menyelesaikan dengan %1$s" + "Transaksi ini tidak dapat diselesaikan dengan %1$s." + "Tidak dapat menggunakan %1$s." + "Bayar dengan" + "Selesaikan dengan" + "Layanan pilihan Anda untuk ketuk & bayar telah dihapus. Pilih yang lain?" + "Ketuk perangkat lain untuk menyelesaikan" + "Sambungkan" + "Tidak dapat menyambung ke jaringan" + "Tersambung" + "Sambungkan ke jaringan" + "Sambungkan ke jaringan %1$s?" + "Android Beam memerlukan NFC untuk diaktifkan. Ingin mengaktifkannya?" + "Android Beam" + diff --git a/NfcSony/res/values-is-rIS/provisioning.xml b/NfcSony/res/values-is-rIS/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-is-rIS/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-is-rIS/strings.xml b/NfcSony/res/values-is-rIS/strings.xml new file mode 100644 index 0000000..96eb6d7 --- /dev/null +++ b/NfcSony/res/values-is-rIS/strings.xml @@ -0,0 +1,49 @@ + + + "NFC-þjónusta" + "NFC" + "Tengiliður móttekinn um NFC" + "Snertu til að bæta þessum aðila við sem tengilið." + "NFC-samskiptum lokið" + "Snertu til að senda þessum aðila samskiptaupplýsingarnar þínar." + "Kveikt á NFC." + "Snertu til að senda" + "Sending berst..." + "Sendir..." + "Sendingu lokið" + "Sendingu lauk ekki" + "Hætt við sendingu" + "Hætta við" + "Snertu til að skoða" + "Tæki viðtakandans styður ekki flutning stórra skráa með NFC." + "Settu tækin hvort upp að öðru aftur" + "Beam er upptekið. Reyndu aftur þegar fyrri flutningi er lokið." + "tæki" + "Tengist %1$s" + "Tengt við %1$s" + "Ekki var hægt að tengjast %1$s" + "Aftengist frá %1$s" + "Aftengt frá %1$s" + "Parast við %1$s" + "Ekki var hægt að parast við %1$s" + "Ekki var hægt að kveikja á Bluetooth" + "Ertu viss um að þú viljir para Bluetooth-tækið %1$s?" + "Já" + "Nei" + "Snertu aftur til að greiða með %1$s" + "Snertu aftur til að ljúka með %1$s" + "Ekki var hægt að ljúka þessari greiðslu með %1$s." + "Ekki var hægt að nota %1$s." + "Greiða með" + "Ljúka með" + "Valin þjónusta fyrir snertigreiðslur var fjarlægð. Velja aðra?" + "Snertu annað tæki til að ljúka" + "Tengjast" + "Ekki var hægt að tengjast neti" + "Tengt" + "Tengjast neti" + "Tengjast netinu %1$s?" + "Android Beam krefst þess að kveikt sé á NFC. Viltu kveikja á því?" + "Android Beam" + diff --git a/NfcSony/res/values-it/provisioning.xml b/NfcSony/res/values-it/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-it/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-it/strings.xml b/NfcSony/res/values-it/strings.xml new file mode 100644 index 0000000..161cfa6 --- /dev/null +++ b/NfcSony/res/values-it/strings.xml @@ -0,0 +1,49 @@ + + + "Servizio NFC" + "NFC" + "Contatto ricevuto via NFC" + "Tocca per aggiungere questa persona come contatto." + "Interazione NFC completa" + "Tocca per inviare a questa persona le informazioni di contatto" + "Comunicazione NFC abilitata." + "Tocca per trasmettere" + "Trasmissione in arrivo..." + "Trasmissione..." + "Trasmissione completata" + "Trasmissione non completata" + "Trasmissione annullata" + "Annulla" + "Tocca per visualizzare" + "Il dispositivo del destinatario non supporta il trasferimento di file di grandi dimensioni tramite Android Beam." + "Avvicina di nuovo i dispositivi" + "Beam è occupato al momento. Riprova al termine del trasferimento precedente." + "dispositivo" + "Collegamento di %1$s" + "%1$s collegato" + "Impossibile collegare %1$s" + "Scollegamento di %1$s" + "%1$s scollegato" + "Accoppiamento di %1$s" + "Impossibile accoppiare %1$s" + "Impossibile attivare il Bluetooth" + "Vuoi accoppiare il dispositivo Bluetooth %1$s?" + "Sì" + "No" + "Tocca ancora per pagare con %1$s" + "Tocca ancora per completare con %1$s" + "Impossibile completare la transazione con %1$s." + "Impossibile utilizzare %1$s." + "Paga con" + "Completa con" + "Il tuo servizio preferito per Touch & Pay è stato rimosso. Vuoi sceglierne un altro?" + "Per completare, tocca un altro dispositivo" + "Connessione" + "Impossibile connettersi alla rete" + "Collegamento avvenuto" + "Connetti a rete" + "Connettere alla rete %1$s?" + "L\'attivazione della funzione Android Beam richiede la tecnologia NFC. Vuoi attivarla?" + "Android Beam" + diff --git a/NfcSony/res/values-iw/provisioning.xml b/NfcSony/res/values-iw/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-iw/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-iw/strings.xml b/NfcSony/res/values-iw/strings.xml new file mode 100644 index 0000000..936d1b0 --- /dev/null +++ b/NfcSony/res/values-iw/strings.xml @@ -0,0 +1,49 @@ + + + "‏שירות Nfc" + "Nfc" + "‏איש הקשר התקבל באמצעות NFC" + "גע כדי להוסיף משתמש זה כאיש קשר" + "‏האינטראקציה עם NFC הושלמה" + "גע כדי לתת למשתמש זה את פרטי הקשר שלך" + "‏NFC מופעל." + "גע כדי להקרין" + "קרן נכנסת..." + "מעביר תוכן..." + "הקרנה הושלמה" + "הקרן לא הושלמה" + "הקרן בוטלה" + "ביטול" + "גע כדי להציג" + "המכשיר המקבל אינו תומך בהעברת קובץ גדול באמצעות קרן." + "קרב שוב את המכשירים" + "‏Beam אינו פנוי כעת. נסה שוב לאחר השלמת ההעברה הקודמת." + "מכשיר" + "מתחבר אל %1$s" + "מחובר אל %1$s" + "לא ניתן להתחבר אל %1$s" + "מתנתק מ-%1$s" + "מנותק מ-%1$s" + "מבצע התאמה עם %1$s" + "לא ניתן לבצע התאמה עם %1$s" + "‏לא ניתן להפעיל Bluetooth" + "‏האם אתה בטוח שאתה רוצה להתאים את מכשיר ה-Bluetooth %1$s?" + "כן" + "לא" + "הקש שוב כדי לשלם באמצעות %1$s" + "הקש שוב כדי להשלים באמצעות %1$s" + "לא ניתן היה להשלים את העסקה הזו באמצעות %1$s." + "לא ניתן היה להשתמש ב-%1$s." + "שלם באמצעות" + "השלם באמצעות" + "השירות המועדף עליך עבור \'הקש ושלם\' הוסר. האם לבחור אחר?" + "הקש על מכשיר אחר להשלמה" + "התחבר" + "לא ניתן להתחבר לרשת" + "מחובר" + "התחבר לרשת" + "להתחבר לרשת %1$s?" + "‏כדי להשתמש ב-Android Beam נדרשת הפעלה של NFC. האם ברצונך להפעיל אותו?" + "Android Beam" + diff --git a/NfcSony/res/values-ja/provisioning.xml b/NfcSony/res/values-ja/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-ja/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-ja/strings.xml b/NfcSony/res/values-ja/strings.xml new file mode 100644 index 0000000..88d193a --- /dev/null +++ b/NfcSony/res/values-ja/strings.xml @@ -0,0 +1,49 @@ + + + "NFCサービス" + "NFC" + "NFC受信の連絡先" + "このユーザーを連絡先に追加するにはタッチします。" + "NFCの双方向処理が完了しました" + "このユーザーに連絡先情報を提供するにはタッチします。" + "NFCは有効です。" + "タップしてビーム" + "ビームを受信しています..." + "ビームしています..." + "ビーム完了" + "ビームが完了しませんでした" + "ビームをキャンセルしました" + "キャンセル" + "タップして表示" + "受信側の端末は、ビーム経由のサイズの大きなファイルの転送をサポートしていません。" + "端末同士をもう一度近づける" + "ビームは現在使用中です。前回の転送が完了したら、もう一度お試しください。" + "端末" + "%1$sを接続しています" + "%1$sを接続しました" + "%1$sを接続できませんでした" + "%1$sを切断しています" + "%1$sを切断しました" + "%1$sをペア設定しています" + "%1$sをペア設定できませんでした" + "Bluetoothを有効にできませんでした" + "Bluetoothデバイス%1$sをペアに設定してもよろしいですか?" + "はい" + "いいえ" + "%1$sで支払うにはもう一度タップしてください" + "%1$sで完了するにはもう一度タップしてください" + "この取引は%1$sで完了できませんでした。" + "%1$sを使用できませんでした。" + "お支払い:" + "完了:" + "「タップ&ペイ」用のサービスが削除されました。別のサービスを選択しますか?" + "完了するには別の端末をタップしてください" + "接続" + "ネットワークに接続できません" + "接続済み" + "ネットワークに接続" + "ネットワーク%1$sに接続しますか?" + "Androidビームを使用するにはNFCを有効にする必要があります。有効にしますか?" + "Androidビーム" + diff --git a/NfcSony/res/values-ka-rGE/provisioning.xml b/NfcSony/res/values-ka-rGE/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-ka-rGE/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-ka-rGE/strings.xml b/NfcSony/res/values-ka-rGE/strings.xml new file mode 100644 index 0000000..8b06c0d --- /dev/null +++ b/NfcSony/res/values-ka-rGE/strings.xml @@ -0,0 +1,49 @@ + + + "NFC სერვისი" + "NFC" + "NFC-თ მიღებული კონტაქტი" + "შეეხეთ, რათა დაამატოთ ეს პიროვნება კონტაქტად." + "NFC ინტერაქცია დასრულდა" + "შეეხეთ, რომ მიაწოდოთ ამ პიროვნებას თქვენი საკონტაქტო ინფორმაცია." + "NFC-გააქტიურებული." + "შეეხეთ სხივით გადასაცემად" + "შემომავალი სხივი..." + "სხივით გადაცემა..." + "სხივით გადაცემა დასრულდა" + "სხივით გადაცემა დასრულდა" + "სხივით გადაცემა გაუქმდა" + "გაუქმება" + "შეეხეთ სანახავად" + "მიმღების მოწყობილობას დიდი ფაილების სხივით გადაცემის მხარდაჭერა არ გააჩნია." + "ისევ მიაახლოვეთ მოწყობილობები ერთმანეთს" + "სხივი ამჟამად დაკავებულია. სცადეთ ხელახლა, როდესაც წინა გადაცემა დასრულდება." + "მოწყობილობა" + "უკავშირდება %1$s" + "დაკავშირებულია %1$s" + "ვერ ვუკავშირდებით %1$s" + "კავშირი წყდება %1$s" + "გათიშულია %1$s" + "დაწყვილება %1$s" + "ვერ წყვილდება %1$s" + "Bluetooth-ის გააქტიურება ვერ მოხერხდა" + "დარწმუნებული ხართ, რომ გსურთ დააწყვილოთ Bluetooth მოწყობილობა %1$s?" + "დიახ" + "არა" + "შეეხეთ ისევ, რათა გადაიხადოთ %1$s-ის გამოყენებით" + "შეეხეთ ისევ, რათა დაასრულოთ %1$s-ის გამოყენებით" + "ამ ტრანზაქციის დასრულება %1$s-ით ვერ მოხერხდა." + "%1$s-ის გამოყენება ვერ მოხერხდა." + "გადახდა აპით:" + "დასრულდეს აპით:" + "შეხებით გადახდის თქვენი რჩეული სერვისი წაიშალა. გსურთ, აირჩიოთ სხვა?" + "დასასრულებლად შეეხეთ მეორე მოწყობილობას" + "დაკავშირება" + "ქსელთან დაკავშირება ვერ ხერხდება" + "დაკავშირებულია" + "ქსელთან დაკავშირება" + "გსურთ %1$s ქსელთან დაკავშირება?" + "Android Beam საჭიროებს NFC ჩართვას. გსურთ მისი ჩართვა?" + "Android სხივი" + diff --git a/NfcSony/res/values-kk-rKZ/provisioning.xml b/NfcSony/res/values-kk-rKZ/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-kk-rKZ/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-kk-rKZ/strings.xml b/NfcSony/res/values-kk-rKZ/strings.xml new file mode 100644 index 0000000..e09ae03 --- /dev/null +++ b/NfcSony/res/values-kk-rKZ/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc қызметі" + "Nfc" + "Контакт NFC арқылы алынды." + "Бұл адамды контакт ретінде қосу үшін оны түртіңіз." + "NFC әрекеттесу аяқталды" + "Бұл адамға байланыс деректеріңізді беру үшін оны түртіңіз." + "NFC (жақын өріс байланысы) қосулы." + "Хабар тарату үшін түртіңіз" + "Келген сигналдар..." + "Хабар таратылуда..." + "Бим (хабар тарату) аяқталды" + "Бим (хабар тарату) аяқталмады" + "Бим (хабар тарату) тоқтатылды" + "Бас тарту" + "Көру үшін түрту" + "Хабар алушының құрылғысы бим арқылы үлкен файл жіберуді қолдамайды." + "Құрылғыларды қайта біріктіру" + "Тарату қазіргі уақытта бос емес. Алдыңғы тасымалдау аяқталғанда әрекетті қайталаңыз." + "құрылғы" + "Connecting %1$s" + "Connected %1$s" + "Could not connect %1$s" + "Disconnecting %1$s" + "Disconnected %1$s" + "Pairing %1$s" + "Could not pair %1$s" + "Bluetooth байланысын қосу мүмкін болмады." + "Bluetooth құрылғысын %1$s құрылғысымен жұптауды қалайсыз ба?" + "Иә" + "Жоқ" + "%1$s арқылы төлеу үшін қайта түртіңіз" + "%1$s арқылы аяқтау үшін қайта түртіңіз" + "Бұл транзакцияны %1$s арқылы аяқтау мүмкін емес." + "%1$s қолдану мүмкін болмады." + "Төлеу" + "Аяқтау" + "Түртіп төлеу үшін таңдаған қызметіңіз алынып тасталған. Басқасын таңдайсыз ба?" + "Аяқтау үшін басқа құрылғыны түртіңіз" + "Қосылу" + "Желіге қосылу мүмкін болмады" + "Қосылды" + "Желіге қосылу" + "%1$s желісіне қосылу керек пе?" + "Android Beam NFC функциясының қосылған болуын қажет етеді. Оны қосу керек пе?" + "Android Beam" + diff --git a/NfcSony/res/values-km-rKH/provisioning.xml b/NfcSony/res/values-km-rKH/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-km-rKH/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-km-rKH/strings.xml b/NfcSony/res/values-km-rKH/strings.xml new file mode 100644 index 0000000..f6d4625 --- /dev/null +++ b/NfcSony/res/values-km-rKH/strings.xml @@ -0,0 +1,49 @@ + + + "សេវា NFC" + "Nfc" + "ទំនាក់ទំនង​ដែល​បាន​ទទួល​តាម NFC" + "ប៉ះ​ដើម្បី​បន្ថែម​មនុស្ស​នេះ​ជា​ទំនាក់ទំនង។" + "អន្តរកម្ម NFC ពេញលេញ" + "ប៉ះ​ដើម្បី​ផ្ដល់​​ព័ត៌មាន​ទំនាក់ទំនង​របស់​អ្នក​ទៅ​មនុស្ស​នេះ។" + "បាន​បើក NFC ។" + "ប៉ះ​​ដើម្បី​ផ្ទេរ" + "ការ​ផ្ទេរ​ចូល..." + "កំពុង​ផ្ទេរ..." + "បញ្ចប់​ការ​ផ្ទេរ" + "ការ​ផ្ទេរ​មិន​បាន​បញ្ចប់" + "បាន​បោះបង់​ការ​ផ្ទេរ" + "បោះ​បង់​" + "ប៉ះ​ដើម្បី​មើល" + "ឧបករណ៍​របស់​អ្នក​ទទួល​មិន​គាំទ្រ​ការ​ផ្ទេរ​ឯកសារ​​ដែល​មាន​ទំហំ​ធំ​តាម​ការ​ផ្ទេរ​ឡើយ។" + "នាំ​ឧបករណ៍​ជាមួយគ្នា​ម្ដងទៀត" + "ប៊ីមកំពុងជាប់រវល់។ សាកល្បងម្តងទៀតនៅពេលដែលការផ្ទេរពីមុនបញ្ចប់។" + "ឧបករណ៍" + "កំពុងភ្ជាប់ %1$s" + "បានភ្ជាប់ %1$s" + "មិនអាចភ្ជាប់ %1$s បានទេ" + "កំពុងផ្តាច់ %1$s" + "បានផ្តាច់ %1$s" + "កំពុងភ្ជាប់ %1$s" + "មិនអាចភ្ជាប់ %1$s បានទេ" + "មិន​អាច​បើក​ប៊្លូធូស" + "តើ​អ្នក​ប្រាកដ​ជា​ចង់​ផ្គូផ្គង​ឧបករណ៍​ប៊្លូធូស %1$s ?" + "បាទ/ចាស" + "ទេ" + "ប៉ះ​ម្ដងទៀត ដើម្បី​បង់ប្រាក់​ជាមួយ %1$s" + "ប៉ះ​ម្ដងទៀត ដើម្បី​បញ្ចប់​ជាមួយ %1$s" + "ប្រតិបត្តិ​ការ​នេះ​មិន​អាច​ត្រូវ​បាន​បញ្ចប់​ជាមួយ %1$s ។" + "មិន​អាច​ប្រើ %1$s ។" + "បង់ប្រាក់​ជាមួយ" + "បំពេញ​ជាមួយ" + "សេវាកម្ម​ដែល​អ្នក​ពេញ​ចិត្ត​សម្រាប់​ប៉ះ & បង់​ប្រាក់​ត្រូវ​បាន​យក​ចេញ។ ជ្រើស​សេវាកម្ម​ផ្សេង?" + "ប៉ះ​ឧបករណ៍​ផ្សេងទៀត​ដើម្បី​បញ្ចប់" + "ភ្ជាប់" + "មិន​អាច​ភ្ជាប់​បណ្ដាញ" + "បាន​ភ្ជាប់" + "​ភ្ជាប់​បណ្ដាញ" + "ភ្ជាប់​បណ្ដាញ %1$s?" + "Android Beam ទាមទារ​ឲ្យ​បើក NFC ។ តើ​អ្នក​ចង់​បើក​វា​ឬ?" + "Android Beam" + diff --git a/NfcSony/res/values-kn-rIN/provisioning.xml b/NfcSony/res/values-kn-rIN/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-kn-rIN/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-kn-rIN/strings.xml b/NfcSony/res/values-kn-rIN/strings.xml new file mode 100644 index 0000000..9dfe6d3 --- /dev/null +++ b/NfcSony/res/values-kn-rIN/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc ಸೇವೆ" + "Nfc" + "NFC ಮೂಲಕ ಸಂಪರ್ಕವನ್ನು ಸ್ವೀಕರಿಸಲಾಗಿದೆ" + "ಈ ವ್ಯಕ್ತಿಯನ್ನು ಸಂಪರ್ಕದ ರೂಪದಲ್ಲಿ ಸೇರಿಸಿಕೊಳ್ಳಲು ಸ್ಪರ್ಶಿಸಿ." + "NFC ಪರಸ್ಪರ ಸಂವಹನ ಪೂರ್ಣಗೊಂಡಿದೆ" + "ಈ ವ್ಯಕ್ತಿಗೆ ನಿಮ್ಮ ಸಂಪರ್ಕ ಮಾಹಿತಿಯನ್ನು ನೀಡಲು ಸ್ಪರ್ಶಿಸಿ." + "NFC ಸಕ್ರಿಯಗೊಂಡಿದೆ." + "ಬೀಮ್ ಮಾಡಲು ಸ್ಪರ್ಶಿಸಿ" + "ಒಳಬರುತ್ತಿರುವ ಬೀಮ್..." + "ಬೀಮ್ ಮಾಡಲಾಗುತ್ತಿದೆ..." + "ಬೀಮ್ ಪೂರ್ಣಗೊಂಡಿದೆ" + "ಬೀಮ್ ಪೂರ್ಣಗೊಂಡಿಲ್ಲ" + "ಬೀಮ್ ರದ್ದುಗೊಂಡಿದೆ" + "ರದ್ದುಮಾಡು" + "ವೀಕ್ಷಿಸಲು ಸ್ಪರ್ಶಿಸಿ" + "ಸ್ವೀಕರಿಸುವವರ ಸಾಧನವು ಬೀಮ್ ಮೂಲಕ ಮಾಡಲಾಗುವ ವರ್ಗಾವಣೆಯನ್ನು ಬೆಂಬಲಿಸುವುದಿಲ್ಲ." + "ಸಾಧನಗಳನ್ನು ಮತ್ತೆ ಒಟ್ಟಿಗೆ ತನ್ನಿ" + "ಬೀಮ್ ಪ್ರಸ್ತುತ ಕಾರ್ಯನಿರತವಾಗಿದೆ. ಹಿಂದಿನ ವರ್ಗಾವಣೆ ಪೂರ್ಣಗೊಂಡ ಬಳಿಕ ಮತ್ತೊಮ್ಮೆ ಪ್ರಯತ್ನಿಸಿ." + "ಸಾಧನ" + "%1$s ಸಾಧನವನ್ನು ಸಂಪರ್ಕಿತಗೊಳಿಸಲಾಗುತ್ತಿದೆ" + "%1$s ಸಾಧನವನ್ನು ಸಂಪರ್ಕಿತಗೊಳಿಸಲಾಗಿದೆ" + "%1$s ಸಾಧನವನ್ನು ಸಂಪರ್ಕಿತಗೊಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ" + "%1$s ಸಾಧನವನ್ನು ಸಂಪರ್ಕ ಕಡಿತಗೊಳಿಸಲಾಗುತ್ತಿದೆ" + "%1$s ಸಾಧನವನ್ನು ಸಂಪರ್ಕಕಡಿತಗೊಳಿಸಲಾಗಿದೆ" + "%1$s ಸಾಧನವನ್ನು ಜೋಡಣೆ ಮಾಡಲಾಗುತ್ತಿದೆ" + "%1$s ಸಾಧನವನ್ನು ಜೋಡಣೆ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ" + "ಬ್ಲೂಟೂತ್ ಸಕ್ರಿಯಗೊಳಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ" + "ನೀವು %1$s ಬ್ಲೂಟೂತ್ ಸಾಧನವನ್ನು ಜೋಡಿಸಲು ಖಚಿತವಾಗಿ ಬಯಸುವಿರಾ?" + "ಹೌದು" + "ಇಲ್ಲ" + "%1$s ಮೂಲಕ ಪ್ಲೇ ಮಾಡಲು ಮತ್ತೆ ಟ್ಯಾಪ್ ಮಾಡಿ" + "%1$s ಮೂಲಕ ಪೂರ್ಣಗೊಳಿಸಲು ಮತ್ತೆ ಟ್ಯಾಪ್ ಮಾಡಿ" + "ಈ ವ್ಯವಹಾರವನ್ನು %1$s ಮೂಲಕ ಪೂರ್ಣಗೊಳಿಸಲು ಸಾಧ್ಯವಾಗುತ್ತಿಲ್ಲ." + "%1$s ಬಳಸಲು ಸಾಧ್ಯವಿಲ್ಲ." + "ಈ ಮೂಲಕ ಪಾವತಿಸಿ" + "ಇದರ ಮೂಲಕ ಪೂರ್ಣಗೊಳಿಸಿ" + "ನಿಮ್ಮ ಮೆಚ್ಚಿನ ಟ್ಯಾಪ್ & ಪಾವತಿ ಸೇವೆಯನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ. ಬೇರೊಂದು ಸೇವೆಯನ್ನು ಆರಿಸುವುದೇ?" + "ಪೂರ್ಣಗೊಳಿಸಲು ಇನ್ನೊಂದು ಸಾಧನವನ್ನು ಟ್ಯಾಪ್ ಮಾಡಿ" + "ಸಂಪರ್ಕಪಡಿಸು" + "ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಸಂಪರ್ಕಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ" + "ಸಂಪರ್ಕಗೊಂಡಿದೆ" + "ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಸಂಪರ್ಕಪಡಿಸಿ" + "%1$s ನೆಟ್‌ವರ್ಕ್‌ಗೆ ಸಂಪರ್ಕಿಸುವುದೇ?" + "Android Beam ಬಳಸಲು NFC ಸಕ್ರಿಯಗೊಂಡಿರಬೇಕು. ನೀವದನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಲು ಬಯಸುವಿರಾ?" + "Android Beam" + diff --git a/NfcSony/res/values-ko/provisioning.xml b/NfcSony/res/values-ko/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-ko/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-ko/strings.xml b/NfcSony/res/values-ko/strings.xml new file mode 100644 index 0000000..e495e30 --- /dev/null +++ b/NfcSony/res/values-ko/strings.xml @@ -0,0 +1,49 @@ + + + "NFC 서비스" + "NFC" + "NFC를 통해 받은 연락처" + "이 사람을 연락처에 추가하려면 터치하세요." + "NFC 통신 완료" + "이 사람에게 내 연락처 정보를 제공하려면 터치하세요." + "NFC 사용" + "공유하기" + "수신 빔..." + "공유 중..." + "Beam 완료" + "Beam이 완료되지 않음" + "Beam 취소됨" + "취소" + "터치하여 보기" + "수신자의 기기는 빔을 통한 대용량 파일 전송을 지원하지 않습니다." + "기기 다시 공유" + "현재 공유 작업을 진행 중입니다. 기존 전송 작업이 완료되면 다시 시도하세요." + "기기" + "%1$s에 연결하는 중" + "%1$s에 연결됨" + "%1$s에 연결할 수 없음" + "%1$s와(과) 연결 해제하는 중" + "%1$s와(과) 연결 해제됨" + "%1$s와(과) 페어링하는 중" + "%1$s와(과) 페어링할 수 없음" + "블루투스를 사용하도록 설정하지 못함" + "블루투스 기기 %1$s을(를) 페어링하시겠습니까?" + "예" + "아니요" + "%1$s(으)로 결제하려면 다시 탭하세요." + "%1$s(으)로 결제를 완료하려면 다시 탭하세요." + "%1$s(으)로 거래를 완료할 수 없습니다." + "%1$s을(를) 사용할 수 없습니다." + "결제 가능한 애플리케이션" + "사용 가능한 애플리케이션" + "탭앤페이에 사용하는 기본 서비스가 삭제되었습니다. 다른 앱을 선택하시겠습니까?" + "다른 기기를 탭하여 완료" + "연결" + "네트워크에 연결할 수 없음" + "연결됨" + "네트워크 연결" + "네트워크 %1$s에 연결하시겠습니까?" + "Android Beam을 사용하려면 NFC를 사용 설정해야 합니다. NFC를 사용 설정하시겠습니까?" + "Android Beam" + diff --git a/NfcSony/res/values-ku/strings.xml b/NfcSony/res/values-ku/strings.xml new file mode 100644 index 0000000..f456f5f --- /dev/null +++ b/NfcSony/res/values-ku/strings.xml @@ -0,0 +1,68 @@ + + + + Nfc خزمەتگوزاری + Nfc + + ناوەکە وەرگیرا لەڕیی NFC + + دەست لێدە بۆ زیاد کردن وەکو پەیوەندی + + کالێکی NFC تەواو بو + + دەست لێدە بۆ پێدانی زانیاری پەیوەندی خۆت بەم کەسە + + NFC چالاک کرا + دەست لیدە بۆ گواستنەوە + پەیوەندی دەیەوێ بێت + ناردن + ناردن تەواو بو + ناردن تەواو نەبو + ناردن دواخرا + هەڵوەشاندن + تاپ بکه‌ بۆ بینین + ئامێری وه‌رگر توانای گونجاندنی په‌ڕگه‌ی گه‌وره‌ی نییه‌. + ناساندنی دوو ئامێره‌که‌ به‌یه‌که‌وه‌ دووباره‌ + + + + + + + + + + نه‌توانرا بلوتوس چالاک بکرێت + + دڵنیای له‌ ناساندنی ئامێری بلوتوسی %1$s؟ + بەڵێ + نا + + تاپ بکه‌ دووباره‌ بۆ ناساندن له‌گه‌ڵ %1$s + + تاپ بکه‌ دووباره‌ بۆ ته‌واوکردن له‌گه‌ڵ %1$s + + گواستنه‌وه‌ نه‌توانرا ته‌واوبکرێت له‌گه‌ڵ %1$s. + + نه‌توانرا به‌کاربهێنرێ %1$s + + ناساندن له‌گه‌ڵ + + ته‌واوکردن له‌گه‌ڵ + + تۆ خزمه‌تگوزاری په‌سه‌ند ده‌که‌یت بۆ تاپ وamp; ناساندن سڕدرایه‌وه‌. یه‌کێکی تر؟ + + + + + + + + + + diff --git a/NfcSony/res/values-ky-rKG/provisioning.xml b/NfcSony/res/values-ky-rKG/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-ky-rKG/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-ky-rKG/strings.xml b/NfcSony/res/values-ky-rKG/strings.xml new file mode 100644 index 0000000..bb14bcf --- /dev/null +++ b/NfcSony/res/values-ky-rKG/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc Кызматы" + "NFC" + "NFC аркылуу алынган байланыш" + "Бул кишини байланыш катары кошуш үчүн, тийиңиз." + "NFC алмашуу аяктады" + "Бул кишиге байланыш маалыматтарыңызды бериш үчүн тийиңиз." + "NFC жандырылган." + "beam кылыш үчүн тийиңиз" + "Кириш beam..." + "Beam өткөрүүдө..." + "Beam аяктады" + "Beam аягына чыккан жок" + "Beam жокко чыгарылды" + "Жокко чыгаруу" + "Көрүш үчүн тийиңиз" + "Алуучунун түзмөгү beam аркылуу чоң файлдарды кабылдабайт." + "Түзмөктөрдү кайрадан бириктириңиз" + "Учурда өткөрүү бош эмес. Мурунку өткөрүү аяктаганда кайра аракет кылыңыз." + "түзмөк" + "%1$s туташтырылууда" + "%1$s туташтырылды" + "%1$s менен туташа алган жок" + "%1$s ажыратылууда" + "%1$s ажыратылды" + "%1$s жупташтырылууда" + "%1$s жупташтырылбай койду" + "Bluetooth жандырылган жок" + "%1$s Bluetooth түзмөгү менен жупташтырууну каалаганыңыз аныкпы?" + "Ооба" + "Жок" + "%1$s менен төлөө үчүн, кайра тийиңиз" + "%1$s менен аягына чыгарыш үчүн, тийиңиз" + "Бул транзакцияны %1$s менен аягына чыгарыш мүмкүн эмес." + "%1$s колдоно албайт." + "менен төлөө" + "менен бүтүрүү" + "Сиздин тандаган басып төлөө кызматыңыз жоюлду. Башкасын тандайлыбы?" + "Аягына чыгарыш үчүн, башка түзмөккө тийиңиз" + "Туташуу" + "Тармакка туташуу мүмкүн болбой жатат" + "Туташып турат" + "Тармакка туташуу" + "%1$s тармагына туташасызбы?" + "Android Beam NFC иштетилишин талап кылат. Аны иштетесизби?" + "Android Beam" + diff --git a/NfcSony/res/values-lb/strings.xml b/NfcSony/res/values-lb/strings.xml new file mode 100644 index 0000000..d9c2e33 --- /dev/null +++ b/NfcSony/res/values-lb/strings.xml @@ -0,0 +1,85 @@ + + + + NFC-Service + NFC + + Kontakt iwwer NFC empfaangen + + Dréck fir dës Persoun als Kontakt anzedroen. + + NFC-Interaktioun ofgeschloss + + Drécke fir dëser Persoun deng Kontakt-Informatiounen ze ginn. + + NFC aktivéiert. + Dréck fir ze beamen + Beam gëtt empfaangen... + Beamen... + Beam ofgeschloss + Beam net ofgeschloss + Beam ofgebrach + Ofbriechen + Dréck fir unzekucken + Den Apparat vum Empfänger ënnerstëtzt keen Transfert vu grousse Fichieren iwwer Beam. + Bréng d\'Apparater nees beieneen + Beam ass momentan beschäftegt. Probéier nees wann déi vireg Iwwerdroung fäerdeg ass. + + Apparat + + %1$s gëtt verbonnen + + %1$s verbonnen + + Konnt net mat %1$s verbannen + + %1$s gëtt getrennt + + %1$s getrennt + + %1$s gëtt gekoppelt + + Konnt net mat %1$s koppelen + + Bluetooth konnt net aktivéiert ginn + + Bass du sécher datt s du de Bluetooth-Apparat %1$s wëlls koppelen? + Jo + Nee + + Dréck nees fir mat %1$s ze bezuelen + + Dréck nees fir mat %1$s ofzeschléissen + + Dës Transaktioun konnt net mat %1$s ofgeschloss ginn. + + Konnt %1$s net benotzen. + + Bezuele mat + + Ofschléisse mat + + Däi preferréierte Service fir Drécken a Bezuelen gouf geläscht. Een aneren auswielen? + + Dréck op en aneren Apparat fir ofzeschléissen + + + Connectéieren + + Kann net mam Netzwierk connectéieren + + Connectéiert + + Mam Netzwierk connectéieren + + Mam Netzwierk %1$s connectéieren? + + Fir Android Beam ze benotze muss NFC aktivéiert sinn. Wëlls du NFC elo aktivéieren? + + Android Beam + diff --git a/NfcSony/res/values-lo-rLA/provisioning.xml b/NfcSony/res/values-lo-rLA/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-lo-rLA/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-lo-rLA/strings.xml b/NfcSony/res/values-lo-rLA/strings.xml new file mode 100644 index 0000000..5746e9e --- /dev/null +++ b/NfcSony/res/values-lo-rLA/strings.xml @@ -0,0 +1,49 @@ + + + "ບໍລິການ NFC" + "NFC" + "ລາຍຊື່ຜູ່ຕິດຕໍ່ທີ່ໄດ້ຮັບຜ່ານ NFC" + "ແຕະເພື່ອເພີ່ມບຸກຄົນນີ້ເຂົ້າໄປໃນລາຍຊື່ຜູ່ຕິດຕໍ່" + "ການປະຕິສຳພັນ NFC ສຳເລັດແລ້ວ" + "ແຕະເພື່ອເອົາຂໍ້ມູນການຕິດຕໍ່ຂອງທ່ານໃຫ້ກັບບຸກຄົນນີ້" + "NFC ເປີດແລ້ວ." + "ແຕະເພື່ອ Beam" + "ມີ beam ເຂົ້າມາ..." + "Beaming..." + "Beam ສຳເລັດແລ້ວ" + "Beam ບໍ່​ສຳເລັດ" + "Beam ຍົກເລີກແລ້ວ" + "ຍົກເລີກ" + "ແຕະ​ເພື່ອ​ເບິ່ງ" + "ອຸປະກອນຂອງຜູ່ຮັບບໍ່ສາມາດຮອງຮັບການໂອນໄຟລ໌ຂະຫນາດໃຫຍ່ໂດຍຜ່ານ beam ໄດ້." + "ນຳອຸປະກອນເຂົ້າມາຫາກັນໃໝ່ອີກຄັ້ງ" + "ປະ​ຈຸ​ບັນ Beam ບໍ່​ວ່າງ. ລອງ​ໃໝ່​ອີກ​ເມື່ອ​ການ​ໂອນທີ່​ຜ່ານ​ມາ​ສຳ​ເລັດແລ້ວ." + "ອຸປະກອນ" + "ກຳ​ລັງ​ເຊື່ອມ​ຕໍ່ %1$s" + "%1$s ທີ່​ເຊື່ອມ​ຕໍ່​ແລ້ວ" + "ບໍ່​ສາມ​າດ​ເຊື່ອ​ມ​ຕໍ່​ກັບ %1$s ໄດ້" + "ກຳ​ລັງ​ຕັດ​ເຊື່ອມ​ຕໍ່ %1$s" + "%1$s ທີ່​ຕັດ​ເຊື່ອມ​ຕໍ່​ແລ້ວ" + "ກຳ​ລັງ​ຈັບ​ຄູ່ %1$s" + "ບໍ່​ສາ​ມາດ​ຈັບ​ຄູ່ %1$s ໄດ້" + "ບໍ່ສາມາດເປີດນຳໃຊ້ Bluetooth ໄດ້" + "ທ່ານແນ່ໃຈບໍວ່າທ່ານຕ້ອງການຈັບຄູ່ອຸປະກອນ Bluetooth %1$s?" + "ຕົກລົງ" + "ບໍ່" + "ແຕະອີກຄັ້ງເພື່ອຈ່າຍໃຊ້ %1$s" + "ແຕະອີກຄັ້ງເພື່ອດຳເນີນການໂດຍໃຊ້ %1$s" + "ການດຳເນີນການນີ້ບໍ່ສາມາດເຮັດໃຫ້ແລ້ວໂດຍໃຊ້ %1$s." + "ບໍ່ສາມາດໃຊ້ %1$s ໄດ້." + "ຈ່າຍໂດຍໃຊ້" + "ດຳເນີນການໂດຍໃຊ້" + "ບໍລິການທີ່ທ່ານຕ້ອງການສຳລັບ ແຕະ​ & ຈ່າຍ ຖືກລຶບອອກແລ້ວ. ເລືອກອັນໃໝ່ບໍ?" + "ແຕະ​ກັບ​ອຸ​ປະ​ກອນ​ອື່ນ​ເພື່ອ​ສຳ​ເລັດ​ຂັ້ນ​ຕອນ" + "ເຊື່ອມຕໍ່" + "ບໍ່​ສາ​ມາດ​ເຊື່ອມ​ຕໍ່ກັບ​ເຄືອຂ່າຍ​ໄດ້" + "ເຊື່ອມຕໍ່ແລ້ວ" + "ເຊື່ອມຕໍ່ກັບເຄືອຂ່າຍ" + "​ເຊື່ອມ​ຕໍ່​ກັບ​ເຄືອ​ຂ່າຍ %1$s ບໍ?" + "Android Beam ຕ້ອງ​ໃຊ້ NFC. ທ່ານ​ຕ້ອງ​ການ​ເປີດ​ໃຊ້ NFC ບໍ່?" + "Android Beam" + diff --git a/NfcSony/res/values-lt/provisioning.xml b/NfcSony/res/values-lt/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-lt/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-lt/strings.xml b/NfcSony/res/values-lt/strings.xml new file mode 100644 index 0000000..a00419c --- /dev/null +++ b/NfcSony/res/values-lt/strings.xml @@ -0,0 +1,49 @@ + + + "ALR paslauga" + "ALR" + "Gauta per ALR" + "Palieskite, kad pridėtumėte šį asmenį kaip kontaktą." + "NFC sąveika baigta" + "Palieskite, kad šiam asmeniui suteiktumėte savo kontakt. inf." + "ALR įgalinta." + "Jei norite perduoti, palieskite" + "Gaunamas perdavimas..." + "Perduodama..." + "Perdavimas baigtas" + "Perdavimas nebuvo užbaigtas" + "Perdavimas atšauktas" + "Atšaukti" + "Jei norite peržiūrėti, palieskite" + "Gavėjo įrenginyje nepalaikomas didelio failo perdavimas naudojant perdavimo funkciją." + "Vėl susiekite įrenginius" + "Perdavimo paslauga šiuo metu užimta. Bandykite dar kartą, kai bus baigtas ankstesnis perkėlimas." + "įrenginys" + "Jungiamasi prie „%1$s“" + "Prisijungta prie „%1$s“" + "Nepavyko prisijungti prie „%1$s“" + "Atsijungiama nuo „%1$s“" + "Atsijungta nuo „%1$s“" + "Susiejama su „%1$s“" + "Nepavyko susieti su „%1$s“" + "Nepavyko įgalinti „Bluetooth“" + "Ar tikrai norite susieti „Bluetooth“ įrenginį „%1$s“?" + "Taip" + "Ne" + "Palieskite dar kartą, kad mokėtumėte naudodami „%1$s“" + "Palieskite dar kartą, kad užbaigtumėte naudodami „%1$s“" + "Nepavyko užbaigti šios operacijos naudojant „%1$s“." + "Nepavyko naudoti „%1$s“." + "Mokėti naudojant" + "Užbaigti naudojant" + "Pašalinta paslauga, kurią pageidaujate naudoti pasirinkę funkciją „Paliesti ir mokėti“. Pasirinkti kitą paslaugą?" + "Norėdami baigti siųsti, palieskite kitą įrenginį" + "Prisijungti" + "Nepavyksta prisijungti prie tinklo" + "Prisijungta" + "Prisijungti prie tinklo" + "Prisijungti prie tinklo „%1$s“?" + "Norint naudoti „Android“ perdavimo funkciją, būtina įgalinti ALR. Ar norite įgalinti šią funkciją?" + "„Android“ perdavimas" + diff --git a/NfcSony/res/values-lv/provisioning.xml b/NfcSony/res/values-lv/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-lv/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-lv/strings.xml b/NfcSony/res/values-lv/strings.xml new file mode 100644 index 0000000..944757f --- /dev/null +++ b/NfcSony/res/values-lv/strings.xml @@ -0,0 +1,49 @@ + + + "NFC pakalpojums" + "NFC" + "Kontaktinformācija saņemta, izmantojot NFC." + "Pieskarieties, lai pievienotu šo personu kā kontaktpersonu." + "NFC mijiedarbība ir pabeigta." + "Pieskarieties, lai sniegtu šai personai savu kontaktinformāciju." + "NFC ir iespējoti." + "Pieskarties, lai projicētu" + "Ienākošie vienumi, izmantojot funkciju Beam..." + "Notiek kopīgošana, izmantojot funkciju Beam..." + "Kopīgošana, izmantojot funkciju Beam, ir pabeigta." + "Kopīgošana, izmantojot funkciju Beam, netika pabeigta" + "Kopīgošana, izmantojot funkciju Beam, atcelta" + "Atcelt" + "Pieskarieties, lai skatītu." + "Saņēmēja ierīce neatbalsta lielu failu pārsūtīšanu, izmantojot funkciju Beam." + "Vēlreiz novietojiet ierīces blakus." + "Funkcija Beam šobrīd ir noslogota. Mēģiniet vēlreiz, tiklīdz ir pabeigta iepriekšējā pārsūtīšana." + "ierīce" + "Notiek savienošana ar ierīci %1$s." + "Savienots ar ierīci %1$s." + "Nevarēja savienot ar ierīci %1$s." + "Notiek ierīces %1$s atvienošana." + "Ierīce %1$s tika atvienota." + "Notiek savienošana pārī ar ierīci %1$s." + "Nevarēja savienot pārī ar ierīci %1$s." + "Nevarēja iespējot Bluetooth" + "Vai tiešām vēlaties savienot pārī Bluetooth ierīci %1$s?" + "Jā" + "Nē" + "Pieskarieties vēlreiz, lai maksātu, izmantojot lietotni %1$s." + "Pieskarieties vēlreiz, lai pabeigtu, izmantojot lietotni %1$s." + "Šo darījumu nevarēja pabeigt, izmantojot lietotni %1$s." + "Nevarēja izmantot lietotni %1$s." + "Maksāt, izmantojot:" + "Pabeigt, izmantojot:" + "Pakalpojums, kuru pēc noklusējuma izmantojāt funkcijai “Pieskarties un maksāt”, tika noņemts. Vai izvēlēties citu pakalpojumu?" + "Pieskarieties citai ierīcei, lai pabeigtu" + "Izveidot savienojumu" + "Nevar izveidot savienojumu ar tīklu." + "Savienojums ir izveidots." + "Savienojuma izveide ar tīklu" + "Vai izveidot savienojumu ar tīklu %1$s?" + "Lai varētu iespējot funkciju Android Beam, ir vajadzīga tehnoloģija NFC. Vai vēlaties to iespējot?" + "Android Beam" + diff --git a/NfcSony/res/values-mk-rMK/provisioning.xml b/NfcSony/res/values-mk-rMK/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-mk-rMK/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-mk-rMK/strings.xml b/NfcSony/res/values-mk-rMK/strings.xml new file mode 100644 index 0000000..36c8b9b --- /dev/null +++ b/NfcSony/res/values-mk-rMK/strings.xml @@ -0,0 +1,49 @@ + + + "NFC услуга" + "NFC" + "Контакт примен преку NFC" + "Допри за да го додадеш ова лице како контакт." + "Интеракцијата со NFC е целосна" + "Допрете да ги дадете вашите информации за контакт на ова лице." + "NFC е овозможена." + "Допри за зрак" + "Дојдовен зрак..." + "Зрачење..." + "Зракот е целосен" + "Зракот не се заврши" + "Зракот е откажан" + "Откажи" + "Допри за да видиш" + "Уредот на примачот не поддржува пренос на големи датотеки преку зрак." + "Повторно спој ги уредите" + "Beam е моментално зафатен. Обидете се повторно кога ќе заврши претходниот пренос." + "уред" + "Се поврзува %1$s" + "%1$s e поврзан" + "Не можеше да се поврзе со %1$s" + "Се исклучува %1$s" + "%1$s е исклучен" + "Се спарува %1$s" + "Не можеше да се спари %1$s" + "Не може да се овозможи Bluetooth" + "Дали сте сигурни дека сакате да го спарите уредот со Bluetooth %1$s?" + "Да" + "Не" + "Допрете повторно за да платите со %1$s" + "Допрете повторно за да заврши со %1$s" + "Оваа трансакција не може да се заврши со %1$s." + "Не можеше да користи %1$s." + "Плати со" + "Заврши со" + "Претпочитаната услуга за допирање и плаќање е отстранета. Избери друга?" + "Допрете друг уред за да се заврши" + "Поврзи се" + "Не може да се поврзе со мрежата." + "Поврзано" + "Поврзи се на мрежа" + "Да се поврзе со мрежата %1$s?" + "Android Beam бара да се овозможи НФЦ. Дали сакате да го овозможите?" + "Android Beam" + diff --git a/NfcSony/res/values-ml-rIN/provisioning.xml b/NfcSony/res/values-ml-rIN/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-ml-rIN/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-ml-rIN/strings.xml b/NfcSony/res/values-ml-rIN/strings.xml new file mode 100644 index 0000000..6b9a83b --- /dev/null +++ b/NfcSony/res/values-ml-rIN/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc സേവനം" + "Nfc" + "NFC മുഖേന ലഭിച്ച കോൺടാക്റ്റ്" + "ഈ വ്യക്തിയെ ഒരു കോൺടാക്‌റ്റ് ആയി ചേർക്കുന്നതിന് സ്‌പർശിക്കുക." + "NFC ഇടപെടൽ പൂർത്തിയായി" + "നിങ്ങളുടെ കോൺടാക്റ്റ് വിവരം ഈ വ്യക്തിയ്ക്ക് നൽകുന്നതിന് സ്‌പർശിക്കുക." + "NFC പ്രവർത്തനക്ഷമമാക്കി." + "ബീം സ്‌പർശിക്കുക" + "ഇൻകമിംഗ് ബീം..." + "ബീമിംഗ്..." + "ബീം പൂർത്തിയായി" + "ബീം പൂർത്തിയായില്ല" + "ബീം റദ്ദാക്കി" + "റദ്ദാക്കുക" + "കാണാൻ സ്‌പർശിക്കുക" + "ബീം മുഖേന വലിയ ഫയൽ കൈമാറുന്നതിനെ സ്വീകർത്താവിന്റെ ഉപകരണം പിന്തുണയ്ക്കുന്നില്ല." + "ഉപകരണങ്ങൾ വീണ്ടും ഒരുമിച്ചുകൊണ്ടുവരുക" + "ബീം നിലവിൽ തിരക്കിലാണ്. മുമ്പുള്ള കൈമാറ്റം പൂർത്തിയാക്കുമ്പോൾ വീണ്ടും ശ്രമിക്കുക." + "ഉപകരണം" + "%1$s, കണക്‌റ്റുചെയ്യുന്നു" + "%1$s, കണക്‌റ്റുചെയ്‌തു" + "%1$s, കണക്‌റ്റുചെയ്യാനായില്ല" + "%1$s, വിച്‌ഛേദിക്കുന്നു" + "%1$s, വിച്‌ഛേദിച്ചു" + "%1$s, ജോടിയാക്കുന്നു" + "%1$s, ജോടിയാക്കാനായില്ല" + "ബ്ലൂടൂത്ത് പ്രവർത്തനക്ഷമമാക്കാനായില്ല" + "നിങ്ങൾക്ക് %1$s എന്ന ബ്ലൂടൂത്ത് ഉപകരണം ജോടിയാക്കണമെന്ന് തീർച്ചയാണോ?" + "വേണം" + "വേണ്ട" + "%1$s എന്നതുപയോഗിച്ച് പണമടയ്‌ക്കുന്നതിന് വീണ്ടും ടാപ്പുചെയ്യുക" + "%1$s എന്നതുപയോഗിച്ച് പൂർത്തിയാക്കുന്നതിന് വീണ്ടും ടാപ്പുചെയ്യുക" + "%1$s എന്നതുപയോഗിച്ച് ഈ ഇടപാട് പൂർത്തിയാക്കാനായില്ല." + "%1$s ഉപയോഗിക്കാനായില്ല." + "ഇതുപയോഗിച്ച് പണമടയ്‌ക്കുക" + "ഇതുപയോഗിച്ച് പൂർത്തിയാക്കുക" + "\'മൊബൈൽ തൊടീച്ച് പണമടയ്ക്കുക\' എന്നതിനായി നിങ്ങൾ തിരഞ്ഞെടുത്ത സേവനം നീക്കംചെയ്‌തു. മറ്റൊന്ന് തിരഞ്ഞെടുക്കണോ?" + "പൂർത്തിയാക്കുന്നതിന് മറ്റൊരു ഉപകരണം ടാപ്പുചെയ്യുക" + "കണക്റ്റുചെയ്യുക" + "നെറ്റ്‌വർക്കിലേക്ക് കണക്റ്റുചെയ്യാനായില്ല" + "കണക്റ്റുചെയ്‌തു" + "നെ‌റ്റ്‌വർക്കിൽ കണക്‌റ്റുചെയ്യുക" + "%1$s എന്ന നെറ്റ്‌വർക്കിലേക്ക് കണക്‌റ്റുചെയ്യണോ?" + "Android ബീമിന്, NFC പ്രവർത്തനക്ഷമമാക്കേണ്ടതുണ്ട്. നിങ്ങൾക്കത് പ്രവർത്തനക്ഷമമാക്കണോ?" + "Android ബീം" + diff --git a/NfcSony/res/values-mn-rMN/provisioning.xml b/NfcSony/res/values-mn-rMN/provisioning.xml new file mode 100644 index 0000000..7b21b72 --- /dev/null +++ b/NfcSony/res/values-mn-rMN/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "аппликэйшн/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-mn-rMN/strings.xml b/NfcSony/res/values-mn-rMN/strings.xml new file mode 100644 index 0000000..bb94efb --- /dev/null +++ b/NfcSony/res/values-mn-rMN/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc Үйлчилгээ" + "NFC" + "NFC-р хүлээн авсан харилцагч" + "Энэ хүнийг харилцагчаар нэмэхийн тулд хүрнэ үү." + "NFC интерактив харилцаа бүрэн" + "Энэ хүнд өөрийн харилцагчийн мэдээллийг өгөхийн тулд товшино уу" + "NFC идэвхжүүлсэн." + "Бийм явуулахын тулд хүрнэ" + "Орж ирж буй бийм..." + "Бийм цацруулж байна..." + "Бийм бүрэн" + "Бийм бүрдээгүй" + "Бийм цуцлагдсан" + "Цуцлах" + "Харахын тулд хүрнэ үү" + "Хүлээн авагч төхөөрөмж том файлыг бийм-ээр дамжуулахыг дэмждэггүй." + "Төхөөрөмжүүдийг дахин хамтад нь байлгах" + "Beam одоохондоо завгүй байна. Өмнөх шилжүүлэг дууссан үед дахин оролдоно уу." + "төхөөрөмж" + "%1$s-г холбож байна" + "%1$s-г холбосон байна" + "%1$s-г холбож чадсангүй" + "%1$s-н холбоог салгаж байна" + "%1$s-н холбоог салгасан байна" + "%1$s-г хослуулан холбож байна" + "%1$s-г хослуулан холбож чадсангүй" + "Блютүүтийг идэвхжүүлж чадсангүй" + "Та Блютүүт төхөөрөмж %1$s-тэй хослуулахдаа итгэлтэй байна уу?" + "Тийм" + "Үгүй" + "%1$s ашиглан төлбөр хийхийн тулд товшино уу" + "%1$s ашиглан гүйцээхийн тулд товшино уу" + "Энэ гүйлгээг %1$s ашиглан гүйцээх боломжгүй." + "%1$s ашиглаж чадсангүй." + "Төлбөр хийх" + "Гүйцээх" + "Товшиж төлөхөд зориулан сонгосон таны үйлчилгээ арилсан. Өөрийг сонгох уу?" + "Дуусгахын тулд өөр төхөөрөмжтэй мөргүүлнэ үү" + "Холбох" + "Сүлжээнд холбогдох боломжгүй" + "Холбогдсон" + "Сүлжээнд холбогдох" + "%1$s сүлжээнд холбогдох уу?" + "Android Beam-г идэвхжүүлэхэд NFC шаардлагатай. Үүнийг идэвхжүүлэх үү?" + "Андройд Цацраг" + diff --git a/NfcSony/res/values-mr-rIN/provisioning.xml b/NfcSony/res/values-mr-rIN/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-mr-rIN/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-mr-rIN/strings.xml b/NfcSony/res/values-mr-rIN/strings.xml new file mode 100644 index 0000000..0863e32 --- /dev/null +++ b/NfcSony/res/values-mr-rIN/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc सेवा" + "Nfc" + "NFC वर संपर्क प्राप्त केला" + "संपर्क म्हणून या व्यक्तीस जोडण्यासाठी स्पर्श करा." + "NFC परस्पर संवाद पूर्ण" + "आपली संपर्क माहिती या व्यक्तीस देण्यासाठी स्पर्श करा." + "NFC सक्षम." + "बीम ला स्पर्श करा" + "येणारे बीम..." + "बीम करत आहे..." + "बीम पूर्ण" + "बीम पूर्ण केले नाही" + "बीम रद्द" + "रद्द करा" + "पाहण्यासाठी स्पर्श करा" + "प्राप्तकर्त्याचे डिव्हाइस बीमद्वारे मोठी फाईल स्थानांतरीत करण्यास समर्थन देत नाही." + "डिव्हाइसेस पुन्हा एकत्र आणा" + "बीम करणे सध्या व्यस्त आहे. मागील स्थानांतरीत करणे पूर्ण झाल्यानंतर पुन्हा प्रयत्न करा." + "डिव्हाइस" + "%1$s कनेक्‍ट करीत आहे" + "%1$s कनेक्‍ट केले" + "%1$s कनेक्‍ट करणे शक्य झाले नाही" + "%1$s डिस्‍कनेक्‍ट करीत आहे" + "%1$s डिस्‍कनेक्‍ट केले" + "%1$s जोडणी करीत आहे" + "%1$s जोडणे शक्य झाले नाही" + "ब्लूटुथ सक्षम करू शकलो नाही" + "आपण %1$s ब्लूटुथ डिव्हाइस जोडू इच्छिता याची आपल्याला खात्री आहे?" + "होय" + "नाही" + "%1$s सह देय देण्यासाठी पुन्हा टॅप करा" + "%1$s सह पूर्ण करण्यासाठी पुन्हा टॅप करा" + "हा व्यवहार %1$s सह पूर्ण होऊ शकला नाही." + "%1$s वापरू शकलो नाही." + "यासह देय द्या" + "यासह पूर्ण करा" + "टॅप करा आणि देय द्या साठीची आपली प्राधान्यकृत सेवा काढली. दुसरी निवडायची?" + "पूर्ण करण्यासाठी दुसरा डिव्हाइस टॅप करा" + "कनेक्ट करा" + "नेटवर्कशी कनेक्‍ट करण्‍यात अक्षम" + "कनेक्ट केले" + "नेटवर्कवर कनेक्ट करा" + "%1$s नेटवर्कशी कनेक्ट करायचे?" + "Android Beam ला सक्षम करण्यासाठी NFC आवश्यक आहे. आपण हे सक्षम करू इच्छिता?" + "Android बीम" + diff --git a/NfcSony/res/values-ms-rMY/provisioning.xml b/NfcSony/res/values-ms-rMY/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-ms-rMY/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-ms-rMY/strings.xml b/NfcSony/res/values-ms-rMY/strings.xml new file mode 100644 index 0000000..caed039 --- /dev/null +++ b/NfcSony/res/values-ms-rMY/strings.xml @@ -0,0 +1,49 @@ + + + "Perkhidmatan Nfc" + "Nfc" + "Kenalan diterima melalui NFC" + "Sentuh untuk menambah orang ini sebagai kenalan." + "Interaksi NFC lengkap" + "Sentuh untuk memberi orang ini maklumat hubungan anda." + "NFC didayakan." + "Sentuh untuk memancarkan" + "Pancar sedang masuk..." + "Memancar..." + "Pancar selesai" + "Pancar tidak lengkap" + "Pancar dibatalkan" + "Batal" + "Sentuh untuk melihat" + "Peranti penerima tidak menyokong pemindahan fail besar melalui pancaran." + "Dekatkan peranti semula" + "Pancaran sibuk pada masa ini. Cuba lagi apabila pemindahan sebelumnya selesai." + "peranti" + "Menyambungkan %1$s" + "%1$s disambungkan" + "Tidak dapat menyambungkan %1$s" + "Memutuskan sambungan %1$s" + "%1$s diputuskan sambungan" + "Memasangkan %1$s" + "Tidak dapat memasangkan %1$s" + "Tidak dapat mendayakan Bluetooth" + "Adakah anda pasti anda mahu memadankan peranti Bluetooth %1$s?" + "Ya" + "Tidak" + "Ketik lagi untuk membayar dengan %1$s" + "Ketik lagi untuk melengkapkan dengan %1$s" + "Transaksi ini tidak dapat dilengkapkan dengan %1$s." + "Tidak dapat menggunakan %1$s." + "Bayar dengan" + "Lengkapkan dengan" + "Perkhidmatan pilihan anda untuk ketik & bayar telah dialih keluar. Pilih yang lain?" + "Ketik peranti lain untuk menyelesaikan" + "Sambung" + "Tidak dapat menyambung ke rangkaian" + "Disambungkan" + "Sambung kepada rangkaian" + "Sambung ke rangkaian %1$s?" + "NFC perlu didayakan untuk menggunakan Pancaran Android. Adakah anda ingin dayakan?" + "Pancaran Android" + diff --git a/NfcSony/res/values-my-rMM/provisioning.xml b/NfcSony/res/values-my-rMM/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-my-rMM/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-my-rMM/strings.xml b/NfcSony/res/values-my-rMM/strings.xml new file mode 100644 index 0000000..6908d83 --- /dev/null +++ b/NfcSony/res/values-my-rMM/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc ဆားဗစ်" + "NFC" + "NFCမှရသောလိပ်စာ" + "ဤပုဂ္ဂိုလ်အား လိပ်စာတွင်ထည့်ရန် တို့ထိပါ" + "NFC ကြားတုံ့ပြန်မှု ပြီးဆုံးပါပြီ" + "ဤပုဂ္ဂိုလ်အား သင့်လိပ်စာပေးရန် တို့ထိပါ" + "NFC ရရှိသည်" + "လှိုင်းလွှင့်ရန်အတွက် ထိပါ" + "အဝင် လှိုင်းလွှင့်ခြင်း..." + "လှိုင်းလွှင့်နေစဥ်..." + "လှိုင်းလွှင့်မှု ပြီးဆုံးပါပြီ" + "လှိုင်းလွှင့်မှု မပြီးဆုံးပါ" + "လှိုင်းလွှင့်မှု ပယ်ဖျက်မည်" + "ထားတော့" + "ကြည့်ရန် တို့ထိပါ" + "လက်ခံစက်သည် လှိုင်းလွှင့်ခြင်းမှတစ်ဆင့် ကြီးမားသောဖိုင်လွဲခြင်းများကို မထောက်ပံ့ပါ" + "ထပ်ပြီး စက်ကို အတူထားပါ" + "Beam လောလောဆယ် အလုပ်များနေ၏။ ယခင် ကူးပြောင်းမှုပြီးမှ ထပ်မံကြိုးစားပါ။" + "ကိရိယာ" + "%1$s အားချိတ်ဆက်နေ၏" + "%1$s အား ချိတ်ဆက်ပြီး" + "%1$s အား မချိတ်ဆက်နိုင်ပါ" + "%1$sအားချိတ်ဆက်မှုဖြတ်တောက်နေ၏" + "%1$s နှင့်မချိတ်ဆက်ထားပါ" + "%1$sနှင့်တွဲချိတ်နေ၏" + "%1$sနှင့်မတွဲချိတ်နိုင်ပါ" + "ဘလူးတုသ်ကို မရရှိနိုင်ပါ" + "ဘလူးတုသ်စက်%1$sနှင့် တွဲချိတ်ရန် ကျိန်းသေပါသလား?" + "မှန်ပါသည်" + "ဟင့်အင်း" + "%1$sနှင့် ပေးချေရန် နောက်တစ်ခါ ခေါက်ထိပါ" + "%1$sနှင့် ပြီးဆုံးရန် နောက်တစ်ခါ ခေါက်ထိပါ" + "%1$sနှင့် လုပ်ငန်းစဥ်မပြီးဆုံးနိုင်ပါ" + "%1$sကို အသုံးပြု၍ မရပါ" + "ဖြင့် ငွေပေးချမေည်" + "ဖြင့် ပြီးဆုံးပါပြီ" + "သင့်ပိုမိုအနှစ်သက်သော tap & payစနစ်အတွက် ဝန်ဆောင်မှုမှာ ဖယ်ရှားထားပါသည် နောက်ထပ်တစ်ခုကို ရွေးမည်လား?" + "ပြီးဆုံးစေရန် နောက်ထပ်စက်ကို တို့ထိပါ" + "ချိတ်ဆက်ရန်" + "ကွန်ရက်ကို ချိတ်ဆက် မရခဲ့" + "ချိတ်ဆက်ထားပြီး" + "ကွန်ယက်ကို ဆက်သွယ်ရန်" + "ကွန်ရက် %1$sကို ချိတ်ဆက်ရမလား?" + "Android Beam သင် NFC ဖွင့်ရန်လိုအပ်သည်။ သင်၎င်းအားဖွင့်လိုသလား?" + "အန်ဒရွိုက် အလင်းတန်း" + diff --git a/NfcSony/res/values-nb/provisioning.xml b/NfcSony/res/values-nb/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-nb/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-nb/strings.xml b/NfcSony/res/values-nb/strings.xml new file mode 100644 index 0000000..cea1dd0 --- /dev/null +++ b/NfcSony/res/values-nb/strings.xml @@ -0,0 +1,49 @@ + + + "NFC-tjeneste" + "NFC" + "Kontakt mottatt per NFC" + "Trykk for å legge til denne personen som en kontakt." + "NFC-samhandling er fullført" + "Berør for å gi denne personen kontaktinformasjonen din." + "NFC er aktivert." + "Berør for å overføre trådløst" + "Innkommende trådløs overføring" + "Sender ..." + "Overføring fullført" + "Overføringen ble ikke fullført" + "Overføringen ble avbrutt" + "Avbryt" + "Trykk for å se" + "Mottakerens enhet støtter ikke store filoverføringer via Android Beam." + "Før enhetene sammen igjen" + "Beam er opptatt for øyeblikket. Prøv igjen når den forrige overføringen er fullført." + "enhet" + "Kobler til %1$s" + "Tilkoblet %1$s" + "Kunne ikke koble til %1$s" + "Kobler fra %1$s" + "Frakoblet %1$s" + "Sammenkobler %1$s" + "Kunne ikke sammenkoble %1$s" + "Kunne ikke aktivere Bluetooth" + "Er du sikker på at koble til Bluetooth-enheten %1$s?" + "Ja" + "Nei" + "Trykk igjen for å betale med %1$s" + "Trykk igjen for å fullføre med %1$s" + "Denne transaksjonen kunne ikke fullføres med %1$s." + "Kunne ikke bruke %1$s." + "Betal med" + "Fullfør med" + "Den foretrukne tjenesten din for berøringsbetaling har blitt fjernet. Vil du velge en annen?" + "Trykk på en annen enhet for å fullføre" + "Koble til" + "Kunne ikke koble til nettverket" + "Tilkoblet" + "Koble til nettverket" + "Vil du koble til nettverket «%1$s»?" + "Android Beam krever at NFC er aktivert. Ønsker du å aktivere NFC?" + "Android Beam" + diff --git a/NfcSony/res/values-ne-rNP/provisioning.xml b/NfcSony/res/values-ne-rNP/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-ne-rNP/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-ne-rNP/strings.xml b/NfcSony/res/values-ne-rNP/strings.xml new file mode 100644 index 0000000..ebdee3c --- /dev/null +++ b/NfcSony/res/values-ne-rNP/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc सेवा" + "Nfc" + "NFC मा प्राप्त सम्पर्क" + "यो व्यक्तिलाई सम्पर्कको रूपमा थप्नको लागि छुनुहोस्" + "NFC कुराकानी पुरा" + "यो व्यक्ति लाई आफ्नो सम्पर्क जानकारी दिन छुनुहोस्" + "NFC सक्षम" + "बिममा छुनुहोस्" + "आगमन बिम..." + "बिम गर्दै..." + "बिम पुरा भयो" + "बिमले पुरा गरेन" + "बिम रद्द भयो" + "रद्द गर्नुहोस्" + "अवलोकन गर्न छुनुहोस्" + "प्राप्तकर्ताको उपकरणले बिम मार्फत ठूलो फाइल ट्रान्सफर गर्न समर्थन गर्दैन।" + "फेरि उपकरणहरू एकै साथ ल्याउनुहोस्" + "बीम अहिले व्यस्त छ। अघिल्लो स्थानान्तरण पूरा भएपछि फेरि प्रयास गर्नुहोस्।" + "यन्त्र" + "%1$s जडान गर्दै" + "जडित %1$s" + "%1$s जडान गर्न सकेन" + "%1$s विच्छेदन गर्दै" + "%1$s विच्छेद गरियो" + "%1$s जोडा गर्दै" + "%1$s जोडा गर्न सकेन" + "ब्लुटुथ सक्षम पार्न सकेन" + "तपाईँ ब्लुटुथ उपकरण जोडी गर्न निश्चित हुनुहुन्छ %1$s?" + "हो" + "होइन" + "%1$sसँग तिर्न फेरी छुनुहोस्" + "%1$s सँग समाप्त गर्न फेरी छुनुहोस्" + "यस transaction %1$s सगँ सम्पन्न गर्न सकिन्न।" + "%1$s प्रयोग गर्न सकेन।" + "सँग तिर्नुहोस्" + "सँग समाप्त गर्नुहोस्" + "तपाईँको प्राथमिकमा पर्ने Tap & Pay सेवा हटाइयो। अर्को चयन गर्नुहुन्छ?" + "पूरा गर्नको लागि अर्को उपकरण ट्याप गर्नुहोस्" + "जडान गर्नुहोस्" + "सञ्जालसँग जडान गर्न असक्षम भयो" + "जोडिएको" + "सञ्जालमा जडान गर्नुहोस्" + "सञ्जाल %1$sमा जडान गर्ने?" + "Android बीम सक्षम गर्नको लागि NFC आवश्यक छ। के तपाईँ यसलाई सक्षम गर्न चाहनुहुन्छ?" + "Android बिम" + diff --git a/NfcSony/res/values-nl/provisioning.xml b/NfcSony/res/values-nl/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-nl/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-nl/strings.xml b/NfcSony/res/values-nl/strings.xml new file mode 100644 index 0000000..6822046 --- /dev/null +++ b/NfcSony/res/values-nl/strings.xml @@ -0,0 +1,49 @@ + + + "NFC-service" + "NFC" + "Contact ontvangen via NFC" + "Raak aan om deze persoon toe te voegen als contact." + "NFC-interactie voltooid" + "Raak aan om deze persoon je contactgegevens te geven." + "NFC ingeschakeld." + "Tikken om uit te zenden" + "Inkomende beam..." + "Beamen..." + "Beam voltooid" + "Beam niet voltooid" + "Beam geannuleerd" + "Annuleren" + "Raak aan om te bekijken" + "Het apparaat van de ontvanger ondersteunt de overdracht van grote bestanden via beam niet." + "Houd de apparaten nogmaals tegen elkaar" + "Beam is momenteel bezet. Probeer het opnieuw zodra de huidige overdracht is voltooid." + "apparaat" + "%1$s verbinden" + "%1$s verbonden" + "Kan %1$s niet verbinden" + "Verbinding met %1$s verbreken" + "Verbinding met %1$s verbroken" + "%1$s koppelen" + "Kan %1$s niet koppelen" + "Kan Bluetooth niet inschakelen" + "Weet je zeker dat je het Bluetooth-apparaat %1$s wilt koppelen?" + "Ja" + "Nee" + "Tik nogmaals om te betalen met %1$s" + "Tik nogmaals om te voltooien met %1$s" + "Deze transactie kan niet worden voltooid met %1$s." + "Kan %1$s niet gebruiken." + "Betalen met" + "Voltooien met" + "Je voorkeursservice voor tikken en betalen is verwijderd. Een andere app kiezen?" + "Tik op een ander apparaat om te voltooien" + "Verbinding maken" + "Kan geen verbinding maken met het netwerk" + "Verbonden" + "Verbinding maken met netwerk" + "Verbinding maken met netwerk %1$s?" + "NFC moet zijn ingeschakeld voor Android Beam. Wil je dit inschakelen?" + "Android Beam" + diff --git a/NfcSony/res/values-oc-rFR/strings.xml b/NfcSony/res/values-oc-rFR/strings.xml new file mode 100644 index 0000000..f787488 --- /dev/null +++ b/NfcSony/res/values-oc-rFR/strings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/values-or-rIN/strings.xml b/NfcSony/res/values-or-rIN/strings.xml new file mode 100644 index 0000000..f787488 --- /dev/null +++ b/NfcSony/res/values-or-rIN/strings.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NfcSony/res/values-pa-rIN/provisioning.xml b/NfcSony/res/values-pa-rIN/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-pa-rIN/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-pa-rIN/strings.xml b/NfcSony/res/values-pa-rIN/strings.xml new file mode 100644 index 0000000..1a4975e --- /dev/null +++ b/NfcSony/res/values-pa-rIN/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc ਸੇਵਾ" + "Nfc" + "NFC ਤੇ ਪ੍ਰਾਪਤ ਕੀਤਾ ਸੰਪਰਕ" + "ਇਸ ਵਿਅਕਤੀ ਨੂੰ ਇੱਕ ਸੰਪਰਕ ਦੇ ਤੌਰ ਤੇ ਜੋੜਨ ਲਈ ਛੋਹਵੋ।" + "NFC ਇੰਟਰੈਕਸ਼ਨ ਪੂਰਾ" + "ਇਸ ਵਿਅਕਤੀ ਨੂੰ ਆਪਣੀ ਸੰਪਰਕ ਜਾਣਕਾਰੀ ਦੇਣ ਲਈ ਛੋਹਵੋ।" + "NFC ਸਮਰਥਿਤ।" + "ਬੀਮ ਕਰਨ ਲਈ ਛੋਹਵੋ" + "ਇਨਕਮਿੰਗ ਬੀਮ..." + "ਬੀਮ ਕਰ ਰਿਹਾ ਹੈ..." + "ਬੀਮ ਪੂਰਾ" + "ਬੀਮ ਪੂਰਾ ਨਹੀਂ ਕੀਤਾ" + "ਬੀਮ ਰੱਦ ਕੀਤਾ" + "ਰੱਦ ਕਰੋ" + "ਦੇਖਣ ਲਈ ਛੋਹਵੋ" + "ਰਿਸੀਵਰ ਦੀ ਡਿਵਾਈਸ ਬੀਮ ਰਾਹੀਂ ਵੱਡੀ ਫਾਈਲ ਟ੍ਰਾਂਸਫਰ ਦਾ ਸਮਰਥਨ ਨਹੀਂ ਕਰਦੀ।" + "ਡਿਵਾਈਸਾਂ ਨੂੰ ਦੁਬਾਰਾ ਇਕੱਠੇ ਨਾਲ ਲਿਆਓ" + "ਬੀਮ ਇਸ ਵੇਲੇ ਰੁੱਝਾ ਹੋਇਆ ਹੈ। ਜਦੋਂ ਪਿਛਲਾ ਟ੍ਰਾਂਸਫਰ ਪੂਰਾ ਹੋ ਜਾਏ ਤਾਂ ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ।" + "ਡਿਵਾਈਸ" + "%1$s ਨੂੰ ਕਨੈਕਟ ਕਰ ਰਿਹਾ ਹੈ" + "%1$s ਨਾਲ ਕਨੈਕਟ ਕੀਤਾ" + "%1$s ਨੂੰ ਕਨੈਕਟ ਨਹੀਂ ਕਰ ਸਕਿਆ" + "%1$s ਨੂੰ ਡਿਸਕਨੈਕਟ ਕਰ ਰਿਹਾ ਹੈ" + "%1$s ਨੂੰ ਡਿਸਕਨੈਕਟ ਕੀਤਾ" + "%1$s ਨੂੰ ਪੇਅਰ ਕਰ ਰਿਹਾ ਹੈ" + "%1$s ਨੂੰ ਪੇਅਰ ਨਹੀਂ ਕਰ ਸਕਿਆ" + "Bluetooth ਨੂੰ ਸਮਰੱਥ ਨਹੀਂ ਬਣਾ ਸਕਿਆ" + "ਕੀ ਤੁਸੀਂ ਯਕੀਨੀ ਤੌਰ ਤੇ Bluetooth ਡਿਵਾਈਸ %1$s ਨਾਲ ਪੇਅਰ ਕਰਨਾ ਚਾਹੁੰਦੇ ਹੋ?" + "ਹਾਂ" + "ਨਹੀਂ" + "%1$s ਨਾਲ ਪੇ ਕਰਨ ਲਈ ਦੁਬਾਰਾ ਟੈਪ ਕਰੋ" + "%1$s ਨਾਲ ਪੂਰਾ ਕਰਨ ਲਈ ਦੁਬਾਰਾ ਟੈਪ ਕਰੋ" + "ਇਹ ਟ੍ਰਾਂਜੈਕਸ਼ਨ %1$s ਨਾਲ ਪੂਰੀ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕੀ।" + "%1$s ਨੂੰ ਨਹੀਂ ਵਰਤ ਸਕਿਆ।" + "ਇਸ ਨਾਲ ਪੇ ਕਰੋ" + "ਇਸ ਨਾਲ ਪੂਰਾ ਕਰੋ" + "ਟੈਪ ਕਰੋ & ਪੇ ਕਰੋ ਲਈ ਤੁਹਾਡੀ ਤਰਜੀਹੀ ਸੇਵਾ ਹਟਾ ਦਿੱਤੀ ਗਈ ਸੀ। ਕੀ ਦੂਜੀ ਚੁਣਨੀ ਹੈ?" + "ਪੂਰਾ ਕਰਨ ਲਈ ਦੂਜੀ ਡਿਵਾਈਸ ਨੂੰ ਟੈਪ ਕਰੋ।" + "ਕਨੈਕਟ ਕਰੋ" + "ਨੈਟਵਰਕ ਨਾਲ ਕਨੈਕਟ ਕਰਨ ਵਿੱਚ ਅਸਮਰੱਥ" + "ਕਨੈਕਟ ਕੀਤਾ" + "ਨੈਟਵਰਕ ਨਾਲ ਕਨੈਕਟ ਕਰੋ" + "ਕੀ %1$s ਨੈਟਵਰਕ ਨਾਲ ਕਨੈਕਟ ਕਰਨਾ ਹੈ?" + "Android ਬੀਮ ਲਈ NFC ਨੂੰ ਸਮਰਥਿਤ ਕਰਨ ਦੀ ਲੋੜ ਹੈ। ਕੀ ਤੁਸੀਂ ਇਸਨੂੰ ਸਮਰੱਥ ਬਣਾਉਣਾ ਚਾਹੁੰਦੇ ਹੋ?" + "Android ਬੀਮ" + diff --git a/NfcSony/res/values-pl/provisioning.xml b/NfcSony/res/values-pl/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-pl/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-pl/strings.xml b/NfcSony/res/values-pl/strings.xml new file mode 100644 index 0000000..28510cb --- /dev/null +++ b/NfcSony/res/values-pl/strings.xml @@ -0,0 +1,49 @@ + + + "Usługa NFC" + "NFC" + "Kontakt odebrany przez NFC" + "Dotknij, aby dodać tę osobę do kontaktów." + "Zakończono interakcję przez NFC" + "Dotknij, aby przekazać tej osobie swoje dane kontaktowe." + "Komunikacja NFC włączona." + "Dotknij, aby przesłać" + "Odbieram dane zbliżeniowo..." + "Przesyłam..." + "Przesyłanie zbliżeniowe zakończone" + "Przesyłanie zbliżeniowe nie udało się" + "Przesyłanie zbliżeniowe zostało anulowane" + "Anuluj" + "Dotknij, by wyświetlić" + "Urządzenie odbierające nie pozwala na zbliżeniowe przesyłanie dużych plików." + "Zbliż urządzenia do siebie" + "Funkcja Beam jest teraz zajęta. Spróbuj ponownie, gdy zakończy się poprzednia transmisja." + "urządzenie" + "Łączę: %1$s" + "Połączono: %1$s" + "Nie udało się połączyć: %1$s" + "Odłączam: %1$s" + "Odłączono: %1$s" + "Paruję: %1$s" + "Nie udało się sparować: %1$s" + "Nie można włączyć Bluetootha" + "Czy na pewno chcesz sparować urządzenie Bluetooth %1$s?" + "Tak" + "Nie" + "Kliknij ponownie, by zapłacić, używając %1$s" + "Kliknij ponownie, by wykonać w %1$s" + "Tej transakcji nie można wykonać w %1$s." + "Nie można użyć %1$s." + "Zapłać w" + "Wykonaj w" + "Twoja preferowana usługa systemu dotknij i zapłać została usunięta. Wybrać inną?" + "Dotknij innego urządzenia, by zakończyć" + "Połącz" + "Nie można połączyć z siecią" + "Połączono" + "Połącz z siecią" + "Połączyć z siecią %1$s?" + "Android Beam wymaga włączenia NFC. Włączyć?" + "Android Beam" + diff --git a/NfcSony/res/values-pt-rPT/provisioning.xml b/NfcSony/res/values-pt-rPT/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-pt-rPT/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-pt-rPT/strings.xml b/NfcSony/res/values-pt-rPT/strings.xml new file mode 100644 index 0000000..0c78c9c --- /dev/null +++ b/NfcSony/res/values-pt-rPT/strings.xml @@ -0,0 +1,49 @@ + + + "Serviço NFC" + "NFC" + "Contacto recebido através de NFC" + "Toque para adicionar esta pessoa como contacto." + "Interação NFC completa" + "Toque para dar a esta pessoa as suas informações de contacto." + "NFC ativado." + "Tocar para transmitir" + "A receber partilha..." + "A transmitir..." + "Partilha concluída" + "A partilha não foi concluída" + "Partilha cancelada" + "Cancelar" + "Tocar para ver" + "O dispositivo do recetor não suporta a transferência de ficheiros grandes através de partilha." + "Juntar os dispositivos novamente" + "O Beam está atualmente ocupado. Tente novamente quando a transferência anterior for concluída." + "dispositivo" + "A ligar o %1$s" + "%1$s ligado" + "Não foi possível ligar o %1$s" + "A desligar o %1$s" + "%1$s desligado" + "A sincronizar o %1$s" + "Não foi possível sincronizar o %1$s" + "Não foi possível ativar o Bluetooth" + "Tem a certeza de que pretende sincronizar o dispositivo Bluetooth %1$s?" + "Sim" + "Não" + "Toque novamente para pagar com %1$s" + "Toque novamente para concluir com %1$s" + "Não foi possível concluir esta transação com %1$s." + "Não foi possível utilizar %1$s." + "Pagar com" + "Concluir com" + "O serviço preferido para tocar e pagar foi removido. Pretende escolher outro?" + "Toque noutro dispositivo para concluir." + "Ligar" + "Não foi possível ligar à rede" + "Ligado" + "Ligar a uma rede" + "Ligar à rede %1$s?" + "A ativação do Android Beam requer NFC. Pretende ativá-lo?" + "Android Beam" + diff --git a/NfcSony/res/values-pt/provisioning.xml b/NfcSony/res/values-pt/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-pt/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-pt/strings.xml b/NfcSony/res/values-pt/strings.xml new file mode 100644 index 0000000..bbf26d0 --- /dev/null +++ b/NfcSony/res/values-pt/strings.xml @@ -0,0 +1,49 @@ + + + "Serviço Nfc" + "Nfc" + "Contato recebido por NFC" + "Toque para adicionar essa pessoa como um contato." + "Interação de NFC completa" + "Toque para fornecer suas informações de contato para essa pessoa" + "NFC habilitado." + "Toque para enviar" + "Beam chegando..." + "Enviando..." + "Envio concluído" + "O envio não foi concluído" + "Envio cancelado" + "Cancelar" + "Toque para visualizar" + "O dispositivo receptor não suporta a transferência de arquivos grandes por meio do Android Beam." + "Una dispositivos novamente" + "O Beam está ocupado no momento. Tente novamente quando a transferência anterior for concluída." + "dispositivo" + "Conectando %1$s" + "%1$s conectado" + "Não foi possível conectar o %1$s" + "Desconectando %1$s" + "%1$s desconectado" + "Pareando %1$s" + "Não foi possível parear o %1$s" + "Não foi possível ativar o Bluetooth" + "Tem certeza de que deseja parear o dispositivo Bluetooth %1$s?" + "Sim" + "Não" + "Toque novamente para pagar com o %1$s" + "Toque novamente para concluir com o %1$s" + "Não foi possível concluir a transação com o %1$s." + "Não foi possível usar o %1$s." + "Pagar com" + "Concluir com" + "Seu serviço preferencial para tocar e pagar foi removido. Escolher outro?" + "Toque em outro dispositivo para concluir" + "Conectar" + "Não é possível conectar-se à rede" + "Conectado" + "Conectar-se à rede" + "Conectar-se à rede %1$s?" + "O Android Beam exige que a NFC esteja ativada. Deseja ativá-la?" + "Android Beam" + diff --git a/NfcSony/res/values-ro/provisioning.xml b/NfcSony/res/values-ro/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-ro/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-ro/strings.xml b/NfcSony/res/values-ro/strings.xml new file mode 100644 index 0000000..fba4ed3 --- /dev/null +++ b/NfcSony/res/values-ro/strings.xml @@ -0,0 +1,49 @@ + + + "Serviciul NFC" + "NFC" + "Persoană din agendă primită prin NFC" + "Atingeţi pentru a adăuga această persoană în Agendă." + "Interacțiune NFC completă" + "Atingeţi pentru a oferi acestei persoane datele dvs. de contact" + "NFC activat." + "Atingeţi pentru transmitere" + "Transmitere primită..." + "Se transmite..." + "Transmitere încheiată" + "Transmiterea nu este finalizată" + "Transmitere anulată" + "Anulați" + "Atingeţi pentru a afişa" + "Dispozitivul destinatarului nu acceptă transferul fişierelor mari prin fascicul." + "Apropiați dispozitivele din nou" + "Beam este ocupat. Încercați după finalizarea transferului anterior." + "dispozitiv" + "Se conectează %1$s" + "S-a conectat %1$s" + "Nu s-a putut conecta %1$s" + "Se deconectează %1$s" + "S-a deconectat %1$s" + "Se asociază %1$s" + "Nu s-a putut asocia %1$s" + "Activarea Bluetooth a eşuat" + "Sigur doriţi să împerecheaţi dispozitivul Bluetooth %1$s?" + "Da" + "Nu" + "Atingeți din nou pentru a plăti cu %1$s" + "Atingeți din nou pentru a finaliza cu %1$s" + "Această tranzacție nu a putut fi finalizată cu %1$s." + "Nu s-a putut utiliza %1$s." + "Plătiți cu" + "Finalizați cu" + "Serviciul preferat pentru funcția „atingeți și plătiți” a fost eliminat. Alegeți altul?" + "Atingeți alt dispozitiv pentru a finaliza" + "Conectați" + "Conectarea la rețea nu este posibilă" + "Conectată" + "Conectați-vă la rețea" + "Doriți să vă conectați la rețeaua %1$s?" + "Android Beam necesită activarea NFC. Activați?" + "Transmitere Android" + diff --git a/NfcSony/res/values-ru/provisioning.xml b/NfcSony/res/values-ru/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-ru/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-ru/strings.xml b/NfcSony/res/values-ru/strings.xml new file mode 100644 index 0000000..f69fe21 --- /dev/null +++ b/NfcSony/res/values-ru/strings.xml @@ -0,0 +1,49 @@ + + + "Служба NFC" + "NFC" + "Данные контакта получены по NFC" + "Нажмите, чтобы добавить контакт." + "Обмен данными по NFC завершен" + "Нажмите, чтобы передать свои контактные данные." + "Служба NFC включена" + "Нажмите, чтобы передать данные" + "Получение данных…" + "Передача данных..." + "Передача данных завершена" + "Передача данных не завершена" + "Передача данных отменена" + "Отмена" + "Нажмите, чтобы просмотреть" + "Устройство получателя не поддерживает передачу больших объемов данных через Android Beam." + "Повторить попытку" + "Передавать данные пока нельзя. Повторите попытку после окончания предыдущего сеанса." + "устройство" + "Устройство %1$s подключается" + "Устройство %1$s подключено" + "Не удалось подключить устройство %1$s" + "Устройство %1$s отключается" + "Устройство %1$s отключено" + "Подключение к устройству %1$s" + "Не удалось подключить устройство %1$s" + "Не удалось включить Bluetooth" + "Подключить Bluetooth-устройство %1$s?" + "Да" + "Нет" + "Чтобы использовать для оплаты приложение \"%1$s\", снова приблизьте телефон" + "Чтобы использовать приложение \"%1$s\", снова приблизьте телефон" + "Для этой операции нельзя использовать приложение \"%1$s\"" + "Невозможно использовать приложение \"%1$s\"" + "Способ оплаты" + "Использовать" + "Вы удалили приложение для бесконтактной оплаты, используемое по умолчанию. Выбрать другое?" + "Коснитесь другого устройства, чтобы передать данные" + "Подключиться" + "Не удалось подключиться к сети" + "Соединение установлено" + "Подключение к сети" + "Подключиться к сети %1$s?" + "Для работы Android Beam требуется NFC. Включить?" + "Android Beam" + diff --git a/NfcSony/res/values-si-rLK/provisioning.xml b/NfcSony/res/values-si-rLK/provisioning.xml new file mode 100644 index 0000000..55fa3b5 --- /dev/null +++ b/NfcSony/res/values-si-rLK/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning" + + diff --git a/NfcSony/res/values-si-rLK/strings.xml b/NfcSony/res/values-si-rLK/strings.xml new file mode 100644 index 0000000..611167a --- /dev/null +++ b/NfcSony/res/values-si-rLK/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc සේවාව" + "Nfc" + "NFC හරහා සම්බන්ධතාව ලැබුණි" + "මෙම පුද්ගලයා සම්බන්ධතාවයක් ලෙස එක් කිරීමට තට්ටු කරන්න." + "NFC ක්‍රියාකාරකම සම්පූර්ණයි" + "මෙම පුද්ගලයාට ඔබගේ සම්බන්ධතා තොරතුරු දීමට ස්පර්ශ කරන්න." + "NFC සබලයි." + "බීම් කිරීමට ස්පර්ශ කරන්න" + "එන බීම්..." + "බීම් කරමින්..." + "බීම් සම්පුර්ණයි" + "බීම් සම්පූර්ණ වූයේ නැත" + "බීම් අවලංගු කරන ලදි" + "අවලංගු කරන්න" + "බැලීමට ස්පර්ශ කරන්න" + "බීම් ඔස්සේ ලැබෙන්නාගේ උපාංගය විශාල ගොනු හුවමාරුව සඳහා සහය නොදක්වයි." + "නැවතත් උපාංග එකට ගෙනෙන්න" + "Beam දැනට කාර්ය බහුලය. පෙර මාරුව සම්පූර්ණ වන විට නැවත උත්සාහ කරන්න." + "උපාංගය" + "%1$s සබැඳෙමින්" + "%1$s සබැඳිණි" + "%1$s සබැඳීමට නොහැකි විය" + "%1$s විසන්ධි කරමින්" + "%1$s විසන්ධි කරන ලදී" + "%1$s යුගල කරමින්" + "%1$s යුගල කිරීමට නොහැකි විය" + "බ්ලූටූත් සබල කළ නොහැක" + "%1$s බ්ලූටූත් උපාංගය යුගල කිරීමට අවශ්‍ය බවට ඔබට විශ්වාසද?" + "ඔව්" + "නැත" + "%1$s සමඟ ගෙවීමට නැවත තට්ටු කරන්න" + "%1$s සමඟ සම්පූර්ණ කිරීමට නැවත තට්ටු කරන්න" + "මෙම ගනුදෙනුව %1$s සමඟ සම්පූර්ණ කළ නොහැක." + "%1$s භාවිතා කළ නොහැක." + "සමඟ ගෙවන්න" + "සමඟ සම්පූර්ණ කරන්න" + "තට්ටු කර ගෙවීම සඳහා ඔබගේ මනාප කරන ලද සේවාව ඉවත් කරන ලදි. වෙනත් එකක් තෝරන්න?" + "සම්පුර්ණ කිරීමට වෙනත් උපාංගයකට තට්ටු කරන්න" + "සම්බන්ධ කරන්න" + "ජාලය වෙත සම්බන්ධ වීමට නොහැක" + "සම්බන්ධ කළ" + "ජාලයට සම්බන්ධ කරන්න" + "%1$s ජාලයට සම්බන්ධ වන්න ද?" + "Android Beam වලට NFC සබල කර තිබීමට අවශ්‍යය. ඔබට එය සබල කිරීමට අවශ්‍යයද?" + "Android බීම්" + diff --git a/NfcSony/res/values-sk/provisioning.xml b/NfcSony/res/values-sk/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-sk/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-sk/strings.xml b/NfcSony/res/values-sk/strings.xml new file mode 100644 index 0000000..f87be62 --- /dev/null +++ b/NfcSony/res/values-sk/strings.xml @@ -0,0 +1,49 @@ + + + "Služba Nfc" + "Nfc" + "Kontakt prijatý cez NFC" + "Dotknutím sa túto osobu pridáte medzi kontakty." + "Interakcia NFC bola dokončená" + "Dotknutím sa tejto osobe odovzdáte svoje kontaktné informácie." + "NFC povolené." + "Dotykom spustíte prenos" + "Prichádzajúci prenos..." + "Prenáša sa..." + "Prenos bol dokončený" + "Prenos nebol dokončený" + "Prenos bol zrušený" + "Zrušiť" + "Zobrazte dotykom" + "Zariadenie príjemcu nepodporuje prenos veľkých súborov prostredníctvom funkcie Beam." + "Znova priblížte zariadenia" + "Aplikácia Beam je momentálne zaneprázdnená. Skúste to znova, keď sa dokončí predchádzajúci prenos." + "zariadenie" + "Pripája sa zariadenie %1$s" + "Zariadenie %1$s je pripojené" + "Zariadenie %1$s sa nepodarilo pripojiť" + "Odpája sa zariadenie %1$s" + "Zariadenie %1$s bolo odpojené" + "Páruje sa zariadenie %1$s" + "Zariadenie %1$s sa nepodarilo spárovať" + "Bluetooth sa nepodarilo povoliť" + "Naozaj chcete spárovať zariadenie Bluetooth %1$s?" + "Áno" + "Nie" + "Klepnutím zaplatíte pomocou aplikácie %1$s" + "Klepnutím akciu dokončíte pomocou aplikácie %1$s" + "Túto transakciu nie je možné dokončiť pomocou aplikácie %1$s." + "Aplikáciu %1$s nie je možné použiť." + "Zaplatiť pomocou" + "Dokončiť pomocou" + "Vaša uprednostňovaná služba pre platby mobilom bola odstránená. Vybrať inú?" + "Akciu dokončíte tým, že sa zariadením dotknete iného zariadenia" + "Pripojiť" + "Nepodarilo sa pripojiť k sieti" + "Pripojené" + "Pripojiť k sieti" + "Pripojiť k sieti %1$s?" + "Android Beam vyžaduje, aby bola funkcia NFC povolená. Chcete ju aktivovať?" + "Android Beam" + diff --git a/NfcSony/res/values-sl/provisioning.xml b/NfcSony/res/values-sl/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-sl/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-sl/strings.xml b/NfcSony/res/values-sl/strings.xml new file mode 100644 index 0000000..2fc02a4 --- /dev/null +++ b/NfcSony/res/values-sl/strings.xml @@ -0,0 +1,49 @@ + + + "Storitev NFC" + "NFC" + "Stik prejet prek NFC-ja" + "Tapnite, če želite dodati to osebo kot stik." + "Pošiljanje prek NFC-ja končano" + "Tapnite, če želite tej osebi poslati svoje podatke za stik." + "NFC omogočen." + "Dotaknite se, da pošljete" + "Dohodni prenos ..." + "Prenašanje ..." + "Prenos končan" + "Prenos se ni končal" + "Prenos je preklican" + "Prekliči" + "Dotaknite se za prikaz" + "Prejemnikova naprava ne podpira prenosa velikih datotek prek funkcije Android Beam." + "Znova združi naprave" + "Android Beam je trenutno zaseden. Poskusite znova, ko se konča prejšnji prenos." + "naprava" + "Povezovanje naprave %1$s" + "Naprava %1$s je povezana" + "Naprave %1$s ni mogoče povezati" + "Prekinjanje povezave z napravo %1$s" + "Povezava z napravo %1$s je prekinjena" + "Seznanjanje naprave %1$s" + "Naprave %1$s ni bilo mogoče seznaniti" + "Bluetootha ni mogoče omogočiti" + "Ste prepričani, da želite seznaniti napravo Bluetooth %1$s?" + "Da" + "Ne" + "Znova se dotaknite, da plačate z aplikacijo %1$s" + "Znova se dotaknite, da dokončate z aplikacijo %1$s" + "Transakcije ni bilo mogoče dokončati z aplikacijo %1$s." + "Aplikacije %1$s ni bilo mogoče uporabiti." + "Plačaj z aplikacijo" + "Dokončaj z aplikacijo" + "Vaša prednostna storitev za plačevanje z dotikom je bila odstranjena. Želite izbrati drugo?" + "Dotaknite se druge naprave, če želite dokončati" + "Vzpostavi povezavo" + "Povezave z omrežjem ni mogoče vzpostaviti" + "Povezava je vzpostavljena" + "Vzpostavitev povezave z omrežjem" + "Želite vzpostaviti povezavo z omrežjem %1$s?" + "Za Android Beam mora biti omogočen NFC. Ali ga želite omogočiti?" + "Android Beam" + diff --git a/NfcSony/res/values-sq-rAL/provisioning.xml b/NfcSony/res/values-sq-rAL/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-sq-rAL/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-sq-rAL/strings.xml b/NfcSony/res/values-sq-rAL/strings.xml new file mode 100644 index 0000000..180714a --- /dev/null +++ b/NfcSony/res/values-sq-rAL/strings.xml @@ -0,0 +1,49 @@ + + + "Shërbimi NFC" + "NFC" + "Kontakti u mor nëpërmjet NFC-së" + "Prek për ta shtuar këtë person si kontakt." + "Ndërveprimi me NFC-në përfundoi" + "Prek për t\'i dhënë këtij personi informacionet e tua të kontaktit." + "NFC-ja u aktivizua." + "Prek për të dërguar me rreze" + "Dërgimi me rreze është në ardhje..." + "Po dërgon me rreze..." + "Dërgimi me rreze përfundoi" + "Dërgimi me rreze nuk përfundoi" + "Dërgimi me rreze u anulua" + "Anulo" + "Prek për të parë" + "Pajisja e marrësit nuk e mbështet transferimin e skedarëve të mëdhenj nëpërmjet dërgimit me rreze." + "Bashkoji pajisjet përsëri" + "Dërgimi me rreze është i zënë aktualisht. Provo përsëri kur të përfundojë transferimi i mëparshëm." + "pajisja" + "%1$s po lidhet" + "%1$s u lidh" + "%1$s nuk mund të lidhej" + "%1$s po shkëputet" + "%1$s u shkëput" + "%1$s po çiftohet" + "%1$s nuk mund të çiftohej" + "Bluetooth-i nuk mund të aktivizohej" + "Je i sigurt që dëshiron ta çiftosh pajisjen me bluetooth: %1$s?" + "Po" + "Jo" + "Trokit përsëri për të paguar me %1$s" + "Trokit përsëri për të përfunduar me %1$s" + "Ky transaksion nuk mund të përfundohej me %1$s." + "%1$s nuk mund të përdorej." + "Paguaj me" + "Përfundo me" + "Shërbimi yt i preferuar për \"trokit dhe paguaj\" u hoq. Të zgjidhet një tjetër?" + "Trokit te një pajisje tjetër për ta përfunduar" + "Lidhu" + "Nuk mund të lidhej me rrjetin" + "U lidh" + "Lidhu me rrjetin" + "Të lidhet me rrjetin %1$s?" + "Dërgimi me rreze i androidit kërkon që NFC-ja të jetë e aktivizuar. Dëshiron që ta aktivizosh?" + "Dërgimi me rreze i androidit" + diff --git a/NfcSony/res/values-sr/provisioning.xml b/NfcSony/res/values-sr/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-sr/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-sr/strings.xml b/NfcSony/res/values-sr/strings.xml new file mode 100644 index 0000000..d48f5a6 --- /dev/null +++ b/NfcSony/res/values-sr/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc Service" + "Nfc" + "Контакт је примљен преко NFC-а" + "Додирните да бисте додали ову особу као контакт." + "NFC интеракција је довршена" + "Додирните да бисте овој особи дали контакт информације." + "NFC је омогућен." + "Додирните за емитовање" + "Долазно пребацивање..." + "Пребацивање..." + "Пребацивање је довршено" + "Пребацивање се није завршило" + "Пребацивање је отказано" + "Откажи" + "Додирните да бисте прегледали" + "Уређај пријемника не подржава пренос великих датотека пребацивањем." + "Поново приближите уређаје" + "Пребацивање је тренутно заузето. Покушајте поново када се претходни пренос заврши." + "уређај" + "Повезивање са уређајем %1$s" + "Уређај %1$s је повезан" + "Повезивање са уређајем %1$s није успело" + "Прекидање везе са уређајем %1$s" + "Веза са уређајем %1$s је прекинута" + "Упаривање уређаја %1$s" + "Упаривање уређаја %1$s није успело" + "Није могуће омогућити Bluetooth" + "Да ли сте сигурни да желите да упарите Bluetooth уређај %1$s?" + "Да" + "Не" + "Додирните поново да бисте платили помоћу апликације %1$s" + "Додирните поново да бисте довршили помоћу апликације %1$s" + "Ову трансакцију није могуће довршити помоћу апликације %1$s." + "Није могуће користити %1$s." + "Плаћање помоћу апликација" + "Довршавање помоћу апликација" + "Изабрана услуга за функцију Додирни и плати је уклоњена. Желите ли да изаберете другу?" + "Додирните други уређај да бисте завршили" + "Повежи" + "Повезивање са мрежом није могуће" + "Повезана је" + "Повежите се са мрежом" + "Желите ли да се повежете са мрежом %1$s?" + "Android пребацивање захтева NFC да бисте могли да га омогућите. Желите ли да га омогућите?" + "Android пребацивање" + diff --git a/NfcSony/res/values-sv/provisioning.xml b/NfcSony/res/values-sv/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-sv/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-sv/strings.xml b/NfcSony/res/values-sv/strings.xml new file mode 100644 index 0000000..4545520 --- /dev/null +++ b/NfcSony/res/values-sv/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc Service" + "NFC" + "Kontakt mottagen via NFC" + "Tryck om du vill lägga till personen som en kontakt." + "NFC-interaktionen är slutförd" + "Tryck om du vill ge personen dina kontaktuppgifter." + "NFC aktiverat." + "Tryck här om du vill överföra" + "Inkommande trådlös överföring ..." + "Överför trådlöst ..." + "Den trådlösa överföringen har slutförts" + "Den trådlösa överföringen slutfördes inte" + "Den trådlösa överföringen avbröts" + "Avbryt" + "Tryck och visa" + "Mottagarens enheten stöder inte trådlös överföring av stora filer." + "För samman enheterna igen" + "Beam används för närvarande. Försök igen när den pågående överföringen är klar." + "enhet" + "Ansluter %1$s" + "%1$s har anslutits" + "Det gick inte att ansluta %1$s" + "Kopplar från %1$s" + "%1$s har kopplats från" + "Kopplar %1$s" + "Det gick inte att koppla %1$s" + "Det gick inte att aktivera Bluetooth" + "Är du säker på att du vill para ihop Bluetooth-enheten %1$s?" + "Ja" + "Nej" + "Tryck igen om du vill betala med %1$s" + "Tryck igen om du vill slutföra med %1$s" + "Transaktionen kunde inte slutföras med %1$s." + "Det gick inte att använda %1$s." + "Betala med" + "Slutför med" + "Standardtjänsten för snudda och betala har tagits bort. Vill du välja en annan?" + "Slutför genom att trycka på en annan enhet" + "Anslut" + "Det gick inte att ansluta till nätverket" + "Ansluten" + "Anslut till nätverk" + "Vill du ansluta till nätverket %1$s?" + "Android Beam kräver att NFC har aktiverats. Vill du göra det?" + "Android Beam" + diff --git a/NfcSony/res/values-sw/provisioning.xml b/NfcSony/res/values-sw/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-sw/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-sw/strings.xml b/NfcSony/res/values-sw/strings.xml new file mode 100644 index 0000000..49ddd86 --- /dev/null +++ b/NfcSony/res/values-sw/strings.xml @@ -0,0 +1,49 @@ + + + "Huduma ya Nfc" + "NFC" + "Anwani imepokewa kupitia NFC" + "Gusa ili kuongeza mtu huyu kama mwasiliani" + "NFC imemaliza kushirikiana" + "Gusa ili kumpa mtu huyu maelezo yako ya mawasiliano." + "NFC imewezeshwa." + "Gusa ili kusambaza" + "Boriti zinazoingia..." + "Inasambaza..." + "Kusambaza kumekamilika" + "Usambazaji haukukamilika" + "Boriti imeghairiwa" + "Ghairi" + "Gusa ili utazame" + "Kifaa cha upokeaji hakihimili uhamishaji wa faili kubwa kupitia kwenye boriti." + "Unganisha vifaa tena" + "Android Beam inatumika kwa sasa. Jaribu tena itakapokamilisha uhamishaji uliotangulia." + "kifaa" + "Inaunganisha %1$s" + "Imeunganisha %1$s" + "Haikuweza kuunganisha %1$s" + "Inaondoa %1$s" + "Imeondoa %1$s" + "Inaoanisha %1$s" + "Haikuweza kuoanisha %1$s" + "Haikuweza kuwezesha Bluetooth" + "Una uhakika unataka kuoanisha kifaa cha Bluetooth cha %1$s?" + "Ndiyo" + "Hapana" + "Gonga tena ili kulipa kwa %1$s" + "Gonga tena ili kukamilisha kwa %1$s" + "Ununuzi huu haukuweza kukamilishwa kwa %1$s." + "Haikuweza kutumia %1$s." + "Lipa kwa" + "Kamilisha kwa" + "Huduma unayopendelea ya kugonga na kulipa iliondolewa. Ungependa kuchagua nyingine?" + "Gonga kifaa kingine ili kukamilisha" + "Unganisha" + "Haiwezi kuunganisha kwenye mtandao" + "Imeunganishwa" + "Unganisha kwenye mtandao" + "Ungependa kuunganisha kwenye mtandao wa %1$s?" + "Boriti ya Android inahitaji NFC iwe imewashwa. Je, unataka kuiwasha?" + "Sambaza kupitia Android" + diff --git a/NfcSony/res/values-ta-rIN/provisioning.xml b/NfcSony/res/values-ta-rIN/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-ta-rIN/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-ta-rIN/strings.xml b/NfcSony/res/values-ta-rIN/strings.xml new file mode 100644 index 0000000..62e9bd0 --- /dev/null +++ b/NfcSony/res/values-ta-rIN/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc சேவை" + "Nfc" + "NFC வழியாக தொடர்பு பெறப்பட்டது" + "தொடர்பாக இவரைச் சேர்க்கத் தொடவும்." + "NFC இடைவினை முழுமையடைந்தது" + "இவருக்கு உங்கள் தொடர்புத் தகவலை வழங்கத் தொடவும்." + "NFC இயக்கப்பட்டது." + "பீம் செய்யத் தட்டவும்" + "உள்வரும் பீம்..." + "பரிமாற்றுகிறது..." + "பீம் முடிந்தது" + "பீம் ஐ முடிக்க முடியவில்லை" + "பீம் ரத்து செய்யப்பட்டது" + "ரத்துசெய்" + "பார்க்கத் தொடவும்" + "பெறுநரின் சாதனம் பீம் வழியான பெரிய கோப்புப் பரிமாற்றத்தை ஆதரிக்கவில்லை." + "மீண்டும் சாதனங்களை ஒன்றாகக் கொண்டுவரவும்" + "பீம் தற்போது பணிமிகுதியில் உள்ளது. முந்தைய இடமாற்றம் முடியும்போது, மீண்டும் முயலவும்." + "சாதனம்" + "%1$sஐ இணைக்கிறது" + "%1$s இணைக்கப்பட்டது" + "%1$sஐ இணைக்க முடியவில்லை" + "%1$s துண்டிக்கப்படுகிறது" + "%1$s துண்டிக்கப்பட்டது" + "%1$s இணைக்கப்படுகிறது" + "%1$sஐ இணைக்க முடியவில்லை" + "புளூடூத்தைச் செயல்படுத்த முடியவில்லை" + "புளூடூத் சாதனம் %1$s உடன் இணைக்க வேண்டும் என்று விரும்புகிறீர்களா?" + "ஆம்" + "இல்லை" + "%1$s உடன் கட்டணம் செலுத்த மீண்டும் தட்டவும்" + "%1$s உடன் நிறைவு செய்ய மீண்டும் தட்டவும்" + "இந்தப் பரிவர்த்தனையை %1$s உடன் முடிக்க முடியவில்லை." + "%1$s ஐப் பயன்படுத்த முடியவில்லை." + "இதனுடன் செலுத்து" + "இதனுடன் நிறைவு செய்" + "தட்டி கட்டண செலுத்தலுக்கான உங்களின் முன்னுரிமை சேவை அகற்றப்பட்டது. மற்றொன்றைத் தேர்ந்தெடுக்கவா?" + "முடிக்க மற்றொரு சாதனத்தைத் தட்டவும்" + "இணை" + "நெட்வொர்க்குடன் இணைக்க முடியவில்லை" + "இணைக்கப்பட்டது" + "நெட்வொர்க்குடன் இணை" + "%1$s நெட்வொர்க்குடன் இணைக்கவா?" + "Android பீம்மிற்கு NFC இயக்கப்பட்டிருக்க வேண்டும். இயக்கவா?" + "Android பீம்" + diff --git a/NfcSony/res/values-te-rIN/provisioning.xml b/NfcSony/res/values-te-rIN/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-te-rIN/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-te-rIN/strings.xml b/NfcSony/res/values-te-rIN/strings.xml new file mode 100644 index 0000000..bf66fd9 --- /dev/null +++ b/NfcSony/res/values-te-rIN/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc సేవ" + "Nfc" + "పరిచయం NFC ద్వారా స్వీకరించబడింది" + "ఈ వ్యక్తిని పరిచయంగా జోడించడానికి తాకండి." + "NFC చర్య పూర్తయింది" + "మీ సంప్రదింపు సమాచారాన్ని ఈ వ్యక్తికి అందించడానికి తాకండి." + "NFC ప్రారంభించబడింది." + "బీమ్ కోసం తాకండి" + "ఇన్‌కమింగ్ Beam..." + "Beam చేస్తోంది..." + "Beam పూర్తయింది" + "Beam పూర్తి కాలేదు" + "Beam రద్దు చేయబడింది" + "రద్దు చేయండి" + "వీక్షించడానికి తాకండి" + "స్వీకర్త పరికరం Beam ద్వారా పెద్ద ఫైల్ బదిలీకి మద్దతు ఇవ్వదు." + "పరికరాలను మళ్లీ సమీపంలోకి తీసుకురండి" + "Beam ప్రస్తుతం బిజీగా ఉంది. మునుపటి బదిలీ పూర్తయిన తర్వాత మళ్లీ ప్రయత్నించండి." + "పరికరం" + "%1$sని కనెక్ట్ చేస్తోంది" + "%1$s కనెక్ట్ చేయబడింది" + "%1$sని కనెక్ట్ చేయడం సాధ్యపడలేదు" + "%1$sని డిస్‌కనెక్ట్ చేస్తోంది" + "%1$s డిస్‌కనెక్ట్ చేయబడింది" + "%1$sని జత చేస్తోంది" + "%1$sని జత చేయడం సాధ్యపడలేదు" + "బ్లూటూత్‌ను ప్రారంభించడం సాధ్యపడలేదు" + "మీరు ఖచ్చితంగా %1$s బ్లూటూత్ పరికరాన్ని జత చేయాలనుకుంటున్నారా?" + "అవును" + "వద్దు" + "%1$sతో చెల్లించడానికి మళ్లీ నొక్కండి" + "%1$sతో పూర్తి చేయడానికి మళ్లీ నొక్కండి" + "%1$sతో ఈ లావాదేవీని పూర్తి చేయడం సాధ్యపడలేదు." + "%1$sని ఉపయోగించడం సాధ్యపడలేదు." + "దీనితో చెల్లించండి" + "దీనితో పూర్తి చేయండి" + "నొక్కి చెల్లించడం కోసం మీ ప్రాధాన్య సేవ తీసివేయబడింది. మరోదాన్ని ఎంచుకోవాలా?" + "పూర్తి చేయడానికి మరో పరికరాన్ని నొక్కండి" + "కనెక్ట్ చేయి" + "నెట్‌వర్క్‌కు కనెక్ట్ చేయడం సాధ్యపడలేదు" + "కనెక్ట్ చేయబడింది" + "నెట్‌‍వర్క్‌కు కనెక్ట్ చేయి" + "%1$s నెట్‌వర్క్‌కు కనెక్ట్ చేయాలా?" + "Android Beam కోసం NFC ప్రారంభించబడటం అవసరం. మీరు దాన్ని ప్రారంభించాలనుకుంటున్నారా?" + "Android Beam" + diff --git a/NfcSony/res/values-th/provisioning.xml b/NfcSony/res/values-th/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-th/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-th/strings.xml b/NfcSony/res/values-th/strings.xml new file mode 100644 index 0000000..426a140 --- /dev/null +++ b/NfcSony/res/values-th/strings.xml @@ -0,0 +1,49 @@ + + + "บริการ NFC" + "Nfc" + "ได้รับข้อมูลรายชื่อติดต่อทาง NFC" + "แตะเพื่อเพิ่มคนนี้เป็นผู้ติดต่อ" + "ดำเนินการโต้ตอบกับ NFC ให้เสร็จสมบูรณ์" + "แตะเพื่อให้ข้อมูลรายชื่อติดต่อของคุณแก่คนนี้" + "เปิดใช้งาน NFC แล้ว" + "แตะเพื่อบีม" + "บีมขาเข้า..." + "กำลังบีม..." + "บีมสำเร็จ" + "บีมไม่เสร็จสมบูรณ์" + "ยกเลิกบีมแล้ว" + "ยกเลิก" + "แตะเพื่อดู" + "อุปกรณ์ของผู้รับไม่สนับสนุนการถ่ายโอนไฟล์ขนาดใหญ่ผ่านการบีม" + "นำอุปกรณ์มารวมกันอีกครั้ง" + "Beam ไม่ว่างในขณะนี้ ลองอีกครั้งเมื่อการโอนก่อนหน้าเสร็จสมบูรณ์แล้ว" + "อุปกรณ์" + "กำลังเชื่อมต่อกับ %1$s" + "เชื่อมต่อกับ %1$s อยู่" + "ไม่สามารถเชื่อมต่อกับ %1$s" + "กำลังยกเลิกการเชื่อมต่อกับ %1$s" + "ยกเลิกการเชื่อมต่อกับ %1$s แล้ว" + "กำลังจับคู่กับ %1$s" + "ไม่สามารถจับคู่กับ %1$s" + "ไม่สามารถเปิดใช้งานบลูทูธ" + "คุณแน่ใจหรือไม่ว่าต้องการจับคู่อุปกรณ์บลูทูธ %1$s นี้" + "ใช่" + "ไม่" + "แตะอีกครั้งเพื่อชำระเงินด้วย %1$s" + "แตะอีกครั้งเพื่อดำเนินการให้เสร็จสมบูรณ์ด้วย %1$s" + "ไม่สามารถดำเนินการธุรกรรมนี้ให้เสร็จสมบูรณ์ด้วย %1$s" + "ไม่สามารถใช้ %1$s" + "ชำระเงินด้วย" + "ดำเนินการให้เสร็จสมบูรณ์ด้วย" + "บริการที่คุณต้องการสำหรับแตะและจ่ายถูกลบ เลือกบริการอื่นไหม" + "แตะอุปกรณ์อื่นเพื่อทำให้เสร็จสมบูรณ์" + "เชื่อมต่อ" + "ไม่สามารถเชื่อมต่อกับเครือข่าย" + "เชื่อมต่อแล้ว" + "เชื่อมต่อกับเครือข่าย" + "เชื่อมต่อกับเครือข่าย %1$s ไหม" + "Android Beam ต้องการให้เปิดใช้ NFC คุณต้องการเปิดใช้ไหม" + "Android Beam" + diff --git a/NfcSony/res/values-tl/provisioning.xml b/NfcSony/res/values-tl/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-tl/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-tl/strings.xml b/NfcSony/res/values-tl/strings.xml new file mode 100644 index 0000000..a37e4b0 --- /dev/null +++ b/NfcSony/res/values-tl/strings.xml @@ -0,0 +1,49 @@ + + + "Serbisyo ng Nfc" + "Nfc" + "Natanggap ang contact sa NFC" + "Pindutin upang idagdag ang taong ito bilang isang contact." + "Kumpleto na ang pakikipag-ugnayan sa NFC" + "Pindutin para ibigay sa taong ito ang impormasyon ng contact mo" + "Pinagana ang NFC." + "Pindutin upang i-beam" + "Dumarating na beam..." + "Nagbi-beam..." + "Kumpleto na ang pag-beam" + "Hindi nakumpleto ang beam" + "Kinansela ang beam" + "Kanselahin" + "Pindutin upang tingnan" + "Hindi sinusuportahan ng device ng receiver ang malaking paglipat ng file sa pamamagitan ng beam." + "Pagsamahing muli ang mga device" + "Kasalukuyang abala ang beam. Subukang muli kapag nakumpleto na ang dating paglilipat." + "device" + "Ikinokonekta ang %1$s" + "Naikonekta na ang %1$s" + "Hindi naikonekta ang %1$s" + "Idinidiskonekta ang %1$s" + "Nadiskonekta ang %1$s" + "Ipinapares ang %1$s" + "Hindi naipares ang %1$s" + "Hindi mapagana ang Bluetooth" + "Sigurado ka bang nais mong ipares ang Bluetooth device na %1$s?" + "Oo" + "Hindi" + "I-tap muli upang magbayad gamit ang %1$s" + "I-tap muli upang kumpletuhin gamit ang %1$s" + "Ang transaksyong ito ay hindi makukumpleto gamit ang %1$s." + "Hindi magamit ang %1$s." + "Magbayad gamit ang" + "Kumpletuhin gamit ang" + "Inalis ang iyong mas gustong serbisyo para sa pag-tap at pagbabayad. Pumili ng isa pa?" + "Mag-tap ng ibang device upang makumpleto" + "Ikonekta" + "Hindi makakonekta sa network" + "Nakakonekta" + "Kumonekta sa network" + "Kokonekta sa network na %1$s?" + "Kailangang i-enable ang NFC upang magamit ang Android Beam. Gusto mo ba itong i-enable?" + "Android Beam" + diff --git a/NfcSony/res/values-tr/provisioning.xml b/NfcSony/res/values-tr/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-tr/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-tr/strings.xml b/NfcSony/res/values-tr/strings.xml new file mode 100644 index 0000000..9ba8a1d --- /dev/null +++ b/NfcSony/res/values-tr/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc Hizmeti" + "Nfc" + "Kişi NFC ile alındı" + "Bu kullanıcıyı kişi olarak eklemek için dokunun." + "NFC etkileşimi tamam" + "Bu kullanıcıya kişi bilgilerinizi vermek için dokunun." + "NFC etkin." + "Göndermek için dokunun" + "Gelen ışın..." + "Işınlanıyor..." + "Işınlama tamamlandı" + "Işınlama tamamlanmadı" + "Işınlama iptal edildi" + "İptal" + "Görüntülemek için dokunun" + "Alıcının cihazı yüksek boyutlu dosyaların ışınlamayla aktarılmasını desteklemiyor." + "Cihazları tekrar bir araya getirin" + "Android Beam şu anda meşgul. Önceki aktarım işlemi tamamlandığında tekrar deneyin." + "cihaz" + "Şu cihaza bağlanılıyor: %1$s" + "Şu cihaza bağlanıldı: %1$s" + "Şu cihaza bağlanılamadı: %1$s" + "Şu cihazın bağlantısı kesiliyor: %1$s" + "Şu cihazın bağlantısı kesildi: %1$s" + "Şu cihazla eşleştiriliyor: %1$s" + "Şu cihazla eşleştirilemedi: %1$s" + "Bluetooth etkinleştirilemedi" + "%1$s adlı Bluetooth cihazını eşlemek istediğinizden emin misiniz?" + "Evet" + "Hayır" + "%1$s ile ödemek için tekrar dokundurun" + "%1$s ile tamamlamak için tekrar hafifçe dokunun" + "Bu işlem %1$s ile tamamlanamadı." + "%1$s kullanılamadı." + "Ödemeyi şu uygulamayla yap:" + "İşlemi şu uygulamayla tamamla:" + "Dokundur ve öde özelliği için tercih ettiğiniz hizmet kaldırıldı. Başka bir hizmet seçmek ister misiniz?" + "Tamamlamak için diğer cihaza hafifçe dokunun" + "Bağlan" + "Ağa bağlanılamıyor" + "Bağlanıldı" + "Ağa bağlanın" + "%1$s adlı ağa bağlanılsın mı?" + "Android Beam, NFC\'nin etkinleştirilmesini gerektiriyor. Etkinleştirmek istiyor musunuz?" + "Android Beam" + diff --git a/NfcSony/res/values-ug/strings.xml b/NfcSony/res/values-ug/strings.xml new file mode 100644 index 0000000..01cdce3 --- /dev/null +++ b/NfcSony/res/values-ug/strings.xml @@ -0,0 +1,68 @@ + + + + Nfc مۇلازىمەت + Nfc + + NFC ئارقىلىق ئالاقەداش قوبۇللىدى + + چېكىلسە بۇ كىشىنى ئالاقەداشقا قوشىدۇ. + + NFC تەسىرلىشىش تامام + + چېكىپ بۇ كىشىگە ئالاقەداش ئۇچۇرىڭىزنى يوللىيالايسىز + + NFC قوزغىتىلدى. + چېكىسلە يوللايدۇ + beam نى قوبۇللاۋاتىدۇ… + Beam بىلەن ئۇچۇر يوللاۋاتىدۇ… + Beam تامام + Beam تاماملانمىدى + Beam دىن ۋاز كەچتى + ۋاز كەچ + چېكىلسە كۆرسىتىدۇ + قوبۇللايدىغان ئۈسكۈنە Beam ئارقىلىق چوڭ ھۆججەت يوللاشنى قوللىمايدۇ. + ئىككى ئۈسكۈنىنى قايتا يېقىنلاشتۇرۇڭ + + + + + + + + + + كۆكچىشنى قوزغىتالمايدۇ + + كۆكچىش ئۈسكۈنە %1$s بىلەن جۈپلەمسىز؟ + ھەئە + ياق + + قايتا چېكىپ %1$s ئارقىلىق پۇل تۆلىگىلى بولىدۇ + + قايتا چېكىپ %1$s ئارقىلىق پۇل تاپشۇرۇشنى تاماملىغىلى بولىدۇ + + بۇ قېتىملىق سودىنى %1$s ئارقىلىق تاماملىغىلى بولمايدۇ. + + %1$s نى ئىشلىتەلمەيدۇ. + + پۇل تاپشۇرۇش ئۇسۇلى: + + تۆۋەندىكى ئەپتە تاماملايدۇ: + + چېكىپلا پۇل تۆلەيدىغان ئالدىنلىق مۇلازىمىتى چىقىرىۋېتىلگەن، باشقىسىنى تاللامسىز؟ + + + + + + + + + + diff --git a/NfcSony/res/values-uk/provisioning.xml b/NfcSony/res/values-uk/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-uk/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-uk/strings.xml b/NfcSony/res/values-uk/strings.xml new file mode 100644 index 0000000..2c7e0b2 --- /dev/null +++ b/NfcSony/res/values-uk/strings.xml @@ -0,0 +1,49 @@ + + + "Служба NFC" + "NFC" + "Контакт, отриманий через NFC" + "Торкніться, щоб додати цю особу як контакт." + "Взаємодію через NFC закінчено" + "Торкніться, щоб надати цій особі свою контактну інформацію." + "NFC увімкнено." + "Торкніться, щоб передати дані" + "Вхідне передавання даних..." + "Передавання даних…" + "Передавання даних виконано" + "Передавання даних не завершено" + "Передавання даних скасовано" + "Скасувати" + "Торкніться, щоб переглянути" + "Пристрій отримувача не підтримує пересилання великих файлів за допомогою функції Передавання даних Android." + "Знову притуліть пристрої один до одного" + "Неможливо передати. Повторіть спробу, коли завершиться попереднє передавання." + "пристрій" + "Під’єднання пристрою %1$s" + "Пристрій %1$s під’єднано" + "Пристрій %1$s не під’єднано" + "Від’єднання пристрою %1$s" + "Пристрій %1$s від’єднано" + "Підключення пристрою %1$s" + "Пристрій %1$s не підключено" + "Не вдалося ввімкнути Bluetooth" + "Дійсно створити пару з пристроєм Bluetooth %1$s?" + "Так" + "Ні" + "Торкніться ще раз, щоб оплатити через %1$s" + "Торкніться ще раз, щоб виконати через %1$s" + "Не вдалося виконати трансакцію через %1$s." + "Не вдалося виконати через %1$s." + "Оплатити через" + "Виконати через" + "Вилучено службу за умовчанням для оплати через NFC. Вибрати іншу?" + "Торкніться іншого пристрою, щоб завершити" + "Під’єднатися" + "Не вдається під’єднатися до мережі" + "Під’єднано" + "Під’єднатися до мережі" + "Під’єднатися до мережі \"%1$s\"?" + "Для роботи функції передавання даних Android потрібно ввімкнути NFC. Увімкнути?" + "Передавання даних Android" + diff --git a/NfcSony/res/values-ur-rPK/provisioning.xml b/NfcSony/res/values-ur-rPK/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-ur-rPK/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-ur-rPK/strings.xml b/NfcSony/res/values-ur-rPK/strings.xml new file mode 100644 index 0000000..8622bc0 --- /dev/null +++ b/NfcSony/res/values-ur-rPK/strings.xml @@ -0,0 +1,49 @@ + + + "‏Nfc سروس" + "Nfc" + "‏NFC پر موصولہ رابطہ" + "اس شخص کو ایک رابطہ کے بطور شامل کرنے کیلئے ٹچ کریں۔" + "‏NFC تعامل مکمل ہو گیا" + "اس شخص کو اپنے رابطہ کی معلومات دینے کیلئے ٹچ کریں۔" + "‏NFC فعال ہے۔" + "بیم کیلئے ٹچ کریں" + "بیم موصول ہو رہا ہے…" + "بیم کی جا رہی ہے…" + "بیم مکمل ہو گیا" + "بیم مکمل نہیں ہوا" + "بیم منسوخ ہو گیا" + "منسوخ کریں" + "دیکھنے کیلئے ٹچ کریں" + "موصول کرنے والے کا آلہ بیم کے ذریعے بڑی فائل کی منتقلی کا تعاون نہیں کرتا ہے۔" + "آلات کو دوبارہ سے ساتھ لائیں" + "‏Beam فی الحال مصروف ہے۔ سابقہ منتقلی مکمل ہونے پر دوبارہ کوشش کریں۔" + "آلہ" + "%1$s منسلک ہو رہا ہے" + "%1$s منسلک ہوگیا" + "%1$s منسلک نہیں ہو سکا" + "%1$s غیر منسلک ہو رہا ہے" + "%1$s غیر منسلک ہوگیا" + "%1$s جوڑا بنا رہا ہے" + "%1$s جوڑا نہیں بنا سکا" + "بلوٹوتھ فعال نہیں ہو سکا" + "کیا آپ واقعی بلوٹوتھ آلہ %1$s کا جوڑا بنانا چاہتے ہیں؟" + "ہاں" + "نہیں" + "%1$s کے ساتھ ادائیگی کرنے کیلئے دوبارہ تھپتھپائیں" + "%1$s کے ساتھ مکمل کرنے کیلئے دوبارہ تھپتھپائیں" + "یہ لین دین %1$s کے ساتھ مکمل نہیں ہو سکا۔" + "%1$s کا استعمال نہیں کر سکا۔" + "اس کے ساتھ ادائیگی کریں" + "اس کے ساتھ مکمل کریں" + "تھپتھپائیں اور ادائیگی کریں کیلئے آپ کی پسندیدہ سروس کو ہٹا دیا گیا۔ کوئی دوسری سروس منتخب کریں؟" + "مکمل کرنے کیلئے کوئی دوسرا آلہ تھپتھپائیں" + "مربوط کریں" + "نیٹ ورک سے رابطہ قائم کرنے سے قاصر" + "مربوط" + "نیٹ ورک سے مربوط ہوں" + "نیٹ ورک %1$s سے مربوط کریں؟" + "‏Android Beam کو فعال کیے جانے کیلئے NFC درکار ہے۔ کیا آپ اسے فعال کرنا چاہتے ہیں؟" + "Android Beam" + diff --git a/NfcSony/res/values-uz-rUZ/provisioning.xml b/NfcSony/res/values-uz-rUZ/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-uz-rUZ/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-uz-rUZ/strings.xml b/NfcSony/res/values-uz-rUZ/strings.xml new file mode 100644 index 0000000..1c71337 --- /dev/null +++ b/NfcSony/res/values-uz-rUZ/strings.xml @@ -0,0 +1,49 @@ + + + "Nfc xizmati" + "Nfc" + "Kontakt NFC orqali qabul qilindi" + "Bu shaxsni kontakt sifatida qo‘shish uchun ustiga bosing." + "NFC orqali bog‘landi" + "Bu shaxsga kontaktingiz ma’lumotini o‘tkazish uchun bosing." + "NFC yoqilgan." + "O‘tkazish uchun bosing" + "Kiruvchi o‘tkazma…" + "O‘tkazmoqda…" + "O‘tkazildi" + "O‘tkazilmadi" + "O‘tkazish bekor qilindi" + "Bekor qilish" + "Ko‘rish uchun bosing" + "Qabul qiluvchining qurilmasi o‘tkazish xususiyati orqali katta hajmdagi fayllar o‘tkazmasini qo‘llab-quvvatlamaydi." + "Qurilmalarni yana jipslashtiring" + "Beam hozirda band. Avvalgi o‘tkazma tugallangach, qaytadan urining." + "qurilma" + "%1$s ulanmoqda" + "%1$s ulandi" + "%1$s ulanmadi" + "%1$s bilan aloqa uzilmoqda" + "%1$s bilan aloqa uzildi" + "%1$s ulanmoqda" + "%1$s ulanmadi" + "Bluetooth’ni yoqib bo‘lmadi" + "%1$s Bluetooth qurilmasini bo‘glashga ishonchingiz komilmi?" + "Ha" + "Yo‘q" + "%1$s orqali to‘lash uchun qaytadan bosing." + "%1$s ishini yakunlash uchun qaytadan bosing." + "%1$s bilan o‘tkazma amalga oshmadi." + "%1$sdan foydalanib bo‘lmadi." + "To‘lov usuli:" + "Quyidagi bilan yakunlash:" + "Afzal ko‘rgan bosib & to‘lash xizmati ilovasi o‘chirib tashlandi. Boshqasini tanlang?" + "Ishni yakunlash uchun boshqa qurilmani bosing" + "Ulanish" + "Tarmoqqa ulanib bo‘lmadi" + "Ulandi" + "Tarmoqqa ulanish" + "%1$s tarmog‘iga ulansinmi?" + "Android Beam uchun NFC yoniq bo‘lishi lozim. Uni yoqishni xohlaysizmi?" + "Android Beam" + diff --git a/NfcSony/res/values-vi/provisioning.xml b/NfcSony/res/values-vi/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-vi/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-vi/strings.xml b/NfcSony/res/values-vi/strings.xml new file mode 100644 index 0000000..17cfefd --- /dev/null +++ b/NfcSony/res/values-vi/strings.xml @@ -0,0 +1,49 @@ + + + "Dịch vụ Nfc" + "Nfc" + "Thông tin liên hệ đã nhận qua NFC" + "Chạm để thêm người này làm người liên hệ." + "Hoàn tất tương tác NFC" + "Chạm để gửi cho người này thông tin liên hệ của bạn." + "Đã bật NFC." + "Chạm để truyền" + "Tia đến..." + "Đang chiếu..." + "Hoàn tất chiếu" + "Chiếu chưa hoàn tất" + "Chiếu bị hủy" + "Hủy" + "Chạm để xem" + "Thiết bị của người nhận không hỗ trợ truyền tệp lớn qua quá trình chiếu." + "Nhóm các thiết bị lại với nhau lần nữa" + "Beam hiện đang bận. Hãy thử lại khi quá trình truyền trước đây hoàn tất." + "thiết bị" + "Đang kết nối %1$s" + "Đã kết nối %1$s" + "Không thể kết nối %1$s" + "Đang ngắt kết nối %1$s" + "Đã ngắt kết nối %1$s" + "Đang ghép nối %1$s" + "Không thể ghép nối %1$s" + "Không thể bật Bluetooth" + "Bạn có chắc chắn muốn ghép nối thiết bị Bluetooth %1$s không?" + "Có" + "Không" + "Nhấn lại để thanh toán bằng %1$s" + "Nhấn lại để hoàn tất bằng %1$s" + "Không thể hoàn tất giao dịch này bằng %1$s." + "Không thể sử dụng %1$s." + "Thanh toán bằng" + "Hoàn tất bằng" + "Dịch vụ yêu thích của bạn để nhấn và thanh toán đã bị xóa. Chọn dịch vụ khác?" + "Chạm vào thiết bị khác để hoàn thành" + "Kết nối" + "Không thể kết nối với mạng" + "Đã kết nối" + "Kết nối mạng" + "Kết nối với mạng %1$s?" + "Android Beam yêu cầu bật NFC. Bạn có muốn bật NFC không?" + "Android Beam" + diff --git a/NfcSony/res/values-zh-rCN/provisioning.xml b/NfcSony/res/values-zh-rCN/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-zh-rCN/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-zh-rCN/strings.xml b/NfcSony/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000..934c1fd --- /dev/null +++ b/NfcSony/res/values-zh-rCN/strings.xml @@ -0,0 +1,49 @@ + + + "NFC服务" + "NFC" + "通过NFC收到了联系人信息" + "触摸可将此人添加为联系人。" + "NFC互动完成" + "接触可向此人发送您的联系人信息。" + "NFC已开启。" + "触摸即可传输" + "正在接收传输内容..." + "正在传输信息..." + "传输完毕" + "传输未完成" + "传输已取消" + "取消" + "触摸即可查看" + "接收者的设备不支持传输较大的文件。" + "再次让两台设备接触" + "Beam 目前正忙。请在之前的传输任务完成后重试。" + "设备" + "正在连接到%1$s" + "已连接到%1$s" + "无法连接到%1$s" + "正在断开与%1$s的连接" + "已断开与%1$s的连接" + "正在与%1$s配对" + "无法与%1$s配对" + "无法启用蓝牙" + "您确定要与蓝牙设备%1$s配对吗?" + "是" + "否" + "再次点按即可使用“%1$s”付款" + "再次点按即可使用“%1$s”完成付款" + "无法使用“%1$s”完成本次交易。" + "无法使用“%1$s”。" + "付款方式:" + "使用以下应用完成操作:" + "用于触碰付款的首选服务已遭删除,您要选择使用其他服务吗?" + "请点按其他设备来完成操作" + "连接" + "无法连接到网络" + "已连接" + "连接到网络" + "连接到网络%1$s?" + "需要开启NFC才能使用Android Beam。要开启吗?" + "Android Beam" + diff --git a/NfcSony/res/values-zh-rHK/provisioning.xml b/NfcSony/res/values-zh-rHK/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-zh-rHK/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-zh-rHK/strings.xml b/NfcSony/res/values-zh-rHK/strings.xml new file mode 100644 index 0000000..b2d7884 --- /dev/null +++ b/NfcSony/res/values-zh-rHK/strings.xml @@ -0,0 +1,49 @@ + + + "NFC 服務" + "NFC" + "已透過 NFC 收到聯絡人資料" + "輕觸即可將這個人新增為聯絡人。" + "NFC 互動完成" + "輕觸即可將您的聯絡人資料傳送給這個人。" + "NFC 已啟用。" + "輕觸即可傳送" + "正在接收傳送..." + "正在傳送..." + "傳送完畢" + "傳送未完成" + "傳送已取消" + "取消" + "輕觸即可查看" + "接收者的裝置不支援經由 Beam 傳送大型檔案。" + "再次把裝置放在一起" + "Beam 目前忙碌中。請在完成上一個傳輸工作後再試一次。" + "裝置" + "正在連接%1$s" + "已連接%1$s" + "無法連接%1$s" + "正在中斷連接%1$s" + "已中斷連接%1$s" + "正在配對%1$s" + "無法配對%1$s" + "無法啟用藍牙" + "您確定要與藍牙裝置「%1$s」配對嗎?" + "是" + "否" + "再次輕按即可使用「%1$s」付款" + "再輕按即可使用「%1$s」完成" + "無法使用「%1$s」完成這項交易。" + "無法使用「%1$s」。" + "付款方式" + "用於完成的應用程式" + "您首選的輕按付款服務已被移除,要選擇其他服務嗎?" + "輕按另一個裝置即可完成" + "連接" + "無法連接網絡" + "已連接" + "連接網絡" + "是否連接網絡「%1$s」?" + "需要 NFC 才能啟用 Android Beam。您要啟用嗎?" + "Android Beam" + diff --git a/NfcSony/res/values-zh-rTW/provisioning.xml b/NfcSony/res/values-zh-rTW/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-zh-rTW/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-zh-rTW/strings.xml b/NfcSony/res/values-zh-rTW/strings.xml new file mode 100644 index 0000000..3c33636 --- /dev/null +++ b/NfcSony/res/values-zh-rTW/strings.xml @@ -0,0 +1,49 @@ + + + "NFC 服務" + "NFC" + "已透過 NFC 收到聯絡人資訊" + "輕觸即可將這位使用者新增為聯絡人。" + "NFC 互動完成" + "輕觸即可將您的聯絡人資訊傳送給這位使用者。" + "NFC 已啟用。" + "輕觸即可傳輸" + "正在接收傳輸..." + "即時傳輸中..." + "傳輸完成" + "傳輸未完成" + "傳輸已取消" + "取消" + "輕觸即可查看" + "接收者的裝置不支援透過 Beam 傳輸大型檔案。" + "再次將裝置放在一起" + "Beam 目前忙碌中。請等待先前的傳輸作業完成,然後再試一次。" + "裝置" + "正在與 %1$s 連線" + "已連上 %1$s" + "無法與 %1$s 連線" + "正在中斷與 %1$s 的連線" + "已中斷與 %1$s 的連線" + "%1$s 配對中" + "%1$s 無法配對" + "無法啟用藍牙" + "您確定要與藍牙裝置「%1$s」配對嗎?" + "是" + "否" + "再輕按一下即可使用「%1$s」付款" + "再輕按一下即可使用「%1$s」完成付款" + "無法使用「%1$s」完成這項交易作業。" + "無法使用「%1$s」。" + "使用以下應用程式完成付款:" + "使用以下應用程式完成操作:" + "您偏好的感應付款服務已遭移除,要選擇其他服務嗎?" + "輕按另一個裝置即可完成" + "連線" + "無法連線至網路" + "已連線" + "連線至網路" + "連線至網路 %1$s?" + "需要 NFC 才能啟用 Android Beam。您要啟用嗎?" + "Android Beam" + diff --git a/NfcSony/res/values-zu/provisioning.xml b/NfcSony/res/values-zu/provisioning.xml new file mode 100644 index 0000000..676cad9 --- /dev/null +++ b/NfcSony/res/values-zu/provisioning.xml @@ -0,0 +1,26 @@ + + + + + + + + "application/com.android.managedprovisioning" + "application/com.android.managedprovisioning.v2" + + diff --git a/NfcSony/res/values-zu/strings.xml b/NfcSony/res/values-zu/strings.xml new file mode 100644 index 0000000..e69586c --- /dev/null +++ b/NfcSony/res/values-zu/strings.xml @@ -0,0 +1,49 @@ + + + "Isevisi ye-Nfc" + "i-NFC" + "Othintana naye utholakale nge-NFC" + "Thinta ukuze wengeze lomuntu njengoxhumene naye" + "Ukuxhumana kwe-NFC kuphelile" + "Thinta ukuze unike lomuntu imininingwane yakho yokuxhumana" + "I-NFC ivunyelwe." + "Cindezela kwibhimu" + "Ukuvumelanisa okungenayo..." + "Ukuvumelanisa..." + "Ukuvumelanisa kuqedile" + "Ukuvumelanisa akuqedile" + "Ukuvumelanisa kukhanseliwe" + "Khansela" + "Thinta ukuze ubuke" + "Idivayisi yomamukeli ayisekeli ukuhanjiswa kwamafayela amakhulu ngokuvumelanisa." + "Phinda uhlanganise ndawonye amadivayisi" + "Ukuvumelansia okwamanje kumatasa. Zama futhi uma ukuthutha kwangaphambili kuqeda." + "idivayisi" + "Ixhuma ku-%1$s" + "Ixhunyiwe ku-%1$s" + "Ayikwazanga ukuxhuma ku-%1$s" + "Iyanqamula ku-%1$s" + "Inqanyuliwe ku-%1$s" + "Ibhangqa i-%1$s" + "Ayikwazanga ukubhangqa i-%1$s" + "Ayikwazanga ukunika amandla i-Bluetooth" + "Ingabe unesiqinisekiso sokuthi ufuna ukuhambisa ngakubili idivayisi %1$s ye-Bluetooth?" + "Yebo" + "Cha" + "Phinda uthephe ukuze ukhokhe nge-%1$s" + "Phinda uthephe ukuze uqedelele nge-%1$s" + "Le nkokhelo ayikwazanga ukuqedelelwa nge-%1$s." + "Ayikwazanga ukusebenzisa i-%1$s." + "Khokha nge-" + "Gcwalisa nge-" + "Isevisi yakho oyincamelayo yokuthepha ukhokhe isusiwe. Khetha enye?" + "Thepha enye idivayisi ukuze uqedele" + "Xhuma" + "Ayikwazi ukuxhuma kunethiwekhi" + "Ixhunyiwe" + "Xhuma kwinethiwekhi" + "Xhuma kwinethiwekhi ye-%1$s?" + "I-Android Beam idinga ukuthi i-NFC inikwe amandla. Ingabe ufuna ukuyinika amandla?" + "I-Android Beam" + diff --git a/NfcSony/res/values/provisioning.xml b/NfcSony/res/values/provisioning.xml new file mode 100644 index 0000000..9823703 --- /dev/null +++ b/NfcSony/res/values/provisioning.xml @@ -0,0 +1,29 @@ + + + + + + + true + + + + application/com.android.managedprovisioning + application/com.android.managedprovisioning.v2 + + diff --git a/NfcSony/res/values/strings.xml b/NfcSony/res/values/strings.xml new file mode 100755 index 0000000..78102a6 --- /dev/null +++ b/NfcSony/res/values/strings.xml @@ -0,0 +1,95 @@ + + + Nfc Service + Nfc + + + Contact received over NFC + + + Touch to add this person as a contact. + + + NFC interaction complete + + + Touch to give this person your contact info. + + + NFC enabled. + Touch to beam + + Incoming beam... + Beaming... + Beam complete + Beam did not complete + Beam canceled + Cancel + Touch to view + The receiver\'s device doesn\'t support large file transfer via beam. + Bring devices together again + Beam is currently busy. Try again when the previous transfer completes. + + + device + + Connecting %1$s + + Connected %1$s + + Could not connect %1$s + + Disconnecting %1$s + + Disconnected %1$s + + Pairing %1$s + + Could not pair %1$s + + Could not enable Bluetooth + + Are you sure you want to pair the Bluetooth device %1$s? + Yes + No + + + Tap again to pay with %1$s + + Tap again to complete with %1$s + + This transaction couldn\'t be completed with %1$s. + + Couldn\'t use %1$s. + + Pay with + + Complete with + + Your preferred service for tap & pay was removed. Choose another?" + + Tap another device to complete + + + + Connect + + Unable to connect to network + + Connected + + Connect to network + + Connect to network %1$s? + + + Android Beam requires NFC to be enabled. Do you want to enable it? + + + Android Beam + diff --git a/NfcSony/sony/src/com/android/nfc/dhimpl/NativeLlcpConnectionlessSocket.java b/NfcSony/sony/src/com/android/nfc/dhimpl/NativeLlcpConnectionlessSocket.java new file mode 100644 index 0000000..db78496 --- /dev/null +++ b/NfcSony/sony/src/com/android/nfc/dhimpl/NativeLlcpConnectionlessSocket.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2010 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.nfc.dhimpl; + +import com.android.nfc.DeviceHost; +import com.android.nfc.LlcpPacket; + +import java.io.IOException; + +/** + * LlcpConnectionlessSocket represents a LLCP Connectionless object to be used + * in a connectionless communication + */ +public class NativeLlcpConnectionlessSocket implements DeviceHost.LlcpConnectionlessSocket { + + private int mHandle; + private int mSap; + private int mLinkMiu; + + public NativeLlcpConnectionlessSocket() { } + + public native boolean doSendTo(int sap, byte[] data); + + public native LlcpPacket doReceiveFrom(int linkMiu); + + public native boolean doClose(); + + @Override + public int getLinkMiu(){ + return mLinkMiu; + } + + @Override + public int getSap(){ + return mSap; + } + + @Override + public void send(int sap, byte[] data) throws IOException { + if (!doSendTo(sap, data)) { + throw new IOException(); + } + } + + @Override + public LlcpPacket receive() throws IOException { + LlcpPacket packet = doReceiveFrom(mLinkMiu); + if (packet == null) { + throw new IOException(); + } + return packet; + } + + public int getHandle(){ + return mHandle; + } + + @Override + public void close() throws IOException { + if (!doClose()) { + throw new IOException(); + } + } +} diff --git a/NfcSony/sony/src/com/android/nfc/dhimpl/NativeLlcpServiceSocket.java b/NfcSony/sony/src/com/android/nfc/dhimpl/NativeLlcpServiceSocket.java new file mode 100644 index 0000000..3a7e57f --- /dev/null +++ b/NfcSony/sony/src/com/android/nfc/dhimpl/NativeLlcpServiceSocket.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2010 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.nfc.dhimpl; + +import com.android.nfc.DeviceHost; +import com.android.nfc.DeviceHost.LlcpSocket; + +import java.io.IOException; + +/** + * LlcpServiceSocket represents a LLCP Service to be used in a + * Connection-oriented communication + */ +public class NativeLlcpServiceSocket implements DeviceHost.LlcpServerSocket { + private int mHandle; + private int mLocalMiu; + private int mLocalRw; + private int mLocalLinearBufferLength; + private int mSap; + private String mServiceName; + + public NativeLlcpServiceSocket(){ } + + private native NativeLlcpSocket doAccept(int miu, int rw, int linearBufferLength); + @Override + public LlcpSocket accept() throws IOException { + LlcpSocket socket = doAccept(mLocalMiu, mLocalRw, mLocalLinearBufferLength); + if (socket == null) throw new IOException(); + return socket; + } + + private native boolean doClose(); + @Override + public void close() throws IOException { + if (!doClose()) { + throw new IOException(); + } + } +} diff --git a/NfcSony/sony/src/com/android/nfc/dhimpl/NativeLlcpSocket.java b/NfcSony/sony/src/com/android/nfc/dhimpl/NativeLlcpSocket.java new file mode 100644 index 0000000..69506c5 --- /dev/null +++ b/NfcSony/sony/src/com/android/nfc/dhimpl/NativeLlcpSocket.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2010 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.nfc.dhimpl; + +import com.android.nfc.DeviceHost; + +import java.io.IOException; + +/** + * LlcpClientSocket represents a LLCP Connection-Oriented client to be used in a + * connection-oriented communication + */ +public class NativeLlcpSocket implements DeviceHost.LlcpSocket { + private int mHandle; + private int mSap; + private int mLocalMiu; + private int mLocalRw; + + public NativeLlcpSocket(){ } + + private native boolean doConnect(int nSap); + @Override + public void connectToSap(int sap) throws IOException { + if (!doConnect(sap)) { + throw new IOException(); + } + } + + private native boolean doConnectBy(String sn); + @Override + public void connectToService(String serviceName) throws IOException { + if (!doConnectBy(serviceName)) { + throw new IOException(); + } + } + + private native boolean doClose(); + @Override + public void close() throws IOException { + if (!doClose()) { + throw new IOException(); + } + } + + private native boolean doSend(byte[] data); + @Override + public void send(byte[] data) throws IOException { + if (!doSend(data)) { + throw new IOException(); + } + } + + private native int doReceive(byte[] recvBuff); + @Override + public int receive(byte[] recvBuff) throws IOException { + int receiveLength = doReceive(recvBuff); + if (receiveLength == -1) { + throw new IOException(); + } + return receiveLength; + } + + private native int doGetRemoteSocketMiu(); + @Override + public int getRemoteMiu() { return doGetRemoteSocketMiu(); } + + private native int doGetRemoteSocketRw(); + @Override + public int getRemoteRw() { return doGetRemoteSocketRw(); } + + @Override + public int getLocalSap(){ + return mSap; + } + + @Override + public int getLocalMiu(){ + return mLocalMiu; + } + + @Override + public int getLocalRw(){ + return mLocalRw; + } +} diff --git a/NfcSony/sony/src/com/android/nfc/dhimpl/NativeNfcManager.java b/NfcSony/sony/src/com/android/nfc/dhimpl/NativeNfcManager.java new file mode 100644 index 0000000..4a4e8d9 --- /dev/null +++ b/NfcSony/sony/src/com/android/nfc/dhimpl/NativeNfcManager.java @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2010 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.nfc.dhimpl; + +import com.android.nfc.DeviceHost; +import com.android.nfc.LlcpException; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.content.Context; +import android.content.SharedPreferences; +import android.nfc.ErrorCodes; +import android.nfc.tech.Ndef; +import android.nfc.tech.TagTechnology; +import android.util.Log; +import com.android.nfc.NfcDiscoveryParameters; + +import java.io.File; + +/** + * Native interface to the NFC Manager functions + */ +public class NativeNfcManager implements DeviceHost { + private static final String TAG = "NativeNfcManager"; + private static final boolean DBG = true; + + static final String PREF = "NxpDeviceHost"; + + + static final String DRIVER_NAME = "sony"; + + public static final String INTERNAL_TARGET_DESELECTED_ACTION = "com.android.nfc.action.INTERNAL_TARGET_DESELECTED"; + + static final int DEFAULT_LLCP_MIU = 128; + static final int DEFAULT_LLCP_RWSIZE = 1; + + //TODO: dont hardcode this + private static final byte[][] EE_WIPE_APDUS = { + {(byte)0x00, (byte)0xa4, (byte)0x04, (byte)0x00, (byte)0x00}, + {(byte)0x00, (byte)0xa4, (byte)0x04, (byte)0x00, (byte)0x07, (byte)0xa0, (byte)0x00, + (byte)0x00, (byte)0x04, (byte)0x76, (byte)0x20, (byte)0x10, (byte)0x00}, + {(byte)0x80, (byte)0xe2, (byte)0x01, (byte)0x03, (byte)0x00}, + {(byte)0x00, (byte)0xa4, (byte)0x04, (byte)0x00, (byte)0x00}, + {(byte)0x00, (byte)0xa4, (byte)0x04, (byte)0x00, (byte)0x07, (byte)0xa0, (byte)0x00, + (byte)0x00, (byte)0x04, (byte)0x76, (byte)0x30, (byte)0x30, (byte)0x00}, + {(byte)0x80, (byte)0xb4, (byte)0x00, (byte)0x00, (byte)0x00}, + {(byte)0x00, (byte)0xa4, (byte)0x04, (byte)0x00, (byte)0x00}, + }; + + static { + System.loadLibrary("nfc_sony_jni"); + } + + /* Native structure */ + private int mNative; + + private final DeviceHostListener mListener; + private final Context mContext; + + private native int nativeInitializeDriver(); + + private native boolean nativeInitializeDriver_llcp(); + + public NativeNfcManager(Context context, DeviceHostListener listener) { + mListener = listener; + nativeInitializeDriver(); + nativeInitializeDriver_llcp(); + mContext = context; + } + + private native int nativeGetLastError(); + + public int doGetLastError() { + return nativeGetLastError(); + } + + @Override + public void checkFirmware() { + } + + private native int nativeInitialize(); + + @Override + public boolean initialize() { + boolean result; + if (nativeInitialize() != 0) { + result = false; + } else { + result = true; + } + return result; + } + + private native int nativeDeinitialize(); + + @Override + public boolean deinitialize() { + boolean result; + if (nativeDeinitialize() != 0) { + result = false; + } else { + result = true; + } + return result; + } + + @Override + public String getName() { + return DRIVER_NAME; + } + + @Override + public boolean sendRawFrame(byte[] data) + { + return false; + } + + @Override + public boolean routeAid(byte[] aid, int route) + { + return false; + } + + @Override + public boolean unrouteAid(byte[] aid) + { + return false; + } + + private native void doCommitRouting(); + + @Override + public boolean commitRouting() + { + doCommitRouting(); + return true; + } + + private native int nativeStartDiscover(byte b1, byte b2); + + @Override + public void enableDiscovery(NfcDiscoveryParameters params, boolean restart) { + nativeStartDiscover((byte)0, (byte)0); + } + + private native int nativeStopReaderAction(); + + @Override + public void disableDiscovery() { + nativeStopReaderAction(); + } + + private native NativeLlcpConnectionlessSocket doCreateLlcpConnectionlessSocket(int nSap, + String sn); + + @Override + public LlcpConnectionlessSocket createLlcpConnectionlessSocket(int nSap, String sn) + throws LlcpException { + LlcpConnectionlessSocket socket = doCreateLlcpConnectionlessSocket(nSap, sn); + if (socket != null) { + return socket; + } else { + /* Get Error Status */ + int error = doGetLastError(); + + Log.d(TAG, "failed to create llcp socket: " + ErrorCodes.asString(error)); + + switch (error) { + case ErrorCodes.ERROR_BUFFER_TO_SMALL: + case ErrorCodes.ERROR_INSUFFICIENT_RESOURCES: + throw new LlcpException(error); + default: + throw new LlcpException(ErrorCodes.ERROR_SOCKET_CREATION); + } + } + } + + private native NativeLlcpServiceSocket doCreateLlcpServiceSocket(int nSap, String sn, int miu, + int rw, int linearBufferLength); + @Override + public LlcpServerSocket createLlcpServerSocket(int nSap, String sn, int miu, + int rw, int linearBufferLength) throws LlcpException { + LlcpServerSocket socket = doCreateLlcpServiceSocket(nSap, sn, miu, rw, linearBufferLength); + if (socket != null) { + return socket; + } else { + /* Get Error Status */ + int error = doGetLastError(); + + Log.d(TAG, "failed to create llcp socket: " + ErrorCodes.asString(error)); + + switch (error) { + case ErrorCodes.ERROR_BUFFER_TO_SMALL: + case ErrorCodes.ERROR_INSUFFICIENT_RESOURCES: + throw new LlcpException(error); + default: + throw new LlcpException(ErrorCodes.ERROR_SOCKET_CREATION); + } + } + } + + private native NativeLlcpSocket doCreateLlcpSocket(int sap, int miu, int rw, + int linearBufferLength); + @Override + public LlcpSocket createLlcpSocket(int sap, int miu, int rw, + int linearBufferLength) throws LlcpException { + LlcpSocket socket = doCreateLlcpSocket(sap, miu, rw, linearBufferLength); + if (socket != null) { + return socket; + } else { + /* Get Error Status */ + int error = doGetLastError(); + + Log.d(TAG, "failed to create llcp socket: " + ErrorCodes.asString(error)); + + switch (error) { + case ErrorCodes.ERROR_BUFFER_TO_SMALL: + case ErrorCodes.ERROR_INSUFFICIENT_RESOURCES: + throw new LlcpException(error); + default: + throw new LlcpException(ErrorCodes.ERROR_SOCKET_CREATION); + } + } + } + + @Override + public native boolean doCheckLlcp(); + + @Override + public native boolean doActivateLlcp(); + + private native void nativeResetTimeout(); + + @Override + public void resetTimeouts() { + nativeResetTimeout(); + } + + public native void nativeAbort(); + + @Override + public void doAbort() { + nativeAbort(); + } + + private native boolean nativeSetTimeout(int tech, int timeout); + @Override + public boolean setTimeout(int tech, int timeout) { + return nativeSetTimeout(tech, timeout); + } + + private native int nativeGetTimeout(int tech); + @Override + public int getTimeout(int tech) { + return nativeGetTimeout(tech); + } + + + @Override + public boolean canMakeReadOnly(int ndefType) { + // TODO check defined value + return (ndefType == Ndef.TYPE_1/* 1 */ || ndefType == Ndef.TYPE_2/* 2 */ || + ndefType == Ndef.TYPE_MIFARE_CLASSIC/* 0x65 */); + } + + + private native int nativeGetTransceiveSendMaxSize(int technology); + @Override + public int getMaxTransceiveLength(int technology) { + return nativeGetTransceiveSendMaxSize(technology); + } + + @Override + public void setP2pInitiatorModes(int modes) { + } + + @Override + public void setP2pTargetModes(int modes) { + } + + @Override + public boolean enableScreenOffSuspend() { + // Snooze mode not supported on NXP silicon + Log.i(TAG, "Snooze mode is not supported on NXP NFCC"); + return false; + } + + @Override + public boolean disableScreenOffSuspend() { + // Snooze mode not supported on NXP silicon + Log.i(TAG, "Snooze mode is not supported on NXP NFCC"); + return true; + } + + @Override + public boolean getExtendedLengthApdusSupported() { + // Not supported on the PN544 + return false; + } + + @Override + public int getDefaultLlcpMiu() { + return DEFAULT_LLCP_MIU; + } + + @Override + public int getDefaultLlcpRwSize() { + return DEFAULT_LLCP_RWSIZE; + } + + private native String nativeDump(); + @Override + public String dump() { + return nativeDump(); + } + + /** + * Notifies Ndef Message (TODO: rename into notifyTargetDiscovered) + */ + private void notifyNdefMessageListeners(NativeNfcTag tag) { + mListener.onRemoteEndpointDiscovered(tag); + } + + /** + * Notifies P2P Device detected, to activate LLCP link + */ + private void notifyLlcpLinkActivation(NativeP2pDevice device) { + mListener.onLlcpLinkActivated(device); + } + + /** + * Notifies P2P Device detected, to activate LLCP link + */ + private void notifyLlcpLinkDeactivated(NativeP2pDevice device) { + mListener.onLlcpLinkDeactivated(device); + } + + private void notifyRfFieldActivated() { + mListener.onRemoteFieldActivated(); + } + + private void notifyRfFieldDeactivated() { + mListener.onRemoteFieldDeactivated(); + } + + private native void nativeEnableReaderMode(int technologies); + public boolean enableReaderMode(int technologies) { + Log.i(TAG, "call: enableReaderMode technologies=" + technologies); + nativeEnableReaderMode(technologies); + return true; + } + + private native void nativeDisableReaderMode(); + public boolean disableReaderMode() { + Log.i(TAG, "call: disableReaderMode"); + nativeDisableReaderMode(); + return true; + } + + private void notifyConnectivityListeners() { + //mListener.onConnectivityEvent(); + } + + private void notifySeApduReceived(byte[] apdu) { + //mListener.onSeApduReceived(apdu); + } + + private void notifySeEmvCardRemoval() { + //mListener.onSeEmvCardRemoval(); + } + + private void notifySeFieldActivated() { + mListener.onRemoteFieldActivated(); + } + + private void notifySeFieldDeactivated() { + mListener.onRemoteFieldDeactivated(); + } + + private void notifySeMifareAccess(byte[] block) { + //mListener.onSeMifareAccess(block); + } + + private void notifyTargetDeselected() { + //mListener.onCardEmulationDeselected(); + } + + private void notifyTransactionListeners(byte[] aid) { + } + + private void notifyTransactionListeners(byte[] aid, byte[] data) { + //mListener.onCardEmulationAidSelected(aid, data); + } + + private void notifyUartAbnormal() { + //mListener.onUartAbnormal(); + } + + private void notifyUimTransactionListeners(byte[] aid, byte[] parameter) { + Log.e(TAG, "notifyUimTransactionListeners aid=" + aid + ", parameter=" + parameter); + byte[][] data = { aid, parameter }; + //mListener.onHciEvtTransaction(data); + } + + public native int[] doGetSecureElementList(); + + public native int doGetSecureElementTechList(); + + public native void doPrbsOff(); + + public native void doPrbsOn(int i, int j); + + public native void doSelectSecureElement(int i); + + public native void doSetEEPROM(byte[] array); + + public native void doSetSEPowerOffState(int i, boolean z); + + public native void doDeselectSecureElement(int i); + + public native int SWPSelfTest(int i); + + public int swpTest() { + return SWPSelfTest(0); + } +} diff --git a/NfcSony/sony/src/com/android/nfc/dhimpl/NativeNfcTag.java b/NfcSony/sony/src/com/android/nfc/dhimpl/NativeNfcTag.java new file mode 100644 index 0000000..8ff45e9 --- /dev/null +++ b/NfcSony/sony/src/com/android/nfc/dhimpl/NativeNfcTag.java @@ -0,0 +1,769 @@ +/* + * Copyright (C) 2010 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.nfc.dhimpl; + +import com.android.nfc.DeviceHost; +import com.android.nfc.DeviceHost.TagEndpoint; + +import android.nfc.FormatException; +import android.nfc.NdefMessage; +import android.nfc.tech.IsoDep; +import android.nfc.tech.MifareClassic; +import android.nfc.tech.MifareUltralight; +import android.nfc.tech.Ndef; +import android.nfc.tech.NfcA; +import android.nfc.tech.NfcB; +import android.nfc.tech.NfcF; +import android.nfc.tech.NfcV; +import android.nfc.tech.TagTechnology; +import android.os.Bundle; +import android.util.Log; + +/** + * Native interface to the NFC tag functions + */ +public class NativeNfcTag implements TagEndpoint { + static final boolean DBG = false; + + static final int STATUS_CODE_TARGET_LOST = 146; + + private int[] mTechList; + private int[] mTechHandles; + private int[] mTechLibNfcTypes; + private Bundle[] mTechExtras; + private byte[][] mTechPollBytes; + private byte[][] mTechActBytes; + private byte[] mUid; + private byte[] mReadData; + private byte[] mRecvData; + private int mTagMaxLength; + + // mConnectedHandle stores the *real* libnfc handle + // that we're connected to. + private int mConnectedHandle; + + // mConnectedTechIndex stores to which technology + // the upper layer stack is connected. Note that + // we may be connected to a libnfchandle without being + // connected to a technology - technology changes + // may occur runtime, whereas the underlying handle + // could stay present. Usually all technologies are on the + // same handle, with the exception of multi-protocol + // tags. + private int mConnectedTechIndex; // Index in mTechHandles + + private final String TAG = "NativeNfcTag"; + + private boolean mIsPresent; // Whether the tag is known to be still present + + private PresenceCheckWatchdog mWatchdog; + class PresenceCheckWatchdog extends Thread { + + private final DeviceHost.TagDisconnectedCallback tagDisconnectedCallback; + private int watchdogTimeout; + + private boolean isPresent = true; + private boolean isStopped = false; + private boolean isPaused = false; + private boolean doCheck = true; + + public PresenceCheckWatchdog(int presenceCheckDelay, DeviceHost.TagDisconnectedCallback callback) { + watchdogTimeout = presenceCheckDelay; + tagDisconnectedCallback = callback; + } + + public synchronized void pause() { + isPaused = true; + doCheck = false; + this.notifyAll(); + } + + public synchronized void doResume() { + isPaused = false; + // We don't want to resume presence checking immediately, + // but go through at least one more wait period. + doCheck = false; + this.notifyAll(); + } + + public synchronized void end() { + isStopped = true; + doCheck = false; + this.notifyAll(); + } + + @Override + public void run() { + if (DBG) Log.d(TAG, "Starting background presence check"); + synchronized (this) { + while (isPresent && !isStopped) { + try { + if (!isPaused) { + doCheck = true; + } + this.wait(watchdogTimeout); + if (doCheck) { + isPresent = nativePresenceCheck(); + } else { + // 1) We are paused, waiting for unpause + // 2) We just unpaused, do pres check in next iteration + // (after watchdogTimeout ms sleep) + // 3) We just set the timeout, wait for this timeout + // to expire once first. + // 4) We just stopped, exit loop anyway + } + } catch (InterruptedException e) { + // Activity detected, loop + } + } + } + + synchronized (NativeNfcTag.this) { + mIsPresent = false; + } + // Restart the polling loop + + Log.d(TAG, "Tag lost, restarting polling loop"); + if (tagDisconnectedCallback != null) { + tagDisconnectedCallback.onTagDisconnected(mConnectedHandle); + } + if (DBG) Log.d(TAG, "Stopping background presence check"); + } + } + + private native boolean nativeConnectTech(); + public synchronized int connectWithStatus(int technology) { + Log.i(TAG, "call: connectWithStatus technology=" + technology); + if (mWatchdog != null) { + mWatchdog.pause(); + } + int status = -1; + for (int i = 0; i < mTechList.length; i++) { + if (mTechList[i] == technology) { + boolean ret = nativeConnectTech(); + if (ret) { + mConnectedHandle = mTechHandles[i]; + mConnectedTechIndex = i; + status = 0; + } + break; + } + } + if (mWatchdog != null) { + mWatchdog.doResume(); + } + return status; + } + + @Override + public synchronized boolean connect(int technology) { + Log.i(TAG, "call: connect technology=" + technology); + return connectWithStatus(technology) == 0; + } + + @Override + public synchronized void startPresenceChecking(int presenceCheckDelay, + DeviceHost.TagDisconnectedCallback callback) { + Log.i(TAG, "call: startPresenceChecking presenceCheckDelay=" + presenceCheckDelay); + // Once we start presence checking, we allow the upper layers + // to know the tag is in the field. + mIsPresent = true; + if (mConnectedTechIndex == -1 && mTechList.length > 0) { + connect(mTechList[0]); + } + if (mWatchdog == null) { + mWatchdog = new PresenceCheckWatchdog(presenceCheckDelay, callback); + mWatchdog.start(); + } + } + + @Override + public synchronized boolean isPresent() { + // Returns whether the tag is still in the field to the best + // of our knowledge. + return mIsPresent; + } + + @Override + public synchronized boolean disconnect() { + Log.i(TAG, "call: disconnect"); + boolean result = false; + + mIsPresent = false; + if (mWatchdog != null) { + // Watchdog has already disconnected or will do it + mWatchdog.end(); + try { + mWatchdog.join(); + } catch (InterruptedException e) { + // Should never happen. + } + mWatchdog = null; + result = true; + } else { + result = true; + } + + mConnectedTechIndex = -1; + mConnectedHandle = -1; + return result; + } + + public synchronized int reconnectWithStatus() { + Log.i(TAG, "call: reconnectWithStatus"); + return 0; + } + + @Override + public synchronized boolean reconnect() { + Log.i(TAG, "call: reconnect"); + return reconnectWithStatus() == 0; + } + + public synchronized int reconnectWithStatus(int handle) { + Log.i(TAG, "call: reconnectWithStatus handle=" + handle); + return 0; + } + + private native int nativeGetTransceiveRcvMaxSize(int tech); + private native int nativeTransceiveData(byte tech, byte[] data, int len); + @Override + public synchronized byte[] transceive(byte[] data, boolean raw, int[] returnCode) { + Log.i(TAG, "call: transceive data=" + data + ", raw=" + raw + " returnCode=" + returnCode); + if (mWatchdog != null) { + mWatchdog.pause(); + } + int recvLength = nativeGetTransceiveRcvMaxSize(mTechList[mConnectedTechIndex]); + mRecvData = new byte[recvLength]; + nativeTransceiveData((byte)mTechList[mConnectedTechIndex], data, data.length); + if (mWatchdog != null) { + mWatchdog.doResume(); + } + return mRecvData; + } + + private native int nativeCheckNdefData(int[] ndefinfo); + private synchronized int checkNdefWithStatus(int[] ndefinfo) { + Log.i(TAG, "call: checkNdefWithStatus ndefinfo=" + ndefinfo); + if (mWatchdog != null) { + mWatchdog.pause(); + } + int ret = nativeCheckNdefData(ndefinfo); + mTagMaxLength = ndefinfo[0]; + if (mWatchdog != null) { + mWatchdog.doResume(); + } + return ret; + } + + @Override + public synchronized boolean checkNdef(int[] ndefinfo) { + Log.i(TAG, "call: checkNdef ndefinfo=" + ndefinfo); + return checkNdefWithStatus(ndefinfo) == 0; + } + + private native int nativeReadNdefData(); + @Override + public synchronized byte[] readNdef() { + Log.i(TAG, "call: readNdef"); + if (mWatchdog != null) { + mWatchdog.pause(); + } + mReadData = new byte[mTagMaxLength]; + nativeReadNdefData(); + if (mWatchdog != null) { + mWatchdog.doResume(); + } + return mReadData; + } + + private native int nativeWriteNdefData(byte[] buf, int bufLen); + @Override + public synchronized boolean writeNdef(byte[] buf) { + Log.i(TAG, "call: writeNdef buf=" + buf); + if (mWatchdog != null) { + mWatchdog.pause(); + } + boolean result = nativeWriteNdefData(buf, buf.length) == 0; + if (mWatchdog != null) { + mWatchdog.doResume(); + } + return result; + } + + native boolean nativePresenceCheck(); + @Override + public synchronized boolean presenceCheck() { + Log.i(TAG, "call: presenceCheck"); + if (mWatchdog != null) { + mWatchdog.pause(); + } + boolean result = nativePresenceCheck(); + if (mWatchdog != null) { + mWatchdog.doResume(); + } + return result; + } + + native int nativeFormatable(); + @Override + public synchronized boolean formatNdef(byte[] key) { + Log.i(TAG, "call: formatNdef key=" + key); + if (mWatchdog != null) { + mWatchdog.pause(); + } + boolean result = nativeFormatable() == 0; + if (mWatchdog != null) { + mWatchdog.doResume(); + } + return result; + } + + native int nativeSetNdefReadOnly(); + @Override + public synchronized boolean makeReadOnly() { + Log.i(TAG, "call: makeReadOnly"); + if (mWatchdog != null) { + mWatchdog.pause(); + } + boolean result = nativeSetNdefReadOnly() == 0; + if (mWatchdog != null) { + mWatchdog.doResume(); + } + return result; + } + + native boolean nativeIsFormatable(); + @Override + public synchronized boolean isNdefFormatable() { + Log.i(TAG, "call: isNdefFormatable"); + return nativeIsFormatable(); + } + + @Override + public int getHandle() { + // This is just a handle for the clients; it can simply use the first + // technology handle we have. + if (mTechHandles.length > 0) { + return mTechHandles[0]; + } else { + return 0; + } + } + + @Override + public byte[] getUid() { + return mUid; + } + + @Override + public int[] getTechList() { + return mTechList; + } + + private int getConnectedHandle() { + return mConnectedHandle; + } + + private int getConnectedLibNfcType() { + if (mConnectedTechIndex != -1 && mConnectedTechIndex < mTechLibNfcTypes.length) { + return mTechLibNfcTypes[mConnectedTechIndex]; + } else { + return 0; + } + } + + @Override + public int getConnectedTechnology() { + if (mConnectedTechIndex != -1 && mConnectedTechIndex < mTechList.length) { + return mTechList[mConnectedTechIndex]; + } else { + return 0; + } + } + + private int getNdefType(int libnfctype, int javatype) { + Log.i(TAG, "call: getNdefType libnfctype=" + libnfctype + " javatype=" + javatype); + return mTechLibNfcTypes[mConnectedTechIndex]; + } + + private void addTechnology(int tech, int handle, int libnfctype) { + Log.i(TAG, "call: getConnectedTechnology tech=" + tech + ", handle=" + handle + " libnfctype=" + libnfctype); + int[] mNewTechList = new int[mTechList.length + 1]; + System.arraycopy(mTechList, 0, mNewTechList, 0, mTechList.length); + mNewTechList[mTechList.length] = tech; + mTechList = mNewTechList; + + int[] mNewHandleList = new int[mTechHandles.length + 1]; + System.arraycopy(mTechHandles, 0, mNewHandleList, 0, mTechHandles.length); + mNewHandleList[mTechHandles.length] = handle; + mTechHandles = mNewHandleList; + + int[] mNewTypeList = new int[mTechLibNfcTypes.length + 1]; + System.arraycopy(mTechLibNfcTypes, 0, mNewTypeList, 0, mTechLibNfcTypes.length); + mNewTypeList[mTechLibNfcTypes.length] = libnfctype; + mTechLibNfcTypes = mNewTypeList; + } + + @Override + public void removeTechnology(int tech) { + Log.i(TAG, "call: removeTechnology tech=" + tech); + synchronized (this) { + int techIndex = getTechIndex(tech); + if (techIndex != -1) { + int[] mNewTechList = new int[mTechList.length - 1]; + System.arraycopy(mTechList, 0, mNewTechList, 0, techIndex); + System.arraycopy(mTechList, techIndex + 1, mNewTechList, techIndex, + mTechList.length - techIndex - 1); + mTechList = mNewTechList; + + int[] mNewHandleList = new int[mTechHandles.length - 1]; + System.arraycopy(mTechHandles, 0, mNewHandleList, 0, techIndex); + System.arraycopy(mTechHandles, techIndex + 1, mNewTechList, techIndex, + mTechHandles.length - techIndex - 1); + mTechHandles = mNewHandleList; + + int[] mNewTypeList = new int[mTechLibNfcTypes.length - 1]; + System.arraycopy(mTechLibNfcTypes, 0, mNewTypeList, 0, techIndex); + System.arraycopy(mTechLibNfcTypes, techIndex + 1, mNewTypeList, techIndex, + mTechLibNfcTypes.length - techIndex - 1); + mTechLibNfcTypes = mNewTypeList; + } + } + } + + public void addNdefFormatableTechnology(int handle, int libnfcType) { + Log.i(TAG, "call: addNdefFormatableTechnology handle=" + handle + ", libnfcType=" + libnfcType); + synchronized (this) { + addTechnology(TagTechnology.NDEF_FORMATABLE, handle, libnfcType); + } + } + + // This method exists to "patch in" the ndef technologies, + // which is done inside Java instead of the native JNI code. + // To not create some nasty dependencies on the order on which things + // are called (most notably getTechExtras()), it needs some additional + // checking. + public void addNdefTechnology(NdefMessage msg, int handle, int libnfcType, + int javaType, int maxLength, int cardState) { + Log.i(TAG, "call: addNdefTechnology msg=" + msg + ", handle=" + handle + ", libnfcType=" + libnfcType + " javaType=" + javaType + " maxLength=" + maxLength + ", cardState=" + cardState); + synchronized (this) { + addTechnology(TagTechnology.NDEF, handle, libnfcType); + + Bundle extras = new Bundle(); + extras.putParcelable(Ndef.EXTRA_NDEF_MSG, msg); + extras.putInt(Ndef.EXTRA_NDEF_MAXLENGTH, maxLength); + extras.putInt(Ndef.EXTRA_NDEF_CARDSTATE, cardState); + extras.putInt(Ndef.EXTRA_NDEF_TYPE, getNdefType(libnfcType, javaType)); + + if (mTechExtras == null) { + // This will build the tech extra's for the first time, + // including a NULL ref for the NDEF tech we generated above. + Bundle[] builtTechExtras = getTechExtras(); + builtTechExtras[builtTechExtras.length - 1] = extras; + } + else { + // Tech extras were built before, patch the NDEF one in + Bundle[] oldTechExtras = getTechExtras(); + Bundle[] newTechExtras = new Bundle[oldTechExtras.length + 1]; + System.arraycopy(oldTechExtras, 0, newTechExtras, 0, oldTechExtras.length); + newTechExtras[oldTechExtras.length] = extras; + mTechExtras = newTechExtras; + } + + + } + } + + private int getTechIndex(int tech) { + Log.i(TAG, "call: getTechIndex tech=" + tech); + int techIndex = -1; + for (int i = 0; i < mTechList.length; i++) { + if (mTechList[i] == tech) { + techIndex = i; + break; + } + } + return techIndex; + } + + private boolean hasTech(int tech) { + Log.i(TAG, "call: hasTech tech=" + tech); + boolean hasTech = false; + for (int i = 0; i < mTechList.length; i++) { + if (mTechList[i] == tech) { + hasTech = true; + break; + } + } + return hasTech; + } + + private boolean hasTechOnHandle(int tech, int handle) { + Log.i(TAG, "call: hasTechOnHandle tech=" + tech + ", handle=" + handle); + boolean hasTech = false; + for (int i = 0; i < mTechList.length; i++) { + if (mTechList[i] == tech && mTechHandles[i] == handle) { + hasTech = true; + break; + } + } + return hasTech; + } + + private boolean isUltralightC() { + Log.i(TAG, "call: isUltralightC"); + /* Make a best-effort attempt at classifying ULTRALIGHT + * vs ULTRALIGHT-C (based on NXP's public AN1303). + * The memory layout is as follows: + * Page # BYTE1 BYTE2 BYTE3 BYTE4 + * 2 INT1 INT2 LOCK LOCK + * 3 OTP OTP OTP OTP (NDEF CC if NDEF-formatted) + * 4 DATA DATA DATA DATA (version info if factory-state) + * + * Read four blocks from page 2, which will get us both + * the lock page, the OTP page and the version info. + */ + boolean isUltralightC = false; + byte[] readCmd = { 0x30, 0x02 }; + int[] retCode = new int[2]; + byte[] respData = transceive(readCmd, false, retCode); + if (respData != null && respData.length == 16) { + // Check the lock bits (last 2 bytes in page2) + // and the OTP bytes (entire page 3) + if (respData[2] == 0 && respData[3] == 0 && respData[4] == 0 && + respData[5] == 0 && respData[6] == 0 && respData[7] == 0) { + // Very likely to be a blank card, look at version info + // in page 4. + if ((respData[8] == (byte)0x02) && respData[9] == (byte)0x00) { + // This is Ultralight-C + isUltralightC = true; + } else { + // 0xFF 0xFF would indicate Ultralight, but we also use Ultralight + // as a fallback if it's anything else + isUltralightC = false; + } + } else { + // See if we can find the NDEF CC in the OTP page and if it's + // smaller than major version two + if (respData[4] == (byte)0xE1 && ((respData[5] & 0xff) < 0x20)) { + // OK, got NDEF. Technically we'd have to search for the + // NDEF TLV as well. However, this would add too much + // time for discovery and we can make already make a good guess + // with the data we have here. Byte 2 of the OTP page + // indicates the size of the tag - 0x06 is UL, anything + // above indicates UL-C. + if ((respData[6] & 0xff) > 0x06) { + isUltralightC = true; + } + } else { + // Fall back to ultralight + isUltralightC = false; + } + } + } + return isUltralightC; + } + + @Override + public Bundle[] getTechExtras() { + Log.i(TAG, "call: getTechExtras"); + synchronized (this) { + if (mTechExtras != null) return mTechExtras; + mTechExtras = new Bundle[mTechList.length]; + for (int i = 0; i < mTechList.length; i++) { + Bundle extras = new Bundle(); + switch (mTechList[i]) { + case TagTechnology.NFC_A: { + byte[] actBytes = mTechActBytes[i]; + if ((actBytes != null) && (actBytes.length > 0)) { + extras.putShort(NfcA.EXTRA_SAK, (short) (actBytes[0] & (short) 0xFF)); + } else { + // Unfortunately Jewel doesn't have act bytes, + // ignore this case. + } + extras.putByteArray(NfcA.EXTRA_ATQA, mTechPollBytes[i]); + break; + } + + case TagTechnology.NFC_B: { + // What's returned from the PN544 is actually: + // 4 bytes app data + // 3 bytes prot info + byte[] appData = new byte[4]; + byte[] protInfo = new byte[3]; + if (mTechPollBytes[i].length >= 7) { + System.arraycopy(mTechPollBytes[i], 0, appData, 0, 4); + System.arraycopy(mTechPollBytes[i], 4, protInfo, 0, 3); + + extras.putByteArray(NfcB.EXTRA_APPDATA, appData); + extras.putByteArray(NfcB.EXTRA_PROTINFO, protInfo); + } + break; + } + + case TagTechnology.NFC_F: { + byte[] pmm = new byte[8]; + byte[] sc = new byte[2]; + if (mTechPollBytes[i].length >= 8) { + // At least pmm is present + System.arraycopy(mTechPollBytes[i], 0, pmm, 0, 8); + extras.putByteArray(NfcF.EXTRA_PMM, pmm); + } + if (mTechPollBytes[i].length == 10) { + System.arraycopy(mTechPollBytes[i], 8, sc, 0, 2); + extras.putByteArray(NfcF.EXTRA_SC, sc); + } + break; + } + + case TagTechnology.ISO_DEP: { + if (hasTech(TagTechnology.NFC_A)) { + extras.putByteArray(IsoDep.EXTRA_HIST_BYTES, mTechActBytes[i]); + } + else { + extras.putByteArray(IsoDep.EXTRA_HI_LAYER_RESP, mTechActBytes[i]); + } + break; + } + + case TagTechnology.NFC_V: { + // First byte response flags, second byte DSFID + if (mTechPollBytes[i] != null && mTechPollBytes[i].length >= 2) { + extras.putByte(NfcV.EXTRA_RESP_FLAGS, mTechPollBytes[i][0]); + extras.putByte(NfcV.EXTRA_DSFID, mTechPollBytes[i][1]); + } + break; + } + + case TagTechnology.MIFARE_ULTRALIGHT: { + boolean isUlc = isUltralightC(); + extras.putBoolean(MifareUltralight.EXTRA_IS_UL_C, isUlc); + break; + } + + default: { + // Leave the entry in the array null + continue; + } + } + mTechExtras[i] = extras; + } + return mTechExtras; + } + } + + @Override + public NdefMessage findAndReadNdef() { + Log.i(TAG, "call: findAndReadNdef"); + // Try to find NDEF on any of the technologies. + int[] technologies = getTechList(); + int[] handles = mTechHandles; + NdefMessage ndefMsg = null; + boolean foundFormattable = false; + int formattableHandle = 0; + int formattableLibNfcType = 0; + int status; + + for (int techIndex = 0; techIndex < technologies.length; techIndex++) { + // have we seen this handle before? + for (int i = 0; i < techIndex; i++) { + if (handles[i] == handles[techIndex]) { + continue; // don't check duplicate handles + } + } + + status = connectWithStatus(technologies[techIndex]); + if (status != 0) { + Log.d(TAG, "Connect Failed - status = "+ status); + if (status == STATUS_CODE_TARGET_LOST) { + break; + } + continue; // try next handle + } + // Check if this type is NDEF formatable + if (!foundFormattable) { + if (isNdefFormatable()) { + foundFormattable = true; + formattableHandle = getConnectedHandle(); + formattableLibNfcType = getConnectedLibNfcType(); + // We'll only add formattable tech if no ndef is + // found - this is because libNFC refuses to format + // an already NDEF formatted tag. + } + reconnect(); + } + + int[] ndefinfo = new int[2]; + status = checkNdefWithStatus(ndefinfo); + if (status != 0) { + Log.d(TAG, "Check NDEF Failed - status = " + status); + if (status == STATUS_CODE_TARGET_LOST) { + break; + } + continue; // try next handle + } + + // found our NDEF handle + boolean generateEmptyNdef = false; + + int supportedNdefLength = ndefinfo[0]; + int cardState = ndefinfo[1]; + byte[] buff = readNdef(); + if (buff != null) { + try { + ndefMsg = new NdefMessage(buff); + addNdefTechnology(ndefMsg, + getConnectedHandle(), + getConnectedLibNfcType(), + getConnectedTechnology(), + supportedNdefLength, cardState); + reconnect(); + } catch (FormatException e) { + // Create an intent anyway, without NDEF messages + generateEmptyNdef = true; + } + } else { + generateEmptyNdef = true; + } + + if (generateEmptyNdef) { + ndefMsg = null; + addNdefTechnology(null, + getConnectedHandle(), + getConnectedLibNfcType(), + getConnectedTechnology(), + supportedNdefLength, cardState); + foundFormattable = false; + reconnect(); + } + break; + } + + if (ndefMsg == null && foundFormattable) { + // Tag is not NDEF yet, and found a formattable target, + // so add formattable tech to tech list. + addNdefFormatableTechnology( + formattableHandle, + formattableLibNfcType); + } + + return ndefMsg; + } + + native boolean nativeTagDisconnect(); +} diff --git a/NfcSony/sony/src/com/android/nfc/dhimpl/NativeP2pDevice.java b/NfcSony/sony/src/com/android/nfc/dhimpl/NativeP2pDevice.java new file mode 100644 index 0000000..85949ad --- /dev/null +++ b/NfcSony/sony/src/com/android/nfc/dhimpl/NativeP2pDevice.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2010 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.nfc.dhimpl; + +import com.android.nfc.DeviceHost.NfcDepEndpoint; + +/** + * Native interface to the P2P Initiator functions + */ +public class NativeP2pDevice implements NfcDepEndpoint { + + private int mHandle; + + private int mMode; + + private byte mLlcpVersion; + + private byte[] mGeneralBytes; + + private boolean mConnectedFlag = false; + + @Override + public byte[] receive() { + return null; + } + + @Override + public boolean send(byte[] data) { + return false; + } + + @Override + public boolean connect() { + return true; + } + + private native boolean nativeDisconnect(boolean connectedFlag); + @Override + public boolean disconnect() { + mConnectedFlag = nativeDisconnect(mConnectedFlag); + return mConnectedFlag; + } + + @Override + public byte[] transceive(byte[] data) { + return null; + } + + @Override + public int getHandle() { + return mHandle; + } + + @Override + public int getMode() { + return mMode; + } + + @Override + public byte[] getGeneralBytes() { + return mGeneralBytes; + } + + @Override + public byte getLlcpVersion() { + return mLlcpVersion; + } +} diff --git a/NfcSony/src/com/android/nfc/BeamShareActivity.java b/NfcSony/src/com/android/nfc/BeamShareActivity.java new file mode 100644 index 0000000..cff601c --- /dev/null +++ b/NfcSony/src/com/android/nfc/BeamShareActivity.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2014 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.nfc; + +import java.util.ArrayList; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.ClipData; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.nfc.BeamShareData; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.NfcAdapter; +import android.os.Bundle; +import android.os.UserHandle; +import android.util.Log; +import android.webkit.URLUtil; + +import com.android.internal.R; + +/** + * This class is registered by NfcService to handle + * ACTION_SHARE intents. It tries to parse data contained + * in ACTION_SHARE intents in either a content/file Uri, + * which can be sent using NFC handover, or alternatively + * it tries to parse texts and URLs to store them in a simple + * Text or Uri NdefRecord. The data is then passed on into + * NfcService to transmit on NFC tap. + * + */ +public class BeamShareActivity extends Activity { + static final String TAG ="BeamShareActivity"; + static final boolean DBG = false; + + ArrayList mUris; + NdefMessage mNdefMessage; + NfcAdapter mNfcAdapter; + Intent mLaunchIntent; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mUris = new ArrayList(); + mNdefMessage = null; + mNfcAdapter = NfcAdapter.getDefaultAdapter(this); + mLaunchIntent = getIntent(); + if (mNfcAdapter == null) { + Log.e(TAG, "NFC adapter not present."); + finish(); + } else { + if (!mNfcAdapter.isEnabled()) { + showNfcDialogAndExit(com.android.nfc.R.string.beam_requires_nfc_enabled); + } else { + parseShareIntentAndFinish(mLaunchIntent); + } + } + } + + + private void showNfcDialogAndExit(int msgId) { + IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED); + registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, null); + + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this, + AlertDialog.THEME_DEVICE_DEFAULT_LIGHT); + dialogBuilder.setMessage(msgId); + dialogBuilder.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialogInterface) { + finish(); + } + }); + dialogBuilder.setPositiveButton(R.string.yes, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + if (!mNfcAdapter.isEnabled()) { + mNfcAdapter.enable(); + // Wait for enable broadcast + } else { + parseShareIntentAndFinish(mLaunchIntent); + } + } + }); + dialogBuilder.setNegativeButton(R.string.no, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + finish(); + } + }); + dialogBuilder.show(); + } + + void tryUri(Uri uri) { + if (uri.getScheme().equalsIgnoreCase("content") || + uri.getScheme().equalsIgnoreCase("file")) { + // Typically larger data, this can be shared using NFC handover + mUris.add(uri); + } else { + // Just put this Uri in an NDEF message + mNdefMessage = new NdefMessage(NdefRecord.createUri(uri)); + } + } + + void tryText(String text) { + if (URLUtil.isValidUrl(text)) { + Uri parsedUri = Uri.parse(text); + tryUri(parsedUri); + } else { + mNdefMessage = new NdefMessage(NdefRecord.createTextRecord(null, text)); + } + } + + public void parseShareIntentAndFinish(Intent intent) { + if (intent == null || (!intent.getAction().equalsIgnoreCase(Intent.ACTION_SEND) && + !intent.getAction().equalsIgnoreCase(Intent.ACTION_SEND_MULTIPLE))) return; + + // First, see if the intent contains clip-data, and if so get data from there + ClipData clipData = intent.getClipData(); + if (clipData != null && clipData.getItemCount() > 0) { + for (int i = 0; i < clipData.getItemCount(); i++) { + ClipData.Item item = clipData.getItemAt(i); + // First try to get an Uri + Uri uri = item.getUri(); + String plainText = item.coerceToText(this).toString(); + if (uri != null) { + if (DBG) Log.d(TAG, "Found uri in ClipData."); + tryUri(uri); + } else if (plainText != null) { + if (DBG) Log.d(TAG, "Found text in ClipData."); + tryText(plainText); + } else { + if (DBG) Log.d(TAG, "Did not find any shareable data in ClipData."); + } + } + } else { + if (intent.getAction().equalsIgnoreCase(Intent.ACTION_SEND)) { + final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); + final CharSequence text = intent.getCharSequenceExtra(Intent.EXTRA_TEXT); + if (uri != null) { + if (DBG) Log.d(TAG, "Found uri in ACTION_SEND intent."); + tryUri(uri); + } else if (text != null) { + if (DBG) Log.d(TAG, "Found EXTRA_TEXT in ACTION_SEND intent."); + tryText(text.toString()); + } else { + if (DBG) Log.d(TAG, "Did not find any shareable data in ACTION_SEND intent."); + } + } else { + final ArrayList uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); + final ArrayList texts = intent.getCharSequenceArrayListExtra( + Intent.EXTRA_TEXT); + + if (uris != null && uris.size() > 0) { + for (Uri uri : uris) { + if (DBG) Log.d(TAG, "Found uri in ACTION_SEND_MULTIPLE intent."); + tryUri(uri); + } + } else if (texts != null && texts.size() > 0) { + // Try EXTRA_TEXT, but just for the first record + if (DBG) Log.d(TAG, "Found text in ACTION_SEND_MULTIPLE intent."); + tryText(texts.get(0).toString()); + } else { + if (DBG) Log.d(TAG, "Did not find any shareable data in " + + "ACTION_SEND_MULTIPLE intent."); + } + } + } + + BeamShareData shareData = null; + UserHandle myUserHandle = new UserHandle(UserHandle.myUserId()); + if (mUris.size() > 0) { + // Uris have our first preference for sharing + Uri[] uriArray = new Uri[mUris.size()]; + int numValidUris = 0; + for (Uri uri : mUris) { + try { + grantUriPermission("com.android.nfc", uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); + uriArray[numValidUris++] = uri; + if (DBG) Log.d(TAG, "Found uri: " + uri); + } catch (SecurityException e) { + Log.e(TAG, "Security exception granting uri permission to NFC process."); + numValidUris = 0; + break; + } + } + if (numValidUris > 0) { + shareData = new BeamShareData(null, uriArray, myUserHandle, 0); + } else { + // No uris left + shareData = new BeamShareData(null, null, myUserHandle, 0); + } + } else if (mNdefMessage != null) { + shareData = new BeamShareData(mNdefMessage, null, myUserHandle, 0); + if (DBG) Log.d(TAG, "Created NDEF message:" + mNdefMessage.toString()); + } else { + if (DBG) Log.d(TAG, "Could not find any data to parse."); + // Activity may have set something to share over NFC, so pass on anyway + shareData = new BeamShareData(null, null, myUserHandle, 0); + } + mNfcAdapter.invokeBeam(shareData); + finish(); + } + + final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (NfcAdapter.ACTION_ADAPTER_STATE_CHANGED.equals(intent.getAction())) { + int state = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE, + NfcAdapter.STATE_OFF); + if (state == NfcAdapter.STATE_ON) { + parseShareIntentAndFinish(mLaunchIntent); + } + } + } + }; +} diff --git a/NfcSony/src/com/android/nfc/ConfirmConnectToWifiNetworkActivity.java b/NfcSony/src/com/android/nfc/ConfirmConnectToWifiNetworkActivity.java new file mode 100644 index 0000000..5284796 --- /dev/null +++ b/NfcSony/src/com/android/nfc/ConfirmConnectToWifiNetworkActivity.java @@ -0,0 +1,155 @@ +package com.android.nfc; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.Handler; +import android.view.View; +import android.widget.Toast; + +public class ConfirmConnectToWifiNetworkActivity extends Activity + implements View.OnClickListener, DialogInterface.OnDismissListener { + + public static final int ENABLE_WIFI_TIMEOUT_MILLIS = 5000; + private WifiConfiguration mCurrentWifiConfiguration; + private AlertDialog mAlertDialog; + private boolean mEnableWifiInProgress; + private Handler mHandler; + + @Override + protected void onCreate(Bundle savedInstanceState) { + + Intent intent = getIntent(); + mCurrentWifiConfiguration = + intent.getParcelableExtra(NfcWifiProtectedSetup.EXTRA_WIFI_CONFIG); + + String printableSsid = mCurrentWifiConfiguration.getPrintableSsid(); + mAlertDialog = new AlertDialog.Builder(this, AlertDialog.THEME_DEVICE_DEFAULT_LIGHT) + .setTitle(R.string.title_connect_to_network) + .setMessage( + String.format(getResources().getString(R.string.prompt_connect_to_network), + printableSsid)) + .setOnDismissListener(this) + .setNegativeButton(com.android.internal.R.string.cancel, null) + .setPositiveButton(R.string.wifi_connect, null) + .create(); + + mEnableWifiInProgress = false; + mHandler = new Handler(); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); + registerReceiver(mBroadcastReceiver, intentFilter); + + mAlertDialog.show(); + + super.onCreate(savedInstanceState); + + mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this); + } + + + @Override + public void onClick(View v) { + WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); + + if (!wifiManager.isWifiEnabled()) { + wifiManager.setWifiEnabled(true); + mEnableWifiInProgress = true; + + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + if (getAndClearEnableWifiInProgress()) { + showFailToast(); + ConfirmConnectToWifiNetworkActivity.this.finish(); + } + } + }, ENABLE_WIFI_TIMEOUT_MILLIS); + + } else { + doConnect(wifiManager); + } + + mAlertDialog.dismiss(); + } + + private void doConnect(WifiManager wifiManager) { + int networkId = wifiManager.addNetwork(mCurrentWifiConfiguration); + + if (networkId < 0) { + showFailToast(); + } else { + + wifiManager.connect(networkId, + new WifiManager.ActionListener() { + @Override + public void onSuccess() { + Toast.makeText(ConfirmConnectToWifiNetworkActivity.this, + R.string.status_wifi_connected, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onFailure(int reason) { + showFailToast(); + } + }); + } + } + + + private void showFailToast() { + Toast.makeText(ConfirmConnectToWifiNetworkActivity.this, + R.string.status_unable_to_connect, Toast.LENGTH_SHORT).show(); + } + + @Override + public void onDismiss(DialogInterface dialog) { + if (!mEnableWifiInProgress) { + finish(); + } + } + + + @Override + protected void onDestroy() { + ConfirmConnectToWifiNetworkActivity.this.unregisterReceiver(mBroadcastReceiver); + super.onDestroy(); + } + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) { + int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 0); + if (mCurrentWifiConfiguration != null + && wifiState == WifiManager.WIFI_STATE_ENABLED) { + if (getAndClearEnableWifiInProgress()) { + doConnect( + (WifiManager) ConfirmConnectToWifiNetworkActivity.this + .getSystemService(Context.WIFI_SERVICE)); + } + } + } + } + }; + + private boolean getAndClearEnableWifiInProgress() { + boolean enableWifiInProgress; + + synchronized (this) { + enableWifiInProgress = mEnableWifiInProgress; + mEnableWifiInProgress = false; + } + + return enableWifiInProgress; + } +} diff --git a/NfcSony/src/com/android/nfc/DeviceHost.java b/NfcSony/src/com/android/nfc/DeviceHost.java new file mode 100644 index 0000000..21d720e --- /dev/null +++ b/NfcSony/src/com/android/nfc/DeviceHost.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2011 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.nfc; + +import android.annotation.Nullable; +import android.nfc.NdefMessage; +import android.os.Bundle; + +import java.io.IOException; + +public interface DeviceHost { + public interface DeviceHostListener { + public void onRemoteEndpointDiscovered(TagEndpoint tag); + + /** + */ + public void onHostCardEmulationActivated(); + public void onHostCardEmulationData(byte[] data); + public void onHostCardEmulationDeactivated(); + + /** + * Notifies P2P Device detected, to activate LLCP link + */ + public void onLlcpLinkActivated(NfcDepEndpoint device); + + /** + * Notifies P2P Device detected, to activate LLCP link + */ + public void onLlcpLinkDeactivated(NfcDepEndpoint device); + + public void onLlcpFirstPacketReceived(NfcDepEndpoint device); + + public void onRemoteFieldActivated(); + + public void onRemoteFieldDeactivated(); + } + + public interface TagEndpoint { + boolean connect(int technology); + boolean reconnect(); + boolean disconnect(); + + boolean presenceCheck(); + boolean isPresent(); + void startPresenceChecking(int presenceCheckDelay, + @Nullable TagDisconnectedCallback callback); + + int[] getTechList(); + void removeTechnology(int tech); // TODO remove this one + Bundle[] getTechExtras(); + byte[] getUid(); + int getHandle(); + + byte[] transceive(byte[] data, boolean raw, int[] returnCode); + + boolean checkNdef(int[] out); + byte[] readNdef(); + boolean writeNdef(byte[] data); + NdefMessage findAndReadNdef(); + boolean formatNdef(byte[] key); + boolean isNdefFormatable(); + boolean makeReadOnly(); + + int getConnectedTechnology(); + } + + public interface TagDisconnectedCallback { + void onTagDisconnected(long handle); + } + + public interface NfceeEndpoint { + // TODO flesh out multi-EE and use this + } + + public interface NfcDepEndpoint { + + /** + * Peer-to-Peer Target + */ + public static final short MODE_P2P_TARGET = 0x00; + /** + * Peer-to-Peer Initiator + */ + public static final short MODE_P2P_INITIATOR = 0x01; + /** + * Invalid target mode + */ + public static final short MODE_INVALID = 0xff; + + public byte[] receive(); + + public boolean send(byte[] data); + + public boolean connect(); + + public boolean disconnect(); + + public byte[] transceive(byte[] data); + + public int getHandle(); + + public int getMode(); + + public byte[] getGeneralBytes(); + + public byte getLlcpVersion(); + } + + public interface LlcpSocket { + public void connectToSap(int sap) throws IOException; + + public void connectToService(String serviceName) throws IOException; + + public void close() throws IOException; + + public void send(byte[] data) throws IOException; + + public int receive(byte[] recvBuff) throws IOException; + + public int getRemoteMiu(); + + public int getRemoteRw(); + + public int getLocalSap(); + + public int getLocalMiu(); + + public int getLocalRw(); + } + + public interface LlcpServerSocket { + public LlcpSocket accept() throws IOException, LlcpException; + + public void close() throws IOException; + } + + public interface LlcpConnectionlessSocket { + public int getLinkMiu(); + + public int getSap(); + + public void send(int sap, byte[] data) throws IOException; + + public LlcpPacket receive() throws IOException; + + public void close() throws IOException; + } + + /** + * Called at boot if NFC is disabled to give the device host an opportunity + * to check the firmware version to see if it needs updating. Normally the firmware version + * is checked during {@link #initialize(boolean enableScreenOffSuspend)}, + * but the firmware may need to be updated after an OTA update. + * + *

This is called from a thread + * that may block for long periods of time during the update process. + */ + public void checkFirmware(); + + public boolean initialize(); + + public boolean deinitialize(); + + public String getName(); + + public void enableDiscovery(NfcDiscoveryParameters params, boolean restart); + + public void disableDiscovery(); + + public boolean sendRawFrame(byte[] data); + + public boolean routeAid(byte[] aid, int route); + + public boolean unrouteAid(byte[] aid); + + public boolean commitRouting(); + + public LlcpConnectionlessSocket createLlcpConnectionlessSocket(int nSap, String sn) + throws LlcpException; + + public LlcpServerSocket createLlcpServerSocket(int nSap, String sn, int miu, + int rw, int linearBufferLength) throws LlcpException; + + public LlcpSocket createLlcpSocket(int sap, int miu, int rw, + int linearBufferLength) throws LlcpException; + + public boolean doCheckLlcp(); + + public boolean doActivateLlcp(); + + public void resetTimeouts(); + + public boolean setTimeout(int technology, int timeout); + + public int getTimeout(int technology); + + public void doAbort(); + + boolean canMakeReadOnly(int technology); + + int getMaxTransceiveLength(int technology); + + void setP2pInitiatorModes(int modes); + + void setP2pTargetModes(int modes); + + boolean getExtendedLengthApdusSupported(); + + int getDefaultLlcpMiu(); + + int getDefaultLlcpRwSize(); + + String dump(); + + boolean enableScreenOffSuspend(); + + boolean disableScreenOffSuspend(); +} diff --git a/NfcSony/src/com/android/nfc/ForegroundUtils.java b/NfcSony/src/com/android/nfc/ForegroundUtils.java new file mode 100644 index 0000000..0202356 --- /dev/null +++ b/NfcSony/src/com/android/nfc/ForegroundUtils.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2014 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.nfc; + +import java.util.ArrayList; +import java.util.List; + +import android.app.ActivityManagerNative; +import android.app.IActivityManager; +import android.app.IProcessObserver; +import android.os.RemoteException; +import android.util.Log; +import android.util.SparseArray; +import android.util.SparseBooleanArray; + +public class ForegroundUtils extends IProcessObserver.Stub { + static final boolean DBG = false; + private final String TAG = "ForegroundUtils"; + private final IActivityManager mIActivityManager; + + private final Object mLock = new Object(); + // We need to keep track of the individual PIDs per UID, + // since a single UID may have multiple processes running + // that transition into foreground/background state. + private final SparseArray mForegroundUidPids = + new SparseArray(); + private final SparseArray> mBackgroundCallbacks = + new SparseArray>(); + + private static class Singleton { + private static final ForegroundUtils INSTANCE = new ForegroundUtils(); + } + + private ForegroundUtils() { + mIActivityManager = ActivityManagerNative.getDefault(); + try { + mIActivityManager.registerProcessObserver(this); + } catch (RemoteException e) { + // Should not happen! + Log.e(TAG, "ForegroundUtils: could not get IActivityManager"); + } + } + + public interface Callback { + void onUidToBackground(int uid); + } + + public static ForegroundUtils getInstance() { + return Singleton.INSTANCE; + } + + /** + * Checks whether the specified UID has any activities running in the foreground, + * and if it does, registers a callback for when that UID no longer has any foreground + * activities. This is done atomically, so callers can be ensured that they will + * get a callback if this method returns true. + * + * @param callback Callback to be called + * @param uid The UID to be checked + * @return true when the UID has an Activity in the foreground and the callback + * , false otherwise + */ + public boolean registerUidToBackgroundCallback(Callback callback, int uid) { + synchronized (mLock) { + if (!isInForegroundLocked(uid)) { + return false; + } + // This uid is in the foreground; register callback for when it moves + // into the background. + List callbacks = mBackgroundCallbacks.get(uid, new ArrayList()); + callbacks.add(callback); + mBackgroundCallbacks.put(uid, callbacks); + return true; + } + } + + /** + * @param uid The UID to be checked + * @return whether the UID has any activities running in the foreground + */ + public boolean isInForeground(int uid) { + synchronized (mLock) { + return isInForegroundLocked(uid); + } + } + + /** + * @return a list of UIDs currently in the foreground, or an empty list + * if none are found. + */ + public List getForegroundUids() { + ArrayList uids = new ArrayList(mForegroundUidPids.size()); + synchronized (mLock) { + for (int i = 0; i < mForegroundUidPids.size(); i++) { + uids.add(mForegroundUidPids.keyAt(i)); + } + } + return uids; + } + + private boolean isInForegroundLocked(int uid) { + return mForegroundUidPids.get(uid) != null; + } + + private void handleUidToBackground(int uid) { + ArrayList pendingCallbacks = null; + synchronized (mLock) { + List callbacks = mBackgroundCallbacks.get(uid); + if (callbacks != null) { + pendingCallbacks = new ArrayList(callbacks); + // Only call them once + mBackgroundCallbacks.remove(uid); + } + } + // Release lock for callbacks + if (pendingCallbacks != null) { + for (Callback callback : pendingCallbacks) { + callback.onUidToBackground(uid); + } + } + } + + @Override + public void onForegroundActivitiesChanged(int pid, int uid, + boolean hasForegroundActivities) throws RemoteException { + boolean uidToBackground = false; + synchronized (mLock) { + SparseBooleanArray foregroundPids = mForegroundUidPids.get(uid, + new SparseBooleanArray()); + if (hasForegroundActivities) { + foregroundPids.put(pid, true); + } else { + foregroundPids.delete(pid); + } + if (foregroundPids.size() == 0) { + mForegroundUidPids.remove(uid); + uidToBackground = true; + } else { + mForegroundUidPids.put(uid, foregroundPids); + } + } + if (uidToBackground) { + handleUidToBackground(uid); + } + if (DBG) { + if (DBG) Log.d(TAG, "Foreground changed, PID: " + Integer.toString(pid) + " UID: " + + Integer.toString(uid) + " foreground: " + + hasForegroundActivities); + synchronized (mLock) { + Log.d(TAG, "Foreground UID/PID combinations:"); + for (int i = 0; i < mForegroundUidPids.size(); i++) { + int foregroundUid = mForegroundUidPids.keyAt(i); + SparseBooleanArray foregroundPids = mForegroundUidPids.get(foregroundUid); + if (foregroundPids.size() == 0) { + Log.e(TAG, "No PIDS associated with foreground UID!"); + } + for (int j = 0; j < foregroundPids.size(); j++) + Log.d(TAG, "UID: " + Integer.toString(foregroundUid) + " PID: " + + Integer.toString(foregroundPids.keyAt(j))); + } + } + } + } + + + @Override + public void onProcessDied(int pid, int uid) throws RemoteException { + if (DBG) Log.d(TAG, "Process died; UID " + Integer.toString(uid) + " PID " + + Integer.toString(pid)); + onForegroundActivitiesChanged(pid, uid, false); + } + + @Override + public void onProcessStateChanged(int pid, int uid, int procState) + throws RemoteException { + // Don't care + } +} diff --git a/NfcSony/src/com/android/nfc/LlcpException.java b/NfcSony/src/com/android/nfc/LlcpException.java new file mode 100644 index 0000000..9757087 --- /dev/null +++ b/NfcSony/src/com/android/nfc/LlcpException.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2010 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.nfc; + +import android.nfc.ErrorCodes; + +/** + * Generic exception thrown in case something unexpected happened during an + * LLCP communication. + */ +public class LlcpException extends Exception { + /** + * Constructs a new LlcpException with the current stack trace and the + * specified detail message. + * + * @param s the detail message for this exception. + */ + public LlcpException(String s) { + super(s); + } + + public LlcpException(int error) { + super(ErrorCodes.asString(error)); + } +} diff --git a/NfcSony/src/com/android/nfc/LlcpPacket.java b/NfcSony/src/com/android/nfc/LlcpPacket.java new file mode 100644 index 0000000..ea27e9f --- /dev/null +++ b/NfcSony/src/com/android/nfc/LlcpPacket.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2010 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.nfc; + +/** + * Represents a LLCP packet received in a LLCP Connectionless communication; + */ +public class LlcpPacket { + + private int mRemoteSap; + + private byte[] mDataBuffer; + + public LlcpPacket() { + + } + + /** + * Returns the remote Service Access Point number + */ + public int getRemoteSap() { + return mRemoteSap; + } + + /** + * Returns the data buffer + */ + public byte[] getDataBuffer() { + return mDataBuffer; + } + +} diff --git a/NfcSony/src/com/android/nfc/NfcApplication.java b/NfcSony/src/com/android/nfc/NfcApplication.java new file mode 100644 index 0000000..33b5cab --- /dev/null +++ b/NfcSony/src/com/android/nfc/NfcApplication.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2012 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.nfc; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.Application; +import android.os.Process; +import android.os.UserHandle; +import android.view.HardwareRenderer; + +import java.util.Iterator; +import java.util.List; + +public class NfcApplication extends Application { + + static final String TAG = "NfcApplication"; + static final String NFC_PROCESS = "com.android.nfc"; + + NfcService mNfcService; + + public NfcApplication() { + + } + + @Override + public void onCreate() { + super.onCreate(); + + boolean isMainProcess = false; + // We start a service in a separate process to do + // handover transfer. We don't want to instantiate an NfcService + // object in those cases, hence check the name of the process + // to determine whether we're the main NFC service, or the + // handover process + ActivityManager am = (ActivityManager)this.getSystemService(ACTIVITY_SERVICE); + List processes = am.getRunningAppProcesses(); + Iterator i = processes.iterator(); + while (i.hasNext()) { + RunningAppProcessInfo appInfo = (RunningAppProcessInfo)(i.next()); + if (appInfo.pid == Process.myPid()) { + isMainProcess = (NFC_PROCESS.equals(appInfo.processName)); + break; + } + } + if (UserHandle.myUserId() == 0 && isMainProcess) { + mNfcService = new NfcService(this); + HardwareRenderer.enableForegroundTrimming(); + } + } +} diff --git a/NfcSony/src/com/android/nfc/NfcBackupAgent.java b/NfcSony/src/com/android/nfc/NfcBackupAgent.java new file mode 100644 index 0000000..27fdf76 --- /dev/null +++ b/NfcSony/src/com/android/nfc/NfcBackupAgent.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2011 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.nfc; + +import android.app.backup.BackupAgentHelper; +import android.app.backup.SharedPreferencesBackupHelper; + +public class NfcBackupAgent extends BackupAgentHelper { + // Backup identifier + static final String SHARED_PREFS_BACKUP_KEY = "shared_prefs"; + + @Override + public void onCreate() { + SharedPreferencesBackupHelper helper = + new SharedPreferencesBackupHelper(this, NfcService.PREF); + addHelper(SHARED_PREFS_BACKUP_KEY, helper); + } +} + diff --git a/NfcSony/src/com/android/nfc/NfcBootstrapService.java b/NfcSony/src/com/android/nfc/NfcBootstrapService.java new file mode 100644 index 0000000..1b2b7b1 --- /dev/null +++ b/NfcSony/src/com/android/nfc/NfcBootstrapService.java @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2015 The CyanogenMod 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.nfc; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +/** + * This is just a no-op service to allow Nfc app being started at boot time. + */ +public class NfcBootstrapService extends Service { + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + stopSelf(startId); + return Service.START_NOT_STICKY; + } + +} diff --git a/NfcSony/src/com/android/nfc/NfcDiscoveryParameters.java b/NfcSony/src/com/android/nfc/NfcDiscoveryParameters.java new file mode 100644 index 0000000..1149836 --- /dev/null +++ b/NfcSony/src/com/android/nfc/NfcDiscoveryParameters.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2014 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.nfc; + +/** + * Parameters for enabling NFC tag discovery and polling, + * and host card emulation. + */ +public final class NfcDiscoveryParameters { + + public static class Builder { + + private NfcDiscoveryParameters mParameters; + + private Builder() { + mParameters = new NfcDiscoveryParameters(); + } + + public NfcDiscoveryParameters.Builder setTechMask(int techMask) { + mParameters.mTechMask = techMask; + return this; + } + + public NfcDiscoveryParameters.Builder setEnableLowPowerDiscovery(boolean enable) { + mParameters.mEnableLowPowerDiscovery = enable; + return this; + } + + public NfcDiscoveryParameters.Builder setEnableReaderMode(boolean enable) { + mParameters.mEnableReaderMode = enable; + + if (enable) { + mParameters.mEnableLowPowerDiscovery = false; + } + + return this; + } + + public NfcDiscoveryParameters.Builder setEnableHostRouting(boolean enable) { + mParameters.mEnableHostRouting = enable; + return this; + } + + public NfcDiscoveryParameters.Builder setEnableP2p(boolean enable) { + mParameters.mEnableP2p = enable; + return this; + } + + public NfcDiscoveryParameters build() { + if (mParameters.mEnableReaderMode && + (mParameters.mEnableLowPowerDiscovery || mParameters.mEnableP2p)) { + throw new IllegalStateException("Can't enable LPTD/P2P and reader mode " + + "simultaneously"); + } + return mParameters; + } + } + + static final int NFC_POLL_DEFAULT = -1; + + // NOTE: when adding a new field, don't forget to update equals() and toString() below + private int mTechMask = 0; + private boolean mEnableLowPowerDiscovery = true; + private boolean mEnableReaderMode = false; + private boolean mEnableHostRouting = false; + private boolean mEnableP2p = false; + + public NfcDiscoveryParameters() {} + + public int getTechMask() { + return mTechMask; + } + + public boolean shouldEnableLowPowerDiscovery() { + return mEnableLowPowerDiscovery; + } + + public boolean shouldEnableReaderMode() { + return mEnableReaderMode; + } + + public boolean shouldEnableHostRouting() { + return mEnableHostRouting; + } + + public boolean shouldEnableDiscovery() { + return mTechMask != 0 || mEnableHostRouting; + } + + public boolean shouldEnableP2p() { + return mEnableP2p; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + + if ((obj == null) || (obj.getClass() != this.getClass())) { + return false; + } + NfcDiscoveryParameters params = (NfcDiscoveryParameters) obj; + return mTechMask == params.mTechMask && + (mEnableLowPowerDiscovery == params.mEnableLowPowerDiscovery) && + (mEnableReaderMode == params.mEnableReaderMode) && + (mEnableHostRouting == params.mEnableHostRouting) + && (mEnableP2p == params.mEnableP2p); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (mTechMask == NFC_POLL_DEFAULT) { + sb.append("mTechMask: default\n"); + } else { + sb.append("mTechMask: " + Integer.toString(mTechMask) + "\n"); + } + sb.append("mEnableLPD: " + Boolean.toString(mEnableLowPowerDiscovery) + "\n"); + sb.append("mEnableReader: " + Boolean.toString(mEnableReaderMode) + "\n"); + sb.append("mEnableHostRouting: " + Boolean.toString(mEnableHostRouting) + "\n"); + sb.append("mEnableP2p: " + Boolean.toString(mEnableP2p)); + return sb.toString(); + } + + public static NfcDiscoveryParameters.Builder newBuilder() { + return new Builder(); + } + + public static NfcDiscoveryParameters getDefaultInstance() { + return new NfcDiscoveryParameters(); + } + + public static NfcDiscoveryParameters getNfcOffParameters() { + return new NfcDiscoveryParameters(); + } +} diff --git a/NfcSony/src/com/android/nfc/NfcDispatcher.java b/NfcSony/src/com/android/nfc/NfcDispatcher.java new file mode 100644 index 0000000..53ba0f3 --- /dev/null +++ b/NfcSony/src/com/android/nfc/NfcDispatcher.java @@ -0,0 +1,669 @@ +/* + * Copyright (C) 2011 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.nfc; + +import android.bluetooth.BluetoothAdapter; + +import com.android.nfc.RegisteredComponentCache.ComponentInfo; +import com.android.nfc.handover.HandoverDataParser; +import com.android.nfc.handover.PeripheralHandoverService; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.ActivityManagerNative; +import android.app.IActivityManager; +import android.app.PendingIntent; +import android.app.PendingIntent.CanceledException; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.res.Resources.NotFoundException; +import android.net.Uri; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.tech.Ndef; +import android.nfc.tech.NfcBarcode; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +/** + * Dispatch of NFC events to start activities + */ +class NfcDispatcher { + private static final boolean DBG = false; + private static final String TAG = "NfcDispatcher"; + + static final int DISPATCH_SUCCESS = 1; + static final int DISPATCH_FAIL = 2; + static final int DISPATCH_UNLOCK = 3; + + private final Context mContext; + private final IActivityManager mIActivityManager; + private final RegisteredComponentCache mTechListFilters; + private final ContentResolver mContentResolver; + private final HandoverDataParser mHandoverDataParser; + private final String[] mProvisioningMimes; + private final ScreenStateHelper mScreenStateHelper; + private final NfcUnlockManager mNfcUnlockManager; + private final boolean mDeviceSupportsBluetooth; + + // Locked on this + private PendingIntent mOverrideIntent; + private IntentFilter[] mOverrideFilters; + private String[][] mOverrideTechLists; + private boolean mProvisioningOnly; + + NfcDispatcher(Context context, + HandoverDataParser handoverDataParser, + boolean provisionOnly) { + mContext = context; + mIActivityManager = ActivityManagerNative.getDefault(); + mTechListFilters = new RegisteredComponentCache(mContext, + NfcAdapter.ACTION_TECH_DISCOVERED, NfcAdapter.ACTION_TECH_DISCOVERED); + mContentResolver = context.getContentResolver(); + mHandoverDataParser = handoverDataParser; + mScreenStateHelper = new ScreenStateHelper(context); + mNfcUnlockManager = NfcUnlockManager.getInstance(); + mDeviceSupportsBluetooth = BluetoothAdapter.getDefaultAdapter() != null; + + synchronized (this) { + mProvisioningOnly = provisionOnly; + } + String[] provisionMimes = null; + if (provisionOnly) { + try { + // Get accepted mime-types + provisionMimes = context.getResources(). + getStringArray(R.array.provisioning_mime_types); + } catch (NotFoundException e) { + provisionMimes = null; + } + } + mProvisioningMimes = provisionMimes; + } + + public synchronized void setForegroundDispatch(PendingIntent intent, + IntentFilter[] filters, String[][] techLists) { + if (DBG) Log.d(TAG, "Set Foreground Dispatch"); + mOverrideIntent = intent; + mOverrideFilters = filters; + mOverrideTechLists = techLists; + } + + public synchronized void disableProvisioningMode() { + mProvisioningOnly = false; + } + + /** + * Helper for re-used objects and methods during a single tag dispatch. + */ + static class DispatchInfo { + public final Intent intent; + + final Intent rootIntent; + final Uri ndefUri; + final String ndefMimeType; + final PackageManager packageManager; + final Context context; + + public DispatchInfo(Context context, Tag tag, NdefMessage message) { + intent = new Intent(); + intent.putExtra(NfcAdapter.EXTRA_TAG, tag); + intent.putExtra(NfcAdapter.EXTRA_ID, tag.getId()); + if (message != null) { + intent.putExtra(NfcAdapter.EXTRA_NDEF_MESSAGES, new NdefMessage[] {message}); + ndefUri = message.getRecords()[0].toUri(); + ndefMimeType = message.getRecords()[0].toMimeType(); + } else { + ndefUri = null; + ndefMimeType = null; + } + + rootIntent = new Intent(context, NfcRootActivity.class); + rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intent); + rootIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + + this.context = context; + packageManager = context.getPackageManager(); + } + + public Intent setNdefIntent() { + intent.setAction(NfcAdapter.ACTION_NDEF_DISCOVERED); + if (ndefUri != null) { + intent.setData(ndefUri); + return intent; + } else if (ndefMimeType != null) { + intent.setType(ndefMimeType); + return intent; + } + return null; + } + + public Intent setTechIntent() { + intent.setData(null); + intent.setType(null); + intent.setAction(NfcAdapter.ACTION_TECH_DISCOVERED); + return intent; + } + + public Intent setTagIntent() { + intent.setData(null); + intent.setType(null); + intent.setAction(NfcAdapter.ACTION_TAG_DISCOVERED); + return intent; + } + + /** + * Launch the activity via a (single) NFC root task, so that it + * creates a new task stack instead of interfering with any existing + * task stack for that activity. + * NfcRootActivity acts as the task root, it immediately calls + * start activity on the intent it is passed. + */ + boolean tryStartActivity() { + // Ideally we'd have used startActivityForResult() to determine whether the + // NfcRootActivity was able to launch the intent, but startActivityForResult() + // is not available on Context. Instead, we query the PackageManager beforehand + // to determine if there is an Activity to handle this intent, and base the + // result of off that. + List activities = packageManager.queryIntentActivitiesAsUser(intent, 0, + ActivityManager.getCurrentUser()); + if (activities.size() > 0) { + context.startActivityAsUser(rootIntent, UserHandle.CURRENT); + return true; + } + return false; + } + + boolean tryStartActivity(Intent intentToStart) { + List activities = packageManager.queryIntentActivitiesAsUser( + intentToStart, 0, ActivityManager.getCurrentUser()); + if (activities.size() > 0) { + rootIntent.putExtra(NfcRootActivity.EXTRA_LAUNCH_INTENT, intentToStart); + context.startActivityAsUser(rootIntent, UserHandle.CURRENT); + return true; + } + return false; + } + } + + /** Returns: + *

    + *
  • DISPATCH_SUCCESS if dispatched to an activity, + *
  • DISPATCH_FAIL if no activities were found to dispatch to, + *
  • DISPATCH_UNLOCK if the tag was used to unlock the device + *
+ */ + public int dispatchTag(Tag tag) { + PendingIntent overrideIntent; + IntentFilter[] overrideFilters; + String[][] overrideTechLists; + boolean provisioningOnly; + + synchronized (this) { + overrideFilters = mOverrideFilters; + overrideIntent = mOverrideIntent; + overrideTechLists = mOverrideTechLists; + provisioningOnly = mProvisioningOnly; + } + + boolean screenUnlocked = false; + if (!provisioningOnly && + mScreenStateHelper.checkScreenState() == ScreenStateHelper.SCREEN_STATE_ON_LOCKED) { + screenUnlocked = handleNfcUnlock(tag); + if (!screenUnlocked) { + return DISPATCH_FAIL; + } + } + + NdefMessage message = null; + Ndef ndef = Ndef.get(tag); + if (ndef != null) { + message = ndef.getCachedNdefMessage(); + } else { + NfcBarcode nfcBarcode = NfcBarcode.get(tag); + if (nfcBarcode != null && nfcBarcode.getType() == NfcBarcode.TYPE_KOVIO) { + message = decodeNfcBarcodeUri(nfcBarcode); + } + } + + if (DBG) Log.d(TAG, "dispatch tag: " + tag.toString() + " message: " + message); + + DispatchInfo dispatch = new DispatchInfo(mContext, tag, message); + + resumeAppSwitches(); + + if (tryOverrides(dispatch, tag, message, overrideIntent, overrideFilters, + overrideTechLists)) { + return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS; + } + + if (tryPeripheralHandover(message)) { + if (DBG) Log.i(TAG, "matched BT HANDOVER"); + return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS; + } + + if (NfcWifiProtectedSetup.tryNfcWifiSetup(ndef, mContext)) { + if (DBG) Log.i(TAG, "matched NFC WPS TOKEN"); + return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS; + } + + if (tryNdef(dispatch, message, provisioningOnly)) { + return screenUnlocked ? DISPATCH_UNLOCK : DISPATCH_SUCCESS; + } + + if (screenUnlocked) { + // We only allow NDEF-based mimeType matching in case of an unlock + return DISPATCH_UNLOCK; + } + + if (provisioningOnly) { + // We only allow NDEF-based mimeType matching + return DISPATCH_FAIL; + } + + // Only allow NDEF-based mimeType matching for unlock tags + if (tryTech(dispatch, tag)) { + return DISPATCH_SUCCESS; + } + + dispatch.setTagIntent(); + if (dispatch.tryStartActivity()) { + if (DBG) Log.i(TAG, "matched TAG"); + return DISPATCH_SUCCESS; + } + + if (DBG) Log.i(TAG, "no match"); + return DISPATCH_FAIL; + } + + private boolean handleNfcUnlock(Tag tag) { + return mNfcUnlockManager.tryUnlock(tag); + } + + /** + * Checks for the presence of a URL stored in a tag with tech NfcBarcode. + * If found, decodes URL and returns NdefMessage message containing an + * NdefRecord containing the decoded URL. If not found, returns null. + * + * URLs are decoded as follows: + * + * Ignore first byte (which is 0x80 ORd with a manufacturer ID, corresponding + * to ISO/IEC 7816-6). + * The second byte describes the payload data format. There are four defined data + * format values that identify URL data. Depending on the data format value, the + * associated prefix is appended to the URL data: + * + * 0x01: URL with "http://www." prefix + * 0x02: URL with "https://www." prefix + * 0x03: URL with "http://" prefix + * 0x04: URL with "https://" prefix + * + * Other data format values do not identify URL data and are not handled by this function. + * URL payload is encoded in US-ASCII, following the limitations defined in RFC3987. + * see http://www.ietf.org/rfc/rfc3987.txt + * + * The final two bytes of a tag with tech NfcBarcode are always reserved for CRC data, + * and are therefore not part of the payload. They are ignored in the decoding of a URL. + * + * The default assumption is that the URL occupies the entire payload of the NfcBarcode + * ID and all bytes of the NfcBarcode payload are decoded until the CRC (final two bytes) + * is reached. However, the OPTIONAL early terminator byte 0xfe can be used to signal + * an early end of the URL. Once this function reaches an early terminator byte 0xfe, + * URL decoding stops and the NdefMessage is created and returned. Any payload data after + * the first early terminator byte is ignored for the purposes of URL decoding. + */ + private NdefMessage decodeNfcBarcodeUri(NfcBarcode nfcBarcode) { + final byte URI_PREFIX_HTTP_WWW = (byte) 0x01; // "http://www." + final byte URI_PREFIX_HTTPS_WWW = (byte) 0x02; // "https://www." + final byte URI_PREFIX_HTTP = (byte) 0x03; // "http://" + final byte URI_PREFIX_HTTPS = (byte) 0x04; // "https://" + + NdefMessage message = null; + byte[] tagId = nfcBarcode.getTag().getId(); + // All tags of NfcBarcode technology and Kovio type have lengths of a multiple of 16 bytes + if (tagId.length >= 4 + && (tagId[1] == URI_PREFIX_HTTP_WWW || tagId[1] == URI_PREFIX_HTTPS_WWW + || tagId[1] == URI_PREFIX_HTTP || tagId[1] == URI_PREFIX_HTTPS)) { + // Look for optional URI terminator (0xfe), used to indicate the end of a URI prior to + // the end of the full NfcBarcode payload. No terminator means that the URI occupies the + // entire length of the payload field. Exclude checking the CRC in the final two bytes + // of the NfcBarcode tagId. + int end = 2; + for (; end < tagId.length - 2; end++) { + if (tagId[end] == (byte) 0xfe) { + break; + } + } + byte[] payload = new byte[end - 1]; // Skip also first byte (manufacturer ID) + System.arraycopy(tagId, 1, payload, 0, payload.length); + NdefRecord uriRecord = new NdefRecord( + NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI, tagId, payload); + message = new NdefMessage(uriRecord); + } + return message; + } + + boolean tryOverrides(DispatchInfo dispatch, Tag tag, NdefMessage message, PendingIntent overrideIntent, + IntentFilter[] overrideFilters, String[][] overrideTechLists) { + if (overrideIntent == null) { + return false; + } + Intent intent; + + // NDEF + if (message != null) { + intent = dispatch.setNdefIntent(); + if (intent != null && + isFilterMatch(intent, overrideFilters, overrideTechLists != null)) { + try { + overrideIntent.send(mContext, Activity.RESULT_OK, intent); + if (DBG) Log.i(TAG, "matched NDEF override"); + return true; + } catch (CanceledException e) { + return false; + } + } + } + + // TECH + intent = dispatch.setTechIntent(); + if (isTechMatch(tag, overrideTechLists)) { + try { + overrideIntent.send(mContext, Activity.RESULT_OK, intent); + if (DBG) Log.i(TAG, "matched TECH override"); + return true; + } catch (CanceledException e) { + return false; + } + } + + // TAG + intent = dispatch.setTagIntent(); + if (isFilterMatch(intent, overrideFilters, overrideTechLists != null)) { + try { + overrideIntent.send(mContext, Activity.RESULT_OK, intent); + if (DBG) Log.i(TAG, "matched TAG override"); + return true; + } catch (CanceledException e) { + return false; + } + } + return false; + } + + boolean isFilterMatch(Intent intent, IntentFilter[] filters, boolean hasTechFilter) { + if (filters != null) { + for (IntentFilter filter : filters) { + if (filter.match(mContentResolver, intent, false, TAG) >= 0) { + return true; + } + } + } else if (!hasTechFilter) { + return true; // always match if both filters and techlists are null + } + return false; + } + + boolean isTechMatch(Tag tag, String[][] techLists) { + if (techLists == null) { + return false; + } + + String[] tagTechs = tag.getTechList(); + Arrays.sort(tagTechs); + for (String[] filterTechs : techLists) { + if (filterMatch(tagTechs, filterTechs)) { + return true; + } + } + return false; + } + + boolean tryNdef(DispatchInfo dispatch, NdefMessage message, boolean provisioningOnly) { + if (message == null) { + return false; + } + Intent intent = dispatch.setNdefIntent(); + + // Bail out if the intent does not contain filterable NDEF data + if (intent == null) return false; + + if (provisioningOnly) { + if (mProvisioningMimes == null || + !(Arrays.asList(mProvisioningMimes).contains(intent.getType()))) { + Log.e(TAG, "Dropping NFC intent in provisioning mode."); + return false; + } + } + + // Try to start AAR activity with matching filter + List aarPackages = extractAarPackages(message); + for (String pkg : aarPackages) { + dispatch.intent.setPackage(pkg); + if (dispatch.tryStartActivity()) { + if (DBG) Log.i(TAG, "matched AAR to NDEF"); + return true; + } + } + + // Try to perform regular launch of the first AAR + if (aarPackages.size() > 0) { + String firstPackage = aarPackages.get(0); + PackageManager pm; + try { + UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser()); + pm = mContext.createPackageContextAsUser("android", 0, + currentUser).getPackageManager(); + } catch (NameNotFoundException e) { + Log.e(TAG, "Could not create user package context"); + return false; + } + Intent appLaunchIntent = pm.getLaunchIntentForPackage(firstPackage); + if (appLaunchIntent != null && dispatch.tryStartActivity(appLaunchIntent)) { + if (DBG) Log.i(TAG, "matched AAR to application launch"); + return true; + } + // Find the package in Market: + Intent marketIntent = getAppSearchIntent(firstPackage); + if (marketIntent != null && dispatch.tryStartActivity(marketIntent)) { + if (DBG) Log.i(TAG, "matched AAR to market launch"); + return true; + } + } + + // regular launch + dispatch.intent.setPackage(null); + if (dispatch.tryStartActivity()) { + if (DBG) Log.i(TAG, "matched NDEF"); + return true; + } + + return false; + } + + static List extractAarPackages(NdefMessage message) { + List aarPackages = new LinkedList(); + for (NdefRecord record : message.getRecords()) { + String pkg = checkForAar(record); + if (pkg != null) { + aarPackages.add(pkg); + } + } + return aarPackages; + } + + boolean tryTech(DispatchInfo dispatch, Tag tag) { + dispatch.setTechIntent(); + + String[] tagTechs = tag.getTechList(); + Arrays.sort(tagTechs); + + // Standard tech dispatch path + ArrayList matches = new ArrayList(); + List registered = mTechListFilters.getComponents(); + + PackageManager pm; + try { + UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser()); + pm = mContext.createPackageContextAsUser("android", 0, + currentUser).getPackageManager(); + } catch (NameNotFoundException e) { + Log.e(TAG, "Could not create user package context"); + return false; + } + // Check each registered activity to see if it matches + for (ComponentInfo info : registered) { + // Don't allow wild card matching + if (filterMatch(tagTechs, info.techs) && + isComponentEnabled(pm, info.resolveInfo)) { + // Add the activity as a match if it's not already in the list + if (!matches.contains(info.resolveInfo)) { + matches.add(info.resolveInfo); + } + } + } + + if (matches.size() == 1) { + // Single match, launch directly + ResolveInfo info = matches.get(0); + dispatch.intent.setClassName(info.activityInfo.packageName, info.activityInfo.name); + if (dispatch.tryStartActivity()) { + if (DBG) Log.i(TAG, "matched single TECH"); + return true; + } + dispatch.intent.setComponent(null); + } else if (matches.size() > 1) { + // Multiple matches, show a custom activity chooser dialog + Intent intent = new Intent(mContext, TechListChooserActivity.class); + intent.putExtra(Intent.EXTRA_INTENT, dispatch.intent); + intent.putParcelableArrayListExtra(TechListChooserActivity.EXTRA_RESOLVE_INFOS, + matches); + if (dispatch.tryStartActivity(intent)) { + if (DBG) Log.i(TAG, "matched multiple TECH"); + return true; + } + } + return false; + } + + public boolean tryPeripheralHandover(NdefMessage m) { + if (m == null || !mDeviceSupportsBluetooth) return false; + + if (DBG) Log.d(TAG, "tryHandover(): " + m.toString()); + + HandoverDataParser.BluetoothHandoverData handover = mHandoverDataParser.parseBluetooth(m); + if (handover == null || !handover.valid) return false; + + Intent intent = new Intent(mContext, PeripheralHandoverService.class); + intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_DEVICE, handover.device); + intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_NAME, handover.name); + intent.putExtra(PeripheralHandoverService.EXTRA_PERIPHERAL_TRANSPORT, handover.transport); + mContext.startServiceAsUser(intent, UserHandle.CURRENT); + + return true; + } + + + /** + * Tells the ActivityManager to resume allowing app switches. + * + * If the current app called stopAppSwitches() then our startActivity() can + * be delayed for several seconds. This happens with the default home + * screen. As a system service we can override this behavior with + * resumeAppSwitches(). + */ + void resumeAppSwitches() { + try { + mIActivityManager.resumeAppSwitches(); + } catch (RemoteException e) { } + } + + /** Returns true if the tech list filter matches the techs on the tag */ + boolean filterMatch(String[] tagTechs, String[] filterTechs) { + if (filterTechs == null || filterTechs.length == 0) return false; + + for (String tech : filterTechs) { + if (Arrays.binarySearch(tagTechs, tech) < 0) { + return false; + } + } + return true; + } + + static String checkForAar(NdefRecord record) { + if (record.getTnf() == NdefRecord.TNF_EXTERNAL_TYPE && + Arrays.equals(record.getType(), NdefRecord.RTD_ANDROID_APP)) { + return new String(record.getPayload(), StandardCharsets.US_ASCII); + } + return null; + } + + /** + * Returns an intent that can be used to find an application not currently + * installed on the device. + */ + static Intent getAppSearchIntent(String pkg) { + Intent market = new Intent(Intent.ACTION_VIEW); + market.setData(Uri.parse("market://details?id=" + pkg)); + return market; + } + + static boolean isComponentEnabled(PackageManager pm, ResolveInfo info) { + boolean enabled = false; + ComponentName compname = new ComponentName( + info.activityInfo.packageName, info.activityInfo.name); + try { + // Note that getActivityInfo() will internally call + // isEnabledLP() to determine whether the component + // enabled. If it's not, null is returned. + if (pm.getActivityInfo(compname,0) != null) { + enabled = true; + } + } catch (PackageManager.NameNotFoundException e) { + enabled = false; + } + if (!enabled) { + Log.d(TAG, "Component not enabled: " + compname); + } + return enabled; + } + + void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + synchronized (this) { + pw.println("mOverrideIntent=" + mOverrideIntent); + pw.println("mOverrideFilters=" + mOverrideFilters); + pw.println("mOverrideTechLists=" + mOverrideTechLists); + } + } +} diff --git a/NfcSony/src/com/android/nfc/NfcPermissions.java b/NfcSony/src/com/android/nfc/NfcPermissions.java new file mode 100644 index 0000000..50adf23 --- /dev/null +++ b/NfcSony/src/com/android/nfc/NfcPermissions.java @@ -0,0 +1,35 @@ +package com.android.nfc; + + +import android.content.Context; +import android.os.UserHandle; + +public class NfcPermissions { + + /** + * NFC ADMIN permission - only for system apps + */ + private static final String ADMIN_PERM = android.Manifest.permission.WRITE_SECURE_SETTINGS; + private static final String ADMIN_PERM_ERROR = "WRITE_SECURE_SETTINGS permission required"; + + /** + * Regular NFC permission + */ + static final String NFC_PERMISSION = android.Manifest.permission.NFC; + private static final String NFC_PERM_ERROR = "NFC permission required"; + + public static void validateUserId(int userId) { + if (userId != UserHandle.getCallingUserId()) { + throw new SecurityException("userId passed in is not the calling user."); + } + } + + public static void enforceAdminPermissions(Context context) { + context.enforceCallingOrSelfPermission(ADMIN_PERM, ADMIN_PERM_ERROR); + } + + + public static void enforceUserPermissions(Context context) { + context.enforceCallingOrSelfPermission(NFC_PERMISSION, NFC_PERM_ERROR); + } +} diff --git a/NfcSony/src/com/android/nfc/NfcRootActivity.java b/NfcSony/src/com/android/nfc/NfcRootActivity.java new file mode 100644 index 0000000..cc216f2 --- /dev/null +++ b/NfcSony/src/com/android/nfc/NfcRootActivity.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2011 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.nfc; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.os.Bundle; +import android.os.UserHandle; + +public class NfcRootActivity extends Activity { + + static final String EXTRA_LAUNCH_INTENT = "launchIntent"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intent = getIntent(); + if (intent != null && intent.hasExtra(EXTRA_LAUNCH_INTENT)) { + final Intent launchIntent = intent.getParcelableExtra(EXTRA_LAUNCH_INTENT); + if (launchIntent != null) { + try { + startActivityAsUser(launchIntent, UserHandle.CURRENT); + } catch (ActivityNotFoundException e) { + } + } + } + finish(); + } +} diff --git a/NfcSony/src/com/android/nfc/NfcService.java b/NfcSony/src/com/android/nfc/NfcService.java new file mode 100755 index 0000000..7ab83e5 --- /dev/null +++ b/NfcSony/src/com/android/nfc/NfcService.java @@ -0,0 +1,2121 @@ +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * Not a Contribution. + * + * Copyright (C) 2010 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.nfc; + +import android.app.ActivityManager; +import android.app.Application; +import android.app.KeyguardManager; +import android.app.PendingIntent; +import android.app.admin.DevicePolicyManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.IPackageManager; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.UserInfo; +import android.content.res.Resources.NotFoundException; +import android.media.AudioManager; +import android.media.SoundPool; +import android.nfc.BeamShareData; +import android.nfc.ErrorCodes; +import android.nfc.FormatException; +import android.nfc.IAppCallback; +import android.nfc.INfcAdapter; +import android.nfc.INfcAdapterExtras; +import android.nfc.INfcCardEmulation; +import android.nfc.INfcTag; +import android.nfc.INfcUnlockHandler; +import android.nfc.NdefMessage; +import android.nfc.NfcAdapter; +import android.nfc.Tag; +import android.nfc.TechListParcel; +import android.nfc.TransceiveResult; +import android.nfc.tech.Ndef; +import android.nfc.tech.TagTechnology; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.PowerManager; +import android.os.Process; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import com.android.nfc.DeviceHost.DeviceHostListener; +import com.android.nfc.DeviceHost.LlcpConnectionlessSocket; +import com.android.nfc.DeviceHost.LlcpServerSocket; +import com.android.nfc.DeviceHost.LlcpSocket; +import com.android.nfc.DeviceHost.NfcDepEndpoint; +import com.android.nfc.DeviceHost.TagEndpoint; +import com.android.nfc.cardemulation.CardEmulationManager; +import com.android.nfc.dhimpl.NativeNfcManager; +import com.android.nfc.handover.HandoverDataParser; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + + +public class NfcService implements DeviceHostListener { + static final boolean DBG = false; + static final String TAG = "NfcService"; + + public static final String SERVICE_NAME = "nfc"; + + public static final String PREF = "NfcServicePrefs"; + + static final String PREF_NFC_ON = "nfc_on"; + static final boolean NFC_ON_DEFAULT = true; + static final String PREF_NDEF_PUSH_ON = "ndef_push_on"; + static final boolean NDEF_PUSH_ON_DEFAULT = true; + static final String PREF_FIRST_BEAM = "first_beam"; + static final String PREF_FIRST_BOOT = "first_boot"; + static final String PREF_AIRPLANE_OVERRIDE = "airplane_override"; + + static final int MSG_NDEF_TAG = 0; + static final int MSG_LLCP_LINK_ACTIVATION = 1; + static final int MSG_LLCP_LINK_DEACTIVATED = 2; + static final int MSG_MOCK_NDEF = 3; + static final int MSG_LLCP_LINK_FIRST_PACKET = 4; + static final int MSG_ROUTE_AID = 5; + static final int MSG_UNROUTE_AID = 6; + static final int MSG_COMMIT_ROUTING = 7; + static final int MSG_INVOKE_BEAM = 8; + static final int MSG_RF_FIELD_ACTIVATED = 9; + static final int MSG_RF_FIELD_DEACTIVATED = 10; + static final int MSG_RESUME_POLLING = 11; + + static final long MAX_POLLING_PAUSE_TIMEOUT = 40000; + + static final int TASK_ENABLE = 1; + static final int TASK_DISABLE = 2; + static final int TASK_BOOT = 3; + + // Polling technology masks + static final int NFC_POLL_A = 0x01; + static final int NFC_POLL_B = 0x02; + static final int NFC_POLL_F = 0x04; + static final int NFC_POLL_ISO15693 = 0x08; + static final int NFC_POLL_B_PRIME = 0x10; + static final int NFC_POLL_KOVIO = 0x20; + + // minimum screen state that enables NFC polling + static final int NFC_POLLING_MODE = ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED; + + // Time to wait for NFC controller to initialize before watchdog + // goes off. This time is chosen large, because firmware download + // may be a part of initialization. + static final int INIT_WATCHDOG_MS = 90000; + + // Time to wait for routing to be applied before watchdog + // goes off + static final int ROUTING_WATCHDOG_MS = 10000; + + // Default delay used for presence checks + static final int DEFAULT_PRESENCE_CHECK_DELAY = 125; + + // The amount of time we wait before manually launching + // the Beam animation when called through the share menu. + static final int INVOKE_BEAM_DELAY_MS = 1000; + + // RF field events as defined in NFC extras + public static final String ACTION_RF_FIELD_ON_DETECTED = + "com.android.nfc_extras.action.RF_FIELD_ON_DETECTED"; + public static final String ACTION_RF_FIELD_OFF_DETECTED = + "com.android.nfc_extras.action.RF_FIELD_OFF_DETECTED"; + + // for use with playSound() + public static final int SOUND_START = 0; + public static final int SOUND_END = 1; + public static final int SOUND_ERROR = 2; + + public static final String ACTION_LLCP_UP = + "com.android.nfc.action.LLCP_UP"; + + public static final String ACTION_LLCP_DOWN = + "com.android.nfc.action.LLCP_DOWN"; + + // Timeout to re-apply routing if a tag was present and we postponed it + private static final int APPLY_ROUTING_RETRY_TIMEOUT_MS = 5000; + + private final UserManager mUserManager; + + // NFC Execution Environment + // fields below are protected by this + private final ReaderModeDeathRecipient mReaderModeDeathRecipient = + new ReaderModeDeathRecipient(); + private final NfcUnlockManager mNfcUnlockManager; + + private final NfceeAccessControl mNfceeAccessControl; + + List mInstalledPackages; // cached version of installed packages + + // fields below are used in multiple threads and protected by synchronized(this) + final HashMap mObjectMap = new HashMap(); + int mScreenState; + boolean mInProvisionMode; // whether we're in setup wizard and enabled NFC provisioning + boolean mIsNdefPushEnabled; + NfcDiscoveryParameters mCurrentDiscoveryParameters = + NfcDiscoveryParameters.getNfcOffParameters(); + + ReaderModeParams mReaderModeParams; + + // mState is protected by this, however it is only modified in onCreate() + // and the default AsyncTask thread so it is read unprotected from that + // thread + int mState; // one of NfcAdapter.STATE_ON, STATE_TURNING_ON, etc + // fields below are final after onCreate() + Context mContext; + private DeviceHost mDeviceHost; + private SharedPreferences mPrefs; + private SharedPreferences.Editor mPrefsEditor; + private PowerManager.WakeLock mRoutingWakeLock; + + int mStartSound; + int mEndSound; + int mErrorSound; + SoundPool mSoundPool; // playback synchronized on this + P2pLinkManager mP2pLinkManager; + TagService mNfcTagService; + NfcAdapterService mNfcAdapter; + boolean mIsAirplaneSensitive; + boolean mIsAirplaneToggleable; + boolean mIsDebugBuild; + boolean mIsHceCapable; + boolean mPollingPaused; + + private NfcDispatcher mNfcDispatcher; + private PowerManager mPowerManager; + private KeyguardManager mKeyguard; + private HandoverDataParser mHandoverDataParser; + private ContentResolver mContentResolver; + private CardEmulationManager mCardEmulationManager; + + private ScreenStateHelper mScreenStateHelper; + private ForegroundUtils mForegroundUtils; + + private int mUserId; + private static NfcService sService; + + public static NfcService getInstance() { + return sService; + } + + @Override + public void onRemoteEndpointDiscovered(TagEndpoint tag) { + sendMessage(NfcService.MSG_NDEF_TAG, tag); + } + + /** + * Notifies transaction + */ + @Override + public void onHostCardEmulationActivated() { + if (mCardEmulationManager != null) { + mCardEmulationManager.onHostCardEmulationActivated(); + } + } + + @Override + public void onHostCardEmulationData(byte[] data) { + if (mCardEmulationManager != null) { + mCardEmulationManager.onHostCardEmulationData(data); + } + } + + @Override + public void onHostCardEmulationDeactivated() { + if (mCardEmulationManager != null) { + mCardEmulationManager.onHostCardEmulationDeactivated(); + } + } + + /** + * Notifies P2P Device detected, to activate LLCP link + */ + @Override + public void onLlcpLinkActivated(NfcDepEndpoint device) { + sendMessage(NfcService.MSG_LLCP_LINK_ACTIVATION, device); + } + + /** + * Notifies P2P Device detected, to activate LLCP link + */ + @Override + public void onLlcpLinkDeactivated(NfcDepEndpoint device) { + sendMessage(NfcService.MSG_LLCP_LINK_DEACTIVATED, device); + } + + /** + * Notifies P2P Device detected, first packet received over LLCP link + */ + @Override + public void onLlcpFirstPacketReceived(NfcDepEndpoint device) { + sendMessage(NfcService.MSG_LLCP_LINK_FIRST_PACKET, device); + } + + @Override + public void onRemoteFieldActivated() { + sendMessage(NfcService.MSG_RF_FIELD_ACTIVATED, null); + } + + @Override + public void onRemoteFieldDeactivated() { + sendMessage(NfcService.MSG_RF_FIELD_DEACTIVATED, null); + } + + final class ReaderModeParams { + public int flags; + public IAppCallback callback; + public int presenceCheckDelay; + } + + public NfcService(Application nfcApplication) { + mUserId = ActivityManager.getCurrentUser(); + mContext = nfcApplication; + + mNfcTagService = new TagService(); + mNfcAdapter = new NfcAdapterService(); + Log.i(TAG, "Starting NFC service"); + + sService = this; + + mScreenStateHelper = new ScreenStateHelper(mContext); + mContentResolver = mContext.getContentResolver(); + mDeviceHost = new NativeNfcManager(mContext, this); + + mNfcUnlockManager = NfcUnlockManager.getInstance(); + + mHandoverDataParser = new HandoverDataParser(); + boolean isNfcProvisioningEnabled = false; + try { + isNfcProvisioningEnabled = mContext.getResources().getBoolean( + R.bool.enable_nfc_provisioning); + } catch (NotFoundException e) { + } + + if (isNfcProvisioningEnabled) { + mInProvisionMode = Settings.Secure.getInt(mContentResolver, + Settings.Global.DEVICE_PROVISIONED, 0) == 0; + } else { + mInProvisionMode = false; + } + + mNfcDispatcher = new NfcDispatcher(mContext, mHandoverDataParser, mInProvisionMode); + mP2pLinkManager = new P2pLinkManager(mContext, mHandoverDataParser, + mDeviceHost.getDefaultLlcpMiu(), mDeviceHost.getDefaultLlcpRwSize()); + + mPrefs = mContext.getSharedPreferences(PREF, Context.MODE_PRIVATE); + mPrefsEditor = mPrefs.edit(); + + mNfceeAccessControl = new NfceeAccessControl(mContext); + + mState = NfcAdapter.STATE_OFF; + mIsNdefPushEnabled = mPrefs.getBoolean(PREF_NDEF_PUSH_ON, NDEF_PUSH_ON_DEFAULT); + setBeamShareActivityState(mIsNdefPushEnabled); + + mIsDebugBuild = "userdebug".equals(Build.TYPE) || "eng".equals(Build.TYPE); + + mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + + mRoutingWakeLock = mPowerManager.newWakeLock( + PowerManager.PARTIAL_WAKE_LOCK, "NfcService:mRoutingWakeLock"); + + mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); + mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + + mScreenState = mScreenStateHelper.checkScreenState(); + + ServiceManager.addService(SERVICE_NAME, mNfcAdapter); + + // Intents for all users + IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); + filter.addAction(Intent.ACTION_SCREEN_ON); + filter.addAction(Intent.ACTION_USER_PRESENT); + filter.addAction(Intent.ACTION_USER_SWITCHED); + registerForAirplaneMode(filter); + mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, null); + + IntentFilter ownerFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); + ownerFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + mContext.registerReceiver(mOwnerReceiver, ownerFilter); + + ownerFilter = new IntentFilter(); + ownerFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + ownerFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + ownerFilter.addDataScheme("package"); + mContext.registerReceiver(mOwnerReceiver, ownerFilter); + + IntentFilter policyFilter = new IntentFilter(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); + mContext.registerReceiverAsUser(mPolicyReceiver, UserHandle.ALL, policyFilter, null, null); + + updatePackageCache(); + + PackageManager pm = mContext.getPackageManager(); + mIsHceCapable = pm.hasSystemFeature(PackageManager.FEATURE_NFC_HOST_CARD_EMULATION); + if (mIsHceCapable) { + mCardEmulationManager = new CardEmulationManager(mContext); + } + mForegroundUtils = ForegroundUtils.getInstance(); + new EnableDisableTask().execute(TASK_BOOT); // do blocking boot tasks + } + + void initSoundPool() { + synchronized (this) { + if (mSoundPool == null) { + mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0); + mStartSound = mSoundPool.load(mContext, R.raw.start, 1); + mEndSound = mSoundPool.load(mContext, R.raw.end, 1); + mErrorSound = mSoundPool.load(mContext, R.raw.error, 1); + } + } + } + + void releaseSoundPool() { + synchronized (this) { + if (mSoundPool != null) { + mSoundPool.release(); + mSoundPool = null; + } + } + } + + void registerForAirplaneMode(IntentFilter filter) { + final String airplaneModeRadios = Settings.System.getString(mContentResolver, + Settings.Global.AIRPLANE_MODE_RADIOS); + final String toggleableRadios = Settings.System.getString(mContentResolver, + Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS); + + mIsAirplaneSensitive = airplaneModeRadios == null ? true : + airplaneModeRadios.contains(Settings.Global.RADIO_NFC); + mIsAirplaneToggleable = toggleableRadios == null ? false : + toggleableRadios.contains(Settings.Global.RADIO_NFC); + + if (mIsAirplaneSensitive) { + filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED); + } + } + + void updatePackageCache() { + PackageManager pm = mContext.getPackageManager(); + List packages = pm.getInstalledPackages(0, UserHandle.USER_OWNER); + synchronized (this) { + mInstalledPackages = packages; + } + } + + boolean isSecHal() { + String nfcSecHal = SystemProperties.get("ro.nfc.sec_hal"); + if (!TextUtils.isEmpty(nfcSecHal)) { + Log.i(TAG, "This device uses SEC NFC CHIP."); + return true; + } + return false; + } + + /** + * Manages tasks that involve turning on/off the NFC controller. + *

+ *

All work that might turn the NFC adapter on or off must be done + * through this task, to keep the handling of mState simple. + * In other words, mState is only modified in these tasks (and we + * don't need a lock to read it in these tasks). + *

+ *

These tasks are all done on the same AsyncTask background + * thread, so they are serialized. Each task may temporarily transition + * mState to STATE_TURNING_OFF or STATE_TURNING_ON, but must exit in + * either STATE_ON or STATE_OFF. This way each task can be guaranteed + * of starting in either STATE_OFF or STATE_ON, without needing to hold + * NfcService.this for the entire task. + *

+ *

AsyncTask's are also implicitly queued. This is useful for corner + * cases like turning airplane mode on while TASK_ENABLE is in progress. + * The TASK_DISABLE triggered by airplane mode will be correctly executed + * immediately after TASK_ENABLE is complete. This seems like the most sane + * way to deal with these situations. + *

+ *

{@link #TASK_ENABLE} enables the NFC adapter, without changing + * preferences + *

{@link #TASK_DISABLE} disables the NFC adapter, without changing + * preferences + *

{@link #TASK_BOOT} does first boot work and may enable NFC + */ + class EnableDisableTask extends AsyncTask { + @Override + protected Void doInBackground(Integer... params) { + // Sanity check mState + switch (mState) { + case NfcAdapter.STATE_TURNING_OFF: + case NfcAdapter.STATE_TURNING_ON: + Log.e(TAG, "Processing EnableDisable task " + params[0] + " from bad state " + + mState); + return null; + } + + /* AsyncTask sets this thread to THREAD_PRIORITY_BACKGROUND, + * override with the default. THREAD_PRIORITY_BACKGROUND causes + * us to service software I2C too slow for firmware download + * with the NXP PN544. + * TODO: move this to the DAL I2C layer in libnfc-nxp, since this + * problem only occurs on I2C platforms using PN544 + */ + Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); + + switch (params[0].intValue()) { + case TASK_ENABLE: + enableInternal(); + break; + case TASK_DISABLE: + disableInternal(); + break; + case TASK_BOOT: + Log.d(TAG, "checking on firmware download"); + boolean airplaneOverride = mPrefs.getBoolean(PREF_AIRPLANE_OVERRIDE, false); + if (mPrefs.getBoolean(PREF_NFC_ON, NFC_ON_DEFAULT) && + (!mIsAirplaneSensitive || !isAirplaneModeOn() || airplaneOverride)) { + Log.d(TAG, "NFC is on. Doing normal stuff"); + enableInternal(); + } else if (!isSecHal()) { + Log.d(TAG, "NFC is off. Checking firmware version"); + mDeviceHost.checkFirmware(); + } + if (mPrefs.getBoolean(PREF_FIRST_BOOT, true)) { + Log.i(TAG, "First Boot"); + mPrefsEditor.putBoolean(PREF_FIRST_BOOT, false); + mPrefsEditor.apply(); + } + break; + } + + // Restore default AsyncTask priority + Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); + return null; + } + + /** + * Enable NFC adapter functions. + * Does not toggle preferences. + */ + boolean enableInternal() { + if (mState == NfcAdapter.STATE_ON) { + return true; + } + Log.i(TAG, "Enabling NFC"); + updateState(NfcAdapter.STATE_TURNING_ON); + + WatchDogThread watchDog = new WatchDogThread("enableInternal", INIT_WATCHDOG_MS); + watchDog.start(); + try { + mRoutingWakeLock.acquire(); + try { + if (!mDeviceHost.initialize()) { + Log.w(TAG, "Error enabling NFC"); + updateState(NfcAdapter.STATE_OFF); + return false; + } + } finally { + mRoutingWakeLock.release(); + } + } finally { + watchDog.cancel(); + } + + if (mIsHceCapable) { + // Generate the initial card emulation routing table + mCardEmulationManager.onNfcEnabled(); + } + + synchronized (NfcService.this) { + mObjectMap.clear(); + mP2pLinkManager.enableDisable(mIsNdefPushEnabled, true); + updateState(NfcAdapter.STATE_ON); + } + + initSoundPool(); + + /* Start polling loop */ + + applyRouting(true); + return true; + } + + /** + * Disable all NFC adapter functions. + * Does not toggle preferences. + */ + boolean disableInternal() { + if (mState == NfcAdapter.STATE_OFF) { + return true; + } + Log.i(TAG, "Disabling NFC"); + updateState(NfcAdapter.STATE_TURNING_OFF); + + /* Sometimes mDeviceHost.deinitialize() hangs, use a watch-dog. + * Implemented with a new thread (instead of a Handler or AsyncTask), + * because the UI Thread and AsyncTask thread-pools can also get hung + * when the NFC controller stops responding */ + WatchDogThread watchDog = new WatchDogThread("disableInternal", ROUTING_WATCHDOG_MS); + watchDog.start(); + + if (mIsHceCapable) { + mCardEmulationManager.onNfcDisabled(); + } + + mP2pLinkManager.enableDisable(false, false); + + // Stop watchdog if tag present + // A convenient way to stop the watchdog properly consists of + // disconnecting the tag. The polling loop shall be stopped before + // to avoid the tag being discovered again. + maybeDisconnectTarget(); + + mNfcDispatcher.setForegroundDispatch(null, null, null); + + + boolean result = mDeviceHost.deinitialize(); + if (DBG) Log.d(TAG, "mDeviceHost.deinitialize() = " + result); + + watchDog.cancel(); + + synchronized (NfcService.this) { + mCurrentDiscoveryParameters = NfcDiscoveryParameters.getNfcOffParameters(); + updateState(NfcAdapter.STATE_OFF); + } + + releaseSoundPool(); + + return result; + } + + void updateState(int newState) { + synchronized (NfcService.this) { + if (newState == mState) { + return; + } + mState = newState; + Intent intent = new Intent(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + intent.putExtra(NfcAdapter.EXTRA_ADAPTER_STATE, mState); + mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); + } + } + } + + void saveNfcOnSetting(boolean on) { + synchronized (NfcService.this) { + mPrefsEditor.putBoolean(PREF_NFC_ON, on); + mPrefsEditor.apply(); + } + } + + public void playSound(int sound) { + synchronized (this) { + if (mSoundPool == null) { + Log.w(TAG, "Not playing sound when NFC is disabled"); + return; + } + switch (sound) { + case SOUND_START: + mSoundPool.play(mStartSound, 1.0f, 1.0f, 0, 0, 1.0f); + break; + case SOUND_END: + mSoundPool.play(mEndSound, 1.0f, 1.0f, 0, 0, 1.0f); + break; + case SOUND_ERROR: + mSoundPool.play(mErrorSound, 1.0f, 1.0f, 0, 0, 1.0f); + break; + } + } + } + + synchronized int getUserId() { + return mUserId; + } + + void setBeamShareActivityState(boolean enabled) { + UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + // Propagate the state change to all user profiles related to the current + // user. Note that the list returned by getUserProfiles contains the + // current user. + List luh = um.getUserProfiles(); + for (UserHandle uh : luh){ + enforceBeamShareActivityPolicy(mContext, uh, enabled); + } + } + + void enforceBeamShareActivityPolicy(Context context, UserHandle uh, + boolean isGlobalEnabled){ + UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); + IPackageManager mIpm = IPackageManager.Stub.asInterface(ServiceManager.getService("package")); + boolean isActiveForUser = + (!um.hasUserRestriction(UserManager.DISALLOW_OUTGOING_BEAM, uh)) && + isGlobalEnabled; + if (DBG){ + Log.d(TAG, "Enforcing a policy change on user: " + uh + + ", isActiveForUser = " + isActiveForUser); + } + try { + mIpm.setComponentEnabledSetting(new ComponentName( + BeamShareActivity.class.getPackageName$(), + BeamShareActivity.class.getName()), + isActiveForUser ? + PackageManager.COMPONENT_ENABLED_STATE_ENABLED : + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP, + uh.getIdentifier()); + } catch (RemoteException e) { + Log.w(TAG, "Unable to change Beam status for user " + uh); + } + } + + final class NfcAdapterService extends INfcAdapter.Stub { + /** + * An interface for vendor specific extensions + */ + public IBinder getNfcAdapterVendorInterface(String vendor) { + return null; + } + + @Override + public boolean enable() throws RemoteException { + NfcPermissions.enforceAdminPermissions(mContext); + + saveNfcOnSetting(true); + + if (mIsAirplaneSensitive && isAirplaneModeOn()) { + if (!mIsAirplaneToggleable) { + Log.i(TAG, "denying enable() request (airplane mode)"); + return false; + } + // Make sure the override survives a reboot + mPrefsEditor.putBoolean(PREF_AIRPLANE_OVERRIDE, true); + mPrefsEditor.apply(); + } + new EnableDisableTask().execute(TASK_ENABLE); + + return true; + } + + @Override + public boolean disable(boolean saveState) throws RemoteException { + NfcPermissions.enforceAdminPermissions(mContext); + + if (saveState) { + saveNfcOnSetting(false); + } + + new EnableDisableTask().execute(TASK_DISABLE); + + return true; + } + + @Override + public void pausePolling(int timeoutInMs) { + NfcPermissions.enforceAdminPermissions(mContext); + + if (timeoutInMs <= 0 || timeoutInMs > MAX_POLLING_PAUSE_TIMEOUT) { + Log.e(TAG, "Refusing to pause polling for " + timeoutInMs + "ms."); + return; + } + + synchronized (NfcService.this) { + mPollingPaused = true; + mDeviceHost.disableDiscovery(); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_RESUME_POLLING), timeoutInMs); + } + } + + @Override + public void resumePolling() { + NfcPermissions.enforceAdminPermissions(mContext); + + synchronized (NfcService.this) { + if (!mPollingPaused) { + return; + } + + mHandler.removeMessages(MSG_RESUME_POLLING); + mPollingPaused = false; + new ApplyRoutingTask().execute(); + } + } + + @Override + public boolean isNdefPushEnabled() throws RemoteException { + synchronized (NfcService.this) { + return mState == NfcAdapter.STATE_ON && mIsNdefPushEnabled; + } + } + + @Override + public boolean enableNdefPush() throws RemoteException { + NfcPermissions.enforceAdminPermissions(mContext); + synchronized (NfcService.this) { + if (mIsNdefPushEnabled) { + return true; + } + Log.i(TAG, "enabling NDEF Push"); + mPrefsEditor.putBoolean(PREF_NDEF_PUSH_ON, true); + mPrefsEditor.apply(); + mIsNdefPushEnabled = true; + setBeamShareActivityState(true); + if (isNfcEnabled()) { + mP2pLinkManager.enableDisable(true, true); + } + } + return true; + } + + @Override + public boolean disableNdefPush() throws RemoteException { + NfcPermissions.enforceAdminPermissions(mContext); + synchronized (NfcService.this) { + if (!mIsNdefPushEnabled) { + return true; + } + Log.i(TAG, "disabling NDEF Push"); + mPrefsEditor.putBoolean(PREF_NDEF_PUSH_ON, false); + mPrefsEditor.apply(); + mIsNdefPushEnabled = false; + setBeamShareActivityState(false); + if (isNfcEnabled()) { + mP2pLinkManager.enableDisable(false, true); + } + } + return true; + } + + @Override + public void setForegroundDispatch(PendingIntent intent, + IntentFilter[] filters, TechListParcel techListsParcel) { + NfcPermissions.enforceUserPermissions(mContext); + + // Short-cut the disable path + if (intent == null && filters == null && techListsParcel == null) { + mNfcDispatcher.setForegroundDispatch(null, null, null); + return; + } + + // Validate the IntentFilters + if (filters != null) { + if (filters.length == 0) { + filters = null; + } else { + for (IntentFilter filter : filters) { + if (filter == null) { + throw new IllegalArgumentException("null IntentFilter"); + } + } + } + } + + // Validate the tech lists + String[][] techLists = null; + if (techListsParcel != null) { + techLists = techListsParcel.getTechLists(); + } + + mNfcDispatcher.setForegroundDispatch(intent, filters, techLists); + } + + + @Override + public void setAppCallback(IAppCallback callback) { + NfcPermissions.enforceUserPermissions(mContext); + + // don't allow Beam for managed profiles, or devices with a device owner or policy owner + UserInfo userInfo = mUserManager.getUserInfo(UserHandle.getCallingUserId()); + if(!mUserManager.hasUserRestriction( + UserManager.DISALLOW_OUTGOING_BEAM, userInfo.getUserHandle())) { + mP2pLinkManager.setNdefCallback(callback, Binder.getCallingUid()); + } else if (DBG) { + Log.d(TAG, "Disabling default Beam behavior"); + } + } + + @Override + public void verifyNfcPermission() { + NfcPermissions.enforceUserPermissions(mContext); + } + + @Override + public void invokeBeam() { + NfcPermissions.enforceUserPermissions(mContext); + + if (mForegroundUtils.isInForeground(Binder.getCallingUid())) { + mP2pLinkManager.onManualBeamInvoke(null); + } else { + Log.e(TAG, "Calling activity not in foreground."); + } + } + + @Override + public void invokeBeamInternal(BeamShareData shareData) { + NfcPermissions.enforceAdminPermissions(mContext); + Message msg = Message.obtain(); + msg.what = MSG_INVOKE_BEAM; + msg.obj = shareData; + // We have to send this message delayed for two reasons: + // 1) This is an IPC call from BeamShareActivity, which is + // running when the user has invoked Beam through the + // share menu. As soon as BeamShareActivity closes, the UI + // will need some time to rebuild the original Activity. + // Waiting here for a while gives a better chance of the UI + // having been rebuilt, which means the screenshot that the + // Beam animation is using will be more accurate. + // 2) Similarly, because the Activity that launched BeamShareActivity + // with an ACTION_SEND intent is now in paused state, the NDEF + // callbacks that it has registered may no longer be valid. + // Allowing the original Activity to resume will make sure we + // it has a chance to re-register the NDEF message / callback, + // so we share the right data. + // + // Note that this is somewhat of a hack because the delay may not actually + // be long enough for 2) on very slow devices, but there's no better + // way to do this right now without additional framework changes. + mHandler.sendMessageDelayed(msg, INVOKE_BEAM_DELAY_MS); + } + + @Override + public INfcTag getNfcTagInterface() throws RemoteException { + return mNfcTagService; + } + + @Override + public INfcCardEmulation getNfcCardEmulationInterface() { + if (mIsHceCapable) { + return mCardEmulationManager.getNfcCardEmulationInterface(); + } else { + return null; + } + } + + @Override + public int getState() throws RemoteException { + synchronized (NfcService.this) { + return mState; + } + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + NfcService.this.dump(fd, pw, args); + } + + @Override + public void dispatch(Tag tag) throws RemoteException { + NfcPermissions.enforceAdminPermissions(mContext); + mNfcDispatcher.dispatchTag(tag); + } + + @Override + public void setP2pModes(int initiatorModes, int targetModes) throws RemoteException { + NfcPermissions.enforceAdminPermissions(mContext); + mDeviceHost.setP2pInitiatorModes(initiatorModes); + mDeviceHost.setP2pTargetModes(targetModes); + applyRouting(true); + } + + @Override + public void setReaderMode(IBinder binder, IAppCallback callback, int flags, Bundle extras) + throws RemoteException { + synchronized (NfcService.this) { + if (!isNfcEnabled()) { + Log.e(TAG, "setReaderMode() called while NFC is not enabled."); + return; + } + if (flags != 0) { + try { + mReaderModeParams = new ReaderModeParams(); + mReaderModeParams.callback = callback; + mReaderModeParams.flags = flags; + mReaderModeParams.presenceCheckDelay = extras != null + ? (extras.getInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, + DEFAULT_PRESENCE_CHECK_DELAY)) + : DEFAULT_PRESENCE_CHECK_DELAY; + binder.linkToDeath(mReaderModeDeathRecipient, 0); + } catch (RemoteException e) { + Log.e(TAG, "Remote binder has already died."); + return; + } + } else { + try { + mReaderModeParams = null; + binder.unlinkToDeath(mReaderModeDeathRecipient, 0); + } catch (NoSuchElementException e) { + Log.e(TAG, "Reader mode Binder was never registered."); + } + } + applyRouting(false); + } + } + + @Override + public INfcAdapterExtras getNfcAdapterExtrasInterface(String pkg) throws RemoteException { + // nfc-extras implementation is no longer present in AOSP. + return null; + } + + @Override + public void addNfcUnlockHandler(INfcUnlockHandler unlockHandler, int[] techList) { + NfcPermissions.enforceAdminPermissions(mContext); + + int lockscreenPollMask = computeLockscreenPollMask(techList); + synchronized (NfcService.this) { + mNfcUnlockManager.addUnlockHandler(unlockHandler, lockscreenPollMask); + } + + applyRouting(false); + } + + @Override + public void removeNfcUnlockHandler(INfcUnlockHandler token) throws RemoteException { + synchronized (NfcService.this) { + mNfcUnlockManager.removeUnlockHandler(token.asBinder()); + } + + applyRouting(false); + } + + private int computeLockscreenPollMask(int[] techList) { + + Map techCodeToMask = new HashMap(); + + techCodeToMask.put(TagTechnology.NFC_A, NfcService.NFC_POLL_A); + techCodeToMask.put(TagTechnology.NFC_B, NfcService.NFC_POLL_B); + techCodeToMask.put(TagTechnology.NFC_V, NfcService.NFC_POLL_ISO15693); + techCodeToMask.put(TagTechnology.NFC_F, NfcService.NFC_POLL_F); + techCodeToMask.put(TagTechnology.NFC_BARCODE, NfcService.NFC_POLL_KOVIO); + + int mask = 0; + + for (int i = 0; i < techList.length; i++) { + if (techCodeToMask.containsKey(techList[i])) { + mask |= techCodeToMask.get(techList[i]).intValue(); + } + } + + return mask; + } + } + + final class ReaderModeDeathRecipient implements IBinder.DeathRecipient { + @Override + public void binderDied() { + synchronized (NfcService.this) { + if (mReaderModeParams != null) { + mReaderModeParams = null; + applyRouting(false); + } + } + } + } + + final class TagService extends INfcTag.Stub { + @Override + public int close(int nativeHandle) throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + + TagEndpoint tag = null; + + if (!isNfcEnabled()) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the tag in the hmap */ + tag = (TagEndpoint) findObject(nativeHandle); + if (tag != null) { + /* Remove the device from the hmap */ + unregisterObject(nativeHandle); + tag.disconnect(); + return ErrorCodes.SUCCESS; + } + /* Restart polling loop for notification */ + applyRouting(true); + return ErrorCodes.ERROR_DISCONNECT; + } + + @Override + public int connect(int nativeHandle, int technology) throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + + TagEndpoint tag = null; + + if (!isNfcEnabled()) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the tag in the hmap */ + tag = (TagEndpoint) findObject(nativeHandle); + if (tag == null) { + return ErrorCodes.ERROR_DISCONNECT; + } + + if (!tag.isPresent()) { + return ErrorCodes.ERROR_DISCONNECT; + } + + // Note that on most tags, all technologies are behind a single + // handle. This means that the connect at the lower levels + // will do nothing, as the tag is already connected to that handle. + if (tag.connect(technology)) { + return ErrorCodes.SUCCESS; + } else { + return ErrorCodes.ERROR_DISCONNECT; + } + } + + @Override + public int reconnect(int nativeHandle) throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + + TagEndpoint tag = null; + + // Check if NFC is enabled + if (!isNfcEnabled()) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the tag in the hmap */ + tag = (TagEndpoint) findObject(nativeHandle); + if (tag != null) { + if (tag.reconnect()) { + return ErrorCodes.SUCCESS; + } else { + return ErrorCodes.ERROR_DISCONNECT; + } + } + return ErrorCodes.ERROR_DISCONNECT; + } + + @Override + public int[] getTechList(int nativeHandle) throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + + // Check if NFC is enabled + if (!isNfcEnabled()) { + return null; + } + + /* find the tag in the hmap */ + TagEndpoint tag = (TagEndpoint) findObject(nativeHandle); + if (tag != null) { + return tag.getTechList(); + } + return null; + } + + @Override + public boolean isPresent(int nativeHandle) throws RemoteException { + TagEndpoint tag = null; + + // Check if NFC is enabled + if (!isNfcEnabled()) { + return false; + } + + /* find the tag in the hmap */ + tag = (TagEndpoint) findObject(nativeHandle); + if (tag == null) { + return false; + } + + return tag.isPresent(); + } + + @Override + public boolean isNdef(int nativeHandle) throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + + TagEndpoint tag = null; + + // Check if NFC is enabled + if (!isNfcEnabled()) { + return false; + } + + /* find the tag in the hmap */ + tag = (TagEndpoint) findObject(nativeHandle); + int[] ndefInfo = new int[2]; + if (tag == null) { + return false; + } + return tag.checkNdef(ndefInfo); + } + + @Override + public TransceiveResult transceive(int nativeHandle, byte[] data, boolean raw) + throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + + TagEndpoint tag = null; + byte[] response; + + // Check if NFC is enabled + if (!isNfcEnabled()) { + return null; + } + + /* find the tag in the hmap */ + tag = (TagEndpoint) findObject(nativeHandle); + if (tag != null) { + // Check if length is within limits + if (data.length > getMaxTransceiveLength(tag.getConnectedTechnology())) { + return new TransceiveResult(TransceiveResult.RESULT_EXCEEDED_LENGTH, null); + } + int[] targetLost = new int[1]; + response = tag.transceive(data, raw, targetLost); + int result; + if (response != null) { + result = TransceiveResult.RESULT_SUCCESS; + } else if (targetLost[0] == 1) { + result = TransceiveResult.RESULT_TAGLOST; + } else { + result = TransceiveResult.RESULT_FAILURE; + } + return new TransceiveResult(result, response); + } + return null; + } + + @Override + public NdefMessage ndefRead(int nativeHandle) throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + + TagEndpoint tag; + + // Check if NFC is enabled + if (!isNfcEnabled()) { + return null; + } + + /* find the tag in the hmap */ + tag = (TagEndpoint) findObject(nativeHandle); + if (tag != null) { + byte[] buf = tag.readNdef(); + if (buf == null) { + return null; + } + + /* Create an NdefMessage */ + try { + return new NdefMessage(buf); + } catch (FormatException e) { + return null; + } + } + return null; + } + + @Override + public int ndefWrite(int nativeHandle, NdefMessage msg) throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + + TagEndpoint tag; + + // Check if NFC is enabled + if (!isNfcEnabled()) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the tag in the hmap */ + tag = (TagEndpoint) findObject(nativeHandle); + if (tag == null) { + return ErrorCodes.ERROR_IO; + } + + if (msg == null) return ErrorCodes.ERROR_INVALID_PARAM; + + if (tag.writeNdef(msg.toByteArray())) { + return ErrorCodes.SUCCESS; + } else { + return ErrorCodes.ERROR_IO; + } + + } + + @Override + public boolean ndefIsWritable(int nativeHandle) throws RemoteException { + throw new UnsupportedOperationException(); + } + + @Override + public int ndefMakeReadOnly(int nativeHandle) throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + + TagEndpoint tag; + + // Check if NFC is enabled + if (!isNfcEnabled()) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the tag in the hmap */ + tag = (TagEndpoint) findObject(nativeHandle); + if (tag == null) { + return ErrorCodes.ERROR_IO; + } + + if (tag.makeReadOnly()) { + return ErrorCodes.SUCCESS; + } else { + return ErrorCodes.ERROR_IO; + } + } + + @Override + public int formatNdef(int nativeHandle, byte[] key) throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + + TagEndpoint tag; + + // Check if NFC is enabled + if (!isNfcEnabled()) { + return ErrorCodes.ERROR_NOT_INITIALIZED; + } + + /* find the tag in the hmap */ + tag = (TagEndpoint) findObject(nativeHandle); + if (tag == null) { + return ErrorCodes.ERROR_IO; + } + + if (tag.formatNdef(key)) { + return ErrorCodes.SUCCESS; + } else { + return ErrorCodes.ERROR_IO; + } + } + + @Override + public Tag rediscover(int nativeHandle) throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + + TagEndpoint tag = null; + + // Check if NFC is enabled + if (!isNfcEnabled()) { + return null; + } + + /* find the tag in the hmap */ + tag = (TagEndpoint) findObject(nativeHandle); + if (tag != null) { + // For now the prime usecase for rediscover() is to be able + // to access the NDEF technology after formatting without + // having to remove the tag from the field, or similar + // to have access to NdefFormatable in case low-level commands + // were used to remove NDEF. So instead of doing a full stack + // rediscover (which is poorly supported at the moment anyway), + // we simply remove these two technologies and detect them + // again. + tag.removeTechnology(TagTechnology.NDEF); + tag.removeTechnology(TagTechnology.NDEF_FORMATABLE); + tag.findAndReadNdef(); + // Build a new Tag object to return + Tag newTag = new Tag(tag.getUid(), tag.getTechList(), + tag.getTechExtras(), tag.getHandle(), this); + return newTag; + } + return null; + } + + @Override + public int setTimeout(int tech, int timeout) throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + boolean success = mDeviceHost.setTimeout(tech, timeout); + if (success) { + return ErrorCodes.SUCCESS; + } else { + return ErrorCodes.ERROR_INVALID_PARAM; + } + } + + @Override + public int getTimeout(int tech) throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + + return mDeviceHost.getTimeout(tech); + } + + @Override + public void resetTimeouts() throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + + mDeviceHost.resetTimeouts(); + } + + @Override + public boolean canMakeReadOnly(int ndefType) throws RemoteException { + return mDeviceHost.canMakeReadOnly(ndefType); + } + + @Override + public int getMaxTransceiveLength(int tech) throws RemoteException { + return mDeviceHost.getMaxTransceiveLength(tech); + } + + @Override + public boolean getExtendedLengthApdusSupported() throws RemoteException { + return mDeviceHost.getExtendedLengthApdusSupported(); + } + } + + boolean isNfcEnabledOrShuttingDown() { + synchronized (this) { + return (mState == NfcAdapter.STATE_ON || mState == NfcAdapter.STATE_TURNING_OFF); + } + } + + boolean isNfcEnabled() { + synchronized (this) { + return mState == NfcAdapter.STATE_ON; + } + } + + class WatchDogThread extends Thread { + final Object mCancelWaiter = new Object(); + final int mTimeout; + boolean mCanceled = false; + + public WatchDogThread(String threadName, int timeout) { + super(threadName); + mTimeout = timeout; + } + + @Override + public void run() { + try { + synchronized (mCancelWaiter) { + mCancelWaiter.wait(mTimeout); + if (mCanceled) { + return; + } + } + } catch (InterruptedException e) { + // Should not happen; fall-through to abort. + Log.w(TAG, "Watchdog thread interruped."); + interrupt(); + } + Log.e(TAG, "Watchdog triggered, aborting."); + mDeviceHost.doAbort(); + } + + public synchronized void cancel() { + synchronized (mCancelWaiter) { + mCanceled = true; + mCancelWaiter.notify(); + } + } + } + + static byte[] hexStringToBytes(String s) { + if (s == null || s.length() == 0) return null; + int len = s.length(); + if (len % 2 != 0) { + s = '0' + s; + len++; + } + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } + + /** + * Read mScreenState and apply NFC-C polling and NFC-EE routing + */ + void applyRouting(boolean force) { + synchronized (this) { + if (!isNfcEnabledOrShuttingDown()) { + return; + } + WatchDogThread watchDog = new WatchDogThread("applyRouting", ROUTING_WATCHDOG_MS); + if (mInProvisionMode) { + mInProvisionMode = Settings.Secure.getInt(mContentResolver, + Settings.Global.DEVICE_PROVISIONED, 0) == 0; + if (!mInProvisionMode) { + // Notify dispatcher it's fine to dispatch to any package now + // and allow handover transfers. + mNfcDispatcher.disableProvisioningMode(); + } + } + // Special case: if we're transitioning to unlocked state while + // still talking to a tag, postpone re-configuration. + if (mScreenState == ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED && isTagPresent()) { + Log.d(TAG, "Not updating discovery parameters, tag connected."); + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESUME_POLLING), + APPLY_ROUTING_RETRY_TIMEOUT_MS); + return; + } + + try { + watchDog.start(); + // Compute new polling parameters + NfcDiscoveryParameters newParams = computeDiscoveryParameters(mScreenState); + if (force || !newParams.equals(mCurrentDiscoveryParameters)) { + if (newParams.shouldEnableDiscovery()) { + boolean shouldRestart = mCurrentDiscoveryParameters.shouldEnableDiscovery(); + mDeviceHost.enableDiscovery(newParams, shouldRestart); + } else { + mDeviceHost.disableDiscovery(); + } + mCurrentDiscoveryParameters = newParams; + } else { + Log.d(TAG, "Discovery configuration equal, not updating."); + } + } finally { + watchDog.cancel(); + } + } + } + + private NfcDiscoveryParameters computeDiscoveryParameters(int screenState) { + // Recompute discovery parameters based on screen state + NfcDiscoveryParameters.Builder paramsBuilder = NfcDiscoveryParameters.newBuilder(); + // Polling + if (screenState >= NFC_POLLING_MODE) { + // Check if reader-mode is enabled + if (mReaderModeParams != null) { + int techMask = 0; + if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_A) != 0) + techMask |= NFC_POLL_A; + if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_B) != 0) + techMask |= NFC_POLL_B; + if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_F) != 0) + techMask |= NFC_POLL_F; + if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_V) != 0) + techMask |= NFC_POLL_ISO15693; + if ((mReaderModeParams.flags & NfcAdapter.FLAG_READER_NFC_BARCODE) != 0) + techMask |= NFC_POLL_KOVIO; + + paramsBuilder.setTechMask(techMask); + paramsBuilder.setEnableReaderMode(true); + } else { + paramsBuilder.setTechMask(NfcDiscoveryParameters.NFC_POLL_DEFAULT); + paramsBuilder.setEnableP2p(mIsNdefPushEnabled); + } + } else if (screenState == ScreenStateHelper.SCREEN_STATE_ON_LOCKED && mInProvisionMode) { + paramsBuilder.setTechMask(NfcDiscoveryParameters.NFC_POLL_DEFAULT); + // enable P2P for MFM/EDU/Corp provisioning + paramsBuilder.setEnableP2p(true); + } else if (screenState == ScreenStateHelper.SCREEN_STATE_ON_LOCKED && + mNfcUnlockManager.isLockscreenPollingEnabled()) { + // For lock-screen tags, no low-power polling + paramsBuilder.setTechMask(mNfcUnlockManager.getLockscreenPollMask()); + paramsBuilder.setEnableLowPowerDiscovery(false); + paramsBuilder.setEnableP2p(false); + } + + if (mIsHceCapable && mScreenState >= ScreenStateHelper.SCREEN_STATE_ON_LOCKED) { + // Host routing is always enabled at lock screen or later + paramsBuilder.setEnableHostRouting(true); + } + + return paramsBuilder.build(); + } + + private boolean isTagPresent() { + for (Object object : mObjectMap.values()) { + if (object instanceof TagEndpoint) { + return ((TagEndpoint) object).isPresent(); + } + } + return false; + } + /** + * Disconnect any target if present + */ + void maybeDisconnectTarget() { + if (!isNfcEnabledOrShuttingDown()) { + return; + } + Object[] objectsToDisconnect; + synchronized (this) { + Object[] objectValues = mObjectMap.values().toArray(); + // Copy the array before we clear mObjectMap, + // just in case the HashMap values are backed by the same array + objectsToDisconnect = Arrays.copyOf(objectValues, objectValues.length); + mObjectMap.clear(); + } + for (Object o : objectsToDisconnect) { + if (DBG) Log.d(TAG, "disconnecting " + o.getClass().getName()); + if (o instanceof TagEndpoint) { + // Disconnect from tags + TagEndpoint tag = (TagEndpoint) o; + tag.disconnect(); + } else if (o instanceof NfcDepEndpoint) { + // Disconnect from P2P devices + NfcDepEndpoint device = (NfcDepEndpoint) o; + if (device.getMode() == NfcDepEndpoint.MODE_P2P_TARGET) { + // Remote peer is target, request disconnection + device.disconnect(); + } else { + // Remote peer is initiator, we cannot disconnect + // Just wait for field removal + } + } + } + } + + Object findObject(int key) { + synchronized (this) { + Object device = mObjectMap.get(key); + if (device == null) { + Log.w(TAG, "Handle not found"); + } + return device; + } + } + + void registerTagObject(TagEndpoint tag) { + synchronized (this) { + mObjectMap.put(tag.getHandle(), tag); + } + } + + void unregisterObject(int handle) { + synchronized (this) { + mObjectMap.remove(handle); + } + } + + /** + * For use by code in this process + */ + public LlcpSocket createLlcpSocket(int sap, int miu, int rw, int linearBufferLength) + throws LlcpException { + return mDeviceHost.createLlcpSocket(sap, miu, rw, linearBufferLength); + } + + /** + * For use by code in this process + */ + public LlcpConnectionlessSocket createLlcpConnectionLessSocket(int sap, String sn) + throws LlcpException { + return mDeviceHost.createLlcpConnectionlessSocket(sap, sn); + } + + /** + * For use by code in this process + */ + public LlcpServerSocket createLlcpServerSocket(int sap, String sn, int miu, int rw, + int linearBufferLength) throws LlcpException { + return mDeviceHost.createLlcpServerSocket(sap, sn, miu, rw, linearBufferLength); + } + + public void sendMockNdefTag(NdefMessage msg) { + sendMessage(MSG_MOCK_NDEF, msg); + } + + public void routeAids(String aid, int route) { + Message msg = mHandler.obtainMessage(); + msg.what = MSG_ROUTE_AID; + msg.arg1 = route; + msg.obj = aid; + mHandler.sendMessage(msg); + } + + public void unrouteAids(String aid) { + sendMessage(MSG_UNROUTE_AID, aid); + } + + public void commitRouting() { + mHandler.sendEmptyMessage(MSG_COMMIT_ROUTING); + } + + public boolean sendData(byte[] data) { + return mDeviceHost.sendRawFrame(data); + } + + void sendMessage(int what, Object obj) { + Message msg = mHandler.obtainMessage(); + msg.what = what; + msg.obj = obj; + mHandler.sendMessage(msg); + } + + final class NfcServiceHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_ROUTE_AID: { + int route = msg.arg1; + String aid = (String) msg.obj; + mDeviceHost.routeAid(hexStringToBytes(aid), route); + // Restart polling config + break; + } + case MSG_UNROUTE_AID: { + String aid = (String) msg.obj; + mDeviceHost.unrouteAid(hexStringToBytes(aid)); + break; + } + case MSG_INVOKE_BEAM: { + mP2pLinkManager.onManualBeamInvoke((BeamShareData)msg.obj); + break; + } + case MSG_COMMIT_ROUTING: { + boolean commit = false; + synchronized (NfcService.this) { + if (mCurrentDiscoveryParameters.shouldEnableDiscovery()) { + commit = true; + } else { + Log.d(TAG, "Not committing routing because discovery is disabled."); + } + } + if (commit) { + mDeviceHost.commitRouting(); + } + break; + } + case MSG_MOCK_NDEF: { + NdefMessage ndefMsg = (NdefMessage) msg.obj; + Bundle extras = new Bundle(); + extras.putParcelable(Ndef.EXTRA_NDEF_MSG, ndefMsg); + extras.putInt(Ndef.EXTRA_NDEF_MAXLENGTH, 0); + extras.putInt(Ndef.EXTRA_NDEF_CARDSTATE, Ndef.NDEF_MODE_READ_ONLY); + extras.putInt(Ndef.EXTRA_NDEF_TYPE, Ndef.TYPE_OTHER); + Tag tag = Tag.createMockTag(new byte[]{0x00}, + new int[]{TagTechnology.NDEF}, + new Bundle[]{extras}); + Log.d(TAG, "mock NDEF tag, starting corresponding activity"); + Log.d(TAG, tag.toString()); + int dispatchStatus = mNfcDispatcher.dispatchTag(tag); + if (dispatchStatus == NfcDispatcher.DISPATCH_SUCCESS) { + playSound(SOUND_END); + } else if (dispatchStatus == NfcDispatcher.DISPATCH_FAIL) { + playSound(SOUND_ERROR); + } + break; + } + + case MSG_NDEF_TAG: + if (DBG) Log.d(TAG, "Tag detected, notifying applications"); + TagEndpoint tag = (TagEndpoint) msg.obj; + ReaderModeParams readerParams = null; + int presenceCheckDelay = DEFAULT_PRESENCE_CHECK_DELAY; + DeviceHost.TagDisconnectedCallback callback = + new DeviceHost.TagDisconnectedCallback() { + @Override + public void onTagDisconnected(long handle) { + applyRouting(false); + } + }; + synchronized (NfcService.this) { + readerParams = mReaderModeParams; + } + if (readerParams != null) { + presenceCheckDelay = readerParams.presenceCheckDelay; + if ((readerParams.flags & NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK) != 0) { + if (DBG) Log.d(TAG, "Skipping NDEF detection in reader mode"); + tag.startPresenceChecking(presenceCheckDelay, callback); + dispatchTagEndpoint(tag, readerParams); + break; + } + } + + boolean playSound = readerParams == null || + (readerParams.flags & NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS) == 0; + if (mScreenState == ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED && playSound) { + playSound(SOUND_START); + } + if (tag.getConnectedTechnology() == TagTechnology.NFC_BARCODE) { + // When these tags start containing NDEF, they will require + // the stack to deal with them in a different way, since + // they are activated only really shortly. + // For now, don't consider NDEF on these. + if (DBG) Log.d(TAG, "Skipping NDEF detection for NFC Barcode"); + tag.startPresenceChecking(presenceCheckDelay, callback); + dispatchTagEndpoint(tag, readerParams); + break; + } + NdefMessage ndefMsg = tag.findAndReadNdef(); + + if (ndefMsg != null) { + tag.startPresenceChecking(presenceCheckDelay, callback); + dispatchTagEndpoint(tag, readerParams); + } else { + if (tag.reconnect()) { + tag.startPresenceChecking(presenceCheckDelay, callback); + dispatchTagEndpoint(tag, readerParams); + } else { + tag.disconnect(); + playSound(SOUND_ERROR); + } + } + break; + case MSG_LLCP_LINK_ACTIVATION: + if (mIsDebugBuild) { + Intent actIntent = new Intent(ACTION_LLCP_UP); + mContext.sendBroadcast(actIntent); + } + llcpActivated((NfcDepEndpoint) msg.obj); + break; + + case MSG_LLCP_LINK_DEACTIVATED: + if (mIsDebugBuild) { + Intent deactIntent = new Intent(ACTION_LLCP_DOWN); + mContext.sendBroadcast(deactIntent); + } + NfcDepEndpoint device = (NfcDepEndpoint) msg.obj; + boolean needsDisconnect = false; + + Log.d(TAG, "LLCP Link Deactivated message. Restart polling loop."); + synchronized (NfcService.this) { + /* Check if the device has been already unregistered */ + if (mObjectMap.remove(device.getHandle()) != null) { + /* Disconnect if we are initiator */ + if (device.getMode() == NfcDepEndpoint.MODE_P2P_TARGET) { + if (DBG) Log.d(TAG, "disconnecting from target"); + needsDisconnect = true; + } else { + if (DBG) Log.d(TAG, "not disconnecting from initiator"); + } + } + } + if (needsDisconnect) { + device.disconnect(); // restarts polling loop + } + + mP2pLinkManager.onLlcpDeactivated(); + break; + case MSG_LLCP_LINK_FIRST_PACKET: + mP2pLinkManager.onLlcpFirstPacketReceived(); + break; + case MSG_RF_FIELD_ACTIVATED: + Intent fieldOnIntent = new Intent(ACTION_RF_FIELD_ON_DETECTED); + sendNfcEeAccessProtectedBroadcast(fieldOnIntent); + break; + case MSG_RF_FIELD_DEACTIVATED: + Intent fieldOffIntent = new Intent(ACTION_RF_FIELD_OFF_DETECTED); + sendNfcEeAccessProtectedBroadcast(fieldOffIntent); + break; + case MSG_RESUME_POLLING: + mNfcAdapter.resumePolling(); + break; + default: + Log.e(TAG, "Unknown message received"); + break; + } + } + + private void sendNfcEeAccessProtectedBroadcast(Intent intent) { + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + // Resume app switches so the receivers can start activites without delay + mNfcDispatcher.resumeAppSwitches(); + ArrayList matchingPackages = new ArrayList(); + ArrayList preferredPackages = new ArrayList(); + synchronized (this) { + for (PackageInfo pkg : mInstalledPackages) { + if (pkg != null && pkg.applicationInfo != null) { + if (mNfceeAccessControl.check(pkg.applicationInfo)) { + matchingPackages.add(pkg.packageName); + if (mCardEmulationManager != null && + mCardEmulationManager.packageHasPreferredService(pkg.packageName)) { + preferredPackages.add(pkg.packageName); + } + } + } + } + if (preferredPackages.size() > 0) { + // If there's any packages in here which are preferred, only + // send field events to those packages, to prevent other apps + // with signatures in nfcee_access.xml from acting upon the events. + for (String packageName : preferredPackages){ + intent.setPackage(packageName); + mContext.sendBroadcast(intent); + } + } else { + for (String packageName : matchingPackages){ + intent.setPackage(packageName); + mContext.sendBroadcast(intent); + } + } + } + } + + private boolean llcpActivated(NfcDepEndpoint device) { + Log.d(TAG, "LLCP Activation message"); + + if (device.getMode() == NfcDepEndpoint.MODE_P2P_TARGET) { + if (DBG) Log.d(TAG, "NativeP2pDevice.MODE_P2P_TARGET"); + if (device.connect()) { + /* Check LLCP compliancy */ + if (mDeviceHost.doCheckLlcp()) { + /* Activate LLCP Link */ + if (mDeviceHost.doActivateLlcp()) { + if (DBG) Log.d(TAG, "Initiator Activate LLCP OK"); + synchronized (NfcService.this) { + // Register P2P device + mObjectMap.put(device.getHandle(), device); + } + mP2pLinkManager.onLlcpActivated(device.getLlcpVersion()); + return true; + } else { + /* should not happen */ + Log.w(TAG, "Initiator LLCP activation failed. Disconnect."); + device.disconnect(); + } + } else { + if (DBG) Log.d(TAG, "Remote Target does not support LLCP. Disconnect."); + device.disconnect(); + } + } else { + if (DBG) Log.d(TAG, "Cannot connect remote Target. Polling loop restarted."); + /* + * The polling loop should have been restarted in failing + * doConnect + */ + } + } else if (device.getMode() == NfcDepEndpoint.MODE_P2P_INITIATOR) { + if (DBG) Log.d(TAG, "NativeP2pDevice.MODE_P2P_INITIATOR"); + /* Check LLCP compliancy */ + if (mDeviceHost.doCheckLlcp()) { + /* Activate LLCP Link */ + if (mDeviceHost.doActivateLlcp()) { + if (DBG) Log.d(TAG, "Target Activate LLCP OK"); + synchronized (NfcService.this) { + // Register P2P device + mObjectMap.put(device.getHandle(), device); + } + mP2pLinkManager.onLlcpActivated(device.getLlcpVersion()); + return true; + } + } else { + Log.w(TAG, "checkLlcp failed"); + } + } + + return false; + } + + private void dispatchTagEndpoint(TagEndpoint tagEndpoint, ReaderModeParams readerParams) { + Tag tag = new Tag(tagEndpoint.getUid(), tagEndpoint.getTechList(), + tagEndpoint.getTechExtras(), tagEndpoint.getHandle(), mNfcTagService); + registerTagObject(tagEndpoint); + if (readerParams != null) { + try { + if ((readerParams.flags & NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS) == 0) { + playSound(SOUND_END); + } + if (readerParams.callback != null) { + readerParams.callback.onTagDiscovered(tag); + return; + } else { + // Follow normal dispatch below + } + } catch (RemoteException e) { + Log.e(TAG, "Reader mode remote has died, falling back.", e); + // Intentional fall-through + } catch (Exception e) { + // Catch any other exception + Log.e(TAG, "App exception, not dispatching.", e); + return; + } + } + int dispatchResult = mNfcDispatcher.dispatchTag(tag); + if (dispatchResult == NfcDispatcher.DISPATCH_FAIL) { + unregisterObject(tagEndpoint.getHandle()); + playSound(SOUND_ERROR); + } else if (dispatchResult == NfcDispatcher.DISPATCH_SUCCESS) { + playSound(SOUND_END); + } + } + } + + private NfcServiceHandler mHandler = new NfcServiceHandler(); + + class ApplyRoutingTask extends AsyncTask { + @Override + protected Void doInBackground(Integer... params) { + synchronized (NfcService.this) { + if (params == null || params.length != 1) { + // force apply current routing + applyRouting(true); + return null; + } + mScreenState = params[0].intValue(); + + mRoutingWakeLock.acquire(); + try { + applyRouting(false); + } finally { + mRoutingWakeLock.release(); + } + return null; + } + } + } + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(Intent.ACTION_SCREEN_ON) + || action.equals(Intent.ACTION_SCREEN_OFF) + || action.equals(Intent.ACTION_USER_PRESENT)) { + // Perform applyRouting() in AsyncTask to serialize blocking calls + int screenState = ScreenStateHelper.SCREEN_STATE_OFF; + if (action.equals(Intent.ACTION_SCREEN_OFF)) { + screenState = ScreenStateHelper.SCREEN_STATE_OFF; + } else if (action.equals(Intent.ACTION_SCREEN_ON)) { + screenState = mKeyguard.isKeyguardLocked() + ? ScreenStateHelper.SCREEN_STATE_ON_LOCKED + : ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED; + } else if (action.equals(Intent.ACTION_USER_PRESENT)) { + screenState = ScreenStateHelper.SCREEN_STATE_ON_UNLOCKED; + } + + new ApplyRoutingTask().execute(Integer.valueOf(screenState)); + } else if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) { + boolean isAirplaneModeOn = intent.getBooleanExtra("state", false); + // Query the airplane mode from Settings.System just to make sure that + // some random app is not sending this intent + if (isAirplaneModeOn != isAirplaneModeOn()) { + return; + } + if (!mIsAirplaneSensitive) { + return; + } + mPrefsEditor.putBoolean(PREF_AIRPLANE_OVERRIDE, false); + mPrefsEditor.apply(); + if (isAirplaneModeOn) { + new EnableDisableTask().execute(TASK_DISABLE); + } else if (!isAirplaneModeOn && mPrefs.getBoolean(PREF_NFC_ON, NFC_ON_DEFAULT)) { + new EnableDisableTask().execute(TASK_ENABLE); + } + } else if (action.equals(Intent.ACTION_USER_SWITCHED)) { + int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); + synchronized (this) { + mUserId = userId; + } + mP2pLinkManager.onUserSwitched(getUserId()); + if (mIsHceCapable) { + mCardEmulationManager.onUserSwitched(getUserId()); + } + } + } + }; + + + private final BroadcastReceiver mOwnerReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(Intent.ACTION_PACKAGE_REMOVED) || + action.equals(Intent.ACTION_PACKAGE_ADDED) || + action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE) || + action.equals(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE)) { + updatePackageCache(); + + if (action.equals(Intent.ACTION_PACKAGE_REMOVED)) { + // Clear the NFCEE access cache in case a UID gets recycled + mNfceeAccessControl.invalidateCache(); + } + } + } + }; + + private final BroadcastReceiver mPolicyReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent){ + String action = intent.getAction(); + if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED + .equals(action)) { + enforceBeamShareActivityPolicy(context, + new UserHandle(getSendingUserId()), mIsNdefPushEnabled); + } + } + }; + + /** + * Returns true if airplane mode is currently on + */ + boolean isAirplaneModeOn() { + return Settings.System.getInt(mContentResolver, + Settings.Global.AIRPLANE_MODE_ON, 0) == 1; + } + + /** + * for debugging only - no i18n + */ + static String stateToString(int state) { + switch (state) { + case NfcAdapter.STATE_OFF: + return "off"; + case NfcAdapter.STATE_TURNING_ON: + return "turning on"; + case NfcAdapter.STATE_ON: + return "on"; + case NfcAdapter.STATE_TURNING_OFF: + return "turning off"; + default: + return ""; + } + } + + void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump nfc from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " without permission " + android.Manifest.permission.DUMP); + return; + } + + synchronized (this) { + pw.println("mState=" + stateToString(mState)); + pw.println("mIsZeroClickRequested=" + mIsNdefPushEnabled); + pw.println("mScreenState=" + ScreenStateHelper.screenStateToString(mScreenState)); + pw.println("mIsAirplaneSensitive=" + mIsAirplaneSensitive); + pw.println("mIsAirplaneToggleable=" + mIsAirplaneToggleable); + pw.println(mCurrentDiscoveryParameters); + mP2pLinkManager.dump(fd, pw, args); + if (mIsHceCapable) { + mCardEmulationManager.dump(fd, pw, args); + } + mNfcDispatcher.dump(fd, pw, args); + pw.println(mDeviceHost.dump()); + } + } +} diff --git a/NfcSony/src/com/android/nfc/NfcUnlockManager.java b/NfcSony/src/com/android/nfc/NfcUnlockManager.java new file mode 100644 index 0000000..8d2b249 --- /dev/null +++ b/NfcSony/src/com/android/nfc/NfcUnlockManager.java @@ -0,0 +1,99 @@ +package com.android.nfc; + +import android.nfc.INfcUnlockHandler; +import android.nfc.Tag; +import android.os.IBinder; +import android.util.Log; + +import java.util.HashMap; +import java.util.Iterator; + +/** + * Singleton for handling NFC Unlock related logic and state. + */ +class NfcUnlockManager { + private static final String TAG = "NfcUnlockManager"; + + private final HashMap mUnlockHandlers; + private int mLockscreenPollMask; + + private static class UnlockHandlerWrapper { + final INfcUnlockHandler mUnlockHandler; + final int mPollMask; + + + private UnlockHandlerWrapper(INfcUnlockHandler unlockHandler, int pollMask) { + mUnlockHandler = unlockHandler; + mPollMask = pollMask; + } + } + + public static NfcUnlockManager getInstance() { + return Singleton.INSTANCE; + } + + + synchronized int addUnlockHandler(INfcUnlockHandler unlockHandler, int pollMask) { + if (mUnlockHandlers.containsKey(unlockHandler.asBinder())) { + return mLockscreenPollMask; + } + + mUnlockHandlers.put(unlockHandler.asBinder(), + new UnlockHandlerWrapper(unlockHandler, pollMask)); + return (mLockscreenPollMask |= pollMask); + } + + synchronized int removeUnlockHandler(IBinder unlockHandler) { + if (mUnlockHandlers.containsKey(unlockHandler)) { + mUnlockHandlers.remove(unlockHandler); + mLockscreenPollMask = recomputePollMask(); + } + + return mLockscreenPollMask; + } + + synchronized boolean tryUnlock(Tag tag) { + Iterator iterator = mUnlockHandlers.keySet().iterator(); + while (iterator.hasNext()) { + try { + IBinder binder = iterator.next(); + UnlockHandlerWrapper handlerWrapper = mUnlockHandlers.get(binder); + if (handlerWrapper.mUnlockHandler.onUnlockAttempted(tag)) { + return true; + } + } catch (Exception e) { + Log.e(TAG, "failed to communicate with unlock handler, removing", e); + iterator.remove(); + mLockscreenPollMask = recomputePollMask(); + } + } + + return false; + } + + private int recomputePollMask() { + int pollMask = 0; + for (UnlockHandlerWrapper wrapper : mUnlockHandlers.values()) { + pollMask |= wrapper.mPollMask; + } + return pollMask; + } + + synchronized int getLockscreenPollMask() { + return mLockscreenPollMask; + } + + synchronized boolean isLockscreenPollingEnabled() { + return mLockscreenPollMask != 0; + } + + private static class Singleton { + private static final NfcUnlockManager INSTANCE = new NfcUnlockManager(); + } + + private NfcUnlockManager() { + mUnlockHandlers = new HashMap(); + mLockscreenPollMask = 0; + } +} + diff --git a/NfcSony/src/com/android/nfc/NfcWifiProtectedSetup.java b/NfcSony/src/com/android/nfc/NfcWifiProtectedSetup.java new file mode 100644 index 0000000..d642175 --- /dev/null +++ b/NfcSony/src/com/android/nfc/NfcWifiProtectedSetup.java @@ -0,0 +1,170 @@ +/* +* Copyright (C) 2014 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.nfc; + + +import android.content.Context; +import android.content.Intent; +import android.net.wifi.WifiConfiguration; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.tech.Ndef; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.Log; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.BitSet; + +public final class NfcWifiProtectedSetup { + + public static final String NFC_TOKEN_MIME_TYPE = "application/vnd.wfa.wsc"; + + public static final String EXTRA_WIFI_CONFIG = "com.android.nfc.WIFI_CONFIG_EXTRA"; + + /* + * ID into configuration record for SSID and Network Key in hex. + * Obtained from WFA Wifi Simple Configuration Technical Specification v2.0.2.1. + */ + private static final short CREDENTIAL_FIELD_ID = 0x100E; + private static final short SSID_FIELD_ID = 0x1045; + private static final short NETWORK_KEY_FIELD_ID = 0x1027; + private static final short AUTH_TYPE_FIELD_ID = 0x1003; + + private static final short AUTH_TYPE_EXPECTED_SIZE = 2; + + private static final short AUTH_TYPE_OPEN = 0; + private static final short AUTH_TYPE_WPA_PSK = 0x0002; + private static final short AUTH_TYPE_WPA_EAP = 0x0008; + private static final short AUTH_TYPE_WPA2_EAP = 0x0010; + private static final short AUTH_TYPE_WPA2_PSK = 0x0020; + + private static final int MAX_NETWORK_KEY_SIZE_BYTES = 64; + + private NfcWifiProtectedSetup() {} + + public static boolean tryNfcWifiSetup(Ndef ndef, Context context) { + + if (ndef == null || context == null) { + return false; + } + + NdefMessage cachedNdefMessage = ndef.getCachedNdefMessage(); + if (cachedNdefMessage == null) { + return false; + } + + final WifiConfiguration wifiConfiguration; + try { + wifiConfiguration = parse(cachedNdefMessage); + } catch (BufferUnderflowException e) { + // malformed payload + return false; + } + + if (wifiConfiguration != null &&!UserManager.get(context).hasUserRestriction( + UserManager.DISALLOW_CONFIG_WIFI, UserHandle.CURRENT)) { + Intent configureNetworkIntent = new Intent() + .putExtra(EXTRA_WIFI_CONFIG, wifiConfiguration) + .setClass(context, ConfirmConnectToWifiNetworkActivity.class) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + + context.startActivityAsUser(configureNetworkIntent, UserHandle.CURRENT); + return true; + } + + return false; + } + + private static WifiConfiguration parse(NdefMessage message) { + NdefRecord[] records = message.getRecords(); + + for (NdefRecord record : records) { + if (new String(record.getType()).equals(NFC_TOKEN_MIME_TYPE)) { + ByteBuffer payload = ByteBuffer.wrap(record.getPayload()); + while (payload.hasRemaining()) { + short fieldId = payload.getShort(); + short fieldSize = payload.getShort(); + if (fieldId == CREDENTIAL_FIELD_ID) { + return parseCredential(payload, fieldSize); + } + } + } + } + return null; + } + + private static WifiConfiguration parseCredential(ByteBuffer payload, short size) { + int startPosition = payload.position(); + WifiConfiguration result = new WifiConfiguration(); + while (payload.position() < startPosition + size) { + short fieldId = payload.getShort(); + short fieldSize = payload.getShort(); + + // sanity check + if (payload.position() + fieldSize > startPosition + size) { + return null; + } + + switch (fieldId) { + case SSID_FIELD_ID: + byte[] ssid = new byte[fieldSize]; + payload.get(ssid); + result.SSID = "\"" + new String(ssid) + "\""; + break; + case NETWORK_KEY_FIELD_ID: + if (fieldSize > MAX_NETWORK_KEY_SIZE_BYTES) { + return null; + } + byte[] networkKey = new byte[fieldSize]; + payload.get(networkKey); + result.preSharedKey = "\"" + new String(networkKey) + "\""; + break; + case AUTH_TYPE_FIELD_ID: + if (fieldSize != AUTH_TYPE_EXPECTED_SIZE) { + // corrupt data + return null; + } + + short authType = payload.getShort(); + populateAllowedKeyManagement(result.allowedKeyManagement, authType); + break; + default: + // unknown / unparsed tag + payload.position(payload.position() + fieldSize); + break; + } + } + + if (result.preSharedKey != null && result.SSID != null) { + return result; + } + + return null; + } + + private static void populateAllowedKeyManagement(BitSet allowedKeyManagement, short authType) { + if (authType == AUTH_TYPE_WPA_PSK || authType == AUTH_TYPE_WPA2_PSK) { + allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); + } else if (authType == AUTH_TYPE_WPA_EAP || authType == AUTH_TYPE_WPA2_EAP) { + allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_EAP); + } else if (authType == AUTH_TYPE_OPEN) { + allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); + } + } +} diff --git a/NfcSony/src/com/android/nfc/NfceeAccessControl.java b/NfcSony/src/com/android/nfc/NfceeAccessControl.java new file mode 100644 index 0000000..5da1202 --- /dev/null +++ b/NfcSony/src/com/android/nfc/NfceeAccessControl.java @@ -0,0 +1,289 @@ +/* + * Copyright (C) 2011 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.nfc; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.HashMap; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.Signature; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Environment; +import android.util.Log; + +public class NfceeAccessControl { + static final String TAG = "NfceeAccess"; + static final boolean DBG = false; + + public static final String NFCEE_ACCESS_PATH = "/etc/nfcee_access.xml"; + + /** + * Map of signatures to valid packages names, as read from nfcee_access.xml. + * An empty list of package names indicates that any package + * with this signature is allowed. + */ + final HashMap mNfceeAccess; // contents final after onCreate() + + /** + * Map from UID to NFCEE access, used as a cache. + * Note: if a UID contains multiple packages they must all be + * signed with the same certificate so in effect UID == certificate + * used to sign the package. + */ + final HashMap mUidCache; // contents guarded by this + + final Context mContext; + final boolean mDebugPrintSignature; + + NfceeAccessControl(Context context) { + mContext = context; + mNfceeAccess = new HashMap(); + mUidCache = new HashMap(); + mDebugPrintSignature = parseNfceeAccess(); + } + + /** + * Check if the {uid, pkg} combination may use NFCEE. + * Also verify with package manager that this {uid, pkg} combination + * is valid if it is not cached. + */ + public boolean check(int uid, String pkg) { + synchronized (this) { + Boolean cached = mUidCache.get(uid); + if (cached != null) { + return cached; + } + + boolean access = false; + + // Ensure the claimed package is present in the calling UID + PackageManager pm = mContext.getPackageManager(); + String[] pkgs = pm.getPackagesForUid(uid); + for (String uidPkg : pkgs) { + if (uidPkg.equals(pkg)) { + // Ensure the package has access permissions + if (checkPackageNfceeAccess(pkg)) { + access = true; + } + break; + } + } + + mUidCache.put(uid, access); + return access; + } + } + + /** + * Check if the given ApplicationInfo may use the NFCEE. + * Assumes ApplicationInfo came from package manager, + * so no need to confirm {uid, pkg} is valid. + */ + public boolean check(ApplicationInfo info) { + synchronized (this) { + Boolean access = mUidCache.get(info.uid); + if (access == null) { + access = checkPackageNfceeAccess(info.packageName); + mUidCache.put(info.uid, access); + } + return access; + } + } + + public void invalidateCache() { + synchronized (this) { + mUidCache.clear(); + } + } + + /** + * Check with package manager if the pkg may use NFCEE. + * Does not use cache. + */ + boolean checkPackageNfceeAccess(String pkg) { + PackageManager pm = mContext.getPackageManager(); + try { + PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES); + if (info.signatures == null) { + return false; + } + + for (Signature s : info.signatures){ + if (s == null) { + continue; + } + String[] packages = mNfceeAccess.get(s); + if (packages == null) { + continue; + } + if (packages.length == 0) { + // wildcard access + if (DBG) Log.d(TAG, "Granted NFCEE access to " + pkg + " (wildcard)"); + return true; + } + for (String p : packages) { + if (pkg.equals(p)) { + // explicit package access + if (DBG) Log.d(TAG, "Granted access to " + pkg + " (explicit)"); + return true; + } + } + } + + if (mDebugPrintSignature) { + Log.w(TAG, "denied NFCEE access for " + pkg + " with signature:"); + for (Signature s : info.signatures) { + if (s != null) { + Log.w(TAG, s.toCharsString()); + } + } + } + } catch (NameNotFoundException e) { + // ignore + } + return false; + } + + /** + * Parse nfcee_access.xml, populate mNfceeAccess + * Policy is to ignore unexpected XML elements and continue processing, + * except for obvious errors within a group since they might cause + * package names to by ignored and therefore wildcard access granted + * by mistake. Those errors invalidate the entire group. + */ + boolean parseNfceeAccess() { + File file = new File(Environment.getRootDirectory(), NFCEE_ACCESS_PATH); + FileReader reader = null; + boolean debug = false; + try { + reader = new FileReader(file); + XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + XmlPullParser parser = factory.newPullParser(); + parser.setInput(reader); + + int event; + ArrayList packages = new ArrayList(); + Signature signature = null; + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + while (true) { + event = parser.next(); + String tag = parser.getName(); + if (event == XmlPullParser.START_TAG && "signer".equals(tag)) { + signature = null; + packages.clear(); + for (int i = 0; i < parser.getAttributeCount(); i++) { + if ("android:signature".equals(parser.getAttributeName(i))) { + signature = new Signature(parser.getAttributeValue(i)); + break; + } + } + if (signature == null) { + Log.w(TAG, "signer tag is missing android:signature attribute, igorning"); + continue; + } + if (mNfceeAccess.containsKey(signature)) { + Log.w(TAG, "duplicate signature, ignoring"); + signature = null; + continue; + } + } else if (event == XmlPullParser.END_TAG && "signer".equals(tag)) { + if (signature == null) { + Log.w(TAG, "mis-matched signer tag"); + continue; + } + mNfceeAccess.put(signature, packages.toArray(new String[0])); + packages.clear(); + } else if (event == XmlPullParser.START_TAG && "package".equals(tag)) { + if (signature == null) { + Log.w(TAG, "ignoring unnested packge tag"); + continue; + } + String name = null; + for (int i = 0; i < parser.getAttributeCount(); i++) { + if ("android:name".equals(parser.getAttributeName(i))) { + name = parser.getAttributeValue(i); + break; + } + } + if (name == null) { + Log.w(TAG, "package missing android:name, ignoring signer group"); + signature = null; // invalidate signer + continue; + } + // check for duplicate package names + if (packages.contains(name)) { + Log.w(TAG, "duplicate package name in signer group, ignoring"); + continue; + } + packages.add(name); + } else if (event == XmlPullParser.START_TAG && "debug".equals(tag)) { + debug = true; + } else if (event == XmlPullParser.END_DOCUMENT) { + break; + } + } + } catch (XmlPullParserException e) { + Log.w(TAG, "failed to load NFCEE access list", e); + mNfceeAccess.clear(); // invalidate entire access list + } catch (FileNotFoundException e) { + Log.w(TAG, "could not find " + NFCEE_ACCESS_PATH + ", no NFCEE access allowed"); + } catch (IOException e) { + Log.e(TAG, "Failed to load NFCEE access list", e); + mNfceeAccess.clear(); // invalidate entire access list + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e2) { } + } + } + Log.i(TAG, "read " + mNfceeAccess.size() + " signature(s) for NFCEE access"); + return debug; + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("mNfceeAccess="); + for (Signature s : mNfceeAccess.keySet()) { + pw.printf("\t%s [", s.toCharsString()); + String[] ps = mNfceeAccess.get(s); + for (String p : ps) { + pw.printf("%s, ", p); + } + pw.println("]"); + } + synchronized (this) { + pw.println("mNfceeUidCache="); + for (Integer uid : mUidCache.keySet()) { + Boolean b = mUidCache.get(uid); + pw.printf("\t%d %s\n", uid, b); + } + } + } +} diff --git a/NfcSony/src/com/android/nfc/P2pEventManager.java b/NfcSony/src/com/android/nfc/P2pEventManager.java new file mode 100644 index 0000000..1f84947 --- /dev/null +++ b/NfcSony/src/com/android/nfc/P2pEventManager.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2011 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.nfc; + +import com.android.nfc.beam.SendUi; + +import android.app.NotificationManager; +import android.content.Context; +import android.content.res.Configuration; +import android.os.Vibrator; + +/** + * Manages vibration, sound and animation for P2P events. + */ +public class P2pEventManager implements P2pEventListener, SendUi.Callback { + static final String TAG = "NfcP2pEventManager"; + static final boolean DBG = true; + + static final long[] VIBRATION_PATTERN = {0, 100, 10000}; + + final Context mContext; + final NfcService mNfcService; + final P2pEventListener.Callback mCallback; + final Vibrator mVibrator; + final NotificationManager mNotificationManager; + final SendUi mSendUi; + + // only used on UI thread + boolean mSending; + boolean mNdefSent; + boolean mNdefReceived; + boolean mInDebounce; + + public P2pEventManager(Context context, P2pEventListener.Callback callback) { + mNfcService = NfcService.getInstance(); + mContext = context; + mCallback = callback; + mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + mNotificationManager = (NotificationManager) mContext.getSystemService( + Context.NOTIFICATION_SERVICE); + + mSending = false; + final int uiModeType = mContext.getResources().getConfiguration().uiMode + & Configuration.UI_MODE_TYPE_MASK; + if (uiModeType == Configuration.UI_MODE_TYPE_APPLIANCE) { + // "Appliances" don't intrinsically have a way of confirming this, so we + // don't use the UI and just autoconfirm where necessary. + // Don't instantiate SendUi or else we'll use memory and never reclaim it. + mSendUi = null; + } else { + mSendUi = new SendUi(context, this); + } + } + + @Override + public void onP2pInRange() { + mNdefSent = false; + mNdefReceived = false; + mInDebounce = false; + + if (mSendUi != null) { + mSendUi.takeScreenshot(); + } + } + + @Override + public void onP2pNfcTapRequested() { + mNfcService.playSound(NfcService.SOUND_START); + mNdefSent = false; + mNdefReceived = false; + mInDebounce = false; + + mVibrator.vibrate(VIBRATION_PATTERN, -1); + if (mSendUi != null) { + mSendUi.takeScreenshot(); + mSendUi.showPreSend(true); + } + } + + @Override + public void onP2pTimeoutWaitingForLink() { + if (mSendUi != null) { + mSendUi.finish(SendUi.FINISH_SCALE_UP); + } + } + + @Override + public void onP2pSendConfirmationRequested() { + mNfcService.playSound(NfcService.SOUND_START); + mVibrator.vibrate(VIBRATION_PATTERN, -1); + if (mSendUi != null) { + mSendUi.showPreSend(false); + } else { + mCallback.onP2pSendConfirmed(); + } + } + + @Override + public void onP2pSendComplete() { + mNfcService.playSound(NfcService.SOUND_END); + mVibrator.vibrate(VIBRATION_PATTERN, -1); + if (mSendUi != null) { + mSendUi.finish(SendUi.FINISH_SEND_SUCCESS); + } + mSending = false; + mNdefSent = true; + } + + @Override + public void onP2pHandoverNotSupported() { + mNfcService.playSound(NfcService.SOUND_ERROR); + mVibrator.vibrate(VIBRATION_PATTERN, -1); + mSendUi.finishAndToast(SendUi.FINISH_SCALE_UP, + mContext.getString(R.string.beam_handover_not_supported)); + mSending = false; + mNdefSent = false; + } + + @Override + public void onP2pHandoverBusy() { + mNfcService.playSound(NfcService.SOUND_ERROR); + mVibrator.vibrate(VIBRATION_PATTERN, -1); + mSendUi.finishAndToast(SendUi.FINISH_SCALE_UP, mContext.getString(R.string.beam_busy)); + mSending = false; + mNdefSent = false; + } + + @Override + public void onP2pReceiveComplete(boolean playSound) { + mVibrator.vibrate(VIBRATION_PATTERN, -1); + if (playSound) mNfcService.playSound(NfcService.SOUND_END); + if (mSendUi != null) { + // TODO we still don't have a nice receive solution + // The sanest solution right now is just to scale back up what we had + // and start the new activity. It is not perfect, but at least it is + // consistent behavior. All other variants involve making the old + // activity screenshot disappear, and then removing the animation + // window hoping the new activity has started by then. This just goes + // wrong too often and can look weird. + mSendUi.finish(SendUi.FINISH_SCALE_UP); + } + mNdefReceived = true; + } + + @Override + public void onP2pOutOfRange() { + if (mSending) { + mNfcService.playSound(NfcService.SOUND_ERROR); + mSending = false; + } + if (!mNdefSent && !mNdefReceived && mSendUi != null) { + mSendUi.finish(SendUi.FINISH_SCALE_UP); + } + mInDebounce = false; + } + + @Override + public void onSendConfirmed() { + if (!mSending) { + if (mSendUi != null) { + mSendUi.showStartSend(); + } + mCallback.onP2pSendConfirmed(); + } + mSending = true; + + } + + @Override + public void onCanceled() { + mSendUi.finish(SendUi.FINISH_SCALE_UP); + mCallback.onP2pCanceled(); + } + + @Override + public void onP2pSendDebounce() { + mInDebounce = true; + mNfcService.playSound(NfcService.SOUND_ERROR); + if (mSendUi != null) { + mSendUi.showSendHint(); + } + } + + @Override + public void onP2pResumeSend() { + mVibrator.vibrate(VIBRATION_PATTERN, -1); + mNfcService.playSound(NfcService.SOUND_START); + if (mInDebounce) { + if (mSendUi != null) { + mSendUi.showStartSend(); + } + } + mInDebounce = false; + } + +} diff --git a/NfcSony/src/com/android/nfc/P2pLinkManager.java b/NfcSony/src/com/android/nfc/P2pLinkManager.java new file mode 100755 index 0000000..b083549 --- /dev/null +++ b/NfcSony/src/com/android/nfc/P2pLinkManager.java @@ -0,0 +1,1124 @@ +/* + * Copyright (C) 2011 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.nfc; + +import android.content.Intent; +import android.content.pm.UserInfo; + +import com.android.nfc.beam.BeamManager; +import com.android.nfc.beam.BeamSendService; +import com.android.nfc.beam.BeamTransferRecord; + +import android.os.UserManager; +import com.android.nfc.echoserver.EchoServer; +import com.android.nfc.handover.HandoverClient; +import com.android.nfc.handover.HandoverDataParser; +import com.android.nfc.handover.HandoverServer; +import com.android.nfc.ndefpush.NdefPushClient; +import com.android.nfc.ndefpush.NdefPushServer; +import com.android.nfc.snep.SnepClient; +import com.android.nfc.snep.SnepMessage; +import com.android.nfc.snep.SnepServer; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.net.Uri; +import android.nfc.BeamShareData; +import android.nfc.IAppCallback; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.nfc.NfcAdapter; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.Log; +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; + +/** + * Interface to listen for P2P events. + * All callbacks are made from the UI thread. + */ +interface P2pEventListener { + /** + * Indicates the user has expressed an intent to share + * over NFC, but a remote device has not come into range + * yet. Prompt the user to NFC tap. + */ + public void onP2pNfcTapRequested(); + + /** + * Indicates the user has expressed an intent to share over + * NFC, but the link hasn't come up yet and we no longer + * want to wait for it + */ + public void onP2pTimeoutWaitingForLink(); + + /** + * Indicates a P2P device is in range. + *

onP2pInRange() and onP2pOutOfRange() will always be called + * alternately. + */ + public void onP2pInRange(); + + /** + * Called when a NDEF payload is prepared to send, and confirmation is + * required. Call Callback.onP2pSendConfirmed() to make the confirmation. + */ + public void onP2pSendConfirmationRequested(); + + /** + * Called to indicate a send was successful. + */ + public void onP2pSendComplete(); + + /** + * + * Called to indicate the link has broken while we were trying to send + * a message. We'll start a debounce timer for the user to get the devices + * back together. UI may show a hint to achieve that + */ + public void onP2pSendDebounce(); + + /** + * Called to indicate a link has come back up after being temporarily + * broken, and sending is resuming + */ + public void onP2pResumeSend(); + + /** + * Called to indicate the remote device does not support connection handover + */ + public void onP2pHandoverNotSupported(); + + /** + * Called to indicate the device is busy with another handover transfer + */ + public void onP2pHandoverBusy(); + + /** + * Called to indicate a receive was successful. + */ + public void onP2pReceiveComplete(boolean playSound); + + /** + * Indicates the P2P device went out of range. + */ + public void onP2pOutOfRange(); + + public interface Callback { + public void onP2pSendConfirmed(); + public void onP2pCanceled(); + } +} + +/** + * Manages sending and receiving NDEF message over LLCP link. + * Does simple debouncing of the LLCP link - so that even if the link + * drops and returns the user does not know. + */ +class P2pLinkManager implements Handler.Callback, P2pEventListener.Callback { + static final String TAG = "NfcP2pLinkManager"; + static final boolean DBG = true; + + /** Include this constant as a meta-data entry in the manifest + * of an application to disable beaming the market/AAR link, like this: + *

{@code
+     *  
+     *      
+     *  
+     *  }
+ */ + static final String DISABLE_BEAM_DEFAULT = "android.nfc.disable_beam_default"; + + /** Enables the LLCP EchoServer, which can be used to test the android + * LLCP stack against nfcpy. + */ + static final boolean ECHOSERVER_ENABLED = false; + + // TODO dynamically assign SAP values + static final int NDEFPUSH_SAP = 0x10; + static final int HANDOVER_SAP = 0x14; + + static final int LINK_SEND_PENDING_DEBOUNCE_MS = 3000; + static final int LINK_SEND_CONFIRMED_DEBOUNCE_MS = 5000; + static final int LINK_SEND_COMPLETE_DEBOUNCE_MS = 500; + static final int LINK_SEND_CANCELED_DEBOUNCE_MS = 250; + + // The amount of time we wait for the link to come up + // after a user has manually invoked Beam. + static final int WAIT_FOR_LINK_TIMEOUT_MS = 10000; + + static final int MSG_DEBOUNCE_TIMEOUT = 1; + static final int MSG_RECEIVE_COMPLETE = 2; + static final int MSG_RECEIVE_HANDOVER = 3; + static final int MSG_SEND_COMPLETE = 4; + static final int MSG_START_ECHOSERVER = 5; + static final int MSG_STOP_ECHOSERVER = 6; + static final int MSG_HANDOVER_NOT_SUPPORTED = 7; + static final int MSG_SHOW_CONFIRMATION_UI = 8; + static final int MSG_WAIT_FOR_LINK_TIMEOUT = 9; + static final int MSG_HANDOVER_BUSY = 10; + + // values for mLinkState + static final int LINK_STATE_DOWN = 1; + static final int LINK_STATE_UP = 2; + static final int LINK_STATE_DEBOUNCE = 3; + + // values for mSendState + static final int SEND_STATE_NOTHING_TO_SEND = 1; + static final int SEND_STATE_NEED_CONFIRMATION = 2; + static final int SEND_STATE_PENDING = 3; + static final int SEND_STATE_SENDING = 4; + static final int SEND_STATE_COMPLETE = 5; + static final int SEND_STATE_CANCELED = 6; + + // return values for doSnepProtocol + static final int SNEP_SUCCESS = 0; + static final int SNEP_FAILURE = 1; + + // return values for doHandover + static final int HANDOVER_SUCCESS = 0; + static final int HANDOVER_FAILURE = 1; + static final int HANDOVER_UNSUPPORTED = 2; + static final int HANDOVER_BUSY = 3; + + final NdefPushServer mNdefPushServer; + final SnepServer mDefaultSnepServer; + final HandoverServer mHandoverServer; + final EchoServer mEchoServer; + final Context mContext; + final P2pEventListener mEventListener; + final Handler mHandler; + final HandoverDataParser mHandoverDataParser; + final ForegroundUtils mForegroundUtils; + + final int mDefaultMiu; + final int mDefaultRwSize; + + // Locked on NdefP2pManager.this + PackageManager mPackageManager; + int mLinkState; + int mSendState; // valid during LINK_STATE_UP or LINK_STATE_DEBOUNCE + boolean mIsSendEnabled; + boolean mIsReceiveEnabled; + NdefMessage mMessageToSend; // not valid in SEND_STATE_NOTHING_TO_SEND + Uri[] mUrisToSend; // not valid in SEND_STATE_NOTHING_TO_SEND + UserHandle mUserHandle; // not valid in SEND_STATE_NOTHING_TO_SEND + int mSendFlags; // not valid in SEND_STATE_NOTHING_TO_SEND + IAppCallback mCallbackNdef; + int mNdefCallbackUid; + SendTask mSendTask; + SharedPreferences mPrefs; + SnepClient mSnepClient; + HandoverClient mHandoverClient; + NdefPushClient mNdefPushClient; + ConnectTask mConnectTask; + boolean mLlcpServicesConnected; + long mLastLlcpActivationTime; + byte mPeerLlcpVersion; + + public P2pLinkManager(Context context, HandoverDataParser handoverDataParser, int defaultMiu, + int defaultRwSize) { + mNdefPushServer = new NdefPushServer(NDEFPUSH_SAP, mNppCallback); + mDefaultSnepServer = new SnepServer(mDefaultSnepCallback, defaultMiu, defaultRwSize); + mHandoverServer = new HandoverServer(context, HANDOVER_SAP, handoverDataParser, mHandoverCallback); + + if (ECHOSERVER_ENABLED) { + mEchoServer = new EchoServer(); + } else { + mEchoServer = null; + } + mPackageManager = context.getPackageManager(); + mContext = context; + mEventListener = new P2pEventManager(context, this); + mHandler = new Handler(this); + mLinkState = LINK_STATE_DOWN; + mSendState = SEND_STATE_NOTHING_TO_SEND; + mIsSendEnabled = false; + mIsReceiveEnabled = false; + mPrefs = context.getSharedPreferences(NfcService.PREF, Context.MODE_PRIVATE); + mHandoverDataParser = handoverDataParser; + mDefaultMiu = defaultMiu; + mDefaultRwSize = defaultRwSize; + mLlcpServicesConnected = false; + mNdefCallbackUid = -1; + mForegroundUtils = ForegroundUtils.getInstance(); + } + + /** + * May be called from any thread. + * Assumes that NFC is already on if any parameter is true. + */ + public void enableDisable(boolean sendEnable, boolean receiveEnable) { + synchronized (this) { + if (!mIsReceiveEnabled && receiveEnable) { + mDefaultSnepServer.start(); + mNdefPushServer.start(); + mHandoverServer.start(); + if (mEchoServer != null) { + mHandler.sendEmptyMessage(MSG_START_ECHOSERVER); + } + } else if (mIsReceiveEnabled && !receiveEnable) { + if (DBG) Log.d(TAG, "enableDisable: llcp deactivate"); + onLlcpDeactivated (); + mDefaultSnepServer.stop(); + mNdefPushServer.stop(); + mHandoverServer.stop(); + if (mEchoServer != null) { + mHandler.sendEmptyMessage(MSG_STOP_ECHOSERVER); + } + } + mIsSendEnabled = sendEnable; + mIsReceiveEnabled = receiveEnable; + } + } + + /** + * May be called from any thread. + * @return whether the LLCP link is in an active or debounce state + */ + public boolean isLlcpActive() { + synchronized (this) { + return mLinkState != LINK_STATE_DOWN; + } + } + + /** + * Set NDEF callback for sending. + * May be called from any thread. + * NDEF callbacks may be set at any time (even if NFC is + * currently off or P2P send is currently off). They will become + * active as soon as P2P send is enabled. + */ + public void setNdefCallback(IAppCallback callbackNdef, int callingUid) { + synchronized (this) { + mCallbackNdef = callbackNdef; + mNdefCallbackUid = callingUid; + } + } + + + public void onManualBeamInvoke(BeamShareData shareData) { + synchronized (P2pLinkManager.this) { + if (mLinkState != LINK_STATE_DOWN) { + return; + } + if (mForegroundUtils.getForegroundUids().contains(mNdefCallbackUid)) { + // Try to get data from the registered NDEF callback + prepareMessageToSend(false); + } else { + mMessageToSend = null; + mUrisToSend = null; + } + if (mMessageToSend == null && mUrisToSend == null && shareData != null) { + // No data from the NDEF callback, get data from ShareData + if (shareData.uris != null) { + mUrisToSend = shareData.uris; + } else if (shareData.ndefMessage != null) { + mMessageToSend = shareData.ndefMessage; + } + + mUserHandle = shareData.userHandle; + } + if (mMessageToSend != null || + (mUrisToSend != null && mHandoverDataParser.isHandoverSupported())) { + mSendState = SEND_STATE_PENDING; + mEventListener.onP2pNfcTapRequested(); + scheduleTimeoutLocked(MSG_WAIT_FOR_LINK_TIMEOUT, WAIT_FOR_LINK_TIMEOUT_MS); + } + } + } + + /** + * Must be called on UI Thread. + */ + public void onLlcpActivated(byte peerLlcpVersion) { + Log.i(TAG, "LLCP activated"); + synchronized (P2pLinkManager.this) { + if (mEchoServer != null) { + mEchoServer.onLlcpActivated(); + } + mLastLlcpActivationTime = SystemClock.elapsedRealtime(); + mPeerLlcpVersion = peerLlcpVersion; + switch (mLinkState) { + case LINK_STATE_DOWN: + if (DBG) Log.d(TAG, "onP2pInRange()"); + // Start taking a screenshot + mEventListener.onP2pInRange(); + mLinkState = LINK_STATE_UP; + // If we had a pending send (manual Beam invoke), + // mark it as sending + if (mSendState == SEND_STATE_PENDING) { + mSendState = SEND_STATE_SENDING; + mHandler.removeMessages(MSG_WAIT_FOR_LINK_TIMEOUT); + // Immediately try to connect LLCP services + connectLlcpServices(); + } else { + mSendState = SEND_STATE_NOTHING_TO_SEND; + prepareMessageToSend(true); + if (mMessageToSend != null || + (mUrisToSend != null && mHandoverDataParser.isHandoverSupported())) { + // We have data to send, connect LLCP services + connectLlcpServices(); + if ((mSendFlags & NfcAdapter.FLAG_NDEF_PUSH_NO_CONFIRM) != 0) { + mSendState = SEND_STATE_SENDING; + } else { + mSendState = SEND_STATE_NEED_CONFIRMATION; + } + } + } + break; + case LINK_STATE_UP: + if (DBG) Log.d(TAG, "Duplicate onLlcpActivated()"); + return; + case LINK_STATE_DEBOUNCE: + // Immediately connect and try to send again + mLinkState = LINK_STATE_UP; + if (mSendState == SEND_STATE_SENDING || + mSendState == SEND_STATE_NEED_CONFIRMATION) { + // If we have something to send still, connect LLCP + connectLlcpServices(); + } + mHandler.removeMessages(MSG_DEBOUNCE_TIMEOUT); + break; + } + } + } + + /** + * Must be called on UI Thread. + */ + public void onLlcpFirstPacketReceived() { + synchronized (P2pLinkManager.this) { + long totalTime = SystemClock.elapsedRealtime() - mLastLlcpActivationTime; + if (DBG) Log.d(TAG, "Took " + Long.toString(totalTime) + " to get first LLCP PDU"); + } + } + + public void onUserSwitched(int userId) { + // Update the cached package manager in case of user switch + synchronized (P2pLinkManager.this) { + try { + mPackageManager = mContext.createPackageContextAsUser("android", 0, + new UserHandle(userId)).getPackageManager(); + } catch (NameNotFoundException e) { + Log.e(TAG, "Failed to retrieve PackageManager for user"); + } + } + } + + void prepareMessageToSend(boolean generatePlayLink) { + synchronized (P2pLinkManager.this) { + mMessageToSend = null; + mUrisToSend = null; + if (!mIsSendEnabled) { + return; + } + + List foregroundUids = mForegroundUtils.getForegroundUids(); + if (foregroundUids.isEmpty()) { + Log.e(TAG, "Could not determine foreground UID."); + return; + } + + if (isBeamDisabled(foregroundUids.get(0))) { + if (DBG) Log.d(TAG, "Beam is disabled by policy."); + return; + } + + if (mCallbackNdef != null) { + if (foregroundUids.contains(mNdefCallbackUid)) { + try { + BeamShareData shareData = mCallbackNdef.createBeamShareData(mPeerLlcpVersion); + mMessageToSend = shareData.ndefMessage; + mUrisToSend = shareData.uris; + mUserHandle = shareData.userHandle; + mSendFlags = shareData.flags; + return; + } catch (Exception e) { + Log.e(TAG, "Failed NDEF callback: ", e); + } + } else { + // This is not necessarily an error - we no longer unset callbacks from + // the app process itself (to prevent IPC calls on every pause). + // Hence it may simply be a stale callback. + if (DBG) Log.d(TAG, "Last registered callback is not running in the foreground."); + } + } + + // fall back to default NDEF for the foreground activity, unless the + // application disabled this explicitly in their manifest. + String[] pkgs = mPackageManager.getPackagesForUid(foregroundUids.get(0)); + if (pkgs != null && pkgs.length >= 1) { + if (!generatePlayLink || beamDefaultDisabled(pkgs[0])) { + if (DBG) Log.d(TAG, "Disabling default Beam behavior"); + mMessageToSend = null; + mUrisToSend = null; + } else { + mMessageToSend = createDefaultNdef(pkgs[0]); + mUrisToSend = null; + } + } + + if (DBG) Log.d(TAG, "mMessageToSend = " + mMessageToSend); + if (DBG) Log.d(TAG, "mUrisToSend = " + mUrisToSend); + } + } + + private boolean isBeamDisabled(int uid) { + UserManager userManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + UserInfo userInfo = userManager.getUserInfo(UserHandle.getUserId(uid)); + return userManager.hasUserRestriction( + UserManager.DISALLOW_OUTGOING_BEAM, userInfo.getUserHandle()); + + } + + boolean beamDefaultDisabled(String pkgName) { + try { + ApplicationInfo ai = mPackageManager.getApplicationInfo(pkgName, + PackageManager.GET_META_DATA); + if (ai == null || ai.metaData == null) { + return false; + } + return ai.metaData.getBoolean(DISABLE_BEAM_DEFAULT); + } catch (NameNotFoundException e) { + return false; + } + } + + NdefMessage createDefaultNdef(String pkgName) { + NdefRecord appUri = NdefRecord.createUri(Uri.parse( + "http://play.google.com/store/apps/details?id=" + pkgName + "&feature=beam")); + NdefRecord appRecord = NdefRecord.createApplicationRecord(pkgName); + return new NdefMessage(new NdefRecord[] { appUri, appRecord }); + } + + void disconnectLlcpServices() { + synchronized (this) { + if (mConnectTask != null) { + mConnectTask.cancel(true); + mConnectTask = null; + } + // Close any already connected LLCP clients + if (mNdefPushClient != null) { + mNdefPushClient.close(); + mNdefPushClient = null; + } + if (mSnepClient != null) { + mSnepClient.close(); + mSnepClient = null; + } + if (mHandoverClient != null) { + mHandoverClient.close(); + mHandoverClient = null; + } + mLlcpServicesConnected = false; + } + } + + /** + * Must be called on UI Thread. + */ + public void onLlcpDeactivated() { + Log.i(TAG, "LLCP deactivated."); + synchronized (this) { + if (mEchoServer != null) { + mEchoServer.onLlcpDeactivated(); + } + + switch (mLinkState) { + case LINK_STATE_DOWN: + case LINK_STATE_DEBOUNCE: + Log.i(TAG, "Duplicate onLlcpDectivated()"); + break; + case LINK_STATE_UP: + // Debounce + mLinkState = LINK_STATE_DEBOUNCE; + int debounceTimeout = 0; + switch (mSendState) { + case SEND_STATE_NOTHING_TO_SEND: + debounceTimeout = 0; + break; + case SEND_STATE_NEED_CONFIRMATION: + debounceTimeout = LINK_SEND_PENDING_DEBOUNCE_MS; + break; + case SEND_STATE_SENDING: + debounceTimeout = LINK_SEND_CONFIRMED_DEBOUNCE_MS; + break; + case SEND_STATE_COMPLETE: + debounceTimeout = LINK_SEND_COMPLETE_DEBOUNCE_MS; + break; + case SEND_STATE_CANCELED: + debounceTimeout = LINK_SEND_CANCELED_DEBOUNCE_MS; + } + scheduleTimeoutLocked(MSG_DEBOUNCE_TIMEOUT, debounceTimeout); + if (mSendState == SEND_STATE_SENDING) { + Log.e(TAG, "onP2pSendDebounce()"); + mEventListener.onP2pSendDebounce(); + } + cancelSendNdefMessage(); + disconnectLlcpServices(); + break; + } + } + } + + void onHandoverUnsupported() { + mHandler.sendEmptyMessage(MSG_HANDOVER_NOT_SUPPORTED); + } + + void onHandoverBusy() { + mHandler.sendEmptyMessage(MSG_HANDOVER_BUSY); + } + + void onSendComplete(NdefMessage msg, long elapsedRealtime) { + // Make callbacks on UI thread + mHandler.sendEmptyMessage(MSG_SEND_COMPLETE); + } + + void sendNdefMessage() { + synchronized (this) { + cancelSendNdefMessage(); + mSendTask = new SendTask(); + mSendTask.execute(); + } + } + + void cancelSendNdefMessage() { + synchronized (P2pLinkManager.this) { + if (mSendTask != null) { + mSendTask.cancel(true); + } + } + } + + void connectLlcpServices() { + synchronized (P2pLinkManager.this) { + if (mConnectTask != null) { + Log.e(TAG, "Still had a reference to mConnectTask!"); + } + mConnectTask = new ConnectTask(); + mConnectTask.execute(); + } + } + + // Must be called on UI-thread + void onLlcpServicesConnected() { + if (DBG) Log.d(TAG, "onLlcpServicesConnected"); + synchronized (P2pLinkManager.this) { + if (mLinkState != LINK_STATE_UP) { + return; + } + mLlcpServicesConnected = true; + if (mSendState == SEND_STATE_NEED_CONFIRMATION) { + if (DBG) Log.d(TAG, "onP2pSendConfirmationRequested()"); + mEventListener.onP2pSendConfirmationRequested(); + } else if (mSendState == SEND_STATE_SENDING) { + mEventListener.onP2pResumeSend(); + sendNdefMessage(); + } else { + // Either nothing to send or canceled/complete, ignore + } + } + } + + final class ConnectTask extends AsyncTask { + @Override + protected void onPostExecute(Boolean result) { + if (isCancelled()) { + if (DBG) Log.d(TAG, "ConnectTask was cancelled"); + return; + } + if (result) { + onLlcpServicesConnected(); + } else { + Log.e(TAG, "Could not connect required NFC transports"); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + boolean needsHandover = false; + boolean needsNdef = false; + boolean success = false; + HandoverClient handoverClient = null; + SnepClient snepClient = null; + NdefPushClient nppClient = null; + + synchronized(P2pLinkManager.this) { + if (mUrisToSend != null) { + needsHandover = true; + } + + if (mMessageToSend != null) { + needsNdef = true; + } + } + // We know either is requested - otherwise this task + // wouldn't have been started. + if (needsHandover) { + handoverClient = new HandoverClient(); + try { + handoverClient.connect(); + success = true; // Regardless of NDEF result + } catch (IOException e) { + handoverClient = null; + } + } + + if (needsNdef || (needsHandover && handoverClient == null)) { + snepClient = new SnepClient(); + try { + snepClient.connect(); + success = true; + } catch (IOException e) { + snepClient = null; + } + + if (!success) { + nppClient = new NdefPushClient(); + try { + nppClient.connect(); + success = true; + } catch (IOException e) { + nppClient = null; + } + } + } + + synchronized (P2pLinkManager.this) { + if (isCancelled()) { + // Cancelled by onLlcpDeactivated on UI thread + if (handoverClient != null) { + handoverClient.close(); + } + if (snepClient != null) { + snepClient.close(); + } + if (nppClient != null) { + nppClient.close(); + } + return false; + } else { + // Once assigned, these are the responsibility of + // the code on the UI thread to release - typically + // through onLlcpDeactivated(). + mHandoverClient = handoverClient; + mSnepClient = snepClient; + mNdefPushClient = nppClient; + return success; + } + } + } + }; + + final class SendTask extends AsyncTask { + NdefPushClient nppClient; + SnepClient snepClient; + HandoverClient handoverClient; + + int doHandover(Uri[] uris, UserHandle userHandle) throws IOException { + NdefMessage response = null; + BeamManager beamManager = BeamManager.getInstance(); + + if (beamManager.isBeamInProgress()) { + return HANDOVER_BUSY; + } + + NdefMessage request = mHandoverDataParser.createHandoverRequestMessage(); + if (request != null) { + if (handoverClient != null) { + response = handoverClient.sendHandoverRequest(request); + } + if (response == null && snepClient != null) { + // Remote device may not support handover service, + // try the (deprecated) SNEP GET implementation + // for devices running Android 4.1 + SnepMessage snepResponse = snepClient.get(request); + response = snepResponse.getNdefMessage(); + } + if (response == null) { + return HANDOVER_UNSUPPORTED; + } + } else { + return HANDOVER_UNSUPPORTED; + } + + if (!beamManager.startBeamSend(mContext, + mHandoverDataParser.getOutgoingHandoverData(response), uris, userHandle)) { + return HANDOVER_BUSY; + } + + return HANDOVER_SUCCESS; + } + + int doSnepProtocol(NdefMessage msg) throws IOException { + if (msg != null) { + snepClient.put(msg); + return SNEP_SUCCESS; + } else { + return SNEP_FAILURE; + } + } + + @Override + public Void doInBackground(Void... args) { + NdefMessage m; + Uri[] uris; + UserHandle userHandle; + boolean result = false; + + synchronized (P2pLinkManager.this) { + if (mLinkState != LINK_STATE_UP || mSendState != SEND_STATE_SENDING) { + return null; + } + m = mMessageToSend; + uris = mUrisToSend; + userHandle = mUserHandle; + snepClient = mSnepClient; + handoverClient = mHandoverClient; + nppClient = mNdefPushClient; + } + + long time = SystemClock.elapsedRealtime(); + + if (uris != null) { + if (DBG) Log.d(TAG, "Trying handover request"); + try { + int handoverResult = doHandover(uris, userHandle); + switch (handoverResult) { + case HANDOVER_SUCCESS: + result = true; + break; + case HANDOVER_FAILURE: + result = false; + break; + case HANDOVER_UNSUPPORTED: + result = false; + onHandoverUnsupported(); + break; + case HANDOVER_BUSY: + result = false; + onHandoverBusy(); + break; + } + } catch (IOException e) { + result = false; + } + } + + if (!result && m != null && snepClient != null) { + if (DBG) Log.d(TAG, "Sending ndef via SNEP"); + try { + int snepResult = doSnepProtocol(m); + switch (snepResult) { + case SNEP_SUCCESS: + result = true; + break; + case SNEP_FAILURE: + result = false; + break; + default: + result = false; + } + } catch (IOException e) { + result = false; + } + } + + if (!result && m != null && nppClient != null) { + result = nppClient.push(m); + } + + time = SystemClock.elapsedRealtime() - time; + if (DBG) Log.d(TAG, "SendTask result=" + result + ", time ms=" + time); + if (result) { + onSendComplete(m, time); + } + + return null; + } + }; + + + final HandoverServer.Callback mHandoverCallback = new HandoverServer.Callback() { + @Override + public void onHandoverRequestReceived() { + onReceiveHandover(); + } + + @Override + public void onHandoverBusy() { + P2pLinkManager.this.onHandoverBusy(); + } + }; + + final NdefPushServer.Callback mNppCallback = new NdefPushServer.Callback() { + @Override + public void onMessageReceived(NdefMessage msg) { + onReceiveComplete(msg); + } + }; + + final SnepServer.Callback mDefaultSnepCallback = new SnepServer.Callback() { + @Override + public SnepMessage doPut(NdefMessage msg) { + onReceiveComplete(msg); + return SnepMessage.getMessage(SnepMessage.RESPONSE_SUCCESS); + } + + @Override + public SnepMessage doGet(int acceptableLength, NdefMessage msg) { + // The NFC Forum Default SNEP server is not allowed to respond to + // SNEP GET requests - see SNEP 1.0 TS section 6.1. However, + // since Android 4.1 used the NFC Forum default server to + // implement connection handover, we will support this + // until we can deprecate it. + NdefMessage response = mHandoverDataParser.getIncomingHandoverData(msg).handoverSelect; + if (response != null) { + onReceiveHandover(); + return SnepMessage.getSuccessResponse(response); + } else { + return SnepMessage.getMessage(SnepMessage.RESPONSE_NOT_IMPLEMENTED); + } + } + }; + + void onReceiveHandover() { + mHandler.obtainMessage(MSG_RECEIVE_HANDOVER).sendToTarget(); + } + + void onReceiveComplete(NdefMessage msg) { + // Make callbacks on UI thread + mHandler.obtainMessage(MSG_RECEIVE_COMPLETE, msg).sendToTarget(); + } + + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_START_ECHOSERVER: + synchronized (this) { + mEchoServer.start(); + break; + } + case MSG_STOP_ECHOSERVER: + synchronized (this) { + mEchoServer.stop(); + break; + } + case MSG_WAIT_FOR_LINK_TIMEOUT: + synchronized (this) { + // User wanted to send something but no link + // came up. Just cancel the send + mSendState = SEND_STATE_NOTHING_TO_SEND; + mEventListener.onP2pTimeoutWaitingForLink(); + } + break; + case MSG_DEBOUNCE_TIMEOUT: + synchronized (this) { + if (mLinkState != LINK_STATE_DEBOUNCE) { + break; + } + if (DBG) Log.d(TAG, "Debounce timeout"); + mLinkState = LINK_STATE_DOWN; + mSendState = SEND_STATE_NOTHING_TO_SEND; + mMessageToSend = null; + mUrisToSend = null; + if (DBG) Log.d(TAG, "onP2pOutOfRange()"); + mEventListener.onP2pOutOfRange(); + } + break; + case MSG_RECEIVE_HANDOVER: + // We're going to do a handover request + synchronized (this) { + if (mLinkState == LINK_STATE_DOWN) { + break; + } + if (mSendState == SEND_STATE_SENDING) { + cancelSendNdefMessage(); + } + mSendState = SEND_STATE_NOTHING_TO_SEND; + if (DBG) Log.d(TAG, "onP2pReceiveComplete()"); + mEventListener.onP2pReceiveComplete(false); + } + break; + case MSG_RECEIVE_COMPLETE: + NdefMessage m = (NdefMessage) msg.obj; + synchronized (this) { + if (mLinkState == LINK_STATE_DOWN) { + break; + } + if (mSendState == SEND_STATE_SENDING) { + cancelSendNdefMessage(); + } + mSendState = SEND_STATE_NOTHING_TO_SEND; + if (DBG) Log.d(TAG, "onP2pReceiveComplete()"); + mEventListener.onP2pReceiveComplete(true); + NfcService.getInstance().sendMockNdefTag(m); + } + break; + case MSG_HANDOVER_NOT_SUPPORTED: + synchronized (P2pLinkManager.this) { + mSendTask = null; + + if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_SENDING) { + break; + } + mSendState = SEND_STATE_NOTHING_TO_SEND; + if (DBG) Log.d(TAG, "onP2pHandoverNotSupported()"); + mEventListener.onP2pHandoverNotSupported(); + } + break; + case MSG_SEND_COMPLETE: + synchronized (P2pLinkManager.this) { + mSendTask = null; + + if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_SENDING) { + break; + } + mSendState = SEND_STATE_COMPLETE; + mHandler.removeMessages(MSG_DEBOUNCE_TIMEOUT); + if (DBG) Log.d(TAG, "onP2pSendComplete()"); + mEventListener.onP2pSendComplete(); + if (mCallbackNdef != null) { + try { + mCallbackNdef.onNdefPushComplete(mPeerLlcpVersion); + } catch (Exception e) { + Log.e(TAG, "Failed NDEF completed callback: " + e.getMessage()); + } + } + } + break; + case MSG_HANDOVER_BUSY: + synchronized (P2pLinkManager.this) { + mSendTask = null; + + if (mLinkState == LINK_STATE_DOWN || mSendState != SEND_STATE_SENDING) { + break; + } + mSendState = SEND_STATE_NOTHING_TO_SEND; + if (DBG) Log.d(TAG, "onP2pHandoverBusy()"); + mEventListener.onP2pHandoverBusy(); + } + } + return true; + } + + + @Override + public void onP2pSendConfirmed() { + onP2pSendConfirmed(true); + } + + private void onP2pSendConfirmed(boolean requireConfirmation) { + if (DBG) Log.d(TAG, "onP2pSendConfirmed()"); + synchronized (this) { + if (mLinkState == LINK_STATE_DOWN || (requireConfirmation + && mSendState != SEND_STATE_NEED_CONFIRMATION)) { + return; + } + mSendState = SEND_STATE_SENDING; + if (mLinkState == LINK_STATE_UP) { + if (mLlcpServicesConnected) { + sendNdefMessage(); + } // else, will send messages when link comes up + } else if (mLinkState == LINK_STATE_DEBOUNCE) { + // Restart debounce timeout and tell user to tap again + scheduleTimeoutLocked(MSG_DEBOUNCE_TIMEOUT, LINK_SEND_CONFIRMED_DEBOUNCE_MS); + mEventListener.onP2pSendDebounce(); + } + } + } + + + @Override + public void onP2pCanceled() { + synchronized (this) { + mSendState = SEND_STATE_CANCELED; + if (mLinkState == LINK_STATE_DOWN) { + // If we were waiting for the link to come up, stop doing so + mHandler.removeMessages(MSG_WAIT_FOR_LINK_TIMEOUT); + } else if (mLinkState == LINK_STATE_DEBOUNCE) { + // We're in debounce state so link is down. Reschedule debounce + // timeout to occur sooner, we don't want to wait any longer. + scheduleTimeoutLocked(MSG_DEBOUNCE_TIMEOUT, LINK_SEND_CANCELED_DEBOUNCE_MS); + } else { + // Link is up, nothing else to do but wait for link to go down + } + } + } + + void scheduleTimeoutLocked(int what, int timeout) { + // Cancel any outstanding debounce timeouts. + mHandler.removeMessages(what); + mHandler.sendEmptyMessageDelayed(what, timeout); + } + + static String sendStateToString(int state) { + switch (state) { + case SEND_STATE_NOTHING_TO_SEND: + return "SEND_STATE_NOTHING_TO_SEND"; + case SEND_STATE_NEED_CONFIRMATION: + return "SEND_STATE_NEED_CONFIRMATION"; + case SEND_STATE_SENDING: + return "SEND_STATE_SENDING"; + case SEND_STATE_COMPLETE: + return "SEND_STATE_COMPLETE"; + case SEND_STATE_CANCELED: + return "SEND_STATE_CANCELED"; + default: + return ""; + } + } + + static String linkStateToString(int state) { + switch (state) { + case LINK_STATE_DOWN: + return "LINK_STATE_DOWN"; + case LINK_STATE_DEBOUNCE: + return "LINK_STATE_DEBOUNCE"; + case LINK_STATE_UP: + return "LINK_STATE_UP"; + default: + return ""; + } + } + + void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + synchronized (this) { + pw.println("mIsSendEnabled=" + mIsSendEnabled); + pw.println("mIsReceiveEnabled=" + mIsReceiveEnabled); + pw.println("mLinkState=" + linkStateToString(mLinkState)); + pw.println("mSendState=" + sendStateToString(mSendState)); + + pw.println("mCallbackNdef=" + mCallbackNdef); + pw.println("mMessageToSend=" + mMessageToSend); + pw.println("mUrisToSend=" + mUrisToSend); + } + } +} diff --git a/NfcSony/src/com/android/nfc/RegisteredComponentCache.java b/NfcSony/src/com/android/nfc/RegisteredComponentCache.java new file mode 100644 index 0000000..8d73317 --- /dev/null +++ b/NfcSony/src/com/android/nfc/RegisteredComponentCache.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2009 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.nfc; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.os.UserHandle; +import android.util.Log; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * A cache of intent filters registered to receive the TECH_DISCOVERED dispatch. + */ +public class RegisteredComponentCache { + private static final String TAG = "RegisteredComponentCache"; + private static final boolean DEBUG = false; + + final Context mContext; + final String mAction; + final String mMetaDataName; + final AtomicReference mReceiver; + + // synchronized on this + private ArrayList mComponents; + + public RegisteredComponentCache(Context context, String action, String metaDataName) { + mContext = context; + mAction = action; + mMetaDataName = metaDataName; + + generateComponentsList(); + + final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context1, Intent intent) { + generateComponentsList(); + } + }; + mReceiver = new AtomicReference(receiver); + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addDataScheme("package"); + mContext.registerReceiverAsUser(receiver, UserHandle.ALL, intentFilter, null, null); + // Register for events related to sdcard installation. + IntentFilter sdFilter = new IntentFilter(); + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + mContext.registerReceiverAsUser(receiver, UserHandle.ALL, sdFilter, null, null); + // Generate a new list upon switching users as well + IntentFilter userFilter = new IntentFilter(); + userFilter.addAction(Intent.ACTION_USER_SWITCHED); + mContext.registerReceiverAsUser(receiver, UserHandle.ALL, userFilter, null, null); + } + + public static class ComponentInfo { + public final ResolveInfo resolveInfo; + public final String[] techs; + + ComponentInfo(ResolveInfo resolveInfo, String[] techs) { + this.resolveInfo = resolveInfo; + this.techs = techs; + } + + @Override + public String toString() { + StringBuilder out = new StringBuilder("ComponentInfo: "); + out.append(resolveInfo); + out.append(", techs: "); + for (String tech : techs) { + out.append(tech); + out.append(", "); + } + return out.toString(); + } + } + + /** + * @return a collection of {@link RegisteredComponentCache.ComponentInfo} objects for all + * registered authenticators. + */ + public ArrayList getComponents() { + synchronized (this) { + // It's safe to return a reference here since mComponents is always replaced and + // never updated when it changes. + return mComponents; + } + } + + /** + * Stops the monitoring of package additions, removals and changes. + */ + public void close() { + final BroadcastReceiver receiver = mReceiver.getAndSet(null); + if (receiver != null) { + mContext.unregisterReceiver(receiver); + } + } + + @Override + protected void finalize() throws Throwable { + if (mReceiver.get() != null) { + Log.e(TAG, "RegisteredServicesCache finalized without being closed"); + } + close(); + super.finalize(); + } + + void dump(ArrayList components) { + for (ComponentInfo component : components) { + Log.i(TAG, component.toString()); + } + } + + void generateComponentsList() { + PackageManager pm; + try { + UserHandle currentUser = new UserHandle(ActivityManager.getCurrentUser()); + pm = mContext.createPackageContextAsUser("android", 0, + currentUser).getPackageManager(); + } catch (NameNotFoundException e) { + Log.e(TAG, "Could not create user package context"); + return; + } + ArrayList components = new ArrayList(); + List resolveInfos = pm.queryIntentActivitiesAsUser(new Intent(mAction), + PackageManager.GET_META_DATA, ActivityManager.getCurrentUser()); + for (ResolveInfo resolveInfo : resolveInfos) { + try { + parseComponentInfo(pm, resolveInfo, components); + } catch (XmlPullParserException e) { + Log.w(TAG, "Unable to load component info " + resolveInfo.toString(), e); + } catch (IOException e) { + Log.w(TAG, "Unable to load component info " + resolveInfo.toString(), e); + } + } + + if (DEBUG) { + dump(components); + } + + synchronized (this) { + mComponents = components; + } + } + + void parseComponentInfo(PackageManager pm, ResolveInfo info, + ArrayList components) throws XmlPullParserException, IOException { + ActivityInfo ai = info.activityInfo; + + XmlResourceParser parser = null; + try { + parser = ai.loadXmlMetaData(pm, mMetaDataName); + if (parser == null) { + throw new XmlPullParserException("No " + mMetaDataName + " meta-data"); + } + + parseTechLists(pm.getResourcesForApplication(ai.applicationInfo), ai.packageName, + parser, info, components); + } catch (NameNotFoundException e) { + throw new XmlPullParserException("Unable to load resources for " + ai.packageName); + } finally { + if (parser != null) parser.close(); + } + } + + void parseTechLists(Resources res, String packageName, XmlPullParser parser, + ResolveInfo resolveInfo, ArrayList components) + throws XmlPullParserException, IOException { + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG) { + eventType = parser.next(); + } + + ArrayList items = new ArrayList(); + String tagName; + eventType = parser.next(); + do { + tagName = parser.getName(); + if (eventType == XmlPullParser.START_TAG && "tech".equals(tagName)) { + items.add(parser.nextText()); + } else if (eventType == XmlPullParser.END_TAG && "tech-list".equals(tagName)) { + int size = items.size(); + if (size > 0) { + String[] techs = new String[size]; + techs = items.toArray(techs); + items.clear(); + components.add(new ComponentInfo(resolveInfo, techs)); + } + } + eventType = parser.next(); + } while (eventType != XmlPullParser.END_DOCUMENT); + } +} diff --git a/NfcSony/src/com/android/nfc/ScreenStateHelper.java b/NfcSony/src/com/android/nfc/ScreenStateHelper.java new file mode 100644 index 0000000..b044c28 --- /dev/null +++ b/NfcSony/src/com/android/nfc/ScreenStateHelper.java @@ -0,0 +1,52 @@ +package com.android.nfc; + +import android.app.KeyguardManager; +import android.content.Context; +import android.os.PowerManager; + +/** + * Helper class for determining the current screen state for NFC activities. + */ +class ScreenStateHelper { + + static final int SCREEN_STATE_UNKNOWN = 0; + static final int SCREEN_STATE_OFF = 1; + static final int SCREEN_STATE_ON_LOCKED = 2; + static final int SCREEN_STATE_ON_UNLOCKED = 3; + + private final PowerManager mPowerManager; + private final KeyguardManager mKeyguardManager; + + ScreenStateHelper(Context context) { + mKeyguardManager = (KeyguardManager) + context.getSystemService(Context.KEYGUARD_SERVICE); + mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); + } + + int checkScreenState() { + //TODO: fix deprecated api + if (!mPowerManager.isScreenOn()) { + return SCREEN_STATE_OFF; + } else if (mKeyguardManager.isKeyguardLocked()) { + return SCREEN_STATE_ON_LOCKED; + } else { + return SCREEN_STATE_ON_UNLOCKED; + } + } + + /** + * For debugging only - no i18n + */ + static String screenStateToString(int screenState) { + switch (screenState) { + case SCREEN_STATE_OFF: + return "OFF"; + case SCREEN_STATE_ON_LOCKED: + return "ON_LOCKED"; + case SCREEN_STATE_ON_UNLOCKED: + return "ON_UNLOCKED"; + default: + return "UNKNOWN"; + } + } +} diff --git a/NfcSony/src/com/android/nfc/TechListChooserActivity.java b/NfcSony/src/com/android/nfc/TechListChooserActivity.java new file mode 100644 index 0000000..8c2c34e --- /dev/null +++ b/NfcSony/src/com/android/nfc/TechListChooserActivity.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2008 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.nfc; + +import com.android.internal.app.ResolverActivity; + +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.Log; + +import java.util.ArrayList; + +public class TechListChooserActivity extends ResolverActivity { + public static final String EXTRA_RESOLVE_INFOS = "rlist"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + Intent intent = getIntent(); + Parcelable targetParcelable = intent.getParcelableExtra(Intent.EXTRA_INTENT); + if (!(targetParcelable instanceof Intent)) { + Log.w("TechListChooserActivity", "Target is not an intent: " + targetParcelable); + finish(); + return; + } + Intent target = (Intent)targetParcelable; + ArrayList rList = intent.getParcelableArrayListExtra(EXTRA_RESOLVE_INFOS); + CharSequence title = getResources().getText(com.android.internal.R.string.chooseActivity); + super.onCreate(savedInstanceState, target, title, null, rList, false); + } +} diff --git a/NfcSony/src/com/android/nfc/beam/BeamManager.java b/NfcSony/src/com/android/nfc/beam/BeamManager.java new file mode 100644 index 0000000..87ea881 --- /dev/null +++ b/NfcSony/src/com/android/nfc/beam/BeamManager.java @@ -0,0 +1,142 @@ +/* +* Copyright (C) 2008 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.nfc.beam; + +import com.android.nfc.NfcService; +import com.android.nfc.handover.HandoverDataParser; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.UserHandle; +import android.util.Log; + +/** + * Manager for starting and stopping Beam transfers. Prevents more than one transfer from + * happening at a time. + */ +public class BeamManager implements Handler.Callback { + private static final String TAG = "BeamManager"; + private static final boolean DBG = false; + + private static final String ACTION_WHITELIST_DEVICE = + "android.btopp.intent.action.WHITELIST_DEVICE"; + public static final int MSG_BEAM_COMPLETE = 0; + + private final Object mLock; + + private boolean mBeamInProgress; + private final Handler mCallback; + + private NfcService mNfcService; + + private static final class Singleton { + public static final BeamManager INSTANCE = new BeamManager(); + } + + private BeamManager() { + mLock = new Object(); + mBeamInProgress = false; + mCallback = new Handler(Looper.getMainLooper(), this); + mNfcService = NfcService.getInstance(); + } + + public static BeamManager getInstance() { + return Singleton.INSTANCE; + } + + public boolean isBeamInProgress() { + synchronized (mLock) { + return mBeamInProgress; + } + } + + public boolean startBeamReceive(Context context, + HandoverDataParser.BluetoothHandoverData handoverData) { + synchronized (mLock) { + if (mBeamInProgress) { + return false; + } else { + mBeamInProgress = true; + } + } + + BeamTransferRecord transferRecord = + BeamTransferRecord.forBluetoothDevice( + handoverData.device, handoverData.carrierActivating, null); + + Intent receiveIntent = new Intent(context.getApplicationContext(), + BeamReceiveService.class); + receiveIntent.putExtra(BeamReceiveService.EXTRA_BEAM_TRANSFER_RECORD, transferRecord); + receiveIntent.putExtra(BeamReceiveService.EXTRA_BEAM_COMPLETE_CALLBACK, + new Messenger(mCallback)); + whitelistOppDevice(context, handoverData.device); + context.startServiceAsUser(receiveIntent, UserHandle.CURRENT); + return true; + } + + public boolean startBeamSend(Context context, + HandoverDataParser.BluetoothHandoverData outgoingHandoverData, + Uri[] uris, UserHandle userHandle) { + synchronized (mLock) { + if (mBeamInProgress) { + return false; + } else { + mBeamInProgress = true; + } + } + + BeamTransferRecord transferRecord = BeamTransferRecord.forBluetoothDevice( + outgoingHandoverData.device, outgoingHandoverData.carrierActivating, + uris); + Intent sendIntent = new Intent(context.getApplicationContext(), + BeamSendService.class); + sendIntent.putExtra(BeamSendService.EXTRA_BEAM_TRANSFER_RECORD, transferRecord); + sendIntent.putExtra(BeamSendService.EXTRA_BEAM_COMPLETE_CALLBACK, + new Messenger(mCallback)); + context.startServiceAsUser(sendIntent, userHandle); + return true; + } + + @Override + public boolean handleMessage(Message msg) { + if (msg.what == MSG_BEAM_COMPLETE) { + synchronized (mLock) { + mBeamInProgress = false; + } + + boolean success = msg.arg1 == 1; + if (success) { + mNfcService.playSound(NfcService.SOUND_END); + } + return true; + } + return false; + } + + void whitelistOppDevice(Context context, BluetoothDevice device) { + if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP"); + Intent intent = new Intent(ACTION_WHITELIST_DEVICE); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + context.sendBroadcastAsUser(intent, UserHandle.CURRENT); + } + +} diff --git a/NfcSony/src/com/android/nfc/beam/BeamReceiveService.java b/NfcSony/src/com/android/nfc/beam/BeamReceiveService.java new file mode 100644 index 0000000..e94f8bf --- /dev/null +++ b/NfcSony/src/com/android/nfc/beam/BeamReceiveService.java @@ -0,0 +1,163 @@ +package com.android.nfc.beam; + +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + + +/** + * @hide + */ +public class BeamReceiveService extends Service implements BeamTransferManager.Callback { + private static String TAG = "BeamReceiveService"; + private static boolean DBG = true; + + public static final String EXTRA_BEAM_TRANSFER_RECORD + = "com.android.nfc.beam.EXTRA_BEAM_TRANSFER_RECORD"; + public static final String EXTRA_BEAM_COMPLETE_CALLBACK + = "com.android.nfc.beam.TRANSFER_COMPLETE_CALLBACK"; + + private BeamStatusReceiver mBeamStatusReceiver; + private boolean mBluetoothEnabledByNfc; + private int mStartId; + private BeamTransferManager mTransferManager; + private Messenger mCompleteCallback; + + private final BluetoothAdapter mBluetoothAdapter; + private final BroadcastReceiver mBluetoothStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + if (state == BluetoothAdapter.STATE_OFF) { + mBluetoothEnabledByNfc = false; + } + } + } + }; + + public BeamReceiveService() { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + mStartId = startId; + + BeamTransferRecord transferRecord; + if (intent == null || + (transferRecord = intent.getParcelableExtra(EXTRA_BEAM_TRANSFER_RECORD)) == null) { + if (DBG) Log.e(TAG, "No transfer record provided. Stopping."); + stopSelf(startId); + return START_NOT_STICKY; + } + + mCompleteCallback = intent.getParcelableExtra(EXTRA_BEAM_COMPLETE_CALLBACK); + + if (prepareToReceive(transferRecord)) { + if (DBG) Log.i(TAG, "Ready for incoming Beam transfer"); + return START_STICKY; + } else { + invokeCompleteCallback(false); + stopSelf(startId); + return START_NOT_STICKY; + } + } + + // TODO: figure out a way to not duplicate this code + @Override + public void onCreate() { + super.onCreate(); + + // register BT state receiver + IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); + registerReceiver(mBluetoothStateReceiver, filter); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mBeamStatusReceiver != null) { + unregisterReceiver(mBeamStatusReceiver); + } + unregisterReceiver(mBluetoothStateReceiver); + mTransferManager = null; + } + + boolean prepareToReceive(BeamTransferRecord transferRecord) { + if (mTransferManager != null) { + return false; + } + + if (transferRecord.dataLinkType != BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) { + // only support BT + return false; + } + + if (!mBluetoothAdapter.isEnabled()) { + if (!mBluetoothAdapter.enableNoAutoConnect()) { + Log.e(TAG, "Error enabling Bluetooth."); + return false; + } + mBluetoothEnabledByNfc = true; + if (DBG) Log.d(TAG, "Queueing out transfer " + + Integer.toString(transferRecord.id)); + } + + mTransferManager = new BeamTransferManager(this, this, transferRecord, true); + + // register Beam status receiver + mBeamStatusReceiver = new BeamStatusReceiver(this, mTransferManager); + registerReceiver(mBeamStatusReceiver, mBeamStatusReceiver.getIntentFilter(), + BeamStatusReceiver.BEAM_STATUS_PERMISSION, new Handler()); + + mTransferManager.start(); + mTransferManager.updateNotification(); + return true; + } + + private void invokeCompleteCallback(boolean success) { + if (mCompleteCallback != null) { + try { + Message msg = Message.obtain(null, BeamManager.MSG_BEAM_COMPLETE); + msg.arg1 = success ? 1 : 0; + mCompleteCallback.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "failed to invoke Beam complete callback", e); + } + } + } + + @Override + public void onTransferComplete(BeamTransferManager transfer, boolean success) { + // Play success sound + if (!success) { + if (DBG) Log.d(TAG, "Transfer failed, final state: " + + Integer.toString(transfer.mState)); + } + + if (mBluetoothEnabledByNfc) { + mBluetoothEnabledByNfc = false; + mBluetoothAdapter.disable(); + } + + invokeCompleteCallback(success); + stopSelf(mStartId); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +} diff --git a/NfcSony/src/com/android/nfc/beam/BeamSendService.java b/NfcSony/src/com/android/nfc/beam/BeamSendService.java new file mode 100644 index 0000000..6c013ea --- /dev/null +++ b/NfcSony/src/com/android/nfc/beam/BeamSendService.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2014 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.nfc.beam; + +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + +public class BeamSendService extends Service implements BeamTransferManager.Callback { + private static String TAG = "BeamSendService"; + private static boolean DBG = true; + + public static String EXTRA_BEAM_TRANSFER_RECORD + = "com.android.nfc.beam.EXTRA_BEAM_TRANSFER_RECORD"; + public static final String EXTRA_BEAM_COMPLETE_CALLBACK + = "com.android.nfc.beam.TRANSFER_COMPLETE_CALLBACK"; + + private BeamTransferManager mTransferManager; + private BeamStatusReceiver mBeamStatusReceiver; + private boolean mBluetoothEnabledByNfc; + private Messenger mCompleteCallback; + private int mStartId; + + private final BluetoothAdapter mBluetoothAdapter; + private final BroadcastReceiver mBluetoothStateReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + handleBluetoothStateChanged(intent); + } + } + }; + + public BeamSendService() { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + } + + @Override + public void onCreate() { + super.onCreate(); + + // register BT state receiver + IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); + registerReceiver(mBluetoothStateReceiver, filter); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + if (mBeamStatusReceiver != null) { + unregisterReceiver(mBeamStatusReceiver); + } + unregisterReceiver(mBluetoothStateReceiver); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + mStartId = startId; + + BeamTransferRecord transferRecord; + if (intent == null || + (transferRecord = intent.getParcelableExtra(EXTRA_BEAM_TRANSFER_RECORD)) == null) { + if (DBG) Log.e(TAG, "No transfer record provided. Stopping."); + stopSelf(startId); + return START_NOT_STICKY; + } + + mCompleteCallback = intent.getParcelableExtra(EXTRA_BEAM_COMPLETE_CALLBACK); + + if (doTransfer(transferRecord)) { + if (DBG) Log.i(TAG, "Starting outgoing Beam transfer"); + return START_STICKY; + } else { + invokeCompleteCallback(false); + stopSelf(startId); + return START_NOT_STICKY; + } + } + + boolean doTransfer(BeamTransferRecord transferRecord) { + if (createBeamTransferManager(transferRecord)) { + // register Beam status receiver + mBeamStatusReceiver = new BeamStatusReceiver(this, mTransferManager); + registerReceiver(mBeamStatusReceiver, mBeamStatusReceiver.getIntentFilter(), + BeamStatusReceiver.BEAM_STATUS_PERMISSION, new Handler()); + + if (transferRecord.dataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) { + if (mBluetoothAdapter.isEnabled()) { + // Start the transfer + mTransferManager.start(); + } else { + if (!mBluetoothAdapter.enableNoAutoConnect()) { + Log.e(TAG, "Error enabling Bluetooth."); + mTransferManager = null; + return false; + } + mBluetoothEnabledByNfc = true; + if (DBG) Log.d(TAG, "Queueing out transfer " + + Integer.toString(transferRecord.id)); + } + } + return true; + } + + return false; + } + + boolean createBeamTransferManager(BeamTransferRecord transferRecord) { + if (mTransferManager != null) { + return false; + } + + if (transferRecord.dataLinkType != BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) { + // only support BT + return false; + } + + mTransferManager = new BeamTransferManager(this, this, transferRecord, false); + mTransferManager.updateNotification(); + return true; + } + + private void handleBluetoothStateChanged(Intent intent) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + if (state == BluetoothAdapter.STATE_ON) { + if (mTransferManager != null && + mTransferManager.mDataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) { + mTransferManager.start(); + } + } else if (state == BluetoothAdapter.STATE_OFF) { + mBluetoothEnabledByNfc = false; + } + } + + private void invokeCompleteCallback(boolean success) { + if (mCompleteCallback != null) { + try { + Message msg = Message.obtain(null, BeamManager.MSG_BEAM_COMPLETE); + msg.arg1 = success ? 1 : 0; + mCompleteCallback.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "failed to invoke Beam complete callback", e); + } + } + } + + @Override + public void onTransferComplete(BeamTransferManager transfer, boolean success) { + // Play success sound + if (!success) { + if (DBG) Log.d(TAG, "Transfer failed, final state: " + + Integer.toString(transfer.mState)); + } + + if (mBluetoothEnabledByNfc) { + mBluetoothEnabledByNfc = false; + mBluetoothAdapter.disable(); + } + + invokeCompleteCallback(success); + stopSelf(mStartId); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } +} diff --git a/NfcSony/src/com/android/nfc/beam/BeamStatusReceiver.java b/NfcSony/src/com/android/nfc/beam/BeamStatusReceiver.java new file mode 100644 index 0000000..67b5b82 --- /dev/null +++ b/NfcSony/src/com/android/nfc/beam/BeamStatusReceiver.java @@ -0,0 +1,155 @@ +package com.android.nfc.beam; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.Uri; +import android.util.Log; + +import java.io.File; + +/** + * @hide + */ +public class BeamStatusReceiver extends BroadcastReceiver { + private static final boolean DBG = true; + private static final String TAG = "BeamStatusReceiver"; + + private static final String ACTION_HANDOVER_STARTED = + "android.nfc.handover.intent.action.HANDOVER_STARTED"; + + private static final String ACTION_TRANSFER_PROGRESS = + "android.nfc.handover.intent.action.TRANSFER_PROGRESS"; + + private static final String ACTION_TRANSFER_DONE = + "android.nfc.handover.intent.action.TRANSFER_DONE"; + + private static final String EXTRA_HANDOVER_DATA_LINK_TYPE = + "android.nfc.handover.intent.extra.HANDOVER_DATA_LINK_TYPE"; + + + private static final String EXTRA_TRANSFER_PROGRESS = + "android.nfc.handover.intent.extra.TRANSFER_PROGRESS"; + + private static final String EXTRA_TRANSFER_URI = + "android.nfc.handover.intent.extra.TRANSFER_URI"; + + private static final String EXTRA_OBJECT_COUNT = + "android.nfc.handover.intent.extra.OBJECT_COUNT"; + + private static final String EXTRA_TRANSFER_STATUS = + "android.nfc.handover.intent.extra.TRANSFER_STATUS"; + + private static final String EXTRA_TRANSFER_MIMETYPE = + "android.nfc.handover.intent.extra.TRANSFER_MIME_TYPE"; + + private static final String ACTION_STOP_BLUETOOTH_TRANSFER = + "android.btopp.intent.action.STOP_HANDOVER_TRANSFER"; + + // FIXME: Needs to stay in sync with com.android.bluetooth.opp.Constants + private static final int HANDOVER_TRANSFER_STATUS_SUCCESS = 0; + private static final int HANDOVER_TRANSFER_STATUS_FAILURE = 1; + + // permission needed to be able to receive handover status requests + public static final String BEAM_STATUS_PERMISSION = + "android.permission.NFC_HANDOVER_STATUS"; + + // Needed to build cancel intent in Beam notification + public static final String EXTRA_INCOMING = + "com.android.nfc.handover.extra.INCOMING"; + + public static final String EXTRA_TRANSFER_ID = + "android.nfc.handover.intent.extra.TRANSFER_ID"; + + public static final String EXTRA_ADDRESS = + "android.nfc.handover.intent.extra.ADDRESS"; + + public static final String ACTION_CANCEL_HANDOVER_TRANSFER = + "com.android.nfc.handover.action.CANCEL_HANDOVER_TRANSFER"; + + public static final int DIRECTION_INCOMING = 0; + public static final int DIRECTION_OUTGOING = 1; + + private final Context mContext; + private final BeamTransferManager mTransferManager; + + BeamStatusReceiver(Context context, BeamTransferManager transferManager) { + mContext = context; + mTransferManager = transferManager; + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + int dataLinkType = intent.getIntExtra(EXTRA_HANDOVER_DATA_LINK_TYPE, + BeamTransferManager.DATA_LINK_TYPE_BLUETOOTH); + + if (ACTION_CANCEL_HANDOVER_TRANSFER.equals(action)) { + if (mTransferManager != null) { + mTransferManager.cancel(); + } + } else if (ACTION_TRANSFER_PROGRESS.equals(action) || + ACTION_TRANSFER_DONE.equals(action) || + ACTION_HANDOVER_STARTED.equals(action)) { + handleTransferEvent(intent, dataLinkType); + } + } + + public IntentFilter getIntentFilter() { + IntentFilter filter = new IntentFilter(ACTION_TRANSFER_DONE); + filter.addAction(ACTION_TRANSFER_PROGRESS); + filter.addAction(ACTION_CANCEL_HANDOVER_TRANSFER); + filter.addAction(ACTION_HANDOVER_STARTED); + return filter; + } + + private void handleTransferEvent(Intent intent, int deviceType) { + String action = intent.getAction(); + int id = intent.getIntExtra(EXTRA_TRANSFER_ID, -1); + + String sourceAddress = intent.getStringExtra(EXTRA_ADDRESS); + + if (sourceAddress == null) return; + + if (mTransferManager == null) { + // There is no transfer running for this source address; most likely + // the transfer was cancelled. We need to tell BT OPP to stop transferring. + if (id != -1) { + if (deviceType == BeamTransferManager.DATA_LINK_TYPE_BLUETOOTH) { + if (DBG) Log.d(TAG, "Didn't find transfer, stopping"); + Intent cancelIntent = new Intent(ACTION_STOP_BLUETOOTH_TRANSFER); + cancelIntent.putExtra(EXTRA_TRANSFER_ID, id); + mContext.sendBroadcast(cancelIntent); + } + } + return; + } + + mTransferManager.setBluetoothTransferId(id); + + if (action.equals(ACTION_TRANSFER_DONE)) { + int handoverStatus = intent.getIntExtra(EXTRA_TRANSFER_STATUS, + HANDOVER_TRANSFER_STATUS_FAILURE); + if (handoverStatus == HANDOVER_TRANSFER_STATUS_SUCCESS) { + String uriString = intent.getStringExtra(EXTRA_TRANSFER_URI); + String mimeType = intent.getStringExtra(EXTRA_TRANSFER_MIMETYPE); + Uri uri = Uri.parse(uriString); + if (uri != null && uri.getScheme() == null) { + uri = Uri.fromFile(new File(uri.getPath())); + } + mTransferManager.finishTransfer(true, uri, mimeType); + } else { + mTransferManager.finishTransfer(false, null, null); + } + } else if (action.equals(ACTION_TRANSFER_PROGRESS)) { + float progress = intent.getFloatExtra(EXTRA_TRANSFER_PROGRESS, 0.0f); + mTransferManager.updateFileProgress(progress); + } else if (action.equals(ACTION_HANDOVER_STARTED)) { + int count = intent.getIntExtra(EXTRA_OBJECT_COUNT, 0); + if (count > 0) { + mTransferManager.setObjectCount(count); + } + } + } +} diff --git a/NfcSony/src/com/android/nfc/beam/BeamTransferManager.java b/NfcSony/src/com/android/nfc/beam/BeamTransferManager.java new file mode 100644 index 0000000..d0914bf --- /dev/null +++ b/NfcSony/src/com/android/nfc/beam/BeamTransferManager.java @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2012 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.nfc.beam; + +import com.android.nfc.R; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Notification.Builder; +import android.bluetooth.BluetoothDevice; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.media.MediaScannerConnection; +import android.net.Uri; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.Log; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; + +/** + * A BeamTransferManager object represents a set of files + * that were received through NFC connection handover + * from the same source address. + * + * It manages starting, stopping, and processing the transfer, as well + * as the user visible notification. + * + * For Bluetooth, files are received through OPP, and + * we have no knowledge how many files will be transferred + * as part of a single transaction. + * Hence, a transfer has a notion of being "alive": if + * the last update to a transfer was within WAIT_FOR_NEXT_TRANSFER_MS + * milliseconds, we consider a new file transfer from the + * same source address as part of the same transfer. + * The corresponding URIs will be grouped in a single folder. + * + * @hide + */ + +public class BeamTransferManager implements Handler.Callback, + MediaScannerConnection.OnScanCompletedListener { + interface Callback { + + void onTransferComplete(BeamTransferManager transfer, boolean success); + }; + static final String TAG = "BeamTransferManager"; + + static final Boolean DBG = true; + + // In the states below we still accept new file transfer + static final int STATE_NEW = 0; + static final int STATE_IN_PROGRESS = 1; + static final int STATE_W4_NEXT_TRANSFER = 2; + // In the states below no new files are accepted. + static final int STATE_W4_MEDIA_SCANNER = 3; + static final int STATE_FAILED = 4; + static final int STATE_SUCCESS = 5; + static final int STATE_CANCELLED = 6; + static final int STATE_CANCELLING = 7; + static final int MSG_NEXT_TRANSFER_TIMER = 0; + + static final int MSG_TRANSFER_TIMEOUT = 1; + static final int DATA_LINK_TYPE_BLUETOOTH = 1; + + // We need to receive an update within this time period + // to still consider this transfer to be "alive" (ie + // a reason to keep the handover transport enabled). + static final int ALIVE_CHECK_MS = 20000; + + // The amount of time to wait for a new transfer + // once the current one completes. + static final int WAIT_FOR_NEXT_TRANSFER_MS = 4000; + + static final String BEAM_DIR = "beam"; + + static final String ACTION_WHITELIST_DEVICE = + "android.btopp.intent.action.WHITELIST_DEVICE"; + + static final String ACTION_STOP_BLUETOOTH_TRANSFER = + "android.btopp.intent.action.STOP_HANDOVER_TRANSFER"; + + final boolean mIncoming; // whether this is an incoming transfer + + final int mTransferId; // Unique ID of this transfer used for notifications + int mBluetoothTransferId; // ID of this transfer in Bluetooth namespace + + final PendingIntent mCancelIntent; + final Context mContext; + final Handler mHandler; + final NotificationManager mNotificationManager; + final BluetoothDevice mRemoteDevice; + final Callback mCallback; + final boolean mRemoteActivating; + + // Variables below are only accessed on the main thread + int mState; + int mCurrentCount; + int mSuccessCount; + int mTotalCount; + int mDataLinkType; + boolean mCalledBack; + Long mLastUpdate; // Last time an event occurred for this transfer + float mProgress; // Progress in range [0..1] + ArrayList mUris; // Received uris from transport + ArrayList mTransferMimeTypes; // Mime-types received from transport + Uri[] mOutgoingUris; // URIs to send + ArrayList mPaths; // Raw paths on the filesystem for Beam-stored files + HashMap mMimeTypes; // Mime-types associated with each path + HashMap mMediaUris; // URIs found by the media scanner for each path + int mUrisScanned; + Long mStartTime; + + public BeamTransferManager(Context context, Callback callback, + BeamTransferRecord pendingTransfer, boolean incoming) { + mContext = context; + mCallback = callback; + mRemoteDevice = pendingTransfer.remoteDevice; + mIncoming = incoming; + mTransferId = pendingTransfer.id; + mBluetoothTransferId = -1; + mDataLinkType = pendingTransfer.dataLinkType; + mRemoteActivating = pendingTransfer.remoteActivating; + mStartTime = 0L; + // For incoming transfers, count can be set later + mTotalCount = (pendingTransfer.uris != null) ? pendingTransfer.uris.length : 0; + mLastUpdate = SystemClock.elapsedRealtime(); + mProgress = 0.0f; + mState = STATE_NEW; + mUris = pendingTransfer.uris == null + ? new ArrayList() + : new ArrayList(Arrays.asList(pendingTransfer.uris)); + mTransferMimeTypes = new ArrayList(); + mMimeTypes = new HashMap(); + mPaths = new ArrayList(); + mMediaUris = new HashMap(); + mCancelIntent = buildCancelIntent(); + mUrisScanned = 0; + mCurrentCount = 0; + mSuccessCount = 0; + mOutgoingUris = pendingTransfer.uris; + mHandler = new Handler(Looper.getMainLooper(), this); + mHandler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS); + mNotificationManager = (NotificationManager) mContext.getSystemService( + Context.NOTIFICATION_SERVICE); + } + + void whitelistOppDevice(BluetoothDevice device) { + if (DBG) Log.d(TAG, "Whitelisting " + device + " for BT OPP"); + Intent intent = new Intent(ACTION_WHITELIST_DEVICE); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); + mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); + } + + public void start() { + if (mStartTime > 0) { + // already started + return; + } + + mStartTime = System.currentTimeMillis(); + + if (!mIncoming) { + if (mDataLinkType == BeamTransferRecord.DATA_LINK_TYPE_BLUETOOTH) { + new BluetoothOppHandover(mContext, mRemoteDevice, mUris, mRemoteActivating).start(); + } + } + } + + public void updateFileProgress(float progress) { + if (!isRunning()) return; // Ignore when we're no longer running + + mHandler.removeMessages(MSG_NEXT_TRANSFER_TIMER); + + this.mProgress = progress; + + // We're still receiving data from this device - keep it in + // the whitelist for a while longer + if (mIncoming && mRemoteDevice != null) whitelistOppDevice(mRemoteDevice); + + updateStateAndNotification(STATE_IN_PROGRESS); + } + + public synchronized void setBluetoothTransferId(int id) { + if (mBluetoothTransferId == -1 && id != -1) { + mBluetoothTransferId = id; + if (mState == STATE_CANCELLING) { + sendBluetoothCancelIntentAndUpdateState(); + } + } + } + + public void finishTransfer(boolean success, Uri uri, String mimeType) { + if (!isRunning()) return; // Ignore when we're no longer running + + mCurrentCount++; + if (success && uri != null) { + mSuccessCount++; + if (DBG) Log.d(TAG, "Transfer success, uri " + uri + " mimeType " + mimeType); + mProgress = 0.0f; + if (mimeType == null) { + mimeType = MimeTypeUtil.getMimeTypeForUri(mContext, uri); + } + if (mimeType != null) { + mUris.add(uri); + mTransferMimeTypes.add(mimeType); + } else { + if (DBG) Log.d(TAG, "Could not get mimeType for file."); + } + } else { + Log.e(TAG, "Handover transfer failed"); + // Do wait to see if there's another file coming. + } + mHandler.removeMessages(MSG_NEXT_TRANSFER_TIMER); + if (mCurrentCount == mTotalCount) { + if (mIncoming) { + processFiles(); + } else { + updateStateAndNotification(mSuccessCount > 0 ? STATE_SUCCESS : STATE_FAILED); + } + } else { + mHandler.sendEmptyMessageDelayed(MSG_NEXT_TRANSFER_TIMER, WAIT_FOR_NEXT_TRANSFER_MS); + updateStateAndNotification(STATE_W4_NEXT_TRANSFER); + } + } + + public boolean isRunning() { + if (mState != STATE_NEW && mState != STATE_IN_PROGRESS && mState != STATE_W4_NEXT_TRANSFER) { + return false; + } else { + return true; + } + } + + public void setObjectCount(int objectCount) { + mTotalCount = objectCount; + } + + void cancel() { + if (!isRunning()) return; + + // Delete all files received so far + for (Uri uri : mUris) { + File file = new File(uri.getPath()); + if (file.exists()) file.delete(); + } + + if (mBluetoothTransferId != -1) { + // we know the ID, we can cancel immediately + sendBluetoothCancelIntentAndUpdateState(); + } else { + updateStateAndNotification(STATE_CANCELLING); + } + + } + + private void sendBluetoothCancelIntentAndUpdateState() { + Intent cancelIntent = new Intent(ACTION_STOP_BLUETOOTH_TRANSFER); + cancelIntent.putExtra(BeamStatusReceiver.EXTRA_TRANSFER_ID, mBluetoothTransferId); + mContext.sendBroadcast(cancelIntent); + updateStateAndNotification(STATE_CANCELLED); + } + + void updateNotification() { + Builder notBuilder = new Notification.Builder(mContext); + notBuilder.setColor(mContext.getResources().getColor( + com.android.internal.R.color.system_notification_accent_color)); + notBuilder.setWhen(mStartTime); + notBuilder.setVisibility(Notification.VISIBILITY_PUBLIC); + String beamString; + if (mIncoming) { + beamString = mContext.getString(R.string.beam_progress); + } else { + beamString = mContext.getString(R.string.beam_outgoing); + } + if (mState == STATE_NEW || mState == STATE_IN_PROGRESS || + mState == STATE_W4_NEXT_TRANSFER || mState == STATE_W4_MEDIA_SCANNER) { + notBuilder.setAutoCancel(false); + notBuilder.setSmallIcon(mIncoming ? android.R.drawable.stat_sys_download : + android.R.drawable.stat_sys_upload); + notBuilder.setTicker(beamString); + notBuilder.setContentTitle(beamString); + notBuilder.addAction(R.drawable.ic_menu_cancel_holo_dark, + mContext.getString(R.string.cancel), mCancelIntent); + float progress = 0; + if (mTotalCount > 0) { + float progressUnit = 1.0f / mTotalCount; + progress = (float) mCurrentCount * progressUnit + mProgress * progressUnit; + } + if (mTotalCount > 0 && progress > 0) { + notBuilder.setProgress(100, (int) (100 * progress), false); + } else { + notBuilder.setProgress(100, 0, true); + } + } else if (mState == STATE_SUCCESS) { + notBuilder.setAutoCancel(true); + notBuilder.setSmallIcon(mIncoming ? android.R.drawable.stat_sys_download_done : + android.R.drawable.stat_sys_upload_done); + notBuilder.setTicker(mContext.getString(R.string.beam_complete)); + notBuilder.setContentTitle(mContext.getString(R.string.beam_complete)); + + if (mIncoming) { + notBuilder.setContentText(mContext.getString(R.string.beam_touch_to_view)); + Intent viewIntent = buildViewIntent(); + PendingIntent contentIntent = PendingIntent.getActivity( + mContext, mTransferId, viewIntent, 0, null); + + notBuilder.setContentIntent(contentIntent); + } + } else if (mState == STATE_FAILED) { + notBuilder.setAutoCancel(false); + notBuilder.setSmallIcon(mIncoming ? android.R.drawable.stat_sys_download_done : + android.R.drawable.stat_sys_upload_done); + notBuilder.setTicker(mContext.getString(R.string.beam_failed)); + notBuilder.setContentTitle(mContext.getString(R.string.beam_failed)); + } else if (mState == STATE_CANCELLED || mState == STATE_CANCELLING) { + notBuilder.setAutoCancel(false); + notBuilder.setSmallIcon(mIncoming ? android.R.drawable.stat_sys_download_done : + android.R.drawable.stat_sys_upload_done); + notBuilder.setTicker(mContext.getString(R.string.beam_canceled)); + notBuilder.setContentTitle(mContext.getString(R.string.beam_canceled)); + } else { + return; + } + + mNotificationManager.notify(null, mTransferId, notBuilder.build()); + } + + void updateStateAndNotification(int newState) { + this.mState = newState; + this.mLastUpdate = SystemClock.elapsedRealtime(); + + mHandler.removeMessages(MSG_TRANSFER_TIMEOUT); + if (isRunning()) { + // Update timeout timer if we're still running + mHandler.sendEmptyMessageDelayed(MSG_TRANSFER_TIMEOUT, ALIVE_CHECK_MS); + } + + updateNotification(); + + if ((mState == STATE_SUCCESS || mState == STATE_FAILED || mState == STATE_CANCELLED) + && !mCalledBack) { + mCalledBack = true; + // Notify that we're done with this transfer + mCallback.onTransferComplete(this, mState == STATE_SUCCESS); + } + } + + void processFiles() { + // Check the amount of files we received in this transfer; + // If more than one, create a separate directory for it. + String extRoot = Environment.getExternalStorageDirectory().getPath(); + File beamPath = new File(extRoot + "/" + BEAM_DIR); + + if (!checkMediaStorage(beamPath) || mUris.size() == 0) { + Log.e(TAG, "Media storage not valid or no uris received."); + updateStateAndNotification(STATE_FAILED); + return; + } + + if (mUris.size() > 1) { + beamPath = generateMultiplePath(extRoot + "/" + BEAM_DIR + "/"); + if (!beamPath.isDirectory() && !beamPath.mkdir()) { + Log.e(TAG, "Failed to create multiple path " + beamPath.toString()); + updateStateAndNotification(STATE_FAILED); + return; + } + } + + for (int i = 0; i < mUris.size(); i++) { + Uri uri = mUris.get(i); + String mimeType = mTransferMimeTypes.get(i); + + File srcFile = new File(uri.getPath()); + + File dstFile = generateUniqueDestination(beamPath.getAbsolutePath(), + uri.getLastPathSegment()); + Log.d(TAG, "Renaming from " + srcFile); + if (!srcFile.renameTo(dstFile)) { + if (DBG) Log.d(TAG, "Failed to rename from " + srcFile + " to " + dstFile); + srcFile.delete(); + return; + } else { + mPaths.add(dstFile.getAbsolutePath()); + mMimeTypes.put(dstFile.getAbsolutePath(), mimeType); + if (DBG) Log.d(TAG, "Did successful rename from " + srcFile + " to " + dstFile); + } + } + + // We can either add files to the media provider, or provide an ACTION_VIEW + // intent to the file directly. We base this decision on the mime type + // of the first file; if it's media the platform can deal with, + // use the media provider, if it's something else, just launch an ACTION_VIEW + // on the file. + String mimeType = mMimeTypes.get(mPaths.get(0)); + if (mimeType.startsWith("image/") || mimeType.startsWith("video/") || + mimeType.startsWith("audio/")) { + String[] arrayPaths = new String[mPaths.size()]; + MediaScannerConnection.scanFile(mContext, mPaths.toArray(arrayPaths), null, this); + updateStateAndNotification(STATE_W4_MEDIA_SCANNER); + } else { + // We're done. + updateStateAndNotification(STATE_SUCCESS); + } + + } + + public boolean handleMessage(Message msg) { + if (msg.what == MSG_NEXT_TRANSFER_TIMER) { + // We didn't receive a new transfer in time, finalize this one + if (mIncoming) { + processFiles(); + } else { + updateStateAndNotification(mSuccessCount > 0 ? STATE_SUCCESS : STATE_FAILED); + } + return true; + } else if (msg.what == MSG_TRANSFER_TIMEOUT) { + // No update on this transfer for a while, fail it. + if (DBG) Log.d(TAG, "Transfer timed out for id: " + Integer.toString(mTransferId)); + updateStateAndNotification(STATE_FAILED); + } + return false; + } + + public synchronized void onScanCompleted(String path, Uri uri) { + if (DBG) Log.d(TAG, "Scan completed, path " + path + " uri " + uri); + if (uri != null) { + mMediaUris.put(path, uri); + } + mUrisScanned++; + if (mUrisScanned == mPaths.size()) { + // We're done + updateStateAndNotification(STATE_SUCCESS); + } + } + + + Intent buildViewIntent() { + if (mPaths.size() == 0) return null; + + Intent viewIntent = new Intent(Intent.ACTION_VIEW); + + String filePath = mPaths.get(0); + Uri mediaUri = mMediaUris.get(filePath); + Uri uri = mediaUri != null ? mediaUri : + Uri.parse(ContentResolver.SCHEME_FILE + "://" + filePath); + viewIntent.setDataAndTypeAndNormalize(uri, mMimeTypes.get(filePath)); + viewIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + return viewIntent; + } + + PendingIntent buildCancelIntent() { + Intent intent = new Intent(BeamStatusReceiver.ACTION_CANCEL_HANDOVER_TRANSFER); + intent.putExtra(BeamStatusReceiver.EXTRA_ADDRESS, mRemoteDevice.getAddress()); + intent.putExtra(BeamStatusReceiver.EXTRA_INCOMING, mIncoming ? + BeamStatusReceiver.DIRECTION_INCOMING : BeamStatusReceiver.DIRECTION_OUTGOING); + PendingIntent pi = PendingIntent.getBroadcast(mContext, mTransferId, intent, + PendingIntent.FLAG_ONE_SHOT); + + return pi; + } + + static boolean checkMediaStorage(File path) { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + if (!path.isDirectory() && !path.mkdir()) { + Log.e(TAG, "Not dir or not mkdir " + path.getAbsolutePath()); + return false; + } + return true; + } else { + Log.e(TAG, "External storage not mounted, can't store file."); + return false; + } + } + + static File generateUniqueDestination(String path, String fileName) { + int dotIndex = fileName.lastIndexOf("."); + String extension = null; + String fileNameWithoutExtension = null; + if (dotIndex < 0) { + extension = ""; + fileNameWithoutExtension = fileName; + } else { + extension = fileName.substring(dotIndex); + fileNameWithoutExtension = fileName.substring(0, dotIndex); + } + File dstFile = new File(path + File.separator + fileName); + int count = 0; + while (dstFile.exists()) { + dstFile = new File(path + File.separator + fileNameWithoutExtension + "-" + + Integer.toString(count) + extension); + count++; + } + return dstFile; + } + + static File generateMultiplePath(String beamRoot) { + // Generate a unique directory with the date + String format = "yyyy-MM-dd"; + SimpleDateFormat sdf = new SimpleDateFormat(format, Locale.US); + String newPath = beamRoot + "beam-" + sdf.format(new Date()); + File newFile = new File(newPath); + int count = 0; + while (newFile.exists()) { + newPath = beamRoot + "beam-" + sdf.format(new Date()) + "-" + + Integer.toString(count); + newFile = new File(newPath); + count++; + } + return newFile; + } +} + diff --git a/NfcSony/src/com/android/nfc/beam/BeamTransferRecord.aidl b/NfcSony/src/com/android/nfc/beam/BeamTransferRecord.aidl new file mode 100644 index 0000000..93af205 --- /dev/null +++ b/NfcSony/src/com/android/nfc/beam/BeamTransferRecord.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2013 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.nfc.beam; + +parcelable BeamTransferRecord; diff --git a/NfcSony/src/com/android/nfc/beam/BeamTransferRecord.java b/NfcSony/src/com/android/nfc/beam/BeamTransferRecord.java new file mode 100644 index 0000000..d8f8668 --- /dev/null +++ b/NfcSony/src/com/android/nfc/beam/BeamTransferRecord.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2012 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.nfc.beam; + +import android.bluetooth.BluetoothDevice; +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; + +public class BeamTransferRecord implements Parcelable { + + public static int DATA_LINK_TYPE_BLUETOOTH = 0; + + public int id; + public boolean remoteActivating; + public Uri[] uris; + public int dataLinkType; + + // Data link specific fields + public BluetoothDevice remoteDevice; + + + private BeamTransferRecord(BluetoothDevice remoteDevice, + boolean remoteActivating, Uri[] uris) { + this.id = 0; + this.remoteDevice = remoteDevice; + this.remoteActivating = remoteActivating; + this.uris = uris; + + this.dataLinkType = DATA_LINK_TYPE_BLUETOOTH; + } + + public static final BeamTransferRecord forBluetoothDevice( + BluetoothDevice remoteDevice, boolean remoteActivating, + Uri[] uris) { + return new BeamTransferRecord(remoteDevice, remoteActivating, uris); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public BeamTransferRecord createFromParcel(Parcel in) { + int deviceType = in.readInt(); + + if (deviceType != DATA_LINK_TYPE_BLUETOOTH) { + // only support BLUETOOTH + return null; + } + + BluetoothDevice remoteDevice = in.readParcelable(getClass().getClassLoader()); + boolean remoteActivating = (in.readInt() == 1); + int numUris = in.readInt(); + Uri[] uris = null; + if (numUris > 0) { + uris = new Uri[numUris]; + in.readTypedArray(uris, Uri.CREATOR); + } + + return new BeamTransferRecord(remoteDevice, + remoteActivating, uris); + + } + + @Override + public BeamTransferRecord[] newArray(int size) { + return new BeamTransferRecord[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(dataLinkType); + dest.writeParcelable(remoteDevice, 0); + dest.writeInt(remoteActivating ? 1 : 0); + dest.writeInt(uris != null ? uris.length : 0); + if (uris != null && uris.length > 0) { + dest.writeTypedArray(uris, 0); + } + } +} diff --git a/NfcSony/src/com/android/nfc/beam/BluetoothOppHandover.java b/NfcSony/src/com/android/nfc/beam/BluetoothOppHandover.java new file mode 100644 index 0000000..8e04093 --- /dev/null +++ b/NfcSony/src/com/android/nfc/beam/BluetoothOppHandover.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2012 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.nfc.beam; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; +import android.util.Log; + +import java.util.ArrayList; + +public class BluetoothOppHandover implements Handler.Callback { + static final String TAG = "BluetoothOppHandover"; + static final boolean DBG = true; + + static final int STATE_INIT = 0; + static final int STATE_TURNING_ON = 1; + static final int STATE_WAITING = 2; // Need to wait for remote side turning on BT + static final int STATE_COMPLETE = 3; + + static final int MSG_START_SEND = 0; + + static final int REMOTE_BT_ENABLE_DELAY_MS = 5000; + + static final String ACTION_HANDOVER_SEND = + "android.nfc.handover.intent.action.HANDOVER_SEND"; + + static final String ACTION_HANDOVER_SEND_MULTIPLE = + "android.nfc.handover.intent.action.HANDOVER_SEND_MULTIPLE"; + + final Context mContext; + final BluetoothDevice mDevice; + + final ArrayList mUris; + final boolean mRemoteActivating; + final Handler mHandler; + final Long mCreateTime; + + int mState; + + public BluetoothOppHandover(Context context, BluetoothDevice device, ArrayList uris, + boolean remoteActivating) { + mContext = context; + mDevice = device; + mUris = uris; + mRemoteActivating = remoteActivating; + mCreateTime = SystemClock.elapsedRealtime(); + + mHandler = new Handler(context.getMainLooper(), this); + mState = STATE_INIT; + } + + /** + * Main entry point. This method is usually called after construction, + * to begin the BT sequence. Must be called on Main thread. + */ + public void start() { + if (mRemoteActivating) { + Long timeElapsed = SystemClock.elapsedRealtime() - mCreateTime; + if (timeElapsed < REMOTE_BT_ENABLE_DELAY_MS) { + mHandler.sendEmptyMessageDelayed(MSG_START_SEND, + REMOTE_BT_ENABLE_DELAY_MS - timeElapsed); + } else { + // Already waited long enough for BT to come up + // - start send. + sendIntent(); + } + } else { + // Remote BT enabled already, start send immediately + sendIntent(); + } + } + + void complete() { + mState = STATE_COMPLETE; + } + + void sendIntent() { + Intent intent = new Intent(); + intent.setPackage("com.android.bluetooth"); + String mimeType = MimeTypeUtil.getMimeTypeForUri(mContext, mUris.get(0)); + intent.setType(mimeType); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + for (Uri uri : mUris) { + // TODO we need to transfer our permission grant from NFC + // to the Bluetooth process. This works, but we don't have + // a good framework API for revoking permission yet. + try { + mContext.grantUriPermission("com.android.bluetooth", uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION); + } catch (SecurityException e) { + Log.e(TAG, "Failed to transfer permission to Bluetooth process."); + } + } + if (mUris.size() == 1) { + intent.setAction(ACTION_HANDOVER_SEND); + intent.putExtra(Intent.EXTRA_STREAM, mUris.get(0)); + } else { + intent.setAction(ACTION_HANDOVER_SEND_MULTIPLE); + intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, mUris); + } + if (DBG) Log.d(TAG, "Handing off outging transfer to BT"); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + mContext.sendBroadcast(intent); + + complete(); + } + + + @Override + public boolean handleMessage(Message msg) { + if (msg.what == MSG_START_SEND) { + sendIntent(); + return true; + } + return false; + } +} diff --git a/NfcSony/src/com/android/nfc/beam/FireflyRenderer.java b/NfcSony/src/com/android/nfc/beam/FireflyRenderer.java new file mode 100644 index 0000000..d87a5d9 --- /dev/null +++ b/NfcSony/src/com/android/nfc/beam/FireflyRenderer.java @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2011 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.nfc.beam; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.SurfaceTexture; +import android.opengl.GLUtils; +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; +import javax.microedition.khronos.opengles.GL10; + +public class FireflyRenderer { + private static final String LOG_TAG = "NfcFireflyThread"; + + static final int NUM_FIREFLIES = 200; + + static final float NEAR_CLIPPING_PLANE = 50f; + static final float FAR_CLIPPING_PLANE = 100f; + + // All final variables below only need to be allocated once + // and can be reused between subsequent Beams + static final int[] sEglConfig = { + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 0, + EGL10.EGL_DEPTH_SIZE, 0, + EGL10.EGL_STENCIL_SIZE, 0, + EGL10.EGL_NONE + }; + + // Vertices for drawing a 32x32 rect + static final float mVertices[] = { + 0.0f, 0.0f, 0.0f, // 0, Top Left + 0.0f, 32.0f, 0.0f, // 1, Bottom Left + 32.0f, 32.0f, 0.0f, // 2, Bottom Right + 32.0f, 0.0f, 0.0f, // 3, Top Right + }; + + // Mapping coordinates for the texture + static final float mTextCoords[] = { + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + }; + + // Connecting order (draws a square) + static final short[] mIndices = { 0, 1, 2, 0, 2, 3 }; + + final Context mContext; + + // Buffer holding the vertices + final FloatBuffer mVertexBuffer; + + // Buffer holding the indices + final ShortBuffer mIndexBuffer; + + // Buffer holding the texture mapping coordinates + final FloatBuffer mTextureBuffer; + + final Firefly[] mFireflies; + + FireflyRenderThread mFireflyRenderThread; + + // The surface to render the flies on, including width and height + SurfaceTexture mSurface; + int mDisplayWidth; + int mDisplayHeight; + + public FireflyRenderer(Context context) { + mContext = context; + + // First, build the vertex, texture and index buffers + ByteBuffer vbb = ByteBuffer.allocateDirect(mVertices.length * 4); // Float => 4 bytes + vbb.order(ByteOrder.nativeOrder()); + mVertexBuffer = vbb.asFloatBuffer(); + mVertexBuffer.put(mVertices); + mVertexBuffer.position(0); + + ByteBuffer ibb = ByteBuffer.allocateDirect(mIndices.length * 2); // Short => 2 bytes + ibb.order(ByteOrder.nativeOrder()); + mIndexBuffer = ibb.asShortBuffer(); + mIndexBuffer.put(mIndices); + mIndexBuffer.position(0); + + ByteBuffer tbb = ByteBuffer.allocateDirect(mTextCoords.length * 4); + tbb.order(ByteOrder.nativeOrder()); + mTextureBuffer = tbb.asFloatBuffer(); + mTextureBuffer.put(mTextCoords); + mTextureBuffer.position(0); + + mFireflies = new Firefly[NUM_FIREFLIES]; + for (int i = 0; i < NUM_FIREFLIES; i++) { + mFireflies[i] = new Firefly(); + } + } + + /** + * Starts rendering fireflies on the given surface. + * Must be called from the UI-thread. + */ + public void start(SurfaceTexture surface, int width, int height) { + mSurface = surface; + mDisplayWidth = width; + mDisplayHeight = height; + + mFireflyRenderThread = new FireflyRenderThread(); + mFireflyRenderThread.start(); + } + + /** + * Stops rendering fireflies. + * Must be called from the UI-thread. + */ + public void stop() { + if (mFireflyRenderThread != null) { + mFireflyRenderThread.finish(); + try { + mFireflyRenderThread.join(); + } catch (InterruptedException e) { + Log.e(LOG_TAG, "Couldn't wait for FireflyRenderThread."); + } + mFireflyRenderThread = null; + } + } + + private class FireflyRenderThread extends Thread { + EGL10 mEgl; + EGLDisplay mEglDisplay; + EGLConfig mEglConfig; + EGLContext mEglContext; + EGLSurface mEglSurface; + GL10 mGL; + + // Holding the handle to the texture + int mTextureId; + + // Read/written by multiple threads + volatile boolean mFinished; + + @Override + public void run() { + if (!initGL()) { + Log.e(LOG_TAG, "Failed to initialize OpenGL."); + return; + } + loadStarTexture(); + + mGL.glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + + mGL.glViewport(0, 0, mDisplayWidth, mDisplayHeight); + + // make adjustments for screen ratio + mGL.glMatrixMode(GL10.GL_PROJECTION); + mGL.glLoadIdentity(); + mGL.glFrustumf(-mDisplayWidth, mDisplayWidth, mDisplayHeight, -mDisplayHeight, NEAR_CLIPPING_PLANE, FAR_CLIPPING_PLANE); + + // Switch back to modelview + mGL.glMatrixMode(GL10.GL_MODELVIEW); + mGL.glLoadIdentity(); + + mGL.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST); + mGL.glDepthMask(true); + + + for (Firefly firefly : mFireflies) { + firefly.reset(); + } + + for (int i = 0; i < 3; i++) { + // Call eglSwapBuffers 3 times - this will allocate the necessary + // buffers, and make sure the animation looks smooth from the start. + mGL.glClear(GL10.GL_COLOR_BUFFER_BIT); + if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { + Log.e(LOG_TAG, "Could not swap buffers"); + mFinished = true; + } + } + + long startTime = System.currentTimeMillis(); + + while (!mFinished) { + long timeElapsedMs = System.currentTimeMillis() - startTime; + startTime = System.currentTimeMillis(); + + checkCurrent(); + + mGL.glClear(GL10.GL_COLOR_BUFFER_BIT); + mGL.glLoadIdentity(); + + mGL.glEnable(GL10.GL_TEXTURE_2D); + mGL.glEnable(GL10.GL_BLEND); + mGL.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE); + + for (Firefly firefly : mFireflies) { + firefly.updatePositionAndScale(timeElapsedMs); + firefly.draw(mGL); + } + + if (!mEgl.eglSwapBuffers(mEglDisplay, mEglSurface)) { + Log.e(LOG_TAG, "Could not swap buffers"); + mFinished = true; + } + + long elapsed = System.currentTimeMillis() - startTime; + try { + Thread.sleep(Math.max(30 - elapsed, 0)); + } catch (InterruptedException e) { + + } + } + finishGL(); + } + + public void finish() { + mFinished = true; + } + + void loadStarTexture() { + int[] textureIds = new int[1]; + mGL.glGenTextures(1, textureIds, 0); + mTextureId = textureIds[0]; + + InputStream in = null; + try { + // Remember that both texture dimensions must be a power of 2! + in = mContext.getAssets().open("star.png"); + + Bitmap bitmap = BitmapFactory.decodeStream(in); + mGL.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId); + + mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR); + mGL.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR); + + GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); + + bitmap.recycle(); + + } catch (IOException e) { + Log.e(LOG_TAG, "IOException opening assets."); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { } + } + } + } + + private void checkCurrent() { + if (!mEglContext.equals(mEgl.eglGetCurrentContext()) || + !mEglSurface.equals(mEgl.eglGetCurrentSurface(EGL10.EGL_DRAW))) { + if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + throw new RuntimeException("eglMakeCurrent failed " + + GLUtils.getEGLErrorString(mEgl.eglGetError())); + } + } + } + + boolean initGL() { + // Initialize openGL engine + mEgl = (EGL10) EGLContext.getEGL(); + + mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { + Log.e(LOG_TAG, "eglGetDisplay failed " + + GLUtils.getEGLErrorString(mEgl.eglGetError())); + return false; + } + + int[] version = new int[2]; + if (!mEgl.eglInitialize(mEglDisplay, version)) { + Log.e(LOG_TAG, "eglInitialize failed " + + GLUtils.getEGLErrorString(mEgl.eglGetError())); + return false; + } + + mEglConfig = chooseEglConfig(); + if (mEglConfig == null) { + Log.e(LOG_TAG, "eglConfig not initialized."); + return false; + } + + mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, null); + + mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, mSurface, null); + + if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { + int error = mEgl.eglGetError(); + Log.e(LOG_TAG,"createWindowSurface returned error " + Integer.toString(error)); + return false; + } + + if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + Log.e(LOG_TAG, "eglMakeCurrent failed " + + GLUtils.getEGLErrorString(mEgl.eglGetError())); + return false; + } + + mGL = (GL10) mEglContext.getGL(); + + return true; + } + + private void finishGL() { + if (mEgl == null || mEglDisplay == null) { + // Nothing to free + return; + } + // Unbind the current surface and context from the display + mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_CONTEXT); + + if (mEglSurface != null) { + mEgl.eglDestroySurface(mEglDisplay, mEglSurface); + } + + if (mEglContext != null) { + mEgl.eglDestroyContext(mEglDisplay, mEglContext); + } + } + + private EGLConfig chooseEglConfig() { + int[] configsCount = new int[1]; + EGLConfig[] configs = new EGLConfig[1]; + if (!mEgl.eglChooseConfig(mEglDisplay, sEglConfig, configs, 1, configsCount)) { + throw new IllegalArgumentException("eglChooseConfig failed " + + GLUtils.getEGLErrorString(mEgl.eglGetError())); + } else if (configsCount[0] > 0) { + return configs[0]; + } + return null; + } + } + + private class Firefly { + static final float TEXTURE_HEIGHT = 30f; // TODO use measurement of texture size + static final float SPEED = .5f; + + float mX; // between -mDisplayHeight and mDisplayHeight + float mY; // between -mDisplayWidth and mDisplayWidth + float mZ; // between 0.0 (near) and 1.0 (far) + float mZ0; + float mT; + float mScale; + float mAlpha; + + public Firefly() { + } + + void reset() { + mX = (float) (Math.random() * mDisplayWidth) * 4 - 2 * mDisplayWidth; + mY = (float) (Math.random() * mDisplayHeight) * 4 - 2 * mDisplayHeight; + mZ0 = mZ = (float) (Math.random()) * 2 - 1; + mT = 0f; + mScale = 1.5f; + mAlpha = 0f; + } + + public void updatePositionAndScale(long timeElapsedMs) { + mT += timeElapsedMs; + mZ = mZ0 + mT/1000f * SPEED; + mAlpha = 1f-mZ; + if(mZ > 1.0) reset(); + } + + public void draw(GL10 gl) { + gl.glLoadIdentity(); + + // Counter clockwise winding + gl.glFrontFace(GL10.GL_CCW); + + gl.glEnableClientState(GL10.GL_VERTEX_ARRAY); + gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY); + + gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mVertexBuffer); + gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mTextureBuffer); + + gl.glTranslatef(mX, mY, -NEAR_CLIPPING_PLANE-mZ*(FAR_CLIPPING_PLANE-NEAR_CLIPPING_PLANE)); + gl.glColor4f(1, 1, 1, mAlpha); + + // scale around center + gl.glTranslatef(TEXTURE_HEIGHT/2, TEXTURE_HEIGHT/2, 0); + gl.glScalef(mScale, mScale, 0); + gl.glTranslatef(-TEXTURE_HEIGHT/2, -TEXTURE_HEIGHT/2, 0); + + gl.glDrawElements(GL10.GL_TRIANGLES, mIndices.length, GL10.GL_UNSIGNED_SHORT, + mIndexBuffer); + + gl.glColor4f(1, 1, 1, 1); + gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY); + gl.glDisableClientState(GL10.GL_VERTEX_ARRAY); + } + } +} diff --git a/NfcSony/src/com/android/nfc/beam/MimeTypeUtil.java b/NfcSony/src/com/android/nfc/beam/MimeTypeUtil.java new file mode 100644 index 0000000..73d7fd6 --- /dev/null +++ b/NfcSony/src/com/android/nfc/beam/MimeTypeUtil.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2014 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.nfc.beam; + +import android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; +import android.util.Log; +import android.webkit.MimeTypeMap; + +public final class MimeTypeUtil { + + private static final String TAG = "MimeTypeUtil"; + + private MimeTypeUtil() {} + + public static String getMimeTypeForUri(Context context, Uri uri) { + if (uri.getScheme() == null) return null; + + if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) { + ContentResolver cr = context.getContentResolver(); + return cr.getType(uri); + } else if (uri.getScheme().equals(ContentResolver.SCHEME_FILE)) { + String extension = MimeTypeMap.getFileExtensionFromUrl(uri.getPath()).toLowerCase(); + if (extension != null) { + return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + } else { + return null; + } + } else { + Log.d(TAG, "Could not determine mime type for Uri " + uri); + return null; + } + } +} diff --git a/NfcSony/src/com/android/nfc/beam/SendUi.java b/NfcSony/src/com/android/nfc/beam/SendUi.java new file mode 100644 index 0000000..7eaea08 --- /dev/null +++ b/NfcSony/src/com/android/nfc/beam/SendUi.java @@ -0,0 +1,891 @@ +/* + * Copyright (C) 2011 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.nfc.beam; + +import com.android.nfc.R; +import com.android.nfc.beam.FireflyRenderer; + +import android.animation.Animator; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.TimeAnimator; +import android.app.ActivityManager; +import android.app.StatusBarManager; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.PixelFormat; +import android.graphics.SurfaceTexture; +import android.os.AsyncTask; +import android.os.Binder; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.ActionMode; +import android.view.Display; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import com.android.internal.policy.PhoneWindow; +import android.view.SearchEvent; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.TextureView; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.WindowManager.LayoutParams; +import android.view.accessibility.AccessibilityEvent; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +/** + * This class is responsible for handling the UI animation + * around Android Beam. The animation consists of the following + * animators: + * + * mPreAnimator: scales the screenshot down to INTERMEDIATE_SCALE + * mSlowSendAnimator: scales the screenshot down to 0.2f (used as a "send in progress" animation) + * mFastSendAnimator: quickly scales the screenshot down to 0.0f (used for send success) + * mFadeInAnimator: fades the current activity back in (used after mFastSendAnimator completes) + * mScaleUpAnimator: scales the screenshot back up to full screen (used for failure or receiving) + * mHintAnimator: Slowly turns up the alpha of the "Touch to Beam" hint + * + * Possible sequences are: + * + * mPreAnimator => mSlowSendAnimator => mFastSendAnimator => mFadeInAnimator (send success) + * mPreAnimator => mSlowSendAnimator => mScaleUpAnimator (send failure) + * mPreAnimator => mScaleUpAnimator (p2p link broken, or data received) + * + * Note that mFastSendAnimator and mFadeInAnimator are combined in a set, as they + * are an atomic animation that cannot be interrupted. + * + * All methods of this class must be called on the UI thread + */ +public class SendUi implements Animator.AnimatorListener, View.OnTouchListener, + TimeAnimator.TimeListener, TextureView.SurfaceTextureListener, android.view.Window.Callback { + static final String TAG = "SendUi"; + + static final float INTERMEDIATE_SCALE = 0.6f; + + static final float[] PRE_SCREENSHOT_SCALE = {1.0f, INTERMEDIATE_SCALE}; + static final int PRE_DURATION_MS = 350; + + static final float[] SEND_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 0.2f}; + static final int SLOW_SEND_DURATION_MS = 8000; // Stretch out sending over 8s + static final int FAST_SEND_DURATION_MS = 350; + + static final float[] SCALE_UP_SCREENSHOT_SCALE = {INTERMEDIATE_SCALE, 1.0f}; + static final int SCALE_UP_DURATION_MS = 300; + + static final int FADE_IN_DURATION_MS = 250; + static final int FADE_IN_START_DELAY_MS = 350; + + static final int SLIDE_OUT_DURATION_MS = 300; + + static final float[] BLACK_LAYER_ALPHA_DOWN_RANGE = {0.9f, 0.0f}; + static final float[] BLACK_LAYER_ALPHA_UP_RANGE = {0.0f, 0.9f}; + + static final float[] TEXT_HINT_ALPHA_RANGE = {0.0f, 1.0f}; + static final int TEXT_HINT_ALPHA_DURATION_MS = 500; + static final int TEXT_HINT_ALPHA_START_DELAY_MS = 300; + + public static final int FINISH_SCALE_UP = 0; + public static final int FINISH_SEND_SUCCESS = 1; + + static final int STATE_IDLE = 0; + static final int STATE_W4_SCREENSHOT = 1; + static final int STATE_W4_SCREENSHOT_PRESEND_REQUESTED = 2; + static final int STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED = 3; + static final int STATE_W4_SCREENSHOT_THEN_STOP = 4; + static final int STATE_W4_PRESEND = 5; + static final int STATE_W4_TOUCH = 6; + static final int STATE_W4_NFC_TAP = 7; + static final int STATE_SENDING = 8; + static final int STATE_COMPLETE = 9; + + // all members are only used on UI thread + final WindowManager mWindowManager; + final Context mContext; + final Display mDisplay; + final DisplayMetrics mDisplayMetrics; + final Matrix mDisplayMatrix; + final WindowManager.LayoutParams mWindowLayoutParams; + final LayoutInflater mLayoutInflater; + final StatusBarManager mStatusBarManager; + final View mScreenshotLayout; + final ImageView mScreenshotView; + final ImageView mBlackLayer; + final TextureView mTextureView; + final TextView mTextHint; + final TextView mTextRetry; + final Callback mCallback; + + // The mFrameCounter animation is purely used to count down a certain + // number of (vsync'd) frames. This is needed because the first 3 + // times the animation internally calls eglSwapBuffers(), large buffers + // are allocated by the graphics drivers. This causes the animation + // to look janky. So on platforms where we can use hardware acceleration, + // the animation order is: + // Wait for hw surface => start frame counter => start pre-animation after 3 frames + // For platforms where no hw acceleration can be used, the pre-animation + // is started immediately. + final TimeAnimator mFrameCounterAnimator; + + final ObjectAnimator mPreAnimator; + final ObjectAnimator mSlowSendAnimator; + final ObjectAnimator mFastSendAnimator; + final ObjectAnimator mFadeInAnimator; + final ObjectAnimator mHintAnimator; + final ObjectAnimator mScaleUpAnimator; + final ObjectAnimator mAlphaDownAnimator; + final ObjectAnimator mAlphaUpAnimator; + final AnimatorSet mSuccessAnimatorSet; + + // Besides animating the screenshot, the Beam UI also renders + // fireflies on platforms where we can do hardware-acceleration. + // Firefly rendering is only started once the initial + // "pre-animation" has scaled down the screenshot, to avoid + // that animation becoming janky. Likewise, the fireflies are + // stopped in their tracks as soon as we finish the animation, + // to make the finishing animation smooth. + final boolean mHardwareAccelerated; + final FireflyRenderer mFireflyRenderer; + + String mToastString; + Bitmap mScreenshotBitmap; + + int mState; + int mRenderedFrames; + + View mDecor; + + // Used for holding the surface + SurfaceTexture mSurface; + int mSurfaceWidth; + int mSurfaceHeight; + + public interface Callback { + public void onSendConfirmed(); + public void onCanceled(); + } + + public SendUi(Context context, Callback callback) { + mContext = context; + mCallback = callback; + + mDisplayMetrics = new DisplayMetrics(); + mDisplayMatrix = new Matrix(); + mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + mStatusBarManager = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE); + + mDisplay = mWindowManager.getDefaultDisplay(); + + mLayoutInflater = (LayoutInflater) + context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + mScreenshotLayout = mLayoutInflater.inflate(R.layout.screenshot, null); + + mScreenshotView = (ImageView) mScreenshotLayout.findViewById(R.id.screenshot); + mScreenshotLayout.setFocusable(true); + + mTextHint = (TextView) mScreenshotLayout.findViewById(R.id.calltoaction); + mTextRetry = (TextView) mScreenshotLayout.findViewById(R.id.retrytext); + mBlackLayer = (ImageView) mScreenshotLayout.findViewById(R.id.blacklayer); + mTextureView = (TextureView) mScreenshotLayout.findViewById(R.id.fireflies); + mTextureView.setSurfaceTextureListener(this); + + // We're only allowed to use hardware acceleration if + // isHighEndGfx() returns true - otherwise, we're too limited + // on resources to do it. + mHardwareAccelerated = ActivityManager.isHighEndGfx(); + int hwAccelerationFlags = mHardwareAccelerated ? + WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED : 0; + + mWindowLayoutParams = new WindowManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, 0, 0, + WindowManager.LayoutParams.TYPE_SYSTEM_ALERT, + WindowManager.LayoutParams.FLAG_FULLSCREEN + | hwAccelerationFlags + | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN, + PixelFormat.OPAQUE); + mWindowLayoutParams.privateFlags |= + WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; + mWindowLayoutParams.token = new Binder(); + + mFrameCounterAnimator = new TimeAnimator(); + mFrameCounterAnimator.setTimeListener(this); + + PropertyValuesHolder preX = PropertyValuesHolder.ofFloat("scaleX", PRE_SCREENSHOT_SCALE); + PropertyValuesHolder preY = PropertyValuesHolder.ofFloat("scaleY", PRE_SCREENSHOT_SCALE); + mPreAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, preX, preY); + mPreAnimator.setInterpolator(new DecelerateInterpolator()); + mPreAnimator.setDuration(PRE_DURATION_MS); + mPreAnimator.addListener(this); + + PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", SEND_SCREENSHOT_SCALE); + PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", SEND_SCREENSHOT_SCALE); + PropertyValuesHolder alphaDown = PropertyValuesHolder.ofFloat("alpha", + new float[]{1.0f, 0.0f}); + + mSlowSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX, postY); + mSlowSendAnimator.setInterpolator(new DecelerateInterpolator()); + mSlowSendAnimator.setDuration(SLOW_SEND_DURATION_MS); + + mFastSendAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, postX, + postY, alphaDown); + mFastSendAnimator.setInterpolator(new DecelerateInterpolator()); + mFastSendAnimator.setDuration(FAST_SEND_DURATION_MS); + mFastSendAnimator.addListener(this); + + PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX", SCALE_UP_SCREENSHOT_SCALE); + PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY", SCALE_UP_SCREENSHOT_SCALE); + + mScaleUpAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, scaleUpX, scaleUpY); + mScaleUpAnimator.setInterpolator(new DecelerateInterpolator()); + mScaleUpAnimator.setDuration(SCALE_UP_DURATION_MS); + mScaleUpAnimator.addListener(this); + + PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha", 1.0f); + mFadeInAnimator = ObjectAnimator.ofPropertyValuesHolder(mScreenshotView, fadeIn); + mFadeInAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); + mFadeInAnimator.setDuration(FADE_IN_DURATION_MS); + mFadeInAnimator.setStartDelay(FADE_IN_START_DELAY_MS); + mFadeInAnimator.addListener(this); + + PropertyValuesHolder alphaUp = PropertyValuesHolder.ofFloat("alpha", TEXT_HINT_ALPHA_RANGE); + mHintAnimator = ObjectAnimator.ofPropertyValuesHolder(mTextHint, alphaUp); + mHintAnimator.setInterpolator(null); + mHintAnimator.setDuration(TEXT_HINT_ALPHA_DURATION_MS); + mHintAnimator.setStartDelay(TEXT_HINT_ALPHA_START_DELAY_MS); + + alphaDown = PropertyValuesHolder.ofFloat("alpha", BLACK_LAYER_ALPHA_DOWN_RANGE); + mAlphaDownAnimator = ObjectAnimator.ofPropertyValuesHolder(mBlackLayer, alphaDown); + mAlphaDownAnimator.setInterpolator(new DecelerateInterpolator()); + mAlphaDownAnimator.setDuration(400); + + alphaUp = PropertyValuesHolder.ofFloat("alpha", BLACK_LAYER_ALPHA_UP_RANGE); + mAlphaUpAnimator = ObjectAnimator.ofPropertyValuesHolder(mBlackLayer, alphaUp); + mAlphaUpAnimator.setInterpolator(new DecelerateInterpolator()); + mAlphaUpAnimator.setDuration(200); + + mSuccessAnimatorSet = new AnimatorSet(); + mSuccessAnimatorSet.playSequentially(mFastSendAnimator, mFadeInAnimator); + + // Create a Window with a Decor view; creating a window allows us to get callbacks + // on key events (which require a decor view to be dispatched). + mContext.setTheme(android.R.style.Theme_Black_NoTitleBar_Fullscreen); + Window window = new PhoneWindow(mContext); + window.setCallback(this); + window.requestFeature(Window.FEATURE_NO_TITLE); + mDecor = window.getDecorView(); + window.setContentView(mScreenshotLayout, mWindowLayoutParams); + + if (mHardwareAccelerated) { + mFireflyRenderer = new FireflyRenderer(context); + } else { + mFireflyRenderer = null; + } + mState = STATE_IDLE; + } + + public void takeScreenshot() { + // There's no point in taking the screenshot if + // we're still finishing the previous animation. + if (mState >= STATE_W4_TOUCH) { + return; + } + mState = STATE_W4_SCREENSHOT; + new ScreenshotTask().execute(); + } + + /** Show pre-send animation */ + public void showPreSend(boolean promptToNfcTap) { + switch (mState) { + case STATE_IDLE: + Log.e(TAG, "Unexpected showPreSend() in STATE_IDLE"); + return; + case STATE_W4_SCREENSHOT: + // Still waiting for screenshot, store request in state + // and wait for screenshot completion. + if (promptToNfcTap) { + mState = STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED; + } else { + mState = STATE_W4_SCREENSHOT_PRESEND_REQUESTED; + } + return; + case STATE_W4_SCREENSHOT_PRESEND_REQUESTED: + case STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED: + Log.e(TAG, "Unexpected showPreSend() in STATE_W4_SCREENSHOT_PRESEND_REQUESTED"); + return; + case STATE_W4_PRESEND: + // Expected path, continue below + break; + default: + Log.e(TAG, "Unexpected showPreSend() in state " + Integer.toString(mState)); + return; + } + // Update display metrics + mDisplay.getRealMetrics(mDisplayMetrics); + + final int statusBarHeight = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + + mBlackLayer.setVisibility(View.GONE); + mBlackLayer.setAlpha(0f); + mScreenshotLayout.setOnTouchListener(this); + mScreenshotView.setImageBitmap(mScreenshotBitmap); + mScreenshotView.setTranslationX(0f); + mScreenshotView.setAlpha(1.0f); + mScreenshotView.setPadding(0, statusBarHeight, 0, 0); + + mScreenshotLayout.requestFocus(); + + if (promptToNfcTap) { + mTextHint.setText(mContext.getResources().getString(R.string.ask_nfc_tap)); + } else { + mTextHint.setText(mContext.getResources().getString(R.string.touch)); + } + mTextHint.setAlpha(0.0f); + mTextHint.setVisibility(View.VISIBLE); + mHintAnimator.start(); + + // Lock the orientation. + // The orientation from the configuration does not specify whether + // the orientation is reverse or not (ie landscape or reverse landscape). + // So we have to use SENSOR_LANDSCAPE or SENSOR_PORTRAIT to make sure + // we lock in portrait / landscape and have the sensor determine + // which way is up. + int orientation = mContext.getResources().getConfiguration().orientation; + + switch (orientation) { + case Configuration.ORIENTATION_LANDSCAPE: + mWindowLayoutParams.screenOrientation = + ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; + break; + case Configuration.ORIENTATION_PORTRAIT: + mWindowLayoutParams.screenOrientation = + ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; + break; + default: + mWindowLayoutParams.screenOrientation = + ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; + break; + } + + mWindowManager.addView(mDecor, mWindowLayoutParams); + // Disable statusbar pull-down + mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND); + + mToastString = null; + + if (!mHardwareAccelerated) { + mPreAnimator.start(); + } // else, we will start the animation once we get the hardware surface + mState = promptToNfcTap ? STATE_W4_NFC_TAP : STATE_W4_TOUCH; + } + + /** Show starting send animation */ + public void showStartSend() { + if (mState < STATE_SENDING) return; + + mTextRetry.setVisibility(View.GONE); + // Update the starting scale - touchscreen-mashers may trigger + // this before the pre-animation completes. + float currentScale = mScreenshotView.getScaleX(); + PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", + new float[] {currentScale, 0.0f}); + PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", + new float[] {currentScale, 0.0f}); + + mSlowSendAnimator.setValues(postX, postY); + + float currentAlpha = mBlackLayer.getAlpha(); + if (mBlackLayer.isShown() && currentAlpha > 0.0f) { + PropertyValuesHolder alphaDown = PropertyValuesHolder.ofFloat("alpha", + new float[] {currentAlpha, 0.0f}); + mAlphaDownAnimator.setValues(alphaDown); + mAlphaDownAnimator.start(); + } + mSlowSendAnimator.start(); + } + + public void finishAndToast(int finishMode, String toast) { + mToastString = toast; + + finish(finishMode); + } + + /** Return to initial state */ + public void finish(int finishMode) { + switch (mState) { + case STATE_IDLE: + return; + case STATE_W4_SCREENSHOT: + case STATE_W4_SCREENSHOT_PRESEND_REQUESTED: + case STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED: + // Screenshot is still being captured on a separate thread. + // Update state, and stop everything when the capture is done. + mState = STATE_W4_SCREENSHOT_THEN_STOP; + return; + case STATE_W4_SCREENSHOT_THEN_STOP: + Log.e(TAG, "Unexpected call to finish() in STATE_W4_SCREENSHOT_THEN_STOP"); + return; + case STATE_W4_PRESEND: + // We didn't build up any animation state yet, but + // did store the bitmap. Clear out the bitmap, reset + // state and bail. + mScreenshotBitmap = null; + mState = STATE_IDLE; + return; + default: + // We've started animations and attached a view; tear stuff down below. + break; + } + + // Stop rendering the fireflies + if (mFireflyRenderer != null) { + mFireflyRenderer.stop(); + } + + mTextHint.setVisibility(View.GONE); + mTextRetry.setVisibility(View.GONE); + + float currentScale = mScreenshotView.getScaleX(); + float currentAlpha = mScreenshotView.getAlpha(); + + if (finishMode == FINISH_SCALE_UP) { + mBlackLayer.setVisibility(View.GONE); + PropertyValuesHolder scaleUpX = PropertyValuesHolder.ofFloat("scaleX", + new float[] {currentScale, 1.0f}); + PropertyValuesHolder scaleUpY = PropertyValuesHolder.ofFloat("scaleY", + new float[] {currentScale, 1.0f}); + PropertyValuesHolder scaleUpAlpha = PropertyValuesHolder.ofFloat("alpha", + new float[] {currentAlpha, 1.0f}); + mScaleUpAnimator.setValues(scaleUpX, scaleUpY, scaleUpAlpha); + + mScaleUpAnimator.start(); + } else if (finishMode == FINISH_SEND_SUCCESS){ + // Modify the fast send parameters to match the current scale + PropertyValuesHolder postX = PropertyValuesHolder.ofFloat("scaleX", + new float[] {currentScale, 0.0f}); + PropertyValuesHolder postY = PropertyValuesHolder.ofFloat("scaleY", + new float[] {currentScale, 0.0f}); + PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", + new float[] {currentAlpha, 0.0f}); + mFastSendAnimator.setValues(postX, postY, alpha); + + // Reset the fadeIn parameters to start from alpha 1 + PropertyValuesHolder fadeIn = PropertyValuesHolder.ofFloat("alpha", + new float[] {0.0f, 1.0f}); + mFadeInAnimator.setValues(fadeIn); + + mSlowSendAnimator.cancel(); + mSuccessAnimatorSet.start(); + } + mState = STATE_COMPLETE; + } + + void dismiss() { + if (mState < STATE_W4_TOUCH) return; + // Immediately set to IDLE, to prevent .cancel() calls + // below from immediately calling into dismiss() again. + // (They can do so on the same thread). + mState = STATE_IDLE; + mSurface = null; + mFrameCounterAnimator.cancel(); + mPreAnimator.cancel(); + mSlowSendAnimator.cancel(); + mFastSendAnimator.cancel(); + mSuccessAnimatorSet.cancel(); + mScaleUpAnimator.cancel(); + mAlphaUpAnimator.cancel(); + mAlphaDownAnimator.cancel(); + mWindowManager.removeView(mDecor); + mStatusBarManager.disable(StatusBarManager.DISABLE_NONE); + mScreenshotBitmap = null; + if (mToastString != null) { + Toast.makeText(mContext, mToastString, Toast.LENGTH_LONG).show(); + } + mToastString = null; + } + + /** + * @return the current display rotation in degrees + */ + static float getDegreesForRotation(int value) { + switch (value) { + case Surface.ROTATION_90: + return 90f; + case Surface.ROTATION_180: + return 180f; + case Surface.ROTATION_270: + return 270f; + } + return 0f; + } + + final class ScreenshotTask extends AsyncTask { + @Override + protected Bitmap doInBackground(Void... params) { + return createScreenshot(); + } + + @Override + protected void onPostExecute(Bitmap result) { + if (mState == STATE_W4_SCREENSHOT) { + // Screenshot done, wait for request to start preSend anim + mScreenshotBitmap = result; + mState = STATE_W4_PRESEND; + } else if (mState == STATE_W4_SCREENSHOT_THEN_STOP) { + // We were asked to finish, move to idle state and exit + mState = STATE_IDLE; + } else if (mState == STATE_W4_SCREENSHOT_PRESEND_REQUESTED || + mState == STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED) { + if (result != null) { + mScreenshotBitmap = result; + boolean requestTap = (mState == STATE_W4_SCREENSHOT_PRESEND_NFC_TAP_REQUESTED); + mState = STATE_W4_PRESEND; + showPreSend(requestTap); + } else { + // Failed to take screenshot; reset state to idle + // and don't do anything + Log.e(TAG, "Failed to create screenshot"); + mState = STATE_IDLE; + } + } else { + Log.e(TAG, "Invalid state on screenshot completion: " + Integer.toString(mState)); + } + } + }; + + /** + * Returns a screenshot of the current display contents. + */ + Bitmap createScreenshot() { + // We need to orient the screenshot correctly (and the Surface api seems to + // take screenshots only in the natural orientation of the device :!) + mDisplay.getRealMetrics(mDisplayMetrics); + boolean hasNavBar = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_showNavigationBar); + + float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels}; + float degrees = getDegreesForRotation(mDisplay.getRotation()); + final int statusBarHeight = mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.status_bar_height); + + // Navbar has different sizes, depending on orientation + final int navBarHeight = hasNavBar ? mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_height) : 0; + final int navBarHeightLandscape = hasNavBar ? mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_height_landscape) : 0; + + final int navBarWidth = hasNavBar ? mContext.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.navigation_bar_width) : 0; + + boolean requiresRotation = (degrees > 0); + if (requiresRotation) { + // Get the dimensions of the device in its native orientation + mDisplayMatrix.reset(); + mDisplayMatrix.preRotate(-degrees); + mDisplayMatrix.mapPoints(dims); + dims[0] = Math.abs(dims[0]); + dims[1] = Math.abs(dims[1]); + } + + Bitmap bitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]); + // Bail if we couldn't take the screenshot + if (bitmap == null) { + return null; + } + + if (requiresRotation) { + // Rotate the screenshot to the current orientation + Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels, + mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(ss); + c.translate(ss.getWidth() / 2, ss.getHeight() / 2); + c.rotate(360f - degrees); + c.translate(-dims[0] / 2, -dims[1] / 2); + c.drawBitmap(bitmap, 0, 0, null); + + bitmap = ss; + } + + // TODO this is somewhat device-specific; need generic solution. + // Crop off the status bar and the nav bar + // Portrait: 0, statusBarHeight, width, height - status - nav + // Landscape: 0, statusBarHeight, width - navBar, height - status + int newLeft = 0; + int newTop = statusBarHeight; + int newWidth = bitmap.getWidth(); + int newHeight = bitmap.getHeight(); + float smallestWidth = (float)Math.min(newWidth, newHeight); + float smallestWidthDp = smallestWidth / (mDisplayMetrics.densityDpi / 160f); + if (bitmap.getWidth() < bitmap.getHeight()) { + // Portrait mode: status bar is at the top, navbar bottom, width unchanged + newHeight = bitmap.getHeight() - statusBarHeight - navBarHeight; + } else { + // Landscape mode: status bar is at the top + // Navbar: bottom on >599dp width devices, otherwise to the side + if (smallestWidthDp > 599) { + newHeight = bitmap.getHeight() - statusBarHeight - navBarHeightLandscape; + } else { + newHeight = bitmap.getHeight() - statusBarHeight; + newWidth = bitmap.getWidth() - navBarWidth; + } + } + bitmap = Bitmap.createBitmap(bitmap, newLeft, newTop, newWidth, newHeight); + + return bitmap; + } + + @Override + public void onAnimationStart(Animator animation) { } + + @Override + public void onAnimationEnd(Animator animation) { + if (animation == mScaleUpAnimator || animation == mSuccessAnimatorSet || + animation == mFadeInAnimator) { + // These all indicate the end of the animation + dismiss(); + } else if (animation == mFastSendAnimator) { + // After sending is done and we've faded out, reset the scale to 1 + // so we can fade it back in. + mScreenshotView.setScaleX(1.0f); + mScreenshotView.setScaleY(1.0f); + } else if (animation == mPreAnimator) { + if (mHardwareAccelerated && (mState == STATE_W4_TOUCH || mState == STATE_W4_NFC_TAP)) { + mFireflyRenderer.start(mSurface, mSurfaceWidth, mSurfaceHeight); + } + } + } + + @Override + public void onAnimationCancel(Animator animation) { } + + @Override + public void onAnimationRepeat(Animator animation) { } + + @Override + public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { + // This gets called on animation vsync + if (++mRenderedFrames < 4) { + // For the first 3 frames, call invalidate(); this calls eglSwapBuffers + // on the surface, which will allocate large buffers the first three calls + // as Android uses triple buffering. + mScreenshotLayout.invalidate(); + } else { + // Buffers should be allocated, start the real animation + mFrameCounterAnimator.cancel(); + mPreAnimator.start(); + } + } + + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mState != STATE_W4_TOUCH) { + return false; + } + mState = STATE_SENDING; + // Ignore future touches + mScreenshotView.setOnTouchListener(null); + + // Cancel any ongoing animations + mFrameCounterAnimator.cancel(); + mPreAnimator.cancel(); + + mCallback.onSendConfirmed(); + return true; + } + + @Override + public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { + if (mHardwareAccelerated && mState < STATE_COMPLETE) { + mRenderedFrames = 0; + + mFrameCounterAnimator.start(); + mSurface = surface; + mSurfaceWidth = width; + mSurfaceHeight = height; + } + } + + @Override + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { + // Since we've disabled orientation changes, we can safely ignore this + } + + @Override + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { + mSurface = null; + + return true; + } + + @Override + public void onSurfaceTextureUpdated(SurfaceTexture surface) { } + + public void showSendHint() { + if (mAlphaDownAnimator.isRunning()) { + mAlphaDownAnimator.cancel(); + } + if (mSlowSendAnimator.isRunning()) { + mSlowSendAnimator.cancel(); + } + mBlackLayer.setScaleX(mScreenshotView.getScaleX()); + mBlackLayer.setScaleY(mScreenshotView.getScaleY()); + mBlackLayer.setVisibility(View.VISIBLE); + mTextHint.setVisibility(View.GONE); + + mTextRetry.setText(mContext.getResources().getString(R.string.beam_try_again)); + mTextRetry.setVisibility(View.VISIBLE); + + PropertyValuesHolder alphaUp = PropertyValuesHolder.ofFloat("alpha", + new float[] {mBlackLayer.getAlpha(), 0.9f}); + mAlphaUpAnimator.setValues(alphaUp); + mAlphaUpAnimator.start(); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + int keyCode = event.getKeyCode(); + if (keyCode == KeyEvent.KEYCODE_BACK) { + mCallback.onCanceled(); + return true; + } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || + keyCode == KeyEvent.KEYCODE_VOLUME_UP) { + // Treat as if it's a touch event + return onTouch(mScreenshotView, null); + } else { + return false; + } + } + + @Override + public boolean dispatchKeyShortcutEvent(KeyEvent event) { + return false; + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + return mScreenshotLayout.dispatchTouchEvent(event); + } + + @Override + public boolean dispatchTrackballEvent(MotionEvent event) { + return false; + } + + @Override + public boolean dispatchGenericMotionEvent(MotionEvent event) { + return false; + } + + @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + return false; + } + + @Override + public View onCreatePanelView(int featureId) { + return null; + } + + @Override + public boolean onCreatePanelMenu(int featureId, Menu menu) { + return false; + } + + @Override + public boolean onPreparePanel(int featureId, View view, Menu menu) { + return false; + } + + @Override + public boolean onMenuOpened(int featureId, Menu menu) { + return false; + } + + @Override + public boolean onMenuItemSelected(int featureId, MenuItem item) { + return false; + } + + @Override + public void onWindowAttributesChanged(LayoutParams attrs) { + } + + @Override + public void onContentChanged() { + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + } + + @Override + public void onAttachedToWindow() { + + } + + @Override + public void onDetachedFromWindow() { + } + + @Override + public void onPanelClosed(int featureId, Menu menu) { + + } + + @Override + public boolean onSearchRequested(SearchEvent searchEvent) { + return onSearchRequested(); + } + + @Override + public boolean onSearchRequested() { + return false; + } + + @Override + public ActionMode onWindowStartingActionMode( + android.view.ActionMode.Callback callback) { + return null; + } + + public ActionMode onWindowStartingActionMode( + android.view.ActionMode.Callback callback, int type) { + return null; + } + + @Override + public void onActionModeStarted(ActionMode mode) { + } + + @Override + public void onActionModeFinished(ActionMode mode) { + } +} diff --git a/NfcSony/src/com/android/nfc/cardemulation/AidRoutingManager.java b/NfcSony/src/com/android/nfc/cardemulation/AidRoutingManager.java new file mode 100644 index 0000000..96225b7 --- /dev/null +++ b/NfcSony/src/com/android/nfc/cardemulation/AidRoutingManager.java @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2013 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.nfc.cardemulation; + +import android.util.Log; +import android.util.SparseArray; + +import com.android.nfc.NfcService; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class AidRoutingManager { + static final String TAG = "AidRoutingManager"; + + static final boolean DBG = false; + + static final int ROUTE_HOST = 0x00; + + // Every routing table entry is matched exact + static final int AID_MATCHING_EXACT_ONLY = 0x00; + // Every routing table entry can be matched either exact or prefix + static final int AID_MATCHING_EXACT_OR_PREFIX = 0x01; + // Every routing table entry is matched as a prefix + static final int AID_MATCHING_PREFIX_ONLY = 0x02; + + // This is the default IsoDep protocol route; it means + // that for any AID that needs to be routed to this + // destination, we won't need to add a rule to the routing + // table, because this destination is already the default route. + // + // For Nexus devices, the default route is always 0x00. + final int mDefaultRoute; + + // For Nexus devices, just a static route to the eSE + // OEMs/Carriers could manually map off-host AIDs + // to the correct eSE/UICC based on state they keep. + final int mDefaultOffHostRoute; + + // How the NFC controller can match AIDs in the routing table; + // see AID_MATCHING constants + final int mAidMatchingSupport; + + final Object mLock = new Object(); + + // mAidRoutingTable contains the current routing table. The index is the route ID. + // The route can include routes to a eSE/UICC. + SparseArray> mAidRoutingTable = + new SparseArray>(); + + // Easy look-up what the route is for a certain AID + HashMap mRouteForAid = new HashMap(); + + private native int doGetDefaultRouteDestination(); + private native int doGetDefaultOffHostRouteDestination(); + private native int doGetAidMatchingMode(); + + public AidRoutingManager() { + mDefaultRoute = doGetDefaultRouteDestination(); + if (DBG) Log.d(TAG, "mDefaultRoute=0x" + Integer.toHexString(mDefaultRoute)); + mDefaultOffHostRoute = doGetDefaultOffHostRouteDestination(); + if (DBG) Log.d(TAG, "mDefaultOffHostRoute=0x" + Integer.toHexString(mDefaultOffHostRoute)); + mAidMatchingSupport = doGetAidMatchingMode(); + if (DBG) Log.d(TAG, "mAidMatchingSupport=0x" + Integer.toHexString(mAidMatchingSupport)); + } + + public boolean supportsAidPrefixRouting() { + return mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX || + mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY; + } + + void clearNfcRoutingTableLocked() { + for (Map.Entry aidEntry : mRouteForAid.entrySet()) { + String aid = aidEntry.getKey(); + if (aid.endsWith("*")) { + if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) { + Log.e(TAG, "Device does not support prefix AIDs but AID [" + aid + + "] is registered"); + } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) { + if (DBG) Log.d(TAG, "Unrouting prefix AID " + aid); + // Cut off '*' since controller anyway treats all AIDs as a prefix + aid = aid.substring(0, aid.length() - 1); + } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) { + if (DBG) Log.d(TAG, "Unrouting prefix AID " + aid); + } + } else { + if (DBG) Log.d(TAG, "Unrouting exact AID " + aid); + } + + NfcService.getInstance().unrouteAids(aid); + } + } + + public boolean configureRouting(HashMap aidMap) { + SparseArray> aidRoutingTable = new SparseArray>(aidMap.size()); + HashMap routeForAid = new HashMap(aidMap.size()); + // Then, populate internal data structures first + for (Map.Entry aidEntry : aidMap.entrySet()) { + int route = aidEntry.getValue() ? ROUTE_HOST : mDefaultOffHostRoute; + String aid = aidEntry.getKey(); + Set entries = aidRoutingTable.get(route, new HashSet()); + entries.add(aid); + aidRoutingTable.put(route, entries); + routeForAid.put(aid, route); + } + + synchronized (mLock) { + if (routeForAid.equals(mRouteForAid)) { + if (DBG) Log.d(TAG, "Routing table unchanged, not updating"); + return false; + } + + // Otherwise, update internal structures and commit new routing + clearNfcRoutingTableLocked(); + mRouteForAid = routeForAid; + mAidRoutingTable = aidRoutingTable; + if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) { + /* If a non-default route registers an exact AID which is shorter + * than this exact AID, this will create a problem with controllers + * that treat every AID in the routing table as a prefix. + * For example, if App A registers F0000000041010 as an exact AID, + * and App B registers F000000004 as an exact AID, and App B is not + * the default route, the following would be added to the routing table: + * F000000004 -> non-default destination + * However, because in this mode, the controller treats every routing table + * entry as a prefix, it means F0000000041010 would suddenly go to the non-default + * destination too, whereas it should have gone to the default. + * + * The only way to prevent this is to add the longer AIDs of the + * default route at the top of the table, so they will be matched first. + */ + Set defaultRouteAids = mAidRoutingTable.get(mDefaultRoute); + if (defaultRouteAids != null) { + for (String defaultRouteAid : defaultRouteAids) { + // Check whether there are any shorted AIDs routed to non-default + // TODO this is O(N^2) run-time complexity... + for (Map.Entry aidEntry : mRouteForAid.entrySet()) { + String aid = aidEntry.getKey(); + int route = aidEntry.getValue(); + if (defaultRouteAid.startsWith(aid) && route != mDefaultRoute) { + if (DBG) + Log.d(TAG, "Adding AID " + defaultRouteAid + " for default " + + "route, because a conflicting shorter AID will be " + + "added to the routing table"); + NfcService.getInstance().routeAids(defaultRouteAid, mDefaultRoute); + } + } + } + } + } + + // Add AID entries for all non-default routes + for (int i = 0; i < mAidRoutingTable.size(); i++) { + int route = mAidRoutingTable.keyAt(i); + if (route != mDefaultRoute) { + Set aidsForRoute = mAidRoutingTable.get(route); + for (String aid : aidsForRoute) { + if (aid.endsWith("*")) { + if (mAidMatchingSupport == AID_MATCHING_EXACT_ONLY) { + Log.e(TAG, "This device does not support prefix AIDs."); + } else if (mAidMatchingSupport == AID_MATCHING_PREFIX_ONLY) { + if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route " + + Integer.toString(route)); + // Cut off '*' since controller anyway treats all AIDs as a prefix + NfcService.getInstance().routeAids(aid.substring(0, + aid.length() - 1), route); + } else if (mAidMatchingSupport == AID_MATCHING_EXACT_OR_PREFIX) { + if (DBG) Log.d(TAG, "Routing prefix AID " + aid + " to route " + + Integer.toString(route)); + NfcService.getInstance().routeAids(aid, route); + } + } else { + if (DBG) Log.d(TAG, "Routing exact AID " + aid + " to route " + + Integer.toString(route)); + NfcService.getInstance().routeAids(aid, route); + } + } + } + } + } + + // And finally commit the routing + NfcService.getInstance().commitRouting(); + + return true; + } + + /** + * This notifies that the AID routing table in the controller + * has been cleared (usually due to NFC being turned off). + */ + public void onNfccRoutingTableCleared() { + // The routing table in the controller was cleared + // To stay in sync, clear our own tables. + synchronized (mLock) { + mAidRoutingTable.clear(); + mRouteForAid.clear(); + } + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("Routing table:"); + pw.println(" Default route: " + ((mDefaultRoute == 0x00) ? "host" : "secure element")); + synchronized (mLock) { + for (int i = 0; i < mAidRoutingTable.size(); i++) { + Set aids = mAidRoutingTable.valueAt(i); + pw.println(" Routed to 0x" + Integer.toHexString(mAidRoutingTable.keyAt(i)) + ":"); + for (String aid : aids) { + pw.println(" \"" + aid + "\""); + } + } + } + } +} diff --git a/NfcSony/src/com/android/nfc/cardemulation/AppChooserActivity.java b/NfcSony/src/com/android/nfc/cardemulation/AppChooserActivity.java new file mode 100644 index 0000000..5ed96c0 --- /dev/null +++ b/NfcSony/src/com/android/nfc/cardemulation/AppChooserActivity.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2013 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.nfc.cardemulation; + +import com.android.internal.R; +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + +import java.util.ArrayList; +import java.util.List; + +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.graphics.drawable.Drawable; +import android.nfc.NfcAdapter; +import android.nfc.cardemulation.ApduServiceInfo; +import android.nfc.cardemulation.CardEmulation; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +public class AppChooserActivity extends AlertActivity + implements AdapterView.OnItemClickListener { + + static final String TAG = "AppChooserActivity"; + + public static final String EXTRA_APDU_SERVICES = "services"; + public static final String EXTRA_CATEGORY = "category"; + public static final String EXTRA_FAILED_COMPONENT = "failed_component"; + + private int mIconSize; + private ListView mListView; + private ListAdapter mListAdapter; + private CardEmulation mCardEmuManager; + private String mCategory; + + final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + finish(); + } + }; + + @Override + protected void onDestroy() { + super.onDestroy(); + unregisterReceiver(mReceiver); + } + + protected void onCreate(Bundle savedInstanceState, String category, + ArrayList options, ComponentName failedComponent) { + super.onCreate(savedInstanceState); + setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert); + + IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); + registerReceiver(mReceiver, filter); + + if ((options == null || options.size() == 0) && failedComponent == null) { + Log.e(TAG, "No components passed in."); + finish(); + return; + } + + mCategory = category; + boolean isPayment = CardEmulation.CATEGORY_PAYMENT.equals(mCategory); + + final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); + mCardEmuManager = CardEmulation.getInstance(adapter); + AlertController.AlertParams ap = mAlertParams; + + final ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); + mIconSize = am.getLauncherLargeIconSize(); + + // Three cases: + // 1. Failed component and no alternatives: just an OK box + // 2. Failed component and alternatives: pick alternative + // 3. No failed component and alternatives: pick alternative + PackageManager pm = getPackageManager(); + + CharSequence applicationLabel = "unknown"; + if (failedComponent != null) { + try { + ApplicationInfo info = pm.getApplicationInfo(failedComponent.getPackageName(), 0); + applicationLabel = info.loadLabel(pm); + } catch (NameNotFoundException e) { + } + + } + if (options.size() == 0 && failedComponent != null) { + String formatString = getString(com.android.nfc.R.string.transaction_failure); + ap.mTitle = ""; + ap.mMessage = String.format(formatString, applicationLabel); + ap.mPositiveButtonText = getString(R.string.ok); + setupAlert(); + } else { + mListAdapter = new ListAdapter(this, options); + if (failedComponent != null) { + String formatString = getString(com.android.nfc.R.string.could_not_use_app); + ap.mTitle = String.format(formatString, applicationLabel); + ap.mNegativeButtonText = getString(R.string.cancel); + } else { + if (CardEmulation.CATEGORY_PAYMENT.equals(category)) { + ap.mTitle = getString(com.android.nfc.R.string.pay_with); + } else { + ap.mTitle = getString(com.android.nfc.R.string.complete_with); + } + } + ap.mView = getLayoutInflater().inflate(com.android.nfc.R.layout.cardemu_resolver, null); + + mListView = (ListView) ap.mView.findViewById(com.android.nfc.R.id.resolver_list); + if (isPayment) { + mListView.setDivider(getResources().getDrawable(android.R.color.transparent)); + int height = (int) (getResources().getDisplayMetrics().density * 16); + mListView.setDividerHeight(height); + } else { + mListView.setPadding(0, 0, 0, 0); + } + mListView.setAdapter(mListAdapter); + mListView.setOnItemClickListener(this); + + setupAlert(); + } + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + Intent intent = getIntent(); + ArrayList services = intent.getParcelableArrayListExtra(EXTRA_APDU_SERVICES); + String category = intent.getStringExtra(EXTRA_CATEGORY); + ComponentName failedComponent = intent.getParcelableExtra(EXTRA_FAILED_COMPONENT); + onCreate(savedInstanceState, category, services, failedComponent); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + DisplayAppInfo info = (DisplayAppInfo) mListAdapter.getItem(position); + mCardEmuManager.setDefaultForNextTap(info.serviceInfo.getComponent()); + Intent dialogIntent = new Intent(this, TapAgainDialog.class); + dialogIntent.putExtra(TapAgainDialog.EXTRA_CATEGORY, mCategory); + dialogIntent.putExtra(TapAgainDialog.EXTRA_APDU_SERVICE, info.serviceInfo); + startActivity(dialogIntent); + finish(); + } + + final class DisplayAppInfo { + ApduServiceInfo serviceInfo; + CharSequence displayLabel; + Drawable displayIcon; + Drawable displayBanner; + + public DisplayAppInfo(ApduServiceInfo serviceInfo, CharSequence label, Drawable icon, + Drawable banner) { + this.serviceInfo = serviceInfo; + displayIcon = icon; + displayLabel = label; + displayBanner = banner; + } + } + + final class ListAdapter extends BaseAdapter { + private final LayoutInflater mInflater; + private final boolean mIsPayment; + private List mList; + + public ListAdapter(Context context, ArrayList services) { + mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + // For each component, get the corresponding app name and icon + PackageManager pm = getPackageManager(); + mList = new ArrayList(); + mIsPayment = CardEmulation.CATEGORY_PAYMENT.equals(mCategory); + for (ApduServiceInfo service : services) { + CharSequence label = service.getDescription(); + if (label == null) label = service.loadLabel(pm); + Drawable icon = service.loadIcon(pm); + Drawable banner = null; + if (mIsPayment) { + banner = service.loadBanner(pm); + if (banner == null) { + Log.e(TAG, "Not showing " + label + " because no banner specified."); + continue; + } + } + DisplayAppInfo info = new DisplayAppInfo(service, label, icon, banner); + mList.add(info); + } + } + + @Override + public int getCount() { + return mList.size(); + } + + @Override + public Object getItem(int position) { + return mList.get(position); + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + if (mIsPayment) { + view = mInflater.inflate( + com.android.nfc.R.layout.cardemu_payment_item, parent, false); + } else { + view = mInflater.inflate( + com.android.nfc.R.layout.cardemu_item, parent, false); + } + final ViewHolder holder = new ViewHolder(view); + view.setTag(holder); + + } else { + view = convertView; + } + + final ViewHolder holder = (ViewHolder) view.getTag(); + DisplayAppInfo appInfo = mList.get(position); + if (mIsPayment) { + holder.banner.setImageDrawable(appInfo.displayBanner); + } else { + ViewGroup.LayoutParams lp = holder.icon.getLayoutParams(); + lp.width = lp.height = mIconSize; + holder.icon.setImageDrawable(appInfo.displayIcon); + holder.text.setText(appInfo.displayLabel); + } + return view; + } + } + + static class ViewHolder { + public TextView text; + public ImageView icon; + public ImageView banner; + public ViewHolder(View view) { + text = (TextView) view.findViewById(com.android.nfc.R.id.applabel); + icon = (ImageView) view.findViewById(com.android.nfc.R.id.appicon); + banner = (ImageView) view.findViewById(com.android.nfc.R.id.banner); + } + } +} \ No newline at end of file diff --git a/NfcSony/src/com/android/nfc/cardemulation/CardEmulationManager.java b/NfcSony/src/com/android/nfc/cardemulation/CardEmulationManager.java new file mode 100644 index 0000000..f34522a --- /dev/null +++ b/NfcSony/src/com/android/nfc/cardemulation/CardEmulationManager.java @@ -0,0 +1,393 @@ +/* + * Copyright (C) 2014 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.nfc.cardemulation; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.List; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.nfc.INfcCardEmulation; +import android.nfc.cardemulation.AidGroup; +import android.nfc.cardemulation.ApduServiceInfo; +import android.nfc.cardemulation.CardEmulation; +import android.os.Binder; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.Settings; +import android.util.Log; + +import com.android.nfc.NfcPermissions; +import com.android.nfc.cardemulation.RegisteredServicesCache; + +/** + * CardEmulationManager is the central entity + * responsible for delegating to individual components + * implementing card emulation: + * - RegisteredServicesCache keeping track of HCE and SE services on the device + * - RegisteredAidCache keeping track of AIDs registered by those services and manages + * the routing table in the NFCC. + * - HostEmulationManager handles incoming APDUs for the host and forwards to HCE + * services as necessary. + */ +public class CardEmulationManager implements RegisteredServicesCache.Callback, + PreferredServices.Callback { + static final String TAG = "CardEmulationManager"; + static final boolean DBG = false; + + final RegisteredAidCache mAidCache; + final RegisteredServicesCache mServiceCache; + final HostEmulationManager mHostEmulationManager; + final PreferredServices mPreferredServices; + final Context mContext; + final CardEmulationInterface mCardEmulationInterface; + + public CardEmulationManager(Context context) { + mContext = context; + mCardEmulationInterface = new CardEmulationInterface(); + mAidCache = new RegisteredAidCache(context); + mHostEmulationManager = new HostEmulationManager(context, mAidCache); + mServiceCache = new RegisteredServicesCache(context, this); + mPreferredServices = new PreferredServices(context, mServiceCache, mAidCache, this); + + mServiceCache.initialize(); + } + + public INfcCardEmulation getNfcCardEmulationInterface() { + return mCardEmulationInterface; + } + + public void onHostCardEmulationActivated() { + mHostEmulationManager.onHostEmulationActivated(); + mPreferredServices.onHostEmulationActivated(); + } + + public void onHostCardEmulationData(byte[] data) { + mHostEmulationManager.onHostEmulationData(data); + } + + public void onHostCardEmulationDeactivated() { + mHostEmulationManager.onHostEmulationDeactivated(); + mPreferredServices.onHostEmulationDeactivated(); + } + + public void onOffHostAidSelected() { + mHostEmulationManager.onOffHostAidSelected(); + } + + public void onUserSwitched(int userId) { + mServiceCache.invalidateCache(userId); + mPreferredServices.onUserSwitched(userId); + } + + public void onNfcEnabled() { + mAidCache.onNfcEnabled(); + } + + public void onNfcDisabled() { + mAidCache.onNfcDisabled(); + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mServiceCache.dump(fd, pw, args); + mPreferredServices.dump(fd, pw, args); + mAidCache.dump(fd, pw, args); + mHostEmulationManager.dump(fd, pw, args); + } + + @Override + public void onServicesUpdated(int userId, List services) { + // Verify defaults are still sane + verifyDefaults(userId, services); + // Update the AID cache + mAidCache.onServicesUpdated(userId, services); + // Update the preferred services list + mPreferredServices.onServicesUpdated(); + } + + void verifyDefaults(int userId, List services) { + ComponentName defaultPaymentService = + getDefaultServiceForCategory(userId, CardEmulation.CATEGORY_PAYMENT, false); + if (DBG) Log.d(TAG, "Current default: " + defaultPaymentService); + if (defaultPaymentService != null) { + // Validate the default is still installed and handling payment + ApduServiceInfo serviceInfo = mServiceCache.getService(userId, defaultPaymentService); + if (serviceInfo == null || !serviceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) { + if (serviceInfo == null) { + Log.e(TAG, "Default payment service unexpectedly removed."); + } else if (!serviceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) { + if (DBG) Log.d(TAG, "Default payment service had payment category removed"); + } + int numPaymentServices = 0; + ComponentName lastFoundPaymentService = null; + for (ApduServiceInfo service : services) { + if (service.hasCategory(CardEmulation.CATEGORY_PAYMENT)) { + numPaymentServices++; + lastFoundPaymentService = service.getComponent(); + } + } + if (DBG) Log.d(TAG, "Number of payment services is " + + Integer.toString(numPaymentServices)); + if (numPaymentServices == 0) { + if (DBG) Log.d(TAG, "Default removed, no services left."); + // No payment services left, unset default and don't ask the user + setDefaultServiceForCategoryChecked(userId, null, CardEmulation.CATEGORY_PAYMENT); + } else if (numPaymentServices == 1) { + // Only one left, automatically make it the default + if (DBG) Log.d(TAG, "Default removed, making remaining service default."); + setDefaultServiceForCategoryChecked(userId, lastFoundPaymentService, + CardEmulation.CATEGORY_PAYMENT); + } else if (numPaymentServices > 1) { + // More than one left, unset default and ask the user if he wants + // to set a new one + if (DBG) Log.d(TAG, "Default removed, asking user to pick."); + setDefaultServiceForCategoryChecked(userId, null, + CardEmulation.CATEGORY_PAYMENT); + Intent intent = new Intent(mContext, DefaultRemovedActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + } + } else { + // Default still exists and handles the category, nothing do + if (DBG) Log.d(TAG, "Default payment service still ok."); + } + } else { + // A payment service may have been removed, leaving only one; + // in that case, automatically set that app as default. + int numPaymentServices = 0; + ComponentName lastFoundPaymentService = null; + for (ApduServiceInfo service : services) { + if (service.hasCategory(CardEmulation.CATEGORY_PAYMENT)) { + numPaymentServices++; + lastFoundPaymentService = service.getComponent(); + } + } + if (numPaymentServices > 1) { + // More than one service left, leave default unset + if (DBG) Log.d(TAG, "No default set, more than one service left."); + } else if (numPaymentServices == 1) { + // Make single found payment service the default + if (DBG) Log.d(TAG, "No default set, making single service default."); + setDefaultServiceForCategoryChecked(userId, lastFoundPaymentService, + CardEmulation.CATEGORY_PAYMENT); + } else { + // No payment services left, leave default at null + if (DBG) Log.d(TAG, "No default set, last payment service removed."); + } + } + } + + ComponentName getDefaultServiceForCategory(int userId, String category, + boolean validateInstalled) { + if (!CardEmulation.CATEGORY_PAYMENT.equals(category)) { + Log.e(TAG, "Not allowing defaults for category " + category); + return null; + } + // Load current payment default from settings + String name = Settings.Secure.getStringForUser( + mContext.getContentResolver(), Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT, + userId); + if (name != null) { + ComponentName service = ComponentName.unflattenFromString(name); + if (!validateInstalled || service == null) { + return service; + } else { + return mServiceCache.hasService(userId, service) ? service : null; + } + } else { + return null; + } + } + + boolean setDefaultServiceForCategoryChecked(int userId, ComponentName service, + String category) { + if (!CardEmulation.CATEGORY_PAYMENT.equals(category)) { + Log.e(TAG, "Not allowing defaults for category " + category); + return false; + } + // TODO Not really nice to be writing to Settings.Secure here... + // ideally we overlay our local changes over whatever is in + // Settings.Secure + if (service == null || mServiceCache.hasService(userId, service)) { + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT, + service != null ? service.flattenToString() : null, userId); + } else { + Log.e(TAG, "Could not find default service to make default: " + service); + } + return true; + } + + boolean isServiceRegistered(int userId, ComponentName service) { + boolean serviceFound = mServiceCache.hasService(userId, service); + if (!serviceFound) { + // If we don't know about this service yet, it may have just been enabled + // using PackageManager.setComponentEnabledSetting(). The PackageManager + // broadcasts are delayed by 10 seconds in that scenario, which causes + // calls to our APIs referencing that service to fail. + // Hence, update the cache in case we don't know about the service. + if (DBG) Log.d(TAG, "Didn't find passed in service, invalidating cache."); + mServiceCache.invalidateCache(userId); + } + return mServiceCache.hasService(userId, service); + } + + /** + * Returns whether a service in this package is preferred, + * either because it's the default payment app or it's running + * in the foreground. + */ + public boolean packageHasPreferredService(String packageName) { + return mPreferredServices.packageHasPreferredService(packageName); + } + + /** + * This class implements the application-facing APIs + * and are called from binder. All calls must be + * permission-checked. + * + */ + final class CardEmulationInterface extends INfcCardEmulation.Stub { + @Override + public boolean isDefaultServiceForCategory(int userId, ComponentName service, + String category) { + NfcPermissions.enforceUserPermissions(mContext); + NfcPermissions.validateUserId(userId); + if (!isServiceRegistered(userId, service)) { + return false; + } + ComponentName defaultService = + getDefaultServiceForCategory(userId, category, true); + return (defaultService != null && defaultService.equals(service)); + } + + @Override + public boolean isDefaultServiceForAid(int userId, + ComponentName service, String aid) throws RemoteException { + NfcPermissions.validateUserId(userId); + NfcPermissions.enforceUserPermissions(mContext); + if (!isServiceRegistered(userId, service)) { + return false; + } + return mAidCache.isDefaultServiceForAid(userId, service, aid); + } + + @Override + public boolean setDefaultServiceForCategory(int userId, + ComponentName service, String category) throws RemoteException { + NfcPermissions.validateUserId(userId); + NfcPermissions.enforceAdminPermissions(mContext); + if (!isServiceRegistered(userId, service)) { + return false; + } + return setDefaultServiceForCategoryChecked(userId, service, category); + } + + @Override + public boolean setDefaultForNextTap(int userId, ComponentName service) + throws RemoteException { + NfcPermissions.validateUserId(userId); + NfcPermissions.enforceAdminPermissions(mContext); + if (!isServiceRegistered(userId, service)) { + return false; + } + return mPreferredServices.setDefaultForNextTap(service); + } + + @Override + public boolean registerAidGroupForService(int userId, + ComponentName service, AidGroup aidGroup) throws RemoteException { + NfcPermissions.validateUserId(userId); + NfcPermissions.enforceUserPermissions(mContext); + if (!isServiceRegistered(userId, service)) { + return false; + } + return mServiceCache.registerAidGroupForService(userId, Binder.getCallingUid(), service, + aidGroup); + } + + @Override + public AidGroup getAidGroupForService(int userId, + ComponentName service, String category) throws RemoteException { + NfcPermissions.validateUserId(userId); + NfcPermissions.enforceUserPermissions(mContext); + if (!isServiceRegistered(userId, service)) { + return null; + } + return mServiceCache.getAidGroupForService(userId, Binder.getCallingUid(), service, + category); + } + + @Override + public boolean removeAidGroupForService(int userId, + ComponentName service, String category) throws RemoteException { + NfcPermissions.validateUserId(userId); + NfcPermissions.enforceUserPermissions(mContext); + if (!isServiceRegistered(userId, service)) { + return false; + } + return mServiceCache.removeAidGroupForService(userId, Binder.getCallingUid(), service, + category); + } + + @Override + public List getServices(int userId, String category) + throws RemoteException { + NfcPermissions.validateUserId(userId); + NfcPermissions.enforceAdminPermissions(mContext); + return mServiceCache.getServicesForCategory(userId, category); + } + + @Override + public boolean setPreferredService(ComponentName service) + throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + if (!isServiceRegistered(UserHandle.getCallingUserId(), service)) { + Log.e(TAG, "setPreferredService: unknown component."); + return false; + } + return mPreferredServices.registerPreferredForegroundService(service, + Binder.getCallingUid()); + } + + @Override + public boolean unsetPreferredService() throws RemoteException { + NfcPermissions.enforceUserPermissions(mContext); + return mPreferredServices.unregisteredPreferredForegroundService( + Binder.getCallingUid()); + + } + + @Override + public boolean supportsAidPrefixRegistration() throws RemoteException { + return mAidCache.supportsAidPrefixRegistration(); + } + } + + @Override + public void onPreferredPaymentServiceChanged(ComponentName service) { + mAidCache.onPreferredPaymentServiceChanged(service); + mHostEmulationManager.onPreferredPaymentServiceChanged(service); + } + + @Override + public void onPreferredForegroundServiceChanged(ComponentName service) { + mAidCache.onPreferredForegroundServiceChanged(service); + mHostEmulationManager.onPreferredForegroundServiceChanged(service); + }; +} diff --git a/NfcSony/src/com/android/nfc/cardemulation/DefaultRemovedActivity.java b/NfcSony/src/com/android/nfc/cardemulation/DefaultRemovedActivity.java new file mode 100644 index 0000000..d9fa7e9 --- /dev/null +++ b/NfcSony/src/com/android/nfc/cardemulation/DefaultRemovedActivity.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2014 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.nfc.cardemulation; + +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.provider.Settings; +import com.android.internal.R; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + +public class DefaultRemovedActivity extends AlertActivity implements + DialogInterface.OnClickListener { + @Override + protected void onCreate(Bundle savedInstanceState) { + setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert); + super.onCreate(savedInstanceState); + + AlertController.AlertParams ap = mAlertParams; + + ap.mMessage = getString(com.android.nfc.R.string.default_pay_app_removed); + ap.mNegativeButtonText = getString(R.string.no); + ap.mPositiveButtonText = getString(R.string.yes); + ap.mPositiveButtonListener = this; + setupAlert(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + // Launch into Settings + Intent intent = new Intent(Settings.ACTION_NFC_PAYMENT_SETTINGS); + startActivity(intent); + } +} \ No newline at end of file diff --git a/NfcSony/src/com/android/nfc/cardemulation/HostEmulationManager.java b/NfcSony/src/com/android/nfc/cardemulation/HostEmulationManager.java new file mode 100644 index 0000000..b481130 --- /dev/null +++ b/NfcSony/src/com/android/nfc/cardemulation/HostEmulationManager.java @@ -0,0 +1,526 @@ +/* + * Copyright (C) 2013 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.nfc.cardemulation; + +import android.app.ActivityManager; +import android.app.KeyguardManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.nfc.cardemulation.ApduServiceInfo; +import android.nfc.cardemulation.CardEmulation; +import android.nfc.cardemulation.HostApduService; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.os.UserHandle; +import android.util.Log; + +import com.android.nfc.NfcService; +import com.android.nfc.cardemulation.RegisteredAidCache.AidResolveInfo; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; + +public class HostEmulationManager { + static final String TAG = "HostEmulationManager"; + static final boolean DBG = false; + + static final int STATE_IDLE = 0; + static final int STATE_W4_SELECT = 1; + static final int STATE_W4_SERVICE = 2; + static final int STATE_W4_DEACTIVATE = 3; + static final int STATE_XFER = 4; + + /** Minimum AID lenth as per ISO7816 */ + static final int MINIMUM_AID_LENGTH = 5; + + /** Length of Select APDU header including length byte */ + static final int SELECT_APDU_HDR_LENGTH = 5; + + static final byte INSTR_SELECT = (byte)0xA4; + + static final String ANDROID_HCE_AID = "A000000476416E64726F6964484345"; + static final byte[] ANDROID_HCE_RESPONSE = {0x14, (byte)0x81, 0x00, 0x00, (byte)0x90, 0x00}; + + static final byte[] AID_NOT_FOUND = {0x6A, (byte)0x82}; + static final byte[] UNKNOWN_ERROR = {0x6F, 0x00}; + + final Context mContext; + final RegisteredAidCache mAidCache; + final Messenger mMessenger = new Messenger (new MessageHandler()); + final KeyguardManager mKeyguard; + final Object mLock; + + // All variables below protected by mLock + + // Variables below are for a non-payment service, + // that is typically only bound in the STATE_XFER state. + Messenger mService; + boolean mServiceBound; + ComponentName mServiceName; + + // Variables below are for a payment service, + // which is typically bound persistently to improve on + // latency. + Messenger mPaymentService; + boolean mPaymentServiceBound; + ComponentName mPaymentServiceName; + + // mActiveService denotes the service interface + // that is the current active one, until a new SELECT AID + // comes in that may be resolved to a different service. + // On deactivation, mActiveService stops being valid. + Messenger mActiveService; + ComponentName mActiveServiceName; + + String mLastSelectedAid; + int mState; + byte[] mSelectApdu; + + public HostEmulationManager(Context context, RegisteredAidCache aidCache) { + mContext = context; + mLock = new Object(); + mAidCache = aidCache; + mState = STATE_IDLE; + mKeyguard = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + } + + public void onPreferredPaymentServiceChanged(ComponentName service) { + synchronized (mLock) { + if (service != null) { + bindPaymentServiceLocked(ActivityManager.getCurrentUser(), service); + } else { + unbindPaymentServiceLocked(); + } + } + } + + public void onPreferredForegroundServiceChanged(ComponentName service) { + synchronized (mLock) { + if (service != null) { + bindServiceIfNeededLocked(service); + } else { + unbindServiceIfNeededLocked(); + } + } + } + + public void onHostEmulationActivated() { + Log.d(TAG, "notifyHostEmulationActivated"); + synchronized (mLock) { + // Regardless of what happens, if we're having a tap again + // activity up, close it + Intent intent = new Intent(TapAgainDialog.ACTION_CLOSE); + intent.setPackage("com.android.nfc"); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + if (mState != STATE_IDLE) { + Log.e(TAG, "Got activation event in non-idle state"); + } + mState = STATE_W4_SELECT; + } + } + + public void onHostEmulationData(byte[] data) { + Log.d(TAG, "notifyHostEmulationData"); + String selectAid = findSelectAid(data); + ComponentName resolvedService = null; + synchronized (mLock) { + if (mState == STATE_IDLE) { + Log.e(TAG, "Got data in idle state."); + return; + } else if (mState == STATE_W4_DEACTIVATE) { + Log.e(TAG, "Dropping APDU in STATE_W4_DECTIVATE"); + return; + } + if (selectAid != null) { + if (selectAid.equals(ANDROID_HCE_AID)) { + NfcService.getInstance().sendData(ANDROID_HCE_RESPONSE); + return; + } + AidResolveInfo resolveInfo = mAidCache.resolveAid(selectAid); + if (resolveInfo == null || resolveInfo.services.size() == 0) { + // Tell the remote we don't handle this AID + NfcService.getInstance().sendData(AID_NOT_FOUND); + return; + } + mLastSelectedAid = selectAid; + if (resolveInfo.defaultService != null) { + // Resolve to default + // Check if resolvedService requires unlock + ApduServiceInfo defaultServiceInfo = resolveInfo.defaultService; + if (defaultServiceInfo.requiresUnlock() && + mKeyguard.isKeyguardLocked() && mKeyguard.isKeyguardSecure()) { + // Just ignore all future APDUs until next tap + mState = STATE_W4_DEACTIVATE; + launchTapAgain(resolveInfo.defaultService, resolveInfo.category); + return; + } + // In no circumstance should this be an OffHostService - + // we should never get this AID on the host in the first place + if (!defaultServiceInfo.isOnHost()) { + Log.e(TAG, "AID that was meant to go off-host was routed to host." + + " Check routing table configuration."); + NfcService.getInstance().sendData(AID_NOT_FOUND); + return; + } + resolvedService = defaultServiceInfo.getComponent(); + } else if (mActiveServiceName != null) { + for (ApduServiceInfo serviceInfo : resolveInfo.services) { + if (mActiveServiceName.equals(serviceInfo.getComponent())) { + resolvedService = mActiveServiceName; + break; + } + } + } + if (resolvedService == null) { + // We have no default, and either one or more services. + // Ask the user to confirm. + // Just ignore all future APDUs until we resolve to only one + mState = STATE_W4_DEACTIVATE; + launchResolver((ArrayList)resolveInfo.services, null, + resolveInfo.category); + return; + } + } + switch (mState) { + case STATE_W4_SELECT: + if (selectAid != null) { + Messenger existingService = bindServiceIfNeededLocked(resolvedService); + if (existingService != null) { + Log.d(TAG, "Binding to existing service"); + mState = STATE_XFER; + sendDataToServiceLocked(existingService, data); + } else { + // Waiting for service to be bound + Log.d(TAG, "Waiting for new service."); + // Queue SELECT APDU to be used + mSelectApdu = data; + mState = STATE_W4_SERVICE; + } + } else { + Log.d(TAG, "Dropping non-select APDU in STATE_W4_SELECT"); + NfcService.getInstance().sendData(UNKNOWN_ERROR); + } + break; + case STATE_W4_SERVICE: + Log.d(TAG, "Unexpected APDU in STATE_W4_SERVICE"); + break; + case STATE_XFER: + if (selectAid != null) { + Messenger existingService = bindServiceIfNeededLocked(resolvedService); + if (existingService != null) { + sendDataToServiceLocked(existingService, data); + mState = STATE_XFER; + } else { + // Waiting for service to be bound + mSelectApdu = data; + mState = STATE_W4_SERVICE; + } + } else if (mActiveService != null) { + // Regular APDU data + sendDataToServiceLocked(mActiveService, data); + } else { + // No SELECT AID and no active service. + Log.d(TAG, "Service no longer bound, dropping APDU"); + } + break; + } + } + } + + public void onHostEmulationDeactivated() { + Log.d(TAG, "notifyHostEmulationDeactivated"); + synchronized (mLock) { + if (mState == STATE_IDLE) { + Log.e(TAG, "Got deactivation event while in idle state"); + } + sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_LINK_LOSS); + mActiveService = null; + mActiveServiceName = null; + unbindServiceIfNeededLocked(); + mState = STATE_IDLE; + } + } + + public void onOffHostAidSelected() { + Log.d(TAG, "notifyOffHostAidSelected"); + synchronized (mLock) { + if (mState != STATE_XFER || mActiveService == null) { + // Don't bother telling, we're not bound to any service yet + } else { + sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_DESELECTED); + } + mActiveService = null; + mActiveServiceName = null; + unbindServiceIfNeededLocked(); + mState = STATE_W4_SELECT; + + //close the TapAgainDialog + Intent intent = new Intent(TapAgainDialog.ACTION_CLOSE); + intent.setPackage("com.android.nfc"); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } + } + + Messenger bindServiceIfNeededLocked(ComponentName service) { + if (mPaymentServiceBound && mPaymentServiceName.equals(service)) { + Log.d(TAG, "Service already bound as payment service."); + return mPaymentService; + } else if (mServiceBound && mServiceName.equals(service)) { + Log.d(TAG, "Service already bound as regular service."); + return mService; + } else { + Log.d(TAG, "Binding to service " + service); + unbindServiceIfNeededLocked(); + Intent aidIntent = new Intent(HostApduService.SERVICE_INTERFACE); + aidIntent.setComponent(service); + if (mContext.bindServiceAsUser(aidIntent, mConnection, + Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) { + } else { + Log.e(TAG, "Could not bind service."); + } + return null; + } + } + + void sendDataToServiceLocked(Messenger service, byte[] data) { + if (service != mActiveService) { + sendDeactivateToActiveServiceLocked(HostApduService.DEACTIVATION_DESELECTED); + mActiveService = service; + if (service.equals(mPaymentService)) { + mActiveServiceName = mPaymentServiceName; + } else { + mActiveServiceName = mServiceName; + } + } + Message msg = Message.obtain(null, HostApduService.MSG_COMMAND_APDU); + Bundle dataBundle = new Bundle(); + dataBundle.putByteArray("data", data); + msg.setData(dataBundle); + msg.replyTo = mMessenger; + try { + mActiveService.send(msg); + } catch (RemoteException e) { + Log.e(TAG, "Remote service has died, dropping APDU"); + } + } + + void sendDeactivateToActiveServiceLocked(int reason) { + if (mActiveService == null) return; + Message msg = Message.obtain(null, HostApduService.MSG_DEACTIVATED); + msg.arg1 = reason; + try { + mActiveService.send(msg); + } catch (RemoteException e) { + // Don't care + } + } + + void unbindPaymentServiceLocked() { + if (mPaymentServiceBound) { + mContext.unbindService(mPaymentConnection); + mPaymentServiceBound = false; + mPaymentService = null; + mPaymentServiceName = null; + } + } + + void bindPaymentServiceLocked(int userId, ComponentName service) { + unbindPaymentServiceLocked(); + + Intent intent = new Intent(HostApduService.SERVICE_INTERFACE); + intent.setComponent(service); + if (!mContext.bindServiceAsUser(intent, mPaymentConnection, + Context.BIND_AUTO_CREATE, new UserHandle(userId))) { + Log.e(TAG, "Could not bind (persistent) payment service."); + } + } + + void unbindServiceIfNeededLocked() { + if (mServiceBound) { + Log.d(TAG, "Unbinding from service " + mServiceName); + mContext.unbindService(mConnection); + mServiceBound = false; + mService = null; + mServiceName = null; + } + } + + void launchTapAgain(ApduServiceInfo service, String category) { + Intent dialogIntent = new Intent(mContext, TapAgainDialog.class); + dialogIntent.putExtra(TapAgainDialog.EXTRA_CATEGORY, category); + dialogIntent.putExtra(TapAgainDialog.EXTRA_APDU_SERVICE, service); + dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + mContext.startActivityAsUser(dialogIntent, UserHandle.CURRENT); + } + + void launchResolver(ArrayList services, ComponentName failedComponent, + String category) { + Intent intent = new Intent(mContext, AppChooserActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putParcelableArrayListExtra(AppChooserActivity.EXTRA_APDU_SERVICES, services); + intent.putExtra(AppChooserActivity.EXTRA_CATEGORY, category); + if (failedComponent != null) { + intent.putExtra(AppChooserActivity.EXTRA_FAILED_COMPONENT, failedComponent); + } + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + } + + String findSelectAid(byte[] data) { + if (data == null || data.length < SELECT_APDU_HDR_LENGTH + MINIMUM_AID_LENGTH) { + if (DBG) Log.d(TAG, "Data size too small for SELECT APDU"); + return null; + } + // To accept a SELECT AID for dispatch, we require the following: + // Class byte must be 0x00: logical channel set to zero, no secure messaging, no chaining + // Instruction byte must be 0xA4: SELECT instruction + // P1: must be 0x04: select by application identifier + // P2: File control information is only relevant for higher-level application, + // and we only support "first or only occurrence". + if (data[0] == 0x00 && data[1] == INSTR_SELECT && data[2] == 0x04) { + if (data[3] != 0x00) { + Log.d(TAG, "Selecting next, last or previous AID occurrence is not supported"); + } + int aidLength = data[4]; + if (data.length < SELECT_APDU_HDR_LENGTH + aidLength) { + return null; + } + return bytesToString(data, SELECT_APDU_HDR_LENGTH, aidLength); + } + return null; + } + + private ServiceConnection mPaymentConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mLock) { + mPaymentServiceName = name; + mPaymentService = new Messenger(service); + mPaymentServiceBound = true; + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (mLock) { + mPaymentService = null; + mPaymentServiceBound = false; + mPaymentServiceName = null; + } + } + }; + + private ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mLock) { + mService = new Messenger(service); + mServiceBound = true; + mServiceName = name; + Log.d(TAG, "Service bound"); + mState = STATE_XFER; + // Send pending select APDU + if (mSelectApdu != null) { + sendDataToServiceLocked(mService, mSelectApdu); + mSelectApdu = null; + } + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (mLock) { + Log.d(TAG, "Service unbound"); + mService = null; + mServiceBound = false; + } + } + }; + + class MessageHandler extends Handler { + @Override + public void handleMessage(Message msg) { + synchronized(mLock) { + if (mActiveService == null) { + Log.d(TAG, "Dropping service response message; service no longer active."); + return; + } else if (!msg.replyTo.getBinder().equals(mActiveService.getBinder())) { + Log.d(TAG, "Dropping service response message; service no longer bound."); + return; + } + } + if (msg.what == HostApduService.MSG_RESPONSE_APDU) { + Bundle dataBundle = msg.getData(); + if (dataBundle == null) { + return; + } + byte[] data = dataBundle.getByteArray("data"); + if (data == null || data.length == 0) { + Log.e(TAG, "Dropping empty R-APDU"); + return; + } + int state; + synchronized(mLock) { + state = mState; + } + if (state == STATE_XFER) { + Log.d(TAG, "Sending data"); + NfcService.getInstance().sendData(data); + } else { + Log.d(TAG, "Dropping data, wrong state " + Integer.toString(state)); + } + } else if (msg.what == HostApduService.MSG_UNHANDLED) { + synchronized (mLock) { + AidResolveInfo resolveInfo = mAidCache.resolveAid(mLastSelectedAid); + boolean isPayment = false; + if (resolveInfo.services.size() > 0) { + launchResolver((ArrayList)resolveInfo.services, + mActiveServiceName, resolveInfo.category); + } + } + } + } + } + + static String bytesToString(byte[] bytes, int offset, int length) { + final char[] hexChars = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; + char[] chars = new char[length * 2]; + int byteValue; + for (int j = 0; j < length; j++) { + byteValue = bytes[offset + j] & 0xFF; + chars[j * 2] = hexChars[byteValue >>> 4]; + chars[j * 2 + 1] = hexChars[byteValue & 0x0F]; + } + return new String(chars); + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("Bound services: "); + if (mPaymentServiceBound) { + pw.println(" payment: " + mPaymentServiceName); + } + if (mServiceBound) { + pw.println(" other: " + mServiceName); + } + } +} diff --git a/NfcSony/src/com/android/nfc/cardemulation/PreferredServices.java b/NfcSony/src/com/android/nfc/cardemulation/PreferredServices.java new file mode 100644 index 0000000..9118aa5 --- /dev/null +++ b/NfcSony/src/com/android/nfc/cardemulation/PreferredServices.java @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2014 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.nfc.cardemulation; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +import com.android.nfc.ForegroundUtils; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.nfc.cardemulation.ApduServiceInfo; +import android.nfc.cardemulation.CardEmulation; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; +import android.provider.Settings.SettingNotFoundException; +import android.util.Log; + +/** + * This class keeps track of what HCE/SE-based services are + * preferred by the user. It currently has 3 inputs: + * 1) The default set in tap&pay menu for payment category + * 2) An app in the foreground asking for a specific + * service for a specific category + * 3) If we had to disambiguate a previous tap (because no + * preferred service was there), we need to temporarily + * store the user's choice for the next tap. + * + * This class keeps track of all 3 inputs, and computes a new + * preferred services as needed. It then passes this service + * (if it changed) through a callback, which allows other components + * to adapt as necessary (ie the AID cache can update its AID + * mappings and the routing table). + */ +public class PreferredServices implements com.android.nfc.ForegroundUtils.Callback { + static final String TAG = "PreferredCardEmulationServices"; + static final Uri paymentDefaultUri = Settings.Secure.getUriFor( + Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT); + static final Uri paymentForegroundUri = Settings.Secure.getUriFor( + Settings.Secure.NFC_PAYMENT_FOREGROUND); + + final SettingsObserver mSettingsObserver; + final Context mContext; + final RegisteredServicesCache mServiceCache; + final RegisteredAidCache mAidCache; + final Callback mCallback; + final ForegroundUtils mForegroundUtils = ForegroundUtils.getInstance(); + final Handler mHandler = new Handler(Looper.getMainLooper()); + + final class PaymentDefaults { + boolean preferForeground; // The current selection mode for this category + ComponentName settingsDefault; // The component preferred in settings (eg Tap&Pay) + ComponentName currentPreferred; // The computed preferred component + } + + final Object mLock = new Object(); + // Variables below synchronized on mLock + PaymentDefaults mPaymentDefaults = new PaymentDefaults(); + + ComponentName mForegroundRequested; // The component preferred by fg app + int mForegroundUid; // The UID of the fg app, or -1 if fg app didn't request + + ComponentName mNextTapDefault; // The component preferred by active disambig dialog + boolean mClearNextTapDefault = false; // Set when the next tap default must be cleared + + ComponentName mForegroundCurrent; // The currently computed foreground component + + public interface Callback { + void onPreferredPaymentServiceChanged(ComponentName service); + void onPreferredForegroundServiceChanged(ComponentName service); + } + + public PreferredServices(Context context, RegisteredServicesCache serviceCache, + RegisteredAidCache aidCache, Callback callback) { + mContext = context; + mServiceCache = serviceCache; + mAidCache = aidCache; + mCallback = callback; + mSettingsObserver = new SettingsObserver(mHandler); + mContext.getContentResolver().registerContentObserver( + paymentDefaultUri, + true, mSettingsObserver, UserHandle.USER_ALL); + + mContext.getContentResolver().registerContentObserver( + paymentForegroundUri, + true, mSettingsObserver, UserHandle.USER_ALL); + + // Load current settings defaults for payments + loadDefaultsFromSettings(ActivityManager.getCurrentUser()); + } + + private final class SettingsObserver extends ContentObserver { + public SettingsObserver(Handler handler) { + super(handler); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + // Do it just for the current user. If it was in fact + // a change made for another user, we'll sync it down + // on user switch. + int currentUser = ActivityManager.getCurrentUser(); + loadDefaultsFromSettings(currentUser); + } + }; + + void loadDefaultsFromSettings(int userId) { + boolean paymentDefaultChanged = false; + boolean paymentPreferForegroundChanged = false; + // Load current payment default from settings + String name = Settings.Secure.getStringForUser( + mContext.getContentResolver(), Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT, + userId); + ComponentName newDefault = name != null ? ComponentName.unflattenFromString(name) : null; + boolean preferForeground = false; + try { + preferForeground = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.NFC_PAYMENT_FOREGROUND) != 0; + } catch (SettingNotFoundException e) { + } + synchronized (mLock) { + paymentPreferForegroundChanged = (preferForeground != mPaymentDefaults.preferForeground); + mPaymentDefaults.preferForeground = preferForeground; + + mPaymentDefaults.settingsDefault = newDefault; + if (newDefault != null && !newDefault.equals(mPaymentDefaults.currentPreferred)) { + paymentDefaultChanged = true; + mPaymentDefaults.currentPreferred = newDefault; + } else if (newDefault == null && mPaymentDefaults.currentPreferred != null) { + paymentDefaultChanged = true; + mPaymentDefaults.currentPreferred = newDefault; + } else { + // Same default as before + } + } + // Notify if anything changed + if (paymentDefaultChanged) { + mCallback.onPreferredPaymentServiceChanged(newDefault); + } + if (paymentPreferForegroundChanged) { + computePreferredForegroundService(); + } + } + + void computePreferredForegroundService() { + ComponentName preferredService = null; + boolean changed = false; + synchronized (mLock) { + // Prio 1: next tap default + preferredService = mNextTapDefault; + if (preferredService == null) { + // Prio 2: foreground requested by app + preferredService = mForegroundRequested; + } + if (preferredService != null && !preferredService.equals(mForegroundCurrent)) { + mForegroundCurrent = preferredService; + changed = true; + } else if (preferredService == null && mForegroundCurrent != null){ + mForegroundCurrent = preferredService; + changed = true; + } + } + // Notify if anything changed + if (changed) { + mCallback.onPreferredForegroundServiceChanged(preferredService); + } + } + + public boolean setDefaultForNextTap(ComponentName service) { + // This is a trusted API, so update without checking + synchronized (mLock) { + mNextTapDefault = service; + } + computePreferredForegroundService(); + return true; + } + + public void onServicesUpdated() { + // If this service is the current foreground service, verify + // there are no conflicts + boolean changed = false; + synchronized (mLock) { + // Check if the current foreground service is still allowed to override; + // it could have registered new AIDs that make it conflict with user + // preferences. + if (mForegroundCurrent != null) { + if (!isForegroundAllowedLocked(mForegroundCurrent)) { + Log.d(TAG, "Removing foreground preferred service because of conflict."); + mForegroundRequested = null; + mForegroundUid = -1; + changed = true; + } + } else { + // Don't care about this service + } + } + if (changed) { + computePreferredForegroundService(); + } + } + + // Verifies whether a service is allowed to register as preferred + boolean isForegroundAllowedLocked(ComponentName service) { + if (service.equals(mPaymentDefaults.currentPreferred)) { + // If the requester is already the payment default, allow it to request foreground + // override as well (it could use this to make sure it handles AIDs of category OTHER) + return true; + } + ApduServiceInfo serviceInfo = mServiceCache.getService(ActivityManager.getCurrentUser(), + service); + // Do some sanity checking + if (!mPaymentDefaults.preferForeground) { + // Foreground apps are not allowed to override payment default + // Check if this app registers payment AIDs, in which case we'll fail anyway + if (serviceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) { + Log.d(TAG, "User doesn't allow payment services to be overridden."); + return false; + } + // If no payment AIDs, get AIDs of category other, and see if there's any + // conflict with payment AIDs of current default payment app. That means + // the current default payment app said this was a payment AID, and the + // foreground app says it was not. In this case we'll still prefer the payment + // app, since that is the one that the user has explicitly selected (and said + // it's not allowed to be overridden). + final List otherAids = serviceInfo.getAids(); + ApduServiceInfo paymentServiceInfo = mServiceCache.getService( + ActivityManager.getCurrentUser(), mPaymentDefaults.currentPreferred); + if (paymentServiceInfo != null && otherAids != null && otherAids.size() > 0) { + for (String aid : otherAids) { + RegisteredAidCache.AidResolveInfo resolveInfo = mAidCache.resolveAid(aid); + if (CardEmulation.CATEGORY_PAYMENT.equals(resolveInfo.category) && + paymentServiceInfo.equals(resolveInfo.defaultService)) { + Log.d(TAG, "AID " + aid + " is handled by the default payment app, " + + "and the user has not allowed payments to be overridden."); + return false; + } + } + return true; + } else { + // Could not find payment service or fg app doesn't register other AIDs; + // okay to proceed. + return true; + } + } else { + // Payment allows override, so allow anything. + return true; + } + } + + public boolean registerPreferredForegroundService(ComponentName service, int callingUid) { + boolean success = false; + synchronized (mLock) { + if (isForegroundAllowedLocked(service)) { + if (mForegroundUtils.registerUidToBackgroundCallback(this, callingUid)) { + mForegroundRequested = service; + mForegroundUid = callingUid; + success = true; + } else { + Log.e(TAG, "Calling UID is not in the foreground, ignorning!"); + success = false; + } + } else { + Log.e(TAG, "Requested foreground service conflicts with default payment app."); + } + } + if (success) { + computePreferredForegroundService(); + } + return success; + } + + boolean unregisterForegroundService(int uid) { + boolean success = false; + synchronized (mLock) { + if (mForegroundUid == uid) { + mForegroundRequested = null; + mForegroundUid = -1; + success = true; + } // else, other UID in foreground + } + if (success) { + computePreferredForegroundService(); + } + return success; + } + + public boolean unregisteredPreferredForegroundService(int callingUid) { + // Verify the calling UID is in the foreground + if (mForegroundUtils.isInForeground(callingUid)) { + return unregisterForegroundService(callingUid); + } else { + Log.e(TAG, "Calling UID is not in the foreground, ignorning!"); + return false; + } + } + + @Override + public void onUidToBackground(int uid) { + unregisterForegroundService(uid); + } + + public void onHostEmulationActivated() { + synchronized (mLock) { + mClearNextTapDefault = (mNextTapDefault != null); + } + } + + public void onHostEmulationDeactivated() { + // If we had any next tap defaults set, clear them out + boolean changed = false; + synchronized (mLock) { + if (mClearNextTapDefault) { + // The reason we need to check this boolean is because the next tap + // default may have been set while the user held the phone + // on the reader; when the user then removes his phone from + // the reader (causing the "onHostEmulationDeactivated" event), + // the next tap default would immediately be cleared + // again. Instead, clear out defaults only if a next tap default + // had already been set at time of activation, which is captured + // by mClearNextTapDefault. + if (mNextTapDefault != null) { + mNextTapDefault = null; + changed = true; + } + mClearNextTapDefault = false; + } + } + if (changed) { + computePreferredForegroundService(); + } + } + + public void onUserSwitched(int userId) { + loadDefaultsFromSettings(userId); + } + + public boolean packageHasPreferredService(String packageName) { + if (packageName == null) return false; + + if (mPaymentDefaults.currentPreferred != null && + packageName.equals(mPaymentDefaults.currentPreferred.getPackageName())) { + return true; + } else if (mForegroundCurrent != null && + packageName.equals(mForegroundCurrent.getPackageName())) { + return true; + } else { + return false; + } + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("Preferred services (in order of importance): "); + pw.println(" *** Current preferred foreground service: " + mForegroundCurrent); + pw.println(" *** Current preferred payment service: " + mPaymentDefaults.currentPreferred); + pw.println(" Next tap default: " + mNextTapDefault); + pw.println(" Default for foreground app (UID: " + mForegroundUid + + "): " + mForegroundRequested); + pw.println(" Default in payment settings: " + mPaymentDefaults.settingsDefault); + pw.println(" Payment settings allows override: " + mPaymentDefaults.preferForeground); + pw.println(""); + } +} diff --git a/NfcSony/src/com/android/nfc/cardemulation/RegisteredAidCache.java b/NfcSony/src/com/android/nfc/cardemulation/RegisteredAidCache.java new file mode 100644 index 0000000..0ac04b7 --- /dev/null +++ b/NfcSony/src/com/android/nfc/cardemulation/RegisteredAidCache.java @@ -0,0 +1,634 @@ +/* + * Copyright (C) 2014 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.nfc.cardemulation; + +import android.app.ActivityManager; +import android.content.ComponentName; +import android.content.Context; +import android.nfc.cardemulation.ApduServiceInfo; +import android.nfc.cardemulation.CardEmulation; +import android.util.Log; + +import com.google.android.collect.Maps; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.NavigableMap; +import java.util.PriorityQueue; +import java.util.TreeMap; + +public class RegisteredAidCache { + static final String TAG = "RegisteredAidCache"; + + static final boolean DBG = false; + + // mAidServices maps AIDs to services that have registered them. + // It's a TreeMap in order to be able to quickly select subsets + // of AIDs that conflict with each other. + final TreeMap> mAidServices = + new TreeMap>(); + + // mAidCache is a lookup table for quickly mapping an exact or prefix AID to one or + // more handling services. It differs from mAidServices in the sense that it + // has already accounted for defaults, and hence its return value + // is authoritative for the current set of services and defaults. + // It is only valid for the current user. + final TreeMap mAidCache = new TreeMap(); + + // Represents a single AID registration of a service + final class ServiceAidInfo { + ApduServiceInfo service; + String aid; + String category; + + @Override + public String toString() { + return "ServiceAidInfo{" + + "service=" + service.getComponent() + + ", aid='" + aid + '\'' + + ", category='" + category + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ServiceAidInfo that = (ServiceAidInfo) o; + + if (!aid.equals(that.aid)) return false; + if (!category.equals(that.category)) return false; + if (!service.equals(that.service)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = service.hashCode(); + result = 31 * result + aid.hashCode(); + result = 31 * result + category.hashCode(); + return result; + } + } + + // Represents a list of services, an optional default and a category that + // an AID was resolved to. + final class AidResolveInfo { + List services = new ArrayList(); + ApduServiceInfo defaultService = null; + String category = null; + boolean mustRoute = true; // Whether this AID should be routed at all + + @Override + public String toString() { + return "AidResolveInfo{" + + "services=" + services + + ", defaultService=" + defaultService + + ", category='" + category + '\'' + + ", mustRoute=" + mustRoute + + '}'; + } + } + + final AidResolveInfo EMPTY_RESOLVE_INFO = new AidResolveInfo(); + + final Context mContext; + final AidRoutingManager mRoutingManager; + + final Object mLock = new Object(); + + ComponentName mPreferredPaymentService; + ComponentName mPreferredForegroundService; + + boolean mNfcEnabled = false; + boolean mSupportsPrefixes = false; + + public RegisteredAidCache(Context context) { + mContext = context; + mRoutingManager = new AidRoutingManager(); + mPreferredPaymentService = null; + mPreferredForegroundService = null; + mSupportsPrefixes = mRoutingManager.supportsAidPrefixRouting(); + if (mSupportsPrefixes) { + if (DBG) Log.d(TAG, "Controller supports AID prefix routing"); + } + } + + public AidResolveInfo resolveAid(String aid) { + synchronized (mLock) { + if (DBG) Log.d(TAG, "resolveAid: resolving AID " + aid); + if (aid.length() < 10) { + Log.e(TAG, "AID selected with fewer than 5 bytes."); + return EMPTY_RESOLVE_INFO; + } + AidResolveInfo resolveInfo = new AidResolveInfo(); + if (mSupportsPrefixes) { + // Our AID cache may contain prefixes which also match this AID, + // so we must find all potential prefixes and merge the ResolveInfo + // of those prefixes plus any exact match in a single result. + String shortestAidMatch = aid.substring(0, 10); // Minimum AID length is 5 bytes + String longestAidMatch = aid + "*"; // Longest potential matching AID + + if (DBG) Log.d(TAG, "Finding AID registrations in range [" + shortestAidMatch + + " - " + longestAidMatch + "]"); + NavigableMap matchingAids = + mAidCache.subMap(shortestAidMatch, true, longestAidMatch, true); + + resolveInfo.category = CardEmulation.CATEGORY_OTHER; + for (Map.Entry entry : matchingAids.entrySet()) { + boolean isPrefix = isPrefix(entry.getKey()); + String entryAid = isPrefix ? entry.getKey().substring(0, + entry.getKey().length() - 1) : entry.getKey(); // Cut off '*' if prefix + if (entryAid.equalsIgnoreCase(aid) || (isPrefix && aid.startsWith(entryAid))) { + if (DBG) Log.d(TAG, "resolveAid: AID " + entry.getKey() + " matches."); + AidResolveInfo entryResolveInfo = entry.getValue(); + if (entryResolveInfo.defaultService != null) { + if (resolveInfo.defaultService != null) { + // This shouldn't happen; for every prefix we have only one + // default service. + Log.e(TAG, "Different defaults for conflicting AIDs!"); + } + resolveInfo.defaultService = entryResolveInfo.defaultService; + resolveInfo.category = entryResolveInfo.category; + } + for (ApduServiceInfo serviceInfo : entryResolveInfo.services) { + if (!resolveInfo.services.contains(serviceInfo)) { + resolveInfo.services.add(serviceInfo); + } + } + } + } + } else { + resolveInfo = mAidCache.get(aid); + } + if (DBG) Log.d(TAG, "Resolved to: " + resolveInfo); + return resolveInfo; + } + } + + public boolean supportsAidPrefixRegistration() { + return mSupportsPrefixes; + } + + public boolean isDefaultServiceForAid(int userId, ComponentName service, String aid) { + AidResolveInfo resolveInfo = resolveAid(aid); + if (resolveInfo == null || resolveInfo.services == null || + resolveInfo.services.size() == 0) { + return false; + } + + if (resolveInfo.defaultService != null) { + return service.equals(resolveInfo.defaultService.getComponent()); + } else if (resolveInfo.services.size() == 1) { + return service.equals(resolveInfo.services.get(0).getComponent()); + } else { + // More than one service, not the default + return false; + } + } + + /** + * Resolves a conflict between multiple services handling the same + * AIDs. Note that the AID itself is not an input to the decision + * process - the algorithm just looks at the competing services + * and what preferences the user has indicated. In short, it works like + * this: + * + * 1) If there is a preferred foreground service, that service wins + * 2) Else, if there is a preferred payment service, that service wins + * 3) Else, if there is no winner, and all conflicting services will be + * in the list of resolved services. + */ + AidResolveInfo resolveAidConflictLocked(Collection conflictingServices, + boolean makeSingleServiceDefault) { + if (conflictingServices == null || conflictingServices.size() == 0) { + Log.e(TAG, "resolveAidConflict: No services passed in."); + return null; + } + AidResolveInfo resolveInfo = new AidResolveInfo(); + resolveInfo.category = CardEmulation.CATEGORY_OTHER; + + ApduServiceInfo matchedForeground = null; + ApduServiceInfo matchedPayment = null; + for (ServiceAidInfo serviceAidInfo : conflictingServices) { + boolean serviceClaimsPaymentAid = + CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category); + if (serviceAidInfo.service.getComponent().equals(mPreferredForegroundService)) { + resolveInfo.services.add(serviceAidInfo.service); + if (serviceClaimsPaymentAid) { + resolveInfo.category = CardEmulation.CATEGORY_PAYMENT; + } + matchedForeground = serviceAidInfo.service; + } else if (serviceAidInfo.service.getComponent().equals(mPreferredPaymentService) && + serviceClaimsPaymentAid) { + resolveInfo.services.add(serviceAidInfo.service); + resolveInfo.category = CardEmulation.CATEGORY_PAYMENT; + matchedPayment = serviceAidInfo.service; + } else { + if (serviceClaimsPaymentAid) { + // If this service claims it's a payment AID, don't route it, + // because it's not the default. Otherwise, add it to the list + // but not as default. + if (DBG) Log.d(TAG, "resolveAidLocked: (Ignoring handling service " + + serviceAidInfo.service.getComponent() + + " because it's not the payment default.)"); + } else { + resolveInfo.services.add(serviceAidInfo.service); + } + } + } + if (matchedForeground != null) { + // 1st priority: if the foreground app prefers a service, + // and that service asks for the AID, that service gets it + if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to foreground preferred " + + matchedForeground); + resolveInfo.defaultService = matchedForeground; + } else if (matchedPayment != null) { + // 2nd priority: if there is a preferred payment service, + // and that service claims this as a payment AID, that service gets it + if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to payment default " + + "default " + matchedPayment); + resolveInfo.defaultService = matchedPayment; + } else { + if (resolveInfo.services.size() == 1 && makeSingleServiceDefault) { + if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: making single handling service " + + resolveInfo.services.get(0).getComponent() + " default."); + resolveInfo.defaultService = resolveInfo.services.get(0); + } else { + // Nothing to do, all services already in list + if (DBG) Log.d(TAG, "resolveAidLocked: DECISION: routing to all matching services"); + } + } + return resolveInfo; + } + + class DefaultServiceInfo { + ServiceAidInfo paymentDefault; + ServiceAidInfo foregroundDefault; + } + + DefaultServiceInfo findDefaultServices(ArrayList serviceAidInfos) { + DefaultServiceInfo defaultServiceInfo = new DefaultServiceInfo(); + + for (ServiceAidInfo serviceAidInfo : serviceAidInfos) { + boolean serviceClaimsPaymentAid = + CardEmulation.CATEGORY_PAYMENT.equals(serviceAidInfo.category); + if (serviceAidInfo.service.getComponent().equals(mPreferredForegroundService)) { + defaultServiceInfo.foregroundDefault = serviceAidInfo; + } else if (serviceAidInfo.service.getComponent().equals(mPreferredPaymentService) && + serviceClaimsPaymentAid) { + defaultServiceInfo.paymentDefault = serviceAidInfo; + } + } + return defaultServiceInfo; + } + + AidResolveInfo resolvePrefixAidConflictLocked(ArrayList prefixServices, + ArrayList conflictingServices) { + // Find defaults among the prefix services themselves + DefaultServiceInfo prefixDefaultInfo = findDefaultServices(prefixServices); + + // Find any defaults among the children + DefaultServiceInfo conflictingDefaultInfo = findDefaultServices(conflictingServices); + + // Three conditions under which the prefix root AID gets to be the default + // 1. A service registering the prefix root AID is the current foreground preferred + // 2. A service registering the prefix root AID is the current tap & pay default AND + // no child is the current foreground preferred + // 3. There is only one service for the prefix root AID, and there are no children + if (prefixDefaultInfo.foregroundDefault != null) { + if (DBG) Log.d(TAG, "Prefix AID service " + + prefixDefaultInfo.foregroundDefault.service.getComponent() + " has foreground" + + " preference, ignoring conflicting AIDs."); + // Foreground default trumps any conflicting services, treat as normal AID conflict + // and ignore children + return resolveAidConflictLocked(prefixServices, true); + } else if (prefixDefaultInfo.paymentDefault != null) { + // Check if any of the conflicting services is foreground default + if (conflictingDefaultInfo.foregroundDefault != null) { + // Conflicting AID registration is in foreground, trumps prefix tap&pay default + if (DBG) Log.d(TAG, "One of the conflicting AID registrations is foreground " + + "preferred, ignoring prefix."); + return EMPTY_RESOLVE_INFO; + } else { + // Prefix service is tap&pay default, treat as normal AID conflict for just prefix + if (DBG) Log.d(TAG, "Prefix AID service " + + prefixDefaultInfo.paymentDefault.service.getComponent() + " is payment" + + " default, ignoring conflicting AIDs."); + return resolveAidConflictLocked(prefixServices, true); + } + } else { + if (conflictingDefaultInfo.foregroundDefault != null || + conflictingDefaultInfo.paymentDefault != null) { + if (DBG) Log.d(TAG, "One of the conflicting AID registrations is either payment " + + "default or foreground preferred, ignoring prefix."); + return EMPTY_RESOLVE_INFO; + } else { + // No children that are preferred; add all services of the root + // make single service default if no children are present + if (DBG) Log.d(TAG, "No service has preference, adding all."); + return resolveAidConflictLocked(prefixServices, conflictingServices.isEmpty()); + } + } + } + + void generateServiceMapLocked(List services) { + // Easiest is to just build the entire tree again + mAidServices.clear(); + for (ApduServiceInfo service : services) { + if (DBG) Log.d(TAG, "generateServiceMap component: " + service.getComponent()); + List prefixAids = service.getPrefixAids(); + for (String aid : service.getAids()) { + if (!CardEmulation.isValidAid(aid)) { + Log.e(TAG, "Aid " + aid + " is not valid."); + continue; + } + if (aid.endsWith("*") && !supportsAidPrefixRegistration()) { + Log.e(TAG, "Prefix AID " + aid + " ignored on device that doesn't support it."); + continue; + } else if (supportsAidPrefixRegistration() && prefixAids.size() > 0 && !isPrefix(aid)) { + // Check if we already have an overlapping prefix registered for this AID + boolean foundPrefix = false; + for (String prefixAid : prefixAids) { + String prefix = prefixAid.substring(0, prefixAid.length() - 1); + if (aid.startsWith(prefix)) { + Log.e(TAG, "Ignoring exact AID " + aid + " because prefix AID " + prefixAid + + " is already registered"); + foundPrefix = true; + break; + } + } + if (foundPrefix) { + continue; + } + } + ServiceAidInfo serviceAidInfo = new ServiceAidInfo(); + serviceAidInfo.aid = aid.toUpperCase(); + serviceAidInfo.service = service; + serviceAidInfo.category = service.getCategoryForAid(aid); + + if (mAidServices.containsKey(serviceAidInfo.aid)) { + final ArrayList serviceAidInfos = + mAidServices.get(serviceAidInfo.aid); + serviceAidInfos.add(serviceAidInfo); + } else { + final ArrayList serviceAidInfos = + new ArrayList(); + serviceAidInfos.add(serviceAidInfo); + mAidServices.put(serviceAidInfo.aid, serviceAidInfos); + } + } + } + } + + static boolean isPrefix(String aid) { + return aid.endsWith("*"); + } + + final class PrefixConflicts { + NavigableMap> conflictMap; + final ArrayList services = new ArrayList(); + final HashSet aids = new HashSet(); + } + + PrefixConflicts findConflictsForPrefixLocked(String prefixAid) { + PrefixConflicts prefixConflicts = new PrefixConflicts(); + String plainAid = prefixAid.substring(0, prefixAid.length() - 1); // Cut off "*" + String lastAidWithPrefix = String.format("%-32s", plainAid).replace(' ', 'F'); + if (DBG) Log.d(TAG, "Finding AIDs in range [" + plainAid + " - " + + lastAidWithPrefix + "]"); + prefixConflicts.conflictMap = + mAidServices.subMap(plainAid, true, lastAidWithPrefix, true); + for (Map.Entry> entry : + prefixConflicts.conflictMap.entrySet()) { + if (!entry.getKey().equalsIgnoreCase(prefixAid)) { + if (DBG) + Log.d(TAG, "AID " + entry.getKey() + " conflicts with prefix; " + + " adding handling services for conflict resolution."); + prefixConflicts.services.addAll(entry.getValue()); + prefixConflicts.aids.add(entry.getKey()); + } + } + return prefixConflicts; + } + + void generateAidCacheLocked() { + mAidCache.clear(); + // Get all exact and prefix AIDs in an ordered list + PriorityQueue aidsToResolve = new PriorityQueue(mAidServices.keySet()); + + while (!aidsToResolve.isEmpty()) { + final ArrayList resolvedAids = new ArrayList(); + + String aidToResolve = aidsToResolve.peek(); + // Because of the lexicographical ordering, all following AIDs either start with the + // same bytes and are longer, or start with different bytes. + + // A special case is if another service registered the same AID as a prefix, in + // which case we want to start with that AID, since it conflicts with this one + if (aidsToResolve.contains(aidToResolve + "*")) { + aidToResolve = aidToResolve + "*"; + } + if (DBG) Log.d(TAG, "generateAidCacheLocked: starting with aid " + aidToResolve); + + if (isPrefix(aidToResolve)) { + // This AID itself is a prefix; let's consider this prefix as the "root", + // and all conflicting AIDs as its children. + // For example, if "A000000003*" is the prefix root, + // "A000000003", "A00000000301*", "A0000000030102" are all conflicting children AIDs + final ArrayList prefixServices = new ArrayList( + mAidServices.get(aidToResolve)); + + // Find all conflicting children services + PrefixConflicts prefixConflicts = findConflictsForPrefixLocked(aidToResolve); + + // Resolve conflicts + AidResolveInfo resolveInfo = resolvePrefixAidConflictLocked(prefixServices, + prefixConflicts.services); + mAidCache.put(aidToResolve, resolveInfo); + resolvedAids.add(aidToResolve); + if (resolveInfo.defaultService != null) { + // This prefix is the default; therefore, AIDs of all conflicting children + // will no longer be evaluated. + resolvedAids.addAll(prefixConflicts.aids); + } else if (resolveInfo.services.size() > 0) { + // This means we don't have a default for this prefix and all its + // conflicting children. So, for all conflicting AIDs, just add + // all handling services without setting a default + boolean foundChildService = false; + for (Map.Entry> entry : + prefixConflicts.conflictMap.entrySet()) { + if (!entry.getKey().equalsIgnoreCase(aidToResolve)) { + if (DBG) + Log.d(TAG, "AID " + entry.getKey() + " shared with prefix; " + + " adding all handling services."); + AidResolveInfo childResolveInfo = resolveAidConflictLocked( + entry.getValue(), false); + // Special case: in this case all children AIDs must be routed to the + // host, so we can ask the user which service is preferred. + // Since these are all "children" of the prefix, they don't need + // to be routed, since the prefix will already get routed to the host + childResolveInfo.mustRoute = false; + mAidCache.put(entry.getKey(),childResolveInfo); + resolvedAids.add(entry.getKey()); + foundChildService |= !childResolveInfo.services.isEmpty(); + } + } + // Special case: if in the end we didn't add any children services, + // and the prefix has only one service, make that default + if (!foundChildService && resolveInfo.services.size() == 1) { + resolveInfo.defaultService = resolveInfo.services.get(0); + } + } else { + // This prefix is not handled at all; we will evaluate + // the children separately in next passes. + } + } else { + // Exact AID and no other conflicting AID registrations present + // This is true because aidsToResolve is lexicographically ordered, and + // so by necessity all other AIDs are different than this AID or longer. + if (DBG) Log.d(TAG, "Exact AID, resolving."); + final ArrayList conflictingServiceInfos = + new ArrayList(mAidServices.get(aidToResolve)); + mAidCache.put(aidToResolve, resolveAidConflictLocked(conflictingServiceInfos, true)); + resolvedAids.add(aidToResolve); + } + + // Remove the AIDs we resolved from the list of AIDs to resolve + if (DBG) Log.d(TAG, "AIDs: " + resolvedAids + " were resolved."); + aidsToResolve.removeAll(resolvedAids); + resolvedAids.clear(); + } + + updateRoutingLocked(); + } + + void updateRoutingLocked() { + if (!mNfcEnabled) { + if (DBG) Log.d(TAG, "Not updating routing table because NFC is off."); + return; + } + final HashMap routingEntries = Maps.newHashMap(); + // For each AID, find interested services + for (Map.Entry aidEntry: + mAidCache.entrySet()) { + String aid = aidEntry.getKey(); + AidResolveInfo resolveInfo = aidEntry.getValue(); + if (!resolveInfo.mustRoute) { + if (DBG) Log.d(TAG, "Not routing AID " + aid + " on request."); + continue; + } + if (resolveInfo.services.size() == 0) { + // No interested services + } else if (resolveInfo.defaultService != null) { + // There is a default service set, route to where that service resides - + // either on the host (HCE) or on an SE. + routingEntries.put(aid, resolveInfo.defaultService.isOnHost()); + } else if (resolveInfo.services.size() == 1) { + // Only one service, but not the default, must route to host + // to ask the user to choose one. + routingEntries.put(aid, true); + } else if (resolveInfo.services.size() > 1) { + // Multiple services, need to route to host to ask + routingEntries.put(aid, true); + } + } + mRoutingManager.configureRouting(routingEntries); + } + + public void onServicesUpdated(int userId, List services) { + if (DBG) Log.d(TAG, "onServicesUpdated"); + synchronized (mLock) { + if (ActivityManager.getCurrentUser() == userId) { + // Rebuild our internal data-structures + generateServiceMapLocked(services); + generateAidCacheLocked(); + } else { + if (DBG) Log.d(TAG, "Ignoring update because it's not for the current user."); + } + } + } + + public void onPreferredPaymentServiceChanged(ComponentName service) { + if (DBG) Log.d(TAG, "Preferred payment service changed."); + synchronized (mLock) { + mPreferredPaymentService = service; + generateAidCacheLocked(); + } + } + + public void onPreferredForegroundServiceChanged(ComponentName service) { + if (DBG) Log.d(TAG, "Preferred foreground service changed."); + synchronized (mLock) { + mPreferredForegroundService = service; + generateAidCacheLocked(); + } + } + + public void onNfcDisabled() { + synchronized (mLock) { + mNfcEnabled = false; + } + mRoutingManager.onNfccRoutingTableCleared(); + } + + public void onNfcEnabled() { + synchronized (mLock) { + mNfcEnabled = true; + updateRoutingLocked(); + } + } + + String dumpEntry(Map.Entry entry) { + StringBuilder sb = new StringBuilder(); + String category = entry.getValue().category; + ApduServiceInfo defaultServiceInfo = entry.getValue().defaultService; + sb.append(" \"" + entry.getKey() + "\" (category: " + category + ")\n"); + ComponentName defaultComponent = defaultServiceInfo != null ? + defaultServiceInfo.getComponent() : null; + + for (ApduServiceInfo serviceInfo : entry.getValue().services) { + sb.append(" "); + if (serviceInfo.getComponent().equals(defaultComponent)) { + sb.append("*DEFAULT* "); + } + sb.append(serviceInfo.getComponent() + + " (Description: " + serviceInfo.getDescription() + ")\n"); + } + return sb.toString(); + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println(" AID cache entries: "); + for (Map.Entry entry : mAidCache.entrySet()) { + pw.println(dumpEntry(entry)); + } + pw.println(" Service preferred by foreground app: " + mPreferredForegroundService); + pw.println(" Preferred payment service: " + mPreferredPaymentService); + pw.println(""); + mRoutingManager.dump(fd, pw, args); + pw.println(""); + } +} diff --git a/NfcSony/src/com/android/nfc/cardemulation/RegisteredServicesCache.java b/NfcSony/src/com/android/nfc/cardemulation/RegisteredServicesCache.java new file mode 100644 index 0000000..be84e53 --- /dev/null +++ b/NfcSony/src/com/android/nfc/cardemulation/RegisteredServicesCache.java @@ -0,0 +1,563 @@ +/* + * Copyright (C) 2013 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.nfc.cardemulation; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.app.ActivityManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.nfc.cardemulation.AidGroup; +import android.nfc.cardemulation.ApduServiceInfo; +import android.nfc.cardemulation.CardEmulation; +import android.nfc.cardemulation.HostApduService; +import android.nfc.cardemulation.OffHostApduService; +import android.os.UserHandle; +import android.util.AtomicFile; +import android.util.Log; +import android.util.SparseArray; +import android.util.Xml; + +import com.android.internal.util.FastXmlSerializer; +import com.google.android.collect.Maps; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +/** + * This class is inspired by android.content.pm.RegisteredServicesCache + * That class was not re-used because it doesn't support dynamically + * registering additional properties, but generates everything from + * the manifest. Since we have some properties that are not in the manifest, + * it's less suited. + */ +public class RegisteredServicesCache { + static final String XML_INDENT_OUTPUT_FEATURE = "http://xmlpull.org/v1/doc/features.html#indent-output"; + static final String TAG = "RegisteredServicesCache"; + static final boolean DEBUG = false; + + final Context mContext; + final AtomicReference mReceiver; + + final Object mLock = new Object(); + // All variables below synchronized on mLock + + // mUserServices holds the card emulation services that are running for each user + final SparseArray mUserServices = new SparseArray(); + final Callback mCallback; + final AtomicFile mDynamicAidsFile; + + public interface Callback { + void onServicesUpdated(int userId, final List services); + }; + + static class DynamicAids { + public final int uid; + public final HashMap aidGroups = Maps.newHashMap(); + + DynamicAids(int uid) { + this.uid = uid; + } + }; + + private static class UserServices { + /** + * All services that have registered + */ + final HashMap services = + Maps.newHashMap(); // Re-built at run-time + final HashMap dynamicAids = + Maps.newHashMap(); // In memory cache of dynamic AID store + }; + + private UserServices findOrCreateUserLocked(int userId) { + UserServices services = mUserServices.get(userId); + if (services == null) { + services = new UserServices(); + mUserServices.put(userId, services); + } + return services; + } + + public RegisteredServicesCache(Context context, Callback callback) { + mContext = context; + mCallback = callback; + + final BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); + String action = intent.getAction(); + if (DEBUG) Log.d(TAG, "Intent action: " + action); + if (uid != -1) { + boolean replaced = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) && + (Intent.ACTION_PACKAGE_ADDED.equals(action) || + Intent.ACTION_PACKAGE_REMOVED.equals(action)); + if (!replaced) { + int currentUser = ActivityManager.getCurrentUser(); + if (currentUser == UserHandle.getUserId(uid)) { + invalidateCache(UserHandle.getUserId(uid)); + } else { + // Cache will automatically be updated on user switch + } + } else { + if (DEBUG) Log.d(TAG, "Ignoring package intent due to package being replaced."); + } + } + } + }; + mReceiver = new AtomicReference(receiver); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); + intentFilter.addAction(Intent.ACTION_PACKAGE_FIRST_LAUNCH); + intentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED); + intentFilter.addDataScheme("package"); + mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, intentFilter, null, null); + + // Register for events related to sdcard operations + IntentFilter sdFilter = new IntentFilter(); + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + mContext.registerReceiverAsUser(mReceiver.get(), UserHandle.ALL, sdFilter, null, null); + + File dataDir = mContext.getFilesDir(); + mDynamicAidsFile = new AtomicFile(new File(dataDir, "dynamic_aids.xml")); + } + + void initialize() { + synchronized (mLock) { + readDynamicAidsLocked(); + } + invalidateCache(ActivityManager.getCurrentUser()); + } + + void dump(ArrayList services) { + for (ApduServiceInfo service : services) { + if (DEBUG) Log.d(TAG, service.toString()); + } + } + + boolean containsServiceLocked(ArrayList services, ComponentName serviceName) { + for (ApduServiceInfo service : services) { + if (service.getComponent().equals(serviceName)) return true; + } + return false; + } + + public boolean hasService(int userId, ComponentName service) { + return getService(userId, service) != null; + } + + public ApduServiceInfo getService(int userId, ComponentName service) { + synchronized (mLock) { + UserServices userServices = findOrCreateUserLocked(userId); + return userServices.services.get(service); + } + } + + public List getServices(int userId) { + final ArrayList services = new ArrayList(); + synchronized (mLock) { + UserServices userServices = findOrCreateUserLocked(userId); + services.addAll(userServices.services.values()); + } + return services; + } + + public List getServicesForCategory(int userId, String category) { + final ArrayList services = new ArrayList(); + synchronized (mLock) { + UserServices userServices = findOrCreateUserLocked(userId); + for (ApduServiceInfo service : userServices.services.values()) { + if (service.hasCategory(category)) services.add(service); + } + } + return services; + } + + ArrayList getInstalledServices(int userId) { + PackageManager pm; + try { + pm = mContext.createPackageContextAsUser("android", 0, + new UserHandle(userId)).getPackageManager(); + } catch (NameNotFoundException e) { + Log.e(TAG, "Could not create user package context"); + return null; + } + + ArrayList validServices = new ArrayList(); + + List resolvedServices = pm.queryIntentServicesAsUser( + new Intent(HostApduService.SERVICE_INTERFACE), + PackageManager.GET_META_DATA, userId); + + List resolvedOffHostServices = pm.queryIntentServicesAsUser( + new Intent(OffHostApduService.SERVICE_INTERFACE), + PackageManager.GET_META_DATA, userId); + resolvedServices.addAll(resolvedOffHostServices); + + for (ResolveInfo resolvedService : resolvedServices) { + try { + boolean onHost = !resolvedOffHostServices.contains(resolvedService); + ServiceInfo si = resolvedService.serviceInfo; + ComponentName componentName = new ComponentName(si.packageName, si.name); + // Check if the package holds the NFC permission + if (pm.checkPermission(android.Manifest.permission.NFC, si.packageName) != + PackageManager.PERMISSION_GRANTED) { + Log.e(TAG, "Skipping application component " + componentName + + ": it must request the permission " + + android.Manifest.permission.NFC); + continue; + } + if (!android.Manifest.permission.BIND_NFC_SERVICE.equals( + si.permission)) { + Log.e(TAG, "Skipping APDU service " + componentName + + ": it does not require the permission " + + android.Manifest.permission.BIND_NFC_SERVICE); + continue; + } + ApduServiceInfo service = new ApduServiceInfo(pm, resolvedService, onHost); + if (service != null) { + validServices.add(service); + } + } catch (XmlPullParserException e) { + Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e); + } catch (IOException e) { + Log.w(TAG, "Unable to load component info " + resolvedService.toString(), e); + } + } + + return validServices; + } + + public void invalidateCache(int userId) { + final ArrayList validServices = getInstalledServices(userId); + if (validServices == null) { + return; + } + synchronized (mLock) { + UserServices userServices = findOrCreateUserLocked(userId); + + // Find removed services + Iterator> it = + userServices.services.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry entry = + (Map.Entry) it.next(); + if (!containsServiceLocked(validServices, entry.getKey())) { + Log.d(TAG, "Service removed: " + entry.getKey()); + it.remove(); + } + } + for (ApduServiceInfo service : validServices) { + if (DEBUG) Log.d(TAG, "Adding service: " + service.getComponent() + + " AIDs: " + service.getAids()); + userServices.services.put(service.getComponent(), service); + } + + // Apply dynamic AID mappings + ArrayList toBeRemoved = new ArrayList(); + for (Map.Entry entry : + userServices.dynamicAids.entrySet()) { + // Verify component / uid match + ComponentName component = entry.getKey(); + DynamicAids dynamicAids = entry.getValue(); + ApduServiceInfo serviceInfo = userServices.services.get(component); + if (serviceInfo == null || (serviceInfo.getUid() != dynamicAids.uid)) { + toBeRemoved.add(component); + continue; + } else { + for (AidGroup group : dynamicAids.aidGroups.values()) { + serviceInfo.setOrReplaceDynamicAidGroup(group); + } + } + } + + if (toBeRemoved.size() > 0) { + for (ComponentName component : toBeRemoved) { + Log.d(TAG, "Removing dynamic AIDs registered by " + component); + userServices.dynamicAids.remove(component); + } + // Persist to filesystem + writeDynamicAidsLocked(); + } + } + + mCallback.onServicesUpdated(userId, Collections.unmodifiableList(validServices)); + dump(validServices); + } + + private void readDynamicAidsLocked() { + FileInputStream fis = null; + try { + if (!mDynamicAidsFile.getBaseFile().exists()) { + Log.d(TAG, "Dynamic AIDs file does not exist."); + return; + } + fis = mDynamicAidsFile.openRead(); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(fis, null); + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG && + eventType != XmlPullParser.END_DOCUMENT) { + eventType = parser.next(); + } + String tagName = parser.getName(); + if ("services".equals(tagName)) { + boolean inService = false; + ComponentName currentComponent = null; + int currentUid = -1; + ArrayList currentGroups = new ArrayList(); + while (eventType != XmlPullParser.END_DOCUMENT) { + tagName = parser.getName(); + if (eventType == XmlPullParser.START_TAG) { + if ("service".equals(tagName) && parser.getDepth() == 2) { + String compString = parser.getAttributeValue(null, "component"); + String uidString = parser.getAttributeValue(null, "uid"); + if (compString == null || uidString == null) { + Log.e(TAG, "Invalid service attributes"); + } else { + try { + currentUid = Integer.parseInt(uidString); + currentComponent = ComponentName.unflattenFromString(compString); + inService = true; + } catch (NumberFormatException e) { + Log.e(TAG, "Could not parse service uid"); + } + } + } + if ("aid-group".equals(tagName) && parser.getDepth() == 3 && inService) { + AidGroup group = AidGroup.createFromXml(parser); + if (group != null) { + currentGroups.add(group); + } else { + Log.e(TAG, "Could not parse AID group."); + } + } + } else if (eventType == XmlPullParser.END_TAG) { + if ("service".equals(tagName)) { + // See if we have a valid service + if (currentComponent != null && currentUid >= 0 && + currentGroups.size() > 0) { + final int userId = UserHandle.getUserId(currentUid); + DynamicAids dynAids = new DynamicAids(currentUid); + for (AidGroup group : currentGroups) { + dynAids.aidGroups.put(group.getCategory(), group); + } + UserServices services = findOrCreateUserLocked(userId); + services.dynamicAids.put(currentComponent, dynAids); + } + currentUid = -1; + currentComponent = null; + currentGroups.clear(); + inService = false; + } + } + eventType = parser.next(); + }; + } + } catch (Exception e) { + Log.e(TAG, "Could not parse dynamic AIDs file, trashing."); + mDynamicAidsFile.delete(); + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + } + } + } + } + + private boolean writeDynamicAidsLocked() { + FileOutputStream fos = null; + try { + fos = mDynamicAidsFile.startWrite(); + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, "utf-8"); + out.startDocument(null, true); + out.setFeature(XML_INDENT_OUTPUT_FEATURE, true); + out.startTag(null, "services"); + for (int i = 0; i < mUserServices.size(); i++) { + final UserServices user = mUserServices.valueAt(i); + for (Map.Entry service : user.dynamicAids.entrySet()) { + out.startTag(null, "service"); + out.attribute(null, "component", service.getKey().flattenToString()); + out.attribute(null, "uid", Integer.toString(service.getValue().uid)); + for (AidGroup group : service.getValue().aidGroups.values()) { + group.writeAsXml(out); + } + out.endTag(null, "service"); + } + } + out.endTag(null, "services"); + out.endDocument(); + mDynamicAidsFile.finishWrite(fos); + return true; + } catch (Exception e) { + Log.e(TAG, "Error writing dynamic AIDs", e); + if (fos != null) { + mDynamicAidsFile.failWrite(fos); + } + return false; + } + } + + public boolean registerAidGroupForService(int userId, int uid, + ComponentName componentName, AidGroup aidGroup) { + ArrayList newServices = null; + boolean success; + synchronized (mLock) { + UserServices services = findOrCreateUserLocked(userId); + // Check if we can find this service + ApduServiceInfo serviceInfo = getService(userId, componentName); + if (serviceInfo == null) { + Log.e(TAG, "Service " + componentName + " does not exist."); + return false; + } + if (serviceInfo.getUid() != uid) { + // This is probably a good indication something is wrong here. + // Either newer service installed with different uid (but then + // we should have known about it), or somebody calling us from + // a different uid. + Log.e(TAG, "UID mismatch."); + return false; + } + // Do another AID validation, since a caller could have thrown in a modified + // AidGroup object with invalid AIDs over Binder. + List aids = aidGroup.getAids(); + for (String aid : aids) { + if (!CardEmulation.isValidAid(aid)) { + Log.e(TAG, "AID " + aid + " is not a valid AID"); + return false; + } + } + serviceInfo.setOrReplaceDynamicAidGroup(aidGroup); + DynamicAids dynAids = services.dynamicAids.get(componentName); + if (dynAids == null) { + dynAids = new DynamicAids(uid); + services.dynamicAids.put(componentName, dynAids); + } + dynAids.aidGroups.put(aidGroup.getCategory(), aidGroup); + success = writeDynamicAidsLocked(); + if (success) { + newServices = new ArrayList(services.services.values()); + } else { + Log.e(TAG, "Failed to persist AID group."); + // Undo registration + dynAids.aidGroups.remove(aidGroup.getCategory()); + } + } + if (success) { + // Make callback without the lock held + mCallback.onServicesUpdated(userId, newServices); + } + return success; + } + + public AidGroup getAidGroupForService(int userId, int uid, ComponentName componentName, + String category) { + ApduServiceInfo serviceInfo = getService(userId, componentName); + if (serviceInfo != null) { + if (serviceInfo.getUid() != uid) { + Log.e(TAG, "UID mismatch"); + return null; + } + return serviceInfo.getDynamicAidGroupForCategory(category); + } else { + Log.e(TAG, "Could not find service " + componentName); + return null; + } + } + + public boolean removeAidGroupForService(int userId, int uid, ComponentName componentName, + String category) { + boolean success = false; + ArrayList newServices = null; + synchronized (mLock) { + UserServices services = findOrCreateUserLocked(userId); + ApduServiceInfo serviceInfo = getService(userId, componentName); + if (serviceInfo != null) { + if (serviceInfo.getUid() != uid) { + // Calling from different uid + Log.e(TAG, "UID mismatch"); + return false; + } + if (!serviceInfo.removeDynamicAidGroupForCategory(category)) { + Log.e(TAG," Could not find dynamic AIDs for category " + category); + return false; + } + // Remove from local cache + DynamicAids dynAids = services.dynamicAids.get(componentName); + if (dynAids != null) { + AidGroup deletedGroup = dynAids.aidGroups.remove(category); + success = writeDynamicAidsLocked(); + if (success) { + newServices = new ArrayList(services.services.values()); + } else { + Log.e(TAG, "Could not persist deleted AID group."); + dynAids.aidGroups.put(category, deletedGroup); + return false; + } + } else { + Log.e(TAG, "Could not find aid group in local cache."); + } + } else { + Log.e(TAG, "Service " + componentName + " does not exist."); + } + } + if (success) { + mCallback.onServicesUpdated(userId, newServices); + } + return success; + } + + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("Registered HCE services for current user: "); + UserServices userServices = findOrCreateUserLocked(ActivityManager.getCurrentUser()); + for (ApduServiceInfo service : userServices.services.values()) { + service.dump(fd, pw, args); + pw.println(""); + } + pw.println(""); + } + +} diff --git a/NfcSony/src/com/android/nfc/cardemulation/TapAgainDialog.java b/NfcSony/src/com/android/nfc/cardemulation/TapAgainDialog.java new file mode 100644 index 0000000..b1880b4 --- /dev/null +++ b/NfcSony/src/com/android/nfc/cardemulation/TapAgainDialog.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2013 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.nfc.cardemulation; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.nfc.NfcAdapter; +import android.nfc.cardemulation.ApduServiceInfo; +import android.nfc.cardemulation.CardEmulation; +import android.os.Bundle; +import android.view.Window; +import android.view.WindowManager; +import android.widget.TextView; + +import com.android.internal.R; +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + +public class TapAgainDialog extends AlertActivity implements DialogInterface.OnClickListener { + public static final String ACTION_CLOSE = "com.android.nfc.cardmeulation.close_tap_dialog"; + public static final String EXTRA_APDU_SERVICE = "apdu_service"; + + public static final String EXTRA_CATEGORY = "category"; + + // Variables below only accessed on the main thread + private CardEmulation mCardEmuManager; + private boolean mClosedOnRequest = false; + final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mClosedOnRequest = true; + finish(); + } + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert); + + final NfcAdapter adapter = NfcAdapter.getDefaultAdapter(this); + mCardEmuManager = CardEmulation.getInstance(adapter); + Intent intent = getIntent(); + String category = intent.getStringExtra(EXTRA_CATEGORY); + ApduServiceInfo serviceInfo = intent.getParcelableExtra(EXTRA_APDU_SERVICE); + IntentFilter filter = new IntentFilter(ACTION_CLOSE); + filter.addAction(Intent.ACTION_SCREEN_OFF); + registerReceiver(mReceiver, filter); + AlertController.AlertParams ap = mAlertParams; + + ap.mTitle = ""; + ap.mView = getLayoutInflater().inflate(com.android.nfc.R.layout.tapagain, null); + + PackageManager pm = getPackageManager(); + TextView tv = (TextView) ap.mView.findViewById(com.android.nfc.R.id.textview); + String description = serviceInfo.getDescription(); + if (description == null) { + CharSequence label = serviceInfo.loadLabel(pm); + if (label == null) { + finish(); + } else { + description = label.toString(); + } + } + if (CardEmulation.CATEGORY_PAYMENT.equals(category)) { + String formatString = getString(com.android.nfc.R.string.tap_again_to_pay); + tv.setText(String.format(formatString, description)); + } else { + String formatString = getString(com.android.nfc.R.string.tap_again_to_complete); + tv.setText(String.format(formatString, description)); + } + ap.mNegativeButtonText = getString(R.string.cancel); + setupAlert(); + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + unregisterReceiver(mReceiver); + } + + @Override + protected void onStop() { + super.onStop(); + if (!mClosedOnRequest) { + mCardEmuManager.setDefaultForNextTap(null); + } + } + + @Override + public void onClick(DialogInterface dialog, int which) { + finish(); + } +} \ No newline at end of file diff --git a/NfcSony/src/com/android/nfc/echoserver/EchoServer.java b/NfcSony/src/com/android/nfc/echoserver/EchoServer.java new file mode 100644 index 0000000..71d0bf8 --- /dev/null +++ b/NfcSony/src/com/android/nfc/echoserver/EchoServer.java @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2012 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.nfc.echoserver; + +import com.android.nfc.DeviceHost.LlcpConnectionlessSocket; +import com.android.nfc.LlcpException; +import com.android.nfc.DeviceHost.LlcpServerSocket; +import com.android.nfc.DeviceHost.LlcpSocket; +import com.android.nfc.LlcpPacket; +import com.android.nfc.NfcService; + +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import java.io.IOException; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +/** + * EchoServer is an implementation of the echo server that is used in the + * nfcpy LLCP test suite. Enabling the EchoServer allows to test Android + * NFC devices against nfcpy. + * It has two main features (which run simultaneously): + * 1) A connection-based server, which has a receive buffer of two + * packets. Once a packet is received, a 2-second sleep is initiated. + * After these 2 seconds, all packets that are in the receive buffer + * are echoed back on the same connection. The connection-based server + * does not drop packets, but simply blocks if the queue is full. + * 2) A connection-less mode, which has a receive buffer of two packets. + * On LLCP link activation, we try to receive data on a pre-determined + * connection-less SAP. Like the connection-based server, all data in + * the buffer is echoed back to the SAP from which the data originated + * after a sleep of two seconds. + * The main difference is that the connection-less SAP is supposed + * to drop packets when the buffer is full. + * + * To use with nfcpy: + * - Adapt default_miu (see ECHO_MIU below) + * - llcp-test-client.py --mode=target --co-echo=17 --cl-echo=18 -t 1 + * + * Modify -t to execute the different tests. + * + */ +public class EchoServer { + static boolean DBG = true; + + static final int DEFAULT_CO_SAP = 0x11; + static final int DEFAULT_CL_SAP = 0x12; + + // Link MIU + static final int MIU = 128; + + static final String TAG = "EchoServer"; + static final String CONNECTION_SERVICE_NAME = "urn:nfc:sn:co-echo"; + static final String CONNECTIONLESS_SERVICE_NAME = "urn:nfc:sn:cl-echo"; + + ServerThread mServerThread; + ConnectionlessServerThread mConnectionlessServerThread; + NfcService mService; + + public interface WriteCallback { + public void write(byte[] data); + } + + public EchoServer() { + mService = NfcService.getInstance(); + } + + static class EchoMachine implements Handler.Callback { + static final int QUEUE_SIZE = 2; + static final int ECHO_DELAY_IN_MS = 2000; + + /** + * ECHO_MIU must be set equal to default_miu in nfcpy. + * The nfcpy echo server is expected to maintain the + * packet boundaries and sizes of the requests - that is, + * if the nfcpy client sends a service data unit of 48 bytes + * in a packet, the echo packet should have a payload of + * 48 bytes as well. The "problem" is that the current + * Android LLCP implementation simply pushes all received data + * in a single large buffer, causing us to loose the packet + * boundaries, not knowing how much data to put in a single + * response packet. The ECHO_MIU parameter determines exactly that. + * We use ECHO_MIU=48 because of a bug in PN544, which does not respect + * the target length reduction parameter of the p2p protocol. + */ + static final int ECHO_MIU = 128; + + final BlockingQueue dataQueue; + final Handler handler; + final boolean dumpWhenFull; + final WriteCallback callback; + + // shutdown can be modified from multiple threads, protected by this + boolean shutdown = false; + + EchoMachine(WriteCallback callback, boolean dumpWhenFull) { + this.callback = callback; + this.dumpWhenFull = dumpWhenFull; + dataQueue = new LinkedBlockingQueue(QUEUE_SIZE); + handler = new Handler(this); + } + + public void pushUnit(byte[] unit, int size) { + if (dumpWhenFull && dataQueue.remainingCapacity() == 0) { + if (DBG) Log.d(TAG, "Dumping data unit"); + } else { + try { + // Split up the packet in ECHO_MIU size packets + int sizeLeft = size; + int offset = 0; + if (dataQueue.isEmpty()) { + // First message: start echo'ing in 2 seconds + handler.sendMessageDelayed(handler.obtainMessage(), ECHO_DELAY_IN_MS); + } + + if (sizeLeft == 0) { + // Special case: also send a zero-sized data unit + dataQueue.put(new byte[] {}); + } + while (sizeLeft > 0) { + int minSize = Math.min(size, ECHO_MIU); + byte[] data = new byte[minSize]; + System.arraycopy(unit, offset, data, 0, minSize); + dataQueue.put(data); + sizeLeft -= minSize; + offset += minSize; + } + } catch (InterruptedException e) { + // Ignore + } + } + } + + /** Shuts down the EchoMachine. May block until callbacks + * in progress are completed. + */ + public synchronized void shutdown() { + dataQueue.clear(); + shutdown = true; + } + + @Override + public synchronized boolean handleMessage(Message msg) { + if (shutdown) return true; + while (!dataQueue.isEmpty()) { + callback.write(dataQueue.remove()); + } + return true; + } + } + + public class ServerThread extends Thread implements WriteCallback { + final EchoMachine echoMachine; + + boolean running = true; + LlcpServerSocket serverSocket; + LlcpSocket clientSocket; + + public ServerThread() { + super(); + echoMachine = new EchoMachine(this, false); + } + + private void handleClient(LlcpSocket socket) { + boolean connectionBroken = false; + byte[] dataUnit = new byte[1024]; + + // Get raw data from remote server + while (!connectionBroken) { + try { + int size = socket.receive(dataUnit); + if (DBG) Log.d(TAG, "read " + size + " bytes"); + if (size < 0) { + connectionBroken = true; + break; + } else { + echoMachine.pushUnit(dataUnit, size); + } + } catch (IOException e) { + // Connection broken + connectionBroken = true; + if (DBG) Log.d(TAG, "connection broken by IOException", e); + } + } + } + + @Override + public void run() { + if (DBG) Log.d(TAG, "about create LLCP service socket"); + try { + serverSocket = mService.createLlcpServerSocket(DEFAULT_CO_SAP, + CONNECTION_SERVICE_NAME, MIU, 1, 1024); + } catch (LlcpException e) { + return; + } + if (serverSocket == null) { + if (DBG) Log.d(TAG, "failed to create LLCP service socket"); + return; + } + if (DBG) Log.d(TAG, "created LLCP service socket"); + + while (running) { + + try { + if (DBG) Log.d(TAG, "about to accept"); + clientSocket = serverSocket.accept(); + if (DBG) Log.d(TAG, "accept returned " + clientSocket); + handleClient(clientSocket); + } catch (LlcpException e) { + Log.e(TAG, "llcp error", e); + running = false; + } catch (IOException e) { + Log.e(TAG, "IO error", e); + running = false; + } + } + + echoMachine.shutdown(); + + try { + clientSocket.close(); + } catch (IOException e) { + // Ignore + } + clientSocket = null; + + try { + serverSocket.close(); + } catch (IOException e) { + // Ignore + } + serverSocket = null; + } + + @Override + public void write(byte[] data) { + if (clientSocket != null) { + try { + clientSocket.send(data); + Log.e(TAG, "Send success!"); + } catch (IOException e) { + Log.e(TAG, "Send failed."); + } + } + } + + public void shutdown() { + running = false; + if (serverSocket != null) { + try { + serverSocket.close(); + } catch (IOException e) { + // ignore + } + serverSocket = null; + } + } + } + + public class ConnectionlessServerThread extends Thread implements WriteCallback { + final EchoMachine echoMachine; + + LlcpConnectionlessSocket socket; + int mRemoteSap; + boolean mRunning = true; + + public ConnectionlessServerThread() { + super(); + echoMachine = new EchoMachine(this, true); + } + + @Override + public void run() { + boolean connectionBroken = false; + LlcpPacket packet; + if (DBG) Log.d(TAG, "about create LLCP connectionless socket"); + try { + socket = mService.createLlcpConnectionLessSocket( + DEFAULT_CL_SAP, CONNECTIONLESS_SERVICE_NAME); + if (socket == null) { + if (DBG) Log.d(TAG, "failed to create LLCP connectionless socket"); + return; + } + + while (mRunning && !connectionBroken) { + try { + packet = socket.receive(); + if (packet == null || packet.getDataBuffer() == null) { + break; + } + byte[] dataUnit = packet.getDataBuffer(); + int size = dataUnit.length; + + if (DBG) Log.d(TAG, "read " + packet.getDataBuffer().length + " bytes"); + if (size < 0) { + connectionBroken = true; + break; + } else { + mRemoteSap = packet.getRemoteSap(); + echoMachine.pushUnit(dataUnit, size); + } + } catch (IOException e) { + // Connection broken + connectionBroken = true; + if (DBG) Log.d(TAG, "connection broken by IOException", e); + } + } + } catch (LlcpException e) { + Log.e(TAG, "llcp error", e); + } finally { + echoMachine.shutdown(); + + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + } + } + } + + } + + public void shutdown() { + mRunning = false; + } + + @Override + public void write(byte[] data) { + try { + socket.send(mRemoteSap, data); + } catch (IOException e) { + if (DBG) Log.d(TAG, "Error writing data."); + } + } + } + + public void onLlcpActivated() { + synchronized (this) { + // Connectionless server can only be started once the link is up + // - otherwise, all calls to receive() on the connectionless socket + // will fail immediately. + if (mConnectionlessServerThread == null) { + mConnectionlessServerThread = new ConnectionlessServerThread(); + mConnectionlessServerThread.start(); + } + } + } + + public void onLlcpDeactivated() { + synchronized (this) { + if (mConnectionlessServerThread != null) { + mConnectionlessServerThread.shutdown(); + mConnectionlessServerThread = null; + } + } + } + + /** + * Needs to be called on the UI thread + */ + public void start() { + synchronized (this) { + if (mServerThread == null) { + mServerThread = new ServerThread(); + mServerThread.start(); + } + } + + } + + public void stop() { + synchronized (this) { + if (mServerThread != null) { + mServerThread.shutdown(); + mServerThread = null; + } + } + } +} diff --git a/NfcSony/src/com/android/nfc/handover/BluetoothPeripheralHandover.java b/NfcSony/src/com/android/nfc/handover/BluetoothPeripheralHandover.java new file mode 100644 index 0000000..10400be --- /dev/null +++ b/NfcSony/src/com/android/nfc/handover/BluetoothPeripheralHandover.java @@ -0,0 +1,558 @@ +/* + * Copyright (C) 2012 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.nfc.handover; + +import android.bluetooth.BluetoothA2dp; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothInputDevice; +import android.bluetooth.BluetoothProfile; +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.session.MediaSessionLegacyHelper; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelUuid; +import android.provider.Settings; +import android.util.Log; +import android.view.KeyEvent; +import android.widget.Toast; + +import com.android.nfc.R; + +/** + * Connects / Disconnects from a Bluetooth headset (or any device that + * might implement BT HSP, HFP, A2DP, or HOGP sink) when touched with NFC. + * + * This object is created on an NFC interaction, and determines what + * sequence of Bluetooth actions to take, and executes them. It is not + * designed to be re-used after the sequence has completed or timed out. + * Subsequent NFC interactions should use new objects. + * + */ +public class BluetoothPeripheralHandover implements BluetoothProfile.ServiceListener { + static final String TAG = "BluetoothPeripheralHandover"; + static final boolean DBG = false; + + static final String ACTION_ALLOW_CONNECT = "com.android.nfc.handover.action.ALLOW_CONNECT"; + static final String ACTION_DENY_CONNECT = "com.android.nfc.handover.action.DENY_CONNECT"; + + static final int TIMEOUT_MS = 20000; + + static final int STATE_INIT = 0; + static final int STATE_WAITING_FOR_PROXIES = 1; + static final int STATE_INIT_COMPLETE = 2; + static final int STATE_WAITING_FOR_BOND_CONFIRMATION = 3; + static final int STATE_BONDING = 4; + static final int STATE_CONNECTING = 5; + static final int STATE_DISCONNECTING = 6; + static final int STATE_COMPLETE = 7; + + static final int RESULT_PENDING = 0; + static final int RESULT_CONNECTED = 1; + static final int RESULT_DISCONNECTED = 2; + + static final int ACTION_INIT = 0; + static final int ACTION_DISCONNECT = 1; + static final int ACTION_CONNECT = 2; + + static final int MSG_TIMEOUT = 1; + static final int MSG_NEXT_STEP = 2; + + final Context mContext; + final BluetoothDevice mDevice; + final String mName; + final Callback mCallback; + final BluetoothAdapter mBluetoothAdapter; + final int mTransport; + final boolean mProvisioning; + + final Object mLock = new Object(); + + // only used on main thread + int mAction; + int mState; + int mHfpResult; // used only in STATE_CONNECTING and STATE_DISCONNETING + int mA2dpResult; // used only in STATE_CONNECTING and STATE_DISCONNETING + int mHidResult; + + // protected by mLock + BluetoothA2dp mA2dp; + BluetoothHeadset mHeadset; + BluetoothInputDevice mInput; + + public interface Callback { + public void onBluetoothPeripheralHandoverComplete(boolean connected); + } + + public BluetoothPeripheralHandover(Context context, BluetoothDevice device, String name, + int transport, Callback callback) { + checkMainThread(); // mHandler must get get constructed on Main Thread for toasts to work + mContext = context; + mDevice = device; + mName = name; + mTransport = transport; + mCallback = callback; + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + + ContentResolver contentResolver = mContext.getContentResolver(); + mProvisioning = Settings.Secure.getInt(contentResolver, + Settings.Global.DEVICE_PROVISIONED, 0) == 0; + + mState = STATE_INIT; + } + + public boolean hasStarted() { + return mState != STATE_INIT; + } + + /** + * Main entry point. This method is usually called after construction, + * to begin the BT sequence. Must be called on Main thread. + */ + public boolean start() { + checkMainThread(); + if (mState != STATE_INIT || mBluetoothAdapter == null + || (mProvisioning && mTransport != BluetoothDevice.TRANSPORT_LE)) { + return false; + } + + + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); + filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); + filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED); + filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); + filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED); + filter.addAction(ACTION_ALLOW_CONNECT); + filter.addAction(ACTION_DENY_CONNECT); + + mContext.registerReceiver(mReceiver, filter); + + mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_TIMEOUT), TIMEOUT_MS); + + mAction = ACTION_INIT; + + nextStep(); + + return true; + } + + /** + * Called to execute next step in state machine + */ + void nextStep() { + if (mAction == ACTION_INIT) { + nextStepInit(); + } else if (mAction == ACTION_CONNECT) { + nextStepConnect(); + } else { + nextStepDisconnect(); + } + } + + /* + * Enables bluetooth and gets the profile proxies + */ + void nextStepInit() { + switch (mState) { + case STATE_INIT: + if (mA2dp == null || mHeadset == null || mInput == null) { + mState = STATE_WAITING_FOR_PROXIES; + if (!getProfileProxys()) { + complete(false); + } + break; + } + // fall-through + case STATE_WAITING_FOR_PROXIES: + mState = STATE_INIT_COMPLETE; + // Check connected devices and see if we need to disconnect + synchronized(mLock) { + if (mTransport == BluetoothDevice.TRANSPORT_LE) { + if (mInput.getConnectedDevices().contains(mDevice)) { + Log.i(TAG, "ACTION_DISCONNECT addr=" + mDevice + " name=" + mName); + mAction = ACTION_DISCONNECT; + } else { + Log.i(TAG, "ACTION_CONNECT addr=" + mDevice + " name=" + mName); + mAction = ACTION_CONNECT; + } + } else { + if (mA2dp.getConnectedDevices().contains(mDevice) || + mHeadset.getConnectedDevices().contains(mDevice)) { + Log.i(TAG, "ACTION_DISCONNECT addr=" + mDevice + " name=" + mName); + mAction = ACTION_DISCONNECT; + } else { + Log.i(TAG, "ACTION_CONNECT addr=" + mDevice + " name=" + mName); + mAction = ACTION_CONNECT; + } + } + } + nextStep(); + } + + } + + void nextStepDisconnect() { + switch (mState) { + case STATE_INIT_COMPLETE: + mState = STATE_DISCONNECTING; + synchronized (mLock) { + if (mTransport == BluetoothDevice.TRANSPORT_LE) { + if (mInput.getConnectionState(mDevice) + != BluetoothProfile.STATE_DISCONNECTED) { + mHidResult = RESULT_PENDING; + mInput.disconnect(mDevice); + toast(getToastString(R.string.disconnecting_peripheral)); + break; + } else { + mHidResult = RESULT_DISCONNECTED; + } + } else { + if (mHeadset.getConnectionState(mDevice) + != BluetoothProfile.STATE_DISCONNECTED) { + mHfpResult = RESULT_PENDING; + mHeadset.disconnect(mDevice); + } else { + mHfpResult = RESULT_DISCONNECTED; + } + if (mA2dp.getConnectionState(mDevice) + != BluetoothProfile.STATE_DISCONNECTED) { + mA2dpResult = RESULT_PENDING; + mA2dp.disconnect(mDevice); + } else { + mA2dpResult = RESULT_DISCONNECTED; + } + if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) { + toast(getToastString(R.string.disconnecting_peripheral)); + break; + } + } + } + // fall-through + case STATE_DISCONNECTING: + if (mTransport == BluetoothDevice.TRANSPORT_LE) { + if (mHidResult == RESULT_DISCONNECTED) { + toast(getToastString(R.string.disconnected_peripheral)); + complete(false); + } + + break; + } else { + if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) { + // still disconnecting + break; + } + if (mA2dpResult == RESULT_DISCONNECTED && mHfpResult == RESULT_DISCONNECTED) { + toast(getToastString(R.string.disconnected_peripheral)); + } + complete(false); + break; + } + + } + + } + + private String getToastString(int resid) { + return mContext.getString(resid, mName != null ? mName : R.string.device); + } + + boolean getProfileProxys() { + + if (mTransport == BluetoothDevice.TRANSPORT_LE) { + if (!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.INPUT_DEVICE)) + return false; + } else { + if(!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.HEADSET)) + return false; + + if(!mBluetoothAdapter.getProfileProxy(mContext, this, BluetoothProfile.A2DP)) + return false; + } + + return true; + } + + void nextStepConnect() { + switch (mState) { + case STATE_INIT_COMPLETE: + + if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) { + requestPairConfirmation(); + mState = STATE_WAITING_FOR_BOND_CONFIRMATION; + break; + } + + if (mTransport == BluetoothDevice.TRANSPORT_LE) { + if (mDevice.getBondState() != BluetoothDevice.BOND_NONE) { + mDevice.removeBond(); + requestPairConfirmation(); + mState = STATE_WAITING_FOR_BOND_CONFIRMATION; + break; + } + } + // fall-through + case STATE_WAITING_FOR_BOND_CONFIRMATION: + if (mDevice.getBondState() != BluetoothDevice.BOND_BONDED) { + startBonding(); + break; + } + // fall-through + case STATE_BONDING: + // Bluetooth Profile service will correctly serialize + // HFP then A2DP connect + mState = STATE_CONNECTING; + synchronized (mLock) { + if (mTransport == BluetoothDevice.TRANSPORT_LE) { + if (mInput.getConnectionState(mDevice) + != BluetoothProfile.STATE_CONNECTED) { + mHidResult = RESULT_PENDING; + mInput.connect(mDevice); + toast(getToastString(R.string.connecting_peripheral)); + break; + } else { + mHidResult = RESULT_CONNECTED; + } + } else { + if (mHeadset.getConnectionState(mDevice) != + BluetoothProfile.STATE_CONNECTED) { + mHfpResult = RESULT_PENDING; + mHeadset.connect(mDevice); + } else { + mHfpResult = RESULT_CONNECTED; + } + if (mA2dp.getConnectionState(mDevice) != BluetoothProfile.STATE_CONNECTED) { + mA2dpResult = RESULT_PENDING; + mA2dp.connect(mDevice); + } else { + mA2dpResult = RESULT_CONNECTED; + } + if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) { + toast(getToastString(R.string.connecting_peripheral)); + break; + } + } + } + // fall-through + case STATE_CONNECTING: + if (mTransport == BluetoothDevice.TRANSPORT_LE) { + if (mHidResult == RESULT_PENDING) { + break; + } else if (mHidResult == RESULT_CONNECTED) { + toast(getToastString(R.string.connected_peripheral)); + mDevice.setAlias(mName); + complete(true); + } else { + toast (getToastString(R.string.connect_peripheral_failed)); + complete(false); + } + } else { + if (mA2dpResult == RESULT_PENDING || mHfpResult == RESULT_PENDING) { + // another connection type still pending + break; + } + if (mA2dpResult == RESULT_CONNECTED || mHfpResult == RESULT_CONNECTED) { + // we'll take either as success + toast(getToastString(R.string.connected_peripheral)); + if (mA2dpResult == RESULT_CONNECTED) startTheMusic(); + mDevice.setAlias(mName); + complete(true); + } else { + toast (getToastString(R.string.connect_peripheral_failed)); + complete(false); + } + } + break; + } + } + + void startBonding() { + mState = STATE_BONDING; + toast(getToastString(R.string.pairing_peripheral)); + if (!mDevice.createBond(mTransport)) { + toast(getToastString(R.string.pairing_peripheral_failed)); + complete(false); + } + } + + void handleIntent(Intent intent) { + String action = intent.getAction(); + // Everything requires the device to match... + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (!mDevice.equals(device)) return; + + if (ACTION_ALLOW_CONNECT.equals(action)) { + nextStepConnect(); + } else if (ACTION_DENY_CONNECT.equals(action)) { + complete(false); + } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action) + && mState == STATE_BONDING) { + int bond = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, + BluetoothAdapter.ERROR); + if (bond == BluetoothDevice.BOND_BONDED) { + nextStepConnect(); + } else if (bond == BluetoothDevice.BOND_NONE) { + toast(getToastString(R.string.pairing_peripheral_failed)); + complete(false); + } + } else if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(action) && + (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) { + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR); + if (state == BluetoothProfile.STATE_CONNECTED) { + mHfpResult = RESULT_CONNECTED; + nextStep(); + } else if (state == BluetoothProfile.STATE_DISCONNECTED) { + mHfpResult = RESULT_DISCONNECTED; + nextStep(); + } + } else if (BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action) && + (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) { + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR); + if (state == BluetoothProfile.STATE_CONNECTED) { + mA2dpResult = RESULT_CONNECTED; + nextStep(); + } else if (state == BluetoothProfile.STATE_DISCONNECTED) { + mA2dpResult = RESULT_DISCONNECTED; + nextStep(); + } + } else if (BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED.equals(action) && + (mState == STATE_CONNECTING || mState == STATE_DISCONNECTING)) { + int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR); + if (state == BluetoothProfile.STATE_CONNECTED) { + mHidResult = RESULT_CONNECTED; + nextStep(); + } else if (state == BluetoothProfile.STATE_DISCONNECTED) { + mHidResult = RESULT_DISCONNECTED; + nextStep(); + } + } + } + + void complete(boolean connected) { + if (DBG) Log.d(TAG, "complete()"); + mState = STATE_COMPLETE; + mContext.unregisterReceiver(mReceiver); + mHandler.removeMessages(MSG_TIMEOUT); + synchronized (mLock) { + if (mA2dp != null) { + mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, mA2dp); + } + if (mHeadset != null) { + mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mHeadset); + } + + if (mInput != null) { + mBluetoothAdapter.closeProfileProxy(BluetoothProfile.INPUT_DEVICE, mInput); + } + + mA2dp = null; + mHeadset = null; + mInput = null; + } + mCallback.onBluetoothPeripheralHandoverComplete(connected); + } + + void toast(CharSequence text) { + Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show(); + } + + void startTheMusic() { + MediaSessionLegacyHelper helper = MediaSessionLegacyHelper.getHelper(mContext); + if (helper != null) { + KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_MEDIA_PLAY); + helper.sendMediaButtonEvent(keyEvent, false); + keyEvent = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY); + helper.sendMediaButtonEvent(keyEvent, false); + } else { + Log.w(TAG, "Unable to send media key event"); + } + } + + void requestPairConfirmation() { + Intent dialogIntent = new Intent(mContext, ConfirmConnectActivity.class); + dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + dialogIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + + mContext.startActivity(dialogIntent); + } + + final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_TIMEOUT: + if (mState == STATE_COMPLETE) return; + Log.i(TAG, "Timeout completing BT handover"); + complete(false); + break; + case MSG_NEXT_STEP: + nextStep(); + break; + } + } + }; + + final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + handleIntent(intent); + } + }; + + static void checkMainThread() { + if (Looper.myLooper() != Looper.getMainLooper()) { + throw new IllegalThreadStateException("must be called on main thread"); + } + } + + @Override + public void onServiceConnected(int profile, BluetoothProfile proxy) { + synchronized (mLock) { + switch (profile) { + case BluetoothProfile.HEADSET: + mHeadset = (BluetoothHeadset) proxy; + if (mA2dp != null) { + mHandler.sendEmptyMessage(MSG_NEXT_STEP); + } + break; + case BluetoothProfile.A2DP: + mA2dp = (BluetoothA2dp) proxy; + if (mHeadset != null) { + mHandler.sendEmptyMessage(MSG_NEXT_STEP); + } + break; + case BluetoothProfile.INPUT_DEVICE: + mInput = (BluetoothInputDevice) proxy; + if (mInput != null) { + mHandler.sendEmptyMessage(MSG_NEXT_STEP); + } + break; + } + } + } + + @Override + public void onServiceDisconnected(int profile) { + // We can ignore these + } +} diff --git a/NfcSony/src/com/android/nfc/handover/ConfirmConnectActivity.java b/NfcSony/src/com/android/nfc/handover/ConfirmConnectActivity.java new file mode 100644 index 0000000..4a7cd75 --- /dev/null +++ b/NfcSony/src/com/android/nfc/handover/ConfirmConnectActivity.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2012 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.nfc.handover; + +import android.app.Activity; +import android.app.AlertDialog; +import android.bluetooth.BluetoothDevice; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.os.Bundle; + +import com.android.nfc.R; + +public class ConfirmConnectActivity extends Activity { + BluetoothDevice mDevice; + AlertDialog mAlert = null; + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + AlertDialog.Builder builder = new AlertDialog.Builder(this, + AlertDialog.THEME_DEVICE_DEFAULT_LIGHT); + Intent launchIntent = getIntent(); + mDevice = launchIntent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (mDevice == null) finish(); + Resources res = getResources(); + String deviceName = mDevice.getName() != null ? mDevice.getName() : ""; + String confirmString = String.format(res.getString(R.string.confirm_pairing), deviceName); + builder.setMessage(confirmString) + .setCancelable(false) + .setPositiveButton(res.getString(R.string.pair_yes), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Intent allowIntent = new Intent(BluetoothPeripheralHandover.ACTION_ALLOW_CONNECT); + allowIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + sendBroadcast(allowIntent); + ConfirmConnectActivity.this.mAlert = null; + ConfirmConnectActivity.this.finish(); + } + }) + .setNegativeButton(res.getString(R.string.pair_no), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + Intent denyIntent = new Intent(BluetoothPeripheralHandover.ACTION_DENY_CONNECT); + denyIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + sendBroadcast(denyIntent); + ConfirmConnectActivity.this.mAlert = null; + ConfirmConnectActivity.this.finish(); + } + }); + mAlert = builder.create(); + mAlert.show(); + } + + @Override + protected void onStop() { + super.onStop(); + if (mAlert != null) { + mAlert.dismiss(); + Intent denyIntent = new Intent(BluetoothPeripheralHandover.ACTION_DENY_CONNECT); + denyIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); + sendBroadcast(denyIntent); + } + } +} diff --git a/NfcSony/src/com/android/nfc/handover/HandoverClient.java b/NfcSony/src/com/android/nfc/handover/HandoverClient.java new file mode 100644 index 0000000..20e4ef6 --- /dev/null +++ b/NfcSony/src/com/android/nfc/handover/HandoverClient.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2012 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.nfc.handover; + +import android.nfc.FormatException; +import android.nfc.NdefMessage; +import android.util.Log; + +import com.android.nfc.LlcpException; +import com.android.nfc.NfcService; +import com.android.nfc.DeviceHost.LlcpSocket; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +public final class HandoverClient { + private static final String TAG = "HandoverClient"; + private static final int MIU = 128; + private static final boolean DBG = false; + + private static final int DISCONNECTED = 0; + private static final int CONNECTING = 1; + private static final int CONNECTED = 2; + + private static final Object mLock = new Object(); + + // Variables below synchronized on mLock + LlcpSocket mSocket; + int mState; + + public void connect() throws IOException { + synchronized (mLock) { + if (mState != DISCONNECTED) { + throw new IOException("Socket in use."); + } + mState = CONNECTING; + } + NfcService service = NfcService.getInstance(); + LlcpSocket sock = null; + try { + sock = service.createLlcpSocket(0, MIU, 1, 1024); + } catch (LlcpException e) { + synchronized (mLock) { + mState = DISCONNECTED; + } + throw new IOException("Could not create socket"); + } + try { + if (DBG) Log.d(TAG, "about to connect to service " + + HandoverServer.HANDOVER_SERVICE_NAME); + sock.connectToService(HandoverServer.HANDOVER_SERVICE_NAME); + } catch (IOException e) { + if (sock != null) { + try { + sock.close(); + } catch (IOException e2) { + // Ignore + } + } + synchronized (mLock) { + mState = DISCONNECTED; + } + throw new IOException("Could not connect to handover service"); + } + synchronized (mLock) { + mSocket = sock; + mState = CONNECTED; + } + } + + public void close() { + synchronized (mLock) { + if (mSocket != null) { + try { + mSocket.close(); + } catch (IOException e) { + // Ignore + } + mSocket = null; + } + mState = DISCONNECTED; + } + } + public NdefMessage sendHandoverRequest(NdefMessage msg) throws IOException { + if (msg == null) return null; + + LlcpSocket sock = null; + synchronized (mLock) { + if (mState != CONNECTED) { + throw new IOException("Socket not connected"); + } + sock = mSocket; + } + int offset = 0; + byte[] buffer = msg.toByteArray(); + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + + try { + int remoteMiu = sock.getRemoteMiu(); + if (DBG) Log.d(TAG, "about to send a " + buffer.length + " byte message"); + while (offset < buffer.length) { + int length = Math.min(buffer.length - offset, remoteMiu); + byte[] tmpBuffer = Arrays.copyOfRange(buffer, offset, offset+length); + if (DBG) Log.d(TAG, "about to send a " + length + " byte packet"); + sock.send(tmpBuffer); + offset += length; + } + + // Now, try to read back the handover response + byte[] partial = new byte[sock.getLocalMiu()]; + NdefMessage handoverSelectMsg = null; + while (true) { + int size = sock.receive(partial); + if (size < 0) { + break; + } + byteStream.write(partial, 0, size); + try { + handoverSelectMsg = new NdefMessage(byteStream.toByteArray()); + // If we get here, message is complete + break; + } catch (FormatException e) { + // Ignore, and try to fetch more bytes + } + } + return handoverSelectMsg; + } catch (IOException e) { + if (DBG) Log.d(TAG, "couldn't connect to handover service"); + } finally { + if (sock != null) { + try { + if (DBG) Log.d(TAG, "about to close"); + sock.close(); + } catch (IOException e) { + // Ignore + } + } + try { + byteStream.close(); + } catch (IOException e) { + // Ignore + } + } + return null; + } +} diff --git a/NfcSony/src/com/android/nfc/handover/HandoverDataParser.java b/NfcSony/src/com/android/nfc/handover/HandoverDataParser.java new file mode 100644 index 0000000..691488f --- /dev/null +++ b/NfcSony/src/com/android/nfc/handover/HandoverDataParser.java @@ -0,0 +1,473 @@ +/* + * Copyright (C) 2012 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.nfc.handover; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Random; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.Intent; +import android.nfc.FormatException; +import android.nfc.NdefMessage; +import android.nfc.NdefRecord; +import android.os.UserHandle; +import android.util.Log; + +/** + * Manages handover of NFC to other technologies. + */ +public class HandoverDataParser { + private static final String TAG = "NfcHandover"; + private static final boolean DBG = false; + + private static final byte[] TYPE_BT_OOB = "application/vnd.bluetooth.ep.oob" + .getBytes(StandardCharsets.US_ASCII); + private static final byte[] TYPE_BLE_OOB = "application/vnd.bluetooth.le.oob" + .getBytes(StandardCharsets.US_ASCII); + + private static final byte[] TYPE_NOKIA = "nokia.com:bt".getBytes(StandardCharsets.US_ASCII); + + private static final byte[] RTD_COLLISION_RESOLUTION = {0x63, 0x72}; // "cr"; + + private static final int CARRIER_POWER_STATE_INACTIVE = 0; + private static final int CARRIER_POWER_STATE_ACTIVE = 1; + private static final int CARRIER_POWER_STATE_ACTIVATING = 2; + private static final int CARRIER_POWER_STATE_UNKNOWN = 3; + + private static final int BT_HANDOVER_TYPE_MAC = 0x1B; + private static final int BT_HANDOVER_TYPE_LE_ROLE = 0x1C; + private static final int BT_HANDOVER_TYPE_LONG_LOCAL_NAME = 0x09; + private static final int BT_HANDOVER_TYPE_SHORT_LOCAL_NAME = 0x08; + public static final int BT_HANDOVER_LE_ROLE_CENTRAL_ONLY = 0x01; + + private final BluetoothAdapter mBluetoothAdapter; + + private final Object mLock = new Object(); + // Variables below synchronized on mLock + + private String mLocalBluetoothAddress; + + public static class BluetoothHandoverData { + public boolean valid = false; + public BluetoothDevice device; + public String name; + public boolean carrierActivating = false; + public int transport = BluetoothDevice.TRANSPORT_AUTO; + } + + public static class IncomingHandoverData { + public final NdefMessage handoverSelect; + public final BluetoothHandoverData handoverData; + + public IncomingHandoverData(NdefMessage handoverSelect, + BluetoothHandoverData handoverData) { + this.handoverSelect = handoverSelect; + this.handoverData = handoverData; + } + } + + public HandoverDataParser() { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + } + + static NdefRecord createCollisionRecord() { + byte[] random = new byte[2]; + new Random().nextBytes(random); + return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, RTD_COLLISION_RESOLUTION, null, random); + } + + NdefRecord createBluetoothAlternateCarrierRecord(boolean activating) { + byte[] payload = new byte[4]; + payload[0] = (byte) (activating ? CARRIER_POWER_STATE_ACTIVATING : + CARRIER_POWER_STATE_ACTIVE); // Carrier Power State: Activating or active + payload[1] = 1; // length of carrier data reference + payload[2] = 'b'; // carrier data reference: ID for Bluetooth OOB data record + payload[3] = 0; // Auxiliary data reference count + return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_ALTERNATIVE_CARRIER, null, + payload); + } + + NdefRecord createBluetoothOobDataRecord() { + byte[] payload = new byte[8]; + // Note: this field should be little-endian per the BTSSP spec + // The Android 4.1 implementation used big-endian order here. + // No single Android implementation has ever interpreted this + // length field when parsing this record though. + payload[0] = (byte) (payload.length & 0xFF); + payload[1] = (byte) ((payload.length >> 8) & 0xFF); + + synchronized (mLock) { + if (mLocalBluetoothAddress == null) { + mLocalBluetoothAddress = mBluetoothAdapter.getAddress(); + } + + byte[] addressBytes = addressToReverseBytes(mLocalBluetoothAddress); + System.arraycopy(addressBytes, 0, payload, 2, 6); + } + + return new NdefRecord(NdefRecord.TNF_MIME_MEDIA, TYPE_BT_OOB, new byte[]{'b'}, payload); + } + + public boolean isHandoverSupported() { + return (mBluetoothAdapter != null); + } + + public NdefMessage createHandoverRequestMessage() { + if (mBluetoothAdapter == null) { + return null; + } + + NdefRecord[] dataRecords = new NdefRecord[] { + createBluetoothOobDataRecord() + }; + return new NdefMessage( + createHandoverRequestRecord(), + dataRecords); + } + + NdefMessage createBluetoothHandoverSelectMessage(boolean activating) { + return new NdefMessage(createHandoverSelectRecord( + createBluetoothAlternateCarrierRecord(activating)), + createBluetoothOobDataRecord()); + } + + NdefRecord createHandoverSelectRecord(NdefRecord alternateCarrier) { + NdefMessage nestedMessage = new NdefMessage(alternateCarrier); + byte[] nestedPayload = nestedMessage.toByteArray(); + + ByteBuffer payload = ByteBuffer.allocate(nestedPayload.length + 1); + payload.put((byte)0x12); // connection handover v1.2 + payload.put(nestedPayload); + + byte[] payloadBytes = new byte[payload.position()]; + payload.position(0); + payload.get(payloadBytes); + return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_SELECT, null, + payloadBytes); + } + + NdefRecord createHandoverRequestRecord() { + NdefRecord[] messages = new NdefRecord[] { + createBluetoothAlternateCarrierRecord(false) + }; + + NdefMessage nestedMessage = new NdefMessage(createCollisionRecord(), messages); + + byte[] nestedPayload = nestedMessage.toByteArray(); + + ByteBuffer payload = ByteBuffer.allocate(nestedPayload.length + 1); + payload.put((byte) 0x12); // connection handover v1.2 + payload.put(nestedMessage.toByteArray()); + + byte[] payloadBytes = new byte[payload.position()]; + payload.position(0); + payload.get(payloadBytes); + return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_HANDOVER_REQUEST, null, + payloadBytes); + } + + /** + * Returns null if message is not a Handover Request, + * returns the IncomingHandoverData (Hs + parsed data) if it is. + */ + public IncomingHandoverData getIncomingHandoverData(NdefMessage handoverRequest) { + if (handoverRequest == null) return null; + if (mBluetoothAdapter == null) return null; + + if (DBG) Log.d(TAG, "getIncomingHandoverData():" + handoverRequest.toString()); + + NdefRecord handoverRequestRecord = handoverRequest.getRecords()[0]; + if (handoverRequestRecord.getTnf() != NdefRecord.TNF_WELL_KNOWN) { + return null; + } + + if (!Arrays.equals(handoverRequestRecord.getType(), NdefRecord.RTD_HANDOVER_REQUEST)) { + return null; + } + + // we have a handover request, look for BT OOB record + BluetoothHandoverData bluetoothData = null; + for (NdefRecord dataRecord : handoverRequest.getRecords()) { + if (dataRecord.getTnf() == NdefRecord.TNF_MIME_MEDIA) { + if (Arrays.equals(dataRecord.getType(), TYPE_BT_OOB)) { + bluetoothData = parseBtOob(ByteBuffer.wrap(dataRecord.getPayload())); + } + } + } + + NdefMessage hs = tryBluetoothHandoverRequest(bluetoothData); + if (hs != null) { + return new IncomingHandoverData(hs, bluetoothData); + } + + return null; + } + + public BluetoothHandoverData getOutgoingHandoverData(NdefMessage handoverSelect) { + return parseBluetooth(handoverSelect); + } + + private NdefMessage tryBluetoothHandoverRequest(BluetoothHandoverData bluetoothData) { + NdefMessage selectMessage = null; + if (bluetoothData != null) { + // Note: there could be a race where we conclude + // that Bluetooth is already enabled, and shortly + // after the user turns it off. That will cause + // the transfer to fail, but there's nothing + // much we can do about it anyway. It shouldn't + // be common for the user to be changing BT settings + // while waiting to receive a picture. + boolean bluetoothActivating = !mBluetoothAdapter.isEnabled(); + + // return BT OOB record so they can perform handover + selectMessage = (createBluetoothHandoverSelectMessage(bluetoothActivating)); + if (DBG) Log.d(TAG, "Waiting for incoming transfer, [" + + bluetoothData.device.getAddress() + "]->[" + mLocalBluetoothAddress + "]"); + } + + return selectMessage; + } + + + + boolean isCarrierActivating(NdefRecord handoverRec, byte[] carrierId) { + byte[] payload = handoverRec.getPayload(); + if (payload == null || payload.length <= 1) return false; + // Skip version + byte[] payloadNdef = new byte[payload.length - 1]; + System.arraycopy(payload, 1, payloadNdef, 0, payload.length - 1); + NdefMessage msg; + try { + msg = new NdefMessage(payloadNdef); + } catch (FormatException e) { + return false; + } + + for (NdefRecord alt : msg.getRecords()) { + byte[] acPayload = alt.getPayload(); + if (acPayload != null) { + ByteBuffer buf = ByteBuffer.wrap(acPayload); + int cps = buf.get() & 0x03; // Carrier Power State is in lower 2 bits + int carrierRefLength = buf.get() & 0xFF; + if (carrierRefLength != carrierId.length) return false; + + byte[] carrierRefId = new byte[carrierRefLength]; + buf.get(carrierRefId); + if (Arrays.equals(carrierRefId, carrierId)) { + // Found match, returning whether power state is activating + return (cps == CARRIER_POWER_STATE_ACTIVATING); + } + } + } + + return true; + } + + BluetoothHandoverData parseBluetoothHandoverSelect(NdefMessage m) { + // TODO we could parse this a lot more strictly; right now + // we just search for a BT OOB record, and try to cross-reference + // the carrier state inside the 'hs' payload. + for (NdefRecord oob : m.getRecords()) { + if (oob.getTnf() == NdefRecord.TNF_MIME_MEDIA && + Arrays.equals(oob.getType(), TYPE_BT_OOB)) { + BluetoothHandoverData data = parseBtOob(ByteBuffer.wrap(oob.getPayload())); + if (data != null && isCarrierActivating(m.getRecords()[0], oob.getId())) { + data.carrierActivating = true; + } + return data; + } + + if (oob.getTnf() == NdefRecord.TNF_MIME_MEDIA && + Arrays.equals(oob.getType(), TYPE_BLE_OOB)) { + return parseBleOob(ByteBuffer.wrap(oob.getPayload())); + } + } + + return null; + } + + public BluetoothHandoverData parseBluetooth(NdefMessage m) { + NdefRecord r = m.getRecords()[0]; + short tnf = r.getTnf(); + byte[] type = r.getType(); + + // Check for BT OOB record + if (r.getTnf() == NdefRecord.TNF_MIME_MEDIA && Arrays.equals(r.getType(), TYPE_BT_OOB)) { + return parseBtOob(ByteBuffer.wrap(r.getPayload())); + } + + // Check for BLE OOB record + if (r.getTnf() == NdefRecord.TNF_MIME_MEDIA && Arrays.equals(r.getType(), TYPE_BLE_OOB)) { + return parseBleOob(ByteBuffer.wrap(r.getPayload())); + } + + // Check for Handover Select, followed by a BT OOB record + if (tnf == NdefRecord.TNF_WELL_KNOWN && + Arrays.equals(type, NdefRecord.RTD_HANDOVER_SELECT)) { + return parseBluetoothHandoverSelect(m); + } + + // Check for Nokia BT record, found on some Nokia BH-505 Headsets + if (tnf == NdefRecord.TNF_EXTERNAL_TYPE && Arrays.equals(type, TYPE_NOKIA)) { + return parseNokia(ByteBuffer.wrap(r.getPayload())); + } + + return null; + } + + BluetoothHandoverData parseNokia(ByteBuffer payload) { + BluetoothHandoverData result = new BluetoothHandoverData(); + result.valid = false; + + try { + payload.position(1); + byte[] address = new byte[6]; + payload.get(address); + result.device = mBluetoothAdapter.getRemoteDevice(address); + result.valid = true; + payload.position(14); + int nameLength = payload.get(); + byte[] nameBytes = new byte[nameLength]; + payload.get(nameBytes); + result.name = new String(nameBytes, StandardCharsets.UTF_8); + } catch (IllegalArgumentException e) { + Log.i(TAG, "nokia: invalid BT address"); + } catch (BufferUnderflowException e) { + Log.i(TAG, "nokia: payload shorter than expected"); + } + if (result.valid && result.name == null) result.name = ""; + return result; + } + + BluetoothHandoverData parseBtOob(ByteBuffer payload) { + BluetoothHandoverData result = new BluetoothHandoverData(); + result.valid = false; + + try { + payload.position(2); // length + byte[] address = parseMacFromBluetoothRecord(payload); + result.device = mBluetoothAdapter.getRemoteDevice(address); + result.valid = true; + + while (payload.remaining() > 0) { + byte[] nameBytes; + int len = payload.get(); + int type = payload.get(); + switch (type) { + case BT_HANDOVER_TYPE_SHORT_LOCAL_NAME: + nameBytes = new byte[len - 1]; + payload.get(nameBytes); + result.name = new String(nameBytes, StandardCharsets.UTF_8); + break; + case BT_HANDOVER_TYPE_LONG_LOCAL_NAME: + if (result.name != null) break; // prefer short name + nameBytes = new byte[len - 1]; + payload.get(nameBytes); + result.name = new String(nameBytes, StandardCharsets.UTF_8); + break; + default: + payload.position(payload.position() + len - 1); + break; + } + } + } catch (IllegalArgumentException e) { + Log.i(TAG, "BT OOB: invalid BT address"); + } catch (BufferUnderflowException e) { + Log.i(TAG, "BT OOB: payload shorter than expected"); + } + if (result.valid && result.name == null) result.name = ""; + return result; + } + + BluetoothHandoverData parseBleOob(ByteBuffer payload) { + BluetoothHandoverData result = new BluetoothHandoverData(); + result.valid = false; + result.transport = BluetoothDevice.TRANSPORT_LE; + + try { + + while (payload.remaining() > 0) { + byte[] nameBytes; + int len = payload.get(); + int type = payload.get(); + switch (type) { + case BT_HANDOVER_TYPE_MAC: // mac address + byte[] address = parseMacFromBluetoothRecord(payload); + payload.position(payload.position() + 1); // advance over random byte + result.device = mBluetoothAdapter.getRemoteDevice(address); + result.valid = true; + break; + case BT_HANDOVER_TYPE_LE_ROLE: + byte role = payload.get(); + if (role == BT_HANDOVER_LE_ROLE_CENTRAL_ONLY) { + // only central role supported, can't pair + result.valid = false; + return result; + } + break; + case BT_HANDOVER_TYPE_LONG_LOCAL_NAME: + nameBytes = new byte[len - 1]; + payload.get(nameBytes); + result.name = new String(nameBytes, StandardCharsets.UTF_8); + break; + default: + payload.position(payload.position() + len - 1); + break; + } + } + } catch (IllegalArgumentException e) { + Log.i(TAG, "BT OOB: invalid BT address"); + } catch (BufferUnderflowException e) { + Log.i(TAG, "BT OOB: payload shorter than expected"); + } + if (result.valid && result.name == null) result.name = ""; + return result; + } + + private byte[] parseMacFromBluetoothRecord(ByteBuffer payload) { + byte[] address = new byte[6]; + payload.get(address); + // ByteBuffer.order(LITTLE_ENDIAN) doesn't work for + // ByteBuffer.get(byte[]), so manually swap order + for (int i = 0; i < 3; i++) { + byte temp = address[i]; + address[i] = address[5 - i]; + address[5 - i] = temp; + } + return address; + } + + static byte[] addressToReverseBytes(String address) { + String[] split = address.split(":"); + byte[] result = new byte[split.length]; + + for (int i = 0; i < split.length; i++) { + // need to parse as int because parseByte() expects a signed byte + result[split.length - 1 - i] = (byte)Integer.parseInt(split[i], 16); + } + + return result; + } +} + diff --git a/NfcSony/src/com/android/nfc/handover/HandoverServer.java b/NfcSony/src/com/android/nfc/handover/HandoverServer.java new file mode 100644 index 0000000..23ac1d9 --- /dev/null +++ b/NfcSony/src/com/android/nfc/handover/HandoverServer.java @@ -0,0 +1,263 @@ +/* + * Copyright (C) 2012 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.nfc.handover; + +import com.android.nfc.DeviceHost.LlcpServerSocket; +import com.android.nfc.DeviceHost.LlcpSocket; +import com.android.nfc.LlcpException; +import com.android.nfc.NfcService; +import com.android.nfc.beam.BeamManager; +import com.android.nfc.beam.BeamReceiveService; +import com.android.nfc.beam.BeamTransferRecord; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.content.Intent; +import android.nfc.FormatException; +import android.nfc.NdefMessage; +import android.os.UserHandle; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; + +public final class HandoverServer { + static final String HANDOVER_SERVICE_NAME = "urn:nfc:sn:handover"; + static final String TAG = "HandoverServer"; + static final Boolean DBG = false; + + static final int MIU = 128; + + final HandoverDataParser mHandoverDataParser; + final int mSap; + final Callback mCallback; + private final Context mContext; + + ServerThread mServerThread = null; + boolean mServerRunning = false; + + public interface Callback { + void onHandoverRequestReceived(); + void onHandoverBusy(); + } + + public HandoverServer(Context context, int sap, HandoverDataParser manager, Callback callback) { + mContext = context; + mSap = sap; + mHandoverDataParser = manager; + mCallback = callback; + } + + public synchronized void start() { + if (mServerThread == null) { + mServerThread = new ServerThread(); + mServerThread.start(); + mServerRunning = true; + } + } + + public synchronized void stop() { + if (mServerThread != null) { + mServerThread.shutdown(); + mServerThread = null; + mServerRunning = false; + } + } + + private class ServerThread extends Thread { + private boolean mThreadRunning = true; + LlcpServerSocket mServerSocket; + + @Override + public void run() { + boolean threadRunning; + synchronized (HandoverServer.this) { + threadRunning = mThreadRunning; + } + + while (threadRunning) { + try { + synchronized (HandoverServer.this) { + mServerSocket = NfcService.getInstance().createLlcpServerSocket(mSap, + HANDOVER_SERVICE_NAME, MIU, 1, 1024); + } + if (mServerSocket == null) { + if (DBG) Log.d(TAG, "failed to create LLCP service socket"); + return; + } + if (DBG) Log.d(TAG, "created LLCP service socket"); + synchronized (HandoverServer.this) { + threadRunning = mThreadRunning; + } + + while (threadRunning) { + LlcpServerSocket serverSocket; + synchronized (HandoverServer.this) { + serverSocket = mServerSocket; + } + + if (serverSocket == null) { + if (DBG) Log.d(TAG, "Server socket shut down."); + return; + } + if (DBG) Log.d(TAG, "about to accept"); + LlcpSocket communicationSocket = serverSocket.accept(); + if (DBG) Log.d(TAG, "accept returned " + communicationSocket); + if (communicationSocket != null) { + new ConnectionThread(communicationSocket).start(); + } + + synchronized (HandoverServer.this) { + threadRunning = mThreadRunning; + } + } + if (DBG) Log.d(TAG, "stop running"); + } catch (LlcpException e) { + Log.e(TAG, "llcp error", e); + } catch (IOException e) { + Log.e(TAG, "IO error", e); + } finally { + synchronized (HandoverServer.this) { + if (mServerSocket != null) { + if (DBG) Log.d(TAG, "about to close"); + try { + mServerSocket.close(); + } catch (IOException e) { + // ignore + } + mServerSocket = null; + } + } + } + + synchronized (HandoverServer.this) { + threadRunning = mThreadRunning; + } + } + } + + public void shutdown() { + synchronized (HandoverServer.this) { + mThreadRunning = false; + if (mServerSocket != null) { + try { + mServerSocket.close(); + } catch (IOException e) { + // ignore + } + mServerSocket = null; + } + } + } + } + + private class ConnectionThread extends Thread { + private final LlcpSocket mSock; + + ConnectionThread(LlcpSocket socket) { + super(TAG); + mSock = socket; + } + + @Override + public void run() { + if (DBG) Log.d(TAG, "starting connection thread"); + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + + try { + boolean running; + synchronized (HandoverServer.this) { + running = mServerRunning; + } + + byte[] partial = new byte[mSock.getLocalMiu()]; + + NdefMessage handoverRequestMsg = null; + while (running) { + int size = mSock.receive(partial); + if (size < 0) { + break; + } + byteStream.write(partial, 0, size); + // 1) Try to parse a handover request message from bytes received so far + try { + handoverRequestMsg = new NdefMessage(byteStream.toByteArray()); + } catch (FormatException e) { + // Ignore, and try to fetch more bytes + } + + if (handoverRequestMsg != null) { + BeamManager beamManager = BeamManager.getInstance(); + + if (beamManager.isBeamInProgress()) { + mCallback.onHandoverBusy(); + break; + } + + // 2) convert to handover response + HandoverDataParser.IncomingHandoverData handoverData + = mHandoverDataParser.getIncomingHandoverData(handoverRequestMsg); + if (handoverData == null) { + Log.e(TAG, "Failed to create handover response"); + break; + } + + // 3) send handover response + int offset = 0; + byte[] buffer = handoverData.handoverSelect.toByteArray(); + int remoteMiu = mSock.getRemoteMiu(); + while (offset < buffer.length) { + int length = Math.min(buffer.length - offset, remoteMiu); + byte[] tmpBuffer = Arrays.copyOfRange(buffer, offset, offset+length); + mSock.send(tmpBuffer); + offset += length; + } + // We're done + mCallback.onHandoverRequestReceived(); + if (!beamManager.startBeamReceive(mContext, handoverData.handoverData)) { + mCallback.onHandoverBusy(); + break; + } + // We can process another handover transfer + byteStream = new ByteArrayOutputStream(); + } + + synchronized (HandoverServer.this) { + running = mServerRunning; + } + } + + } catch (IOException e) { + if (DBG) Log.d(TAG, "IOException"); + } finally { + try { + if (DBG) Log.d(TAG, "about to close"); + mSock.close(); + } catch (IOException e) { + // ignore + } + try { + byteStream.close(); + } catch (IOException e) { + // ignore + } + } + if (DBG) Log.d(TAG, "finished connection thread"); + } + } +} + diff --git a/NfcSony/src/com/android/nfc/handover/PeripheralHandoverService.java b/NfcSony/src/com/android/nfc/handover/PeripheralHandoverService.java new file mode 100644 index 0000000..6c30244 --- /dev/null +++ b/NfcSony/src/com/android/nfc/handover/PeripheralHandoverService.java @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2012 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.nfc.handover; + +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.media.AudioManager; +import android.media.SoundPool; +import android.nfc.NfcAdapter; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.util.Log; + +import com.android.nfc.R; + +public class PeripheralHandoverService extends Service implements BluetoothPeripheralHandover.Callback { + static final String TAG = "PeripheralHandoverService"; + static final boolean DBG = true; + + static final int MSG_PAUSE_POLLING = 0; + + public static final String BUNDLE_TRANSFER = "transfer"; + public static final String EXTRA_PERIPHERAL_DEVICE = "device"; + public static final String EXTRA_PERIPHERAL_NAME = "headsetname"; + public static final String EXTRA_PERIPHERAL_TRANSPORT = "transporttype"; + + // Amount of time to pause polling when connecting to peripherals + private static final int PAUSE_POLLING_TIMEOUT_MS = 35000; + private static final int PAUSE_DELAY_MILLIS = 300; + + private static final Object sLock = new Object(); + + // Variables below only accessed on main thread + final Messenger mMessenger; + + SoundPool mSoundPool; + int mSuccessSound; + int mStartId; + + BluetoothAdapter mBluetoothAdapter; + NfcAdapter mNfcAdapter; + Handler mHandler; + BluetoothPeripheralHandover mBluetoothPeripheralHandover; + boolean mBluetoothHeadsetConnected; + boolean mBluetoothEnabledByNfc; + + class MessageHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_PAUSE_POLLING: + mNfcAdapter.pausePolling(PAUSE_POLLING_TIMEOUT_MS); + break; + } + } + } + + final BroadcastReceiver mBluetoothStatusReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { + handleBluetoothStateChanged(intent); + } + } + }; + + public PeripheralHandoverService() { + mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + mHandler = new MessageHandler(); + mMessenger = new Messenger(mHandler); + mBluetoothHeadsetConnected = false; + mBluetoothEnabledByNfc = false; + mStartId = 0; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + + synchronized (sLock) { + if (mStartId != 0) { + mStartId = startId; + // already running + return START_STICKY; + } + mStartId = startId; + } + + if (intent == null) { + if (DBG) Log.e(TAG, "Intent is null, can't do peripheral handover."); + stopSelf(startId); + return START_NOT_STICKY; + } + + if (doPeripheralHandover(intent.getExtras())) { + return START_STICKY; + } else { + stopSelf(startId); + return START_NOT_STICKY; + } + } + + @Override + public void onCreate() { + super.onCreate(); + + mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0); + mSuccessSound = mSoundPool.load(this, R.raw.end, 1); + mNfcAdapter = NfcAdapter.getDefaultAdapter(getApplicationContext()); + + IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); + registerReceiver(mBluetoothStatusReceiver, filter); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (mSoundPool != null) { + mSoundPool.release(); + } + unregisterReceiver(mBluetoothStatusReceiver); + } + + boolean doPeripheralHandover(Bundle msgData) { + if (mBluetoothPeripheralHandover != null) { + Log.d(TAG, "Ignoring pairing request, existing handover in progress."); + return true; + } + + if (msgData == null) { + return false; + } + + BluetoothDevice device = msgData.getParcelable(EXTRA_PERIPHERAL_DEVICE); + String name = msgData.getString(EXTRA_PERIPHERAL_NAME); + int transport = msgData.getInt(EXTRA_PERIPHERAL_TRANSPORT); + + mBluetoothPeripheralHandover = new BluetoothPeripheralHandover( + this, device, name, transport, this); + + if (transport == BluetoothDevice.TRANSPORT_LE) { + mHandler.sendMessageDelayed( + mHandler.obtainMessage(MSG_PAUSE_POLLING), PAUSE_DELAY_MILLIS); + } + if (mBluetoothAdapter.isEnabled()) { + if (!mBluetoothPeripheralHandover.start()) { + mHandler.removeMessages(MSG_PAUSE_POLLING); + mNfcAdapter.resumePolling(); + } + } else { + // Once BT is enabled, the headset pairing will be started + if (!enableBluetooth()) { + Log.e(TAG, "Error enabling Bluetooth."); + mBluetoothPeripheralHandover = null; + return false; + } + } + + return true; + } + + private void handleBluetoothStateChanged(Intent intent) { + int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, + BluetoothAdapter.ERROR); + if (state == BluetoothAdapter.STATE_ON) { + // If there is a pending device pairing, start it + if (mBluetoothPeripheralHandover != null && + !mBluetoothPeripheralHandover.hasStarted()) { + if (!mBluetoothPeripheralHandover.start()) { + mNfcAdapter.resumePolling(); + } + } + } else if (state == BluetoothAdapter.STATE_OFF) { + mBluetoothEnabledByNfc = false; + mBluetoothHeadsetConnected = false; + } + } + + @Override + public void onBluetoothPeripheralHandoverComplete(boolean connected) { + // Called on the main thread + int transport = mBluetoothPeripheralHandover.mTransport; + mBluetoothPeripheralHandover = null; + mBluetoothHeadsetConnected = connected; + + // resume polling immediately if the connection failed, + // otherwise just wait for polling to come back up after the timeout + // This ensures we don't disconnect if the user left the volantis + // on the tag after pairing completed, which results in automatic + // disconnection + if (transport == BluetoothDevice.TRANSPORT_LE && !connected) { + if (mHandler.hasMessages(MSG_PAUSE_POLLING)) { + mHandler.removeMessages(MSG_PAUSE_POLLING); + } + + // do this unconditionally as the polling could have been paused as we were removing + // the message in the handler. It's a no-op if polling is already enabled. + mNfcAdapter.resumePolling(); + } + disableBluetoothIfNeeded(); + + synchronized (sLock) { + stopSelf(mStartId); + mStartId = 0; + } + } + + + boolean enableBluetooth() { + if (!mBluetoothAdapter.isEnabled()) { + mBluetoothEnabledByNfc = true; + return mBluetoothAdapter.enableNoAutoConnect(); + } + return true; + } + + void disableBluetoothIfNeeded() { + if (!mBluetoothEnabledByNfc) return; + + if (!mBluetoothHeadsetConnected) { + mBluetoothAdapter.disable(); + mBluetoothEnabledByNfc = false; + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public boolean onUnbind(Intent intent) { + // prevent any future callbacks to the client, no rebind call needed. + return false; + } +} diff --git a/NfcSony/src/com/android/nfc/ndefpush/NdefPushClient.java b/NfcSony/src/com/android/nfc/ndefpush/NdefPushClient.java new file mode 100755 index 0000000..842fb98 --- /dev/null +++ b/NfcSony/src/com/android/nfc/ndefpush/NdefPushClient.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2011 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.nfc.ndefpush; + +import com.android.nfc.DeviceHost.LlcpSocket; +import com.android.nfc.LlcpException; +import com.android.nfc.NfcService; + +import android.nfc.NdefMessage; +import android.util.Log; + +import java.io.IOException; +import java.util.Arrays; + +/** + * Simple client to push the local NDEF message to a server on the remote side of an + * LLCP connection, using the Android Ndef Push Protocol. + */ +public class NdefPushClient { + private static final String TAG = "NdefPushClient"; + private static final int MIU = 128; + private static final boolean DBG = true; + + private static final int DISCONNECTED = 0; + private static final int CONNECTING = 1; + private static final int CONNECTED = 2; + + final Object mLock = new Object(); + // Variables below locked on mLock + private int mState = DISCONNECTED; + private LlcpSocket mSocket; + + public void connect() throws IOException { + synchronized (mLock) { + if (mState != DISCONNECTED) { + throw new IOException("Socket still in use."); + } + mState = CONNECTING; + } + NfcService service = NfcService.getInstance(); + LlcpSocket sock = null; + if (DBG) Log.d(TAG, "about to create socket"); + try { + sock = service.createLlcpSocket(0, MIU, 1, 1024); + } catch (LlcpException e) { + synchronized (mLock) { + mState = DISCONNECTED; + } + throw new IOException("Could not create socket."); + } + try { + if (DBG) Log.d(TAG, "about to connect to service " + NdefPushServer.SERVICE_NAME); + sock.connectToService(NdefPushServer.SERVICE_NAME); + } catch (IOException e) { + if (sock != null) { + try { + sock.close(); + } catch (IOException e2) { + + } + } + synchronized (mLock) { + mState = DISCONNECTED; + } + throw new IOException("Could not connect service."); + } + + synchronized (mLock) { + mSocket = sock; + mState = CONNECTED; + } + } + + public boolean push(NdefMessage msg) { + LlcpSocket sock = null; + synchronized (mLock) { + if (mState != CONNECTED) { + Log.e(TAG, "Not connected to NPP."); + return false; + } + sock = mSocket; + } + // We only handle a single immediate action for now + NdefPushProtocol proto = new NdefPushProtocol(msg, NdefPushProtocol.ACTION_IMMEDIATE); + byte[] buffer = proto.toByteArray(); + int offset = 0; + int remoteMiu; + + try { + remoteMiu = sock.getRemoteMiu(); + if (DBG) Log.d(TAG, "about to send a " + buffer.length + " byte message"); + while (offset < buffer.length) { + int length = Math.min(buffer.length - offset, remoteMiu); + byte[] tmpBuffer = Arrays.copyOfRange(buffer, offset, offset+length); + if (DBG) Log.d(TAG, "about to send a " + length + " byte packet"); + sock.send(tmpBuffer); + offset += length; + } + return true; + } catch (IOException e) { + Log.e(TAG, "couldn't send tag"); + if (DBG) Log.d(TAG, "exception:", e); + } finally { + if (sock != null) { + try { + if (DBG) Log.d(TAG, "about to close"); + sock.close(); + } catch (IOException e) { + // Ignore + } + } + } + return false; + } + + public void close() { + synchronized (mLock) { + if (mSocket != null) { + try { + if (DBG) Log.d(TAG, "About to close NPP socket."); + mSocket.close(); + } catch (IOException e) { + // Ignore + } + mSocket = null; + } + mState = DISCONNECTED; + } + } +} diff --git a/NfcSony/src/com/android/nfc/ndefpush/NdefPushProtocol.java b/NfcSony/src/com/android/nfc/ndefpush/NdefPushProtocol.java new file mode 100644 index 0000000..5520615 --- /dev/null +++ b/NfcSony/src/com/android/nfc/ndefpush/NdefPushProtocol.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2010 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.nfc.ndefpush; + +import android.util.Log; + +import android.nfc.NdefMessage; +import android.nfc.FormatException; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; + +/** + * Implementation of the NDEF Push Protocol. + */ +public class NdefPushProtocol { + public static final byte ACTION_IMMEDIATE = (byte) 0x01; + public static final byte ACTION_BACKGROUND = (byte) 0x02; + + private static final String TAG = "NdefMessageSet"; + private static final byte VERSION = 1; + + private int mNumMessages; + private byte[] mActions; + private NdefMessage[] mMessages; + + public NdefPushProtocol(NdefMessage msg, byte action) { + mNumMessages = 1; + mActions = new byte[1]; + mActions[0] = action; + mMessages = new NdefMessage[1]; + mMessages[0] = msg; + } + + public NdefPushProtocol(byte[] actions, NdefMessage[] messages) { + if (actions.length != messages.length || actions.length == 0) { + throw new IllegalArgumentException( + "actions and messages must be the same size and non-empty"); + } + + // Keep a copy of these arrays + int numMessages = actions.length; + mActions = new byte[numMessages]; + System.arraycopy(actions, 0, mActions, 0, numMessages); + mMessages = new NdefMessage[numMessages]; + System.arraycopy(messages, 0, mMessages, 0, numMessages); + mNumMessages = numMessages; + } + + public NdefPushProtocol(byte[] data) throws FormatException { + ByteArrayInputStream buffer = new ByteArrayInputStream(data); + DataInputStream input = new DataInputStream(buffer); + + // Check version of protocol + byte version; + try { + version = input.readByte(); + } catch (java.io.IOException e) { + Log.w(TAG, "Unable to read version"); + throw new FormatException("Unable to read version"); + } + + if(version != VERSION) { + Log.w(TAG, "Got version " + version + ", expected " + VERSION); + throw new FormatException("Got version " + version + ", expected " + VERSION); + } + + // Read numMessages + try { + mNumMessages = input.readInt(); + } catch(java.io.IOException e) { + Log.w(TAG, "Unable to read numMessages"); + throw new FormatException("Error while parsing NdefMessageSet"); + } + if(mNumMessages == 0) { + Log.w(TAG, "No NdefMessage inside NdefMessageSet packet"); + throw new FormatException("Error while parsing NdefMessageSet"); + } + + // Read actions and messages + mActions = new byte[mNumMessages]; + mMessages = new NdefMessage[mNumMessages]; + for(int i = 0; i < mNumMessages; i++) { + // Read action + try { + mActions[i] = input.readByte(); + } catch(java.io.IOException e) { + Log.w(TAG, "Unable to read action for message " + i); + throw new FormatException("Error while parsing NdefMessageSet"); + } + // Read message length + int length; + try { + length = input.readInt(); + } catch(java.io.IOException e) { + Log.w(TAG, "Unable to read length for message " + i); + throw new FormatException("Error while parsing NdefMessageSet"); + } + byte[] bytes = new byte[length]; + // Read message + int lengthRead; + try { + lengthRead = input.read(bytes); + } catch(java.io.IOException e) { + Log.w(TAG, "Unable to read bytes for message " + i); + throw new FormatException("Error while parsing NdefMessageSet"); + } + if(length != lengthRead) { + Log.w(TAG, "Read " + lengthRead + " bytes but expected " + + length); + throw new FormatException("Error while parsing NdefMessageSet"); + } + // Create and store NdefMessage + try { + mMessages[i] = new NdefMessage(bytes); + } catch(FormatException e) { + throw e; + } + } + } + + public NdefMessage getImmediate() { + // Find and return the first immediate message + for(int i = 0; i < mNumMessages; i++) { + if(mActions[i] == ACTION_IMMEDIATE) { + return mMessages[i]; + } + } + return null; + } + + public byte[] toByteArray() { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024); + DataOutputStream output = new DataOutputStream(buffer); + + try { + output.writeByte(VERSION); + output.writeInt(mNumMessages); + for(int i = 0; i < mNumMessages; i++) { + output.writeByte(mActions[i]); + byte[] bytes = mMessages[i].toByteArray(); + output.writeInt(bytes.length); + output.write(bytes); + } + } catch(java.io.IOException e) { + return null; + } + + return buffer.toByteArray(); + } +} diff --git a/NfcSony/src/com/android/nfc/ndefpush/NdefPushServer.java b/NfcSony/src/com/android/nfc/ndefpush/NdefPushServer.java new file mode 100755 index 0000000..bf92c17 --- /dev/null +++ b/NfcSony/src/com/android/nfc/ndefpush/NdefPushServer.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) 2010 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.nfc.ndefpush; + +import com.android.nfc.DeviceHost.LlcpServerSocket; +import com.android.nfc.DeviceHost.LlcpSocket; +import com.android.nfc.LlcpException; +import com.android.nfc.NfcService; + +import android.nfc.FormatException; +import android.nfc.NdefMessage; +import android.nfc.NfcAdapter; +import android.util.Log; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * A simple server that accepts NDEF messages pushed to it over an LLCP connection. Those messages + * are typically set on the client side by using {@link NfcAdapter#enableForegroundNdefPush}. + */ +public class NdefPushServer { + private static final String TAG = "NdefPushServer"; + private static final boolean DBG = true; + + private static final int MIU = 248; + + int mSap; + + static final String SERVICE_NAME = "com.android.npp"; + + NfcService mService = NfcService.getInstance(); + + final Callback mCallback; + + /** Protected by 'this', null when stopped, non-null when running */ + ServerThread mServerThread = null; + + public interface Callback { + void onMessageReceived(NdefMessage msg); + } + + public NdefPushServer(final int sap, Callback callback) { + mSap = sap; + mCallback = callback; + } + + /** Connection class, used to handle incoming connections */ + private class ConnectionThread extends Thread { + private LlcpSocket mSock; + + ConnectionThread(LlcpSocket sock) { + super(TAG); + mSock = sock; + } + + @Override + public void run() { + if (DBG) Log.d(TAG, "starting connection thread"); + try { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024); + byte[] partial = new byte[1024]; + int size; + boolean connectionBroken = false; + + // Get raw data from remote server + while(!connectionBroken) { + try { + size = mSock.receive(partial); + if (DBG) Log.d(TAG, "read " + size + " bytes"); + if (size < 0) { + connectionBroken = true; + break; + } else { + buffer.write(partial, 0, size); + } + } catch (IOException e) { + // Connection broken + connectionBroken = true; + if (DBG) Log.d(TAG, "connection broken by IOException", e); + } + } + + // Build NDEF message set from the stream + NdefPushProtocol msg = new NdefPushProtocol(buffer.toByteArray()); + if (DBG) Log.d(TAG, "got message " + msg.toString()); + + // Send the intent for the fake tag + mCallback.onMessageReceived(msg.getImmediate()); + } catch (FormatException e) { + Log.e(TAG, "badly formatted NDEF message, ignoring", e); + } finally { + try { + if (DBG) Log.d(TAG, "about to close"); + mSock.close(); + } catch (IOException e) { + // ignore + } + } + if (DBG) Log.d(TAG, "finished connection thread"); + } + } + + /** Server class, used to listen for incoming connection request */ + class ServerThread extends Thread { + // Variables below synchronized on NdefPushServer.this + boolean mRunning = true; + LlcpServerSocket mServerSocket; + + @Override + public void run() { + boolean threadRunning; + synchronized (NdefPushServer.this) { + threadRunning = mRunning; + } + while (threadRunning) { + if (DBG) Log.d(TAG, "about create LLCP service socket"); + try { + synchronized (NdefPushServer.this) { + mServerSocket = mService.createLlcpServerSocket(mSap, SERVICE_NAME, + MIU, 1, 1024); + } + if (mServerSocket == null) { + if (DBG) Log.d(TAG, "failed to create LLCP service socket"); + return; + } + if (DBG) Log.d(TAG, "created LLCP service socket"); + synchronized (NdefPushServer.this) { + threadRunning = mRunning; + } + + while (threadRunning) { + LlcpServerSocket serverSocket; + synchronized (NdefPushServer.this) { + serverSocket = mServerSocket; + } + if (serverSocket == null) return; + + if (DBG) Log.d(TAG, "about to accept"); + LlcpSocket communicationSocket = serverSocket.accept(); + if (DBG) Log.d(TAG, "accept returned " + communicationSocket); + if (communicationSocket != null) { + new ConnectionThread(communicationSocket).start(); + } + + synchronized (NdefPushServer.this) { + threadRunning = mRunning; + } + } + if (DBG) Log.d(TAG, "stop running"); + } catch (LlcpException e) { + Log.e(TAG, "llcp error", e); + } catch (IOException e) { + Log.e(TAG, "IO error", e); + } finally { + synchronized (NdefPushServer.this) { + if (mServerSocket != null) { + if (DBG) Log.d(TAG, "about to close"); + try { + mServerSocket.close(); + } catch (IOException e) { + // ignore + } + mServerSocket = null; + } + } + } + + synchronized (NdefPushServer.this) { + threadRunning = mRunning; + } + } + } + + public void shutdown() { + synchronized (NdefPushServer.this) { + mRunning = false; + if (mServerSocket != null) { + try { + mServerSocket.close(); + } catch (IOException e) { + // ignore + } + mServerSocket = null; + } + } + } + } + + public void start() { + synchronized (this) { + if (DBG) Log.d(TAG, "start, thread = " + mServerThread); + if (mServerThread == null) { + if (DBG) Log.d(TAG, "starting new server thread"); + mServerThread = new ServerThread(); + mServerThread.start(); + } + } + } + + public void stop() { + synchronized (this) { + if (DBG) Log.d(TAG, "stop, thread = " + mServerThread); + if (mServerThread != null) { + if (DBG) Log.d(TAG, "shuting down server thread"); + mServerThread.shutdown(); + mServerThread = null; + } + } + } +} diff --git a/NfcSony/src/com/android/nfc/snep/SnepClient.java b/NfcSony/src/com/android/nfc/snep/SnepClient.java new file mode 100644 index 0000000..03824cc --- /dev/null +++ b/NfcSony/src/com/android/nfc/snep/SnepClient.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2011 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.nfc.snep; + +import com.android.nfc.DeviceHost.LlcpSocket; +import com.android.nfc.LlcpException; +import com.android.nfc.NfcService; + +import android.nfc.NdefMessage; +import android.util.Log; + +import java.io.IOException; + +public final class SnepClient { + private static final String TAG = "SnepClient"; + private static final boolean DBG = false; + private static final int DEFAULT_ACCEPTABLE_LENGTH = 100*1024; + private static final int DEFAULT_MIU = 128; + private static final int DEFAULT_RWSIZE = 1; + SnepMessenger mMessenger = null; + private final Object mTransmissionLock = new Object(); + + private final String mServiceName; + private final int mPort; + private int mState = DISCONNECTED; + private final int mAcceptableLength; + private final int mFragmentLength; + private final int mMiu; + private final int mRwSize; + + private static final int DISCONNECTED = 0; + private static final int CONNECTING = 1; + private static final int CONNECTED = 2; + + public SnepClient() { + mServiceName = SnepServer.DEFAULT_SERVICE_NAME; + mPort = SnepServer.DEFAULT_PORT; + mAcceptableLength = DEFAULT_ACCEPTABLE_LENGTH; + mFragmentLength = -1; + mMiu = DEFAULT_MIU; + mRwSize = DEFAULT_RWSIZE; + } + + public SnepClient(String serviceName) { + mServiceName = serviceName; + mPort = -1; + mAcceptableLength = DEFAULT_ACCEPTABLE_LENGTH; + mFragmentLength = -1; + mMiu = DEFAULT_MIU; + mRwSize = DEFAULT_RWSIZE; + } + + public SnepClient(int miu, int rwSize) { + mServiceName = SnepServer.DEFAULT_SERVICE_NAME; + mPort = SnepServer.DEFAULT_PORT; + mAcceptableLength = DEFAULT_ACCEPTABLE_LENGTH; + mFragmentLength = -1; + mMiu = miu; + mRwSize = rwSize; + } + + SnepClient(String serviceName, int fragmentLength) { + mServiceName = serviceName; + mPort = -1; + mAcceptableLength = DEFAULT_ACCEPTABLE_LENGTH; + mFragmentLength = fragmentLength; + mMiu = DEFAULT_MIU; + mRwSize = DEFAULT_RWSIZE; + } + + SnepClient(String serviceName, int acceptableLength, int fragmentLength) { + mServiceName = serviceName; + mPort = -1; + mAcceptableLength = acceptableLength; + mFragmentLength = fragmentLength; + mMiu = DEFAULT_MIU; + mRwSize = DEFAULT_RWSIZE; + } + + public void put(NdefMessage msg) throws IOException { + SnepMessenger messenger; + synchronized (this) { + if (mState != CONNECTED) { + throw new IOException("Socket not connected."); + } + messenger = mMessenger; + } + + synchronized (mTransmissionLock) { + try { + messenger.sendMessage(SnepMessage.getPutRequest(msg)); + messenger.getMessage(); + } catch (SnepException e) { + throw new IOException(e); + } + } + } + + public SnepMessage get(NdefMessage msg) throws IOException { + SnepMessenger messenger; + synchronized (this) { + if (mState != CONNECTED) { + throw new IOException("Socket not connected."); + } + messenger = mMessenger; + } + + synchronized (mTransmissionLock) { + try { + messenger.sendMessage(SnepMessage.getGetRequest(mAcceptableLength, msg)); + return messenger.getMessage(); + } catch (SnepException e) { + throw new IOException(e); + } + } + } + + public void connect() throws IOException { + synchronized (this) { + if (mState != DISCONNECTED) { + throw new IOException("Socket already in use."); + } + mState = CONNECTING; + } + + LlcpSocket socket = null; + SnepMessenger messenger; + try { + if (DBG) Log.d(TAG, "about to create socket"); + // Connect to the snep server on the remote side + socket = NfcService.getInstance().createLlcpSocket(0, mMiu, mRwSize, 1024); + if (socket == null) { + throw new IOException("Could not connect to socket."); + } + if (mPort == -1) { + if (DBG) Log.d(TAG, "about to connect to service " + mServiceName); + socket.connectToService(mServiceName); + } else { + if (DBG) Log.d(TAG, "about to connect to port " + mPort); + socket.connectToSap(mPort); + } + int miu = socket.getRemoteMiu(); + int fragmentLength = (mFragmentLength == -1) ? miu : Math.min(miu, mFragmentLength); + messenger = new SnepMessenger(true, socket, fragmentLength); + } catch (LlcpException e) { + synchronized (this) { + mState = DISCONNECTED; + } + throw new IOException("Could not connect to socket"); + } catch (IOException e) { + if (socket != null) { + try { + socket.close(); + } catch (IOException e2) { + } + } + synchronized (this) { + mState = DISCONNECTED; + } + throw new IOException("Failed to connect to socket"); + } + + synchronized (this) { + mMessenger = messenger; + mState = CONNECTED; + } + } + + public void close() { + synchronized (this) { + if (mMessenger != null) { + try { + mMessenger.close(); + } catch (IOException e) { + // ignore + } finally { + mMessenger = null; + mState = DISCONNECTED; + } + } + } + } +} diff --git a/NfcSony/src/com/android/nfc/snep/SnepException.java b/NfcSony/src/com/android/nfc/snep/SnepException.java new file mode 100644 index 0000000..b8b717e --- /dev/null +++ b/NfcSony/src/com/android/nfc/snep/SnepException.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2011 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.nfc.snep; + +public class SnepException extends Exception { + + public SnepException(String message) { + super(message); + } + + public SnepException(Exception cause) { + super(cause); + } + + public SnepException(String message, Exception cause) { + super(message, cause); + } +} diff --git a/NfcSony/src/com/android/nfc/snep/SnepMessage.java b/NfcSony/src/com/android/nfc/snep/SnepMessage.java new file mode 100644 index 0000000..50996d0 --- /dev/null +++ b/NfcSony/src/com/android/nfc/snep/SnepMessage.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2011 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.nfc.snep; + +import android.nfc.FormatException; +import android.nfc.NdefMessage; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; + +public final class SnepMessage { + public static final byte VERSION_MAJOR = (byte) 0x1; + public static final byte VERSION_MINOR = (byte) 0x0; + public static final byte VERSION = (0xF0 & (VERSION_MAJOR << 4)) | (0x0F & VERSION_MINOR); + + public static final byte REQUEST_CONTINUE = (byte) 0x00; + public static final byte REQUEST_GET = (byte) 0x01; + public static final byte REQUEST_PUT = (byte) 0x02; + public static final byte REQUEST_REJECT = (byte) 0x7F; + + public static final byte RESPONSE_CONTINUE = (byte) 0x80; + public static final byte RESPONSE_SUCCESS = (byte) 0x81; + public static final byte RESPONSE_NOT_FOUND = (byte) 0xC0; + public static final byte RESPONSE_EXCESS_DATA = (byte) 0xC1; + public static final byte RESPONSE_BAD_REQUEST = (byte) 0xC2; + public static final byte RESPONSE_NOT_IMPLEMENTED = (byte) 0xE0; + public static final byte RESPONSE_UNSUPPORTED_VERSION = (byte) 0xE1; + public static final byte RESPONSE_REJECT = (byte) 0xFF; + + private static final int HEADER_LENGTH = 6; + private final byte mVersion; + private final byte mField; + private final int mLength; + private final int mAcceptableLength; + private final NdefMessage mNdefMessage; + + public static SnepMessage getGetRequest(int acceptableLength, NdefMessage ndef) { + return new SnepMessage(VERSION, REQUEST_GET, 4 + ndef.toByteArray().length, + acceptableLength, ndef); + } + + public static SnepMessage getPutRequest(NdefMessage ndef) { + return new SnepMessage(VERSION, REQUEST_PUT, ndef.toByteArray().length, 0, ndef); + } + + public static SnepMessage getMessage(byte field) { + return new SnepMessage(VERSION, field, 0, 0, null); + } + + public static SnepMessage getSuccessResponse(NdefMessage ndef) { + if (ndef == null) { + return new SnepMessage(VERSION, RESPONSE_SUCCESS, 0, 0, null); + } else { + return new SnepMessage(VERSION, RESPONSE_SUCCESS, ndef.toByteArray().length, 0, ndef); + } + } + + public static SnepMessage fromByteArray(byte[] data) throws FormatException { + return new SnepMessage(data); + } + + private SnepMessage(byte[] data) throws FormatException { + ByteBuffer input = ByteBuffer.wrap(data); + int ndefOffset; + int ndefLength; + + mVersion = input.get(); + mField = input.get(); + mLength = input.getInt(); + if (mField == REQUEST_GET) { + mAcceptableLength = input.getInt(); + ndefOffset = HEADER_LENGTH + 4; + ndefLength = mLength - 4; + } else { + mAcceptableLength = -1; + ndefOffset = HEADER_LENGTH; + ndefLength = mLength; + } + + if (ndefLength > 0) { + byte[] bytes = new byte[ndefLength]; + System.arraycopy(data, ndefOffset, bytes, 0, ndefLength); + mNdefMessage = new NdefMessage(bytes); + } else { + mNdefMessage = null; + } + } + + SnepMessage(byte version, byte field, int length, int acceptableLength, + NdefMessage ndefMessage) { + mVersion = version; + mField = field; + mLength = length; + mAcceptableLength = acceptableLength; + mNdefMessage = ndefMessage; + } + + public byte[] toByteArray() { + byte[] bytes; + if (mNdefMessage != null) { + bytes = mNdefMessage.toByteArray(); + } else { + bytes = new byte[0]; + } + + ByteArrayOutputStream buffer; + try { + if (mField == REQUEST_GET) { + buffer = new ByteArrayOutputStream(bytes.length + HEADER_LENGTH + 4); + } else { + buffer = new ByteArrayOutputStream(bytes.length + HEADER_LENGTH); + } + + DataOutputStream output = new DataOutputStream(buffer); + output.writeByte(mVersion); + output.writeByte(mField); + if (mField == REQUEST_GET) { + output.writeInt(bytes.length + 4); + output.writeInt(mAcceptableLength); + } else { + output.writeInt(bytes.length); + } + output.write(bytes); + } catch(IOException e) { + return null; + } + + return buffer.toByteArray(); + } + + public NdefMessage getNdefMessage() { + return mNdefMessage; + } + + public byte getField() { + return mField; + } + + public byte getVersion() { + return mVersion; + } + + public int getLength() { + return mLength; + } + + public int getAcceptableLength() { + if (mField != REQUEST_GET) { + throw new UnsupportedOperationException( + "Acceptable Length only available on get request messages."); + } + return mAcceptableLength; + } +} diff --git a/NfcSony/src/com/android/nfc/snep/SnepMessenger.java b/NfcSony/src/com/android/nfc/snep/SnepMessenger.java new file mode 100644 index 0000000..97ac1f0 --- /dev/null +++ b/NfcSony/src/com/android/nfc/snep/SnepMessenger.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2011 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.nfc.snep; + +import com.android.nfc.DeviceHost.LlcpSocket; + +import android.nfc.FormatException; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.util.Arrays; + +public class SnepMessenger { + private static final String TAG = "SnepMessager"; + private static final boolean DBG = false; + private static final int HEADER_LENGTH = 6; + final LlcpSocket mSocket; + final int mFragmentLength; + final boolean mIsClient; + + public SnepMessenger(boolean isClient, LlcpSocket socket, int fragmentLength) { + mSocket = socket; + mFragmentLength = fragmentLength; + mIsClient = isClient; + } + + public void sendMessage(SnepMessage msg) throws IOException { + byte[] buffer = msg.toByteArray(); + byte remoteContinue; + if (mIsClient) { + remoteContinue = SnepMessage.RESPONSE_CONTINUE; + } else { + remoteContinue = SnepMessage.REQUEST_CONTINUE; + } + if (DBG) Log.d(TAG, "about to send a " + buffer.length + " byte message"); + + // Send first fragment + int length = Math.min(buffer.length, mFragmentLength); + byte[] tmpBuffer = Arrays.copyOfRange(buffer, 0, length); + if (DBG) Log.d(TAG, "about to send a " + length + " byte fragment"); + mSocket.send(tmpBuffer); + + if (length == buffer.length) { + return; + } + + // Look for Continue or Reject from peer. + int offset = length; + byte[] responseBytes = new byte[HEADER_LENGTH]; + mSocket.receive(responseBytes); + SnepMessage snepResponse; + try { + snepResponse = SnepMessage.fromByteArray(responseBytes); + } catch (FormatException e) { + throw new IOException("Invalid SNEP message", e); + } + + if (DBG) Log.d(TAG, "Got response from first fragment: " + snepResponse.getField()); + if (snepResponse.getField() != remoteContinue) { + throw new IOException("Invalid response from server (" + + snepResponse.getField() + ")"); + } + + // Send remaining fragments. + while (offset < buffer.length) { + length = Math.min(buffer.length - offset, mFragmentLength); + tmpBuffer = Arrays.copyOfRange(buffer, offset, offset + length); + if (DBG) Log.d(TAG, "about to send a " + length + " byte fragment"); + mSocket.send(tmpBuffer); + offset += length; + } + } + + public SnepMessage getMessage() throws IOException, SnepException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(mFragmentLength); + byte[] partial = new byte[mFragmentLength]; + int size; + int requestSize = 0; + int readSize = 0; + byte requestVersion = 0; + boolean doneReading = false; + byte fieldContinue; + byte fieldReject; + if (mIsClient) { + fieldContinue = SnepMessage.REQUEST_CONTINUE; + fieldReject = SnepMessage.REQUEST_REJECT; + } else { + fieldContinue = SnepMessage.RESPONSE_CONTINUE; + fieldReject = SnepMessage.RESPONSE_REJECT; + } + + size = mSocket.receive(partial); + if (DBG) Log.d(TAG, "read " + size + " bytes"); + if (size < 0) { + try { + mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray()); + } catch (IOException e) { + // Ignore + } + throw new IOException("Error reading SNEP message."); + } else if (size < HEADER_LENGTH) { + try { + mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray()); + } catch (IOException e) { + // Ignore + } + throw new IOException("Invalid fragment from sender."); + } else { + readSize = size - HEADER_LENGTH; + buffer.write(partial, 0, size); + } + + DataInputStream dataIn = new DataInputStream(new ByteArrayInputStream(partial)); + requestVersion = dataIn.readByte(); + byte requestField = dataIn.readByte(); + requestSize = dataIn.readInt(); + + if (DBG) Log.d(TAG, "read " + readSize + " of " + requestSize); + + if (((requestVersion & 0xF0) >> 4) != SnepMessage.VERSION_MAJOR) { + // Invalid protocol version; treat message as complete. + return new SnepMessage(requestVersion, requestField, 0, 0, null); + } + + if (requestSize > readSize) { + if (DBG) Log.d(TAG, "requesting continuation"); + mSocket.send(SnepMessage.getMessage(fieldContinue).toByteArray()); + } else { + doneReading = true; + } + + // Remaining fragments + while (!doneReading) { + try { + size = mSocket.receive(partial); + if (DBG) Log.d(TAG, "read " + size + " bytes"); + if (size < 0) { + try { + mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray()); + } catch (IOException e) { + // Ignore + } + throw new IOException(); + } else { + readSize += size; + buffer.write(partial, 0, size); + if (readSize == requestSize) { + doneReading = true; + } + } + } catch (IOException e) { + try { + mSocket.send(SnepMessage.getMessage(fieldReject).toByteArray()); + } catch (IOException e2) { + // Ignore + } + throw e; + } + } + + // Build NDEF message set from the stream + try { + return SnepMessage.fromByteArray(buffer.toByteArray()); + } catch (FormatException e) { + Log.e(TAG, "Badly formatted NDEF message, ignoring", e); + throw new SnepException(e); + } + } + + public void close() throws IOException { + mSocket.close(); + } +} diff --git a/NfcSony/src/com/android/nfc/snep/SnepServer.java b/NfcSony/src/com/android/nfc/snep/SnepServer.java new file mode 100644 index 0000000..866ed32 --- /dev/null +++ b/NfcSony/src/com/android/nfc/snep/SnepServer.java @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2011 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.nfc.snep; + +import com.android.nfc.DeviceHost.LlcpServerSocket; +import com.android.nfc.DeviceHost.LlcpSocket; +import com.android.nfc.LlcpException; +import com.android.nfc.NfcService; + +import android.nfc.NdefMessage; +import android.nfc.NfcAdapter; +import android.util.Log; + +import java.io.IOException; + +/** + * A simple server that accepts NDEF messages pushed to it over an LLCP connection. Those messages + * are typically set on the client side by using {@link NfcAdapter#enableForegroundNdefPush}. + */ +public final class SnepServer { + private static final String TAG = "SnepServer"; + private static final boolean DBG = false; + private static final int DEFAULT_MIU = 248; + private static final int DEFAULT_RW_SIZE = 1; + + public static final int DEFAULT_PORT = 4; + + public static final String DEFAULT_SERVICE_NAME = "urn:nfc:sn:snep"; + + final Callback mCallback; + final String mServiceName; + final int mServiceSap; + final int mFragmentLength; + final int mMiu; + final int mRwSize; + + /** Protected by 'this', null when stopped, non-null when running */ + ServerThread mServerThread = null; + boolean mServerRunning = false; + + public interface Callback { + public SnepMessage doPut(NdefMessage msg); + public SnepMessage doGet(int acceptableLength, NdefMessage msg); + } + + public SnepServer(Callback callback) { + mCallback = callback; + mServiceName = DEFAULT_SERVICE_NAME; + mServiceSap = DEFAULT_PORT; + mFragmentLength = -1; + mMiu = DEFAULT_MIU; + mRwSize = DEFAULT_RW_SIZE; + } + + public SnepServer(String serviceName, int serviceSap, Callback callback) { + mCallback = callback; + mServiceName = serviceName; + mServiceSap = serviceSap; + mFragmentLength = -1; + mMiu = DEFAULT_MIU; + mRwSize = DEFAULT_RW_SIZE; + } + + public SnepServer(Callback callback, int miu, int rwSize) { + mCallback = callback; + mServiceName = DEFAULT_SERVICE_NAME; + mServiceSap = DEFAULT_PORT; + mFragmentLength = -1; + mMiu = miu; + mRwSize = rwSize; + } + + SnepServer(String serviceName, int serviceSap, int fragmentLength, Callback callback) { + mCallback = callback; + mServiceName = serviceName; + mServiceSap = serviceSap; + mFragmentLength = fragmentLength; + mMiu = DEFAULT_MIU; + mRwSize = DEFAULT_RW_SIZE; + } + + /** Connection class, used to handle incoming connections */ + private class ConnectionThread extends Thread { + private final LlcpSocket mSock; + private final SnepMessenger mMessager; + + ConnectionThread(LlcpSocket socket, int fragmentLength) { + super(TAG); + mSock = socket; + mMessager = new SnepMessenger(false, socket, fragmentLength); + } + + @Override + public void run() { + if (DBG) Log.d(TAG, "starting connection thread"); + try { + boolean running; + synchronized (SnepServer.this) { + running = mServerRunning; + } + + while (running) { + if (!handleRequest(mMessager, mCallback)) { + break; + } + + synchronized (SnepServer.this) { + running = mServerRunning; + } + } + } catch (IOException e) { + if (DBG) Log.e(TAG, "Closing from IOException"); + } finally { + try { + if (DBG) Log.d(TAG, "about to close"); + mSock.close(); + } catch (IOException e) { + // ignore + } + } + + if (DBG) Log.d(TAG, "finished connection thread"); + } + } + + static boolean handleRequest(SnepMessenger messenger, Callback callback) throws IOException { + SnepMessage request; + try { + request = messenger.getMessage(); + } catch (SnepException e) { + if (DBG) Log.w(TAG, "Bad snep message", e); + try { + messenger.sendMessage(SnepMessage.getMessage( + SnepMessage.RESPONSE_BAD_REQUEST)); + } catch (IOException e2) { + // Ignore + } + return false; + } + + if (((request.getVersion() & 0xF0) >> 4) != SnepMessage.VERSION_MAJOR) { + messenger.sendMessage(SnepMessage.getMessage( + SnepMessage.RESPONSE_UNSUPPORTED_VERSION)); + } else if (request.getField() == SnepMessage.REQUEST_GET) { + messenger.sendMessage(callback.doGet(request.getAcceptableLength(), + request.getNdefMessage())); + } else if (request.getField() == SnepMessage.REQUEST_PUT) { + if (DBG) Log.d(TAG, "putting message " + request.toString()); + messenger.sendMessage(callback.doPut(request.getNdefMessage())); + } else { + if (DBG) Log.d(TAG, "Unknown request (" + request.getField() +")"); + messenger.sendMessage(SnepMessage.getMessage( + SnepMessage.RESPONSE_BAD_REQUEST)); + } + return true; + } + + /** Server class, used to listen for incoming connection request */ + class ServerThread extends Thread { + private boolean mThreadRunning = true; + LlcpServerSocket mServerSocket; + + @Override + public void run() { + boolean threadRunning; + synchronized (SnepServer.this) { + threadRunning = mThreadRunning; + } + + while (threadRunning) { + if (DBG) Log.d(TAG, "about create LLCP service socket"); + try { + synchronized (SnepServer.this) { + mServerSocket = NfcService.getInstance().createLlcpServerSocket(mServiceSap, + mServiceName, mMiu, mRwSize, 1024); + } + if (mServerSocket == null) { + if (DBG) Log.d(TAG, "failed to create LLCP service socket"); + return; + } + if (DBG) Log.d(TAG, "created LLCP service socket"); + synchronized (SnepServer.this) { + threadRunning = mThreadRunning; + } + + while (threadRunning) { + LlcpServerSocket serverSocket; + synchronized (SnepServer.this) { + serverSocket = mServerSocket; + } + + if (serverSocket == null) { + if (DBG) Log.d(TAG, "Server socket shut down."); + return; + } + if (DBG) Log.d(TAG, "about to accept"); + LlcpSocket communicationSocket = serverSocket.accept(); + if (DBG) Log.d(TAG, "accept returned " + communicationSocket); + if (communicationSocket != null) { + int fragmentLength = (mFragmentLength == -1) ? + mMiu : Math.min(mMiu, mFragmentLength); + new ConnectionThread(communicationSocket, fragmentLength).start(); + } + + synchronized (SnepServer.this) { + threadRunning = mThreadRunning; + } + } + if (DBG) Log.d(TAG, "stop running"); + } catch (LlcpException e) { + Log.e(TAG, "llcp error", e); + } catch (IOException e) { + Log.e(TAG, "IO error", e); + } finally { + synchronized (SnepServer.this) { + if (mServerSocket != null) { + if (DBG) Log.d(TAG, "about to close"); + try { + mServerSocket.close(); + } catch (IOException e) { + // ignore + } + mServerSocket = null; + } + } + } + + synchronized (SnepServer.this) { + threadRunning = mThreadRunning; + } + } + } + + public void shutdown() { + synchronized (SnepServer.this) { + mThreadRunning = false; + if (mServerSocket != null) { + try { + mServerSocket.close(); + } catch (IOException e) { + // ignore + } + mServerSocket = null; + } + } + } + } + + public void start() { + synchronized (SnepServer.this) { + if (DBG) Log.d(TAG, "start, thread = " + mServerThread); + if (mServerThread == null) { + if (DBG) Log.d(TAG, "starting new server thread"); + mServerThread = new ServerThread(); + mServerThread.start(); + mServerRunning = true; + } + } + } + + public void stop() { + synchronized (SnepServer.this) { + if (DBG) Log.d(TAG, "stop, thread = " + mServerThread); + if (mServerThread != null) { + if (DBG) Log.d(TAG, "shuting down server thread"); + mServerThread.shutdown(); + mServerThread = null; + mServerRunning = false; + } + } + } +} diff --git a/NfcSony/src/com/android/nfc/sony/NativeNfcSetting.java b/NfcSony/src/com/android/nfc/sony/NativeNfcSetting.java new file mode 100644 index 0000000..dfe1611 --- /dev/null +++ b/NfcSony/src/com/android/nfc/sony/NativeNfcSetting.java @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2014 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.nfc.sony; + +import android.util.Log; + +public class NativeNfcSetting { + private static final String TAG = "NativeNfcSetting"; + + private static final byte BIT_0 = 1; + private static final byte BIT_1 = 2; + private static final byte BIT_2 = 4; + private static final byte BIT_3 = 8; + private static final byte BIT_4 = 16; + private static final byte BIT_5 = 32; + private static final byte BIT_ALL = 15; + private static final byte BIT_OFF = 0; + private static final boolean DBG = false; + private static final int INT_DEFAULT = 0; + private static final int INT_TYPE_A = 1; + private static final int INT_TYPE_ALL = 4; + private static final int INT_TYPE_F212 = 2; + private static final int INT_TYPE_F424 = 3; + private static final byte RF_REG_6 = 32; + private static final byte RF_REG_7 = 36; + private static final byte RF_REG_8 = 52; + private static final int TGT_DEFAULT = 0; + private static final int TGT_TYPE_ALL = 3; + private static final int TGT_TYPE_A_WAIT = 1; + private static final int TGT_TYPE_F_WAIT = 2; + + private byte mAutoPollOffListen; + private byte mListenChangeSwitch; + private int mListenTime; + private int[] mPollGapListenTime; + private int[] mPollTimeRfOnToPoll; + private byte mPolloingChangeSwitch; + private int mReg6Data; + private int mReg7Data; + private int mReg8Data; + private int mReg14Data; + private int mReg15Data; + private byte mRegAddress; + private byte mSelectPollType; + private byte mSpdTime; + private byte mTypeAWait; + private boolean mTypeBWait; + private byte mTypeFWait; + + public NativeNfcSetting() { + initPollingParam(); + initListenParam(); + initRfParam(); + } + + private native int nativeSetListenParameter(byte listenchangeSwitch, int[] pollGapListenTime, + int listenTime, byte typeAWait, byte typeFWait, boolean typeBWait, byte autoPollOffListen); + private boolean changeListenParam() { + int ret = nativeSetListenParameter(mListenChangeSwitch, mPollGapListenTime, + mListenTime, mTypeAWait, mTypeFWait, mTypeBWait, mAutoPollOffListen); + if (ret != 0) { + Log.e("NativeNfcSetting", "Error!!! nativeSetListenParameter() return = " + ret); + return false; + } + return true; + } + + private native int nativeSetPollParameter(byte polloingChangeSwitch, byte selectPollType, int[] pollTimeRfOnToPoll); + private boolean changePollParam() { + int ret = nativeSetPollParameter(this.mPolloingChangeSwitch, this.mSelectPollType, this.mPollTimeRfOnToPoll); + if (ret != 0) { + Log.e("NativeNfcSetting", "Error!!! nativeSetPollParameter() return = " + ret); + return false; + } + return true; + } + + private native int nativeRfParameter(byte[] param); + private boolean changeRfParam() { + return nativeRfParameter(concat(new byte[][] { + createRegData((byte)32, mReg6Data), + createRegData((byte)36, mReg7Data), + createRegData((byte)52, mReg8Data), + createRegData((byte)48, mReg14Data), + createRegData((byte) 5, mReg15Data), })) == 0; + } + + private byte[] concat(byte[]... arrays) { + int len = 0; + for (int i = 0; i < arrays.length; i++) { + len += arrays[i].length; + } + byte[] result = new byte[len]; + int pos = 0; + for (int i = 0; i < arrays.length; i++) { + byte[] array = arrays[i]; + System.arraycopy(array, 0, result, pos, array.length); + pos += array.length; + } + return result; + } + + private byte[] createRegData(byte address, int value) { + Log.i(TAG, "call: createRegData address=" + address + ", value=" + value); + byte[] regData = new byte[6]; + regData[0] = 0; + regData[1] = address; + regData[2] = ((byte)(0xFF & value >>> 24)); + regData[3] = ((byte)(0xFF & value >>> 16)); + regData[4] = ((byte)(0xFF & value >>> 8)); + regData[5] = ((byte)(0xFF & value >>> 0)); + return regData; + } + + private void initListenParam() { + Log.i(TAG, "call: initListenParam"); + mListenChangeSwitch = 0; + mPollGapListenTime = new int[3]; + mPollGapListenTime[0] = 10; + mPollGapListenTime[1] = 10; + mPollGapListenTime[2] = 10; + mListenTime = 15; + mTypeAWait = 4; + mTypeFWait = 0; + mTypeBWait = true; + mAutoPollOffListen = 7; + } + + private void initPollingParam() { + Log.i(TAG, "call: initPollingParam"); + mPolloingChangeSwitch = 0; + mSelectPollType = 15; + mPollTimeRfOnToPoll = new int[4]; + mPollTimeRfOnToPoll[0] = 6; + mPollTimeRfOnToPoll[1] = 6; + mPollTimeRfOnToPoll[2] = 21; + mPollTimeRfOnToPoll[3] = 21; + } + + private void initRfParam() { + Log.i(TAG, "call: initRfParam"); + mRegAddress = 0; + mReg6Data = 0x40c043f; + mReg7Data = 0x143f133f; + mReg8Data = 0x60000000; + mReg14Data = 0x1f280000; + mReg15Data = 0x28080000; + } + + private native boolean nativeClearCIDSupport(); + + private native boolean nativeSetCIDSupport(); + + private boolean setListenSetting(byte switchBit, int index, int value) { + Log.i(TAG, "call: setListenSetting switchBit=" + switchBit + ", index=" + index + ", value=" + value); + mListenChangeSwitch = ((byte)(switchBit | mListenChangeSwitch)); + switch (index) { + case 8: + mPollGapListenTime[0] = value; + return true; + case 9: + mPollGapListenTime[1] = value; + return true; + case 10: + mPollGapListenTime[2] = value; + return true; + case 11: + mListenTime = value; + return true; + case 12: + mTypeAWait = ((byte)value); + return true; + case 13: + mTypeFWait = ((byte)value); + return true; + case 14: + if (value != 0) { + mTypeBWait = true; + return true; + } + mTypeBWait = false; + return true; + default: + mAutoPollOffListen = ((byte)value); + return true; + } + } + + private boolean setPollSetting(byte switchBit, int index, int value) { + Log.i(TAG, "call: setPollSetting switchBit=" + switchBit + ", index=" + index + ", value=" + value); + mPolloingChangeSwitch = ((byte)(switchBit | mPolloingChangeSwitch)); + switch (index) { + case 0: + if (value != 0) { + this.mSelectPollType = ((byte)(0x1 | this.mSelectPollType)); + return true; + } + this.mSelectPollType = ((byte)(0xFFFFFFFE & this.mSelectPollType)); + return true; + case 1: + if (value != 0) { + this.mSelectPollType = ((byte)(0x2 | this.mSelectPollType)); + return true; + } + this.mSelectPollType = ((byte)(0xFFFFFFFD & this.mSelectPollType)); + return true; + case 2: + if (value != 0) { + this.mSelectPollType = ((byte)(0x4 | this.mSelectPollType)); + return true; + } + this.mSelectPollType = ((byte)(0xFFFFFFFB & this.mSelectPollType)); + return true; + case 3: + if (value != 0) { + this.mSelectPollType = ((byte)(0x8 | this.mSelectPollType)); + return true; + } + this.mSelectPollType = ((byte)(0xFFFFFFF7 & this.mSelectPollType)); + return true; + case 4: + this.mPollTimeRfOnToPoll[0] = value; + return true; + case 5: + this.mPollTimeRfOnToPoll[1] = value; + return true; + case 6: + this.mPollTimeRfOnToPoll[2] = value; + return true; + default: + mPollTimeRfOnToPoll[3] = value; + return true; + } + } + + private boolean setRfSetting(byte address, int index, int value) { + Log.i(TAG, "call: setRfSetting address=" + address + ", index=" + index + " value=" + value); + // TODO + return true; + } + + public boolean changeParameter(int target) { + Log.i(TAG, "call: changeParameter target=" + target); + if (target == 1) { + return changePollParam(); + } else if (target == 2) { + return changeListenParam(); + } else if (target == 3) { + return changeRfParam(); + } + return false; + } + + public void setP2pModes(int initiatorModes, int targetModes) { + Log.i(TAG, "call: setP2pModes initiatorModes=" + initiatorModes + ", targetModes=" + targetModes); + initPollingParam(); + initListenParam(); + } + + public boolean setParameter(int index, int value) { + Log.i(TAG, "call: setParameter index=" + index + ", value=" + value); + // TODO + setPollSetting((byte)1, index, value); + setPollSetting((byte)2, index, value); + setListenSetting((byte)1, index, value); + setListenSetting((byte)2, index, value); + setListenSetting((byte)4, index, value); + setListenSetting((byte)8, index, value); + setListenSetting((byte)16, index, value); + setListenSetting((byte)32, index, value); + setRfSetting((byte)32, index, value); + setRfSetting((byte)36, index, value); + setRfSetting((byte)52, index, value); + setRfSetting((byte)40, index, value); + setRfSetting((byte)48, index, value); + return true; + } +} + diff --git a/NfcSony/src/com/android/nfc/sony/NativeNfcUtility.java b/NfcSony/src/com/android/nfc/sony/NativeNfcUtility.java new file mode 100644 index 0000000..a1ff10f --- /dev/null +++ b/NfcSony/src/com/android/nfc/sony/NativeNfcUtility.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 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.nfc.sony; + +//import android.nfc.INfcUtilityCallback; +import android.os.Handler; +import android.os.Message; +import android.os.RemoteException; + +public class NativeNfcUtility { + private static final String TAG = "NativeNfcUtility"; + + private static final boolean DBG = false; + private static final int MSG_WAIT_SIM_BOOT = 1; + private static boolean mIsLock = false; + //private static INfcUtilityCallback mStaticCallback; + final NativeNfcUtilityHandler mHandler = new NativeNfcUtilityHandler(); + private native int nativeWaitSimBoot(boolean paramBoolean); + + void startWaitSimBoot() { + nativeWaitSimBoot(mIsLock); +// if (mStaticCallback != null) { +// try { +// mStaticCallback.SimBootComplete(); +// } catch (RemoteException localRemoteException) { +// e.printStackTrace(); +// } + } + +// public boolean waitSimBoot(INfcUtilityCallback callback, boolean isLock) { +// mIsLock = isLock; +// mStaticCallback = callback; +// this.mHandler.sendEmptyMessage(1); +// return true; +// } + + final class NativeNfcUtilityHandler extends Handler { + NativeNfcUtilityHandler() { + } + + public void handleMessage(Message paramMessage) { + NativeNfcUtility.this.startWaitSimBoot(); + } + } +} + diff --git a/configs/libnfc-brcm.conf b/configs/libnfc-brcm.conf index bb754fb..8e245ce 100644 --- a/configs/libnfc-brcm.conf +++ b/configs/libnfc-brcm.conf @@ -1,21 +1,10 @@ +## this file is used by Broadcom's Hardware Abstraction Layer at external/libnfc-nci/halimpl/ + ############################################################################### # Application options -APPL_TRACE_LEVEL=0xFF +APPL_TRACE_LEVEL=0x02 PROTOCOL_TRACE_LEVEL=0xFFFFFFFF -############################################################################### -# Logging -# Set USE_RAW_NCI_TRACE = 1 for protocol logging in a raw format -# instead of decoded -# Set LOG_TO_FILE = 1 to log protocol traces to a file on the sdcard -# instead of to adb logcat -# Set LOGCAT_FILTER to the filter string which is passed to logcat -#USE_RAW_NCI_TRACE=0 -#LOG_TO_FILE=0 -#LOGCAT_FILTER="BrcmNciX:V BrcmNciR:V BrcmNote:V *:S" - -PRESERVE_STORAGE=1 - ############################################################################### # performance measurement # Change this setting to control how often USERIAL log the performance (throughput) @@ -25,26 +14,23 @@ PRESERVE_STORAGE=1 ############################################################################### # File used for NFA storage -NFA_STORAGE="/data/bcmnfc" +NFA_STORAGE="/data/nfc" ############################################################################### -# Low Power Mode Settings +# Snooze Mode Settings +# +# By default snooze mode is enabled. Set SNOOZE_MODE_CFG byte[0] to 0 +# to disable. # -# If NFA_DM_LP_CFG is not provided, stack default settings are -# used (see nfa_dm_brcm_cfg.c). They are as follows: -# 1 Power cycle to full power mode from CEx -# 5 Parameter for low power mode command -# 0 Primary Threshold for battery monitor -# 0-7 representing below voltages: -# {2, 2.2, 2.7, 2.8, 2.9, 3, 3.1, 3.2} -# 8 Secondary Threshold for battery monitor -# 0-15 representing below voltages: -# {5.2, 4.87, 4.54, 4.22, 3.9, 3.73, 3.57, 3.4, -# 3.2, 3.1, 3.0, 2.9, 2.8, 2.7, 2.2, 2.0} +# If SNOOZE_MODE_CFG is not provided, the default settings are used: +# They are as follows: +# 8 Sleep Mode (0=Disabled 1=UART 8=SPI/I2C) +# 0 Idle Threshold Host +# 0 Idle Threshold HC +# 0 NFC Wake active mode (0=ActiveLow 1=ActiveHigh) +# 1 Host Wake active mode (0=ActiveLow 1=ActiveHigh) # -#NFA_DM_LP_CFG={01:05:00:08} -# LPM Disable FW VBAT MON -NFA_DM_LP_CFG={01:01:00:08} +#SNOOZE_MODE_CFG={08:00:00:00:01} ############################################################################### # Insert a delay in milliseconds after NFC_WAKE and before write to NFCC @@ -58,30 +44,35 @@ NFC_WAKE_DELAY=20 # Delay after deasserting NFC-Wake before turn off chip (default 0) # POST_POWER_OFF_DELAY # Delay after turning off chip, before USERIAL_close returns (default 0) -# CE3_PRE_POWER_OFF_DELAY -# Delay after deasserting NFC-Wake before turn off chip (default 1000) -# when going to CE3 Switch Off mode # #POWER_ON_DELAY=300 -PRE_POWER_OFF_DELAY=1500 +#PRE_POWER_OFF_DELAY=0 #POST_POWER_OFF_DELAY=0 -#CE3_PRE_POWER_OFF_DELAY=1000 +############################################################################### +# LPTD mode configuration +# byte[0] is the length of the remaining bytes in this value +# if set to 0, LPTD params will NOT be sent to NFCC (i.e. disabled). +# byte[1] is the param id it should be set to B9. +# byte[2] is the length of the LPTD parameters +# byte[3] indicates if LPTD is enabled +# if set to 0, LPTD will be disabled (parameters will still be sent). +# byte[4-n] are the LPTD parameters. +# By default, LPTD is enabled and default settings are used. +# See nfc_hal_dm_cfg.c for defaults +LPTD_CFG={23:B9:21:01:02:FF:FF:04:A0:0F:40:00:80:02:02:10:00:00:00:31:0C:30:00:00:00:00:00:00:00:00:00:00:00:00:00:00} ############################################################################### -# Device Manager Config +# Startup Configuration (100 bytes maximum) # -# If NFA_DM_CFG is not provided, stack default settings are -# used (see nfa_dm_cfg.c). They are as follows: -# 0 (FALSE) Automatic NDEF detection when background polling -# 0 (FALSE) Automatic NDEF read when background polling +# For the 0xCA parameter, byte[9] (marked by 'AA') is for UICC0, and byte[10] (marked by BB) is +# for UICC1. The values are defined as: +# 0 : UICCx only supports ISO_DEP in low power mode. +# 2 : UICCx only supports Mifare in low power mode. +# 3 : UICCx supports both ISO_DEP and Mifare in low power mode. # -#NFA_DM_CFG={00:00} - -############################################################################### -# Default poll duration (in ms) -# The defualt is 500ms if not set (see nfc_target.h) same as M0 -NFA_DM_DISC_DURATION_POLL=500 +# AA BB +NFA_DM_START_UP_CFG={1F:CB:01:01:A5:01:01:CA:14:00:00:00:00:06:E8:03:00:00:00:00:00:00:00:00:00:00:00:00:00:80:01:01} ############################################################################### # Startup Vendor Specific Configuration (100 bytes maximum); @@ -93,39 +84,44 @@ NFA_DM_DISC_DURATION_POLL=500 # byte[5] 0=turn off SWP frame logging; 1=turn on # NFA_DM_START_UP_VSC_CFG={05:2F:09:02:01:01} -#HW FSM -#NFA_DM_START_UP_VSC_CFG={04:2F:06:01:00} +############################################################################### +# Antenna Configuration - This data is used when setting 0xC8 config item +# at startup (before discovery is started). If not used, no value is sent. +# +# The settings for this value are documented here: +# http://wcgbu.broadcom.com/wpan/PM/Project%20Document%20Library/bcm20791B0/ +# Design/Doc/PHY%20register%20settings/BCM20791-B2-1027-02_PHY_Recommended_Reg_Settings.xlsx +# This document is maintained by Paul Forshaw. +# +# The values marked as ?? should be tweaked per antenna or customer/app: +# {20:C8:1E:06:??:00:??:??:??:00:??:24:00:1C:00:75:00:77:00:76:00:1C:00:03:00:0A:00:??:01:00:00:40:04} +# array[0] = 0x20 is length of the payload from array[1] to the end +# array[1] = 0xC8 is PREINIT_DSP_CFG +#PREINIT_DSP_CFG={20:C8:1E:06:1F:00:0F:03:3C:00:04:24:00:1C:00:75:00:77:00:76:00:1C:00:03:00:0A:00:48:01:00:00:40:04} ############################################################################### # Configure crystal frequency when internal LPO can't detect the frequency. #XTAL_FREQUENCY=0 - -############################################################################### -# Use Nexus S NXP RC work to allow our stack/firmware to work with a retail -# Nexus S that causes IOP issues. Note, this will not pass conformance and -# should be removed for production. -#USE_NXP_P2P_RC_WORKAROUND=1 - ############################################################################### # Configure the default Destination Gate used by HCI (the default is 4, which # is the ETSI loopback gate. -#NFA_HCI_DEFAULT_DEST_GATE=0x04 +NFA_HCI_DEFAULT_DEST_GATE=0xF0 ############################################################################### # Configure the single default SE to use. The default is to use the first # SE that is detected by the stack. This value might be used when the phone # supports multiple SE (e.g. 0xF3 and 0xF4) but you want to force it to use # one of them (e.g. 0xF4). -ACTIVE_SE=0xF4 +#ACTIVE_SE=0xF3 ############################################################################### # Configure the NFC Extras to open and use a static pipe. If the value is # not set or set to 0, then the default is use a dynamic pipe based on a # destination gate (see NFA_HCI_DEFAULT_DEST_GATE). Note there is a value # for each UICC (where F3="UICC0" and F4="UICC1") -NFA_HCI_STATIC_PIPE_ID_F3=0x71 -NFA_HCI_STATIC_PIPE_ID_F4=0x71 - +#NFA_HCI_STATIC_PIPE_ID_F3=0x70 +#NFA_HCI_STATIC_PIPE_ID_01=0x19 +NFA_HCI_STATIC_PIPE_ID_C0=0x19 ############################################################################### # When disconnecting from Oberthur secure element, perform a warm-reset of # the secure element to deselect the applet. @@ -136,41 +132,26 @@ OBERTHUR_WARM_RESET_COMMAND=0x03 ############################################################################### # Force UICC to only listen to the following technology(s). # The bits are defined as tNFA_TECHNOLOGY_MASK in nfa_api.h. -# Default is NFA_TECHNOLOGY_MASK_A | NFA_TECHNOLOGY_MASK_B. -#UICC_LISTEN_TECH_MASK=0x03 +# Default is NFA_TECHNOLOGY_MASK_A | NFA_TECHNOLOGY_MASK_B | NFA_TECHNOLOGY_MASK_F +UICC_LISTEN_TECH_MASK=0x07 ############################################################################### -# AID for Empty Select command -# If specified, this AID will be substituted when an Empty SELECT command is -# detected. The first byte is the length of the AID. Maximum length is 16. -AID_FOR_EMPTY_SELECT={08:A0:00:00:01:51:00:00:00} +# Force HOST listen feature enable or disable. +# 0: Disable +# 1: Enable +HOST_LISTEN_ENABLE=0x01 ############################################################################### -# Force tag polling for the following technology(s). -# The bits are defined as tNFA_TECHNOLOGY_MASK in nfa_api.h. -# Default is NFA_TECHNOLOGY_MASK_A | NFA_TECHNOLOGY_MASK_B | -# NFA_TECHNOLOGY_MASK_F | NFA_TECHNOLOGY_MASK_ISO15693 | -# NFA_TECHNOLOGY_MASK_B_PRIME | NFA_TECHNOLOGY_MASK_A_ACTIVE | -# NFA_TECHNOLOGY_MASK_F_ACTIVE. -# -# Notable bits: -# NFA_TECHNOLOGY_MASK_A 0x01 -# NFA_TECHNOLOGY_MASK_B 0x02 -# NFA_TECHNOLOGY_MASK_F 0x04 -# NFA_TECHNOLOGY_MASK_ISO15693 0x08 -# NFA_TECHNOLOGY_MASK_B_PRIME 0x10 -# NFA_TECHNOLOGY_MASK_KOVIO 0x20 -# NFA_TECHNOLOGY_MASK_A_ACTIVE 0x40 -# NFA_TECHNOLOGY_MASK_F_ACTIVE 0x80 -POLLING_TECH_MASK=0xEF +# Allow UICC to be powered off if there is no traffic. +# Timeout is in ms. If set to 0, then UICC will not be powered off. +#UICC_IDLE_TIMEOUT=30000 +UICC_IDLE_TIMEOUT=0 ############################################################################### -# Force P2P to only listen for the following technology(s). -# The bits are defined as tNFA_TECHNOLOGY_MASK in nfa_api.h. -# Default is NFA_TECHNOLOGY_MASK_A | NFA_TECHNOLOGY_MASK_F | -# NFA_TECHNOLOGY_MASK_A_ACTIVE | NFA_TECHNOLOGY_MASK_F_ACTIVE -P2P_LISTEN_TECH_MASK=0xC4 - +# AID for Empty Select command +# If specified, this AID will be substituted when an Empty SELECT command is +# detected. The first byte is the length of the AID. Maximum length is 16. +AID_FOR_EMPTY_SELECT={08:A0:00:00:01:51:00:00:00} ############################################################################### # Maximum Number of Credits to be allowed by the NFCC # This value overrides what the NFCC specifices allowing the host to have @@ -183,15 +164,29 @@ MAX_RF_DATA_CREDITS=1 # the NFCC to send PPSE requests to the DH. # The default setting is enabled (i.e. T4t Virtual SE is registered). #REGISTER_VIRTUAL_SE=1 -REGISTER_VIRTUAL_SE=0 ############################################################################### # When screen is turned off, specify the desired power state of the controller. # 0: power-off-sleep state; DEFAULT # 1: full-power state # 2: screen-off card-emulation (CE4/CE3/CE1 modes are used) -# 3: FPM CE in snooze mode, Switch Off, Battery Off still available. -SCREEN_OFF_POWER_STATE=3 +SCREEN_OFF_POWER_STATE=1 + +############################################################################### +# Firmware patch file +# If the value is not set then patch download is disabled. +FW_PATCH="/vendor/firmware/bcm2079x_firmware.ncd" + +############################################################################### +# Firmware pre-patch file (sent before the above patch file) +# If the value is not set then pre-patch is not used. +FW_PRE_PATCH="/vendor/firmware/bcm2079x_pre_firmware.ncd" + +############################################################################### +# Firmware patch format +# 1 = HCD +# 2 = NCD (default) +#NFA_CONFIG_FORMAT=2 ############################################################################### # SPD Debug mode @@ -204,14 +199,6 @@ SCREEN_OFF_POWER_STATE=3 # Note, this resets after a power-cycle. #SPD_MAX_RETRY_COUNT=3 -############################################################################### -# Patch RAM Version Checking -# By default the stack will reject any attempt to download a patch where major -# version is < the one that is in NVM. If this config item is set to 1 then -# this version check is skipped. -# -#SPD_IGNORE_VERSION=0 - ############################################################################### # transport driver # @@ -276,20 +263,67 @@ BCMI2CNFC_ADDRESS=0 NFC_WRITE_DELAY=20 ############################################################################### -# Configure the default NfcA/IsoDep techology and protocol route. Can be -# either a secure element (e.g. 0xF4) or the host (0x00) -DEFAULT_ISODEP_ROUTE=0xF3 +# Maximum Number of Credits to be allowed by the NFCC +# This value overrides what the NFCC specifices allowing the host to have +# the control to work-around transport limitations. If this value does +# not exist or is set to 0, the NFCC will provide the number of credits. +MAX_RF_DATA_CREDITS=1 ############################################################################### -# Configure the default "off-host" AID route. The default is 0xF4 -DEFAULT_OFFHOST_ROUTE=0xF4 +# Antenna Configuration - This data is used when setting 0xC8 config item +# at startup (before discovery is started). If not used, no value is sent. +# +# The settings for this value are documented here: +# http://wcgbu.broadcom.com/wpan/PM/Project%20Document%20Library/bcm20791B0/ +# Design/Doc/PHY%20register%20settings/BCM20791-B2-1027-02_PHY_Recommended_Reg_Settings.xlsx +# This document is maintained by Paul Forshaw. +# +# The values marked as ?? should be tweaked per antenna or customer/app: +# {20:C8:1E:06:??:00:??:??:??:00:??:24:00:1C:00:75:00:77:00:76:00:1C:00:03:00:0A:00:??:01:00:00:40:04} +# array[0] = 0x20 is length of the payload from array[1] to the end +# array[1] = 0xC8 is PREINIT_DSP_CFG +#PREINIT_DSP_CFG={20:C8:1E:06:1F:00:0F:03:3C:00:04:24:00:1C:00:75:00:77:00:76:00:1C:00:03:00:0A:00:48:01:00:00:40:04} -POWER_SAVER_WORKAROUND_1=0xF3 -PRESENCE_CHECK_ALGORITHM=1 +############################################################################### +# Force tag polling for the following technology(s). +# The bits are defined as tNFA_TECHNOLOGY_MASK in nfa_api.h. +# Default is NFA_TECHNOLOGY_MASK_A | NFA_TECHNOLOGY_MASK_B | +# NFA_TECHNOLOGY_MASK_F | NFA_TECHNOLOGY_MASK_ISO15693 | +# NFA_TECHNOLOGY_MASK_B_PRIME | NFA_TECHNOLOGY_MASK_KOVIO | +# NFA_TECHNOLOGY_MASK_A_ACTIVE | NFA_TECHNOLOGY_MASK_F_ACTIVE. +# +# Notable bits: +# NFA_TECHNOLOGY_MASK_A 0x01 /* NFC Technology A */ +# NFA_TECHNOLOGY_MASK_B 0x02 /* NFC Technology B */ +# NFA_TECHNOLOGY_MASK_F 0x04 /* NFC Technology F */ +# NFA_TECHNOLOGY_MASK_ISO15693 0x08 /* Proprietary Technology */ +# NFA_TECHNOLOGY_MASK_KOVIO 0x20 /* Proprietary Technology */ +# NFA_TECHNOLOGY_MASK_A_ACTIVE 0x40 /* NFC Technology A active mode */ +# NFA_TECHNOLOGY_MASK_F_ACTIVE 0x80 /* NFC Technology F active mode */ +POLLING_TECH_MASK=0xEF -AID_MATCHING_MODE=2 -AUTO_ADJUST_ISO_BITRATE=1 +############################################################################### +# Force P2P to only listen for the following technology(s). +# The bits are defined as tNFA_TECHNOLOGY_MASK in nfa_api.h. +# Default is NFA_TECHNOLOGY_MASK_A | NFA_TECHNOLOGY_MASK_F | +# NFA_TECHNOLOGY_MASK_A_ACTIVE | NFA_TECHNOLOGY_MASK_F_ACTIVE +# +# Notable bits: +# NFA_TECHNOLOGY_MASK_A 0x01 /* NFC Technology A */ +# NFA_TECHNOLOGY_MASK_F 0x04 /* NFC Technology F */ +# NFA_TECHNOLOGY_MASK_A_ACTIVE 0x40 /* NFC Technology A active mode */ +# NFA_TECHNOLOGY_MASK_F_ACTIVE 0x80 /* NFC Technology F active mode */ +P2P_LISTEN_TECH_MASK=0xC5 +PRESERVE_STORAGE=0x01 + +############################################################################### +# Maximum EE supported number +# NXP PN547C2 0x02 +# NXP PN65T 0x03 +NFA_MAX_EE_SUPPORTED=0x03 + +############################################################################### # NCI Hal Module name -NCI_HAL_MODULE="nfc_nci.bcm2079x" +NCI_HAL_MODULE="nfc_nci.pn54x" diff --git a/configs/libnfc-nxp.conf b/configs/libnfc-nxp.conf new file mode 100644 index 0000000..e3cc0ea --- /dev/null +++ b/configs/libnfc-nxp.conf @@ -0,0 +1,340 @@ +## This file is used by NFC NXP NCI HAL(external/libnfc-nci/halimpl/pn547) +## and NFC Service Java Native Interface Extensions (packages/apps/Nfc/nci/jni/extns/pn547) + +############################################################################### +# Application options +# Logging Levels +# NXPLOG_DEFAULT_LOGLEVEL 0x01 +# ANDROID_LOG_DEBUG 0x03 +# ANDROID_LOG_WARN 0x02 +# ANDROID_LOG_ERROR 0x01 +# ANDROID_LOG_SILENT 0x00 +# +NXPLOG_EXTNS_LOGLEVEL=0x02 +NXPLOG_NCIHAL_LOGLEVEL=0x02 +NXPLOG_NCIX_LOGLEVEL=0x02 +NXPLOG_NCIR_LOGLEVEL=0x02 +NXPLOG_FWDNLD_LOGLEVEL=0x02 +NXPLOG_TML_LOGLEVEL=0x02 + +############################################################################### +# Extension for Mifare reader enable +# 0x00 - Disabled +# 0x01 - Enabled +MIFARE_READER_ENABLE=0x01 + +############################################################################### +# File location for Firmware +#FW_STORAGE="/vendor/firmware/libpn547_fw.so" + +############################################################################### +# System clock source selection configuration +#define CLK_SRC_XTAL 1 +#define CLK_SRC_PLL 2 + +NXP_SYS_CLK_SRC_SEL=0x02 + +############################################################################### +# System clock frequency selection configuration +#define CLK_FREQ_13MHZ 1 +#define CLK_FREQ_19_2MHZ 2 +#define CLK_FREQ_24MHZ 3 +#define CLK_FREQ_26MHZ 4 +#define CLK_FREQ_38_4MHZ 5 +#define CLK_FREQ_52MHZ 6 + +NXP_SYS_CLK_FREQ_SEL=0x02 + +############################################################################### +# The timeout value to be used for clock request acknowledgment +# min value = 0x01 to max = 0x19 + +NXP_SYS_CLOCK_TO_CFG=0x0A + +############################################################################### +# NXP proprietary settings +NXP_ACT_PROP_EXTN={2F, 02, 00} + +############################################################################### +# NFC forum profile settings +NXP_NFC_PROFILE_EXTN={20, 02, 05, 01, A0, 44, 01, 00} + +############################################################################### +# Standby enable settings +NXP_CORE_STANDBY={2F, 00, 01, 01} + + +############################################################################### +# NXP RF ALM (NO BOOSTER) configuration settings for FW VERSION = 08.01.1D +############################################################################### + +# *** ALM(NO BOOSTER) FW VERSION = 08.01.1D *** +NXP_RF_CONF_BLK_1={ + 20, 02, F3, 20, + A0, 0D, 03, 00, 40, 03, + A0, 0D, 03, 04, 43, 20, + A0, 0D, 03, 04, FF, 05, + A0, 0D, 06, 06, 44, A3, 90, 03, 00, + A0, 0D, 06, 06, 30, CF, 00, 08, 00, + A0, 0D, 06, 06, 2F, 8F, 05, 80, 0C, + A0, 0D, 04, 06, 03, 00, 71, + A0, 0D, 03, 06, 48, 18, + A0, 0D, 03, 06, 43, A0, + A0, 0D, 06, 06, 42, 00, 00, F1, F6, + A0, 0D, 06, 06, 41, 80, 00, 00, 00, + A0, 0D, 03, 06, 37, 18, + A0, 0D, 03, 06, 16, 00, + A0, 0D, 03, 06, 15, 00, + A0, 0D, 06, 06, FF, 05, 00, 00, 00, + A0, 0D, 06, 08, 44, 00, 00, 00, 00, + A0, 0D, 06, 20, 4A, 00, 00, 00, 00, + A0, 0D, 06, 20, 42, 88, 10, FF, FF, + A0, 0D, 03, 20, 16, 00, + A0, 0D, 03, 20, 15, 00, + A0, 0D, 06, 22, 44, 22, 00, 02, 00, + A0, 0D, 06, 22, 2D, 50, 44, 0C, 00, + A0, 0D, 04, 32, 03, 40, 3D, + A0, 0D, 06, 32, 42, F8, 10, FF, FF, + A0, 0D, 03, 32, 16, 00, + A0, 0D, 03, 32, 15, 01, + A0, 0D, 03, 32, 0D, 22, + A0, 0D, 03, 32, 14, 22, + A0, 0D, 06, 32, 4A, 30, 07, 01, 1F, + A0, 0D, 06, 34, 2D, 24, 77, 0C, 00, + A0, 0D, 06, 34, 34, 00, 00, E4, 03, + A0, 0D, 06, 34, 44, 21, 00, 02, 00 +} +# *** ALM(NO BOOSTER) FW VERSION = 08.01.1D *** +NXP_RF_CONF_BLK_2={ + 20, 02, F4, 1F, + A0, 0D, 06, 35, 44, 21, 00, 02, 00, + A0, 0D, 06, 38, 4A, 53, 07, 01, 1B, + A0, 0D, 06, 38, 42, 68, 10, FF, FF, + A0, 0D, 03, 38, 16, 00, + A0, 0D, 03, 38, 15, 00, + A0, 0D, 06, 3A, 2D, 15, 47, 0D, 00, + A0, 0D, 06, 3C, 4A, 52, 07, 01, 1B, + A0, 0D, 06, 3C, 42, 68, 10, FF, FF, + A0, 0D, 03, 3C, 16, 00, + A0, 0D, 03, 3C, 15, 00, + A0, 0D, 06, 3E, 2D, 15, 47, 0D, 00, + A0, 0D, 06, 40, 42, F0, 10, FF, FF, + A0, 0D, 03, 40, 0D, 02, + A0, 0D, 03, 40, 14, 02, + A0, 0D, 06, 40, 4A, 12, 07, 00, 00, + A0, 0D, 03, 40, 16, 00, + A0, 0D, 03, 40, 15, 00, + A0, 0D, 06, 42, 2D, 15, 47, 0D, 00, + A0, 0D, 06, 46, 44, 21, 00, 02, 00, + A0, 0D, 06, 46, 2D, 05, 47, 0E, 00, + A0, 0D, 06, 44, 4A, 33, 07, 01, 07, + A0, 0D, 06, 44, 42, 88, 10, FF, FF, + A0, 0D, 03, 44, 16, 00, + A0, 0D, 03, 44, 15, 00, + A0, 0D, 06, 4A, 44, 22, 00, 02, 00, + A0, 0D, 06, 4A, 2D, 05, 37, 0C, 00, + A0, 0D, 06, 48, 4A, 33, 07, 01, 07, + A0, 0D, 06, 48, 42, 88, 10, FF, FF, + A0, 0D, 03, 48, 16, 00, + A0, 0D, 03, 48, 15, 00, + A0, 0D, 06, 4E, 44, 22, 00, 02, 00 +} +# *** ALM(NO BOOSTER) FW VERSION = 08.01.1D *** +NXP_RF_CONF_BLK_3={ + 20, 02, F7, 1E, + A0, 0D, 06, 4E, 2D, 05, 37, 0C, 00, + A0, 0D, 06, 4C, 4A, 33, 07, 01, 07, + A0, 0D, 06, 4C, 42, 88, 10, FF, FF, + A0, 0D, 03, 4C, 16, 00, + A0, 0D, 03, 4C, 15, 00, + A0, 0D, 06, 52, 44, 22, 00, 02, 00, + A0, 0D, 06, 52, 2D, 05, 25, 0C, 00, + A0, 0D, 06, 50, 42, 90, 10, FF, FF, + A0, 0D, 06, 50, 4A, 11, 0F, 01, 07, + A0, 0D, 03, 50, 16, 00, + A0, 0D, 03, 50, 15, 00, + A0, 0D, 06, 56, 2D, 05, 9E, 0C, 00, + A0, 0D, 06, 56, 44, 22, 00, 02, 00, + A0, 0D, 06, 5C, 2D, 05, 69, 0C, 00, + A0, 0D, 06, 5C, 44, 21, 00, 02, 00, + A0, 0D, 06, 54, 42, 88, 10, FF, FF, + A0, 0D, 06, 54, 4A, 33, 07, 01, 07, + A0, 0D, 03, 54, 16, 00, + A0, 0D, 03, 54, 15, 00, + A0, 0D, 06, 5A, 42, 90, 10, FF, FF, + A0, 0D, 06, 5A, 4A, 31, 07, 01, 07, + A0, 0D, 03, 5A, 16, 00, + A0, 0D, 03, 5A, 15, 00, + A0, 0D, 06, 98, 2F, AF, 05, 80, 0F, + A0, 0D, 06, 9A, 42, 00, 00, F1, F6, + A0, 0D, 06, 30, 44, A3, 90, 03, 00, + A0, 0D, 06, 6C, 44, A3, 90, 03, 00, + A0, 0D, 06, 6C, 30, CF, 00, 08, 00, + A0, 0D, 06, 6C, 2F, 8F, 05, 80, 0C, + A0, 0D, 06, 70, 2F, 8F, 05, 80, 12 +} +# *** ALM(NO BOOSTER) FW VERSION = 08.01.1D *** +NXP_RF_CONF_BLK_4={ + 20, 02, F7, 1E, + A0, 0D, 06, 70, 30, CF, 00, 08, 00, + A0, 0D, 06, 74, 2F, 8F, 05, 80, 12, + A0, 0D, 06, 74, 30, DF, 00, 07, 00, + A0, 0D, 06, 78, 2F, 1F, 06, 80, 01, + A0, 0D, 06, 78, 30, 3F, 00, 04, 00, + A0, 0D, 06, 78, 44, A2, 90, 03, 00, + A0, 0D, 03, 78, 47, 00, + A0, 0D, 06, 7C, 2F, AF, 05, 80, 0F, + A0, 0D, 06, 7C, 30, CF, 00, 07, 00, + A0, 0D, 06, 7C, 44, A3, 90, 03, 00, + A0, 0D, 06, 7D, 30, CF, 00, 08, 00, + A0, 0D, 06, 80, 2F, AF, 05, 80, 90, + A0, 0D, 06, 80, 44, A3, 90, 03, 00, + A0, 0D, 06, 84, 2F, AF, 05, 80, 92, + A0, 0D, 06, 84, 44, A3, 90, 03, 00, + A0, 0D, 06, 88, 2F, 7F, 04, 80, 10, + A0, 0D, 06, 88, 30, 5F, 00, 16, 00, + A0, 0D, 03, 88, 47, 00, + A0, 0D, 06, 88, 44, A1, 90, 03, 00, + A0, 0D, 03, 0C, 48, 18, + A0, 0D, 03, 10, 43, 20, + A0, 0D, 06, 6A, 42, F8, 10, FF, FF, + A0, 0D, 03, 6A, 16, 00, + A0, 0D, 03, 6A, 15, 01, + A0, 0D, 06, 6A, 4A, 30, 0F, 01, 1F, + A0, 0D, 06, 8C, 42, 88, 10, FF, FF, + A0, 0D, 06, 8C, 4A, 33, 07, 01, 07, + A0, 0D, 03, 8C, 16, 00, + A0, 0D, 03, 8C, 15, 00, + A0, 0D, 06, 92, 42, 90, 10, FF, FF +} +# *** ALM(NO BOOSTER) FW VERSION = 08.01.1D *** +NXP_RF_CONF_BLK_5={ + 20, 02, 37, 07, + A0, 0D, 06, 92, 4A, 31, 07, 01, 07, + A0, 0D, 03, 92, 16, 00, + A0, 0D, 03, 92, 15, 00, + A0, 0D, 06, 0A, 30, CF, 00, 08, 00, + A0, 0D, 06, 0A, 2F, 8F, 05, 80, 0C, + A0, 0D, 03, 0A, 48, 10, + A0, 0D, 06, 0A, 44, A3, 90, 03, 00 +} +# *** ALM(NO BOOSTER) FW VERSION = 08.01.1D *** +NXP_RF_CONF_BLK_6={ +} + +############################################################################### +# Core configuration extensions +# It includes +# A002 - Clock Request +# 0x00 - Disabled +# 0x01 - Enabled +# A003 - Clock Selection +# Please refer to User Manual +# A004 - Clock Time Out +# Defined in ms +# A00E - Load Modulation Mode +# 0x00 - PLM +# 0x01 - ALM +# A011 - Clock specific configuration +# Please refer to User Manual +# A012 - NFCEE interface 2 configuration +# 0x00 - SWP 2 interface is used +# 0x02 - DWP interface is used +# A013 - TVdd configuration +# 0x00 - TVdd is set to 3.1V in Poll mode +# 0x02 - TVdd is set to 2.7V in Poll mode +# A040-A043 - Low Power Card Detector +# Please refer to Application Note of LPCD +# A05E - Jewel Reader +# 0x00 - RID is not sent during activation +# 0x01 - RID is sent during activation +# A061 - Retry after LPCD +# 0b0000XXXX - Number of retry if activation failed +# 0bXXXX0000 - Duration to wait before retry (10ms per step) +# Please refer to User Manual +# A0CD - SWP interface 1: S1 line behavior +# Defined S1 High time-out during Activation sequence +# A0EC - SWP1 interface +# 0x00 - Disabled +# 0x01 - Enabled +# A0ED - SWP2 interface +# 0x00 - Disabled +# 0x01 - Enabled +NXP_CORE_CONF_EXTN={20, 02, 40, 0F, + A0, 02, 01, 01, + A0, 04, 01, 0A, + A0, 0E, 01, 01, + A0, 11, 04, 01, 22, 67, CD, + A0, 12, 01, 00, + A0, 13, 01, 00, + A0, 40, 01, 01, + A0, 41, 01, 02, + A0, 42, 01, 19, + A0, 43, 01, 00, + A0, 5E, 01, 01, + A0, 61, 01, 00, + A0, CD, 01, 0F, + A0, EC, 01, 01, + A0, ED, 01, 00 + } + +############################################################################### +# Core configuration settings +# It includes +# 18 - Poll Mode NFC-F: PF_BIT_RATE +# 21 - Poll Mode ISO-DEP: PI_BIT_RATE +# 28 - Poll Mode NFC-DEP: PN_NFC_DEP_SPEED +# 30 - Lis. Mode NFC-A: LA_BIT_FRAME_SDD +# 31 - Lis. Mode NFC-A: LA_PLATFORM_CONFIG +# 33 - Lis. Mode NFC-A: LA_SEL_INFO +# 50 - Lis. Mode NFC-F: LF_PROTOCOL_TYPE +# 54 - Lis. Mode NFC-F: LF_CON_BITR_F +# 5B - Lis. Mode ISO-DEP: LI_BIT_RATE +# 60 - Lis. Mode NFC-DEP: LN_WT +# 80 - Other Param.: RF_FIELD_INFO +# 81 - Other Param.: RF_NFCEE_ACTION +# 82 - Other Param.: NFCDEP_OP +NXP_CORE_CONF={ 20, 02, 2E, 0E, + 18, 01, 01, + 21, 01, 00, + 28, 01, 01, + 30, 01, 08, + 31, 01, 03, + 33, 04, 01, 02, 03, 04, + 50, 01, 02, + 54, 01, 06, + 5B, 01, 00, + 60, 01, 0E, + 80, 01, 01, + 81, 01, 01, + 82, 01, 0E, + 3C, 01, 04 + } + +############################################################################### +# Mifare Classic Key settings +#NXP_CORE_MFCKEY_SETTING={20, 02, 25,04, A0, 51, 06, A0, A1, A2, A3, A4, A5, +# A0, 52, 06, D3, F7, D3, F7, D3, F7, +# A0, 53, 06, FF, FF, FF, FF, FF, FF, +# A0, 54, 06, 00, 00, 00, 00, 00, 00} + +############################################################################### +# Default SE Options +# No secure element 0x00 +# eSE 0x01 +# UICC 0x02 +# MULTI_SE 0x03 +NXP_DEFAULT_SE=0x02 + +############################################################################### +NXP_DEFAULT_NFCEE_TIMEOUT=0x06 + +############################################################################### +#Enable SWP full power mode when phone is power off +NXP_SWP_FULL_PWR_ON=0x00 + +############################################################################### +#Chip type +#PN547C2 0x01 +#PN65T 0x02 +NXP_NFC_CHIP=0x01 diff --git a/device.mk b/device.mk index bd92c36..d9326b9 100644 --- a/device.mk +++ b/device.mk @@ -48,11 +48,14 @@ PRODUCT_COPY_FILES += \ frameworks/native/data/etc/android.hardware.telephony.gsm.xml:system/etc/permissions/android.hardware.telephony.gsm.xml \ frameworks/native/data/etc/handheld_core_hardware.xml:system/etc/permissions/handheld_core_hardware.xml -# NFC Permissions -#PRODUCT_COPY_FILES += \ -# frameworks/native/data/etc/android.hardware.nfc.xml:system/etc/permissions/android.hardware.nfc.xml \ -# frameworks/native/data/etc/android.hardware.nfc.hce.xml:system/etc/permissions/android.hardware.nfc.hce.xml \ -# frameworks/native/data/etc/com.android.nfc_extras.xml:system/etc/permissions/com.android.nfc_extras.xml +# SONY NFC Permissions +PRODUCT_COPY_FILES += \ + frameworks/native/data/etc/android.hardware.nfc.xml:system/etc/permissions/android.hardware.nfc.xml \ + frameworks/native/data/etc/com.android.nfc_extras.xml:system/etc/permissions/com.android.nfc_extras.xml \ + frameworks/native/data/etc/com.nxp.mifare.xml:system/etc/permissions/com.nxp.mifare.xml \ + $(LOCAL_PATH)/configs/libnfc-brcm.conf:system/etc/libnfc-brcm.conf \ + $(LOCAL_PATH)/configs/libnfc-nxp.conf:system/etc/libnfc-nxp.conf \ + $(LOCAL_PATH)/configs/nfcee_access.xml:system/etc/nfcee_access.xml # Audio PRODUCT_COPY_FILES += \ @@ -110,18 +113,11 @@ PRODUCT_PACKAGES += \ PRODUCT_COPY_FILES += \ $(LOCAL_PATH)/configs/media_profiles.xml:system/etc/media_profiles.xml -# NFC -#PRODUCT_PACKAGES += \ -# com.android.nfc_extras \ -# NfcNci \ -# nfc_nci.bcm2079x.msm8974 \ -# Tag - -#PRODUCT_COPY_FILES += \ -# $(LOCAL_PATH)/configs/nfcee_access.xml:system/etc/nfcee_access.xml \ -# $(LOCAL_PATH)/configs/libnfc-brcm-20791b05.conf:system/etc/libnfc-brcm-20791b05.conf \ -# $(LOCAL_PATH)/configs/libnfc-brcm-20791b04.conf:system/etc/libnfc-brcm-20791b04.conf \ -# $(LOCAL_PATH)/configs/libnfc-brcm.conf:system/etc/libnfc-brcm.conf +# SONY NFC +PRODUCT_PACKAGES += \ + com.android.nfc_extras \ + NfcSony \ + Tag # Radio PRODUCT_PACKAGES += \ @@ -136,7 +132,12 @@ PRODUCT_PACKAGES += \ init.crda.sh \ init.qcom.rc \ init.qcom.usb.rc \ - ueventd.qcom.rc + ueventd.qcom.rc \ + +# Ramdisk for felica +PRODUCT_PACKAGES += \ + init.carrier.rc \ + init.felica.sh # Thermal PRODUCT_COPY_FILES += \ diff --git a/overlay/frameworks/base/core/res/res/values/config.xml b/overlay/frameworks/base/core/res/res/values/config.xml index e7223b7..d09404f 100644 --- a/overlay/frameworks/base/core/res/res/values/config.xml +++ b/overlay/frameworks/base/core/res/res/values/config.xml @@ -19,7 +19,37 @@ - + + + + managed_profile + ime + sync_failing + sync_active + cast + hotspot + location + su + bluetooth + nfc + tty + speakerphone + headset + zen + mute + volume + wifi + cdma_eri + data_connection + phone_evdo_signal + phone_signal + battery + alarm_clock + secure + clock + felica_lock + false diff --git a/overlay/frameworks/base/packages/Keyguard/res/values-en-rGB/strings.xml b/overlay/frameworks/base/packages/Keyguard/res/values-en-rGB/strings.xml new file mode 100644 index 0000000..be4efdc --- /dev/null +++ b/overlay/frameworks/base/packages/Keyguard/res/values-en-rGB/strings.xml @@ -0,0 +1,36 @@ + + + + + + "Keyguard" + + "I'm Full Man, UnPlug Me !!" + + "Leave Me Alone, I'm Eating" + + "Come On, Charge Faster You Piece Of Crap" + + "Come On, Charge Faster You Piece Of Crap" + + "Hey, Need Some Electricity Here !!" + + diff --git a/overlay/frameworks/base/packages/Keyguard/res/values/config.xml b/overlay/frameworks/base/packages/Keyguard/res/values/config.xml new file mode 100644 index 0000000..90d3b18 --- /dev/null +++ b/overlay/frameworks/base/packages/Keyguard/res/values/config.xml @@ -0,0 +1,28 @@ + + + + + + + + 900 + + + 1500 + diff --git a/overlay/frameworks/base/packages/Keyguard/res/values/strings.xml b/overlay/frameworks/base/packages/Keyguard/res/values/strings.xml new file mode 100644 index 0000000..437414a --- /dev/null +++ b/overlay/frameworks/base/packages/Keyguard/res/values/strings.xml @@ -0,0 +1,45 @@ + + + + + + Keyguard + + + Im Full Man, UnPlug Me !! + + + Leave Me Alone, Im Eating + + + Come On, Charge Faster You Piece Of Crap + + + Come On, Charge Faster You Piece Of Crap + + + Hey, Need Some Electricity Here !! + + diff --git a/rootdir/Android.mk b/rootdir/Android.mk index 84881d3..c71546b 100644 --- a/rootdir/Android.mk +++ b/rootdir/Android.mk @@ -39,3 +39,19 @@ LOCAL_MODULE_TAGS := optional eng LOCAL_MODULE_CLASS := ETC LOCAL_SRC_FILES := etc/init.crda.sh include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := init.carrier.rc +LOCAL_MODULE_TAGS := optional eng +LOCAL_MODULE_CLASS := ETC +LOCAL_SRC_FILES := etc/init.carrier.rc +LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT) +include $(BUILD_PREBUILT) + +include $(CLEAR_VARS) +LOCAL_MODULE := init.felica.sh +LOCAL_MODULE_TAGS := optional eng +LOCAL_MODULE_CLASS := ETC +LOCAL_SRC_FILES := etc/init.felica.sh +LOCAL_MODULE_PATH := $(TARGET_ROOT_OUT) +include $(BUILD_PREBUILT) diff --git a/rootdir/etc/fstab.twrp b/rootdir/etc/fstab.twrp index 2fd92a7..0f2e7e1 100644 --- a/rootdir/etc/fstab.twrp +++ b/rootdir/etc/fstab.twrp @@ -8,6 +8,6 @@ /efs3 emmc /dev/block/platform/msm_sdcc.1/by-name/modemst2 flags=backup=1;subpartitionof=/efs1 /external_sd vfat /dev/block/mmcblk1p1 /dev/block/mmcblk1 flags=display="Micro SDcard";storage;wipeingui;removable /usbstorage vfat /dev/block/sda1 /dev/block/sda flags=display="USB Storage";storage;wipeingui;removable -/modem emmc /dev/block/platform/msm_sdcc.1/by-name/modem flags=backup=1;display="Modem";fsflags=ro +/modem vfat /dev/block/platform/msm_sdcc.1/by-name/modem flags=backup=1;display="Modem";fsflags=rw,context=u:object_r:firmware_file:s0 /firmware vfat /dev/block/platform/msm_sdcc.1/by-name/apnhlos flags=backup=1;subpartitionof=/modem;mounttodecrypt;fsflags=ro /misc emmc /dev/block/platform/msm_sdcc.1/by-name/fota diff --git a/rootdir/etc/init.carrier.rc b/rootdir/etc/init.carrier.rc new file mode 100644 index 0000000..5bda92f --- /dev/null +++ b/rootdir/etc/init.carrier.rc @@ -0,0 +1,54 @@ +# Copyright (C) 2012 The Android Open Source Project +# +# IMPORTANT: Do not create world writable files or directories. +# This is a common source of Android security bugs. +# + +# Tmm Add Start +on init + export EXTERNAL_STORAGE_DOCOMO /storage/extSdCard + +on post-fs-data +# FeliCa + exec /system/bin/sh /init.felica.sh + + mkdir /efs/FeliCaLock 0770 system system + chown system system /efs/FeliCaLock/01 + chmod 0660 /efs/FeliCaLock/01 + chown system system /efs/FeliCaLock/02 + chmod 0660 /efs/FeliCaLock/02 + +# Japan Add NFC Type Setting(Osaifu.cfg) + mkdir /data/misc/osaifu 0755 system system + chmod 644 /data/misc/osaifu/osaifu.cfg + chown system system /data/misc/osaifu/osaifu.cfg + +# Fingerprint + mkdir /dev/validity 0770 system system + +service mfsc /system/bin/mfsc + class core + user root + group system felicalock nfc + oneshot + +service mfdp /system/bin/mfdp + class core + user root + group system felicalock nfc + oneshot + +# JPN: For MobileTV [ISDBT] \android\device\samsung\kltedcm\init.kltedcm.rc + mkdir /data/one-seg 0775 system system + chown system system /data/one-seg + chmod 0777 /data/one-seg + + chown system system /dev/isdbt + chmod 0666 /dev/isdbt + +# JPN: For MobileTV [ISDBT] \android\device\samsung\kltedcm\init.kltedcm.rc +service mobileTV /system/bin/broadcastProcessObserver + class main + user system + group system radio audio camera graphics inet net_bt net_bt_admin net_raw sdcard_rw sdcard_r shell + diff --git a/rootdir/etc/init.felica.sh b/rootdir/etc/init.felica.sh new file mode 100755 index 0000000..fced8b7 --- /dev/null +++ b/rootdir/etc/init.felica.sh @@ -0,0 +1,21 @@ +#!/system/bin/sh + +#/sbin/setpropex ro.warranty_bit 0 +#/sbin/setpropex ro.emmc_checksum 0 + +KBC_DATA_PATH=/data/media/0/kbc + +CMDLINE_FILE=$KBC_DATA_PATH/cmdline +if [ -f $CMDLINE_FILE ]; then + FELICA_CMDLINE=`cat $CMDLINE_FILE` + echo "$FELICA_CMDLINE" > /proc/cmdline + exit 0 +fi + +FELICA_KEY_FILE=$KBC_DATA_PATH/felica_key +if [ -f $FELICA_KEY_FILE ]; then + FELICA_KEY=`cat $FELICA_KEY_FILE` + BASE_CMDLINE=`cat /proc/cmdline` + echo "cordon=$FELICA_KEY $BASE_CMDLINE" > /proc/cmdline + exit 0 +fi diff --git a/rootdir/etc/init.qcom.rc b/rootdir/etc/init.qcom.rc index 95dfd2b..a1f4377 100644 --- a/rootdir/etc/init.qcom.rc +++ b/rootdir/etc/init.qcom.rc @@ -26,6 +26,7 @@ # import init.qcom.usb.rc +import init.carrier.rc on early-init export LD_SHIM_LIBS "/system/lib/libril.so|libril_shim.so" diff --git a/rootdir/etc/ueventd.qcom.rc b/rootdir/etc/ueventd.qcom.rc index 19025dd..46565f7 100644 --- a/rootdir/etc/ueventd.qcom.rc +++ b/rootdir/etc/ueventd.qcom.rc @@ -226,15 +226,15 @@ v/ppp 0660 radio vpn /sys/devices/i2c.73/i2c-16/16-0018/input/input* delay 0664 system system #JPN FeliCa -/dev/felica 0660 mfc system -/dev/felica_pon 0660 mfc system -/dev/felica_cen 0660 mfc felicalock -/dev/felica_rfs 0440 mfc system -/dev/felica_rws 0660 mfc system -/dev/felica_ant 0660 mfc system -/dev/felica_int_poll 0400 mfc system -/dev/felica_uid 0220 mfc system -/dev/felica_uicc 0660 mfc system +/dev/felica 0666 root system +/dev/felica_pon 0666 root system +/dev/felica_cen 0666 root system +/dev/felica_rfs 0444 root system +/dev/felica_rws 0666 root system +/dev/felica_ant 0666 root system +/dev/felica_int_poll 0400 root system +/dev/felica_uid 0222 root system +/dev/felica_uicc 0666 root system #JPN NFC /dev/ttyHSL1 0660 nfc nfc