Lớp học lập trình về quyền riêng tư trên Android

1. Phần giới thiệu

Kiến thức bạn sẽ học được

  • Lý do quyền riêng tư ngày càng trở nên quan trọng đối với người dùng.
  • Các phương pháp hay nhất về quyền riêng tư trên Android trong một số bản phát hành trước đây.
  • Cách tích hợp các phương pháp hay nhất về quyền riêng tư vào các ứng dụng hiện có để tăng cường khả năng bảo đảm quyền riêng tư.

Sản phẩm bạn sẽ tạo ra

Trong lớp học lập trình này, bạn bắt đầu bằng một ứng dụng mẫu cho phép người dùng lưu kỷ niệm trên ảnh của họ.

Ứng dụng này sẽ bắt đầu bằng những màn hình sau:

  • Màn hình yêu cầu cấp quyền – màn hình yêu cầu người dùng cấp tất cả các quyền trước khi chuyển đến màn hình chính.
  • Màn hình chính – màn hình hiển thị toàn bộ nhật ký ảnh hiện có của người dùng, đồng thời cho phép họ thêm nhật ký ảnh mới.
  • Màn hình thêm nhật ký – màn hình cho phép người dùng tạo nhật ký ảnh mới. Tại đây, người dùng có thể duyệt qua các ảnh hiện có trong thư viện của mình, chụp ảnh mới bằng camera và thêm thành phố mình đang ở vào nhật ký ảnh.
  • Màn hình camera – màn hình cho phép người dùng chụp ảnh và lưu vào nhật ký ảnh.

Ứng dụng này đang hoạt động, nhưng có nhiều điểm chưa hoàn thiện về quyền riêng tư mà chúng ta sẽ cùng nhau cải thiện!

Khi tham gia lớp học lập trình, bạn sẽ:

  • ...thấy lý do khiến quyền riêng tư lại quan trọng đối với ứng dụng
  • ...tìm hiểu các tính năng và phương pháp chính hay nhất về quyền riêng tư trên Android.
  • ...biết cách triển khai các phương pháp hay nhất này trong ứng dụng hiện có, bằng cách thực hiện các bước sau:
  • Yêu cầu cấp quyền theo ngữ cảnh
  • Hạn chế quyền truy cập thông tin vị trí của ứng dụng
  • Sử dụng công cụ chọn ảnh và các điểm cải tiến khác về bộ nhớ
  • Sử dụng các API kiểm tra quyền truy cập dữ liệu

Khi hoàn tất, bạn sẽ có một ứng dụng:

  • ...triển khai các phương pháp hay nhất về quyền riêng tư nêu trên.
  • ...bảo đảm quyền riêng tư và bảo vệ người dùng bằng cách xử lý cẩn thận dữ liệu cá nhân của họ, từ đó nâng cao trải nghiệm người dùng.

Những gì bạn cần

Sẽ hữu ích hơn rất nhiều nếu bạn

2. Tại sao quyền riêng tư lại quan trọng?

Nghiên cứu cho thấy mọi người đều ý thức về quyền riêng tư của mình. Một khảo sát do Viện Nghiên cứu Pew thực hiện cho thấy 84% người Mỹ cảm thấy họ có ít khả năng hoặc không kiểm soát được dữ liệu mà các công ty và ứng dụng thu thập. Điều khiến họ thất vọng chủ yếu là do họ không biết dữ liệu của mình sẽ ra sao ngoài việc được sử dụng trực tiếp. Ví dụ: họ lo lắng rằng dữ liệu bị sử dụng cho các mục đích khác, chẳng hạn như tạo hồ sơ cho quảng cáo nhắm mục tiêu hoặc thậm chí là bị bán cho các bên khác. Và một khi dữ liệu bị sử dụng cho những mục đích như vậy thì dường như không có cách nào để xoá dữ liệu đó.

Mối lo ngại về quyền riêng tư này đã ảnh hưởng đáng kể đến việc mọi người quyết định dùng dịch vụ hoặc ứng dụng nào. Trên thực tế, một nghiên cứu tương tự của Viện Nghiên cứu Pew cho thấy hơn một nửa (52%) số người trưởng thành ở Hoa Kỳ quyết định không sử dụng một sản phẩm hoặc dịch vụ là do lo ngại về quyền riêng tư, chẳng hạn như lo lắng về việc sản phẩm hoặc dịch vụ đó thu thập bao nhiêu dữ liệu về họ.

Vì vậy, bạn cần nâng cao và chứng minh quyền riêng tư cho ứng dụng của mình để cải thiện trải nghiệm ứng dụng cho người dùng. Nghiên cứu cho thấy rằng việc này cũng có thể giúp bạn tăng cơ sở người dùng.

Nhiều tính năng và phương pháp hay nhất mà chúng tôi sẽ đề cập trong lớp học lập trình này đều liên quan trực tiếp đến việc giảm lượng dữ liệu mà ứng dụng của bạn truy cập hoặc tăng cường khả năng kiểm soát của người dùng đối với dữ liệu cá nhân của họ. Cả hai điểm cải tiến này đều trực tiếp giải quyết những mối lo ngại mà người dùng đã chia sẻ trong nghiên cứu chúng tôi thấy trước đó.

3. Thiết lập môi trường

Để bạn có thể bắt đầu nhanh chóng, chúng tôi đã chuẩn bị một dự án khởi đầu để bạn tiếp tục xây dựng ứng dụng từ đó. Ở bước này, bạn sẽ tải mã xuống cho toàn bộ lớp học lập trình, bao gồm cả dự án khởi đầu, sau đó chạy ứng dụng khởi đầu trên trình mô phỏng hoặc thiết bị của bạn.

Nếu đã cài đặt git, bạn có thể chỉ cần chạy lệnh bên dưới. Để kiểm tra xem git đã được cài đặt hay chưa, hãy nhập git –version vào dòng lệnh hoặc cửa sổ dòng lệnh và xác minh rằng mã này được thực thi đúng cách.

git clone https://github.com/android/privacy-codelab

Nếu chưa cài đặt git, bạn có thể nhấp vào đường liên kết để tải xuống tất cả mã dành cho lớp học lập trình này.

Cách thiết lập lớp học lập trình:

  1. Mở dự án ở thư mục PhotoLog_Start trong Android Studio.
  2. Chạy cấu hình chạy PhotoLog_Start trên một thiết bị hoặc trình mô phỏng chạy Android 12 (S) trở lên.

d98ce953b749b2be.png

Bạn sẽ thấy một màn hình yêu cầu bạn cấp quyền chạy ứng dụng! Điều này có nghĩa là bạn đã thiết lập thành công môi trường.

4. Phương pháp hay nhất: Yêu cầu cấp quyền theo ngữ cảnh

Nhiều người trong số các bạn biết rằng quyền khi bắt đầu chạy là "chìa khoá" để mở nhiều chức năng chính quan trọng giúp mang lại trải nghiệm tuyệt vời cho người dùng. Tuy nhiên, bạn có biết rằng thời điểm và cách thức ứng dụng yêu cầu cấp quyền cũng tác động đáng kể đến trải nghiệm người dùng?

Hãy xem cách ứng dụng PhotoLog_Start yêu cầu cấp quyền để biết lý do ứng dụng này không có mô hình quản lý quyền tối ưu:

  1. Ngay sau khi chạy ứng dụng, người dùng sẽ lập tức nhận được thông báo nhắc cấp nhiều quyền. Điều này có thể khiến người dùng cảm thấy lúng túng và mất niềm tin vào ứng dụng của chúng ta hoặc trong trường hợp xấu nhất là gỡ cài đặt ứng dụng!
  2. Ứng dụng này không cho phép người dùng tiếp tục thao tác cho đến khi người dùng cấp hết các quyền. Người dùng có thể không đủ tin tưởng vào ứng dụng của chúng ta nếu phải cấp quyền truy cập vào tất cả những thông tin nhạy cảm này khi chạy ứng dụng.

Như bạn có thể đoán, danh sách ở trên cho thấy tập hợp các điểm cải tiến mà chúng ta sẽ cùng nhau thực hiện để cải thiện quy trình yêu cầu cấp quyền của ứng dụng! Hãy cùng bắt đầu nhé.

Như chúng ta có thể thấy, các phương pháp hay nhất mà Android đề xuất cho biết rằng chúng ta nên yêu cầu cấp quyền theo ngữ cảnh khi người dùng mới sử dụng một tính năng lần đầu. Vì nếu một ứng dụng yêu cầu cấp quyền để bật một tính năng mà người dùng đã tương tác, thì yêu cầu đó không khiến người dùng cảm thấy bất ngờ. Nhờ vậy, người dùng có trải nghiệm tốt hơn. Trong ứng dụng PhotoLog, chúng ta nên đợi cho đến khi người dùng nhấp vào các nút camera hoặc vị trí lần đầu rồi mới yêu cầu cấp quyền.

Đầu tiên, hãy xoá màn hình yêu cầu cấp quyền buộc người dùng phải chấp thuận tất cả các quyền trước khi chuyển đến trang chủ. Logic này hiện được định nghĩa trong MainActivity.kt, vì vậy, hãy chuyển đến đó:

val startNavigation =
   if (permissionManager.hasAllPermissions) {
       Screens.Home.route
   } else {
       Screens.Permissions.route
   }

Mã này kiểm tra xem người dùng đã cấp tất cả các quyền hay chưa trước khi cho phép họ chuyển đến trang chủ. Như đã đề cập trước đó, điều này không tuân theo các phương pháp hay nhất của chúng tôi về trải nghiệm người dùng. Hãy thay đổi mã này thành mã dưới đây để cho phép người dùng thao tác trên ứng dụng của chúng ta mà không cần cấp tất cả các quyền:

val startNavigation = Screens.Home.route

Bây giờ, chúng ta không cần màn hình yêu cầu cấp quyền nữa, nên cũng có thể xoá dòng bên dưới khỏi NavHost:

composable(Screens.Permissions.route) { PermissionScreen(navController) }

Tiếp theo, hãy xoá dòng bên dưới khỏi lớp Screens (Màn hình):

object Permissions : Screens("permissions")

Cuối cùng, chúng ta cũng có thể xoá tệp PermissionsScreen.kt.

Bây giờ, hãy xoá và cài đặt lại ứng dụng. Đây là một cách để đặt lại các quyền đã cấp trước đó! Ngay lúc này, bạn có thể chuyển đến màn hình chính. Tuy nhiên, khi bạn nhấn các nút camera hoặc vị trí trên màn hình "Add Log" (Thêm nhật ký) thì sẽ không có gì xảy ra vì ứng dụng không còn có logic để yêu cầu người dùng cấp quyền nữa. Hãy khắc phục vấn đề đó.

Thêm logic để yêu cầu quyền truy cập vào camera

Chúng ta sẽ bắt đầu với quyền truy cập vào camera. Dựa trên mã mẫu mà chúng ta thấy trong tài liệu về yêu cầu cấp quyền, chúng ta sẽ cần đăng ký lệnh gọi lại quyền trước để sử dụng hợp đồng RequestPermission().

Hãy đánh giá logic mà chúng ta cần:

  • Nếu người dùng đồng ý cấp quyền, chúng ta nên đăng ký quyền với viewModel, đồng thời chuyển đến màn hình camera nếu người dùng chưa đạt đến giới hạn về số lượng ảnh đã thêm.
  • Nếu người dùng từ chối cấp quyền, chúng ta có thể thông báo cho họ rằng tính năng này không hoạt động vì họ đã từ chối cấp quyền.

Để thực thi logic này, chúng ta có thể thêm khối mã dưới đây vào: // TODO: Step 1. Register ActivityResult to request Camera permission

val requestCameraPermission =
   rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
       if (isGranted) {
           viewModel.onPermissionChange(CAMERA, isGranted)
           canAddPhoto {
               navController.navigate(Screens.Camera.route)
           }
       }
       else {
           coroutineScope.launch {
               snackbarHostState.showSnackbar("Camera currently disabled due to denied permission.")
           }
       }
   }

Bây giờ, chúng ta cần xác minh rằng ứng dụng có quyền truy cập vào camera trước khi chuyển đến màn hình camera, cũng như cần yêu cầu cấp quyền đó nếu người dùng chưa cấp. Để triển khai logic này, chúng ta có thể thêm khối mã dưới đây vào: // TODO: Step 2. Check & request for Camera permission before navigating to the camera screen

canAddPhoto {
   when {
       state.hasCameraAccess -> navController.navigate(Screens.Camera.route)
       // TODO: Step 4. Trigger rationale screen for Camera if needed
       else -> requestCameraPermission.launch(CAMERA)
   }
}

Bây giờ, hãy thử chạy lại ứng dụng rồi nhấp vào biểu tượng camera trên màn hình "Add Log" (Thêm nhật ký). Bạn sẽ thấy một hộp thoại yêu cầu cấp quyền truy cập vào camera. Xin chúc mừng! Cách này tốt hơn nhiều so với việc yêu cầu người dùng phê duyệt tất cả các quyền trước khi họ dùng thử ứng dụng phải không?

Tuy nhiên, chúng ta có thể làm tốt hơn không? Có! Chúng ta có thể kiểm tra xem hệ thống có khuyến nghị chúng ta giải thích lý do ứng dụng cần quyền truy cập vào camera hay không. Điều này có thể giúp tăng tỷ lệ chọn cho phép đối với quyền này, đồng thời đảm bảo ứng dụng vẫn có khả năng yêu cầu cấp quyền lại vào thời điểm thích hợp hơn.

Để làm được như vậy, hãy tạo một màn hình giải thích lý do ứng dụng của chúng ta cần truy cập vào camera của người dùng. Bạn có thể thực hiện việc này bằng cách thêm khối mã dưới đây vào: // TODO: Step 3. Add explanation dialog for Camera permission

var showExplanationDialogForCameraPermission by remember { mutableStateOf(false) }
if (showExplanationDialogForCameraPermission) {
   CameraExplanationDialog(
       onConfirm = {
           requestCameraPermission.launch(CAMERA)
           showExplanationDialogForCameraPermission = false
       },
       onDismiss = { showExplanationDialogForCameraPermission = false },
   )
}

Giờ đây, khi đã có hộp thoại, chúng ta chỉ cần kiểm tra xem có nên giải thích lý do trước khi yêu cầu cấp quyền truy cập vào camera hay không. Chúng ta thực hiện việc này bằng cách gọi API shouldShowRequestPermissionRationale() của ActivityCompat. Nếu API này trả về giá trị true, chúng ta chỉ cần đặt showExplanationDialogForCameraPermission thành true để hiển thị hộp thoại giải thích.

Hãy thêm khối mã dưới đây vào giữa trường hợp state.hasCameraAccess và trường hợp else hoặc ở nơi TODO sau đây đã được thêm vào trước đó trong hướng dẫn: // TODO: Step 4. Add explanation dialog for Camera permission

ActivityCompat.shouldShowRequestPermissionRationale(context.getActivity(),
           CAMERA) -> showExplanationDialogForCameraPermission = true

Logic hoàn chỉnh cho nút camera giờ đây sẽ có dạng như sau:

canAddPhoto {
   when {
       state.hasCameraAccess -> navController.navigate(Screens.Camera.route)
       ActivityCompat.shouldShowRequestPermissionRationale(context.getActivity(),
           CAMERA) -> showExplanationDialogForCameraPermission = true
       else -> requestCameraPermission.launch(CAMERA)
   }
}

Xin chúc mừng! Chúng ta đã xử lý xong quyền truy cập vào camera mà vẫn tuân theo tất cả các phương pháp hay nhất của Android! Hãy tiếp tục, xoá và cài đặt lại ứng dụng một lần nữa, rồi thử nhấn nút camera trên trang "Add Log" (Thêm nhật ký). Nếu bạn từ chối cấp quyền thì ứng dụng sẽ không cho phép bạn sử dụng các chức năng khác như mở album ảnh.

Tuy nhiên, vào lần tới khi nhấp vào biểu tượng camera sau khi từ chối cấp quyền, bạn sẽ thấy thông báo giải thích mà chúng ta vừa thêm!* Lưu ý rằng thông báo nhắc cấp quyền hệ thống chỉ xuất hiện sau khi người dùng nhấp vào "tiếp tục" trên thông báo giải thích. Nếu người dùng nhấp vào "để sau" thì chúng ta sẽ để họ dùng ứng dụng mà không có gián đoạn nào khác. Như vậy, ứng dụng có thể tránh bị người dùng từ chối cấp quyền khác và vẫn có khả năng yêu cầu cấp quyền lại vào một thời điểm khác, khi người dùng có thể sẵn sàng cấp quyền hơn.

  • Lưu ý: hành vi chính xác của API shouldShowRequestPermissionRationale() là chi tiết triển khai nội bộ và có thể thay đổi.

Thêm logic để yêu cầu quyền truy cập thông tin vị trí

Bây giờ, hãy thao tác tương tự đối với vị trí. Trước tiên, chúng ta có thể đăng ký ActivityResult cho quyền truy cập thông tin vị trí bằng cách thêm khối mã dưới đây vào: // TODO: Step 5. Register ActivityResult to request Location permissions

val requestLocationPermissions =
   rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
       if (isGranted) {
           viewModel.onPermissionChange(ACCESS_COARSE_LOCATION, isGranted)
           viewModel.onPermissionChange(ACCESS_FINE_LOCATION, isGranted)
           viewModel.fetchLocation()
       }
       else {
           coroutineScope.launch {
               snackbarHostState.showSnackbar("Location currently disabled due to denied permission.")
           }
       }
   }

Sau đó, chúng ta có thể tiếp tục và thêm hộp thoại giải thích về quyền truy cập thông tin vị trí bằng cách thêm khối mã dưới đây vào: // TODO: Step 6. Add explanation dialog for Location permissions

var showExplanationDialogForLocationPermission by remember { mutableStateOf(false) }
if (showExplanationDialogForLocationPermission) {
   LocationExplanationDialog(
       onConfirm = {
           // TODO: Step 10. Change location request to only request COARSE location.
           requestLocationPermissions.launch(arrayOf(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION))
           showExplanationDialogForLocationPermission = false
       },
       onDismiss = { showExplanationDialogForLocationPermission = false },
   )
}

Tiếp theo, hãy tiếp tục và kiểm tra, giải thích (nếu cần) và yêu cầu cấp quyền truy cập thông tin vị trí. Nếu được cấp quyền, chúng ta có thể tìm nạp thông tin vị trí và điền vào nhật ký ảnh. Hãy tiếp tục và thêm khối mã dưới đây vào: // TODO: Step 7. Check, request, and explain Location permissions

when {
   state.hasLocationAccess -> viewModel.fetchLocation()
   ActivityCompat.shouldShowRequestPermissionRationale(context.getActivity(),
       ACCESS_COARSE_LOCATION) ||
   ActivityCompat.shouldShowRequestPermissionRationale(
       context.getActivity(), ACCESS_FINE_LOCATION) ->
       showExplanationDialogForLocationPermission = true
   else -> requestLocationPermissions.launch(arrayOf(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION))
}

Vậy là bạn đã hoàn tất. Chúng ta đã hoàn thành phần về quyền truy cập của lớp học lập trình này! Hãy tiếp tục và thử đặt lại ứng dụng của bạn rồi xem kết quả.

Dưới đây là phần tóm tắt về cách chúng ta cải thiện trải nghiệm người dùng và những lợi ích mà ứng dụng của bạn mang lại:

  • Yêu cầu cấp quyền theo ngữ cảnh (khi người dùng đang sử dụng tính năng) thay vì ngay sau khi khởi chạy ứng dụng → Hạn chế nhầm lẫn và giảm tỷ lệ người dùng bỏ cuộc.
  • Tạo màn hình giải thích để cho người dùng biết lý do ứng dụng của chúng ta cần có quyền truy cập → Tăng cường tính minh bạch cho người dùng.
  • Sử dụng API shouldShowRequestPermissionRationale() để xác định thời điểm hệ thống cho rằng ứng dụng của bạn cần hiển thị màn hình giải thích → Tăng tỷ lệ đồng ý cấp quyền và giảm tỷ lệ từ chối cấp quyền vĩnh viễn.

5. Phương pháp hay nhất: Hạn chế quyền truy cập thông tin vị trí của ứng dụng

Thông tin vị trí là một trong những quyền truy cập thông tin nhạy cảm nhất và đó là lý do Android có đề cập đến quyền này trong Bảng tổng quan về quyền riêng tư.

Tóm lại, trong Android 12, chúng ta đã cung cấp thêm cho người dùng các quyền kiểm soát đối với thông tin vị trí. Giờ đây, người dùng có lựa chọn rõ ràng để chia sẻ dữ liệu vị trí ít chính xác hơn cho các ứng dụng bằng cách chọn vị trí ước chừng thay vì vị trí chính xác khi các ứng dụng yêu cầu cấp quyền truy cập thông tin vị trí.

Vị trí ước chừng cung cấp cho ứng dụng thông tin ước tính về vị trí của người dùng trong vòng 3 kilômét vuông. Thông tin này đủ độ chính xác cho nhiều tính năng của ứng dụng. Tất cả các nhà phát triển có ứng dụng cần quyền truy cập thông tin vị trí nên xem xét trường hợp sử dụng và chỉ yêu cầu ACCESS_FINE_LOCATION nếu người dùng chủ động sử dụng một tính năng cần có thông vị trí chính xác của họ.

ea5cc51fce3f219e.png

Hình ảnh trực quan hoá phạm vi ước tính vị trí tương đối giữa thành phố Los Angeles, California.

Quyền truy cập thông tin vị trí ước chừng chắc chắn là đủ cho ứng dụng PhotoLog của chúng ta, vì chúng ta chỉ cần thông tin thành phố của người dùng để nhắc họ về nơi diễn ra "kỷ niệm". Tuy nhiên, ứng dụng này đang yêu cầu cả ACCESS_COARSE_LOCATIONACCESS_FINE_LOCATION của người dùng. Hãy thay đổi điều đó.

Trước tiên, chúng ta cần chỉnh sửa kết quả hoạt động cho vị trí và cung cấp hàm ActivityResultContracts.RequestPermission() dưới dạng tham số thay vì ActivityResultContracts.RequestMultiplePermissions(), để phản ánh thực tế là chúng ta sẽ chỉ yêu cầu ACCESS_COARSE_LOCATION.

Hãy thay thế đối tượng requestLocationsPermissions hiện tại (được biểu thị bằng // TODO: Step 8. Change activity result to only request Coarse Location) bằng khối mã dưới đây:

val requestLocationPermissions =
   rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
       if (isGranted) {
           viewModel.onPermissionChange(ACCESS_COARSE_LOCATION, isGranted)
       }
   }

Tiếp theo, chúng ta sẽ thay đổi các phương thức launch() để chỉ yêu cầu ACCESS_COARSE_LOCATION chứ không yêu cầu cả hai quyền truy cập thông tin vị trí.

Hãy thay thế:

requestLocationPermissions.launch(arrayOf(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION))

...bằng:

requestLocationPermissions.launch(ACCESS_COARSE_LOCATION)

Chúng ta cần thay đổi 2 thực thể của phương thức launch() trong PhotoLog. Một phương thức nằm trong logic onConfirm() thuộc LocationExplanationDialog được biểu thị bằng // TODO: Step 9. Change location request to only request COARSE location và một phương thức nằm trong mục danh sách "Vị trí" được biểu thị bằng // TODO: Step 10. Change location request to only request COARSE location

Cuối cùng, vì chúng ta không còn yêu cầu cấp quyền ACCESS_FINE_LOCATION cho PhotoLog nữa, nên hãy tiếp tục và xoá phần này khỏi phương thức onPermissionChange() trong AddLogViewModel.kt:

Manifest.permission.ACCESS_FINE_LOCATION -> {
   uiState = uiState.copy(hasLocationAccess = isGranted)
}

Và cũng đừng quên xoá ACCESS_FINE_LOCATION khỏi Tệp kê khai của ứng dụng:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Giờ đây, chúng ta đã hoàn tất phần về thông tin vị trí của lớp học lập trình này! Hãy tiếp tục và gỡ cài đặt/cài đặt lại ứng dụng của bạn rồi xem kết quả!

6. Phương pháp hay nhất: Hạn chế quyền truy cập vào bộ nhớ

Thông thường, các ứng dụng sẽ dùng ảnh được lưu trữ trên một thiết bị. Để cho phép người dùng chọn hình ảnh và video mà họ muốn, những ứng dụng này thường triển khai bộ chọn tệp riêng. Các bộ chọn này yêu cầu ứng dụng phải có quyền truy cập vào bộ nhớ chung. Người dùng không muốn cấp quyền truy cập vào tất cả ảnh của họ còn nhà phát triển không muốn duy trì bộ chọn tệp độc lập.

Android 13 ra mắt công cụ chọn ảnh: một công cụ giúp người dùng chọn tệp nội dung nghe nhìn mà không cần phải cấp cho ứng dụng quyền truy cập vào toàn bộ thư viện nội dung nghe nhìn của người dùng. Phiên bản này cũng được điều chỉnh cho phiên bản cũ là Android 11 và 12 với sự trợ giúp của Bản cập nhật hệ thống Google Play.

Đối với tính năng trong ứng dụng PhotoLog, chúng ta sẽ sử dụng PickMultipleVisualMedia ActivityResultContract. Tính năng này sẽ sử dụng Công cụ chọn ảnh của Android khi có trên thiết bị và dựa vào ý định ACTION_OPEN_DOCUMENT trên các thiết bị cũ.

Trước tiên, hãy đăng ký ActivityResultContract trong tệp AddLogScreen. Để thực hiện việc này, hãy thêm khối mã dưới đây sau dòng: // TODO: Step 11. Register ActivityResult to launch the Photo Picker

val pickImage = rememberLauncherForActivityResult(
   PickMultipleVisualMedia(MAX_LOG_PHOTOS_LIMIT),
    viewModel::onPhotoPickerSelect
)

Lưu ý: MAX_LOG_PHOTOS_LIMIT ở đây biểu thị giới hạn tối đa về số lượng ảnh mà chúng ta chọn để đặt khi thêm vào nhật ký (trong trường hợp này là 3).

Bây giờ, chúng ta cần thay thế bộ chọn bên trong ứng dụng bằng Công cụ chọn ảnh của Android. Hãy thêm mã dưới đây sau khối: // TODO: Step 12. Replace the below line showing our internal UI by launching the Android Photo Picker instead

pickImage.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly))

Sau khi thêm 2 dòng mã này, chúng ta hiện có thể truy cập vào ảnh trên thiết bị mà không cần có quyền truy cập. Việc này mang lại trải nghiệm người dùng tốt hơn nhiều và không yêu cầu duy trì mã!

Vì PhotoLog có thể truy cập vào ảnh mà không còn cần quyền truy cập vào lưới ảnh cũ và bộ nhớ nữa nên giờ đây, chúng ta phải xoá toàn bộ mã chứa lưới ảnh cũ khỏi mục quyền truy cập vào bộ nhớ trong tệp kê khai cho logic phía sau. Ứng dụng của chúng ta không cần mã này nữa.

7. Đề xuất: Sử dụng API kiểm tra quyền truy cập dữ liệu trong bản gỡ lỗi

Bạn có một ứng dụng lớn với nhiều tính năng và cộng tác viên (hoặc bạn mong đợi nó trong tương lai!) khiến bạn khó theo dõi loại dữ liệu người dùng mà ứng dụng đang truy cập? Bạn có biết rằng ngay cả khi những lượt truy cập dữ liệu đến từ API hoặc SDK đã được sử dụng tại một thời điểm nhưng hiện vẫn tồn tại trong ứng dụng của bạn, thì ứng dụng này vẫn sẽ chịu trách nhiệm cho việc truy cập dữ liệu đó không?

Chúng tôi hiểu rằng khó có thể theo dõi tất cả các địa điểm mà ứng dụng của bạn đang truy cập vào dữ liệu cá nhân, bao gồm tất cả SDK được đưa vào và các phần phụ thuộc khác. Do đó, để giúp bạn hiểu rõ hơn về cách ứng dụng và các phần phụ thuộc của ứng dụng truy cập vào dữ liệu cá nhân của người dùng, Android 11 ra mắt tính năng kiểm tra quyền truy cập dữ liệu. API này cho phép nhà phát triển thực hiện các thao tác cụ thể (chẳng hạn như in ra tệp nhật ký) mỗi khi xảy ra một trong các sự kiện sau đây:

  • Mã của ứng dụng truy cập vào dữ liệu cá nhân.
  • Mã trong thư viện phần phụ thuộc hoặc SDK truy cập vào dữ liệu cá nhân.

Trước tiên, hãy cùng tìm hiểu các thông tin cơ bản về cách thức hoạt động của API kiểm tra quyền truy cập dữ liệu trên Android. Để sử dụng tính năng kiểm tra quyền truy cập dữ liệu, chúng ta sẽ đăng ký một thực thể của AppOpsManager.OnOpNotedCallback (yêu cầu nhắm đến Android 11 trở lên).

Chúng ta cũng cần ghi đè 3 phương thức trong lệnh gọi lại. Hệ thống sẽ gọi lệnh này khi ứng dụng truy cập vào dữ liệu người dùng theo nhiều cách. Đó là:

  • onNoted() – được gọi khi ứng dụng gọi đồng bộ (liên kết hai chiều) API truy cập vào dữ liệu người dùng. Đây thường là những lệnh gọi API không yêu cầu gọi lại.
  • onAsyncNoted() – được gọi khi ứng dụng gọi không đồng bộ (liên kết một chiều) API truy cập vào dữ liệu người dùng. Đây thường là những lệnh gọi API yêu cầu gọi lại và việc truy cập dữ liệu diễn ra khi lệnh gọi lại được thực hiện.
  • onSelfNoted()rất hiếm khi xảy ra khi một ứng dụng truyền mã nhận dạng riêng biệt (UID) của ứng dụng đó vào noteOp() chẳng hạn.

Bây giờ, hãy xác định phương thức nào trong số này áp dụng cho quyền truy cập dữ liệu của ứng dụng PhotoLog. PhotoLog chủ yếu truy cập vào dữ liệu người dùng ở 2 nơi, một lần khi chúng ta kích hoạt camera và một lần khác khi chúng ta truy cập vào thông tin vị trí của người dùng. Cả hai lệnh gọi này đều là lệnh gọi API không đồng bộ vì cả hai đều hoạt động tương đối tốn nhiều tài nguyên. Do đó, chúng ta muốn hệ thống sẽ gọi onAsyncNoted() khi chúng ta truy cập dữ liệu người dùng tương ứng.

Hãy cùng tìm hiểu cách sử dụng các API kiểm tra quyền truy cập dữ liệu cho PhotoLog!

Trước tiên, chúng ta cần tạo một thực thể của ​​AppOpsManager.OnOpNotedCallback() và ghi đè 3 phương thức ở trên.

Đối với cả ba phương thức trong đối tượng, hãy tiếp tục và ghi lại thao tác cụ thể khi truy cập vào dữ liệu cá nhân của người dùng. Thao tác này sẽ chứa thêm thông tin về loại dữ liệu người dùng đã được truy cập. Ngoài ra, vì chúng ta muốn onAsyncNoted() sẽ được gọi khi ứng dụng truy cập vào thông tin vị trí và camera, hãy làm điều gì đó đặc biệt và ghi lại biểu tượng cảm xúc của quyền truy cập thông tin vị trí cũng như biểu tượng cảm xúc của quyền truy cập camera trên bản đồ. Để thực hiện việc này, chúng ta có thể thêm khối mã dưới đây vào: // TODO: Step 1. Create Data Access Audit Listener Object

@RequiresApi(Build.VERSION_CODES.R)
object DataAccessAuditListener : AppOpsManager.OnOpNotedCallback() {
   // For the purposes of this codelab, we are just logging to console,
   // but you can also integrate other logging and reporting systems here to track
   // your app's private data access.
   override fun onNoted(op: SyncNotedAppOp) {
       Log.d("DataAccessAuditListener","Sync Private Data Accessed: ${op.op}")
   }

   override fun onSelfNoted(op: SyncNotedAppOp) {
       Log.d("DataAccessAuditListener","Self Private Data accessed: ${op.op}")
   }

   override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {
       var emoji = when (asyncNotedAppOp.op) {
           OPSTR_COARSE_LOCATION -> "\uD83D\uDDFA"
           OPSTR_CAMERA -> "\uD83D\uDCF8"
           else -> "?"
       }

       Log.d("DataAccessAuditListener", "Async Private Data ($emoji) Accessed:
       ${asyncNotedAppOp.op}")
   }
}

Sau đó, chúng ta sẽ cần triển khai logic gọi lại vừa tạo. Để có kết quả tốt nhất, chúng ta cần triển khai càng sớm càng tốt vì hệ thống sẽ chỉ bắt đầu theo dõi quyền truy cập dữ liệu sau khi chúng ta đăng ký lệnh gọi lại. Để đăng ký lệnh gọi lại, chúng ta có thể thêm khối mã dưới đây vào: // TODO: Step 2. Register Data Access Audit Callback.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
   val appOpsManager = getSystemService(AppOpsManager::class.java) as AppOpsManager
   appOpsManager.setOnOpNotedCallback(mainExecutor, DataAccessAuditListener)
}

8. Kết thúc

Cùng tóm tắt những nội dung chúng ta đã đề cập! Chúng ta...

  • ...đã tìm hiểu lý do khiến quyền riêng tư lại quan trọng đối với ứng dụng.
  • ...được giới thiệu các tính năng bảo mật của Android.
  • ...triển khai nhiều phương pháp chính hay nhất về quyền riêng tư cho ứng dụng bằng cách:
  • Yêu cầu cấp quyền theo ngữ cảnh
  • Hạn chế quyền truy cập thông tin vị trí của ứng dụng
  • Sử dụng công cụ chọn ảnh và các điểm cải tiến khác về bộ nhớ
  • Sử dụng các API kiểm tra quyền truy cập dữ liệu
  • ...đã triển khai những phương pháp hay nhất này trong ứng dụng hiện có để nâng cao khả năng bảo vệ quyền riêng tư cho ứng dụng.

Chúng tôi hy vọng bạn hài lòng với hành trình này. Chúng ta đã cải thiện được trải nghiệm người dùng và quyền riêng tư của PhotoLog, đồng thời học được nhiều khái niệm trong suốt hành trình!

Cách tìm mã tham chiếu (không bắt buộc):

Bạn có thể xem mã giải pháp cho lớp học lập trình này trong thư mục PhotoLog_End (nếu chưa có). Nếu bạn đã làm theo hướng dẫn của lớp học lập trình này, thì mã trong thư mục PhotoLog_Start sẽ giống với mã trong thư mục PhotoLog_End.

Tìm hiểu thêm

Vậy là xong! Để tìm hiểu thêm về các phương pháp hay nhất mà chúng tôi đã đề cập ở trên, hãy xem Trang đích về quyền riêng tư trên Android.