How to open a PDF in an external viewer from an AIR app running on Android.

Opening files in external apps from AIR on Android is not as trivial as it would seem.

Here’s the problem: AIRs mobile profile does not support File.openWithDefaultApplication(). I know, this is retarded.

I tried countless approaches to this seemingly trivial problem, including using StageWebView, server-side conversion of the PDFs to SWF together with SWFLoader, and various combinations of these. Nothing would work to my satisfaction.

Eventually I came across a method of extending an AIR app with native Android OS functionality. That way it is possible to open files with Android’s native Intent class. Here’s the steps I took to accomplish this:

Please be aware that I am not very familiar with Android development (this is part of the reason why I choose AIR in the first place, doh), so there may be better ways to accomplish this. However, the steps below worked for me.

Preparations

  1. Set up Eclipse with Android SDK: http://www.vogella.de/articles/Android/article.html#installation_eclipse
  2. Set up a project in Eclipse: http://www.jamesward.com/2011/05/11/extending-air-for-android/ (Steps 1–10)
  3. In AndroidManifest.xml, Add at least the following permissions:
    android.permission.INTERNET
    android.permission.WRITE_EXTERNAL_STORAGE

Code

If you followed James Ward’s terrific guide (many thanks!) up until to step 10 you should now have a MainApp.java file in your project. Put this inside it: (remember to change package to whatever you use)

package com.mypackage;

import air.app.AppEntry;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import android.os.Looper;
import android.util.Log;

public class MainApp extends AppEntry {

	private boolean stopped=false;
	private Thread serverThread;
	private ServerSocket ss;

	@Override
	public void onCreate(Bundle arg0) {
		super.onCreate(arg0);

		serverThread = new Thread(new Runnable() {

			public void run()
			{
				try
				{
					Looper.prepare();
					ss = new ServerSocket(12345);
					ss.setReuseAddress(true);
					ss.setPerformancePreferences(100, 100, 1);

					while (!stopped)
					{
						System.out.println("test test");

						Socket accept = ss.accept();
						accept.setPerformancePreferences(10, 100, 1);
						accept.setKeepAlive(true);

						DataInputStream _in = null;
						try
						{
							_in = new DataInputStream(new BufferedInputStream(accept.getInputStream(),1024));
						}
						catch (IOException e2)
						{
							e2.printStackTrace();
						}

						int method =_in.readInt();

						switch (method)
						{
						// Open PDF
						case 1:
							String path = _in.readLine();

							// Remove junk in the beginning of the string. Not sure if this is necessary in every case.
							path = path.substring(2);

							// Contruct the Intent
							File file = new File(path);
							Intent i = new Intent(Intent.ACTION_VIEW);
							i.setDataAndType(Uri.fromFile(file), "application/pdf");
							i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

							// Try to start the intent activity. If no activity is found, tell the user via an alert.
							try {
								startActivity(i);
							}
							catch (ActivityNotFoundException e) {
								messageHandler.sendMessage(Message.obtain(messageHandler, 0));
							}

							break;
						}
					}
				}

				catch (Throwable e)
				{
					e.printStackTrace();
					Log.e(getClass().getSimpleName(), "Error in Listener",e);
				}

				try
				{
					ss.close();
				}
				catch (IOException e)
				{
					Log.e(getClass().getSimpleName(), "keep it simple");
				}
			}

		},"Server thread");
		serverThread.start();

	}

	@Override
	public void onDestroy() {
		stopped=true;
		try {
			ss.close();
		} catch (IOException e) {}
		serverThread.interrupt();
		try {
			serverThread.join();
		} catch (InterruptedException e) {}
	}

	// This is a handler for displaying alerts
	private Handler messageHandler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			switch(msg.what) {
			case 0:
				AlertDialog alertDialog = new AlertDialog.Builder(MainApp.this).create();
				alertDialog.setTitle("No PDF viewer found");
				alertDialog.setMessage("Please install one from the Market.");
				alertDialog.setButton("OK", new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog, int which) {
						return;
					} });
				alertDialog.show();
				break;
			}
		}

	};

}

This code will recieve a path from AIR via the socket, and open the file with the preferred application using the Intent class. If a capable app is not installed on the device, an alert will inform the user.

Ok, so that’s the Android part. Now for the AIR part.

Add this code to your AIR app, where appropriate:

var s:Socket = new Socket();
s.connect("localhost", 12345);
s.addEventListener(Event.CONNECT, function(event:Event):void {
	trace('connected!');
	(event.currentTarget as Socket).writeInt(1);
	(event.currentTarget as Socket).writeUTF("/sdcard/MyApp/pdfdocument.pdf");
	(event.currentTarget as Socket).flush();
	(event.currentTarget as Socket).close();
});
s.addEventListener(IOErrorEvent.IO_ERROR, function(event:IOErrorEvent):void {
	trace('error! ' + event.errorID);
});
s.addEventListener(ProgressEvent.SOCKET_DATA, function(event:ProgressEvent):void {
	trace('progress ');
});

 

And that’s it! Took me two freaking days to figure this stuff out.

I hope this post will save someone else from wasting a bunch of time.

4 Responses to “How to open a PDF in an external viewer from an AIR app running on Android.”

  1. Andreas says:

    Hej
    Jag såg att du jobbar i Stockholm, så jag hoppas att svenska funkar.
    Jag håller på att utveckla min första Andoid-App i Flash 5.5, utan någon tidigare erfarenhet av vare sig AIR eller Android. Jag har dock stött på problem när jag försöker få appen att öppna PDF-filer. Kan du kontakta mig om du skulle vara intresserad av några konsult-timmar för att få detta att fungera, gärna under de närmaste dagarna.
    /Andreas

  2. cole says:

    I went through all the steps, but i am stuck on a 2031 error which seems to be a socket error. Any ideas what might be the cause of this? Thanks in advance.

  3. Gal says:

    I followed your tutorial, but still didn’t succeed:

    When I run the application, my device (Asus TF101) gives me this error:

    “the file path is not valid”.

    writeUTF(“/sdcard/MyApp/pdfdocument.pdf”);

    I tried to changing in the address in the AS code, is it really “MyApp”, or some other value? (I tried to name it after my Java Package name, which I think is also my app. name..)

    I also tried to put the PDF file in several locations, should it reside in the assets folder in the Java app. ? Is it included in the APK automatically, or I should add it in some way?

    Thanx again :)
    Gal

  4. SlaterEbony says:

    I had a dream to make my firm, however I didn’t have got enough of cash to do it. Thank God my close colleague said to use the mortgage loans. Thus I used the financial loan and made real my old dream.

Leave a Reply