审核数据访问权限

您可以通过执行 *数据访问审核* 来深入了解您的应用及其依赖项如何访问用户的私有数据。此过程适用于运行 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() 的方法。此方法提供有关数据访问的更多信息。在位置回调的情况下,消息包含侦听器的系统标识哈希值。

  • onSelfNoted() 在应用将其自己的 UID 传递到 noteOp() 的极少数情况下调用此方法。

按归因标签审核数据访问权限

您的应用可能有多个主要用例,例如,允许用户捕获照片并与联系人共享这些照片。如果您开发多用途应用,则在审核其数据访问权限时,可以为应用的每个部分应用 *归因标签*。在传递到 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>

创建归因标签

在您访问数据的活动的 onCreate() 方法中(例如,您请求位置或访问用户联系人列表的活动),调用 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);
    }
}