Audita el acceso a los datos

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:

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 a onAsyncNoted() contiene un método llamado getMessage(). 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 a noteOp().

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