David Madore's WebLog: Où je m'énerve une fois de plus sur la difficulté de migrer d'un Android à un autre (et j'explique quelques choses)

Index of all entries / Index de toutes les entréesXML (RSS 1.0) • Recent comments / Commentaires récents

↓Entry #2620 [older| permalink|newer] / ↓Entrée #2620 [précédente| permalien|suivante] ↓

(lundi)

Où je m'énerve une fois de plus sur la difficulté de migrer d'un Android à un autre (et j'explique quelques choses)

Bon, en attendant de vous raconter que je me suis acheté une moto, je vais vous raconter que je me suis acheté un téléphone, et je vais endosser mes habits préférés de râleur qui se plaint que le monde informatique est vraiment merdique. Je pourrais utiliser le même temps à essayer de débloquer tout ce qui me pose problème au lieu de le décrire à grands coups de phrases interminables dans mon blog mais where would be the fun in that? (sérieusement, ranter sur les problèmes que je rencontre au lieu d'essayer de les résoudre ne semble peut-être pas très productif, mais en fait (a) ça me permet de faire mentalement le point sur où j'en suis et (b) qui sait, peut-être qu'un expert Android passera par là et aura des informations sur les points techniques que je vais évoquer, même si je n'y crois pas trop).

Je vais essayer de faire en sorte qu'au moins le début de cette entrée ne soit pas trop technique (voire, possiblement instructif) pour les non-geeks (ignorez juste les détails des modèles et numéros de version que je me sentais quand même obligé de donner très tôt). Plus loin, en revanche, je me mettrai à parler de SQL et ça deviendra fatalement technique.

Donc. Le contexte est que mon téléphone actuel (un Nexus 5, cf. ci-dessous) est à bout de souffle. Physiquement, d'abord (le cache arrière se détache et il est scotché pour rester en place ; et la batterie a une autonomie faiblissante). Mais logiciellement aussi. D'abord parce qu'il n'y a plus de mises à jour, donc la sécurité devient de plus en plus mauvaise avec le temps qui passe (et, loi de Murphy oblige, juste après que les mises à jour ont cessé pour ce téléphone, il y a eu une cascade de trous de sécurité Android). Mais aussi simplement du point de vue performance. Je n'ai jamais bien compris par quelle magie les ordinateurs, tablettes et téléphones ralentissent avec le temps, comme si les processeurs s'usaient en vieillissant (pour ceux de mes lecteurs qui ne sont pas calés en informatique : non, pas du tout — en tout cas ça ne peut pas produire cet effet-là, où alors de façon extrêmement marginale par exemple sur l'usure de la mémoire flash/SSD), mais le phénomène est insupportable. Dans le cas de mon téléphone, c'est surtout le manque de mémoire qui le rend de moins en moins utilisable. Mémoire flash/SSD : il m'est de plus en plus difficile de tenir les photos que je veux conserver et les cartes OSM dans les 32Go dont il dispose. Mais surtout mémoire vive/RAM : basculer entre trois, parfois seulement deux, applications Android est devenu(?) impossible, si je lance la navitation dans Google Maps pour guider mon poussinet en voiture, je ne peux essentiellement rien faire en même temps (une fois que nous rentrions de Normandie et que je le guidais avec Google Maps/Navitation, nous sommes passés devant un panneau routier qui annonçait un site touristique possiblement intéressant, le poussinet m'a demandé de regarder ce que c'était, le temps que j'arrive à ouvrir un Firefox et à faire la recherche nous avons dû rester peut-être cinq bonnes minutes à tourner aléatoirement dans le bled puis à stationner comme des cons). Ce n'est plus tenable.

Bref, le nouveau téléphone que je viens d'acheter est un OnePlus 6 (nom de code enchilada), mais ce n'est pas important dans cette histoire. J'ai installé dessus la version 16.0 de LineageOS (une version libre et communautaire d'Android, pas énormément modifiée par rapport à la version basique AOSP distribuée par Google), ainsi que l'excellent mode de secours TWRP (lui aussi libre et communautaire), mais tout ça n'est pas terriblement important non plus dans ce que je vais raconter, à part peut-être que c'est un Android 9 Pie, sur lequel je suis root et où je peux aussi faire des opérations en mode recovery (= de secours). Mon téléphone actuel est sous Android 7.1.2 (Nougat ; c'est un Nexus 5 =hammerhead, sous LineageOS 14.1). J'ai des griefs contre le téléphone lui-même et contre l'emmerdement pour installer LineageOS (qui ne sont essentiellement pas la faute de LineageOS), mais ce n'est pas de ça que je veux parler (j'en parle un petit peu dans ce fil Twitter).

Le problème se pose donc maintenant, comme à chaque fois que je change de téléphone — et la difficulté à le résoudre ne s'améliore pas et explique que je n'aime vraiment pas changer de téléphone :

Comment transférer les données de l'ancien téléphone vers le nouveau ?

Par les données, j'entends principalement :

  • mes contacts téléphoniques,
  • l'historique des appels téléphoniques passés (correctement liés aux contacts, j'entends),
  • l'archive de tous mes SMS et MMS (avec, dans le cas des MMS, les photos ou autres pièces jointes qu'ils peuvent contenir ; et là aussi, correctement liés aux contacts),
  • les données de diverses autres applications (historique de conversation sous Xabber, réglages de Firefox, ce genre de choses).

J'accepte en revanche de perdre les préférences système, c'est-à-dire de les reparcourir pour refaire les choix de ce que je veux ou pas (il y a quand même eu des évolutions importantes entre Android 7 et 9, je conçois qu'il soit de toute façon une bonne idée de reparcourir tous les réglages et de prendre des décisions sur tout ça), c'est d'ailleurs essentiellement fait. J'accepte aussi de gérer manuellement les petits détails esthétiques comme le fond d'écran ou le choix des sonneries. (Vous voyez ? Je suis quelqu'un de raisonnable, j'accepte des compromis !)

Le dernier point ci-dessus (les données des applications tierces) ne pose, en fait, guère de problème, pour la raison suivante : comme ce sont des applications distinctes d'Android, je peux facilement mettre exactement la même version sur le nouveau téléphone que sur l'ancien, recopier les données brutalement, et grosso modo ça marchera (il peut y avoir toutes sortes de merdouilles, par exemple avec les permissions, ou, dans le cas de Firefox, le sel qui sert à nommer le profil, mais je m'en suis souvent bien tiré par le passé, je suis plutôt optimiste pour cette fois-ci).

Les trois gros points noirs sont donc : les contacts, l'historique des appels et les SMS+MMS. Je ne sais pas si l'utilisateur lambda l'accepte, mais à mes yeux, il n'est pas question que le fait de changer de téléphone me fasse perdre l'une de ces informations.

N'y a-t-il aucun moyen prévu par Android pour faire cette migration de données ? Si, il me semble qu'il y en a deux ou trois, mais ils sont insatisfaisants pour diverses raisons.

La première, qui existe peut-être en plusieurs sous-variantes (je ne comprends pas bien) consiste à envoyer toutes les données à Google.

Dans le cas des contacts, par exemple, cela consiste à activer (et c'est d'ailleurs le cas par défaut) la synchronisation des contacts avec les serveurs de Google. Plus exactement, quand on crée un contact sous Android, il est affecté à un « compte » (choisi au moment de créer le contact), et s'il s'agit du compte Google (ce qui, là aussi, doit être le cas par défaut), les informations sur ce contact sont transmises à Google, qui les stocke, et maintient la synchronisation de ces données entre tous vos appareils Android affectés à ce compte.

C'est bien pratique, mais… c'est une invasion proprement hallucinante de Google dans notre vie privée. Je suis loin d'être un maniaque de la protection de la vie privée (je mets plein d'informations sur moi en ligne, je permets à Google de me fliquer de toutes sortes de manières, parfois par choix et parfois par flemme de faire autrement), mais là j'ai vraiment un problème. Parce que ça concerne des informations sur d'autres personnes : je ne vois pas ce qui m'autorise, quand un ami me donne son numéro de téléphone, à le transmettre à Big Brother, même si ça doit simplifier ma gestion des téléphones multiples. Même si 99.999% des gens s'en foutent et que ces contacts sont certainement déjà connus de Google pour plein d'autres raisons, je ne veux pas participer au petit jeu du flicage des autres.

Et je note que Google pouvait parfaitement concevoir un système dans lequel ils fournissaient le même système de synchronisation sans qu'ils aient eux-mêmes la possibilité de lire les contacts des gens : Mozilla, par exemple, a créé un système plutôt bien pensé de synchronisation des favoris (=bookmarks) entre instances de Firefox, qui passe par les serveurs de Mozilla mais ne permet pas à Mozilla de savoir ce que vos favoris contiennent (seulement des choses basiques comme leur volume total et à quel moment vous en ajoutez), parce que tout est chiffré côté utilisateur et Mozilla ne voit que la version chiffrée. Cette différence éthique énorme entre Google et Mozilla, malheureusement, doit passer complètement au-dessus de la tête du grand public. Toujours est-il que Google n'a pas fait preuve de cette délicatesse : n'importe qui chez Google peut lire tous vos contacts Android s'ils sont synchronisés avec eux (comme il peut lire vos mails s'ils sont hébergés chez Gmail, etc.). C'est normal, c'est leur fonds de commerce.

Mais même en laissant complètement de côté cette histoire de vie privée (je crois qu'en principe Android fournit les mécanismes nécessaires pour répliquer à un niveau personnel les services de synchronisation offerts par Google : au prix d'efforts de programmation monstrueux, je pourrais avoir mon propre serveur sur lequel sont synchronisés mes contacts Android, et qui est géré exactement comme je l'entends), même en laissant de côté toute histoire de confidentialité, je n'aime pas du tout l'idée d'une synchronisation automatique de quoi que ce soit : c'est un coup à ce que les données soient abîmées d'un côté de la synchronisation et que cette corruption se propage méthodiquement aux autres côtés, j'ai déjà vécu des accidents de ce genre. (Donc même s'il existe un système de synchronisation un peu moins invasif que celui de Google, par exemple avec NextCloud, et même si ça marchait parfaitement ce dont je doute, je n'ai que très médiocrement envie de passer par ce genre de solution.)

Confession : lors de mes tout débuts d'utilisation d'Android, j'ai une fois par accident activé (ou pas désactivé à temps, je ne sais plus) la synchronisation des contacts avec Google. (À cette époque, on ne pouvait même pas créer de contact non attaché à un compte Google, donc certains de mes contacts sont/étaient marqués synchronisables avec mon compte Google.) Il y en a donc une version qui est arrivée sur les serveurs de Google. J'en suis désolé pour les gens dont j'ai ainsi accidentellement transmis le numéro de téléphone à Big Brother. Mais j'en suis désolé aussi pour une raison pratique, c'est que régulièrement, depuis, ces données « zombies » (pleines de vieux contacts avec lesquels je n'ai plus aucune relation et que j'ai effacés depuis) viennent polluer mes vrais contacts, par exemple quand j'initialise un nouveau téléphone Android, Google y envoie méthodiquement tous ces contacts moisis, avant que j'aie le temps de désactiver la synchronisation. Je ne sais pas bien quoi faire (j'ai essayé une fois de tous les effacer, mais Google s'en est rendu compte et a envoyé un avertissement du genre trop de contacts effacés d'un coup, synchronisation désactivée, qui est un garde-fou sans doute utile contre certaines erreurs mais qui, dans mon cas, ne m'arrangeait pas du tout).

Par ailleurs, je ne sais pas exactement ce que le système de synchronisation avec Google permet de gérer exactement. Les contacts j'en suis sûr, mais l'historique des appels et les SMS+MMS, je ne sais pas bien. J'ai encore moins envie de partager ces données avec Google, bien sûr.

Une variante du système de synchronisation (mais je ne sais pas dans quelle mesure c'est une variante, une autre facette de la même chose ou encore un système différent) consiste à faire une copie de sauvegarde (=backup) d'essentiellement toutes les données d'Android sur les serveurs de Google (le Google Drive je crois qu'ils appellent ça). Je n'ai pas exploré en quoi ça consiste exactement, parce que ça me convient aussi peu que le système de synchronisation, et pour les mêmes raisons. (Google explique quelque part que le backup est chiffré avec le mot de passe du compte Google, mais mon mot de passe de compte Google, justement, Google l'a, donc ce n'est pas du tout du chiffrement contre leurs propres yeux.)

Pour résumer, Google a prévu dans Android plein de mécanismes pour transférer les données d'un téléphone vers un autre, à condition qu'on veuille bien transmettre à leur autorité bienveillante toutes ces données. Si on ne veut pas, you're on your own.

(On me signale qu'au moins certains opérateurs nord-américains proposent un service de synchronisation semblable à celui de Google : on leur confie les données — contacts, SMS, etc. — et ils s'arrangent pour les partager entre tous les téléphones. Ça fait une façon de retenir l'utilisateur chez l'opérateur, c'est assez habile.)

Y a-t-il d'autres mécanismes ? Oui : une des philosophies d'Android (comme d'iOS) est qu'il y a une application pour tout. Il y a donc une, et même tout un paquet, d'applications qui proposent de sauvegarder et de restaurer au moins certaines des données que j'ai évoquées. J'ai plusieurs problèmes avec cette solution.

D'abord un problème de principe que j'ai du mal à expliquer précisément, mais qui recouvre largement ce que j'écrivais ici : j'exècre cette attitude consistant à faire penser que, sous Android, on ne peut rien faire soi-même, il faut toujours passer par des applications tierces, presque jamais libres/open-source, dont on ne contrôle pas exactement ce qu'elles font et qui vous proposent un peu magiquement de résoudre votre problème. Je n'ai pas forcément plus envie de confier à une telle application qu'à Google mes données pour les sauvegarder.

Mais j'ai aussi un problème pratique plus bête : il semble que toutes ces applications fonctionnent généralement mal. S'agissant de la sauvegarde et restauration des SMS+MMS, des problèmes que j'ai moi-même constatés par le passé avec certaines de ces applications, et/ou dont j'ai eu écho avec d'autres, sont, par exemple : les SMS sont sauvegardés mais pas les pièces jointes des MMS (i.e., les photos, vidéos, etc., envoyés directement par mobile) ; les messages envoyés à plusieurs destinataires disparaissent ou se multiplient en autant d'exemplaires que de destinataires ; les SMS longs sont coupés ou découpés ; certains caractères exotiques subissent des transformations aléatoires (Unicode cassé, caractères d'échappement dupliqués, etc.) ; les SMS sont sauvegardés mais n'apparaissent plus correctement reliés au contact expéditeur/destinataire ; l'affichage par ordre chronologique est subtilement cassé (décalages, réordonnement, etc.) ; l'information que le SMS a été lu ou non n'est pas correctement reproduite ; et ainsi de suite. J'ai des exigences assez élevées concernant la préservation de l'information, je ne me satisfais pas de sauvegardes approximatives et j'ai particulièrement peur des corruptions subtiles qu'on ne découvre parfois que longtemps après. Si on veut me recommander une de ces applications, ce serait bien qu'on me confirme au moins qu'elle ne souffre d'aucun des problèmes que je viens de lister à titre d'exemples. (À vrai dire je suis déjà gêné par l'idée que les numéros et identifiants internes des SMS, et autres données invisibles, dans les bases de données Android soient modifiés par l'opération de sauvegarde+restauration, mais bon, je peux faire un effort pour ignorer cette subtilité si j'ai un certain degré de confiance dans le fait que ce qui est vraiment visible de l'utilisateur sera reproduit avec la plus parfaite fidélité.)

Google prévoit quand même la possibilité d'exporter les contacts sous le format ouvert vCard, mais d'une part ce format (ou peut-être la façon dont Android exporte) souffre de limitations, par exemple je ne suis pas certain qu'il stocke correctement la division prénom/nom, ni la photo, d'autre part même si ça résout le problème pour les contacts ça ne fait rien pour l'historique des appels et les SMS+MMS.

Ce qui me perturbe, c'est qu'il me semble que, même si ça ne représente qu'une infime minorité dans l'ensemble des utilisateurs d'Android, le nombre de geeks qui auront, comme moi, des scrupules à passer par la synchronisation chez Google et des exigences élevées de fidélité de la copie, ne devrait quand même pas être minuscule. Que font-ils, alors, les autres ? Pourquoi les problèmes que je rencontre ne sont-ils pas documentés sur le blog d'au moins un autre geek linuxien barbu ? J'ai quand même du mal à croire que je sois seul au monde ! (Je connais pas mal de copains geeks ayant des tendances copyistes et un degré élevé de méfiance vis-à-vis des Big Brothers comme Google, mais je ne leur ai pas demandé un par un ce qu'ils font quand ils achètent un nouveau téléphone : si vous sentez que vous entrez plus ou moins dans ces cases, n'hésitez pas à me laisser votre réponse en commentaire !)

La solution qui me semblerait bonne, la solution que je veux vraiment, c'est la migration des bases de données d'Android. Expliquons encore un peu avant de rentrer dans les détails techniques.

L'architecture Android est groupée en applications (qui ont des noms simples pour les utilisateurs mais aussi des noms de code qui sont en gros des noms de classes Java : par exemple, l'application Contacts s'appelle en interne com.android.contacts), certaines étant d'ailleurs plus ou moins cachées à la vue normale (comme com.android.providers.contacts, dont le rapport exact avec com.android.contacts m'échappe), et chaque application peut avoir toutes sortes de données associées, dont je ne comprends pas bien dans quelles circonstances d'autres applications sont autorisées à la lire, et la plupart de ces données sont organisées en « bases de données SQLite » appartenant à l'application.

Pour expliquer rapidement au profane de quoi il s'agit, et en simplifiant assez, une base de données (de type SQL, ou apparenté) est un assortiment de tables, chaque table étant comme un gros tableau dont les lignes, ou enregistrements, (qui peuvent être très nombreuses et ajoutées au fur et à mesure) décrivent généralement des objets, et les colonnes (rarement plus que quelques dizaines, et dont le nombre n'est pas censé changer) des propriétés de ces objets. Par exemple, une base de données simpliste de contacts aurait une unique table, celle des contacts, avec ligne par contact et des colonnes donnant, disons, le nom, le numéro de téléphone, l'adresse, et je ne sais quelle autre information sur le contact (ceci est un exemple extrêmement simpliste, ne serait-ce que parce qu'il ne permet qu'un numéro de téléphone par contact). Souvent, on ajoute à une table une colonne « identifiant » qui attribue à chaque ligne un numéro (généralement de façon simplement consécutive : chaque nouvelle ligne ajoutée recevra simplement le premier identifiant non encore attribué). Les tables sont souvent organisées de façon relationnelle, c'est-à-dire qu'une colonne d'une table peut faire référence à une colonne d'une autre. Par exemple, si je reprends mon exemple de table de contacts, si on veut permettre à un contact d'avoir plusieurs numéros de téléphones, une façon de s'y prendre serait d'avoir deux tables, une table des contacts et une des numéros de téléphone : on ne met pas les numéros de téléphone dans la table des contacts, mais on donne à chaque contact un identifiant (dans une colonne « identifiant »), et la table des numéros de téléphone aurait, disons, trois colonnes, une colonne avec le numéro de téléphone, une avec l'identifiant du contact auquel il se rapporte (c'est là l'aspect relationnel), et une avec le type du numéro (fixe, mobile, personnel, de travail, que sais-je encore). Mon exemple est toujours simpliste, mais il commence à l'être un peu moins. Bref, une base de données SQL est un assortiment de tables, mais aussi de choses supplémentaires comme — des index permettant de retrouver facilement une ligne d'une table en connaissant certaines valeurs de ses colonnes (s'il y a un identifiant, par exemple, il y aura un index selon l'identifiant, parce que c'est bien l'intérêt de l'identifiant de permettre de retrouver la ligne), — des contraintes (d'intégrité), c'est-à-dire des propriétés que la table doit vérifier en permanence (par exemple, que la valeur de telle(s) colonne(s) doit être unique : il ne peut pas y avoir plusieurs lignes ayant la (les) même(s) valeur(s) dans la (les) colonne(s) en question ; un autre exemple serait que l'identifiant de contact dans la table des numéros de téléphone doit impérativement référencer un identifiant qui existe dans la table des contacts), — des déclencheurs (=triggers), c'est-à-dire des opérations à effectuer automatiquement lors de certaines modifications (par exemple, si on efface un contact, on peut vouloir automatiquement propager l'effacement de tous les numéros de téléphone associés) souvent pour maintenir les contraintes d'intégrité, — des vues c'est-à-dire des façons prédéfinies d'extraire les informations des tables, — et encore quantité d'autres choses du même genre. Mais l'important, ce sont les tables.

Le programme SQLite est un système de bases de données qui permet de stocker des bases de données (tables, index, contraintes, déclencheurs et tout le tralala) dans des fichiers généralement nommés .db, et de les consulter ou les modifier avec un programme appelé sqlite3 (le 3 est un numéro de version, mais peu importe). C'est ce qui est utilisé par l'essentiel des bases de données d'Android (disons qu'Android encourage fortement, sans le rendre obligatoire, de stocker les informations dans des bases de données SQLite).

En théorie, tout va bien, donc : mon Android stocke mes contacts, mon journal d'appel, mes SMS+MMS, et tout ce qui peut m'intéresser, sous forme de bases de données SQLite, je comment s'appellent les fichiers qui les contiennent (respectivement /data/data/com.android.providers.contacts/databases/contacts2.db pour les contacts, /data/data/com.android.providers.contacts/databases/calllog.db pour le journal d'appels, et /data/user_de/0/com.android.providers.telephony/databases/mmssms.db pour les SMS+MMS, ou plus exactement, les SMS et les métadonnées des MMS), je peux copier ces fichiers depuis et vers le téléphone (le fait qu'il soit rooté joue ici !), je peux lancer sqlite3 (soit sur le téléphone lui-même, soit sur un Linux où j'aurais copié les fichiers) pour les interroger ou les modifier ou même les « dumper » (c'est-à-dire extraire toute l'information comme des suites de commandes SQL permettant de la dupliquer), et j'ai même puisque Android est open-source, la description des colonnes et l'usage qui en est fait. En théorie.

En fait, en théorie, je n'ai qu'à recopier les fichiers SQLite de mon ancien Android vers le nouveau, et tout marchera.

En théorie, aussi, il n'y a pas de différence entre la théorie et la pratique.

En pratique, c'est bien différent.

D'abord, je ne peux pas juste recopier les fichiers SQLite de mon ancien Android vers le nouveau, parce que la signature de la base a changé. Le mot signature n'est pas du tout ici un terme de sécurité ou de cryptographie : c'est juste la description exacte de la base de données sans son contenu, i.e., pour chaque table le nom des colonnes et leur type (entier, chaîne de caractères, etc.), la liste des index à créer à partir de quelles colonnes, les contraintes d'intégrité à vérifier, etc. Autrement dit, la table des contacts de mon ancien Android et celle de mon nouveau n'ont pas les mêmes colonnes.

C'est quelque chose que je déteste profondément chez Google : ils n'arrêtent pas de changer d'avis sur tout (au moins sur les questions techniques). Sans arrêt. Ils prennent des décisions, et en changent, et encore, et encore, et encore. Android est notamment victime de cette Google-girouette : quand j'avais programmé une petite application merdique pour Android, j'avais suivi la documentation et les consignes contenue dedans ; à la version suivante, tout étant différent, la manière dont j'avais fait les choses était devenue obsolète : j'ai commencé mollement à me renseigner sur comment mettre à jour mon application, mais à la version suivante d'Android, tout avait de nouveau complètement changé ; et ainsi de suite. J'ai abandonné mon application (je ne sais même pas si Google la laisse encore apparaître sur le Play Store) parce que c'était insupportable : à chaque fois que j'essayais de prendre le temps de me renseigner sur ce qu'il faudrait changer pour la mettre à jour, tout avait encore une fois changé. Je ne suis pas forcément partisan de tout graver dans le marbre, en informatique, dès qu'on démarre un projet / système / programme (ni même de devoir conserver une compatibilité ascendante ad vitam æternam), mais l'obsession maniaque de Google à tout casser tout le temps et encore et encore est de la pure folie.

Il va de soi que les schémas des bases de données d'Android suivent ce mouvement général où ce qui était tout neuf tout brillant le matin est obsolète à l'heure du déjeuner et même plus supporté le soir. Et parfois c'est toute la base de données qui est rendue obsolète : ah, mais on n'utlise plus du tout la base de données de l'application com.android.example.foobar, c'est un truc obsolète : maintenant, c'est com.android.providers.example.foobar qui sert à la place (pourquoi ? parce que fsck you, voilà pourquoi).

Les schémas, bien sûr, sont totalement illisibles. Pour exemple, voici à quoi ressemblent les schémas (juste les schémas ! c'est-à-dire uniquement la définition des tables, index, déclencheurs, vues, etc., sans aucun contenu proprement dit) des bases de données contacts2.db d'Android Nougat et Pie (← j'ai écrit Oreo à tort dans le nom du fichier, et Git ne permet pas de changer sans créer une nouvelle version, donc tant pis) telles que dumpées depuis mon ancien et mon nouveau téléphone : même si vous n'y comprenez rien du tout, vous voyez un peu la complexité ? (et notez la longueur hallucinante des lignes, aussi, notamment celle qui commence par CREATE TABLE raw_contacts et qui est la table la plus importante de l'histoire), juste pour stocker une base de données de contacts téléphoniques ‽ Imaginez un peu la difficulté de comprendre les différences entre ces deux schémas, et écrire le code de conversion. (Reconnaissons que SQLite n'aide vraiment pas les choses parce qu'il ne permet pas de supprimer une colonne d'une table, ce qui est absolument hallucinant quand on y pense : donc dès qu'une colonne est créée, la seule façon de la « détruire » est de la renommer en un nom indiquant qu'elle est obsolète, ou simplement de l'ignorer et de cesser de s'en servir. Le fait que Google n'arrête pas de changer d'avis sur le schéma signifie que les colonnes obsolètes doivent s'accumuler comme autant de fossiles d'une époque où elles étaient encore toutes neuves toutes brillantes.)

Déjà, il faudrait comprendre exactement comment ces différentes colonnes servent : quand je vois des colonnes nommées times_contacted et x_times_contacted (la deuxième est nouvelle dans Pie, donc), je me doute bien qu'elles servent à mémoriser combien de fois chaque contact a été contacté, mais je ne sais pas pourquoi il y en a deux différentes. Il y a parfois un peu de documentation quelque part dans le source ou ailleurs ; par exemple, s'agissant de la base de données de contacts, dans (les commentaires et le code du fichier) ContactsContract.java, mais ça ne m'explique pas tout (par exemple pas la différence entre times_contacted et x_times_contacted), probablement parce que je ne comprends pas ce qu'est un contrat au sens Android (je connais le Java en général, mais pas les subtilités de la terminologie et l'organisation des classes Android, ni même des fichiers dans le source, d'ailleurs), et probablement parce que ceci est juste censé documenter la partie « publique » de la base de données de contacts, pas la manière dont l'application elle-même utilise la base de données sous-jacente (pour ça, vous pouvez vous brosser pour avoir une doc).

En théorie (on en revient toujours à en théorie !), Android prévoit les mécanismes de migration d'une version de la base de données vers une autre : en plus de la collection des tables, une base de données SQLite a un numéro interne appelé le pragma user_version, qui permet de procéder à une migration harmonieuse des tables : grosso modo, le code va lire ce pragma avant de faire quoi que ce soit avec la base, et si c'est une version plus ancienne que ce qu'il attend, il va procéder aux transformations nécessaires pour mettre le schéma à jour de la dernière version (et modifier le pragma en conséquence). S'agissant de la base de données des contacts, ce code de migration est par exemple dans ce fichier (et vous ne pouvez pas imaginer le mal que j'ai eu à le dénicher dans le labyrinthe du code Android !), et c'est au moins utile au sens où on voit chaque version qui a été traversée par le fichier et comment la migration est censée se faire. En pratique, cependant (toujours la différence avec en théorie), ce code semble ne jamais marcher. En 2014, par exemple, quand je suis passé de mon avant-avant-dernier téléphone à mon avant-dernier ou quelque chose comme ça, ça n'a pas marché, j'ai passé un temps invraisemblable à comprendre ce qui se passait, et j'ai fini par comprendre qu'une migration échouait parce que ma base de données de contacts (qui a été migrée depuis les toutes premières versions d'Android) avait une colonne devenue obsolète dont le nom était identique avec une nouvelle colonne que le code de migration essayait de créer (ou quelque chose de ce goût-là). Du coup, le code de migration échoue et on perd tous les contacts — ou alors il faut procéder à la migration « à la main » à grands renforts de SQLite, de Perl, et d'incantations magiques à base de bave de crapaud, sans avoir d'idée vraiment claire de ce que fait chaque colonne.

Cette fois-ci, j'ai essayé de copier mon ancienne base de données sur mon nouveau téléphone (en passant par le mode recovery, bien sûr) pour procéder à la migration, mais je me suis retrouvé avec une base à moitié cassée, où certains contacts avaient bien fonctionné, mais d'autres n'étaient pas apparus, d'autres, au contraire, étaient réapparus alors qu'ils avaient été effacés depuis longtemps (la table deleted_contacts n'avait pas été migrée correctement ; mais pourquoi y a-t-il une table deleted_contacts pour commencer, alors qu'il est clairement prévu de simplement effacer des lignes de la table des contacts ? je n'en ai aucune idée !). J'avoue que, pour l'historique des appels, tout s'est passé impeccablement. Pour la base de données des contacts, j'ai fini par recourir à la stratégie tarabiscotée suivante :

  • recopier la base de données de contacts de l'ancien téléphone vers le nouveau (contacts2.db qui se trouve dans /data/data/com.android.providers.contacts/databases/ ; en même temps que celle du journal d'appels, calllog.db, et profile.db dont je ne sais même pas à quoi elle sert, qui sont dans le même répertoire), laisser le code Android faire la migration en démarrant le téléphone ;
  • faire une migration à la main en exécutant ce qu'on peut des commandes SQLite contenues dans ContactsDatabaseHelper.java sur une copie séparée de la base de données de l'ancien téléphone (note : il faut effectuer ça sur le téléphone, parce que si on essaye depuis un Linux, on a des erreurs concernant un no such collation sequence: PHONEBOOK) ;
  • dumper (au format texte !) les bases de données résultant des deux points précédents, et faire un diff -u pour voir leurs différences ;
  • exécuter les lignes de différence (surtout les INSERT) apparaissant dans la base de données du deuxième point et pas du premier, en ignorant les éventuelles erreurs.

Ça… marchouille (j'ai encore dû réparer des petits problèmes à la main — ce qui m'énerve parce que je vais sans doute devoir tout recommencer). Mais vous voyez un peu le niveau de complication et de hack juste pour garder mes contacts ‽

Bon, ça c'était pour la base de contacts. Pour les SMS+MMS, le problème est un peu différent : la base de données évidente est mmssms.db dans le répertoire /data/user_de/0/com.android.providers.telephony/, le user_version ne semble pas avoir changé, je l'ai recopiée mais rien ne s'est passé. J'ai l'impression qu'il y a une autre base de données qui sert (en plus ? à la place ? selon les circonstances ? comme cache ? je n'en sais rien). J'ai essayé de m'envoyer sur le téléphone un SMS contenant une séquence de lettres improbable pour voir quels fichiers apparaissaient contenant cette séquence, et elle est apparue dans pas moins de quatre fichiers différents :

  • /data/data/com.google.android.gms/databases/icing_mmssms.db-wal
  • /data/data/com.android.providers.telephony/databases/mmssms.db
  • /data/data/com.android.messaging/databases/bugle_db-wal
  • /data/data/com.android.messaging/databases/bugle_db

Je suis perplexe quant à l'existence d'un fichier mmssms.db dans deux répertoires différents dont je n'ai aucune idée de ce à quoi ils correspondent (on me souffle que le DE de user_de signifie Device Encrypted, mais ça ne m'explique pas pourquoi mmssms.db est tantôt dans /data/user_de/0/com.android.providers.telephony/databases/ et tantôt dans /data/data/com.android.providers.telephony/databases/ — ou peut-être les deux à la fois — et comment ceci interagit). Je suis encore plus perplexe quant à la signification des deux-trois autres bases de données (je ne sais pas non plus ce qu'est un fichier WAL, même si je sais que ça signifie Write-Ahead Log, je ne sais comprends pas pourquoi ils ne sont pas tout vides quand le téléphone est redémarré en mode recovery). Certaines sont peut-être de simples caches, mais comment ordonner leur reconstruction ? Et je ne sais pas pourquoi, d'ailleurs, bugle_db avec un souligné et pas un point comme toutes les autres bases de données du coin. Tout ça n'est documenté nulle part, et pour analyser ce qui se passe, je suis face à des millions et des millions de lignes de code qui se renvoient les unes aux autres selon des conventions que je ne maîtrise pas du tout (il y a l'air d'avoir des bouts de choses intéressantes dans SmsProvider.java, et MmsSmsDatabaseHelper.java pour la migration ; et s'agissant de ce fameux bugle_db c'est plutôt par là que ça se passe, mais ce ne sont là que des bouts de longs fils d'Ariane que je dois encore suivre).

En tout cas, le fait qu'un SMS reçu fasse son apparition dans quatre bases de données différentes est le signe indubitable qu'il y a beaucoup de choses pourries dans le royaume d'Android. J'avais un copain de promo à l'ENS qui répétait régulièrement ce slogan que j'ai fini par baptiser en son nom :

Théorème de Horvai : Quand il y a duplication d'une information dans un système informatique, fatalement, tôt ou tard, les copies censées être identiques finiront par se désynchroniser. (Et si le cas n'est pas prévu pour les resynchroniser simplement, cela causera des inconvénients graves.)

J'ai eu d'innombrables occasions de me rendre compte à quel point c'était vrai. (On pourrait sans doute généraliser en disant que tout invariant finira par être cassé, l'égalité de deux données n'étant qu'un invariant particulier, mais c'est tout de même l'invariant qui casse le plus souvent et le plus facilement, et qui cause le plus de maux de têtes.) Le fait que le contenu du SMS soit dupliqué dans trois ou quatre bases de données différentes est un très très mauvais signe.

J'en suis là de mes investigations. Je mettrai peut-être à jour cette entrée si j'arrive à comprendre comment migrer mes SMS (pour les MMS, en plus, il y a le contenu de /data/user_de/0/com.android.providers.telephony/app_parts/ dont je ne sais pas s'il suffit simplement de le recopier ou s'il faudra prononcer des incantations magiques supplémentaires). En attendant, j'ai furieusement envie de jeter le téléphone par la fenêtre et de maudire tous les développeurs Android jusqu'à la 1729e génération. (Le but de cette entrée est aussi de me permettre à moi-même de faire une pause en dumpant ce que j'ai réussi à comprendre, de façon à pouvoir le reprendre plus tard.)

S'il y a des gens qui ont quelques informations sur les entrailles d'Android (sur des points comme les questions que j'ai posées ci-dessus), ou même simplement sur où trouver de telles informations, n'hésitez pas à poster des commentaires !

Mise à jour / compléments () :

  • Finalement, pour ce qui est des MMS+SMS, ça n'a pas été très difficile : pour les SMS, il m'a suffi de copier la base de données mmssms.db qui, sous l'ancien téléphone était dans /data/user_de/0/com.android.providers.telephony/databases/ vers /data/data/com.android.providers.telephony/databases/ sur le nouveau (je ne comprends pas cette duplication, mais l'essentiel est que ça marche) ; pour les MMS, j'ai recopié le contenu de /data/user_de/0/com.android.providers.telephony/app_parts/ sur le nouveau téléphone. La base de données bugle_db semble pouvoir être ignorée purement et simplement, parce qu'il s'agit d'une sorte de surcouche qui regroupe les conversations (et elle est reconstruite automatiquement au moins dans certains cas, donc le théorème de Horvai perd de son tranchant) : c'est vraiment mmssms.db qui est la copie primaire.
  • En revanche, je me suis rendu compte que j'avais beaucoup plus d'applications Android que ce que je croyais, et même si pour chacune individuellement la copie des données n'est généralement pas compliquée, le faire 50 ou 60 fois devient vite très fastidieux (d'autant qu'on ne sait pas toujours exactement ce qu'on veut copier ou pas). Surtout qu'il y a des permissions auxquelles il faut faire bien attention et qui ne sont pas pareil entre l'ancien et le nouvel Android (note à toutes fins utiles : après chown et chmod appropriés penser utiliser la commande restorecon pour restaurer le contexte SELinux).
  • Le gag suivant m'a mordu et mérite sans doute que je le signale : sur le nouvel Android (Pie), je suppose que la version de SQLite est plus récente, toujours est-il qu'à côté d'un fichier foobar.db, il y a souvent un fichier foobar.db-wal et foobar.db-shm contenant un log de modifications non encore écrites dans la base de données proprement dite (voir ici pour les détails) ; je pensais qu'on pourrait les ignorer une fois tout programme utilisant cette base de données terminé, mais en fait non : ces fichiers peuvent persister, et ne sont pas simplement ignorable. Avant de copier une autre version de foobar.db il est nécessaire de jouer complètement ce log : le plus simple pour ça est simplement d'exécuter sqlite3 foobar.db .schema, ce qui ne modifiera pas le contenu de la base de données mais obligera le log à être purgé (c'est-à-dire correctement retranscrit dans la base).
  • Pour chaque application à recopier de l'ancien vers le nouveau téléphone, ma procédure approximative (et fastidieuse !) a donc été : installer l'application sur le nouveau téléphone, la lancer une fois mis quitter immédiatement en ayant fait le moins possible (ceci sert à forcer l'application à créer les bons répertoires avec les bonnes permissions, c'est possiblement utile) ; puis, sur l'ancien et le nouveau téléphone, arrêter l'application de force et effacer son cache ; s'il traîne (sur le nouveau téléphone) des fichiers foobar.db-wal et foobar.db-shm, lancer sqlite3 foobar.db .schema comme expliqué au point précédent ; puis utiliser adb pull et adb push pour recopier les données souhaitées de l'ancien vers le nouveau téléphone (typiquement, le contenu de shared_prefs/, databases/ et files/, mais ajuster au cas par cas) ; pour préserver les permissions, le mieux est de pousser vers un nom comme machin.new et ensuite faire cat machin.new >machin et rm machin.new (un petit script aidera à ne pas retaper ces commandes à chaque fois), sinon, penser à chown, chmod et restorecon (cf. ci-dessus) ; enfin, lancer l'application sur le nouveau téléphone et vérifier que ça bien fonctionné.

↑Entry #2620 [older| permalink|newer] / ↑Entrée #2620 [précédente| permalien|suivante] ↑

Recent entries / Entrées récentesIndex of all entries / Index de toutes les entrées