Front-End Web & Mobile

Retrieving Media on Android With KitKat

Introduction

With the introduction of Android version 4.4 KitKat, the way developers commonly access media files has changed. This post introduces some of the issues relevant to Amazon Web Service customers, including breaking changes to the S3Uploader sample code and how these changes were resolved.

What’s new

KitKat has introduced a new way of providing and retrieving content with the Android Storage Access Framework. This new feature allows developers to share content in the cloud, such as pictures in Amazon S3, with document providers, a subclass of content providers.

How it can break old code

Previously, if you were trying to get data from a content provider, you would launch an Intent with Intent.ACTION_GET_CONTENT. The data that was passed back contained a URI which could be used to retrieve the file path of the media the user selected. With KitKat, the URI returned is now relative to the content provider the media came from. This means that you can no longer access the underlying file for the selected media, because it may not be stored on the device. This change may break your app if it uses the passed-back URI to get a file path on KitKat.

The following sample code demonstrates how we updated the S3Uploader sample to work with KitKat.

What needs to be changed

There are two main changes that need to be made. First, we must modify PutObjectRequest to take a file stream instead of a path. And second, because we are using a stream, we need to provide metadata that could previously have been gathered automatically with a file path—namely, the content type and size. Note that the size may not always be available, but we should always try to retrieve it. If we do not include it, the SDK will have to load all of the data into memory to calculate the content size.

1. Getting the Metadata

Here, instead of querying for the file path using MediaStore.Images.Media.DATA, we use the OpenableColumns class to query for content size and type. We then wrap that information in an ObjectMetadata object.

    String[] filePathColumn = { MediaStore.Images.Media.DATA };

    Cursor cursor = getContentResolver().query(selectedImage,
                filePathColumn, null, null, null);

Changes to

    String[] fileSizeColumn = { OpenableColumns.SIZE }; 

    Cursor cursor = getContentResolver().query(selectedImage,
                fileSizeColumn, null, null, null);
    cursor.moveToFirst();

    int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
    String size = cursor.getString(sizeIndex);
    cursor.close();

    ObjectMetadata metadata = new ObjectMetadata();
    metadata.setContentType(getContentResolver().getType(selectedImage));
    if(size != null){
        metadata.setContentLength(Long.parseLong(size));
    }

2. The PutObjectRequest

Next, we use a different constructor for PutObjectRequest to take an input stream and an ObjectMetadata instead of a file path.

    PutObjectRequest por = new PutObjectRequest(
        Constants.getPictureBucket(), Constants.PICTURE_NAME,
        new java.io.File(filePath));

Changes to

    PutObjectRequest por = new PutObjectRequest(
        Constants.getPictureBucket(), Constants.PICTURE_NAME,
        resolver.openInputStream(selectedImage), metadata);

Conclusion

If you are uploading data to S3 by getting the file path location through a content resolver with MediaStore.Images.Media.DATA, your app will not work with Android version 4.4 KitKat. Instead, you will need to get an input stream and metadata from a content resolver in order to upload the data. We hope this blog post makes it clear how to update your app to work with AWS and KitKat. As always, please let us know what you think by leaving a comment below.