Puedes obtener estadísticas sobre cómo tu app y sus dependencias acceden a los datos privados de los usuarios con la auditoría de acceso a los datos. Ese proceso, que está disponible en dispositivos que ejecutan Android 11 (nivel de API 30) y versiones posteriores, te permite identificar mejor el posible acceso inesperado a los datos. Tu app puede registrar una instancia de AppOpsManager.OnOpNotedCallback
, que puede realizar acciones cada vez que ocurre uno de los siguientes eventos:
- El código de tu app accede a datos privados. Si necesitas ayuda para determinar qué parte lógica de tu app invocó el evento, puedes auditar el acceso a los datos por etiqueta de atribución.
- El código de una biblioteca dependiente o SDK accede a datos privados.
Se invoca la auditoría de acceso a los datos en el subproceso en el que se solicitan datos. Esto significa que, si un SDK o una biblioteca de terceros de tu app llama a una API que accede a datos privados, la auditoría de acceso a los datos le permite a OnOpNotedCallback
examinar la información sobre la llamada. Por lo general, este objeto de devolución de llamada puede determinar si la llamada provino de tu app o del SDK observando el estado actual de la app, como el seguimiento de pila del subproceso actual.
Registro del acceso a los datos
Para realizar una auditoría de acceso a los datos con una instancia de AppOpsManager.OnOpNotedCallback
, implementa la lógica de devolución de llamada en el componente donde intentas auditar el acceso a los datos, como dentro del método onCreate()
o una aplicación de onCreate()
de una actividad.
En el siguiente fragmento de código, se define un AppOpsManager.OnOpNotedCallback
para auditar el acceso a datos dentro de una sola actividad:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() { private fun logPrivateDataAccess(opCode: String, trace: String) { Log.i(MY_APP_TAG, "Private data accessed. " + "Operation: $opCode\nStack Trace:\n$trace") } override fun onNoted(syncNotedAppOp: SyncNotedAppOp) { logPrivateDataAccess( syncNotedAppOp.op, Throwable().stackTrace.toString()) } override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) { logPrivateDataAccess( syncNotedAppOp.op, Throwable().stackTrace.toString()) } override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) { logPrivateDataAccess(asyncNotedAppOp.op, asyncNotedAppOp.message) } } val appOpsManager = getSystemService(AppOpsManager::class.java) as AppOpsManager appOpsManager.setOnOpNotedCallback(mainExecutor, appOpsCallback) }
Java
@Override public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) { AppOpsManager.OnOpNotedCallback appOpsCallback = new AppOpsManager.OnOpNotedCallback() { private void logPrivateDataAccess(String opCode, String trace) { Log.i(MY_APP_TAG, "Private data accessed. " + "Operation: $opCode\nStack Trace:\n$trace"); } @Override public void onNoted(@NonNull SyncNotedAppOp syncNotedAppOp) { logPrivateDataAccess(syncNotedAppOp.getOp(), Arrays.toString(new Throwable().getStackTrace())); } @Override public void onSelfNoted(@NonNull SyncNotedAppOp syncNotedAppOp) { logPrivateDataAccess(syncNotedAppOp.getOp(), Arrays.toString(new Throwable().getStackTrace())); } @Override public void onAsyncNoted(@NonNull AsyncNotedAppOp asyncNotedAppOp) { logPrivateDataAccess(asyncNotedAppOp.getOp(), asyncNotedAppOp.getMessage()); } }; AppOpsManager appOpsManager = getSystemService(AppOpsManager.class); if (appOpsManager != null) { appOpsManager.setOnOpNotedCallback(getMainExecutor(), appOpsCallback); } }
Se llama a los métodos onAsyncNoted()
y onSelfNoted()
en situaciones específicas:
Se llama a
onAsyncNoted()
si el acceso a los datos no ocurre durante la llamada a la API de tu app. El ejemplo más común es cuando tu app registra un objeto de escucha y el acceso a los datos se produce cada vez que se invoca la devolución de llamada de ese objeto.El argumento
AsyncNotedOp
que se pasa aonAsyncNoted()
contiene un método llamadogetMessage()
. Ese método proporciona más información sobre el acceso a los datos. En el caso de devoluciones de llamada de ubicación, el mensaje contiene el hash de identidad del sistema del objeto de escucha.Se llama a
onSelfNoted()
en el caso poco frecuente de que una app pase su propio UID anoteOp()
.
Cómo auditar el acceso a los datos por etiqueta de atribución
Es posible que tu app tenga varios casos prácticos principales, como permitir que los usuarios tomen fotos y las compartan con sus contactos. Si desarrollas una aplicación multiuso de este tipo, puedes aplicar una etiqueta de atribución a cada parte de la aplicación durante las auditorías de acceso a los datos. Se devuelve el contexto attributionTag
en los objetos pasados en las llamadas a onNoted()
.
Esto te permite hacer el seguimiento de acceso a los datos hasta las partes lógicas de tu código con mayor facilidad.
Para definir etiquetas de atribución en la aplicación, completa los pasos que se indican en las siguientes secciones.
Cómo declarar etiquetas de atribución en manifiesto
Si la app se orienta a Android 12 (nivel de API 31) o versiones posteriores, debes declarar las etiquetas de atribución en el archivo de manifiesto de tu app, con el formato que se muestra en el siguiente fragmento de código. Si tratas de usar una etiqueta de atribución que no declaras en el archivo de manifiesto de tu app, el sistema crea una etiqueta null
por ti y registra un mensaje en Logcat.
<manifest ...> <!-- The value of "android:tag" must be a literal string, and the value of "android:label" must be a resource. The value of "android:label" is user-readable. --> <attribution android:tag="sharePhotos" android:label="@string/share_photos_attribution_label" /> ... </manifest>
Cómo crear etiquetas de atribución
En el método onCreate()
de la actividad en la que accedes a los datos, como la actividad en la que solicitas la ubicación o el acceso a la lista de contactos del usuario, llama a createAttributionContext()
y pasa la etiqueta de atribución que quieras asociar con una parte de la app.
En el siguiente fragmento de código, se muestra cómo crear una etiqueta de atribución para la parte de compartir ubicación de una foto de una app:
Kotlin
class SharePhotoLocationActivity : AppCompatActivity() { lateinit var attributionContext: Context override fun onCreate(savedInstanceState: Bundle?) { attributionContext = createAttributionContext("sharePhotos") } fun getLocation() { val locationManager = attributionContext.getSystemService( LocationManager::class.java) as LocationManager // Use "locationManager" to access device location information. } }
Java
public class SharePhotoLocationActivity extends AppCompatActivity { private Context attributionContext; @Override public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) { attributionContext = createAttributionContext("sharePhotos"); } public void getLocation() { LocationManager locationManager = attributionContext.getSystemService(LocationManager.class); if (locationManager != null) { // Use "locationManager" to access device location information. } } }
Cómo incluir las etiquetas de atribución en los registros de acceso
Actualiza la devolución de llamada AppOpsManager.OnOpNotedCallback
para que los registros de tu app incluyan los nombres de las etiquetas de atribución que definiste.
En el siguiente fragmento de código, se muestra la lógica actualizada que registra las etiquetas de atribución:
Kotlin
val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() { private fun logPrivateDataAccess( opCode: String, attributionTag: String, trace: String) { Log.i(MY_APP_TAG, "Private data accessed. " + "Operation: $opCode\n " + "Attribution Tag:$attributionTag\nStack Trace:\n$trace") } override fun onNoted(syncNotedAppOp: SyncNotedAppOp) { logPrivateDataAccess(syncNotedAppOp.op, syncNotedAppOp.attributionTag, Throwable().stackTrace.toString()) } override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) { logPrivateDataAccess(syncNotedAppOp.op, syncNotedAppOp.attributionTag, Throwable().stackTrace.toString()) } override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) { logPrivateDataAccess(asyncNotedAppOp.op, asyncNotedAppOp.attributionTag, asyncNotedAppOp.message) } }
Java
@Override public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) { AppOpsManager.OnOpNotedCallback appOpsCallback = new AppOpsManager.OnOpNotedCallback() { private void logPrivateDataAccess(String opCode, String attributionTag, String trace) { Log.i("MY_APP_TAG", "Private data accessed. " + "Operation: $opCode\n " + "Attribution Tag:$attributionTag\nStack Trace:\n$trace"); } @Override public void onNoted(@NonNull SyncNotedAppOp syncNotedAppOp) { logPrivateDataAccess(syncNotedAppOp.getOp(), syncNotedAppOp.getAttributionTag(), Arrays.toString(new Throwable().getStackTrace())); } @Override public void onSelfNoted(@NonNull SyncNotedAppOp syncNotedAppOp) { logPrivateDataAccess(syncNotedAppOp.getOp(), syncNotedAppOp.getAttributionTag(), Arrays.toString(new Throwable().getStackTrace())); } @Override public void onAsyncNoted(@NonNull AsyncNotedAppOp asyncNotedAppOp) { logPrivateDataAccess(asyncNotedAppOp.getOp(), asyncNotedAppOp.getAttributionTag(), asyncNotedAppOp.getMessage()); } }; AppOpsManager appOpsManager = getSystemService(AppOpsManager.class); if (appOpsManager != null) { appOpsManager.setNotedAppOpsCollector(appOpsCollector); } }