Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get documents in an android directory that PhoneGap will see

I'd like to be able to copy some files into my PhoneGap / Cordova's Documents directory, so that they show up when I use the cordova-plugin-file API to list that directory. Unfortunately, there seems to be some disconnect between the file API and what's actually laid out on the tablet's storage. Here's what the File plugin spec says the system directory structure should look like:

Android File System Layout

  • file:///android_asset/ | cordova.file.applicationDirectory
  • file:///android_asset/data/data/<app-id>/ | cordova.file.applicationStorageDirectory
  • file:///android_asset/data/data/<app-id>/cache | cordova.file.cacheDirectory
  • file:///android_asset/data/data/<app-id>/files | cordova.file.dataDirectory
  • file:///android_asset/data/data/<app-id>/Documents | cordova.file.documents
  • <sdcard>/ | cordova.file.externalRootDirectory
  • <sdcard>/Android/data/<app-id>/ | cordova.file.externalApplicationStorageDirectory
  • <sdcard>/Android/data/<app-id>/cache | cordova.file.externalCacheDirectry
  • <sdcard>/Android/data/<app-id>/files | cordova.file.externalDataDirectory

Unfortunately, I'm not seeing this when I plug my device (4.4.2 / Lenovo tablet) into my PC or Mac. Instead, I see:

- Internal Storage
|- .IdeaDesktopHD
|- .lelauncher
|- .magic
|- .powercenterhd
|- Alarms
|- Android
|- Audio
|- Bluetooth
|- Contact
|- data
|- DCIM
|- Document
|- Download
|- googleota
|- legc
|- LenovoReaper
|- LesyncDownload
|- Movies
|- MyFavorite
|- Notifications
|- Others
|- Pictures
|- Podcasts
|- powercenterhd
|- Ringtones
|- SHAREit

Any idea where I should be copying the files so that my app can see them?

like image 915
eb1 Avatar asked Apr 16 '15 14:04

eb1


1 Answers

Well now. Part of my problem was that I got bit by the asynchronous nature of the filesystem / cordova-plugin-file API on cordova. I had to do some code refactoring to get the file list to show up properly, but once I did, the files displayed properly regardless of where they were on the device.

Here's the applicable code. Note that you'll need the cordova-plugin-file added to your Cordova/PhoneGap project, and that it won't work in the browser. I actually have this block inside another if/then block -- if it's running in a browser, show the html5 <input type=file>, if it's in a mobile device, show this block:

var localURLs    = [
    cordova.file.dataDirectory,
    cordova.file.documentsDirectory,
    cordova.file.externalApplicationStorageDirectory,
    cordova.file.externalCacheDirectory,
    cordova.file.externalRootDirectory,
    cordova.file.externalDataDirectory,
    cordova.file.sharedDirectory,
    cordova.file.syncedDataDirectory
];
var index = 0;
var i;
var statusStr = "";
var addFileEntry = function (entry) {
    var dirReader = entry.createReader();
    dirReader.readEntries(
        function (entries) {
            var fileStr = "";
            var i;
            for (i = 0; i < entries.length; i++) {
                if (entries[i].isDirectory === true) {
                    // Recursive -- call back into this subdirectory
                    addFileEntry(entries[i]);
                } else {
                   fileStr += (entries[i].fullPath + "<br>"); // << replace with something useful
                   index++;
                }
            }
            // add this directory's contents to the status
            statusStr += fileStr;
            // display the file list in #results
            if (statusStr.length > 0) {
                $("#results").html(statusStr);
            } 
        },
        function (error) {
            console.log("readEntries error: " + error.code);
            statusStr += "<p>readEntries error: " + error.code + "</p>";
        }
    );
};
var addError = function (error) {
    console.log("getDirectory error: " + error.code);
    statusStr += "<p>getDirectory error: " + error.code + ", " + error.message + "</p>";
};
for (i = 0; i < localURLs.length; i++) {
    if (localURLs[i] === null || localURLs[i].length === 0) {
        continue; // skip blank / non-existent paths for this platform
    }
    window.resolveLocalFileSystemURL(localURLs[i], addFileEntry, addError);
}

EDIT (Feb 2018): even if you can see the files on Android File Transfer, you might not get any results back programmatically, even if you have build time permissions set in your Cordova app. This is due to runtime permission checks added to Android (I believe > 6.0). There are a couple plugins that can help get around this; at some point, I'm guessing the file plugin will add automatic requests for it as well. Here's what I've done as of Cordova cli-7.0.1:

In your config.xml, set the needed app permissions. You'll need READ_EXTERNAL_STORAGE (and write as well, if you are going to do that). I'm also adding two plugins that are referenced below:

<plugin name="cordova-plugin-device" source="npm" spec="1.1.6" />
<plugin name="cordova.plugins.diagnostic" spec="^3.7.1" />

<config-file platform="android" parent="/manifest" mode="replace">
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
</config-file>

Then, preferably somewhere in your app's startup code (i.e., the handler for the device ready event), check for the runtime permissions and add them if needed:

if (device.platform === "Android") {
    // request read access to the external storage if we don't have it
    cordova.plugins.diagnostic.getExternalStorageAuthorizationStatus(function (status) {
        if (status === cordova.plugins.diagnostic.permissionStatus.GRANTED) {
            console.log("External storage use is authorized");
        } else {
            cordova.plugins.diagnostic.requestExternalStorageAuthorization(function (result) {
                console.log("Authorization request for external storage use was " + (result === cordova.plugins.diagnostic.permissionStatus.GRANTED ? "granted" : "denied"));
            }, function (error) {
                console.error(error);
            });
        }
    }, function (error) {
        console.error("The following error occurred: " + error);
    });
}

EDIT (August 2021): Android 10.x and above introduces the concept of scoped storage, which is further refined in Android 11 (see https://developer.android.com/about/versions/11/privacy/storage). Most of this is to tighten up security on your device. What it means for your app is that reading/writing to permanent files should be limited to the two following sandboxed directories if you're a good Android citizen:

  • cordova.file.externalDataDirectory (if you've got an SD card)
  • cordova.file.datadirectory

Unless you have a good reason to be looking around the entire filesystem (and can convince the Google Play store team of this), your app is likely to get rejected if you try to invoke All files access. From the linked article:

Note: If you publish your app to Google Play, carefully read the notice. If you target Android 11 and declare All files access, it can affect your ability to publish and update your app on Google Play.

like image 81
eb1 Avatar answered Nov 09 '22 10:11

eb1