Atelier de programmation sur la confidentialité sur Android  |  Android Developers (original) (raw)

1. Introduction

Points abordés

Objectifs de l'atelier

Dans cet atelier de programmation, vous allez commencer avec un exemple d'application permettant aux utilisateurs d'enregistrer leurs souvenirs photo.

L'atelier débute avec les écrans suivants :

L'application fonctionne, mais elle comporte de nombreuses erreurs liées à la confidentialité que nous allons améliorer ensemble.

À mesure que vous progresserez dans l'atelier de programmation, vous allez découvrir ce qui suit :

Lorsque vous aurez terminé, vous disposerez d'une application qui :

Ce dont vous avez besoin

Souhaitable

2. Pourquoi la confidentialité est-elle importante ?

Des études montrent que les utilisateurs se soucient de leur vie privée. Une enquête menée par le Pew Research Institute a révélé que 84 % des Américains estiment qu'ils ont peu ou pas de contrôle sur les données collectées par les entreprises et les applications. Leur principal sujet d'inquiétude est de ne pas savoir ce qu'il advient de leurs données au-delà de leur utilisation directe. Par exemple, ils craignent que ces données ne soient utilisées à d'autres fins, par exemple afin de créer des profils pour la publicité ciblée, ou même qu'elles soient vendues à d'autres parties. Et une fois les données dans la nature, il semble n'y avoir aucun moyen de les supprimer.

Ce problème de confidentialité affecte déjà de manière significative les décisions des utilisateurs concernant les services ou applications qu'ils utilisent. D'ailleurs, cette même étude du Pew Research Institute a révélé que plus de la moitié des adultes américains (52 %) ont décidé de ne pas utiliser un produit ou service pour des raisons de confidentialité. Par exemple, ils sont inquiets de voir autant de données les concernant être collectées.

Par conséquent, il est essentiel d'améliorer et de démontrer la confidentialité de votre application pour améliorer l'expérience de vos utilisateurs. Des études montrent que cela peut également vous aider à développer votre base d'utilisateurs.

Un grand nombre des fonctionnalités et des bonnes pratiques que nous allons étudier dans cet atelier de programmation sont directement liées à la réduction de la quantité de données auxquelles votre application accède ou à l'amélioration du sentiment de contrôle des utilisateurs sur leurs données privées. Ces deux améliorations répondent directement aux préoccupations des utilisateurs lors des études que nous avons vues précédemment.

3. Configurer votre environnement

Pour vous aider à démarrer le plus rapidement possible, nous avons préparé un projet de démarrage. Au cours de cette étape, vous allez télécharger le code de l'intégralité de l'atelier de programmation, y compris le projet de démarrage, puis exécuter l'application de démarrage sur votre émulateur ou votre appareil.

Si git est installé, vous pouvez simplement exécuter la commande ci-dessous. Pour vérifier si git est installé, saisissez git –version dans le terminal ou la ligne de commande, et vérifiez qu'il s'exécute correctement.

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

Si vous n'avez pas git, cliquez sur le lien ci-dessous pour télécharger l'ensemble du code de cet atelier de programmation :

Pour configurer l'atelier de programmation :

  1. Ouvrez le projet dans le répertoire PhotoLog_Start d'Android Studio.
  2. Exécutez la configuration d'exécution PhotoLog_Start sur un appareil ou un émulateur exécutant Android 12 (S) ou une version ultérieure.

d98ce953b749b2be.png

Vous devriez voir un écran vous demandant d'accorder des autorisations pour exécuter l'application. Cela signifie que vous avez correctement configuré l'environnement.

4. Bonne pratique : demandez des autorisations en contexte

Beaucoup d'entre vous savent que les autorisations d'exécution sont essentielles pour déverrouiller de nombreuses fonctionnalités clés qui sont nécessaires pour une expérience utilisateur de qualité. Mais saviez-vous que le moment et la façon dont votre application demande ces autorisations peut impacter l'expérience utilisateur ?

Voyons comment l'application PhotoLog_Start demande les autorisations, pour vous montrer pourquoi elle ne dispose pas d'un modèle d'autorisations optimal :

  1. Juste après le lancement, l'utilisateur reçoit immédiatement des invites lui demandant d'accorder plusieurs autorisations. Cela peut le dérouter et lui faire perdre sa confiance dans notre application, voire l'inciter à la désinstaller dans le pire des cas.
  2. L'application ne permet pas à l'utilisateur de continuer tant que toutes les autorisations ne sont pas accordées. Le problème, c'est que l'utilisateur ne fait peut-être pas encore suffisamment confiance à notre application dès son lancement pour accorder l'accès à toutes ces informations sensibles.

Comme vous l'avez probablement deviné, la liste ci-dessus représente les améliorations que nous allons apporter ensemble pour optimiser le processus de demande d'autorisations de l'application. Entrons à présent dans le vif du sujet.

Les bonnes pratiques recommandées par Android indiquent qu'il faut demander des autorisations en contexte la première fois que les utilisateurs interagissent avec une fonctionnalité. En effet, si une application demande l'autorisation d'activer une fonctionnalité avec laquelle l'utilisateur interagit déjà, cette demande n'est pas surprenante. Cela améliore l'expérience utilisateur. Dans l'application PhotoLog, nous devons attendre la première fois que les utilisateurs cliquent sur l'appareil photo ou sur les boutons de localisation avant de demander des autorisations.

Commençons par supprimer l'écran des autorisations qui oblige les utilisateurs à approuver toutes les autorisations avant d'accéder à la page d'accueil. Cette logique est actuellement définie dans MainActivity.kt. Voyons donc comment procéder :

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

L'application vérifie si l'utilisateur a accordé toutes les autorisations avant de lui permettre de passer à la page d'accueil. Comme indiqué précédemment, cette pratique ne respecte pas nos bonnes pratiques en matière d'expérience utilisateur. Implémentons à présent le code suivant, qui permet aux utilisateurs d'interagir avec notre application sans accorder toutes les autorisations :

val startNavigation = Screens.Home.route

Comme nous n'avons plus besoin de l'écran des autorisations, nous pouvons également supprimer cette ligne de NavHost :

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

Supprimez ensuite cette ligne de la classe Screens :

object Permissions : Screens("permissions")

Enfin, nous pouvons également supprimer le fichier PermissionsScreen.kt.

Maintenant, supprimez et réinstallez votre application, ce qui permet de réinitialiser les autorisations précédemment accordées. Vous devriez être en mesure d'accéder immédiatement à l'écran d'accueil, mais lorsque vous appuyez sur les boutons de la caméra ou de localisation sur l'écran d'ajout de journal, rien ne se passe, car l'application ne dispose plus de la logique pour demander des autorisations à l'utilisateur. Nous allons résoudre ce problème.

Ajouter une logique pour demander l'autorisation d'accéder à l'appareil photo

Commençons par l'autorisation d'accès à l'appareil photo. En nous basant sur les exemples de code figurant dans la documentation sur les demandes d'autorisation, nous devons d'abord enregistrer le rappel d'autorisations pour utiliser le contrat RequestPermission().

Évaluons la logique dont nous avons besoin :

Pour exécuter cette logique, nous pouvons ajouter ce bloc de code à : // 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.") } } }

Nous devons maintenant vérifier que l'application dispose de l'autorisation d'accès à l'appareil photo avant d'accéder à l'écran de l'appareil photo, puis demander cette autorisation si l'utilisateur ne l'a pas encore accordée. Pour implémenter cette logique, nous pouvons ajouter le bloc de code suivant à : // 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) } }

Essayez à nouveau d'exécuter l'application, puis cliquez sur l'icône en forme de caméra de l'écran d'ajout de journal. Une boîte de dialogue doit s'afficher pour demander l'autorisation d'accès à l'appareil photo. Félicitations ! C'est bien mieux que de demander aux utilisateurs d'approuver toutes les autorisations avant même d'avoir essayé l'application, n'est-ce pas ?

Mais peut-on faire mieux ? Oui ! Nous pouvons vérifier si le système recommande une justification pour expliquer pourquoi notre application a besoin d'accéder à l'appareil photo. Cela permet d'augmenter potentiellement les taux d'activation pour l'autorisation et de préserver la capacité de vos applications à redemander l'autorisation à un moment plus opportun.

Pour cela, nous allons créer un écran expliquant pourquoi notre application a besoin d'accéder à l'appareil photo de l'utilisateur. Pour ce faire, ajoutez le bloc de code suivant à : // 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 }, ) }

Maintenant que nous disposons de la boîte de dialogue, nous devons vérifier s'il est pertinent d'afficher la justification avant de demander l'autorisation d'accéder à l'appareil photo. Pour ce faire, nous appelons l'API shouldShowRequestPermissionRationale() de ActivityCompat. Si la valeur est "true", nous devons aussi définir showExplanationDialogForCameraPermission sur "true" pour afficher la boîte de dialogue d'explication.

Ajoutons le bloc de code suivant entre le cas state.hasCameraAccess et le cas else, ou à l'endroit où le TODO suivant a été précédemment ajouté dans les instructions : // TODO: Step 4. Add explanation dialog for Camera permission

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

La logique complète du bouton de l'appareil photo devrait maintenant ressembler à :

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

Félicitations ! Nous avons terminé de gérer l'autorisation d'accès à l'appareil photo, conformément à toutes les bonnes pratiques Android. Supprimez l'application, puis réinstallez-la. Essayez d'appuyer sur le bouton de l'appareil photo depuis la page d'ajout de journal. Si vous refusez l'autorisation, l'application ne vous empêche pas d'utiliser les autres fonctionnalités telles que l'ouverture de l'album photo.

Toutefois, la prochaine fois que vous cliquerez sur l'icône en forme de caméra après avoir refusé l'autorisation, vous devriez voir l'invite d'explication que nous venons d'ajouter. Notez que l'invite d'autorisation du système ne s'affiche que lorsque l'utilisateur clique sur "Continuer" dans l'invite d'explication. Si l'utilisateur clique sur "Pas maintenant", nous l'autorisons à utiliser l'application sans interruption supplémentaire. Cela permet à l'application d'éviter les refus supplémentaires de l'utilisateur et de préserver notre capacité à redemander l'autorisation à un autre moment, lorsque l'utilisateur est plus susceptible de l'accorder.

Ajouter une logique pour demander l'autorisation d'accéder à la position

Faisons de même pour la localisation. Nous pouvons commencer par enregistrer ActivityResult pour les autorisations d'accéder à la position en ajoutant le bloc de code suivant à : // 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.") } } }

Nous pouvons ensuite ajouter la boîte de dialogue d'explication sur les autorisations d'accéder à la position en ajoutant le bloc de code suivant à : // 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 }, ) }

Vérifions à présent la procédure, expliquons (si nécessaire) et demandons les autorisations d'accéder à la position. Si l'autorisation est accordée, nous pouvons récupérer le lieu et remplir le journal de photos. Ajoutons à présent le bloc de code suivant à : // 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)) }

La section "Autorisations" de cet atelier de programmation est terminée. Essayez de réinitialiser votre application pour voir les résultats.

Résumé des améliorations que nous avons apportées à l'expérience utilisateur et les avantages pour votre application :

5. Bonne pratique : réduisez l'accès de votre application à la position

La localisation est l'une des autorisations les plus sensibles. C'est pourquoi Android l'inclut dans le Tableau de bord Confidentialité.

Pour résumer, dans Android 12, nous avons fourni aux utilisateurs des commandes de localisation supplémentaires. Les utilisateurs ont désormais la possibilité de partager avec les applications des données de localisation moins précises en sélectionnant position approximative au lieu de position exacte lorsqu'une application demande à accéder à sa position.

La position approximative fournit à l'application une estimation de la position de l'utilisateur dans un rayon de trois kilomètres carrés, ce qui devrait être suffisant pour bon nombre des fonctionnalités de votre application. Nous encourageons tous les développeurs dont les applications ont besoin d'accéder aux données de localisation d'examiner leur cas d'utilisation et de ne demander ACCESS_FINE_LOCATION que si l'utilisateur interagit activement avec une fonctionnalité qui nécessite sa position exacte.

ea5cc51fce3f219e.png

Graphique permettant de visualiser une estimation approximative de la position, centrée sur le centre-ville de Los Angeles, en Californie.

L'accès approximatif à la position suffit pour notre application PhotoLog. En effet, nous n'avons besoin que de la ville de l'utilisateur pour lui rappeler l'origine du "souvenir". Toutefois, l'application demande actuellement à l'utilisateur les autorisations ACCESS_COARSE_LOCATION et ACCESS_FINE_LOCATION. Il est temps de changer cela.

Tout d'abord, nous devons modifier le résultat d'activité pour la position et fournir la fonction ActivityResultContracts.RequestPermission() en tant que paramètre au lieu de ActivityResultContracts.RequestMultiplePermissions(), afin de refléter le fait que nous ne demanderons que ACCESS_COARSE_LOCATION.

Remplaçons l'objet requestLocationsPermissions actuel (indiqué par // TODO: Step 8. Change activity result to only request Coarse Location) par le bloc de code suivant :

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

Ensuite, nous allons modifier les méthodes launch() pour ne demander que ACCESS_COARSE_LOCATION au lieu des deux autorisations d'accéder à la position.

Remplaçons :

requestLocationPermissions.launch(arrayOf(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION))

Avec :

requestLocationPermissions.launch(ACCESS_COARSE_LOCATION)

Deux instances de la méthode launch() dans PhotoLog doivent être modifiées. L'une d'elles se trouve dans la logique onConfirm() dans le LocationExplanationDialog, désignée par // TODO: Step 9. Change location request to only request COARSE location, l'autre se trouve dans l'élément de liste "Location" signalé par // TODO: Step 10. Change location request to only request COARSE location

Enfin, maintenant que nous ne demandons plus l'autorisation ACCESS_FINE_LOCATION pour PhotoLog, nous allons supprimer cette section de la méthode onPermissionChange() dans AddLogViewModel.kt :

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

N'oubliez pas de supprimer également ACCESS_FINE_LOCATION du fichier manifeste de l'application :

La section "Position" de l'atelier de programmation est terminée. Désinstallez, puis réinstallez votre application pour voir les résultats.

6. Bonne pratique : limitez l'utilisation des autorisations de stockage

Il est courant que les applications fonctionnent avec les photos stockées sur un appareil. Pour permettre aux utilisateurs de choisir les images et les vidéos souhaitées, ces applications implémentent souvent leurs propres sélecteurs de fichier, ce qui nécessite de demander l'autorisation d'accéder à un espace de stockage étendu. Les utilisateurs n'aiment pas autoriser l'accès à toutes leurs photos et les développeurs n'aiment pas avoir à maintenir un outil de sélection de fichiers indépendant.

Android 13 introduit le sélecteur de photos, un outil qui permet à l'utilisateur de sélectionner des fichiers multimédias sans qu'il soit nécessaire d'autoriser une application à accéder à l'ensemble de sa bibliothèque multimédia. Elle est également rétroportée vers Android 11 et 12 à l'aide des mises à jour du système Google Play.

Pour cette fonctionnalité dans notre application PhotoLog, nous allons utiliser l'élément ActivityResultContract PickMultipleVisualMedia. Il utilise l'outil de sélection de photos Android lorsqu'il est installé sur l'appareil et s'appuie sur l'intent ACTION_OPEN_DOCUMENT sur les anciens appareils.

Enregistrez d'abord notre ActivityResultContrat dans le fichier AddLogScreen. Pour ce faire, ajoutez le bloc de code suivant après cette ligne : // TODO: Step 11. Register ActivityResult to launch the Photo Picker

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

Remarque : MAX_LOG_PHOTOS_LIMIT représente ici la limite maximale de photos que nous avons choisies pour l'ajout à un journal (ici, 3).

Nous devons maintenant remplacer le sélecteur interne intégré à l'application par l'outil de sélection de photos Android. Ajoutez le code suivant après le bloc : // TODO: Step 12. Replace the below line showing our internal UI by launching the Android Photo Picker instead

pickImage.launch(PickVisualMediaRequest(PickVisualMedia.ImageOnly))

En ajoutant ces deux lignes de code, nous disposons d'un moyen sans autorisation d'accéder aux photos de l'appareil, qui offre une expérience utilisateur plus agréable et ne nécessite pas de maintenance du code.

Étant donné que PhotoLog n'a plus besoin de l'ancienne grille de photos et des autorisations de stockage pour accéder aux photos, nous devons maintenant supprimer tout le code contenant notre ancienne grille de photos. Cela inclut l'autorisation d'accès au stockage dans le fichier manifeste et la logique qui se trouve derrière, car ce n'est plus nécessaire dans notre application.

7. Recommandation : utiliser les API d'audit d'accès aux données dans les versions de débogage

Disposez-vous d'une grande application offrant de nombreuses fonctionnalités et collaborateurs (ou avez-vous prévu d'en créer une dans le futur ?), rendant difficile de savoir à quel type de données utilisateur l'application accède ? Saviez-vous que même si les accès aux données proviennent d'API ou de SDK qui étaient utilisés autrefois, mais qui ne jouent désormais plus aucun rôle dans votre application, celle-ci sera toujours responsable de l'accès aux données ?

Nous comprenons qu'il est difficile de suivre tous les endroits où vos applications accèdent à des données privées, y compris tous les SDK inclus et d'autres dépendances. Par conséquent, pour vous aider à fournir une meilleure transparence vis-à-vis de la manière dont votre application et ses dépendances accèdent aux données privées des utilisateurs, Android 11 propose un audit de l'accès aux données. Cette API permet aux développeurs d'effectuer des actions spécifiques, telles que l'impression dans un fichier journal, chaque fois que l'un des événements suivants se produit :

Commençons par revoir les principes de base de l'API d'audit des accès aux données sur Android. Pour adopter l'audit des accès aux données, nous allons enregistrer une instance de AppOpsManager.OnOpNotedCallback (Android 11 ou version ultérieure).

Nous devons également ignorer les trois méthodes du rappel, qui sont appelées par le système lorsque l'application accède aux données utilisateur de différentes manières. Ce sont les méthodes suivantes :

Déterminons maintenant laquelle de ces méthodes s'applique aux accès aux données de l'application PhotoLog. PhotoLog accède principalement aux données de l'utilisateur à deux endroits : une fois lorsque nous activons la caméra et une autre fois quand nous accédons à la position de l'utilisateur. Il s'agit d'appels d'API asynchrones, car ils consomment tous deux des ressources relativement importantes. Nous nous attendons donc à ce que le système appelle onAsyncNoted() lorsque nous accédons aux données utilisateur correspondantes.

Voyons maintenant comment adopter les API d'audit des accès aux données pour PhotoLog.

Tout d'abord, nous devons créer une instance de AppOpsManager.OnOpNotedCallback() et remplacer les trois méthodes ci-dessus.

Pour les trois méthodes dans l'objet, passons directement à l'impression de l'opération spécifique qui a accédé aux données utilisateur privées. Cette opération nous donnera des informations supplémentaires sur le type de données utilisateur concerné. De plus, comme nous nous attendons à ce que l'élément onAsyncNoted() soit appelé lorsque notre application accède aux informations sur l'appareil photo et aux informations de localisation, nous allons faire quelque chose de spécial et imprimer un emoji de carte pour les accès à la position et un emoji d'appareil photo pour les accès à l'appareil photo. Pour ce faire, nous pouvons ajouter le bloc de code suivant à : // 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}")

} }

Nous devrons ensuite implémenter la logique de rappel que nous venons de créer. Pour obtenir de meilleurs résultats, nous vous conseillons de procéder à cette opération le plus tôt possible, car le système commence à effectuer le suivi de l'accès aux données après l'enregistrement du rappel. Pour enregistrer le rappel, nous pouvons ajouter le bloc de code suivant à : // 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. Conclusion

Passons rapidement en revue les points abordés. Ce que nous avons découvert :

Nous espérons que vous avez apprécié cet atelier d'amélioration de la confidentialité et de l'expérience utilisateur de PhotoLog, et que vous y avez appris de nombreux concepts.

Pour trouver notre code de référence (facultatif) :

Si vous ne l'avez pas déjà fait, vous pouvez consulter le code de la solution pour cet atelier de programmation dans le dossier PhotoLog_End. Si vous avez suivi attentivement les instructions de cet atelier de programmation, le code du dossier PhotoLog_Start doit être identique à celui du dossier PhotoLog_End.

En savoir plus

Et voilà ! Pour en savoir plus sur les bonnes pratiques décrites ci-dessus, consultez la page de destination des règles de confidentialité d'Android.