审计数据访问权限

您可以通过执行 *数据访问审计* 来深入了解您的应用程序及其依赖项如何访问用户的私有数据。此过程适用于运行 Android 11(API 级别 30)及更高版本的设备,可帮助您更好地识别潜在的意外数据访问。您的应用程序可以注册 AppOpsManager.OnOpNotedCallback 的实例,该实例可以在以下事件之一发生时执行操作

  • 您的应用程序代码访问私有数据。为了帮助您确定应用程序的哪个逻辑部分调用了该事件,您可以 按归因标记审计数据访问权限.
  • 依赖库或 SDK 中的代码访问私有数据。

数据访问审计在发生数据请求的线程上调用。这意味着,如果您的应用程序中的第三方 SDK 或库调用访问私有数据的 API,数据访问审计将允许您的 OnOpNotedCallback 检查有关该调用的信息。通常,此回调对象可以通过查看应用程序的当前状态(例如,当前线程的堆栈跟踪)来判断该调用是来自您的应用程序还是来自 SDK。

记录数据访问

要使用 AppOpsManager.OnOpNotedCallback 的实例执行数据访问审计,请在您打算审计数据访问的组件中实现回调逻辑,例如在活动的 onCreate() 方法或应用程序的 onCreate() 方法中。

以下代码片段定义了一个用于审计单个活动中的数据访问的 AppOpsManager.OnOpNotedCallback

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

onAsyncNoted()onSelfNoted() 方法在特定情况下被调用

  • onAsyncNoted() 在数据访问未在您的应用程序的 API 调用期间发生时被调用。最常见的示例是,当您的应用程序注册监听器时,每次调用监听器的回调时都会发生数据访问。

    传递给 onAsyncNoted()AsyncNotedOp 参数包含一个名为 getMessage() 的方法。此方法提供有关数据访问的更多信息。在位置回调的情况下,消息包含侦听器的系统标识哈希。

  • 当应用程序将其自己的 UID 传递给 noteOp() 时,会调用 onSelfNoted(),这种情况非常罕见。

通过归属标签审核数据访问

您的应用程序可能有多个主要用例,例如允许用户拍摄照片并与联系人共享这些照片。如果您开发的是多用途应用程序,则在审核其数据访问时,可以为应用程序的每个部分应用一个归属标签。当您调用 onNoted() 时,传递给该调用的对象中会返回 attributionTag 上下文。这有助于您更轻松地将数据访问跟踪回代码的逻辑部分。

要为您的应用程序定义归属标签,请完成以下部分中的步骤。

在清单中声明归属标签

如果您的应用程序面向 Android 12(API 级别 31)或更高版本,则必须在应用程序的清单文件中声明归属标签,使用以下代码片段中所示的格式。如果尝试使用未在应用程序清单文件中声明的归属标签,系统将为您创建一个 null 标签,并在 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>

创建归属标签

在您访问数据的 Activity 的 onCreate() 方法中(例如,请求位置或访问用户联系人列表的 Activity),调用 createAttributionContext(),传入您希望与应用程序一部分关联的归属标签。

以下代码片段演示了如何为应用程序的照片位置共享部分创建一个归属标签

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

在访问日志中包含归属标签

更新您的 AppOpsManager.OnOpNotedCallback 回调,以便应用程序的日志包含您定义的归属标签的名称。

以下代码片段显示了记录归属标签的更新逻辑

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