Monday, December 31, 2012

Creating Android FTP client | FTP operations


Many programmers have the following questions in mind.
"How do I do FTP operations from my android app?"
"How can I write a simple FTP client?"
"I want to store my application data in a FTP link at runtime. How can I do it?"
"How to upload images from my Android app to an FTP server/host ?"

There are many FTP client apps available for Android. But that's not an in-app solution.

What I'm going to describe here is a very simple way to build an ftp client inbuilt into your app.
With this you would be able to download a file from ftp host / upload file to a ftp host etc.

Prerequisites:

1. You would need an ftp implementation. I use a library from apache commons project.
   Copy org.apache.commons.net_2.0.0.v200905272248.jar file to the libs/ folder of your android app code

2. You would need an ftp host and an account to access it. I use drivehq.com.
   drivehq.com provies 1 GB of free ftp space. All my sample codes are uploaded to the same ftp host.
   Once you have an account you would have an UID & PW to access the host.
   In the sample apps, I have mentioned it as FTP_UID and FTP_PW. Replace these with the actual uid&pw

3. Add permission to your Android manifest
   <uses-permission android:name="android.permission.INTERNET"></uses-permission>
 

Note: Do all the ftp operations on a separate thread. Do not block the UI thread.
All the following operations use the FTPClient class which is defined in the jar file.
The sample implementation can be found in my sample code: MyFTPClient.java
import import org.apache.commons.net.ftp.*;

Sample App: FTPTest

Connecting to a FTP host
      mFTPClient = new FTPClient();  
      // connecting to the host  
      mFTPClient.connect("ftp.drivehq.com", 21);  
       boolean status = mFTPClient.login("FTP_UID", "FTP_PW");  
       // now check the reply code, if positive mean connection success  
      if (FTPReply.isPositiveCompletion(mFTPClient.getReplyCode())) {  
           /* Set File Transfer Mode  
            *  
            * To avoid corruption issue you must specified a correct  
            * transfer mode, such as ASCII_FILE_TYPE, BINARY_FILE_TYPE,  
            * EBCDIC_FILE_TYPE .etc. Here, I use BINARY_FILE_TYPE  
            * for transferring text, image, and compressed files.  
            */  
           mFTPClient.setFileType(FTP.ASCII_FILE_TYPE);  
           mFTPClient.enterLocalPassiveMode();  
      }  

Disconnecting from a ftp host

      mFTPClient.logout();  
      mFTPClient.disconnect();  

Retrieving current FTP directory
 workingDir = mFTPClient.printWorkingDirectory();  

Change the ftp directory path
 mFTPClient.changeWorkingDirectory(directory_path);  

Download a file from ftp host
      FileOutputStream desFileStream = new FileOutputStream(desFilePath);;  
      status = mFTPClient.retrieveFile(srcFilePath, desFileStream);  
      desFileStream.close();  

Upload a file to an ftp host
      FileInputStream srcFileStream = context.openFileInput(srcFilePath);  
   status = mFTPClient.storeFile(desFileName, srcFileStream);  
      srcFileStream.close();  

Print files in list
      public void ftpPrintFilesList(String dir_path)  
      {  
        try {  
          FTPFile[] ftpFiles = mFTPClient.listFiles(dir_path);  
          int length = ftpFiles.length;  
          for (int i = 0; i < length; i++) {  
            String name = ftpFiles[i].getName();  
            boolean isFile = ftpFiles[i].isFile();  
            if (isFile) {  
              Log.i(TAG, "File : " + name);  
            }  
            else {  
              Log.i(TAG, "Directory : " + name);  
            }  
          }  
        } catch(Exception e) {  
          e.printStackTrace();  
        }  
      }   

Create a directory in ftp host
      boolean status = mFTPClient.makeDirectory(new_dir_path);  

Remove a directory in ftp host
      boolean status = mFTPClient.removeDirectory(dir_path);  

Remove a file in ftp host
      boolean status = mFTPClient.deleteFile(filePath);  

Rename a file in ftp host
      boolean status = mFTPClient.rename(from, to);  

Tuesday, August 7, 2012

Android - How to write a simple barcode scanner or a QR scanner


Integrating a barcode scanner to your android application is simpler than in other platforms.

There are two ways you can get your android app the ability to scan barcodes.
1. Sending intents to the barcode scanner app - The simplest way
2. Using google source for Zxing - For those who can easily crunch huge volume of code

As the post title says "Simple barcode / QR code scanner" I will discuss the first method here

1. Sending intent for starting the scan

We have to use the intents provided by the barcode scanner app and start the activity for result using startActivityForResult

           Intent intentScan = new Intent("com.google.zxing.client.android.SCAN");  
           intentScan.addCategory(Intent.CATEGORY_DEFAULT);  
   
           // If only QR code should be scanned  
           //intentScan.putExtra("SCAN_FORMATS", "QR_CODE");  
   
           try {  
                this.startActivityForResult(intentScan, MY_REQUEST_CODE);  
           } catch (ActivityNotFoundException e) {  
                downloadFromMarket();  
           }  

If you don't specify any "SCAN_FORMATS" as intent extras, it will scan for all the supported formats (QR_CODE,UPC_A,UPC_E,EAN_8,EAN_13,CODE_39,CODE_93,CODE_128 )
You can speficy only the formats you want by comma separated strings. Example:

 intentScan.putExtra("SCAN_FORMATS", "QR_CODE,UPC_A,UPC_E");  

2. Handling the onActivityResult

Retrieve the "SCAN_RESULT" and "SCAN_RESULT_FORMAT" from the result. In the below code, both the data are extracted and set to a textview.
Make sure you access the intent only when resultCode == Activity.RESULT_OK

      protected void onActivityResult(int requestCode, int resultCode, Intent data) {  
   
           if (requestCode == MY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {  
                String contents = data.getStringExtra("SCAN_RESULT");  
                String formatName = data.getStringExtra("SCAN_RESULT_FORMAT");  
                  
                TextView tv = (TextView)findViewById(R.id.tvResult);  
                tv.setText("Format: " + formatName+ " Code: "+contents);  
           }  
      }  

3. The Barcode scanner app 

The Barcode scanner app from zxing is opensource and you can download the source code form google code. But here we are interested only in the final application.
   
If the barcode scanner app is not present in the device, we can make the user to download it when startActivityForResult() fails with ActivityNotFoundException

      public void downloadFromMarket() {  
           AlertDialog.Builder downloadDialog = new AlertDialog.Builder(this);  
           downloadDialog.setTitle("Warning");  
           downloadDialog.setMessage("Barcode app not found. Download?");  
           downloadDialog.setPositiveButton("Yes", new DialogInterface.OnClickListener() {  
                public void onClick(DialogInterface dialogInterface, int i) {  
                     Uri uri = Uri.parse("market://search?q=pname:com.google.zxing.client.android");  
                     Intent intent = new Intent(Intent.ACTION_VIEW, uri);  
                     startActivity(intent);  
                }  
           });  
   
           downloadDialog.show();  
      }  

Android - Intercepting SMS messages and consuming it

Sometimes we have to send messages to our Android apps through SMS messages.
So we have to write a SMS receiver to receive the message. After handling the message, we can decide if we want to allow the SMS message to be displayed in the inbox of the default messaging client or not.

To acheive this, we need to do the following:

1. Add an extra permission to receive the incoming SMS message
2. Declare our SMS receiver in the AndroidManifest xml file
3. Writing a SMS receiver class to handle received messages
4. Decide if we have to showup the message in the default messaging client

Permissions needed
 android.permission.RECEIVE_SMS  

Adding receiver to Android Manifest
Add the sms receiver class to your manifest. If you want your app to handle the SMS message before any other app, then set the priority to the maximum: 999
Setting the maximum priority also comes with added responsibility. You can actually mess up other SMS messages that you dont want to handle from reaching the messaging app.

Add the following lines to AndroidManifest.xml
           <uses-permission android:name="android.permission.RECEIVE_SMS" />  
   
     <receiver android:name=".MySMSReceiver">        
        <intent-filter android:priority="999">   
             <action android:name="android.provider.Telephony.SMS_RECEIVED" />  
           </intent-filter>  
     </receiver>  
   

Receiver code
The MySMSReceiver class should extend the BroadcastReceiver and override the onReceive() method
Assuming the app package is com.test.myapp, add this code to MySMSReceiver.java

 package com.test.myapp;  
   
 import android.content.BroadcastReceiver;  
 import android.content.Context;  
 import android.content.Intent;  
 import android.os.Bundle;  
 import android.telephony.gsm.SmsMessage;  
 import android.widget.Toast;  
   
 public class MySMSReceiver extends BroadcastReceiver {  
   
      @Override  
      public void onReceive(Context context, Intent intent) {  
           String smsData = null;  
   
           if (intent.getAction().equals(android.provider.Telephony.SMS_RECEIVED)) {  
                  
                Bundle pudsBundle = intent.getExtras();  
              Object[] pdus = (Object[]) pudsBundle.get("pdus");  
              SmsMessage messages =SmsMessage.createFromPdu((byte[]) pdus[0]);       
              smsData = messages.getMessageBody();  
                     
                // now smsData has the actual SMS message  
                // Add code to handle the message  
   
           }  
      }  
 }  

Now using the above code, you can intercept the SMS messages.
This will also allow the default messaging client to receive the same SMS message and display it in the Inbox.
What if you want to consume the message and not show it in the messaging app?
Just call abortBroadcast()

 
 public class MySMSReceiver extends BroadcastReceiver {

 @Override
 public void onReceive(Context context, Intent intent) {
  String smsData = null;
  boolean bHandled = false;
  
  if (intent.getAction().equals(android.provider.Telephony.SMS_RECEIVED)) {
   
   Bundle pudsBundle = intent.getExtras();
      Object[] pdus = (Object[]) pudsBundle.get("pdus");
      SmsMessage messages =SmsMessage.createFromPdu((byte[]) pdus[0]);         
      smsData = messages.getMessageBody();
        
   // now smsData has the actual SMS message
   // Add code to handle the message
   // If this message should be aborted set bHandled = true
   
      if (bHandled) {
    abortBroadcast();
   }  
  }
 }
}