Sunday, 23 December 2012

How to create an Android app like Airdroid (Part I)

Airdroid is one of my favorite apps when it comes to managing my phone or tablet. It is so useful that I almost never use a cable to connect my phone to my PC. If you have not used it already, you are missing out on a great experience. Surprisingly, it is a fully functional free app (as of December 2012), available for all devices running Android 2.1 or higher.

Some of the features that I love about this app, and use extensively are,
  1. Send/receive SMS from PC
  2. Transfer files from PC to phone
  3. Manage contacts from PC
All the above work over WiFi. Being the developer that I am, I couldn't help but think how it works. So, in this series we will be making our own app, which will have some functionality similar to that of Airdroid.

While this app is a rather complex one because of so many features, at its core it must be doing the following,
  1. Run a HTTP server on the phone
  2. Map the commands that it receives from the PC (which will be a client that connects to the HTTP server) to the API available in the SDK.
Let us get started now

I again assume you do have some knowledge of the Android SDK and how to create an Android project in Eclipse.

Despite being the first part of the series, we will be implementing some major features here, namely
  • Get a HTTP server running from inside our app
  • Determine and display the IP address of the phone, so that we can connect to it from the PC.
WiFi functionality is rather erratic on the emulators. So, I recommend debugging this app using a real device.

We don't need to actually create a HTTP server from the scratch. There are a lot of fully functional HTTP servers available that we can embed in to our app. So, we will be using Jetty for this purpose. It might not be the best choice, but it is quite easy to embed and use.

Firstly, we must download the requisite JARs. Download both of the following, and add them as external JARs in your project. I have chosen Jetty 7 and Servlet API 2.5.
When you view your Project Properties, the Java Build Path should like this, 
Make sure all the JARs are ticked
Now that we have taken care of the dependencies, we get started with the code. There is a lot of documentation available about Jetty here if you are interested.

Your manifest file should include the following permissions,
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
First, let us create the Servlet that our server should serve when a client connects to it. To keep it simple, this is a form that has only one text field and one submit button. When the form is posted, the same servlet will display a welcome message. I will name this servlet, SimpleServlet.java
package com.jdepths.httpserver;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SimpleServlet extends HttpServlet{
    
    private static final long serialVersionUID = -621262173529602L;
    
    /**
     * This method is called when the user enters the URL in the
     * browser. 
     */
    @Override
    protected void doGet(HttpServletRequest req
                         , HttpServletResponse resp)
                         throws ServletException, IOException {    
        resp.setContentType("text/html");
        resp.setStatus(HttpServletResponse.SC_OK);
        PrintWriter out=resp.getWriter();
        out.println("<html>" +
                "<title>Simple Form</title>" +
                "<body>" +
                "<form method='post'>" +
                "<input type='text' name='user_name'/><br/>" +
                "<input type='submit' value='Go'/>" +
                "</form>" +
                "</body>" +
                "</html>");
    }
    
    /**
     * This method is called when the 'Go' button is pressed
     */    
    @Override
    protected void doPost(HttpServletRequest req
                          , HttpServletResponse resp)
                          throws ServletException, IOException {    
        resp.setContentType("text/html");
        resp.setStatus(HttpServletResponse.SC_OK);
        PrintWriter out=resp.getWriter();
        String username=req.getParameter("user_name");
        
        if(username==null || username.equals(""))
            username="<NO_NAME_SPECIFIED>";
        
        out.println("<html>" +
                "<title>Welcome</title>" +
                "<body>" +
                "Welcome " + username +
                "</body>" +
                "</html>");
    }
    
}
That is all the servlet does. It is not specific to Android in any way. It is an ordinary servlet not making use of any special API.

Now we will start using the Android API. We need to get the IP address of our phone, when it is connected to WiFi. Without this IP, we will not know what to type in to the address bar of a browser on the PC. It can be obtained using the following method.
public String getIPAddress(){
    ConnectivityManager con;
    con=(ConnectivityManager)context
                     .getSystemService(Context.CONNECTIVITY_SERVICE);
    if(con
       .getNetworkInfo(ConnectivityManager.TYPE_WIFI)
       .isConnected()){

        // Check if  the phone is connected to a WiFi network
        WifiManager wifi;
        wifi=(WifiManager)context
                   .getSystemService(Context.WIFI_SERVICE);

        int ip=wifi.getConnectionInfo().getIpAddress();

        //Deprecated, but there doesn't seem to be a substitute
        return Formatter.formatIpAddress(ip);

    }else{
        return "You are not connected to WIFI";
    }
}
This should return a String that has the IP address. We will make this a part of our class that manages the Jetty server. Let us call this class, MyHttpServer. Our server should run on port 8080. As this server is going to deal with servlets, we will add a ServletContextHandler to it. The context path of the servlets for now shall be just /.

To the ServletContextHandler we can add any number of servlets, but for this tutorial, we will only be adding one. Here is the complete code of MyHttpServer.class.
package com.jdepths.httpserver;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.text.format.Formatter;
import android.util.Log;

/**
 * Creates the jetty server and manages it
 * 
 * @author Hathy
 *
 */
public class MyHttpServer{
    
    ServletContextHandler handler;
    Server server;
    Context context;
    
    /**
     * Due to a bug in Android 2.x, it is recommended that you
     * do this. I have seen the bug only on the emulators.
     */
    static{
        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB){
            System.setProperty(
                   "java.net.preferIPv6Addresses"
                 , "false");
        }
    }
        
    /**
     * Create the jetty server and let it listen at port 8080
     */
    MyHttpServer(Context context){
        
        this.context=context;
        
        server=new Server(8080); // The port is being set here
        handler=new ServletContextHandler();
        handler.setContextPath("/");
        handler.addServlet(
                new ServletHolder(
                    new SimpleServlet()),"/");
        server.setHandler(handler);
    }
    
    /*
     * We better run the server in a separate thread.
     */
    public void start(){
        new Thread(){
            public void run(){
                try{
                    server.start();
                }catch(Exception e){
                    Log.e("SERVER_START",e.toString());
                }
            }
        }.start();
    }
    
    public String getIPAddress(){
        ConnectivityManager con;
        con = (ConnectivityManager)context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
        if(con.getNetworkInfo(
                 ConnectivityManager.TYPE_WIFI)
                .isConnected()){
            WifiManager wifi;
            wifi=(WifiManager)context
                    .getSystemService(Context.WIFI_SERVICE);
            int ip=wifi.getConnectionInfo().getIpAddress();
            return Formatter.formatIpAddress(ip);
        }else{
            return "You are not connected to WIFI";
        }
    }
        
    
    public void stop(){
        try{
            server.stop();
        }catch(Exception e){
            Log.e("SERVER_STOP",e.toString());
        }
    }
}
That is all. The server is now ready to serve requests. Now we need to simply create an Activity and start the server in it. Also, this Activity should display the IP address. I won't be creating any layout for this Activity, as it will just contain one TextView. So, here is MainActivity.java
package com.jdepths.httpserver;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.TextView;

public class MainActivity extends Activity {

    MyHttpServer server;
    TextView ipAddress;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ipAddress=new TextView(this);
        setContentView(ipAddress);
        server=new MyHttpServer(MainActivity.this);
        server.start();

        ipAddress.setText(server.getIPAddress());
    }

    @Override
    protected void onDestroy() {    
        super.onDestroy();
        server.stop();
    }
        
}
We are all done. Build the project, and deploy it to your Android device. Turn on the WiFi, and start the app. If all goes well, you should see an IP address on your phone screen.

On your PC, which is connected to the same WiFi network, open a browser and type in the IP address you got followed by :8080 (the port). Hit enter and you should see the form which is being served by your phone.

Alternatively, on your phone too, you can open up a browser and type in http://localhost:8080, and see the same form.
The form as rendered on Firefox
Thanks for reading. If you have any comments, doubts or feedback, please do write to me. In the next part of this series, we will add the functionality to send/receive SMS from a PC.

1 comment: