Data Access Auditing
Android 11 (API level 30) introduces a mechanism for performing data access auditing. With data access auditing, we can track our application (and its dependencies) access to user's private data (location, contacts, etc.)
To start auditing our application's data access, we need to use AppOpsManager.OnOpNotedCallback
class.
We can register this callback within an Application
or Activity
based on our needs. If your application accesses user private data in multiple components, such as in a foreground service or in a background task, register the callback in your Application
class.
Basic Usage​
Let's say that we want to audit our application that accesses user's location data.
First, make sure to add this dependency to your build.gradle.kts
file to be able to get location data:
dependencies {
implementation("com.google.android.gms:play-services-location:21.2.0")
}
Next, we need to create an Application
subclass that registers the AppOpsManager.OnOpNotedCallback
:
package com.hanmajid.androidnotebook
import android.app.AppOpsManager
import android.app.Application
import android.app.AsyncNotedAppOp
import android.app.SyncNotedAppOp
import android.os.Build
import android.util.Log
/**
* App's custom [Application] that registers the [AppOpsManager.OnOpNotedCallback].
*/
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() {
private fun logPrivateDataAccess(
opCode: String,
attributionTag: String?,
trace: String,
) {
Log.i(
"TAG", "Private data accessed. " +
"Operation: $opCode\n " +
"Attribution Tag:$attributionTag\nStack Trace:\n$trace"
)
}
override fun onNoted(syncNotedAppOp: SyncNotedAppOp) {
logPrivateDataAccess(
syncNotedAppOp.op,
syncNotedAppOp.attributionTag,
Log.getStackTraceString(Throwable()),
)
}
override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) {
logPrivateDataAccess(
syncNotedAppOp.op,
syncNotedAppOp.attributionTag,
Log.getStackTraceString(Throwable())
)
}
override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {
logPrivateDataAccess(
asyncNotedAppOp.op,
asyncNotedAppOp.attributionTag,
asyncNotedAppOp.message
)
}
}
val appOpsManager = getSystemService(AppOpsManager::class.java)
appOpsManager.setOnOpNotedCallback(mainExecutor, appOpsCallback)
}
}
}
The callback above will log any occurring data access event to Logcat.
Next, we need to set MainApplication
as our custom Application
class in our AndroidManifest.xml
file. We also need to add ACCESS_COARSE_LOCATION
permission to get user's location data for this example:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Add this permission: -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:name=".MainApplication"
>
</application>
</manifest>
Lastly, we only need to try retrieving the user's location data in our MainActivity.kt
file:
package com.hanmajid.androidnotebook
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.ui.Modifier
import androidx.core.app.ActivityCompat
import com.google.android.gms.location.LocationServices
import com.hanmajid.androidnotebook.ui.theme.AndroidNotebookTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AndroidNotebookTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Column {
Button(onClick = {
simpleGetLocation(this@MainActivity)
}) {
Text(text = "Get Location")
}
}
}
}
}
}
/**
* Simple method to retrieve user's coarse location.
*
* This is a simplified version that doesn't request runtime permission.
*/
private fun simpleGetLocation(context: Context) {
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
Toast.makeText(context, "Please grant location permission first", Toast.LENGTH_LONG)
.show()
return
}
fusedLocationClient.lastLocation
.addOnSuccessListener { location: Location? ->
Log.i("TAG", location?.toString())
}
}
}
If we click the "Get Location" button, our registered callback will log the event to Logcat:
Adding Attribution Tag​
You can also add attribution tag to your auditing process to help you determine which part of your application is accessing the private data.
Continuing from the code from the previous section, we first need to create a string tag that labels the attribution tag:
<resources>
<string name="get_location_attribution_label">Get User Coarse Location</string>
</resources>
Then, we need to add an <attribution>
tag to our AndroidManifest.xml
file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
</application>
<attribution
android:label="@string/get_location_attribution_label"
android:tag="getLocation" />
</manifest>
The android:label
value must be a string resource that is readable for user. The android:tag
value must a string literal that will be used in our next step.
Then, we need to add a new method inside MainActivity.kt
file that retrieves user location data, but with an attribution tag:
class MainActivity : ComponentActivity() {
/**
* Simple method to retrieve user's coarse location with the specified [attributionTag].
*
* This is a simplified version that doesn't request runtime permission.
*/
private fun simpleGetLocationWithAttribution(attributionTag: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val attributionContext = createAttributionContext(attributionTag)
simpleGetLocation(attributionContext)
}
}
}
Lastly, we only need to call the method somewhere within our application:
simpleGetLocationWithAttribution("getLocation")
Calling the above method will log the event (with its attribution tag) to our Logcat: