Zwiększając bezpieczeństwo swojej aplikacji, pomagasz zachować zaufanie użytkowników i integralność urządzeń.
Na tej stronie znajdziesz kilka sprawdzonych metod, które mają znaczący, pozytywny wpływ na bezpieczeństwo aplikacji.
Wymuszanie bezpiecznej komunikacji
Zabezpieczenie danych wymienianych między aplikacją a innymi aplikacjami lub między aplikacją a witryną zwiększa stabilność aplikacji i chroni przesyłane i odbierane dane.
Zabezpieczanie komunikacji między aplikacjami
Aby zapewnić bezpieczniejszą komunikację między aplikacjami, używaj domyślnych intencji z wybierakiem aplikacji, uprawnień na podstawie sygnatury i nieeksportowanych dostawców treści.
Wyświetlanie selektora aplikacji
Jeśli niejawna intencja może uruchomić co najmniej 2 aplikacje na urządzeniu użytkownika, wyświetl wyraźnie selektor aplikacji. Ta strategia interakcji umożliwia użytkownikom przesyłanie informacji poufnych do aplikacji, której ufają.
Kotlin
val intent = Intent(Intent.ACTION_SEND) val possibleActivitiesList: List<ResolveInfo> = packageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL) // Verify that an activity in at least two apps on the user's device // can handle the intent. Otherwise, start the intent only if an app // on the user's device can handle the intent. if (possibleActivitiesList.size > 1) { // Create intent to show chooser. // Title is something similar to "Share this photo with." val chooser = resources.getString(R.string.chooser_title).let { title -> Intent.createChooser(intent, title) } startActivity(chooser) } else if (intent.resolveActivity(packageManager) != null) { startActivity(intent) }
Java
Intent intent = new Intent(Intent.ACTION_SEND); List<ResolveInfo> possibleActivitiesList = getPackageManager() .queryIntentActivities(intent, PackageManager.MATCH_ALL); // Verify that an activity in at least two apps on the user's device // can handle the intent. Otherwise, start the intent only if an app // on the user's device can handle the intent. if (possibleActivitiesList.size() > 1) { // Create intent to show chooser. // Title is something similar to "Share this photo with." String title = getResources().getString(R.string.chooser_title); Intent chooser = Intent.createChooser(intent, title); startActivity(chooser); } else if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); }
Powiązane informacje:
Stosowanie uprawnień na podstawie podpisu
Udostępniając dane między 2 aplikacjami, które są Twoją własnością lub są przez Ciebie kontrolowane, używaj uprawnień na podstawie sygnatury. Te uprawnienia nie wymagają potwierdzenia przez użytkownika. Zamiast tego sprawdzają, czy aplikacje uzyskujące dostęp do danych są podpisane tym samym kluczem podpisywania. Dzięki temu te uprawnienia zapewniają płynniejsze i bezpieczniejsze korzystanie z aplikacji.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp"> <permission android:name="my_custom_permission_name" android:protectionLevel="signature" />
Powiązane informacje:
Blokowanie dostępu do dostawców treści aplikacji
Jeśli nie zamierzasz wysyłać danych z aplikacji do innej aplikacji, której nie jesteś właścicielem, wyraźnie zablokuj dostęp do obiektów ContentProvider
aplikacji innych deweloperów. To ustawienie jest szczególnie ważne, jeśli aplikację można instalować na urządzeniach z Androidem 4.1.1 (poziom interfejsu API 16) lub starszym, ponieważ atrybut android:exported
elementu <provider>
w tych wersjach Androida domyślnie ma wartość true
.
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp"> <application ... > <provider android:name="android.support.v4.content.FileProvider" android:authorities="com.example.myapp.fileprovider" ... android:exported="false"> <!-- Place child elements of <provider> here. --> </provider> ... </application> </manifest>
Pytaj o dane uwierzytelniające przed wyświetleniem informacji poufnych
Gdy prosisz użytkowników o dane logowania, aby mogli uzyskać dostęp do poufnych informacji lub treści premium w aplikacji, poproś o podanie kodu PIN, hasła, wzoru lub danych biometrycznych, takich jak rozpoznawanie twarzy lub odcisków palców.
Więcej informacji o tym, jak poprosić o dane biometryczne, znajdziesz w przewodniku na temat uwierzytelniania biometrycznego.
Stosowanie zabezpieczeń sieciowych
W sekcjach poniżej znajdziesz informacje o tym, jak poprawić bezpieczeństwo sieci aplikacji.
Korzystanie z ruchu TLS
Jeśli Twoja aplikacja komunikuje się z serwerem WWW, który ma certyfikat wystawiony przez dobrze znany, zaufany urząd certyfikacji (CA), użyj żądania HTTPS w ten sposób:
Kotlin
val url = URL("https://www.google.com") val urlConnection = url.openConnection() as HttpsURLConnection urlConnection.connect() urlConnection.inputStream.use { ... }
Java
URL url = new URL("https://www.google.com"); HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection(); urlConnection.connect(); InputStream in = urlConnection.getInputStream();
Dodawanie konfiguracji zabezpieczeń sieci
Jeśli Twoja aplikacja używa nowych lub niestandardowych urzędów certyfikacji, możesz zadeklarować ustawienia zabezpieczeń sieci w pliku konfiguracyjnym. Pozwala to utworzyć konfigurację bez modyfikowania kodu aplikacji.
Aby dodać do aplikacji plik konfiguracji zabezpieczeń sieci:
- Zadeklaruj konfigurację w pliku manifestu aplikacji:
-
Dodaj plik zasobu XML znajdujący się w folderze
res/xml/network_security_config.xml
.Określ, że cały ruch do określonych domen musi używać protokołu HTTPS, wyłączając tekst zwykły:
<network-security-config> <domain-config cleartextTrafficPermitted="false"> <domain includeSubdomains="true">secure.example.com</domain> ... </domain-config> </network-security-config>
W trakcie procesu tworzenia możesz użyć elementu
<debug-overrides>
, aby zezwolić na zainstalowane przez użytkownika certyfikaty. Ten element zastępuje opcje krytyczne dla bezpieczeństwa w aplikacji podczas debugowania i testowania, nie wpływając na konfigurację wersji aplikacji. Poniższy fragment kodu pokazuje, jak zdefiniować ten element w pliku XML konfiguracji zabezpieczeń sieci aplikacji:<network-security-config> <debug-overrides> <trust-anchors> <certificates src="user" /> </trust-anchors> </debug-overrides> </network-security-config>
<manifest ... > <application android:networkSecurityConfig="@xml/network_security_config" ... > <!-- Place child elements of <application> element here. --> </application> </manifest>
Powiązane informacje: konfiguracja zabezpieczeń sieci
Tworzenie własnego menedżera zaufania
Sprawdzacz TLS nie powinien akceptować każdego certyfikatu. Może być konieczne skonfigurowanie menedżera zaufania i obsługa wszystkich ostrzeżeń TLS, które pojawiają się, jeśli w Twoim przypadku użycia występuje jeden z tych warunków:
- Komunikujesz się z serwerem WWW, który ma certyfikat podpisany przez nowy lub niestandardowy urząd certyfikacji.
- Urządzenie, którego używasz, nie ufa temu urzędowi certyfikacji.
- Nie możesz używać konfiguracji bezpieczeństwa sieci.
Więcej informacji o wykonaniu tych czynności znajdziesz w dyskusji na temat obsługi nieznanej instytucji certyfikującej.
Powiązane informacje:
Ostrożnie używaj obiektów WebView
Obiekty WebView
w aplikacji nie powinny pozwalać użytkownikom na przechodzenie do witryn, które są poza Twoją kontrolą. W miarę możliwości używaj listy dozwolonych, aby ograniczyć treści wczytywane przez obiekty WebView
w aplikacji.
Dodatkowo nigdy nie włączaj obsługi interfejsu JavaScript, chyba że całkowicie kontrolujesz zawartość obiektów WebView
swojej aplikacji i nie masz do niej zaufania.
Korzystanie z kanałów wiadomości HTML
Jeśli Twoja aplikacja musi korzystać z interfejsu JavaScript na urządzeniach z Androidem 6.0 (poziom interfejsu API 23) lub nowszym, zamiast komunikacji między witryną a aplikacją używaj kanałów wiadomości HTML, jak w tym fragmencie kodu:
Kotlin
val myWebView: WebView = findViewById(R.id.webview) // channel[0] and channel[1] represent the two ports. // They are already entangled with each other and have been started. val channel: Array<out WebMessagePort> = myWebView.createWebMessageChannel() // Create handler for channel[0] to receive messages. channel[0].setWebMessageCallback(object : WebMessagePort.WebMessageCallback() { override fun onMessage(port: WebMessagePort, message: WebMessage) { Log.d(TAG, "On port $port, received this message: $message") } }) // Send a message from channel[1] to channel[0]. channel[1].postMessage(WebMessage("My secure message"))
Java
WebView myWebView = (WebView) findViewById(R.id.webview); // channel[0] and channel[1] represent the two ports. // They are already entangled with each other and have been started. WebMessagePort[] channel = myWebView.createWebMessageChannel(); // Create handler for channel[0] to receive messages. channel[0].setWebMessageCallback(new WebMessagePort.WebMessageCallback() { @Override public void onMessage(WebMessagePort port, WebMessage message) { Log.d(TAG, "On port " + port + ", received this message: " + message); } }); // Send a message from channel[1] to channel[0]. channel[1].postMessage(new WebMessage("My secure message"));
Powiązane informacje:
Przyznaj odpowiednie uprawnienia
Poproś o dostęp do minimalnej liczby uprawnień, która jest niezbędna do prawidłowego działania aplikacji. Jeśli to możliwe, usuń uprawnienia, gdy aplikacja ich już nie potrzebuje.
Odkładanie uprawnień do czasu wykonania zamierzonego działania
W miarę możliwości nie dodawaj do aplikacji uprawnień pozwalających wykonać działanie, które można wykonać w innej aplikacji. Zamiast tego użyj intencji, która odroczy żądanie do innej aplikacji, która ma już odpowiednie uprawnienia.
Ten przykład pokazuje, jak użyć zamiaru, aby kierować użytkowników do aplikacji kontaktów zamiast prosić o uprawnienia READ_CONTACTS
i WRITE_CONTACTS
:
Kotlin
// Delegates the responsibility of creating the contact to a contacts app, // which has already been granted the appropriate WRITE_CONTACTS permission. Intent(Intent.ACTION_INSERT).apply { type = ContactsContract.Contacts.CONTENT_TYPE }.also { intent -> // Make sure that the user has a contacts app installed on their device. intent.resolveActivity(packageManager)?.run { startActivity(intent) } }
Java
// Delegates the responsibility of creating the contact to a contacts app, // which has already been granted the appropriate WRITE_CONTACTS permission. Intent insertContactIntent = new Intent(Intent.ACTION_INSERT); insertContactIntent.setType(ContactsContract.Contacts.CONTENT_TYPE); // Make sure that the user has a contacts app installed on their device. if (insertContactIntent.resolveActivity(getPackageManager()) != null) { startActivity(insertContactIntent); }
Gdy aplikacja musi przeprowadzać operację wejścia-wyjścia na podstawie plików, np. uzyskać dostęp do pamięci lub wybrać plik, nie potrzebuje specjalnych uprawnień, ponieważ system może wykonywać te operacje w imieniu aplikacji. Co więcej, gdy użytkownik wybierze treści w określonym identyfikatorze URI, wywoływana aplikacja otrzymuje uprawnienia do wybranego zasobu.
Powiązane informacje:
Bezpieczne udostępnianie danych w aplikacjach
Aby bezpieczniej udostępniać treści innym aplikacjom, postępuj zgodnie z tymi sprawdzonymi metodami:
- W razie potrzeby zastosuj uprawnienia tylko do odczytu lub tylko do zapisu.
-
Za pomocą flagi
FLAG_GRANT_READ_URI_PERMISSION
iFLAG_GRANT_WRITE_URI_PERMISSION
możesz przyznać klientom jednorazowy dostęp do danych. - Podczas udostępniania danych używaj identyfikatorów URI
content://
, a niefile://
. Przykłady:FileProvider
Ten fragment kodu pokazuje, jak za pomocą flag przyznawania uprawnień dotyczących identyfikatora URI i uprawnień dostawcy treści wyświetlić plik PDF aplikacji w oddzielnej aplikacji do przeglądania plików PDF:
Kotlin
// Create an Intent to launch a PDF viewer for a file owned by this app. Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("content://com.example/personal-info.pdf") // This flag gives the started app read access to the file. addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }.also { intent -> // Make sure that the user has a PDF viewer app installed on their device. intent.resolveActivity(packageManager)?.run { startActivity(intent) } }
Java
// Create an Intent to launch a PDF viewer for a file owned by this app. Intent viewPdfIntent = new Intent(Intent.ACTION_VIEW); viewPdfIntent.setData(Uri.parse("content://com.example/personal-info.pdf")); // This flag gives the started app read access to the file. viewPdfIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Make sure that the user has a PDF viewer app installed on their device. if (viewPdfIntent.resolveActivity(getPackageManager()) != null) { startActivity(viewPdfIntent); }
Uwaga: uruchamianie plików ze strony głównej aplikacji z możliwością zapisu stanowi naruszenie zasad W^X.
Z tego powodu zaufane aplikacje kierowane na Androida 10 (poziom interfejsu API 29) lub nowszego nie mogą wywoływać funkcji exec()
w plikach w katalogu domowym aplikacji, tylko w kodzie binarnym zawartym w pliku APK aplikacji.
Oprócz tego aplikacje kierowane na Androida 10 lub nowszego nie mogą modyfikować kodu wykonywalnego z plików, które zostały otwarte przy użyciu dlopen()
. Obejmuje to wszystkie pliki obiektów udostępnionych (.so
) z przemieszczonym tekstem.
Powiązane informacje:
android:grantUriPermissions
Bezpieczne przechowywanie danych
Chociaż aplikacja może wymagać dostępu do poufnych informacji użytkownika, użytkownicy przyznają jej dostęp do swoich danych tylko wtedy, gdy będą mieli pewność, że odpowiednio je zabezpieczasz.
przechowywanie prywatnych danych w pamięci wewnętrznej;
przechowywać wszystkie prywatne dane użytkownika w pamięci wewnętrznej urządzenia, która jest oddzielona od innych aplikacji, Jako dodatkowy środek bezpieczeństwa, gdy użytkownik odinstaluje aplikację, urządzenie usuwa wszystkie pliki zapisane przez tę aplikację na wewnętrznej pamięci.
Poniższy fragment kodu pokazuje jedną z możliwości zapisywania danych na wewnętrznym dysku:
Kotlin
// Creates a file with this name, or replaces an existing file // that has the same name. Note that the file name cannot contain // path separators. val FILE_NAME = "sensitive_info.txt" val fileContents = "This is some top-secret information!" File(filesDir, FILE_NAME).bufferedWriter().use { writer -> writer.write(fileContents) }
Java
// Creates a file with this name, or replaces an existing file // that has the same name. Note that the file name cannot contain // path separators. final String FILE_NAME = "sensitive_info.txt"; String fileContents = "This is some top-secret information!"; try (BufferedWriter writer = new BufferedWriter(new FileWriter(new File(getFilesDir(), FILE_NAME)))) { writer.write(fileContents); } catch (IOException e) { // Handle exception. }
Ten fragment kodu pokazuje odwrotną operację, czyli odczyt danych z pamięci wewnętrznej:
Kotlin
val FILE_NAME = "sensitive_info.txt" val contents = File(filesDir, FILE_NAME).bufferedReader().useLines { lines -> lines.fold("") { working, line -> "$working\n$line" } }
Java
final String FILE_NAME = "sensitive_info.txt"; StringBuffer stringBuffer = new StringBuffer(); try (BufferedReader reader = new BufferedReader(new FileReader(new File(getFilesDir(), FILE_NAME)))) { String line = reader.readLine(); while (line != null) { stringBuffer.append(line).append('\n'); line = reader.readLine(); } } catch (IOException e) { // Handle exception. }
Powiązane informacje:
Przechowywanie danych w pamięci zewnętrznej w zależności od przypadku użycia
Używaj zewnętrznego miejsca na dane w przypadku dużych plików bez poufnych danych, które są specyficzne dla Twojej aplikacji, a także plików, które Twoja aplikacja udostępnia innym aplikacjom. Konkretne interfejsy API, których używasz, zależą od tego, czy Twoja aplikacja ma mieć dostęp do plików związanych z aplikacją, czy do plików współdzielonych.
Jeśli plik nie zawiera informacji prywatnych ani poufnych, ale jest przydatny dla użytkownika tylko w Twojej aplikacji, przechowuj go w katalogu aplikacji na zewnętrznym urządzeniu pamięci masowej.
Jeśli Twoja aplikacja musi uzyskać dostęp do pliku, który jest przydatny dla innych aplikacji, albo musi go przechowywać, użyj jednego z tych interfejsów API (w zależności od przypadku użycia):
- Pliki multimedialne: aby przechowywać obrazy, pliki audio i filmy udostępniane między aplikacjami oraz uzyskiwać do nich dostęp, użyj interfejsu Media Store API.
- Inne pliki: aby przechowywać inne typy plików udostępnionych, w tym pobrane pliki, i uzyskać do nich dostęp, użyj interfejsu Storage Access Framework.
Sprawdzanie dostępności miejsca na dane
Jeśli Twoja aplikacja współpracuje z wymiennym zewnętrznym urządzeniem do przechowywania danych, pamiętaj, że użytkownik może je odłączyć, gdy aplikacja będzie próbować uzyskać do niego dostęp. Uwzględnij logikę, aby sprawdzać, czy urządzenie do przechowywania danych jest dostępne.
Sprawdzanie poprawności danych
Jeśli aplikacja korzysta z danych z zewnętrznego magazynu, sprawdź, czy zawartość danych nie została uszkodzona ani zmodyfikowana. Uwzględnij logikę do obsługi plików, które nie są już w stabilnym formacie.
Ten fragment kodu zawiera przykład weryfikatora hasha:
Kotlin
val hash = calculateHash(stream) // Store "expectedHash" in a secure location. if (hash == expectedHash) { // Work with the content. } // Calculating the hash code can take quite a bit of time, so it shouldn't // be done on the main thread. suspend fun calculateHash(stream: InputStream): String { return withContext(Dispatchers.IO) { val digest = MessageDigest.getInstance("SHA-512") val digestStream = DigestInputStream(stream, digest) while (digestStream.read() != -1) { // The DigestInputStream does the work; nothing for us to do. } digest.digest().joinToString(":") { "%02x".format(it) } } }
Java
Executor threadPoolExecutor = Executors.newFixedThreadPool(4); private interface HashCallback { void onHashCalculated(@Nullable String hash); } boolean hashRunning = calculateHash(inputStream, threadPoolExecutor, hash -> { if (Objects.equals(hash, expectedHash)) { // Work with the content. } }); if (!hashRunning) { // There was an error setting up the hash function. } private boolean calculateHash(@NonNull InputStream stream, @NonNull Executor executor, @NonNull HashCallback hashCallback) { final MessageDigest digest; try { digest = MessageDigest.getInstance("SHA-512"); } catch (NoSuchAlgorithmException nsa) { return false; } // Calculating the hash code can take quite a bit of time, so it shouldn't // be done on the main thread. executor.execute(() -> { String hash; try (DigestInputStream digestStream = new DigestInputStream(stream, digest)) { while (digestStream.read() != -1) { // The DigestInputStream does the work; nothing for us to do. } StringBuilder builder = new StringBuilder(); for (byte aByte : digest.digest()) { builder.append(String.format("%02x", aByte)).append(':'); } hash = builder.substring(0, builder.length() - 1); } catch (IOException e) { hash = null; } final String calculatedHash = hash; runOnUiThread(() -> hashCallback.onHashCalculated(calculatedHash)); }); return true; }
przechowywać w plikach pamięci podręcznej tylko dane niepoufne.
Aby mieć szybszy dostęp do niepoufnych danych aplikacji, przechowuj je w pamięci podręcznej urządzenia. W przypadku pamięci podręcznej większej niż 1 MB użyj opcji getExternalCacheDir()
.
W przypadku pamięci podręcznej o rozmiarze 1 MB lub mniejszym użyj opcji getCacheDir()
.
Obie metody zwracają obiekt File
, który zawiera zapisane w pamięci podręcznej dane aplikacji.
Ten fragment kodu pokazuje, jak zapisać w pamięci podręcznej plik pobrany ostatnio przez aplikację:
Kotlin
val cacheFile = File(myDownloadedFileUri).let { fileToCache -> File(cacheDir.path, fileToCache.name) }
Java
File cacheDir = getCacheDir(); File fileToCache = new File(myDownloadedFileUri); String fileToCacheName = fileToCache.getName(); File cacheFile = new File(cacheDir.getPath(), fileToCacheName);
Uwaga: jeśli użyjesz getExternalCacheDir()
, aby umieścić pamięć podręczną aplikacji w pamięci współdzielonej, użytkownik może wysunąć z niego nośnik znajdujący się w tym miejscu w trakcie działania aplikacji. Uwzględnij logikę, która pozwoli Ci sprawnie obsłużyć brak pamięci podręcznej spowodowany przez takie zachowanie użytkownika.
Uwaga: te pliki nie są chronione.
Dlatego każda aplikacja kierowana na Androida 10 (poziom interfejsu API 29) lub niższego i mająca uprawnienia WRITE_EXTERNAL_STORAGE
może uzyskać dostęp do zawartości tej pamięci podręcznej.
Powiązane informacje: Omówienie przechowywania danych i plików
Korzystanie z SharedPreferences w trybie prywatnym
Jeśli używasz funkcji getSharedPreferences()
do tworzenia obiektów SharedPreferences
w aplikacji lub do uzyskiwania do nich dostępu, użyj funkcji MODE_PRIVATE
. Dzięki temu tylko Twoja aplikacja będzie mieć dostęp do informacji w udostępnionym pliku preferencji.
Jeśli chcesz udostępniać dane między aplikacjami, nie używaj obiektów SharedPreferences
. Zamiast tego wykonaj czynności opisane w artykule Bezpieczne udostępnianie danych w aplikacjach.
Biblioteka Security udostępnia też klasę EncryptedSharedPreferences, która otacza klasę SharedPreferences i automatycznie szyfruje klucze i wartości.
Powiązane informacje:
Aktualizuj usługi i zależne komponenty
Większość aplikacji do wykonywania zadań specjalistycznych korzysta z bibliotek zewnętrznych i informacji o systemie urządzenia. Utrzymywanie aktualności zależności aplikacji zwiększa bezpieczeństwo tych punktów komunikacji.
Sprawdzanie dostawcy zabezpieczeń usług Google Play
Uwaga: ta sekcja dotyczy tylko aplikacji kierowanych na urządzenia z zainstalowanymi usługami Google Play.
Jeśli aplikacja korzysta z usług Google Play, sprawdź, czy są one aktualne na urządzeniu, na którym jest zainstalowana. Wykonaj kontrolę asynchronicznie poza wątkiem UI. Jeśli urządzenie nie jest aktualne, wywołaj błąd autoryzacji.
Aby sprawdzić, czy Usługi Google Play są aktualne na urządzeniu, na którym jest zainstalowana aplikacja, wykonaj czynności opisane w tym przewodniku: Aktualizowanie dostawcy zabezpieczeń w celu ochrony przed atakami na SSL.
Powiązane informacje:
Zaktualizuj wszystkie zależności aplikacji
Przed wdrożeniem aplikacji upewnij się, że wszystkie biblioteki, pakiety SDK i inne zależności są aktualne:
- W przypadku zależności własnych, takich jak pakiet Android SDK, użyj narzędzi do aktualizacji dostępnych w Android Studio, np. Menedżera pakietu SDK.
- W przypadku zależności zewnętrznych sprawdź strony internetowe bibliotek, których używa Twoja aplikacja, i zainstaluj wszystkie dostępne aktualizacje i poprawki zabezpieczeń.
Powiązane informacje: Dodawanie zależności kompilacji
Więcej informacji
Aby dowiedzieć się więcej o tym, jak zwiększyć bezpieczeństwo aplikacji, zapoznaj się z tymi materiałami:
- Lista kontrolna zabezpieczeń dotycząca jakości aplikacji podstawowych
- Program ulepszania zabezpieczeń aplikacji
- Kanał dla deweloperów aplikacji na Androida w YouTube
- Android Konfiguracja zabezpieczeń sieci – Codelab
- Android Protected Confirmation: Nowy wymiar bezpieczeństwa transakcji