teaching machines

Copying assets out to storage on Android

PocketSphinx depends on some files to get its job done. The HOWTO assumes you push those files over manually. We’d rather bundle them with our app as resources or assets. Assets have the advantage of retaining their directory structure, so we prefer to use them. However, assets don’t have a path to their location on the file system; they are bundled up with our application. PocketSphinx expects a path.

Our solution is to mirror out the assets directory to storage the first time an app is run (or whenever that directory disappears). These two methods recursively copy the asset structure we want to mirror:

/**
 * Copy the asset at the specified path to this app's data directory. If the
 * asset is a directory, its contents are also copied.
 * 
 * @param path
 * Path to asset, relative to app's assets directory.
 */
private void copyAsset(String path) {
  AssetManager manager = getAssets();

  // If we have a directory, we make it and recurse. If a file, we copy its
  // contents.
  try {
    String[] contents = manager.list(path);

    // The documentation suggests that list throws an IOException, but doesn't
    // say under what conditions. It'd be nice if it did so when the path was
    // to a file. That doesn't appear to be the case. If the returned array is
    // null or has 0 length, we assume the path is to a file. This means empty
    // directories will get turned into files.
    if (contents == null || contents.length == 0)
      throw new IOException();

    // Make the directory.
    File dir = new File(getExternalFilesDir(null), path);
    dir.mkdirs();

    // Recurse on the contents.
    for (String entry : contents) {
      copyAsset(path + "/" + entry);
    }
  } catch (IOException e) {
    copyFileAsset(path);
  }
}

/**
 * Copy the asset file specified by path to app's data directory. Assumes
 * parent directories have already been created.
 * 
 * @param path
 * Path to asset, relative to app's assets directory.
 */
private void copyFileAsset(String path) {
  File file = new File(getExternalFilesDir(null), path);
  try {
    InputStream in = getAssets().open(path);
    OutputStream out = new FileOutputStream(file);
    byte[] buffer = new byte[1024];
    int read = in.read(buffer);
    while (read != -1) {
      out.write(buffer, 0, read);
      read = in.read(buffer);
    }
    out.close();
    in.close();
  } catch (IOException e) {
    Log.e(e);
  }
}

Comments

  1. Loïc says:

    Working like a charm.

    I used it for copying OCR data.

    Thanks

  2. Borja says:

    Awesome! I wasted much much time searching the internet for a way on how to do this and none work until I reached this solution!

    Thanks a lot!

  3. Shru says:

    This is so perfect that I have put links to this post on each forum where the questions related to this were asked.

  4. AndroidNoob says:

    * @param path
    * Path to asset, relative to app’s assets directory.

    So if i have an assert as follows — AssertFolder/VideoClips , how do i specify the parameter to the function?

    1. Chris Johnson says:

      The same way the AssetManager would expect them: as a relative path from assets/. If your folder is assets/AssertFolder/VideoClips, then you’d pass “AssertFolder/VideoClips”. See https://developer.android.com/reference/android/content/res/AssetManager.html#list(java.lang.String) for more.

Leave a Reply

Your email address will not be published. Required fields are marked *