Tech Note: Downloading Data in the Background on Mobile
Oculus Developer Blog
|
Posted by Gabor Szauer
|
November 7, 2017
|
Share

Gear VR applications sometimes need to download large files such as video or game levels. The Mobile VRC, our checklist of launch requirements of Gear VR applications, contains the following rule for handling downloads:

The app must continue to download content if the user removes the headset or removes the device from headset. (more)

Many apps fail this requirement the first time because they implement downloading within their own process. When a Gear VR is undocked from the headset the foreground application is killed, thus ending any data transfer that might have been in progress. Google's AndroidDownloadManager was created to solve this sort of case.

The download manager handles long running downloads of large files in the background. It takes care of resuming downloads, handles spotty internet connections, and resumes downloads even after a phone reboot.

This post shows how to build a Gear VR application that can use the download manager. A complete Unity plugin is also provided, as well as all .jar files needed to use the Download Manager.

TL;DR: Unity plugin

DOWNLOAD UNITY PACKAGE

The plugin contains a downloadhelper.jar file, which is the compiled Java code that is covered below. It also contains DownloadHelper.cs, a C# script that will let your Unity project interact with the Java code.

In order to handle permissions using android API under 23 the download helper relies on the Android Support Library. This plugin contains also contains android-support-v4.jar, which contains compatibility code for API levels under 23. If you already have a copy of android-support-v4.jar in your project, you don't have to include this one.

The DownloadHelper script provides a public API made up of only 3 functions:

  • public static long StartDownload(string url, string destination, string title)
  • public static int GetDownloadProgress(long id)
  • public static void CancelDownload(long id)

The StartDownload function takes three arguments and returns a download id, or -1 if an error occurs. The id which is returned can be used to query download progress, or to cancel the download. The first argument is the URL of the file to download. The second argument is the destination of where the downloaded file will be stored, this path is relative to Application.persistentDataPath. The last argument of this function is the title of the download to display in the download pulldown menu:

The GetDownloadProgress function takes the download id returned from the StartDownload function for an argument and returns an integer in the range of 0 to 100, or -1 if an error has occurred. The CancelDownload function also takes a download id and stops the specified download.

The plugin will ask for permission at runtime to download files when using Marshmallow (API 23) and above. To ask the user for read / write permissions when the app launches, add the following permissions to the android manifest file:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

Permissions

Before exploring the actual download manager code, it's important to mention that the app will need WRITE_EXTERNAL_STORAGE and READ_EXTERNAL_STORAGE permissions. Starting with API 23 Android supports runtime permission popups. In order to support android API < 23, the following permissions have to be added to the android manifest file:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

To support API < 23, runtime permissions are queried with ContextCompat.checkSelfPermissions and requested using ActivityCompat.requestPermissions.

How it works: the Java code

An instance of DownloadManager can be obtained from a context with the following code:

    public DownloadManager GetDownloadManager(Context context) {
    	return (DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);
  }

The DownloadManager class contains a Request class. This class is used to actually download stuff. The code snippet below shows the minimal code needed to download a file. The request class has other properties that can be set such as specifying the type of network connection to download over and various display settings. To learn more about these functions, refer to the DownloadManager.Request documentation.

public long StartDownload(String url, String destination, String title) {
      Uri dlURI = Uri.parse(url);
      Uri destURI = Uri.parse(destination);
    
      DownloadManager manager = GetDownloadManager(mContext);
      DownloadManager.Request request = new DownloadManager.Request(dlURI);
      
      request.setTitle(title);
      request.setDestinationUri(destURI);
      request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
  
      long id = manager.enqueue(request);
      return id;
    }

Any active download can be stopped using the remove function of the DownloadManager:

public void CancelDownload(long id) {
      DownloadManager manager = GetDownloadManager(mContext);
      manager.remove(id);
  }

The download manager also contains a Query class. This class can be used to query the current state of a download, based on the download id:

public int GetDownloadProgress(long downloadId) {
      DownloadManager manager = GetDownloadManager(mContext);
      DownloadManager.Query q = new DownloadManager.Query();
      
      q.setFilterById(downloadId);
      Cursor cursor = manager.query(q);
      cursor.moveToFirst();
 
      int downloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR));
      int total = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
      return (int) ((downloaded * 100L) / total);
    }

This query is a bit naïve in that it only returns a value between 0 and 100. A download can be in many states, for example users can pause a download or a download can fail. A more robust version of this function could return some status code to indicate the state of the download. The state of the download can be queried with the following code:

int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)); 

These are the relevant states that the above function will return:

  • DownloadManager.STATUS_PAUSED
  • DownloadManager.STATUS_PENDING
  • DownloadManager.STATUS_RUNNING
  • DownloadManager.STATUS_SUCCESSFUL
  • DownloadManager.STATUS_FAILED

For more information on what can be queried, refer to the Cursor class documentation. Below is a link to the final DownloadHelper.java file. This file contains the code to start, cancel and query file downloads, it also handles permissions using the compatibility API asking for runtime permissions when using API Level 23+.

DownloadHelper.java