Cấp quyền truy cập một phần vào ảnh và video

Android 14 ra mắt tính năng Quyền truy cập vào ảnh đã chọn, cho phép người dùng cấp cho ứng dụng quyền truy cập vào một số hình ảnh và video cụ thể trong thư viện của họ, thay vì cấp quyền truy cập vào tất cả nội dung nghe nhìn thuộc một loại nhất định.

Thay đổi này chỉ được bật nếu ứng dụng của bạn nhắm đến Android 14 (API cấp 34) trở lên. Nếu chưa sử dụng công cụ chọn ảnh, bạn nên triển khai công cụ này trong ứng dụng để mang lại trải nghiệm nhất quán khi chọn hình ảnh và video, đồng thời tăng cường quyền riêng tư của người dùng mà không cần yêu cầu bất kỳ quyền truy cập bộ nhớ nào.

Nếu bạn duy trì bộ chọn thư viện của riêng mình bằng cách sử dụng quyền truy cập bộ nhớ và cần duy trì toàn quyền kiểm soát việc triển khai, hãy điều chỉnh cách triển khai để sử dụng quyền READ_MEDIA_VISUAL_USER_SELECTED mới. Nếu ứng dụng của bạn không dùng quyền mới, hệ thống sẽ chạy ứng dụng ở chế độ tương thích.

SDK mục tiêu Đã khai báo READ_MEDIA_VISUAL_USER_SELECTED Đã bật quyền truy cập vào Photos Hành vi trải nghiệm người dùng
SDK 33 Không Không Không có
Do ứng dụng kiểm soát
SDK 34 Không Do hệ thống kiểm soát (hành vi tương thích)
Do ứng dụng kiểm soát

Việc tạo bộ chọn thư viện của riêng bạn đòi hỏi phải phát triển và bảo trì rộng rãi, đồng thời ứng dụng của bạn cần yêu cầu quyền truy cập vào bộ nhớ để có được sự đồng ý rõ ràng của người dùng. Người dùng có thể từ chối các yêu cầu này hoặc nếu ứng dụng của bạn đang chạy trên thiết bị chạy Android 14 và ứng dụng của bạn nhắm đến Android 14 (API cấp 34) trở lên, hãy hạn chế quyền truy cập vào nội dung nghe nhìn đã chọn. Hình ảnh sau đây cho thấy ví dụ về cách yêu cầu quyền và chọn nội dung nghe nhìn bằng các tuỳ chọn mới.

(Đuôi
Hình 1. Hộp thoại mới cho phép người dùng chọn những ảnh và video cụ thể mà họ muốn cung cấp cho ứng dụng của bạn, ngoài các lựa chọn thông thường để cấp toàn quyền truy cập hoặc từ chối tất cả quyền truy cập.

Phần này minh hoạ phương pháp được đề xuất để tạo bộ chọn thư viện của riêng bạn bằng MediaStore. Nếu đã duy trì bộ chọn thư viện cho ứng dụng và cần duy trì toàn quyền kiểm soát, bạn có thể sử dụng các ví dụ này để điều chỉnh cách triển khai. Nếu bạn không cập nhật cách triển khai để xử lý Quyền truy cập vào ảnh đã chọn, thì hệ thống sẽ chạy ứng dụng của bạn ở chế độ tương thích.

Yêu cầu cấp quyền

Trước tiên, hãy yêu cầu quyền truy cập bộ nhớ chính xác trong tệp kê khai Android, tuỳ thuộc vào phiên bản hệ điều hành:

<!-- Devices running Android 12L (API level 32) or lower  -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

<!-- Devices running Android 13 (API level 33) or higher -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<!-- To handle the reselection within the app on devices running Android 14
     or higher if your app targets Android 14 (API level 34) or higher.  -->
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />

Sau đó, hãy yêu cầu quyền khi bắt đầu chạy chính xác, cũng tuỳ thuộc vào phiên bản hệ điều hành:

// Register ActivityResult handler
val requestPermissions = registerForActivityResult(RequestMultiplePermissions()) { results ->
    // Handle permission requests results
    // See the permission example in the Android platform samples: https://github.com/android/platform-samples
}

// Permission request logic
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
    requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED))
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    requestPermissions.launch(arrayOf(READ_MEDIA_IMAGES, READ_MEDIA_VIDEO))
} else {
    requestPermissions.launch(arrayOf(READ_EXTERNAL_STORAGE))
}

Một số ứng dụng không cần quyền

Kể từ Android 10 (API cấp 29), ứng dụng không cần quyền truy cập bộ nhớ để thêm tệp vào bộ nhớ dùng chung nữa. Điều này có nghĩa là các ứng dụng có thể thêm hình ảnh vào thư viện, quay video và lưu vào bộ nhớ dùng chung hoặc tải hoá đơn PDF xuống mà không cần yêu cầu quyền truy cập vào bộ nhớ. Nếu ứng dụng của bạn chỉ thêm tệp vào bộ nhớ dùng chung và không truy vấn hình ảnh hoặc video, thì bạn nên ngừng yêu cầu quyền truy cập bộ nhớ và đặt maxSdkVersion của API 28 trong AndroidManifest.xml:

<!-- No permission is needed to add files to shared storage on Android 10 (API level 29) or higher  -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />

Xử lý việc lựa chọn lại nội dung nghe nhìn

Với tính năng Quyền truy cập vào ảnh đã chọn trong Android 14, ứng dụng của bạn nên sử dụng quyền READ_MEDIA_VISUAL_USER_SELECTED mới để kiểm soát việc chọn lại nội dung nghe nhìn và cập nhật giao diện của ứng dụng để cho phép người dùng cấp cho ứng dụng quyền truy cập vào một nhóm ảnh và video khác. Hình ảnh sau đây cho thấy ví dụ về cách yêu cầu quyền và chọn lại nội dung nghe nhìn:

(Đuôi
Hình 2. Hộp thoại mới cũng cho phép người dùng chọn lại những ảnh và video mà họ muốn cung cấp cho ứng dụng của bạn.

Khi mở hộp thoại lựa chọn, ảnh, video hoặc cả hai sẽ xuất hiện tuỳ thuộc vào quyền được yêu cầu. Ví dụ: nếu bạn yêu cầu quyền READ_MEDIA_VIDEO mà không có quyền READ_MEDIA_IMAGES, thì chỉ video mới xuất hiện trong giao diện người dùng để người dùng chọn tệp.

// Allow the user to select only videos
requestPermissions.launch(arrayOf(READ_MEDIA_VIDEO, READ_MEDIA_VISUAL_USER_SELECTED))

Bạn có thể kiểm tra xem ứng dụng của mình có toàn quyền, một phần quyền hoặc bị từ chối quyền truy cập vào thư viện ảnh của thiết bị hay không và cập nhật giao diện cho phù hợp. Hãy yêu cầu các quyền này khi ứng dụng cần quyền truy cập vào bộ nhớ, thay vì khi khởi động. Xin lưu ý rằng bạn có thể thay đổi quyền cấp giữa các lệnh gọi lại trong vòng đời ứng dụng onStartonResume, vì người dùng có thể thay đổi quyền truy cập trong phần cài đặt mà không cần đóng ứng dụng.

if (
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
    (
        ContextCompat.checkSelfPermission(context, READ_MEDIA_IMAGES) == PERMISSION_GRANTED ||
        ContextCompat.checkSelfPermission(context, READ_MEDIA_VIDEO) == PERMISSION_GRANTED
    )
) {
    // Full access on Android 13 (API level 33) or higher
} else if (
    Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE &&
    ContextCompat.checkSelfPermission(context, READ_MEDIA_VISUAL_USER_SELECTED) == PERMISSION_GRANTED
) {
    // Partial access on Android 14 (API level 34) or higher
}  else if (ContextCompat.checkSelfPermission(context, READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
    // Full access up to Android 12 (API level 32)
} else {
    // Access denied
}

Truy vấn thư viện thiết bị

Sau khi xác minh rằng bạn có quyền truy cập vào các quyền phù hợp vào bộ nhớ, bạn có thể tương tác với MediaStore để truy vấn thư viện thiết bị (phương pháp tương tự vẫn áp dụng cho dù quyền truy cập đã cấp là một phần hay toàn bộ):

data class Media(
    val uri: Uri,
    val name: String,
    val size: Long,
    val mimeType: String,
)

// Run the querying logic in a coroutine outside of the main thread to keep the app responsive.
// Keep in mind that this code snippet is querying only images of the shared storage.
suspend fun getImages(contentResolver: ContentResolver): List<Media> = withContext(Dispatchers.IO) {
    val projection = arrayOf(
        Images.Media._ID,
        Images.Media.DISPLAY_NAME,
        Images.Media.SIZE,
        Images.Media.MIME_TYPE,
    )

    val collectionUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Query all the device storage volumes instead of the primary only
        Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL)
    } else {
        Images.Media.EXTERNAL_CONTENT_URI
    }

    val images = mutableListOf<Media>()

    contentResolver.query(
        collectionUri,
        projection,
        null,
        null,
        "${Images.Media.DATE_ADDED} DESC"
    )?.use { cursor ->
        val idColumn = cursor.getColumnIndexOrThrow(Images.Media._ID)
        val displayNameColumn = cursor.getColumnIndexOrThrow(Images.Media.DISPLAY_NAME)
        val sizeColumn = cursor.getColumnIndexOrThrow(Images.Media.SIZE)
        val mimeTypeColumn = cursor.getColumnIndexOrThrow(Images.Media.MIME_TYPE)

        while (cursor.moveToNext()) {
            val uri = ContentUris.withAppendedId(collectionUri, cursor.getLong(idColumn))
            val name = cursor.getString(displayNameColumn)
            val size = cursor.getLong(sizeColumn)
            val mimeType = cursor.getString(mimeTypeColumn)

            val image = Media(uri, name, size, mimeType)
            images.add(image)
        }
    }

    return@withContext images
}

Đoạn mã này được đơn giản hoá để minh hoạ cách tương tác với MediaStore. Trong ứng dụng sẵn sàng phát hành, hãy sử dụng tính năng phân trang bằng một công cụ như thư viện Paging để đảm bảo hiệu suất tốt.

Truy vấn lựa chọn gần đây nhất

Các ứng dụng trên Android 15 trở lên và Android 14 có hỗ trợ bản cập nhật hệ thống Google Play có thể truy vấn lựa chọn gần đây nhất về hình ảnh và video mà người dùng thực hiện khi truy cập một phần bằng cách bật QUERY_ARG_LATEST_SELECTION_ONLY:

if (getExtensionVersion(Build.VERSION_CODES.U) >= 12) {
    val queryArgs = bundleOf(
        QUERY_ARG_SQL_SORT_ORDER to "${Images.Media.DATE_ADDED} DESC"
        QUERY_ARG_LATEST_SELECTION_ONLY to true
    )

    contentResolver.query(collectionUri, projection, queryArgs, null)
}

Quyền truy cập vào ảnh và video được giữ nguyên khi nâng cấp thiết bị

Trong trường hợp ứng dụng của bạn sử dụng một thiết bị nâng cấp từ phiên bản Android trước đó lên Android 14, hệ thống sẽ tự động truy cập đầy đủ ảnh và video của người dùng, đồng thời tự động cấp một số quyền cho ứng dụng của bạn. Hành vi chính xác này phụ thuộc vào nhóm quyền được cấp cho ứng dụng của bạn trước khi thiết bị đó nâng cấp lên Android 14.

Quyền trên Android 13

Hãy cân nhắc trường hợp sau:

  1. Ứng dụng của bạn được cài đặt trên một thiết bị chạy Android 13.
  2. Người dùng đã cấp quyền READ_MEDIA_IMAGES và quyền READ_MEDIA_VIDEO cho ứng dụng của bạn.
  3. Sau đó, thiết bị sẽ nâng cấp lên Android 14 trong khi ứng dụng của bạn vẫn được cài đặt.
  4. Ứng dụng của bạn bắt đầu nhắm đến Android 14 (API cấp 34) trở lên.

Trong trường hợp này, ứng dụng của bạn vẫn có toàn quyền truy cập vào ảnh và video của người dùng. Hệ thống cũng tự động cấp các quyền READ_MEDIA_IMAGESREAD_MEDIA_VIDEO cho ứng dụng của bạn.

Quyền trên Android 12 trở xuống

Hãy cân nhắc trường hợp sau:

  1. Ứng dụng của bạn được cài đặt trên một thiết bị chạy Android 13.
  2. Người dùng đã cấp quyền READ_EXTERNAL_STORAGE hoặc quyền WRITE_EXTERNAL_STORAGE cho ứng dụng của bạn.
  3. Sau đó, thiết bị sẽ nâng cấp lên Android 14 trong khi ứng dụng của bạn vẫn được cài đặt.
  4. Ứng dụng của bạn bắt đầu nhắm đến Android 14 (API cấp 34) trở lên.

Trong trường hợp này, ứng dụng của bạn vẫn có toàn quyền truy cập vào ảnh và video của người dùng. Hệ thống cũng tự động cấp quyền READ_MEDIA_IMAGES và quyền READ_MEDIA_VIDEO cho ứng dụng của bạn.

Các phương pháp hay nhất

Phần này trình bày một số phương pháp hay nhất để sử dụng quyền READ_MEDIA_VISUAL_USER_SELECTED. Để biết thêm thông tin, hãy xem các phương pháp hay nhất về quyền của chúng tôi.

Không lưu trữ vĩnh viễn trạng thái quyền

Không lưu trữ vĩnh viễn trạng thái cấp quyền (bao gồm cả SharedPreferences hoặc DataStore). Trạng thái đã lưu trữ có thể không đồng bộ với trạng thái thực tế. Trạng thái của quyền có thể thay đổi sau khi đặt lại quyền, trạng thái ngủ đông của ứng dụng, thay đổi do người dùng thực hiện trong chế độ cài đặt của ứng dụng, hoặc khi ứng dụng của bạn chuyển sang chạy ở chế độ nền. Thay vào đó, hãy kiểm tra quyền truy cập bộ nhớ bằng ContextCompat.checkSelfPermission().

Không giả định có toàn quyền truy cập vào ảnh và video

Dựa trên những thay đổi được giới thiệu trong Android 14, ứng dụng của bạn có thể chỉ có quyền truy cập một phần vào thư viện ảnh của thiết bị. Nếu ứng dụng đang lưu dữ liệu MediaStore vào bộ nhớ đệm khi được truy vấn bằng ContentResolver, thì có thể bộ nhớ đệm chưa cập nhật.

  • Luôn truy vấn MediaStore bằng ContentResolver, thay vì dựa vào bộ nhớ đệm được lưu trữ.
  • Lưu kết quả trong bộ nhớ khi ứng dụng chạy trên nền trước.
  • Làm mới kết quả khi ứng dụng của bạn trải qua vòng đời ứng dụng onResume vì người dùng có thể chuyển từ quyền truy cập đầy đủ sang quyền truy cập một phần thông qua phần cài đặt quyền.

Xem quyền truy cập URI là tạm thời

Nếu người dùng chọn Chọn ảnh và video trong hộp thoại cấp quyền của hệ thống, thì quyền truy cập của ứng dụng vào ảnh và video đã chọn sẽ hết hạn. Ứng dụng của bạn phải luôn xử lý trường hợp không có quyền truy cập vào mọi Uri, bất kể quyền của các ứng dụng đó.

Lọc loại nội dung nghe nhìn có thể chọn theo quyền

Hộp thoại lựa chọn nhạy cảm với loại quyền được yêu cầu:

  • Việc chỉ yêu cầu READ_MEDIA_IMAGES sẽ chỉ hiển thị hình ảnh có thể chọn.
  • Việc chỉ yêu cầu READ_MEDIA_VIDEO sẽ chỉ hiển thị video để có thể chọn.
  • Việc yêu cầu cả READ_MEDIA_IMAGESREAD_MEDIA_VIDEO sẽ cho phép chọn toàn bộ thư viện ảnh.

Dựa trên các trường hợp sử dụng của ứng dụng, bạn phải đảm bảo yêu cầu các quyền phù hợp để tránh mang lại trải nghiệm không tốt cho người dùng. Nếu một tính năng chỉ dự kiến sẽ chọn video, hãy nhớ chỉ yêu cầu READ_MEDIA_VIDEO.

Yêu cầu cấp quyền trong một thao tác

Để ngăn người dùng nhìn thấy nhiều hộp thoại thời gian chạy hệ thống, hãy yêu cầu quyền READ_MEDIA_VISUAL_USER_SELECTED, ACCESS_MEDIA_LOCATION và quyền "đọc nội dung đa phương tiện" (READ_MEDIA_IMAGES, READ_MEDIA_VIDEO hoặc cả hai) trong một thao tác.

Cho phép người dùng quản lý lựa chọn của họ

Khi người dùng chọn chế độ truy cập một phần, ứng dụng của bạn không được giả định rằng thư viện ảnh của thiết bị trống và phải cho phép người dùng cấp thêm tệp.

Người dùng có thể quyết định chuyển từ quyền truy cập đầy đủ sang quyền truy cập một phần thông qua phần cài đặt quyền mà không cần cấp quyền truy cập vào một số tệp nội dung đa phương tiện trực quan.

Chế độ tương thích

Nếu bạn duy trì bộ chọn thư viện của riêng mình bằng cách sử dụng quyền truy cập bộ nhớ nhưng chưa điều chỉnh ứng dụng để sử dụng quyền READ_MEDIA_VISUAL_USER_SELECTED mới, thì hệ thống sẽ chạy ứng dụng của bạn ở chế độ tương thích bất cứ khi nào người dùng cần chọn hoặc chọn lại nội dung nghe nhìn.

Hành vi trong quá trình chọn nội dung đa phương tiện ban đầu

Trong quá trình lựa chọn ban đầu, nếu người dùng chọn "Chọn ảnh và video" (xem hình 1), thì các quyền READ_MEDIA_IMAGESREAD_MEDIA_VIDEO sẽ được cấp trong phiên hoạt động của ứng dụng, cấp quyền truy cập tạm thời vào ảnh và video do người dùng chọn. Khi ứng dụng của bạn chuyển sang chạy ở chế độ nền hoặc khi người dùng chủ động tắt ứng dụng, thì sau cùng hệ thống sẽ từ chối những quyền đó. Hành vi này cũng giống như các quyền một lần khác.

Hành vi trong quá trình chọn lại nội dung nghe nhìn

Nếu sau này ứng dụng của bạn cần quyền truy cập vào ảnh và video bổ sung, bạn phải yêu cầu cấp lại quyền READ_MEDIA_IMAGES hoặc READ_MEDIA_VIDEO theo cách thủ công. Hệ thống sẽ tuân theo quy trình tương tự như với yêu cầu cấp quyền ban đầu, nhắc người dùng chọn ảnh và video (xem hình 2).

Nếu ứng dụng của bạn làm theo các phương pháp hay nhất về quyền, thì thay đổi này sẽ không gây ảnh hưởng. Điều này đặc biệt đúng nếu ứng dụng của bạn không giả định rằng quyền truy cập URI được giữ lại, lưu trữ trạng thái quyền của hệ thống hoặc làm mới nhóm hình ảnh hiển thị sau khi thay đổi quyền. Tuy nhiên, hành vi này có thể không lý tưởng tuỳ thuộc vào trường hợp sử dụng của ứng dụng. Để mang lại trải nghiệm tốt nhất cho người dùng, bạn nên triển khai công cụ chọn ảnh hoặc điều chỉnh công cụ chọn thư viện của ứng dụng để trực tiếp xử lý hành vi này bằng cách sử dụng quyền READ_MEDIA_VISUAL_USER_SELECTED.