Kiểm tra hoạt động truy cập vào dữ liệu

Bạn có thể hiểu rõ hơn về cách ứng dụng và các phần phụ thuộc của nó truy cập vào dữ liệu cá nhân từ người dùng bằng cách thực hiện kiểm tra hoạt động truy cập dữ liệu. Quy trình này có sẵn trên các thiết bị chạy Android 11 (API cấp 30) trở lên, cho phép bạn xác định tốt hơn hoạt động truy cập vào dữ liệu không mong muốn. Ứng dụng của bạn có thể đăng ký một phiên bản của AppOpsManager.OnOpNotedCallback, có thể thực hiện các hành động mỗi khi một trong những sự kiện sau xảy ra:

  • Mã của ứng dụng truy cập dữ liệu cá nhân. Để giúp bạn xác định phần logic nào trong ứng dụng đã gọi sự kiện đó, bạn có thể kiểm tra quyền truy cập dữ liệu theo thẻ thuộc tính.
  • Mã trong thư viện phần phụ thuộc hoặc SDK truy cập vào dữ liệu cá nhân.

Kiểm tra quyền truy cập vào dữ liệu được gọi trên luồng (thread) khi diễn ra yêu cầu dữ liệu. Điều này có nghĩa là, nếu một SDK hoặc thư viện của bên thứ ba trong ứng dụng gọi một API truy cập dữ liệu cá nhân, thì việc kiểm tra quyền truy cập dữ liệu sẽ cho phép OnOpNotedCallback kiểm tra thông tin về lệnh gọi. Thông thường, đối tượng của hàm gọi lại này có thể cho biết liệu lệnh gọi đến từ ứng dụng của bạn hay SDK bằng cách xem xét trạng thái hiện tại của ứng dụng, chẳng hạn như dấu vết ngăn xếp của luồng hiện tại.

Ghi nhật ký truy cập dữ liệu

Để thực hiện kiểm tra hoạt động truy cập dữ liệu bằng cách sử dụng một phiên bản của AppOpsManager.OnOpNotedCallback, triển khai logic lệnh gọi lại trong thành phần mà bạn dự định kiểm tra quyền truy cập dữ liệu, chẳng hạn như trong phương thứconCreate() của hoạt động.

Đoạn mã sau đây xác định một AppOpsManager.OnOpNotedCallback để kiểm tra quyền truy cập dữ liệu trong một hoạt động riêng:

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

Gọi các phương thức onAsyncNoted()onSelfNoted() trong các trường hợp cụ thể:

  • onAsyncNoted() sẽ được gọi nếu hoạt động truy cập dữ liệu không xảy ra trong lệnh gọi API của ứng dụng. Ví dụ phổ biến nhất là khi ứng dụng của bạn đăng ký một trình nghe và hoạt động truy cập dữ liệu diễn ra mỗi khi lệnh gọi lại của trình nghe được gọi ra.

    Tham số AsyncNotedOp được chuyển vào onAsyncNoted() chứa một phương thức có tên là getMessage(). Phương thức này cung cấp thêm thông tin về hoạt động truy cập dữ liệu. Trong trường hợp các lệnh gọi lại vị trí, thông báo sẽ chứa hàm băm system-identity-hash của trình nghe.

  • onSelfNoted() sẽ được gọi trong trường hợp rất hiếm khi xảy ra việc một ứng dụng chuyển giao diện người dùng riêng (UID) của ứng dụng vào noteOp().

Kiểm tra truy cập dữ liệu theo thẻ phân bổ

Ứng dụng của bạn có thể có một số trường hợp thường sử dụng, chẳng hạn như cho phép người dùng chụp ảnh và chia sẻ các ảnh này với các liên hệ của họ. Nếu phát triển một ứng dụng đa dụng như vậy, bạn có thể áp dụng một thẻ phân bổ cho từng phần của ứng dụng khi tiến hành kiểm tra hoạt động truy cập dữ liệu. Ngữ cảnh attributionTag được trả về trong các đối tượng đã chuyển đến các lệnh gọi đến onNoted(). Điều này giúp bạn dễ dàng truy hoạt động truy cập dữ liệu trở lại các phần hợp lý trong mã của mình.

Để xác định thẻ phân bổ trong ứng dụng, hãy hoàn thành các bước trong các phần sau.

Khai báo thẻ phân bổ trong tệp kê khai

Nếu ứng dụng nhắm đến Android 12 (API cấp 31) trở lên, bạn phải khai báo các thẻ phân bổ trong tệp kê khai của ứng dụng bằng cách sử dụng định dạng được hiển thị trong đoạn mã sau đây. Nếu bạn cố gắng sử dụng thẻ phân bổ mà không khai báo trong tệp kê khai của ứng dụng, hệ thống sẽ tạo một thẻ null cho bạn và ghi lại thông báo trong 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" should be user-readable. -->
    <attribution android:tag="sharePhotos"
                 android:label="@string/share_photos_attribution_label" />
    ...
</manifest>

Tạo thẻ phân bổ

Trong phương thức onCreate() của hoạt động truy cập dữ liệu, chẳng hạn như hoạt động mà bạn yêu cầu vị trí hoặc quyền truy cập vào danh sách liên hệ của người dùng, hãy gọi createAttributionContext(), chuyển đến thẻ phân bổ bạn muốn liên kết một phần của ứng dụng.

Đoạn mã sau đây minh họa cách tạo thẻ phân bổ cho phần "chia sẻ vị trí ảnh" của ứng dụng:

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

Đưa các thẻ phân bổ vào nhật ký truy cập

Hãy cập nhật lệnh gọi lại AppOpsManager.OnOpNotedCallback để đưa tên thẻ phân bổ bạn đã xác định vào nhật ký ứng dụng.

Đoạn mã sau hiển thị logic đã cập nhật ghi lại các thẻ phân bổ:

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