Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Thursday, 27 November 2014

How to download files using web cache validation (ETag) - Android

If your app often download files, you want to efficiently keep track of the files you already have so you don't need to download them again.
The HTTP protocol helps us handle this task with much ease by making use of ETags

The class below is ready to use. Copy and paste the code into your Android project.
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.URL;

public class FileDownloadHelper {
	private final int BUFFER_SIZE=1024*32;
	private final String IFNONEMATCH_HEADER_FIELD = "If-None-Match";
	private final String ETAG_HEADER_FIELD = "ETag";
	
	public void downloadToFile(String sourceUrl, String destFile) throws IOException{
		URL url = new URL(sourceUrl);
			
		downloadToFileHTTP(url, destFile);		
	}
		
	private void downloadToFileHTTP(URL url, String destFile) throws IOException{
		HttpURLConnection connection = (HttpURLConnection) url.openConnection();
		// accept compressed content
		connection.setRequestProperty("Accept-Encoding", "gzip, deflate");
		try{
			writeHTTPDataToFileStream(connection, destFile);
		}finally{
			connection.disconnect();
		}	
	}
	
	private void writeHTTPDataToFileStream(HttpURLConnection connection, String destFile) throws IOException{
		String etagFileName = buildEtagFileName(destFile);
		String etag = readTextFile(etagFileName);
		
		addETagHeader(connection, etag);
		
		int responseCode = connection.getResponseCode(); 		
		if(responseCode == HttpURLConnection.HTTP_OK){
			FileOutputStream destFileStream = new FileOutputStream(destFile);
			try{
				InputStream connectionStream = new BufferedInputStream(connection.getInputStream());
				try{
					int bytesRead;
					byte[] readBuffer = new byte[BUFFER_SIZE];
					while((bytesRead = connectionStream.read(readBuffer)) > 0){
						destFileStream.write(readBuffer, 0, bytesRead);
					}
				}finally{
					connectionStream.close();
				}
			}finally{
				destFileStream.close();
			}
			saveETagHeader(connection, destFile);
		}
		
	}
	
	private void saveETagHeader(HttpURLConnection connection, String destFile) throws IOException {
		String etag = connection.getHeaderField(ETAG_HEADER_FIELD);
		if(etag != null && etag.length()>2){
			String etagFile = buildEtagFileName(destFile);
			saveToTextFile(etagFile, etag);
		}
	}

	private void addETagHeader(HttpURLConnection connection, String etag){
		// greater than 2 because it must at least include the double quotes
		if(etag!=null && etag.length()>2){
			connection.addRequestProperty(IFNONEMATCH_HEADER_FIELD, etag);
		}
	}
	
	private String readTextFile(String fileName) throws IOException{
		File f = new File(fileName);
		if(f.exists() && f.length()>0){
			Reader reader = new BufferedReader(new FileReader(fileName));
			try {
				char[] buffer = new char[1024 * 8];
				StringBuilder resultBuilder = new StringBuilder();
				int br;
				while ((br = reader.read(buffer)) > 0) {
					resultBuilder.append(buffer, 0, br);
				}
				return resultBuilder.toString();
			} finally {
				reader.close();
			}
		}
		return "";
	}
	
	private void saveToTextFile(String fileName, String contents)	throws IOException {
		Writer writer = new BufferedWriter(new FileWriter(fileName));
		try {
			writer.write(contents);
		} finally {
			writer.close();
		}
	}
	
	private String buildEtagFileName(String fileName){
		return fileName + ".etag";
	}	
}


Note that a .etag file will be created in the same directory as the downloaded file and reused in future downloads.
Just call FileDownloadHelper.downloadToFile like this:

FileDownloadHelper client = new FileDownloadHelper(); // assuming you are in the activity context String fileName = getFilesDir().getAbsolutePath()+"/devhelpers.html"; // this will download an html page but you can pass any url to the method String webPageUrl = "http://devhelpers.blogspot.ca/"; client.downloadToFile(webPageUrl, fileName);

Sunday, 16 November 2014

How to write to and read from text files in Java/Android

There are many ways to read and write files in Java and the size and type of data you're working with usually dictate the best method to do so.

For working with text files that are small enough, I find that the classes FileReader and FileWriter provide the most cost effective implementations.

The helper methods below are ready for use. They rely on the system's default Charset so be careful if you're passing around files across different systems and encodings. The code works on any Java platform including Android.

public static void saveToTextFile(String fileName, String contents) throws IOException{
 Writer writer = new BufferedWriter(new FileWriter(fileName));
 try{
  writer.write(contents);
 }finally{
  writer.close();
 }
}

public static String readTextFile(String fileName) throws IOException{
 Reader reader = new BufferedReader(new FileReader(fileName));
 try{
  char[] buffer = new char[1024*8];
  StringBuilder resultBuilder = new StringBuilder();
  int br;   
  while((br = reader.read(buffer))>0){
   resultBuilder.append(buffer, 0, br);
  }
  return resultBuilder.toString();
 }finally{
  reader.close();
 }
}


An usage example on Android would be
// 'this' here is the activity context
String fileName = this.getFilesDir().getAbsolutePath()+"/test.txt";
String contents = "Hello, World of Android";
try {
 saveToTextFile(fileName, contents);
 //do something...
 String contentsRead = readTextFile(fileName);
 Log.d("MYAPP", "I wrote '"+contents+"' and I read '"+contentsRead+"'");
} catch (IOException e) {   
 Log.e("MYAPP", e.getMessage(),e);
}

Saturday, 8 November 2014

How to encrypt strings using AES - Android/Java

Keeping your user's personal information safe is extremely important and sometimes you need to encrypt and store short strings (like user name and password) in the users's device or personal account.

The class Cypher provides various encryption algorithms and AES becoming very much a standard over 3DES.

The helper class FreeCryptoHelper is simple, ready to use and to be modified to your needs.

Pass your encryption key as an array of bytes to the constructor of the class. Read the comments in the code, keeping your key safe is extremely important. Also the length of the array must be observed or you will get an exception trying to create the key spec.

Call encryptString to encrypt a string and its counterpart decryptString to decrypt it.

Feel free to drop a comment if you have questions or suggestions.

Note that I'm using the Base64 class present in the android SDK. If you want to use pure java, checkout the Java class documentation
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import android.util.Base64;

public class FreeCryptoHelper {
  
 private SecretKeySpec mKeySpec;
 
 public FreeCryptoHelper(byte[] encryptionKey){
  // make sure encryptionKey is either 32, 64 or 128 bits long
  // and if you intend to hardcode it, have it well obfuscated within your code 
  mKeySpec = new SecretKeySpec(encryptionKey, "AES");
 }
 
 public String encryptString(String clearText) throws InvalidKeyException, NoSuchAlgorithmException, 
                NoSuchPaddingException, IllegalBlockSizeException, 
                BadPaddingException{
  Cipher cipher = Cipher.getInstance("AES");
  cipher.init(Cipher.ENCRYPT_MODE, mKeySpec);
  byte[] encData = cipher.doFinal(clearText.getBytes());
  
  return Base64.encodeToString(encData, Base64.DEFAULT);
 }

 public String decryptString(String encryptedText) throws InvalidKeyException, 
                NoSuchAlgorithmException, NoSuchPaddingException, 
                IllegalBlockSizeException, BadPaddingException{
  byte[] encData = Base64.decode(encryptedText, Base64.DEFAULT);
  Cipher cipher = Cipher.getInstance("AES");
  cipher.init(Cipher.DECRYPT_MODE, mKeySpec);
  byte[] decData = cipher.doFinal(encData);
  
  return new String(decData);
 }
}

Tuesday, 4 November 2014

How to connect to an HTTPS web server using self-signed certificates (Android/Java)

Self-signed certificates are quite often used by devs and companies that don't want to pay for a real certificate until is time to go to production.
The Android (Java) HttpsURLConnection will refuse to connect to any web server using a self-signed certificate by default. To get around this, you need replace the default socket factory and make the host name verifier more lenient.
To replace the socket factory, you first need a trust manager. The class below is ready for use
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import javax.net.ssl.X509TrustManager;

public class AllForgivingTrustManager implements X509TrustManager {

 @Override
 public void checkClientTrusted(X509Certificate[] arg0, String arg1)
   throws CertificateException {  
 }

 @Override
 public void checkServerTrusted(X509Certificate[] arg0, String arg1)
   throws CertificateException {  
 }

 @Override
 public X509Certificate[] getAcceptedIssuers() {  
  return new X509Certificate[0];
 }

}
Then you create a socket factory that uses our trust manager
private SSLSocketFactory createSocketFactory() throws KeyManagementException, NoSuchAlgorithmException{
 TrustManager[] trustMan = new TrustManager[] { new AllForgivingTrustManager() };
 SSLContext sslContext = SSLContext.getInstance("TLS");
 sslContext.init(new KeyManager[0], trustMan, new SecureRandom());
 return (SSLSocketFactory) sslContext.getSocketFactory();
}

The last part: create the HTTPS connection. Having a helper method for that can be quite handy. Note the setHostnameVerifier method being called below. Only do that if you want to ignore the host name in the certificate and keep in mind, ignoring the host name can be very dangerous!
private HttpsURLConnection getConnection(URL url) throws IOException{
 HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
 // set this only if you intend to POST data
        connection.setDoOutput(true);
 connection.setHostnameVerifier(new AllowAllHostnameVerifier());
 connection.setSSLSocketFactory(createSocketFactory());
 
 return connection;
}
And it's done! You can use all this to retrieve any web page or file. See my previous post How to download a file/web page using HTTP on Android/Java for more details.
public void downloadToFile(String sourceUrl, FileOutputStream destFile) throws IOException{
 URL url = new URL(sourceUrl);
 HttpsURLConnection connection = (HttpsURLConnection) getConnection(url);
 try{
  InputStream connectionStream = new BufferedInputStream(connection.getInputStream());
  try{
   int bytesRead;
   byte[] readBuffer = new byte[1024*32];
   while((bytesRead = connectionStream.read(readBuffer)) > 0){
    destFile.write(readBuffer, 0, bytesRead);
   }
  }finally{
   connectionStream.close();
  }
 }finally{
  connection.disconnect();
 }
}

Saturday, 1 November 2014

How to download a file/web page using HTTP on Android/Java

If you're using HTTP and not HTTPS, the method below is ready to go!
I'll be writing how to do it in HTTPS shortly. Please come back later ;)
private void downloadToFile(String sourceUrl, FileOutputStream destFile) throws IOException{
	URL url = new URL(sourceUrl);
	HttpURLConnection connection = (HttpURLConnection) url.openConnection();
	try{
		InputStream connectionStream = new BufferedInputStream(connection.getInputStream());
		try{
			int bytesRead;
			byte[] readBuffer = new byte[1024*32];
			while((bytesRead = connectionStream.read(readBuffer)) > 0){
				destFile.write(readBuffer, 0, bytesRead);
			}
		}finally{
			connectionStream.close();
		}
	}finally{
		connection.disconnect();
	}
}

public void testDownloadWebPage() throws IOException{
	String fileName = "webpage.html";
	String webPageUrl = "http://devhelpers.blogspot.com/";
        // openFileOutput is available in the activity / context and will create a file in the app's data directory
	FileOutputStream fs = openFileOutput(fileName, Context.MODE_PRIVATE);
	try{
		client.downloadToFile(webPageUrl, fs);
	}finally{
		fs.close();
	}

}

How to parse and create JSON string Android/Java

Use the package org.json.JSONObject, present in the android SDK.

Example:
  JSONObject j = new JSONObject();
  // values can be strings, booleans, ints and/or objects
  j.put("stringFieldName", "fieldValue");
  j.put("intFieldName", 42);
  // put throws an exception if any of the arguments is invalid.
  // if you're not sure about if what you're trying to write to JSON is null or not, you can use putOpt
  String nonNullField = "nonNullFieldName";
  String nullField = "nullFieldName";
  String thisIsAnArgumentAndImNotSureIfItWillBeNull = null;
  j.putOpt(nonNullField, "this is a non nul value");
  // nullField will not be added to the JSON
  j.putOpt(nullField, thisIsAnArgumentAndImNotSureIfItWillBeNull);

  String jsonString = j.toString();
  // jsonString is
  // {"nonNullFieldName":"this is a non nul value","intFieldName":42,"stringFieldName":"fieldValue"}
  // use JSONObject.toString(2) to return an indented JSON string with 2 spaces
  /*
   {
     "nonNullFieldName": "this is a non null value",
     "intFieldName": 42,
     "stringFieldName": "fieldValue"
   }
  */
 

You can created JSON trees by putting a JSONObject into another JSONObject
Parsing a JSON string is just as easy
  String jsonString = ...;//something like // {"nonNullFieldName":"this is a non null value","intFieldName":42,"stringFieldName":"fieldValue"}
  JSONObject j = new JSONObject(jsonString);
  // use the get methods to return the parsed values
  int intValue = j.getInt(...);
  String strValue = j.getString(...);


It's a little different if you're using pure java though
  JsonObject jb = Json.createObjectBuilder();
  jb.add("firstName", "Donald duck");
  jb.add("address", "Jupiter")
  jb.add("number", 42);
  
  // and here you have it!
  String jsonText = jb.toString();
  
More here