2012-09-13

Achat in-app : attention aux signatures

APK avec signature de test vs signature développeur. 

 Le plug-in ADT pour Eclipse simplifie considérablement le travail de signature d'une application. En effet ADT signe avec une clef de test votre APK pour une future installation sur un terminal (ou émulateur).

Si pratique qu'elle soit, la clef de test sera refusée par le PlayStore : chaque développeur doit créer sa propre clef pour une signature unique.

Dans ce monde manichéen nous avons donc des APK signés avec la clef développeur sur le PlayStore et des APK signées par ADT. Et lorsqu'on essaye de faire un achat in-app :


Erreur liée à l'application
Cette version de l'application n'est pas configurée pour la facturation via Google Play. Pour plus d'informations, veuillez consulter le centre d'aide.


Ne cherchez pas trop longtemps le centre d'aide. Cette erreur est causée par la signature de l'APK.

Comment automatiser la signature avec sa clef de développement ?

Utilisons encore la documentation Android sur la signature avec jarsigner :
$ jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore my.keystore \
-storepass PA55W0RDST0RE -keypass PA55W0RDKEY my.apk myDevNickName

Il y a de très fortes chances que vous obteniez ce message :

jarsigner: unable to sign jar: java.util.zip.ZipException: invalid entry compressed size (expected 765 but got 771 bytes)

Ce message, comme son code erreur ne l'indique pas, est causé par la présence d'une signature dans l'APK : vous avez utilisé l'APK créée par ADT et stockée dans workspace/MyAndroidApp/bin/ qui est en fait signée avec la clef temporaire.

Pour supprimer la précédente signature, une simple commande zip (manpage):
zip my.apk -d "META-INF/*"

En un script : signandup.sh

#!/bin/sh
zip my.apk -d "META-INF/*"
jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore my.keystore \
-storepass PA55W0RDST0RE -keypass PA55W0RDKEY my.apk myDevNickName
adb install -r my.apk

2012-06-28

Les détails sympas de JellyBean Android 4.1

Vous avez-vu les vidéos avec du JellyBean ? C'était rapide ? Oui ... 60 fps ça pardonne pas !

Prenons quelques minutes, statiques, et regardons vers où JellyBean nous mène.


Le nouveau logo de debug Android : une droidgibus !!

Le feedback du déplacement du doigt sur le lock-screen

Pour réduire le clavier on utilise une flèche vers le bas. Logique non ?

Les "receivers" sont affichés sur 2 colonnes ET le texte desactivé est "limite" visible.

Feedback de la position du doigt. On ne retrouve pas le même que le lockscreen mais les deux bleus holo combinés

En revanche sur la home, le feedback du lock-screen. La boule "Google" est pour "Google Now".

Application "Photo". A gauche l'application et a droite la "Galerie" avec un slide du doigt (en rouge).

Redimensionnement des widgets.

La date est complète mais compacte, L'icone pour les parametres et l'escalier pour chasser toutes les notifications.

LongClick sur une notification : on va pouvoir trouver l'application qui notifie ! Yeah !

Future option ?

Vous remarquez ? Les "switchers" sont à angles droits !

Le popup à les coins légèrement arrondis et il y a 3 gris.
Freenaute, il n'y a pas d'EAP-SIM :(


L'icône plus, très moche, n'a pas été mise à la poubelle. Pire, on lui adjoint un "refresh" "réussi".

Amélioration du menu Application : réinitialiser les associations par défaut.

Précision pertinente.

Nouveautés pertinente pour les tablettes !

Voila voila, on va pouvoir limiter les gros vilains qui fouinent sur la SD :)

Il va falloir patcher :)

C'est qui qui publie avec les options de débogage ? Hein ?

Pour faire apparaitre l'easter egg : comme pour toutes les autres versions.

Tadaaaaaaaaaaaaaaa !

Tiens ... un nouveau feedback ?

Voila, votre prochain achat sera un Nexus.

2012-06-25

Maîtrisez vos versions beta avec Zubhium

[Billet non sponsorisé]

Google Play Store

Distribuer des versions instables de ses applications via le Play Store est périlleux pour la distribution et surtout pour son image de marque.

Le principal avantage est que l'utilisateur n'a pas besoin de changer les paramètres de son terminal pour installer l'application, on bénéficie des serveurs de google et on est tiré vers le haut par les exigences (capture d'écran, bannière, description, ...).

Comme inconvénients, il y a justement la lourdeur des informations obligatoires, l'absence de kill-switch et la suppression des versions anciennes (juste désactivées). Le principal inconvénient est (de très loin) la horde de crétins insidieusement infiltrée dans le reste des utilisateurs :

Jusqu'à présent j'utilisais l'auto hébergement avec un réducteur d'URL efficace et prévenais mes volontaires par sms/mail. C'est au détour d'une lecture de StackOverFlow que j'ai découvert Zubhium ...

La solution zubhium.com


Zubhium est un service en beta et gratuit qui se propose pour l'hébergement de vos apk, la notification des nouvelles versions, le recueil des crashlogs (optionnel) et des feedback (anonymes) des utilisateurs. Il faut préalablement s'inscrire et le système est prêt à fonctionner.

Déclarer un apk

Tout commence par la déclaration de votre beta. Plus simple que Google : un nom, une icône et une description.

L'étape suivante est l'envoi de l'apk, phase très simple qui fonctionne même si on utilise l'apk signée par Eclipse (disponible dans bin/ !).

La dernière étape est la déclaration des adresses e-mail des vos cobayes. Vos cobayes n'ont pas besoin de s'inscrire, l'adresse ne sert qu'à les prévenir.

Les règles de confidentialité sont précisées ici, à vous de juger de l'opportunité de déclarer telle ou telle adresse e-mail.

Vous pouvez également distribuer un lien direct vers votre beta en le copiant/collant dans un tweet par exemple.

Déclarer une nouvelle version


Les nouvelles versions sont nommées "push". Il n'y a que deux contraintes pour faire un nouveau push ce qui rend l'opération largement plus simple que sur le Play Store. Votre apk doit avoir une version supérieure aux "push" existants et vous devez déclarer les modifications apportées.

Ces modifications seront transmises à vos volontaires dans le corps de l'email de notification. Notez que jusqu'à présent il n'est pas nécessaire d’intégrer le SDK de Zubhium à l'application.

Bloquer une ancienne version

Plusieurs raisons peuvent vous emmener à clôturer une version : cesser de recevoir des crash-logs dus à un bug résolu, orienter vers une nouvelle beta ou tout simplement passer à la version officielle sur le site marchand de Google ou Amazon.

Pour pouvoir interagir avec une ancienne beta, il faut nécessairement intégrer le SDK. La documentation est suffisamment bien rédigée pour ne pas l'aborder dans ce billet. Si vous avez des soucis on peut en causer sur twitter.

Le blocage se fait dans l'onglet "Manage" et en cliquant sur "LIVE" :

L'interface vous propose de
  • Envoyer un message qui sera affiché à chaque démarrage de l'application dont la version beta a expiré.
  • Proposer aux utilisateurs un lien vers une beta plus récente OU un store (Google ou Amazon)
  • Forcer l'utilisateur à passer à la version officielle (Google ou Amazon) en bloquant complètement l'application (Kill-switch)

Les crashlogs


Comme précisé dans la documentation, Zubhium peut servir de back-end à ACRA et ainsi agréger les informations obtenues :

En savoir plus sur le terminal:

L'état du réseau :

La pile :

Les logs (que je n'active JAMAIS) :

Et le dump :

Limite plus pratique que DDMS non ?

Bilan

Ça marche et ça marche excellemment bien ! C'est un outil bien pensé et gratuit.

Le SDK permet, sans effort de développement de garder la main sur sa beta et de récupérer les crash de manière textuelle ET graphique. J'adooooore la possibilité d'uploader un apk non signé !

Il est dommage que la fenêtre de feedback ne soit pas (encore) internationalisée, ça peut freiner les cobayes anglophobes. Il y aurait également la possibilité de faire pointer la fin d'une beta vers la toute dernière version.

Bref, je souhaite à Zubhium un bon développement et de perdurer dans la philosophie KISS et surtout de perdurer dans le temps.

Nice works guys.

2012-06-16

Appliquer un gradient XML pour le contour d'un shape


Pour l'instant (SDK API LEVEL 15) il n'est pas possible de mettre un gradient comme contour d'une forme géométrique dans un drawable.

Le contour se définit par l'élément <stroke> dans le drawable et n'accepte comme paramètre de couleur android:color qui est soit une ressource "color" soit une couleur littérale.

Je vais détailler comment faire ce contour en dégradé uniquement avec les drawables XML, donc sans utiliser de bitmap (png).

Stroke par l'exemple

Prenons le drawable XML (res/drawable/radiogroup2.xml) suivant :

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
    <solid android:color="#000" />
    <stroke
        android:width="3dip"
        android:color="#0af" />
    <corners android:radius="5dip" />
</shape>
que j'applique à un widget de l'interface :
<RadioGroup
            android:id="@+id/rgModeGPS"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:background="@drawable/radiogroup2" >
(...)
</RadioGroup>
Qui donne ceci :

Gradient par l'exemple

On peut s'approcher de notre objectif en utilisant un gradient (res/drawable/radiogroup3.xml) :
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
    <solid android:color="#000" />
    <gradient
        android:angle="270"
        android:endColor="#0048ff"
        android:startColor="#00fffc"  />
    <corners android:radius="5dip" />
</shape>
que j'applique à un widget de l'interface :
<RadioGroup
            android:id="@+id/rgModeGPS"
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:background="@drawable/radiogroup3" >
(...)
</RadioGroup>
Qui donne ceci :

Solution

La solution passe par l'utilisation simultanée du dégradé et d'un rectangle noir. Pour cela il faut utiliser plusieurs couches (layer)

Le drawable XML (res/drawable/radiogroup3.xml) doit comprendre un item avec le gradient et un item avec solid noir  :
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <!-- Définition du gradient -->
    <item
        android:bottom="0dip"
        android:left="0dip"
        android:right="0dip"
        android:top="0dip">
        <shape>
            <solid android:color="#112233" />

            <gradient
                android:angle="270"
                android:endColor="#0048ff"
                android:startColor="#00fffc" />

            <corners android:radius="5dip" />
        </shape>
    </item>
    
    <!-- Définition du fond noir -->
    <item
        android:bottom="5dip"
        android:left="5dip"
        android:right="5dip"
        android:top="5dip">
        <shape>
            <!-- Le padding aère le contenu -->
            <padding
                android:bottom="10dip"
                android:left="10dip"
                android:right="10dip"
                android:top="10dip" />

            <solid android:color="#000" />

            <corners android:radius="5dip" />
        </shape>
    </item>

</layer-list>
      Tadaaaaaaaaaaaaaa!

2012-05-27

Suivre ses comptes bancaires avec linxo

Ce billet n'est pas sponsorisé.

3615 mavie.

Mes différents déménagements et projets m'ont emmené à avoir plusieurs comptes bancaires dans plusieurs banques. Par fidélité et un petit peu par laisser-aller j'ai donc des revenus qui "tombent" sur plusieurs comptes et surtout des prélèvements automatiques sur des comptes que je surveille peu.

En attendant le courage de fermer les comptes et donner les nouveaux RIB, je me sers des sites web des banques ou des applications mobiles. Dans le premier cas il faut systématiquement s'authentifier et c'est pénible (surtout chez Tarneaud) et dans l'autre cas c'est trop souvent un portage bridé de la version iOS (inadaptée à Android).

2012-05-06

Quelques conseils sur SeekBar.setThumb()

Toujours dans mon projet de tracking GPS, j'utilise une SeekBar comme feedback de l'état de progression. Le véhicule se déplace sur toute la longueur de l'écran symbolisant la distance parcourue depuis l'événement n-1 et celle à parcourir avant l'évènement n.

Techniquement, la SeekBar, barre de progression, utilise un curseur (nommé thumb) qui est un Drawable.

Problème 1 : afficher complètement le thumb.

Lorsque la progression de la SeekBar est à 0, le curseur était affiché à moitié. Réciproquement une seule moitié était affichée lorsque le curseur est au maximum.
La solution se trouve dans la documentation de l'API :
sb.setThumbOffset(0);
Explication par le source Android AbsSeekBar.java:
108    // Assuming the thumb drawable is symmetric, set the thumb offset
109    // such that the thumb will hang halfway off either edge of the
110    // progress bar.
111    mThumbOffset = thumb.getIntrinsicWidth() / 2;

Problème 2 : setThumb fait disparaître le curseur.

Quelques secondes après l'affichage du Drawable comme curseur, ce dernier disparaissait.
La solution a été donnée par Romain GUY, il faut refaire une étape de setThumb() (cf paragraphe précédent)
Drawable retourDrawable;
retourDrawable = getResources().getDrawable(
  R.drawable.voituremgps1234);
retourDrawable.setBounds(new Rect(0, 0, 
  retourDrawable.getIntrinsicWidth(),
  retourDrawable.getIntrinsicHeight()));
sb.setThumb(retourDrawable);

Problème 3 : setThumb remet le curseur à 0

Ce problème m'a donné pas mal de fil à retordre. La responsabilité de setThumb dans le repositionnement du curseur n'est pas immédiate. C'est en procédant au pas à pas que le lien m'est apparu. Concrètement, le curseur est positionné à zéro mais ne réponds pas aux ordres de repositionnement.

C'est probablement dans l'API mais je n'ai pas trouvé.

Mon conseil est de mettre le curseur à une valeur nulle ou négative avant de le remettre à la bonne position :
sb.setThumb(retourDrawable); // Le drawable
sb.setProgress(-1); // progress négatif
sb.setProgress(positionThumb); // progress à la bonne place.

2012-04-26


Il y a deux jours, j'ai reçu un sms non sollicité, un spam.

Non mobilobox, c'est un numéro porté, je ne suis plus trait (meuh) par le cartel des 3 escrocs que sont Béton, Vivendi et Mandarine. Je suis chez FreeMobile ce qui te vaut la chance incroyable de ne pas être signalé au 33700.

Ça marche pas de chez çamarchepas.com

Ma version personnelle de SignalSpam envoie directement les SMS. Elle ne passe pas obligatoirement par “Intent.ACTION_VIEW“ comme la version grand public disponible sur le PlayStore.

Donc ... erreur sur erreur : l'application refuse obstinément d'envoyer le message au 33700. Mon code est correct, mais ça ne fonctionne pas. Par dépit je passe en mode “Intent.ACTION_VIEW“ pour laisser l'application native de ICS.

Diantre ... même l'application native se casse les dents.

Explications

Le 33700 est géré par l'Association SMS+ (association loi 1901) alias L'Association Française du Multimédia Mobile. L'AFMM précise sur une page de son site les opérateurs membres :
  •  Auchan Telecom
  • Bouygues Telecom
  • Debitel
  • NRJ Mobile
  • Omer Telecom
  • Orange France
  • Orange Réunion
  • SFR
  • SRR
  • Tele2 Mobile
  • Telegate
Il n'y a pas FreeMobile.

Steuplait Monsieur Free ...

Bon M. Free, nous savons toi et moi que ta marge de progression dans le mobile est géante. Tu es encore jeune et plein d'ambition.

Donc, si tu veux vraiment progresser dans le détail de confort inutile à 99% de la population des mobinautes, gère le 33700.

Toi et moi aimons ces détails inutiles qui ont fait ta singularité dans l'ADSL ;)

Message Perso : Hey mobilobox, fais-moi plaisir, internet c'est avec un I majuscule. Autre chose, qu'utilises-tu comme logiciel pourri en ASCII en 2012 ? Cet anachronisme technologique fait commercialement désordre.

2012-04-25

fill_parent ou match_parent

Selon votre plus ou moins grande ancienneté avec le SDK Android, il doit vous arriver de mélanger au sein d'une même description d'interface XML des “fill_parent“ ou des “match_parent“ pour vos “android:layout_width“ ou vos “android:layout_height“.

Les deux valeurs sont équivalentes, le widget doit prendre toute la place laissée vacante dans le widget parent. La différence réside dans la version de l'API. Si votre projet vise les SDK < 7, vous devez utiliser “fill_parent“. Au delà, pour le SDK 8 et supérieurs, la valeur conseillée est “match_parent“.

Si votre projet vise un numéro d'API strictement plus grand que 7 :
find . -iname "*.xml" -exec sed -i "" "s/fill_parent/match_parent/g" {} \;
Si votre projet vise un numéro d'API strictement inférieur ou égale à 7 :
find . -iname "*.xml" -exec sed -i "" "s/match_parent/fill_parent/g" {} \;


Plus de lecture : ici (Anglais).

2012-04-23

Mettre à jour un Galaxy Nexus chiffré sans perdre de données.

Dans un précédent billet, je donnais une méthode propre pour passer son Galaxy Nexus en rom aokp. Propre, ok mais au prix de vos données.
Il est possible de mettre à jour la partie ROM du terminal sans toucher à la partie RWM. Simple sans être trivial, voici comment faire.
Attention, cette méthode de mise à jour est conforme à l'esprit de la norme ISO 1664.

Sauvegardez vos données. Sauvegardez vos données. Sauvegardez vos données.

Cette méthode n'a été vérifiée QUE et UNIQUEMENT sur un terminal chiffré équipé d'une ROM AOKP.

Préparer la rom AOKP qui va bien.

Durant cette phase, les commandes seront exécutées sur mon ordinateur personnel. La préparation doit se faire sur une machine séparée pour plusieurs raisons :
  • On ne peut pas échanger de données entre une session chiffrée et une session en clair (cwm).
  • Zip et Bzip2 sont des opérations gourmandes en temps CPU et I/O.
Le billet est réalisé avec la build 32 de la rom aokp récupérable ici
Une fois téléchargé, je prépare 2 archives supplémentaires : system.zip et system.tar.bz2
$ cd /tmp/
$ unzip aokp_maguro_build-32.zip
$ cd aokp_maguro_build-32/system/
$ # Création de l'archive ZIP
$ zip -r ../../system.zip *
$ # Création de l'archive tar.bz2
$ tar cjf ../../system.tar.bz2 *
Un ordre d'idée des tailles obtenues :
$ ls -alh  /tmp/system.*
 -rw-r--r--  1 ckg  ckg   107M 17 avr 14:23 /tmp/system.tar.bz2
 -rw-r--r--  1 ckg  ckg   108M 17 avr 14:20 /tmp/system.zip

Sauvegardez apk+data (méthode intelligente)

Pour une sauvegarde en toute tranquillité, utilisez Titanium Backup ou Rerware Backup sur le play store.

Sauvegardez apk+data (méthode barbare)

~ # # Création des dossiers
~ # mkdir /mnt/sdcard/backup/apk
~ # mkdir /mnt/sdcard/backup/apk-data
~ # # Sauvegarde des apk
~ # cp /data/app/*.apk /mnt/backup/apk
~ # cd /data/data/
~ # # Purge des caches
~ # rm -r */cache/*
~ # # Dans l'absolu, lib/ est optionnel
~ # for i in *;do tar cjf /mnt/sdcard/backup/apk-data/$i.tar.bz2 $i;done;
Si vous souhaitez récupérer les sous-dossiers de /mnt/sdcard/backup via MTP il faut forcer le daemon mtp à recharger sa liste de fichier :
~ # pkill -HUP f_mtp 
il ne reste plus qu'a sauvegarder la carte SD.

Shell minimal : Clockwork Mode Recovery

La suite des opérations se fait dans le mode recovery.
$ adb reboot recovery
Une fois le recovery actif, je lance une session shell.
Le mount m'indique que seule la partition /cache est chargée :
~ # mount
rootfs on / type rootfs (rw)
tmpfs on /dev type tmpfs (rw,nosuid,relatime,mode=755)
devpts on /dev/pts type devpts (rw,relatime,mode=600)
proc on /proc type proc (rw,relatime)
sysfs on /sys type sysfs (rw,relatime)
/dev/block/platform/omap/omap_hsmmc.0/by-name/cache on /cache type ext4 (rw,nodev,noatime,nodiratime,barrier=1,data=ordered)
Visiblement, pas de /system monté. Le chemin pour trouver /system est indiqué par /cache.
Pour monter /system :
~ # mkdir /x
~ # mount /dev/block/platform/omap/omap_hsmmc.0/by-name/system /x/
On peut vérifier :
~ # ls /x/
addon.d     build.prop  framework   media       tts         xbin
app         etc         lib         su-backup   usr
bin         fonts       lost+found  system      vendor
La partition /system/ n'est pas chiffrée.
Si votre téléphone est saisi par un tiers vous pouvez le considérer comme corrompu. Je reviendrai un autre jour sur le "comment détecter la compromission d'un terminal". C'est la fin de la phase de préparation depuis le shell minimal.

Pousser la rom aokp complete

Normalement, vous devriez avoir la place de pousser la rom complete dans le /data/media de votre terminal. Depuis l'ordinateur :
$ adb push system.zip /data/media
Depuis la session shell (adb shell)
~ # unzip -o /data/media/system.zip -d /x/
Si vous obtenez "unzip: zip flags 1 and 8 are not supported", utilisez l'archive system.tar.bz2 définie plus haut alors, depuis l'ordinateur :
$ adb push system.tar.bz2 /data/media
Depuis la session shell (adb shell)
~ # cd /data/media/
~ # bunzip2 system.tar.bz2
~ # tar xf system.tar -C /x/

Fin

Avant de débrancher, il faut umount puis reboot :
~ # umount /x/
~ # reboot

2012-04-16

import org.iso.9001.*;

Semaine sans Android ... l'audit de renouvellement par BVC s'est très bien passé. Ouf. Cependant d'après l'auditeur, le système documentaire gagnerait en pertinence en étant plus informatisé.

Note pour le futur, la maîtrise de la diffusion d'un document peut reposer en partie sur l'utilisateur final. Lorsque ce dernier effectue une consultation non maitrisée (document imprimé ou PDF) il s'engage à toujours se référer au document original. Cette pirouette est permise par l'esprit général du 6.2.1 que je limitais, à tort, à l'incidence sur la conformité du produit.

J'ai commencé ma formation avec le référentiel 1994 pour lequel le report d'une telle responsabilité était inenvisageable. 12 après la publication du référentiel 2000 il me reste des réflexes à dépoussiérer.

Comment prouver la vérification / validation / approbation du document ? Dans un premier temps, plaider la faible complexité de l'organisme qui m'emploie et me contenter de la mise à disposition du PDF sur un serveur partagé en lecture seule.
Dans un second temps, il faudra mettre en place une PKI et signer ces foutus PDF.

Il va falloir jouer avec iText et BouncyCastle ... avec une application tablet Android ? :-)