vendredi 20 février 2015

10 things to know about Device Owner Apps in Android 5

Device Owner Apps is another key feature of Android for Enterprise available in Android 5 (API 21). Device Owner app is a special kind of Admin app that help you create users, and configure global settings without the need to be a privileged system app.

Let’s see 10 facts to know about Device Owner Apps.

1. It can be set via NFC

Because this app has some special “power”, it can only be set using special means.

The official way of setting a Device Owner App is by using a NFC message. You could use a classic NFC tag, or Android Beam.
This message has a special MIME type (application/com.android.managedprovisioning) and contains at least 3 properties :

For instance, on an Android Beam, you could create an Activity implementing NfcAdapter.CreateNdefMessageCallback and NfcAdapter.OnNdefPushCompleteCallback, and have the following code in your createNdefMessage()

import static android.app.admin.DevicePolicyManager.*;
... 
 Properties p = new Properties();
 p.setProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, "com.test.my_device_owner_app");
 p.setProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION, "https://raw.githubusercontent.com/.../MY_APP.apk");
 p.setProperty(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM,AGt-"ELmIalMx6tdNKWbKBZ4YdGo"); 
 ByteArrayOutputStream bos = new ByteArrayOutputStream();
 OutputStream out = new ObjectOutputStream(bos);
 p.store(out, "");
 final byte[] bytes = bos.toByteArray();

 NdefMessage msg = new NdefMessage(NdefRecord.createMime("application/com.android.managedprovisioning", bytes));

Some additionnal information may also be provided like Wifi informations, proxy, locale, time zone, email address, etc. All other options are available under the EXTRA_PROVISIONING_XXXXconstants in DevicePolicyManager

The checksum of the APK is calculated with :

cat MY_APP.apk | openssl dgst -binary -sha1 | openssl base64 | tr '+/' '-_' | tr -d '='

(Under Windows, you could use a cygwin-like bash (like Babun for instance) to provide these commands.)

Important thing to know : It has to be done on an unprovisioned device , on the FIRST step, once your device is booted (if your device is already provisionned, go to Settings > Backup & Reset > Factory data reset to reset it. Beware : all your data will be lost). Once the NFC message is delivered, the second step will ask you to setup the Wifi connection. Once the Wifi connection is validated, the APK is donwloaded and your device is provisioned.
Also make sure the device screen are unlocked. NFC is not working when the screen is locked on Android.

see also http://stackoverflow.com/questions/21183328/how-to-make-my-app-a-device-owner

2. it can also be set via a shell command

Another way to set a Device Owner App on your device is by using the dpm command line tool. It’s useful when you are developping or when you have a full access on a rooted device.
In this case, your app will have to be installed first, like any other casual app, and then you could set it as a Device Owner App using the shell (mostly through adb if you are developing)

for instance :

$adh shell dpm set-device-owner com.test.my_device_owner_app/.MyDeviceAdminReceiver

The dpm utility is really simple actually. Its goal is to create a new file called device_owner.xml under /data/system/device_owner.xml (if the storage is not encrypted) that references the Device/Profile owner apps.

The android platform is then reading this file to check which application is considered as Device Owner or Profile Owner App.

On a rooted device, you could indeed, create this file by yourself, but since the dpm tool is doing it, you’d better use it (DRY principle) :

Runtime.getRuntime().exec(dpm set-device-owner com.test.my_device_owner_app);

Also notice that this tool is working only if no account is set for the user (make sure no account is set in Settings > Accounts) before its use.

More information on this tool is provided in a previous post.

3. There can be only one Device Owner App per device…

Once the Device Admin app is set, it’s absolutely possible to update new versions of this app in a normal way, without re-provisioning it through NFC or the dpm tool).

The file /data/system/device_owner.xml contains the package name of applications registered as Device Owner / Profile Owner apps. Updating these apps won’t infer on this file since the package name stays the same. Just make sure you’re always using the same certificate as the one you previously used when first setting you device owner for the first time (which is a standard rule of security for every application update in Android anyway).
Permissions can also be updated without the need to re-provision it through NFC, nor dpm.

you could also take a look at this question on SO.

4. … but there may be a Profile Owner for each user.

A Profile Owner App is, like Device Owner App, a specific kind of Admin App.
By definition there can be only one Device Owner app, but there may be a Profile Owner for each user.

The difference with the Device Owner App is that a Device Owner App is per-device basis, while a Profile Owner app is on a per profile basis. That means that while only one Device Owner App can be set, multiple Profile Owner app can be set, one on each user profile.

A Profile Owner App, is a subset of Device Owner App, that allows only certain API specific to users and profile. Profile Owner Apps can typically restrict the applications accessible or configure the settings for a specific user.

Setting a Profile Owner App can be done from a Device Owner App, when creating a new user, using the createAndInitializeUser() method where the DeviceAdminReceiver’s implementation represents the component that handle the Profile Owner.

When the user is created with createandInitializeUser(), the application corresponding to the package associated to the DeviceAdminReceiver is installer in the user’s profile. Internally, the API IPackageManager.installExistingPackageAsUser and the user is then started in background. The profile’s DeviceAdminReceiver and its associated package are considered as admin and Profile Owner for this user

When developping, you could also use dpm and specify for which user you want to set this app as a Profile Owner.

$adh shell dpm set-profile-owner com.test.my_device_owner_app/.MyDeviceAdminReceiver <USER_ID>

Notice that the use of dpm set-profile-owner is only possible for a user that is not set up (not yet initialized).

There’s also a way to set an application as a profile owner, without the need of a Device Owner App, or dpm. In this case, the user will be prompted to accept this application as a profile owner by sending a specific Intent (not detailed here though).

5. They can not be unset by any user …

… even the owner is not allowed to unset the Device Owner app!

Because a Device Owner app has full control over the device. The Device Owner app can not be modified by the user and the only way of removing this app is to do a factory reset.

You could reset your device, by going into Settings > Backup & Reset > Factory data reset. If this option is disabled, you’ll have to reboot your device in Recovery Mode and factory reset from here (depending on your hardware).

dpm utility offers no way to unset the Device Owner application.

Fortunately, there ‘s a way a unset it programmatically : ask the app to unset by itself. You can use DevicePolicyManager.clearDeviceOwnerApp() to make sure your app is not set as a Device Owner app anymore.

For example, in one of your Device Owner App’s activity :

DevicePolicyManager mDPM = (DevicePolicyManager) this.getSystemService(Context.DEVICE_POLICY_SERVICE);
mDPM.clearDeviceOwnerApp(getPackage());

6. They can dynamically manage users

A Device Owner app can create a new user, swith to its session, remove it.
This is a big advantage over other normal apps which are not autorized to do this.

2 methods are available to create users :

  • createUser() creates a new user, but this user which is not provisionned nor restricted. In a programmatic sense, no Profile Owner app is associated to it.
  • createAndInitializeUser() creates a new user and associate a Profile Owner app which is used to provision and restrict the user programmatically.

Device Owner apps also provides a way to switch to a user session from its handle.
This is particulary useful when you create a user and want to switch directly to its session.
A Device Owner App can also remove a specific user from its handle.

DevicePolicyManager mDPM = (DevicePolicyManager) this.getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName mDeviceAdminRcvr = new ComponentName(this, DeviceAdminRcvr.class);
UserHandle newUserHandle = mDPM.createUser(mDeviceAdminRcvr, "Temporary user");
mDPM.switchUser(mDeviceAdminRcvr, newUserHandle);

...
// once done with the user
mDPM.removeUser(mDeviceAdminRcvr, newUserHandle);

7. They can restrict the apps accessible for a user

You can easily limit the applications accessible for your user by “hiding” them using setApplicationHidden.

Example of use to hide the Google+ app :

DevicePolicyManager mDPM = (DevicePolicyManager) this.getSystemService(Context.DEVICE_POLICY_SERVICE);
ComponentName mDeviceAdminRcvr = new ComponentName(this, DeviceAdminRcvr.class);
mDPM.setApplicationHidden(mDeviceAdminRcvr, "com.google.android.apps.plus", false)

You can set them visible in a same way by placing the last flag to true.
Also notice that, you will not see notifications of hidden applications.

Bad thing is that you cannot, programmatically and silently, install new applications for a user. To do this, you’ll need to have the permission INSTALL_PACKAGES, and this permission is not given for third party apps. You could still use it if your app is a system app or on a rooted device.

8. They can restrict the device and configure its settings

Device Owner App and Profile Owner App ARE Admin Apps. Everything you were used to do with Admin apps, can also be done with a Device Owner app. That includes restricting the security policy (password policies, lock policies, encryption policies, camera policies) and also wipe its data, lock the device or prompt for a password.

A Device Owner app is going further by offering some settings customization, through DevicePolicyManager.addUserRestriction, DevicePolicyManager.setSecureSetting, DevicePolicyManager.setGlobalSetting

You should know that you can :

  • Disallow some settings modifications with DevicePolicyManager.addUserRestriction. The list of settings that could be disallowed is provided in the UserManager.DISALLOW_XXX constants with some specific cases depending if you are calling from a Device Owner App, Profile Owner App, or from the Owner user.
  • Modifiy Global device settings with DevicePolicyManage.setGlobalSetting like auto time, adb enabled (Only a subset of Global Settings is available though). Only a Device Owner App can call this method.
  • Modify User settings with DevicePolicyManager.setSecureSetting. Only 2 settings are modifiable like the default input method and the skipping of the first use hint. These settings can be set from a Device Owner App or a Profile Owner App.

Sadly the documentation is not clear about what settings you can set or value to use, so I’won’t go in more detail here (I’m preparing a complete article on these).

9. They can make a your app a real Kiosk app

Screen Pinning is another great feature of Android for Workplace and Education. And when it’s configured from a Device Owner App through setLockTaskPackages, it offers a real Kiosk App.

Here are some screen pinning behavior that could be set when a Device Owner App autorizes an app to be pinned :

  • Only the Back(enter image description here) button remains visible in the navigation bar. Home(enter image description here) and Overview(enter image description here) buttons are not visible
  • … and because of this, it’s impossible to unpin the application manually
  • You can enter the pin mode without user confirmation.

You can have more details in my blog post Android 5 Screen Pinning.

10. Create Restricted Profile is impossible once a Device Owner App is set

The Android source code indicates that the creation a restricted profile is not allowed from Settings on tablets with a device owner or phones.

Programmatically, it’s not possible to create a new profile based on an existing one. The API exists but hidden (UserManager.createProfileForUser(String, int, int)). Accessing this API via reflection cannot be done because it needs the system permission MANAGE_USERS.

You should be aware of that and because you have more power, means you’ll have to restrict your user by yourself when a Device Owner App is set. You could use all restrictions API explained earlier to do that.

Going further

For now, because the documentation is very light on these subjets, and no book is out, The best way to go further is by browsing the Android Source Code base.

Some classes are particularly interesting : LauncherApps, DevicePolicyManager, DevicePolicyManagerService, deviceAdminReceiver, UserManager

vendredi 6 février 2015

Android 5 Screen Pinning

One of the key feature of Android Lollipop “For Workplace And Education” is a new Screen mode called “Screen Pinning”. This new mode offers a true way to create Kiosk applications within the android Lollipop platform.

In the next chapters, we’ll see in more details how to use them in Android 5 (API 21).

Pin an app : the manual way

Pinning an application is not enabled by default. You’ll have to enable it by going to : Settings > Security > Advanced > Screen Pinning : On. These step only need to be done once.

You can pin an app by :

  • Launch your app
  • touch on the Overview Button (enter image description here)
  • select your app and touch the pin icon enter image description here at the bottom of the app.

A confirmation message will appear. You need to confirm this dialog message to start the pinning mode :

enter image description here

To exit the screen pinning mode :

  • Touch and hold Back (enter image description here) and Overview(enter image description here) buttons at the same time for 2-3 seconds.

7 Things to know about the manual pinning mode

  • Home (enter image description here) and Overview (enter image description here) buttons are disabled, but every time you touch one of them, you’ll get a message to help you exit the pinning mode.
  • If a Screen Lock (Pattern, Pin or Password) is activated, you can force the user to unlock the Screen before unpinning. The confirmation dialog shown when starting a app pinning will provide an extra option “Ask for unlock pattern before unpinning”.
  • If a Screen lock is activated and this option is set, you’ll go to this Screen Lock and need to unlock your screen to go Home. Once the Screen Lock is displayed, You have no way to go back your app.
  • No notifications will be shown. Even your own app’s notifications won’t show.
  • From a pinned app, you cannot start a secondary app, unless this one has the same shared user ID (which means that the sharedUserIdis set in the AndroidManifest.xml and that second application is packaged with the same certificate). Other apps’ Activities won’t be allowed to be started and doing so (by using Context.startActivity()) will simply be ignored.
  • the Status bar is invisible. No notification, time, battery charge or other status information is displayed.
  • It’s always possible to turn the device off, or to turn the volume.

This fact that the user can unpin the application and go back to the Screen Lock is annoying, we’ll see, in the next chapter, how we programmatically pin an to go even further in pinning apps.

Pin an app : the programmatic way

Screen pinning is referred as “Lock Task mode” in the documentation API.
This mode is mainly referred in 3 public API available in Activity through startLockTask()/startLockTask() and DevicePolicyManager through setLockTaskPackages() . We’ll see also see other API useful to subscribe to Lock Task’s events, and to get current’s activity state.

There are actually two Lock Task modes, depending on whether you are “authorized” by a Device Owner App or not. These two modes also have impacts on the behavior of the pinning.

Note :Authorized” is clearly not the best word of choice for this because its sense is not clear in this context. This is however used in the API’s documentation to refer to the different level of restriction a user will have when entering in the Screen Pinning mode. Both modes can indeed pin the app, but the authorized one will put the user in a more restricted experience as we’ll see later.

Before going further, some things to know about the API :

  • It’s only possible for an app to pin itself. There is no public API to tell another app to pin (except for system-apps, which we’ll discuss later).
  • Because of this last restriction, you can only pin in authorized mode an app that you own.
  • Rebooting the device will always bring you back to the original mode : no application pinned.
  • No special permission is required.

Un-authorized Task Lock mode

Any application’s Activity can programmatically be pinned by using Activity.startLockTask().

A use-case for this is to pin the screen when a user touch a button. You could go further and only call this method if the app is not already pinned. You can check its status by using ActivityManager.isInLockTaskMode(). For example :

// Main Activity
protected void onCreate(Bundle savedInstanceState) {
    // ...
    am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
    lockBtn = (Button) findViewById(R.id.btn_lock);
    lockBtn.setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
            pin();
        }
    });
    // ...
}

void pin() {
    if(!am.isInLockTaskMode()) {
        startLockTask();
    }
}

Exiting the Pinning mode is reversely done by using Activity.stopLockTask() :

void unlockClicked() {
    stopLockTask();
} 

Because this mode you can be used freely by any developer, a confirmation message will appear to the end user to confirm entering in this mode. Additionnaly, this mode is not totally restricted compared to the “Authorized Task Lock mode”. Here are some points to know when using pinning in unauthorized mode :

  • Just after the startLockTask() is executed, the user will get the confirmation dialog “Use Screen Pinning ?”.
  • You can manually unlock the app at anytime : Touch and hold Back (enter image description here) and Overview(enter image description here) buttons at the same time for 2-3 seconds.
  • Unlock (manually or programmatically) will bring you back to the Screen lock if a Keyguard is set and the option “Ask for unlock pattern before unpinning” has been set.

Authorized Task Lock mode

Only a Device Owner app can specify which apps are authorized to pin. So this mode is typically useful for “Organizations” who want to restrict their user to only one app without no way to manually exit the app.
DevicePolicyManager.setLockTaskPackages() is used to specify which apps (though their package names) are pinnable. In other words, it specify which apps are authorized to be pinned in a more restrictive way.
This method has to be called from a Device Owner application (Setting an application as a Device Owner App is beyond the scope of this article, and is probably be an idea for a future article).

If you try to use from an application that is not set as a device or profile owner, you’ll get a SecurityException.

setLockTaskPackages() is used with a ComponentName referring to your DeviceAdminReceiver’s implementation. You also pass it an Array of authorized packages as a last parameter :

 DevicePolicyManager mDPM = (DevicePolicyManager) this.getSystemService(DEVICE_POLICY_SERVICE);
 ComponentName mDeviceAdminRcvr = new ComponentName(this, MyDeviceAdminReceiver.class);

  mDPM.setLockTaskPackages(mDeviceAdminRcvr, {"com.android.test.authorizedpinningapp"});

Once your application is authorized in your Device Admin App, you can then call the Activity.startLockTask() from the app you want to pin (as presented in earlier chapter).

6 Things to know about the Authorized Pinning mode :

  • Only the Back(enter image description here) button remains visible in the navigation bar. Home(enter image description here) and Overview(enter image description here) buttons are not visible
  • … and because of this, it’s impossible to unpin the application manually
  • You’ll have to provide a way to programmatically unpin your app. (otherwise, you’ll have no way to go back Home, unless you reboot the device)
  • You can enter the pin mode without user confirmation.
  • An app which is authorized though the use of setLockTaskPackages can still be pinned manually by going to Overview (enter image description here) and click on the Pin icon. In this case, the behavior is the same as a manual pinning (confirmation message, etc.).
  • If the Back button is going back to another activity, a Toast appears “Unpinning is not allowed by your Organization”

Task Lock events

DeviceAdminReceiver’s onLockTaskModeEntering and onLockTaskExiting methods can be used in your Device Owner App to subscribe to events relative to entering and exiting “Lock Task mode”.
All pinning events are raised the same way, no matter if they are authorized or not, manually or programmatically pinned.

Here’s a short example :

public class MyDeviceAdminReceiver extends DeviceAdminReceiver {

    public void onLockTaskModeEntering(Context context, Intent intent, String pkg) {
Toast.makeText(context, "Lock task mode entered", LENGTH_LONG).show();
      // ....
    }

    public void onLockTaskModeExiting(Context context, Intent intent) {
        Toast.makeText(context, "Lock task mode exited", LENGTH_LONG).show();
       // ...
    }
}

The second parameter of onLockTaskModeEntering and onLockTaskModeExiting is relative to the sender’s intent. You can retrieve two useful information from this intent :

  • The corresponding Action by calling intent.getAction(). Depending on which method your are, you’ll get DeviceAdminReceiver.ACTION_LOCK_TASK_ENTERING or DeviceAdminReceiver.ACTION_LOCK_TASK_EXITING value.
  • The corresponding App Package by calling … no, no it’s a trap ! getPackage() will return null here. To get the package of the app whose Lock Task has changed, you’ll have to use intent.getStringExtra(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE).

Going further :

The source code of this article is available on my Github’s repository.
The announcement on Android’s developers site.
Android Source Code on Github.
Android API : DevicePolicyManager, Activity, DeviceAdminReceiver, ActivityManager

jeudi 5 février 2015

Supervision JVisualVM de Tomcat 7 sous Vagrant

Le contexte en quelques mots :

  • J’ai une VM sous Vagrant (ou plus généralement sur un VirtualBox) headless (type Ubuntu)
  • Le VM fait tourner un serveur Tomcat 7
  • Je souhaite superviser ce serveur Tomcat 7 via JMX depuis le Host VirtualBox en utilisant JVisualVM (ou tout autre client JMX).

Paramétrer Tomcat :

Ce qu’il faut savoir lorsque l’on (active le serveur JMX de Tomcat via setenv.sh)[http://tomcat.apache.org/tomcat-7.0-doc/monitoring.html#Enabling_JMX_Remote], c’est qu’il créé un port JMX additionnel avec un numéro aléatoire. C’est problématique dans le cas ou nous souhaitons nous connecter derrière un firewall, mais c’est également problématique dans le cas d’un accès depuis Vagrant car les ports entre le Host et le Guest sont forwardés explicitement dans le Vagrantfile (ou dans la configuration VirtualBox). Et comme nous ne pouvons pas savoir quel port forwarder, nous ne pouvons pas assurer une connexion JMX.

L’idée est de s’appuyer sur le JmxRemoteLifecycleListener pour fixer les numéros de ports utilisé lors de la connexion JMX, à travers les attributs rmiRegistryPortPlatform (le port normallement fixé via -Dcom.sun.management.jmxremote.port) et rmiServerPortPlatform (le port au numéro créé aléatoirement).

Editer le fichier <TOMCAT_HOME>/conf/server.xml et ajouter le noeud Listener. Dans mon exemple je mets les ports 10100 et 10101. A adapter si besoin :

<Server port="8005" shutdown="SHUTDOWN">
  ...

  <Listener className="org.apache.catalina.mbeans.JmxRemoteLifecycleListener"
    rmiRegistryPortPlatform="10100" rmiServerPortPlatform="10101" />

  ...
</Server>

Dans le fichier <TOMCAT_HOME>/conf/setenv.sh, il n’est plus nécessaire de spécifier le port, on limite au strict minimum, à savoir : l’activation de JMX, et les flags permettant de se connecter dans authentification et sans SSL (à adapter selon les besoins pour ces derniers).

 CATALINA_OPTS="$CATALINA_OPTS \
   ...
   -Dcom.sun.management.jmxremote \
   -Dcom.sun.management.jmxremote.authenticate=false \
   -Dcom.sun.management.jmxremote.ssl=false"

Paramétrer Vagrant

Pour assurer une communication entre le Host et le Guest, il faut assurer un Port forwarding.
Pour plus d’informations sur la communication entre Guest -> Host et Host -> Guest, aller voir cette réponse sur StackOverflow.
Notre Vagrantfile peut donc maintenant être configurer pour assurer le forward des ports du Guest vers le Host :

  config.vm.network "forwarded_port", guest: 10100, host: 10100
  config.vm.network "forwarded_port", guest: 10101, host: 10101

Paramétrer JVisualVM

Malgré que les ports soient forwardés sur notre Host avec les mêmes ports, nous ne pouvons pas utiliser les adresses 127.0.0.1 ou localhost pour la connexion depuis JVisualVm : ces adresses sont associés à l’interface réseau LoopBack (ma machine local - mon Host - n’expose pas directement ces ports. Ils ne sont visible en local que par l’adapteur VirtualBox).
Il est donc nécessaire d’utiliser l’adresse IP associé à l’interface réseau VirtualBox. Pour connaitre cette adresse , sous Windows :

C:\> ipconfig /all
  ...
Carte Ethernet VirtualBox Host-Only Network :

   Suffixe DNS propre à la connexion. . . :
   Description. . . . . . . . . . . . . . : VirtualBox Host-Only Ethernet Adapter
   Adresse physique . . . . . . . . . . . : 08-00-27-00-7C-55
   DHCP activé. . . . . . . . . . . . . . : Non
   Configuration automatique activée. . . : Oui
   Adresse IPv4. . . . . . . . . . . . . .: 192.168.56.1(préféré)
   Masque de sous-réseau. . . . . . . . . : 255.255.255.0
   Passerelle par défaut. . . . . . . . . :
   NetBIOS sur Tcpip. . . . . . . . . . . : Activé

Ici notre adresse est 192.168.56.1 : c’est cette adresse IP que nous devons utiliser pour JVisualVM.

Dans JVisualVM, Créer un nouveau Remote avec cette adresse :

  • Clic droit sur Remote > Add Remote Host > 192.168.56.1.
  • Advanced Settings > Port 10100, puis valider
  • Clic-droit sur le Remote nouvellement créé, puis Add JMX Connection.
  • renseigner Connection:192.168.56.1:10100, puis valider.
  • La connexion apparait dans la partie Local. Sélectionner le connexion, puis clic-droit > Open.

vendredi 30 janvier 2015

Where is stored my system APK ?

Given a package name of a system app, how can I get its APK path ?
First, connect your device and from the platform-tools, and do a :

adb shell dumpsys > dumpsys.txt

Now, you can edit the file dumpsys.txt with your favorite editor and search for the following string :
Package [<YOUR_PACKAGE_NAME>]

For example : with the package name com.google.android.setupwizard
you’ll have :

 Package [com.google.android.setupwizard] (bf142e1):
    userId=10018 gids=[3003]
    pkg=Package{33203806 com.google.android.setupwizard}
    codePath=/system/priv-app/SetupWizard
    ...

You see that codePath is interesting here. You can now easily navigate to thiscodePath to get the name of the APK , by listing the files available :

$ adb shell
$ cd /system/priv-app/SetupWizard
$ ls 
SetupWizard.apk
arm64

To backup this APK, you can now simply run the following :

adb pull /system/priv-app/SetupWizard/SetupWizard.apk SetupWizard-backup.apk

lundi 12 janvier 2015

Android Shell command DPM : Device Policy Manager

Device Policy Manager is available through the command line tool dpm and cand be use in an ADB shell. This tool allows you to set an application as Device Owner or Profile Owner without the need to provision it through NFC. Useful when developing !
The first thing to do is to install the application as a normal one, and then set this application as device/profile owner.
Usage :
usage: dpm [subcommand] [options]
usage: dpm set-device-owner <COMPONENT>
usage: dpm set-profile-owner <COMPONENT> <USER_ID>

dpm set-device-owner: Sets the given component as active admin, and its package as device owner.
dpm set-profile-owner: Sets the given component as active admin and profile owner for an existing user.
The parameter <COMPONENT> is composed of package-name/class-name of the DeviceAdminReceiver class you implemented in your Device/Profile Owner application. It splits the String at the first / taking the part before as the package name and the part after as the
class name. If the / is immediately followed by a . then the final class name will be the concatenation of the package name with the string following the /.
com.foo.mypackage/com.foo.mypackage.MyDeviceAdminReceiver will become package=com.foo.mypackage and class=com.foo.mypackage.MyDeviceAdminReceiver.
You could shorten the component to com.foo.mypackage/.MyDeviceAdminReceiver as well.
Example :
adb shell 
dpm set-device-owner com.foo.deviceowner/.DeviceAdminRcvr
The parameter <USER_ID>is the serial number of the user. 0 is a constant for the owner of the device. For any other user, you could programmatically get the current user id with the following code :
UserManager userManager = (UserManager)getSystemService(Context.USER_SERVICE);
UserHandle me = android.os.Process.myUserHandle();
long serialNumber = userManager.getSerialNumberForUser(me);
Notice that once the Device Owner application is set, it cannot be unset with the dpm command. You’ll need to programmatically use the DevicePolicyManager.clearDeviceOwnerApp() method or factory reset your device.
UPDATE:
“Device owner can only be set on an unprovisioned device, unless it was initiated by “adb”, in which case we allow it if no account is associated with the device” says the source code. So, make sure you don’t have any account (like Gmail) associated to your current user set before using the dpm command.
sources : Dpm.java

jeudi 27 novembre 2014

La Loi de Dietzler

La Loi de Dietzler, énoncée par Neal Ford lors de sa conférence “Abstraction Distraction” ( et qui n’est pas sans rappeler le Principe de Pareto), évoque une loi empirique selon laquelle un outil informatique présentant une abstraction élevée ne pourra jamais répondre à 100% des besoins de l’utilisateur.

Cette loi a été initialement rapportée par Terry Dietzler, un collègue de Neal Ford, qui travaillait alors sur des projets Access. Neal Ford l’étend à tous les Langages de 4ème génération.

 Il définit trois catégories :

  •  80% des besoins seront rapides et facile à créer. 
  • 10% des besoins seront possibles à créer mais nécessiteront d’adapter l’outil, le contourner, le “tordre”. 
  • 10% des besoins restants seront impossibles à créer car l’utilisateur sera emprisonné par une abstraction trop élevée. 
 Si un outil est prévu pour un cadre trop idéal et présente un niveau d’abstraction trop élevé sans offrir de possibilités de sortir de ce cadre, alors l’utilisateur va se sentir frustré de ne pas pouvoir en tirer parti intégralement et va, à terme le délaisser.

 Selon Ford, les L4G suivent ce cadre : ils permettent d’implémenter très rapidement des problématiques générales, mais présentent trop d’abstractions ce qui rend compliqué - voire impossible - l’implémentation des cas particuliers. En pratique, on se rend compte qu’aucun cadre n’est idéal et que beaucoup de développeurs - si ce n'est tous - ont besoin d’implémenter des cas particuliers.

 Ce que l’on peut tirer de cette loi, c’est que si l’on souhaite mettre en place un outil offrant un niveau d’abstraction élevé, il faut toujours laisser à l’utilisateur la possibilité d’accéder à la couche de plus bas niveau (sous cette abstraction), pour qu’il puisse prendre en compte les exceptions représentées par les cas particuliers à son contexte.

samedi 4 octobre 2014

Jenkins et Scritpler : comprendre l'execution en mode distribué

Pour la mise en place de jobs de construction un peu tordu, la définition dans Jenkins est vite compliquée à gérer.
Il est plus judicieux de créer de scripter son job dans ce cas. Groovy est un intéressant, notamment pour les développeurs issus du monde Java.

Scriptler permet d’exécuter des scripts Grrovy sur Jenkins. Il est intéressant sur plusieurs points :

  • Permet de centraliser les scripts à un même endroit. Facilite la maintenance et la réutilisabilité
  • Les scripts sont paramétrables.
  • les scripts sont commités sur un repo Git local accessible via http://MON_SERVER_JENKINS/scritpler.git à chaque modification. C’est idéal pour pouvoir par la suite pusher vers d’autres repos et assurer ainsi leur sauvegarde. Dommage pour ce point il n’est pas possible de créer de hooks pour pusher automatiquement vers un repository remote, car JGit (le moteur Git de Jenkins) ne prends pas en compte les hooks. Il faudra donc trouver une autre solution pour pusher les commits vers un repo remote (via un Job Jenkins par exemple)
  • offre la possibilité de gérer l’exécution sur le master ou le slave. C’est sur ce point qu’il est utile de connaitre quelques subtilités.
  • Le job Jenkins execute les scripts Groovy de scripter en les référencant dans un build Step.

Scriptler possède deux systèmes d’exécution de scripts, pas ou peu documenté, voici les subtilités à connaitre lors d’un exécution en mode distribué :

Execution sur Master

Appelé “Restriction - Script is always executed on Master“. Dans ce mode, le script s’exécute sur le master dans la même VM que l’exécution du Job :

  • On dispose de l’API de Jenkins pour interagir avec les informations de build (par exemple, renommer un build).
  • On dispose des logs de construction.
  • On dispose des variables d’environnements du build dispo via http://MON_SERVEUR_JENKINS/env-vars.html/?.
    Attention : certaines variables sont relatives à l’exécuteur. Par exemple, $WORKSPACE indique le chemin du workspace sur l’exécuteur et donc, en mode distribué, sur le slave d’exécution.
  • Si l’exécuteur est un slave, le workspace - et donc les sources - ne sont pas accessibles.

Execution sur Slave

Type de script par défaut. Dans ce mode, le script s’exécute sur la même machine que l’exécuteur Jenkins (donc le slave) dans une VM différente. Il s’agit à un mode equivalent à l’execution de script via une commande groovy externe, hors Jenkins.

  • On ne dispose pas de l’API Jenkins.
  • On ne dispose des variables d’environnements du build. Il faudra passer en paramètres du scripts les variables dont on a besoin.
  • On dispose du workspace et donc, de toutes les ressources checkoutées, construites, etc.
  • On n’as pas accès aux logs construction.

En mode distribué, on se rend compte donc de certaines limitations.
un exemple concret :
Lire une information d’un fichier du workspace pour alimenter une description de build n’est pas faisable en mode distribué. Les sources du workspace sont sur le slave, et l’API de modification de description de build n’y est pas disponible (elle est disponible seulement sur le master). Mon billet sur “Afficher la révision comme numéro de build” n’est donc pas fonctionnel en mode distribué… :-(

A suivre…

mercredi 1 octobre 2014

Retour de DroidCon 2014

Pour cette seconde édition française, la conférence a eu lieu le 22 et 23 Septembre 2014 à Paris. 500 personnes étaient présentes. Trois thèmes sont abordés :

  • Android Everywhere
  • UI/UX
  • Android Development

Je suis loin d’être exhaustif, mais voici un rapide compte-rendu de ce que j’ai vu voir et ressentir de cette conférence.

Android Everywhere

Evidemment le gadget de cette année, c’était la montre connectée. Google ne pouvait évidemment pas reprendre Android tel quel car l’interface est trop petite et doit être adaptée : d’ou l’arrivée d’Android Wear : l’OS spécialisé pour les Wearables.

D’un point de vue ergonomie, on réduit l’approche disruptive de l’utilisation du téléphone (on checke son téléphone, pour vérifier si on a pas de messages, d’appels manqués, tout ça pendant qu’on parle à des amis). L’idée est donc de checker l’information plus souvent, plus rapidement, mais surtout que ce soit l’information qui arrive à l’utilisateur par rapport à son contexte : “show me the information before I even know I need it”. L’information est ensuite oubliée - approche “Fire and Forget”.

Concernant l’ergonomie, on est principalement sur des interactions minimalistes switch/tap. Pour les commandes plus avancées, on utilise la commande vocale “OK Google”.
La montre sous AndroidWear est un objet connecté, certes, mais surtout un objet “compagnon”. Elle a besoin d’un device maitre pour interagir. Dans le cas de gestion de la commande vocale, la montre se connecte au téléphone, puis le téléphone envoi la commande vocale aux serveurs de Google qui analysent puis redescendent l’information vers le téléphone, puis vers la montre.

On a donc 2 types d’applications :

  • Les notifications : toutes les applications qui apparaissent sur le téléphone apparaissent également sur la montre. Pas de développement spécifique dans ce cas.
  • Les applications. utilisées pour envoyer des données. Pour un UI spécifique, ou encore pour les commandes vocales.

Quelques ressources sympa :

UI/UX

Facebook, Twitter sont présents pour nous parler de leur processus de développement de leur application mobile. Un des points qui ressort de ces conférences, c’est que les applications mobiles ne sont clairement plus des “gadgets que l’on propose en plus de l’application Web”.

Il y’a clairement eu des ratés sur le développement d’applications mobiles. Un cas concret : Facebook et sa première application mobile : développée en HTML5 pour simplifiée les développements. Résultat : les développements ont en effet été simplifié, mais expérience utilisateur n’as pas été au rendez vous. Il est nécessaire de revoir la philosophie Web et de se tourner vers une philosophie “Mobile-first”, dans laquelle, on privilégie l’expérience utilisateur. Performance, ergonomie et interface adaptée au device (proche du natif).

Il est important aussi de prendre en compte l’expérience Cross-Plateforme comme favorisant la boucle d’engagement de l’utilisateur : Lui proposer un même application sur plusieurs platformes assure qu’il utilise cette application plus fréquemment. L’idée étant de fidéliser un utilisateur avec un application, peut importe le support. Plus il s’engage et plus la monétisation est potentiellement importante.
(c’est l’approche proposée par Amazon GameCircle pour les jeux : on commencer une partie sur un device et on la continue sur un autre).
Mais avant tout il faut savoir ce que veut l’utilisateur. le comment et pas le pourquoi. L’utilisation et pas application.
L’idée pour connaitre l’engagement, c’est de mesurer, d’apprendre des utilisateurs. Mesurer, Itérer et améliorer.
L’approche A/B Testing et de feature flipping est beaucoup mise en avant pour le développement mobile. Le Feature-flipping a une vraie plus-value, notamment pour corriger rapidement des features non compatibles sur certains devices.
Enfin, il est toujours utile de tester l’application sur soi même : Eat you own dogfood est également une bonne pratique pour tester l’application en condition réelle sur soit même. Pour les bêta tests, il est bon de savoir que Google propose des canaux de distributions pour les applications en cours de développement : Google Play Alpha Channel et Google Play Bêta Channel.

Côté UI, c’est évidemment le Material Design est qui est la grosse évolution graphique apportée par Android L. On retrouve des concepts nouveaux (Floating Action Button) ou qui sont mis beaucoup plus en avant (Cards). Ces deux dernières fonctionnalités sont rétro-compatibles avec le support v7.
Les animations et les Ripple Effets sont par contre spécifiques à Android L (API 21).
L’idée derrière Material Design est de proposer un thème Cross-platform qui devienne le standard graphique pour les applications Google, que ce soit les apps Web (Google Drive récemment), Android ou Chrome OS.

aucun rapport mais rigolo : Gource. Un outils de visualisation graphique d’un dépot SVN.

Quelques ressources intéressantes :

Android Development

Beaucoup de présentation sur des libraries ou langages permettant de simplifier les développements Android. Ce qu’il en ressort, c’est que le développement d’application Android est assez boiler-plate et qu’il faut mettre en place des approches pour simplifier le développement et faciliter la maintenance. Plusieurs approches :

  • Injection de ByteCode à la compilation via Mimic ou AfterBurner ( Stéphane Nicolas).
  • Développement en Groovy sur Android (Guillaume Laforge). Java est trop verbeux. Groovy réduit les coûts de développement, facilite la lecture et la maintenance.
  • utilisation de la Functional Reactive Programming via RxJava et RxAndroid pour fluidifier les compositions d’appels asynchrones.

Zoom sur les bibliothèques d’annotations : ButterKnife / RoboGuice (@InjectViews), Dagger / RoboGuice (@Inject), Otto / RoboGuice / EventBus (@Observes), Memento / IcePick (@Statefull), Hugo (@Log). Il serait judicieux que ces annotations deviennent des standards intégrés à une prochaine version d’Android (…mais quand? )

Zoom sur les bibliothèques pour mieux découper son application : Dagger (dependency injection), Mortar découpage MVC.

Aucun rapport mais utile : Droid@Screen, l’outil que les conférenciers utilisent pour partager leur écran pendant les conférences. Il souffre toujours d’un petit problème de lag, mais reste un outil super utile pour les présentations.

Concernant les processus de développements, les retours laissent apparaitre que les outils manquent parfois de maturité.
C’est notamment le cas sur les frameworks de tests d’intégration (Functional Testing). Deux challengers se démarquent : Robotium et Espresso. Malgré que ce dernier soit encore en version bêta, il semble plus prometteur : meilleurs performances, développement plus cohérents, système de matchers issus de Hamcrest plus adaptable.
Concernant les IDE, tout le monde semble être passé à Android Studio (lui aussi encore en bêta) et utilise Gradle pour la construction des applications.
Beaucoup de bêta et les retours laissent à penser que l’écosystème n’est pas encore tout à fait mature !

Les stands

Microsoft est présent pour nous parler de Xamarin, mais également de remettre en avant Visual Studio qui intègre Unity - la plateforme de développement de jeux 3D - et également Cordova et le debugger JS intégré.

Amazon est présent pour nous présenter son écosystem Amazon App Store, qui porte pour l’instant 240’000 applications mais qui a pour vocation de rattraper le Play Store (qui lui en a quelques 1 400 000 !). L’idée de l’Amazon App Store est de centraliser les achats ( qu’ils soient physiques, virtuels ou in-app) et de faciliter la monétisation des apps en proposant des achats One-Click in-app (Acheter un T-shirt Angry bird dans son jeu sera possible !). Ils présentent également leur gamme Fire, notamment le FirePhone et son interface Dynamic Perspective qui suit les mouvements de tête.

Intel présente les outils de tracking permettant d’optimiser les performances des applications, notamment via GPA orienté GPU ou INDE.
Il y’avait aussi Alcatel qui présentait sa gamme One Touch, notamment un téléphone “compagnon” qui se connecte en bluetooth sur une tablette trop grosse pour tenir dans la poche; ID.Apps, éditeur d’applications mobiles, Xebia, Octo, et également GenyMobile qui présentait notamment GenyMotion, son “super-fast Android Emulator”.

zoom sur : GenyMotion, anciennement AndroVM, propose une VM Android qui s’éxecute dans un VirtualBox. Il ne s’agit pas d’émulation, mais bien de VM (sur archi x86). Bien plus performant. GenyMotion intègre en plus un ensemble d’interface pour simuler les capteurs. Approche très intéressante pour les tests. avec prochainement une possibilité de scripting à la Vagrant.

Pour finir

Pour ceux qui souhaitent voir des photos, vous pouvez jeter un oeil sur Flickr BeMyApp
Tous les talks ont été filmés : je mettrai le lien dès que les vidéos seront dispo.
Vous pouvez quand même jeter un oeil sur le site officiel.

Petit apparté : Après plusieurs mois de non-blogging, je me décide enfin à reprendre l’écriture. La plateforme Blogger est sympa, mais l’éditeur WYSIWYG, me laisse un peu…perplexe, d’autant plus qu’il me crache du code HTML pas forcément tip-top que je dois me retaper à la main.
Du coup, je tente une nouvelle approche, en écrivant en Markdown dans StackEdit qui permet de publier vers Blogger (entre autre).

jeudi 17 avril 2014

Utiliser l'Omnibox pour faciliter les recherches du développeur

Le développeur est souvent amené à effectuer des recherches spécifiques pour lesquelles Google n'est pas le meilleur moteur de recherche : code source, docs d'API, icônes, librairies...

Il est possible de customiser facilement l'Omnibox de Chrome pour nous aider à faire des recherches plus rapides en préfixant rapidement par un mot-clé simple. Le tout de manière simple, paramétrable et facilement adaptable à tout contexte.

Pour l'utiliser, il suffit de préfixer la recherche dans l'Omnibox avec le mot-clé pour effectuer la recherche sur le moteur adapté.

  1. Cliquer sur la barre 'Omnibox"
  2. puis "Edit Search Engines..."
  3. Ajouter un nouveau moteur de recherche, sachant que la plupart des outils proposent des URL de recherches, il suffit de les trouver et paramétrer des "raccourcis" pour ces recherches. Dans cette URL, le %s sera remplacé par le terme de la recherche.

Quelques exemples utiles pour le développeur :
Code source
mot-clé source pour http://grepcode.com/search/?query=%s

manpages (d'Ubuntu)
mot-clé man pour http://manpages.ubuntu.com/cgi-bin/search.py?q=%s

Artefacts Maven
mot-clé artefact pour http://search.maven.org/#search%7Cga%7C1%7C%s

docs Web
mot-clé doc pour Devdocs.io

et plein d'autres :



On pourrait même imaginer des moteurs pour des ressources manquantes : Code ASCII, entité HTML, couleur, code d'erreur SGBD,

Et au sein de l'entreprise ?
Nexus, OpenGrok, ou Jenkins offrent des possibilités pour s'intégrer à l'Omnibox de Chrome de manière très simple.

pour aller plus loin.
Les sites mettant en place des descriptions OpenSearch sont automatiquement ajoutées en tant que moteur dans l'omnibox (c'est le cas notamment de StackOverflow). Pour comprendre le principe d'OpenSearch, je conseille de faire un tour sur la page dédiée et d'aller voir sur le Projet Mycroft qui recense un grand nombre de moteurs de recherches intégrable (ou pas) à l'Omnibox.


jeudi 16 janvier 2014

Arquillian, JPA et Datasets (2/2) : Utiliser Arquillian Persistence Extension

Dans la première partie de l’article sur Arquillian, JPA et Dataset, je montrais comment paramétrer nos test pour gérer les transactions, l’injection de données. Au final, le résultat est assez compliqué à mettre en place. Dans cet article, je vais faire voir comment utiliser une Extension d'Arquillian pour nous faciliter la vie.

Tout d’abord, pour répondre aux problématiques évoquées dans l'article précédent,nous pourrions utiliser DBUnit ou Unitils, mais le problème est que ces frameworks ne sont pas compatibles avec CDI. Nous allons donc utiliser "Arquillian Persistence Extension". La solution est assez jeune (encore en Alpha !) mais elle est prometteuse car elle apporte des réponses et simplifie le travail du développeur. Je vais donc entrer plus en détail sur son utilisation dans cet article.

Arquillian Persistence Extension offre plusieurs fonctionnalités :
  • Assure la gestion des transactions pour chaque test unitaires 
  • Assure l’injection de jeux de données de tests spécifiquement pour chaque test dans différent formats. Cette fonctionnalité est offerte par @UsingDataSet 
  • Compare les données en fin de test avec un jeu de test attendu. offert par @ShouldMatchDataSet 
  • Eviction du cache de second niveau entre le passage des tests. 
Je ne reviens pas ici sur les Entity et Dao expliqué au chapitre précédent, ce sont les mêmes. Je vais me concentrer sur la classe de test.

 

 Un peu de configuration

Malgré que H2 ne soit pas indiqué explicitement comme étant supportée, j’ai fais mes tests sur cette version et confirme qu’elle est fonctionnelle avec l’extension persistence.
Il faudra juste prendre en compte deux particularités : Le Dialecte doit être pris en compte dans le persistence.xml
<properties>
    <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
    <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
    <property name="hibernate.show_sql" value="true"/>
    </properties>

Le DataTypeFactory doit être prise en compte dans le fichier de configuration arquillian.xml pour pouvoir s’interfacer avec H2.
<extension qualifier="persistence-dbunit">
    <property name="datatypeFactory">org.dbunit.ext.h2.H2DataTypeFactory</property>
</extension>
Enfin, on ajoute la dépendance Maven
<dependency>
  <groupId>org.jboss.arquillian.extension</groupId>
  <artifactId>arquillian-persistence-impl</artifactId>
  <version>1.0.0.Alpha6</version>
  <scope>test</scope>
</dependency>
Une fois la dépendance Maven apportée, nous avons accès à plusieurs annotations (dont @UsingDataSet et @ShouldMatchDataSet) qui vont nous simplifier la vie.

 

La classe de test

Dans notre classe de test WeatherDaoImpl, je ne définis pas d'EntityManager, ni de UserTransaction. La prise en compte de la transaction est automatique dès lors que la méthode est annotée avec @UsingDataSet ou @ShouldMatchDataSet. Pour le développeur, pas besoin de créer des transactions manuellement, ni même d’insérer l’EntityManager à ce niveau.
@RunWith(Arquillian.class)
public class WeatherDaoTest {
  
    @EJB
    WeatherInfoDao dao;

    @Test
 ...

 

@UsingDataSet

L’annotation @UsingDataSet permet d’injecter un jeux de données. Le fichier peut être au format XML, JSON, YAML ou même SQL. Le format est automatiquement pris en compte grâce à l’extension du fichier.
@Test
@UsingDataSet("datasets/weather.json")
public void accessTemperature() {
  
    WeatherInfo info = dao.getInfoFromTown("NTE");
    Assert.assertEquals("32°C", info.getTemperature());
}

Exemple de fichier de DataSet
{
"WeatherInfo":
  [
    {
      "townCode" : "NTE",
      "townName" : "Nantes",
      "temperature" : "32°C",
      "isRainingInAnHour" : false
    },
    {
      "townCode" : "BRT",
      "townName" : "Brest",
      "temperature" : "32°C",
      "isRainingInAnHour" : false
    }
  ]
}

Le même DataSet en YAML offre plus de concision.Je pars sur ce format dans l'article, mais il faut savoir que le développeur est libre de choisir le format selon ses besoins au cas par cas (un test en JSON, un autre en XML par exemple). Cependant, je préconiserais quand même d'avoir une uniformisation des formats de fichiers pour simplifier la maintenabilité de l'application.
WeatherInfo:
  - townCode: NTE
    townName: Nantes
    temperature: 32°C
    isRainingInAnHour: false
  - townCode: BRT
    townName: Brest
    temperature: 18°C
    isRainingInAnHour: true

Quelques points à savoir sur l’utilisation des DataSets :
  • la valeur de la PK des enregistrements n’est pas obligatoire dans le DataSet. Elle sera auto-incrémentée automatiquement lors de chaque ajout. mais... 
  • Persistence extension s’appuie sur DBUnit. Lors de l”ajout, la PK est incrémentée mais la valeur de la séquences utilisée par l’AUTO_INCREMENT n’est pas mise à jour. Ceci est un important à savoir car si l’on souahite ajouter un nouvel enregistrement depuis le test, celui-ci risque de se positionner sur un enregistrement existant (le numéro 1 par exemple) et lancer une exception d’unicité (du type : A different object with the same identifier value was already associated with the session). 
  • Lorsque l’on fait des tests de création, il est donc indispensable d’ajouter explicitement les valeurs de PK dans les datasets en les positionnant à des valeurs hautes pour éviter les collisions (c’est à dire supérieur au nombre d’enregistrement qui pourraient être créés pendant les tests). 
  • Lors des tests de lecture ou de modification de données existantes, les valeurs de PK pourront être omises. 
Ainsi, dans ce second test :
@Test
@UsingDataSet("datasets/weather2.yml")
public void createNewInfo() throws Exception {
    WeatherInfo info = new WeatherInfo("LMS", "Le Mans", "28°C", false);
     
    dao.saveInfo(info);
    Assert.assertEquals(3, dao.getAllInfos().size());

}

on effectue l'injection avec le DataSet weather2.yml suivant :
WeatherInfo:
  - id: 998
    townCode: NTE
    townName: Nantes
    temperature: 32°C
    isRainingInAnHour: false
  - id: 999
    townCode: BRT
    townName: Brest
    temperature: 18°C
    isRainingInAnHour: true

 

@ShouldMatchDataSet

L’annotation @ShouldMatchDataSet ajoute une assertion supplémentaire sur l’état de la base de données attendue en fin d’un test. Il indique donc un DataSet résultat attendu. Si l’état de la base est identique au Matching Dataset, alors le test est OK. Si la base est dans un autre état, le test est en échec.
Pour les matching Datasets, il est possible (et d’ailleurs très préférable) d'exclure certaines colonnes. Par exemple, la colonne des valeurs de PK n’as en général aucun intérêt à être testée et peux donc être exclus avec excludeColumns.

@Test
@UsingDataSet("datasets/weather2.yml")
@ShouldMatchDataSet(value="datasets/expected-weather2.yml", excludeColumns="id")
public void createNewInfo() throws Exception {
  
    WeatherInfo info = new WeatherInfo("LMS", "Le Mans", "28°C", false);
    dao.saveInfo(info);
}
WeatherInfo:
  - townCode: NTE
    townName: Nantes
    temperature: 32°C
    isRainingInAnHour: false
  - townCode: BRT
    townName: Brest
    temperature: 18°C
    isRainingInAnHour: true
  - townCode: LMS
    townName: Le Mans
    temperature: 28°C
    isRainingInAnHour: false

A savoir :
  • On peut également utiliser @ShouldMatchDataSet seul, sans avoir au préalable inséré de Dataset avec @UsingDataSet. Ca peut être utile lors des tests de méthode de création. 
  • La méthode de test annotée @ShouldMatchDataSet est executée au sein d’une transaction.
@Test
@ShouldMatchDataSet(value="datasets/expected-weather2.yml", excludeColumns="id")
public void createNewInfoFromScratch() throws Exception {
     
    WeatherInfo info1 = new WeatherInfo("NTE", "Nantes", "32°C", false);
    WeatherInfo info2 = new WeatherInfo("BRT", "Brest", "18°C", true);
    WeatherInfo info3 = new WeatherInfo("LMS", "Le Mans", "28°C", false);
        
    dao.saveInfo(info1);
    dao.saveInfo(info2);
    dao.saveInfo(info3);
}

 

@Transactional

Cette annotation permet de rendre une méthode de test transactionnelle, simplement en annotant la méthode ! Cette annotation est prise en compte par le Arquillian Transaction Extension. (extension dont je n’ai pas parlé auparavant mais qui est tiré de manière transitive par le Arquillian Persistence Extension). Pour rappel, les tests classiques (simplement annoté avec @Test) ne sont pas exécutes dans un contexte transactionnel.
Par exemple, le code ci-dessous finit en erreur car il s’exécute hors transaction. (Pour rappel, le DAO est marqué pour s'exécuter au sein d'une transaction car TransactionAttribute est MANDATORY).

@Test
public void createNewInfoFromScratchWithoutTransaction() throws Exception {
  
    WeatherInfo info1 = new WeatherInfo("NTE", "Nantes", "32°C", false);
    WeatherInfo info2 = new WeatherInfo("BRT", "Brest", "18°C", true);
         
    dao.saveInfo(info1);
    dao.saveInfo(info2);
   
    Assert.assertEquals(2,  dao.getAllInfos().size());
}

Dans ce cas, le TU doit donc obligatoirement être transactionnel. Pour résoudre cette erreur, il suffit simplement d’annoter la méthode @Transactional.

import org.jboss.arquillian.transaction.api.annotation.Transactional;
... 
@Test
@Transactional
public void createNewInfoFromScratchWithTransaction() throws Exception {
    WeatherInfo info1 = new WeatherInfo("NTE", "Nantes", "32°C", false);
    WeatherInfo info2 = new WeatherInfo("BRT", "Brest", "18°C", true);
        
    dao.saveInfo(info1);
    dao.saveInfo(info2);
     
    Assert.assertEquals(2,  dao.getAllInfos().size());
}

 

En conclusion

L’extension nous permet de simplifier énormément l’écriture de tests JPA dans un contexte JavaEE par rapport à l’article précédent. Le code reste clair, facile à comprendre et la dissociation entre code et données de tests est un gros avantages pour la maintenabilité des tests.

De plus, L’extension offre des possibilités complémentaires : injection de scripts SQL, création de schéma (si utilisation hors ORM par exemple), insertion SQL en @Before/@After, définition de stratégies de Cleanup, éviction de cache de second niveau.

En revanche, ce qui pêche un peu, c’est le manque de documentation. J’ai du dépouiller le code source pour comprendre comment utiliser l’appli. Cependant, même en Alpha mais est déjà fonctionnelle et pourra vous simplifier la vie lors de la création des Tests de vos application JavaEE.

A tester donc !

Pour aller plus loin

Les sources des l'article sont disponibles sur Github.
 Les sources du projet “Arquillian Persistence Extension” sont disponibles sur Github.
Comprendre les problématiques d’unicité de valeurs de PK dans DBUnit : http://sipxconfig.blogspot.fr/2005/03/dbunit-seed-data-use-high-primary-ids.html

mercredi 8 janvier 2014

Arquillian, JPA et Datasets (1/2) : Première prise en main

Le premier article Tester son application JavaEE avec Arquillian montrait comment effectuer des tests sur des composants EJB.

Lors de l’écriture des tests unitaires, il est nécessaire de tester les composants touchant la couche persistance JPA dans un contexte transactionnel. Nous allons donc voir comment prendre en compte la persistance et les transactions lors des tests avec Arquillian.

Quelques points à savoir avant de commencer :
  • Lors du passage des tests unitaires, il est important d’isoler les données utilisés pour les tests de ceux utilisées pour l’intégration. Il est donc nécessaire de créer une base dédiée pour les tests. Créer une base "manuellement" pour chaque test est trop long et pas industriel. Il est donc nécessaire de prendre une base embarquée type HSQLDB ou H2. Ici, nous prenons une base de donnée embarquée H2 car elle est déjà intégrée dans le profile par défaut JBoss, il n’y a donc pas de configuration supplémentaire à faire dans les drivers JDBC. 
  • il est préférable de garder les TU au sein d’une transaction. L’état de la base reste propre entre chaque passage de test et les données modifiées pendant les tests doivent être rollbackées.
  • C’est lors des tests de persistance qu’Arquillian prend tout son sens par rapport à Spring Test. En effet, lors de l’écriture de l’article précédent, comme on ne travaillait qu’avec des EJB, on aurait pu utiliser Spring Test, puisqu’il interprete certaines annotations standards (notamment @Inject, @EJB). A partir du moment ou l’on utilise JPA, Datasources, Transactions, Producers et Resources alors Spring Test n’est plus en capacité de répondre à nos besoins : il nous faut un conteneur EE.
  • En corollaire du point précédent, les tests en mode "JBoss Embedded" ne sont plus possible avec JPA et les transactions. Il faudra utiliser un déploiement en Container : managed ou remote.
  • Le site d’Arquillian propose un article complet disponible à cette adresse : http://arquillian.org/guides/testing_java_persistence/ Cet article est une bonne base et nous allons voir comment aller plus loin en injectant des données et gérer les transactions.

 

Création de l’Entité JPA

L’entité JPA assure le mapping avec la base de données. Dans le cas de test, l’Entity WeatherInfo porte des infos de temps et est mappée avec une table. Les détails de l’implémentation de la table ne nous importe pas ici, on va laisser l'ORM générer la table.
L’entité doit être annotée @Entity et contenir une clé primaire représentée par @Id.
@Entity
public class WeatherInfo implements Serializable {
     
    @Id @GeneratedValue
    private long id;
    private String townName;
    private String townCode;
    private String temperature;
    private boolean isRainingInAnHour;
    ...

 

Création du Dao

Le Dao (Object d’Accès au Donnés) sur l’entité WeatherInfo est représenté sous la forme d’un EJB Stateless avec l’annotation @Stateless.
L’EntityManager est injecté dans l’EJB avec @PersistenceContext. Pour simplifier l’exemple, les méthodes du Dao effectuent des créations de requêtes depuis le PersistenceContext. J’ai annoté le Dao en tant que @TransactionAttribute = MANDATORY : Je force le Dao a être marqué pour s'exécuter au sein d'une transaction. Ce flag est une sécurité et assure qu'il est bien appelé au sein d’une transaction déjà ouverte. Pour le respect de l’architecture en couche, c’est primordial. Si le Dao est appelé depuis un service, alors la transaction est portée par le service et est utilisée par le Dao. Si un autre appelant (service IHM par exemple) appelle directement le Dao hors transaction, c’est qu’il ne respecte pas les couches de l’architecture et n’est pas "autorisé" à être appelé (ce qui finira en Exception).

@Stateless
@TransactionAttribute(TransactionAttributeType.MANDATORY)
public class WeatherInfoDao {

    @PersistenceContext
    EntityManager em;

    public WeatherInfo getInfoFromTown(String townId) {
        WeatherInfo info = em.createQuery("select w from WeatherInfo w where w.townCode='"
                          + townId + "'", WeatherInfo.class).getSingleResult();
        return info;
 }

 

Configuration JPA

Le fichier persistence.xml est disponible dans src/test/resources/test-resource.xml. Il est injecté dans l’archive grace à Shrinkwrap et renommé en persistence.xml pour que le mapping JPA soit pris en compte. (je ne m’étends pas sur les spécificités JPA ici). Ce fichier définit le jta-data-source à utiliser ainsi que les propriétés spécifiques de l'ORM, et pour lequel on demande la création du schéma (create-drop). le dialect doit être org.hibernate.dialect.H2Dialect pour H2.

<persistence version="2.0" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns="http://java.sun.com/xml/ns/persistence" xsi:schemalocation="
        http://java.sun.com/xml/ns/persistence
        http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
    <persistence-unit name="test">
        <jta-data-source>jdbc/arquillian</jta-data-source>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect" />
            <property name="hibernate.hbm2ddl.auto" value="create-drop" />
            <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

 

Déploiement du datasource

Même si je fais les tests dans une base de données embarquées, je dois définir un Datasource H2 et le déployer au sein de JBoss.
Pour les tests, je peux très bien intégrer ce datasource dans le WEB-INF du WAR pour qu’il soit déployé en même temps que l’application. (les fichiers nommés en *-ds.xml sont reconnus et déployés en tant que datasource, c’est pour cette raison que le fichier weather-ds.xml en pris en compte en tant que datasource lors du déploiement de l’application).
Cette façon de faire est donc idéale pour les micro-déploiements des tests unitaires.
Ce datasource est défini comme suit :
<datasources xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns="http://www.jboss.org/ironjacamar/schema" 
        xsi:schemalocation="http://www.jboss.org/ironjacamar/schema
        http://docs.jboss.org/ironjacamar/schema/datasources_1_0.xsd">
    <datasource enabled="true" jndi-name="jdbc/arquillian" pool-name="ArquillianEmbeddedH2Pool">
        <connection-url>jdbc:h2:mem:arquillian;DB_CLOSE_DELAY=-1</connection-url>
        <driver>h2</driver>
    </datasource>
</datasources>
Ce qui est important à voir ici, c’est le JNDI name qui est le même que celui référencé dans le persistence.xml.

 

Ecriture de notre classe de tests

Je reprends le même cas de test de l’article précédent, que je vais étoffer pour prendre en compte les tests de Dao. Dans la classe de test WeatherDaoTest, j'injecte notre Dao, puis l’EntityManager ainsi que le UserTransaction.
@RunWith(Arquillian.class)
public class WeatherDaoTest {
    @EJB
    WeatherInfoDao dao;

    @PersistenceContext
    EntityManager em;
     
    @Inject
    UserTransaction utx;

On remarque qu’il faut explicitement enlister l’EntityManager dans la transaction JTA. Cette étape est nécessaire car j'utilise les deux ressources indépendamment. Cela peut sembler anormal si on utilise JPA depuis un EJB car dans ce cas, l’enlistement est automatique, mais ici, il faut l’ajouter explicitement. Arquillian exécute les méthodes @Before et @After dans le conteneur, respectivement avant et après les méthode de tests.
 La méthode @Before est invoquée après que les injections (EJB, em et utx) aient eu lieu.
@Before
    public void preparePersistenceTest() throws Exception {
        clearData();
 insertData();
 startTransaction();
    }
J'ai besoin d’assurer une injection et suppression de données et pour chaque cas de test. Ces injections/suppressions de données sont prises en compte dans les méthodes clearData() et insertData() et appelées depuis la preparePersistenceTest().
preparePersistenceTest() est appelé dans le @Before, la transaction n’est donc pas encore ouverte. Il faut que ces méthodes aient la responsabilité d’ouverture/fermeture des transactions pour assurer l’ajout de données. L’ajout de données est réalisé en créant les entités et en les persistant :

private void insertData() throws Exception {
    utx.begin();
    em.joinTransaction();
     
    // on ajoute des objets
    WeatherInfo info1 = new WeatherInfo("NTE", "Nantes", "32°C", false);
    WeatherInfo info2 = new WeatherInfo("BRT", "Brest", "18°C", true);
     
    em.persist(info1);
    em.persist(info2);
     
    utx.commit();
    // clear the persistence context (first-level cache)
    em.clear();
}

private void clearData() throws Exception {
    utx.begin();
    em.joinTransaction();
    em.createQuery("delete from WeatherInfo").executeUpdate();
    utx.commit();
}
Une fois les ajouts de donnés effectués, la méthode preparePersistenceTest() va lancer la transaction en appelant le startTransaction() qui va ouvrir la transaction pour les tests.

private void startTransaction() throws Exception {
    utx.begin();
    em.joinTransaction();
}

Une fois le test effectué, la méthode annotée @After est appelée et dans notre cas commiter la transaction.

@After
public void commitTransaction() throws Exception {
    utx.commit();
}

Les sources complètes de l’application de tests sont disponibles ici. Je vous invite à y jeter un œil pour mieux comprendre ce qu’elles font. Les tests sont ici présentés en utilisant Wildfly !!!! Attention, pour Wildfly, il faut modifier la dépendance, et pour l’instant, Wildfly n’est pas en Release et donc le container plugin n’est pas pas encore disponible en version Release non plus. La seule différence pour nous est de prendre en compte le container dans la version adéquate, c’est à dire :

<dependency>
  <groupId>org.wildfly</groupId>
  <artifactId>wildfly-arquillian-container-managed</artifactId>
  <version>8.0.0.Beta1</version>
  <scope>test</scope>
</dependency>

 

En conclusion

C'est une approche intéressante et fonctionnelle, mais qui reste compliquée :
  • Beaucoup de code technique et qui nécessite une maitrise du cycle de vie des test unitaires et des transactions (@Before, @After...).
  • Des méthodes de chargement de données communes à toutes les méthodes de tests. Peut-être que dans certains cas de tests précis, on souhaiterait ne charger que certains lots de données : avec cette approche, on ne peut pas.
  • Une création de données depuis du code Java, explicite et donc très (trop!) verbeux lorsque l’on dispose de beaucoup de jeux de données. De plus, le code Java n'est pas non plus le format le plus adapté pour représenter des jeux de données (difficulté de lecture).
  • Pas de tests sur le jeu de données attendu. Comment assurer que les données de sorties sont bien celles que nous attendions pour notre cas de test précis ?
Bien sûr, toutes ces problématiques pourraient être résolues, mais demandent beaucoup de code technique pour être mis en place. Dans un prochain article, nous allons voir comment tirer parti de l’extension Arquillian Persistence Extension pour faciliter les tests Arquillian avec JPA et datasets.

Pour aller plus loin :