diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index a57bdb44d6d089ca6f6b403e4b1bc1000cf9cfa9..aed22eba9fe6f20a56c9654f3abcfda2b0518260 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1,8 +1,8 @@
 <?xml version='1.0' encoding='utf-8'?>
-<manifest android:hardwareAccelerated="true" android:versionCode="106012" android:versionName="1.6.2-alpha" package="fr.duniter.cesium" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
+<manifest android:hardwareAccelerated="true" android:versionCode="106030" android:versionName="1.6.3" package="fr.duniter.cesium" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
     <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
     <uses-permission android:name="android.permission.INTERNET" />
-    <application android:hardwareAccelerated="true" android:icon="@mipmap/icon" android:label="@string/app_name" android:supportsRtl="true">
+    <application tools:replace="android:appComponentFactory" android:appComponentFactory="androidx.core.app.CoreComponentFactory" android:hardwareAccelerated="true" android:icon="@mipmap/icon" android:label="@string/app_name" android:supportsRtl="true">
         <activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale" android:label="@string/activity_name" android:launchMode="singleTop" android:name="MainActivity" android:theme="@android:style/Theme.DeviceDefault.NoActionBar" android:windowSoftInputMode="adjustResize">
             <intent-filter android:label="@string/launcher_name">
                 <action android:name="android.intent.action.MAIN" />
@@ -15,12 +15,12 @@
         <activity android:clearTaskOnLaunch="true" android:configChanges="orientation|keyboardHidden|screenSize" android:exported="false" android:name="com.google.zxing.client.android.CaptureActivity" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:windowSoftInputMode="stateAlwaysHidden" />
         <activity android:label="Share" android:name="com.google.zxing.client.android.encode.EncodeActivity" />
     </application>
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.FLASHLIGHT" />
     <uses-feature android:name="android.hardware.camera" android:required="true" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     
     
-    <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="28" tools:overrideLibrary="org.kaliumjni.lib" />
+    <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="29" tools:overrideLibrary="org.kaliumjni.lib" />
 </manifest>
diff --git a/CordovaLib/build.gradle b/CordovaLib/build.gradle
index ff6c034051b5025b82f4a011ca4dc3bfb6390d46..512d20910d034b2d63a6355f3cc4a18e03c70d56 100644
--- a/CordovaLib/build.gradle
+++ b/CordovaLib/build.gradle
@@ -25,15 +25,12 @@ ext {
 buildscript {
     repositories {
         jcenter()
-        maven {
-            url "https://maven.google.com"
-        }
         google()
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.1.2'
-        classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
+        classpath 'com.android.tools.build:gradle:3.6.2'
+        classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
         classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
     }
 }
diff --git a/android.json b/android.json
index 69eac73fc48c2c238c6c42a43105e6d6ed9339e8..a5dd9f710a3b8c87e8726f68e39aeba27887f878 100644
--- a/android.json
+++ b/android.json
@@ -67,6 +67,14 @@
             {
               "xml": "<feature name=\"BarcodeScanner\"><param name=\"android-package\" value=\"com.phonegap.plugins.barcodescanner.BarcodeScanner\" /></feature>",
               "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
             }
           ]
         }
@@ -75,11 +83,11 @@
         "parents": {
           "/*": [
             {
-              "xml": "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />",
+              "xml": "<uses-permission android:name=\"android.permission.INTERNET\" />",
               "count": 1
             },
             {
-              "xml": "<uses-permission android:name=\"android.permission.INTERNET\" />",
+              "xml": "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />",
               "count": 1
             }
           ],
@@ -210,6 +218,9 @@
     "cordova-plugin-ionic-webview": {
       "ANDROID_SUPPORT_ANNOTATIONS_VERSION": "27.+",
       "PACKAGE_NAME": "fr.duniter.cesium"
+    },
+    "cordova-plugin-file": {
+      "PACKAGE_NAME": "fr.duniter.cesium"
     }
   },
   "dependent_plugins": {},
@@ -387,6 +398,179 @@
       "clobbers": [
         "Ionic.WebView"
       ]
+    },
+    {
+      "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": {
@@ -407,6 +591,7 @@
     "ionic-plugin-keyboard": "2.2.1",
     "phonegap-plugin-barcodescanner": "7.0.0",
     "cordova-plugin-ionic-keyboard": "2.2.0",
-    "cordova-plugin-ionic-webview": "4.1.3"
+    "cordova-plugin-ionic-webview": "4.1.3",
+    "cordova-plugin-file": "6.0.2"
   }
-}
+}
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000000000000000000000000000000000000..f1f2de17f8d82f97c056ff3735a5a2de52668df7
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,70 @@
+/* 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.
+*/
+
+// GENERATED FILE! DO NOT EDIT!
+
+buildscript {
+    repositories {
+        jcenter()
+        google()
+    }
+
+    // Switch the Android Gradle plugin version requirement depending on the
+    // installed version of Gradle. This dependency is documented at
+    // http://tools.android.com/tech-docs/new-build-system/version-compatibility
+    // and https://issues.apache.org/jira/browse/CB-8143
+    dependencies {
+        classpath 'com.android.tools.build:gradle:1.0.0+'
+    }
+}
+
+apply plugin: 'com.android.library'
+
+dependencies {
+    compile fileTree(dir: 'libs', include: '*.jar')
+    debugCompile project(path: ":CordovaLib", configuration: "debug")
+    releaseCompile project(path: ":CordovaLib", configuration: "release")
+}
+
+android {
+    compileSdkVersion 29
+  buildToolsVersion '30.0.0 rc2'
+    publishNonDefault true
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_6
+      targetCompatibility JavaVersion.VERSION_1_6
+    }
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = ['src']
+            resources.srcDirs = ['src']
+            aidl.srcDirs = ['src']
+            renderscript.srcDirs = ['src']
+            res.srcDirs = ['res']
+            assets.srcDirs = ['assets']
+            jniLibs.srcDirs = ['libs']
+        }
+    }
+}
+
+if (file('build-extras.gradle').exists()) {
+    apply from: 'build-extras.gradle'
+}
diff --git a/assets/www/config.js b/assets/www/config.js
index 7fc038ed900be23c8443114c5bac47227d1092af..7b3bb56f0d871fd7e2282a2f67540ee2d4ae87b4 100644
--- a/assets/www/config.js
+++ b/assets/www/config.js
@@ -95,8 +95,8 @@ angular.module("cesium.config", [])
 			"defaultCountry": "France"
 		}
 	},
-	"version": "1.6.2",
-	"build": "2020-04-13T21:20:46.576Z",
+	"version": "1.6.3",
+	"build": "2020-04-14T15:27:14.343Z",
 	"newIssueUrl": "https://git.duniter.org/clients/cesium-grp/cesium/issues/new"
 })
 
diff --git a/assets/www/cordova_plugins.js b/assets/www/cordova_plugins.js
index ba983110e27562fc0ba420708c60bc70bb2d28b9..b649a5ce2cff6f26e98624791764b0b116c16cd1 100644
--- a/assets/www/cordova_plugins.js
+++ b/assets/www/cordova_plugins.js
@@ -173,6 +173,179 @@ module.exports = [
     "clobbers": [
       "Ionic.WebView"
     ]
+  },
+  {
+    "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 = 
@@ -195,7 +368,8 @@ module.exports.metadata =
   "ionic-plugin-keyboard": "2.2.1",
   "phonegap-plugin-barcodescanner": "7.0.0",
   "cordova-plugin-ionic-keyboard": "2.2.0",
-  "cordova-plugin-ionic-webview": "4.1.3"
+  "cordova-plugin-ionic-webview": "4.1.3",
+  "cordova-plugin-file": "6.0.2"
 };
 // BOTTOM OF METADATA
 });
\ No newline at end of file
diff --git a/assets/www/dist_js/cesium.js b/assets/www/dist_js/cesium.js
index 66a4da938ffc711b6c4fa517dc29244b6f8da2be..b462bdc2992aedc8a86a90fa3ff27512e6660563 100644
--- a/assets/www/dist_js/cesium.js
+++ b/assets/www/dist_js/cesium.js
@@ -21039,17 +21039,6 @@ $templateCache.put('templates/blockchain/lookup.html','<ion-view><ion-nav-title>
 $templateCache.put('templates/blockchain/lookup_lg.html','<ion-view><ion-nav-title><span translate>BLOCKCHAIN.LOOKUP.TITLE</span></ion-nav-title><ion-content class="padding no-padding-xs no-padding-sm" scroll="true"><ng-include src="::\'templates/blockchain/list_blocks_lg.html\'"></ng-include></ion-content></ion-view>');
 $templateCache.put('templates/blockchain/unlock_condition_popover.html','<ion-popover-view class="fit"><ion-header-bar><h1 class="title" translate>BLOCKCHAIN.VIEW.TX_OUTPUT_UNLOCK_CONDITIONS</h1></ion-header-bar><ion-content scroll="true"><div class="row" ng-repeat="condition in popoverData.unlockConditions track by $index" ng-style="::condition.style"><span class="gray" ng-if="::condition.operator">{{::\'BLOCKCHAIN.VIEW.TX_OUTPUT_OPERATOR.\'+condition.operator|translate}}&nbsp;</span><div ng-if="::condition.type==\'SIG\'"><i class="icon ion-key dark"></i> <span class="dark" ng-bind-html="::\'BLOCKCHAIN.VIEW.TX_OUTPUT_FUNCTION.SIG\' | translate"></span> <a ng-click="goState(\'app.wot_identity\', {pubkey:condition.value})" style="text-decoration: none" class="positive">{{condition.value|formatPubkey}}</a></div><div ng-if="::condition.type==\'XHX\'"><i class="icon ion-lock-combination dark"></i> <span class="dark" ng-bind-html="::\'BLOCKCHAIN.VIEW.TX_OUTPUT_FUNCTION.XHX\' | translate"></span> <a copy-on-click="{{::condition.value}}" class="positive">{{::condition.value|formatPubkey}}...</a></div><div ng-if="condition.type==\'CSV\'"><i class="icon ion-clock dark"></i> <span class="dark" ng-bind-html="::\'BLOCKCHAIN.VIEW.TX_OUTPUT_FUNCTION.CSV\' | translate"></span> {{::condition.value|formatDuration}}</div><div ng-if="condition.type==\'CLTV\'"><i class="icon ion-clock dark"></i> <span class="dark" ng-bind-html="::\'BLOCKCHAIN.VIEW.TX_OUTPUT_FUNCTION.CLTV\' | translate"></span> {{::condition.value|medianDate}}</div></div></ion-content></ion-popover-view>');
 $templateCache.put('templates/blockchain/view_block.html','<ion-view><ion-nav-title><span class="title visible-xs visible-sm" ng-if="number==\'current\'">{{\'BLOCKCHAIN.VIEW.TITLE_CURRENT\'|translate}}</span> <span class="title visible-xs visible-sm" ng-if="number!=\'current\'">{{\'BLOCKCHAIN.VIEW.TITLE\'|translate:formData}}</span></ion-nav-title><ion-content class="no-padding-xs no-padding-sm" scroll="true"><div class="row no-padding"><div class="col no-padding"><div class="center padding" ng-if="loading"><ion-spinner icon="android"></ion-spinner></div><div class="list item-text-wrap no-padding-xs" ng-if="!loading"><div class="item item-text-wrap"><h3><span class="dark"><i class="icon ion-clock"></i> {{formData.medianTime | medianFromNowAndDate}}</span></h3><h3><span class="dark"><i class="icon ion-lock-combination"></i> {{\'BLOCKCHAIN.VIEW.COMPUTED_BY\'|translate}} </span><a class="positive" ui-sref="app.wot_identity({pubkey:issuer.pubkey, uid: issuer.uid})"><i class="icon ion-person positive"></i> {{issuer.name||issuer.uid}} <span class="gray" ng-if="issuer.name">({{issuer.uid}})</span></a></h3><h3><a ng-click="openRawBlock($event)"><i class="icon ion-share"></i> {{\'BLOCKCHAIN.VIEW.SHOW_RAW\'|translate}}</a></h3></div><span class="item item-divider">{{\'BLOCKCHAIN.VIEW.TECHNICAL_DIVIDER\' | translate}}</span><ion-item class="item-icon-left item-text-wrap" ng-if="!compactMode || $root.settings.expertMode"><i class="icon ion-gear-b"></i> {{\'BLOCKCHAIN.VIEW.VERSION\'|translate}} <span class="badge badge-stable">{{::formData.version}}</span></ion-item><ion-item class="item-icon-left item-text-wrap" ng-if="!compactMode || $root.settings.expertMode" copy-on-click="{{::formData.powMin}}"><i class="icon ion-lock-combination"></i> {{\'BLOCKCHAIN.VIEW.POW_MIN\'|translate}}<h4 class="gray">{{\'BLOCKCHAIN.VIEW.POW_MIN_HELP\'|translate}}</h4><span class="badge badge-stable">{{::formData.powMin}}</span></ion-item><ion-item class="item-icon-left item-text-wrap" copy-on-click="{{::formData.hash}}"><i class="icon ion-pound"></i> {{\'BLOCKCHAIN.VIEW.HASH\'|translate}}<h5 class="visible-xs visible-sm dark">{{::formData.hash}}</h5></ion-item><span class="item item-divider">{{\'BLOCKCHAIN.VIEW.DATA_DIVIDER\' | translate}}</span><ion-item ng-if="compactMode && formData.empty" class="item-icon-left item-text-wrap">{{\'BLOCKCHAIN.VIEW.EMPTY\'|translate}}</ion-item><ion-item ng-if="!compactMode || formData.dividend" class="item-icon-left item-text-wrap" copy-on-click="{{::formData.dividend/100}}"><i class="icon ion-arrow-up-c"></i><div class="col col-60">{{\'COMMON.UNIVERSAL_DIVIDEND\'|translate}}<h4 class="gray">{{\'BLOCKCHAIN.VIEW.UNIVERSAL_DIVIDEND_HELP\'|translate: {membersCount: formData.membersCount} }}</h4></div><span class="badge badge-balanced" ng-if="formData.dividend">+1 <span ng-bind-html="formData.currency|currencySymbol: {useRelative: true} "></span> / {{\'COMMON.MEMBER\'|translate|lowercase}} </span><span class="badge badge-stable" ng-if="!formData.dividend">0</span> <span class="badge badge-secondary" ng-if="formData.dividend">+ {{formData.dividend| formatAmount: {currency: formData.currency, useRelative: false} }} / {{\'COMMON.MEMBER\'|translate|lowercase}}</span></ion-item><ng-if ng-if="!compactMode || formData.identitiesCount"><ion-item class="item-icon-left"><i class="icon ion-person"></i> <b class="ion-clock" style="position: absolute; top: 16px; left: 39px; font-size: 12px"></b> {{\'BLOCKCHAIN.VIEW.IDENTITIES_COUNT\'|translate}} <span class="badge badge-balanced" ng-if="formData.identitiesCount">+{{::formData.identitiesCount}}</span> <span class="badge badge-stable" ng-if="!formData.identitiesCount">0</span></ion-item><div class="padding-bottom item-icon-left-padding item-icon-right-padding" ng-if="formData.identitiesCount"><ion-item ng-repeat="identity in ::formData.identities" class="item-border-large item-small-height" ng-include="::\'templates/blockchain/link_identity.html\'"></ion-item></div></ng-if><ng-if ng-if="!compactMode || formData.joinersCount"><ion-item class="item-icon-left"><i class="icon ion-person-add"></i> {{\'BLOCKCHAIN.VIEW.JOINERS_COUNT\'|translate}} <span class="badge badge-balanced" ng-if="formData.joinersCount">+{{::formData.joinersCount}}</span> <span class="badge badge-stable" ng-if="!formData.joinersCount">0</span></ion-item><div class="padding-bottom item-icon-left-padding item-icon-right-padding" ng-if="formData.joinersCount"><ion-item ng-repeat="identity in ::formData.joiners" class="item-border-large item-small-height" ng-include="::\'templates/blockchain/link_identity.html\'"></ion-item></div></ng-if><ng-if ng-if="!compactMode || formData.activesCount"><ion-item class="item-icon-left"><i class="icon ion-person"></i> <b class="ion-refresh" style="position: absolute; top: 25px; left: 39px; font-size: 12px"></b> {{\'BLOCKCHAIN.VIEW.ACTIVES_COUNT\'|translate}}<h4 class="gray">{{\'BLOCKCHAIN.VIEW.ACTIVES_COUNT_HELP\'|translate}}</h4><span class="badge badge-balanced" ng-if="formData.activesCount">{{::formData.activesCount}}</span> <span class="badge badge-stable" ng-if="!formData.activesCount">0</span></ion-item><div class="padding-bottom item-icon-left-padding item-icon-right-padding" ng-if="formData.activesCount"><ion-item ng-repeat="identity in ::formData.actives" class="item-border-large item-small-height" ng-include="::\'templates/blockchain/link_identity.html\'"></ion-item></div></ng-if><ng-if ng-if="!compactMode || (formData.excludedCount-formData.revokedCount)"><ion-item class="item-icon-left"><i class="icon ion-person"></i> <b class="ion-close dark" style="position: absolute; top: 25px; left: 39px; font-size: 12px"></b> {{\'BLOCKCHAIN.VIEW.EXCLUDED_COUNT\'|translate}}<h4 class="gray">{{\'BLOCKCHAIN.VIEW.EXCLUDED_COUNT_HELP\'|translate}}</h4><span class="badge badge-assertive" ng-if="formData.excludedCount-formData.revokedCount">-{{::formData.excludedCount-formData.revokedCount}}</span> <span class="badge badge-stable" ng-if="!(formData.excludedCount-formData.revokedCount)">0</span></ion-item><div class="padding-bottom item-icon-left-padding item-icon-right-padding" ng-if="formData.excludedCount"><ion-item ng-repeat="identity in ::formData.excluded" class="item-border-large item-small-height" ng-include="::\'templates/blockchain/link_identity.html\'"></ion-item></div></ng-if><ng-if ng-if="!compactMode || formData.leaversCount"><ion-item class="item-icon-left" ng-if="!compactMode || formData.leaversCount"><i class="icon ion-person"></i> <b class="ion-minus" style="position: absolute; top: 25px; left: 39px; font-size: 12px"></b> {{\'BLOCKCHAIN.VIEW.LEAVERS_COUNT\'|translate}}<h4 class="gray">{{\'BLOCKCHAIN.VIEW.LEAVERS_COUNT_HELP\'|translate}}</h4><span class="badge badge-assertive" ng-if="formData.leaversCount">-{{::formData.leaversCount}}</span> <span class="badge badge-stable" ng-if="!formData.leaversCount">0</span></ion-item><div class="padding-bottom item-icon-left-padding item-icon-right-padding" ng-if="formData.leaversCount"><ion-item ng-repeat="identity in ::formData.leavers" class="item-border-large item-small-height" ng-include="::\'templates/blockchain/link_identity.html\'"></ion-item></div></ng-if><ng-if ng-if="!compactMode || formData.revokedCount"><ion-item class="item-icon-left"><i class="icon ion-person"></i> <b class="ion-minus-circled assertive" style="position: absolute; top: 25px; left: 39px; font-size: 12px"></b> {{\'BLOCKCHAIN.VIEW.REVOKED_COUNT\'|translate}}<h4 class="gray">{{\'BLOCKCHAIN.VIEW.REVOKED_COUNT_HELP\'|translate}}</h4><span class="badge badge-balanced" ng-if="formData.revokedCount">-{{::formData.revokedCount}}</span> <span class="badge badge-stable" ng-if="!formData.revokedCount">0</span></ion-item><div class="padding-bottom item-icon-left-padding item-icon-right-padding" ng-if="formData.revokedCount"><ion-item ng-repeat="identity in ::formData.revoked" class="item-border-large item-small-height" ng-include="::\'templates/blockchain/link_identity.html\'"></ion-item></div></ng-if><ng-if ng-if="!compactMode || formData.certificationsCount"><ion-item class="item-icon-left"><i class="icon ion-ribbon-a"></i> {{\'BLOCKCHAIN.VIEW.CERT_COUNT\'|translate}} <span class="badge badge-stable" ng-class="{\'badge-positive\':formData.certificationsCount}">{{::formData.certificationsCount}}</span></ion-item><div class="padding-bottom item-icon-left-padding item-icon-right-padding no-padding-xs" ng-if="formData.certificationsCount"><div ng-repeat="(key, certs) in formData.certifications" class="item item-border-large item-small-height"><div class="row no-padding"><div class="col col-center no-padding"><ng-repeat ng-repeat="cert in certs"><ng-include src="::\'templates/blockchain/link_identity.html\'" onload="identity=cert.from"></ng-include><br></ng-repeat></div><div class="col col-10 col-center gray text-center no-padding"><h2><i class="icon ion-arrow-right-a"></i></h2></div><div class="col col-40 col-center no-padding" ng-include="::\'templates/blockchain/link_identity.html\'" onload="identity=certs[0].to"></div></div></div></div></ng-if><ng-if ng-if="!compactMode || formData.transactionsCount"><ion-item class="item-icon-left"><i class="icon ion-card"></i> {{\'BLOCKCHAIN.VIEW.TX_COUNT\'|translate}} <span class="badge badge-stable" ng-class="{\'badge-positive\':formData.transactionsCount}">{{::formData.transactionsCount}}</span></ion-item><div class="padding-bottom item-icon-left-padding item-icon-right-padding no-padding-xs" ng-if="formData.transactionsCount"><div ng-repeat="tx in ::formData.transactions" class="item item-small-height item-border-large"><div class="row no-padding" style="padding-top: 3px"><div class="col col-40 col-center no-padding list no-margin"><div ng-repeat="identity in ::tx.issuers" class="item no-padding item-small-height"><ng-include src="\'templates/blockchain/link_identity.html\'"></ng-include></div></div><div class="col col-10 col-center gray text-center no-padding"><h2><i class="icon ion-arrow-right-a"></i></h2></div><div class="col no-padding padding-right no-padding-xs col-text-wrap list no-margin"><span class="gray" ng-if="tx.toHimself" translate="">BLOCKCHAIN.VIEW.TX_TO_HIMSELF</span><div ng-repeat="output in ::tx.outputs" class="item no-padding item-small-height"><ng-include ng-if="::output.pubkey" src="\'templates/blockchain/link_identity.html\'" onload="identity=output"></ng-include><span ng-if="::!output.pubkey && output.unlockFunctions"><i class="icon ion-locked"></i> (<a ng-click="showUnlockConditionPopover(output, $event)"> <i ng-repeat="unlockFunction in ::output.unlockFunctions" ng-class="::{\'ion-key\': (unlockFunction==\'SIG\'), \'ion-clock\': (unlockFunction==\'CSV\' || unlockFunction==\'CLTV\'), \'ion-lock-combination\': (unlockFunction==\'XHX\') }" class="icon"></i> </a>) </span><span class="badge badge-balanced" ng-bind-html="::output.amount | formatAmount:{currency: formData.currency, useRelative: false} "></span></div></div></div></div></div></ng-if></div></div></div></ion-content></ion-view>');
-$templateCache.put('templates/common/badge_certification_count.html','<span ng-attr-id="{{$ctrl.csId}}" class="badge badge-balanced" ng-class="{\'badge-energized\': $ctrl.requirements.willNeedCertificationCount || ($ctrl.requirements.needCertificationCount + $ctrl.requirements.pendingCertificationCount >= $ctrl.parameters.sigQty),\n               \'badge-assertive\': ($ctrl.requirements.needCertificationCount + $ctrl.requirements.pendingCertificationCount < $ctrl.parameters.sigQty)}"><span ng-if="$ctrl.requirements.certificationCount || !$ctrl.requirements.pendingCertificationCount"><i ng-if="!$ctrl.requirements.needCertificationCount" class="ion-android-done"></i> {{$ctrl.requirements.certificationCount}} <i ng-if="$ctrl.requirements.willNeedCertificationCount" class="ion-android-warning"></i> </span><span ng-if="$ctrl.requirements.pendingCertificationCount"><ng-if ng-if="$ctrl.requirements.certificationCount">+</ng-if><i class="ion-clock"></i> {{$ctrl.requirements.pendingCertificationCount}}</span></span>');
-$templateCache.put('templates/common/badge_given_certification_count.html','<div ng-attr-id="{{$ctrl.csId}}" class="badge badge-calm" ng-class="{\'badge-assertive\': $ctrl.identity.given_cert.length >= $ctrl.parameters.sigStock}"><span><i ng-if="$ctrl.identity.given_cert.length" class="ion-android-done"></i> {{$ctrl.identity.given_cert.length}} </span><span ng-if="$ctrl.identity.given_cert_pending.length">(<ng-if ng-if="$ctrl.identity.given_cert.length">+</ng-if><i class="ion-clock"></i> {{$ctrl.identity.given_cert_pending.length}}) </span><small>/ {{$ctrl.parameters.sigStock}}</small></div>');
-$templateCache.put('templates/common/form_error_messages.html','<div class="form-error" ng-message="minlength"><span translate="ERROR.FIELD_TOO_SHORT"></span></div><div class="form-error" ng-message="maxlength"><span translate="ERROR.FIELD_TOO_LONG"></span></div><div class="form-error" ng-message="pattern"><span translate="ERROR.FIELD_ACCENT"></span></div><div class="form-error" ng-message="required"><span translate="ERROR.FIELD_REQUIRED"></span></div>');
-$templateCache.put('templates/common/popover_copy.html','<ion-popover-view class="popover-copy" style="height: {{(!rows || rows &lt;= 1) ? 50 : rows*22}}px"><ion-content scroll="false"><div class="list"><div class="item item-input"><input type="text" autocomplete="off" ng-if="!rows || rows &lt;= 1" ng-model="value"><textarea ng-if="rows && rows > 1" ng-model="value" rows="{{rows}}" cols="10">\n      </textarea></div></div></ion-content></ion-popover-view>');
-$templateCache.put('templates/common/popover_helptip.html','<ion-popover-view class="popover-helptip"><ion-content scroll="false" class="list"><p><i ng-if="icon.position && !icon.position.startsWith(\'bottom-\')" class="{{icon.class}} icon-{{icon.position}} hidden-xs" style="{{icon.style}}"></i><a ng-click="closePopover()" class="pull-right button-close" ng-class="{\'pull-left\': icon.position === \'right\', \'pull-right\': icon.position !== \'right\'}"><i class="ion-close"></i> </a><span>&nbsp;</span></p><p class="padding light"><ng-bind-html ng-bind-html="content | translate:contentParams"></ng-bind-html><ng-bind-html ng-bind-html="trustContent"></ng-bind-html></p><div class="text-center" ng-if="!tour"><button class="button button-small button-stable" ng-if="!hasNext" ng-click="closePopover(true)" translate>COMMON.BTN_UNDERSTOOD</button> <button class="button button-small button-stable" id="helptip-btn-ok" ng-if="hasNext" ng-click="closePopover(false)" translate>COMMON.BTN_UNDERSTOOD</button> <button id="helptip-btn-ok" class="button button-small button-positive icon-right ink" ng-if="hasNext" ng-click="closePopover(true)"><i class="icon ion-chevron-right"></i></button></div><div class="text-center" ng-if="tour"><button class="button button-small button-positive" id="helptip-btn-ok" ng-if="!hasNext" ng-click="closePopover(false)" translate>COMMON.BTN_CLOSE</button> <button id="helptip-btn-ok" class="button button-small button-positive icon-right ink" ng-if="hasNext" ng-click="closePopover(true)">{{\'COMMON.BTN_CONTINUE\'|translate}} <i class="icon ion-chevron-right"></i></button></div><p><i ng-if="icon.position && icon.position.startsWith(\'bottom-\')" class="{{icon.class}} icon-{{icon.position}} hidden-xs"></i></p></ion-content></ion-popover-view>');
-$templateCache.put('templates/common/popover_locales.html','<ion-popover-view class="fit popover-locales" style="height: {{locales.length*48}}px"><ion-content scroll="false"><div class="list item-text-wrap block"><a ng-repeat="l in locales track by l.id" class="item item-icon-left ink" ng-click="changeLanguage(l.id)"><i class="item-image avatar" style="background-image: url(./img/flag-{{l.flag}}.png)"></i> {{l.label | translate}}</a></div></ion-content></ion-popover-view>');
-$templateCache.put('templates/common/popover_profile.html','');
-$templateCache.put('templates/common/popover_share.html','<ion-popover-view class="popover-share"><ion-content scroll="false"><div class="bar bar-header"><h1 class="title">{{titleKey|translate:titleValues}}</h1><span class="gray pull-right">{{time|formatDate}}</span></div><div class="list no-margin no-padding has-header has-footer block"><div class="item item-input"><input type="text" autocomplete="off" ng-model="value"></div></div><div class="bar bar-footer"><div class="button-bar"><a class="button button-icon positive icon ion-social-facebook" href="https://www.facebook.com/sharer/sharer.php?u={{postUrl|formatEncodeURI}}&amp;title={{postMessage|formatEncodeURI}}" onclick="window.open(this.href, \'facebook-share\',\'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=580,height=296\');return false;" title="{{\'COMMON.POPOVER_SHARE.SHARE_ON_FACEBOOK\'|translate}}"></a> <a class="button button-icon positive icon ion-social-twitter" href="https://twitter.com/intent/tweet?url={{postUrl|formatEncodeURI}}&amp;text={{postMessage|formatEncodeURI}}" onclick="window.open(this.href, \'twitter-share\',\'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=580,height=296\');return false;" title="{{\'COMMON.POPOVER_SHARE.SHARE_ON_TWITTER\'|translate}}"></a> <a class="button button-icon positive icon ion-social-googleplus" href="https://plus.google.com/share?url={{postUrl|formatEncodeURI}}" onclick="window.open(this.href, \'google-plus-share\', \'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=296,width=580\');return false;" title="{{\'COMMON.POPOVER_SHARE.SHARE_ON_GOOGLEPLUS\'|translate}}"></a> <a class="button button-icon positive icon ion-social-diaspora" href="https://sharetodiaspora.github.io/?title={{postMessage|formatEncodeURI}}&amp;url={{postUrl|formatEncodeURI}}" onclick="window.open(this.href, \'diaspora-share\',\'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=580,height=296\');return false;" title="{{\'COMMON.POPOVER_SHARE.SHARE_ON_DIASPORA\'|translate}}"></a> <a class="button-close" title="{{\'COMMON.BTN_CLOSE\'|translate}}" ng-click="closePopover()"><i class="icon ion-close"></i></a></div></div></ion-content></ion-popover-view>');
-$templateCache.put('templates/common/popup_password.html','<form name="pwdForm" ng-submit="submit($event)"><div class="list" ng-init="setForm(pwdForm)"><label class="item item-input" ng-class="{\'item-input-error\': pwdForm.$submitted && pwdForm.password.$invalid}"><input name="password" type="password" placeholder="{{\'ACCOUNT.SECURITY.KEYFILE.PASSWORD_POPUP.PASSWORD_HELP\' | translate}}" ng-model="formData.password" ng-minlength="1" required></label><div class="form-errors" ng-if="pwdForm.$submitted && pwdForm.pseudo.$error" ng-messages="pwdForm.password.$error"><div class="form-error" ng-message="required"><span translate="ERROR.FIELD_REQUIRED"></span></div><div class="form-error" ng-message="minlength"><span translate="ERROR.FIELD_TOO_SHORT"></span></div></div><div class="form-errors" ng-if="error"><div class="form-error">{{error|translate}}</div></div></div></form>');
-$templateCache.put('templates/common/qrcode.html','<a ng-attr-id="{{ qrcodeId }}" ng-show="!loading" class="qrcode fade-in pull-right" ng-class="{\'active\': toggleQRCode}" ng-click="toggleQRCode = !toggleQRCode"><div class="content"></div><div class="footer item item-icon-left item-text-wrap ink" on-hold="copy(formData.pubkey)" copy-on-click="{{:rebind:formData.pubkey}}" ng-click="$event.stopPropagation()"><i class="icon ion-key"></i> <span>{{:locale:\'COMMON.PUBKEY\'|translate}}</span><h4 id="pubkey" class="dark">{{:rebind:formData.pubkey}}</h4></div></a>');
-$templateCache.put('templates/common/view_passcode.html','<ion-view left-buttons="leftButtons"><ion-nav-title><span class="visible-xs visible-sm" translate>COMMON.PASSCODE.TITLE</span></ion-nav-title><ion-content scroll="false"></ion-content></ion-view>');
 $templateCache.put('templates/currency/items_network.html','<ion-item id="helptip-network-blockchain" class="item-icon-left item-text-wrap"><i class="icon ion-clock"></i> <span class="col col-60" translate="">CURRENCY.VIEW.MEDIAN_TIME</span> <span class="badge badge-stable">{{formData.medianTime | medianDate}}</span></ion-item><ion-item class="item-icon-left item-text-wrap"><i class="icon ion-lock-combination"></i> <span class="col col-75" translate="">CURRENCY.VIEW.POW_MIN</span> <span class="badge badge-stable">{{formData.difficulty | formatInteger}}</span></ion-item><cs-extension-point name="network-actual"></cs-extension-point><div class="item item-divider"><span translate="">CURRENCY.VIEW.NETWORK_RULES_DIVIDER</span></div><ion-item class="item-icon-left item-text-wrap"><i class="icon ion-clock" style="position: absolute; font-size: 20px; left: 16px; margin-top: 11px"></i> <b class="icon-secondary ion-lock-combination" style="left: 14px; margin-top: -4px"></b> <b class="icon-secondary ion-arrow-right-c" style="font-size: 12px; left: 28px; margin-top: -4px"></b> <b class="icon-secondary ion-lock-combination" style="left: 38px; margin-top: -4px"></b> <span class="col col-75" translate="">CURRENCY.VIEW.AVG_GEN_TIME</span> <span class="badge badge-stable">{{formData.avgGenTime | formatDuration}}</span></ion-item><div id="helptip-network-peers" class="item item-divider"><div class="pull-left"><span ng-if="search.type==\'member\'" translate="">PEER.MEMBERS</span> <span ng-if="search.type==\'mirror\'" translate="">PEER.MIRRORS</span> <span ng-if="search.type==\'offline\'" translate="">PEER.OFFLINE</span> <span ng-if="!search.type" translate="">PEER.PEERS</span> <span ng-if="!search.loading">({{search.results.length}})</span></div><div class="buttons pull-right"><ion-spinner class="icon" icon="android" ng-if="search.loading"></ion-spinner></div></div><ng-include src="::\'templates/network/items_peers.html\'"></ng-include>');
 $templateCache.put('templates/currency/items_parameters.html','<div bind-notifier="{ rebind:formData.useRelative }"><ion-item class="item-icon-left item-text-wrap visible-xs visible-sm"><i class="icon ion-android-bookmark"></i> <span translate>CURRENCY.VIEW.CURRENCY_NAME</span><div class="item-note dark" ng-if="!loading">{{formData.currency}} (<span ng-bind-html=":rebind:formData.currency | currencySymbol:formData.useRelative"></span>)</div></ion-item><ion-item id="helptip-currency-mass-member" class="item-icon-left item-text-wrap"><i class="icon ion-pie-graph"></i><div class="col col-60"><span translate>CURRENCY.VIEW.SHARE</span> <span class="gray">(M<sub>t</sub>/N<sub>t</sub>)</span></div><span id="helptip-currency-mass-member-unit" ng-if="!loading" class="badge badge-calm" ng-bind-html=":rebind:formData.MoverN | formatAmount:{currency: formData.currency, useRelative: formData.useRelative, currentUD: formData.currentUD}"></span></ion-item><ion-item class="item-icon-left item-text-wrap"><i class="icon ion-record"></i><div class="col col-60"><span translate>CURRENCY.VIEW.MASS</span> <span class="gray">(M<sub>t</sub>)</span></div><span class="badge badge-energized" ng-if="!loading" ng-bind-html=":rebind:formData.M | formatAmount:{currency: formData.currency, useRelative: formData.useRelative, currentUD: formData.currentUD}"></span></ion-item><cs-extension-point name="parameters-actual"></cs-extension-point><ion-item class="item-icon-left item-text-wrap"><i class="icon ion-arrow-graph-up-right"></i><div class="col col-60"><span translate>CURRENCY.VIEW.C_ACTUAL</span> <span class="gray">(c<sub>{{\'CURRENCY.VIEW.CURRENT\'|translate}}</sub>)</span></div><span class="badge badge-stable">{{formData.cactual | formatNumeral: \'0,0.00\'}} %&nbsp;/&nbsp;{{formData.dt | formatPeriod}}</span></ion-item><ion-item class="item-icon-left item-text-wrap"><i class="icon ion-load-c"></i><div class="col col-60"><span translate>CURRENCY.VIEW.UD</span> <span class="gray">({{\'COMMON.UD\'|translate}}<sub>t</sub>)</span></div><div class="badge badge-royal" ng-if="!loading"><span ng-if="formData.useRelative">1<ng-bind-html ng-bind-html=":rebind:formData.currency| currencySymbol:true"></ng-bind-html></span><span ng-if="!formData.useRelative" ng-bind-html=":rebind:formData.currentUD | formatAmount:{currency: formData.currency, useRelative: formData.useRelative, currentUD: formData.currentUD}"></span> &nbsp;/&nbsp;{{formData.dt | formatPeriod}}</div></ion-item><div class="item item-toggle dark"><div class="item-label text-right gray" translate>COMMON.BTN_RELATIVE_UNIT</div><label class="toggle toggle-royal" id="helptip-currency-change-unit"><input type="checkbox" ng-model="formData.useRelative"><div class="track"><div class="handle"></div></div></label></div><a name="helptip-currency-rules-anchor"></a><div class="item item-divider" id="helptip-currency-rules"><span translate>CURRENCY.VIEW.MONEY_RULES_DIVIDER</span></div><ion-item class="item-icon-left item-text-wrap"><i class="icon ion-arrow-graph-up-right"></i><div class="col col-60"><span translate>CURRENCY.VIEW.C_RULE</span> <span class="gray">(c)</span></div><span class="item-note dark" ng-if="!loading && !formData.udReevalTime0">{{formData.c*100 | formatNumeral: \'0,0.00\'}} %&nbsp;/&nbsp;{{formData.dt | formatPeriod}}</span><span class="badge badge-stable" ng-if="!loading && formData.udReevalTime0">{{formData.c*100 | formatNumeral: \'0,0.00\'}} %&nbsp;/&nbsp;{{formData.dtReeval | formatDuration}}</span></ion-item><ion-item class="item-icon-left item-text-wrap" ng-if="formData.udReevalTime0 && formData.allRules"><i class="icon ion-load-c"></i> <b class="ion-clock icon-secondary" style="font-size: 18px; left: 36px; top: -12px"></b><div class="col col-60"><span translate>CURRENCY.VIEW.DT_REEVAL</span> <span class="gray">(dt<sub>{{\'CURRENCY.VIEW.REEVAL_SYMBOL\'|translate}}</sub>)</span></div><span class="item-note dark" ng-if="!loading" translate="CURRENCY.VIEW.DT_REEVAL_VALUE" translate-values="formData"></span></ion-item><ion-item class="item-icon-left item-text-wrap" ng-if="formData.udReevalTime0 && formData.allRules"><i class="icon ion-load-c"></i> <b class="ion-calendar icon-secondary" style="font-size: 18px; left: 36px; top: -12px"></b><div class="col col-60"><span translate>CURRENCY.VIEW.UD_REEVAL_TIME0</span> <span class="gray">(t0<sub>{{\'CURRENCY.VIEW.REEVAL_SYMBOL\'|translate}}</sub>)</span></div><span class="item-note dark" ng-if="!loading">{{formData.udReevalTime0|medianDate}}</span></ion-item><ion-item class="item-icon-left item-text-wrap" ng-if="formData.allRules"><i class="icon ion-load-c"></i> <b class="ion-calculator icon-secondary" style="font-size: 18px; left: 36px; top: -12px"></b><div class="col col-60"><span translate>CURRENCY.VIEW.UD_RULE</span> <span class="gray" ng-if="formData.udReevalTime0">- {{\'COMMON.UD\'|translate}}<sub>{{formData.dt|formatPeriod}}</sub>(t<sub>{{\'CURRENCY.VIEW.REEVAL_SYMBOL\'|translate}}</sub>)</span></div><span class="item-note dark" ng-if="!loading && !formData.udReevalTime0">{{\'COMMON.UD\'|translate}}<sub>t-1</sub> + c<sup>2</sup> * M<sub>t-1</sub>/N<sub>t-1</sub></span><span class="item-note dark" ng-if="!loading && formData.udReevalTime0">{{\'COMMON.UD\'|translate}}<sub>{{formData.dt|formatPeriod}}</sub>(t<sub>{{\'CURRENCY.VIEW.REEVAL_SYMBOL\'|translate}}</sub> - dt<sub>{{\'CURRENCY.VIEW.REEVAL_SYMBOL\'|translate}}</sub>)+ c<sup>2</sup> * (M/N)(t<sub>{{\'CURRENCY.VIEW.REEVAL_SYMBOL\'|translate}}</sub> - dt<sub>{{\'CURRENCY.VIEW.REEVAL_SYMBOL\'|translate}}</sub>) / dt<sub>{{\'CURRENCY.VIEW.REEVAL_SYMBOL\'|translate}}</sub></span></ion-item><div class="item item-toggle dark"><div class="item-label text-right gray" translate>CURRENCY.VIEW.DISPLAY_ALL_RULES</div><label class="toggle toggle-royal"><input type="checkbox" ng-model="formData.allRules"><div class="track"><div class="handle"></div></div></label></div></div>');
 $templateCache.put('templates/currency/items_wot.html','<div bind-notifier="{ rebind:formData.useRelative }"><a name="helptip-currency-newcomers-anchor"></a><ion-item class="item-icon-left item-text-wrap"><i class="icon ion-person-stalker"></i><div class="col col-60"><span translate>CURRENCY.VIEW.MEMBERS</span> <span class="gray">(N<sub>{{\'CURRENCY.VIEW.CURRENT\'|translate}}</sub>)</span></div><span class="badge badge-calm" ng-if="!loading">{{formData.N | formatInteger}}</span></ion-item><ion-item id="helptip-currency-newcomers" class="item-icon-left item-text-wrap"><i class="icon ion-arrow-graph-up-right"></i><div class="col col-75"><span translate="CURRENCY.VIEW.MEMBERS_VARIATION" translate-values="{duration: formData.durationFromLastUD}"></span> <span class="gray">(&#916;N)</span></div><div class="badge" ng-if="!loading" ng-class="{\'badge-balanced\': (formData.N>formData.Nprev), \'badge-stable\': (formData.N==formData.Nprev) ,\'badge-assertive\': (formData.Nprev>formData.N)}">{{formData.N > formData.Nprev ? \'+\' : \'\'}}{{formData.N - formData.Nprev}}</div></ion-item><cs-extension-point name="wot-actual"></cs-extension-point><div class="item item-divider"><span translate>CURRENCY.VIEW.WOT_RULES_DIVIDER</span></div><ion-item class="item-icon-left item-text-wrap"><i class="icon ion-ribbon-b"></i> <span class="col col-75" translate>CURRENCY.VIEW.SIG_QTY_RULE</span> <span class="badge badge-balanced" ng-if="!loading">{{formData.sigQty}}</span></ion-item><ion-item class="item-icon-left item-text-wrap"><i class="icon ion-person"></i> <b class="ion-clock icon-secondary" style="font-size: 18px; left: 33px; top: -12px"></b> <span class="col col-60" translate>CURRENCY.VIEW.MS_WINDOW</span> <span class="badge badge-assertive" ng-if="!loading">{{formData.msWindow | formatDuration}}</span></ion-item><ion-item class="item-icon-left item-text-wrap"><i class="icon ion-person"></i> <b class="ion-calendar icon-secondary" style="font-size: 18px; left: 33px; top: -12px"></b> <span class="col col-60" translate>CURRENCY.VIEW.MS_VALIDITY</span> <span class="badge badge-balanced" ng-if="!loading">{{formData.msValidity | formatDuration}}</span></ion-item><ion-item class="item-icon-left item-text-wrap" ng-if="formData.allWotRules"><i class="icon ion-ribbon-b"></i> <b class="ion-clock icon-secondary" style="font-size: 18px; left: 33px; top: -12px"></b> <span class="col col-60" translate>CURRENCY.VIEW.SIG_WINDOW</span> <span class="badge badge-stable" ng-if="!loading">{{formData.sigWindow | formatDuration}}</span></ion-item><ion-item class="item-icon-left item-text-wrap" ng-if="formData.allWotRules"><i class="icon ion-ribbon-b"></i> <b class="ion-calendar icon-secondary" style="font-size: 18px; left: 33px; top: -12px"></b> <span class="col col-60" translate>CURRENCY.VIEW.SIG_VALIDITY</span> <span class="badge badge-balanced" ng-if="!loading">{{formData.sigValidity | formatDuration}}</span></ion-item><ion-item class="item-icon-left item-text-wrap" ng-if="formData.allWotRules"><i class="icon ion-ribbon-a"></i> <span class="col col-75" translate>CURRENCY.VIEW.SIG_STOCK</span> <span class="badge badge-stable" ng-if="!loading">{{formData.sigStock}}</span></ion-item><ion-item class="item-icon-left item-text-wrap" ng-if="formData.allWotRules"><i class="icon ion-clock" style="position: absolute; font-size: 20px; left: 16px"></i> <b class="ion-ribbon-a icon-secondary" style="left: 16px; top: -15px"></b> <b class="ion-arrow-right-c icon-secondary" style="left: 28px; top: -15px"></b> <b class="ion-ribbon-a icon-secondary" style="left: 40px; top: -15px"></b> <span class="col col-75" translate>CURRENCY.VIEW.SIG_PERIOD</span> <span class="badge badge-stable" ng-if="!loading">{{formData.sigPeriod | formatDuration}}</span></ion-item><ion-item class="item-icon-left item-text-wrap" ng-if="formData.allWotRules"><i class="icon ion-steam"></i> <b class="ion-person icon-secondary" style="left: 38px; top: -17px"></b><div class="col col-75"><span ng-bind-html="\'CURRENCY.VIEW.STEP_MAX\'|translate"></span> <span class="gray">(stepMax)</span></div><span class="badge badge-assertive" ng-if="!loading">{{formData.stepMax}}</span></ion-item><ion-item class="item-icon-left item-text-wrap" ng-if="formData.allWotRules"><i class="icon ion-ribbon-b"></i> <b class="ion-star icon-secondary" style="color: yellow; font-size: 16px; left: 25px; top: -7px"></b> <span class="col col-75" translate>CURRENCY.VIEW.SENTRIES</span> <span class="badge badge-stable" ng-if="!loading">{{formData.sentries}}</span></ion-item><ion-item class="item-icon-left item-text-wrap" ng-if="formData.allWotRules"><i class="icon ion-ribbon-b"></i> <b class="ion-star icon-secondary" style="color: yellow; font-size: 16px; left: 25px; top: -7px"></b> <span class="col col-75" translate>CURRENCY.VIEW.SENTRIES_FORMULA</span> <span class="item-note dark" ng-if="!loading">{{\'CURRENCY.VIEW.MATH_CEILING\'| translate}}( N<sub>t</sub><sup>^ (1 / stepMax)</sup>)</span></ion-item><ion-item class="item-icon-left item-text-wrap" ng-if="formData.allWotRules"><i class="icon ion-pull-request"></i> <span class="col col-75" translate>CURRENCY.VIEW.XPERCENT</span> <span class="badge badge-stable" ng-if="!loading">{{formData.xpercent*100| formatNumeral: \'0,0\'}} %</span></ion-item><div class="item item-toggle dark"><div class="item-label text-right gray" translate>CURRENCY.VIEW.DISPLAY_ALL_RULES</div><label class="toggle toggle-royal"><input type="checkbox" ng-model="formData.allWotRules"><div class="track"><div class="handle"></div></div></label></div></div>');
@@ -21062,6 +21051,17 @@ $templateCache.put('templates/currency/view_currency_lg.html','<ion-view left-bu
 $templateCache.put('templates/help/help.html','<a name="join"></a><h2 translate>HELP.JOIN.SECTION</h2><a name="join-salt"></a><div class="row responsive-sm" ng-class="itemsClass[\'join-salt\']"><div class="col col-20" translate>LOGIN.SALT</div><div class="col" translate>HELP.JOIN.SALT</div></div><a name="join-password"></a><div class="row responsive-sm" ng-class="itemsClass[\'join-password\']"><div class="col col-20" translate>LOGIN.PASSWORD</div><div class="col" translate>HELP.JOIN.PASSWORD</div></div><a name="join-pseudo"></a><div class="row responsive-sm" ng-class="itemsClass[\'join-pseudo\']"><div class="col col-20" translate>ACCOUNT.NEW.PSEUDO</div><div class="col" translate>HELP.JOIN.PSEUDO</div></div><a name="login"></a><h2 translate>HELP.LOGIN.SECTION</h2><a name="login-pubkey"></a><div class="row responsive-sm" ng-class="itemsClass[\'login-pubkey\']"><div class="col col-20" translate>HELP.LOGIN.PUBKEY</div><div class="col" translate>HELP.LOGIN.PUBKEY_DEF</div></div><a name="login-method"></a><div class="row responsive-sm" ng-class="itemsClass[\'login-method\']"><div class="col col-20" translate>HELP.LOGIN.METHOD</div><div class="col" translate>HELP.LOGIN.METHOD_DEF</div></div><a name="glossary"></a><h2 translate>HELP.GLOSSARY.SECTION</h2><a name="pubkey"></a><div class="row responsive-sm" ng-class="itemsClass.pubkey"><div class="col col-20" translate>COMMON.PUBKEY</div><div class="col" translate>HELP.GLOSSARY.PUBKEY_DEF</div></div><a name="blockchain"></a><div class="row responsive-sm" ng-class="itemsClass.blockchain"><div class="col col-20" translate>HELP.GLOSSARY.BLOCKCHAIN</div><div class="col" translate>HELP.GLOSSARY.BLOCKCHAIN_DEF</div></div><a name="universal_dividend"></a> <a name="ud"></a><div class="row responsive-sm" ng-class="itemsClass.ud"><div class="col col-20" translate>COMMON.UNIVERSAL_DIVIDEND</div><div class="col" translate>HELP.GLOSSARY.UNIVERSAL_DIVIDEND_DEF</div></div><a name="member"></a><div class="row responsive-sm" ng-class="itemsClass.member"><div class="col col-20" translate>HELP.GLOSSARY.MEMBER</div><div class="col" translate>HELP.GLOSSARY.MEMBER_DEF</div></div><a name="wot"></a><div class="row responsive-sm" ng-class="itemsClass.wot"><div class="col col-20" translate>HELP.GLOSSARY.WOT</div><div class="col" translate>HELP.GLOSSARY.WOT_DEF</div></div><a name="currency_rules"></a><div class="row responsive-sm" ng-class="itemsClass.currency_rules"><div class="col col-20" translate>HELP.GLOSSARY.CURRENCY_RULES</div><div class="col" translate>HELP.GLOSSARY.CURRENCY_RULES_DEF</div></div><a name="distance_rule"></a><div class="row responsive-sm" ng-class="itemsClass.distance_rule"><div class="col col-20" translate>HELP.GLOSSARY.DISTANCE_RULE</div><div class="col" translate>HELP.GLOSSARY.DISTANCE_RULE_DEF</div></div>');
 $templateCache.put('templates/help/modal_help.html','<ion-modal-view class="modal-full-height modal-help"><ion-header-bar class="bar-positive"><button class="button button-clear" ng-click="closeModal()" translate>COMMON.BTN_CLOSE</button><h1 class="title" translate>HELP.TITLE</h1></ion-header-bar><ion-content scroll="true" class="padding no-padding-xs"><div ng-class="listClass"><ng-include src="::\'templates/help/help.html\'"></ng-include></div><div class="padding hidden-xs text-center"><button class="button button-positive ink" type="submit" ng-click="closeModal()">{{\'COMMON.BTN_CLOSE\' | translate}}</button></div></ion-content></ion-modal-view>');
 $templateCache.put('templates/help/view_help.html','<ion-view left-buttons="leftButtons"><ion-nav-title><span class="visible-xs visible-sm" translate="">HELP.TITLE</span></ion-nav-title><ion-content scroll="true" class="padding"><ng-include src="::\'templates/help/help.html\'"></ng-include></ion-content></ion-view>');
+$templateCache.put('templates/common/badge_certification_count.html','<span ng-attr-id="{{$ctrl.csId}}" class="badge badge-balanced" ng-class="{\'badge-energized\': $ctrl.requirements.willNeedCertificationCount || ($ctrl.requirements.needCertificationCount + $ctrl.requirements.pendingCertificationCount >= $ctrl.parameters.sigQty),\n               \'badge-assertive\': ($ctrl.requirements.needCertificationCount + $ctrl.requirements.pendingCertificationCount < $ctrl.parameters.sigQty)}"><span ng-if="$ctrl.requirements.certificationCount || !$ctrl.requirements.pendingCertificationCount"><i ng-if="!$ctrl.requirements.needCertificationCount" class="ion-android-done"></i> {{$ctrl.requirements.certificationCount}} <i ng-if="$ctrl.requirements.willNeedCertificationCount" class="ion-android-warning"></i> </span><span ng-if="$ctrl.requirements.pendingCertificationCount"><ng-if ng-if="$ctrl.requirements.certificationCount">+</ng-if><i class="ion-clock"></i> {{$ctrl.requirements.pendingCertificationCount}}</span></span>');
+$templateCache.put('templates/common/badge_given_certification_count.html','<div ng-attr-id="{{$ctrl.csId}}" class="badge badge-calm" ng-class="{\'badge-assertive\': $ctrl.identity.given_cert.length >= $ctrl.parameters.sigStock}"><span><i ng-if="$ctrl.identity.given_cert.length" class="ion-android-done"></i> {{$ctrl.identity.given_cert.length}} </span><span ng-if="$ctrl.identity.given_cert_pending.length">(<ng-if ng-if="$ctrl.identity.given_cert.length">+</ng-if><i class="ion-clock"></i> {{$ctrl.identity.given_cert_pending.length}}) </span><small>/ {{$ctrl.parameters.sigStock}}</small></div>');
+$templateCache.put('templates/common/form_error_messages.html','<div class="form-error" ng-message="minlength"><span translate="ERROR.FIELD_TOO_SHORT"></span></div><div class="form-error" ng-message="maxlength"><span translate="ERROR.FIELD_TOO_LONG"></span></div><div class="form-error" ng-message="pattern"><span translate="ERROR.FIELD_ACCENT"></span></div><div class="form-error" ng-message="required"><span translate="ERROR.FIELD_REQUIRED"></span></div>');
+$templateCache.put('templates/common/popover_copy.html','<ion-popover-view class="popover-copy" style="height: {{(!rows || rows &lt;= 1) ? 50 : rows*22}}px"><ion-content scroll="false"><div class="list"><div class="item item-input"><input type="text" autocomplete="off" ng-if="!rows || rows &lt;= 1" ng-model="value"><textarea ng-if="rows && rows > 1" ng-model="value" rows="{{rows}}" cols="10">\n      </textarea></div></div></ion-content></ion-popover-view>');
+$templateCache.put('templates/common/popover_helptip.html','<ion-popover-view class="popover-helptip"><ion-content scroll="false" class="list"><p><i ng-if="icon.position && !icon.position.startsWith(\'bottom-\')" class="{{icon.class}} icon-{{icon.position}} hidden-xs" style="{{icon.style}}"></i><a ng-click="closePopover()" class="pull-right button-close" ng-class="{\'pull-left\': icon.position === \'right\', \'pull-right\': icon.position !== \'right\'}"><i class="ion-close"></i> </a><span>&nbsp;</span></p><p class="padding light"><ng-bind-html ng-bind-html="content | translate:contentParams"></ng-bind-html><ng-bind-html ng-bind-html="trustContent"></ng-bind-html></p><div class="text-center" ng-if="!tour"><button class="button button-small button-stable" ng-if="!hasNext" ng-click="closePopover(true)" translate>COMMON.BTN_UNDERSTOOD</button> <button class="button button-small button-stable" id="helptip-btn-ok" ng-if="hasNext" ng-click="closePopover(false)" translate>COMMON.BTN_UNDERSTOOD</button> <button id="helptip-btn-ok" class="button button-small button-positive icon-right ink" ng-if="hasNext" ng-click="closePopover(true)"><i class="icon ion-chevron-right"></i></button></div><div class="text-center" ng-if="tour"><button class="button button-small button-positive" id="helptip-btn-ok" ng-if="!hasNext" ng-click="closePopover(false)" translate>COMMON.BTN_CLOSE</button> <button id="helptip-btn-ok" class="button button-small button-positive icon-right ink" ng-if="hasNext" ng-click="closePopover(true)">{{\'COMMON.BTN_CONTINUE\'|translate}} <i class="icon ion-chevron-right"></i></button></div><p><i ng-if="icon.position && icon.position.startsWith(\'bottom-\')" class="{{icon.class}} icon-{{icon.position}} hidden-xs"></i></p></ion-content></ion-popover-view>');
+$templateCache.put('templates/common/popover_locales.html','<ion-popover-view class="fit popover-locales" style="height: {{locales.length*48}}px"><ion-content scroll="false"><div class="list item-text-wrap block"><a ng-repeat="l in locales track by l.id" class="item item-icon-left ink" ng-click="changeLanguage(l.id)"><i class="item-image avatar" style="background-image: url(./img/flag-{{l.flag}}.png)"></i> {{l.label | translate}}</a></div></ion-content></ion-popover-view>');
+$templateCache.put('templates/common/popover_profile.html','');
+$templateCache.put('templates/common/popover_share.html','<ion-popover-view class="popover-share"><ion-content scroll="false"><div class="bar bar-header"><h1 class="title">{{titleKey|translate:titleValues}}</h1><span class="gray pull-right">{{time|formatDate}}</span></div><div class="list no-margin no-padding has-header has-footer block"><div class="item item-input"><input type="text" autocomplete="off" ng-model="value"></div></div><div class="bar bar-footer"><div class="button-bar"><a class="button button-icon positive icon ion-social-facebook" href="https://www.facebook.com/sharer/sharer.php?u={{postUrl|formatEncodeURI}}&amp;title={{postMessage|formatEncodeURI}}" onclick="window.open(this.href, \'facebook-share\',\'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=580,height=296\');return false;" title="{{\'COMMON.POPOVER_SHARE.SHARE_ON_FACEBOOK\'|translate}}"></a> <a class="button button-icon positive icon ion-social-twitter" href="https://twitter.com/intent/tweet?url={{postUrl|formatEncodeURI}}&amp;text={{postMessage|formatEncodeURI}}" onclick="window.open(this.href, \'twitter-share\',\'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=580,height=296\');return false;" title="{{\'COMMON.POPOVER_SHARE.SHARE_ON_TWITTER\'|translate}}"></a> <a class="button button-icon positive icon ion-social-googleplus" href="https://plus.google.com/share?url={{postUrl|formatEncodeURI}}" onclick="window.open(this.href, \'google-plus-share\', \'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=296,width=580\');return false;" title="{{\'COMMON.POPOVER_SHARE.SHARE_ON_GOOGLEPLUS\'|translate}}"></a> <a class="button button-icon positive icon ion-social-diaspora" href="https://sharetodiaspora.github.io/?title={{postMessage|formatEncodeURI}}&amp;url={{postUrl|formatEncodeURI}}" onclick="window.open(this.href, \'diaspora-share\',\'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,width=580,height=296\');return false;" title="{{\'COMMON.POPOVER_SHARE.SHARE_ON_DIASPORA\'|translate}}"></a> <a class="button-close" title="{{\'COMMON.BTN_CLOSE\'|translate}}" ng-click="closePopover()"><i class="icon ion-close"></i></a></div></div></ion-content></ion-popover-view>');
+$templateCache.put('templates/common/popup_password.html','<form name="pwdForm" ng-submit="submit($event)"><div class="list" ng-init="setForm(pwdForm)"><label class="item item-input" ng-class="{\'item-input-error\': pwdForm.$submitted && pwdForm.password.$invalid}"><input name="password" type="password" placeholder="{{\'ACCOUNT.SECURITY.KEYFILE.PASSWORD_POPUP.PASSWORD_HELP\' | translate}}" ng-model="formData.password" ng-minlength="1" required></label><div class="form-errors" ng-if="pwdForm.$submitted && pwdForm.pseudo.$error" ng-messages="pwdForm.password.$error"><div class="form-error" ng-message="required"><span translate="ERROR.FIELD_REQUIRED"></span></div><div class="form-error" ng-message="minlength"><span translate="ERROR.FIELD_TOO_SHORT"></span></div></div><div class="form-errors" ng-if="error"><div class="form-error">{{error|translate}}</div></div></div></form>');
+$templateCache.put('templates/common/qrcode.html','<a ng-attr-id="{{ qrcodeId }}" ng-show="!loading" class="qrcode fade-in pull-right" ng-class="{\'active\': toggleQRCode}" ng-click="toggleQRCode = !toggleQRCode"><div class="content"></div><div class="footer item item-icon-left item-text-wrap ink" on-hold="copy(formData.pubkey)" copy-on-click="{{:rebind:formData.pubkey}}" ng-click="$event.stopPropagation()"><i class="icon ion-key"></i> <span>{{:locale:\'COMMON.PUBKEY\'|translate}}</span><h4 id="pubkey" class="dark">{{:rebind:formData.pubkey}}</h4></div></a>');
+$templateCache.put('templates/common/view_passcode.html','<ion-view left-buttons="leftButtons"><ion-nav-title><span class="visible-xs visible-sm" translate>COMMON.PASSCODE.TITLE</span></ion-nav-title><ion-content scroll="false"></ion-content></ion-view>');
 $templateCache.put('templates/home/home.html','<ion-view id="home"><ion-nav-title></ion-nav-title><ion-nav-buttons side="secondary"></ion-nav-buttons><ion-content class="positive-900-bg circle-bg-dark"><div class="row padding-horizontal no-padding-xxs responsive-lg"><div class="col text-center no-padding-xs main-container"><div id="helptip-home-logo" class="logo"></div><h4><span class="hidden-xs" translate="">HOME.WELCOME</span> <b ng-show="!loading" translate-values=":currency:{currency: $root.currency.name}" translate="">HOME.MESSAGE</b></h4><div class="center padding" ng-if="loading"><ion-spinner icon="android"></ion-spinner></div><div class="center padding animate-fade-in animate-show-hide ng-hide" ng-show="!loading && error"><div class="card card-item padding"><p class="item-content item-text-wrap"><span class="dark" trust-as-html="\'HOME.CONNECTION_ERROR\'|translate:node"></span></p><button type="button" class="button button-positive icon icon-left ion-refresh ink" ng-click="reload()">{{\'COMMON.BTN_REFRESH\'|translate}}</button></div></div><div class="center animate-show-hide ng-hide" ng-show="!loading && !error"><button type="button" class="button button-block button-stable button-raised icon-left icon ion-easel ink-dark hidden-xs" ng-show="login" ng-click="startHelpTour($event)">{{\'COMMON.BTN_HELP_TOUR\'|translate}}</button> <button type="button" class="button button-block button-positive button-raised ink-dark" ng-click="showJoinModal()" ng-if="!login" translate="">LOGIN.CREATE_FREE_ACCOUNT</button> <button type="button" class="item button button-block button-raised icon icon-left ion-person ink-dark" ng-class="{\'button-stable\': smallscreen, \'button-positive\': !smallscreen}" ui-sref="app.view_wallet" ng-show="login" translate="">MENU.ACCOUNT</button> <button type="button" class="item button button-block button-stable button-raised icon icon-left ion-card ink-dark visible-xs" ui-sref="app.view_wallet_tx" ng-if="login"><b class="icon-secondary ion-clock" style="left: 52px; top: -2px; font-size: 10pt; display: block"></b> {{\'MENU.TRANSACTIONS\'|translate}}</button> <button type="button" class="item button button-block button-positive button-raised icon icon-left ion-paper-airplane ink-dark visible-xs" ng-click="showTransferModal()" ng-if="login" translate="">COMMON.BTN_SEND_MONEY</button><br class="visible-xs visible-sm"><div class="text-center no-padding" ng-show="!login"><br class="visible-xs visible-sm">{{\'LOGIN.HAVE_ACCOUNT_QUESTION\'|translate}} <b></b></div><div class="text-center no-padding" ng-show="login"><br class="visible-xs visible-sm"><span ng-bind-html="\'HOME.NOT_YOUR_ACCOUNT_QUESTION\'|translate:{pubkey: walletData.pubkey}"></span><br><b><a class="assertive" ng-click="logout({askConfirm: true})" translate="">HOME.BTN_CHANGE_ACCOUNT</a></b></div><button type="button" class="button button-block button-stable button-raised ink visible-xs visible-sm" ui-sref="app.view_wallet" ng-if="!login" translate="">COMMON.BTN_LOGIN</button><div class="text-center no-padding visible-xs stable"><br>{{\'COMMON.APP_VERSION\'|translate:{version: config.version} }} | <a href="#" ng-click="showAboutModal()" translate="">HOME.BTN_ABOUT</a></div></div></div><div class="col no-padding" ng-class="{\'col-30\': !feed, \'col-10\': feed}">&nbsp;</div><div class="col col-30 no-padding" ng-if="feed"><div class="feed padding-horizontal no-padding-xs padding-top"><h3 class="padding-left"><i class="icon ion-speakerphone"></i> {{feed.title}} <small><a ng-click="openLink($event, feed.home_page_url)" class="gray"><span translate="">HOME.SHOW_ALL_FEED</span> <i class="icon ion-chevron-right"></i></a></small></h3><div class="animate-show-hide ng-hide" ng-show="feed"><div ng-repeat="item in feed.items" class="card padding no-margin-xs"><div class="header"><i ng-if="item.author.avatar" class="avatar" style="background-image: url({{item.author.avatar}})"></i> <a ng-class="{\'avatar-left-padding\': item.author.avatar}" class="author" ng-click="item.author.url && openLink($event, item.author.url)">{{item.author.name}} </a><a ng-if="item.time" title="{{item.time|formatDate}}" ng-click="openLink($event, item.url)" class="item-note"><small><i class="icon ion-clock"></i>&nbsp;{{item.time|formatFromNow}}</small></a></div><h2 class="title feed-title"><a ng-click="openLink($event, item.url)">{{item.title}}</a></h2><div ng-if="item.content" class="content feed-content" trust-as-html="item.content"></div><h4 class="card-footer feed-footer text-right positive-100"><a ng-click="openLink($event, item.url)"><span ng-if="item.truncated" translate="">HOME.READ_MORE</span> <span ng-if="!item.truncated" translate="">COMMON.BTN_SHOW</span> <i class="icon ion-chevron-right"></i></a></h4></div></div></div></div></div></ion-content></ion-view>');
 $templateCache.put('templates/join/modal_choose_account_type.html','<ion-modal-view class="modal-full-height"><ion-header-bar class="bar-positive"><button class="button button-clear visible-xs" ng-if="!slides.slider.activeIndex" ng-click="closeModal()" translate="">COMMON.BTN_CANCEL</button> <button class="button button-icon button-clear icon ion-ios-arrow-back buttons header-item" ng-click="slidePrev()" ng-if="slides.slider.activeIndex"></button><h1 class="title" translate="">ACCOUNT.NEW.TITLE</h1><button class="button button-clear icon-right visible-xs" ng-if="slides.slider.activeIndex === 0" ng-click="slideNext()"><span translate="">COMMON.BTN_NEXT</span> <i class="icon ion-ios-arrow-right"></i></button></ion-header-bar><ion-slides options="slides.options" slider="slides.slider"><ion-slide-page><ion-content class="has-header padding"><div class="center padding" ng-if="loading"><ion-spinner class="icon" icon="android"></ion-spinner></div><div ng-if="!loading"><p ng-bind-html="\'ACCOUNT.NEW.INTRO_WARNING_TIME\'|translate:currency"></p><div class="row responsive-sm"><div class="col"><div class="item card item-icon-left padding item-text-wrap stable-bg"><i class="icon ion-android-warning assertive"></i><p class="item-content item-icon-left-padding"><span class="dark" translate="">ACCOUNT.NEW.INTRO_WARNING_SECURITY</span><br><small translate="">ACCOUNT.NEW.INTRO_WARNING_SECURITY_HELP</small></p></div></div><div class="col"><div class="item card item-icon-left padding item-text-wrap stable-bg"><i class="icon ion-information-circled positive"></i><p class="item-content item-icon-left-padding"><span class="dark" trust-as-html="\'ACCOUNT.NEW.REGISTRATION_NODE\'|translate:currency.node"></span><br><small trust-as-html="\'ACCOUNT.NEW.REGISTRATION_NODE_HELP\'|translate:currency.node"></small></p></div></div></div></div><div class="padding hidden-xs text-right"><button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate="">COMMON.BTN_CANCEL</button> <button class="button button-positive icon-right ion-chevron-right ink" ng-click="slideNext()" ng-disabled="loading" type="button" translate="">COMMON.BTN_START</button></div></ion-content></ion-slide-page><ion-slide-page><ion-content class="has-header padding"><p translate="">ACCOUNT.NEW.SELECT_ACCOUNT_TYPE</p><div class="list"><div class="item item-complex card stable-bg item-icon-left item-icon-right ink" ng-click="selectAccountTypeAndClose(\'member\')"><div class="item-content item-text-wrap"><i class="item-image icon dark ion-person"></i><h2 translate="">ACCOUNT.NEW.MEMBER_ACCOUNT</h2><h4 class="gray" ng-bind-html="\'ACCOUNT.NEW.MEMBER_ACCOUNT_HELP\'|translate:currency"></h4><i class="icon dark ion-ios-arrow-right"></i></div></div><cs-extension-point name="select-account-type"></cs-extension-point><div class="item item-complex card stable-bg item-icon-left item-icon-right ink" ng-click="selectAccountTypeAndClose(\'wallet\')"><div class="item-content item-text-wrap"><i class="item-image icon dark ion-card"></i><h2 translate="">ACCOUNT.NEW.WALLET_ACCOUNT</h2><h4 class="gray" translate="">ACCOUNT.NEW.WALLET_ACCOUNT_HELP</h4><i class="icon dark ion-ios-arrow-right"></i></div></div></div><div class="padding hidden-xs text-right"><button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate="">COMMON.BTN_CANCEL</button></div></ion-content></ion-slide-page></ion-slides></ion-modal-view>');
 $templateCache.put('templates/join/modal_join_member.html','<ion-modal-view class="modal-full-height"><ion-header-bar class="bar-positive"><button class="button button-clear visible-xs" ng-if="!slides.slider.activeIndex" ng-click="closeModal()" translate>COMMON.BTN_CANCEL</button> <button class="button button-icon button-clear icon ion-ios-arrow-back buttons header-item" ng-click="doPrev()" ng-if="slides.slider.activeIndex && slideBehavior.hasPreviousButton"></button> <button class="button button-icon button-clear icon ion-ios-help-outline visible-xs" ng-if="slideBehavior.helpAnchor" ng-click="showHelpModal(slideBehavior.helpAnchor)"></button><h1 class="title" translate>ACCOUNT.NEW.MEMBER_ACCOUNT_TITLE</h1><button class="button button-clear icon-right visible-xs" ng-if="slideBehavior.hasNextButton" ng-click="doNext()"><span translate>COMMON.BTN_NEXT</span> <i class="icon ion-ios-arrow-right"></i></button> <button class="button button-clear icon-right visible-xs" ng-class="{\'button-text-stable\': !isLicenseRead}" ng-if="slideBehavior.hasAcceptButton" ng-click="isLicenseRead ? doNext() : undefined"><span translate>ACCOUNT.NEW.BTN_ACCEPT</span> <i class="icon ion-ios-arrow-right"></i></button> <button class="button button-clear icon-right visible-xs" ng-if="slideBehavior.hasSendButton" ng-click="doNewAccount()"><i class="icon ion-android-send"></i></button></ion-header-bar><ion-slides options="slides.options" slider="slides.slider"><ion-slide-page ng-if="licenseFileUrl"><ion-content class="has-header" scroll="false"><div class="padding" translate>ACCOUNT.NEW.INFO_LICENSE</div><div class="center padding" ng-if="loading"><ion-spinner class="icon" icon="android"></ion-spinner></div><iframe ng-if="!loading" class="padding-left padding-right no-padding-xs iframe-license" id="iframe-license" ng-src="{{licenseFileUrl}}"></iframe><div class="padding hidden-xs text-right"><button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate>COMMON.BTN_CANCEL</button> <button class="button button-calm icon-right ion-chevron-right ink" ng-click="doNext(\'licenceForm\')" ng-disabled="!isLicenseRead" type="button" translate>ACCOUNT.NEW.BTN_ACCEPT_LICENSE</button></div></ion-content></ion-slide-page><ion-slide-page><ion-content class="has-header" scroll="true"><form name="pseudoForm" novalidate="" ng-submit="doNext(\'pseudoForm\')"><div class="item item-text-wrap text-center padding"><a class="pull-right icon-help hidden-xs" ng-click="showHelpModal(\'join-pseudo\')"></a> <span translate>ACCOUNT.NEW.PSEUDO_WARNING</span></div><div class="list" ng-init="setForm(pseudoForm, \'pseudoForm\')"><div class="item item-input" ng-class="{\'item-input-error\': (pseudoForm.$submitted && pseudoForm.pseudo.$invalid) || (uiAlreadyUsed && formData.pseudo)}"><span class="input-label" translate>ACCOUNT.NEW.PSEUDO</span> <input id="pseudo" name="pseudo" type="text" placeholder="{{\'ACCOUNT.NEW.PSEUDO_HELP\' | translate}}" ng-model="formData.pseudo" autocomplete="off" ng-minlength="3" ng-maxlength="100" ng-pattern="userIdPattern" ng-model-options="{ debounce: 250 }" required></div><div class="form-errors" ng-show="pseudoForm.$submitted && pseudoForm.pseudo.$error" ng-messages="pseudoForm.pseudo.$error"><div class="form-error" ng-message="minlength"><span translate="ERROR.FIELD_TOO_SHORT_WITH_LENGTH" translate-values="{minLength: 3}"></span></div><div class="form-error" ng-message="maxlength"><span translate="ERROR.FIELD_TOO_LONG_WITH_LENGTH" translate-values="{maxLength: 100}"></span></div><div class="form-error" ng-message="required"><span translate="ERROR.FIELD_REQUIRED"></span></div><div class="form-error" ng-message="pattern"><span translate="ERROR.INVALID_USER_ID"></span></div></div><div class="text-right" style="min-height: 18px"><div class="form-error gray" ng-if="formData.computing && formData.pseudo"><ion-spinner class="icon ion-spinner-small" icon="android" ng-if="formData.computing && formData.pseudo"></ion-spinner><span translate>ACCOUNT.NEW.CHECKING_PSEUDO</span></div><ng-if ng-if="!formData.computing && formData.pseudo"><div class="form-error balanced" ng-if="!uiAlreadyUsed "><i class="icon ion-checkmark balanced"></i> <span translate>ACCOUNT.NEW.PSEUDO_AVAILABLE</span></div><div class="form-error" ng-if="uiAlreadyUsed"><i class="icon ion-close-circled assertive"></i> <span translate>ACCOUNT.NEW.PSEUDO_NOT_AVAILABLE</span></div></ng-if></div><div class="padding hidden-xs text-right"><button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate>COMMON.BTN_CANCEL</button> <button class="button button-calm icon-right ion-chevron-right ink" type="submit" ng-disabled="uiAlreadyUsed" translate>COMMON.BTN_NEXT</button></div></div></form></ion-content></ion-slide-page><ion-slide-page ng-if="!formData.pubkey"><ion-content class="has-header" scroll="true"><form name="saltForm" novalidate="" ng-submit="doNext(\'saltForm\')"><div class="list" ng-init="setForm(saltForm, \'saltForm\')"><div class="item item-text-wrap text-center padding hidden-xs"><a class="pull-right icon-help" ng-click="showHelpModal(\'join-salt\')"></a> <span translate>ACCOUNT.NEW.SALT_WARNING</span></div><div class="item item-input" ng-class="{ \'item-input-error\': saltForm.$submitted && saltForm.username.$invalid}"><span class="input-label" translate>LOGIN.SALT</span> <input ng-if="!showUsername" name="username" type="password" placeholder="{{\'LOGIN.SALT_HELP\' | translate}}" ng-change="formDataChanged()" ng-model="formData.username" autocomplete="off" ng-minlength="8" different-to="formData.pseudo" required> <input ng-if="showUsername" name="username" type="text" placeholder="{{\'LOGIN.SALT_HELP\' | translate}}" ng-change="formDataChanged()" ng-model="formData.username" autocomplete="off" ng-minlength="8" different-to="formData.pseudo" required></div><div class="form-errors" ng-show="saltForm.$submitted && saltForm.username.$error" ng-messages="saltForm.username.$error"><div class="form-error" ng-message="minlength"><span translate="ERROR.FIELD_TOO_SHORT_WITH_LENGTH" translate-values="{minLength: 8}"></span></div><div class="form-error" ng-message="required"><span translate="ERROR.FIELD_REQUIRED"></span></div><div class="form-error" ng-message="differentTo"><span translate="ERROR.EQUALS_TO_PSEUDO"></span></div></div><div class="item item-input" ng-class="{ \'item-input-error\': saltForm.$submitted && saltForm.confirmSalt.$invalid}"><span class="input-label pull-right" translate>ACCOUNT.NEW.SALT_CONFIRM</span> <input ng-if="!showUsername" name="confirmUsername" type="password" placeholder="{{\'ACCOUNT.NEW.SALT_CONFIRM_HELP\' | translate}}" ng-model="formData.confirmUsername" autocomplete="off" compare-to="formData.username"> <input ng-if="showUsername" name="confirmUsername" type="text" placeholder="{{\'ACCOUNT.NEW.SALT_CONFIRM_HELP\' | translate}}" ng-model="formData.confirmUsername" autocomplete="off" compare-to="formData.username"></div><div class="form-errors" ng-show="saltForm.$submitted && saltForm.confirmUsername.$error" ng-messages="saltForm.confirmUsername.$error"><div class="form-error" ng-message="compareTo"><span translate="ERROR.SALT_NOT_CONFIRMED"></span></div></div><div class="item item-toggle dark"><span translate>COMMON.SHOW_VALUES</span><label class="toggle toggle-royal"><input type="checkbox" ng-model="showUsername"><div class="track"><div class="handle"></div></div></label></div><div class="padding hidden-xs text-right"><button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate>COMMON.BTN_CANCEL</button> <button class="button button-calm icon-right ion-chevron-right ink" type="submit" translate>COMMON.BTN_NEXT <i class="icon ion-arrow-right-a"></i></button></div></div></form></ion-content></ion-slide-page><ion-slide-page ng-if="!formData.pubkey"><ion-content class="has-header" scroll="true"><form name="passwordForm" novalidate="" ng-submit="doNext(\'passwordForm\')"><div class="item item-text-wrap text-center padding hidden-xs"><a class="pull-right icon-help" ng-click="showHelpModal(\'join-password\')"></a> <span translate>ACCOUNT.NEW.PASSWORD_WARNING</span></div><div class="list" ng-init="setForm(passwordForm, \'passwordForm\')"><div class="item item-input" ng-class="{ \'item-input-error\': passwordForm.$submitted && passwordForm.password.$invalid}"><span class="input-label" translate>LOGIN.PASSWORD</span> <input ng-if="!showPassword" name="password" type="password" placeholder="{{\'LOGIN.PASSWORD_HELP\' | translate}}" ng-model="formData.password" autocomplete="off" ng-change="formDataChanged()" ng-minlength="8" different-to="formData.username" required> <input ng-if="showPassword" name="text" type="text" placeholder="{{\'LOGIN.PASSWORD_HELP\' | translate}}" ng-model="formData.password" autocomplete="off" ng-change="formDataChanged()" ng-minlength="8" different-to="formData.username" required></div><div class="form-errors" ng-show="passwordForm.$submitted && passwordForm.password.$error" ng-messages="passwordForm.password.$error"><div class="form-error" ng-message="minlength"><span translate="ERROR.FIELD_TOO_SHORT_WITH_LENGTH" translate-values="{minLength: 8}"></span></div><div class="form-error" ng-message="required"><span translate="ERROR.FIELD_REQUIRED"></span></div><div class="form-error" ng-message="differentTo"><span translate="ERROR.EQUALS_TO_SALT"></span></div></div><div class="item item-input" ng-class="{ \'item-input-error\': passwordForm.$submitted && passwordForm.confirmPassword.$invalid}"><span class="input-label" translate>ACCOUNT.NEW.PASSWORD_CONFIRM</span> <input ng-if="!showPassword" name="confirmPassword" type="password" placeholder="{{\'ACCOUNT.NEW.PASSWORD_CONFIRM_HELP\' | translate}}" ng-model="formData.confirmPassword" autocomplete="off" compare-to="formData.password"> <input ng-if="showPassword" name="confirmPassword" type="text" placeholder="{{\'ACCOUNT.NEW.PASSWORD_CONFIRM_HELP\' | translate}}" ng-model="formData.confirmPassword" autocomplete="off" compare-to="formData.password"></div><div class="form-errors" ng-show="passwordForm.$submitted && passwordForm.confirmPassword.$error" ng-messages="passwordForm.confirmPassword.$error"><div class="form-error" ng-message="compareTo"><span translate="ERROR.PASSWORD_NOT_CONFIRMED"></span></div></div><div class="item item-toggle dark"><span translate>COMMON.SHOW_VALUES</span><label class="toggle toggle-royal"><input type="checkbox" ng-model="showPassword"><div class="track"><div class="handle"></div></div></label></div></div><div class="padding hidden-xs text-right"><button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate>COMMON.BTN_CANCEL</button> <button class="button button-calm icon-right ion-chevron-right ink" type="submit" ng-click="getRevocationDocument()" translate>COMMON.BTN_NEXT</button></div><div class="padding hidden-xs"></div></form></ion-content></ion-slide-page><ion-slide-page><ion-content class="has-header" scroll="true"><div class="center padding" ng-if="formData.computing"><ion-spinner icon="android"></ion-spinner></div><ng-if ng-if="!formData.computing"><div class="animate-fade-in animate-show-hide ng-hide" ng-show="accountAvailable"><div class="padding text-center" translate>ACCOUNT.NEW.LAST_SLIDE_CONGRATULATION</div><div class="list"><ion-item class="item text-center item-text-wrap"><h3 class="gray" translate>LOGIN.ASSOCIATED_PUBKEY</h3><h3 class="dark bold">{{formData.pubkey}}</h3></ion-item></div><div class="padding hidden-xs text-right"><button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate>COMMON.BTN_CANCEL</button> <button class="button button-positive ink" ng-click="doNewAccount()" translate>COMMON.BTN_SEND <i class="icon ion-android-send"></i></button></div></div><div class="animate-fade-in animate-show-hide ng-hide" ng-show="!accountAvailable"><ion-item class="item-icon-left item-text-wrap text-center"><i class="icon ion-minus-circled assertive"></i> <span id="modal-license" trust-as-html="\'ERROR.EXISTING_ACCOUNT\'|translate"></span></ion-item><div class="list"><ion-item class="item item-text-wrap item-border"><div class="padding text-center"><span class="gray text-no-wrap">{{formData.pubkey}}</span></div></ion-item><div class="padding text-center"><span translate>ERROR.EXISTING_ACCOUNT_REQUEST</span></div></div><div class="padding hidden-xs text-left"><button class="button button-assertive icon-left ion-chevron-left ink" ng-click="identifierRecovery()" translate>COMMON.BTN_MODIFY</button></div></div></ng-if></ion-content></ion-slide-page></ion-slides></ion-modal-view>');
@@ -32171,6 +32171,34 @@ $templateCache.put('plugins/rml9/templates/07-button.html','<!-- Button: Open a
 $templateCache.put('plugins/rml9/templates/07-view.html','<leaflet id="map-geojson" center="map.center" geojson="map.geojson"></leaflet>\n');
 $templateCache.put('plugins/rml9/templates/final-button.html','<!-- Button: Open a view, using `ui-sref` attribute -->\n<button ng-if class="button button-balanced button-small-padding icon ion-android-archive"\n        ui-sref="app.rml9({pubkey: formData.pubkey})"\n        title="{{\'RML9.BTN_SWOW_TX\' | translate}}">\n</button>\n\n');
 $templateCache.put('plugins/rml9/templates/final-view.html','<ion-view left-buttons="leftButtons">\n  <ion-nav-title>\n    {{\'RML9.VIEW.TITLE\' | translate}}\n  </ion-nav-title>\n\n  <ion-content>\n    <div class="list">\n\n      <!-- buttons bar -->\n      <div class="center padding">\n        <div class="buttons">\n          <button class="button button-balanced icon-left icon ion-archive"\n                  ng-click="onExportButtonClick()">{{\'RML9.BTN_EXPORT\' | translate}}\n          </button>\n        </div>\n      </div>\n\n      <!-- the pubkey -->\n      <div class="item">\n        {{\'COMMON.PUBKEY\'|translate}}\n        <div class="badge">{{pubkey|formatPubkey}}</div>\n      </div>\n\n      <!-- the balance -->\n      <div class="item">\n        {{\'RML9.VIEW.BALANCE\'|translate}}\n        <div class="badge badge-calm">\n          {{balance|formatAmount}} <span ng-bind-html="$root.currency.name|currencySymbol"></span>\n        </div>\n      </div>\n\n      <!-- a text divider-->\n      <div class="item item-divider">{{\'RML9.VIEW.DIVIDER\'|translate:{pubkey: pubkey} }}</div>\n\n      <!-- iterate on each TX -->\n      <div class="row">\n        <div class="col col-75">\n          <div class="item item-text-wrap" ng-repeat="item in items">\n\n            <h3>\n              {{item.time|formatDate}}\n              <span ng-if="item.comment" class="gray"> | {{item.comment}}</span>\n            </h3>\n\n            <h4 ng-if="item.uid" class="positive"><i class="icon ion-person"></i> {{item.name||item.uid}}</h4>\n            <h4 ng-if="!item.uid" class="gray"><i class="icon ion-key"></i> {{item.pubkey|formatPubkey}}</h4>\n\n            <div class="badge"\n                 ng-class="{\'badge-balanced\': item.amount > 0}">\n              {{item.amount|formatAmount}} <span ng-bind-html="$root.currency.name|currencySymbol"></span>\n            </div>\n          </div>\n\n        </div>\n\n        <div class="col col-25">\n\n          <!-- [NEW] TX input chart -->\n          <p class="gray" translate>RML9.CHART.INPUT_CHART_TITLE</p>\n          <canvas id="chart-received-pie" class="chart-pie"\n                  chart-data="inputChart.data"\n                  chart-labels="inputChart.labels">\n          </canvas>\n\n          <!-- [NEW] TX input chart -->\n          <p class="gray" translate>RML9.CHART.OUTPUT_CHART_TITLE</p>\n          <canvas id="chart-sent-pie" class="chart-pie"\n                  chart-data="outputChart.data"\n                  chart-labels="outputChart.labels">\n          </canvas>\n        </div>\n      </div>\n    </div>\n  </ion-content>\n</ion-view>\n');
+$templateCache.put('plugins/graph/templates/account/graph_balance.html','\n    <!-- button bar -->\n    <div class="button-bar-inline "\n         style="top: 33px; margin-top:-33px; position: relative;">\n      <button\n        class="button button-stable button-clear no-padding-xs pull-right"\n        ng-click="showActionsPopover($event)">\n        <i class="icon ion-navicon-round"></i>\n      </button>\n    </div>\n\n    <div class="padding-left padding-right">\n      <canvas id="account-balance" class="chart-bar"\n              height="{{height}}" width="{{width}}"\n              chart-data="data"\n              chart-dataset-override="datasetOverride"\n              chart-colors="colors"\n              chart-options="options"\n              chart-labels="labels"\n              chart-click="onChartClick">\n      </canvas>\n    </div>\n\n    <ng-include src="::\'plugins/graph/templates/common/graph_range_bar.html\'"></ng-include>\n');
+$templateCache.put('plugins/graph/templates/account/graph_certifications.html','\n    <div class="padding-left padding-right">\n      <canvas id="account-certifications" class="chart-bar"\n              height="{{height}}" width="{{width}}"\n              chart-data="data"\n              chart-dataset-override="datasetOverride"\n              chart-colors="colors"\n              chart-options="options"\n              chart-labels="labels"\n              chart-click="onChartClick">\n      </canvas>\n    </div>\n');
+$templateCache.put('plugins/graph/templates/account/graph_sum_tx.html','<div class="row responsive-sm" ng-if="!loading">\n\n  <div class="col col-10 hidden-xs hidden-sm">&nbsp;</div>\n\n  <div class="col text-center">\n\n    <!-- TX input chart -->\n    <p class="gray padding text-wrap"\n       ng-if="inputChart.data.length"\n       translate>GRAPH.ACCOUNT.INPUT_CHART_TITLE</p>\n    <canvas id="chart-received-pie" class="chart-pie"\n            chart-data="inputChart.data"\n            chart-labels="inputChart.labels"\n            chart-colors="inputChart.colors"\n            chart-click="onInputChartClick">\n    </canvas>\n\n  </div>\n\n  <div class="col col-10 hidden-xs hidden-sm">&nbsp;</div>\n\n  <div class="col text-center">\n\n    <!-- TX output chart -->\n    <p class="gray padding text-wrap"\n       ng-if="outputChart.data.length"\n       translate>GRAPH.ACCOUNT.OUTPUT_CHART_TITLE</p>\n    <canvas id="chart-sent-pie" class="chart-pie"\n            chart-data="outputChart.data"\n            chart-labels="outputChart.labels"\n            chart-colors="outputChart.colors"\n            chart-click="onOutputChartClick">\n    </canvas>\n\n  </div>\n\n  <div class="col col-10 hidden-xs hidden-sm">&nbsp;</div>\n\n</div>\n');
+$templateCache.put('plugins/graph/templates/account/view_identity_tx_extend.html','<!-- Buttons section -->\n<ng-if ng-if=":state:enable && extensionPoint === \'buttons\'">\n\n  <button class="button button-stable button-small-padding icon ion-stats-bars"\n          ui-sref="app.wot_identity_stats({pubkey: formData.pubkey})"\n          title="{{\'GRAPH.ACCOUNT.BTN_SHOW_STATS\' | translate}}">\n  </button>\n\n</ng-if>\n');
+$templateCache.put('plugins/graph/templates/account/view_stats.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n  <ion-nav-title>\n    {{\'GRAPH.ACCOUNT.TITLE\' | translate}}{{id}}\n  </ion-nav-title>\n\n  <ion-content scroll="true" class="no-padding">\n\n\n\n    <div class="list" >\n\n      <!--  - - - - Balance - - - - -->\n      <ng-controller ng-controller="GpAccountBalanceCtrl">\n        <div class="center padding" ng-if="loading">\n          <ion-spinner icon="android"></ion-spinner>\n        </div>\n\n        <div class="item item-divider" ng-if="!loading" >\n          {{\'GRAPH.ACCOUNT.BALANCE_DIVIDER\'|translate}}\n          <ion-spinner ng-if="loadingRange" class="ion-spinner-small" icon="android"></ion-spinner>\n        </div>\n\n        <div class="item no-padding-xs"\n             ng-if="!loading"\n             ng-include="::\'plugins/graph/templates/account/graph_balance.html\'"\n             ng-init="setSize(350, 1000)">\n        </div>\n      </ng-controller>\n\n    </div>\n\n    <div class="item no-padding-xs"\n         ng-include="::\'plugins/graph/templates/account/graph_sum_tx.html\'"\n         ng-controller="GpAccountSumTxCtrl">\n    </div>\n\n    <!--  - - - - WOT - - - -\n    <div class="item item-divider" translate>\n      GRAPH.ACCOUNT.WOT_DIVIDER\n    </div>\n\n    <div class="item no-padding-xs"\n         ng-include="::\'plugins/graph/templates/account/graph_certifications.html\'"\n         ng-controller="GpAccountCertificationCtrl"\n         ng-init="setSize(350, 1000)">\n    </div>-->\n\n  </ion-content>\n\n</ion-view>\n');
+$templateCache.put('plugins/graph/templates/account/view_wallet_tx_extend.html','<!-- Buttons section -->\n<ng-if ng-if=":state:enable && extensionPoint === \'buttons\'">\n\n  <button class="button button-stable button-small-padding icon ion-stats-bars"\n          ui-sref="app.wot_identity_stats({pubkey: formData.pubkey})"\n          title="{{\'GRAPH.ACCOUNT.BTN_SHOW_STATS\' | translate}}">\n  </button>\n\n</ng-if>\n');
+$templateCache.put('plugins/graph/templates/blockchain/graph_block_issuers.html','\n    <div class="row responsive-lg">\n\n      <!-- bar -->\n      <div class="col col-75">\n        <canvas id="bar" class="chart-bar"\n                height="{{height}}" width="{{width}}"\n                chart-data="data"\n                chart-labels="labels"\n                chart-colors="colors"\n                chart-options="barOptions"\n                chart-click="onChartClick">\n        </canvas>\n      </div>\n\n      <!-- pie -->\n      <div class="col col-25 padding-top">\n        <canvas id="blocksByIssuer-pie" class="chart-pie"\n                chart-data="data"\n                chart-labels="labels"\n                chart-colors="colors"\n                chart-click="onChartClick">\n        </canvas>\n\n        <div class="gray padding-top text-center">\n          <small ng-bind-html="\'GRAPH.BLOCKCHAIN.BLOCKS_ISSUERS_HELP\'| translate:{issuerCount: data.length, blockCount: blockCount }"></small>\n        </div>\n      </div>\n    </div>\n');
+$templateCache.put('plugins/graph/templates/blockchain/graph_tx_count.html','\n    <!-- button bar -->\n    <div class="button-bar-inline "\n         style="top: 33px; margin-top:-33px; position: relative;">\n      <button\n        class="button button-stable button-clear no-padding-xs pull-right"\n        ng-click="showActionsPopover($event)">\n        <i class="icon ion-navicon-round"></i>\n      </button>\n    </div>\n\n    <div class="padding-left padding-right">\n      <canvas id="tx-line" class="chart-bar"\n              height="{{height}}" width="{{width}}"\n              chart-data="data"\n              chart-dataset-override="datasetOverride"\n              chart-colors="colors"\n              chart-options="options"\n              chart-labels="labels"\n              chart-click="onChartClick">\n      </canvas>\n    </div>\n\n    <ng-include src="::\'plugins/graph/templates/common/graph_range_bar.html\'"></ng-include>\n');
+$templateCache.put('plugins/graph/templates/blockchain/view_stats.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n  <ion-nav-title>\n    {{\'GRAPH.BLOCKCHAIN.TITLE\' | translate}}{{id}}\n  </ion-nav-title>\n\n  <ion-content scroll="true" class="no-padding">\n\n    <div class="center padding" ng-if="loading">\n      <ion-spinner icon="android"></ion-spinner>\n    </div>\n\n    <div class="list" ng-if="!loading">\n\n\n      <!-- TX count -->\n      <ng-controller ng-controller="GpBlockchainTxCountCtrl">\n        <div class="center padding" ng-if="loading">\n          <ion-spinner icon="android"></ion-spinner>\n        </div>\n\n        <div class="item item-divider" ng-if="!loading" >\n          {{\'GRAPH.BLOCKCHAIN.TX_DIVIDER\'|translate}}\n          <ion-spinner ng-if="loadingRange" class="ion-spinner-small" icon="android"></ion-spinner>\n        </div>\n\n        <div class="item no-padding-xs no-padding-sm"\n             ng-if="!loading"\n             ng-include="::\'plugins/graph/templates/blockchain/graph_tx_count.html\'"\n             ng-init="setSize(350, 1000)">\n        </div>\n      </ng-controller>\n\n\n      <!-- Blocks issuer -->\n      <ng-controller ng-controller="GpBlockchainIssuersCtrl">\n\n        <div class="item item-divider" ng-if="!loading" translate>GRAPH.BLOCKCHAIN.BLOCKS_ISSUERS_DIVIDER</div>\n\n        <div class="item no-padding-xs no-padding-sm"\n             ng-if="!loading"\n             ng-include="::\'plugins/graph/templates/blockchain/graph_block_issuers.html\'"\n             ng-init="setSize(300, 750)">\n        </div>\n\n      </ng-controller>\n\n  </ion-content>\n\n</ion-view>\n');
+$templateCache.put('plugins/graph/templates/common/graph_range_bar.html','\n  <div class="range range-positive no-padding-left no-padding-right">\n    <a\n      class="button button-stable button-clear no-padding pull-left"\n      ng-click="goPreviousRange($event)">\n      <i class="icon ion-chevron-left"></i>\n    </a>\n    <input type="range"\n           ng-model="formData.timePct"\n           name="timePct"\n           min="0" max="100"\n           value="{{formData.timePct}}"\n           ng-change="onRangeChanged();"\n           ng-model-options="{ debounce: 250 }">\n    <a\n      class="button button-stable button-clear no-padding pull-right"\n      ng-click="goNextRange($event)">\n      <i class="icon ion-chevron-right"></i>\n    </a>\n  </div>\n');
+$templateCache.put('plugins/graph/templates/common/popover_range_actions.html','<ion-popover-view class="has-header popover-graph-currency">\n  <ion-header-bar>\n    <h1 class="title" translate>COMMON.POPOVER_ACTIONS_TITLE</h1>\n  </ion-header-bar>\n  <ion-content scroll="false">\n    <div class="list item-text-wrap">\n\n      <!-- scale -->\n      <a class="item item-icon-left ink"\n         ng-click="toggleScale()">\n        <i class="icon ion-ios-checkmark-empty" ng-show="formData.scale==\'logarithmic\'"></i>\n        <span ng-bind-html="\'GRAPH.COMMON.LOGARITHMIC_SCALE\' | translate"></span>\n      </a>\n\n      <!-- duration divider -->\n      <div class="item item-divider">\n        {{\'GRAPH.COMMON.RANGE_DURATION_DIVIDER\'|translate}}\n      </div>\n\n      <!-- duration: hour -->\n      <a class="item item-icon-left ink"\n         ng-click="setRangeDuration(\'hour\')">\n        <i class="icon ion-ios-checkmark-empty" ng-show="formData.rangeDuration==\'hour\'"></i>\n        <span ng-bind-html="\'GRAPH.COMMON.RANGE_DURATION.HOUR\' | translate"></span>\n      </a>\n\n      <!-- duration: day -->\n      <a class="item item-icon-left ink"\n         ng-click="setRangeDuration(\'day\')">\n        <i class="icon ion-ios-checkmark-empty" ng-show="formData.rangeDuration==\'day\'"></i>\n        <span ng-bind-html="\'GRAPH.COMMON.RANGE_DURATION.DAY\' | translate"></span>\n      </a>\n\n      <!-- duration: month -->\n      <a class="item item-icon-left ink"\n         ng-click="setRangeDuration(\'month\')">\n        <i class="icon ion-ios-checkmark-empty" ng-show="formData.rangeDuration==\'month\'"></i>\n        <span ng-bind-html="\'GRAPH.COMMON.RANGE_DURATION.MONTH\' | translate"></span>\n      </a>\n\n    </div>\n  </ion-content>\n</ion-popover-view>\n');
+$templateCache.put('plugins/graph/templates/currency/graph_du.html','\n  <!-- graphs button bar -->\n  <div class="button-bar-inline "\n       style="top: 33px; margin-top:-33px; position: relative;">\n    <button\n      class="button button-stable button-clear no-padding-xs no-padding-sm pull-right"\n      ng-click="showActionsPopover($event)">\n      <i class="icon ion-navicon-round"></i>\n    </button>\n  </div>\n\n  <canvas id="monetaryMass-bar" class="chart-bar"\n          height="{{height}}"\n          width="{{width}}"\n          chart-data="data"\n          chart-labels="labels"\n          chart-colors="colors"\n          chart-dataset-override="datasetOverride"\n          chart-options="options"\n          chart-click="showBlock">\n  </canvas>\n');
+$templateCache.put('plugins/graph/templates/currency/graph_members_count.html','  <canvas id="membersCount-bar" class="chart-line"\n          height="{{height}}"\n          width="{{width}}"\n          chart-data="data"\n          chart-labels="labels"\n          chart-colors="colors"\n          chart-options="options"\n          chart-dataset-override="datasetOverride"\n          chart-click="onChartClick">\n  </canvas>\n');
+$templateCache.put('plugins/graph/templates/currency/graph_monetary_mass.html','\n  <!-- graphs button bar -->\n  <div class="button-bar-inline "\n       style="top: 33px; margin-top:-33px; position: relative;">\n    <button\n      class="button button-stable button-clear no-padding-xs pull-right"\n      ng-click="showActionsPopover($event)">\n      <i class="icon ion-navicon-round"></i>\n    </button>\n  </div>\n\n  <canvas id="monetaryMass-bar"\n          class="chart-bar"\n          height="{{height}}"\n          width="{{width}}"\n          chart-data="data"\n          chart-labels="labels"\n          chart-colors="colors"\n          chart-dataset-override="datasetOverride"\n          chart-options="options"\n          chart-click="onChartClick">\n  </canvas>\n');
+$templateCache.put('plugins/graph/templates/currency/popover_monetary_mass_actions.html','<ion-popover-view class="fit has-header">\n  <ion-header-bar>\n    <h1 class="title" translate>COMMON.POPOVER_ACTIONS_TITLE</h1>\n  </ion-header-bar>\n  <ion-content scroll="false">\n    <div class="list item-text-wrap">\n\n      <a class="item item-icon-left ink"\n         ng-click="toggleScale()">\n        <i class="icon ion-ios-checkmark-empty" ng-show="formData.scale==\'logarithmic\'"></i>\n        <span ng-bind-html="\'GRAPH.COMMON.LOGARITHMIC_SCALE\' | translate"></span>\n      </a>\n\n    </div>\n  </ion-content>\n</ion-popover-view>\n');
+$templateCache.put('plugins/graph/templates/currency/tab_blocks_extend.html','<!-- buttons -->\n<ng-if ng-if=":state:enable && extensionPoint === \'buttons\'">\n  <div class="item item-divider">\n    <a class="badge button button-text button-small button-small-padding ink" ui-sref="app.currency.tab_blocks_stats">\n      <i class="icon ion-stats-bars"></i>\n      <span translate>GRAPH.COMMON.BTN_SHOW_STATS</span>\n    </a>\n  </div>\n</ng-if>\n');
+$templateCache.put('plugins/graph/templates/currency/view_currency_extend.html','\n<!-- section actual parameters -->\n<ng-if ng-if=":state:enable && extensionPoint === \'parameters-actual\'" >\n\n  <ng-if ng-if="!smallscreen">\n    <div class="item padding-left padding-right no-padding-xs no-padding-sm"\n         ng-include="::\'plugins/graph/templates/currency/graph_monetary_mass.html\'"\n         ng-controller="GpCurrencyMonetaryMassCtrl"\n         ng-init="displayShareAxis=false;">\n    </div>\n    <div class="item buttons no-padding-top ">\n      <a class="pull-right button button-text button-small button-small-padding ink" ui-sref="app.currency_stats_lg">\n        <i class="icon ion-stats-bars"></i>\n        <span translate>GRAPH.COMMON.BTN_SHOW_DETAILED_STATS</span>\n      </a>\n    </div>\n  </ng-if>\n\n  <div class="item item-divider"\n       ng-if="smallscreen">\n    <a class="badge button button-text button-small button-small-padding ink" ui-sref="app.currency.tab_parameters_stats">\n      <i class="icon ion-stats-bars"></i>\n      <span translate>GRAPH.COMMON.BTN_SHOW_STATS</span>\n    </a>\n  </div>\n</ng-if>\n\n<!-- section Wot -->\n<ng-if ng-if=":state:enable && extensionPoint === \'wot-actual\'" >\n\n  <ng-if ng-if="!smallscreen">\n    <div class="item padding-left padding-right no-padding-xs no-padding-sm"\n         ng-include="::\'plugins/graph/templates/currency/graph_members_count.html\'"\n         ng-controller="GpCurrencyMembersCountCtrl">\n    </div>\n    <div class="item buttons no-padding-top ">\n      <a class="pull-right button button-text button-small button-small-padding ink" ui-sref="app.currency_stats_lg">\n        <i class="icon ion-stats-bars"></i>\n        <span translate>GRAPH.COMMON.BTN_SHOW_DETAILED_STATS</span>\n      </a>\n    </div>\n  </ng-if>\n\n  <div class="item item-divider"\n       ng-if="smallscreen">\n    <a class="badge button button-text button-small button-small-padding ink" ui-sref="app.currency.tab_wot_stats">\n      <i class="icon ion-stats-bars"></i>\n      <span translate>GRAPH.COMMON.BTN_SHOW_STATS</span>\n    </a>\n  </div>\n</ng-if>\n\n<!-- section Wot -->\n<ng-if ng-if=":state:enable && extensionPoint === \'network-actual\'" >\n\n  <div class="item padding-left padding-right no-padding-xs no-padding-sm"\n       ng-if="!smallscreen"\n       ng-include="::\'plugins/graph/templates/blockchain/graph_block_issuers.html\'"\n       ng-controller="GpBlockchainIssuersCtrl">\n  </div>\n\n  <div class="item item-divider"\n       ng-if="smallscreen">\n    <a class="badge button button-text button-small button-small-padding ink" ui-sref="app.currency.tab_network_stats">\n      <i class="icon ion-stats-bars"></i>\n      <span translate>GRAPH.COMMON.BTN_SHOW_STATS</span>\n    </a>\n  </div>\n</ng-if>\n\n');
+$templateCache.put('plugins/graph/templates/currency/view_stats_lg.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n  <ion-nav-title>\n    {{\'GRAPH.BLOCKCHAIN.TITLE\' | translate}}\n  </ion-nav-title>\n\n  <ion-content scroll="true" class="padding" >\n\n\n\n    <div class="list" >\n\n      <!-- Monetary mass -->\n      <ng-controller ng-controller="GpCurrencyMonetaryMassCtrl" >\n\n        <div class="center padding" ng-if="loading">\n          <ion-spinner icon="android"></ion-spinner>\n        </div>\n\n        <div class="item no-padding-xs" ng-if="!loading"\n             ng-include="::\'plugins/graph/templates/currency/graph_monetary_mass.html\'"\n             ng-init="setSize(250, 1000)">\n        </div>\n\n        <div ng-if="!loading"\n             class="item item-toggle dark no-border text-right">\n          <span class="" translate>COMMON.BTN_RELATIVE_UNIT</span>\n          <label class="toggle toggle-royal" id="helptip-currency-change-unit">\n            <input type="checkbox" ng-model="formData.useRelative">\n            <div class="track">\n              <div class="handle"></div>\n            </div>\n          </label>\n        </div>\n      </ng-controller>\n\n      <!-- DU -->\n      <ng-controller ng-controller="GpCurrencyDUCtrl" >\n        <div class="item no-padding-xs"\n             ng-if="!loading"\n             ng-include="::\'plugins/graph/templates/currency/graph_du.html\'"\n             ng-init="setSize(250, 1000)">\n        </div>\n      </ng-controller>\n\n      <!-- Member count  -->\n      <ng-controller ng-controller="GpCurrencyMembersCountCtrl" >\n      <div class="item no-padding-xs"\n           ng-if="!loading"\n           ng-include="::\'plugins/graph/templates/currency/graph_members_count.html\'"\n           ng-init="setSize(250, 1000)">\n      </div>\n\n\n    </div>\n\n  </ion-content>\n\n</ion-view>\n');
+$templateCache.put('plugins/graph/templates/currency/view_wot_stats.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n\n  <ion-content scroll="true" >\n\n    <div class="center padding" ng-if="loading">\n      <ion-spinner icon="android"></ion-spinner>\n    </div>\n\n    <ng-include\n      ng-if="!loading"\n      src="\'plugins/graph/templates/currency/graph_members_count.html\'" ></ng-include>\n  </ion-content>\n  </ion-view>\n');
+$templateCache.put('plugins/graph/templates/docstats/graph.html','\n  <!-- graphs button bar -->\n  <div class="button-bar-inline "\n       style="top: 33px; margin-top:-33px; position: relative;">\n    <button\n      class="button button-stable button-clear no-padding-xs no-padding-sm pull-right"\n      ng-click="showActionsPopover($event)">\n      <i class="icon ion-navicon-round"></i>\n    </button>\n  </div>\n\n  <canvas id="{{::chartIdPrefix}}{{chart.id}}"\n          class="chart-line"\n          height="{{height}}"\n          width="{{width}}"\n          chart-data="chart.data"\n          chart-labels="labels"\n          chart-dataset-override="chart.datasetOverride"\n          chart-options="chart.options"\n          chart-click="onChartClick">\n  </canvas>\n\n  <ng-include src="::\'plugins/graph/templates/common/graph_range_bar.html\'"></ng-include>\n');
+$templateCache.put('plugins/graph/templates/docstats/view_stats.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n  <ion-nav-title>\n    {{\'GRAPH.DOC_STATS.TITLE\' | translate}}\n  </ion-nav-title>\n\n  <ion-content scroll="true" class="padding" >\n\n    <div class="list" >\n\n      <!-- Doc stat -->\n      <div class="center padding" ng-if="loading">\n        <ion-spinner icon="android"></ion-spinner>\n      </div>\n\n      <div class="item no-padding-xs no-padding-sm" ng-if="!loading"\n           ng-repeat="chart in charts"\n           ng-include="::\'plugins/graph/templates/docstats/graph.html\'"\n           ng-init="setSize(250, 1000)">\n      </div>\n\n    </div>\n\n  </ion-content>\n\n</ion-view>\n');
+$templateCache.put('plugins/graph/templates/network/view_es_network_extend.html','<!-- Buttons section -->\n<ng-if ng-if=":state:enable && extensionPoint === \'documents-buttons\'">\n  <a class="button button-text button-small ink"\n     ui-sref="app.doc_stats_lg" >\n    <i class="icon ion-stats-bars"></i>\n    <span>{{\'NETWORK.VIEW.BTN_GRAPH\'|translate}}</span>\n  </a>\n</ng-if>\n');
+$templateCache.put('plugins/graph/templates/network/view_es_peer_extend.html','<!-- Buttons section -->\n<ng-if ng-if=":state:enable && extensionPoint === \'general\'">\n\n  <a class="item item-icon-left item-icon-right item-text-wrap ink"\n     ng-if="isReachable"\n     ui-sref="app.doc_stats_lg(node)">\n    <i class="icon ion-stats-bars"></i>\n    <span translate>GRAPH.DOC_STATS.TITLE</span>\n    <i class="gray icon ion-ios-arrow-right"></i>\n  </a>\n\n  <a class="item item-icon-left item-icon-right item-text-wrap ink"\n     ng-if="isReachable"\n     ui-sref="app.doc_synchro_lg(node)">\n    <i class="icon ion-stats-bars"></i>\n    <span translate>GRAPH.SYNCHRO.TITLE</span>\n    <i class="gray icon ion-ios-arrow-right"></i>\n  </a>\n</ng-if>\n\n');
+$templateCache.put('plugins/graph/templates/network/view_network_extend.html','<!-- Buttons section -->\n<ng-if ng-if=":state:enable && extensionPoint === \'blockchain-buttons\'">\n  <a class="button button-text button-small ink"\n     ui-sref="app.blockchain_stats" >\n    <i class="icon ion-stats-bars"></i>\n    <span>{{\'NETWORK.VIEW.BTN_GRAPH\'|translate}}</span>\n  </a>\n</ng-if>\n');
+$templateCache.put('plugins/graph/templates/network/view_peer_extend.html','<!-- Buttons section -->\n<ng-if ng-if=":state:enable && extensionPoint === \'general\'">\n\n  <a class="item item-icon-left item-icon-right item-text-wrap ink"\n    ui-sref="app.view_peer_stats({pubkey: node.pubkey})">\n    <i class="icon ion-stats-bars"></i>\n    <span translate>GRAPH.PEER.VIEW.BLOCK_COUNT_LABEL</span>\n    <span class="badge"\n          ng-if="!loading"\n          ng-class="{\'badge-stable\': blockCount > 0, \'badge-assertive\': !blockCount}">\n      {{!blockCount ? \'GRAPH.PEER.VIEW.NO_BLOCK\' : \'GRAPH.PEER.VIEW.BLOCK_COUNT\' | translate:{count: blockCount} }}\n    </span>\n    <ion-spinner class="badge" icon="android" ng-if="loading"></ion-spinner>\n    <i class="gray icon ion-ios-arrow-right"></i>\n  </a>\n</ng-if>\n\n');
+$templateCache.put('plugins/graph/templates/network/view_peer_stats.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n  <ion-nav-title>\n    {{\'GRAPH.BLOCKCHAIN.TITLE\' | translate}}{{id}}\n  </ion-nav-title>\n\n  <ion-content scroll="true" class="no-padding">\n\n    <div class="center padding" ng-if="loading">\n      <ion-spinner icon="android"></ion-spinner>\n    </div>\n\n    <div class="list" ng-if="!loading">\n\n      <!--  - - - - TX divider - - - - -->\n      <div class="item item-divider hidden-xs hidden-sm" translate>\n        GRAPH.BLOCKCHAIN.TX_DIVIDER\n      </div>\n\n      <div class="item no-padding-xs"\n           ng-include="::\'plugins/graph/templates/blockchain/graph_tx_count.html\'"\n           ng-init="setSize(350, 1000)">\n      </div>\n\n  </ion-content>\n\n</ion-view>\n');
+$templateCache.put('plugins/graph/templates/synchro/graph.html','\n  <!-- graphs button bar -->\n  <div class="button-bar-inline "\n       style="top: 33px; margin-top:-33px; position: relative;">\n    <button\n      class="button button-stable button-clear no-padding-xs pull-right"\n      ng-click="showActionsPopover($event)">\n      <i class="icon ion-navicon-round"></i>\n    </button>\n  </div>\n\n  <canvas id="synchro-chart-{{chart.id}}"\n          class="chart-bar"\n          height="{{height}}"\n          width="{{width}}"\n          chart-data="chart.data"\n          chart-labels="labels"\n          chart-dataset-override="chart.datasetOverride"\n          chart-options="chart.options">\n  </canvas>\n\n  <ng-include src="::\'plugins/graph/templates/common/graph_range_bar.html\'"></ng-include>\n');
+$templateCache.put('plugins/graph/templates/synchro/view_stats.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n  <ion-nav-title>\n    {{\'GRAPH.SYNCHRO.TITLE\' | translate}}\n  </ion-nav-title>\n\n  <ion-content scroll="true" class="padding" >\n\n    <div class="list" >\n\n      <div class="center padding" ng-if="loading">\n        <ion-spinner icon="android"></ion-spinner>\n      </div>\n\n      <div class="item no-padding-xs" ng-if="!loading"\n           ng-repeat="chart in charts"\n           ng-include="::\'plugins/graph/templates/synchro/graph.html\'"\n           ng-init="setSize(250, 1000)">\n      </div>\n\n    </div>\n\n  </ion-content>\n\n</ion-view>\n');
 $templateCache.put('plugins/es/templates/blockchain/items_blocks.html','<div class="padding gray" ng-if=":rebind:!search.loading && !search.results.length" translate="">COMMON.SEARCH_NO_RESULT</div><ng-if ng-if=":rebind:!smallscreen"><ng-repeat ng-repeat="block in :rebind:search.results track by block.number" ng-include="!block.empty ? \'templates/blockchain/item_block_lg.html\' : \'templates/blockchain/item_block_empty_lg.html\'"></ng-repeat></ng-if><ng-if ng-if=":rebind:smallscreen"><ng-repeat ng-repeat="block in :rebind:search.results track by block.number" ng-include="::\'templates/blockchain/item_block.html\'"></ng-repeat></ng-if>');
 $templateCache.put('plugins/es/templates/blockchain/lookup.html','<ion-view><ion-nav-title><span translate>BLOCKCHAIN.LOOKUP.TITLE</span></ion-nav-title><ion-nav-buttons side="secondary"><button class="button button-icon button-clear icon ion-navicon visible-xs visible-sm" ng-click="toggleCompactMode()"><b class="icon-secondary" ng-class="{\'ion-arrow-down-b\': !compactMode, \'ion-arrow-up-b\': compactMode}" style="top: -12px; left: 11px; font-size: 10px"></b> <b class="icon-secondary" ng-class="{\'ion-arrow-up-b\': !compactMode,\'ion-arrow-down-b\': compactMode}" style="top: 12px; left: 11px; font-size: 10px"></b></button> <button class="button button-icon button-clear icon ion-android-more-vertical visible-xs visible-sm" ng-click="showActionsPopover($event)"></button></ion-nav-buttons><ion-content class="padding no-padding-xs no-padding-sm" scroll="true"><ng-include src="::\'plugins/es/templates/blockchain/lookup_form.html\'"></ng-include></ion-content></ion-view>');
 $templateCache.put('plugins/es/templates/blockchain/lookup_form.html','<div class="lookupForm"><div class="item no-padding"><div class="button button-small button-text button-stable button-icon-event padding no-padding-right ink" ng-repeat="filter in search.filters" ng-if="filter"><span ng-bind-html="\'BLOCKCHAIN.LOOKUP.TX_SEARCH_FILTER.\'+filter.type|translate:filter"></span> <i class="icon ion-close" ng-click="itemRemove($index)"></i></div><label class="item-input"><i class="icon ion-search placeholder-icon"></i> <input type="text" class="visible-xs visible-sm" placeholder="{{\'BLOCKCHAIN.LOOKUP.SEARCH_HELP\'|translate}}" ng-model="search.text" ng-model-options="{ debounce: 650 }" ng-change="doSearchText()"><div class="helptip-anchor-center"><a id="helptip-blockchain-search-text"></a></div></label></div><div class="padding-top padding-xs" style="display: block; height: 60px"><div class="pull-left"><h4 ng-if="search.type==\'last\'" translate="">BLOCKCHAIN.LOOKUP.LAST_BLOCKS</h4><h4 ng-if="search.type==\'text\'">{{\'COMMON.RESULTS_LIST\'|translate}}</h4><h5 class="dark" ng-if="!search.loading && search.total"><span translate="COMMON.RESULTS_COUNT" translate-values="{count: search.total}"></span> <small class="gray" ng-if=":rebind:search.took && expertMode">- {{:rebind:\'COMMON.EXECUTION_TIME\'|translate: {duration: search.took} }} </small><small class="gray" ng-if=":rebind:expertMode && search.filters && search.filters.length">- <a ng-click="toggleShowQuery()" ng-if="!showQuery">{{\'DOCUMENT.LOOKUP.SHOW_QUERY\'|translate }} <i class="icon ion-arrow-down-b gray"></i> </a><a ng-click="toggleShowQuery()" ng-if="showQuery">{{\'DOCUMENT.LOOKUP.HIDE_QUERY\'|translate }} <i class="icon ion-arrow-up-b gray"></i></a></small></h5><h5 class="gray" ng-if="search.loading"><ion-spinner class="icon ion-spinner-small" icon="android"></ion-spinner><span translate="">COMMON.SEARCHING</span><br></h5></div></div><div class="item no-border no-padding" ng-if=":rebind:search.filters && search.filters.length && expertMode"><small class="no-padding no-margin" ng-if="showQuery"><span class="gray text-wrap dark">{{:rebind:search.query}}</span></small></div><ion-list class="list list-blocks" ng-class="::motion.ionListClass"><ng-include src="::\'plugins/es/templates/blockchain/items_blocks.html\'"></ng-include></ion-list><ion-infinite-scroll ng-if="search.hasMore" spinner="android" on-infinite="showMore()" distance="1%"></ion-infinite-scroll></div>');
@@ -32271,34 +32299,6 @@ $templateCache.put('plugins/es/templates/wot/popover_certification_actions.html'
 $templateCache.put('plugins/es/templates/wot/view_certifications_extend.html','<ng-if ng-if=":state:enable && extensionPoint === \'nav-buttons\'"><button class="button button-icon button-clear icon ion-android-more-vertical visible-xs visible-sm" ng-click="showCertificationActionsPopover($event)"></button></ng-if><ng-if ng-if=":state:enable && extensionPoint === \'buttons\'"><button class="button button-stable button-small-padding icon ion-android-more-vertical" ng-click="showCertificationActionsPopover($event)" title="{{\'COMMON.POPOVER_ACTIONS_TITLE\' | translate}}"></button></ng-if>');
 $templateCache.put('plugins/es/templates/wot/view_identity_extend.html','<ng-if ng-if=":state:enable && extensionPoint === \'hero\'"><small class="light" style="display: inline-block" ng-include="::\'plugins/es/templates/common/view_likes.html\'"></small></ng-if><ng-if ng-if=":state:enable && extensionPoint === \'buttons-top-fab\'"><button id="fab-compose-{{:rebind:formData.pubkey}}" class="button button-fab button-fab-top-left button-fab-hero mini button-stable spin" style="left: 88px" ng-click="showNewMessageModal()"><i class="icon ion-email"></i></button></ng-if><ng-if ng-if=":state:enable && extensionPoint === \'buttons\'"><button class="button button-stable button-small-padding icon ion-email" ng-disabled="loading" ng-click="showNewMessageModal()" title="{{\'MESSAGE.BTN_WRITE\' | translate}}"></button></ng-if><ng-if ng-if=":state:enable && extensionPoint === \'after-buttons\'"><button class="button button-stable button-small-padding icon ion-android-more-vertical" ng-click="showActionsPopover($event)"></button></ng-if><ng-if ng-if=":state:enable && extensionPoint === \'after-general\'"><span class="item item-divider item-divider-top-border"><span>{{\'PROFILE.PROFILE_DIVIDER\' | translate}} <a style="font-size: 12pt; cursor: pointer" ng-click="showProfileHelp=!showProfileHelp" class="icon positive ion-ios-help-outline ink" title="{{\'PROFILE.PROFILE_DIVIDER_HELP\' | translate}}"></a></span></span><div class="item item-text-wrap positive item-small-height" ng-show="showProfileHelp"><small translate>PROFILE.PROFILE_DIVIDER_HELP</small></div><ng-include src="::\'plugins/es/templates/user/items_profile.html\'" ng-init="showName=false;"></ng-include></ng-if>');
 $templateCache.put('plugins/es/templates/wot/view_popover_actions.html','<ion-popover-view class="fit has-header"><ion-header-bar><h1 class="title" translate>COMMON.POPOVER_ACTIONS_TITLE</h1></ion-header-bar><ion-content scroll="false"><div class="list item-text-wrap"><a class="item item-icon-left ink visible-xs visible-sm" ng-click="showSharePopover($event)"><i class="icon ion-android-share-alt"></i> {{\'COMMON.BTN_SHARE\' | translate}} </a><a class="item item-icon-left assertive ink" ng-if="canDelete" ng-click="delete()"><i class="icon ion-trash-a"></i> {{\'COMMON.BTN_DELETE\' | translate}} </a><a class="item item-icon-left ink" ng-if="!canEdit && likeData.likes" ng-click="hideActionsPopover() && toggleLike($event)"><i class="icon" ng-class="{\'ion-heart-broken\': likeData.likes.wasHit, \'ion-heart\': !likeData.likes.wasHit}"></i> {{(likeData.likes.wasHit ? \'COMMON.BTN_LIKE_REMOVE\' : \'COMMON.BTN_LIKE\' )| translate}} </a><a class="item item-icon-left ink" ng-if="!canEdit && likeData.abuses" ng-disabled="!!likeData.abuses.wasHitCount" ng-class="{\'gray\': !!likeData.abuses.wasHitCount}" ng-click="hideActionsPopover() && reportAbuse($event)"><i class="icon ion-android-warning"></i> {{\'COMMON.BTN_REPORT_ABUSE_DOTS\' | translate}}</a></div></ion-content></ion-popover-view>');
-$templateCache.put('plugins/graph/templates/account/graph_balance.html','\n    <!-- button bar -->\n    <div class="button-bar-inline "\n         style="top: 33px; margin-top:-33px; position: relative;">\n      <button\n        class="button button-stable button-clear no-padding-xs pull-right"\n        ng-click="showActionsPopover($event)">\n        <i class="icon ion-navicon-round"></i>\n      </button>\n    </div>\n\n    <div class="padding-left padding-right">\n      <canvas id="account-balance" class="chart-bar"\n              height="{{height}}" width="{{width}}"\n              chart-data="data"\n              chart-dataset-override="datasetOverride"\n              chart-colors="colors"\n              chart-options="options"\n              chart-labels="labels"\n              chart-click="onChartClick">\n      </canvas>\n    </div>\n\n    <ng-include src="::\'plugins/graph/templates/common/graph_range_bar.html\'"></ng-include>\n');
-$templateCache.put('plugins/graph/templates/account/graph_certifications.html','\n    <div class="padding-left padding-right">\n      <canvas id="account-certifications" class="chart-bar"\n              height="{{height}}" width="{{width}}"\n              chart-data="data"\n              chart-dataset-override="datasetOverride"\n              chart-colors="colors"\n              chart-options="options"\n              chart-labels="labels"\n              chart-click="onChartClick">\n      </canvas>\n    </div>\n');
-$templateCache.put('plugins/graph/templates/account/graph_sum_tx.html','<div class="row responsive-sm" ng-if="!loading">\n\n  <div class="col col-10 hidden-xs hidden-sm">&nbsp;</div>\n\n  <div class="col text-center">\n\n    <!-- TX input chart -->\n    <p class="gray padding text-wrap"\n       ng-if="inputChart.data.length"\n       translate>GRAPH.ACCOUNT.INPUT_CHART_TITLE</p>\n    <canvas id="chart-received-pie" class="chart-pie"\n            chart-data="inputChart.data"\n            chart-labels="inputChart.labels"\n            chart-colors="inputChart.colors"\n            chart-click="onInputChartClick">\n    </canvas>\n\n  </div>\n\n  <div class="col col-10 hidden-xs hidden-sm">&nbsp;</div>\n\n  <div class="col text-center">\n\n    <!-- TX output chart -->\n    <p class="gray padding text-wrap"\n       ng-if="outputChart.data.length"\n       translate>GRAPH.ACCOUNT.OUTPUT_CHART_TITLE</p>\n    <canvas id="chart-sent-pie" class="chart-pie"\n            chart-data="outputChart.data"\n            chart-labels="outputChart.labels"\n            chart-colors="outputChart.colors"\n            chart-click="onOutputChartClick">\n    </canvas>\n\n  </div>\n\n  <div class="col col-10 hidden-xs hidden-sm">&nbsp;</div>\n\n</div>\n');
-$templateCache.put('plugins/graph/templates/account/view_identity_tx_extend.html','<!-- Buttons section -->\n<ng-if ng-if=":state:enable && extensionPoint === \'buttons\'">\n\n  <button class="button button-stable button-small-padding icon ion-stats-bars"\n          ui-sref="app.wot_identity_stats({pubkey: formData.pubkey})"\n          title="{{\'GRAPH.ACCOUNT.BTN_SHOW_STATS\' | translate}}">\n  </button>\n\n</ng-if>\n');
-$templateCache.put('plugins/graph/templates/account/view_stats.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n  <ion-nav-title>\n    {{\'GRAPH.ACCOUNT.TITLE\' | translate}}{{id}}\n  </ion-nav-title>\n\n  <ion-content scroll="true" class="no-padding">\n\n\n\n    <div class="list" >\n\n      <!--  - - - - Balance - - - - -->\n      <ng-controller ng-controller="GpAccountBalanceCtrl">\n        <div class="center padding" ng-if="loading">\n          <ion-spinner icon="android"></ion-spinner>\n        </div>\n\n        <div class="item item-divider" ng-if="!loading" >\n          {{\'GRAPH.ACCOUNT.BALANCE_DIVIDER\'|translate}}\n          <ion-spinner ng-if="loadingRange" class="ion-spinner-small" icon="android"></ion-spinner>\n        </div>\n\n        <div class="item no-padding-xs"\n             ng-if="!loading"\n             ng-include="::\'plugins/graph/templates/account/graph_balance.html\'"\n             ng-init="setSize(350, 1000)">\n        </div>\n      </ng-controller>\n\n    </div>\n\n    <div class="item no-padding-xs"\n         ng-include="::\'plugins/graph/templates/account/graph_sum_tx.html\'"\n         ng-controller="GpAccountSumTxCtrl">\n    </div>\n\n    <!--  - - - - WOT - - - -\n    <div class="item item-divider" translate>\n      GRAPH.ACCOUNT.WOT_DIVIDER\n    </div>\n\n    <div class="item no-padding-xs"\n         ng-include="::\'plugins/graph/templates/account/graph_certifications.html\'"\n         ng-controller="GpAccountCertificationCtrl"\n         ng-init="setSize(350, 1000)">\n    </div>-->\n\n  </ion-content>\n\n</ion-view>\n');
-$templateCache.put('plugins/graph/templates/account/view_wallet_tx_extend.html','<!-- Buttons section -->\n<ng-if ng-if=":state:enable && extensionPoint === \'buttons\'">\n\n  <button class="button button-stable button-small-padding icon ion-stats-bars"\n          ui-sref="app.wot_identity_stats({pubkey: formData.pubkey})"\n          title="{{\'GRAPH.ACCOUNT.BTN_SHOW_STATS\' | translate}}">\n  </button>\n\n</ng-if>\n');
-$templateCache.put('plugins/graph/templates/blockchain/graph_block_issuers.html','\n    <div class="row responsive-lg">\n\n      <!-- bar -->\n      <div class="col col-75">\n        <canvas id="bar" class="chart-bar"\n                height="{{height}}" width="{{width}}"\n                chart-data="data"\n                chart-labels="labels"\n                chart-colors="colors"\n                chart-options="barOptions"\n                chart-click="onChartClick">\n        </canvas>\n      </div>\n\n      <!-- pie -->\n      <div class="col col-25 padding-top">\n        <canvas id="blocksByIssuer-pie" class="chart-pie"\n                chart-data="data"\n                chart-labels="labels"\n                chart-colors="colors"\n                chart-click="onChartClick">\n        </canvas>\n\n        <div class="gray padding-top text-center">\n          <small ng-bind-html="\'GRAPH.BLOCKCHAIN.BLOCKS_ISSUERS_HELP\'| translate:{issuerCount: data.length, blockCount: blockCount }"></small>\n        </div>\n      </div>\n    </div>\n');
-$templateCache.put('plugins/graph/templates/blockchain/graph_tx_count.html','\n    <!-- button bar -->\n    <div class="button-bar-inline "\n         style="top: 33px; margin-top:-33px; position: relative;">\n      <button\n        class="button button-stable button-clear no-padding-xs pull-right"\n        ng-click="showActionsPopover($event)">\n        <i class="icon ion-navicon-round"></i>\n      </button>\n    </div>\n\n    <div class="padding-left padding-right">\n      <canvas id="tx-line" class="chart-bar"\n              height="{{height}}" width="{{width}}"\n              chart-data="data"\n              chart-dataset-override="datasetOverride"\n              chart-colors="colors"\n              chart-options="options"\n              chart-labels="labels"\n              chart-click="onChartClick">\n      </canvas>\n    </div>\n\n    <ng-include src="::\'plugins/graph/templates/common/graph_range_bar.html\'"></ng-include>\n');
-$templateCache.put('plugins/graph/templates/blockchain/view_stats.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n  <ion-nav-title>\n    {{\'GRAPH.BLOCKCHAIN.TITLE\' | translate}}{{id}}\n  </ion-nav-title>\n\n  <ion-content scroll="true" class="no-padding">\n\n    <div class="center padding" ng-if="loading">\n      <ion-spinner icon="android"></ion-spinner>\n    </div>\n\n    <div class="list" ng-if="!loading">\n\n\n      <!-- TX count -->\n      <ng-controller ng-controller="GpBlockchainTxCountCtrl">\n        <div class="center padding" ng-if="loading">\n          <ion-spinner icon="android"></ion-spinner>\n        </div>\n\n        <div class="item item-divider" ng-if="!loading" >\n          {{\'GRAPH.BLOCKCHAIN.TX_DIVIDER\'|translate}}\n          <ion-spinner ng-if="loadingRange" class="ion-spinner-small" icon="android"></ion-spinner>\n        </div>\n\n        <div class="item no-padding-xs no-padding-sm"\n             ng-if="!loading"\n             ng-include="::\'plugins/graph/templates/blockchain/graph_tx_count.html\'"\n             ng-init="setSize(350, 1000)">\n        </div>\n      </ng-controller>\n\n\n      <!-- Blocks issuer -->\n      <ng-controller ng-controller="GpBlockchainIssuersCtrl">\n\n        <div class="item item-divider" ng-if="!loading" translate>GRAPH.BLOCKCHAIN.BLOCKS_ISSUERS_DIVIDER</div>\n\n        <div class="item no-padding-xs no-padding-sm"\n             ng-if="!loading"\n             ng-include="::\'plugins/graph/templates/blockchain/graph_block_issuers.html\'"\n             ng-init="setSize(300, 750)">\n        </div>\n\n      </ng-controller>\n\n  </ion-content>\n\n</ion-view>\n');
-$templateCache.put('plugins/graph/templates/common/graph_range_bar.html','\n  <div class="range range-positive no-padding-left no-padding-right">\n    <a\n      class="button button-stable button-clear no-padding pull-left"\n      ng-click="goPreviousRange($event)">\n      <i class="icon ion-chevron-left"></i>\n    </a>\n    <input type="range"\n           ng-model="formData.timePct"\n           name="timePct"\n           min="0" max="100"\n           value="{{formData.timePct}}"\n           ng-change="onRangeChanged();"\n           ng-model-options="{ debounce: 250 }">\n    <a\n      class="button button-stable button-clear no-padding pull-right"\n      ng-click="goNextRange($event)">\n      <i class="icon ion-chevron-right"></i>\n    </a>\n  </div>\n');
-$templateCache.put('plugins/graph/templates/common/popover_range_actions.html','<ion-popover-view class="has-header popover-graph-currency">\n  <ion-header-bar>\n    <h1 class="title" translate>COMMON.POPOVER_ACTIONS_TITLE</h1>\n  </ion-header-bar>\n  <ion-content scroll="false">\n    <div class="list item-text-wrap">\n\n      <!-- scale -->\n      <a class="item item-icon-left ink"\n         ng-click="toggleScale()">\n        <i class="icon ion-ios-checkmark-empty" ng-show="formData.scale==\'logarithmic\'"></i>\n        <span ng-bind-html="\'GRAPH.COMMON.LOGARITHMIC_SCALE\' | translate"></span>\n      </a>\n\n      <!-- duration divider -->\n      <div class="item item-divider">\n        {{\'GRAPH.COMMON.RANGE_DURATION_DIVIDER\'|translate}}\n      </div>\n\n      <!-- duration: hour -->\n      <a class="item item-icon-left ink"\n         ng-click="setRangeDuration(\'hour\')">\n        <i class="icon ion-ios-checkmark-empty" ng-show="formData.rangeDuration==\'hour\'"></i>\n        <span ng-bind-html="\'GRAPH.COMMON.RANGE_DURATION.HOUR\' | translate"></span>\n      </a>\n\n      <!-- duration: day -->\n      <a class="item item-icon-left ink"\n         ng-click="setRangeDuration(\'day\')">\n        <i class="icon ion-ios-checkmark-empty" ng-show="formData.rangeDuration==\'day\'"></i>\n        <span ng-bind-html="\'GRAPH.COMMON.RANGE_DURATION.DAY\' | translate"></span>\n      </a>\n\n      <!-- duration: month -->\n      <a class="item item-icon-left ink"\n         ng-click="setRangeDuration(\'month\')">\n        <i class="icon ion-ios-checkmark-empty" ng-show="formData.rangeDuration==\'month\'"></i>\n        <span ng-bind-html="\'GRAPH.COMMON.RANGE_DURATION.MONTH\' | translate"></span>\n      </a>\n\n    </div>\n  </ion-content>\n</ion-popover-view>\n');
-$templateCache.put('plugins/graph/templates/currency/graph_du.html','\n  <!-- graphs button bar -->\n  <div class="button-bar-inline "\n       style="top: 33px; margin-top:-33px; position: relative;">\n    <button\n      class="button button-stable button-clear no-padding-xs no-padding-sm pull-right"\n      ng-click="showActionsPopover($event)">\n      <i class="icon ion-navicon-round"></i>\n    </button>\n  </div>\n\n  <canvas id="monetaryMass-bar" class="chart-bar"\n          height="{{height}}"\n          width="{{width}}"\n          chart-data="data"\n          chart-labels="labels"\n          chart-colors="colors"\n          chart-dataset-override="datasetOverride"\n          chart-options="options"\n          chart-click="showBlock">\n  </canvas>\n');
-$templateCache.put('plugins/graph/templates/currency/graph_members_count.html','  <canvas id="membersCount-bar" class="chart-line"\n          height="{{height}}"\n          width="{{width}}"\n          chart-data="data"\n          chart-labels="labels"\n          chart-colors="colors"\n          chart-options="options"\n          chart-dataset-override="datasetOverride"\n          chart-click="onChartClick">\n  </canvas>\n');
-$templateCache.put('plugins/graph/templates/currency/graph_monetary_mass.html','\n  <!-- graphs button bar -->\n  <div class="button-bar-inline "\n       style="top: 33px; margin-top:-33px; position: relative;">\n    <button\n      class="button button-stable button-clear no-padding-xs pull-right"\n      ng-click="showActionsPopover($event)">\n      <i class="icon ion-navicon-round"></i>\n    </button>\n  </div>\n\n  <canvas id="monetaryMass-bar"\n          class="chart-bar"\n          height="{{height}}"\n          width="{{width}}"\n          chart-data="data"\n          chart-labels="labels"\n          chart-colors="colors"\n          chart-dataset-override="datasetOverride"\n          chart-options="options"\n          chart-click="onChartClick">\n  </canvas>\n');
-$templateCache.put('plugins/graph/templates/currency/popover_monetary_mass_actions.html','<ion-popover-view class="fit has-header">\n  <ion-header-bar>\n    <h1 class="title" translate>COMMON.POPOVER_ACTIONS_TITLE</h1>\n  </ion-header-bar>\n  <ion-content scroll="false">\n    <div class="list item-text-wrap">\n\n      <a class="item item-icon-left ink"\n         ng-click="toggleScale()">\n        <i class="icon ion-ios-checkmark-empty" ng-show="formData.scale==\'logarithmic\'"></i>\n        <span ng-bind-html="\'GRAPH.COMMON.LOGARITHMIC_SCALE\' | translate"></span>\n      </a>\n\n    </div>\n  </ion-content>\n</ion-popover-view>\n');
-$templateCache.put('plugins/graph/templates/currency/tab_blocks_extend.html','<!-- buttons -->\n<ng-if ng-if=":state:enable && extensionPoint === \'buttons\'">\n  <div class="item item-divider">\n    <a class="badge button button-text button-small button-small-padding ink" ui-sref="app.currency.tab_blocks_stats">\n      <i class="icon ion-stats-bars"></i>\n      <span translate>GRAPH.COMMON.BTN_SHOW_STATS</span>\n    </a>\n  </div>\n</ng-if>\n');
-$templateCache.put('plugins/graph/templates/currency/view_currency_extend.html','\n<!-- section actual parameters -->\n<ng-if ng-if=":state:enable && extensionPoint === \'parameters-actual\'" >\n\n  <ng-if ng-if="!smallscreen">\n    <div class="item padding-left padding-right no-padding-xs no-padding-sm"\n         ng-include="::\'plugins/graph/templates/currency/graph_monetary_mass.html\'"\n         ng-controller="GpCurrencyMonetaryMassCtrl"\n         ng-init="displayShareAxis=false;">\n    </div>\n    <div class="item buttons no-padding-top ">\n      <a class="pull-right button button-text button-small button-small-padding ink" ui-sref="app.currency_stats_lg">\n        <i class="icon ion-stats-bars"></i>\n        <span translate>GRAPH.COMMON.BTN_SHOW_DETAILED_STATS</span>\n      </a>\n    </div>\n  </ng-if>\n\n  <div class="item item-divider"\n       ng-if="smallscreen">\n    <a class="badge button button-text button-small button-small-padding ink" ui-sref="app.currency.tab_parameters_stats">\n      <i class="icon ion-stats-bars"></i>\n      <span translate>GRAPH.COMMON.BTN_SHOW_STATS</span>\n    </a>\n  </div>\n</ng-if>\n\n<!-- section Wot -->\n<ng-if ng-if=":state:enable && extensionPoint === \'wot-actual\'" >\n\n  <ng-if ng-if="!smallscreen">\n    <div class="item padding-left padding-right no-padding-xs no-padding-sm"\n         ng-include="::\'plugins/graph/templates/currency/graph_members_count.html\'"\n         ng-controller="GpCurrencyMembersCountCtrl">\n    </div>\n    <div class="item buttons no-padding-top ">\n      <a class="pull-right button button-text button-small button-small-padding ink" ui-sref="app.currency_stats_lg">\n        <i class="icon ion-stats-bars"></i>\n        <span translate>GRAPH.COMMON.BTN_SHOW_DETAILED_STATS</span>\n      </a>\n    </div>\n  </ng-if>\n\n  <div class="item item-divider"\n       ng-if="smallscreen">\n    <a class="badge button button-text button-small button-small-padding ink" ui-sref="app.currency.tab_wot_stats">\n      <i class="icon ion-stats-bars"></i>\n      <span translate>GRAPH.COMMON.BTN_SHOW_STATS</span>\n    </a>\n  </div>\n</ng-if>\n\n<!-- section Wot -->\n<ng-if ng-if=":state:enable && extensionPoint === \'network-actual\'" >\n\n  <div class="item padding-left padding-right no-padding-xs no-padding-sm"\n       ng-if="!smallscreen"\n       ng-include="::\'plugins/graph/templates/blockchain/graph_block_issuers.html\'"\n       ng-controller="GpBlockchainIssuersCtrl">\n  </div>\n\n  <div class="item item-divider"\n       ng-if="smallscreen">\n    <a class="badge button button-text button-small button-small-padding ink" ui-sref="app.currency.tab_network_stats">\n      <i class="icon ion-stats-bars"></i>\n      <span translate>GRAPH.COMMON.BTN_SHOW_STATS</span>\n    </a>\n  </div>\n</ng-if>\n\n');
-$templateCache.put('plugins/graph/templates/currency/view_stats_lg.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n  <ion-nav-title>\n    {{\'GRAPH.BLOCKCHAIN.TITLE\' | translate}}\n  </ion-nav-title>\n\n  <ion-content scroll="true" class="padding" >\n\n\n\n    <div class="list" >\n\n      <!-- Monetary mass -->\n      <ng-controller ng-controller="GpCurrencyMonetaryMassCtrl" >\n\n        <div class="center padding" ng-if="loading">\n          <ion-spinner icon="android"></ion-spinner>\n        </div>\n\n        <div class="item no-padding-xs" ng-if="!loading"\n             ng-include="::\'plugins/graph/templates/currency/graph_monetary_mass.html\'"\n             ng-init="setSize(250, 1000)">\n        </div>\n\n        <div ng-if="!loading"\n             class="item item-toggle dark no-border text-right">\n          <span class="" translate>COMMON.BTN_RELATIVE_UNIT</span>\n          <label class="toggle toggle-royal" id="helptip-currency-change-unit">\n            <input type="checkbox" ng-model="formData.useRelative">\n            <div class="track">\n              <div class="handle"></div>\n            </div>\n          </label>\n        </div>\n      </ng-controller>\n\n      <!-- DU -->\n      <ng-controller ng-controller="GpCurrencyDUCtrl" >\n        <div class="item no-padding-xs"\n             ng-if="!loading"\n             ng-include="::\'plugins/graph/templates/currency/graph_du.html\'"\n             ng-init="setSize(250, 1000)">\n        </div>\n      </ng-controller>\n\n      <!-- Member count  -->\n      <ng-controller ng-controller="GpCurrencyMembersCountCtrl" >\n      <div class="item no-padding-xs"\n           ng-if="!loading"\n           ng-include="::\'plugins/graph/templates/currency/graph_members_count.html\'"\n           ng-init="setSize(250, 1000)">\n      </div>\n\n\n    </div>\n\n  </ion-content>\n\n</ion-view>\n');
-$templateCache.put('plugins/graph/templates/currency/view_wot_stats.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n\n  <ion-content scroll="true" >\n\n    <div class="center padding" ng-if="loading">\n      <ion-spinner icon="android"></ion-spinner>\n    </div>\n\n    <ng-include\n      ng-if="!loading"\n      src="\'plugins/graph/templates/currency/graph_members_count.html\'" ></ng-include>\n  </ion-content>\n  </ion-view>\n');
-$templateCache.put('plugins/graph/templates/docstats/graph.html','\n  <!-- graphs button bar -->\n  <div class="button-bar-inline "\n       style="top: 33px; margin-top:-33px; position: relative;">\n    <button\n      class="button button-stable button-clear no-padding-xs no-padding-sm pull-right"\n      ng-click="showActionsPopover($event)">\n      <i class="icon ion-navicon-round"></i>\n    </button>\n  </div>\n\n  <canvas id="{{::chartIdPrefix}}{{chart.id}}"\n          class="chart-line"\n          height="{{height}}"\n          width="{{width}}"\n          chart-data="chart.data"\n          chart-labels="labels"\n          chart-dataset-override="chart.datasetOverride"\n          chart-options="chart.options"\n          chart-click="onChartClick">\n  </canvas>\n\n  <ng-include src="::\'plugins/graph/templates/common/graph_range_bar.html\'"></ng-include>\n');
-$templateCache.put('plugins/graph/templates/docstats/view_stats.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n  <ion-nav-title>\n    {{\'GRAPH.DOC_STATS.TITLE\' | translate}}\n  </ion-nav-title>\n\n  <ion-content scroll="true" class="padding" >\n\n    <div class="list" >\n\n      <!-- Doc stat -->\n      <div class="center padding" ng-if="loading">\n        <ion-spinner icon="android"></ion-spinner>\n      </div>\n\n      <div class="item no-padding-xs no-padding-sm" ng-if="!loading"\n           ng-repeat="chart in charts"\n           ng-include="::\'plugins/graph/templates/docstats/graph.html\'"\n           ng-init="setSize(250, 1000)">\n      </div>\n\n    </div>\n\n  </ion-content>\n\n</ion-view>\n');
-$templateCache.put('plugins/graph/templates/network/view_es_network_extend.html','<!-- Buttons section -->\n<ng-if ng-if=":state:enable && extensionPoint === \'documents-buttons\'">\n  <a class="button button-text button-small ink"\n     ui-sref="app.doc_stats_lg" >\n    <i class="icon ion-stats-bars"></i>\n    <span>{{\'NETWORK.VIEW.BTN_GRAPH\'|translate}}</span>\n  </a>\n</ng-if>\n');
-$templateCache.put('plugins/graph/templates/network/view_es_peer_extend.html','<!-- Buttons section -->\n<ng-if ng-if=":state:enable && extensionPoint === \'general\'">\n\n  <a class="item item-icon-left item-icon-right item-text-wrap ink"\n     ng-if="isReachable"\n     ui-sref="app.doc_stats_lg(node)">\n    <i class="icon ion-stats-bars"></i>\n    <span translate>GRAPH.DOC_STATS.TITLE</span>\n    <i class="gray icon ion-ios-arrow-right"></i>\n  </a>\n\n  <a class="item item-icon-left item-icon-right item-text-wrap ink"\n     ng-if="isReachable"\n     ui-sref="app.doc_synchro_lg(node)">\n    <i class="icon ion-stats-bars"></i>\n    <span translate>GRAPH.SYNCHRO.TITLE</span>\n    <i class="gray icon ion-ios-arrow-right"></i>\n  </a>\n</ng-if>\n\n');
-$templateCache.put('plugins/graph/templates/network/view_network_extend.html','<!-- Buttons section -->\n<ng-if ng-if=":state:enable && extensionPoint === \'blockchain-buttons\'">\n  <a class="button button-text button-small ink"\n     ui-sref="app.blockchain_stats" >\n    <i class="icon ion-stats-bars"></i>\n    <span>{{\'NETWORK.VIEW.BTN_GRAPH\'|translate}}</span>\n  </a>\n</ng-if>\n');
-$templateCache.put('plugins/graph/templates/network/view_peer_extend.html','<!-- Buttons section -->\n<ng-if ng-if=":state:enable && extensionPoint === \'general\'">\n\n  <a class="item item-icon-left item-icon-right item-text-wrap ink"\n    ui-sref="app.view_peer_stats({pubkey: node.pubkey})">\n    <i class="icon ion-stats-bars"></i>\n    <span translate>GRAPH.PEER.VIEW.BLOCK_COUNT_LABEL</span>\n    <span class="badge"\n          ng-if="!loading"\n          ng-class="{\'badge-stable\': blockCount > 0, \'badge-assertive\': !blockCount}">\n      {{!blockCount ? \'GRAPH.PEER.VIEW.NO_BLOCK\' : \'GRAPH.PEER.VIEW.BLOCK_COUNT\' | translate:{count: blockCount} }}\n    </span>\n    <ion-spinner class="badge" icon="android" ng-if="loading"></ion-spinner>\n    <i class="gray icon ion-ios-arrow-right"></i>\n  </a>\n</ng-if>\n\n');
-$templateCache.put('plugins/graph/templates/network/view_peer_stats.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n  <ion-nav-title>\n    {{\'GRAPH.BLOCKCHAIN.TITLE\' | translate}}{{id}}\n  </ion-nav-title>\n\n  <ion-content scroll="true" class="no-padding">\n\n    <div class="center padding" ng-if="loading">\n      <ion-spinner icon="android"></ion-spinner>\n    </div>\n\n    <div class="list" ng-if="!loading">\n\n      <!--  - - - - TX divider - - - - -->\n      <div class="item item-divider hidden-xs hidden-sm" translate>\n        GRAPH.BLOCKCHAIN.TX_DIVIDER\n      </div>\n\n      <div class="item no-padding-xs"\n           ng-include="::\'plugins/graph/templates/blockchain/graph_tx_count.html\'"\n           ng-init="setSize(350, 1000)">\n      </div>\n\n  </ion-content>\n\n</ion-view>\n');
-$templateCache.put('plugins/graph/templates/synchro/graph.html','\n  <!-- graphs button bar -->\n  <div class="button-bar-inline "\n       style="top: 33px; margin-top:-33px; position: relative;">\n    <button\n      class="button button-stable button-clear no-padding-xs pull-right"\n      ng-click="showActionsPopover($event)">\n      <i class="icon ion-navicon-round"></i>\n    </button>\n  </div>\n\n  <canvas id="synchro-chart-{{chart.id}}"\n          class="chart-bar"\n          height="{{height}}"\n          width="{{width}}"\n          chart-data="chart.data"\n          chart-labels="labels"\n          chart-dataset-override="chart.datasetOverride"\n          chart-options="chart.options">\n  </canvas>\n\n  <ng-include src="::\'plugins/graph/templates/common/graph_range_bar.html\'"></ng-include>\n');
-$templateCache.put('plugins/graph/templates/synchro/view_stats.html','<ion-view left-buttons="leftButtons"\n          cache-view="false">\n  <ion-nav-title>\n    {{\'GRAPH.SYNCHRO.TITLE\' | translate}}\n  </ion-nav-title>\n\n  <ion-content scroll="true" class="padding" >\n\n    <div class="list" >\n\n      <div class="center padding" ng-if="loading">\n        <ion-spinner icon="android"></ion-spinner>\n      </div>\n\n      <div class="item no-padding-xs" ng-if="!loading"\n           ng-repeat="chart in charts"\n           ng-include="::\'plugins/graph/templates/synchro/graph.html\'"\n           ng-init="setSize(250, 1000)">\n      </div>\n\n    </div>\n\n  </ion-content>\n\n</ion-view>\n');
 $templateCache.put('plugins/map/templates/common/edit_position_extend.html','<div class="item no-padding hidden-xs hidden-sm {{ionItemClass}}" ng-if="formData.geoPoint && formData.geoPoint.lat && formData.geoPoint.lon">\n  <leaflet id="{{::mapId}}"\n           height="250px"\n           center="map.center"\n           markers="map.markers"\n           defaults="map.defaults">\n  </leaflet>\n</div>\n');
 $templateCache.put('plugins/map/templates/network/item_search_tooltip.html','<a href="#">\n  {{peer.dns || peer.server}}\n  <span class="{{peer.uid ? \'positive\' : \'gray\'}}">\n    <i class="icon {{peer.uid ? \'ion-person\' : \'ion-key\'}}"></i>\n    {{peer.uid ? (peer.name||peer.uid) : (peer.pubkey|formatPubkey) }}\n  </span>\n  <span class="gray">{{peer.ipv4 ? (peer.ipv4 + \':\' + peer.port) : \'\'}}</span>\n  <span class="{{peer.bma.useSsl  ? \'\' : \'ng-hide\'}}"><i class="ion-locked"></i> <small>SSL</small></span>\n</a>\n');
 $templateCache.put('plugins/map/templates/network/lookup_extend.html','<!-- FIXME issue #755 - https://git.duniter.org/clients/cesium-grp/cesium/issues/755\n<a ng-if="enable"\n   class="button button-text button-small ink hidden-sm hidden-xs"\n   title="{{\'MAP.NETWORK.LOOKUP.BTN_MAP_HELP\' | translate}}"\n        ui-sref="app.view_network_map">\n  <i class="icon ion-ios-location"></i>\n  {{\'MAP.NETWORK.LOOKUP.BTN_MAP\' | translate}}\n</a>\n -->\n');
@@ -32312,12 +32312,12 @@ $templateCache.put('plugins/map/templates/wot/item_search_tooltip.html','<a href
 $templateCache.put('plugins/map/templates/wot/lookup_lg_extend.html','<a ng-if="enable"\n   class="button button-text button-small ink hidden-sm hidden-xs"\n   title="{{\'MAP.WOT.LOOKUP.BTN_MAP_HELP\' | translate}}"\n   ui-sref="app.view_wot_map">\n  <i class="icon ion-ios-location"></i>\n  {{\'MAP.WOT.LOOKUP.BTN_MAP\' | translate}}\n</a>\n');
 $templateCache.put('plugins/map/templates/wot/popup_marker.html','\n<div class="item no-border no-padding item-avatar "\n     ng-if="loadingMarker">\n\n  <i class="item-image icon ion-person"></i>\n\n  <div class="item-content item-avatar-left-padding padding-top" >\n    <h2 class="stable-bg">\n      &nbsp;\n    </h2>\n    <h4 class="stable-bg col-75">\n      &nbsp;\n    </h4>\n    <h4 class="stable-bg col-50">\n      &nbsp;\n    </h4>\n  </div>\n</div>\n\n<a class="item no-border no-padding item-avatar ink animate-fade-in animate-show-hide ng-hide"\n   ng-show="!loadingMarker"\n   ui-sref="app.wot_identity({pubkey: formData.pubkey, uid: formData.uid})">\n\n  <i ng-if="formData.avatar" class="item-image avatar" style="background-image: url({{::formData.avatar.src}})"></i>\n  <i ng-if="!formData.avatar && formData.uid" class="item-image icon ion-person"></i>\n  <i ng-if="!formData.avatar && !formData.uid" class="item-image icon ion-card"></i>\n\n  <div class="item-content item-avatar-left-padding padding-top">\n    <h2 class="dark">\n      {{formData.name}}\n    </h2>\n    <h4>\n      <span ng-if="formData.uid" class="positive">\n        <b class="ion-person"></b>\n        {{formData.uid}}\n      </span>\n      <span class="gray" title="{{formData.pubkey}}"><b class="ion-key"></b> {{formData.pubkey|formatPubkey}}</span>\n      <span class="assertive" ng-if="!formData.isMember">\n        {{::\'WOT.NOT_MEMBER_PARENTHESIS\'|translate}}\n      </span>\n    </h4>\n    <h4 ng-if="formData.profile.city" class="gray" title="{{formData.profile.city}}">\n      <b class="ion-location"></b> {{formData.profile.city}}\n    </h4>\n  </div>\n</a>\n<!-- buttons -->\n<div class="item no-border no-padding">\n  <div class="pull-left gray">\n    <!-- show description -->\n    <a class="animate-fade-in animate-show-hide gray ng-hide"\n       ng-class="{\'ion-arrow-down-b\': !showDescription, \'ion-arrow-up-b\': showDescription}"\n       ng-click="showDescription=!showDescription;"\n       title="{{\'PROFILE.DESCRIPTION\'|translate}}"\n       ng-show="!loadingMarker && formData.profile.description">\n      &nbsp;\n    </a>\n  </div>\n  <div style="font-size: 18px;" class="pull-right gray">\n    <!-- share -->\n    <a class="icon ion-android-share-alt "\n       ng-click="showSharePopover($event)"\n       title="{{\'COMMON.BTN_SHARE\' | translate}}">&nbsp;</a>\n    <!-- certify -->\n    <a class="icon ion-ribbon-b"\n       ng-click="certify()"\n       title="{{\'WOT.BTN_CERTIFY\' | translate}}"\n       ng-hide="!canCertify">&nbsp;</a>\n    <!-- compose message -->\n    <a class="icon ion-compose"\n       ng-click="showNewMessageModal()"\n       title="{{\'MESSAGE.BTN_WRITE\' | translate}}">&nbsp;</a>\n    <!-- transfer -->\n    <a class="icon ion-card"\n       ng-click="showTransferModal({pubkey:formData.pubkey, uid: formData.name||formData.uid})"\n       title="{{\'COMMON.BTN_SEND_MONEY\' | translate}}">&nbsp;</a>\n\n  </div>\n</div>\n<div class="item no-border no-padding item-text-wrap hidden-xs ng-hide" ng-show="showDescription">\n  <small trust-as-html="formData.profile.description|truncText:500"></small>\n</div>\n');
 $templateCache.put('plugins/map/templates/wot/view_map.html','<ion-view left-buttons="leftButtons" class="view-map-wot">\n  <ion-nav-title>\n    <span class="hidden-xs" translate>MAP.WOT.VIEW.TITLE</span>\n  </ion-nav-title>\n\n  <ion-nav-buttons side="secondary">\n    <button class="button button-icon button-clear icon ion-loop visible-xs visible-sm" ng-click="load()">\n    </button>\n  </ion-nav-buttons>\n\n  <ion-content data-tap-disabled="true">\n    <a id="helptip-map-wot" style="left: 150px; top: 50px; position: relative;"></a>\n    <leaflet id="{{::mapId}}"\n             height="100%"\n             layers="map.layers"\n             markers="map.markers"\n             lf-center="map.center"\n             bounds="map.bounds">\n    </leaflet>\n  </ion-content>\n</ion-view>\n');
-$templateCache.put('plugins/es/templates/message/tabs/tab_list.html','<ion-view><ion-nav-buttons side="secondary"><cs-extension-point name="nav-buttons"></cs-extension-point><button class="button button-icon button-clear icon ion-android-more-vertical" ng-click="showActionsPopover($event)"></button></ion-nav-buttons><ion-content><ion-refresher pulling-text="{{\'COMMON.BTN_REFRESH\' | translate}}" on-refresh="refresh()"></ion-refresher><cs-extension-point name="buttons"></cs-extension-point><ng-include src="::\'plugins/es/templates/message/list.html\'"></ng-include></ion-content><div class="visible-xs visible-sm"><button ng-if="fabButtonNewMessageId" id="{{::fabButtonNewMessageId}}" class="button button-fab button-fab-bottom-right button-assertive spin has-footer" ng-click="showNewMessageModal()"><i class="icon ion-compose"></i></button></div></ion-view>');
-$templateCache.put('plugins/es/templates/registry/tabs/tab_registry.html','<ion-view><ion-nav-buttons side="secondary"><cs-extension-point name="nav-buttons"></cs-extension-point><button class="button button-icon button-clear" ng-click="showFiltersPopover($event)"><i class="icon ion-android-funnel"></i></button> <button class="button button-icon button-clear icon ion-android-more-vertical" ng-click="showActionsPopover($event)"></button></ion-nav-buttons><ion-content><ion-refresher pulling-text="{{\'COMMON.BTN_REFRESH\' | translate}}" on-refresh="doSearch()"></ion-refresher><cs-extension-point name="buttons"></cs-extension-point><ng-include src="::\'plugins/es/templates/registry/lookup_form.html\'"></ng-include><ng-include src="::\'plugins/es/templates/registry/lookup_list.html\'"></ng-include></ion-content></ion-view>');
 $templateCache.put('plugins/graph/templates/currency/tabs/tab_blocks_stats.html','<ion-view>\n  <ion-content>\n    <div\n       ng-include="::\'plugins/graph/templates/blockchain/graph_tx_count.html\'"\n       ng-controller="GpBlockchainTxCountCtrl"\n       ng-init="setSize(500,700,false)">\n      </div>\n  </ion-content>\n</ion-view>\n');
 $templateCache.put('plugins/graph/templates/currency/tabs/tab_network_stats.html','<ion-view>\n  <ion-content>\n\n    <div class="list">\n      <div class="item"\n         ng-include="::\'plugins/graph/templates/blockchain/graph_block_issuers.html\'"\n         ng-controller="GpBlockchainIssuersCtrl"\n           ng-init="setSize(500,700,true)">\n      </div>\n    </div>\n  </ion-content>\n</ion-view>\n');
 $templateCache.put('plugins/graph/templates/currency/tabs/tab_parameters_stats.html','<ion-view>\n  <ion-content>\n    <div class="list no-padding-xs no-padding-sm">\n\n      <ng-container ng-controller="GpCurrencyMonetaryMassCtrl">\n\n        <div class="center padding" ng-if="loading">\n          <ion-spinner icon="android"></ion-spinner>\n        </div>\n\n        <!-- Monetary mass -->\n        <div class="item"\n             ng-include="::\'plugins/graph/templates/currency/graph_monetary_mass.html\'"\n             ng-init="setSize(500,700,true)">\n        </div>\n      </ng-container>\n\n      <!-- DU -->\n      <ng-container ng-controller="GpCurrencyDUCtrl">\n        <div class="item"\n             ng-include="::\'plugins/graph/templates/currency/graph_du.html\'"\n             ng-init="setSize(500,700,true)">\n        </div>\n      </ng-container>\n    </div>\n  </ion-content>\n</ion-view>\n');
-$templateCache.put('plugins/graph/templates/currency/tabs/tab_wot_stats.html','<ion-view>\n  <ion-content>\n    <div class="center padding" ng-if="loading">\n      <ion-spinner icon="android"></ion-spinner>\n    </div>\n\n    <div class="list no-padding">\n      <div class="item no-padding-top"\n           ng-include="::\'plugins/graph/templates/currency/graph_members_count.html\'"\n           ng-init="setSize(600,700,false)">\n      </div>\n    </div>\n  </ion-content>\n</ion-view>\n');}]);
+$templateCache.put('plugins/graph/templates/currency/tabs/tab_wot_stats.html','<ion-view>\n  <ion-content>\n    <div class="center padding" ng-if="loading">\n      <ion-spinner icon="android"></ion-spinner>\n    </div>\n\n    <div class="list no-padding">\n      <div class="item no-padding-top"\n           ng-include="::\'plugins/graph/templates/currency/graph_members_count.html\'"\n           ng-init="setSize(600,700,false)">\n      </div>\n    </div>\n  </ion-content>\n</ion-view>\n');
+$templateCache.put('plugins/es/templates/message/tabs/tab_list.html','<ion-view><ion-nav-buttons side="secondary"><cs-extension-point name="nav-buttons"></cs-extension-point><button class="button button-icon button-clear icon ion-android-more-vertical" ng-click="showActionsPopover($event)"></button></ion-nav-buttons><ion-content><ion-refresher pulling-text="{{\'COMMON.BTN_REFRESH\' | translate}}" on-refresh="refresh()"></ion-refresher><cs-extension-point name="buttons"></cs-extension-point><ng-include src="::\'plugins/es/templates/message/list.html\'"></ng-include></ion-content><div class="visible-xs visible-sm"><button ng-if="fabButtonNewMessageId" id="{{::fabButtonNewMessageId}}" class="button button-fab button-fab-bottom-right button-assertive spin has-footer" ng-click="showNewMessageModal()"><i class="icon ion-compose"></i></button></div></ion-view>');
+$templateCache.put('plugins/es/templates/registry/tabs/tab_registry.html','<ion-view><ion-nav-buttons side="secondary"><cs-extension-point name="nav-buttons"></cs-extension-point><button class="button button-icon button-clear" ng-click="showFiltersPopover($event)"><i class="icon ion-android-funnel"></i></button> <button class="button button-icon button-clear icon ion-android-more-vertical" ng-click="showActionsPopover($event)"></button></ion-nav-buttons><ion-content><ion-refresher pulling-text="{{\'COMMON.BTN_REFRESH\' | translate}}" on-refresh="doSearch()"></ion-refresher><cs-extension-point name="buttons"></cs-extension-point><ng-include src="::\'plugins/es/templates/registry/lookup_form.html\'"></ng-include><ng-include src="::\'plugins/es/templates/registry/lookup_list.html\'"></ng-include></ion-content></ion-view>');}]);
 
 angular.module('cesium.es.plugin', [
     // Services
diff --git a/assets/www/plugins/cordova-plugin-file/www/DirectoryEntry.js b/assets/www/plugins/cordova-plugin-file/www/DirectoryEntry.js
new file mode 100644
index 0000000000000000000000000000000000000000..bb676eb6c995b051b51fd9bbb6b65508b79b6aeb
--- /dev/null
+++ b/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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/DirectoryReader.js b/assets/www/plugins/cordova-plugin-file/www/DirectoryReader.js
new file mode 100644
index 0000000000000000000000000000000000000000..417c85f10769b3ad49cf1caa26a0812357f5e7cb
--- /dev/null
+++ b/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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/Entry.js b/assets/www/plugins/cordova-plugin-file/www/Entry.js
new file mode 100644
index 0000000000000000000000000000000000000000..b296d999e9284fe07b1419adc4f0fd2b64ec282f
--- /dev/null
+++ b/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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/File.js b/assets/www/plugins/cordova-plugin-file/www/File.js
new file mode 100644
index 0000000000000000000000000000000000000000..c7717865329ab6d9170601ffe9395f6292cb3ac3
--- /dev/null
+++ b/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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/FileEntry.js b/assets/www/plugins/cordova-plugin-file/www/FileEntry.js
new file mode 100644
index 0000000000000000000000000000000000000000..6651b5542e23e9be8bcbbacaf7a00ce64230936d
--- /dev/null
+++ b/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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/FileError.js b/assets/www/plugins/cordova-plugin-file/www/FileError.js
new file mode 100644
index 0000000000000000000000000000000000000000..f378c38746f37e505b25c091b78f691526440788
--- /dev/null
+++ b/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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/FileReader.js b/assets/www/plugins/cordova-plugin-file/www/FileReader.js
new file mode 100644
index 0000000000000000000000000000000000000000..5c030913b9eb7caa32e7f137760562fe578525e1
--- /dev/null
+++ b/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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/FileSystem.js b/assets/www/plugins/cordova-plugin-file/www/FileSystem.js
new file mode 100644
index 0000000000000000000000000000000000000000..ad9ea785932d46328fd0cbbc654c04e1b8eab7f4
--- /dev/null
+++ b/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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/FileUploadOptions.js b/assets/www/plugins/cordova-plugin-file/www/FileUploadOptions.js
new file mode 100644
index 0000000000000000000000000000000000000000..6acd09334b02f780ccaa42712b837cb41bf415bc
--- /dev/null
+++ b/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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/FileUploadResult.js b/assets/www/plugins/cordova-plugin-file/www/FileUploadResult.js
new file mode 100644
index 0000000000000000000000000000000000000000..e3c3a743144a6795c8f5f2eb50919539dc68bc41
--- /dev/null
+++ b/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;
+};
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/FileWriter.js b/assets/www/plugins/cordova-plugin-file/www/FileWriter.js
new file mode 100644
index 0000000000000000000000000000000000000000..c3e85621992e2201982bef3aebc7e04d0e7d2887
--- /dev/null
+++ b/assets/www/plugins/cordova-plugin-file/www/FileWriter.js
@@ -0,0 +1,328 @@
+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 FileReader = require('./FileReader');
+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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/Flags.js b/assets/www/plugins/cordova-plugin-file/www/Flags.js
new file mode 100644
index 0000000000000000000000000000000000000000..cdb12bb7310cf9aabe37248fb7c72cb2247f6ea7
--- /dev/null
+++ b/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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/LocalFileSystem.js b/assets/www/plugins/cordova-plugin-file/www/LocalFileSystem.js
new file mode 100644
index 0000000000000000000000000000000000000000..4ce6848120f0cb9825f3884511db9e77f4f522c8
--- /dev/null
+++ b/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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/Metadata.js b/assets/www/plugins/cordova-plugin-file/www/Metadata.js
new file mode 100644
index 0000000000000000000000000000000000000000..22366e167dec22362f49f857a38c68e4451424e6
--- /dev/null
+++ b/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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/ProgressEvent.js b/assets/www/plugins/cordova-plugin-file/www/ProgressEvent.js
new file mode 100644
index 0000000000000000000000000000000000000000..cbecdb12bc11507cbc99495932af20b2dea57c93
--- /dev/null
+++ b/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;
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/android/FileSystem.js b/assets/www/plugins/cordova-plugin-file/www/android/FileSystem.js
new file mode 100644
index 0000000000000000000000000000000000000000..0124fa673d5e6cc17a04ea30592f0bc7637584c2
--- /dev/null
+++ b/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
+    }
+};
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/browser/isChrome.js b/assets/www/plugins/cordova-plugin-file/www/browser/isChrome.js
new file mode 100644
index 0000000000000000000000000000000000000000..90450d8624dfc81805031f9359abfb3893b4416b
--- /dev/null
+++ b/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;
+};
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/fileSystemPaths.js b/assets/www/plugins/cordova-plugin-file/www/fileSystemPaths.js
new file mode 100644
index 0000000000000000000000000000000000000000..4bfc0f62946403acfb17abf980e4acda4c9bc4db
--- /dev/null
+++ b/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', []);
+});
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/fileSystems-roots.js b/assets/www/plugins/cordova-plugin-file/www/fileSystems-roots.js
new file mode 100644
index 0000000000000000000000000000000000000000..6e02953e8391d7cc5ca207ee36cc55294f59aa53
--- /dev/null
+++ b/assets/www/plugins/cordova-plugin-file/www/fileSystems-roots.js
@@ -0,0 +1,49 @@
+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];
+            if (fsRoot) {
+                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', []);
+    }
+};
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/fileSystems.js b/assets/www/plugins/cordova-plugin-file/www/fileSystems.js
new file mode 100644
index 0000000000000000000000000000000000000000..e61ceafc8167ecc8349102a10d676651289ccaca
--- /dev/null
+++ b/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);
+};
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/requestFileSystem.js b/assets/www/plugins/cordova-plugin-file/www/requestFileSystem.js
new file mode 100644
index 0000000000000000000000000000000000000000..7f652193cd85135fc5cac39f1870f828ebd1933c
--- /dev/null
+++ b/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;
+})();
+
+});
diff --git a/assets/www/plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js b/assets/www/plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js
new file mode 100644
index 0000000000000000000000000000000000000000..73715bc0e30458b6ed530057cff9c28cb1a5a371
--- /dev/null
+++ b/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);
+    };
+})();
+
+});
diff --git a/build-extras.gradle b/build-extras.gradle
index d3275a4912d554bb917c8a433edde180eb32cea8..38129572df5b58ca187c9905a9ce7ea60d5ced72 100644
--- a/build-extras.gradle
+++ b/build-extras.gradle
@@ -1,5 +1,21 @@
 configurations.all {
     resolutionStrategy {
-        force 'com.android.support:support-v4:27.1.0'
+      force 'androidx.legacy:legacy-support-v4:1.0.0'
+      force 'androidx.appcompat:appcompat:1.0.0'
     }
 }
+dependencies {
+  implementation(project(path: "CordovaLib")) {
+    exclude group: 'com.android.support', module:'support-v4'
+  }
+}
+
+// Overrides the value of minSdkVersion set in AndroidManifest.xml. Useful when creating multiple APKs based on SDK version
+ext.cdvMinSdkVersion=16
+
+  // Overrides the automatically detected android.compileSdkVersion value
+ext.cdvCompileSdkVersion=29
+
+// Overrides the automatically detected android.buildToolsVersion value
+ext.cdvBuildToolsVersion='29.0.2'
+//ext.cdvBuildToolsVersion = '30.0.0-rc2'
diff --git a/build.gradle b/build.gradle
index f8c95a8d1af1eeba57f49f7af886693c3493400d..b425e7f025b5b4c6476aa0e62c59cc2e36f48810 100644
--- a/build.gradle
+++ b/build.gradle
@@ -48,7 +48,7 @@ allprojects {
 }
 
 task wrapper(type: Wrapper) {
-    gradleVersion = '4.4.0'
+    gradleVersion = '4.10.3'
 }
 
 // Configuration properties. Set these via environment variables, build-extras.gradle, or gradle.properties.
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000000000000000000000000000000000000..65dae2fae53a0d276f61d01d344a237d9e39a760
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,4 @@
+
+
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index ddb3e1024ce810a0ed5e3707cbcaf77801c61f17..0a96057d40ba4df7cf2e3d305bab2990ed46ef6e 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
 distributionUrl=https://services.gradle.org/distributions/gradle-4.10.3-all.zip
diff --git a/platform_www/cordova_plugins.js b/platform_www/cordova_plugins.js
index ba983110e27562fc0ba420708c60bc70bb2d28b9..b649a5ce2cff6f26e98624791764b0b116c16cd1 100644
--- a/platform_www/cordova_plugins.js
+++ b/platform_www/cordova_plugins.js
@@ -173,6 +173,179 @@ module.exports = [
     "clobbers": [
       "Ionic.WebView"
     ]
+  },
+  {
+    "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 = 
@@ -195,7 +368,8 @@ module.exports.metadata =
   "ionic-plugin-keyboard": "2.2.1",
   "phonegap-plugin-barcodescanner": "7.0.0",
   "cordova-plugin-ionic-keyboard": "2.2.0",
-  "cordova-plugin-ionic-webview": "4.1.3"
+  "cordova-plugin-ionic-webview": "4.1.3",
+  "cordova-plugin-file": "6.0.2"
 };
 // BOTTOM OF METADATA
 });
\ No newline at end of file
diff --git a/platform_www/plugins/cordova-plugin-file/www/DirectoryEntry.js b/platform_www/plugins/cordova-plugin-file/www/DirectoryEntry.js
new file mode 100644
index 0000000000000000000000000000000000000000..bb676eb6c995b051b51fd9bbb6b65508b79b6aeb
--- /dev/null
+++ b/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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/DirectoryReader.js b/platform_www/plugins/cordova-plugin-file/www/DirectoryReader.js
new file mode 100644
index 0000000000000000000000000000000000000000..417c85f10769b3ad49cf1caa26a0812357f5e7cb
--- /dev/null
+++ b/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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/Entry.js b/platform_www/plugins/cordova-plugin-file/www/Entry.js
new file mode 100644
index 0000000000000000000000000000000000000000..b296d999e9284fe07b1419adc4f0fd2b64ec282f
--- /dev/null
+++ b/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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/File.js b/platform_www/plugins/cordova-plugin-file/www/File.js
new file mode 100644
index 0000000000000000000000000000000000000000..c7717865329ab6d9170601ffe9395f6292cb3ac3
--- /dev/null
+++ b/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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/FileEntry.js b/platform_www/plugins/cordova-plugin-file/www/FileEntry.js
new file mode 100644
index 0000000000000000000000000000000000000000..6651b5542e23e9be8bcbbacaf7a00ce64230936d
--- /dev/null
+++ b/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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/FileError.js b/platform_www/plugins/cordova-plugin-file/www/FileError.js
new file mode 100644
index 0000000000000000000000000000000000000000..f378c38746f37e505b25c091b78f691526440788
--- /dev/null
+++ b/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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/FileReader.js b/platform_www/plugins/cordova-plugin-file/www/FileReader.js
new file mode 100644
index 0000000000000000000000000000000000000000..5c030913b9eb7caa32e7f137760562fe578525e1
--- /dev/null
+++ b/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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/FileSystem.js b/platform_www/plugins/cordova-plugin-file/www/FileSystem.js
new file mode 100644
index 0000000000000000000000000000000000000000..ad9ea785932d46328fd0cbbc654c04e1b8eab7f4
--- /dev/null
+++ b/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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/FileUploadOptions.js b/platform_www/plugins/cordova-plugin-file/www/FileUploadOptions.js
new file mode 100644
index 0000000000000000000000000000000000000000..6acd09334b02f780ccaa42712b837cb41bf415bc
--- /dev/null
+++ b/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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/FileUploadResult.js b/platform_www/plugins/cordova-plugin-file/www/FileUploadResult.js
new file mode 100644
index 0000000000000000000000000000000000000000..e3c3a743144a6795c8f5f2eb50919539dc68bc41
--- /dev/null
+++ b/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;
+};
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/FileWriter.js b/platform_www/plugins/cordova-plugin-file/www/FileWriter.js
new file mode 100644
index 0000000000000000000000000000000000000000..c3e85621992e2201982bef3aebc7e04d0e7d2887
--- /dev/null
+++ b/platform_www/plugins/cordova-plugin-file/www/FileWriter.js
@@ -0,0 +1,328 @@
+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 FileReader = require('./FileReader');
+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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/Flags.js b/platform_www/plugins/cordova-plugin-file/www/Flags.js
new file mode 100644
index 0000000000000000000000000000000000000000..cdb12bb7310cf9aabe37248fb7c72cb2247f6ea7
--- /dev/null
+++ b/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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/LocalFileSystem.js b/platform_www/plugins/cordova-plugin-file/www/LocalFileSystem.js
new file mode 100644
index 0000000000000000000000000000000000000000..4ce6848120f0cb9825f3884511db9e77f4f522c8
--- /dev/null
+++ b/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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/Metadata.js b/platform_www/plugins/cordova-plugin-file/www/Metadata.js
new file mode 100644
index 0000000000000000000000000000000000000000..22366e167dec22362f49f857a38c68e4451424e6
--- /dev/null
+++ b/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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/ProgressEvent.js b/platform_www/plugins/cordova-plugin-file/www/ProgressEvent.js
new file mode 100644
index 0000000000000000000000000000000000000000..cbecdb12bc11507cbc99495932af20b2dea57c93
--- /dev/null
+++ b/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;
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/android/FileSystem.js b/platform_www/plugins/cordova-plugin-file/www/android/FileSystem.js
new file mode 100644
index 0000000000000000000000000000000000000000..0124fa673d5e6cc17a04ea30592f0bc7637584c2
--- /dev/null
+++ b/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
+    }
+};
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/browser/isChrome.js b/platform_www/plugins/cordova-plugin-file/www/browser/isChrome.js
new file mode 100644
index 0000000000000000000000000000000000000000..90450d8624dfc81805031f9359abfb3893b4416b
--- /dev/null
+++ b/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;
+};
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/fileSystemPaths.js b/platform_www/plugins/cordova-plugin-file/www/fileSystemPaths.js
new file mode 100644
index 0000000000000000000000000000000000000000..4bfc0f62946403acfb17abf980e4acda4c9bc4db
--- /dev/null
+++ b/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', []);
+});
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/fileSystems-roots.js b/platform_www/plugins/cordova-plugin-file/www/fileSystems-roots.js
new file mode 100644
index 0000000000000000000000000000000000000000..6e02953e8391d7cc5ca207ee36cc55294f59aa53
--- /dev/null
+++ b/platform_www/plugins/cordova-plugin-file/www/fileSystems-roots.js
@@ -0,0 +1,49 @@
+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];
+            if (fsRoot) {
+                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', []);
+    }
+};
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/fileSystems.js b/platform_www/plugins/cordova-plugin-file/www/fileSystems.js
new file mode 100644
index 0000000000000000000000000000000000000000..e61ceafc8167ecc8349102a10d676651289ccaca
--- /dev/null
+++ b/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);
+};
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/requestFileSystem.js b/platform_www/plugins/cordova-plugin-file/www/requestFileSystem.js
new file mode 100644
index 0000000000000000000000000000000000000000..7f652193cd85135fc5cac39f1870f828ebd1933c
--- /dev/null
+++ b/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;
+})();
+
+});
diff --git a/platform_www/plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js b/platform_www/plugins/cordova-plugin-file/www/resolveLocalFileSystemURI.js
new file mode 100644
index 0000000000000000000000000000000000000000..73715bc0e30458b6ed530057cff9c28cb1a5a371
--- /dev/null
+++ b/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);
+    };
+})();
+
+});
diff --git a/res/xml/config.xml b/res/xml/config.xml
index 08786b47305f27aad1c445a45201366eb8ba879f..a359ac09c8647c4c55073472c01f606498571417 100644
--- a/res/xml/config.xml
+++ b/res/xml/config.xml
@@ -1,5 +1,5 @@
 <?xml version='1.0' encoding='utf-8'?>
-<widget android-versionCode="106012" id="fr.duniter.cesium" ios-CFBundleIdentifier="org.duniter.cesium" version="1.6.2-alpha" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
+<widget android-versionCode="106030" id="fr.duniter.cesium" ios-CFBundleIdentifier="org.duniter.cesium" version="1.6.3" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
     <feature name="Clipboard">
         <param name="android-package" value="com.verso.cordova.clipboard.Clipboard" />
     </feature>
@@ -50,6 +50,11 @@
     <feature name="BarcodeScanner">
         <param name="android-package" value="com.phonegap.plugins.barcodescanner.BarcodeScanner" />
     </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="CDVIonicKeyboard">
         <param name="android-package" onload="true" value="io.ionic.keyboard.CDVIonicKeyboard" />
     </feature>
@@ -109,7 +114,7 @@
     <preference name="xwalkVersion" value="19" />
     <preference name="xwalkMultipleApk" value="false" />
     <preference name="android-minSdkVersion" value="16" />
-    <preference name="android-targetSdkVersion" value="28" />
+    <preference name="android-targetSdkVersion" value="29" />
     <preference name="StatusBarOverlaysWebView" value="false" />
     <preference name="StatusBarBackgroundColor" value="#000" />
     <preference name="StatusBarStyle" value="lightcontent" />
diff --git a/src/com/crypho/plugins/AES.java b/src/com/crypho/plugins/AES.java
index 03c2e33264b365f658359254e1edcaf2fa59b61f..4fe04418b27e2e39a4bc3f7cb1c810ba0df5d011 100644
--- a/src/com/crypho/plugins/AES.java
+++ b/src/com/crypho/plugins/AES.java
@@ -1,9 +1,6 @@
 package com.crypho.plugins;
 
-import android.util.Log;
 import android.util.Base64;
-
-import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.security.Key;
@@ -16,67 +13,77 @@ import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.KeyGenerator;
 
 public class AES {
-	private static final String CIPHER_MODE = "CCM";
-	private static final int KEY_SIZE = 256;
-	private static final int VERSION = 1;
-	private static final Cipher CIPHER = getCipher();
+    private static final String CIPHER_MODE = "CCM";
+    private static final int KEY_SIZE = 256;
+    private static final int VERSION = 1;
+    private static final Cipher GLOBAL_CIPHER = getCipher(CIPHER_MODE);
+
+    public static JSONObject encrypt(byte[] msg, byte[] adata) throws Exception {
+        byte[] iv, ct, secretKeySpec_enc;
+        synchronized (GLOBAL_CIPHER) {
+            SecretKeySpec secretKeySpec = generateKeySpec();
+            secretKeySpec_enc = secretKeySpec.getEncoded();
+            initCipher(Cipher.ENCRYPT_MODE, secretKeySpec, null, adata, GLOBAL_CIPHER);
+            iv = GLOBAL_CIPHER.getIV();
+            ct = GLOBAL_CIPHER.doFinal(msg);
+        }
 
-	public static JSONObject encrypt(byte[] msg, byte[] adata) throws Exception {
-		byte[] iv, ct, secretKeySpec_enc;
-		synchronized (CIPHER) {
-			SecretKeySpec secretKeySpec = generateKeySpec();
-			secretKeySpec_enc = secretKeySpec.getEncoded();
-			initCipher(Cipher.ENCRYPT_MODE, secretKeySpec, null, adata);
-			iv = CIPHER.getIV();
-			ct = CIPHER.doFinal(msg);
-		}
+        JSONObject value = new JSONObject();
+        value.put("iv", Base64.encodeToString(iv, Base64.DEFAULT));
+        value.put("v", Integer.toString(VERSION));
+        value.put("ks", Integer.toString(KEY_SIZE));
+        value.put("cipher", "AES");
+        value.put("mode", CIPHER_MODE);
+        value.put("adata", Base64.encodeToString(adata, Base64.DEFAULT));
+        value.put("ct", Base64.encodeToString(ct, Base64.DEFAULT));
 
-		JSONObject value = new JSONObject();
-		value.put("iv", Base64.encodeToString(iv, Base64.DEFAULT));
-		value.put("v", Integer.toString(VERSION));
-		value.put("ks", Integer.toString(KEY_SIZE));
-		value.put("cipher", "AES");
-		value.put("mode", CIPHER_MODE);
-		value.put("adata", Base64.encodeToString(adata, Base64.DEFAULT));
-		value.put("ct", Base64.encodeToString(ct, Base64.DEFAULT));
+        JSONObject result = new JSONObject();
+        result.put("key", Base64.encodeToString(secretKeySpec_enc, Base64.DEFAULT));
+        result.put("value", value);
+        result.put("native", true);
 
-		JSONObject result = new JSONObject();
-		result.put("key", Base64.encodeToString(secretKeySpec_enc, Base64.DEFAULT));
-		result.put("value", value);
-		result.put("native", true);
+        return result;
+    }
 
-		return result;
-	}
+    public static String decrypt(byte[] buf, byte[] key, byte[] iv, byte[] adata, String cipherMode) throws Exception {
+        Cipher cipher;
+        if ( cipherMode == CIPHER_MODE ) {
+            cipher = GLOBAL_CIPHER;
+        } else {
+            cipher = getCipher(cipherMode);
+        }
+        SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
+        synchronized (cipher) {
+            initCipher(Cipher.DECRYPT_MODE, secretKeySpec, iv, adata, cipher);
+            return new String(cipher.doFinal(buf));
+        }
+    }
 
-	public static String decrypt(byte[] buf, byte[] key, byte[] iv, byte[] adata) throws Exception {
-		SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
-		synchronized (CIPHER) {
-			initCipher(Cipher.DECRYPT_MODE, secretKeySpec, iv, adata);
-			return new String(CIPHER.doFinal(buf));
-		}
-	}
+    private static SecretKeySpec generateKeySpec() throws Exception {
+        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
+        keyGenerator.init(KEY_SIZE, new SecureRandom());
+        SecretKey sc = keyGenerator.generateKey();
+        return new SecretKeySpec(sc.getEncoded(), "AES");
+    }
 
-	private static SecretKeySpec generateKeySpec() throws Exception {
-	    KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
-	    keyGenerator.init(KEY_SIZE, new SecureRandom());
-	    SecretKey sc = keyGenerator.generateKey();
-	    return new SecretKeySpec(sc.getEncoded(), "AES");
-	}
+    private static void initCipher(int cipherMode, Key key, byte[] iv, byte[] adata, Cipher cipher) throws Exception {
+        if (iv != null) {
+            cipher.init(cipherMode, key, new IvParameterSpec(iv));
+        } else {
+            cipher.init(cipherMode, key);
+        }
+        cipher.updateAAD(adata);
+    }
 
-	private static void initCipher(int cipherMode, Key key, byte[] iv, byte[] adata) throws Exception {
-		if (iv != null) {
-			CIPHER.init(cipherMode, key, new IvParameterSpec(iv));
-		} else {
-			CIPHER.init(cipherMode, key);
-		}
-		CIPHER.updateAAD(adata);
-	}
+    private static Cipher getCipher(String cipherMode) {
+	    if ( cipherMode == null ) {
+	        cipherMode = CIPHER_MODE;
+	    }
 
-	private static Cipher getCipher() {
-		try {
-			return Cipher.getInstance("AES/" + CIPHER_MODE + "/NoPadding");
-		} catch (Exception e) {
-			return null;
-		}
-	}
+        try {
+            return Cipher.getInstance("AES/" + cipherMode + "/NoPadding");
+        } catch (Exception e) {
+            return null;
+        }
+    }
 }
diff --git a/src/com/crypho/plugins/AbstractRSA.java b/src/com/crypho/plugins/AbstractRSA.java
new file mode 100644
index 0000000000000000000000000000000000000000..6efc10776ae97330e1954b8810f7321a916c088d
--- /dev/null
+++ b/src/com/crypho/plugins/AbstractRSA.java
@@ -0,0 +1,124 @@
+package com.crypho.plugins;
+
+import android.content.Context;
+import android.os.Build;
+import android.security.keystore.KeyProperties;
+import android.util.Log;
+
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.spec.AlgorithmParameterSpec;
+
+import javax.crypto.Cipher;
+
+public abstract class AbstractRSA {
+    protected static final String TAG = "SecureStorage";
+    static final Integer CERT_VALID_YEARS = 100;
+    static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
+    private final Cipher CIPHER = getCipher();
+
+
+    abstract AlgorithmParameterSpec getInitParams(Context ctx, String alias, Integer userAuthenticationValidityDuration) throws Exception;
+
+    boolean encryptionKeysAvailable(String alias) {
+        return isEntryAvailable(alias);
+    }
+
+    String getRSAKey() {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            return KeyProperties.KEY_ALGORITHM_RSA;
+        }
+        return "RSA";
+    }
+
+    private Cipher getCipher() {
+        try {
+            return Cipher.getInstance("RSA/ECB/PKCS1Padding");
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    private byte[] runCipher(int cipherMode, String alias, byte[] buf) throws Exception {
+        Key key = loadKey(cipherMode, alias);
+        assert CIPHER != null;
+        synchronized (CIPHER) {
+            CIPHER.init(cipherMode, key);
+            return CIPHER.doFinal(buf);
+        }
+    }
+
+    public void createKeyPair(Context ctx, String alias, Integer userAuthenticationValidityDuration) throws Exception {
+        AlgorithmParameterSpec spec = getInitParams(ctx, alias, userAuthenticationValidityDuration);
+        KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance(getRSAKey(), KEYSTORE_PROVIDER);
+        kpGenerator.initialize(spec);
+        kpGenerator.generateKeyPair();
+    }
+
+    public byte[] encrypt(byte[] buf, String alias) throws Exception {
+        return runCipher(Cipher.ENCRYPT_MODE, alias, buf);
+    }
+
+
+    public byte[] decrypt(byte[] buf, String alias) throws Exception {
+        return runCipher(Cipher.DECRYPT_MODE, alias, buf);
+    }
+
+    protected abstract boolean isEntryAvailable(String alias);
+
+    Key loadKey(int cipherMode, String alias) throws Exception {
+        KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
+        keyStore.load(null, null);
+
+        if (!keyStore.containsAlias(alias)) {
+            throw new Exception("KeyStore doesn't contain alias: " + alias);
+        }
+
+        Key key;
+        switch (cipherMode) {
+            case Cipher.ENCRYPT_MODE:
+                key = keyStore.getCertificate(alias).getPublicKey();
+                if (key == null) {
+                    throw new Exception("Failed to load the public key for " + alias);
+                }
+                break;
+            case Cipher.DECRYPT_MODE:
+                key = keyStore.getKey(alias, null);
+                if (key == null) {
+                    throw new Exception("Failed to load the private key for " + alias);
+                }
+                break;
+            default:
+                throw new Exception("Invalid cipher mode parameter");
+        }
+        return key;
+    }
+
+    private void deleteKey(String alias) {
+		try {
+			KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
+			keyStore.load(null, null);
+			keyStore.deleteEntry(alias);
+		} catch (Exception e) {
+			Log.e(TAG, "Exception deleting key", e);
+		}
+    }
+
+    boolean userAuthenticationRequired(String alias) {
+        try {
+            // Do a quick encrypt/decrypt test
+            byte[] encrypted = encrypt(alias.getBytes(), alias);
+            decrypt(encrypted, alias);
+            return false;
+        } catch (InvalidKeyException noAuthEx) {
+            deleteKey(alias);
+            return true;
+        } catch (Exception e) {
+            // Other
+            return false;
+        }
+    }
+}
+
diff --git a/src/com/crypho/plugins/RSA.java b/src/com/crypho/plugins/RSA.java
index 6b8323e4d843727717bf5fd30f52c0584c8391b2..e09df27ea6c95d36ff328869d5040d35c82253e5 100644
--- a/src/com/crypho/plugins/RSA.java
+++ b/src/com/crypho/plugins/RSA.java
@@ -1,95 +1,56 @@
 package com.crypho.plugins;
 
+import android.annotation.TargetApi;
 import android.content.Context;
+import android.os.Build;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyInfo;
+import android.security.keystore.KeyProperties;
 import android.util.Log;
 
-import android.security.KeyPairGeneratorSpec;
-
-import java.math.BigInteger;
 import java.security.Key;
-import java.security.KeyPairGenerator;
-import java.security.KeyStore;
+import java.security.KeyFactory;
+import java.security.spec.AlgorithmParameterSpec;
+import java.security.spec.RSAKeyGenParameterSpec;
 import java.util.Calendar;
 
 import javax.crypto.Cipher;
-import javax.security.auth.x500.X500Principal;
-
-public class RSA {
-	private static final String KEYSTORE_PROVIDER = "AndroidKeyStore";
-	private static final Cipher CIPHER = getCipher();
-
-	public static byte[] encrypt(byte[] buf, String alias) throws Exception {
-		synchronized (CIPHER) {
-			initCipher(Cipher.ENCRYPT_MODE, alias);
-			return CIPHER.doFinal(buf);
-		}
-	}
-
-	public static byte[] decrypt(byte[] encrypted, String alias) throws Exception {
-		synchronized (CIPHER) {
-			initCipher(Cipher.DECRYPT_MODE, alias);
-			return CIPHER.doFinal(encrypted);
-		}
-	}
-
-	public static void createKeyPair(Context ctx, String alias) throws Exception {
-		Calendar notBefore = Calendar.getInstance();
-		Calendar notAfter = Calendar.getInstance();
-		notAfter.add(Calendar.YEAR, 100);
-		String principalString = String.format("CN=%s, OU=%s", alias, ctx.getPackageName());
-		KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(ctx)
-			.setAlias(alias)
-			.setSubject(new X500Principal(principalString))
-			.setSerialNumber(BigInteger.ONE)
-			.setStartDate(notBefore.getTime())
-			.setEndDate(notAfter.getTime())
-			.setEncryptionRequired()
-			.setKeySize(2048)
-			.setKeyType("RSA")
-			.build();
-		KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance("RSA", KEYSTORE_PROVIDER);
-		kpGenerator.initialize(spec);
-		kpGenerator.generateKeyPair();
-	}
-
-	public static void initCipher(int cipherMode, String alias) throws Exception {
-		KeyStore.PrivateKeyEntry keyEntry = getKeyStoreEntry(alias);
-		if (keyEntry == null) {
-			throw new Exception("Failed to load key for " + alias);
-		}
-		Key key;
-		switch (cipherMode) {
-            case Cipher.ENCRYPT_MODE:
-                key = keyEntry.getCertificate().getPublicKey();
-                break;
-			case  Cipher.DECRYPT_MODE:
-				key = keyEntry.getPrivateKey();
-				break;
-			default : throw new Exception("Invalid cipher mode parameter");
-		}
-		CIPHER.init(cipherMode, key);
-	}
-
-
-	public static boolean isEntryAvailable(String alias) {
-		try {
-			return getKeyStoreEntry(alias) != null;
-		} catch (Exception e) {
-			return false;
-		}
-	}
-
-	private static KeyStore.PrivateKeyEntry getKeyStoreEntry(String alias) throws Exception {
-		KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER);
-		keyStore.load(null, null);
-		return (KeyStore.PrivateKeyEntry) keyStore.getEntry(alias, null);
-	}
 
-	private static Cipher getCipher() {
-		try {
-			return Cipher.getInstance("RSA/ECB/PKCS1Padding");
-		} catch (Exception e) {
-			return null;
-		}
-	}
+public class RSA extends AbstractRSA {
+
+    @TargetApi(Build.VERSION_CODES.M)
+    public boolean isEntryAvailable(String alias) {
+        try {
+            Key privateKey = loadKey(Cipher.DECRYPT_MODE, alias);
+            if (privateKey == null) {
+                return false;
+            }
+            KeyFactory factory = KeyFactory.getInstance(privateKey.getAlgorithm(), KEYSTORE_PROVIDER);
+            KeyInfo keyInfo = factory.getKeySpec(privateKey, KeyInfo.class);
+            return keyInfo.isInsideSecureHardware();
+        } catch (Exception e) {
+            Log.i(TAG, "Checking encryption keys failed.", e);
+            return false;
+        }
+    }
+
+    @Override
+    @TargetApi(Build.VERSION_CODES.M)
+    public AlgorithmParameterSpec getInitParams(Context ctx, String alias, Integer userAuthenticationValidityDuration) {
+        Calendar notAfter = Calendar.getInstance();
+        notAfter.add(Calendar.YEAR, CERT_VALID_YEARS);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            return new KeyGenParameterSpec.Builder(alias, KeyProperties.PURPOSE_DECRYPT | KeyProperties.PURPOSE_ENCRYPT)
+                .setCertificateNotBefore(Calendar.getInstance().getTime())
+                .setCertificateNotAfter(notAfter.getTime())
+                .setAlgorithmParameterSpec(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4))
+                .setUserAuthenticationRequired(true)
+                .setUserAuthenticationValidityDurationSeconds(userAuthenticationValidityDuration)
+                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
+                .setBlockModes(KeyProperties.BLOCK_MODE_ECB)
+                .build();
+        }
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/src/com/crypho/plugins/RSAFactory.java b/src/com/crypho/plugins/RSAFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..08716ae96528fec30ae3d8f0bac614dc3f5d96a3
--- /dev/null
+++ b/src/com/crypho/plugins/RSAFactory.java
@@ -0,0 +1,12 @@
+package com.crypho.plugins;
+
+import android.os.Build;
+
+public class RSAFactory {
+    public static AbstractRSA getRSA() {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+            return new RSALegacy();
+        }
+        return new RSA();
+    }
+}
diff --git a/src/com/crypho/plugins/RSALegacy.java b/src/com/crypho/plugins/RSALegacy.java
new file mode 100644
index 0000000000000000000000000000000000000000..d6f9b5f1de405fa8a54fe9eb565fdcca5a960f93
--- /dev/null
+++ b/src/com/crypho/plugins/RSALegacy.java
@@ -0,0 +1,43 @@
+package com.crypho.plugins;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.security.KeyPairGeneratorSpec;
+
+import java.math.BigInteger;
+import java.security.spec.AlgorithmParameterSpec;
+import java.util.Calendar;
+
+import javax.crypto.Cipher;
+import javax.security.auth.x500.X500Principal;
+
+public class RSALegacy extends AbstractRSA {
+
+    @Override
+    public boolean isEntryAvailable(String alias) {
+        try {
+            return loadKey(Cipher.ENCRYPT_MODE, alias) != null;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    @Override
+    @TargetApi(Build.VERSION_CODES.KITKAT)
+    public AlgorithmParameterSpec getInitParams(Context ctx, String alias, Integer userAuthenticationValidityDuration) throws Exception {
+        Calendar notAfter = Calendar.getInstance();
+        notAfter.add(Calendar.YEAR, CERT_VALID_YEARS);
+
+        return new KeyPairGeneratorSpec.Builder(ctx)
+            .setAlias(alias)
+            .setSubject(new X500Principal(String.format("CN=%s, OU=%s", alias, ctx.getPackageName())))
+            .setSerialNumber(BigInteger.ONE)
+            .setStartDate(Calendar.getInstance().getTime())
+            .setEndDate(notAfter.getTime())
+            .setEncryptionRequired()
+            .setKeySize(2048)
+            .setKeyType(getRSAKey())
+            .build();
+    }
+}
\ No newline at end of file
diff --git a/src/com/crypho/plugins/SecureStorage.java b/src/com/crypho/plugins/SecureStorage.java
index b4aef51f4500e117d367f83569c06570f5cec647..12cd433ea4afb06846e29ea259ef1167cc4c5f09 100644
--- a/src/com/crypho/plugins/SecureStorage.java
+++ b/src/com/crypho/plugins/SecureStorage.java
@@ -1,40 +1,52 @@
 package com.crypho.plugins;
 
-import java.lang.reflect.Method;
-import java.util.Hashtable;
-
-import android.util.Log;
-import android.util.Base64;
-import android.os.Build;
+import android.annotation.TargetApi;
 import android.app.KeyguardManager;
 import android.content.Context;
 import android.content.Intent;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.provider.Settings;
+import android.util.Base64;
+import android.util.Log;
 
 import org.apache.cordova.CallbackContext;
 import org.apache.cordova.CordovaArgs;
 import org.apache.cordova.CordovaPlugin;
+import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
-import org.json.JSONArray;
-import javax.crypto.Cipher;
+
+import java.lang.reflect.Method;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.concurrent.Executor;
 
 public class SecureStorage extends CordovaPlugin {
     private static final String TAG = "SecureStorage";
 
-    private static final boolean SUPPORTS_NATIVE_AES = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
     private static final boolean SUPPORTED = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+    private static final Boolean IS_API_29_AVAILABLE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
+    private static final Integer DEFAULT_AUTHENTICATION_VALIDITY_TIME = 60 * 60 * 24; // Fallback to 24h. Workaround to avoid asking for credentials too "often"
 
     private static final String MSG_NOT_SUPPORTED = "API 19 (Android 4.4 KitKat) is required. This device is running API " + Build.VERSION.SDK_INT;
     private static final String MSG_DEVICE_NOT_SECURE = "Device is not secure";
+    private static final String MSG_KEYS_FAILED = "Generate RSA Encryption Keys failed. ";
 
     private Hashtable<String, SharedPreferencesHandler> SERVICE_STORAGE = new Hashtable<String, SharedPreferencesHandler>();
     private String INIT_SERVICE;
-    private volatile CallbackContext initContext, secureDeviceContext;
-    private volatile boolean initContextRunning = false;
+    private String INIT_PACKAGENAME;
+    private volatile CallbackContext secureDeviceContext, generateKeysContext, unlockCredentialsContext;
+    private volatile boolean generateKeysContextRunning = false;
+
+    private AbstractRSA rsa = RSAFactory.getRSA();
 
     @Override
     public void onResume(boolean multitasking) {
         if (secureDeviceContext != null) {
+
             if (isDeviceSecure()) {
                 secureDeviceContext.success();
             } else {
@@ -43,65 +55,78 @@ public class SecureStorage extends CordovaPlugin {
             secureDeviceContext = null;
         }
 
-        if (initContext != null && !initContextRunning) {
-            cordova.getThreadPool().execute(new Runnable() {
-                public void run() {
-                    initContextRunning = true;
-                    try {
-                        String alias = service2alias(INIT_SERVICE);
-                        if (!RSA.isEntryAvailable(alias)) {
-                            //Solves Issue #96. The RSA key may have been deleted by changing the lock type.
-                            getStorage(INIT_SERVICE).clear();
-                            RSA.createKeyPair(getContext(), alias);
+        if (unlockCredentialsContext != null) {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                cordova.getThreadPool().execute(new Runnable() {
+                    public void run() {
+                        if (unlockCredentialsContext != null) {
+                            String alias = service2alias(INIT_SERVICE);
+                            if (rsa.userAuthenticationRequired(alias)) {
+                                unlockCredentialsContext.error("User not authenticated");
+                            }
+                            unlockCredentialsContext.success();
+                            unlockCredentialsContext = null;
                         }
-                        initSuccess(initContext);
-                    } catch (Exception e) {
-                        Log.e(TAG, "Init failed :", e);
-                        initContext.error(e.getMessage());
-                    } finally {
-                        initContext = null;
-                        initContextRunning = false;
                     }
-                }
-            });
-        }
-    }
-
-    private boolean isDeviceSecure() {
-        KeyguardManager keyguardManager = (KeyguardManager)(getContext().getSystemService(Context.KEYGUARD_SERVICE));
-        try {
-            Method isSecure = null;
-            isSecure = keyguardManager.getClass().getMethod("isDeviceSecure");
-            return ((Boolean) isSecure.invoke(keyguardManager)).booleanValue();
-        } catch (Exception e) {
-            return keyguardManager.isKeyguardSecure();
+                });
+            }
         }
     }
 
     @Override
     public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext) throws JSONException {
-        if(!SUPPORTED){
+        if (!SUPPORTED) {
             Log.w(TAG, MSG_NOT_SUPPORTED);
             callbackContext.error(MSG_NOT_SUPPORTED);
             return false;
         }
         if ("init".equals(action)) {
             String service = args.getString(0);
+            JSONObject options = args.getJSONObject(1);
+
+            String packageName = options.optString("packageName", getContext().getPackageName());
+
+            Context ctx = null;
+
+            // Solves #151. By default, we use our own ApplicationContext
+            // If packageName is provided, we try to get the Context of another Application with that packageName
+            try {
+                ctx = getPackageContext(packageName);
+            } catch (Exception e) {
+                // This will fail if the application with given packageName is not installed
+                // OR if we do not have required permissions and cause a security violation
+                Log.e(TAG, "Init failed :", e);
+                callbackContext.error(e.getMessage());
+            }
+
+            INIT_PACKAGENAME = ctx.getPackageName();
             String alias = service2alias(service);
             INIT_SERVICE = service;
 
-            SharedPreferencesHandler PREFS = new SharedPreferencesHandler(alias + "_SS", getContext());
+            SharedPreferencesHandler PREFS = new SharedPreferencesHandler(alias, ctx);
             SERVICE_STORAGE.put(service, PREFS);
-
             if (!isDeviceSecure()) {
                 Log.e(TAG, MSG_DEVICE_NOT_SECURE);
                 callbackContext.error(MSG_DEVICE_NOT_SECURE);
-            } else if (!RSA.isEntryAvailable(alias)) {
-                initContext = callbackContext;
-                unlockCredentials();
+            } else if (!rsa.encryptionKeysAvailable(alias)) {
+                // Encryption Keys aren't available, proceed to generate them
+                Integer userAuthenticationValidityDuration = options.optInt("userAuthenticationValidityDuration", DEFAULT_AUTHENTICATION_VALIDITY_TIME);
+                generateKeysContext = callbackContext;
+                generateEncryptionKeys(userAuthenticationValidityDuration);
+                if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
+                    unlockCredentialsLegacy();
+                }
+            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && rsa.userAuthenticationRequired(alias)) {
+                // User has to confirm authentication via device credentials.
+                String title = options.optString("unlockCredentialsTitle", null);
+                String description = options.optString("unlockCredentialsDescription", null);
+
+                unlockCredentialsContext = callbackContext;
+                unlockCredentials(title, description);
             } else {
                 initSuccess(callbackContext);
             }
+
             return true;
         }
         if ("set".equals(action)) {
@@ -114,12 +139,12 @@ public class SecureStorage extends CordovaPlugin {
                     try {
                         JSONObject result = AES.encrypt(value.getBytes(), adata.getBytes());
                         byte[] aes_key = Base64.decode(result.getString("key"), Base64.DEFAULT);
-                        byte[] aes_key_enc = RSA.encrypt(aes_key, service2alias(service));
+                        byte[] aes_key_enc = rsa.encrypt(aes_key, service2alias(service));
                         result.put("key", Base64.encodeToString(aes_key_enc, Base64.DEFAULT));
                         getStorage(service).store(key, result.toString());
-                        callbackContext.success();
+                        callbackContext.success(key);
                     } catch (Exception e) {
-                        Log.e(TAG, "Encrypt (RSA/AES) failed :", e);
+                        Log.e(TAG, "Encrypt failed :", e);
                         callbackContext.error(e.getMessage());
                     }
                 }
@@ -133,18 +158,18 @@ public class SecureStorage extends CordovaPlugin {
             if (value != null) {
                 JSONObject json = new JSONObject(value);
                 final byte[] encKey = Base64.decode(json.getString("key"), Base64.DEFAULT);
-                JSONObject data = json.getJSONObject("value");
+                final JSONObject data = json.getJSONObject("value");
                 final byte[] ct = Base64.decode(data.getString("ct"), Base64.DEFAULT);
                 final byte[] iv = Base64.decode(data.getString("iv"), Base64.DEFAULT);
                 final byte[] adata = Base64.decode(data.getString("adata"), Base64.DEFAULT);
                 cordova.getThreadPool().execute(new Runnable() {
                     public void run() {
                         try {
-                            byte[] decryptedKey = RSA.decrypt(encKey, service2alias(service));
-                            String decrypted = new String(AES.decrypt(ct, decryptedKey, iv, adata));
+                            byte[] decryptedKey = rsa.decrypt(encKey, service2alias(service));
+                            String decrypted = new String(AES.decrypt(ct, decryptedKey, iv, adata, data.getString("mode")));
                             callbackContext.success(decrypted);
                         } catch (Exception e) {
-                            Log.e(TAG, "Decrypt (RSA/AES) failed :", e);
+                            Log.e(TAG, "Decrypt failed :", e);
                             callbackContext.error(e.getMessage());
                         }
                     }
@@ -154,70 +179,18 @@ public class SecureStorage extends CordovaPlugin {
             }
             return true;
         }
-        if ("decrypt_rsa".equals(action)) {
-            final String service = args.getString(0);
-            // getArrayBuffer does base64 decoding
-            final byte[] decryptMe = args.getArrayBuffer(1);
-            cordova.getThreadPool().execute(new Runnable() {
-                public void run() {
-                    try {
-                        byte[] decrypted = RSA.decrypt(decryptMe, service2alias(service));
-                        callbackContext.success(new String (decrypted));
-                    } catch (Exception e) {
-                        Log.e(TAG, "Decrypt (RSA) failed :", e);
-                        callbackContext.error(e.getMessage());
-                    }
-                }
-            });
-            return true;
-        }
-        if ("encrypt_rsa".equals(action)) {
-            final String service = args.getString(0);
-            final String encryptMe = args.getString(1);
-            cordova.getThreadPool().execute(new Runnable() {
-                public void run() {
-                    try {
-                        byte[] encrypted = RSA.encrypt(encryptMe.getBytes(), service2alias(service));
-                        callbackContext.success(Base64.encodeToString(encrypted, Base64.DEFAULT));
-                    } catch (Exception e) {
-                        Log.e(TAG, "Encrypt (RSA) failed :", e);
-                        callbackContext.error(e.getMessage());
-                    }
-                }
-            });
-            return true;
-        }
-
         if ("secureDevice".equals(action)) {
+            // Open the Security Settings screen. The app developer should inform the user about
+            // the security requirements of the app and initialize again after the user has changed the screen-lock settings
             secureDeviceContext = callbackContext;
-            unlockCredentials();
+            secureDevice();
             return true;
         }
-        //SharedPreferences interface
         if ("remove".equals(action)) {
             String service = args.getString(0);
             String key = args.getString(1);
             getStorage(service).remove(key);
-            callbackContext.success();
-            return true;
-        }
-        if ("store".equals(action)) {
-            String service = args.getString(0);
-            String key = args.getString(1);
-            String value = args.getString(2);
-            getStorage(service).store(key, value);
-            callbackContext.success();
-            return true;
-        }
-        if ("fetch".equals(action)) {
-            String service = args.getString(0);
-            String key = args.getString(1);
-            String value = getStorage(service).fetch(key);
-            if (value != null) {
-                callbackContext.success(value);
-            } else {
-                callbackContext.error("Key [" + key + "] not found.");
-            }
+            callbackContext.success(key);
             return true;
         }
         if ("keys".equals(action)) {
@@ -234,9 +207,20 @@ public class SecureStorage extends CordovaPlugin {
         return false;
     }
 
+    private boolean isDeviceSecure() {
+        KeyguardManager keyguardManager = (KeyguardManager) (getContext().getSystemService(Context.KEYGUARD_SERVICE));
+        try {
+            Method isSecure = null;
+            isSecure = keyguardManager.getClass().getMethod("isDeviceSecure");
+            return ((Boolean) isSecure.invoke(keyguardManager)).booleanValue();
+        } catch (Exception e) {
+            return keyguardManager.isKeyguardSecure();
+        }
+    }
+
     private String service2alias(String service) {
-        String res = getContext().getPackageName() + "." + service;
-        return  res;
+        String res = INIT_PACKAGENAME + "." + service;
+        return res;
     }
 
     private SharedPreferencesHandler getStorage(String service) {
@@ -244,12 +228,70 @@ public class SecureStorage extends CordovaPlugin {
     }
 
     private void initSuccess(CallbackContext context) {
-        // 0 is falsy in js while 1 is truthy
-        context.success(SUPPORTS_NATIVE_AES ? 1 : 0);
+        context.success();
     }
 
-    private void unlockCredentials() {
+    /**
+     * Create the Confirm Credentials screen.
+     * You can customize the title and description or Android will provide a generic one for you if you leave it null
+     *
+     * @param title
+     * @param description
+     */
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    private void unlockCredentials(final String title, final String description) {
         cordova.getActivity().runOnUiThread(new Runnable() {
+            public void run() {
+                if (IS_API_29_AVAILABLE && isDeviceSecure()) {
+                    // Building a biometric prompt instance with custom title and description.
+                    BiometricPrompt.Builder biometricPromptBuilder = new BiometricPrompt.Builder(getContext());
+                    biometricPromptBuilder.setTitle(title);
+                    biometricPromptBuilder.setDescription(description);
+                    //biometricPromptBuilder.setDeviceCredentialAllowed(true);
+                    BiometricPrompt biometricPrompt = biometricPromptBuilder.build();
+                    CancellationSignal cancellationSignal = new CancellationSignal();
+                    final Executor executor = getExecutor();
+                    // Launching the credential confirmation popup to get biometric validation.
+                    // If biometric is not available will open the other unlock methods.
+                    biometricPrompt.authenticate(cancellationSignal, executor, new BiometricPrompt.AuthenticationCallback() {
+                        @Override
+                        public void onAuthenticationError(int errorCode, CharSequence errString) {
+                            super.onAuthenticationError(errorCode, errString);
+                        }
+
+                        @Override
+                        public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
+                            super.onAuthenticationHelp(helpCode, helpString);
+                        }
+
+                        @Override
+                        public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
+                            super.onAuthenticationSucceeded(result);
+                        }
+
+                        @Override
+                        public void onAuthenticationFailed() {
+                            super.onAuthenticationFailed();
+                        }
+                    });
+                } else {
+                    KeyguardManager keyguardManager = (KeyguardManager) (getContext().getSystemService(Context.KEYGUARD_SERVICE));
+                    Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(title, description);
+                    if (intent != null) {
+                        startActivity(intent);
+                    } else {
+                        Log.e(TAG, "Error creating Confirm Credentials Intent");
+                        unlockCredentialsContext.error("Cant't unlock credentials, error creating Confirm Credentials Intent");
+                    }
+                }
+            }
+        });
+    }
+
+    @TargetApi(Build.VERSION_CODES.KITKAT)
+    private void unlockCredentialsLegacy() {
+        cordova.getActivity().runOnUiThread(new Runnable() {
+            @Override
             public void run() {
                 Intent intent = new Intent("com.android.credentials.UNLOCK");
                 startActivity(intent);
@@ -257,10 +299,84 @@ public class SecureStorage extends CordovaPlugin {
         });
     }
 
+    /**
+     * Generate Encryption Keys in the background.
+     *
+     * @param userAuthenticationValidityDuration User authentication validity duration in seconds
+     */
+    private void generateEncryptionKeys(final Integer userAuthenticationValidityDuration) {
+        if (generateKeysContext != null && !generateKeysContextRunning) {
+            cordova.getThreadPool().execute(new Runnable() {
+                public void run() {
+                    generateKeysContextRunning = true;
+                    try {
+                        String alias = service2alias(INIT_SERVICE);
+                        SharedPreferencesHandler storage = getStorage(INIT_SERVICE);
+                        if(storage.isEmpty()){
+                            //Solves Issue #96. The RSA key may have been deleted by changing the lock type.
+                            getStorage(INIT_SERVICE).clear();
+                            rsa.createKeyPair(getContext(), alias, userAuthenticationValidityDuration);
+                        }
+                        generateKeysContext.success();
+                    } catch (Exception e) {
+                        Log.e(TAG, MSG_KEYS_FAILED, e);
+                        generateKeysContext.error(MSG_KEYS_FAILED + e.getMessage());
+                    } finally {
+                        generateKeysContext = null;
+                        generateKeysContextRunning = false;
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Open Security settings screen.
+     */
+    private void secureDevice() {
+        cordova.getActivity().runOnUiThread(new Runnable() {
+            public void run() {
+                try {
+                    Intent intent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
+                    startActivity(intent);
+                } catch (Exception e) {
+                    Log.e(TAG, "Error opening Security settings to secure device : ", e);
+                    secureDeviceContext.error(e.getMessage());
+                }
+            }
+        });
+    }
+
     private Context getContext() {
         return cordova.getActivity().getApplicationContext();
     }
 
+    /**
+     * Creates a executor with handler to run runnable tasks.
+     */
+    private Executor getExecutor() {
+        return new Executor() {
+            @Override
+            public void execute(Runnable command) {
+                Handler handler = new Handler();
+                handler.post(command);
+            }
+        };
+    }
+
+    private Context getPackageContext(String packageName) throws Exception {
+        Context pkgContext = null;
+
+        Context context = getContext();
+        if (context.getPackageName().equals(packageName)) {
+            pkgContext = context;
+        } else {
+            pkgContext = context.createPackageContext(packageName, 0);
+        }
+
+        return pkgContext;
+    }
+
     private void startActivity(Intent intent) {
         cordova.getActivity().startActivity(intent);
     }
diff --git a/src/com/crypho/plugins/SharedPreferencesHandler.java b/src/com/crypho/plugins/SharedPreferencesHandler.java
index a6f2c02a576ae9572ad6ca3a693603150c518808..fba73b88921ee7b76662353ae0736d784862cbff 100644
--- a/src/com/crypho/plugins/SharedPreferencesHandler.java
+++ b/src/com/crypho/plugins/SharedPreferencesHandler.java
@@ -1,39 +1,49 @@
 package com.crypho.plugins;
 
 import java.util.Set;
+import java.util.HashSet;
+import java.util.Iterator;
 
 import android.content.SharedPreferences;
 import android.content.Context;
 
 public class SharedPreferencesHandler {
 	private SharedPreferences prefs;
-	private static final String MIGRATED_TO_NATIVE_KEY = "_SS_MIGRATED_TO_NATIVE";
-	private static final String MIGRATED_TO_NATIVE_STORAGE_KEY = "_SS_MIGRATED_TO_NATIVE_STORAGE";
 
 	public SharedPreferencesHandler (String prefsName, Context ctx){
-		prefs = ctx.getSharedPreferences(prefsName, 0);
+		prefs = ctx.getSharedPreferences(prefsName  + "_SS", 0);
 	}
 
-    void store(String key, String value){
+    public void store(String key, String value){
         SharedPreferences.Editor editor = prefs.edit();
-        editor.putString(key, value);
+        editor.putString("_SS_" + key, value);
         editor.commit();
     }
 
+    boolean isEmpty() {
+        int numOfPrefs = prefs.getAll().size();
+        return (numOfPrefs == 0);
+    }
+
     String fetch (String key){
-        return prefs.getString(key, null);
+        return prefs.getString("_SS_" + key, null);
     }
 
     void remove (String key){
         SharedPreferences.Editor editor = prefs.edit();
-        editor.remove(key);
+        editor.remove("_SS_" + key);
         editor.commit();
     }
 
     Set keys (){
-    	Set res = prefs.getAll().keySet();
-    	res.remove(MIGRATED_TO_NATIVE_KEY);
-    	res.remove(MIGRATED_TO_NATIVE_STORAGE_KEY);
+        Set res = new HashSet<String>();
+    	Iterator<String> iter = prefs.getAll().keySet().iterator();
+        while (iter.hasNext()) {
+            String key = iter.next();
+            if (key.startsWith("_SS_")  && !key.startsWith("_SS_MIGRATED_")) {
+                res.add(key.replaceFirst("^_SS_", ""));
+            }
+        }
         return res;
     }
 
@@ -42,4 +52,4 @@ public class SharedPreferencesHandler {
         editor.clear();
         editor.commit();
     }
-}
\ No newline at end of file
+}
diff --git a/src/org/apache/cordova/file/AssetFilesystem.java b/src/org/apache/cordova/file/AssetFilesystem.java
new file mode 100644
index 0000000000000000000000000000000000000000..b035c40e6ec3ec0edc0203db933595e645f7ece8
--- /dev/null
+++ b/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");
+    }
+
+}
diff --git a/src/org/apache/cordova/file/ContentFilesystem.java b/src/org/apache/cordova/file/ContentFilesystem.java
new file mode 100644
index 0000000000000000000000000000000000000000..6b983c0896174ae96fdaa307ea1c9185cc8def09
--- /dev/null
+++ b/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;
+	}
+}
diff --git a/src/org/apache/cordova/file/DirectoryManager.java b/src/org/apache/cordova/file/DirectoryManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..07af5ea207407c499a45bf1171b96cc3e6db2b21
--- /dev/null
+++ b/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;
+    }
+}
diff --git a/src/org/apache/cordova/file/EncodingException.java b/src/org/apache/cordova/file/EncodingException.java
new file mode 100644
index 0000000000000000000000000000000000000000..e9e1653bd1facdf88a131e9e2a26c45a483efa57
--- /dev/null
+++ b/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);
+    }
+
+}
diff --git a/src/org/apache/cordova/file/FileExistsException.java b/src/org/apache/cordova/file/FileExistsException.java
new file mode 100644
index 0000000000000000000000000000000000000000..5c4d83dc458a02c0b78457686a58109f02601ea2
--- /dev/null
+++ b/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);
+    }
+
+}
diff --git a/src/org/apache/cordova/file/FileUtils.java b/src/org/apache/cordova/file/FileUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..1d6e61fa3392746b050aca7db49184c56451a086
--- /dev/null
+++ b/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");
+        }
+    }
+}
diff --git a/src/org/apache/cordova/file/Filesystem.java b/src/org/apache/cordova/file/Filesystem.java
new file mode 100644
index 0000000000000000000000000000000000000000..c69d3bdd07a7d3b1a06e67f99f35c6cca883d786
--- /dev/null
+++ b/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;
+        }
+    }
+}
diff --git a/src/org/apache/cordova/file/InvalidModificationException.java b/src/org/apache/cordova/file/InvalidModificationException.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f6bec59cf30ddba141147ca879050dce9437dda
--- /dev/null
+++ b/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);
+    }
+
+}
diff --git a/src/org/apache/cordova/file/LocalFilesystem.java b/src/org/apache/cordova/file/LocalFilesystem.java
new file mode 100644
index 0000000000000000000000000000000000000000..051f99496276254eaa96a05be8dcad4aa7099c2e
--- /dev/null
+++ b/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();
+            }
+        }
+    }
+}
diff --git a/src/org/apache/cordova/file/LocalFilesystemURL.java b/src/org/apache/cordova/file/LocalFilesystemURL.java
new file mode 100644
index 0000000000000000000000000000000000000000..b96b6ee49b8aadf3490736865be8545aa7623012
--- /dev/null
+++ b/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();
+    }
+}
diff --git a/src/org/apache/cordova/file/NoModificationAllowedException.java b/src/org/apache/cordova/file/NoModificationAllowedException.java
new file mode 100644
index 0000000000000000000000000000000000000000..627eafb5626aa9f67bc0ad969cb7b4cae01bdd93
--- /dev/null
+++ b/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);
+    }
+
+}
diff --git a/src/org/apache/cordova/file/PendingRequests.java b/src/org/apache/cordova/file/PendingRequests.java
new file mode 100644
index 0000000000000000000000000000000000000000..4c75f4231866badf8c9b5c67455f5e9d9d6ef8e3
--- /dev/null
+++ b/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;
+        }
+    }
+}
diff --git a/src/org/apache/cordova/file/TypeMismatchException.java b/src/org/apache/cordova/file/TypeMismatchException.java
new file mode 100644
index 0000000000000000000000000000000000000000..1315f9a9e5b8d209fed973fda0334fc49d8c4a72
--- /dev/null
+++ b/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);
+    }
+
+}