Nivel de API: 18
Android 4.3 (JELLY_BEAN_MR2) es una actualización de la versión Jelly Bean que ofrece nuevas funciones para usuarios y desarrolladores de apps. En este documento, se proporciona una introducción a las APIs nuevas más destacadas.
Como desarrollador de apps, debes descargar la imagen del sistema de Android 4.3 y la plataforma del SDK desde el SDK Manager lo antes posible. Si no tienes un dispositivo con Android 4.3 para probar tu app, usa la imagen del sistema de Android 4.3 para probarla en el emulador de Android. Luego, compila tus apps con la plataforma de Android 4.3 para comenzar a usar las APIs más recientes.
Actualiza el nivel de API objetivo
Para optimizar mejor tu app para dispositivos que ejecutan Android 4.3, debes establecer tu targetSdkVersion en "18", instalarla en una imagen del sistema de Android 4.3, probarla y, luego, publicar una actualización con este cambio.
Puedes usar las APIs en Android 4.3 y, al mismo tiempo, admitir versiones anteriores agregando condiciones a tu código que verifiquen el nivel de API del sistema antes de ejecutar las APIs que no admite tu minSdkVersion.
Para obtener más información sobre cómo mantener la retrocompatibilidad, consulta Cómo admitir diferentes versiones de la plataforma.
También hay varias APIs disponibles en la biblioteca de compatibilidad de Android que te permiten implementar funciones nuevas en versiones anteriores de la plataforma.
Para obtener más información sobre cómo funcionan los niveles de API, consulta ¿Qué es el nivel de API?
Cambios importantes en el comportamiento
Si ya publicaste una app para Android, ten en cuenta que los cambios en Android 4.3 podrían afectarla.
Si tu app usa intents implícitos…
Es posible que tu app no funcione correctamente en un entorno de perfil restringido.
Es posible que los usuarios de un entorno de perfil restringido no tengan disponibles todas las apps para Android estándar. Por ejemplo, un perfil restringido podría tener inhabilitadas las apps de navegador web y de cámara. Por lo tanto, tu app no debe suponer qué apps están disponibles, ya que, si llamas a startActivity() sin verificar si hay una app disponible para controlar el Intent, es posible que tu app falle en un perfil restringido.
Cuando uses un intent implícito, siempre debes verificar que haya una app disponible para controlar el intent llamando a resolveActivity() o queryIntentActivities(). Por ejemplo:
Kotlin
val intent = Intent(Intent.ACTION_SEND) ... if (intent.resolveActivity(packageManager) != null) { startActivity(intent) } else { Toast.makeText(context, R.string.app_not_available, Toast.LENGTH_LONG).show() }
Java
Intent intent = new Intent(Intent.ACTION_SEND); ... if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); } else { Toast.makeText(context, R.string.app_not_available, Toast.LENGTH_LONG).show(); }
Si tu app depende de cuentas…
Es posible que tu app no funcione correctamente en un entorno de perfil restringido.
De forma predeterminada, los usuarios de un entorno de perfil restringido no tienen acceso a las cuentas de usuario.
Si tu app depende de un Account, es posible que falle o se comporte de forma inesperada cuando se use en un perfil restringido.
Si quieres evitar que los perfiles restringidos usen tu app por completo porque esta depende de información de la cuenta que es sensible, especifica el atributo android:requiredAccountType en el elemento <application> de tu manifiesto.
Si quieres permitir que los perfiles restringidos sigan usando tu app, aunque no puedan crear sus propias cuentas, puedes inhabilitar las funciones de la app que requieren una cuenta o permitir que los perfiles restringidos accedan a las cuentas que creó el usuario principal. Para obtener más información, consulta la sección que aparece a continuación sobre Cómo admitir cuentas en un perfil restringido.
Si tu app usa VideoView…
Es posible que tu video se vea más pequeño en Android 4.3.
En las versiones anteriores de Android, el widget VideoView calculaba de forma incorrecta el valor "wrap_content" para layout_height y layout_width para que fuera el mismo que "match_parent". Por lo tanto, si bien es posible que el uso de "wrap_content" para la altura o el ancho haya proporcionado el diseño de video deseado,
hacerlo puede dar como resultado un video mucho más pequeño en Android 4.3 y versiones posteriores. Para solucionar el problema, reemplaza "wrap_content" por "match_parent" y verifica que tu video aparezca como se espera en Android 4.3 y en versiones anteriores.
Perfiles restringidos
En las tablets Android, los usuarios ahora pueden crear perfiles restringidos basados en el usuario principal. Cuando los usuarios crean un perfil restringido, pueden habilitar restricciones, como las apps que están disponibles para el perfil. Un nuevo conjunto de APIs en Android 4.3 también te permite crear parámetros de configuración de restricciones detallados para las apps que desarrollas. Por ejemplo, con las nuevas APIs, puedes permitir que los usuarios controlen qué tipo de contenido está disponible en tu app cuando se ejecuta en un entorno de perfil restringido.
La IU para que los usuarios controlen las restricciones que creaste se administra con la aplicación de Configuración del sistema. Para que la configuración de restricciones de tu app aparezca para el usuario, debes declarar las restricciones que proporciona tu app creando un BroadcastReceiver que reciba el intent ACTION_GET_RESTRICTION_ENTRIES. El sistema invoca este intent para consultar todas las apps en busca de restricciones disponibles y, luego, compila la IU para permitir que el usuario principal administre las restricciones de cada perfil restringido.
En el método onReceive() de tu BroadcastReceiver, debes crear un RestrictionEntry para cada restricción que proporcione tu app. Cada RestrictionEntry define un título de restricción, una descripción y uno de los siguientes tipos de datos:
TYPE_BOOLEANpara una restricción que es verdadera o falsa.TYPE_CHOICEpara una restricción que tiene varias opciones que son mutuamente excluyentes (opciones de botones de selección)TYPE_MULTI_SELECTpara una restricción que tiene varias opciones que no son mutuamente excluyentes (opciones de casilla de verificación)
Luego, coloca todos los objetos RestrictionEntry en un ArrayList y colócalo en el resultado del receptor de transmisiones como el valor del elemento adicional EXTRA_RESTRICTIONS_LIST.
El sistema crea la IU para las restricciones de tu app en la app de Configuración y guarda cada restricción con la clave única que proporcionaste para cada objeto RestrictionEntry. Cuando el usuario abre tu app, puedes consultar las restricciones actuales llamando a getApplicationRestrictions().
Esto devuelve un Bundle que contiene los pares clave-valor para cada restricción que definiste con los objetos RestrictionEntry.
Si quieres proporcionar restricciones más específicas que no se puedan controlar con valores booleanos, de selección única y de selección múltiple, puedes crear una actividad en la que el usuario pueda especificar las restricciones y permitir que los usuarios abran esa actividad desde la configuración de restricciones. En tu
receptor de transmisiones, incluye el elemento adicional EXTRA_RESTRICTIONS_INTENT
en el resultado Bundle. Este extra debe especificar un Intent que indique la clase Activity que se iniciará (usa el método putParcelable() para pasar EXTRA_RESTRICTIONS_INTENT con el intent).
Cuando el usuario principal ingresa a tu actividad para establecer restricciones personalizadas, esta debe devolver un resultado que contenga los valores de restricción en un elemento adicional con la clave EXTRA_RESTRICTIONS_LIST o EXTRA_RESTRICTIONS_BUNDLE, según si especificas objetos RestrictionEntry o pares clave-valor, respectivamente.
Cómo admitir cuentas en un perfil restringido
Todas las cuentas que se agreguen al usuario principal estarán disponibles para un perfil restringido, pero no se podrá acceder a ellas desde las APIs de AccountManager de forma predeterminada.
Si intentas agregar una cuenta con AccountManager mientras estás en un perfil restringido, obtendrás un resultado de error. Debido a estas restricciones, tienes las siguientes
tres opciones:
Para obtener acceso a una cuenta desde un perfil restringido, debes agregar el atributo android:restrictedAccountType a la etiqueta <application>:
<application ... android:restrictedAccountType="com.example.account.type" >
Precaución: Si habilitas este atributo, tu app podrá acceder a las cuentas del usuario principal desde perfiles restringidos. Por lo tanto, solo debes permitir esto si la información que muestra tu app no revela información de identificación personal (IIP) que se considere sensible. La configuración del sistema informará al usuario principal que tu app otorga perfiles restringidos a sus cuentas, por lo que debe quedar claro para el usuario que el acceso a la cuenta es importante para la funcionalidad de tu app. Si es posible, también debes proporcionar controles de restricción adecuados para el usuario principal que definan cuánto acceso a la cuenta se permite en tu app.
Si quieres usar cuentas, pero no las necesitas para la funcionalidad principal de tu app, puedes verificar la disponibilidad de la cuenta y, si no está disponible, inhabilitar las funciones.
Primero, debes verificar si hay una cuenta existente disponible. De lo contrario, consulta si es posible crear una cuenta nueva llamando a getUserRestrictions() y verifica el elemento adicional DISALLOW_MODIFY_ACCOUNTS en el resultado. Si es true,
debes inhabilitar cualquier funcionalidad de tu app que requiera acceso a las cuentas.
Por ejemplo:
Kotlin
val um = context.getSystemService(Context.USER_SERVICE) as UserManager val restrictions: Bundle = um.userRestrictions if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) { // cannot add accounts, disable some functionality }
Java
UserManager um = (UserManager) context.getSystemService(Context.USER_SERVICE); Bundle restrictions = um.getUserRestrictions(); if (restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false)) { // cannot add accounts, disable some functionality }
Nota: En este caso, no debes declarar ningún atributo nuevo en tu archivo de manifiesto.
Si, en cambio, es importante que tu app no esté disponible para perfiles restringidos porque depende de información personal sensible en una cuenta (y porque los perfiles restringidos actualmente no pueden agregar cuentas nuevas), agrega el atributo android:requiredAccountType a la etiqueta <application>:
<application ... android:requiredAccountType="com.example.account.type" >
Por ejemplo, la app de Gmail usa este atributo para inhabilitarse en perfiles restringidos, ya que el correo electrónico personal del propietario no debe estar disponible para esos perfiles.
Conexión inalámbrica y conectividad
Bluetooth de bajo consumo (Smart Ready)
Android ahora admite Bluetooth de bajo consumo (LE) con nuevas APIs en android.bluetooth.
Con las nuevas APIs, puedes compilar apps para Android que se comuniquen con periféricos de Bluetooth de bajo consumo, como monitores de frecuencia cardíaca y podómetros.
Como el Bluetooth de bajo consumo es una función de hardware que no está disponible en todos los dispositivos con Android, debes declarar en tu archivo de manifiesto un elemento <uses-feature> para "android.hardware.bluetooth_le":
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
Si ya conoces las APIs de Bluetooth clásico de Android, ten en cuenta que el uso de las APIs de Bluetooth de bajo consumo tiene algunas diferencias. Lo más importante es que ahora hay una clase BluetoothManager que debes usar para algunas operaciones de alto nivel, como adquirir un BluetoothAdapter, obtener una lista de dispositivos conectados y verificar el estado de un dispositivo. Por ejemplo, a continuación, se muestra cómo debes obtener el BluetoothAdapter:
Kotlin
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager bluetoothAdapter = bluetoothManager.adapter
Java
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); bluetoothAdapter = bluetoothManager.getAdapter();
Para descubrir periféricos Bluetooth de bajo consumo, llama a startLeScan() en BluetoothAdapter y pásale una implementación de la interfaz BluetoothAdapter.LeScanCallback. Cuando el adaptador de Bluetooth detecta un periférico de Bluetooth de bajo consumo, tu implementación de BluetoothAdapter.LeScanCallback recibe una llamada al método onLeScan(). Este método te proporciona un objeto BluetoothDevice que representa el dispositivo detectado, el valor de RSSI para el dispositivo y un array de bytes que contiene el registro de anuncio del dispositivo.
Si solo quieres buscar tipos específicos de periféricos, puedes llamar a startLeScan() y, además, incluir un array de objetos UUID que especifiquen los servicios de GATT que admite tu app.
Nota: Solo puedes buscar dispositivos Bluetooth de bajo consumo o dispositivos Bluetooth clásicos con las APIs anteriores. No puedes buscar dispositivos Bluetooth LE y Classic a la vez.
Para conectarte a un periférico Bluetooth de bajo consumo, llama a connectGatt() en el objeto BluetoothDevice correspondiente y pásale una implementación de BluetoothGattCallback. Tu implementación de BluetoothGattCallback recibe devoluciones de llamada relacionadas con el estado de conectividad con el dispositivo y otros eventos. Durante la devolución de llamada onConnectionStateChange(),
puedes comenzar a comunicarte con el dispositivo si el método pasa STATE_CONNECTED como el estado nuevo.
Para acceder a las funciones de Bluetooth en un dispositivo, tu app también debe solicitar ciertos permisos de usuario de Bluetooth. Para obtener más información, consulta la guía de la API de Bluetooth de bajo consumo.
Modo de solo escaneo de Wi-Fi
Cuando se intenta identificar la ubicación del usuario, Android puede usar Wi-Fi para ayudar a determinarla escaneando los puntos de acceso cercanos. Sin embargo, los usuarios suelen mantener desactivada la conexión Wi-Fi para ahorrar batería, lo que genera datos de ubicación menos precisos. Android ahora incluye un modo de solo análisis que permite que la conexión Wi-Fi del dispositivo analice los puntos de acceso para ayudar a obtener la ubicación sin conectarse a un punto de acceso, lo que reduce en gran medida el uso de la batería.
Si quieres obtener la ubicación del usuario, pero la conexión Wi-Fi está desactivada, puedes solicitarle que habilite el modo de solo búsqueda de Wi-Fi llamando a startActivity() con la acción ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE.
Configuración de Wi-Fi
Las nuevas APIs de WifiEnterpriseConfig permiten que los servicios orientados a empresas automaticen la configuración de Wi-Fi para los dispositivos administrados.
Respuesta rápida para llamadas entrantes
Desde Android 4.0, una función llamada "Respuesta rápida" permite que los usuarios respondan las llamadas entrantes con un mensaje de texto inmediato sin necesidad de contestar la llamada ni desbloquear el dispositivo.
Hasta ahora, la app de mensajería predeterminada siempre controlaba estos mensajes rápidos. Ahora, cualquier app puede declarar su capacidad para controlar estos mensajes creando un Service con un filtro de intents para ACTION_RESPOND_VIA_MESSAGE.
Cuando el usuario responde a una llamada entrante con una respuesta rápida, la app de Teléfono envía el intent ACTION_RESPOND_VIA_MESSAGE con un URI que describe al destinatario (la persona que llama) y el extra EXTRA_TEXT con el mensaje que el usuario quiere enviar. Cuando tu servicio reciba el intent, deberá entregar el mensaje y detenerse de inmediato (tu app no debería mostrar una actividad).
Para recibir este intent, debes declarar el permiso SEND_RESPOND_VIA_MESSAGE.
Multimedia
Mejoras en MediaExtractor y MediaCodec
Ahora, Android te facilita la escritura de tus propios reproductores de transmisión adaptable y dinámica a través de HTTP (DASH) de acuerdo con el estándar ISO/IEC 23009-1, con las APIs existentes en MediaCodec y MediaExtractor. Se actualizó el framework subyacente de estas APIs para admitir el análisis de archivos MP4 fragmentados, pero tu app sigue siendo responsable de analizar los metadatos de MPD y pasar los flujos individuales a MediaExtractor.
Si quieres usar DASH con contenido encriptado, ten en cuenta que el método getSampleCryptoInfo() devuelve los metadatos MediaCodec.CryptoInfo que describen la estructura de cada muestra de contenido multimedia encriptado. Además, se agregó el método getPsshInfo() a MediaExtractor para que puedas acceder a los metadatos de PSSH de tu contenido multimedia de DASH.
Este método devuelve un mapa de objetos UUID a bytes, con el
UUID que especifica el esquema criptográfico y los bytes que son los datos específicos
de ese esquema.
DRM de contenido multimedia
La nueva clase MediaDrm proporciona una solución modular para la administración de derechos digitales (DRM) con tu contenido multimedia, ya que separa las cuestiones de DRM de la reproducción de contenido multimedia. Por ejemplo, esta separación de API te permite reproducir contenido encriptado con Widevine sin tener que usar el formato de contenido multimedia de Widevine. Esta solución de DRM también admite la encriptación común de DASH, por lo que puedes usar una variedad de esquemas de DRM con tu contenido de transmisión.
Puedes usar MediaDrm para obtener mensajes opacos de solicitud de claves y procesar
mensajes de respuesta de claves del servidor para la adquisición y el aprovisionamiento de licencias. Tu app es responsable de controlar la comunicación de red con los servidores; la clase MediaDrm solo proporciona la capacidad de generar y procesar los mensajes.
Las APIs de MediaDrm están diseñadas para usarse junto con las APIs de MediaCodec que se introdujeron en Android 4.1 (nivel de API 16), incluidas MediaCodec para codificar y decodificar tu contenido, MediaCrypto para controlar el contenido encriptado y MediaExtractor para extraer y demultiplexar tu contenido.
Primero, debes construir objetos MediaExtractor y MediaCodec. Luego, puedes acceder al UUID que identifica el esquema de DRM, por lo general, desde los metadatos del contenido, y usarlo para construir una instancia de un objeto MediaDrm con su constructor.
Codificación de video desde una Surface
En Android 4.1 (nivel de API 16), se agregó la clase MediaCodec para la codificación y decodificación de bajo nivel de contenido multimedia. Cuando se codificaba un video, Android 4.1 requería que proporcionaras los medios con un array ByteBuffer, pero Android 4.3 ahora te permite usar un Surface como entrada para un codificador. Por ejemplo, esto te permite codificar la entrada de un archivo de video existente o usar fotogramas generados a partir de OpenGL ES.
Para usar un Surface como entrada para tu codificador, primero llama a configure() para tu MediaCodec.
Luego, llama a createInputSurface() para recibir el Surface en el que puedes transmitir tu contenido multimedia.
Por ejemplo, puedes usar el Surface determinado como la ventana para un contexto de OpenGL pasándolo a eglCreateWindowSurface(). Luego, mientras renderizas la superficie, llama a eglSwapBuffers() para pasar el fotograma a MediaCodec.
Para comenzar la codificación, llama a start() en el MediaCodec. Cuando termines, llama a signalEndOfInputStream() para finalizar la codificación y llama a release() en Surface.
Multiplexación de contenido multimedia
La nueva clase MediaMuxer permite la multiplexación entre una transmisión de audio y una de video. Estas APIs sirven como contraparte de la clase MediaExtractor que se agregó en Android 4.2 para desmultiplexar (demuxing) contenido multimedia.
Los formatos de salida admitidos se definen en MediaMuxer.OutputFormat. Actualmente,
MP4 es el único formato de salida compatible y MediaMuxer solo admite
una transmisión de audio o una transmisión de video a la vez.
MediaMuxer está diseñado principalmente para funcionar con MediaCodec,
por lo que puedes procesar videos con MediaCodec y, luego, guardar el
resultado en un archivo MP4 con MediaMuxer. También puedes usar MediaMuxer junto con MediaExtractor para editar contenido multimedia sin necesidad de codificar ni decodificar.
Progreso de la reproducción y limpieza para RemoteControlClient
En Android 4.0 (nivel de API 14), se agregó RemoteControlClient para habilitar los controles de reproducción de contenido multimedia desde clientes de control remoto, como los controles disponibles en la pantalla de bloqueo. Android 4.3 ahora ofrece la capacidad de que esos controladores muestren la posición de reproducción y los controles para avanzar o retroceder la reproducción. Si habilitaste el control remoto para tu
app de contenido multimedia con las APIs de RemoteControlClient, puedes permitir el desplazamiento
en la reproducción implementando dos interfaces nuevas.
Primero, debes habilitar la marca FLAG_KEY_MEDIA_POSITION_UPDATE pasándola a
setTransportControlsFlags().
Luego, implementa las siguientes dos interfaces nuevas:
RemoteControlClient.OnGetPlaybackPositionListener- Esto incluye la devolución de llamada
onGetPlaybackPosition(), que solicita la posición actual de tu contenido multimedia cuando el control remoto necesita actualizar el progreso en su IU. RemoteControlClient.OnPlaybackPositionUpdateListener- Esto incluye la devolución de llamada
onPlaybackPositionUpdate(), que le indica a tu app el nuevo código de tiempo para tu contenido multimedia cuando el usuario arrastra la reproducción con la IU del control remoto.Una vez que actualices la reproducción con la nueva posición, llama a
setPlaybackState()para indicar el nuevo estado, la posición y la velocidad de reproducción.
Una vez que definas estas interfaces, puedes configurarlas para tu RemoteControlClient llamando a setOnGetPlaybackPositionListener() y setPlaybackPositionUpdateListener(), respectivamente.
Gráficos
Compatibilidad con OpenGL ES 3.0
Android 4.3 agrega interfaces de Java y compatibilidad nativa con OpenGL ES 3.0. Entre las nuevas funciones clave que se proporcionan en OpenGL ES 3.0, se incluyen las siguientes:
- Aceleración de efectos visuales avanzados
- Compresión de texturas ETC2/EAC de alta calidad como función estándar
- Una nueva versión del lenguaje de sombreado GLSL ES con compatibilidad con números enteros y de punto flotante de 32 bits
- Renderización de texturas avanzada
- Estandarización más amplia del tamaño de las texturas y los formatos de búfer de renderización
La interfaz de Java para OpenGL ES 3.0 en Android se proporciona con GLES30.
Cuando uses OpenGL ES 3.0, asegúrate de declararlo en tu archivo de manifiesto con la etiqueta <uses-feature> y el atributo android:glEsVersion. Por ejemplo:
<manifest>
<uses-feature android:glEsVersion="0x00030000" />
...
</manifest>Recuerda especificar el contexto de OpenGL ES llamando a setEGLContextClientVersion() y pasando 3 como la versión.
Para obtener más información sobre el uso de OpenGL ES, incluida la manera de comprobar la versión de OpenGL ES compatible con el dispositivo durante el tiempo de ejecución, consulta la guía de la API de OpenGL ES.
Mipmapping para elementos de diseño
Usar un mipmap como fuente para tu mapa de bits o elemento de diseño es una forma sencilla de proporcionar una imagen de calidad y varias escalas de imagen, lo que puede ser particularmente útil si esperas que tu imagen se ajuste durante una animación.
En Android 4.2 (nivel de API 17), se agregó compatibilidad con mapas MIP en la clase Bitmap. Android intercambia las imágenes de mapas MIP en tu Bitmap cuando proporcionas una fuente de mapas MIP y habilitas setHasMipMap(). Ahora, en Android 4.3, también puedes habilitar los mapas MIP para un objeto BitmapDrawable. Para ello, proporciona un recurso de mapa MIP y establece el atributo android:mipMap en un archivo de recursos de mapa de bits o llama a hasMipMap().
Interfaz de usuario
Superposición de objetos View
La nueva clase ViewOverlay proporciona una capa transparente sobre un View en el que puedes agregar contenido visual y que no afecta la jerarquía de diseño. Puedes obtener un ViewOverlay para cualquier View llamando a getOverlay(). La superposición siempre tiene el mismo tamaño y la misma posición que su vista de host (la vista desde la que se creó), lo que te permite agregar contenido que aparece delante de la vista de host, pero que no puede extender los límites de esa vista de host.
Usar un ViewOverlay es particularmente útil cuando quieres crear animaciones, como deslizar una vista fuera de su contenedor o mover elementos por la pantalla sin afectar la jerarquía de vistas. Sin embargo, debido a que el área utilizable de una superposición está restringida a la misma área que su vista de host, si deseas animar una vista que se mueva fuera de su posición en el diseño, debes usar una superposición de una vista superior que tenga los límites de diseño deseados.
Cuando creas una superposición para una vista de widget, como un Button, puedes agregar objetos Drawable a la superposición llamando a add(Drawable). Si llamas a getOverlay() para una vista de diseño, como RelativeLayout, el objeto que se muestra es un ViewGroupOverlay. La clase ViewGroupOverlay es una subclase de ViewOverlay que también te permite agregar objetos View llamando a add(View).
Nota: Todos los elementos de diseño y las vistas que agregues a una superposición son solo visuales. No pueden recibir eventos de enfoque ni de entrada.
Por ejemplo, el siguiente código anima una vista que se desliza hacia la derecha colocando la vista en la superposición de la vista parental y, luego, realizando una animación de traslación en esa vista:
Kotlin
val view: View? = findViewById(R.id.view_to_remove) val container: ViewGroup? = view?.parent as ViewGroup container?.apply { overlay.add(view) ObjectAnimator.ofFloat(view, "translationX", right.toFloat()) .start() }
Java
View view = findViewById(R.id.view_to_remove); ViewGroup container = (ViewGroup) view.getParent(); container.getOverlay().add(view); ObjectAnimator anim = ObjectAnimator.ofFloat(view, "translationX", container.getRight()); anim.start();
Diseño de límites ópticos
En el caso de las vistas que contienen imágenes de fondo de nueve parches, ahora puedes especificar que deben alinearse con las vistas vecinas según los límites "ópticos" de la imagen de fondo, en lugar de los límites de "recorte" de la vista.
Por ejemplo, las figuras 1 y 2 muestran el mismo diseño, pero la versión de la figura 1 usa límites de recorte (el comportamiento predeterminado), mientras que la figura 2 usa límites ópticos. Como las imágenes de nueve parches que se usan para el botón y el marco de fotos incluyen padding alrededor de los bordes, no parecen alinearse entre sí ni con el texto cuando se usan límites de recorte.
Nota: Las capturas de pantalla de las figuras 1 y 2 tienen habilitado el parámetro de configuración para desarrolladores "Mostrar límites de diseño". Para cada vista, las líneas rojas indican los límites ópticos, las líneas azules indican los límites de recorte y el color rosa indica los márgenes.
Figura 1: Diseño con límites de recorte (predeterminado).
Figura 2: Diseño con límites ópticos.
Para alinear las vistas según sus límites ópticos, establece el atributo android:layoutMode en "opticalBounds" en uno de los diseños de nivel superior. Por ejemplo:
<LinearLayout android:layoutMode="opticalBounds" ... >
Figura 3: Vista ampliada del nine-patch del botón Holo con límites ópticos
Para que esto funcione, las imágenes de nueve parches aplicadas al fondo de tus vistas deben especificar los límites ópticos con líneas rojas en la parte inferior y derecha del archivo de nueve parches (como se muestra en la figura 3). Las líneas rojas indican la región que se debe restar de los límites de recorte, lo que deja los límites ópticos de la imagen.
Cuando habilitas los límites ópticos para un ViewGroup en tu diseño, todas las vistas subordinadas heredan el modo de diseño de límites ópticos, a menos que lo anules para una agrupación configurando android:layoutMode en "clipBounds". Todos los elementos de diseño también respetan los límites ópticos de sus vistas secundarias y adaptan sus propios límites en función de los límites ópticos de las vistas que contienen. Sin embargo, los elementos de diseño (subclases de ViewGroup) actualmente no admiten límites ópticos para imágenes de nueve parches aplicadas a su propio fondo.
Si creas una vista personalizada creando una subclase de View, ViewGroup o cualquiera de sus subclases, tu vista heredará estos comportamientos de límites ópticos.
Nota: Todos los widgets compatibles con el tema Holo se actualizaron con límites ópticos, incluidos Button, Spinner, EditText y otros. Por lo tanto, puedes beneficiarte de inmediato si configuras el atributo android:layoutMode en "opticalBounds" si tu app aplica un tema Holo (Theme.Holo, Theme.Holo.Light, etcétera).
Para especificar los límites ópticos de tus propias imágenes de nueve parches con la herramienta Draw 9-patch, mantén presionada la tecla Control cuando hagas clic en los píxeles del borde.
Animación para valores de Rect
Ahora puedes animar entre dos valores de Rect con el nuevo RectEvaluator. Esta nueva clase es una implementación de TypeEvaluator que puedes pasar a ValueAnimator.setEvaluator().
Objeto de escucha de conexión y enfoque de ventanas
Anteriormente, si querías escuchar cuando tu vista se adjuntaba o desconectaba de la ventana, o cuando cambiaba su enfoque, debías anular la clase View para implementar onAttachedToWindow() y onDetachedFromWindow(), o onWindowFocusChanged(), respectivamente.
Ahora, para recibir eventos de conexión y desconexión, puedes implementar ViewTreeObserver.OnWindowAttachListener y establecerlo en una vista con addOnWindowAttachListener().
Para recibir eventos de enfoque, puedes implementar ViewTreeObserver.OnWindowFocusChangeListener y configurarlo en una vista con addOnWindowFocusChangeListener().
Compatibilidad con sobreexploración de TV
Para asegurarte de que tu app ocupe toda la pantalla en cada televisor, ahora puedes habilitar el sobrebarrido en el diseño de tu app. El modo de sobreexploración se determina con la marca FLAG_LAYOUT_IN_OVERSCAN, que puedes habilitar con temas de la plataforma, como Theme_DeviceDefault_NoActionBar_Overscan, o habilitando el estilo windowOverscan en un tema personalizado.
Orientación de la pantalla
El atributo screenOrientation de la etiqueta <activity> ahora admite valores adicionales para respetar la preferencia del usuario de rotación automática:
"userLandscape"- Se comporta igual que
"sensorLandscape", excepto que, si el usuario inhabilita la rotación automática, se bloquea en la orientación horizontal normal y no se gira. "userPortrait"- Se comporta igual que
"sensorPortrait", excepto si el usuario inhabilita la rotación automática. En ese caso, se bloquea en la orientación vertical normal y no se gira. "fullUser"- Se comporta igual que
"fullSensor"y permite la rotación en las cuatro direcciones, excepto si el usuario inhabilita la rotación automática, en cuyo caso se bloquea en la orientación preferida del usuario.
Además, ahora también puedes declarar "locked" para bloquear la orientación de tu app en
la orientación actual de la pantalla.
Animaciones de rotación
El nuevo campo rotationAnimation en WindowManager te permite seleccionar entre una de las tres animaciones que quieras usar cuando el sistema cambie la orientación de la pantalla. Las tres animaciones son las siguientes:
Nota: Estas animaciones solo están disponibles si configuraste tu actividad para que use el modo de "pantalla completa", que puedes habilitar con temas como Theme.Holo.NoActionBar.Fullscreen.
Por ejemplo, a continuación, se muestra cómo puedes habilitar la animación de "fundido cruzado":
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val params: WindowManager.LayoutParams = window.attributes params.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE window.attributes = params ... }
Java
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); WindowManager.LayoutParams params = getWindow().getAttributes(); params.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; getWindow().setAttributes(params); ... }
Entrada del usuario
Nuevos tipos de sensores
El nuevo sensor TYPE_GAME_ROTATION_VECTOR te permite detectar las rotaciones del dispositivo sin preocuparte por las interferencias magnéticas. A diferencia del sensor TYPE_ROTATION_VECTOR, el TYPE_GAME_ROTATION_VECTOR no se basa en el norte magnético.
Los nuevos sensores TYPE_GYROSCOPE_UNCALIBRATED y TYPE_MAGNETIC_FIELD_UNCALIBRATED proporcionan datos de sensores sin procesar sin tener en cuenta las estimaciones de sesgo. Es decir, los sensores TYPE_GYROSCOPE y TYPE_MAGNETIC_FIELD existentes proporcionan datos de sensores que tienen en cuenta el sesgo estimado de la desviación del giroscopio y el hierro duro en el dispositivo, respectivamente. En cambio, las nuevas versiones "sin calibrar" de estos sensores proporcionan los datos sin procesar del sensor y ofrecen los valores de sesgo estimados por separado. Estos sensores te permiten proporcionar tu propia calibración personalizada para los datos de sensores mejorando el sesgo estimado con datos externos.
Notificaciones del objeto de escucha
Android 4.3 agrega una nueva clase de servicio, NotificationListenerService, que permite que tu app reciba información sobre las notificaciones nuevas a medida que el sistema las publica.
Si tu app actualmente usa las APIs del servicio de accesibilidad para acceder a las notificaciones del sistema, debes actualizarla para que use estas APIs en su lugar.
Proveedor de contactos
Consulta para "contactables"
La nueva consulta del proveedor de contactos, Contactables.CONTENT_URI, proporciona una forma eficiente de obtener un Cursor que contenga todas las direcciones de correo electrónico y los números de teléfono que pertenecen a todos los contactos que coinciden con la consulta especificada.
Consulta los cambios en los contactos
Se agregaron nuevas APIs al proveedor de contactos que te permiten consultar de manera eficiente los cambios recientes en los datos de los contactos. Anteriormente, tu app podía recibir una notificación cuando cambiaba algo en los datos de los contactos, pero no sabías exactamente qué había cambiado y debías recuperar todos los contactos y, luego, iterar por ellos para descubrir el cambio.
Para hacer un seguimiento de los cambios en las inserciones y actualizaciones, ahora puedes incluir el parámetro CONTACT_LAST_UPDATED_TIMESTAMP con tu selección para consultar solo los contactos que cambiaron desde la última vez que consultaste al proveedor.
Para hacer un seguimiento de los contactos que se borraron, la nueva tabla ContactsContract.DeletedContacts proporciona un registro de los contactos que se borraron (pero cada contacto borrado se conserva en esta tabla durante un tiempo limitado). Al igual que con CONTACT_LAST_UPDATED_TIMESTAMP, puedes usar el nuevo parámetro de selección, CONTACT_DELETED_TIMESTAMP, para verificar qué contactos se borraron desde la última vez que consultaste al proveedor. La tabla también contiene la constante DAYS_KEPT_MILLISECONDS, que incluye la cantidad de días (en milisegundos) que se conservará el registro.
Además, el proveedor de contactos ahora transmite la acción CONTACTS_DATABASE_CREATED cuando el usuario borra el almacenamiento de contactos a través del menú de configuración del sistema, lo que recrea de manera efectiva la base de datos del proveedor de contactos. Su objetivo es indicar a las apps que deben descartar toda la información de contacto que almacenaron y volver a cargarla con una consulta nueva.
Para ver un código de muestra que use estas APIs para verificar si hay cambios en los contactos, consulta la muestra de ApiDemos disponible en la descarga de muestras del SDK.
Localización
Mejoras en la compatibilidad con texto bidireccional
Las versiones anteriores de Android admiten idiomas y diseños de derecha a izquierda (RTL), pero, a veces, no controlan correctamente el texto con dirección mixta. Por lo tanto, Android 4.3 agrega las APIs de BidiFormatter que te ayudan a dar formato correctamente al texto con contenido en dirección opuesta sin alterar ninguna de sus partes.
Por ejemplo, cuando quieras crear una oración con una variable de cadena, como "¿Quisiste decir 15 Bay Street, Laurel, CA?", normalmente pasarás un recurso de cadenas localizado y la variable a String.format():
Kotlin
val suggestion = String.format(resources.getString(R.string.did_you_mean), address)
Java
Resources res = getResources(); String suggestion = String.format(res.getString(R.string.did_you_mean), address);
Sin embargo, si la configuración regional es hebreo, la cadena con formato se verá de la siguiente manera:
האם התכוונת ל 15 Bay Street, Laurel, CA?
Eso es incorrecto porque el "15" debería estar a la izquierda de "Bay Street". La solución es usar BidiFormatter y su método unicodeWrap(). Por ejemplo, el código anterior se convierte en lo siguiente:
Kotlin
val bidiFormatter = BidiFormatter.getInstance() val suggestion = String.format( resources.getString(R.string.did_you_mean), bidiFormatter.unicodeWrap(address) )
Java
Resources res = getResources(); BidiFormatter bidiFormatter = BidiFormatter.getInstance(); String suggestion = String.format(res.getString(R.string.did_you_mean), bidiFormatter.unicodeWrap(address));
De forma predeterminada, unicodeWrap() usa la heurística de estimación de direccionalidad de primer carácter fuerte, que puede fallar si el primer indicador de dirección del texto no representa la dirección adecuada para el contenido en su totalidad.
Si es necesario, puedes especificar una heurística diferente pasando una de las constantes TextDirectionHeuristic de TextDirectionHeuristics a unicodeWrap().
Nota: Estas APIs nuevas también están disponibles para versiones anteriores de Android a través de la biblioteca de compatibilidad de Android, con la clase BidiFormatter y las APIs relacionadas.
Servicios de accesibilidad
Cómo controlar eventos clave
Ahora, un AccessibilityService puede recibir una devolución de llamada para eventos de entrada de teclas con el método de devolución de llamada onKeyEvent(). Esto permite que tu servicio de accesibilidad controle la entrada de
dispositivos de entrada basados en teclas, como un teclado, y traduzca esos eventos en acciones especiales que,
anteriormente, solo podían realizarse con la entrada táctil o el pad direccional del dispositivo.
Seleccionar texto y copiarlo o pegarlo
AccessibilityNodeInfo ahora proporciona APIs que permiten que un AccessibilityService seleccione, corte, copie y pegue texto en un nodo.
Para especificar la selección de texto que se cortará o copiará, tu servicio de accesibilidad puede usar la nueva acción, ACTION_SET_SELECTION, y pasar con ella la posición de inicio y finalización de la selección con ACTION_ARGUMENT_SELECTION_START_INT y ACTION_ARGUMENT_SELECTION_END_INT.
Como alternativa, puedes seleccionar texto manipulando la posición del cursor con la acción existente, ACTION_NEXT_AT_MOVEMENT_GRANULARITY (antes solo para mover la posición del cursor), y agregando el argumento ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN.
Luego, puedes cortar o copiar con ACTION_CUT,
ACTION_COPY y, luego, pegar con
ACTION_PASTE.
Nota: Estas APIs nuevas también están disponibles para versiones anteriores de Android a través de la biblioteca de compatibilidad de Android, con la clase AccessibilityNodeInfoCompat.
Declara las funciones de accesibilidad
A partir de Android 4.3, un servicio de accesibilidad debe declarar las funciones de accesibilidad en su archivo de metadatos para poder usar ciertas funciones de accesibilidad. Si no se solicita la capacidad en el archivo de metadatos, la función no se ejecutará. Para declarar las capacidades de accesibilidad de tu servicio, debes usar atributos XML que correspondan a las diversas constantes de "capacidad" en la clase AccessibilityServiceInfo.
Por ejemplo, si un servicio no solicita la capacidad flagRequestFilterKeyEvents,
no recibirá eventos de teclas.
Pruebas y depuración
Pruebas automatizadas de la IU
La nueva clase UiAutomation proporciona APIs que te permiten simular acciones del usuario para la automatización de pruebas. Con las APIs de AccessibilityService de la plataforma, las APIs de UiAutomation te permiten inspeccionar el contenido de la pantalla y, además, insertar eventos táctiles y de teclado arbitrarios.
Para obtener una instancia de UiAutomation, llama a Instrumentation.getUiAutomation(). Para que esto funcione, debes proporcionar la opción -w con el comando instrument cuando ejecutes tu InstrumentationTestCase desde adb shell.
Con la instancia de UiAutomation, puedes ejecutar eventos arbitrarios para probar
tu app llamando a executeAndWaitForEvent(), pasándole un Runnable para que realice, un período de tiempo de espera
para la operación y una implementación de la interfaz UiAutomation.AccessibilityEventFilter. En tu implementación de UiAutomation.AccessibilityEventFilter, recibirás una llamada que te permitirá filtrar los eventos que te interesan y determinar el éxito o el fracaso de un caso de prueba determinado.
Para observar todos los eventos durante una prueba, crea una implementación de UiAutomation.OnAccessibilityEventListener y pásala a setOnAccessibilityEventListener().
Luego, tu interfaz de objeto de escucha recibe una llamada a onAccessibilityEvent() cada vez que se produce un evento, y recibe un objeto AccessibilityEvent que describe el evento.
Hay una variedad de otras operaciones que las APIs de UiAutomation exponen en un nivel muy bajo para fomentar el desarrollo de herramientas de prueba de IU, como uiautomator. Por ejemplo,
UiAutomation también puede hacer lo siguiente:
- Cómo insertar eventos de entrada
- Cambiar la orientación de la pantalla
- Cómo tomar capturas de pantalla
Y lo más importante para las herramientas de prueba de IU, las APIs de UiAutomation funcionan más allá de los límites de la aplicación, a diferencia de las de Instrumentation.
Eventos de Systrace para apps
Android 4.3 agrega la clase Trace con dos métodos estáticos, beginSection() y endSection(), que te permiten definir bloques de código para incluir en el informe de Systrace. Si creas secciones de código rastreable en tu app, los registros de systrace te proporcionarán un análisis mucho más detallado de dónde se produce la ralentización en tu app.
Para obtener información sobre cómo usar la herramienta Systrace, consulta Cómo analizar la visualización y el rendimiento con Systrace.
Seguridad
Almacén de claves de Android para claves privadas de la app
Android ahora ofrece un proveedor de seguridad de Java personalizado en la instalación de KeyStore, llamado Android Key Store, que te permite generar y guardar claves privadas que solo tu app puede ver y usar. Para cargar Android Key Store, pasa "AndroidKeyStore" a KeyStore.getInstance().
Para administrar las credenciales privadas de tu app en el almacén de claves de Android, genera una clave nueva con KeyPairGenerator con KeyPairGeneratorSpec. Primero,
llama a getInstance() para obtener una instancia de KeyPairGenerator. Luego, llama a initialize() y pásale una instancia de KeyPairGeneratorSpec, que puedes obtener con KeyPairGeneratorSpec.Builder.
Por último, llama a generateKeyPair() para obtener tu KeyPair.
Almacenamiento de credenciales de hardware
Android ahora también admite el almacenamiento respaldado por hardware para tus credenciales de KeyChain, lo que proporciona más seguridad, ya que las claves no están disponibles para su extracción. Es decir, una vez que las claves se encuentran en un almacén de claves respaldado por hardware (elemento seguro, TPM o TrustZone), se pueden usar para operaciones criptográficas, pero el material de la clave privada no se puede exportar. Ni siquiera el kernel del SO
puede acceder a este material de claves. Si bien no todos los dispositivos con Android admiten el almacenamiento en hardware, puedes verificar en el entorno de ejecución si el almacenamiento respaldado por hardware está disponible llamando a KeyChain.IsBoundKeyAlgorithm().
Declaraciones del manifiesto
Funciones obligatorias declarables
Ahora, se admiten los siguientes valores en el elemento <uses-feature> para que puedas asegurarte de que tu app se instale solo en dispositivos que proporcionen las funciones que necesita.
FEATURE_APP_WIDGETS- Declara que tu app proporciona un widget de app y que solo debe instalarse en dispositivos que incluyan una pantalla principal o una ubicación similar donde los usuarios puedan incorporar widgets de app.
Ejemplo:
<uses-feature android:name="android.software.app_widgets" android:required="true" />
FEATURE_HOME_SCREEN- Declara que tu app se comporta como alternativa a la pantalla principal y que solo debe instalarse en dispositivos que admitan apps de pantalla principal de terceros.
Ejemplo:
<uses-feature android:name="android.software.home_screen" android:required="true" />
FEATURE_INPUT_METHODS- Declara que tu app proporciona un método de entrada personalizado (un teclado compilado con
InputMethodService) y que solo se debe instalar en dispositivos que admitan métodos de entrada de terceros. Ejemplo:<uses-feature android:name="android.software.input_methods" android:required="true" />
FEATURE_BLUETOOTH_LE- Declara que tu app usa las APIs de Bluetooth de bajo consumo y que solo se debe instalar en dispositivos
que puedan comunicarse con otros dispositivos a través de Bluetooth de bajo consumo.
Ejemplo:
<uses-feature android:name="android.software.bluetooth_le" android:required="true" />
Permisos de usuario
Ahora se admiten los siguientes valores en <uses-permission> para declarar los permisos que requiere tu app para acceder a ciertas APIs.
BIND_NOTIFICATION_LISTENER_SERVICE- Es obligatorio para usar las nuevas APIs de
NotificationListenerService. SEND_RESPOND_VIA_MESSAGE- Es obligatorio para recibir el intent
ACTION_RESPOND_VIA_MESSAGE.
Para obtener una vista detallada de todos los cambios de la API en Android 4.3, consulta el Informe de diferencias de la API.