Explorar o código

增加ios状态栏透明变色

xiongzhu %!s(int64=8) %!d(string=hai) anos
pai
achega
c91f3da010
Modificáronse 100 ficheiros con 10333 adicións e 62 borrados
  1. 2 1
      .gitignore
  2. 24 2
      config.xml
  3. 15 0
      package-lock.json
  4. 7 2
      package.json
  5. 198 4
      platforms/android/android.json
  6. 176 1
      platforms/android/assets/www/cordova_plugins.js
  7. 1 1
      platforms/android/assets/www/leitongxue.html
  8. 120 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/DirectoryEntry.js
  9. 75 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/DirectoryReader.js
  10. 263 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/Entry.js
  11. 81 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/File.js
  12. 95 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/FileEntry.js
  13. 49 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/FileError.js
  14. 301 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/FileReader.js
  15. 58 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/FileSystem.js
  16. 44 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/FileUploadOptions.js
  17. 33 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/FileUploadResult.js
  18. 327 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/FileWriter.js
  19. 39 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/Flags.js
  20. 26 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/LocalFileSystem.js
  21. 43 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/Metadata.js
  22. 70 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/ProgressEvent.js
  23. 51 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/android/FileSystem.js
  24. 29 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/browser/isChrome.js
  25. 65 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/fileSystemPaths.js
  26. 47 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/fileSystems-roots.js
  27. 28 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/fileSystems.js
  28. 84 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/requestFileSystem.js
  29. 94 0
      platforms/android/assets/www/plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js
  30. 0 0
      platforms/android/assets/www/static/css/app.44c6fba2497d021b0fe5996010bdf47c.css
  31. 0 0
      platforms/android/assets/www/static/css/app.44c6fba2497d021b0fe5996010bdf47c.css.map
  32. 0 0
      platforms/android/assets/www/static/css/app.4f8eeb06a5dd3d82226904aed0299860.css
  33. 0 0
      platforms/android/assets/www/static/css/app.4f8eeb06a5dd3d82226904aed0299860.css.map
  34. 0 0
      platforms/android/assets/www/static/js/app.069151de1070e52c112d.js
  35. 0 0
      platforms/android/assets/www/static/js/app.069151de1070e52c112d.js.map
  36. 0 0
      platforms/android/assets/www/static/js/app.aba1de22627cc6f1889c.js
  37. 0 0
      platforms/android/assets/www/static/js/app.aba1de22627cc6f1889c.js.map
  38. 0 0
      platforms/android/assets/www/static/js/manifest.3ad1d5771e9b13dbdad2.js.map
  39. 176 1
      platforms/android/platform_www/cordova_plugins.js
  40. 120 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/DirectoryEntry.js
  41. 75 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/DirectoryReader.js
  42. 263 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/Entry.js
  43. 81 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/File.js
  44. 95 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/FileEntry.js
  45. 49 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/FileError.js
  46. 301 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/FileReader.js
  47. 58 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/FileSystem.js
  48. 44 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/FileUploadOptions.js
  49. 33 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/FileUploadResult.js
  50. 327 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/FileWriter.js
  51. 39 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/Flags.js
  52. 26 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/LocalFileSystem.js
  53. 43 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/Metadata.js
  54. 70 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/ProgressEvent.js
  55. 51 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/android/FileSystem.js
  56. 29 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/browser/isChrome.js
  57. 65 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/fileSystemPaths.js
  58. 47 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/fileSystems-roots.js
  59. 28 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/fileSystems.js
  60. 84 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/requestFileSystem.js
  61. 94 0
      platforms/android/platform_www/plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js
  62. 18 2
      platforms/android/res/xml/config.xml
  63. 320 0
      platforms/android/src/com/truckmovers/cordova/RemoteInjectionPlugin.java
  64. 294 0
      platforms/android/src/org/apache/cordova/file/AssetFilesystem.java
  65. 223 0
      platforms/android/src/org/apache/cordova/file/ContentFilesystem.java
  66. 134 0
      platforms/android/src/org/apache/cordova/file/DirectoryManager.java
  67. 29 0
      platforms/android/src/org/apache/cordova/file/EncodingException.java
  68. 29 0
      platforms/android/src/org/apache/cordova/file/FileExistsException.java
  69. 1225 0
      platforms/android/src/org/apache/cordova/file/FileUtils.java
  70. 331 0
      platforms/android/src/org/apache/cordova/file/Filesystem.java
  71. 30 0
      platforms/android/src/org/apache/cordova/file/InvalidModificationException.java
  72. 513 0
      platforms/android/src/org/apache/cordova/file/LocalFilesystem.java
  73. 64 0
      platforms/android/src/org/apache/cordova/file/LocalFilesystemURL.java
  74. 29 0
      platforms/android/src/org/apache/cordova/file/NoModificationAllowedException.java
  75. 94 0
      platforms/android/src/org/apache/cordova/file/PendingRequests.java
  76. 30 0
      platforms/android/src/org/apache/cordova/file/TypeMismatchException.java
  77. 3 3
      platforms/ios/frameworks.json
  78. 231 36
      platforms/ios/ios.json
  79. 184 9
      platforms/ios/platform_www/cordova_plugins.js
  80. 120 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/DirectoryEntry.js
  81. 75 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/DirectoryReader.js
  82. 263 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/Entry.js
  83. 81 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/File.js
  84. 95 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileEntry.js
  85. 49 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileError.js
  86. 301 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileReader.js
  87. 58 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileSystem.js
  88. 44 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileUploadOptions.js
  89. 33 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileUploadResult.js
  90. 327 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileWriter.js
  91. 39 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/Flags.js
  92. 26 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/LocalFileSystem.js
  93. 43 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/Metadata.js
  94. 70 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/ProgressEvent.js
  95. 29 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/browser/isChrome.js
  96. 65 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/fileSystemPaths.js
  97. 47 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/fileSystems-roots.js
  98. 28 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/fileSystems.js
  99. 32 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/ios/FileSystem.js
  100. 84 0
      platforms/ios/platform_www/plugins/cordova-plugin-file/www/requestFileSystem.js

+ 2 - 1
.gitignore

@@ -1,4 +1,5 @@
 .DS_Store
 node_modules/
 .idea
-.gradle
+.gradle
+build/

+ 24 - 2
config.xml

@@ -3,7 +3,7 @@
     <name>雷同学</name>
     <description>        A sample Apache Cordova application that responds to the deviceready event.    </description>
     <author email="dev@cordova.apache.org" href="http://cordova.io">        Apache Cordova Team    </author>
-    <content src="leitongxue.html#/home/recommend" />
+    <content src="leitongxue.html/#/home/recommend" />
     <plugin name="cordova-plugin-whitelist" spec="1" />
     <access origin="*" />
     <allow-intent href="http://*/*" />
@@ -14,6 +14,14 @@
     <allow-intent href="geo:*" />
     <platform name="android">
         <allow-intent href="market:*" />
+        <access origin="*" />
+        <allow-navigation href="*" />
+        <allow-navigation href="http://*/*" />
+        <allow-navigation href="https://*/*" />
+        <allow-navigation href="tel:*" />
+        <allow-navigation href="sms:*" />
+        <allow-navigation href="mailto:*" />
+        <allow-navigation href="geo:*" />
         <config-file parent="/*" target="AndroidManifest.xml">
             <uses-permission android:name="android.permission.INTERNET" />
         </config-file>
@@ -26,9 +34,22 @@
         <preference name="StatusBarOverlaysWebView" value="false" />
     </platform>
     <platform name="ios">
+        <config-file parent="CFBundleLocalizations" target="*-Info.plist">
+            <array>
+                <string>zh_CN</string>
+            </array>
+        </config-file>
         <allow-intent href="itms:*" />
         <allow-intent href="itms-apps:*" />
-        <preference name="StatusBarOverlaysWebView" value="true" />
+        <access origin="*" />
+        <allow-navigation href="*" />
+        <allow-navigation href="http://*/*" />
+        <allow-navigation href="https://*/*" />
+        <allow-navigation href="tel:*" />
+        <allow-navigation href="sms:*" />
+        <allow-navigation href="mailto:*" />
+        <allow-navigation href="geo:*" />
+        <preference name="StatusBarOverlaysWebView" value="false" />
         <splash src="res/screen/ios/Default@2x~universal~anyany.png" />
         <icon height="20" src="res/icon/ios/Icon-20.png" width="20" />
         <icon height="29" src="res/icon/ios/Icon-29.png" width="29" />
@@ -74,6 +95,7 @@
     <plugin name="cordova-plugin-wechatv2" spec="^2.1.2">
         <variable name="WECHATAPPID" value="wx4f228b18e366330c" />
     </plugin>
+    <plugin name="cordova-plugin-remote-injection" spec="^0.5.2" />
     <engine name="android" spec="^6.3.0" />
     <engine name="ios" spec="^4.5.4" />
 </widget>

+ 15 - 0
package-lock.json

@@ -651,11 +651,26 @@
             "resolved": "http://registry.npm.taobao.org/cordova-plugin-device/download/cordova-plugin-device-2.0.2.tgz",
             "integrity": "sha1-/Ajzci5n7ve2xnv8mag99q3Quro="
         },
+        "cordova-plugin-fastrde-injectview": {
+            "version": "0.2.0",
+            "resolved": "http://registry.npm.taobao.org/cordova-plugin-fastrde-injectview/download/cordova-plugin-fastrde-injectview-0.2.0.tgz",
+            "integrity": "sha1-3FQYuPIMpYRpO3qni3IPHd3PkKM="
+        },
+        "cordova-plugin-file": {
+            "version": "6.0.1",
+            "resolved": "http://registry.npm.taobao.org/cordova-plugin-file/download/cordova-plugin-file-6.0.1.tgz",
+            "integrity": "sha1-SWBrjBWlaI1HKPkuSnMloGHeB/U="
+        },
         "cordova-plugin-inappbrowser": {
             "version": "3.0.0",
             "resolved": "http://registry.npm.taobao.org/cordova-plugin-inappbrowser/download/cordova-plugin-inappbrowser-3.0.0.tgz",
             "integrity": "sha1-1K4A02Z2IQdRBXrSWK5K1KkWGto="
         },
+        "cordova-plugin-remote-injection": {
+            "version": "0.5.2",
+            "resolved": "http://registry.npm.taobao.org/cordova-plugin-remote-injection/download/cordova-plugin-remote-injection-0.5.2.tgz",
+            "integrity": "sha1-dUw6UubbLU2r93YRm2/vxPT25eg="
+        },
         "cordova-plugin-statusbar": {
             "version": "2.4.2",
             "resolved": "http://registry.npm.taobao.org/cordova-plugin-statusbar/download/cordova-plugin-statusbar-2.4.2.tgz",

+ 7 - 2
package.json

@@ -17,7 +17,10 @@
         "cordova-plugin-clipboard-x": "^1.0.1",
         "cordova-plugin-crosswalk-webview": "^2.4.1",
         "cordova-plugin-device": "^2.0.2",
+        "cordova-plugin-fastrde-injectview": "^0.2.0",
+        "cordova-plugin-file": "^6.0.1",
         "cordova-plugin-inappbrowser": "^3.0.0",
+        "cordova-plugin-remote-injection": "^0.5.2",
         "cordova-plugin-statusbar": "^2.4.2",
         "cordova-plugin-wechatv2": "^2.1.2",
         "cordova-plugin-whitelist": "^1.3.3",
@@ -47,11 +50,13 @@
             "cordova-plugin-statusbar": {},
             "cordova-plugin-wechatv2": {
                 "WECHATAPPID": "wx4f228b18e366330c"
-            }
+            },
+            "cordova-plugin-file": {},
+            "cordova-plugin-remote-injection": {}
         },
         "platforms": [
             "android",
             "ios"
         ]
     }
-}
+}

+ 198 - 4
platforms/android/android.json

@@ -71,6 +71,14 @@
             {
               "xml": "<preference name=\"WECHATAPPID\" value=\"wx4f228b18e366330c\" />",
               "count": 1
+            },
+            {
+              "xml": "<feature name=\"File\"><param name=\"android-package\" value=\"org.apache.cordova.file.FileUtils\" /><param name=\"onload\" value=\"true\" /></feature>",
+              "count": 1
+            },
+            {
+              "xml": "<allow-navigation href=\"cdvfile:*\" />",
+              "count": 1
             }
           ]
         }
@@ -80,7 +88,7 @@
           "/*": [
             {
               "xml": "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />",
-              "count": 4
+              "count": 5
             },
             {
               "xml": "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />",
@@ -92,7 +100,7 @@
             },
             {
               "xml": "<uses-permission android:name=\"android.permission.INTERNET\" />",
-              "count": 106
+              "count": 120
             },
             {
               "xml": "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />",
@@ -120,7 +128,12 @@
       },
       "config.xml": {
         "parents": {
-          "/*": []
+          "/*": [
+            {
+              "xml": "<feature name=\"RemoteInjection\"><param name=\"android-package\" value=\"com.truckmovers.cordova.RemoteInjectionPlugin\" /><param name=\"onload\" value=\"true\" /></feature>",
+              "count": 1
+            }
+          ]
         }
       }
     }
@@ -165,6 +178,12 @@
     "cordova-plugin-wechatv2": {
       "WECHATAPPID": "wx4f228b18e366330c",
       "PACKAGE_NAME": "com.izouma.leitongxue"
+    },
+    "cordova-plugin-file": {
+      "PACKAGE_NAME": "com.izouma.leitongxue"
+    },
+    "cordova-plugin-remote-injection": {
+      "PACKAGE_NAME": "com.izouma.leitongxue"
     }
   },
   "dependent_plugins": {},
@@ -250,6 +269,179 @@
       "clobbers": [
         "Wechat"
       ]
+    },
+    {
+      "id": "cordova-plugin-file.DirectoryEntry",
+      "file": "plugins/cordova-plugin-file/www/DirectoryEntry.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.DirectoryEntry"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.DirectoryReader",
+      "file": "plugins/cordova-plugin-file/www/DirectoryReader.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.DirectoryReader"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.Entry",
+      "file": "plugins/cordova-plugin-file/www/Entry.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.Entry"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.File",
+      "file": "plugins/cordova-plugin-file/www/File.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.File"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileEntry",
+      "file": "plugins/cordova-plugin-file/www/FileEntry.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileEntry"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileError",
+      "file": "plugins/cordova-plugin-file/www/FileError.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileError"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileReader",
+      "file": "plugins/cordova-plugin-file/www/FileReader.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileReader"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileSystem",
+      "file": "plugins/cordova-plugin-file/www/FileSystem.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileSystem"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileUploadOptions",
+      "file": "plugins/cordova-plugin-file/www/FileUploadOptions.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileUploadOptions"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileUploadResult",
+      "file": "plugins/cordova-plugin-file/www/FileUploadResult.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileUploadResult"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileWriter",
+      "file": "plugins/cordova-plugin-file/www/FileWriter.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileWriter"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.Flags",
+      "file": "plugins/cordova-plugin-file/www/Flags.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.Flags"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.LocalFileSystem",
+      "file": "plugins/cordova-plugin-file/www/LocalFileSystem.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.LocalFileSystem"
+      ],
+      "merges": [
+        "window"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.Metadata",
+      "file": "plugins/cordova-plugin-file/www/Metadata.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.Metadata"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.ProgressEvent",
+      "file": "plugins/cordova-plugin-file/www/ProgressEvent.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.ProgressEvent"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.fileSystems",
+      "file": "plugins/cordova-plugin-file/www/fileSystems.js",
+      "pluginId": "cordova-plugin-file"
+    },
+    {
+      "id": "cordova-plugin-file.requestFileSystem",
+      "file": "plugins/cordova-plugin-file/www/requestFileSystem.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.requestFileSystem"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.resolveLocalFileSystemURI",
+      "file": "plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js",
+      "pluginId": "cordova-plugin-file",
+      "merges": [
+        "window"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.isChrome",
+      "file": "plugins/cordova-plugin-file/www/browser/isChrome.js",
+      "pluginId": "cordova-plugin-file",
+      "runs": true
+    },
+    {
+      "id": "cordova-plugin-file.androidFileSystem",
+      "file": "plugins/cordova-plugin-file/www/android/FileSystem.js",
+      "pluginId": "cordova-plugin-file",
+      "merges": [
+        "FileSystem"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.fileSystems-roots",
+      "file": "plugins/cordova-plugin-file/www/fileSystems-roots.js",
+      "pluginId": "cordova-plugin-file",
+      "runs": true
+    },
+    {
+      "id": "cordova-plugin-file.fileSystemPaths",
+      "file": "plugins/cordova-plugin-file/www/fileSystemPaths.js",
+      "pluginId": "cordova-plugin-file",
+      "merges": [
+        "cordova"
+      ],
+      "runs": true
     }
   ],
   "plugin_metadata": {
@@ -263,6 +455,8 @@
     "cordova-android-support-gradle-release": "1.4.2",
     "cordova-plugin-wkwebview-engine": "1.1.4",
     "cordova-plugin-statusbar": "2.4.2",
-    "cordova-plugin-wechatv2": "2.1.2"
+    "cordova-plugin-wechatv2": "2.1.2",
+    "cordova-plugin-file": "6.0.1",
+    "cordova-plugin-remote-injection": "0.5.2"
   }
 }

+ 176 - 1
platforms/android/assets/www/cordova_plugins.js

@@ -81,6 +81,179 @@ module.exports = [
     "clobbers": [
       "Wechat"
     ]
+  },
+  {
+    "id": "cordova-plugin-file.DirectoryEntry",
+    "file": "plugins/cordova-plugin-file/www/DirectoryEntry.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.DirectoryEntry"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.DirectoryReader",
+    "file": "plugins/cordova-plugin-file/www/DirectoryReader.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.DirectoryReader"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.Entry",
+    "file": "plugins/cordova-plugin-file/www/Entry.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.Entry"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.File",
+    "file": "plugins/cordova-plugin-file/www/File.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.File"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileEntry",
+    "file": "plugins/cordova-plugin-file/www/FileEntry.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileEntry"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileError",
+    "file": "plugins/cordova-plugin-file/www/FileError.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileError"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileReader",
+    "file": "plugins/cordova-plugin-file/www/FileReader.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileReader"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileSystem",
+    "file": "plugins/cordova-plugin-file/www/FileSystem.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileSystem"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileUploadOptions",
+    "file": "plugins/cordova-plugin-file/www/FileUploadOptions.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileUploadOptions"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileUploadResult",
+    "file": "plugins/cordova-plugin-file/www/FileUploadResult.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileUploadResult"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileWriter",
+    "file": "plugins/cordova-plugin-file/www/FileWriter.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileWriter"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.Flags",
+    "file": "plugins/cordova-plugin-file/www/Flags.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.Flags"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.LocalFileSystem",
+    "file": "plugins/cordova-plugin-file/www/LocalFileSystem.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.LocalFileSystem"
+    ],
+    "merges": [
+      "window"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.Metadata",
+    "file": "plugins/cordova-plugin-file/www/Metadata.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.Metadata"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.ProgressEvent",
+    "file": "plugins/cordova-plugin-file/www/ProgressEvent.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.ProgressEvent"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.fileSystems",
+    "file": "plugins/cordova-plugin-file/www/fileSystems.js",
+    "pluginId": "cordova-plugin-file"
+  },
+  {
+    "id": "cordova-plugin-file.requestFileSystem",
+    "file": "plugins/cordova-plugin-file/www/requestFileSystem.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.requestFileSystem"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.resolveLocalFileSystemURI",
+    "file": "plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js",
+    "pluginId": "cordova-plugin-file",
+    "merges": [
+      "window"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.isChrome",
+    "file": "plugins/cordova-plugin-file/www/browser/isChrome.js",
+    "pluginId": "cordova-plugin-file",
+    "runs": true
+  },
+  {
+    "id": "cordova-plugin-file.androidFileSystem",
+    "file": "plugins/cordova-plugin-file/www/android/FileSystem.js",
+    "pluginId": "cordova-plugin-file",
+    "merges": [
+      "FileSystem"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.fileSystems-roots",
+    "file": "plugins/cordova-plugin-file/www/fileSystems-roots.js",
+    "pluginId": "cordova-plugin-file",
+    "runs": true
+  },
+  {
+    "id": "cordova-plugin-file.fileSystemPaths",
+    "file": "plugins/cordova-plugin-file/www/fileSystemPaths.js",
+    "pluginId": "cordova-plugin-file",
+    "merges": [
+      "cordova"
+    ],
+    "runs": true
   }
 ];
 module.exports.metadata = 
@@ -96,7 +269,9 @@ module.exports.metadata =
   "cordova-android-support-gradle-release": "1.4.2",
   "cordova-plugin-wkwebview-engine": "1.1.4",
   "cordova-plugin-statusbar": "2.4.2",
-  "cordova-plugin-wechatv2": "2.1.2"
+  "cordova-plugin-wechatv2": "2.1.2",
+  "cordova-plugin-file": "6.0.1",
+  "cordova-plugin-remote-injection": "0.5.2"
 };
 // BOTTOM OF METADATA
 });

+ 1 - 1
platforms/android/assets/www/leitongxue.html

@@ -1 +1 @@
-<!DOCTYPE html><html><head><meta charset=utf-8><meta name=format-detection content="telephone=no"><meta name=apple-mobile-web-app-capable content=yes><meta id=viewport name=viewport content="initial-scale=1,width=device-width,user-scalable=no"><script src=./cordova.js></script><title>leitongxue</title><link href=./static/css/app.44c6fba2497d021b0fe5996010bdf47c.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.3ad1d5771e9b13dbdad2.js></script><script type=text/javascript src=./static/js/vendor.b5891798b44b4682884b.js></script><script type=text/javascript src=./static/js/app.069151de1070e52c112d.js></script></body></html>
+<!DOCTYPE html><html><head><meta charset=utf-8><meta name=format-detection content="telephone=no"><meta name=apple-mobile-web-app-capable content=yes><meta id=viewport name=viewport content="initial-scale=1,width=device-width,user-scalable=no"><title>leitongxue</title><link href=./static/css/app.4f8eeb06a5dd3d82226904aed0299860.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.3ad1d5771e9b13dbdad2.js></script><script type=text/javascript src=./static/js/vendor.b5891798b44b4682884b.js></script><script type=text/javascript src=./static/js/app.aba1de22627cc6f1889c.js></script></body><script src=./cordova.js></script></html>

+ 120 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/DirectoryEntry.js

@@ -0,0 +1,120 @@
+cordova.define("cordova-plugin-file.DirectoryEntry", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var argscheck = require('cordova/argscheck');
+var utils = require('cordova/utils');
+var exec = require('cordova/exec');
+var Entry = require('./Entry');
+var FileError = require('./FileError');
+var DirectoryReader = require('./DirectoryReader');
+
+/**
+ * An interface representing a directory on the file system.
+ *
+ * {boolean} isFile always false (readonly)
+ * {boolean} isDirectory always true (readonly)
+ * {DOMString} name of the directory, excluding the path leading to it (readonly)
+ * {DOMString} fullPath the absolute full path to the directory (readonly)
+ * {FileSystem} filesystem on which the directory resides (readonly)
+ */
+var DirectoryEntry = function (name, fullPath, fileSystem, nativeURL) {
+
+    // add trailing slash if it is missing
+    if ((fullPath) && !/\/$/.test(fullPath)) {
+        fullPath += '/';
+    }
+    // add trailing slash if it is missing
+    if (nativeURL && !/\/$/.test(nativeURL)) {
+        nativeURL += '/';
+    }
+    DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath, fileSystem, nativeURL);
+};
+
+utils.extend(DirectoryEntry, Entry);
+
+/**
+ * Creates a new DirectoryReader to read entries from this directory
+ */
+DirectoryEntry.prototype.createReader = function () {
+    return new DirectoryReader(this.toInternalURL());
+};
+
+/**
+ * Creates or looks up a directory
+ *
+ * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory
+ * @param {Flags} options to create or exclusively create the directory
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.getDirectory = function (path, options, successCallback, errorCallback) {
+    argscheck.checkArgs('sOFF', 'DirectoryEntry.getDirectory', arguments);
+    var fs = this.filesystem;
+    var win = successCallback && function (result) {
+        var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
+        successCallback(entry);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'getDirectory', [this.toInternalURL(), path, options]);
+};
+
+/**
+ * Deletes a directory and all of it's contents
+ *
+ * @param {Function} successCallback is called with no parameters
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.removeRecursively = function (successCallback, errorCallback) {
+    argscheck.checkArgs('FF', 'DirectoryEntry.removeRecursively', arguments);
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(successCallback, fail, 'File', 'removeRecursively', [this.toInternalURL()]);
+};
+
+/**
+ * Creates or looks up a file
+ *
+ * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file
+ * @param {Flags} options to create or exclusively create the file
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.getFile = function (path, options, successCallback, errorCallback) {
+    argscheck.checkArgs('sOFF', 'DirectoryEntry.getFile', arguments);
+    var fs = this.filesystem;
+    var win = successCallback && function (result) {
+        var FileEntry = require('./FileEntry');
+        var entry = new FileEntry(result.name, result.fullPath, fs, result.nativeURL);
+        successCallback(entry);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'getFile', [this.toInternalURL(), path, options]);
+};
+
+module.exports = DirectoryEntry;
+
+});

+ 75 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/DirectoryReader.js

@@ -0,0 +1,75 @@
+cordova.define("cordova-plugin-file.DirectoryReader", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var exec = require('cordova/exec');
+var FileError = require('./FileError');
+
+/**
+ * An interface that lists the files and directories in a directory.
+ */
+function DirectoryReader (localURL) {
+    this.localURL = localURL || null;
+    this.hasReadEntries = false;
+}
+
+/**
+ * Returns a list of entries from a directory.
+ *
+ * @param {Function} successCallback is called with a list of entries
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryReader.prototype.readEntries = function (successCallback, errorCallback) {
+    // If we've already read and passed on this directory's entries, return an empty list.
+    if (this.hasReadEntries) {
+        successCallback([]);
+        return;
+    }
+    var reader = this;
+    var win = typeof successCallback !== 'function' ? null : function (result) {
+        var retVal = [];
+        for (var i = 0; i < result.length; i++) {
+            var entry = null;
+            if (result[i].isDirectory) {
+                entry = new (require('./DirectoryEntry'))();
+            } else if (result[i].isFile) {
+                entry = new (require('./FileEntry'))();
+            }
+            entry.isDirectory = result[i].isDirectory;
+            entry.isFile = result[i].isFile;
+            entry.name = result[i].name;
+            entry.fullPath = result[i].fullPath;
+            entry.filesystem = new (require('./FileSystem'))(result[i].filesystemName);
+            entry.nativeURL = result[i].nativeURL;
+            retVal.push(entry);
+        }
+        reader.hasReadEntries = true;
+        successCallback(retVal);
+    };
+    var fail = typeof errorCallback !== 'function' ? null : function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'readEntries', [this.localURL]);
+};
+
+module.exports = DirectoryReader;
+
+});

+ 263 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/Entry.js

@@ -0,0 +1,263 @@
+cordova.define("cordova-plugin-file.Entry", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var argscheck = require('cordova/argscheck');
+var exec = require('cordova/exec');
+var FileError = require('./FileError');
+var Metadata = require('./Metadata');
+
+/**
+ * Represents a file or directory on the local file system.
+ *
+ * @param isFile
+ *            {boolean} true if Entry is a file (readonly)
+ * @param isDirectory
+ *            {boolean} true if Entry is a directory (readonly)
+ * @param name
+ *            {DOMString} name of the file or directory, excluding the path
+ *            leading to it (readonly)
+ * @param fullPath
+ *            {DOMString} the absolute full path to the file or directory
+ *            (readonly)
+ * @param fileSystem
+ *            {FileSystem} the filesystem on which this entry resides
+ *            (readonly)
+ * @param nativeURL
+ *            {DOMString} an alternate URL which can be used by native
+ *            webview controls, for example media players.
+ *            (optional, readonly)
+ */
+function Entry (isFile, isDirectory, name, fullPath, fileSystem, nativeURL) {
+    this.isFile = !!isFile;
+    this.isDirectory = !!isDirectory;
+    this.name = name || '';
+    this.fullPath = fullPath || '';
+    this.filesystem = fileSystem || null;
+    this.nativeURL = nativeURL || null;
+}
+
+/**
+ * Look up the metadata of the entry.
+ *
+ * @param successCallback
+ *            {Function} is called with a Metadata object
+ * @param errorCallback
+ *            {Function} is called with a FileError
+ */
+Entry.prototype.getMetadata = function (successCallback, errorCallback) {
+    argscheck.checkArgs('FF', 'Entry.getMetadata', arguments);
+    var success = successCallback && function (entryMetadata) {
+        var metadata = new Metadata({
+            size: entryMetadata.size,
+            modificationTime: entryMetadata.lastModifiedDate
+        });
+        successCallback(metadata);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(success, fail, 'File', 'getFileMetadata', [this.toInternalURL()]);
+};
+
+/**
+ * Set the metadata of the entry.
+ *
+ * @param successCallback
+ *            {Function} is called with a Metadata object
+ * @param errorCallback
+ *            {Function} is called with a FileError
+ * @param metadataObject
+ *            {Object} keys and values to set
+ */
+Entry.prototype.setMetadata = function (successCallback, errorCallback, metadataObject) {
+    argscheck.checkArgs('FFO', 'Entry.setMetadata', arguments);
+    exec(successCallback, errorCallback, 'File', 'setMetadata', [this.toInternalURL(), metadataObject]);
+};
+
+/**
+ * Move a file or directory to a new location.
+ *
+ * @param parent
+ *            {DirectoryEntry} the directory to which to move this entry
+ * @param newName
+ *            {DOMString} new name of the entry, defaults to the current name
+ * @param successCallback
+ *            {Function} called with the new DirectoryEntry object
+ * @param errorCallback
+ *            {Function} called with a FileError
+ */
+Entry.prototype.moveTo = function (parent, newName, successCallback, errorCallback) {
+    argscheck.checkArgs('oSFF', 'Entry.moveTo', arguments);
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    var srcURL = this.toInternalURL();
+    // entry name
+    var name = newName || this.name;
+    var success = function (entry) {
+        if (entry) {
+            if (successCallback) {
+                // create appropriate Entry object
+                var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
+                var fs = newFSName ? new FileSystem(newFSName, { name: '', fullPath: '/' }) : new FileSystem(parent.filesystem.name, { name: '', fullPath: '/' }); // eslint-disable-line no-undef
+                var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
+                successCallback(result);
+            }
+        } else {
+            // no Entry object returned
+            if (fail) {
+                fail(FileError.NOT_FOUND_ERR);
+            }
+        }
+    };
+
+    // copy
+    exec(success, fail, 'File', 'moveTo', [srcURL, parent.toInternalURL(), name]);
+};
+
+/**
+ * Copy a directory to a different location.
+ *
+ * @param parent
+ *            {DirectoryEntry} the directory to which to copy the entry
+ * @param newName
+ *            {DOMString} new name of the entry, defaults to the current name
+ * @param successCallback
+ *            {Function} called with the new Entry object
+ * @param errorCallback
+ *            {Function} called with a FileError
+ */
+Entry.prototype.copyTo = function (parent, newName, successCallback, errorCallback) {
+    argscheck.checkArgs('oSFF', 'Entry.copyTo', arguments);
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    var srcURL = this.toInternalURL();
+        // entry name
+    var name = newName || this.name;
+    // success callback
+    var success = function (entry) {
+        if (entry) {
+            if (successCallback) {
+                // create appropriate Entry object
+                var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
+                var fs = newFSName ? new FileSystem(newFSName, { name: '', fullPath: '/' }) : new FileSystem(parent.filesystem.name, { name: '', fullPath: '/' }); // eslint-disable-line no-undef
+                var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
+                successCallback(result);
+            }
+        } else {
+            // no Entry object returned
+            if (fail) {
+                fail(FileError.NOT_FOUND_ERR);
+            }
+        }
+    };
+
+    // copy
+    exec(success, fail, 'File', 'copyTo', [srcURL, parent.toInternalURL(), name]);
+};
+
+/**
+ * Return a URL that can be passed across the bridge to identify this entry.
+ */
+Entry.prototype.toInternalURL = function () {
+    if (this.filesystem && this.filesystem.__format__) {
+        return this.filesystem.__format__(this.fullPath, this.nativeURL);
+    }
+};
+
+/**
+ * Return a URL that can be used to identify this entry.
+ * Use a URL that can be used to as the src attribute of a <video> or
+ * <audio> tag. If that is not possible, construct a cdvfile:// URL.
+ */
+Entry.prototype.toURL = function () {
+    if (this.nativeURL) {
+        return this.nativeURL;
+    }
+    // fullPath attribute may contain the full URL in the case that
+    // toInternalURL fails.
+    return this.toInternalURL() || 'file://localhost' + this.fullPath;
+};
+
+/**
+ * Backwards-compatibility: In v1.0.0 - 1.0.2, .toURL would only return a
+ * cdvfile:// URL, and this method was necessary to obtain URLs usable by the
+ * webview.
+ * See CB-6051, CB-6106, CB-6117, CB-6152, CB-6199, CB-6201, CB-6243, CB-6249,
+ * and CB-6300.
+ */
+Entry.prototype.toNativeURL = function () {
+    console.log("DEPRECATED: Update your code to use 'toURL'");
+    return this.toURL();
+};
+
+/**
+ * Returns a URI that can be used to identify this entry.
+ *
+ * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI.
+ * @return uri
+ */
+Entry.prototype.toURI = function (mimeType) {
+    console.log("DEPRECATED: Update your code to use 'toURL'");
+    return this.toURL();
+};
+
+/**
+ * Remove a file or directory. It is an error to attempt to delete a
+ * directory that is not empty. It is an error to attempt to delete a
+ * root directory of a file system.
+ *
+ * @param successCallback {Function} called with no parameters
+ * @param errorCallback {Function} called with a FileError
+ */
+Entry.prototype.remove = function (successCallback, errorCallback) {
+    argscheck.checkArgs('FF', 'Entry.remove', arguments);
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(successCallback, fail, 'File', 'remove', [this.toInternalURL()]);
+};
+
+/**
+ * Look up the parent DirectoryEntry of this entry.
+ *
+ * @param successCallback {Function} called with the parent DirectoryEntry object
+ * @param errorCallback {Function} called with a FileError
+ */
+Entry.prototype.getParent = function (successCallback, errorCallback) {
+    argscheck.checkArgs('FF', 'Entry.getParent', arguments);
+    var fs = this.filesystem;
+    var win = successCallback && function (result) {
+        var DirectoryEntry = require('./DirectoryEntry');
+        var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
+        successCallback(entry);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'getParent', [this.toInternalURL()]);
+};
+
+module.exports = Entry;
+
+});

+ 81 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/File.js

@@ -0,0 +1,81 @@
+cordova.define("cordova-plugin-file.File", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * Constructor.
+ * name {DOMString} name of the file, without path information
+ * fullPath {DOMString} the full path of the file, including the name
+ * type {DOMString} mime type
+ * lastModifiedDate {Date} last modified date
+ * size {Number} size of the file in bytes
+ */
+
+var File = function (name, localURL, type, lastModifiedDate, size) {
+    this.name = name || '';
+    this.localURL = localURL || null;
+    this.type = type || null;
+    this.lastModified = lastModifiedDate || null;
+    // For backwards compatibility, store the timestamp in lastModifiedDate as well
+    this.lastModifiedDate = lastModifiedDate || null;
+    this.size = size || 0;
+
+    // These store the absolute start and end for slicing the file.
+    this.start = 0;
+    this.end = this.size;
+};
+
+/**
+ * Returns a "slice" of the file. Since Cordova Files don't contain the actual
+ * content, this really returns a File with adjusted start and end.
+ * Slices of slices are supported.
+ * start {Number} The index at which to start the slice (inclusive).
+ * end {Number} The index at which to end the slice (exclusive).
+ */
+File.prototype.slice = function (start, end) {
+    var size = this.end - this.start;
+    var newStart = 0;
+    var newEnd = size;
+    if (arguments.length) {
+        if (start < 0) {
+            newStart = Math.max(size + start, 0);
+        } else {
+            newStart = Math.min(size, start);
+        }
+    }
+
+    if (arguments.length >= 2) {
+        if (end < 0) {
+            newEnd = Math.max(size + end, 0);
+        } else {
+            newEnd = Math.min(end, size);
+        }
+    }
+
+    var newFile = new File(this.name, this.localURL, this.type, this.lastModified, this.size);
+    newFile.start = this.start + newStart;
+    newFile.end = this.start + newEnd;
+    return newFile;
+};
+
+module.exports = File;
+
+});

+ 95 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/FileEntry.js

@@ -0,0 +1,95 @@
+cordova.define("cordova-plugin-file.FileEntry", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var utils = require('cordova/utils');
+var exec = require('cordova/exec');
+var Entry = require('./Entry');
+var FileWriter = require('./FileWriter');
+var File = require('./File');
+var FileError = require('./FileError');
+
+/**
+ * An interface representing a file on the file system.
+ *
+ * {boolean} isFile always true (readonly)
+ * {boolean} isDirectory always false (readonly)
+ * {DOMString} name of the file, excluding the path leading to it (readonly)
+ * {DOMString} fullPath the absolute full path to the file (readonly)
+ * {FileSystem} filesystem on which the file resides (readonly)
+ */
+var FileEntry = function (name, fullPath, fileSystem, nativeURL) {
+    // remove trailing slash if it is present
+    if (fullPath && /\/$/.test(fullPath)) {
+        fullPath = fullPath.substring(0, fullPath.length - 1);
+    }
+    if (nativeURL && /\/$/.test(nativeURL)) {
+        nativeURL = nativeURL.substring(0, nativeURL.length - 1);
+    }
+
+    FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath, fileSystem, nativeURL]);
+};
+
+utils.extend(FileEntry, Entry);
+
+/**
+ * Creates a new FileWriter associated with the file that this FileEntry represents.
+ *
+ * @param {Function} successCallback is called with the new FileWriter
+ * @param {Function} errorCallback is called with a FileError
+ */
+FileEntry.prototype.createWriter = function (successCallback, errorCallback) {
+    this.file(function (filePointer) {
+        var writer = new FileWriter(filePointer);
+
+        if (writer.localURL === null || writer.localURL === '') {
+            if (errorCallback) {
+                errorCallback(new FileError(FileError.INVALID_STATE_ERR));
+            }
+        } else {
+            if (successCallback) {
+                successCallback(writer);
+            }
+        }
+    }, errorCallback);
+};
+
+/**
+ * Returns a File that represents the current state of the file that this FileEntry represents.
+ *
+ * @param {Function} successCallback is called with the new File object
+ * @param {Function} errorCallback is called with a FileError
+ */
+FileEntry.prototype.file = function (successCallback, errorCallback) {
+    var localURL = this.toInternalURL();
+    var win = successCallback && function (f) {
+        var file = new File(f.name, localURL, f.type, f.lastModifiedDate, f.size);
+        successCallback(file);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'getFileMetadata', [localURL]);
+};
+
+module.exports = FileEntry;
+
+});

+ 49 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/FileError.js

@@ -0,0 +1,49 @@
+cordova.define("cordova-plugin-file.FileError", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * FileError
+ */
+function FileError (error) {
+    this.code = error || null;
+}
+
+// File error codes
+// Found in DOMException
+FileError.NOT_FOUND_ERR = 1;
+FileError.SECURITY_ERR = 2;
+FileError.ABORT_ERR = 3;
+
+// Added by File API specification
+FileError.NOT_READABLE_ERR = 4;
+FileError.ENCODING_ERR = 5;
+FileError.NO_MODIFICATION_ALLOWED_ERR = 6;
+FileError.INVALID_STATE_ERR = 7;
+FileError.SYNTAX_ERR = 8;
+FileError.INVALID_MODIFICATION_ERR = 9;
+FileError.QUOTA_EXCEEDED_ERR = 10;
+FileError.TYPE_MISMATCH_ERR = 11;
+FileError.PATH_EXISTS_ERR = 12;
+
+module.exports = FileError;
+
+});

+ 301 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/FileReader.js

@@ -0,0 +1,301 @@
+cordova.define("cordova-plugin-file.FileReader", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var exec = require('cordova/exec');
+var modulemapper = require('cordova/modulemapper');
+var utils = require('cordova/utils');
+var FileError = require('./FileError');
+var ProgressEvent = require('./ProgressEvent');
+var origFileReader = modulemapper.getOriginalSymbol(window, 'FileReader');
+
+/**
+ * This class reads the mobile device file system.
+ *
+ * For Android:
+ *      The root directory is the root of the file system.
+ *      To read from the SD card, the file name is "sdcard/my_file.txt"
+ * @constructor
+ */
+var FileReader = function () {
+    this._readyState = 0;
+    this._error = null;
+    this._result = null;
+    this._progress = null;
+    this._localURL = '';
+    this._realReader = origFileReader ? new origFileReader() : {}; // eslint-disable-line new-cap
+};
+
+/**
+ * Defines the maximum size to read at a time via the native API. The default value is a compromise between
+ * minimizing the overhead of many exec() calls while still reporting progress frequently enough for large files.
+ * (Note attempts to allocate more than a few MB of contiguous memory on the native side are likely to cause
+ * OOM exceptions, while the JS engine seems to have fewer problems managing large strings or ArrayBuffers.)
+ */
+FileReader.READ_CHUNK_SIZE = 256 * 1024;
+
+// States
+FileReader.EMPTY = 0;
+FileReader.LOADING = 1;
+FileReader.DONE = 2;
+
+utils.defineGetter(FileReader.prototype, 'readyState', function () {
+    return this._localURL ? this._readyState : this._realReader.readyState;
+});
+
+utils.defineGetter(FileReader.prototype, 'error', function () {
+    return this._localURL ? this._error : this._realReader.error;
+});
+
+utils.defineGetter(FileReader.prototype, 'result', function () {
+    return this._localURL ? this._result : this._realReader.result;
+});
+
+function defineEvent (eventName) {
+    utils.defineGetterSetter(FileReader.prototype, eventName, function () {
+        return this._realReader[eventName] || null;
+    }, function (value) {
+        this._realReader[eventName] = value;
+    });
+}
+defineEvent('onloadstart');    // When the read starts.
+defineEvent('onprogress');     // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total)
+defineEvent('onload');         // When the read has successfully completed.
+defineEvent('onerror');        // When the read has failed (see errors).
+defineEvent('onloadend');      // When the request has completed (either in success or failure).
+defineEvent('onabort');        // When the read has been aborted. For instance, by invoking the abort() method.
+
+function initRead (reader, file) {
+    // Already loading something
+    if (reader.readyState === FileReader.LOADING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    reader._result = null;
+    reader._error = null;
+    reader._progress = 0;
+    reader._readyState = FileReader.LOADING;
+
+    if (typeof file.localURL === 'string') {
+        reader._localURL = file.localURL;
+    } else {
+        reader._localURL = '';
+        return true;
+    }
+
+    if (reader.onloadstart) {
+        reader.onloadstart(new ProgressEvent('loadstart', {target: reader}));
+    }
+}
+
+/**
+ * Callback used by the following read* functions to handle incremental or final success.
+ * Must be bound to the FileReader's this along with all but the last parameter,
+ * e.g. readSuccessCallback.bind(this, "readAsText", "UTF-8", offset, totalSize, accumulate)
+ * @param readType The name of the read function to call.
+ * @param encoding Text encoding, or null if this is not a text type read.
+ * @param offset Starting offset of the read.
+ * @param totalSize Total number of bytes or chars to read.
+ * @param accumulate A function that takes the callback result and accumulates it in this._result.
+ * @param r Callback result returned by the last read exec() call, or null to begin reading.
+ */
+function readSuccessCallback (readType, encoding, offset, totalSize, accumulate, r) {
+    if (this._readyState === FileReader.DONE) {
+        return;
+    }
+
+    var CHUNK_SIZE = FileReader.READ_CHUNK_SIZE;
+    if (readType === 'readAsDataURL') {
+        // Windows proxy does not support reading file slices as Data URLs
+        // so read the whole file at once.
+        CHUNK_SIZE = cordova.platformId === 'windows' ? totalSize : // eslint-disable-line no-undef
+            // Calculate new chunk size for data URLs to be multiply of 3
+            // Otherwise concatenated base64 chunks won't be valid base64 data
+            FileReader.READ_CHUNK_SIZE - (FileReader.READ_CHUNK_SIZE % 3) + 3;
+    }
+
+    if (typeof r !== 'undefined') {
+        accumulate(r);
+        this._progress = Math.min(this._progress + CHUNK_SIZE, totalSize);
+
+        if (typeof this.onprogress === 'function') {
+            this.onprogress(new ProgressEvent('progress', {loaded: this._progress, total: totalSize}));
+        }
+    }
+
+    if (typeof r === 'undefined' || this._progress < totalSize) {
+        var execArgs = [
+            this._localURL,
+            offset + this._progress,
+            offset + this._progress + Math.min(totalSize - this._progress, CHUNK_SIZE)];
+        if (encoding) {
+            execArgs.splice(1, 0, encoding);
+        }
+        exec(
+            readSuccessCallback.bind(this, readType, encoding, offset, totalSize, accumulate),
+            readFailureCallback.bind(this),
+            'File', readType, execArgs);
+    } else {
+        this._readyState = FileReader.DONE;
+
+        if (typeof this.onload === 'function') {
+            this.onload(new ProgressEvent('load', {target: this}));
+        }
+
+        if (typeof this.onloadend === 'function') {
+            this.onloadend(new ProgressEvent('loadend', {target: this}));
+        }
+    }
+}
+
+/**
+ * Callback used by the following read* functions to handle errors.
+ * Must be bound to the FileReader's this, e.g. readFailureCallback.bind(this)
+ */
+function readFailureCallback (e) {
+    if (this._readyState === FileReader.DONE) {
+        return;
+    }
+
+    this._readyState = FileReader.DONE;
+    this._result = null;
+    this._error = new FileError(e);
+
+    if (typeof this.onerror === 'function') {
+        this.onerror(new ProgressEvent('error', {target: this}));
+    }
+
+    if (typeof this.onloadend === 'function') {
+        this.onloadend(new ProgressEvent('loadend', {target: this}));
+    }
+}
+
+/**
+ * Abort reading file.
+ */
+FileReader.prototype.abort = function () {
+    if (origFileReader && !this._localURL) {
+        return this._realReader.abort();
+    }
+    this._result = null;
+
+    if (this._readyState === FileReader.DONE || this._readyState === FileReader.EMPTY) {
+        return;
+    }
+
+    this._readyState = FileReader.DONE;
+
+    // If abort callback
+    if (typeof this.onabort === 'function') {
+        this.onabort(new ProgressEvent('abort', {target: this}));
+    }
+    // If load end callback
+    if (typeof this.onloadend === 'function') {
+        this.onloadend(new ProgressEvent('loadend', {target: this}));
+    }
+};
+
+/**
+ * Read text file.
+ *
+ * @param file          {File} File object containing file properties
+ * @param encoding      [Optional] (see http://www.iana.org/assignments/character-sets)
+ */
+FileReader.prototype.readAsText = function (file, encoding) {
+    if (initRead(this, file)) {
+        return this._realReader.readAsText(file, encoding);
+    }
+
+    // Default encoding is UTF-8
+    var enc = encoding || 'UTF-8';
+
+    var totalSize = file.end - file.start;
+    readSuccessCallback.bind(this)('readAsText', enc, file.start, totalSize, function (r) {
+        if (this._progress === 0) {
+            this._result = '';
+        }
+        this._result += r;
+    }.bind(this));
+};
+
+/**
+ * Read file and return data as a base64 encoded data url.
+ * A data url is of the form:
+ *      data:[<mediatype>][;base64],<data>
+ *
+ * @param file          {File} File object containing file properties
+ */
+FileReader.prototype.readAsDataURL = function (file) {
+    if (initRead(this, file)) {
+        return this._realReader.readAsDataURL(file);
+    }
+
+    var totalSize = file.end - file.start;
+    readSuccessCallback.bind(this)('readAsDataURL', null, file.start, totalSize, function (r) {
+        var commaIndex = r.indexOf(',');
+        if (this._progress === 0) {
+            this._result = r;
+        } else {
+            this._result += r.substring(commaIndex + 1);
+        }
+    }.bind(this));
+};
+
+/**
+ * Read file and return data as a binary data.
+ *
+ * @param file          {File} File object containing file properties
+ */
+FileReader.prototype.readAsBinaryString = function (file) {
+    if (initRead(this, file)) {
+        return this._realReader.readAsBinaryString(file);
+    }
+
+    var totalSize = file.end - file.start;
+    readSuccessCallback.bind(this)('readAsBinaryString', null, file.start, totalSize, function (r) {
+        if (this._progress === 0) {
+            this._result = '';
+        }
+        this._result += r;
+    }.bind(this));
+};
+
+/**
+ * Read file and return data as a binary data.
+ *
+ * @param file          {File} File object containing file properties
+ */
+FileReader.prototype.readAsArrayBuffer = function (file) {
+    if (initRead(this, file)) {
+        return this._realReader.readAsArrayBuffer(file);
+    }
+
+    var totalSize = file.end - file.start;
+    readSuccessCallback.bind(this)('readAsArrayBuffer', null, file.start, totalSize, function (r) {
+        var resultArray = (this._progress === 0 ? new Uint8Array(totalSize) : new Uint8Array(this._result));
+        resultArray.set(new Uint8Array(r), this._progress);
+        this._result = resultArray.buffer;
+    }.bind(this));
+};
+
+module.exports = FileReader;
+
+});

+ 58 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/FileSystem.js

@@ -0,0 +1,58 @@
+cordova.define("cordova-plugin-file.FileSystem", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var DirectoryEntry = require('./DirectoryEntry');
+
+/**
+ * An interface representing a file system
+ *
+ * @constructor
+ * {DOMString} name the unique name of the file system (readonly)
+ * {DirectoryEntry} root directory of the file system (readonly)
+ */
+var FileSystem = function (name, root) {
+    this.name = name;
+    if (root) {
+        this.root = new DirectoryEntry(root.name, root.fullPath, this, root.nativeURL);
+    } else {
+        this.root = new DirectoryEntry(this.name, '/', this);
+    }
+};
+
+FileSystem.prototype.__format__ = function (fullPath, nativeUrl) {
+    return fullPath;
+};
+
+FileSystem.prototype.toJSON = function () {
+    return '<FileSystem: ' + this.name + '>';
+};
+
+// Use instead of encodeURI() when encoding just the path part of a URI rather than an entire URI.
+FileSystem.encodeURIPath = function (path) {
+    // Because # is a valid filename character, it must be encoded to prevent part of the
+    // path from being parsed as a URI fragment.
+    return encodeURI(path).replace(/#/g, '%23');
+};
+
+module.exports = FileSystem;
+
+});

+ 44 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/FileUploadOptions.js

@@ -0,0 +1,44 @@
+cordova.define("cordova-plugin-file.FileUploadOptions", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * Options to customize the HTTP request used to upload files.
+ * @constructor
+ * @param fileKey {String}   Name of file request parameter.
+ * @param fileName {String}  Filename to be used by the server. Defaults to image.jpg.
+ * @param mimeType {String}  Mimetype of the uploaded file. Defaults to image/jpeg.
+ * @param params {Object}    Object with key: value params to send to the server.
+ * @param headers {Object}   Keys are header names, values are header values. Multiple
+ *                           headers of the same name are not supported.
+ */
+var FileUploadOptions = function (fileKey, fileName, mimeType, params, headers, httpMethod) {
+    this.fileKey = fileKey || null;
+    this.fileName = fileName || null;
+    this.mimeType = mimeType || null;
+    this.params = params || null;
+    this.headers = headers || null;
+    this.httpMethod = httpMethod || null;
+};
+
+module.exports = FileUploadOptions;
+
+});

+ 33 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/FileUploadResult.js

@@ -0,0 +1,33 @@
+cordova.define("cordova-plugin-file.FileUploadResult", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * FileUploadResult
+ * @constructor
+ */
+module.exports = function FileUploadResult (size, code, content) {
+    this.bytesSent = size;
+    this.responseCode = code;
+    this.response = content;
+};
+
+});

+ 327 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/FileWriter.js

@@ -0,0 +1,327 @@
+cordova.define("cordova-plugin-file.FileWriter", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var exec = require('cordova/exec');
+var FileError = require('./FileError');
+var ProgressEvent = require('./ProgressEvent');
+
+/**
+ * This class writes to the mobile device file system.
+ *
+ * For Android:
+ *      The root directory is the root of the file system.
+ *      To write to the SD card, the file name is "sdcard/my_file.txt"
+ *
+ * @constructor
+ * @param file {File} File object containing file properties
+ * @param append if true write to the end of the file, otherwise overwrite the file
+ */
+var FileWriter = function (file) {
+    this.fileName = '';
+    this.length = 0;
+    if (file) {
+        this.localURL = file.localURL || file;
+        this.length = file.size || 0;
+    }
+    // default is to write at the beginning of the file
+    this.position = 0;
+
+    this.readyState = 0; // EMPTY
+
+    this.result = null;
+
+    // Error
+    this.error = null;
+
+    // Event handlers
+    this.onwritestart = null;   // When writing starts
+    this.onprogress = null;     // While writing the file, and reporting partial file data
+    this.onwrite = null;        // When the write has successfully completed.
+    this.onwriteend = null;     // When the request has completed (either in success or failure).
+    this.onabort = null;        // When the write has been aborted. For instance, by invoking the abort() method.
+    this.onerror = null;        // When the write has failed (see errors).
+};
+
+// States
+FileWriter.INIT = 0;
+FileWriter.WRITING = 1;
+FileWriter.DONE = 2;
+
+/**
+ * Abort writing file.
+ */
+FileWriter.prototype.abort = function () {
+    // check for invalid state
+    if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // set error
+    this.error = new FileError(FileError.ABORT_ERR);
+
+    this.readyState = FileWriter.DONE;
+
+    // If abort callback
+    if (typeof this.onabort === 'function') {
+        this.onabort(new ProgressEvent('abort', {'target': this}));
+    }
+
+    // If write end callback
+    if (typeof this.onwriteend === 'function') {
+        this.onwriteend(new ProgressEvent('writeend', {'target': this}));
+    }
+};
+
+/**
+ * Writes data to the file
+ *
+ * @param data text or blob to be written
+ * @param isPendingBlobReadResult {Boolean} true if the data is the pending blob read operation result
+ */
+FileWriter.prototype.write = function (data, isPendingBlobReadResult) {
+
+    var that = this;
+    var supportsBinary = (typeof window.Blob !== 'undefined' && typeof window.ArrayBuffer !== 'undefined');
+    /* eslint-disable no-undef */
+    var isProxySupportBlobNatively = (cordova.platformId === 'windows8' || cordova.platformId === 'windows');
+    var isBinary;
+
+    // Check to see if the incoming data is a blob
+    if (data instanceof File || (!isProxySupportBlobNatively && supportsBinary && data instanceof Blob)) {
+        var fileReader = new FileReader();
+        /* eslint-enable no-undef */
+        fileReader.onload = function () {
+            // Call this method again, with the arraybuffer as argument
+            FileWriter.prototype.write.call(that, this.result, true /* isPendingBlobReadResult */);
+        };
+        fileReader.onerror = function () {
+            // DONE state
+            that.readyState = FileWriter.DONE;
+
+            // Save error
+            that.error = this.error;
+
+            // If onerror callback
+            if (typeof that.onerror === 'function') {
+                that.onerror(new ProgressEvent('error', {'target': that}));
+            }
+
+            // If onwriteend callback
+            if (typeof that.onwriteend === 'function') {
+                that.onwriteend(new ProgressEvent('writeend', {'target': that}));
+            }
+        };
+
+        // WRITING state
+        this.readyState = FileWriter.WRITING;
+
+        if (supportsBinary) {
+            fileReader.readAsArrayBuffer(data);
+        } else {
+            fileReader.readAsText(data);
+        }
+        return;
+    }
+
+    // Mark data type for safer transport over the binary bridge
+    isBinary = supportsBinary && (data instanceof ArrayBuffer);
+    if (isBinary && cordova.platformId === 'windowsphone') { // eslint-disable-line no-undef
+        // create a plain array, using the keys from the Uint8Array view so that we can serialize it
+        data = Array.apply(null, new Uint8Array(data));
+    }
+
+    // Throw an exception if we are already writing a file
+    if (this.readyState === FileWriter.WRITING && !isPendingBlobReadResult) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // WRITING state
+    this.readyState = FileWriter.WRITING;
+
+    var me = this;
+
+    // If onwritestart callback
+    if (typeof me.onwritestart === 'function') {
+        me.onwritestart(new ProgressEvent('writestart', {'target': me}));
+    }
+
+    // Write file
+    exec(
+        // Success callback
+        function (r) {
+            // If DONE (cancelled), then don't do anything
+            if (me.readyState === FileWriter.DONE) {
+                return;
+            }
+
+            // position always increases by bytes written because file would be extended
+            me.position += r;
+            // The length of the file is now where we are done writing.
+
+            me.length = me.position;
+
+            // DONE state
+            me.readyState = FileWriter.DONE;
+
+            // If onwrite callback
+            if (typeof me.onwrite === 'function') {
+                me.onwrite(new ProgressEvent('write', {'target': me}));
+            }
+
+            // If onwriteend callback
+            if (typeof me.onwriteend === 'function') {
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
+            }
+        },
+        // Error callback
+        function (e) {
+            // If DONE (cancelled), then don't do anything
+            if (me.readyState === FileWriter.DONE) {
+                return;
+            }
+
+            // DONE state
+            me.readyState = FileWriter.DONE;
+
+            // Save error
+            me.error = new FileError(e);
+
+            // If onerror callback
+            if (typeof me.onerror === 'function') {
+                me.onerror(new ProgressEvent('error', {'target': me}));
+            }
+
+            // If onwriteend callback
+            if (typeof me.onwriteend === 'function') {
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
+            }
+        }, 'File', 'write', [this.localURL, data, this.position, isBinary]);
+};
+
+/**
+ * Moves the file pointer to the location specified.
+ *
+ * If the offset is a negative number the position of the file
+ * pointer is rewound.  If the offset is greater than the file
+ * size the position is set to the end of the file.
+ *
+ * @param offset is the location to move the file pointer to.
+ */
+FileWriter.prototype.seek = function (offset) {
+    // Throw an exception if we are already writing a file
+    if (this.readyState === FileWriter.WRITING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    if (!offset && offset !== 0) {
+        return;
+    }
+
+    // See back from end of file.
+    if (offset < 0) {
+        this.position = Math.max(offset + this.length, 0);
+    // Offset is bigger than file size so set position
+    // to the end of the file.
+    } else if (offset > this.length) {
+        this.position = this.length;
+    // Offset is between 0 and file size so set the position
+    // to start writing.
+    } else {
+        this.position = offset;
+    }
+};
+
+/**
+ * Truncates the file to the size specified.
+ *
+ * @param size to chop the file at.
+ */
+FileWriter.prototype.truncate = function (size) {
+    // Throw an exception if we are already writing a file
+    if (this.readyState === FileWriter.WRITING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // WRITING state
+    this.readyState = FileWriter.WRITING;
+
+    var me = this;
+
+    // If onwritestart callback
+    if (typeof me.onwritestart === 'function') {
+        me.onwritestart(new ProgressEvent('writestart', {'target': this}));
+    }
+
+    // Write file
+    exec(
+        // Success callback
+        function (r) {
+            // If DONE (cancelled), then don't do anything
+            if (me.readyState === FileWriter.DONE) {
+                return;
+            }
+
+            // DONE state
+            me.readyState = FileWriter.DONE;
+
+            // Update the length of the file
+            me.length = r;
+            me.position = Math.min(me.position, r);
+
+            // If onwrite callback
+            if (typeof me.onwrite === 'function') {
+                me.onwrite(new ProgressEvent('write', {'target': me}));
+            }
+
+            // If onwriteend callback
+            if (typeof me.onwriteend === 'function') {
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
+            }
+        },
+        // Error callback
+        function (e) {
+            // If DONE (cancelled), then don't do anything
+            if (me.readyState === FileWriter.DONE) {
+                return;
+            }
+
+            // DONE state
+            me.readyState = FileWriter.DONE;
+
+            // Save error
+            me.error = new FileError(e);
+
+            // If onerror callback
+            if (typeof me.onerror === 'function') {
+                me.onerror(new ProgressEvent('error', {'target': me}));
+            }
+
+            // If onwriteend callback
+            if (typeof me.onwriteend === 'function') {
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
+            }
+        }, 'File', 'truncate', [this.localURL, size]);
+};
+
+module.exports = FileWriter;
+
+});

+ 39 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/Flags.js

@@ -0,0 +1,39 @@
+cordova.define("cordova-plugin-file.Flags", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * Supplies arguments to methods that lookup or create files and directories.
+ *
+ * @param create
+ *            {boolean} file or directory if it doesn't exist
+ * @param exclusive
+ *            {boolean} used with create; if true the command will fail if
+ *            target path exists
+ */
+function Flags (create, exclusive) {
+    this.create = create || false;
+    this.exclusive = exclusive || false;
+}
+
+module.exports = Flags;
+
+});

+ 26 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/LocalFileSystem.js

@@ -0,0 +1,26 @@
+cordova.define("cordova-plugin-file.LocalFileSystem", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+exports.TEMPORARY = 0;
+exports.PERSISTENT = 1;
+
+});

+ 43 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/Metadata.js

@@ -0,0 +1,43 @@
+cordova.define("cordova-plugin-file.Metadata", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * Information about the state of the file or directory
+ *
+ * {Date} modificationTime (readonly)
+ */
+var Metadata = function (metadata) {
+    if (typeof metadata === 'object') {
+        this.modificationTime = new Date(metadata.modificationTime);
+        this.size = metadata.size || 0;
+    } else if (typeof metadata === 'undefined') {
+        this.modificationTime = null;
+        this.size = 0;
+    } else {
+        /* Backwards compatiblity with platforms that only return a timestamp */
+        this.modificationTime = new Date(metadata);
+    }
+};
+
+module.exports = Metadata;
+
+});

+ 70 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/ProgressEvent.js

@@ -0,0 +1,70 @@
+cordova.define("cordova-plugin-file.ProgressEvent", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+// If ProgressEvent exists in global context, use it already, otherwise use our own polyfill
+// Feature test: See if we can instantiate a native ProgressEvent;
+// if so, use that approach,
+// otherwise fill-in with our own implementation.
+//
+// NOTE: right now we always fill in with our own. Down the road would be nice if we can use whatever is native in the webview.
+var ProgressEvent = (function () {
+    /*
+    var createEvent = function(data) {
+        var event = document.createEvent('Events');
+        event.initEvent('ProgressEvent', false, false);
+        if (data) {
+            for (var i in data) {
+                if (data.hasOwnProperty(i)) {
+                    event[i] = data[i];
+                }
+            }
+            if (data.target) {
+                // TODO: cannot call <some_custom_object>.dispatchEvent
+                // need to first figure out how to implement EventTarget
+            }
+        }
+        return event;
+    };
+    try {
+        var ev = createEvent({type:"abort",target:document});
+        return function ProgressEvent(type, data) {
+            data.type = type;
+            return createEvent(data);
+        };
+    } catch(e){
+    */
+    return function ProgressEvent (type, dict) {
+        this.type = type;
+        this.bubbles = false;
+        this.cancelBubble = false;
+        this.cancelable = false;
+        this.lengthComputable = false;
+        this.loaded = dict && dict.loaded ? dict.loaded : 0;
+        this.total = dict && dict.total ? dict.total : 0;
+        this.target = dict && dict.target ? dict.target : null;
+    };
+    // }
+})();
+
+module.exports = ProgressEvent;
+
+});

+ 51 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/android/FileSystem.js

@@ -0,0 +1,51 @@
+cordova.define("cordova-plugin-file.androidFileSystem", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+FILESYSTEM_PROTOCOL = 'cdvfile'; // eslint-disable-line no-undef
+
+module.exports = {
+    __format__: function (fullPath, nativeUrl) {
+        var path;
+        var contentUrlMatch = /^content:\/\//.exec(nativeUrl);
+        if (contentUrlMatch) {
+            // When available, use the path from a native content URL, which was already encoded by Android.
+            // This is necessary because JavaScript's encodeURI() does not encode as many characters as
+            // Android, which can result in permission exceptions when the encoding of a content URI
+            // doesn't match the string for which permission was originally granted.
+            path = nativeUrl.substring(contentUrlMatch[0].length - 1);
+        } else {
+            path = FileSystem.encodeURIPath(fullPath); // eslint-disable-line no-undef
+            if (!/^\//.test(path)) {
+                path = '/' + path;
+            }
+
+            var m = /\?.*/.exec(nativeUrl);
+            if (m) {
+                path += m[0];
+            }
+        }
+
+        return FILESYSTEM_PROTOCOL + '://localhost/' + this.name + path; // eslint-disable-line no-undef
+    }
+};
+
+});

+ 29 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/browser/isChrome.js

@@ -0,0 +1,29 @@
+cordova.define("cordova-plugin-file.isChrome", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+module.exports = function () {
+    // window.webkitRequestFileSystem and window.webkitResolveLocalFileSystemURL are available only in Chrome and
+    // possibly a good flag to indicate that we're running in Chrome
+    return window.webkitRequestFileSystem && window.webkitResolveLocalFileSystemURL;
+};
+
+});

+ 65 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/fileSystemPaths.js

@@ -0,0 +1,65 @@
+cordova.define("cordova-plugin-file.fileSystemPaths", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var exec = require('cordova/exec');
+var channel = require('cordova/channel');
+
+exports.file = {
+    // Read-only directory where the application is installed.
+    applicationDirectory: null,
+    // Root of app's private writable storage
+    applicationStorageDirectory: null,
+    // Where to put app-specific data files.
+    dataDirectory: null,
+    // Cached files that should survive app restarts.
+    // Apps should not rely on the OS to delete files in here.
+    cacheDirectory: null,
+    // Android: the application space on external storage.
+    externalApplicationStorageDirectory: null,
+    // Android: Where to put app-specific data files on external storage.
+    externalDataDirectory: null,
+    // Android: the application cache on external storage.
+    externalCacheDirectory: null,
+    // Android: the external storage (SD card) root.
+    externalRootDirectory: null,
+    // iOS: Temp directory that the OS can clear at will.
+    tempDirectory: null,
+    // iOS: Holds app-specific files that should be synced (e.g. to iCloud).
+    syncedDataDirectory: null,
+    // iOS: Files private to the app, but that are meaningful to other applications (e.g. Office files)
+    documentsDirectory: null,
+    // BlackBerry10: Files globally available to all apps
+    sharedDirectory: null
+};
+
+channel.waitForInitialization('onFileSystemPathsReady');
+channel.onCordovaReady.subscribe(function () {
+    function after (paths) {
+        for (var k in paths) {
+            exports.file[k] = paths[k];
+        }
+        channel.initializationComplete('onFileSystemPathsReady');
+    }
+    exec(after, null, 'File', 'requestAllPaths', []);
+});
+
+});

+ 47 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/fileSystems-roots.js

@@ -0,0 +1,47 @@
+cordova.define("cordova-plugin-file.fileSystems-roots", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+// Map of fsName -> FileSystem.
+var fsMap = null;
+var FileSystem = require('./FileSystem');
+var exec = require('cordova/exec');
+
+// Overridden by Android, BlackBerry 10 and iOS to populate fsMap.
+require('./fileSystems').getFs = function (name, callback) {
+    function success (response) {
+        fsMap = {};
+        for (var i = 0; i < response.length; ++i) {
+            var fsRoot = response[i];
+            var fs = new FileSystem(fsRoot.filesystemName, fsRoot);
+            fsMap[fs.name] = fs;
+        }
+        callback(fsMap[name]);
+    }
+
+    if (fsMap) {
+        callback(fsMap[name]);
+    } else {
+        exec(success, null, 'File', 'requestAllFileSystems', []);
+    }
+};
+
+});

+ 28 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/fileSystems.js

@@ -0,0 +1,28 @@
+cordova.define("cordova-plugin-file.fileSystems", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+// Overridden by Android, BlackBerry 10 and iOS to populate fsMap.
+module.exports.getFs = function (name, callback) {
+    callback(null);
+};
+
+});

+ 84 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/requestFileSystem.js

@@ -0,0 +1,84 @@
+cordova.define("cordova-plugin-file.requestFileSystem", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+(function () {
+    // For browser platform: not all browsers use this file.
+    function checkBrowser () {
+        if (cordova.platformId === 'browser' && require('./isChrome')()) { // eslint-disable-line no-undef
+            module.exports = window.requestFileSystem || window.webkitRequestFileSystem;
+            return true;
+        }
+        return false;
+    }
+    if (checkBrowser()) {
+        return;
+    }
+
+    var argscheck = require('cordova/argscheck');
+    var FileError = require('./FileError');
+    var FileSystem = require('./FileSystem');
+    var exec = require('cordova/exec');
+    var fileSystems = require('./fileSystems');
+
+    /**
+     * Request a file system in which to store application data.
+     * @param type  local file system type
+     * @param size  indicates how much storage space, in bytes, the application expects to need
+     * @param successCallback  invoked with a FileSystem object
+     * @param errorCallback  invoked if error occurs retrieving file system
+     */
+    var requestFileSystem = function (type, size, successCallback, errorCallback) {
+        argscheck.checkArgs('nnFF', 'requestFileSystem', arguments);
+        var fail = function (code) {
+            if (errorCallback) {
+                errorCallback(new FileError(code));
+            }
+        };
+
+        if (type < 0) {
+            fail(FileError.SYNTAX_ERR);
+        } else {
+            // if successful, return a FileSystem object
+            var success = function (file_system) {
+                if (file_system) {
+                    if (successCallback) {
+                        fileSystems.getFs(file_system.name, function (fs) {
+                            // This should happen only on platforms that haven't implemented requestAllFileSystems (windows)
+                            if (!fs) {
+                                fs = new FileSystem(file_system.name, file_system.root);
+                            }
+                            successCallback(fs);
+                        });
+                    }
+                } else {
+                    // no FileSystem object returned
+                    fail(FileError.NOT_FOUND_ERR);
+                }
+            };
+            exec(success, fail, 'File', 'requestFileSystem', [type, size]);
+        }
+    };
+
+    module.exports = requestFileSystem;
+})();
+
+});

+ 94 - 0
platforms/android/assets/www/plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js

@@ -0,0 +1,94 @@
+cordova.define("cordova-plugin-file.resolveLocalFileSystemURI", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+(function () {
+    // For browser platform: not all browsers use overrided `resolveLocalFileSystemURL`.
+    function checkBrowser () {
+        if (cordova.platformId === 'browser' && require('./isChrome')()) { // eslint-disable-line no-undef
+            module.exports.resolveLocalFileSystemURL = window.resolveLocalFileSystemURL || window.webkitResolveLocalFileSystemURL;
+            return true;
+        }
+        return false;
+    }
+    if (checkBrowser()) {
+        return;
+    }
+
+    var argscheck = require('cordova/argscheck');
+    var DirectoryEntry = require('./DirectoryEntry');
+    var FileEntry = require('./FileEntry');
+    var FileError = require('./FileError');
+    var exec = require('cordova/exec');
+    var fileSystems = require('./fileSystems');
+
+    /**
+     * Look up file system Entry referred to by local URI.
+     * @param {DOMString} uri  URI referring to a local file or directory
+     * @param successCallback  invoked with Entry object corresponding to URI
+     * @param errorCallback    invoked if error occurs retrieving file system entry
+     */
+    module.exports.resolveLocalFileSystemURL = module.exports.resolveLocalFileSystemURL || function (uri, successCallback, errorCallback) {
+        argscheck.checkArgs('sFF', 'resolveLocalFileSystemURI', arguments);
+        // error callback
+        var fail = function (error) {
+            if (errorCallback) {
+                errorCallback(new FileError(error));
+            }
+        };
+        // sanity check for 'not:valid:filename' or '/not:valid:filename'
+        // file.spec.12 window.resolveLocalFileSystemURI should error (ENCODING_ERR) when resolving invalid URI with leading /.
+        if (!uri || uri.split(':').length > 2) {
+            setTimeout(function () {
+                fail(FileError.ENCODING_ERR);
+            }, 0);
+            return;
+        }
+        // if successful, return either a file or directory entry
+        var success = function (entry) {
+            if (entry) {
+                if (successCallback) {
+                    // create appropriate Entry object
+                    var fsName = entry.filesystemName || (entry.filesystem && entry.filesystem.name) || (entry.filesystem === window.PERSISTENT ? 'persistent' : 'temporary'); // eslint-disable-line no-undef
+                    fileSystems.getFs(fsName, function (fs) {
+                        // This should happen only on platforms that haven't implemented requestAllFileSystems (windows)
+                        if (!fs) {
+                            fs = new FileSystem(fsName, {name: '', fullPath: '/'}); // eslint-disable-line no-undef
+                        }
+                        var result = (entry.isDirectory) ? new DirectoryEntry(entry.name, entry.fullPath, fs, entry.nativeURL) : new FileEntry(entry.name, entry.fullPath, fs, entry.nativeURL);
+                        successCallback(result);
+                    });
+                }
+            } else {
+                // no Entry object returned
+                fail(FileError.NOT_FOUND_ERR);
+            }
+        };
+
+        exec(success, fail, 'File', 'resolveLocalFileSystemURI', [uri]);
+    };
+
+    module.exports.resolveLocalFileSystemURI = function () {
+        console.log('resolveLocalFileSystemURI is deprecated. Please call resolveLocalFileSystemURL instead.');
+        module.exports.resolveLocalFileSystemURL.apply(this, arguments);
+    };
+})();
+
+});

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
platforms/android/assets/www/static/css/app.44c6fba2497d021b0fe5996010bdf47c.css


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
platforms/android/assets/www/static/css/app.44c6fba2497d021b0fe5996010bdf47c.css.map


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
platforms/android/assets/www/static/css/app.4f8eeb06a5dd3d82226904aed0299860.css


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
platforms/android/assets/www/static/css/app.4f8eeb06a5dd3d82226904aed0299860.css.map


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
platforms/android/assets/www/static/js/app.069151de1070e52c112d.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
platforms/android/assets/www/static/js/app.069151de1070e52c112d.js.map


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
platforms/android/assets/www/static/js/app.aba1de22627cc6f1889c.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
platforms/android/assets/www/static/js/app.aba1de22627cc6f1889c.js.map


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
platforms/android/assets/www/static/js/manifest.3ad1d5771e9b13dbdad2.js.map


+ 176 - 1
platforms/android/platform_www/cordova_plugins.js

@@ -81,6 +81,179 @@ module.exports = [
     "clobbers": [
       "Wechat"
     ]
+  },
+  {
+    "id": "cordova-plugin-file.DirectoryEntry",
+    "file": "plugins/cordova-plugin-file/www/DirectoryEntry.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.DirectoryEntry"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.DirectoryReader",
+    "file": "plugins/cordova-plugin-file/www/DirectoryReader.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.DirectoryReader"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.Entry",
+    "file": "plugins/cordova-plugin-file/www/Entry.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.Entry"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.File",
+    "file": "plugins/cordova-plugin-file/www/File.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.File"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileEntry",
+    "file": "plugins/cordova-plugin-file/www/FileEntry.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileEntry"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileError",
+    "file": "plugins/cordova-plugin-file/www/FileError.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileError"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileReader",
+    "file": "plugins/cordova-plugin-file/www/FileReader.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileReader"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileSystem",
+    "file": "plugins/cordova-plugin-file/www/FileSystem.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileSystem"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileUploadOptions",
+    "file": "plugins/cordova-plugin-file/www/FileUploadOptions.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileUploadOptions"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileUploadResult",
+    "file": "plugins/cordova-plugin-file/www/FileUploadResult.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileUploadResult"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileWriter",
+    "file": "plugins/cordova-plugin-file/www/FileWriter.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileWriter"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.Flags",
+    "file": "plugins/cordova-plugin-file/www/Flags.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.Flags"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.LocalFileSystem",
+    "file": "plugins/cordova-plugin-file/www/LocalFileSystem.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.LocalFileSystem"
+    ],
+    "merges": [
+      "window"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.Metadata",
+    "file": "plugins/cordova-plugin-file/www/Metadata.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.Metadata"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.ProgressEvent",
+    "file": "plugins/cordova-plugin-file/www/ProgressEvent.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.ProgressEvent"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.fileSystems",
+    "file": "plugins/cordova-plugin-file/www/fileSystems.js",
+    "pluginId": "cordova-plugin-file"
+  },
+  {
+    "id": "cordova-plugin-file.requestFileSystem",
+    "file": "plugins/cordova-plugin-file/www/requestFileSystem.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.requestFileSystem"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.resolveLocalFileSystemURI",
+    "file": "plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js",
+    "pluginId": "cordova-plugin-file",
+    "merges": [
+      "window"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.isChrome",
+    "file": "plugins/cordova-plugin-file/www/browser/isChrome.js",
+    "pluginId": "cordova-plugin-file",
+    "runs": true
+  },
+  {
+    "id": "cordova-plugin-file.androidFileSystem",
+    "file": "plugins/cordova-plugin-file/www/android/FileSystem.js",
+    "pluginId": "cordova-plugin-file",
+    "merges": [
+      "FileSystem"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.fileSystems-roots",
+    "file": "plugins/cordova-plugin-file/www/fileSystems-roots.js",
+    "pluginId": "cordova-plugin-file",
+    "runs": true
+  },
+  {
+    "id": "cordova-plugin-file.fileSystemPaths",
+    "file": "plugins/cordova-plugin-file/www/fileSystemPaths.js",
+    "pluginId": "cordova-plugin-file",
+    "merges": [
+      "cordova"
+    ],
+    "runs": true
   }
 ];
 module.exports.metadata = 
@@ -96,7 +269,9 @@ module.exports.metadata =
   "cordova-android-support-gradle-release": "1.4.2",
   "cordova-plugin-wkwebview-engine": "1.1.4",
   "cordova-plugin-statusbar": "2.4.2",
-  "cordova-plugin-wechatv2": "2.1.2"
+  "cordova-plugin-wechatv2": "2.1.2",
+  "cordova-plugin-file": "6.0.1",
+  "cordova-plugin-remote-injection": "0.5.2"
 };
 // BOTTOM OF METADATA
 });

+ 120 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/DirectoryEntry.js

@@ -0,0 +1,120 @@
+cordova.define("cordova-plugin-file.DirectoryEntry", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var argscheck = require('cordova/argscheck');
+var utils = require('cordova/utils');
+var exec = require('cordova/exec');
+var Entry = require('./Entry');
+var FileError = require('./FileError');
+var DirectoryReader = require('./DirectoryReader');
+
+/**
+ * An interface representing a directory on the file system.
+ *
+ * {boolean} isFile always false (readonly)
+ * {boolean} isDirectory always true (readonly)
+ * {DOMString} name of the directory, excluding the path leading to it (readonly)
+ * {DOMString} fullPath the absolute full path to the directory (readonly)
+ * {FileSystem} filesystem on which the directory resides (readonly)
+ */
+var DirectoryEntry = function (name, fullPath, fileSystem, nativeURL) {
+
+    // add trailing slash if it is missing
+    if ((fullPath) && !/\/$/.test(fullPath)) {
+        fullPath += '/';
+    }
+    // add trailing slash if it is missing
+    if (nativeURL && !/\/$/.test(nativeURL)) {
+        nativeURL += '/';
+    }
+    DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath, fileSystem, nativeURL);
+};
+
+utils.extend(DirectoryEntry, Entry);
+
+/**
+ * Creates a new DirectoryReader to read entries from this directory
+ */
+DirectoryEntry.prototype.createReader = function () {
+    return new DirectoryReader(this.toInternalURL());
+};
+
+/**
+ * Creates or looks up a directory
+ *
+ * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory
+ * @param {Flags} options to create or exclusively create the directory
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.getDirectory = function (path, options, successCallback, errorCallback) {
+    argscheck.checkArgs('sOFF', 'DirectoryEntry.getDirectory', arguments);
+    var fs = this.filesystem;
+    var win = successCallback && function (result) {
+        var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
+        successCallback(entry);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'getDirectory', [this.toInternalURL(), path, options]);
+};
+
+/**
+ * Deletes a directory and all of it's contents
+ *
+ * @param {Function} successCallback is called with no parameters
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.removeRecursively = function (successCallback, errorCallback) {
+    argscheck.checkArgs('FF', 'DirectoryEntry.removeRecursively', arguments);
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(successCallback, fail, 'File', 'removeRecursively', [this.toInternalURL()]);
+};
+
+/**
+ * Creates or looks up a file
+ *
+ * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file
+ * @param {Flags} options to create or exclusively create the file
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.getFile = function (path, options, successCallback, errorCallback) {
+    argscheck.checkArgs('sOFF', 'DirectoryEntry.getFile', arguments);
+    var fs = this.filesystem;
+    var win = successCallback && function (result) {
+        var FileEntry = require('./FileEntry');
+        var entry = new FileEntry(result.name, result.fullPath, fs, result.nativeURL);
+        successCallback(entry);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'getFile', [this.toInternalURL(), path, options]);
+};
+
+module.exports = DirectoryEntry;
+
+});

+ 75 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/DirectoryReader.js

@@ -0,0 +1,75 @@
+cordova.define("cordova-plugin-file.DirectoryReader", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var exec = require('cordova/exec');
+var FileError = require('./FileError');
+
+/**
+ * An interface that lists the files and directories in a directory.
+ */
+function DirectoryReader (localURL) {
+    this.localURL = localURL || null;
+    this.hasReadEntries = false;
+}
+
+/**
+ * Returns a list of entries from a directory.
+ *
+ * @param {Function} successCallback is called with a list of entries
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryReader.prototype.readEntries = function (successCallback, errorCallback) {
+    // If we've already read and passed on this directory's entries, return an empty list.
+    if (this.hasReadEntries) {
+        successCallback([]);
+        return;
+    }
+    var reader = this;
+    var win = typeof successCallback !== 'function' ? null : function (result) {
+        var retVal = [];
+        for (var i = 0; i < result.length; i++) {
+            var entry = null;
+            if (result[i].isDirectory) {
+                entry = new (require('./DirectoryEntry'))();
+            } else if (result[i].isFile) {
+                entry = new (require('./FileEntry'))();
+            }
+            entry.isDirectory = result[i].isDirectory;
+            entry.isFile = result[i].isFile;
+            entry.name = result[i].name;
+            entry.fullPath = result[i].fullPath;
+            entry.filesystem = new (require('./FileSystem'))(result[i].filesystemName);
+            entry.nativeURL = result[i].nativeURL;
+            retVal.push(entry);
+        }
+        reader.hasReadEntries = true;
+        successCallback(retVal);
+    };
+    var fail = typeof errorCallback !== 'function' ? null : function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'readEntries', [this.localURL]);
+};
+
+module.exports = DirectoryReader;
+
+});

+ 263 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/Entry.js

@@ -0,0 +1,263 @@
+cordova.define("cordova-plugin-file.Entry", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var argscheck = require('cordova/argscheck');
+var exec = require('cordova/exec');
+var FileError = require('./FileError');
+var Metadata = require('./Metadata');
+
+/**
+ * Represents a file or directory on the local file system.
+ *
+ * @param isFile
+ *            {boolean} true if Entry is a file (readonly)
+ * @param isDirectory
+ *            {boolean} true if Entry is a directory (readonly)
+ * @param name
+ *            {DOMString} name of the file or directory, excluding the path
+ *            leading to it (readonly)
+ * @param fullPath
+ *            {DOMString} the absolute full path to the file or directory
+ *            (readonly)
+ * @param fileSystem
+ *            {FileSystem} the filesystem on which this entry resides
+ *            (readonly)
+ * @param nativeURL
+ *            {DOMString} an alternate URL which can be used by native
+ *            webview controls, for example media players.
+ *            (optional, readonly)
+ */
+function Entry (isFile, isDirectory, name, fullPath, fileSystem, nativeURL) {
+    this.isFile = !!isFile;
+    this.isDirectory = !!isDirectory;
+    this.name = name || '';
+    this.fullPath = fullPath || '';
+    this.filesystem = fileSystem || null;
+    this.nativeURL = nativeURL || null;
+}
+
+/**
+ * Look up the metadata of the entry.
+ *
+ * @param successCallback
+ *            {Function} is called with a Metadata object
+ * @param errorCallback
+ *            {Function} is called with a FileError
+ */
+Entry.prototype.getMetadata = function (successCallback, errorCallback) {
+    argscheck.checkArgs('FF', 'Entry.getMetadata', arguments);
+    var success = successCallback && function (entryMetadata) {
+        var metadata = new Metadata({
+            size: entryMetadata.size,
+            modificationTime: entryMetadata.lastModifiedDate
+        });
+        successCallback(metadata);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(success, fail, 'File', 'getFileMetadata', [this.toInternalURL()]);
+};
+
+/**
+ * Set the metadata of the entry.
+ *
+ * @param successCallback
+ *            {Function} is called with a Metadata object
+ * @param errorCallback
+ *            {Function} is called with a FileError
+ * @param metadataObject
+ *            {Object} keys and values to set
+ */
+Entry.prototype.setMetadata = function (successCallback, errorCallback, metadataObject) {
+    argscheck.checkArgs('FFO', 'Entry.setMetadata', arguments);
+    exec(successCallback, errorCallback, 'File', 'setMetadata', [this.toInternalURL(), metadataObject]);
+};
+
+/**
+ * Move a file or directory to a new location.
+ *
+ * @param parent
+ *            {DirectoryEntry} the directory to which to move this entry
+ * @param newName
+ *            {DOMString} new name of the entry, defaults to the current name
+ * @param successCallback
+ *            {Function} called with the new DirectoryEntry object
+ * @param errorCallback
+ *            {Function} called with a FileError
+ */
+Entry.prototype.moveTo = function (parent, newName, successCallback, errorCallback) {
+    argscheck.checkArgs('oSFF', 'Entry.moveTo', arguments);
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    var srcURL = this.toInternalURL();
+    // entry name
+    var name = newName || this.name;
+    var success = function (entry) {
+        if (entry) {
+            if (successCallback) {
+                // create appropriate Entry object
+                var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
+                var fs = newFSName ? new FileSystem(newFSName, { name: '', fullPath: '/' }) : new FileSystem(parent.filesystem.name, { name: '', fullPath: '/' }); // eslint-disable-line no-undef
+                var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
+                successCallback(result);
+            }
+        } else {
+            // no Entry object returned
+            if (fail) {
+                fail(FileError.NOT_FOUND_ERR);
+            }
+        }
+    };
+
+    // copy
+    exec(success, fail, 'File', 'moveTo', [srcURL, parent.toInternalURL(), name]);
+};
+
+/**
+ * Copy a directory to a different location.
+ *
+ * @param parent
+ *            {DirectoryEntry} the directory to which to copy the entry
+ * @param newName
+ *            {DOMString} new name of the entry, defaults to the current name
+ * @param successCallback
+ *            {Function} called with the new Entry object
+ * @param errorCallback
+ *            {Function} called with a FileError
+ */
+Entry.prototype.copyTo = function (parent, newName, successCallback, errorCallback) {
+    argscheck.checkArgs('oSFF', 'Entry.copyTo', arguments);
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    var srcURL = this.toInternalURL();
+        // entry name
+    var name = newName || this.name;
+    // success callback
+    var success = function (entry) {
+        if (entry) {
+            if (successCallback) {
+                // create appropriate Entry object
+                var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
+                var fs = newFSName ? new FileSystem(newFSName, { name: '', fullPath: '/' }) : new FileSystem(parent.filesystem.name, { name: '', fullPath: '/' }); // eslint-disable-line no-undef
+                var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
+                successCallback(result);
+            }
+        } else {
+            // no Entry object returned
+            if (fail) {
+                fail(FileError.NOT_FOUND_ERR);
+            }
+        }
+    };
+
+    // copy
+    exec(success, fail, 'File', 'copyTo', [srcURL, parent.toInternalURL(), name]);
+};
+
+/**
+ * Return a URL that can be passed across the bridge to identify this entry.
+ */
+Entry.prototype.toInternalURL = function () {
+    if (this.filesystem && this.filesystem.__format__) {
+        return this.filesystem.__format__(this.fullPath, this.nativeURL);
+    }
+};
+
+/**
+ * Return a URL that can be used to identify this entry.
+ * Use a URL that can be used to as the src attribute of a <video> or
+ * <audio> tag. If that is not possible, construct a cdvfile:// URL.
+ */
+Entry.prototype.toURL = function () {
+    if (this.nativeURL) {
+        return this.nativeURL;
+    }
+    // fullPath attribute may contain the full URL in the case that
+    // toInternalURL fails.
+    return this.toInternalURL() || 'file://localhost' + this.fullPath;
+};
+
+/**
+ * Backwards-compatibility: In v1.0.0 - 1.0.2, .toURL would only return a
+ * cdvfile:// URL, and this method was necessary to obtain URLs usable by the
+ * webview.
+ * See CB-6051, CB-6106, CB-6117, CB-6152, CB-6199, CB-6201, CB-6243, CB-6249,
+ * and CB-6300.
+ */
+Entry.prototype.toNativeURL = function () {
+    console.log("DEPRECATED: Update your code to use 'toURL'");
+    return this.toURL();
+};
+
+/**
+ * Returns a URI that can be used to identify this entry.
+ *
+ * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI.
+ * @return uri
+ */
+Entry.prototype.toURI = function (mimeType) {
+    console.log("DEPRECATED: Update your code to use 'toURL'");
+    return this.toURL();
+};
+
+/**
+ * Remove a file or directory. It is an error to attempt to delete a
+ * directory that is not empty. It is an error to attempt to delete a
+ * root directory of a file system.
+ *
+ * @param successCallback {Function} called with no parameters
+ * @param errorCallback {Function} called with a FileError
+ */
+Entry.prototype.remove = function (successCallback, errorCallback) {
+    argscheck.checkArgs('FF', 'Entry.remove', arguments);
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(successCallback, fail, 'File', 'remove', [this.toInternalURL()]);
+};
+
+/**
+ * Look up the parent DirectoryEntry of this entry.
+ *
+ * @param successCallback {Function} called with the parent DirectoryEntry object
+ * @param errorCallback {Function} called with a FileError
+ */
+Entry.prototype.getParent = function (successCallback, errorCallback) {
+    argscheck.checkArgs('FF', 'Entry.getParent', arguments);
+    var fs = this.filesystem;
+    var win = successCallback && function (result) {
+        var DirectoryEntry = require('./DirectoryEntry');
+        var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
+        successCallback(entry);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'getParent', [this.toInternalURL()]);
+};
+
+module.exports = Entry;
+
+});

+ 81 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/File.js

@@ -0,0 +1,81 @@
+cordova.define("cordova-plugin-file.File", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * Constructor.
+ * name {DOMString} name of the file, without path information
+ * fullPath {DOMString} the full path of the file, including the name
+ * type {DOMString} mime type
+ * lastModifiedDate {Date} last modified date
+ * size {Number} size of the file in bytes
+ */
+
+var File = function (name, localURL, type, lastModifiedDate, size) {
+    this.name = name || '';
+    this.localURL = localURL || null;
+    this.type = type || null;
+    this.lastModified = lastModifiedDate || null;
+    // For backwards compatibility, store the timestamp in lastModifiedDate as well
+    this.lastModifiedDate = lastModifiedDate || null;
+    this.size = size || 0;
+
+    // These store the absolute start and end for slicing the file.
+    this.start = 0;
+    this.end = this.size;
+};
+
+/**
+ * Returns a "slice" of the file. Since Cordova Files don't contain the actual
+ * content, this really returns a File with adjusted start and end.
+ * Slices of slices are supported.
+ * start {Number} The index at which to start the slice (inclusive).
+ * end {Number} The index at which to end the slice (exclusive).
+ */
+File.prototype.slice = function (start, end) {
+    var size = this.end - this.start;
+    var newStart = 0;
+    var newEnd = size;
+    if (arguments.length) {
+        if (start < 0) {
+            newStart = Math.max(size + start, 0);
+        } else {
+            newStart = Math.min(size, start);
+        }
+    }
+
+    if (arguments.length >= 2) {
+        if (end < 0) {
+            newEnd = Math.max(size + end, 0);
+        } else {
+            newEnd = Math.min(end, size);
+        }
+    }
+
+    var newFile = new File(this.name, this.localURL, this.type, this.lastModified, this.size);
+    newFile.start = this.start + newStart;
+    newFile.end = this.start + newEnd;
+    return newFile;
+};
+
+module.exports = File;
+
+});

+ 95 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/FileEntry.js

@@ -0,0 +1,95 @@
+cordova.define("cordova-plugin-file.FileEntry", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var utils = require('cordova/utils');
+var exec = require('cordova/exec');
+var Entry = require('./Entry');
+var FileWriter = require('./FileWriter');
+var File = require('./File');
+var FileError = require('./FileError');
+
+/**
+ * An interface representing a file on the file system.
+ *
+ * {boolean} isFile always true (readonly)
+ * {boolean} isDirectory always false (readonly)
+ * {DOMString} name of the file, excluding the path leading to it (readonly)
+ * {DOMString} fullPath the absolute full path to the file (readonly)
+ * {FileSystem} filesystem on which the file resides (readonly)
+ */
+var FileEntry = function (name, fullPath, fileSystem, nativeURL) {
+    // remove trailing slash if it is present
+    if (fullPath && /\/$/.test(fullPath)) {
+        fullPath = fullPath.substring(0, fullPath.length - 1);
+    }
+    if (nativeURL && /\/$/.test(nativeURL)) {
+        nativeURL = nativeURL.substring(0, nativeURL.length - 1);
+    }
+
+    FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath, fileSystem, nativeURL]);
+};
+
+utils.extend(FileEntry, Entry);
+
+/**
+ * Creates a new FileWriter associated with the file that this FileEntry represents.
+ *
+ * @param {Function} successCallback is called with the new FileWriter
+ * @param {Function} errorCallback is called with a FileError
+ */
+FileEntry.prototype.createWriter = function (successCallback, errorCallback) {
+    this.file(function (filePointer) {
+        var writer = new FileWriter(filePointer);
+
+        if (writer.localURL === null || writer.localURL === '') {
+            if (errorCallback) {
+                errorCallback(new FileError(FileError.INVALID_STATE_ERR));
+            }
+        } else {
+            if (successCallback) {
+                successCallback(writer);
+            }
+        }
+    }, errorCallback);
+};
+
+/**
+ * Returns a File that represents the current state of the file that this FileEntry represents.
+ *
+ * @param {Function} successCallback is called with the new File object
+ * @param {Function} errorCallback is called with a FileError
+ */
+FileEntry.prototype.file = function (successCallback, errorCallback) {
+    var localURL = this.toInternalURL();
+    var win = successCallback && function (f) {
+        var file = new File(f.name, localURL, f.type, f.lastModifiedDate, f.size);
+        successCallback(file);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'getFileMetadata', [localURL]);
+};
+
+module.exports = FileEntry;
+
+});

+ 49 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/FileError.js

@@ -0,0 +1,49 @@
+cordova.define("cordova-plugin-file.FileError", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * FileError
+ */
+function FileError (error) {
+    this.code = error || null;
+}
+
+// File error codes
+// Found in DOMException
+FileError.NOT_FOUND_ERR = 1;
+FileError.SECURITY_ERR = 2;
+FileError.ABORT_ERR = 3;
+
+// Added by File API specification
+FileError.NOT_READABLE_ERR = 4;
+FileError.ENCODING_ERR = 5;
+FileError.NO_MODIFICATION_ALLOWED_ERR = 6;
+FileError.INVALID_STATE_ERR = 7;
+FileError.SYNTAX_ERR = 8;
+FileError.INVALID_MODIFICATION_ERR = 9;
+FileError.QUOTA_EXCEEDED_ERR = 10;
+FileError.TYPE_MISMATCH_ERR = 11;
+FileError.PATH_EXISTS_ERR = 12;
+
+module.exports = FileError;
+
+});

+ 301 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/FileReader.js

@@ -0,0 +1,301 @@
+cordova.define("cordova-plugin-file.FileReader", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var exec = require('cordova/exec');
+var modulemapper = require('cordova/modulemapper');
+var utils = require('cordova/utils');
+var FileError = require('./FileError');
+var ProgressEvent = require('./ProgressEvent');
+var origFileReader = modulemapper.getOriginalSymbol(window, 'FileReader');
+
+/**
+ * This class reads the mobile device file system.
+ *
+ * For Android:
+ *      The root directory is the root of the file system.
+ *      To read from the SD card, the file name is "sdcard/my_file.txt"
+ * @constructor
+ */
+var FileReader = function () {
+    this._readyState = 0;
+    this._error = null;
+    this._result = null;
+    this._progress = null;
+    this._localURL = '';
+    this._realReader = origFileReader ? new origFileReader() : {}; // eslint-disable-line new-cap
+};
+
+/**
+ * Defines the maximum size to read at a time via the native API. The default value is a compromise between
+ * minimizing the overhead of many exec() calls while still reporting progress frequently enough for large files.
+ * (Note attempts to allocate more than a few MB of contiguous memory on the native side are likely to cause
+ * OOM exceptions, while the JS engine seems to have fewer problems managing large strings or ArrayBuffers.)
+ */
+FileReader.READ_CHUNK_SIZE = 256 * 1024;
+
+// States
+FileReader.EMPTY = 0;
+FileReader.LOADING = 1;
+FileReader.DONE = 2;
+
+utils.defineGetter(FileReader.prototype, 'readyState', function () {
+    return this._localURL ? this._readyState : this._realReader.readyState;
+});
+
+utils.defineGetter(FileReader.prototype, 'error', function () {
+    return this._localURL ? this._error : this._realReader.error;
+});
+
+utils.defineGetter(FileReader.prototype, 'result', function () {
+    return this._localURL ? this._result : this._realReader.result;
+});
+
+function defineEvent (eventName) {
+    utils.defineGetterSetter(FileReader.prototype, eventName, function () {
+        return this._realReader[eventName] || null;
+    }, function (value) {
+        this._realReader[eventName] = value;
+    });
+}
+defineEvent('onloadstart');    // When the read starts.
+defineEvent('onprogress');     // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total)
+defineEvent('onload');         // When the read has successfully completed.
+defineEvent('onerror');        // When the read has failed (see errors).
+defineEvent('onloadend');      // When the request has completed (either in success or failure).
+defineEvent('onabort');        // When the read has been aborted. For instance, by invoking the abort() method.
+
+function initRead (reader, file) {
+    // Already loading something
+    if (reader.readyState === FileReader.LOADING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    reader._result = null;
+    reader._error = null;
+    reader._progress = 0;
+    reader._readyState = FileReader.LOADING;
+
+    if (typeof file.localURL === 'string') {
+        reader._localURL = file.localURL;
+    } else {
+        reader._localURL = '';
+        return true;
+    }
+
+    if (reader.onloadstart) {
+        reader.onloadstart(new ProgressEvent('loadstart', {target: reader}));
+    }
+}
+
+/**
+ * Callback used by the following read* functions to handle incremental or final success.
+ * Must be bound to the FileReader's this along with all but the last parameter,
+ * e.g. readSuccessCallback.bind(this, "readAsText", "UTF-8", offset, totalSize, accumulate)
+ * @param readType The name of the read function to call.
+ * @param encoding Text encoding, or null if this is not a text type read.
+ * @param offset Starting offset of the read.
+ * @param totalSize Total number of bytes or chars to read.
+ * @param accumulate A function that takes the callback result and accumulates it in this._result.
+ * @param r Callback result returned by the last read exec() call, or null to begin reading.
+ */
+function readSuccessCallback (readType, encoding, offset, totalSize, accumulate, r) {
+    if (this._readyState === FileReader.DONE) {
+        return;
+    }
+
+    var CHUNK_SIZE = FileReader.READ_CHUNK_SIZE;
+    if (readType === 'readAsDataURL') {
+        // Windows proxy does not support reading file slices as Data URLs
+        // so read the whole file at once.
+        CHUNK_SIZE = cordova.platformId === 'windows' ? totalSize : // eslint-disable-line no-undef
+            // Calculate new chunk size for data URLs to be multiply of 3
+            // Otherwise concatenated base64 chunks won't be valid base64 data
+            FileReader.READ_CHUNK_SIZE - (FileReader.READ_CHUNK_SIZE % 3) + 3;
+    }
+
+    if (typeof r !== 'undefined') {
+        accumulate(r);
+        this._progress = Math.min(this._progress + CHUNK_SIZE, totalSize);
+
+        if (typeof this.onprogress === 'function') {
+            this.onprogress(new ProgressEvent('progress', {loaded: this._progress, total: totalSize}));
+        }
+    }
+
+    if (typeof r === 'undefined' || this._progress < totalSize) {
+        var execArgs = [
+            this._localURL,
+            offset + this._progress,
+            offset + this._progress + Math.min(totalSize - this._progress, CHUNK_SIZE)];
+        if (encoding) {
+            execArgs.splice(1, 0, encoding);
+        }
+        exec(
+            readSuccessCallback.bind(this, readType, encoding, offset, totalSize, accumulate),
+            readFailureCallback.bind(this),
+            'File', readType, execArgs);
+    } else {
+        this._readyState = FileReader.DONE;
+
+        if (typeof this.onload === 'function') {
+            this.onload(new ProgressEvent('load', {target: this}));
+        }
+
+        if (typeof this.onloadend === 'function') {
+            this.onloadend(new ProgressEvent('loadend', {target: this}));
+        }
+    }
+}
+
+/**
+ * Callback used by the following read* functions to handle errors.
+ * Must be bound to the FileReader's this, e.g. readFailureCallback.bind(this)
+ */
+function readFailureCallback (e) {
+    if (this._readyState === FileReader.DONE) {
+        return;
+    }
+
+    this._readyState = FileReader.DONE;
+    this._result = null;
+    this._error = new FileError(e);
+
+    if (typeof this.onerror === 'function') {
+        this.onerror(new ProgressEvent('error', {target: this}));
+    }
+
+    if (typeof this.onloadend === 'function') {
+        this.onloadend(new ProgressEvent('loadend', {target: this}));
+    }
+}
+
+/**
+ * Abort reading file.
+ */
+FileReader.prototype.abort = function () {
+    if (origFileReader && !this._localURL) {
+        return this._realReader.abort();
+    }
+    this._result = null;
+
+    if (this._readyState === FileReader.DONE || this._readyState === FileReader.EMPTY) {
+        return;
+    }
+
+    this._readyState = FileReader.DONE;
+
+    // If abort callback
+    if (typeof this.onabort === 'function') {
+        this.onabort(new ProgressEvent('abort', {target: this}));
+    }
+    // If load end callback
+    if (typeof this.onloadend === 'function') {
+        this.onloadend(new ProgressEvent('loadend', {target: this}));
+    }
+};
+
+/**
+ * Read text file.
+ *
+ * @param file          {File} File object containing file properties
+ * @param encoding      [Optional] (see http://www.iana.org/assignments/character-sets)
+ */
+FileReader.prototype.readAsText = function (file, encoding) {
+    if (initRead(this, file)) {
+        return this._realReader.readAsText(file, encoding);
+    }
+
+    // Default encoding is UTF-8
+    var enc = encoding || 'UTF-8';
+
+    var totalSize = file.end - file.start;
+    readSuccessCallback.bind(this)('readAsText', enc, file.start, totalSize, function (r) {
+        if (this._progress === 0) {
+            this._result = '';
+        }
+        this._result += r;
+    }.bind(this));
+};
+
+/**
+ * Read file and return data as a base64 encoded data url.
+ * A data url is of the form:
+ *      data:[<mediatype>][;base64],<data>
+ *
+ * @param file          {File} File object containing file properties
+ */
+FileReader.prototype.readAsDataURL = function (file) {
+    if (initRead(this, file)) {
+        return this._realReader.readAsDataURL(file);
+    }
+
+    var totalSize = file.end - file.start;
+    readSuccessCallback.bind(this)('readAsDataURL', null, file.start, totalSize, function (r) {
+        var commaIndex = r.indexOf(',');
+        if (this._progress === 0) {
+            this._result = r;
+        } else {
+            this._result += r.substring(commaIndex + 1);
+        }
+    }.bind(this));
+};
+
+/**
+ * Read file and return data as a binary data.
+ *
+ * @param file          {File} File object containing file properties
+ */
+FileReader.prototype.readAsBinaryString = function (file) {
+    if (initRead(this, file)) {
+        return this._realReader.readAsBinaryString(file);
+    }
+
+    var totalSize = file.end - file.start;
+    readSuccessCallback.bind(this)('readAsBinaryString', null, file.start, totalSize, function (r) {
+        if (this._progress === 0) {
+            this._result = '';
+        }
+        this._result += r;
+    }.bind(this));
+};
+
+/**
+ * Read file and return data as a binary data.
+ *
+ * @param file          {File} File object containing file properties
+ */
+FileReader.prototype.readAsArrayBuffer = function (file) {
+    if (initRead(this, file)) {
+        return this._realReader.readAsArrayBuffer(file);
+    }
+
+    var totalSize = file.end - file.start;
+    readSuccessCallback.bind(this)('readAsArrayBuffer', null, file.start, totalSize, function (r) {
+        var resultArray = (this._progress === 0 ? new Uint8Array(totalSize) : new Uint8Array(this._result));
+        resultArray.set(new Uint8Array(r), this._progress);
+        this._result = resultArray.buffer;
+    }.bind(this));
+};
+
+module.exports = FileReader;
+
+});

+ 58 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/FileSystem.js

@@ -0,0 +1,58 @@
+cordova.define("cordova-plugin-file.FileSystem", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var DirectoryEntry = require('./DirectoryEntry');
+
+/**
+ * An interface representing a file system
+ *
+ * @constructor
+ * {DOMString} name the unique name of the file system (readonly)
+ * {DirectoryEntry} root directory of the file system (readonly)
+ */
+var FileSystem = function (name, root) {
+    this.name = name;
+    if (root) {
+        this.root = new DirectoryEntry(root.name, root.fullPath, this, root.nativeURL);
+    } else {
+        this.root = new DirectoryEntry(this.name, '/', this);
+    }
+};
+
+FileSystem.prototype.__format__ = function (fullPath, nativeUrl) {
+    return fullPath;
+};
+
+FileSystem.prototype.toJSON = function () {
+    return '<FileSystem: ' + this.name + '>';
+};
+
+// Use instead of encodeURI() when encoding just the path part of a URI rather than an entire URI.
+FileSystem.encodeURIPath = function (path) {
+    // Because # is a valid filename character, it must be encoded to prevent part of the
+    // path from being parsed as a URI fragment.
+    return encodeURI(path).replace(/#/g, '%23');
+};
+
+module.exports = FileSystem;
+
+});

+ 44 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/FileUploadOptions.js

@@ -0,0 +1,44 @@
+cordova.define("cordova-plugin-file.FileUploadOptions", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * Options to customize the HTTP request used to upload files.
+ * @constructor
+ * @param fileKey {String}   Name of file request parameter.
+ * @param fileName {String}  Filename to be used by the server. Defaults to image.jpg.
+ * @param mimeType {String}  Mimetype of the uploaded file. Defaults to image/jpeg.
+ * @param params {Object}    Object with key: value params to send to the server.
+ * @param headers {Object}   Keys are header names, values are header values. Multiple
+ *                           headers of the same name are not supported.
+ */
+var FileUploadOptions = function (fileKey, fileName, mimeType, params, headers, httpMethod) {
+    this.fileKey = fileKey || null;
+    this.fileName = fileName || null;
+    this.mimeType = mimeType || null;
+    this.params = params || null;
+    this.headers = headers || null;
+    this.httpMethod = httpMethod || null;
+};
+
+module.exports = FileUploadOptions;
+
+});

+ 33 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/FileUploadResult.js

@@ -0,0 +1,33 @@
+cordova.define("cordova-plugin-file.FileUploadResult", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * FileUploadResult
+ * @constructor
+ */
+module.exports = function FileUploadResult (size, code, content) {
+    this.bytesSent = size;
+    this.responseCode = code;
+    this.response = content;
+};
+
+});

+ 327 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/FileWriter.js

@@ -0,0 +1,327 @@
+cordova.define("cordova-plugin-file.FileWriter", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var exec = require('cordova/exec');
+var FileError = require('./FileError');
+var ProgressEvent = require('./ProgressEvent');
+
+/**
+ * This class writes to the mobile device file system.
+ *
+ * For Android:
+ *      The root directory is the root of the file system.
+ *      To write to the SD card, the file name is "sdcard/my_file.txt"
+ *
+ * @constructor
+ * @param file {File} File object containing file properties
+ * @param append if true write to the end of the file, otherwise overwrite the file
+ */
+var FileWriter = function (file) {
+    this.fileName = '';
+    this.length = 0;
+    if (file) {
+        this.localURL = file.localURL || file;
+        this.length = file.size || 0;
+    }
+    // default is to write at the beginning of the file
+    this.position = 0;
+
+    this.readyState = 0; // EMPTY
+
+    this.result = null;
+
+    // Error
+    this.error = null;
+
+    // Event handlers
+    this.onwritestart = null;   // When writing starts
+    this.onprogress = null;     // While writing the file, and reporting partial file data
+    this.onwrite = null;        // When the write has successfully completed.
+    this.onwriteend = null;     // When the request has completed (either in success or failure).
+    this.onabort = null;        // When the write has been aborted. For instance, by invoking the abort() method.
+    this.onerror = null;        // When the write has failed (see errors).
+};
+
+// States
+FileWriter.INIT = 0;
+FileWriter.WRITING = 1;
+FileWriter.DONE = 2;
+
+/**
+ * Abort writing file.
+ */
+FileWriter.prototype.abort = function () {
+    // check for invalid state
+    if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // set error
+    this.error = new FileError(FileError.ABORT_ERR);
+
+    this.readyState = FileWriter.DONE;
+
+    // If abort callback
+    if (typeof this.onabort === 'function') {
+        this.onabort(new ProgressEvent('abort', {'target': this}));
+    }
+
+    // If write end callback
+    if (typeof this.onwriteend === 'function') {
+        this.onwriteend(new ProgressEvent('writeend', {'target': this}));
+    }
+};
+
+/**
+ * Writes data to the file
+ *
+ * @param data text or blob to be written
+ * @param isPendingBlobReadResult {Boolean} true if the data is the pending blob read operation result
+ */
+FileWriter.prototype.write = function (data, isPendingBlobReadResult) {
+
+    var that = this;
+    var supportsBinary = (typeof window.Blob !== 'undefined' && typeof window.ArrayBuffer !== 'undefined');
+    /* eslint-disable no-undef */
+    var isProxySupportBlobNatively = (cordova.platformId === 'windows8' || cordova.platformId === 'windows');
+    var isBinary;
+
+    // Check to see if the incoming data is a blob
+    if (data instanceof File || (!isProxySupportBlobNatively && supportsBinary && data instanceof Blob)) {
+        var fileReader = new FileReader();
+        /* eslint-enable no-undef */
+        fileReader.onload = function () {
+            // Call this method again, with the arraybuffer as argument
+            FileWriter.prototype.write.call(that, this.result, true /* isPendingBlobReadResult */);
+        };
+        fileReader.onerror = function () {
+            // DONE state
+            that.readyState = FileWriter.DONE;
+
+            // Save error
+            that.error = this.error;
+
+            // If onerror callback
+            if (typeof that.onerror === 'function') {
+                that.onerror(new ProgressEvent('error', {'target': that}));
+            }
+
+            // If onwriteend callback
+            if (typeof that.onwriteend === 'function') {
+                that.onwriteend(new ProgressEvent('writeend', {'target': that}));
+            }
+        };
+
+        // WRITING state
+        this.readyState = FileWriter.WRITING;
+
+        if (supportsBinary) {
+            fileReader.readAsArrayBuffer(data);
+        } else {
+            fileReader.readAsText(data);
+        }
+        return;
+    }
+
+    // Mark data type for safer transport over the binary bridge
+    isBinary = supportsBinary && (data instanceof ArrayBuffer);
+    if (isBinary && cordova.platformId === 'windowsphone') { // eslint-disable-line no-undef
+        // create a plain array, using the keys from the Uint8Array view so that we can serialize it
+        data = Array.apply(null, new Uint8Array(data));
+    }
+
+    // Throw an exception if we are already writing a file
+    if (this.readyState === FileWriter.WRITING && !isPendingBlobReadResult) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // WRITING state
+    this.readyState = FileWriter.WRITING;
+
+    var me = this;
+
+    // If onwritestart callback
+    if (typeof me.onwritestart === 'function') {
+        me.onwritestart(new ProgressEvent('writestart', {'target': me}));
+    }
+
+    // Write file
+    exec(
+        // Success callback
+        function (r) {
+            // If DONE (cancelled), then don't do anything
+            if (me.readyState === FileWriter.DONE) {
+                return;
+            }
+
+            // position always increases by bytes written because file would be extended
+            me.position += r;
+            // The length of the file is now where we are done writing.
+
+            me.length = me.position;
+
+            // DONE state
+            me.readyState = FileWriter.DONE;
+
+            // If onwrite callback
+            if (typeof me.onwrite === 'function') {
+                me.onwrite(new ProgressEvent('write', {'target': me}));
+            }
+
+            // If onwriteend callback
+            if (typeof me.onwriteend === 'function') {
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
+            }
+        },
+        // Error callback
+        function (e) {
+            // If DONE (cancelled), then don't do anything
+            if (me.readyState === FileWriter.DONE) {
+                return;
+            }
+
+            // DONE state
+            me.readyState = FileWriter.DONE;
+
+            // Save error
+            me.error = new FileError(e);
+
+            // If onerror callback
+            if (typeof me.onerror === 'function') {
+                me.onerror(new ProgressEvent('error', {'target': me}));
+            }
+
+            // If onwriteend callback
+            if (typeof me.onwriteend === 'function') {
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
+            }
+        }, 'File', 'write', [this.localURL, data, this.position, isBinary]);
+};
+
+/**
+ * Moves the file pointer to the location specified.
+ *
+ * If the offset is a negative number the position of the file
+ * pointer is rewound.  If the offset is greater than the file
+ * size the position is set to the end of the file.
+ *
+ * @param offset is the location to move the file pointer to.
+ */
+FileWriter.prototype.seek = function (offset) {
+    // Throw an exception if we are already writing a file
+    if (this.readyState === FileWriter.WRITING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    if (!offset && offset !== 0) {
+        return;
+    }
+
+    // See back from end of file.
+    if (offset < 0) {
+        this.position = Math.max(offset + this.length, 0);
+    // Offset is bigger than file size so set position
+    // to the end of the file.
+    } else if (offset > this.length) {
+        this.position = this.length;
+    // Offset is between 0 and file size so set the position
+    // to start writing.
+    } else {
+        this.position = offset;
+    }
+};
+
+/**
+ * Truncates the file to the size specified.
+ *
+ * @param size to chop the file at.
+ */
+FileWriter.prototype.truncate = function (size) {
+    // Throw an exception if we are already writing a file
+    if (this.readyState === FileWriter.WRITING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // WRITING state
+    this.readyState = FileWriter.WRITING;
+
+    var me = this;
+
+    // If onwritestart callback
+    if (typeof me.onwritestart === 'function') {
+        me.onwritestart(new ProgressEvent('writestart', {'target': this}));
+    }
+
+    // Write file
+    exec(
+        // Success callback
+        function (r) {
+            // If DONE (cancelled), then don't do anything
+            if (me.readyState === FileWriter.DONE) {
+                return;
+            }
+
+            // DONE state
+            me.readyState = FileWriter.DONE;
+
+            // Update the length of the file
+            me.length = r;
+            me.position = Math.min(me.position, r);
+
+            // If onwrite callback
+            if (typeof me.onwrite === 'function') {
+                me.onwrite(new ProgressEvent('write', {'target': me}));
+            }
+
+            // If onwriteend callback
+            if (typeof me.onwriteend === 'function') {
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
+            }
+        },
+        // Error callback
+        function (e) {
+            // If DONE (cancelled), then don't do anything
+            if (me.readyState === FileWriter.DONE) {
+                return;
+            }
+
+            // DONE state
+            me.readyState = FileWriter.DONE;
+
+            // Save error
+            me.error = new FileError(e);
+
+            // If onerror callback
+            if (typeof me.onerror === 'function') {
+                me.onerror(new ProgressEvent('error', {'target': me}));
+            }
+
+            // If onwriteend callback
+            if (typeof me.onwriteend === 'function') {
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
+            }
+        }, 'File', 'truncate', [this.localURL, size]);
+};
+
+module.exports = FileWriter;
+
+});

+ 39 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/Flags.js

@@ -0,0 +1,39 @@
+cordova.define("cordova-plugin-file.Flags", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * Supplies arguments to methods that lookup or create files and directories.
+ *
+ * @param create
+ *            {boolean} file or directory if it doesn't exist
+ * @param exclusive
+ *            {boolean} used with create; if true the command will fail if
+ *            target path exists
+ */
+function Flags (create, exclusive) {
+    this.create = create || false;
+    this.exclusive = exclusive || false;
+}
+
+module.exports = Flags;
+
+});

+ 26 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/LocalFileSystem.js

@@ -0,0 +1,26 @@
+cordova.define("cordova-plugin-file.LocalFileSystem", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+exports.TEMPORARY = 0;
+exports.PERSISTENT = 1;
+
+});

+ 43 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/Metadata.js

@@ -0,0 +1,43 @@
+cordova.define("cordova-plugin-file.Metadata", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * Information about the state of the file or directory
+ *
+ * {Date} modificationTime (readonly)
+ */
+var Metadata = function (metadata) {
+    if (typeof metadata === 'object') {
+        this.modificationTime = new Date(metadata.modificationTime);
+        this.size = metadata.size || 0;
+    } else if (typeof metadata === 'undefined') {
+        this.modificationTime = null;
+        this.size = 0;
+    } else {
+        /* Backwards compatiblity with platforms that only return a timestamp */
+        this.modificationTime = new Date(metadata);
+    }
+};
+
+module.exports = Metadata;
+
+});

+ 70 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/ProgressEvent.js

@@ -0,0 +1,70 @@
+cordova.define("cordova-plugin-file.ProgressEvent", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+// If ProgressEvent exists in global context, use it already, otherwise use our own polyfill
+// Feature test: See if we can instantiate a native ProgressEvent;
+// if so, use that approach,
+// otherwise fill-in with our own implementation.
+//
+// NOTE: right now we always fill in with our own. Down the road would be nice if we can use whatever is native in the webview.
+var ProgressEvent = (function () {
+    /*
+    var createEvent = function(data) {
+        var event = document.createEvent('Events');
+        event.initEvent('ProgressEvent', false, false);
+        if (data) {
+            for (var i in data) {
+                if (data.hasOwnProperty(i)) {
+                    event[i] = data[i];
+                }
+            }
+            if (data.target) {
+                // TODO: cannot call <some_custom_object>.dispatchEvent
+                // need to first figure out how to implement EventTarget
+            }
+        }
+        return event;
+    };
+    try {
+        var ev = createEvent({type:"abort",target:document});
+        return function ProgressEvent(type, data) {
+            data.type = type;
+            return createEvent(data);
+        };
+    } catch(e){
+    */
+    return function ProgressEvent (type, dict) {
+        this.type = type;
+        this.bubbles = false;
+        this.cancelBubble = false;
+        this.cancelable = false;
+        this.lengthComputable = false;
+        this.loaded = dict && dict.loaded ? dict.loaded : 0;
+        this.total = dict && dict.total ? dict.total : 0;
+        this.target = dict && dict.target ? dict.target : null;
+    };
+    // }
+})();
+
+module.exports = ProgressEvent;
+
+});

+ 51 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/android/FileSystem.js

@@ -0,0 +1,51 @@
+cordova.define("cordova-plugin-file.androidFileSystem", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+FILESYSTEM_PROTOCOL = 'cdvfile'; // eslint-disable-line no-undef
+
+module.exports = {
+    __format__: function (fullPath, nativeUrl) {
+        var path;
+        var contentUrlMatch = /^content:\/\//.exec(nativeUrl);
+        if (contentUrlMatch) {
+            // When available, use the path from a native content URL, which was already encoded by Android.
+            // This is necessary because JavaScript's encodeURI() does not encode as many characters as
+            // Android, which can result in permission exceptions when the encoding of a content URI
+            // doesn't match the string for which permission was originally granted.
+            path = nativeUrl.substring(contentUrlMatch[0].length - 1);
+        } else {
+            path = FileSystem.encodeURIPath(fullPath); // eslint-disable-line no-undef
+            if (!/^\//.test(path)) {
+                path = '/' + path;
+            }
+
+            var m = /\?.*/.exec(nativeUrl);
+            if (m) {
+                path += m[0];
+            }
+        }
+
+        return FILESYSTEM_PROTOCOL + '://localhost/' + this.name + path; // eslint-disable-line no-undef
+    }
+};
+
+});

+ 29 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/browser/isChrome.js

@@ -0,0 +1,29 @@
+cordova.define("cordova-plugin-file.isChrome", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+module.exports = function () {
+    // window.webkitRequestFileSystem and window.webkitResolveLocalFileSystemURL are available only in Chrome and
+    // possibly a good flag to indicate that we're running in Chrome
+    return window.webkitRequestFileSystem && window.webkitResolveLocalFileSystemURL;
+};
+
+});

+ 65 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/fileSystemPaths.js

@@ -0,0 +1,65 @@
+cordova.define("cordova-plugin-file.fileSystemPaths", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var exec = require('cordova/exec');
+var channel = require('cordova/channel');
+
+exports.file = {
+    // Read-only directory where the application is installed.
+    applicationDirectory: null,
+    // Root of app's private writable storage
+    applicationStorageDirectory: null,
+    // Where to put app-specific data files.
+    dataDirectory: null,
+    // Cached files that should survive app restarts.
+    // Apps should not rely on the OS to delete files in here.
+    cacheDirectory: null,
+    // Android: the application space on external storage.
+    externalApplicationStorageDirectory: null,
+    // Android: Where to put app-specific data files on external storage.
+    externalDataDirectory: null,
+    // Android: the application cache on external storage.
+    externalCacheDirectory: null,
+    // Android: the external storage (SD card) root.
+    externalRootDirectory: null,
+    // iOS: Temp directory that the OS can clear at will.
+    tempDirectory: null,
+    // iOS: Holds app-specific files that should be synced (e.g. to iCloud).
+    syncedDataDirectory: null,
+    // iOS: Files private to the app, but that are meaningful to other applications (e.g. Office files)
+    documentsDirectory: null,
+    // BlackBerry10: Files globally available to all apps
+    sharedDirectory: null
+};
+
+channel.waitForInitialization('onFileSystemPathsReady');
+channel.onCordovaReady.subscribe(function () {
+    function after (paths) {
+        for (var k in paths) {
+            exports.file[k] = paths[k];
+        }
+        channel.initializationComplete('onFileSystemPathsReady');
+    }
+    exec(after, null, 'File', 'requestAllPaths', []);
+});
+
+});

+ 47 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/fileSystems-roots.js

@@ -0,0 +1,47 @@
+cordova.define("cordova-plugin-file.fileSystems-roots", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+// Map of fsName -> FileSystem.
+var fsMap = null;
+var FileSystem = require('./FileSystem');
+var exec = require('cordova/exec');
+
+// Overridden by Android, BlackBerry 10 and iOS to populate fsMap.
+require('./fileSystems').getFs = function (name, callback) {
+    function success (response) {
+        fsMap = {};
+        for (var i = 0; i < response.length; ++i) {
+            var fsRoot = response[i];
+            var fs = new FileSystem(fsRoot.filesystemName, fsRoot);
+            fsMap[fs.name] = fs;
+        }
+        callback(fsMap[name]);
+    }
+
+    if (fsMap) {
+        callback(fsMap[name]);
+    } else {
+        exec(success, null, 'File', 'requestAllFileSystems', []);
+    }
+};
+
+});

+ 28 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/fileSystems.js

@@ -0,0 +1,28 @@
+cordova.define("cordova-plugin-file.fileSystems", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+// Overridden by Android, BlackBerry 10 and iOS to populate fsMap.
+module.exports.getFs = function (name, callback) {
+    callback(null);
+};
+
+});

+ 84 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/requestFileSystem.js

@@ -0,0 +1,84 @@
+cordova.define("cordova-plugin-file.requestFileSystem", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+(function () {
+    // For browser platform: not all browsers use this file.
+    function checkBrowser () {
+        if (cordova.platformId === 'browser' && require('./isChrome')()) { // eslint-disable-line no-undef
+            module.exports = window.requestFileSystem || window.webkitRequestFileSystem;
+            return true;
+        }
+        return false;
+    }
+    if (checkBrowser()) {
+        return;
+    }
+
+    var argscheck = require('cordova/argscheck');
+    var FileError = require('./FileError');
+    var FileSystem = require('./FileSystem');
+    var exec = require('cordova/exec');
+    var fileSystems = require('./fileSystems');
+
+    /**
+     * Request a file system in which to store application data.
+     * @param type  local file system type
+     * @param size  indicates how much storage space, in bytes, the application expects to need
+     * @param successCallback  invoked with a FileSystem object
+     * @param errorCallback  invoked if error occurs retrieving file system
+     */
+    var requestFileSystem = function (type, size, successCallback, errorCallback) {
+        argscheck.checkArgs('nnFF', 'requestFileSystem', arguments);
+        var fail = function (code) {
+            if (errorCallback) {
+                errorCallback(new FileError(code));
+            }
+        };
+
+        if (type < 0) {
+            fail(FileError.SYNTAX_ERR);
+        } else {
+            // if successful, return a FileSystem object
+            var success = function (file_system) {
+                if (file_system) {
+                    if (successCallback) {
+                        fileSystems.getFs(file_system.name, function (fs) {
+                            // This should happen only on platforms that haven't implemented requestAllFileSystems (windows)
+                            if (!fs) {
+                                fs = new FileSystem(file_system.name, file_system.root);
+                            }
+                            successCallback(fs);
+                        });
+                    }
+                } else {
+                    // no FileSystem object returned
+                    fail(FileError.NOT_FOUND_ERR);
+                }
+            };
+            exec(success, fail, 'File', 'requestFileSystem', [type, size]);
+        }
+    };
+
+    module.exports = requestFileSystem;
+})();
+
+});

+ 94 - 0
platforms/android/platform_www/plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js

@@ -0,0 +1,94 @@
+cordova.define("cordova-plugin-file.resolveLocalFileSystemURI", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+(function () {
+    // For browser platform: not all browsers use overrided `resolveLocalFileSystemURL`.
+    function checkBrowser () {
+        if (cordova.platformId === 'browser' && require('./isChrome')()) { // eslint-disable-line no-undef
+            module.exports.resolveLocalFileSystemURL = window.resolveLocalFileSystemURL || window.webkitResolveLocalFileSystemURL;
+            return true;
+        }
+        return false;
+    }
+    if (checkBrowser()) {
+        return;
+    }
+
+    var argscheck = require('cordova/argscheck');
+    var DirectoryEntry = require('./DirectoryEntry');
+    var FileEntry = require('./FileEntry');
+    var FileError = require('./FileError');
+    var exec = require('cordova/exec');
+    var fileSystems = require('./fileSystems');
+
+    /**
+     * Look up file system Entry referred to by local URI.
+     * @param {DOMString} uri  URI referring to a local file or directory
+     * @param successCallback  invoked with Entry object corresponding to URI
+     * @param errorCallback    invoked if error occurs retrieving file system entry
+     */
+    module.exports.resolveLocalFileSystemURL = module.exports.resolveLocalFileSystemURL || function (uri, successCallback, errorCallback) {
+        argscheck.checkArgs('sFF', 'resolveLocalFileSystemURI', arguments);
+        // error callback
+        var fail = function (error) {
+            if (errorCallback) {
+                errorCallback(new FileError(error));
+            }
+        };
+        // sanity check for 'not:valid:filename' or '/not:valid:filename'
+        // file.spec.12 window.resolveLocalFileSystemURI should error (ENCODING_ERR) when resolving invalid URI with leading /.
+        if (!uri || uri.split(':').length > 2) {
+            setTimeout(function () {
+                fail(FileError.ENCODING_ERR);
+            }, 0);
+            return;
+        }
+        // if successful, return either a file or directory entry
+        var success = function (entry) {
+            if (entry) {
+                if (successCallback) {
+                    // create appropriate Entry object
+                    var fsName = entry.filesystemName || (entry.filesystem && entry.filesystem.name) || (entry.filesystem === window.PERSISTENT ? 'persistent' : 'temporary'); // eslint-disable-line no-undef
+                    fileSystems.getFs(fsName, function (fs) {
+                        // This should happen only on platforms that haven't implemented requestAllFileSystems (windows)
+                        if (!fs) {
+                            fs = new FileSystem(fsName, {name: '', fullPath: '/'}); // eslint-disable-line no-undef
+                        }
+                        var result = (entry.isDirectory) ? new DirectoryEntry(entry.name, entry.fullPath, fs, entry.nativeURL) : new FileEntry(entry.name, entry.fullPath, fs, entry.nativeURL);
+                        successCallback(result);
+                    });
+                }
+            } else {
+                // no Entry object returned
+                fail(FileError.NOT_FOUND_ERR);
+            }
+        };
+
+        exec(success, fail, 'File', 'resolveLocalFileSystemURI', [uri]);
+    };
+
+    module.exports.resolveLocalFileSystemURI = function () {
+        console.log('resolveLocalFileSystemURI is deprecated. Please call resolveLocalFileSystemURL instead.');
+        module.exports.resolveLocalFileSystemURL.apply(this, arguments);
+    };
+})();
+
+});

+ 18 - 2
platforms/android/res/xml/config.xml

@@ -26,11 +26,19 @@
     <feature name="Wechat">
         <param name="android-package" value="xu.li.cordova.wechat.Wechat" />
     </feature>
+    <feature name="File">
+        <param name="android-package" value="org.apache.cordova.file.FileUtils" />
+        <param name="onload" value="true" />
+    </feature>
+    <allow-navigation href="cdvfile:*" />
+    <feature name="RemoteInjection">
+        <param name="android-package" value="com.truckmovers.cordova.RemoteInjectionPlugin" />
+        <param name="onload" value="true" />
+    </feature>
     <name>雷同学</name>
     <description>        A sample Apache Cordova application that responds to the deviceready event.    </description>
     <author email="dev@cordova.apache.org" href="http://cordova.io">        Apache Cordova Team    </author>
-    <content src="leitongxue.html#/home/recommend" />
-    <access origin="*" />
+    <content src="leitongxue.html/#/home/recommend" />
     <allow-intent href="http://*/*" />
     <allow-intent href="https://*/*" />
     <allow-intent href="tel:*" />
@@ -38,6 +46,14 @@
     <allow-intent href="mailto:*" />
     <allow-intent href="geo:*" />
     <allow-intent href="market:*" />
+    <access origin="*" />
+    <allow-navigation href="*" />
+    <allow-navigation href="http://*/*" />
+    <allow-navigation href="https://*/*" />
+    <allow-navigation href="tel:*" />
+    <allow-navigation href="sms:*" />
+    <allow-navigation href="mailto:*" />
+    <allow-navigation href="geo:*" />
     <config-file parent="/*" target="AndroidManifest.xml">
         <uses-permission android:name="android.permission.INTERNET" />
     </config-file>

+ 320 - 0
platforms/android/src/com/truckmovers/cordova/RemoteInjectionPlugin.java

@@ -0,0 +1,320 @@
+package com.truckmovers.cordova;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.res.AssetManager;
+import android.util.Base64;
+
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaWebViewEngine;
+import org.apache.cordova.LOG;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.regex.Pattern;
+
+public class RemoteInjectionPlugin extends CordovaPlugin {
+    private static String TAG = "RemoteInjectionPlugin";
+    private static Pattern REMOTE_URL_REGEX = Pattern.compile("^http(s)?://.*");
+
+
+    // List of files to inject before injecting Cordova.
+    private final ArrayList<String> preInjectionFileNames = new ArrayList<String>();
+    private int promptInterval;  // Delay before prompting user to retry in seconds
+
+    private RequestLifecycle lifecycle;
+
+    protected void pluginInitialize() {
+        String pref = webView.getPreferences().getString("CRIInjectFirstFiles", "");
+        for (String path: pref.split(",")) {
+            preInjectionFileNames.add(path.trim());
+        }
+        promptInterval = webView.getPreferences().getInteger("CRIPageLoadPromptInterval", 10);
+
+        final Activity activity = super.cordova.getActivity();
+        final CordovaWebViewEngine engine = super.webView.getEngine();
+        lifecycle = new RequestLifecycle(activity, engine, promptInterval);
+    }
+
+    private void onMessageTypeFailure(String messageId, Object data) {
+        LOG.e(TAG, messageId + " received a data instance that is not an expected type:" + data.getClass().getName());
+    }
+
+    @Override
+    public void onReset() {
+        super.onReset();
+
+        lifecycle.requestStopped();
+    }
+
+    @Override
+    public Object onMessage(String id, Object data) {
+        if (id.equals("onReceivedError")) {
+            // Data is a JSONObject instance with the following keys:
+            // * errorCode
+            // * description
+            // * url
+
+            if (data instanceof JSONObject) {
+                JSONObject json = (JSONObject) data;
+
+                try {
+                    if (isRemote(json.getString("url"))) {
+                        lifecycle.requestStopped();
+                    }
+                } catch (JSONException e) {
+                    LOG.e(TAG, "Unexpected JSON in onReceiveError", e);
+                }
+            } else {
+                onMessageTypeFailure(id, data);
+            }
+        } else if (id.equals("onPageFinished")) {
+            if (data instanceof String) {
+                String url = (String) data;
+                if (isRemote(url)) {
+                    injectCordova();
+                    lifecycle.requestStopped();
+                }
+            } else {
+                onMessageTypeFailure(id, data);
+            }
+        } else if (id.equals("onPageStarted")) {
+            if (data instanceof String) {
+                String url = (String) data;
+
+                if (isRemote(url)) {
+                    lifecycle.requestStarted(url);
+                }
+            } else {
+                onMessageTypeFailure(id, data);
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * @param url
+     * @return true if the URL over HTTP or HTTPS
+     */
+    private boolean isRemote(String url) {
+        return REMOTE_URL_REGEX.matcher((String) url).matches();
+    }
+
+    private void injectCordova() {
+        List<String> jsPaths = new ArrayList<String>();
+        for (String path: preInjectionFileNames) {
+            jsPaths.add(path);
+        }
+
+        jsPaths.add("www/cordova.js");
+
+        // We load the plugin code manually rather than allow cordova to load them (via
+        // cordova_plugins.js).  The reason for this is the WebView will attempt to load the
+        // file in the origin of the page (e.g. https://truckmover.com/plugins/plugin/plugin.js).
+        // By loading them first cordova will skip its loading process altogether.
+        jsPaths.addAll(jsPathsToInject(cordova.getActivity().getResources().getAssets(), "www/plugins"));
+
+        // Initialize the cordova plugin registry.
+        jsPaths.add("www/cordova_plugins.js");
+
+        // The way that I figured out to inject for android is to inject it as a script
+        // tag with the full JS encoded as a data URI
+        // (https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs).  The script tag
+        // is appended to the DOM and executed via a javascript URL (e.g. javascript:doJsStuff()).
+        StringBuilder jsToInject = new StringBuilder();
+        for (String path: jsPaths) {
+            jsToInject.append(readFile(cordova.getActivity().getResources().getAssets(), path));
+        }
+        String jsUrl = "javascript:var script = document.createElement('script');";
+        jsUrl += "script.src=\"data:text/javascript;charset=utf-8;base64,";
+
+        jsUrl += Base64.encodeToString(jsToInject.toString().getBytes(), Base64.NO_WRAP);
+        jsUrl += "\";";
+
+        jsUrl += "document.getElementsByTagName('head')[0].appendChild(script);";
+
+        webView.getEngine().loadUrl(jsUrl, false);
+    }
+
+    private String readFile(AssetManager assets, String filePath) {
+        StringBuilder out = new StringBuilder();
+        BufferedReader in = null;
+        try {
+            InputStream stream = assets.open(filePath);
+            in = new BufferedReader(new InputStreamReader(stream));
+            String str = "";
+
+            while ((str = in.readLine()) != null) {
+                out.append(str);
+                out.append("\n");
+            }
+        } catch (MalformedURLException e) {
+        } catch (IOException e) {
+        } finally {
+            if (in != null) {
+                try {
+                    in.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+        return out.toString();
+    }
+
+    /**
+     * Searches the provided path for javascript files recursively.
+     *
+     * @param assets
+     * @param path start path
+     * @return found JS files
+     */
+    private List<String> jsPathsToInject(AssetManager assets, String path){
+        List jsPaths = new ArrayList<String>();
+
+        try {
+            for (String filePath: assets.list(path)) {
+                String fullPath = path + File.separator + filePath;
+
+                if (fullPath.endsWith(".js")) {
+                    jsPaths.add(fullPath);
+                } else {
+                    List<String> childPaths = jsPathsToInject(assets, fullPath);
+                    if (!childPaths.isEmpty()) {
+                        jsPaths.addAll(childPaths);
+                    }
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        return jsPaths;
+    }
+
+    private static class RequestLifecycle {
+        private final Activity activity;
+        private final CordovaWebViewEngine engine;
+        private UserPromptTask task;
+        private final int promptInterval;
+
+        RequestLifecycle(Activity activity, CordovaWebViewEngine engine, int promptInterval) {
+            this.activity = activity;
+            this.engine = engine;
+            this.promptInterval = promptInterval;
+        }
+
+        boolean isLoading() {
+            return task != null;
+        }
+
+        void requestStopped() {
+            stopTask();
+        }
+
+        void requestStarted(final String url) {
+            startTask(url);
+        }
+
+        private synchronized void stopTask() {
+            if (task != null) {
+                task.cancel();
+                task = null;
+            }
+        }
+
+        private synchronized void startTask(final String url) {
+            if (task != null) {
+                task.cancel();
+            }
+
+            if (promptInterval > 0 ) {
+                task = new UserPromptTask(this, activity, engine, url);
+                new Timer().schedule(task, promptInterval * 1000);
+            }
+        }
+    }
+
+    /**
+     * Prompt the user asking if they want to wait on the current request or retry.
+     */
+    static class UserPromptTask extends TimerTask {
+        private final RequestLifecycle lifecycle;
+        private final Activity activity;
+        private final CordovaWebViewEngine engine;
+        final String url;
+
+        AlertDialog alertDialog;
+
+        UserPromptTask(RequestLifecycle lifecycle, Activity activity, CordovaWebViewEngine engine, String url) {
+            this.lifecycle = lifecycle;
+            this.activity = activity;
+            this.engine = engine;
+            this.url = url;
+        }
+
+        @Override
+        public boolean cancel() {
+            boolean result = super.cancel();
+            cleanup();
+
+            return result;
+        }
+
+        private void cleanup() {
+            if (alertDialog != null) {
+                alertDialog.dismiss();
+                alertDialog = null;
+            }
+        }
+
+        @Override
+        public void run() {
+            if (lifecycle.isLoading()) {
+                // Prompts the user giving them the choice to wait on the current request or retry.
+                lifecycle.activity.runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+                        builder.setMessage("The server is taking longer than expected to respond.")
+                                .setOnDismissListener(new DialogInterface.OnDismissListener() {
+                                    @Override
+                                    public void onDismiss(DialogInterface dialog) {
+                                        UserPromptTask.this.cleanup();
+                                    }
+                                })
+                                .setPositiveButton("Retry", new DialogInterface.OnClickListener() {
+                                    @Override
+                                    public void onClick(DialogInterface dialog, int id) {
+                                        // Obviously only works for GETs but good enough.
+                                        engine.loadUrl(engine.getUrl(), false);
+                                    }
+                                })
+                                .setNegativeButton("Wait", new DialogInterface.OnClickListener() {
+                                    @Override
+                                    public void onClick(DialogInterface dialog, int id) {
+                                        lifecycle.startTask(url);
+                                    }
+                                });
+                        AlertDialog dialog = UserPromptTask.this.alertDialog = builder.create();
+                        dialog.show();
+                    }
+                });
+            } else {
+                lifecycle.stopTask();
+            }
+        }
+    }
+}

+ 294 - 0
platforms/android/src/org/apache/cordova/file/AssetFilesystem.java

@@ -0,0 +1,294 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+ */
+package org.apache.cordova.file;
+
+import android.content.res.AssetManager;
+import android.net.Uri;
+
+import org.apache.cordova.CordovaResourceApi;
+import org.apache.cordova.LOG;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AssetFilesystem extends Filesystem {
+
+    private final AssetManager assetManager;
+
+    // A custom gradle hook creates the cdvasset.manifest file, which speeds up asset listing a tonne.
+    // See: http://stackoverflow.com/questions/16911558/android-assetmanager-list-incredibly-slow
+    private static Object listCacheLock = new Object();
+    private static boolean listCacheFromFile;
+    private static Map<String, String[]> listCache;
+    private static Map<String, Long> lengthCache;
+
+    private static final String LOG_TAG = "AssetFilesystem";
+
+    private void lazyInitCaches() {
+        synchronized (listCacheLock) {
+            if (listCache == null) {
+                ObjectInputStream ois = null;
+                try {
+                    ois = new ObjectInputStream(assetManager.open("cdvasset.manifest"));
+                    listCache = (Map<String, String[]>) ois.readObject();
+                    lengthCache = (Map<String, Long>) ois.readObject();
+                    listCacheFromFile = true;
+                } catch (ClassNotFoundException e) {
+                    e.printStackTrace();
+                } catch (IOException e) {
+                    // Asset manifest won't exist if the gradle hook isn't set up correctly.
+                } finally {
+                    if (ois != null) {
+                        try {
+                            ois.close();
+                        } catch (IOException e) {
+                            LOG.d(LOG_TAG, e.getLocalizedMessage());
+                        }
+                    }
+                }
+                if (listCache == null) {
+                    LOG.w("AssetFilesystem", "Asset manifest not found. Recursive copies and directory listing will be slow.");
+                    listCache = new HashMap<String, String[]>();
+                }
+            }
+        }
+    }
+
+    private String[] listAssets(String assetPath) throws IOException {
+        if (assetPath.startsWith("/")) {
+            assetPath = assetPath.substring(1);
+        }
+        if (assetPath.endsWith("/")) {
+            assetPath = assetPath.substring(0, assetPath.length() - 1);
+        }
+        lazyInitCaches();
+        String[] ret = listCache.get(assetPath);
+        if (ret == null) {
+            if (listCacheFromFile) {
+                ret = new String[0];
+            } else {
+                ret = assetManager.list(assetPath);
+                listCache.put(assetPath, ret);
+            }
+        }
+        return ret;
+    }
+
+    private long getAssetSize(String assetPath) throws FileNotFoundException {
+        if (assetPath.startsWith("/")) {
+            assetPath = assetPath.substring(1);
+        }
+        lazyInitCaches();
+        if (lengthCache != null) {
+            Long ret = lengthCache.get(assetPath);
+            if (ret == null) {
+                throw new FileNotFoundException("Asset not found: " + assetPath);
+            }
+            return ret;
+        }
+        CordovaResourceApi.OpenForReadResult offr = null;
+        try {
+            offr = resourceApi.openForRead(nativeUriForFullPath(assetPath));
+            long length = offr.length;
+            if (length < 0) {
+                // available() doesn't always yield the file size, but for assets it does.
+                length = offr.inputStream.available();
+            }
+            return length;
+        } catch (IOException e) {
+            FileNotFoundException fnfe = new FileNotFoundException("File not found: " + assetPath);
+            fnfe.initCause(e);
+            throw fnfe;
+        } finally {
+            if (offr != null) {
+                try {
+                    offr.inputStream.close();
+                } catch (IOException e) {
+                    LOG.d(LOG_TAG, e.getLocalizedMessage());
+                }
+            }
+        }
+    }
+
+    public AssetFilesystem(AssetManager assetManager, CordovaResourceApi resourceApi) {
+        super(Uri.parse("file:///android_asset/"), "assets", resourceApi);
+        this.assetManager = assetManager;
+	}
+
+    @Override
+    public Uri toNativeUri(LocalFilesystemURL inputURL) {
+        return nativeUriForFullPath(inputURL.path);
+    }
+
+    @Override
+    public LocalFilesystemURL toLocalUri(Uri inputURL) {
+        if (!"file".equals(inputURL.getScheme())) {
+            return null;
+        }
+        File f = new File(inputURL.getPath());
+        // Removes and duplicate /s (e.g. file:///a//b/c)
+        Uri resolvedUri = Uri.fromFile(f);
+        String rootUriNoTrailingSlash = rootUri.getEncodedPath();
+        rootUriNoTrailingSlash = rootUriNoTrailingSlash.substring(0, rootUriNoTrailingSlash.length() - 1);
+        if (!resolvedUri.getEncodedPath().startsWith(rootUriNoTrailingSlash)) {
+            return null;
+        }
+        String subPath = resolvedUri.getEncodedPath().substring(rootUriNoTrailingSlash.length());
+        // Strip leading slash
+        if (!subPath.isEmpty()) {
+            subPath = subPath.substring(1);
+        }
+        Uri.Builder b = new Uri.Builder()
+            .scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
+            .authority("localhost")
+            .path(name);
+        if (!subPath.isEmpty()) {
+            b.appendEncodedPath(subPath);
+        }
+        if (isDirectory(subPath) || inputURL.getPath().endsWith("/")) {
+            // Add trailing / for directories.
+            b.appendEncodedPath("");
+        }
+        return LocalFilesystemURL.parse(b.build());
+    }
+
+    private boolean isDirectory(String assetPath) {
+        try {
+            return listAssets(assetPath).length != 0;
+        } catch (IOException e) {
+            return false;
+        }
+    }
+
+    @Override
+    public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
+        String pathNoSlashes = inputURL.path.substring(1);
+        if (pathNoSlashes.endsWith("/")) {
+            pathNoSlashes = pathNoSlashes.substring(0, pathNoSlashes.length() - 1);
+        }
+
+        String[] files;
+        try {
+            files = listAssets(pathNoSlashes);
+        } catch (IOException e) {
+            FileNotFoundException fnfe = new FileNotFoundException();
+            fnfe.initCause(e);
+            throw fnfe;
+        }
+
+        LocalFilesystemURL[] entries = new LocalFilesystemURL[files.length];
+        for (int i = 0; i < files.length; ++i) {
+            entries[i] = localUrlforFullPath(new File(inputURL.path, files[i]).getPath());
+        }
+        return entries;
+	}
+
+    @Override
+    public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
+                                         String path, JSONObject options, boolean directory)
+            throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+        if (options != null && options.optBoolean("create")) {
+            throw new UnsupportedOperationException("Assets are read-only");
+        }
+
+        // Check whether the supplied path is absolute or relative
+        if (directory && !path.endsWith("/")) {
+            path += "/";
+        }
+
+        LocalFilesystemURL requestedURL;
+        if (path.startsWith("/")) {
+            requestedURL = localUrlforFullPath(normalizePath(path));
+        } else {
+            requestedURL = localUrlforFullPath(normalizePath(inputURL.path + "/" + path));
+        }
+
+        // Throws a FileNotFoundException if it doesn't exist.
+        getFileMetadataForLocalURL(requestedURL);
+
+        boolean isDir = isDirectory(requestedURL.path);
+        if (directory && !isDir) {
+            throw new TypeMismatchException("path doesn't exist or is file");
+        } else if (!directory && isDir) {
+            throw new TypeMismatchException("path doesn't exist or is directory");
+        }
+
+        // Return the directory
+        return makeEntryForURL(requestedURL);
+    }
+
+    @Override
+	public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
+        JSONObject metadata = new JSONObject();
+        long size = inputURL.isDirectory ? 0 : getAssetSize(inputURL.path);
+        try {
+        	metadata.put("size", size);
+        	metadata.put("type", inputURL.isDirectory ? "text/directory" : resourceApi.getMimeType(toNativeUri(inputURL)));
+        	metadata.put("name", new File(inputURL.path).getName());
+        	metadata.put("fullPath", inputURL.path);
+        	metadata.put("lastModifiedDate", 0);
+        } catch (JSONException e) {
+            return null;
+        }
+        return metadata;
+	}
+
+	@Override
+	public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
+		return false;
+	}
+
+    @Override
+    long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset, boolean isBinary) throws NoModificationAllowedException, IOException {
+        throw new NoModificationAllowedException("Assets are read-only");
+    }
+
+    @Override
+    long truncateFileAtURL(LocalFilesystemURL inputURL, long size) throws IOException, NoModificationAllowedException {
+        throw new NoModificationAllowedException("Assets are read-only");
+    }
+
+    @Override
+    String filesystemPathForURL(LocalFilesystemURL url) {
+        return new File(rootUri.getPath(), url.path).toString();
+    }
+
+    @Override
+    LocalFilesystemURL URLforFilesystemPath(String path) {
+        return null;
+    }
+
+    @Override
+    boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException {
+        throw new NoModificationAllowedException("Assets are read-only");
+    }
+
+    @Override
+    boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws NoModificationAllowedException {
+        throw new NoModificationAllowedException("Assets are read-only");
+    }
+
+}

+ 223 - 0
platforms/android/src/org/apache/cordova/file/ContentFilesystem.java

@@ -0,0 +1,223 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+ */
+package org.apache.cordova.file;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.provider.OpenableColumns;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import org.apache.cordova.CordovaResourceApi;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class ContentFilesystem extends Filesystem {
+
+    private final Context context;
+
+	public ContentFilesystem(Context context, CordovaResourceApi resourceApi) {
+		super(Uri.parse("content://"), "content", resourceApi);
+        this.context = context;
+	}
+
+    @Override
+    public Uri toNativeUri(LocalFilesystemURL inputURL) {
+        String authorityAndPath = inputURL.uri.getEncodedPath().substring(this.name.length() + 2);
+        if (authorityAndPath.length() < 2) {
+            return null;
+        }
+        String ret = "content://" + authorityAndPath;
+        String query = inputURL.uri.getEncodedQuery();
+        if (query != null) {
+            ret += '?' + query;
+        }
+        String frag = inputURL.uri.getEncodedFragment();
+        if (frag != null) {
+            ret += '#' + frag;
+        }
+        return Uri.parse(ret);
+    }
+
+    @Override
+    public LocalFilesystemURL toLocalUri(Uri inputURL) {
+        if (!"content".equals(inputURL.getScheme())) {
+            return null;
+        }
+        String subPath = inputURL.getEncodedPath();
+        if (subPath.length() > 0) {
+            subPath = subPath.substring(1);
+        }
+        Uri.Builder b = new Uri.Builder()
+            .scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
+            .authority("localhost")
+            .path(name)
+            .appendPath(inputURL.getAuthority());
+        if (subPath.length() > 0) {
+            b.appendEncodedPath(subPath);
+        }
+        Uri localUri = b.encodedQuery(inputURL.getEncodedQuery())
+            .encodedFragment(inputURL.getEncodedFragment())
+            .build();
+        return LocalFilesystemURL.parse(localUri);
+    }
+
+    @Override
+	public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
+			String fileName, JSONObject options, boolean directory) throws IOException, TypeMismatchException, JSONException {
+        throw new UnsupportedOperationException("getFile() not supported for content:. Use resolveLocalFileSystemURL instead.");
+	}
+
+	@Override
+	public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL)
+			throws NoModificationAllowedException {
+        Uri contentUri = toNativeUri(inputURL);
+		try {
+            context.getContentResolver().delete(contentUri, null, null);
+		} catch (UnsupportedOperationException t) {
+			// Was seeing this on the File mobile-spec tests on 4.0.3 x86 emulator.
+			// The ContentResolver applies only when the file was registered in the
+			// first case, which is generally only the case with images.
+            NoModificationAllowedException nmae = new NoModificationAllowedException("Deleting not supported for content uri: " + contentUri);
+            nmae.initCause(t);
+            throw nmae;
+		}
+        return true;
+	}
+
+	@Override
+	public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL)
+			throws NoModificationAllowedException {
+		throw new NoModificationAllowedException("Cannot remove content url");
+	}
+
+    @Override
+    public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
+        throw new UnsupportedOperationException("readEntriesAtLocalURL() not supported for content:. Use resolveLocalFileSystemURL instead.");
+    }
+
+	@Override
+	public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
+        long size = -1;
+        long lastModified = 0;
+        Uri nativeUri = toNativeUri(inputURL);
+        String mimeType = resourceApi.getMimeType(nativeUri);
+        Cursor cursor = openCursorForURL(nativeUri);
+        try {
+            if (cursor != null && cursor.moveToFirst()) {
+                Long sizeForCursor = resourceSizeForCursor(cursor);
+                if (sizeForCursor != null) {
+                    size = sizeForCursor.longValue();
+                }
+                Long modified = lastModifiedDateForCursor(cursor);
+                if (modified != null)
+                    lastModified = modified.longValue();
+            } else {
+                // Some content providers don't support cursors at all!
+                CordovaResourceApi.OpenForReadResult offr = resourceApi.openForRead(nativeUri);
+                size = offr.length;
+            }
+        } catch (IOException e) {
+            FileNotFoundException fnfe = new FileNotFoundException();
+            fnfe.initCause(e);
+            throw fnfe;
+        } finally {
+        	if (cursor != null)
+        		cursor.close();
+        }
+
+        JSONObject metadata = new JSONObject();
+        try {
+        	metadata.put("size", size);
+        	metadata.put("type", mimeType);
+        	metadata.put("name", name);
+        	metadata.put("fullPath", inputURL.path);
+        	metadata.put("lastModifiedDate", lastModified);
+        } catch (JSONException e) {
+        	return null;
+        }
+        return metadata;
+	}
+
+	@Override
+	public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
+			int offset, boolean isBinary) throws NoModificationAllowedException {
+        throw new NoModificationAllowedException("Couldn't write to file given its content URI");
+    }
+	@Override
+	public long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
+			throws NoModificationAllowedException {
+        throw new NoModificationAllowedException("Couldn't truncate file given its content URI");
+	}
+
+	protected Cursor openCursorForURL(Uri nativeUri) {
+        ContentResolver contentResolver = context.getContentResolver();
+        try {
+            return contentResolver.query(nativeUri, null, null, null, null);
+        } catch (UnsupportedOperationException e) {
+            return null;
+        }
+	}
+
+	private Long resourceSizeForCursor(Cursor cursor) {
+        int columnIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
+        if (columnIndex != -1) {
+            String sizeStr = cursor.getString(columnIndex);
+            if (sizeStr != null) {
+            	return Long.parseLong(sizeStr);
+            }
+        }
+        return null;
+	}
+	
+	protected Long lastModifiedDateForCursor(Cursor cursor) {
+        int columnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED);
+        if (columnIndex == -1) {
+            columnIndex = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED);
+        }
+        if (columnIndex != -1) {
+            String dateStr = cursor.getString(columnIndex);
+            if (dateStr != null) {
+                return Long.parseLong(dateStr);
+            }
+        }
+        return null;
+	}
+
+    @Override
+    public String filesystemPathForURL(LocalFilesystemURL url) {
+        File f = resourceApi.mapUriToFile(toNativeUri(url));
+        return f == null ? null : f.getAbsolutePath();
+    }
+
+	@Override
+	public LocalFilesystemURL URLforFilesystemPath(String path) {
+		// Returns null as we don't support reverse mapping back to content:// URLs
+		return null;
+	}
+
+	@Override
+	public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
+		return true;
+	}
+}

+ 134 - 0
platforms/android/src/org/apache/cordova/file/DirectoryManager.java

@@ -0,0 +1,134 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+package org.apache.cordova.file;
+
+import android.os.Environment;
+import android.os.StatFs;
+
+import java.io.File;
+
+/**
+ * This class provides file directory utilities.
+ * All file operations are performed on the SD card.
+ *
+ * It is used by the FileUtils class.
+ */
+public class DirectoryManager {
+
+    @SuppressWarnings("unused")
+    private static final String LOG_TAG = "DirectoryManager";
+
+    /**
+     * Determine if a file or directory exists.
+     * @param name				The name of the file to check.
+     * @return					T=exists, F=not found
+     */
+    public static boolean testFileExists(String name) {
+        boolean status;
+
+        // If SD card exists
+        if ((testSaveLocationExists()) && (!name.equals(""))) {
+            File path = Environment.getExternalStorageDirectory();
+            File newPath = constructFilePaths(path.toString(), name);
+            status = newPath.exists();
+        }
+        // If no SD card
+        else {
+            status = false;
+        }
+        return status;
+    }
+
+    /**
+     * Get the free space in external storage
+     *
+     * @return 		Size in KB or -1 if not available
+     */
+    public static long getFreeExternalStorageSpace() {
+        String status = Environment.getExternalStorageState();
+        long freeSpaceInBytes = 0;
+
+        // Check if external storage exists
+        if (status.equals(Environment.MEDIA_MOUNTED)) {
+            freeSpaceInBytes = getFreeSpaceInBytes(Environment.getExternalStorageDirectory().getPath());
+        } else {
+            // If no external storage then return -1
+            return -1;
+        }
+
+        return freeSpaceInBytes / 1024;
+    }
+
+    /**
+     * Given a path return the number of free bytes in the filesystem containing the path.
+     *
+     * @param path to the file system
+     * @return free space in bytes
+     */
+    public static long getFreeSpaceInBytes(String path) {
+        try {
+            StatFs stat = new StatFs(path);
+            long blockSize = stat.getBlockSize();
+            long availableBlocks = stat.getAvailableBlocks();
+            return availableBlocks * blockSize;
+        } catch (IllegalArgumentException e) {
+            // The path was invalid. Just return 0 free bytes.
+            return 0;
+        }
+    }
+
+    /**
+     * Determine if SD card exists.
+     *
+     * @return				T=exists, F=not found
+     */
+    public static boolean testSaveLocationExists() {
+        String sDCardStatus = Environment.getExternalStorageState();
+        boolean status;
+
+        // If SD card is mounted
+        if (sDCardStatus.equals(Environment.MEDIA_MOUNTED)) {
+            status = true;
+        }
+
+        // If no SD card
+        else {
+            status = false;
+        }
+        return status;
+    }
+
+    /**
+     * Create a new file object from two file paths.
+     *
+     * @param file1			Base file path
+     * @param file2			Remaining file path
+     * @return				File object
+     */
+    private static File constructFilePaths (String file1, String file2) {
+        File newPath;
+        if (file2.startsWith(file1)) {
+            newPath = new File(file2);
+        }
+        else {
+            newPath = new File(file1 + "/" + file2);
+        }
+        return newPath;
+    }
+}

+ 29 - 0
platforms/android/src/org/apache/cordova/file/EncodingException.java

@@ -0,0 +1,29 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+package org.apache.cordova.file;
+
+@SuppressWarnings("serial")
+public class EncodingException extends Exception {
+
+    public EncodingException(String message) {
+        super(message);
+    }
+
+}

+ 29 - 0
platforms/android/src/org/apache/cordova/file/FileExistsException.java

@@ -0,0 +1,29 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+package org.apache.cordova.file;
+
+@SuppressWarnings("serial")
+public class FileExistsException extends Exception {
+
+    public FileExistsException(String msg) {
+        super(msg);
+    }
+
+}

+ 1225 - 0
platforms/android/src/org/apache/cordova/file/FileUtils.java

@@ -0,0 +1,1225 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+ */
+package org.apache.cordova.file;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.util.Base64;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.LOG;
+import org.apache.cordova.PermissionHelper;
+import org.apache.cordova.PluginResult;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.security.Permission;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+
+/**
+ * This class provides file and directory services to JavaScript.
+ */
+public class FileUtils extends CordovaPlugin {
+    private static final String LOG_TAG = "FileUtils";
+
+    public static int NOT_FOUND_ERR = 1;
+    public static int SECURITY_ERR = 2;
+    public static int ABORT_ERR = 3;
+
+    public static int NOT_READABLE_ERR = 4;
+    public static int ENCODING_ERR = 5;
+    public static int NO_MODIFICATION_ALLOWED_ERR = 6;
+    public static int INVALID_STATE_ERR = 7;
+    public static int SYNTAX_ERR = 8;
+    public static int INVALID_MODIFICATION_ERR = 9;
+    public static int QUOTA_EXCEEDED_ERR = 10;
+    public static int TYPE_MISMATCH_ERR = 11;
+    public static int PATH_EXISTS_ERR = 12;
+
+    /*
+     * Permission callback codes
+     */
+
+    public static final int ACTION_GET_FILE = 0;
+    public static final int ACTION_WRITE = 1;
+    public static final int ACTION_GET_DIRECTORY = 2;
+
+    public static final int WRITE = 3;
+    public static final int READ = 4;
+
+    public static int UNKNOWN_ERR = 1000;
+
+    private boolean configured = false;
+
+    private PendingRequests pendingRequests;
+
+
+
+    /*
+     * We need both read and write when accessing the storage, I think.
+     */
+
+    private String [] permissions = {
+            Manifest.permission.READ_EXTERNAL_STORAGE,
+            Manifest.permission.WRITE_EXTERNAL_STORAGE };
+
+    // This field exists only to support getEntry, below, which has been deprecated
+    private static FileUtils filePlugin;
+
+    private interface FileOp {
+        void run(JSONArray args) throws Exception;
+    }
+
+    private ArrayList<Filesystem> filesystems;
+
+    public void registerFilesystem(Filesystem fs) {
+    	if (fs != null && filesystemForName(fs.name)== null) {
+    		this.filesystems.add(fs);
+    	}
+    }
+
+    private Filesystem filesystemForName(String name) {
+    	for (Filesystem fs:filesystems) {
+    		if (fs != null && fs.name != null && fs.name.equals(name)) {
+    			return fs;
+    		}
+    	}
+    	return null;
+    }
+
+    protected String[] getExtraFileSystemsPreference(Activity activity) {
+        String fileSystemsStr = preferences.getString("androidextrafilesystems", "files,files-external,documents,sdcard,cache,cache-external,assets,root");
+        return fileSystemsStr.split(",");
+    }
+
+    protected void registerExtraFileSystems(String[] filesystems, HashMap<String, String> availableFileSystems) {
+        HashSet<String> installedFileSystems = new HashSet<String>();
+
+        /* Register filesystems in order */
+        for (String fsName : filesystems) {
+            if (!installedFileSystems.contains(fsName)) {
+                String fsRoot = availableFileSystems.get(fsName);
+                if (fsRoot != null) {
+                    File newRoot = new File(fsRoot);
+                    if (newRoot.mkdirs() || newRoot.isDirectory()) {
+                        registerFilesystem(new LocalFilesystem(fsName, webView.getContext(), webView.getResourceApi(), newRoot));
+                        installedFileSystems.add(fsName);
+                    } else {
+                       LOG.d(LOG_TAG, "Unable to create root dir for filesystem \"" + fsName + "\", skipping");
+                    }
+                } else {
+                    LOG.d(LOG_TAG, "Unrecognized extra filesystem identifier: " + fsName);
+                }
+            }
+        }
+    }
+
+    protected HashMap<String, String> getAvailableFileSystems(Activity activity) {
+        Context context = activity.getApplicationContext();
+        HashMap<String, String> availableFileSystems = new HashMap<String,String>();
+
+        availableFileSystems.put("files", context.getFilesDir().getAbsolutePath());
+        availableFileSystems.put("documents", new File(context.getFilesDir(), "Documents").getAbsolutePath());
+        availableFileSystems.put("cache", context.getCacheDir().getAbsolutePath());
+        availableFileSystems.put("root", "/");
+        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+          try {
+            availableFileSystems.put("files-external", context.getExternalFilesDir(null).getAbsolutePath());
+            availableFileSystems.put("sdcard", Environment.getExternalStorageDirectory().getAbsolutePath());
+            availableFileSystems.put("cache-external", context.getExternalCacheDir().getAbsolutePath());
+          }
+          catch(NullPointerException e) {
+              LOG.d(LOG_TAG, "External storage unavailable, check to see if USB Mass Storage Mode is on");
+          }
+        }
+
+        return availableFileSystems;
+    }
+
+    @Override
+    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
+    	super.initialize(cordova, webView);
+    	this.filesystems = new ArrayList<Filesystem>();
+        this.pendingRequests = new PendingRequests();
+
+    	String tempRoot = null;
+    	String persistentRoot = null;
+
+    	Activity activity = cordova.getActivity();
+    	String packageName = activity.getPackageName();
+
+        String location = preferences.getString("androidpersistentfilelocation", "internal");
+
+    	tempRoot = activity.getCacheDir().getAbsolutePath();
+    	if ("internal".equalsIgnoreCase(location)) {
+    		persistentRoot = activity.getFilesDir().getAbsolutePath() + "/files/";
+    		this.configured = true;
+    	} else if ("compatibility".equalsIgnoreCase(location)) {
+    		/*
+    		 *  Fall-back to compatibility mode -- this is the logic implemented in
+    		 *  earlier versions of this plugin, and should be maintained here so
+    		 *  that apps which were originally deployed with older versions of the
+    		 *  plugin can continue to provide access to files stored under those
+    		 *  versions.
+    		 */
+    		if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+    			persistentRoot = Environment.getExternalStorageDirectory().getAbsolutePath();
+    			tempRoot = Environment.getExternalStorageDirectory().getAbsolutePath() +
+    					"/Android/data/" + packageName + "/cache/";
+    		} else {
+    			persistentRoot = "/data/data/" + packageName;
+    		}
+    		this.configured = true;
+    	}
+
+    	if (this.configured) {
+			// Create the directories if they don't exist.
+			File tmpRootFile = new File(tempRoot);
+            File persistentRootFile = new File(persistentRoot);
+            tmpRootFile.mkdirs();
+            persistentRootFile.mkdirs();
+
+    		// Register initial filesystems
+    		// Note: The temporary and persistent filesystems need to be the first two
+    		// registered, so that they will match window.TEMPORARY and window.PERSISTENT,
+    		// per spec.
+    		this.registerFilesystem(new LocalFilesystem("temporary", webView.getContext(), webView.getResourceApi(), tmpRootFile));
+    		this.registerFilesystem(new LocalFilesystem("persistent", webView.getContext(), webView.getResourceApi(), persistentRootFile));
+    		this.registerFilesystem(new ContentFilesystem(webView.getContext(), webView.getResourceApi()));
+            this.registerFilesystem(new AssetFilesystem(webView.getContext().getAssets(), webView.getResourceApi()));
+
+            registerExtraFileSystems(getExtraFileSystemsPreference(activity), getAvailableFileSystems(activity));
+
+    		// Initialize static plugin reference for deprecated getEntry method
+    		if (filePlugin == null) {
+    			FileUtils.filePlugin = this;
+    		}
+    	} else {
+    		LOG.e(LOG_TAG, "File plugin configuration error: Please set AndroidPersistentFileLocation in config.xml to one of \"internal\" (for new applications) or \"compatibility\" (for compatibility with previous versions)");
+    		activity.finish();
+    	}
+    }
+
+    public static FileUtils getFilePlugin() {
+		return filePlugin;
+	}
+
+	private Filesystem filesystemForURL(LocalFilesystemURL localURL) {
+    	if (localURL == null) return null;
+    	return filesystemForName(localURL.fsName);
+    }
+
+    @Override
+    public Uri remapUri(Uri uri) {
+        // Remap only cdvfile: URLs (not content:).
+        if (!LocalFilesystemURL.FILESYSTEM_PROTOCOL.equals(uri.getScheme())) {
+            return null;
+        }
+        try {
+        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(uri);
+        	Filesystem fs = this.filesystemForURL(inputURL);
+        	if (fs == null) {
+        		return null;
+        	}
+        	String path = fs.filesystemPathForURL(inputURL);
+        	if (path != null) {
+        		return Uri.parse("file://" + fs.filesystemPathForURL(inputURL));
+        	}
+        	return null;
+        } catch (IllegalArgumentException e) {
+        	return null;
+        }
+    }
+
+    public boolean execute(String action, final String rawArgs, final CallbackContext callbackContext) {
+        if (!configured) {
+            callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, "File plugin is not configured. Please see the README.md file for details on how to update config.xml"));
+            return true;
+        }
+        if (action.equals("testSaveLocationExists")) {
+            threadhelper(new FileOp() {
+                public void run(JSONArray args) {
+                    boolean b = DirectoryManager.testSaveLocationExists();
+                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("getFreeDiskSpace")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) {
+                    // The getFreeDiskSpace plugin API is not documented, but some apps call it anyway via exec().
+                    // For compatibility it always returns free space in the primary external storage, and
+                    // does NOT fallback to internal store if external storage is unavailable.
+                    long l = DirectoryManager.getFreeExternalStorageSpace();
+                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l));
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("testFileExists")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException {
+                    String fname=args.getString(0);
+                    boolean b = DirectoryManager.testFileExists(fname);
+                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("testDirectoryExists")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException {
+                    String fname=args.getString(0);
+                    boolean b = DirectoryManager.testFileExists(fname);
+                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("readAsText")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException, MalformedURLException {
+                    String encoding = args.getString(1);
+                    int start = args.getInt(2);
+                    int end = args.getInt(3);
+                    String fname=args.getString(0);
+                    readFileAs(fname, start, end, callbackContext, encoding, PluginResult.MESSAGE_TYPE_STRING);
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("readAsDataURL")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException, MalformedURLException  {
+                    int start = args.getInt(1);
+                    int end = args.getInt(2);
+                    String fname=args.getString(0);
+                    readFileAs(fname, start, end, callbackContext, null, -1);
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("readAsArrayBuffer")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException, MalformedURLException  {
+                    int start = args.getInt(1);
+                    int end = args.getInt(2);
+                    String fname=args.getString(0);
+                    readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_ARRAYBUFFER);
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("readAsBinaryString")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException, MalformedURLException  {
+                    int start = args.getInt(1);
+                    int end = args.getInt(2);
+                    String fname=args.getString(0);
+                    readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_BINARYSTRING);
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("write")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
+                    String fname=args.getString(0);
+                    String nativeURL = resolveLocalFileSystemURI(fname).getString("nativeURL");
+                    String data=args.getString(1);
+                    int offset=args.getInt(2);
+                    Boolean isBinary=args.getBoolean(3);
+
+                    if(needPermission(nativeURL, WRITE)) {
+                        getWritePermission(rawArgs, ACTION_WRITE, callbackContext);
+                    }
+                    else {
+                        long fileSize = write(fname, data, offset, isBinary);
+                        callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
+                    }
+
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("truncate")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
+                    String fname=args.getString(0);
+                    int offset=args.getInt(1);
+                    long fileSize = truncateFile(fname, offset);
+                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("requestAllFileSystems")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws IOException, JSONException {
+                    callbackContext.success(requestAllFileSystems());
+                }
+            }, rawArgs, callbackContext);
+        } else if (action.equals("requestAllPaths")) {
+            cordova.getThreadPool().execute(
+                    new Runnable() {
+                        public void run() {
+                        	try {
+					callbackContext.success(requestAllPaths());
+				} catch (JSONException e) {
+					// TODO Auto-generated catch block
+					e.printStackTrace();
+				}
+                        }
+                    }
+            );
+        } else if (action.equals("requestFileSystem")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException {
+                    int fstype = args.getInt(0);
+                    long requiredSize = args.optLong(1);
+                    requestFileSystem(fstype, requiredSize, callbackContext);
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("resolveLocalFileSystemURI")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws IOException, JSONException {
+                    String fname=args.getString(0);
+                    JSONObject obj = resolveLocalFileSystemURI(fname);
+                    callbackContext.success(obj);
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("getFileMetadata")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws FileNotFoundException, JSONException, MalformedURLException {
+                    String fname=args.getString(0);
+                    JSONObject obj = getFileMetadata(fname);
+                    callbackContext.success(obj);
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("getParent")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException, IOException {
+                    String fname=args.getString(0);
+                    JSONObject obj = getParent(fname);
+                    callbackContext.success(obj);
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("getDirectory")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+                    String dirname = args.getString(0);
+                    String path = args.getString(1);
+                    String nativeURL = resolveLocalFileSystemURI(dirname).getString("nativeURL");
+                    boolean containsCreate = (args.isNull(2)) ? false : args.getJSONObject(2).optBoolean("create", false);
+
+                    if(containsCreate && needPermission(nativeURL, WRITE)) {
+                        getWritePermission(rawArgs, ACTION_GET_DIRECTORY, callbackContext);
+                    }
+                    else if(!containsCreate && needPermission(nativeURL, READ)) {
+                        getReadPermission(rawArgs, ACTION_GET_DIRECTORY, callbackContext);
+                    }
+                    else {
+                        JSONObject obj = getFile(dirname, path, args.optJSONObject(2), true);
+                        callbackContext.success(obj);
+                    }
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("getFile")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+                    String dirname = args.getString(0);
+                    String path = args.getString(1);
+                    String nativeURL = resolveLocalFileSystemURI(dirname).getString("nativeURL");
+                    boolean containsCreate = (args.isNull(2)) ? false : args.getJSONObject(2).optBoolean("create", false);
+
+                    if(containsCreate && needPermission(nativeURL, WRITE)) {
+                        getWritePermission(rawArgs, ACTION_GET_FILE, callbackContext);
+                    }
+                    else if(!containsCreate && needPermission(nativeURL, READ)) {
+                        getReadPermission(rawArgs, ACTION_GET_FILE, callbackContext);
+                    }
+                    else {
+                        JSONObject obj = getFile(dirname, path, args.optJSONObject(2), false);
+                        callbackContext.success(obj);
+                    }
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("remove")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException, NoModificationAllowedException, InvalidModificationException, MalformedURLException {
+                    String fname=args.getString(0);
+                    boolean success = remove(fname);
+                    if (success) {
+                        callbackContext.success();
+                    } else {
+                        callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
+                    }
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("removeRecursively")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException, FileExistsException, MalformedURLException, NoModificationAllowedException {
+                    String fname=args.getString(0);
+                    boolean success = removeRecursively(fname);
+                    if (success) {
+                        callbackContext.success();
+                    } else {
+                        callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
+                    }
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("moveTo")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
+                    String fname=args.getString(0);
+                    String newParent=args.getString(1);
+                    String newName=args.getString(2);
+                    JSONObject entry = transferTo(fname, newParent, newName, true);
+                    callbackContext.success(entry);
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("copyTo")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
+                    String fname=args.getString(0);
+                    String newParent=args.getString(1);
+                    String newName=args.getString(2);
+                    JSONObject entry = transferTo(fname, newParent, newName, false);
+                    callbackContext.success(entry);
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("readEntries")) {
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws FileNotFoundException, JSONException, MalformedURLException {
+                    String fname=args.getString(0);
+                    JSONArray entries = readEntries(fname);
+                    callbackContext.success(entries);
+                }
+            }, rawArgs, callbackContext);
+        }
+        else if (action.equals("_getLocalFilesystemPath")) {
+            // Internal method for testing: Get the on-disk location of a local filesystem url.
+            // [Currently used for testing file-transfer]
+            threadhelper( new FileOp( ){
+                public void run(JSONArray args) throws FileNotFoundException, JSONException, MalformedURLException {
+                    String localURLstr = args.getString(0);
+                    String fname = filesystemPathForURL(localURLstr);
+                    callbackContext.success(fname);
+                }
+            }, rawArgs, callbackContext);
+        }
+        else {
+            return false;
+        }
+        return true;
+    }
+
+    private void getReadPermission(String rawArgs, int action, CallbackContext callbackContext) {
+        int requestCode = pendingRequests.createRequest(rawArgs, action, callbackContext);
+        PermissionHelper.requestPermission(this, requestCode, Manifest.permission.READ_EXTERNAL_STORAGE);
+    }
+
+    private void getWritePermission(String rawArgs, int action, CallbackContext callbackContext) {
+        int requestCode = pendingRequests.createRequest(rawArgs, action, callbackContext);
+        PermissionHelper.requestPermission(this, requestCode, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+    }
+
+    private boolean hasReadPermission() {
+        return PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
+    }
+
+    private boolean hasWritePermission() {
+        return PermissionHelper.hasPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+    }
+
+    private boolean needPermission(String nativeURL, int permissionType) throws JSONException {
+        JSONObject j = requestAllPaths();
+        ArrayList<String> allowedStorageDirectories = new ArrayList<String>();
+        allowedStorageDirectories.add(j.getString("applicationDirectory"));
+        allowedStorageDirectories.add(j.getString("applicationStorageDirectory"));
+        if(j.has("externalApplicationStorageDirectory")) {
+            allowedStorageDirectories.add(j.getString("externalApplicationStorageDirectory"));
+        }
+
+        if(permissionType == READ && hasReadPermission()) {
+            return false;
+        }
+        else if(permissionType == WRITE && hasWritePermission()) {
+            return false;
+        }
+
+        // Permission required if the native url lies outside the allowed storage directories
+        for(String directory : allowedStorageDirectories) {
+            if(nativeURL.startsWith(directory)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    public LocalFilesystemURL resolveNativeUri(Uri nativeUri) {
+        LocalFilesystemURL localURL = null;
+
+        // Try all installed filesystems. Return the best matching URL
+        // (determined by the shortest resulting URL)
+        for (Filesystem fs : filesystems) {
+            LocalFilesystemURL url = fs.toLocalUri(nativeUri);
+            if (url != null) {
+                // A shorter fullPath implies that the filesystem is a better
+                // match for the local path than the previous best.
+                if (localURL == null || (url.uri.toString().length() < localURL.toString().length())) {
+                    localURL = url;
+                }
+            }
+        }
+        return localURL;
+    }
+
+    /*
+     * These two native-only methods can be used by other plugins to translate between
+     * device file system paths and URLs. By design, there is no direct JavaScript
+     * interface to these methods.
+     */
+
+    public String filesystemPathForURL(String localURLstr) throws MalformedURLException {
+        try {
+            LocalFilesystemURL inputURL = LocalFilesystemURL.parse(localURLstr);
+            Filesystem fs = this.filesystemForURL(inputURL);
+            if (fs == null) {
+                throw new MalformedURLException("No installed handlers for this URL");
+            }
+            return fs.filesystemPathForURL(inputURL);
+        } catch (IllegalArgumentException e) {
+            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+            mue.initCause(e);
+        	throw mue;
+        }
+    }
+
+    public LocalFilesystemURL filesystemURLforLocalPath(String localPath) {
+        LocalFilesystemURL localURL = null;
+        int shortestFullPath = 0;
+
+        // Try all installed filesystems. Return the best matching URL
+        // (determined by the shortest resulting URL)
+        for (Filesystem fs: filesystems) {
+            LocalFilesystemURL url = fs.URLforFilesystemPath(localPath);
+            if (url != null) {
+                // A shorter fullPath implies that the filesystem is a better
+                // match for the local path than the previous best.
+                if (localURL == null || (url.path.length() < shortestFullPath)) {
+                    localURL = url;
+                    shortestFullPath = url.path.length();
+                }
+            }
+        }
+        return localURL;
+    }
+
+
+	/* helper to execute functions async and handle the result codes
+     *
+     */
+    private void threadhelper(final FileOp f, final String rawArgs, final CallbackContext callbackContext){
+        cordova.getThreadPool().execute(new Runnable() {
+            public void run() {
+                try {
+                    JSONArray args = new JSONArray(rawArgs);
+                    f.run(args);
+                } catch ( Exception e) {
+                    if( e instanceof EncodingException){
+                        callbackContext.error(FileUtils.ENCODING_ERR);
+                    } else if(e instanceof FileNotFoundException) {
+                        callbackContext.error(FileUtils.NOT_FOUND_ERR);
+                    } else if(e instanceof FileExistsException) {
+                        callbackContext.error(FileUtils.PATH_EXISTS_ERR);
+                    } else if(e instanceof NoModificationAllowedException ) {
+                        callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
+                    } else if(e instanceof InvalidModificationException ) {
+                        callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
+                    } else if(e instanceof MalformedURLException ) {
+                        callbackContext.error(FileUtils.ENCODING_ERR);
+                    } else if(e instanceof IOException ) {
+                        callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
+                    } else if(e instanceof EncodingException ) {
+                        callbackContext.error(FileUtils.ENCODING_ERR);
+                    } else if(e instanceof TypeMismatchException ) {
+                        callbackContext.error(FileUtils.TYPE_MISMATCH_ERR);
+                    } else if(e instanceof JSONException ) {
+                        callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
+                    } else if (e instanceof SecurityException) {
+                        callbackContext.error(FileUtils.SECURITY_ERR);
+                    } else {
+                        e.printStackTrace();
+                    	callbackContext.error(FileUtils.UNKNOWN_ERR);
+                    }
+                }
+            }
+        });
+    }
+
+    /**
+     * Allows the user to look up the Entry for a file or directory referred to by a local URI.
+     *
+     * @param uriString of the file/directory to look up
+     * @return a JSONObject representing a Entry from the filesystem
+     * @throws MalformedURLException if the url is not valid
+     * @throws FileNotFoundException if the file does not exist
+     * @throws IOException if the user can't read the file
+     * @throws JSONException
+     */
+    private JSONObject resolveLocalFileSystemURI(String uriString) throws IOException, JSONException {
+        if (uriString == null) {
+            throw new MalformedURLException("Unrecognized filesystem URL");
+        }
+        Uri uri = Uri.parse(uriString);
+        boolean isNativeUri = false;
+
+        LocalFilesystemURL inputURL = LocalFilesystemURL.parse(uri);
+        if (inputURL == null) {
+            /* Check for file://, content:// urls */
+            inputURL = resolveNativeUri(uri);
+            isNativeUri = true;
+        }
+
+        try {
+            Filesystem fs = this.filesystemForURL(inputURL);
+            if (fs == null) {
+                throw new MalformedURLException("No installed handlers for this URL");
+            }
+            if (fs.exists(inputURL)) {
+                if (!isNativeUri) {
+                    // If not already resolved as native URI, resolve to a native URI and back to
+                    // fix the terminating slash based on whether the entry is a directory or file.
+                    inputURL = fs.toLocalUri(fs.toNativeUri(inputURL));
+                }
+
+                return fs.getEntryForLocalURL(inputURL);
+            }
+        } catch (IllegalArgumentException e) {
+            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+            mue.initCause(e);
+        	throw mue;
+        }
+        throw new FileNotFoundException();
+    }
+
+    /**
+     * Read the list of files from this directory.
+     *
+     * @return a JSONArray containing JSONObjects that represent Entry objects.
+     * @throws FileNotFoundException if the directory is not found.
+     * @throws JSONException
+     * @throws MalformedURLException
+     */
+    private JSONArray readEntries(String baseURLstr) throws FileNotFoundException, JSONException, MalformedURLException {
+        try {
+        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
+        	Filesystem fs = this.filesystemForURL(inputURL);
+        	if (fs == null) {
+        		throw new MalformedURLException("No installed handlers for this URL");
+        	}
+        	return fs.readEntriesAtLocalURL(inputURL);
+
+        } catch (IllegalArgumentException e) {
+            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+            mue.initCause(e);
+        	throw mue;
+        }
+    }
+
+    /**
+     * A setup method that handles the move/copy of files/directories
+     *
+     * @param newName for the file directory to be called, if null use existing file name
+     * @param move if false do a copy, if true do a move
+     * @return a Entry object
+     * @throws NoModificationAllowedException
+     * @throws IOException
+     * @throws InvalidModificationException
+     * @throws EncodingException
+     * @throws JSONException
+     * @throws FileExistsException
+     */
+    private JSONObject transferTo(String srcURLstr, String destURLstr, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
+        if (srcURLstr == null || destURLstr == null) {
+            // either no source or no destination provided
+        	throw new FileNotFoundException();
+        }
+
+        LocalFilesystemURL srcURL = LocalFilesystemURL.parse(srcURLstr);
+        LocalFilesystemURL destURL = LocalFilesystemURL.parse(destURLstr);
+
+        Filesystem srcFs = this.filesystemForURL(srcURL);
+        Filesystem destFs = this.filesystemForURL(destURL);
+
+        // Check for invalid file name
+        if (newName != null && newName.contains(":")) {
+            throw new EncodingException("Bad file name");
+        }
+
+        return destFs.copyFileToURL(destURL, newName, srcFs, srcURL, move);
+    }
+
+    /**
+     * Deletes a directory and all of its contents, if any. In the event of an error
+     * [e.g. trying to delete a directory that contains a file that cannot be removed],
+     * some of the contents of the directory may be deleted.
+     * It is an error to attempt to delete the root directory of a filesystem.
+     *
+     * @return a boolean representing success of failure
+     * @throws FileExistsException
+     * @throws NoModificationAllowedException
+     * @throws MalformedURLException
+     */
+    private boolean removeRecursively(String baseURLstr) throws FileExistsException, NoModificationAllowedException, MalformedURLException {
+        try {
+        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
+        	// You can't delete the root directory.
+        	if ("".equals(inputURL.path) || "/".equals(inputURL.path)) {
+        		throw new NoModificationAllowedException("You can't delete the root directory");
+        	}
+
+        	Filesystem fs = this.filesystemForURL(inputURL);
+        	if (fs == null) {
+        		throw new MalformedURLException("No installed handlers for this URL");
+        	}
+        	return fs.recursiveRemoveFileAtLocalURL(inputURL);
+
+        } catch (IllegalArgumentException e) {
+            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+            mue.initCause(e);
+        	throw mue;
+        }
+    }
+
+
+    /**
+     * Deletes a file or directory. It is an error to attempt to delete a directory that is not empty.
+     * It is an error to attempt to delete the root directory of a filesystem.
+     *
+     * @return a boolean representing success of failure
+     * @throws NoModificationAllowedException
+     * @throws InvalidModificationException
+     * @throws MalformedURLException
+     */
+    private boolean remove(String baseURLstr) throws NoModificationAllowedException, InvalidModificationException, MalformedURLException {
+        try {
+        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
+        	// You can't delete the root directory.
+        	if ("".equals(inputURL.path) || "/".equals(inputURL.path)) {
+
+        		throw new NoModificationAllowedException("You can't delete the root directory");
+        	}
+
+        	Filesystem fs = this.filesystemForURL(inputURL);
+        	if (fs == null) {
+        		throw new MalformedURLException("No installed handlers for this URL");
+        	}
+        	return fs.removeFileAtLocalURL(inputURL);
+
+        } catch (IllegalArgumentException e) {
+            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+            mue.initCause(e);
+        	throw mue;
+        }
+    }
+
+    /**
+     * Creates or looks up a file.
+     *
+     * @param baseURLstr base directory
+     * @param path file/directory to lookup or create
+     * @param options specify whether to create or not
+     * @param directory if true look up directory, if false look up file
+     * @return a Entry object
+     * @throws FileExistsException
+     * @throws IOException
+     * @throws TypeMismatchException
+     * @throws EncodingException
+     * @throws JSONException
+     */
+    private JSONObject getFile(String baseURLstr, String path, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+        try {
+        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
+        	Filesystem fs = this.filesystemForURL(inputURL);
+        	if (fs == null) {
+        		throw new MalformedURLException("No installed handlers for this URL");
+        	}
+        	return fs.getFileForLocalURL(inputURL, path, options, directory);
+
+        } catch (IllegalArgumentException e) {
+            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+            mue.initCause(e);
+        	throw mue;
+        }
+
+    }
+
+    /**
+     * Look up the parent DirectoryEntry containing this Entry.
+     * If this Entry is the root of its filesystem, its parent is itself.
+     */
+    private JSONObject getParent(String baseURLstr) throws JSONException, IOException {
+        try {
+        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
+        	Filesystem fs = this.filesystemForURL(inputURL);
+        	if (fs == null) {
+        		throw new MalformedURLException("No installed handlers for this URL");
+        	}
+        	return fs.getParentForLocalURL(inputURL);
+
+        } catch (IllegalArgumentException e) {
+            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+            mue.initCause(e);
+        	throw mue;
+        }
+    }
+
+    /**
+     * Returns a File that represents the current state of the file that this FileEntry represents.
+     *
+     * @return returns a JSONObject represent a W3C File object
+     */
+    private JSONObject getFileMetadata(String baseURLstr) throws FileNotFoundException, JSONException, MalformedURLException {
+        try {
+        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
+        	Filesystem fs = this.filesystemForURL(inputURL);
+        	if (fs == null) {
+        		throw new MalformedURLException("No installed handlers for this URL");
+        	}
+        	return fs.getFileMetadataForLocalURL(inputURL);
+
+        } catch (IllegalArgumentException e) {
+            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+            mue.initCause(e);
+        	throw mue;
+        }
+    }
+
+    /**
+     * Requests a filesystem in which to store application data.
+     *
+     * @param type of file system requested
+     * @param requiredSize required free space in the file system in bytes
+     * @param callbackContext context for returning the result or error
+     * @throws JSONException
+     */
+    private void requestFileSystem(int type, long requiredSize, final CallbackContext callbackContext) throws JSONException {
+        Filesystem rootFs = null;
+        try {
+            rootFs = this.filesystems.get(type);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            // Pass null through
+        }
+        if (rootFs == null) {
+            callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR));
+        } else {
+            // If a nonzero required size was specified, check that the retrieved filesystem has enough free space.
+            long availableSize = 0;
+            if (requiredSize > 0) {
+                availableSize = rootFs.getFreeSpaceInBytes();
+            }
+
+            if (availableSize < requiredSize) {
+                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.QUOTA_EXCEEDED_ERR));
+            } else {
+                JSONObject fs = new JSONObject();
+                fs.put("name", rootFs.name);
+                fs.put("root", rootFs.getRootEntry());
+                callbackContext.success(fs);
+            }
+        }
+    }
+
+    /**
+     * Requests a filesystem in which to store application data.
+     *
+     * @return a JSONObject representing the file system
+     */
+    private JSONArray requestAllFileSystems() throws IOException, JSONException {
+        JSONArray ret = new JSONArray();
+        for (Filesystem fs : filesystems) {
+            ret.put(fs.getRootEntry());
+        }
+        return ret;
+    }
+
+    private static String toDirUrl(File f) {
+        return Uri.fromFile(f).toString() + '/';
+    }
+
+    private JSONObject requestAllPaths() throws JSONException {
+        Context context = cordova.getActivity();
+        JSONObject ret = new JSONObject();
+        ret.put("applicationDirectory", "file:///android_asset/");
+        ret.put("applicationStorageDirectory", toDirUrl(context.getFilesDir().getParentFile()));
+        ret.put("dataDirectory", toDirUrl(context.getFilesDir()));
+        ret.put("cacheDirectory", toDirUrl(context.getCacheDir()));
+        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+          try {
+            ret.put("externalApplicationStorageDirectory", toDirUrl(context.getExternalFilesDir(null).getParentFile()));
+            ret.put("externalDataDirectory", toDirUrl(context.getExternalFilesDir(null)));
+            ret.put("externalCacheDirectory", toDirUrl(context.getExternalCacheDir()));
+            ret.put("externalRootDirectory", toDirUrl(Environment.getExternalStorageDirectory()));
+          }
+          catch(NullPointerException e) {
+            /* If external storage is unavailable, context.getExternal* returns null */
+              LOG.d(LOG_TAG, "Unable to access these paths, most liklely due to USB storage");
+          }
+        }
+        return ret;
+    }
+
+   /**
+     * Returns a JSON object representing the given File. Internal APIs should be modified
+     * to use URLs instead of raw FS paths wherever possible, when interfacing with this plugin.
+     *
+     * @param file the File to convert
+     * @return a JSON representation of the given File
+     * @throws JSONException
+     */
+    public JSONObject getEntryForFile(File file) throws JSONException {
+        JSONObject entry;
+
+        for (Filesystem fs : filesystems) {
+             entry = fs.makeEntryForFile(file);
+             if (entry != null) {
+                 return entry;
+             }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a JSON object representing the given File. Deprecated, as this is only used by
+     * FileTransfer, and because it is a static method that should really be an instance method,
+     * since it depends on the actual filesystem roots in use. Internal APIs should be modified
+     * to use URLs instead of raw FS paths wherever possible, when interfacing with this plugin.
+     *
+     * @param file the File to convert
+     * @return a JSON representation of the given File
+     * @throws JSONException
+     */
+    @Deprecated
+    public static JSONObject getEntry(File file) throws JSONException {
+ 		if (getFilePlugin() != null) {
+             return getFilePlugin().getEntryForFile(file);
+		}
+		return null;
+    }
+
+    /**
+     * Read the contents of a file.
+     * This is done in a background thread; the result is sent to the callback.
+     *
+     * @param start             Start position in the file.
+     * @param end               End position to stop at (exclusive).
+     * @param callbackContext   The context through which to send the result.
+     * @param encoding          The encoding to return contents as.  Typical value is UTF-8. (see http://www.iana.org/assignments/character-sets)
+     * @param resultType        The desired type of data to send to the callback.
+     * @return                  Contents of file.
+     */
+    public void readFileAs(final String srcURLstr, final int start, final int end, final CallbackContext callbackContext, final String encoding, final int resultType) throws MalformedURLException {
+        try {
+        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(srcURLstr);
+        	Filesystem fs = this.filesystemForURL(inputURL);
+        	if (fs == null) {
+        		throw new MalformedURLException("No installed handlers for this URL");
+        	}
+
+            fs.readFileAtURL(inputURL, start, end, new Filesystem.ReadFileCallback() {
+                public void handleData(InputStream inputStream, String contentType) {
+            		try {
+                        ByteArrayOutputStream os = new ByteArrayOutputStream();
+                        final int BUFFER_SIZE = 8192;
+                        byte[] buffer = new byte[BUFFER_SIZE];
+
+                        for (;;) {
+                            int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
+
+                            if (bytesRead <= 0) {
+                                break;
+                            }
+                            os.write(buffer, 0, bytesRead);
+                        }
+
+            			PluginResult result;
+            			switch (resultType) {
+            			case PluginResult.MESSAGE_TYPE_STRING:
+                            result = new PluginResult(PluginResult.Status.OK, os.toString(encoding));
+            				break;
+            			case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
+                            result = new PluginResult(PluginResult.Status.OK, os.toByteArray());
+            				break;
+            			case PluginResult.MESSAGE_TYPE_BINARYSTRING:
+                            result = new PluginResult(PluginResult.Status.OK, os.toByteArray(), true);
+            				break;
+            			default: // Base64.
+                        byte[] base64 = Base64.encode(os.toByteArray(), Base64.NO_WRAP);
+            			String s = "data:" + contentType + ";base64," + new String(base64, "US-ASCII");
+            			result = new PluginResult(PluginResult.Status.OK, s);
+            			}
+
+            			callbackContext.sendPluginResult(result);
+            		} catch (IOException e) {
+            			LOG.d(LOG_TAG, e.getLocalizedMessage());
+            			callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR));
+                    }
+            	}
+            });
+
+
+        } catch (IllegalArgumentException e) {
+            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+            mue.initCause(e);
+        	throw mue;
+        } catch (FileNotFoundException e) {
+        	callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_FOUND_ERR));
+        } catch (IOException e) {
+        	LOG.d(LOG_TAG, e.getLocalizedMessage());
+        	callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR));
+        }
+    }
+
+
+    /**
+     * Write contents of file.
+     *
+     * @param data				The contents of the file.
+     * @param offset			The position to begin writing the file.
+     * @param isBinary          True if the file contents are base64-encoded binary data
+     */
+    /**/
+    public long write(String srcURLstr, String data, int offset, boolean isBinary) throws FileNotFoundException, IOException, NoModificationAllowedException {
+        try {
+        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(srcURLstr);
+        	Filesystem fs = this.filesystemForURL(inputURL);
+        	if (fs == null) {
+        		throw new MalformedURLException("No installed handlers for this URL");
+        	}
+
+            long x = fs.writeToFileAtURL(inputURL, data, offset, isBinary); LOG.d("TEST",srcURLstr + ": "+x); return x;
+        } catch (IllegalArgumentException e) {
+            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+            mue.initCause(e);
+        	throw mue;
+        }
+
+    }
+
+    /**
+     * Truncate the file to size
+     */
+    private long truncateFile(String srcURLstr, long size) throws FileNotFoundException, IOException, NoModificationAllowedException {
+        try {
+        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(srcURLstr);
+        	Filesystem fs = this.filesystemForURL(inputURL);
+        	if (fs == null) {
+        		throw new MalformedURLException("No installed handlers for this URL");
+        	}
+
+            return fs.truncateFileAtURL(inputURL, size);
+        } catch (IllegalArgumentException e) {
+            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
+            mue.initCause(e);
+        	throw mue;
+        }
+    }
+
+
+    /*
+     * Handle the response
+     */
+
+    public void onRequestPermissionResult(int requestCode, String[] permissions,
+                                          int[] grantResults) throws JSONException {
+
+        final PendingRequests.Request req = pendingRequests.getAndRemove(requestCode);
+        if (req != null) {
+            for(int r:grantResults)
+            {
+                if(r == PackageManager.PERMISSION_DENIED)
+                {
+                    req.getCallbackContext().sendPluginResult(new PluginResult(PluginResult.Status.ERROR, SECURITY_ERR));
+                    return;
+                }
+            }
+            switch(req.getAction())
+            {
+                case ACTION_GET_FILE:
+                    threadhelper( new FileOp( ){
+                        public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+                            String dirname = args.getString(0);
+
+                            String path = args.getString(1);
+                            JSONObject obj = getFile(dirname, path, args.optJSONObject(2), false);
+                            req.getCallbackContext().success(obj);
+                        }
+                    }, req.getRawArgs(), req.getCallbackContext());
+                    break;
+                case ACTION_GET_DIRECTORY:
+                    threadhelper( new FileOp( ){
+                        public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+                            String dirname = args.getString(0);
+
+                            String path = args.getString(1);
+                            JSONObject obj = getFile(dirname, path, args.optJSONObject(2), true);
+                            req.getCallbackContext().success(obj);
+                        }
+                    }, req.getRawArgs(), req.getCallbackContext());
+                    break;
+                case ACTION_WRITE:
+                    threadhelper( new FileOp( ){
+                        public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
+                            String fname=args.getString(0);
+                            String data=args.getString(1);
+                            int offset=args.getInt(2);
+                            Boolean isBinary=args.getBoolean(3);
+                            long fileSize = write(fname, data, offset, isBinary);
+                            req.getCallbackContext().sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
+                        }
+                    }, req.getRawArgs(), req.getCallbackContext());
+                    break;
+            }
+        } else {
+           LOG.d(LOG_TAG, "Received permission callback for unknown request code");
+        }
+    }
+}

+ 331 - 0
platforms/android/src/org/apache/cordova/file/Filesystem.java

@@ -0,0 +1,331 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+ */
+package org.apache.cordova.file;
+
+import android.net.Uri;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import org.apache.cordova.CordovaResourceApi;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public abstract class Filesystem {
+
+    protected final Uri rootUri;
+    protected final CordovaResourceApi resourceApi;
+    public final String name;
+    private JSONObject rootEntry;
+
+    public Filesystem(Uri rootUri, String name, CordovaResourceApi resourceApi) {
+        this.rootUri = rootUri;
+        this.name = name;
+        this.resourceApi = resourceApi;
+    }
+
+    public interface ReadFileCallback {
+		public void handleData(InputStream inputStream, String contentType) throws IOException;
+	}
+
+    public static JSONObject makeEntryForURL(LocalFilesystemURL inputURL, Uri nativeURL) {
+        try {
+            String path = inputURL.path;
+            int end = path.endsWith("/") ? 1 : 0;
+            String[] parts = path.substring(0, path.length() - end).split("/+");
+            String fileName = parts[parts.length - 1];
+
+            JSONObject entry = new JSONObject();
+            entry.put("isFile", !inputURL.isDirectory);
+            entry.put("isDirectory", inputURL.isDirectory);
+            entry.put("name", fileName);
+            entry.put("fullPath", path);
+            // The file system can't be specified, as it would lead to an infinite loop,
+            // but the filesystem name can be.
+            entry.put("filesystemName", inputURL.fsName);
+            // Backwards compatibility
+            entry.put("filesystem", "temporary".equals(inputURL.fsName) ? 0 : 1);
+
+            String nativeUrlStr = nativeURL.toString();
+            if (inputURL.isDirectory && !nativeUrlStr.endsWith("/")) {
+                nativeUrlStr += "/";
+            }
+            entry.put("nativeURL", nativeUrlStr);
+            return entry;
+        } catch (JSONException e) {
+            e.printStackTrace();
+            throw new RuntimeException(e);
+        }
+    }
+
+    public JSONObject makeEntryForURL(LocalFilesystemURL inputURL) {
+        Uri nativeUri = toNativeUri(inputURL);
+        return nativeUri == null ? null : makeEntryForURL(inputURL, nativeUri);
+    }
+
+    public JSONObject makeEntryForNativeUri(Uri nativeUri) {
+        LocalFilesystemURL inputUrl = toLocalUri(nativeUri);
+        return inputUrl == null ? null : makeEntryForURL(inputUrl, nativeUri);
+    }
+
+    public JSONObject getEntryForLocalURL(LocalFilesystemURL inputURL) throws IOException {
+        return makeEntryForURL(inputURL);
+    }
+
+    public JSONObject makeEntryForFile(File file) {
+        return makeEntryForNativeUri(Uri.fromFile(file));
+    }
+
+    abstract JSONObject getFileForLocalURL(LocalFilesystemURL inputURL, String path,
+			JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException;
+
+	abstract boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException;
+
+	abstract boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException, NoModificationAllowedException;
+
+	abstract LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException;
+
+    public final JSONArray readEntriesAtLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
+        LocalFilesystemURL[] children = listChildren(inputURL);
+        JSONArray entries = new JSONArray();
+        if (children != null) {
+            for (LocalFilesystemURL url : children) {
+                entries.put(makeEntryForURL(url));
+            }
+        }
+        return entries;
+    }
+
+	abstract JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException;
+
+    public Uri getRootUri() {
+        return rootUri;
+    }
+
+    public boolean exists(LocalFilesystemURL inputURL) {
+        try {
+            getFileMetadataForLocalURL(inputURL);
+        } catch (FileNotFoundException e) {
+            return false;
+        }
+        return true;
+    }
+
+    public Uri nativeUriForFullPath(String fullPath) {
+        Uri ret = null;
+        if (fullPath != null) {
+            String encodedPath = Uri.fromFile(new File(fullPath)).getEncodedPath();
+            if (encodedPath.startsWith("/")) {
+                encodedPath = encodedPath.substring(1);
+            }
+            ret = rootUri.buildUpon().appendEncodedPath(encodedPath).build();
+        }
+        return ret;
+    }
+
+    public LocalFilesystemURL localUrlforFullPath(String fullPath) {
+        Uri nativeUri = nativeUriForFullPath(fullPath);
+        if (nativeUri != null) {
+            return toLocalUri(nativeUri);
+        }
+        return null;
+    }
+
+    /**
+     * Removes multiple repeated //s, and collapses processes ../s.
+     */
+    protected static String normalizePath(String rawPath) {
+        // If this is an absolute path, trim the leading "/" and replace it later
+        boolean isAbsolutePath = rawPath.startsWith("/");
+        if (isAbsolutePath) {
+            rawPath = rawPath.replaceFirst("/+", "");
+        }
+        ArrayList<String> components = new ArrayList<String>(Arrays.asList(rawPath.split("/+")));
+        for (int index = 0; index < components.size(); ++index) {
+            if (components.get(index).equals("..")) {
+                components.remove(index);
+                if (index > 0) {
+                    components.remove(index-1);
+                    --index;
+                }
+            }
+        }
+        StringBuilder normalizedPath = new StringBuilder();
+        for(String component: components) {
+            normalizedPath.append("/");
+            normalizedPath.append(component);
+        }
+        if (isAbsolutePath) {
+            return normalizedPath.toString();
+        } else {
+            return normalizedPath.toString().substring(1);
+        }
+    }
+
+    /**
+     * Gets the free space in bytes available on this filesystem.
+     * Subclasses may override this method to return nonzero free space.
+     */
+    public long getFreeSpaceInBytes() {
+        return 0;
+    }
+
+    public abstract Uri toNativeUri(LocalFilesystemURL inputURL);
+    public abstract LocalFilesystemURL toLocalUri(Uri inputURL);
+
+    public JSONObject getRootEntry() {
+        if (rootEntry == null) {
+            rootEntry = makeEntryForNativeUri(rootUri);
+        }
+        return rootEntry;
+    }
+
+	public JSONObject getParentForLocalURL(LocalFilesystemURL inputURL) throws IOException {
+        Uri parentUri = inputURL.uri;
+        String parentPath = new File(inputURL.uri.getPath()).getParent();
+        if (!"/".equals(parentPath)) {
+            parentUri = inputURL.uri.buildUpon().path(parentPath + '/').build();
+		}
+		return getEntryForLocalURL(LocalFilesystemURL.parse(parentUri));
+	}
+
+    protected LocalFilesystemURL makeDestinationURL(String newName, LocalFilesystemURL srcURL, LocalFilesystemURL destURL, boolean isDirectory) {
+        // I know this looks weird but it is to work around a JSON bug.
+        if ("null".equals(newName) || "".equals(newName)) {
+            newName = srcURL.uri.getLastPathSegment();;
+        }
+
+        String newDest = destURL.uri.toString();
+        if (newDest.endsWith("/")) {
+            newDest = newDest + newName;
+        } else {
+            newDest = newDest + "/" + newName;
+        }
+        if (isDirectory) {
+            newDest += '/';
+        }
+        return LocalFilesystemURL.parse(newDest);
+    }
+
+	/* Read a source URL (possibly from a different filesystem, srcFs,) and copy it to
+	 * the destination URL on this filesystem, optionally with a new filename.
+	 * If move is true, then this method should either perform an atomic move operation
+	 * or remove the source file when finished.
+	 */
+    public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
+            Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
+        // First, check to see that we can do it
+        if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) {
+            throw new NoModificationAllowedException("Cannot move file at source URL");
+        }
+        final LocalFilesystemURL destination = makeDestinationURL(newName, srcURL, destURL, srcURL.isDirectory);
+
+        Uri srcNativeUri = srcFs.toNativeUri(srcURL);
+
+        CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(srcNativeUri);
+        OutputStream os = null;
+        try {
+            os = getOutputStreamForURL(destination);
+        } catch (IOException e) {
+            ofrr.inputStream.close();
+            throw e;
+        }
+        // Closes streams.
+        resourceApi.copyResource(ofrr, os);
+
+        if (move) {
+            srcFs.removeFileAtLocalURL(srcURL);
+        }
+        return getEntryForLocalURL(destination);
+    }
+
+    public OutputStream getOutputStreamForURL(LocalFilesystemURL inputURL) throws IOException {
+        return resourceApi.openOutputStream(toNativeUri(inputURL));
+    }
+
+    public void readFileAtURL(LocalFilesystemURL inputURL, long start, long end,
+                              ReadFileCallback readFileCallback) throws IOException {
+        CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(toNativeUri(inputURL));
+        if (end < 0) {
+            end = ofrr.length;
+        }
+        long numBytesToRead = end - start;
+        try {
+            if (start > 0) {
+                ofrr.inputStream.skip(start);
+            }
+            InputStream inputStream = ofrr.inputStream;
+            if (end < ofrr.length) {
+                inputStream = new LimitedInputStream(inputStream, numBytesToRead);
+            }
+            readFileCallback.handleData(inputStream, ofrr.mimeType);
+        } finally {
+            ofrr.inputStream.close();
+        }
+    }
+
+	abstract long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset,
+			boolean isBinary) throws NoModificationAllowedException, IOException;
+
+	abstract long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
+			throws IOException, NoModificationAllowedException;
+
+	// This method should return null if filesystem urls cannot be mapped to paths
+	abstract String filesystemPathForURL(LocalFilesystemURL url);
+
+	abstract LocalFilesystemURL URLforFilesystemPath(String path);
+
+	abstract boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL);
+
+    protected class LimitedInputStream extends FilterInputStream {
+        long numBytesToRead;
+        public LimitedInputStream(InputStream in, long numBytesToRead) {
+            super(in);
+            this.numBytesToRead = numBytesToRead;
+        }
+        @Override
+        public int read() throws IOException {
+            if (numBytesToRead <= 0) {
+                return -1;
+            }
+            numBytesToRead--;
+            return in.read();
+        }
+        @Override
+        public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
+            if (numBytesToRead <= 0) {
+                return -1;
+            }
+            int bytesToRead = byteCount;
+            if (byteCount > numBytesToRead) {
+                bytesToRead = (int)numBytesToRead; // Cast okay; long is less than int here.
+            }
+            int numBytesRead = in.read(buffer, byteOffset, bytesToRead);
+            numBytesToRead -= numBytesRead;
+            return numBytesRead;
+        }
+    }
+}

+ 30 - 0
platforms/android/src/org/apache/cordova/file/InvalidModificationException.java

@@ -0,0 +1,30 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+
+package org.apache.cordova.file;
+
+@SuppressWarnings("serial")
+public class InvalidModificationException extends Exception {
+
+    public InvalidModificationException(String message) {
+        super(message);
+    }
+
+}

+ 513 - 0
platforms/android/src/org/apache/cordova/file/LocalFilesystem.java

@@ -0,0 +1,513 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+ */
+package org.apache.cordova.file;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import org.apache.cordova.CordovaResourceApi;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.os.Build;
+import android.os.Environment;
+import android.util.Base64;
+import android.net.Uri;
+import android.content.Context;
+import android.content.Intent;
+
+import java.nio.charset.Charset;
+
+public class LocalFilesystem extends Filesystem {
+    private final Context context;
+
+    public LocalFilesystem(String name, Context context, CordovaResourceApi resourceApi, File fsRoot) {
+        super(Uri.fromFile(fsRoot).buildUpon().appendEncodedPath("").build(), name, resourceApi);
+        this.context = context;
+    }
+
+    public String filesystemPathForFullPath(String fullPath) {
+	    return new File(rootUri.getPath(), fullPath).toString();
+	}
+
+	@Override
+	public String filesystemPathForURL(LocalFilesystemURL url) {
+		return filesystemPathForFullPath(url.path);
+	}
+
+	private String fullPathForFilesystemPath(String absolutePath) {
+		if (absolutePath != null && absolutePath.startsWith(rootUri.getPath())) {
+			return absolutePath.substring(rootUri.getPath().length() - 1);
+		}
+		return null;
+	}
+
+    @Override
+    public Uri toNativeUri(LocalFilesystemURL inputURL) {
+        return nativeUriForFullPath(inputURL.path);
+    }
+
+    @Override
+    public LocalFilesystemURL toLocalUri(Uri inputURL) {
+        if (!"file".equals(inputURL.getScheme())) {
+            return null;
+        }
+        File f = new File(inputURL.getPath());
+        // Removes and duplicate /s (e.g. file:///a//b/c)
+        Uri resolvedUri = Uri.fromFile(f);
+        String rootUriNoTrailingSlash = rootUri.getEncodedPath();
+        rootUriNoTrailingSlash = rootUriNoTrailingSlash.substring(0, rootUriNoTrailingSlash.length() - 1);
+        if (!resolvedUri.getEncodedPath().startsWith(rootUriNoTrailingSlash)) {
+            return null;
+        }
+        String subPath = resolvedUri.getEncodedPath().substring(rootUriNoTrailingSlash.length());
+        // Strip leading slash
+        if (!subPath.isEmpty()) {
+            subPath = subPath.substring(1);
+        }
+        Uri.Builder b = new Uri.Builder()
+            .scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
+            .authority("localhost")
+            .path(name);
+        if (!subPath.isEmpty()) {
+            b.appendEncodedPath(subPath);
+        }
+        if (f.isDirectory()) {
+            // Add trailing / for directories.
+            b.appendEncodedPath("");
+        }
+        return LocalFilesystemURL.parse(b.build());
+    }
+
+	@Override
+	public LocalFilesystemURL URLforFilesystemPath(String path) {
+	    return localUrlforFullPath(fullPathForFilesystemPath(path));
+	}
+
+	@Override
+	public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
+			String path, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
+        boolean create = false;
+        boolean exclusive = false;
+
+        if (options != null) {
+            create = options.optBoolean("create");
+            if (create) {
+                exclusive = options.optBoolean("exclusive");
+            }
+        }
+
+        // Check for a ":" character in the file to line up with BB and iOS
+        if (path.contains(":")) {
+            throw new EncodingException("This path has an invalid \":\" in it.");
+        }
+
+        LocalFilesystemURL requestedURL;
+
+        // Check whether the supplied path is absolute or relative
+        if (directory && !path.endsWith("/")) {
+            path += "/";
+        }
+        if (path.startsWith("/")) {
+        	requestedURL = localUrlforFullPath(normalizePath(path));
+        } else {
+        	requestedURL = localUrlforFullPath(normalizePath(inputURL.path + "/" + path));
+        }
+
+        File fp = new File(this.filesystemPathForURL(requestedURL));
+
+        if (create) {
+            if (exclusive && fp.exists()) {
+                throw new FileExistsException("create/exclusive fails");
+            }
+            if (directory) {
+                fp.mkdir();
+            } else {
+                fp.createNewFile();
+            }
+            if (!fp.exists()) {
+                throw new FileExistsException("create fails");
+            }
+        }
+        else {
+            if (!fp.exists()) {
+                throw new FileNotFoundException("path does not exist");
+            }
+            if (directory) {
+                if (fp.isFile()) {
+                    throw new TypeMismatchException("path doesn't exist or is file");
+                }
+            } else {
+                if (fp.isDirectory()) {
+                    throw new TypeMismatchException("path doesn't exist or is directory");
+                }
+            }
+        }
+
+        // Return the directory
+        return makeEntryForURL(requestedURL);
+	}
+
+	@Override
+	public boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException {
+
+        File fp = new File(filesystemPathForURL(inputURL));
+
+        // You can't delete a directory that is not empty
+        if (fp.isDirectory() && fp.list().length > 0) {
+            throw new InvalidModificationException("You can't delete a directory that is not empty.");
+        }
+
+        return fp.delete();
+	}
+
+    @Override
+    public boolean exists(LocalFilesystemURL inputURL) {
+        File fp = new File(filesystemPathForURL(inputURL));
+        return fp.exists();
+    }
+
+    @Override
+    public long getFreeSpaceInBytes() {
+        return DirectoryManager.getFreeSpaceInBytes(rootUri.getPath());
+    }
+
+    @Override
+	public boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException {
+        File directory = new File(filesystemPathForURL(inputURL));
+    	return removeDirRecursively(directory);
+	}
+
+	protected boolean removeDirRecursively(File directory) throws FileExistsException {
+        if (directory.isDirectory()) {
+            for (File file : directory.listFiles()) {
+                removeDirRecursively(file);
+            }
+        }
+
+        if (!directory.delete()) {
+            throw new FileExistsException("could not delete: " + directory.getName());
+        } else {
+            return true;
+        }
+	}
+
+    @Override
+    public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
+        File fp = new File(filesystemPathForURL(inputURL));
+
+        if (!fp.exists()) {
+            // The directory we are listing doesn't exist so we should fail.
+            throw new FileNotFoundException();
+        }
+
+        File[] files = fp.listFiles();
+        if (files == null) {
+            // inputURL is a directory
+            return null;
+        }
+        LocalFilesystemURL[] entries = new LocalFilesystemURL[files.length];
+        for (int i = 0; i < files.length; i++) {
+            entries[i] = URLforFilesystemPath(files[i].getPath());
+        }
+
+        return entries;
+	}
+
+	@Override
+	public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
+        File file = new File(filesystemPathForURL(inputURL));
+
+        if (!file.exists()) {
+            throw new FileNotFoundException("File at " + inputURL.uri + " does not exist.");
+        }
+
+        JSONObject metadata = new JSONObject();
+        try {
+            // Ensure that directories report a size of 0
+        	metadata.put("size", file.isDirectory() ? 0 : file.length());
+        	metadata.put("type", resourceApi.getMimeType(Uri.fromFile(file)));
+        	metadata.put("name", file.getName());
+        	metadata.put("fullPath", inputURL.path);
+        	metadata.put("lastModifiedDate", file.lastModified());
+        } catch (JSONException e) {
+        	return null;
+        }
+        return metadata;
+	}
+
+    private void copyFile(Filesystem srcFs, LocalFilesystemURL srcURL, File destFile, boolean move) throws IOException, InvalidModificationException, NoModificationAllowedException {
+        if (move) {
+            String realSrcPath = srcFs.filesystemPathForURL(srcURL);
+            if (realSrcPath != null) {
+                File srcFile = new File(realSrcPath);
+                if (srcFile.renameTo(destFile)) {
+                    return;
+                }
+                // Trying to rename the file failed.  Possibly because we moved across file system on the device.
+            }
+        }
+
+        CordovaResourceApi.OpenForReadResult offr = resourceApi.openForRead(srcFs.toNativeUri(srcURL));
+        copyResource(offr, new FileOutputStream(destFile));
+
+        if (move) {
+            srcFs.removeFileAtLocalURL(srcURL);
+        }
+    }
+
+    private void copyDirectory(Filesystem srcFs, LocalFilesystemURL srcURL, File dstDir, boolean move) throws IOException, NoModificationAllowedException, InvalidModificationException, FileExistsException {
+        if (move) {
+            String realSrcPath = srcFs.filesystemPathForURL(srcURL);
+            if (realSrcPath != null) {
+                File srcDir = new File(realSrcPath);
+                // If the destination directory already exists and is empty then delete it.  This is according to spec.
+                if (dstDir.exists()) {
+                    if (dstDir.list().length > 0) {
+                        throw new InvalidModificationException("directory is not empty");
+                    }
+                    dstDir.delete();
+                }
+                // Try to rename the directory
+                if (srcDir.renameTo(dstDir)) {
+                    return;
+                }
+                // Trying to rename the file failed.  Possibly because we moved across file system on the device.
+            }
+        }
+
+        if (dstDir.exists()) {
+            if (dstDir.list().length > 0) {
+                throw new InvalidModificationException("directory is not empty");
+            }
+        } else {
+            if (!dstDir.mkdir()) {
+                // If we can't create the directory then fail
+                throw new NoModificationAllowedException("Couldn't create the destination directory");
+            }
+        }
+
+        LocalFilesystemURL[] children = srcFs.listChildren(srcURL);
+        for (LocalFilesystemURL childLocalUrl : children) {
+            File target = new File(dstDir, new File(childLocalUrl.path).getName());
+            if (childLocalUrl.isDirectory) {
+                copyDirectory(srcFs, childLocalUrl, target, false);
+            } else {
+                copyFile(srcFs, childLocalUrl, target, false);
+            }
+        }
+
+        if (move) {
+            srcFs.recursiveRemoveFileAtLocalURL(srcURL);
+        }
+    }
+
+	@Override
+	public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
+			Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
+
+		// Check to see if the destination directory exists
+        String newParent = this.filesystemPathForURL(destURL);
+        File destinationDir = new File(newParent);
+        if (!destinationDir.exists()) {
+            // The destination does not exist so we should fail.
+            throw new FileNotFoundException("The source does not exist");
+        }
+
+        // Figure out where we should be copying to
+        final LocalFilesystemURL destinationURL = makeDestinationURL(newName, srcURL, destURL, srcURL.isDirectory);
+
+        Uri dstNativeUri = toNativeUri(destinationURL);
+        Uri srcNativeUri = srcFs.toNativeUri(srcURL);
+        // Check to see if source and destination are the same file
+        if (dstNativeUri.equals(srcNativeUri)) {
+            throw new InvalidModificationException("Can't copy onto itself");
+        }
+
+        if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) {
+            throw new InvalidModificationException("Source URL is read-only (cannot move)");
+        }
+
+        File destFile = new File(dstNativeUri.getPath());
+        if (destFile.exists()) {
+            if (!srcURL.isDirectory && destFile.isDirectory()) {
+                throw new InvalidModificationException("Can't copy/move a file to an existing directory");
+            } else if (srcURL.isDirectory && destFile.isFile()) {
+                throw new InvalidModificationException("Can't copy/move a directory to an existing file");
+            }
+        }
+
+        if (srcURL.isDirectory) {
+            // E.g. Copy /sdcard/myDir to /sdcard/myDir/backup
+            if (dstNativeUri.toString().startsWith(srcNativeUri.toString() + '/')) {
+                throw new InvalidModificationException("Can't copy directory into itself");
+            }
+            copyDirectory(srcFs, srcURL, destFile, move);
+        } else {
+            copyFile(srcFs, srcURL, destFile, move);
+        }
+        return makeEntryForURL(destinationURL);
+	}
+
+	@Override
+	public long writeToFileAtURL(LocalFilesystemURL inputURL, String data,
+			int offset, boolean isBinary) throws IOException, NoModificationAllowedException {
+
+        boolean append = false;
+        if (offset > 0) {
+            this.truncateFileAtURL(inputURL, offset);
+            append = true;
+        }
+
+        byte[] rawData;
+        if (isBinary) {
+            rawData = Base64.decode(data, Base64.DEFAULT);
+        } else {
+            rawData = data.getBytes(Charset.defaultCharset());
+        }
+        ByteArrayInputStream in = new ByteArrayInputStream(rawData);
+        try
+        {
+        	byte buff[] = new byte[rawData.length];
+            String absolutePath = filesystemPathForURL(inputURL);
+            FileOutputStream out = new FileOutputStream(absolutePath, append);
+            try {
+            	in.read(buff, 0, buff.length);
+            	out.write(buff, 0, rawData.length);
+            	out.flush();
+            } finally {
+            	// Always close the output
+            	out.close();
+            }
+            if (isPublicDirectory(absolutePath)) {
+                broadcastNewFile(Uri.fromFile(new File(absolutePath)));
+            }
+        }
+        catch (NullPointerException e)
+        {
+            // This is a bug in the Android implementation of the Java Stack
+            NoModificationAllowedException realException = new NoModificationAllowedException(inputURL.toString());
+            realException.initCause(e);
+            throw realException;
+        }
+
+        return rawData.length;
+	}
+
+    private boolean isPublicDirectory(String absolutePath) {
+        // TODO: should expose a way to scan app's private files (maybe via a flag).
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            // Lollipop has a bug where SD cards are null.
+            for (File f : context.getExternalMediaDirs()) {
+                if(f != null && absolutePath.startsWith(f.getAbsolutePath())) {
+                    return true;
+                }
+            }
+        }
+
+        String extPath = Environment.getExternalStorageDirectory().getAbsolutePath();
+        return absolutePath.startsWith(extPath);
+    }
+
+     /**
+     * Send broadcast of new file so files appear over MTP
+     */
+    private void broadcastNewFile(Uri nativeUri) {
+        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, nativeUri);
+        context.sendBroadcast(intent);
+    }
+
+	@Override
+	public long truncateFileAtURL(LocalFilesystemURL inputURL, long size) throws IOException {
+        File file = new File(filesystemPathForURL(inputURL));
+
+        if (!file.exists()) {
+            throw new FileNotFoundException("File at " + inputURL.uri + " does not exist.");
+        }
+
+        RandomAccessFile raf = new RandomAccessFile(filesystemPathForURL(inputURL), "rw");
+        try {
+            if (raf.length() >= size) {
+                FileChannel channel = raf.getChannel();
+                channel.truncate(size);
+                return size;
+            }
+
+            return raf.length();
+        } finally {
+            raf.close();
+        }
+
+
+	}
+
+	@Override
+	public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
+		String path = filesystemPathForURL(inputURL);
+		File file = new File(path);
+		return file.exists();
+	}
+
+    // This is a copy & paste from CordovaResource API that is required since CordovaResourceApi
+    // has a bug pre-4.0.0.
+    // TODO: Once cordova-android@4.0.0 is released, delete this copy and make the plugin depend on
+    // 4.0.0 with an engine tag.
+    private static void copyResource(CordovaResourceApi.OpenForReadResult input, OutputStream outputStream) throws IOException {
+        try {
+            InputStream inputStream = input.inputStream;
+            if (inputStream instanceof FileInputStream && outputStream instanceof FileOutputStream) {
+                FileChannel inChannel = ((FileInputStream)input.inputStream).getChannel();
+                FileChannel outChannel = ((FileOutputStream)outputStream).getChannel();
+                long offset = 0;
+                long length = input.length;
+                if (input.assetFd != null) {
+                    offset = input.assetFd.getStartOffset();
+                }
+                // transferFrom()'s 2nd arg is a relative position. Need to set the absolute
+                // position first.
+                inChannel.position(offset);
+                outChannel.transferFrom(inChannel, 0, length);
+            } else {
+                final int BUFFER_SIZE = 8192;
+                byte[] buffer = new byte[BUFFER_SIZE];
+
+                for (;;) {
+                    int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
+
+                    if (bytesRead <= 0) {
+                        break;
+                    }
+                    outputStream.write(buffer, 0, bytesRead);
+                }
+            }
+        } finally {
+            input.inputStream.close();
+            if (outputStream != null) {
+                outputStream.close();
+            }
+        }
+    }
+}

+ 64 - 0
platforms/android/src/org/apache/cordova/file/LocalFilesystemURL.java

@@ -0,0 +1,64 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+ */
+package org.apache.cordova.file;
+
+import android.net.Uri;
+
+public class LocalFilesystemURL {
+	
+	public static final String FILESYSTEM_PROTOCOL = "cdvfile";
+
+    public final Uri uri;
+    public final String fsName;
+    public final String path;
+    public final boolean isDirectory;
+
+	private LocalFilesystemURL(Uri uri, String fsName, String fsPath, boolean isDirectory) {
+		this.uri = uri;
+        this.fsName = fsName;
+        this.path = fsPath;
+        this.isDirectory = isDirectory;
+	}
+
+    public static LocalFilesystemURL parse(Uri uri) {
+        if (!FILESYSTEM_PROTOCOL.equals(uri.getScheme())) {
+            return null;
+        }
+        String path = uri.getPath();
+        if (path.length() < 1) {
+            return null;
+        }
+        int firstSlashIdx = path.indexOf('/', 1);
+        if (firstSlashIdx < 0) {
+            return null;
+        }
+        String fsName = path.substring(1, firstSlashIdx);
+        path = path.substring(firstSlashIdx);
+        boolean isDirectory = path.charAt(path.length() - 1) == '/';
+        return new LocalFilesystemURL(uri, fsName, path, isDirectory);
+    }
+
+    public static LocalFilesystemURL parse(String uri) {
+        return parse(Uri.parse(uri));
+    }
+
+    public String toString() {
+        return uri.toString();
+    }
+}

+ 29 - 0
platforms/android/src/org/apache/cordova/file/NoModificationAllowedException.java

@@ -0,0 +1,29 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+package org.apache.cordova.file;
+
+@SuppressWarnings("serial")
+public class NoModificationAllowedException extends Exception {
+
+    public NoModificationAllowedException(String message) {
+        super(message);
+    }
+
+}

+ 94 - 0
platforms/android/src/org/apache/cordova/file/PendingRequests.java

@@ -0,0 +1,94 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+package org.apache.cordova.file;
+
+import android.util.SparseArray;
+
+import org.apache.cordova.CallbackContext;
+
+/**
+ * Holds pending runtime permission requests
+ */
+class PendingRequests {
+    private int currentReqId = 0;
+    private SparseArray<Request> requests = new SparseArray<Request>();
+
+    /**
+     * Creates a request and adds it to the array of pending requests. Each created request gets a
+     * unique result code for use with requestPermission()
+     * @param rawArgs           The raw arguments passed to the plugin
+     * @param action            The action this request corresponds to (get file, etc.)
+     * @param callbackContext   The CallbackContext for this plugin call
+     * @return                  The request code that can be used to retrieve the Request object
+     */
+    public synchronized int createRequest(String rawArgs, int action, CallbackContext callbackContext)  {
+        Request req = new Request(rawArgs, action, callbackContext);
+        requests.put(req.requestCode, req);
+        return req.requestCode;
+    }
+
+    /**
+     * Gets the request corresponding to this request code and removes it from the pending requests
+     * @param requestCode   The request code for the desired request
+     * @return              The request corresponding to the given request code or null if such a
+     *                      request is not found
+     */
+    public synchronized Request getAndRemove(int requestCode) {
+        Request result = requests.get(requestCode);
+        requests.remove(requestCode);
+        return result;
+    }
+
+    /**
+     * Holds the options and CallbackContext for a call made to the plugin.
+     */
+    public class Request {
+
+        // Unique int used to identify this request in any Android permission callback
+        private int requestCode;
+
+        // Action to be performed after permission request result
+        private int action;
+
+        // Raw arguments passed to plugin
+        private String rawArgs;
+
+        // The callback context for this plugin request
+        private CallbackContext callbackContext;
+
+        private Request(String rawArgs, int action, CallbackContext callbackContext) {
+            this.rawArgs = rawArgs;
+            this.action = action;
+            this.callbackContext = callbackContext;
+            this.requestCode = currentReqId ++;
+        }
+
+        public int getAction() {
+            return this.action;
+        }
+
+        public String getRawArgs() {
+            return rawArgs;
+        }
+
+        public CallbackContext getCallbackContext() {
+            return callbackContext;
+        }
+    }
+}

+ 30 - 0
platforms/android/src/org/apache/cordova/file/TypeMismatchException.java

@@ -0,0 +1,30 @@
+/*
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+*/
+
+
+package org.apache.cordova.file;
+
+@SuppressWarnings("serial")
+public class TypeMismatchException extends Exception {
+
+    public TypeMismatchException(String message) {
+        super(message);
+    }
+
+}

+ 3 - 3
platforms/ios/frameworks.json

@@ -2,13 +2,13 @@
     "ImageIO.framework": 1,
     "CoreLocation.framework": 1,
     "AVFoundation.framework": 1,
-    "WebKit.framework": 1,
-    "QuartzCore.framework": 1,
     "libz.tbd": 1,
     "libsqlite3.0.tbd": 1,
     "CoreTelephony.framework": 1,
     "SystemConfiguration.framework": 1,
     "Security.framework": 1,
     "CFNetwork.framework": 1,
-    "libstdc++.6.tbd": 1
+    "libstdc++.6.tbd": 1,
+    "WebKit.framework": 2,
+    "QuartzCore.framework": 1
 }

+ 231 - 36
platforms/ios/ios.json

@@ -5,6 +5,34 @@
   },
   "config_munge": {
     "files": {
+      "*-Info.plist": {
+        "parents": {
+          "CFBundleLocalizations": [
+            {
+              "xml": "<array><string>zh_CN</string></array>",
+              "count": 11
+            }
+          ],
+          "LSApplicationQueriesSchemes": [
+            {
+              "xml": "<array><string>weixin</string><string>wechat</string></array>",
+              "count": 1
+            }
+          ],
+          "NSAppTransportSecurity": [
+            {
+              "xml": "<dict><key>NSAllowsArbitraryLoads</key><true /></dict>",
+              "count": 1
+            }
+          ],
+          "CFBundleURLTypes": [
+            {
+              "xml": "<array><dict><key>CFBundleURLName</key><string>weixin</string><key>CFBundleURLSchemes</key><array><string>wx4f228b18e366330c</string></array></dict></array>",
+              "count": 1
+            }
+          ]
+        }
+      },
       "config.xml": {
         "parents": {
           "/*": [
@@ -24,6 +52,10 @@
               "xml": "<feature name=\"Device\"><param name=\"ios-package\" value=\"CDVDevice\" /></feature>",
               "count": 1
             },
+            {
+              "xml": "<feature name=\"File\"><param name=\"ios-package\" value=\"CDVFile\" /><param name=\"onload\" value=\"true\" /></feature>",
+              "count": 1
+            },
             {
               "xml": "<feature name=\"InAppBrowser\"><param name=\"ios-package\" value=\"CDVInAppBrowser\" /></feature>",
               "count": 1
@@ -41,45 +73,27 @@
               "count": 1
             },
             {
-              "xml": "<feature name=\"CDVWKWebViewEngine\"><param name=\"ios-package\" value=\"CDVWKWebViewEngine\" /></feature>",
+              "xml": "<feature name=\"Wechat\"><param name=\"ios-package\" value=\"CDVWechat\" /><param name=\"onload\" value=\"true\" /></feature>",
               "count": 1
             },
             {
-              "xml": "<preference name=\"CordovaWebViewEngine\" value=\"CDVWKWebViewEngine\" />",
+              "xml": "<preference name=\"WECHATAPPID\" value=\"wx4f228b18e366330c\" />",
               "count": 1
             },
             {
-              "xml": "<feature name=\"Toast\"><param name=\"ios-package\" value=\"Toast\" /></feature>",
+              "xml": "<feature name=\"CDVWKWebViewEngine\"><param name=\"ios-package\" value=\"CDVWKWebViewEngine\" /></feature>",
               "count": 1
             },
             {
-              "xml": "<feature name=\"Wechat\"><param name=\"ios-package\" value=\"CDVWechat\" /><param name=\"onload\" value=\"true\" /></feature>",
+              "xml": "<preference name=\"CordovaWebViewEngine\" value=\"CDVWKWebViewEngine\" />",
               "count": 1
             },
             {
-              "xml": "<preference name=\"WECHATAPPID\" value=\"wx4f228b18e366330c\" />",
-              "count": 1
-            }
-          ]
-        }
-      },
-      "*-Info.plist": {
-        "parents": {
-          "LSApplicationQueriesSchemes": [
-            {
-              "xml": "<array><string>weixin</string><string>wechat</string></array>",
-              "count": 1
-            }
-          ],
-          "NSAppTransportSecurity": [
-            {
-              "xml": "<dict><key>NSAllowsArbitraryLoads</key><true /></dict>",
+              "xml": "<feature name=\"Toast\"><param name=\"ios-package\" value=\"Toast\" /></feature>",
               "count": 1
-            }
-          ],
-          "CFBundleURLTypes": [
+            },
             {
-              "xml": "<array><dict><key>CFBundleURLName</key><string>weixin</string><key>CFBundleURLSchemes</key><array><string>wx4f228b18e366330c</string></array></dict></array>",
+              "xml": "<feature name=\"RemoteInjection\"><param name=\"ios-package\" value=\"CDVRemoteInjectionPlugin\" /><param name=\"onload\" value=\"true\" /></feature>",
               "count": 1
             }
           ]
@@ -103,12 +117,19 @@
     "cordova-plugin-device": {
       "PACKAGE_NAME": "com.izouma.leitongxue"
     },
+    "cordova-plugin-file": {
+      "PACKAGE_NAME": "com.izouma.leitongxue"
+    },
     "cordova-plugin-inappbrowser": {
       "PACKAGE_NAME": "com.izouma.leitongxue"
     },
     "cordova-plugin-statusbar": {
       "PACKAGE_NAME": "com.izouma.leitongxue"
     },
+    "cordova-plugin-wechatv2": {
+      "WECHATAPPID": "wx4f228b18e366330c",
+      "PACKAGE_NAME": "com.izouma.leitongxue"
+    },
     "cordova-plugin-whitelist": {
       "PACKAGE_NAME": "com.izouma.leitongxue"
     },
@@ -118,8 +139,7 @@
     "cordova-plugin-x-toast": {
       "PACKAGE_NAME": "com.izouma.leitongxue"
     },
-    "cordova-plugin-wechatv2": {
-      "WECHATAPPID": "wx4f228b18e366330c",
+    "cordova-plugin-remote-injection": {
       "PACKAGE_NAME": "com.izouma.leitongxue"
     }
   },
@@ -174,6 +194,179 @@
         "device"
       ]
     },
+    {
+      "id": "cordova-plugin-file.DirectoryEntry",
+      "file": "plugins/cordova-plugin-file/www/DirectoryEntry.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.DirectoryEntry"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.DirectoryReader",
+      "file": "plugins/cordova-plugin-file/www/DirectoryReader.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.DirectoryReader"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.Entry",
+      "file": "plugins/cordova-plugin-file/www/Entry.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.Entry"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.File",
+      "file": "plugins/cordova-plugin-file/www/File.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.File"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileEntry",
+      "file": "plugins/cordova-plugin-file/www/FileEntry.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileEntry"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileError",
+      "file": "plugins/cordova-plugin-file/www/FileError.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileError"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileReader",
+      "file": "plugins/cordova-plugin-file/www/FileReader.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileReader"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileSystem",
+      "file": "plugins/cordova-plugin-file/www/FileSystem.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileSystem"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileUploadOptions",
+      "file": "plugins/cordova-plugin-file/www/FileUploadOptions.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileUploadOptions"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileUploadResult",
+      "file": "plugins/cordova-plugin-file/www/FileUploadResult.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileUploadResult"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.FileWriter",
+      "file": "plugins/cordova-plugin-file/www/FileWriter.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.FileWriter"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.Flags",
+      "file": "plugins/cordova-plugin-file/www/Flags.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.Flags"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.LocalFileSystem",
+      "file": "plugins/cordova-plugin-file/www/LocalFileSystem.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.LocalFileSystem"
+      ],
+      "merges": [
+        "window"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.Metadata",
+      "file": "plugins/cordova-plugin-file/www/Metadata.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.Metadata"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.ProgressEvent",
+      "file": "plugins/cordova-plugin-file/www/ProgressEvent.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.ProgressEvent"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.fileSystems",
+      "file": "plugins/cordova-plugin-file/www/fileSystems.js",
+      "pluginId": "cordova-plugin-file"
+    },
+    {
+      "id": "cordova-plugin-file.requestFileSystem",
+      "file": "plugins/cordova-plugin-file/www/requestFileSystem.js",
+      "pluginId": "cordova-plugin-file",
+      "clobbers": [
+        "window.requestFileSystem"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.resolveLocalFileSystemURI",
+      "file": "plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js",
+      "pluginId": "cordova-plugin-file",
+      "merges": [
+        "window"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.isChrome",
+      "file": "plugins/cordova-plugin-file/www/browser/isChrome.js",
+      "pluginId": "cordova-plugin-file",
+      "runs": true
+    },
+    {
+      "id": "cordova-plugin-file.iosFileSystem",
+      "file": "plugins/cordova-plugin-file/www/ios/FileSystem.js",
+      "pluginId": "cordova-plugin-file",
+      "merges": [
+        "FileSystem"
+      ]
+    },
+    {
+      "id": "cordova-plugin-file.fileSystems-roots",
+      "file": "plugins/cordova-plugin-file/www/fileSystems-roots.js",
+      "pluginId": "cordova-plugin-file",
+      "runs": true
+    },
+    {
+      "id": "cordova-plugin-file.fileSystemPaths",
+      "file": "plugins/cordova-plugin-file/www/fileSystemPaths.js",
+      "pluginId": "cordova-plugin-file",
+      "merges": [
+        "cordova"
+      ],
+      "runs": true
+    },
     {
       "id": "cordova-plugin-inappbrowser.inappbrowser",
       "file": "plugins/cordova-plugin-inappbrowser/www/inappbrowser.js",
@@ -191,6 +384,14 @@
         "window.StatusBar"
       ]
     },
+    {
+      "id": "cordova-plugin-wechatv2.Wechat",
+      "file": "plugins/cordova-plugin-wechatv2/www/wechat.js",
+      "pluginId": "cordova-plugin-wechatv2",
+      "clobbers": [
+        "Wechat"
+      ]
+    },
     {
       "id": "cordova-plugin-wkwebview-engine.ios-wkwebview-exec",
       "file": "plugins/cordova-plugin-wkwebview-engine/src/www/ios/ios-wkwebview-exec.js",
@@ -214,14 +415,6 @@
       "clobbers": [
         "window.plugins.toast"
       ]
-    },
-    {
-      "id": "cordova-plugin-wechatv2.Wechat",
-      "file": "plugins/cordova-plugin-wechatv2/www/wechat.js",
-      "pluginId": "cordova-plugin-wechatv2",
-      "clobbers": [
-        "Wechat"
-      ]
     }
   ],
   "plugin_metadata": {
@@ -230,11 +423,13 @@
     "cordova-plugin-clipboard-x": "1.0.1",
     "cordova-plugin-crosswalk-webview": "2.4.0",
     "cordova-plugin-device": "2.0.2",
+    "cordova-plugin-file": "6.0.1",
     "cordova-plugin-inappbrowser": "3.0.0",
     "cordova-plugin-statusbar": "2.4.2",
+    "cordova-plugin-wechatv2": "2.1.2",
     "cordova-plugin-whitelist": "1.3.3",
     "cordova-plugin-wkwebview-engine": "1.1.4",
     "cordova-plugin-x-toast": "2.6.2",
-    "cordova-plugin-wechatv2": "2.1.2"
+    "cordova-plugin-remote-injection": "0.5.2"
   }
 }

+ 184 - 9
platforms/ios/platform_www/cordova_plugins.js

@@ -49,6 +49,179 @@ module.exports = [
       "device"
     ]
   },
+  {
+    "id": "cordova-plugin-file.DirectoryEntry",
+    "file": "plugins/cordova-plugin-file/www/DirectoryEntry.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.DirectoryEntry"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.DirectoryReader",
+    "file": "plugins/cordova-plugin-file/www/DirectoryReader.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.DirectoryReader"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.Entry",
+    "file": "plugins/cordova-plugin-file/www/Entry.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.Entry"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.File",
+    "file": "plugins/cordova-plugin-file/www/File.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.File"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileEntry",
+    "file": "plugins/cordova-plugin-file/www/FileEntry.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileEntry"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileError",
+    "file": "plugins/cordova-plugin-file/www/FileError.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileError"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileReader",
+    "file": "plugins/cordova-plugin-file/www/FileReader.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileReader"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileSystem",
+    "file": "plugins/cordova-plugin-file/www/FileSystem.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileSystem"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileUploadOptions",
+    "file": "plugins/cordova-plugin-file/www/FileUploadOptions.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileUploadOptions"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileUploadResult",
+    "file": "plugins/cordova-plugin-file/www/FileUploadResult.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileUploadResult"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.FileWriter",
+    "file": "plugins/cordova-plugin-file/www/FileWriter.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.FileWriter"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.Flags",
+    "file": "plugins/cordova-plugin-file/www/Flags.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.Flags"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.LocalFileSystem",
+    "file": "plugins/cordova-plugin-file/www/LocalFileSystem.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.LocalFileSystem"
+    ],
+    "merges": [
+      "window"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.Metadata",
+    "file": "plugins/cordova-plugin-file/www/Metadata.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.Metadata"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.ProgressEvent",
+    "file": "plugins/cordova-plugin-file/www/ProgressEvent.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.ProgressEvent"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.fileSystems",
+    "file": "plugins/cordova-plugin-file/www/fileSystems.js",
+    "pluginId": "cordova-plugin-file"
+  },
+  {
+    "id": "cordova-plugin-file.requestFileSystem",
+    "file": "plugins/cordova-plugin-file/www/requestFileSystem.js",
+    "pluginId": "cordova-plugin-file",
+    "clobbers": [
+      "window.requestFileSystem"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.resolveLocalFileSystemURI",
+    "file": "plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js",
+    "pluginId": "cordova-plugin-file",
+    "merges": [
+      "window"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.isChrome",
+    "file": "plugins/cordova-plugin-file/www/browser/isChrome.js",
+    "pluginId": "cordova-plugin-file",
+    "runs": true
+  },
+  {
+    "id": "cordova-plugin-file.iosFileSystem",
+    "file": "plugins/cordova-plugin-file/www/ios/FileSystem.js",
+    "pluginId": "cordova-plugin-file",
+    "merges": [
+      "FileSystem"
+    ]
+  },
+  {
+    "id": "cordova-plugin-file.fileSystems-roots",
+    "file": "plugins/cordova-plugin-file/www/fileSystems-roots.js",
+    "pluginId": "cordova-plugin-file",
+    "runs": true
+  },
+  {
+    "id": "cordova-plugin-file.fileSystemPaths",
+    "file": "plugins/cordova-plugin-file/www/fileSystemPaths.js",
+    "pluginId": "cordova-plugin-file",
+    "merges": [
+      "cordova"
+    ],
+    "runs": true
+  },
   {
     "id": "cordova-plugin-inappbrowser.inappbrowser",
     "file": "plugins/cordova-plugin-inappbrowser/www/inappbrowser.js",
@@ -66,6 +239,14 @@ module.exports = [
       "window.StatusBar"
     ]
   },
+  {
+    "id": "cordova-plugin-wechatv2.Wechat",
+    "file": "plugins/cordova-plugin-wechatv2/www/wechat.js",
+    "pluginId": "cordova-plugin-wechatv2",
+    "clobbers": [
+      "Wechat"
+    ]
+  },
   {
     "id": "cordova-plugin-wkwebview-engine.ios-wkwebview-exec",
     "file": "plugins/cordova-plugin-wkwebview-engine/src/www/ios/ios-wkwebview-exec.js",
@@ -89,14 +270,6 @@ module.exports = [
     "clobbers": [
       "window.plugins.toast"
     ]
-  },
-  {
-    "id": "cordova-plugin-wechatv2.Wechat",
-    "file": "plugins/cordova-plugin-wechatv2/www/wechat.js",
-    "pluginId": "cordova-plugin-wechatv2",
-    "clobbers": [
-      "Wechat"
-    ]
   }
 ];
 module.exports.metadata = 
@@ -107,12 +280,14 @@ module.exports.metadata =
   "cordova-plugin-clipboard-x": "1.0.1",
   "cordova-plugin-crosswalk-webview": "2.4.0",
   "cordova-plugin-device": "2.0.2",
+  "cordova-plugin-file": "6.0.1",
   "cordova-plugin-inappbrowser": "3.0.0",
   "cordova-plugin-statusbar": "2.4.2",
+  "cordova-plugin-wechatv2": "2.1.2",
   "cordova-plugin-whitelist": "1.3.3",
   "cordova-plugin-wkwebview-engine": "1.1.4",
   "cordova-plugin-x-toast": "2.6.2",
-  "cordova-plugin-wechatv2": "2.1.2"
+  "cordova-plugin-remote-injection": "0.5.2"
 };
 // BOTTOM OF METADATA
 });

+ 120 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/DirectoryEntry.js

@@ -0,0 +1,120 @@
+cordova.define("cordova-plugin-file.DirectoryEntry", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var argscheck = require('cordova/argscheck');
+var utils = require('cordova/utils');
+var exec = require('cordova/exec');
+var Entry = require('./Entry');
+var FileError = require('./FileError');
+var DirectoryReader = require('./DirectoryReader');
+
+/**
+ * An interface representing a directory on the file system.
+ *
+ * {boolean} isFile always false (readonly)
+ * {boolean} isDirectory always true (readonly)
+ * {DOMString} name of the directory, excluding the path leading to it (readonly)
+ * {DOMString} fullPath the absolute full path to the directory (readonly)
+ * {FileSystem} filesystem on which the directory resides (readonly)
+ */
+var DirectoryEntry = function (name, fullPath, fileSystem, nativeURL) {
+
+    // add trailing slash if it is missing
+    if ((fullPath) && !/\/$/.test(fullPath)) {
+        fullPath += '/';
+    }
+    // add trailing slash if it is missing
+    if (nativeURL && !/\/$/.test(nativeURL)) {
+        nativeURL += '/';
+    }
+    DirectoryEntry.__super__.constructor.call(this, false, true, name, fullPath, fileSystem, nativeURL);
+};
+
+utils.extend(DirectoryEntry, Entry);
+
+/**
+ * Creates a new DirectoryReader to read entries from this directory
+ */
+DirectoryEntry.prototype.createReader = function () {
+    return new DirectoryReader(this.toInternalURL());
+};
+
+/**
+ * Creates or looks up a directory
+ *
+ * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory
+ * @param {Flags} options to create or exclusively create the directory
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.getDirectory = function (path, options, successCallback, errorCallback) {
+    argscheck.checkArgs('sOFF', 'DirectoryEntry.getDirectory', arguments);
+    var fs = this.filesystem;
+    var win = successCallback && function (result) {
+        var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
+        successCallback(entry);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'getDirectory', [this.toInternalURL(), path, options]);
+};
+
+/**
+ * Deletes a directory and all of it's contents
+ *
+ * @param {Function} successCallback is called with no parameters
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.removeRecursively = function (successCallback, errorCallback) {
+    argscheck.checkArgs('FF', 'DirectoryEntry.removeRecursively', arguments);
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(successCallback, fail, 'File', 'removeRecursively', [this.toInternalURL()]);
+};
+
+/**
+ * Creates or looks up a file
+ *
+ * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file
+ * @param {Flags} options to create or exclusively create the file
+ * @param {Function} successCallback is called with the new entry
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryEntry.prototype.getFile = function (path, options, successCallback, errorCallback) {
+    argscheck.checkArgs('sOFF', 'DirectoryEntry.getFile', arguments);
+    var fs = this.filesystem;
+    var win = successCallback && function (result) {
+        var FileEntry = require('./FileEntry');
+        var entry = new FileEntry(result.name, result.fullPath, fs, result.nativeURL);
+        successCallback(entry);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'getFile', [this.toInternalURL(), path, options]);
+};
+
+module.exports = DirectoryEntry;
+
+});

+ 75 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/DirectoryReader.js

@@ -0,0 +1,75 @@
+cordova.define("cordova-plugin-file.DirectoryReader", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var exec = require('cordova/exec');
+var FileError = require('./FileError');
+
+/**
+ * An interface that lists the files and directories in a directory.
+ */
+function DirectoryReader (localURL) {
+    this.localURL = localURL || null;
+    this.hasReadEntries = false;
+}
+
+/**
+ * Returns a list of entries from a directory.
+ *
+ * @param {Function} successCallback is called with a list of entries
+ * @param {Function} errorCallback is called with a FileError
+ */
+DirectoryReader.prototype.readEntries = function (successCallback, errorCallback) {
+    // If we've already read and passed on this directory's entries, return an empty list.
+    if (this.hasReadEntries) {
+        successCallback([]);
+        return;
+    }
+    var reader = this;
+    var win = typeof successCallback !== 'function' ? null : function (result) {
+        var retVal = [];
+        for (var i = 0; i < result.length; i++) {
+            var entry = null;
+            if (result[i].isDirectory) {
+                entry = new (require('./DirectoryEntry'))();
+            } else if (result[i].isFile) {
+                entry = new (require('./FileEntry'))();
+            }
+            entry.isDirectory = result[i].isDirectory;
+            entry.isFile = result[i].isFile;
+            entry.name = result[i].name;
+            entry.fullPath = result[i].fullPath;
+            entry.filesystem = new (require('./FileSystem'))(result[i].filesystemName);
+            entry.nativeURL = result[i].nativeURL;
+            retVal.push(entry);
+        }
+        reader.hasReadEntries = true;
+        successCallback(retVal);
+    };
+    var fail = typeof errorCallback !== 'function' ? null : function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'readEntries', [this.localURL]);
+};
+
+module.exports = DirectoryReader;
+
+});

+ 263 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/Entry.js

@@ -0,0 +1,263 @@
+cordova.define("cordova-plugin-file.Entry", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var argscheck = require('cordova/argscheck');
+var exec = require('cordova/exec');
+var FileError = require('./FileError');
+var Metadata = require('./Metadata');
+
+/**
+ * Represents a file or directory on the local file system.
+ *
+ * @param isFile
+ *            {boolean} true if Entry is a file (readonly)
+ * @param isDirectory
+ *            {boolean} true if Entry is a directory (readonly)
+ * @param name
+ *            {DOMString} name of the file or directory, excluding the path
+ *            leading to it (readonly)
+ * @param fullPath
+ *            {DOMString} the absolute full path to the file or directory
+ *            (readonly)
+ * @param fileSystem
+ *            {FileSystem} the filesystem on which this entry resides
+ *            (readonly)
+ * @param nativeURL
+ *            {DOMString} an alternate URL which can be used by native
+ *            webview controls, for example media players.
+ *            (optional, readonly)
+ */
+function Entry (isFile, isDirectory, name, fullPath, fileSystem, nativeURL) {
+    this.isFile = !!isFile;
+    this.isDirectory = !!isDirectory;
+    this.name = name || '';
+    this.fullPath = fullPath || '';
+    this.filesystem = fileSystem || null;
+    this.nativeURL = nativeURL || null;
+}
+
+/**
+ * Look up the metadata of the entry.
+ *
+ * @param successCallback
+ *            {Function} is called with a Metadata object
+ * @param errorCallback
+ *            {Function} is called with a FileError
+ */
+Entry.prototype.getMetadata = function (successCallback, errorCallback) {
+    argscheck.checkArgs('FF', 'Entry.getMetadata', arguments);
+    var success = successCallback && function (entryMetadata) {
+        var metadata = new Metadata({
+            size: entryMetadata.size,
+            modificationTime: entryMetadata.lastModifiedDate
+        });
+        successCallback(metadata);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(success, fail, 'File', 'getFileMetadata', [this.toInternalURL()]);
+};
+
+/**
+ * Set the metadata of the entry.
+ *
+ * @param successCallback
+ *            {Function} is called with a Metadata object
+ * @param errorCallback
+ *            {Function} is called with a FileError
+ * @param metadataObject
+ *            {Object} keys and values to set
+ */
+Entry.prototype.setMetadata = function (successCallback, errorCallback, metadataObject) {
+    argscheck.checkArgs('FFO', 'Entry.setMetadata', arguments);
+    exec(successCallback, errorCallback, 'File', 'setMetadata', [this.toInternalURL(), metadataObject]);
+};
+
+/**
+ * Move a file or directory to a new location.
+ *
+ * @param parent
+ *            {DirectoryEntry} the directory to which to move this entry
+ * @param newName
+ *            {DOMString} new name of the entry, defaults to the current name
+ * @param successCallback
+ *            {Function} called with the new DirectoryEntry object
+ * @param errorCallback
+ *            {Function} called with a FileError
+ */
+Entry.prototype.moveTo = function (parent, newName, successCallback, errorCallback) {
+    argscheck.checkArgs('oSFF', 'Entry.moveTo', arguments);
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    var srcURL = this.toInternalURL();
+    // entry name
+    var name = newName || this.name;
+    var success = function (entry) {
+        if (entry) {
+            if (successCallback) {
+                // create appropriate Entry object
+                var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
+                var fs = newFSName ? new FileSystem(newFSName, { name: '', fullPath: '/' }) : new FileSystem(parent.filesystem.name, { name: '', fullPath: '/' }); // eslint-disable-line no-undef
+                var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
+                successCallback(result);
+            }
+        } else {
+            // no Entry object returned
+            if (fail) {
+                fail(FileError.NOT_FOUND_ERR);
+            }
+        }
+    };
+
+    // copy
+    exec(success, fail, 'File', 'moveTo', [srcURL, parent.toInternalURL(), name]);
+};
+
+/**
+ * Copy a directory to a different location.
+ *
+ * @param parent
+ *            {DirectoryEntry} the directory to which to copy the entry
+ * @param newName
+ *            {DOMString} new name of the entry, defaults to the current name
+ * @param successCallback
+ *            {Function} called with the new Entry object
+ * @param errorCallback
+ *            {Function} called with a FileError
+ */
+Entry.prototype.copyTo = function (parent, newName, successCallback, errorCallback) {
+    argscheck.checkArgs('oSFF', 'Entry.copyTo', arguments);
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    var srcURL = this.toInternalURL();
+        // entry name
+    var name = newName || this.name;
+    // success callback
+    var success = function (entry) {
+        if (entry) {
+            if (successCallback) {
+                // create appropriate Entry object
+                var newFSName = entry.filesystemName || (entry.filesystem && entry.filesystem.name);
+                var fs = newFSName ? new FileSystem(newFSName, { name: '', fullPath: '/' }) : new FileSystem(parent.filesystem.name, { name: '', fullPath: '/' }); // eslint-disable-line no-undef
+                var result = (entry.isDirectory) ? new (require('./DirectoryEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL) : new (require('cordova-plugin-file.FileEntry'))(entry.name, entry.fullPath, fs, entry.nativeURL);
+                successCallback(result);
+            }
+        } else {
+            // no Entry object returned
+            if (fail) {
+                fail(FileError.NOT_FOUND_ERR);
+            }
+        }
+    };
+
+    // copy
+    exec(success, fail, 'File', 'copyTo', [srcURL, parent.toInternalURL(), name]);
+};
+
+/**
+ * Return a URL that can be passed across the bridge to identify this entry.
+ */
+Entry.prototype.toInternalURL = function () {
+    if (this.filesystem && this.filesystem.__format__) {
+        return this.filesystem.__format__(this.fullPath, this.nativeURL);
+    }
+};
+
+/**
+ * Return a URL that can be used to identify this entry.
+ * Use a URL that can be used to as the src attribute of a <video> or
+ * <audio> tag. If that is not possible, construct a cdvfile:// URL.
+ */
+Entry.prototype.toURL = function () {
+    if (this.nativeURL) {
+        return this.nativeURL;
+    }
+    // fullPath attribute may contain the full URL in the case that
+    // toInternalURL fails.
+    return this.toInternalURL() || 'file://localhost' + this.fullPath;
+};
+
+/**
+ * Backwards-compatibility: In v1.0.0 - 1.0.2, .toURL would only return a
+ * cdvfile:// URL, and this method was necessary to obtain URLs usable by the
+ * webview.
+ * See CB-6051, CB-6106, CB-6117, CB-6152, CB-6199, CB-6201, CB-6243, CB-6249,
+ * and CB-6300.
+ */
+Entry.prototype.toNativeURL = function () {
+    console.log("DEPRECATED: Update your code to use 'toURL'");
+    return this.toURL();
+};
+
+/**
+ * Returns a URI that can be used to identify this entry.
+ *
+ * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI.
+ * @return uri
+ */
+Entry.prototype.toURI = function (mimeType) {
+    console.log("DEPRECATED: Update your code to use 'toURL'");
+    return this.toURL();
+};
+
+/**
+ * Remove a file or directory. It is an error to attempt to delete a
+ * directory that is not empty. It is an error to attempt to delete a
+ * root directory of a file system.
+ *
+ * @param successCallback {Function} called with no parameters
+ * @param errorCallback {Function} called with a FileError
+ */
+Entry.prototype.remove = function (successCallback, errorCallback) {
+    argscheck.checkArgs('FF', 'Entry.remove', arguments);
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(successCallback, fail, 'File', 'remove', [this.toInternalURL()]);
+};
+
+/**
+ * Look up the parent DirectoryEntry of this entry.
+ *
+ * @param successCallback {Function} called with the parent DirectoryEntry object
+ * @param errorCallback {Function} called with a FileError
+ */
+Entry.prototype.getParent = function (successCallback, errorCallback) {
+    argscheck.checkArgs('FF', 'Entry.getParent', arguments);
+    var fs = this.filesystem;
+    var win = successCallback && function (result) {
+        var DirectoryEntry = require('./DirectoryEntry');
+        var entry = new DirectoryEntry(result.name, result.fullPath, fs, result.nativeURL);
+        successCallback(entry);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'getParent', [this.toInternalURL()]);
+};
+
+module.exports = Entry;
+
+});

+ 81 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/File.js

@@ -0,0 +1,81 @@
+cordova.define("cordova-plugin-file.File", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * Constructor.
+ * name {DOMString} name of the file, without path information
+ * fullPath {DOMString} the full path of the file, including the name
+ * type {DOMString} mime type
+ * lastModifiedDate {Date} last modified date
+ * size {Number} size of the file in bytes
+ */
+
+var File = function (name, localURL, type, lastModifiedDate, size) {
+    this.name = name || '';
+    this.localURL = localURL || null;
+    this.type = type || null;
+    this.lastModified = lastModifiedDate || null;
+    // For backwards compatibility, store the timestamp in lastModifiedDate as well
+    this.lastModifiedDate = lastModifiedDate || null;
+    this.size = size || 0;
+
+    // These store the absolute start and end for slicing the file.
+    this.start = 0;
+    this.end = this.size;
+};
+
+/**
+ * Returns a "slice" of the file. Since Cordova Files don't contain the actual
+ * content, this really returns a File with adjusted start and end.
+ * Slices of slices are supported.
+ * start {Number} The index at which to start the slice (inclusive).
+ * end {Number} The index at which to end the slice (exclusive).
+ */
+File.prototype.slice = function (start, end) {
+    var size = this.end - this.start;
+    var newStart = 0;
+    var newEnd = size;
+    if (arguments.length) {
+        if (start < 0) {
+            newStart = Math.max(size + start, 0);
+        } else {
+            newStart = Math.min(size, start);
+        }
+    }
+
+    if (arguments.length >= 2) {
+        if (end < 0) {
+            newEnd = Math.max(size + end, 0);
+        } else {
+            newEnd = Math.min(end, size);
+        }
+    }
+
+    var newFile = new File(this.name, this.localURL, this.type, this.lastModified, this.size);
+    newFile.start = this.start + newStart;
+    newFile.end = this.start + newEnd;
+    return newFile;
+};
+
+module.exports = File;
+
+});

+ 95 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileEntry.js

@@ -0,0 +1,95 @@
+cordova.define("cordova-plugin-file.FileEntry", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var utils = require('cordova/utils');
+var exec = require('cordova/exec');
+var Entry = require('./Entry');
+var FileWriter = require('./FileWriter');
+var File = require('./File');
+var FileError = require('./FileError');
+
+/**
+ * An interface representing a file on the file system.
+ *
+ * {boolean} isFile always true (readonly)
+ * {boolean} isDirectory always false (readonly)
+ * {DOMString} name of the file, excluding the path leading to it (readonly)
+ * {DOMString} fullPath the absolute full path to the file (readonly)
+ * {FileSystem} filesystem on which the file resides (readonly)
+ */
+var FileEntry = function (name, fullPath, fileSystem, nativeURL) {
+    // remove trailing slash if it is present
+    if (fullPath && /\/$/.test(fullPath)) {
+        fullPath = fullPath.substring(0, fullPath.length - 1);
+    }
+    if (nativeURL && /\/$/.test(nativeURL)) {
+        nativeURL = nativeURL.substring(0, nativeURL.length - 1);
+    }
+
+    FileEntry.__super__.constructor.apply(this, [true, false, name, fullPath, fileSystem, nativeURL]);
+};
+
+utils.extend(FileEntry, Entry);
+
+/**
+ * Creates a new FileWriter associated with the file that this FileEntry represents.
+ *
+ * @param {Function} successCallback is called with the new FileWriter
+ * @param {Function} errorCallback is called with a FileError
+ */
+FileEntry.prototype.createWriter = function (successCallback, errorCallback) {
+    this.file(function (filePointer) {
+        var writer = new FileWriter(filePointer);
+
+        if (writer.localURL === null || writer.localURL === '') {
+            if (errorCallback) {
+                errorCallback(new FileError(FileError.INVALID_STATE_ERR));
+            }
+        } else {
+            if (successCallback) {
+                successCallback(writer);
+            }
+        }
+    }, errorCallback);
+};
+
+/**
+ * Returns a File that represents the current state of the file that this FileEntry represents.
+ *
+ * @param {Function} successCallback is called with the new File object
+ * @param {Function} errorCallback is called with a FileError
+ */
+FileEntry.prototype.file = function (successCallback, errorCallback) {
+    var localURL = this.toInternalURL();
+    var win = successCallback && function (f) {
+        var file = new File(f.name, localURL, f.type, f.lastModifiedDate, f.size);
+        successCallback(file);
+    };
+    var fail = errorCallback && function (code) {
+        errorCallback(new FileError(code));
+    };
+    exec(win, fail, 'File', 'getFileMetadata', [localURL]);
+};
+
+module.exports = FileEntry;
+
+});

+ 49 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileError.js

@@ -0,0 +1,49 @@
+cordova.define("cordova-plugin-file.FileError", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * FileError
+ */
+function FileError (error) {
+    this.code = error || null;
+}
+
+// File error codes
+// Found in DOMException
+FileError.NOT_FOUND_ERR = 1;
+FileError.SECURITY_ERR = 2;
+FileError.ABORT_ERR = 3;
+
+// Added by File API specification
+FileError.NOT_READABLE_ERR = 4;
+FileError.ENCODING_ERR = 5;
+FileError.NO_MODIFICATION_ALLOWED_ERR = 6;
+FileError.INVALID_STATE_ERR = 7;
+FileError.SYNTAX_ERR = 8;
+FileError.INVALID_MODIFICATION_ERR = 9;
+FileError.QUOTA_EXCEEDED_ERR = 10;
+FileError.TYPE_MISMATCH_ERR = 11;
+FileError.PATH_EXISTS_ERR = 12;
+
+module.exports = FileError;
+
+});

+ 301 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileReader.js

@@ -0,0 +1,301 @@
+cordova.define("cordova-plugin-file.FileReader", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var exec = require('cordova/exec');
+var modulemapper = require('cordova/modulemapper');
+var utils = require('cordova/utils');
+var FileError = require('./FileError');
+var ProgressEvent = require('./ProgressEvent');
+var origFileReader = modulemapper.getOriginalSymbol(window, 'FileReader');
+
+/**
+ * This class reads the mobile device file system.
+ *
+ * For Android:
+ *      The root directory is the root of the file system.
+ *      To read from the SD card, the file name is "sdcard/my_file.txt"
+ * @constructor
+ */
+var FileReader = function () {
+    this._readyState = 0;
+    this._error = null;
+    this._result = null;
+    this._progress = null;
+    this._localURL = '';
+    this._realReader = origFileReader ? new origFileReader() : {}; // eslint-disable-line new-cap
+};
+
+/**
+ * Defines the maximum size to read at a time via the native API. The default value is a compromise between
+ * minimizing the overhead of many exec() calls while still reporting progress frequently enough for large files.
+ * (Note attempts to allocate more than a few MB of contiguous memory on the native side are likely to cause
+ * OOM exceptions, while the JS engine seems to have fewer problems managing large strings or ArrayBuffers.)
+ */
+FileReader.READ_CHUNK_SIZE = 256 * 1024;
+
+// States
+FileReader.EMPTY = 0;
+FileReader.LOADING = 1;
+FileReader.DONE = 2;
+
+utils.defineGetter(FileReader.prototype, 'readyState', function () {
+    return this._localURL ? this._readyState : this._realReader.readyState;
+});
+
+utils.defineGetter(FileReader.prototype, 'error', function () {
+    return this._localURL ? this._error : this._realReader.error;
+});
+
+utils.defineGetter(FileReader.prototype, 'result', function () {
+    return this._localURL ? this._result : this._realReader.result;
+});
+
+function defineEvent (eventName) {
+    utils.defineGetterSetter(FileReader.prototype, eventName, function () {
+        return this._realReader[eventName] || null;
+    }, function (value) {
+        this._realReader[eventName] = value;
+    });
+}
+defineEvent('onloadstart');    // When the read starts.
+defineEvent('onprogress');     // While reading (and decoding) file or fileBlob data, and reporting partial file data (progress.loaded/progress.total)
+defineEvent('onload');         // When the read has successfully completed.
+defineEvent('onerror');        // When the read has failed (see errors).
+defineEvent('onloadend');      // When the request has completed (either in success or failure).
+defineEvent('onabort');        // When the read has been aborted. For instance, by invoking the abort() method.
+
+function initRead (reader, file) {
+    // Already loading something
+    if (reader.readyState === FileReader.LOADING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    reader._result = null;
+    reader._error = null;
+    reader._progress = 0;
+    reader._readyState = FileReader.LOADING;
+
+    if (typeof file.localURL === 'string') {
+        reader._localURL = file.localURL;
+    } else {
+        reader._localURL = '';
+        return true;
+    }
+
+    if (reader.onloadstart) {
+        reader.onloadstart(new ProgressEvent('loadstart', {target: reader}));
+    }
+}
+
+/**
+ * Callback used by the following read* functions to handle incremental or final success.
+ * Must be bound to the FileReader's this along with all but the last parameter,
+ * e.g. readSuccessCallback.bind(this, "readAsText", "UTF-8", offset, totalSize, accumulate)
+ * @param readType The name of the read function to call.
+ * @param encoding Text encoding, or null if this is not a text type read.
+ * @param offset Starting offset of the read.
+ * @param totalSize Total number of bytes or chars to read.
+ * @param accumulate A function that takes the callback result and accumulates it in this._result.
+ * @param r Callback result returned by the last read exec() call, or null to begin reading.
+ */
+function readSuccessCallback (readType, encoding, offset, totalSize, accumulate, r) {
+    if (this._readyState === FileReader.DONE) {
+        return;
+    }
+
+    var CHUNK_SIZE = FileReader.READ_CHUNK_SIZE;
+    if (readType === 'readAsDataURL') {
+        // Windows proxy does not support reading file slices as Data URLs
+        // so read the whole file at once.
+        CHUNK_SIZE = cordova.platformId === 'windows' ? totalSize : // eslint-disable-line no-undef
+            // Calculate new chunk size for data URLs to be multiply of 3
+            // Otherwise concatenated base64 chunks won't be valid base64 data
+            FileReader.READ_CHUNK_SIZE - (FileReader.READ_CHUNK_SIZE % 3) + 3;
+    }
+
+    if (typeof r !== 'undefined') {
+        accumulate(r);
+        this._progress = Math.min(this._progress + CHUNK_SIZE, totalSize);
+
+        if (typeof this.onprogress === 'function') {
+            this.onprogress(new ProgressEvent('progress', {loaded: this._progress, total: totalSize}));
+        }
+    }
+
+    if (typeof r === 'undefined' || this._progress < totalSize) {
+        var execArgs = [
+            this._localURL,
+            offset + this._progress,
+            offset + this._progress + Math.min(totalSize - this._progress, CHUNK_SIZE)];
+        if (encoding) {
+            execArgs.splice(1, 0, encoding);
+        }
+        exec(
+            readSuccessCallback.bind(this, readType, encoding, offset, totalSize, accumulate),
+            readFailureCallback.bind(this),
+            'File', readType, execArgs);
+    } else {
+        this._readyState = FileReader.DONE;
+
+        if (typeof this.onload === 'function') {
+            this.onload(new ProgressEvent('load', {target: this}));
+        }
+
+        if (typeof this.onloadend === 'function') {
+            this.onloadend(new ProgressEvent('loadend', {target: this}));
+        }
+    }
+}
+
+/**
+ * Callback used by the following read* functions to handle errors.
+ * Must be bound to the FileReader's this, e.g. readFailureCallback.bind(this)
+ */
+function readFailureCallback (e) {
+    if (this._readyState === FileReader.DONE) {
+        return;
+    }
+
+    this._readyState = FileReader.DONE;
+    this._result = null;
+    this._error = new FileError(e);
+
+    if (typeof this.onerror === 'function') {
+        this.onerror(new ProgressEvent('error', {target: this}));
+    }
+
+    if (typeof this.onloadend === 'function') {
+        this.onloadend(new ProgressEvent('loadend', {target: this}));
+    }
+}
+
+/**
+ * Abort reading file.
+ */
+FileReader.prototype.abort = function () {
+    if (origFileReader && !this._localURL) {
+        return this._realReader.abort();
+    }
+    this._result = null;
+
+    if (this._readyState === FileReader.DONE || this._readyState === FileReader.EMPTY) {
+        return;
+    }
+
+    this._readyState = FileReader.DONE;
+
+    // If abort callback
+    if (typeof this.onabort === 'function') {
+        this.onabort(new ProgressEvent('abort', {target: this}));
+    }
+    // If load end callback
+    if (typeof this.onloadend === 'function') {
+        this.onloadend(new ProgressEvent('loadend', {target: this}));
+    }
+};
+
+/**
+ * Read text file.
+ *
+ * @param file          {File} File object containing file properties
+ * @param encoding      [Optional] (see http://www.iana.org/assignments/character-sets)
+ */
+FileReader.prototype.readAsText = function (file, encoding) {
+    if (initRead(this, file)) {
+        return this._realReader.readAsText(file, encoding);
+    }
+
+    // Default encoding is UTF-8
+    var enc = encoding || 'UTF-8';
+
+    var totalSize = file.end - file.start;
+    readSuccessCallback.bind(this)('readAsText', enc, file.start, totalSize, function (r) {
+        if (this._progress === 0) {
+            this._result = '';
+        }
+        this._result += r;
+    }.bind(this));
+};
+
+/**
+ * Read file and return data as a base64 encoded data url.
+ * A data url is of the form:
+ *      data:[<mediatype>][;base64],<data>
+ *
+ * @param file          {File} File object containing file properties
+ */
+FileReader.prototype.readAsDataURL = function (file) {
+    if (initRead(this, file)) {
+        return this._realReader.readAsDataURL(file);
+    }
+
+    var totalSize = file.end - file.start;
+    readSuccessCallback.bind(this)('readAsDataURL', null, file.start, totalSize, function (r) {
+        var commaIndex = r.indexOf(',');
+        if (this._progress === 0) {
+            this._result = r;
+        } else {
+            this._result += r.substring(commaIndex + 1);
+        }
+    }.bind(this));
+};
+
+/**
+ * Read file and return data as a binary data.
+ *
+ * @param file          {File} File object containing file properties
+ */
+FileReader.prototype.readAsBinaryString = function (file) {
+    if (initRead(this, file)) {
+        return this._realReader.readAsBinaryString(file);
+    }
+
+    var totalSize = file.end - file.start;
+    readSuccessCallback.bind(this)('readAsBinaryString', null, file.start, totalSize, function (r) {
+        if (this._progress === 0) {
+            this._result = '';
+        }
+        this._result += r;
+    }.bind(this));
+};
+
+/**
+ * Read file and return data as a binary data.
+ *
+ * @param file          {File} File object containing file properties
+ */
+FileReader.prototype.readAsArrayBuffer = function (file) {
+    if (initRead(this, file)) {
+        return this._realReader.readAsArrayBuffer(file);
+    }
+
+    var totalSize = file.end - file.start;
+    readSuccessCallback.bind(this)('readAsArrayBuffer', null, file.start, totalSize, function (r) {
+        var resultArray = (this._progress === 0 ? new Uint8Array(totalSize) : new Uint8Array(this._result));
+        resultArray.set(new Uint8Array(r), this._progress);
+        this._result = resultArray.buffer;
+    }.bind(this));
+};
+
+module.exports = FileReader;
+
+});

+ 58 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileSystem.js

@@ -0,0 +1,58 @@
+cordova.define("cordova-plugin-file.FileSystem", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var DirectoryEntry = require('./DirectoryEntry');
+
+/**
+ * An interface representing a file system
+ *
+ * @constructor
+ * {DOMString} name the unique name of the file system (readonly)
+ * {DirectoryEntry} root directory of the file system (readonly)
+ */
+var FileSystem = function (name, root) {
+    this.name = name;
+    if (root) {
+        this.root = new DirectoryEntry(root.name, root.fullPath, this, root.nativeURL);
+    } else {
+        this.root = new DirectoryEntry(this.name, '/', this);
+    }
+};
+
+FileSystem.prototype.__format__ = function (fullPath, nativeUrl) {
+    return fullPath;
+};
+
+FileSystem.prototype.toJSON = function () {
+    return '<FileSystem: ' + this.name + '>';
+};
+
+// Use instead of encodeURI() when encoding just the path part of a URI rather than an entire URI.
+FileSystem.encodeURIPath = function (path) {
+    // Because # is a valid filename character, it must be encoded to prevent part of the
+    // path from being parsed as a URI fragment.
+    return encodeURI(path).replace(/#/g, '%23');
+};
+
+module.exports = FileSystem;
+
+});

+ 44 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileUploadOptions.js

@@ -0,0 +1,44 @@
+cordova.define("cordova-plugin-file.FileUploadOptions", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * Options to customize the HTTP request used to upload files.
+ * @constructor
+ * @param fileKey {String}   Name of file request parameter.
+ * @param fileName {String}  Filename to be used by the server. Defaults to image.jpg.
+ * @param mimeType {String}  Mimetype of the uploaded file. Defaults to image/jpeg.
+ * @param params {Object}    Object with key: value params to send to the server.
+ * @param headers {Object}   Keys are header names, values are header values. Multiple
+ *                           headers of the same name are not supported.
+ */
+var FileUploadOptions = function (fileKey, fileName, mimeType, params, headers, httpMethod) {
+    this.fileKey = fileKey || null;
+    this.fileName = fileName || null;
+    this.mimeType = mimeType || null;
+    this.params = params || null;
+    this.headers = headers || null;
+    this.httpMethod = httpMethod || null;
+};
+
+module.exports = FileUploadOptions;
+
+});

+ 33 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileUploadResult.js

@@ -0,0 +1,33 @@
+cordova.define("cordova-plugin-file.FileUploadResult", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * FileUploadResult
+ * @constructor
+ */
+module.exports = function FileUploadResult (size, code, content) {
+    this.bytesSent = size;
+    this.responseCode = code;
+    this.response = content;
+};
+
+});

+ 327 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/FileWriter.js

@@ -0,0 +1,327 @@
+cordova.define("cordova-plugin-file.FileWriter", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var exec = require('cordova/exec');
+var FileError = require('./FileError');
+var ProgressEvent = require('./ProgressEvent');
+
+/**
+ * This class writes to the mobile device file system.
+ *
+ * For Android:
+ *      The root directory is the root of the file system.
+ *      To write to the SD card, the file name is "sdcard/my_file.txt"
+ *
+ * @constructor
+ * @param file {File} File object containing file properties
+ * @param append if true write to the end of the file, otherwise overwrite the file
+ */
+var FileWriter = function (file) {
+    this.fileName = '';
+    this.length = 0;
+    if (file) {
+        this.localURL = file.localURL || file;
+        this.length = file.size || 0;
+    }
+    // default is to write at the beginning of the file
+    this.position = 0;
+
+    this.readyState = 0; // EMPTY
+
+    this.result = null;
+
+    // Error
+    this.error = null;
+
+    // Event handlers
+    this.onwritestart = null;   // When writing starts
+    this.onprogress = null;     // While writing the file, and reporting partial file data
+    this.onwrite = null;        // When the write has successfully completed.
+    this.onwriteend = null;     // When the request has completed (either in success or failure).
+    this.onabort = null;        // When the write has been aborted. For instance, by invoking the abort() method.
+    this.onerror = null;        // When the write has failed (see errors).
+};
+
+// States
+FileWriter.INIT = 0;
+FileWriter.WRITING = 1;
+FileWriter.DONE = 2;
+
+/**
+ * Abort writing file.
+ */
+FileWriter.prototype.abort = function () {
+    // check for invalid state
+    if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // set error
+    this.error = new FileError(FileError.ABORT_ERR);
+
+    this.readyState = FileWriter.DONE;
+
+    // If abort callback
+    if (typeof this.onabort === 'function') {
+        this.onabort(new ProgressEvent('abort', {'target': this}));
+    }
+
+    // If write end callback
+    if (typeof this.onwriteend === 'function') {
+        this.onwriteend(new ProgressEvent('writeend', {'target': this}));
+    }
+};
+
+/**
+ * Writes data to the file
+ *
+ * @param data text or blob to be written
+ * @param isPendingBlobReadResult {Boolean} true if the data is the pending blob read operation result
+ */
+FileWriter.prototype.write = function (data, isPendingBlobReadResult) {
+
+    var that = this;
+    var supportsBinary = (typeof window.Blob !== 'undefined' && typeof window.ArrayBuffer !== 'undefined');
+    /* eslint-disable no-undef */
+    var isProxySupportBlobNatively = (cordova.platformId === 'windows8' || cordova.platformId === 'windows');
+    var isBinary;
+
+    // Check to see if the incoming data is a blob
+    if (data instanceof File || (!isProxySupportBlobNatively && supportsBinary && data instanceof Blob)) {
+        var fileReader = new FileReader();
+        /* eslint-enable no-undef */
+        fileReader.onload = function () {
+            // Call this method again, with the arraybuffer as argument
+            FileWriter.prototype.write.call(that, this.result, true /* isPendingBlobReadResult */);
+        };
+        fileReader.onerror = function () {
+            // DONE state
+            that.readyState = FileWriter.DONE;
+
+            // Save error
+            that.error = this.error;
+
+            // If onerror callback
+            if (typeof that.onerror === 'function') {
+                that.onerror(new ProgressEvent('error', {'target': that}));
+            }
+
+            // If onwriteend callback
+            if (typeof that.onwriteend === 'function') {
+                that.onwriteend(new ProgressEvent('writeend', {'target': that}));
+            }
+        };
+
+        // WRITING state
+        this.readyState = FileWriter.WRITING;
+
+        if (supportsBinary) {
+            fileReader.readAsArrayBuffer(data);
+        } else {
+            fileReader.readAsText(data);
+        }
+        return;
+    }
+
+    // Mark data type for safer transport over the binary bridge
+    isBinary = supportsBinary && (data instanceof ArrayBuffer);
+    if (isBinary && cordova.platformId === 'windowsphone') { // eslint-disable-line no-undef
+        // create a plain array, using the keys from the Uint8Array view so that we can serialize it
+        data = Array.apply(null, new Uint8Array(data));
+    }
+
+    // Throw an exception if we are already writing a file
+    if (this.readyState === FileWriter.WRITING && !isPendingBlobReadResult) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // WRITING state
+    this.readyState = FileWriter.WRITING;
+
+    var me = this;
+
+    // If onwritestart callback
+    if (typeof me.onwritestart === 'function') {
+        me.onwritestart(new ProgressEvent('writestart', {'target': me}));
+    }
+
+    // Write file
+    exec(
+        // Success callback
+        function (r) {
+            // If DONE (cancelled), then don't do anything
+            if (me.readyState === FileWriter.DONE) {
+                return;
+            }
+
+            // position always increases by bytes written because file would be extended
+            me.position += r;
+            // The length of the file is now where we are done writing.
+
+            me.length = me.position;
+
+            // DONE state
+            me.readyState = FileWriter.DONE;
+
+            // If onwrite callback
+            if (typeof me.onwrite === 'function') {
+                me.onwrite(new ProgressEvent('write', {'target': me}));
+            }
+
+            // If onwriteend callback
+            if (typeof me.onwriteend === 'function') {
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
+            }
+        },
+        // Error callback
+        function (e) {
+            // If DONE (cancelled), then don't do anything
+            if (me.readyState === FileWriter.DONE) {
+                return;
+            }
+
+            // DONE state
+            me.readyState = FileWriter.DONE;
+
+            // Save error
+            me.error = new FileError(e);
+
+            // If onerror callback
+            if (typeof me.onerror === 'function') {
+                me.onerror(new ProgressEvent('error', {'target': me}));
+            }
+
+            // If onwriteend callback
+            if (typeof me.onwriteend === 'function') {
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
+            }
+        }, 'File', 'write', [this.localURL, data, this.position, isBinary]);
+};
+
+/**
+ * Moves the file pointer to the location specified.
+ *
+ * If the offset is a negative number the position of the file
+ * pointer is rewound.  If the offset is greater than the file
+ * size the position is set to the end of the file.
+ *
+ * @param offset is the location to move the file pointer to.
+ */
+FileWriter.prototype.seek = function (offset) {
+    // Throw an exception if we are already writing a file
+    if (this.readyState === FileWriter.WRITING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    if (!offset && offset !== 0) {
+        return;
+    }
+
+    // See back from end of file.
+    if (offset < 0) {
+        this.position = Math.max(offset + this.length, 0);
+    // Offset is bigger than file size so set position
+    // to the end of the file.
+    } else if (offset > this.length) {
+        this.position = this.length;
+    // Offset is between 0 and file size so set the position
+    // to start writing.
+    } else {
+        this.position = offset;
+    }
+};
+
+/**
+ * Truncates the file to the size specified.
+ *
+ * @param size to chop the file at.
+ */
+FileWriter.prototype.truncate = function (size) {
+    // Throw an exception if we are already writing a file
+    if (this.readyState === FileWriter.WRITING) {
+        throw new FileError(FileError.INVALID_STATE_ERR);
+    }
+
+    // WRITING state
+    this.readyState = FileWriter.WRITING;
+
+    var me = this;
+
+    // If onwritestart callback
+    if (typeof me.onwritestart === 'function') {
+        me.onwritestart(new ProgressEvent('writestart', {'target': this}));
+    }
+
+    // Write file
+    exec(
+        // Success callback
+        function (r) {
+            // If DONE (cancelled), then don't do anything
+            if (me.readyState === FileWriter.DONE) {
+                return;
+            }
+
+            // DONE state
+            me.readyState = FileWriter.DONE;
+
+            // Update the length of the file
+            me.length = r;
+            me.position = Math.min(me.position, r);
+
+            // If onwrite callback
+            if (typeof me.onwrite === 'function') {
+                me.onwrite(new ProgressEvent('write', {'target': me}));
+            }
+
+            // If onwriteend callback
+            if (typeof me.onwriteend === 'function') {
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
+            }
+        },
+        // Error callback
+        function (e) {
+            // If DONE (cancelled), then don't do anything
+            if (me.readyState === FileWriter.DONE) {
+                return;
+            }
+
+            // DONE state
+            me.readyState = FileWriter.DONE;
+
+            // Save error
+            me.error = new FileError(e);
+
+            // If onerror callback
+            if (typeof me.onerror === 'function') {
+                me.onerror(new ProgressEvent('error', {'target': me}));
+            }
+
+            // If onwriteend callback
+            if (typeof me.onwriteend === 'function') {
+                me.onwriteend(new ProgressEvent('writeend', {'target': me}));
+            }
+        }, 'File', 'truncate', [this.localURL, size]);
+};
+
+module.exports = FileWriter;
+
+});

+ 39 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/Flags.js

@@ -0,0 +1,39 @@
+cordova.define("cordova-plugin-file.Flags", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * Supplies arguments to methods that lookup or create files and directories.
+ *
+ * @param create
+ *            {boolean} file or directory if it doesn't exist
+ * @param exclusive
+ *            {boolean} used with create; if true the command will fail if
+ *            target path exists
+ */
+function Flags (create, exclusive) {
+    this.create = create || false;
+    this.exclusive = exclusive || false;
+}
+
+module.exports = Flags;
+
+});

+ 26 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/LocalFileSystem.js

@@ -0,0 +1,26 @@
+cordova.define("cordova-plugin-file.LocalFileSystem", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+exports.TEMPORARY = 0;
+exports.PERSISTENT = 1;
+
+});

+ 43 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/Metadata.js

@@ -0,0 +1,43 @@
+cordova.define("cordova-plugin-file.Metadata", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+/**
+ * Information about the state of the file or directory
+ *
+ * {Date} modificationTime (readonly)
+ */
+var Metadata = function (metadata) {
+    if (typeof metadata === 'object') {
+        this.modificationTime = new Date(metadata.modificationTime);
+        this.size = metadata.size || 0;
+    } else if (typeof metadata === 'undefined') {
+        this.modificationTime = null;
+        this.size = 0;
+    } else {
+        /* Backwards compatiblity with platforms that only return a timestamp */
+        this.modificationTime = new Date(metadata);
+    }
+};
+
+module.exports = Metadata;
+
+});

+ 70 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/ProgressEvent.js

@@ -0,0 +1,70 @@
+cordova.define("cordova-plugin-file.ProgressEvent", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+// If ProgressEvent exists in global context, use it already, otherwise use our own polyfill
+// Feature test: See if we can instantiate a native ProgressEvent;
+// if so, use that approach,
+// otherwise fill-in with our own implementation.
+//
+// NOTE: right now we always fill in with our own. Down the road would be nice if we can use whatever is native in the webview.
+var ProgressEvent = (function () {
+    /*
+    var createEvent = function(data) {
+        var event = document.createEvent('Events');
+        event.initEvent('ProgressEvent', false, false);
+        if (data) {
+            for (var i in data) {
+                if (data.hasOwnProperty(i)) {
+                    event[i] = data[i];
+                }
+            }
+            if (data.target) {
+                // TODO: cannot call <some_custom_object>.dispatchEvent
+                // need to first figure out how to implement EventTarget
+            }
+        }
+        return event;
+    };
+    try {
+        var ev = createEvent({type:"abort",target:document});
+        return function ProgressEvent(type, data) {
+            data.type = type;
+            return createEvent(data);
+        };
+    } catch(e){
+    */
+    return function ProgressEvent (type, dict) {
+        this.type = type;
+        this.bubbles = false;
+        this.cancelBubble = false;
+        this.cancelable = false;
+        this.lengthComputable = false;
+        this.loaded = dict && dict.loaded ? dict.loaded : 0;
+        this.total = dict && dict.total ? dict.total : 0;
+        this.target = dict && dict.target ? dict.target : null;
+    };
+    // }
+})();
+
+module.exports = ProgressEvent;
+
+});

+ 29 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/browser/isChrome.js

@@ -0,0 +1,29 @@
+cordova.define("cordova-plugin-file.isChrome", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+module.exports = function () {
+    // window.webkitRequestFileSystem and window.webkitResolveLocalFileSystemURL are available only in Chrome and
+    // possibly a good flag to indicate that we're running in Chrome
+    return window.webkitRequestFileSystem && window.webkitResolveLocalFileSystemURL;
+};
+
+});

+ 65 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/fileSystemPaths.js

@@ -0,0 +1,65 @@
+cordova.define("cordova-plugin-file.fileSystemPaths", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+var exec = require('cordova/exec');
+var channel = require('cordova/channel');
+
+exports.file = {
+    // Read-only directory where the application is installed.
+    applicationDirectory: null,
+    // Root of app's private writable storage
+    applicationStorageDirectory: null,
+    // Where to put app-specific data files.
+    dataDirectory: null,
+    // Cached files that should survive app restarts.
+    // Apps should not rely on the OS to delete files in here.
+    cacheDirectory: null,
+    // Android: the application space on external storage.
+    externalApplicationStorageDirectory: null,
+    // Android: Where to put app-specific data files on external storage.
+    externalDataDirectory: null,
+    // Android: the application cache on external storage.
+    externalCacheDirectory: null,
+    // Android: the external storage (SD card) root.
+    externalRootDirectory: null,
+    // iOS: Temp directory that the OS can clear at will.
+    tempDirectory: null,
+    // iOS: Holds app-specific files that should be synced (e.g. to iCloud).
+    syncedDataDirectory: null,
+    // iOS: Files private to the app, but that are meaningful to other applications (e.g. Office files)
+    documentsDirectory: null,
+    // BlackBerry10: Files globally available to all apps
+    sharedDirectory: null
+};
+
+channel.waitForInitialization('onFileSystemPathsReady');
+channel.onCordovaReady.subscribe(function () {
+    function after (paths) {
+        for (var k in paths) {
+            exports.file[k] = paths[k];
+        }
+        channel.initializationComplete('onFileSystemPathsReady');
+    }
+    exec(after, null, 'File', 'requestAllPaths', []);
+});
+
+});

+ 47 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/fileSystems-roots.js

@@ -0,0 +1,47 @@
+cordova.define("cordova-plugin-file.fileSystems-roots", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+// Map of fsName -> FileSystem.
+var fsMap = null;
+var FileSystem = require('./FileSystem');
+var exec = require('cordova/exec');
+
+// Overridden by Android, BlackBerry 10 and iOS to populate fsMap.
+require('./fileSystems').getFs = function (name, callback) {
+    function success (response) {
+        fsMap = {};
+        for (var i = 0; i < response.length; ++i) {
+            var fsRoot = response[i];
+            var fs = new FileSystem(fsRoot.filesystemName, fsRoot);
+            fsMap[fs.name] = fs;
+        }
+        callback(fsMap[name]);
+    }
+
+    if (fsMap) {
+        callback(fsMap[name]);
+    } else {
+        exec(success, null, 'File', 'requestAllFileSystems', []);
+    }
+};
+
+});

+ 28 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/fileSystems.js

@@ -0,0 +1,28 @@
+cordova.define("cordova-plugin-file.fileSystems", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+// Overridden by Android, BlackBerry 10 and iOS to populate fsMap.
+module.exports.getFs = function (name, callback) {
+    callback(null);
+};
+
+});

+ 32 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/ios/FileSystem.js

@@ -0,0 +1,32 @@
+cordova.define("cordova-plugin-file.iosFileSystem", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+/* eslint no-undef : 0 */
+FILESYSTEM_PROTOCOL = 'cdvfile';
+
+module.exports = {
+    __format__: function (fullPath) {
+        var path = ('/' + this.name + (fullPath[0] === '/' ? '' : '/') + FileSystem.encodeURIPath(fullPath)).replace('//', '/');
+        return FILESYSTEM_PROTOCOL + '://localhost' + path;
+    }
+};
+
+});

+ 84 - 0
platforms/ios/platform_www/plugins/cordova-plugin-file/www/requestFileSystem.js

@@ -0,0 +1,84 @@
+cordova.define("cordova-plugin-file.requestFileSystem", function(require, exports, module) {
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+*/
+
+(function () {
+    // For browser platform: not all browsers use this file.
+    function checkBrowser () {
+        if (cordova.platformId === 'browser' && require('./isChrome')()) { // eslint-disable-line no-undef
+            module.exports = window.requestFileSystem || window.webkitRequestFileSystem;
+            return true;
+        }
+        return false;
+    }
+    if (checkBrowser()) {
+        return;
+    }
+
+    var argscheck = require('cordova/argscheck');
+    var FileError = require('./FileError');
+    var FileSystem = require('./FileSystem');
+    var exec = require('cordova/exec');
+    var fileSystems = require('./fileSystems');
+
+    /**
+     * Request a file system in which to store application data.
+     * @param type  local file system type
+     * @param size  indicates how much storage space, in bytes, the application expects to need
+     * @param successCallback  invoked with a FileSystem object
+     * @param errorCallback  invoked if error occurs retrieving file system
+     */
+    var requestFileSystem = function (type, size, successCallback, errorCallback) {
+        argscheck.checkArgs('nnFF', 'requestFileSystem', arguments);
+        var fail = function (code) {
+            if (errorCallback) {
+                errorCallback(new FileError(code));
+            }
+        };
+
+        if (type < 0) {
+            fail(FileError.SYNTAX_ERR);
+        } else {
+            // if successful, return a FileSystem object
+            var success = function (file_system) {
+                if (file_system) {
+                    if (successCallback) {
+                        fileSystems.getFs(file_system.name, function (fs) {
+                            // This should happen only on platforms that haven't implemented requestAllFileSystems (windows)
+                            if (!fs) {
+                                fs = new FileSystem(file_system.name, file_system.root);
+                            }
+                            successCallback(fs);
+                        });
+                    }
+                } else {
+                    // no FileSystem object returned
+                    fail(FileError.NOT_FOUND_ERR);
+                }
+            };
+            exec(success, fail, 'File', 'requestFileSystem', [type, size]);
+        }
+    };
+
+    module.exports = requestFileSystem;
+})();
+
+});

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio