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

Bạn có thể hiểu rõ hơn cách ứng dụng của bạn và các phần phụ thuộc của ứng dụng này truy cập vào dữ liệu riêng tư của người dùng bằng cách kiểm tra quyền truy cập dữ liệu. Bạn có thể dùng những quy trình này trên các thiết bị chạy Android 11 (API cấp 30) trở lên để xác định chính xác hơn khả năng xảy ra việc truy cập không mong muốn vào dữ liệu. Ứng dụng của bạn có thể đăng ký một thực thể của AppOpsManager.OnOpNotedCallback, có khả năng 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.

Tính năng kiểm tra quyền truy cập dữ liệu được gọi trên luồng khi có 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 vào dữ liệu riêng tư, thì tính năng 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 lệnh 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

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

Đoạn mã sau đây xác định AppOpsManager.OnOpNotedCallback để kiểm tra quyền truy cập dữ liệu trong một hoạt độ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 quyền 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 sử dụng chính, 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 những người liên hệ của họ. Nếu phát triển ứ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 quyền 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 một 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" is 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 thông tin vị trí hoặc quyền truy cập vào danh bạ 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 với một phần của ứng dụng.

Đoạn mã sau đây minh hoạ cách tạo một thẻ phân bổ cho phần chia sẻ thông tin vị trí trong ả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 các tên thẻ phân bổ mà bạn đã xác định vào nhật ký của ứ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);
    }
}