Únete a ⁠ #Android11: The Beta Launch Show el 3 de junio.

Recomendaciones sobre seguridad de apps

Al hacer que tu app sea más segura, ayudas a preservar la confianza de los usuarios y la integridad del dispositivo.

En esta página, se muestran varias prácticas recomendadas que tienen un impacto positivo en la seguridad de tu app.

Practica la comunicación segura

Cuando proteges los datos que intercambia tu app con otras apps o con un sitio web, mejoras su estabilidad y proteges los datos que envías y recibes.

Usa intents implícitos y proveedores de contenido no exportado

Muestra un selector de apps

Si un intent implícito puede lanzar al menos dos apps posibles en el dispositivo de un usuario, muestra explícitamente un selector de apps. Esta estrategia de interacción permite a los usuarios transferir información sensible a una app de confianza.

Kotlin

    val intent = Intent(ACTION_SEND)
    val possibleActivitiesList: List<ResolveInfo> =
            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 =
            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);
    }
    

Información relacionada:

Aplica permisos basados en firmas

Cuando compartes datos entre dos apps que controlas o posees, usa permisos basados en firmas. Estos permisos no requieren la confirmación del usuario y, en cambio, comprueban que la app que accede a los datos esté firmada con la misma clave. Por lo tanto, estos permisos ofrecen una experiencia del usuario más segura y simplificada.

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.example.myapp">
        <permission android:name="my_custom_permission_name"
                    android:protectionLevel="signature" />
    

Información relacionada:

Inhabilita el acceso a los proveedores de contenido de tu app

A menos que tengas la intención de enviar datos desde tu app a una app diferente que no te pertenece, deberías inhabilitar explícitamente las apps de otros desarrolladores para que no puedan acceder a los objetos ContentProvider que contiene tu app. Esta opción de configuración resulta particularmente importante si tu app se puede instalar en dispositivos que ejecutan Android 4.1.1 (nivel de API 16) o versiones anteriores, ya que el atributo android:exported del elemento <provider> es true de manera predeterminada en esas versiones de Android.

    <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>
    

Pide credenciales antes de mostrar información sensible

Al solicitar credenciales a los usuarios para que puedan acceder a información sensible o contenido premium de tu app, puedes solicitar un PIN, una contraseña, un patrón o una credencial biométrica, como el reconocimiento facial o de huella digital.

Para obtener más información acerca de cómo solicitar credenciales biométricas, consulta la guía acerca de la autenticación biométrica.

Aplica medidas de seguridad de red

En las siguientes secciones, se describe cómo puedes mejorar la seguridad de red de tu app.

Usa tráfico SSL

Si tu app se comunica con un servidor web que tiene un certificado emitido por un CA reconocido y de confianza, la solicitud HTTPS es muy simple.

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();
    

Agrega una configuración de seguridad de red

Si tu app usa CA nuevos o personalizados, puedes declarar la configuración de seguridad de tu red en un archivo de configuración. Este proceso te permite crear una configuración sin modificar ningún código de app.

Para agregar un archivo de configuración de seguridad de red a tu app, sigue estos pasos:

  1. Declara la configuración en el manifiesto de tu app:
  2.     <manifest ... >
            <application
                android:networkSecurityConfig="@xml/network_security_config"
                ... >
                <!-- Place child elements of <application> element here. -->
            </application>
        </manifest>
        
  3. Agrega un archivo de recursos XML, ubicado en res/xml/network_security_config.xml.

    Especifica que todo el tráfico hacia dominios en particular deberían usar HTTPS. Para ello, inhabilita el borrado de texto:

        <network-security-config>
            <domain-config cleartextTrafficPermitted="false">
                <domain includeSubdomains="true">secure.example.com</domain>
                ...
            </domain-config>
        </network-security-config>
        

    Durante el proceso de desarrollo, puedes usar el elemento <debug-overrides> para permitir explícitamente certificados instalados por el usuario. Este elemento anula las opciones importantes de seguridad de tu app durante la depuración y las pruebas sin afectar la configuración de lanzamiento de la app. En el siguiente fragmento, se muestra cómo definir este elemento en tu archivo XML de configuración de seguridad de red de tu app:

        <network-security-config>
            <debug-overrides>
                <trust-anchors>
                    <certificates src="user" />
                </trust-anchors>
            </debug-overrides>
        </network-security-config>
        

Información relacionada: Configuración de seguridad de red

Crea tu propio administrador de confianza

Tu verificador de SSL no debería aceptar todos los certificados. Es posible que tengas que configurar un administrador de confianza y manejar todas las advertencias de SSL que se produzcan si una de las siguientes condiciones se aplican a tu caso práctico:

  • Te comunicas con un servidor web que tiene un certificado firmado por un CA nuevo o personalizado.
  • No todos los dispositivos que usas confían en el CA.
  • No puedes usar una configuración de seguridad de red.

Para obtener más información acerca de cómo completar estos pasos, consulta el debate acerca de cómo manejar una autoridad de certificados desconocida.

Información relacionada:

Usa objetos WebView cuidadosamente

Siempre que sea posible, carga solo contenido de la lista blanca en objetos WebView. En otras palabras, los objetos WebView de tu app no deberían permitir a los usuarios navegar en sitios que se encuentran fuera de tu control.

Además, nunca deberías habilitar la compatibilidad con la interfaz de JavaScript a menos que controles y confíes por completo en el contenido de los objetos WebView de tu app.

Usa canales de mensaje HTML

Si tu app usa compatibilidad con la interfaz de JavaScript en dispositivos que ejecutan Android 6.0 (nivel de API 23) y versiones posteriores, usa los canales de mensajes HTML en lugar de evaluateJavascript() para la comunicación entre un sitio web y tu app, como se muestra en el siguiente fragmento de código:

Kotlin

    val myWebView: WebView = findViewById(R.id.webview)

    // messagePorts[0] and messagePorts[1] represent the two ports.
    // They are already tangled to 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);

    // messagePorts[0] and messagePorts[1] represent the two ports.
    // They are already tangled to 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"));
    

Información relacionada:

Proporciona los permisos adecuados

Tu app solo debería pedir la cantidad mínima de permisos necesarios para funcionar adecuadamente. Cuando sea posible, tu app debería renunciar a algunos de estos permisos cuando ya no sean necesarios.

Usa intents para diferir permisos

Cuando sea posible, no agregues un permiso a tu app para completar una acción que podría completar otra app. En cambio, usa un intent para diferir la solicitud a una app diferente que ya tiene el permiso necesario.

En el siguiente ejemplo, se muestra cómo usar un intent para dirigir a los usuarios a una app de contactos en lugar de solicitar los permisos READ_CONTACTS y 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);
    }
    

Además, si tu app necesita realizar procesos de I/O basados en archivos, como acceder al almacenamiento o elegir un archivo, no se necesita un permiso especial, ya que el sistema puede completar las operaciones en nombre de tu app. Mejor aún, después de que el usuario selecciona el contenido en un URI en particular, la app que realiza la llamada obtiene el permiso para el recurso seleccionado.

Información relacionada:

Comparte datos de manera segura entre apps

Sigue estas prácticas recomendadas para compartir el contenido de tu app con otras apps de manera más segura:

En el siguiente fragmento de código, se muestra cómo usar las marcas para otorgar permisos de URI y permisos de proveedores de contenido para mostrar el archivo PDF de la app en una app de visualización de PDF independiente:

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);
    }
    

Nota: Las apps no confiables que orientan a Android 10 (nivel de API 29) y versiones posteriores no pueden invocar a exec() en archivos dentro del directorio principal de la app. Esta ejecución de archivos desde el directorio principal de la app que admite escritura es una infracción de W^X. Las apps deben cargar solo el código binario incorporado en el archivo APK de una app. Además, las apps que se orientan a Android 10 y versiones posteriores no pueden modificar el código ejecutable en la memoria de los archivos que se abrieron con dlopen() incluido cualquier archivo de objeto (.so) compartido con reubicaciones de texto.

Información relacionada: android:grantUriPermissions

Almacena datos de manera segura

Si bien tu app podría requerir acceso a información sensible del usuario, tus usuarios otorgarán acceso a sus datos a tu app solo si confían en que los protegerás adecuadamente.

Almacena datos privados en el almacenamiento interno

Almacena todos los datos del usuario privados dentro del almacenamiento interno del dispositivo, que prueba para cada app. No es necesario que tu app solicite permiso para ver estos archivos, y otras apps no podrán acceder a los archivos. Como medida de seguridad adicional, cuando el usuario desinstala una app, el dispositivo borra todos los archivos que la app guardó en el almacenamiento interno.

En el siguiente fragmento de código, se muestra una manera de escribir datos en el almacenamiento interno:

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.
    }
    

En el siguiente fragmento de código, se muestra la operación inversa, la lectura de datos desde el almacenamiento interno:

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.
    }
    

Información relacionada:

Usa el almacenamiento externo con precaución

De manera predeterminada, el sistema Android no aplica restricciones de seguridad a los datos que residen en un almacenamiento externo y no se garantiza que el medio de almacenamiento permanezca conectado al dispositivo. Por lo tanto, deberías aplicar las siguientes medidas de seguridad a fin de proporcionar un acceso seguro a la información del almacenamiento externo.

Uso del acceso a directorios determinados

Si tu app necesita acceder solo a un directorio específico dentro del almacenamiento externo del dispositivo, puedes usar el acceso a directorios determinados para limitar el acceso de tu app al almacenamiento externo de un dispositivo en consecuencia. Para conveniencia de los usuarios, tu app debería guardar el URI de acceso al directorio, para que los usuarios no tengan que aprobar el acceso al mismo cada vez que tu app intenta acceder.

Nota: Si usas acceso a directorios determinados con un directorio en particular en un almacenamiento externo, debes saber que el usuario podría expulsar el medio que contiene este almacenamiento mientras tu app se está ejecutando. Deberías incluir lógica para manejar correctamente el cambio al valor Environment.getExternalStorageState() que causa este comportamiento del usuario.

En el siguiente fragmento de código, se usa el acceso a un directorio determinado con el directorio de imágenes dentro de un almacenamiento compartido principal del dispositivo:

Kotlin

    private const val PICTURES_DIR_ACCESS_REQUEST_CODE = 42

    ...

    private fun accessExternalPicturesDirectory() {
        val intent: Intent = (getSystemService(Context.STORAGE_SERVICE) as StorageManager)
                .primaryStorageVolume.createAccessIntent(Environment.DIRECTORY_PICTURES)
        startActivityForResult(intent, PICTURES_DIR_ACCESS_REQUEST_CODE)
    }

    ...

    override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
        if (requestCode == PICTURES_DIR_ACCESS_REQUEST_CODE && resultCode == Activity.RESULT_OK) {

            // User approved access to scoped directory.
            if (resultData != null) {
                val picturesDirUri: Uri = resultData.data

                // Save user's approval for accessing this directory
                // in your app.
                contentResolver.takePersistableUriPermission(
                        picturesDirUri,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION
                )
            }
        }
    }
    

Java

    private static final int PICTURES_DIR_ACCESS_REQUEST_CODE = 42;

    private void accessExternalPicturesDirectory() {
      StorageManager sm =
              (StorageManager) getSystemService(Context.STORAGE_SERVICE);
      StorageVolume volume = sm.getPrimaryStorageVolume();
      Intent intent =
              volume.createAccessIntent(Environment.DIRECTORY_PICTURES);
      startActivityForResult(intent, PICTURES_DIR_ACCESS_REQUEST_CODE);
    }

    ...

    @Override
    public void onActivityResult(int requestCode, int resultCode,
            Intent resultData) {
        if (requestCode == PICTURES_DIR_ACCESS_REQUEST_CODE &&
                resultCode == Activity.RESULT_OK) {

            // User approved access to scoped directory.
            if (resultData != null) {
                Uri picturesDirUri = resultData.getData();

                // Save user's approval for accessing this directory
                // in your app.
                ContentResolver myContentResolver = getContentResolver();
                myContentResolver.takePersistableUriPermission(picturesDirUri,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }
        }
    }
    

Advertencia: No pases null a createAccessIntent() si no hay necesidad, ya que esto garantiza que tu app acceda a todo el volumen que StorageManager encuentre para tu app.

Información relacionada:

Comprueba la validez de los datos

Si tu app usa datos de un almacenamiento externo, asegúrate de que el contenido de los datos no esté dañado ni haya sido modificado. Tu app también debería incluir lógica para manejar archivos que ya no se encuentran en un formato estable.

En el siguiente ejemplo, se muestra el permiso y la lógica que comprueban la validez de un archivo:

AndroidManifest.xml

    <manifest ... >
        <!-- Apps on devices running Android 4.4 (API level 19) or higher cannot
             access external storage outside their own "sandboxed" directory, so
             the READ_EXTERNAL_STORAGE (and WRITE_EXTERNAL_STORAGE) permissions
             aren't necessary. -->
        <uses-permission
              android:name="android.permission.READ_EXTERNAL_STORAGE"
              android:maxSdkVersion="18" />
        ...
    </manifest>

    

MyFileValidityChecker

Kotlin

    private val UNAVAILABLE_STORAGE_STATES: Set<String> =
            setOf(MEDIA_REMOVED, MEDIA_UNMOUNTED, MEDIA_BAD_REMOVAL, MEDIA_UNMOUNTABLE)
    ...
    val ringtone = File(getExternalFilesDir(DIRECTORY_RINGTONES), "my_awesome_new_ringtone.m4a")
    when {
        isExternalStorageEmulated(ringtone) -> {
            Log.e(TAG, "External storage is not present")
        }
        UNAVAILABLE_STORAGE_STATES.contains(getExternalStorageState(ringtone)) -> {
            Log.e(TAG, "External storage is not available")
        }
        else -> {
            val fis = FileInputStream(ringtone)

            // available() determines the approximate number of bytes that
            // can be read without blocking.
            val bytesAvailable: Int = fis.available()
            val fileBuffer = ByteArray(bytesAvailable)
            StringBuilder(bytesAvailable).apply {
                while (fis.read(fileBuffer) != -1) {
                    append(fileBuffer)
                }
                // Implement appropriate logic for checking a file's validity.
                checkFileValidity(this)
            }
        }
    }
    

Java

    File ringtone = new File(getExternalFilesDir(DIRECTORY_RINGTONES,
            "my_awesome_new_ringtone.m4a"));
    if (isExternalStorageEmulated(ringtone)) {
        Logger.e(TAG, "External storage is not present");
    } else if (getExternalStorageState(ringtone) == MEDIA_REMOVED
            | MEDIA_UNMOUNTED | MEDIA_BAD_REMOVAL | MEDIA_UNMOUNTABLE) {
        Logger.e(TAG, "External storage is not available");
    } else {
        FileInputStream fis = new FileInputStream(ringtone);

        // available() determines the approximate number of bytes that
        // can be read without blocking.
        int bytesAvailable = fis.available();
        StringBuilder fileContents = new StringBuilder(bytesAvailable);
        byte[] fileBuffer = new byte[bytesAvailable];
        while (fis.read(fileBuffer) != -1) {
            fileContents.append(fileBuffer);
        }

        // Implement appropriate logic for checking a file's validity.
        checkFileValidity(fileContents);
    }
    

Información relacionada:

Almacena solo datos no sensibles en archivos de caché

Para proporcionar un acceso más rápido a los datos no sensibles de la app, almacénalos en la memoria caché del dispositivo. Para cachés de más de 1 MB, usa getExternalCacheDir(); de lo contrario, usa getCacheDir(). Cada método te proporciona el objeto File que contiene los datos en caché de tu app.

En el siguiente fragmento de código, se muestra cómo almacenar en caché un archivo que tu app descargó recientemente:

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);
    

Nota: Si usas getExternalCacheDir() para colocar la caché de tu app dentro del almacenamiento compartido, el usuario podría expulsar el medio que contiene este almacenamiento mientras tu app se está ejecutando. Deberías incluir lógica para manejar correctamente la falta de caché que causa este comportamiento del usuario.

Precaución: No se aplica seguridad a estos archivos. Por lo tanto, cualquier app que tiene el permiso WRITE_EXTERNAL_STORAGE puede acceder al contenido de esta caché.

Información relacionada: Cómo guardar archivos de caché

Usa SharedPreferences en el modo privado

Al usar getSharedPreferences() para crear o acceder a los objetos SharedPreferences de tu app, usa MODE_PRIVATE. De esa manera, solo tu app puede acceder a la información dentro del archivo de preferencias compartido.

Si quieres compartir datos entre apps, no uses objetos SharedPreferences. En cambio, deberías seguir los pasos necesarios para compartir datos de manera segura entre apps.

Información relacionada: Cómo utilizar las preferencias compartidas

Mantén los servicios y las dependencias actualizados

La mayoría de las apps usan información del sistema del dispositivo y bibliotecas externas para completar tareas especializadas. Al mantener actualizadas las dependencias de tu app, puedes hacer que estos puntos de comunicación sean más seguros.

Revisa el proveedor de seguridad de los Servicios de Google Play

Nota: Esta sección solo se aplica a las apps que se orientan a dispositivos que tienen los Servicios de Google Play instalados.

Si tu app usa los Servicios de Google Play, asegúrate de que estén actualizados en el dispositivo en el que está instalada la app. Esta comprobación debería hacerse de manera asíncrona, por fuera del subproceso de IU. Si el dispositivo no está actualizado, tu app debería activar un error de autorización.

Para determinar si los Servicios de Google Play están actualizados en el dispositivo en el que está instalada tu app, sigue los pasos que aparecen en la guía Cómo actualizar tu proveedor de seguridad para protegerte contra vulnerabilidades de SSL.

Información relacionada:

Actualiza todas las dependencias de la app

Antes de implementar tu app, asegúrate de que todas las bibliotecas, los SDK y otras dependencias estén actualizados de la siguiente manera:

  • Para dependencias propias, como el SDK de Android, usa las herramientas de actualización que se encuentran en Android Studio, como SDK Manager.
  • Para dependencias de terceros, comprueba los sitios web de las bibliotecas que usa tu app, luego instala las actualizaciones y los parches de seguridad disponibles.

Información relacionada: Agrega dependencias de compilación

Más información

Para obtener más información acerca de cómo hacer que tu app sea más segura, consulta los siguientes recursos:

Recursos adicionales

Para obtener más información acerca de cómo hacer que tu app sea más segura, consulta los siguientes recursos:

Codelabs

Blogs