MiraSlide : Développer une application Android (4.2+) multiécran exploitant le WiDi par l'exemple

Antonin F.

Dans ce tutoriel, je vais vous présenter les différentes étapes pour développer une application Android exploitant le WiDi. À l'issue de cette lecture, vous devriez connaître les bases pour développer vos propres solutions multiécrans sous Android. Cependant, le WiDi est loin d'être une solution cantonnée au monde Android et je vous invite à lire l'article de Pierre S. sur le développement d'application WinJS (Windows 8.1).

Commentez ♪

- Titre : MiraSlide : Développer une application Android (4.2+) multiécran exploitant le WiDi par l'exemple

- Auteur : Antonin F.

- Parution : 4 février 2014

- Licence : Copyright ® 2014 Antonin F.. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Prérequis

- II. MiraSlide : Une application de Présentations

- III. Utiliser le WiDi dans Android

- III-A. La récupération du Display

- III-B. Ajout de listeners

- IV. Implémentation du code dans MiraSlide

- IV-A. La récupération du Display

- IV-B. La sélection du Display

- IV-C. La création et l'affichage de la Présentation

- IV-D. Le contrôle de la Présentation

- V. Conclusion

- VI. Sources

- VII. Remerciements

I - Prérequis

Android :  Si vous débutez sur le développement Android, je vous conseille très fortement de lire tous les guides de démarrage sur le site des développeurs Android.

Wireless Display (ou WiDi) : Cette technologie de diffusion de flux audio et vidéo développée par Intel est une alternative aux technologies Miracast du consortium Wi-Fi Alliance, et AirPlay d'Apple. Cependant, la technologie WiDi permet aux appareils le supportant de communiquer avec les récepteurs Miracast. Finalement le WiDi se veut être une « super-surcouche » à Miracast. Afin d'en apprendre plus sur le WiDi, je vous conseille de lire cette présentation par Pierre S. : Tout savoir sur le WiDi.

Enfin, si vous voulez pousser encore plus loin vos connaissances sur le Wireless Display pour Android, vous pouvez consulter la vidéo de la conférence au Paris Android User Group sur la présentation du WiDi pour Android par Xavier Hallade (slides ici).

II - MiraSlide : Une application de Présentations

Code source du projet MiraSlide

Page Google Play de l'application

Le but de cette application est de répondre à un besoin simple : les conférenciers sont soit encombrés, soit esclaves du matériel prêté, soit les deux. Ici, le Wireless Display, associé à un appareil plus léger qu'un ordinateur et pouvant donc être utilisé en télécommande, nous permettrait de diffuser et commander les diapositives depuis son téléphone/tablette, en ayant en plus dans la main les informations supplémentaires que nous apporte un ordinateur (chronomètre, notes…).

Voici donc le but très simple de MiraSlide :

1. Vous sélectionnez votre fichier de présentation, vous connectez votre appareil à un écran récepteur Wireless Display puis vous lancez la présentation.

2. Sur l'écran récepteur, la première page de votre présentation s'affiche.

3. L'écran de votre appareil vous propose alors un chronomètre, ainsi qu'une télécommande affichant la diapositive courante, les notes éventuelles, et des boutons précédent / suivant.

Maintenant que nous avons une idée plus précise de l'application que nous voulons développer, nous allons nous pencher sur les API.

III - Utiliser le WiDi dans Android

Pour ceux qui n'auraient pas bien lu les prérequis, le WiDi (et plus largement la notion d'écran externe) apparaît dans le framework d'Android avec l'API 17 (Android 4.2.2). Deux éléments essentiels sont alors créés :

- Le Display Managerva être l'interface permettant à l'application de connaître les écrans disponibles et d'interagir avec.

- Une Présentation est une vue similaire à un dialogue (dont elle est étendue), mais projetée sur un Display donné. Une des notions les plus importantes à comprendre du fait que la Présentation est une extension d'une Dialog est qu'elle est forcément attachée à une Activity. Ainsi, si cette dernière est mise en pause (si elle n'est plus visible à l'écran, en gros), alors la Présentation n'apparaîtra plus sur le Display associé (et le mode d'écran clone par défaut s'activera). Si vous retournez ensuite à votre Activity, la Présentation reviendra s'afficher sur le Display.

L'Implémentation est finalement très simple.

III-A - La récupération du Display

Elle peut se faire de deux façons. Ou bien en utilisant leMediaRouter introduit avec l'API 16 :

MediaRouter mediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
MediaRouter.RouteInfo route = mediaRouter.getSelectedRoute();
&#160;
if&#160;(route !=&#160;null) {
&#160;&#160;&#160;&#160;Display presentationDisplay = route.getPresentationDisplay();
&#160;
&#160;&#160;&#160;&#160;if&#160;(presentationDisplay !=&#160;null) {
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;// Your code...
&#160;
&#160;&#160;&#160;&#160;}

Ou bien en utilisant le Le Display Manager :

DisplayManager displayManager = (DisplayManager) mActivity.getSystemService(Context.DISPLAY_SERVICE);<br/><font color="#008200">// Selecting DISPLAY_CATEGORY_PRESENTATION prevents the DisplayManager from returning inapropriate Display,<br/><font color="#008200">// like the own device display.<br/>Display[] displays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);<br/>&#160;<br/><b><font color="#006699">if</b>&#160;(displays.length ==&#160;<font color="#009900">0) {<br/>&#160;<br/>&#160;&#160;&#160;&#160;<font color="#008200">// If there is no external display connected, we launch the Display settings. We could launch<br/>&#160;&#160;&#160;&#160;<font color="#008200">// the Wi-Fi display settings with ACTION_Wi-Fi_DISPLAY_SETTINGS but it is a hidden static value<br/>&#160;&#160;&#160;&#160;<font color="#008200">// because there may be not such settings (if the device does not have Wireless display but<br/><font color="#53575e">&#160;&#160;&#160;&#160;</font><font color="#008200">// have API >= 17).</font><br/><font color="#53575e">&#160;&#160;&#160;&#160;</font>startActivity(<b><font color="#006699">new</font></b><font color="#53575e">&#160;</font>Intent(Settings.ACTION_DISPLAY_SETTINGS));<br/><font color="#53575e">&#160;</font><br/>}&#160;<b><font color="#006699">else</font></b><font color="#53575e">&#160;</font>{<br/><font color="#53575e">&#160;</font><br/><font color="#53575e">&#160;&#160;&#160;&#160;</font><font color="#008200">// We should show a DialogBox to let the user select the display if there is more than one but</font><br/><font color="#53575e">&#160;&#160;&#160;&#160;</font><font color="#008200">// for this example we only choose the first one</font><br/><font color="#53575e">&#160;&#160;&#160;&#160;</font>Display display = displays[<font color="#009900">0</font>];<br/><font color="#53575e">&#160;</font><br/>}</colonne>
</ligne>
</tableau>
</section>
<section id="III-B">
<title>La création et l'affichage de la Presentation</title>
<signet id="3.2"/>
<paragraph>Ici non plus, rien de très compliqué. La <b>Présentation</b> n'a besoin que de l'<b>Activity</b> parente et du <b>Display </b>où être affiché pour être créée. Ensuite, la méthode <i><b>show()</b></i> affiche la <b>Présentation </b>sur le <b>Display</b>. Comme une <b>Dialog</b>, la <b>Présentation </b>comprend une méthode <i><b>setContentView() </b></i>grâce à laquelle vous pourrez définir la vue à afficher&#160;: </paragraph>
<code langage="java"><![CDATA[private&#160;void&#160;showPresentation() {
&#160;&#160;&#160;&#160;mPresentation =&#160;new&#160;MyPresentation(this, mDisplay);
&#160;&#160;&#160;&#160;mPresentation.show();
}
&#160;
06private&#160;class&#160;MyPresentation&#160;extends&#160;Presentation {
&#160;
&#160;&#160;&#160;&#160;/* constructors ... */
&#160;
&#160;&#160;&#160;&#160;@Override
&#160;&#160;&#160;&#160;public&#160;void&#160;onCreate(Bundle savedInstanceState) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;super.onCreate(savedInstanceState);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;View v = getLayoutInflater().inflate(R.layout.presentation,&#160;null);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;setContentView(v);
&#160;&#160;&#160;&#160;}
}

III-B - Ajout de listeners

Pour rendre votre système plus fiable, vous pouvez rajouter des listeners au DisplayManager afin d'être prévenu lorsque des Display sont ajoutés ou retirés. Cela est particulièrement pratique pour éviter à une Présentation de continuer à fonctionner alors que le Display qui lui est associé a été déconnecté :

mDisplayManager.registerDisplayListener(new&#160;DisplayListener() {
&#160;
&#160;&#160;&#160;&#160;@Override
&#160;&#160;&#160;&#160;public&#160;void&#160;onDisplayRemoved(int&#160;displayId) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;// Stop presentation ...
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;// Show a message to the user to reconnect the display
&#160;
&#160;&#160;&#160;&#160;}
&#160;
&#160;&#160;&#160;&#160;@Override
&#160;&#160;&#160;&#160;public&#160;void&#160;onDisplayChanged(int&#160;displayId) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;// Something happend. You should check if everything is ok before continuing
&#160;
&#160;&#160;&#160;&#160;}
&#160;
&#160;&#160;&#160;&#160;@Override
&#160;&#160;&#160;&#160;public&#160;void&#160;onDisplayAdded(int&#160;displayId) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;// If you were waiting for a display, maybe you should use it !
&#160;
&#160;&#160;&#160;&#160;},&#160;null);

IV - Implémentation du code dans MiraSlide

Nous n'allons bien évidemment pas revenir sur tout le code de MiraSlide, ce serait long et inutile, car finalement le code concernant le Wireless Display est assez court face au reste du code. Nous allons donc nous focaliser sur les points suivants.

Cependant, je vais rapidement expliquer la structure générale du code qui se divise en quatre éléments principaux.

La MainActivity est l'unique Activity de l'application. Ainsi, si la Présentation est en cours et que l'on se déplace entre les différentes vues, la Présentation ne s'arrête pas.

- Le SelectionFragment est la première vue, qui va permettre à l'utilisateur de sélectionner le fichier (PDF) contenant les diapositives, ainsi que le Display sur lequel afficher la Présentation. Enfin, il permet de lancer ladite Présentation.

- Le ControllerFragment est la vue s'affichant lorsqu'on lance la Présentation . Elle comprend un chronomètre, la diapositive courante ainsi que des boutons précédent et suivant.

- Le PdfViewerPresentation est la vue Présentation gérant ce qui est affiché sur le Display . Il comprend le moteur permettant de récupérer et d'afficher les images des diapositives demandées par le ControllerFragment.

- Le ControllerFragment est la vue Présentation gérant ce qui est affiché sur le Display . Il comprend le moteur permettant de récupérer et d'afficher les images des slides demandés par le ControllerFragment .

IV-A - La récupération du Display

- À la création du SelectionFragment, on récupère le DisplayManager et on déclare les listeners.

- Chaque fois que le SelectionFragment est lancé ou relancé (dans le onResume ), on va vérifier l'état des Display s. S'il n'y en a aucun, on propose d'afficher les paramètres d'affichages. S'il y en a un seul, on le sélectionne automatiquement, et s'il y en a plus, on propose de sélectionner le Display voulu.

Voici le code associé :

public&#160;class&#160;SelectionFragment&#160;extends&#160;Fragment&#160;implements&#160;OnClickListener, DisplayListener {
&#160;
&#160;&#160;&#160;// ...
&#160;
&#160;&#160;&#160;&#160;// The display manager is the object to get information about the different displays
&#160;&#160;&#160;&#160;private&#160;DisplayManager mDisplayManager;
&#160;
&#160;&#160;&#160;&#160;@Override
&#160;&#160;&#160;&#160;public&#160;void&#160;onCreate(Bundle savedInstanceState) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;super.onCreate(savedInstanceState);
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;// ...
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;// We get the display manager to get info about the display. We also register to any change
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;// about the (dis)connection of the displays.
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;mDisplayManager = (DisplayManager) mActivity.getSystemService(Context.DISPLAY_SERVICE);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;mDisplayManager.registerDisplayListener(this,&#160;null);
&#160;&#160;&#160;&#160;}
&#160;&#160;&#160;&#160;
&#160;&#160;&#160;&#160;@Override
&#160;&#160;&#160;&#160;public&#160;void&#160;onResume() {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;super.onResume();
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;// On resume, we check the state of each buttons.
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;checkLaunchable(getView());
&#160;&#160;&#160;&#160;}
&#160;
&#160;&#160;&#160;&#160;// We check the selection of the pdf file and the display, and we update the color of the buttons and we update
&#160;&#160;&#160;&#160;// if the "Launch Projection" button should be enabled
&#160;&#160;&#160;&#160;// @param v : the global view of the fragment
&#160;&#160;&#160;&#160;private&#160;void&#160;checkLaunchable(View v) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;checkDisplay((TextView) v.findViewById(R.id.fragment_selection_button_selectwirelessdisplay));
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;if&#160;(mActivity.getDislay() !=&#160;null) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;v.findViewById(R.id.fragment_selection_button_selectwirelessdisplay).setBackgroundResource(R.drawable.button_green);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;}&#160;else&#160;{
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;v.findViewById(R.id.fragment_selection_button_selectwirelessdisplay).setBackgroundResource(R.drawable.button_red);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;}
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;// ...
&#160;
&#160;&#160;&#160;&#160;}
&#160;
&#160;&#160;&#160;&#160;// We check the state of the external displays, update the text of the display button, and if there is only
&#160;&#160;&#160;&#160;// one external display, we auto select it
&#160;&#160;&#160;&#160;private&#160;void&#160;checkDisplay(TextView displayButton) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;Display[] displays = mDisplayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;if&#160;(displays.length >&#160;1&#160;&& mActivity.getDislay() ==&#160;null) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;displayButton.setText("Select a wireless display");
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;}&#160;else&#160;if&#160;(displays.length ==&#160;1) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;mActivity.setDisplay(displays[0]);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;displayButton.setText("Display selected "&#160;+ displays[0].getName());
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;}&#160;else&#160;{
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;mActivity.setDisplay(null);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;displayButton.setText("Connect to a wireless display");
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;}
&#160;&#160;&#160;&#160;}
&#160;
&#160;&#160;&#160;&#160;// Methods called when a display is added or removed. We change the button state if we add or remove a
&#160;&#160;&#160;&#160;// display, and we stop the presentation if there is a display removed.
&#160;
&#160;&#160;&#160;&#160;@Override
&#160;&#160;&#160;&#160;public&#160;void&#160;onDisplayAdded(int&#160;displayId) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;checkLaunchable(getView());
&#160;&#160;&#160;&#160;}
&#160;
&#160;&#160;&#160;&#160;@Override
&#160;&#160;&#160;&#160;public&#160;void&#160;onDisplayChanged(int&#160;displayId) {
&#160;&#160;&#160;&#160;}
&#160;
&#160;&#160;&#160;&#160;@Override
&#160;&#160;&#160;&#160;public&#160;void&#160;onDisplayRemoved(int&#160;displayId) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;if&#160;(mActivity.getDislay() !=&#160;null&#160;&& displayId == mActivity.getDislay().getDisplayId()) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;mActivity.stopPresentation();
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;checkLaunchable(getView());
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;}
&#160;&#160;&#160;&#160;}
}

IV-B - La sélection du Display

Comme décrit plus haut, le bouton de sélection du Display va évoluer en fonction des Display connectés à l'appareil :

- Si aucun Display n'est connecté, le bouton affiche Connect to a wireless display . Si l'utilisateur clique dessus, le code suivant est appelé. La fenêtre des paramètres d'affichage du téléphone est alors affichée. Si l'appareil est compatible Wireless Display, un bouton  Screen mirroring , ou Wireless Display ou une traduction devrait apparaître (cf image ci-dessous). En cliquant dessus, la liste des appareils visibles compatibles Miracast apparaît. L'utilisateur n'a plus qu'à cliquer dessus pour s'y connecter.

if&#160;(displays.length ==&#160;0) {
&#160;&#160;&#160;&#160;// If there is no external display connected, we launch the Display settings. We could launch the Wi-Fi display
&#160;&#160;&#160;&#160;// settings with ACTION_Wi-Fi_DISPLAY_SETTINGS but it is a hidden static value because there may be not such settings
&#160;&#160;&#160;&#160;// (if the device does not have Wireless display but have API >= 17).
&#160;&#160;&#160;&#160;startActivity(new&#160;Intent(Settings.ACTION_DISPLAY_SETTINGS));
}

- Si un seul Display est connecté, ou si un Display a déjà été sélectionné, le bouton affiche Display selected NOM_DU_DISPLAY . Si l'utilisateur clique dessus, le même code que lors du cas où plusieurs Display s sont connectés est exécuté.

- Si plusieurs Display s sont connectés, le bouton affiche Select a wireless display . Si l'utilisateur clique dessus, le code suivant est appelé. Une Dialog box s'ouvre, listant la liste des Display s disponibles. Si l'utilisateur clique sur un des Display s, ce dernier est alors sélectionné et on se retrouve dans le cas précédent.

// If there is one or more external display, we show a dialog box with the list of the display.
// The user can select the display he wants, or close the dialog.
final&#160;ArrayAdapter arrayAdapter =&#160;new&#160;ArrayAdapter(mActivity, android.R.layout.select_dialog_singlechoice);
for&#160;(int&#160;i =&#160;0; i < displays.length; i++) {
&#160;&#160;&#160;&#160;arrayAdapter.add(displays[i].getName());
}
&#160;
&#160;AlertDialog.Builder builder =&#160;new&#160;AlertDialog.Builder(mActivity).setIcon(R.drawable.ic_launcher).setTitle("Select a display")
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;.setNegativeButton("cancel",&#160;null).setAdapter(arrayAdapter,&#160;new&#160;DialogInterface.OnClickListener() {&#160;
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;@Override
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;public&#160;void&#160;onClick(DialogInterface dialog,&#160;int&#160;which) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;// When the user choose a display through the dialog, we set it in the parent Activity and update
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;// the state of the buttons.
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;mActivity.setDisplay(displays[which]);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;checkLaunchable(getView());
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;}
});
builder.show();

IV-C - La création et l'affichage de la Présentation

Lorsque le Display est sélectionné, ainsi qu'un fichier PDF, le bouton Launch Projection est activé. Lorsque l'utilisateur clique dessus, la Présentation est créée et affichée. On bascule alors l'utilisateur sur la vue Controller . Voici le code exécuté :

// SelectionFragment.java
&#160;
// ...
&#160;
@Override
public&#160;void&#160;onClick(View v) {
&#160;&#160;&#160;&#160;if&#160;(v.getId() == R.id.fragment_selection_button_launchprojection) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;// On click on the "launch projection" button, we... launch the projection
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;mActivity.launchPresentation();
&#160;
}

// MainActivity.java
&#160;
// ...
&#160;
// Set the presentation (created in the selection fragment). This method creates listeners to control the visibility of the controller fragment and if the
// mShowHideControllerActionBarButton should be enabled. Then it shows the presentation.
//
// @param presentation : the presentation to show
//
public&#160;void&#160;launchPresentation() {
&#160;&#160;&#160;&#160;mPresentation =&#160;new&#160;PdfViewerPresentation(this, mDisplay, mPdfPath);
&#160;
&#160;&#160;&#160;&#160;mPresentation.setOnShowListener(new&#160;OnShowListener() {
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;@Override
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;public&#160;void&#160;onShow(DialogInterface dialog) {
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;showHideController(true);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;enableShowHideControllerActionBarButton(true);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;mControllerFragment.notifyViewPager();
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;}
&#160;&#160;&#160;&#160;});
&#160;&#160;&#160;&#160;mPresentation.setOnDismissListener(new&#160;OnDismissListener() {
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;@Override
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;public&#160;void&#160;onDismiss(DialogInterface dialog) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;showHideController(false);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;enableShowHideControllerActionBarButton(false);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;}
&#160;&#160;&#160;&#160;});
&#160;
&#160;&#160;&#160;&#160;mPresentation.show();
}

L'initialisation de la Présentation est très simple, mais le code peut paraître un peu compliqué. Cela est dû à la préparation du PDF et de son affichage. Ce qu'il faut retenir est que l'initialisation se fait dans le constructeur, et que la création de la vue à afficher, et l'affichage de la première diapositive se fait dans le OnCreate(Bundle) , et est appliqué grâce à la méthode setContentView(View) . Voici le code très simplifié :

// PdfViewerPresentation.java
&#160;
// ...
&#160;
// Constructor. It creates the Presentation, then load the display info and the Pdf to show
public&#160;PdfViewerPresentation(Context context, Display display, String pdfFilePath) {
&#160;&#160;&#160;&#160;super(context, display);
&#160;&#160;&#160;&#160;mPdfFilePath = pdfFilePath;
&#160;
&#160;&#160;&#160;&#160;// ...
}
&#160;
@Override
public&#160;void&#160;onCreate(Bundle savedInstanceState) {
&#160;&#160;&#160;&#160;super.onCreate(savedInstanceState);
&#160;
&#160;&#160;&#160;&#160;createContentView();
&#160;
&#160;&#160;&#160;&#160;showPage();
}
&#160;
// Create the imageView to show the pdf pages Bitmaps
private&#160;void&#160;createContentView() {
&#160;&#160;&#160;&#160;mImageView = (ImageView) getLayoutInflater().inflate(R.layout.presentation_main,&#160;null);
&#160;&#160;&#160;&#160;setContentView(mImageView);
}
&#160;
// Show the current page on the Presentation view
private&#160;void&#160;showPage() {
&#160;&#160;&#160;&#160;mImageView.setImageBitmap(getPage(mPage));
}
&#160;
// return the Bitmap of the pdf specified page
public&#160;Bitmap getPage(int&#160;page) {
&#160;&#160;&#160;&#160;&#160;
&#160;&#160;&#160;&#160;// ...
}

IV-D - Le contrôle de la Présentation

Une fois la Présentation lancée, l'utilisateur peut la contrôler grâce au ControllerFragment . Ce dernier comprend un ViewPager qui affiche les diapositives du PDF. Lorsque l'utilisateur change de diapositive (soit en faisant glisser, soit en appuyant sur les boutons Précédent et Suivant ), le ControllerFragment prévient l' Activity parente de la nouvelle diapositive sélectionnée, et l' Activity fait remonter l'information à la Presentation afin qu'elle se mette à jour. Voici le code correspondant :

// ControllerFragment.java
&#160;
// ...
&#160;
// We create the viewpager which shows the pages of the pdf file. If the user changes the slide, the listeners
// updates tell the parent Activity to change the image in the presentation.
//
// @param v : the fragment view
//
private&#160;void&#160;createViewPager(View v) {
&#160;&#160;&#160;&#160;mSlidesPagerAdapter =&#160;new&#160;SlidesPagerAdapter(((FragmentActivity) getActivity()).getSupportFragmentManager());
&#160;&#160;&#160;&#160;mSlidesViewPager = (ViewPager) v.findViewById(R.id.fragment_controller_pager);
&#160;&#160;&#160;&#160;mSlidesViewPager.setAdapter(mSlidesPagerAdapter);
&#160;&#160;&#160;&#160;mSlidesViewPager.setOnPageChangeListener(new&#160;SimpleOnPageChangeListener() {
&#160;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;@Override
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;public&#160;void&#160;onPageSelected(int&#160;page) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;((MainActivity) getActivity()).getPresentation().moveTo(page);
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;}
&#160;&#160;&#160;&#160;});
}
&#160;
@Override
public&#160;void&#160;onClick(View v) {
if&#160;(v.getId() == R.id.fragment_controller_button_pageprev) {
&#160;&#160;&#160;&#160;// On click on the prev button, we move the viewpager one slide back (which will move the presentation slide as well)
&#160;&#160;&#160;&#160;mSlidesViewPager.setCurrentItem(mSlidesViewPager.getCurrentItem() -&#160;1);
&#160;
}&#160;else&#160;if&#160;(v.getId() == R.id.fragment_controller_button_pagenext) {
&#160;&#160;&#160;&#160;// On click on the next button, we move the viewpager one slide next (which will move the presentation slide as well)
&#160;&#160;&#160;&#160;mSlidesViewPager.setCurrentItem(mSlidesViewPager.getCurrentItem() +&#160;1);
}

// MainActivity.java
&#160;
// ...
&#160;
// return the Presentation if it has been created. A presentation needs a display and a pdf file
public&#160;PdfViewerPresentation getPresentation() {
&#160;&#160;&#160;&#160;return&#160;mPresentation;
}

// PdfViewerPresentation.java
&#160;
// ...
&#160;
// Move the presentation to the page 'page'
public&#160;void&#160;moveTo(int&#160;page) {
&#160;&#160;&#160;&#160;if&#160;(page >=&#160;0&#160;&& page < getPageCount()) {
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;mPage = page;
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;showPage();
&#160;&#160;&#160;&#160;}
}
&#160;
// Show the current page on the Presentation view
private&#160;void&#160;showPage() {
&#160;&#160;&#160;&#160;mImageView.setImageBitmap(getPage(mPage));
}
&#160;
// return the Bitmap of the pdf specified page
public&#160;Bitmap getPage(int&#160;page) {
&#160;&#160;&#160;&#160;&#160;
&#160;&#160;&#160;&#160;// ...
}

V - Conclusion

Développer une application exploitant les écrans externes n'est vraiment pas compliqué sous Android. Le framework est simple et fonctionne bien. Cependant, l'application développée, malgré son potentiel, est très loin de repousser le WiDi dans ses retranchements, comme pourrait le faire une application de jeu 3D, ou de streaming vidéo. Enfin, il existe une partie de l'API sur le Wireless Display qui est actuellement cachée dans le code Android et non disponible dans le SDK. Cette API permet de contrôler soi-même la découverte et la connexion aux écrans externes. L'exploitation de cette API est donc dangereuse, car elle peut évoluer sans prévenir ou bien ne pas fonctionner comme espéré d'un appareil à un autre. Cependant, si cela est bien fait, l'exploitation de cette API peut simplifier grandement l'utilisation de l'application par l'utilisateur.

VI - Sources

présentation du WiDi pour Android par Xavier Hallade

le site de développement Android

Code source du projet MiraSlide

Page Google Play de l'application

VII - Remerciements

Merci à l'équipe Intel Blogger Meetup ainsi qu'à Antonin Fouques, Design & Development Engineer chez Genymotion.
Retrouvez-le tutoriel sur la Intel Developer Zone