Cloud Zone is brought to you in partnership with:

Senior Software Engineer & Architect. I have programmed with a variety of languages. Originally:C/C++, Mostly: Java, Occasionally: PHP, VB, Python, Unix Shell, JavaScript, Mainframe EasyTrieve and JCL. I also do Android development at the present time. Tony is a DZone MVB and is not an employee of DZone and has posted 36 posts at DZone. You can read more from them at their website. View Full User Profile

Android Cloud Apps with Azure

08.26.2012
| 8782 views |
  • submit to reddit

A recent study by Gartner predicts a very significant increase in cloud usage by consumers in a few years, due in great part to the ever growing use of smartphone cameras by the average household. In this context, it could be useful to have a smartphone application that is able to upload / download digital content from a cloud provider.

In this article, we will construct a basic Android prototype that will allow us to plug in the Windows Azure cloud provider, and use the Windows Azure Toolkit for Android ( available at GitHub) to do all of the basic cloud operations: upload content to cloud storage, browse the storage, download or delete files in cloud storage. Once those operations are implemented, we will see how to enable our Android application to receive server push notifications.

First things first, we need to set up a Storage Account in the Azure cloud:

A storage account comes with several options as for data management: we can keep data in blob, table or queue storage. In this article, we will use the blob storage to work with images. The storage account has a primary and secondary access key, either one of the two can be used to execute operations on the storage account. Any of those keys can be regenerated if compromised.

1. Preliminaries

First, the prerequisites:

A getting-started document on Windows Azure Toolkit’s GitHub page covers the installation procedure of all the the required software in detail.

This whole project (Cloid) is freely available at GitHub. So here we’ll limit ourselves to presenting the most relevant code sections along with the corresponding screens.

The User Interface is composed of a few basic activity screens, spawned from the main screen (top center):

Since we use a technology not for its own sake but according to our needs, let’s start by specifying what we want:

public abstract class Storage {

    /** All providers will have accesss to context*/
    protected Context context;

    /** All providers will have accesss to SharedPreferences */
    protected CloudPreferences prefs;

    /** All downloads from providers will be saved on SD card */
    protected String DOWNLOAD_PATH = "/sdcard/DCIM/Camera/";

    /**
     * @throws OperationException
     * */
    public Storage(Context ctx) throws OperationException {
        context = ctx;
        prefs = new CloudPreferences(ctx);
    }

    /**
     * @throws OperationException
     * */
    public abstract void uploadToStorage(String file_path) throws OperationException;

    /**
     * @throws OperationException
     * */
    public abstract void downloadFromStorage(String file_name) throws OperationException;

    /**
     * @throws OperationException
     * */
    public abstract void browseStorage() throws OperationException;

    /**
     * @throws OperationException
     * */
    public abstract void deleteInStorage(String file_name) throws OperationException;

}

The above is the contract that our cloud storage provider will satisfy. We’ll provide a MockStorage implementation that will pretend to carry out a command in order to test our UI (i.e. our scrollable items list, progress bar, exception messages, etc.), so that we can later just plug in Azure storage operations. Note from our activities screen above, that we can switch anytime between Azure Storage and Mock Storage with the press of the toggle button “Cloud ON/OFF” in the Settings screen, saving the preferences afterward.

public class MockStorage extends Storage {

    // code here...
    @Override
    public void uploadToStorage(String file_path) throws OperationException {

        doNothingButSleep();
        //throw new OperationException( "Test error message",
        //                          new Throwable("Reason: upload test") );
    }

    // other methods will also do nothing but sleep...

    /***/
    private void doNothingButSleep(){

         try{ Thread.sleep(5000L); }
         catch (InterruptedException iex){ return; }
    }

2. The Azure Toolkit

The Toolkit comes with a sample application called “Simple”, and two library jars:

  1. access control for android.jar in the wa-toolkit-android\library\accesscontrol\bin  folder
  2. azure storage for android.jar in the  wa-toolkit-android\library\storage\bin  folder

Here we will only use the latter, since we will access directly Azure’s Blob storage. Needless to say, this is not the recommended way, since our credentials will be stored on the handset. A better approach security-wise would be to access Azure storage through Web services hosted on either Azure or other public/private clouds.

Once the Toolkit is ready for use, we need to think a bit about Settings. Using an Azure Blob storage only requires 3 fields:  an Account Name, an Access Key, and a container for our images. The Access Key is quite a long string (88 characters) and is kind of a pain to type, so one way to do the setup is to configure the Android res/values/strings.xml file to set the default values:

...
<!-- AZURE configuration direct -->
<string name="azure_direct_account_name">cloid</string>
<string name="azure_direct_access_key">insert-access-key-here</string>
<string name="azure_container">pictures</string>
...

However, because we may want to overwrite the default values above (e.g. create another container), we will also save the values on the Settings screen in Android’s SharedPreferences.

And now, let’s implement the AzureStorage class.

3. Azure Blob Storage Operations

3.1. Storage Initialization

The AzureStorage constructor gets its data from Android preferences (from its superclass), then constructs a connection string used to access the Storage account, creates a Blob client and retrieves a reference to the container of images. If the user changed the default container “pictures” in Settings, then a new (empty) one will be created with that new name. A container is any grouping of blobs under a name. No blob exists outside of a container.

// package here
// other imports
import com.windowsazure.samples.android.storageclient.BlobProperties;
import com.windowsazure.samples.android.storageclient.CloudBlob;
import com.windowsazure.samples.android.storageclient.CloudBlobClient;
import com.windowsazure.samples.android.storageclient.CloudBlobContainer;
import com.windowsazure.samples.android.storageclient.CloudBlockBlob;
import com.windowsazure.samples.android.storageclient.CloudStorageAccount;

public class AzureStorage extends Storage {

private CloudBlobContainer container;

/ * @throws OperationException
* */
public AzureStorage(Context ctx) throws OperationException {
  super(ctx);

  // set from prefs
  String acct_name = prefs.getAccountName();
  String access_key = prefs.getAccessKey();

  // get connection string
  String storageConn = "DefaultEndpointsProtocol=http;" +
  "AccountName=" + acct_name +
  ";AccountKey=" + access_key;

  // get CloudBlobContainer
  try {
     // Retrieve storage account from storageConn
     CloudStorageAccount storageAccount = CloudStorageAccount.parse(conn);

     // Create the blob client
     // to get reference objects for containers and blobs
     CloudBlobClient blobClient = storageAccount.createCloudBlobClient();

    // Retrieve reference to a previously created container
    container = blobClient.getContainerReference( prefs.getContainer() );
    container.createIfNotExist();
  }
  catch (Exception e) {
    throw new OperationException("Error from initBlob: " + e.getMessage(), e);
  }
 }
// code...

We will use that container reference CloudBlobContainer throughout our upcoming Cloud operations.

3.2. Uploading images

We will upload a file from Android’s gallery to the cloud, keeping the same filename. “Screener” is just a utilities class (see GitHub repository) that does a number of useful things, e.g. extracting a file name from its path and setting the right mime type (“image/jpeg”, “image/png”, etc.).

The two kinds of blobs are page blobs and block blobs.  The (very) short story is that page blobs are optimized for read & write operations, while block blobs let us upload large files efficiently. In particular we can upload multiple blocks in parallel to decrease upload time.  Here we are uploading a blob (Gallery image) as a set of blocks.

/**
 * @throws OperationException */
 @Override
 public void uploadToStorage(String file_path) throws OperationException {

     try {
	   // Create or overwrite blob with contents from a local file
	   // use same name than in local storage
	   CloudBlockBlob blob = container.getBlockBlobReference(
	    			       Screener.getNameFromPath(file_path) );
	   File source = new File(file_path);
	   blob.upload( new FileInputStream(source), source.length() );
	   blob.getProperties().contentType = Screener.getImageMimeType(file_path);
	   blob.uploadProperties();
     }
     catch (Exception e) {
	   throw new OperationException("Error from uploadToStorage: "
                                          + e.getMessage(), e);
     }
 }

Bear in mind that we are not checking if the file already exists in cloud storage. Therefore we will overwrite any existing file with the same name as the one we are uploading. That is usually not desirable in production code. Here’s the screen flow of the upload operation:

3.3. Browsing the Cloud

For browsing, we store all our blobs in our container into a list of Items that we will display in Android as a scrollable list of image names in a subclass of android.app.ListActivity. Once one item in the list is clicked (“touched”) by the user, we want to display some image properties such as the image size (important when deciding to download), its mime type, and the date it was last operated upon.

/**
  * @throws OperationException
 * */
 @Override
 public void browseStorage() throws OperationException{

    // reset uri list for refresh - no caching
    Item.itemList.clear();

    // Loop over blobs within the container
    try {
	for (CloudBlob blob : container.listBlobs()) {
	    blob.downloadAttributes();
	    BlobProperties props = blob.getProperties();
	    long ksize =  props.length/1024;
	    String type = props.contentType;
	    Date lastModified = props.lastModified;

	    Item item = new Item(blob.getUri(), blob.getName(), ksize, type, lastModified);
	    Item.itemList.add(item);
	} // end loop
    }
    catch (Exception e) {
	 throw new OperationException("Error from browseStorage: " + e.getMessage(), e);
    }
 }

Here’s the screen flow of the Browse operation. Pressing on an item on the list displays its details and operations on the image, which we will look at next:

3.4. Downloading images

Our download method is pretty straightforward. Note that we are downloading to the Android handset’s SD card by using DOWNLOAD_PATH from the superclass.

/**
 * @throws OperationException
 * */
 @Override
 public void downloadFromStorage(String file_name) throws OperationException{

     try {
	    for (CloudBlob blob : container.listBlobs()) {
	    // Download the item and save it to a file with the same name as arg
		if(blob.getName().equals(file_name)){
	             blob.download( new FileOutputStream(DOWNLOAD_PATH
                                   + blob.getName()) );
		     break;
		}
	    }
    }
    catch (Exception e) {
	throw new OperationException("Error from downloadFromStorage: "
                        + e.getMessage(), e);
    }
}

And the corresponding UI flow. Instead of displaying the image right after the download, we chose to include a link to the Gallery (bottom of the screen) where the freshly retrieved image appears on top of the Gallery’s stack of pictures:

3.5. Deleting images

The Delete operation performed on a blob up in the cloud is also rather simple:

/**
 * @throws OperationException */
 @Override
 public void deleteInStorage(String file_name) throws OperationException{

    try {
   // Retrieve reference to a blob named file_name
   CloudBlockBlob blob = container.getBlockBlobReference(file_name);
   // Delete the blob
   blob.delete();
    }
    catch (Exception e) {
throw new OperationException("Error from deleteInStorage: "
                     + e.getMessage(), e);
    }
  }

And its associated UI screens series. Note that after confirming the operation, and when deletion completes, the browsing list of items is automatically refreshed, and we can see that the image is no longer on the list of blobs in our storage container.

3.6. Wrapping up

The AzureStorage methods are called inside a basic work thread, which will take care of all cloud operations:

// called inside a thread
try {
      // get storage instance from factory
      Storage store = StorageFactory.getStorageInstance(this,
                          StorageFactory.Provider.AZURE_STORAGE);
      // for the progress bar
      incrementWorkCount();

      // do ops
       switch(operation){

            case UPLOAD :   store.uploadToStorage(path);
                            break;
            case BROWSE :   store.browseStorage();
                            break;
            case DOWNLOAD : store.downloadFromStorage(path);
                            // refresh Gallery
                            sendBroadcast( new Intent( Intent.ACTION_MEDIA_MOUNTED,
                                Uri.parse("file://"+ Environment.getExternalStorageDirectory()) ) );
                            break;
             case DELETE :  store.deleteInStorage(path);
                            break;
       } // end switch
 }
 catch (OperationException e) {
     recordError(e);
 }

Notice how we are telling the Android image Gallery to refresh by issuing a broadcast once a new file is downloaded from the cloud to the SD card. There are different ways to do this, but without that call, the Gallery won’t show the new image before the next system scheduled media scan. Again, for the full code, refer to this project on GitHub.

We are done with the basic cloud operations. All we had to do was plug in our AzureStorage implementation class and get an instance of it through a factory, with minimal impact on preexisting code.

4. Push Notifications

Up to this point we have demonstrated device-initiated communication with the cloud. For cloud-initiated or Push communication, The Android platform uses Google Cloud Messaging (GCM).

In a previous article, I wrote about how to integrate GCM into an existing Android application. Here we will add a second set of settings for server push. Our client code will connect with any GCM server and it will set the status on our main activity (last screen shot on the right) once the information in Push preferences is correctly set.

5. Conclusions

The Toolkit documentation is kind of sparse (which is why the community needs more articles like this). Also, the sample application doesn’t cover much (maybe the reason why it’s called “Simple”), and it has room for improvement. However, the library itself is fully functional, and once we figure out the API, it all works quite nicely. Of course, this application is itself pretty basic and doesn’t cover lots of other features, like access control, permissions, metadata, and snapshots. But it is a start.

 

Published at DZone with permission of Tony Siciliani, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)