Monday, September 10, 2012

FTP Client for Android

If you are looking for a FTP client for Android, then the following would work for you-

1. Use Apache FTP client
http://commons.apache.org/net/download_net.cgi

you will also need
http://commons.apache.org/io/download_io.cgi

2. FTP client used in Android
A nicely written class with provision to show progress.

http://code.google.com/p/andro-ftp/source/browse/trunk/src/net/abachar/androftp/transfers/manager/FTPTransferTask.java?r=32

I am pasting the class here in case it disappears from the source-

package net.abachar.androftp.transfers.manager;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.SocketException;
import java.util.List;

import net.abachar.androftp.MainApplication;
import net.abachar.androftp.filelist.manager.FTPFileManager;
import net.abachar.androftp.servers.Logontype;

import org.apache.commons.io.input.CountingInputStream;
import org.apache.commons.io.output.CountingOutputStream;
import org.apache.commons.net.PrintCommandListener;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;

/**
 *
 * @author abachar
 */
public class FTPTransferTask extends TransferTask {

        /** */
        private FTPClient mFTPClient;

        /**
         *
         */
        public FTPTransferTask(TransferTaskProgressListener progressListener, List<Transfer> transferList) {
                super(progressListener, transferList);
        }

        /**
         * @see android.os.AsyncTask#doInBackground(Params[])
         */
        @Override
        protected String doInBackground(Transfer... transfers) {

                // Create ftp client
                mFTPClient = new FTPClient();
                mFTPClient.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));

                String retval = super.doInBackground(transfers);

                // Disconnect ftp client
                if ((mFTPClient != null) && mFTPClient.isConnected()) {
                        try {
                                mFTPClient.logout();
                                mFTPClient.disconnect();
                        } catch (IOException e) {
                                e.printStackTrace();
                        }
                }

                return retval;
        }

        /**
         * @see net.abachar.androftp.transfers.manager.TransferTask#doInBackgroundDownload()
         */
        @Override
        protected void doInBackgroundDownload() {

                try {
                        // Connect it if disconnected
                        if (!mFTPClient.isConnected()) {
                                connect();
                        }

                        // if (binaryTransfer) {
                        mFTPClient.setFileType(FTP.BINARY_FILE_TYPE);
                        // }

                        // Go to directory
                        if (!mFTPClient.printWorkingDirectory().equals(mCurrentTransfer.getSourcePath())) {
                                mFTPClient.changeWorkingDirectory(mCurrentTransfer.getSourcePath());
                        }

                        // Open local file
                        FileOutputStream fos = new FileOutputStream(mCurrentTransfer.getFullDestinationPath());
                        CountingOutputStream cos = new CountingOutputStream(fos) {
                                protected void beforeWrite(int n) {
                                        super.beforeWrite(n);

                                        int progress = Math.round((getCount() * 100) / mCurrentTransfer.getFileSize());
                                        mCurrentTransfer.setProgress(progress);
                                        publishProgress(mCurrentTransfer.getId(), progress);
                                }
                        };

                        // Download file
                        mFTPClient.retrieveFile(mCurrentTransfer.getName(), cos);

                        // Close local file
                        fos.close();

                        // End of transfer
                        publishProgress(mCurrentTransfer.getId(), 101);

                } catch (SocketException e) {
                        e.printStackTrace();
                } catch (IOException e) {
                        e.printStackTrace();
                }
        }

        /**
         * @see net.abachar.androftp.transfers.manager.TransferTask#doInBackgroundUpload()
         */
        @Override
        protected void doInBackgroundUpload() {

                try {
                        // Connect it if disconnected
                        if (!mFTPClient.isConnected()) {
                                connect();
                        }

                        // if (binaryTransfer) {
                        mFTPClient.setFileType(FTP.BINARY_FILE_TYPE);
                        // }

                        // Open local file
                        FileInputStream fis = new FileInputStream(mCurrentTransfer.getFullSourcePath());
                        CountingInputStream cis = new CountingInputStream(fis) {
                                protected void afterRead(int n) {
                                        super.afterRead(n);

                                        int progress = Math.round((getCount() * 100) / mCurrentTransfer.getFileSize());
                                        mCurrentTransfer.setProgress(progress);
                                        publishProgress(mCurrentTransfer.getId(), progress);
                                }
                        };

                        // Go to directory
                        if (!mFTPClient.printWorkingDirectory().equals(mCurrentTransfer.getDestinationPath())) {
                                mFTPClient.changeWorkingDirectory(mCurrentTransfer.getDestinationPath());
                        }

                        // Upload file
                        mFTPClient.storeFile(mCurrentTransfer.getName(), cis);

                        // Close local file
                        fis.close();

                        // End of transfer
                        publishProgress(mCurrentTransfer.getId(), 101);

                } catch (SocketException e) {
                        e.printStackTrace();
                } catch (IOException e) {
                        e.printStackTrace();
                }
        }

        /**
         * @throws IOException
         * @throws SocketException
         *
         */
        private void connect() throws SocketException, IOException {

                FTPFileManager fileManager = (FTPFileManager) MainApplication.getInstance().getServerFileManager();

                // Connect to server
                mFTPClient.connect(fileManager.getHost(), fileManager.getPort());

                // Check the reply code to verify success.
                int reply = mFTPClient.getReplyCode();
                if (!FTPReply.isPositiveCompletion(reply)) {
                        return;
                }

                if (fileManager.getLogontype() == Logontype.NORMAL) {
                        if (!mFTPClient.login(fileManager.getUsername(), fileManager.getPassword())) {
                                mFTPClient.logout();
                                return;
                        }
                }

                // Use passive mode as default because most of us are
                // behind firewalls these days.
                mFTPClient.enterLocalPassiveMode();
        }
}

Saturday, May 19, 2012

Android Scrollable Tabs

I was looking to design scrollable tabs as used in the new Android market app. This is how it looks-

http://developer.android.com/design/building-blocks/tabs.html

I got a very good library below-

http://viewpagerindicator.com/

Some other useful references are-

http://android-developers.blogspot.in/2011/08/horizontal-view-swiping-with-viewpager.html

http://blog.stylingandroid.com/


Sunday, September 4, 2011

Android - loadDataWithBaseURL

Today I was working on a project which had numerous HTML files in various folders. These HTML files were used in a webview interface to show the UI screen. In those HTML files, a couple of icons were referenced. The reference was relative ie the HTML expected the icons to be in the same folder as the HTML. However, the folders didn't have those icon files. So I was forced to look for workaround.

The only option which didn't require any changes in the HTML files and folders was to use "loadDataWithBaseURL". It basically allows you to specify a base folder against which the relative reference would be resolved.  The following is the javadoc for "loadDataWithBaseURL"-


public void loadDataWithBaseURL (String baseUrl, String data, String mimeType, String encoding, String historyUrl)

Since: API Level 1
Load the given data into the WebView, use the provided URL as the base URL for the content. The base URL is the URL that represents the page that is loaded through this interface. As such, it is used to resolve any relative URLs. The historyUrl is used for the history entry.
Note for post 1.0. Due to the change in the WebKit, the access to asset files through "file:///android_asset/" for the sub resources is more restricted. If you provide null or empty string as baseUrl, you won't be able to access asset files. If the baseUrl is anything other than http(s)/ftp(s)/about/javascript as scheme, you can access asset files for sub resources.
Parameters
baseUrlUrl to resolve relative paths with, if null defaults to "about:blank"
dataA String of data in the given encoding.
mimeTypeThe MIMEType of the data. i.e. text/html. If null, defaults to "text/html"
encodingThe encoding of the data. i.e. utf-8, us-ascii
historyUrlURL to use as the history entry. Can be null.

A typical usecase would be something like this-


WebView webview = (WebView)this.findViewById(R.id.webview);

String html = "<html><head><title>TITLE!!!</title></head>";
html += "<body><h1>Image?</h1><img src="icon.png" /></body></html>";

webview.loadDataWithBaseURL("file:///android_res/drawable/", html, "text/html", "UTF-8", null);


Ref: http://remotedroid.net/blog/2011/01/14/referencing-drawables-in-html-in-androids-webview/

The first parameter
baseUrl -- "file:///android_res/drawable/"
specifies the path where the "icon.png" is placed. In this case it is placed in the drawable folder under res of the eclipse project.

One important thing to note over here is that, this feature is supported only in SDK 2.2 and above.

This was an elegant option for resolving the problem I mentioned above; however I was not able to use it due to the version limitation.

The following code snippets shows how I used it-


String html = getHTMLDataBuffer(url);
webView.loadDataWithBaseURL("file:///android_asset/images/", html, "text/html", "UTF-8", null);

where the function getHTMLDataBuffer  was implemented as below-


private String getHTMLDataBuffer(String url) {
InputStream htmlStream;
try {
if (Utils.isReferExternalMemory() && url.contains("sdcard")) {
String tempPath = url.substring(7, url.length());//remove file:// from the url
File file = new File(tempPath);
htmlStream = new FileInputStream(file);
}else{
String tempPath = url.replace("file:///android_asset/", "");
htmlStream = getApplicationContext().getAssets().open(tempPath);
}
Reader is = null;
try {
is = new BufferedReader(new InputStreamReader(htmlStream, "UTF8"));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

// read string from reader
final char[] buffer = new char[1024];
StringBuilder out = new StringBuilder();
int read;
do {
 read = is.read(buffer, 0, buffer.length);
 if (read>0) {
   out.append(buffer, 0, read);
 }
} while (read>=0);

return out.toString();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}



Please note that this is for illustration only and lot of niceties of coding has been ignored here.