arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
openEventsPlatformApp/plugins/cordova-plugin-file/src/android/FileUtils.java
plugins/cordova-plugin-file/src/android/FileUtils.javaRaw
   1/*
   2       Licensed to the Apache Software Foundation (ASF) under one
   3       or more contributor license agreements.  See the NOTICE file
   4       distributed with this work for additional information
   5       regarding copyright ownership.  The ASF licenses this file
   6       to you under the Apache License, Version 2.0 (the
   7       "License"); you may not use this file except in compliance
   8       with the License.  You may obtain a copy of the License at
   9
  10         http://www.apache.org/licenses/LICENSE-2.0
  11
  12       Unless required by applicable law or agreed to in writing,
  13       software distributed under the License is distributed on an
  14       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15       KIND, either express or implied.  See the License for the
  16       specific language governing permissions and limitations
  17       under the License.
  18 */
  19package org.apache.cordova.file;
  20
  21import android.Manifest;
  22import android.app.Activity;
  23import android.content.Context;
  24import android.content.pm.PackageManager;
  25import android.net.Uri;
  26import android.os.Build;
  27import android.os.Environment;
  28import android.util.Base64;
  29
  30import org.apache.cordova.CallbackContext;
  31import org.apache.cordova.CordovaInterface;
  32import org.apache.cordova.CordovaPlugin;
  33import org.apache.cordova.CordovaWebView;
  34import org.apache.cordova.LOG;
  35import org.apache.cordova.PermissionHelper;
  36import org.apache.cordova.PluginResult;
  37
  38import org.json.JSONArray;
  39import org.json.JSONException;
  40import org.json.JSONObject;
  41
  42import java.io.ByteArrayOutputStream;
  43import java.io.File;
  44import java.io.FileNotFoundException;
  45import java.io.IOException;
  46import java.io.InputStream;
  47import java.net.MalformedURLException;
  48import java.security.Permission;
  49import java.util.ArrayList;
  50import java.util.HashMap;
  51import java.util.HashSet;
  52
  53/**
  54 * This class provides file and directory services to JavaScript.
  55 */
  56public class FileUtils extends CordovaPlugin {
  57    private static final String LOG_TAG = "FileUtils";
  58
  59    public static int NOT_FOUND_ERR = 1;
  60    public static int SECURITY_ERR = 2;
  61    public static int ABORT_ERR = 3;
  62
  63    public static int NOT_READABLE_ERR = 4;
  64    public static int ENCODING_ERR = 5;
  65    public static int NO_MODIFICATION_ALLOWED_ERR = 6;
  66    public static int INVALID_STATE_ERR = 7;
  67    public static int SYNTAX_ERR = 8;
  68    public static int INVALID_MODIFICATION_ERR = 9;
  69    public static int QUOTA_EXCEEDED_ERR = 10;
  70    public static int TYPE_MISMATCH_ERR = 11;
  71    public static int PATH_EXISTS_ERR = 12;
  72
  73    /*
  74     * Permission callback codes
  75     */
  76
  77    public static final int ACTION_GET_FILE = 0;
  78    public static final int ACTION_WRITE = 1;
  79    public static final int ACTION_GET_DIRECTORY = 2;
  80
  81    public static final int WRITE = 3;
  82    public static final int READ = 4;
  83
  84    public static int UNKNOWN_ERR = 1000;
  85
  86    private boolean configured = false;
  87
  88    private PendingRequests pendingRequests;
  89
  90
  91
  92    /*
  93     * We need both read and write when accessing the storage, I think.
  94     */
  95
  96    private String [] permissions = {
  97            Manifest.permission.READ_EXTERNAL_STORAGE,
  98            Manifest.permission.WRITE_EXTERNAL_STORAGE };
  99
 100    // This field exists only to support getEntry, below, which has been deprecated
 101    private static FileUtils filePlugin;
 102
 103    private interface FileOp {
 104        void run(JSONArray args) throws Exception;
 105    }
 106
 107    private ArrayList<Filesystem> filesystems;
 108
 109    public void registerFilesystem(Filesystem fs) {
 110    	if (fs != null && filesystemForName(fs.name)== null) {
 111    		this.filesystems.add(fs);
 112    	}
 113    }
 114
 115    private Filesystem filesystemForName(String name) {
 116    	for (Filesystem fs:filesystems) {
 117    		if (fs != null && fs.name != null && fs.name.equals(name)) {
 118    			return fs;
 119    		}
 120    	}
 121    	return null;
 122    }
 123
 124    protected String[] getExtraFileSystemsPreference(Activity activity) {
 125        String fileSystemsStr = preferences.getString("androidextrafilesystems", "files,files-external,documents,sdcard,cache,cache-external,assets,root");
 126        return fileSystemsStr.split(",");
 127    }
 128
 129    protected void registerExtraFileSystems(String[] filesystems, HashMap<String, String> availableFileSystems) {
 130        HashSet<String> installedFileSystems = new HashSet<String>();
 131
 132        /* Register filesystems in order */
 133        for (String fsName : filesystems) {
 134            if (!installedFileSystems.contains(fsName)) {
 135                String fsRoot = availableFileSystems.get(fsName);
 136                if (fsRoot != null) {
 137                    File newRoot = new File(fsRoot);
 138                    if (newRoot.mkdirs() || newRoot.isDirectory()) {
 139                        registerFilesystem(new LocalFilesystem(fsName, webView.getContext(), webView.getResourceApi(), newRoot));
 140                        installedFileSystems.add(fsName);
 141                    } else {
 142                       LOG.d(LOG_TAG, "Unable to create root dir for filesystem \"" + fsName + "\", skipping");
 143                    }
 144                } else {
 145                    LOG.d(LOG_TAG, "Unrecognized extra filesystem identifier: " + fsName);
 146                }
 147            }
 148        }
 149    }
 150
 151    protected HashMap<String, String> getAvailableFileSystems(Activity activity) {
 152        Context context = activity.getApplicationContext();
 153        HashMap<String, String> availableFileSystems = new HashMap<String,String>();
 154
 155        availableFileSystems.put("files", context.getFilesDir().getAbsolutePath());
 156        availableFileSystems.put("documents", new File(context.getFilesDir(), "Documents").getAbsolutePath());
 157        availableFileSystems.put("cache", context.getCacheDir().getAbsolutePath());
 158        availableFileSystems.put("root", "/");
 159        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
 160          try {
 161            availableFileSystems.put("files-external", context.getExternalFilesDir(null).getAbsolutePath());
 162            availableFileSystems.put("sdcard", Environment.getExternalStorageDirectory().getAbsolutePath());
 163            availableFileSystems.put("cache-external", context.getExternalCacheDir().getAbsolutePath());
 164          }
 165          catch(NullPointerException e) {
 166              LOG.d(LOG_TAG, "External storage unavailable, check to see if USB Mass Storage Mode is on");
 167          }
 168        }
 169
 170        return availableFileSystems;
 171    }
 172
 173    @Override
 174    public void initialize(CordovaInterface cordova, CordovaWebView webView) {
 175    	super.initialize(cordova, webView);
 176    	this.filesystems = new ArrayList<Filesystem>();
 177        this.pendingRequests = new PendingRequests();
 178
 179    	String tempRoot = null;
 180    	String persistentRoot = null;
 181
 182    	Activity activity = cordova.getActivity();
 183    	String packageName = activity.getPackageName();
 184
 185        String location = preferences.getString("androidpersistentfilelocation", "internal");
 186
 187    	tempRoot = activity.getCacheDir().getAbsolutePath();
 188    	if ("internal".equalsIgnoreCase(location)) {
 189    		persistentRoot = activity.getFilesDir().getAbsolutePath() + "/files/";
 190    		this.configured = true;
 191    	} else if ("compatibility".equalsIgnoreCase(location)) {
 192    		/*
 193    		 *  Fall-back to compatibility mode -- this is the logic implemented in
 194    		 *  earlier versions of this plugin, and should be maintained here so
 195    		 *  that apps which were originally deployed with older versions of the
 196    		 *  plugin can continue to provide access to files stored under those
 197    		 *  versions.
 198    		 */
 199    		if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
 200    			persistentRoot = Environment.getExternalStorageDirectory().getAbsolutePath();
 201    			tempRoot = Environment.getExternalStorageDirectory().getAbsolutePath() +
 202    					"/Android/data/" + packageName + "/cache/";
 203    		} else {
 204    			persistentRoot = "/data/data/" + packageName;
 205    		}
 206    		this.configured = true;
 207    	}
 208
 209    	if (this.configured) {
 210			// Create the directories if they don't exist.
 211			File tmpRootFile = new File(tempRoot);
 212            File persistentRootFile = new File(persistentRoot);
 213            tmpRootFile.mkdirs();
 214            persistentRootFile.mkdirs();
 215
 216    		// Register initial filesystems
 217    		// Note: The temporary and persistent filesystems need to be the first two
 218    		// registered, so that they will match window.TEMPORARY and window.PERSISTENT,
 219    		// per spec.
 220    		this.registerFilesystem(new LocalFilesystem("temporary", webView.getContext(), webView.getResourceApi(), tmpRootFile));
 221    		this.registerFilesystem(new LocalFilesystem("persistent", webView.getContext(), webView.getResourceApi(), persistentRootFile));
 222    		this.registerFilesystem(new ContentFilesystem(webView.getContext(), webView.getResourceApi()));
 223            this.registerFilesystem(new AssetFilesystem(webView.getContext().getAssets(), webView.getResourceApi()));
 224
 225            registerExtraFileSystems(getExtraFileSystemsPreference(activity), getAvailableFileSystems(activity));
 226
 227    		// Initialize static plugin reference for deprecated getEntry method
 228    		if (filePlugin == null) {
 229    			FileUtils.filePlugin = this;
 230    		}
 231    	} else {
 232    		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)");
 233    		activity.finish();
 234    	}
 235    }
 236
 237    public static FileUtils getFilePlugin() {
 238		return filePlugin;
 239	}
 240
 241	private Filesystem filesystemForURL(LocalFilesystemURL localURL) {
 242    	if (localURL == null) return null;
 243    	return filesystemForName(localURL.fsName);
 244    }
 245
 246    @Override
 247    public Uri remapUri(Uri uri) {
 248        // Remap only cdvfile: URLs (not content:).
 249        if (!LocalFilesystemURL.FILESYSTEM_PROTOCOL.equals(uri.getScheme())) {
 250            return null;
 251        }
 252        try {
 253        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(uri);
 254        	Filesystem fs = this.filesystemForURL(inputURL);
 255        	if (fs == null) {
 256        		return null;
 257        	}
 258        	String path = fs.filesystemPathForURL(inputURL);
 259        	if (path != null) {
 260        		return Uri.parse("file://" + fs.filesystemPathForURL(inputURL));
 261        	}
 262        	return null;
 263        } catch (IllegalArgumentException e) {
 264        	return null;
 265        }
 266    }
 267
 268    public boolean execute(String action, final String rawArgs, final CallbackContext callbackContext) {
 269        if (!configured) {
 270            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"));
 271            return true;
 272        }
 273        if (action.equals("testSaveLocationExists")) {
 274            threadhelper(new FileOp() {
 275                public void run(JSONArray args) {
 276                    boolean b = DirectoryManager.testSaveLocationExists();
 277                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
 278                }
 279            }, rawArgs, callbackContext);
 280        }
 281        else if (action.equals("getFreeDiskSpace")) {
 282            threadhelper( new FileOp( ){
 283                public void run(JSONArray args) {
 284                    // The getFreeDiskSpace plugin API is not documented, but some apps call it anyway via exec().
 285                    // For compatibility it always returns free space in the primary external storage, and
 286                    // does NOT fallback to internal store if external storage is unavailable.
 287                    long l = DirectoryManager.getFreeExternalStorageSpace();
 288                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, l));
 289                }
 290            }, rawArgs, callbackContext);
 291        }
 292        else if (action.equals("testFileExists")) {
 293            threadhelper( new FileOp( ){
 294                public void run(JSONArray args) throws JSONException {
 295                    String fname=args.getString(0);
 296                    boolean b = DirectoryManager.testFileExists(fname);
 297                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
 298                }
 299            }, rawArgs, callbackContext);
 300        }
 301        else if (action.equals("testDirectoryExists")) {
 302            threadhelper( new FileOp( ){
 303                public void run(JSONArray args) throws JSONException {
 304                    String fname=args.getString(0);
 305                    boolean b = DirectoryManager.testFileExists(fname);
 306                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, b));
 307                }
 308            }, rawArgs, callbackContext);
 309        }
 310        else if (action.equals("readAsText")) {
 311            threadhelper( new FileOp( ){
 312                public void run(JSONArray args) throws JSONException, MalformedURLException {
 313                    String encoding = args.getString(1);
 314                    int start = args.getInt(2);
 315                    int end = args.getInt(3);
 316                    String fname=args.getString(0);
 317                    readFileAs(fname, start, end, callbackContext, encoding, PluginResult.MESSAGE_TYPE_STRING);
 318                }
 319            }, rawArgs, callbackContext);
 320        }
 321        else if (action.equals("readAsDataURL")) {
 322            threadhelper( new FileOp( ){
 323                public void run(JSONArray args) throws JSONException, MalformedURLException  {
 324                    int start = args.getInt(1);
 325                    int end = args.getInt(2);
 326                    String fname=args.getString(0);
 327                    readFileAs(fname, start, end, callbackContext, null, -1);
 328                }
 329            }, rawArgs, callbackContext);
 330        }
 331        else if (action.equals("readAsArrayBuffer")) {
 332            threadhelper( new FileOp( ){
 333                public void run(JSONArray args) throws JSONException, MalformedURLException  {
 334                    int start = args.getInt(1);
 335                    int end = args.getInt(2);
 336                    String fname=args.getString(0);
 337                    readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_ARRAYBUFFER);
 338                }
 339            }, rawArgs, callbackContext);
 340        }
 341        else if (action.equals("readAsBinaryString")) {
 342            threadhelper( new FileOp( ){
 343                public void run(JSONArray args) throws JSONException, MalformedURLException  {
 344                    int start = args.getInt(1);
 345                    int end = args.getInt(2);
 346                    String fname=args.getString(0);
 347                    readFileAs(fname, start, end, callbackContext, null, PluginResult.MESSAGE_TYPE_BINARYSTRING);
 348                }
 349            }, rawArgs, callbackContext);
 350        }
 351        else if (action.equals("write")) {
 352            threadhelper( new FileOp( ){
 353                public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
 354                    String fname=args.getString(0);
 355                    String nativeURL = resolveLocalFileSystemURI(fname).getString("nativeURL");
 356                    String data=args.getString(1);
 357                    int offset=args.getInt(2);
 358                    Boolean isBinary=args.getBoolean(3);
 359
 360                    if(needPermission(nativeURL, WRITE)) {
 361                        getWritePermission(rawArgs, ACTION_WRITE, callbackContext);
 362                    }
 363                    else {
 364                        long fileSize = write(fname, data, offset, isBinary);
 365                        callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
 366                    }
 367
 368                }
 369            }, rawArgs, callbackContext);
 370        }
 371        else if (action.equals("truncate")) {
 372            threadhelper( new FileOp( ){
 373                public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
 374                    String fname=args.getString(0);
 375                    int offset=args.getInt(1);
 376                    long fileSize = truncateFile(fname, offset);
 377                    callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
 378                }
 379            }, rawArgs, callbackContext);
 380        }
 381        else if (action.equals("requestAllFileSystems")) {
 382            threadhelper( new FileOp( ){
 383                public void run(JSONArray args) throws IOException, JSONException {
 384                    callbackContext.success(requestAllFileSystems());
 385                }
 386            }, rawArgs, callbackContext);
 387        } else if (action.equals("requestAllPaths")) {
 388            cordova.getThreadPool().execute(
 389                    new Runnable() {
 390                        public void run() {
 391                        	try {
 392					callbackContext.success(requestAllPaths());
 393				} catch (JSONException e) {
 394					// TODO Auto-generated catch block
 395					e.printStackTrace();
 396				}
 397                        }
 398                    }
 399            );
 400        } else if (action.equals("requestFileSystem")) {
 401            threadhelper( new FileOp( ){
 402                public void run(JSONArray args) throws JSONException {
 403                    int fstype = args.getInt(0);
 404                    long requiredSize = args.optLong(1);
 405                    requestFileSystem(fstype, requiredSize, callbackContext);
 406                }
 407            }, rawArgs, callbackContext);
 408        }
 409        else if (action.equals("resolveLocalFileSystemURI")) {
 410            threadhelper( new FileOp( ){
 411                public void run(JSONArray args) throws IOException, JSONException {
 412                    String fname=args.getString(0);
 413                    JSONObject obj = resolveLocalFileSystemURI(fname);
 414                    callbackContext.success(obj);
 415                }
 416            }, rawArgs, callbackContext);
 417        }
 418        else if (action.equals("getFileMetadata")) {
 419            threadhelper( new FileOp( ){
 420                public void run(JSONArray args) throws FileNotFoundException, JSONException, MalformedURLException {
 421                    String fname=args.getString(0);
 422                    JSONObject obj = getFileMetadata(fname);
 423                    callbackContext.success(obj);
 424                }
 425            }, rawArgs, callbackContext);
 426        }
 427        else if (action.equals("getParent")) {
 428            threadhelper( new FileOp( ){
 429                public void run(JSONArray args) throws JSONException, IOException {
 430                    String fname=args.getString(0);
 431                    JSONObject obj = getParent(fname);
 432                    callbackContext.success(obj);
 433                }
 434            }, rawArgs, callbackContext);
 435        }
 436        else if (action.equals("getDirectory")) {
 437            threadhelper( new FileOp( ){
 438                public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
 439                    String dirname = args.getString(0);
 440                    String path = args.getString(1);
 441                    String nativeURL = resolveLocalFileSystemURI(dirname).getString("nativeURL");
 442                    boolean containsCreate = (args.isNull(2)) ? false : args.getJSONObject(2).optBoolean("create", false);
 443
 444                    if(containsCreate && needPermission(nativeURL, WRITE)) {
 445                        getWritePermission(rawArgs, ACTION_GET_DIRECTORY, callbackContext);
 446                    }
 447                    else if(!containsCreate && needPermission(nativeURL, READ)) {
 448                        getReadPermission(rawArgs, ACTION_GET_DIRECTORY, callbackContext);
 449                    }
 450                    else {
 451                        JSONObject obj = getFile(dirname, path, args.optJSONObject(2), true);
 452                        callbackContext.success(obj);
 453                    }
 454                }
 455            }, rawArgs, callbackContext);
 456        }
 457        else if (action.equals("getFile")) {
 458            threadhelper( new FileOp( ){
 459                public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
 460                    String dirname = args.getString(0);
 461                    String path = args.getString(1);
 462                    String nativeURL = resolveLocalFileSystemURI(dirname).getString("nativeURL");
 463                    boolean containsCreate = (args.isNull(2)) ? false : args.getJSONObject(2).optBoolean("create", false);
 464
 465                    if(containsCreate && needPermission(nativeURL, WRITE)) {
 466                        getWritePermission(rawArgs, ACTION_GET_FILE, callbackContext);
 467                    }
 468                    else if(!containsCreate && needPermission(nativeURL, READ)) {
 469                        getReadPermission(rawArgs, ACTION_GET_FILE, callbackContext);
 470                    }
 471                    else {
 472                        JSONObject obj = getFile(dirname, path, args.optJSONObject(2), false);
 473                        callbackContext.success(obj);
 474                    }
 475                }
 476            }, rawArgs, callbackContext);
 477        }
 478        else if (action.equals("remove")) {
 479            threadhelper( new FileOp( ){
 480                public void run(JSONArray args) throws JSONException, NoModificationAllowedException, InvalidModificationException, MalformedURLException {
 481                    String fname=args.getString(0);
 482                    boolean success = remove(fname);
 483                    if (success) {
 484                        callbackContext.success();
 485                    } else {
 486                        callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
 487                    }
 488                }
 489            }, rawArgs, callbackContext);
 490        }
 491        else if (action.equals("removeRecursively")) {
 492            threadhelper( new FileOp( ){
 493                public void run(JSONArray args) throws JSONException, FileExistsException, MalformedURLException, NoModificationAllowedException {
 494                    String fname=args.getString(0);
 495                    boolean success = removeRecursively(fname);
 496                    if (success) {
 497                        callbackContext.success();
 498                    } else {
 499                        callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
 500                    }
 501                }
 502            }, rawArgs, callbackContext);
 503        }
 504        else if (action.equals("moveTo")) {
 505            threadhelper( new FileOp( ){
 506                public void run(JSONArray args) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
 507                    String fname=args.getString(0);
 508                    String newParent=args.getString(1);
 509                    String newName=args.getString(2);
 510                    JSONObject entry = transferTo(fname, newParent, newName, true);
 511                    callbackContext.success(entry);
 512                }
 513            }, rawArgs, callbackContext);
 514        }
 515        else if (action.equals("copyTo")) {
 516            threadhelper( new FileOp( ){
 517                public void run(JSONArray args) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
 518                    String fname=args.getString(0);
 519                    String newParent=args.getString(1);
 520                    String newName=args.getString(2);
 521                    JSONObject entry = transferTo(fname, newParent, newName, false);
 522                    callbackContext.success(entry);
 523                }
 524            }, rawArgs, callbackContext);
 525        }
 526        else if (action.equals("readEntries")) {
 527            threadhelper( new FileOp( ){
 528                public void run(JSONArray args) throws FileNotFoundException, JSONException, MalformedURLException {
 529                    String fname=args.getString(0);
 530                    JSONArray entries = readEntries(fname);
 531                    callbackContext.success(entries);
 532                }
 533            }, rawArgs, callbackContext);
 534        }
 535        else if (action.equals("_getLocalFilesystemPath")) {
 536            // Internal method for testing: Get the on-disk location of a local filesystem url.
 537            // [Currently used for testing file-transfer]
 538            threadhelper( new FileOp( ){
 539                public void run(JSONArray args) throws FileNotFoundException, JSONException, MalformedURLException {
 540                    String localURLstr = args.getString(0);
 541                    String fname = filesystemPathForURL(localURLstr);
 542                    callbackContext.success(fname);
 543                }
 544            }, rawArgs, callbackContext);
 545        }
 546        else {
 547            return false;
 548        }
 549        return true;
 550    }
 551
 552    private void getReadPermission(String rawArgs, int action, CallbackContext callbackContext) {
 553        int requestCode = pendingRequests.createRequest(rawArgs, action, callbackContext);
 554        PermissionHelper.requestPermission(this, requestCode, Manifest.permission.READ_EXTERNAL_STORAGE);
 555    }
 556
 557    private void getWritePermission(String rawArgs, int action, CallbackContext callbackContext) {
 558        int requestCode = pendingRequests.createRequest(rawArgs, action, callbackContext);
 559        PermissionHelper.requestPermission(this, requestCode, Manifest.permission.WRITE_EXTERNAL_STORAGE);
 560    }
 561
 562    private boolean hasReadPermission() {
 563        return PermissionHelper.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE);
 564    }
 565
 566    private boolean hasWritePermission() {
 567        return PermissionHelper.hasPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
 568    }
 569
 570    private boolean needPermission(String nativeURL, int permissionType) throws JSONException {
 571        JSONObject j = requestAllPaths();
 572        ArrayList<String> allowedStorageDirectories = new ArrayList<String>();
 573        allowedStorageDirectories.add(j.getString("applicationStorageDirectory"));
 574        if(j.has("externalApplicationStorageDirectory")) {
 575            allowedStorageDirectories.add(j.getString("externalApplicationStorageDirectory"));
 576        }
 577
 578        if(permissionType == READ && hasReadPermission()) {
 579            return false;
 580        }
 581        else if(permissionType == WRITE && hasWritePermission()) {
 582            return false;
 583        }
 584
 585        // Permission required if the native url lies outside the allowed storage directories
 586        for(String directory : allowedStorageDirectories) {
 587            if(nativeURL.startsWith(directory)) {
 588                return false;
 589            }
 590        }
 591        return true;
 592    }
 593
 594
 595    public LocalFilesystemURL resolveNativeUri(Uri nativeUri) {
 596        LocalFilesystemURL localURL = null;
 597
 598        // Try all installed filesystems. Return the best matching URL
 599        // (determined by the shortest resulting URL)
 600        for (Filesystem fs : filesystems) {
 601            LocalFilesystemURL url = fs.toLocalUri(nativeUri);
 602            if (url != null) {
 603                // A shorter fullPath implies that the filesystem is a better
 604                // match for the local path than the previous best.
 605                if (localURL == null || (url.uri.toString().length() < localURL.toString().length())) {
 606                    localURL = url;
 607                }
 608            }
 609        }
 610        return localURL;
 611    }
 612
 613    /*
 614     * These two native-only methods can be used by other plugins to translate between
 615     * device file system paths and URLs. By design, there is no direct JavaScript
 616     * interface to these methods.
 617     */
 618
 619    public String filesystemPathForURL(String localURLstr) throws MalformedURLException {
 620        try {
 621            LocalFilesystemURL inputURL = LocalFilesystemURL.parse(localURLstr);
 622            Filesystem fs = this.filesystemForURL(inputURL);
 623            if (fs == null) {
 624                throw new MalformedURLException("No installed handlers for this URL");
 625            }
 626            return fs.filesystemPathForURL(inputURL);
 627        } catch (IllegalArgumentException e) {
 628            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
 629            mue.initCause(e);
 630        	throw mue;
 631        }
 632    }
 633
 634    public LocalFilesystemURL filesystemURLforLocalPath(String localPath) {
 635        LocalFilesystemURL localURL = null;
 636        int shortestFullPath = 0;
 637
 638        // Try all installed filesystems. Return the best matching URL
 639        // (determined by the shortest resulting URL)
 640        for (Filesystem fs: filesystems) {
 641            LocalFilesystemURL url = fs.URLforFilesystemPath(localPath);
 642            if (url != null) {
 643                // A shorter fullPath implies that the filesystem is a better
 644                // match for the local path than the previous best.
 645                if (localURL == null || (url.path.length() < shortestFullPath)) {
 646                    localURL = url;
 647                    shortestFullPath = url.path.length();
 648                }
 649            }
 650        }
 651        return localURL;
 652    }
 653
 654
 655	/* helper to execute functions async and handle the result codes
 656     *
 657     */
 658    private void threadhelper(final FileOp f, final String rawArgs, final CallbackContext callbackContext){
 659        cordova.getThreadPool().execute(new Runnable() {
 660            public void run() {
 661                try {
 662                    JSONArray args = new JSONArray(rawArgs);
 663                    f.run(args);
 664                } catch ( Exception e) {
 665                    if( e instanceof EncodingException){
 666                        callbackContext.error(FileUtils.ENCODING_ERR);
 667                    } else if(e instanceof FileNotFoundException) {
 668                        callbackContext.error(FileUtils.NOT_FOUND_ERR);
 669                    } else if(e instanceof FileExistsException) {
 670                        callbackContext.error(FileUtils.PATH_EXISTS_ERR);
 671                    } else if(e instanceof NoModificationAllowedException ) {
 672                        callbackContext.error(FileUtils.NO_MODIFICATION_ALLOWED_ERR);
 673                    } else if(e instanceof InvalidModificationException ) {
 674                        callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
 675                    } else if(e instanceof MalformedURLException ) {
 676                        callbackContext.error(FileUtils.ENCODING_ERR);
 677                    } else if(e instanceof IOException ) {
 678                        callbackContext.error(FileUtils.INVALID_MODIFICATION_ERR);
 679                    } else if(e instanceof EncodingException ) {
 680                        callbackContext.error(FileUtils.ENCODING_ERR);
 681                    } else if(e instanceof TypeMismatchException ) {
 682                        callbackContext.error(FileUtils.TYPE_MISMATCH_ERR);
 683                    } else if(e instanceof JSONException ) {
 684                        callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION));
 685                    } else if (e instanceof SecurityException) {
 686                        callbackContext.error(FileUtils.SECURITY_ERR);
 687                    } else {
 688                        e.printStackTrace();
 689                    	callbackContext.error(FileUtils.UNKNOWN_ERR);
 690                    }
 691                }
 692            }
 693        });
 694    }
 695
 696    /**
 697     * Allows the user to look up the Entry for a file or directory referred to by a local URI.
 698     *
 699     * @param uriString of the file/directory to look up
 700     * @return a JSONObject representing a Entry from the filesystem
 701     * @throws MalformedURLException if the url is not valid
 702     * @throws FileNotFoundException if the file does not exist
 703     * @throws IOException if the user can't read the file
 704     * @throws JSONException
 705     */
 706    private JSONObject resolveLocalFileSystemURI(String uriString) throws IOException, JSONException {
 707        if (uriString == null) {
 708            throw new MalformedURLException("Unrecognized filesystem URL");
 709        }
 710        Uri uri = Uri.parse(uriString);
 711        boolean isNativeUri = false;
 712
 713        LocalFilesystemURL inputURL = LocalFilesystemURL.parse(uri);
 714        if (inputURL == null) {
 715            /* Check for file://, content:// urls */
 716            inputURL = resolveNativeUri(uri);
 717            isNativeUri = true;
 718        }
 719
 720        try {
 721            Filesystem fs = this.filesystemForURL(inputURL);
 722            if (fs == null) {
 723                throw new MalformedURLException("No installed handlers for this URL");
 724            }
 725            if (fs.exists(inputURL)) {
 726                if (!isNativeUri) {
 727                    // If not already resolved as native URI, resolve to a native URI and back to
 728                    // fix the terminating slash based on whether the entry is a directory or file.
 729                    inputURL = fs.toLocalUri(fs.toNativeUri(inputURL));
 730                }
 731
 732                return fs.getEntryForLocalURL(inputURL);
 733            }
 734        } catch (IllegalArgumentException e) {
 735            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
 736            mue.initCause(e);
 737        	throw mue;
 738        }
 739        throw new FileNotFoundException();
 740    }
 741
 742    /**
 743     * Read the list of files from this directory.
 744     *
 745     * @return a JSONArray containing JSONObjects that represent Entry objects.
 746     * @throws FileNotFoundException if the directory is not found.
 747     * @throws JSONException
 748     * @throws MalformedURLException
 749     */
 750    private JSONArray readEntries(String baseURLstr) throws FileNotFoundException, JSONException, MalformedURLException {
 751        try {
 752        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
 753        	Filesystem fs = this.filesystemForURL(inputURL);
 754        	if (fs == null) {
 755        		throw new MalformedURLException("No installed handlers for this URL");
 756        	}
 757        	return fs.readEntriesAtLocalURL(inputURL);
 758
 759        } catch (IllegalArgumentException e) {
 760            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
 761            mue.initCause(e);
 762        	throw mue;
 763        }
 764    }
 765
 766    /**
 767     * A setup method that handles the move/copy of files/directories
 768     *
 769     * @param newName for the file directory to be called, if null use existing file name
 770     * @param move if false do a copy, if true do a move
 771     * @return a Entry object
 772     * @throws NoModificationAllowedException
 773     * @throws IOException
 774     * @throws InvalidModificationException
 775     * @throws EncodingException
 776     * @throws JSONException
 777     * @throws FileExistsException
 778     */
 779    private JSONObject transferTo(String srcURLstr, String destURLstr, String newName, boolean move) throws JSONException, NoModificationAllowedException, IOException, InvalidModificationException, EncodingException, FileExistsException {
 780        if (srcURLstr == null || destURLstr == null) {
 781            // either no source or no destination provided
 782        	throw new FileNotFoundException();
 783        }
 784
 785        LocalFilesystemURL srcURL = LocalFilesystemURL.parse(srcURLstr);
 786        LocalFilesystemURL destURL = LocalFilesystemURL.parse(destURLstr);
 787
 788        Filesystem srcFs = this.filesystemForURL(srcURL);
 789        Filesystem destFs = this.filesystemForURL(destURL);
 790
 791        // Check for invalid file name
 792        if (newName != null && newName.contains(":")) {
 793            throw new EncodingException("Bad file name");
 794        }
 795
 796        return destFs.copyFileToURL(destURL, newName, srcFs, srcURL, move);
 797    }
 798
 799    /**
 800     * Deletes a directory and all of its contents, if any. In the event of an error
 801     * [e.g. trying to delete a directory that contains a file that cannot be removed],
 802     * some of the contents of the directory may be deleted.
 803     * It is an error to attempt to delete the root directory of a filesystem.
 804     *
 805     * @return a boolean representing success of failure
 806     * @throws FileExistsException
 807     * @throws NoModificationAllowedException
 808     * @throws MalformedURLException
 809     */
 810    private boolean removeRecursively(String baseURLstr) throws FileExistsException, NoModificationAllowedException, MalformedURLException {
 811        try {
 812        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
 813        	// You can't delete the root directory.
 814        	if ("".equals(inputURL.path) || "/".equals(inputURL.path)) {
 815        		throw new NoModificationAllowedException("You can't delete the root directory");
 816        	}
 817
 818        	Filesystem fs = this.filesystemForURL(inputURL);
 819        	if (fs == null) {
 820        		throw new MalformedURLException("No installed handlers for this URL");
 821        	}
 822        	return fs.recursiveRemoveFileAtLocalURL(inputURL);
 823
 824        } catch (IllegalArgumentException e) {
 825            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
 826            mue.initCause(e);
 827        	throw mue;
 828        }
 829    }
 830
 831
 832    /**
 833     * Deletes a file or directory. It is an error to attempt to delete a directory that is not empty.
 834     * It is an error to attempt to delete the root directory of a filesystem.
 835     *
 836     * @return a boolean representing success of failure
 837     * @throws NoModificationAllowedException
 838     * @throws InvalidModificationException
 839     * @throws MalformedURLException
 840     */
 841    private boolean remove(String baseURLstr) throws NoModificationAllowedException, InvalidModificationException, MalformedURLException {
 842        try {
 843        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
 844        	// You can't delete the root directory.
 845        	if ("".equals(inputURL.path) || "/".equals(inputURL.path)) {
 846
 847        		throw new NoModificationAllowedException("You can't delete the root directory");
 848        	}
 849
 850        	Filesystem fs = this.filesystemForURL(inputURL);
 851        	if (fs == null) {
 852        		throw new MalformedURLException("No installed handlers for this URL");
 853        	}
 854        	return fs.removeFileAtLocalURL(inputURL);
 855
 856        } catch (IllegalArgumentException e) {
 857            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
 858            mue.initCause(e);
 859        	throw mue;
 860        }
 861    }
 862
 863    /**
 864     * Creates or looks up a file.
 865     *
 866     * @param baseURLstr base directory
 867     * @param path file/directory to lookup or create
 868     * @param options specify whether to create or not
 869     * @param directory if true look up directory, if false look up file
 870     * @return a Entry object
 871     * @throws FileExistsException
 872     * @throws IOException
 873     * @throws TypeMismatchException
 874     * @throws EncodingException
 875     * @throws JSONException
 876     */
 877    private JSONObject getFile(String baseURLstr, String path, JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
 878        try {
 879        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
 880        	Filesystem fs = this.filesystemForURL(inputURL);
 881        	if (fs == null) {
 882        		throw new MalformedURLException("No installed handlers for this URL");
 883        	}
 884        	return fs.getFileForLocalURL(inputURL, path, options, directory);
 885
 886        } catch (IllegalArgumentException e) {
 887            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
 888            mue.initCause(e);
 889        	throw mue;
 890        }
 891
 892    }
 893
 894    /**
 895     * Look up the parent DirectoryEntry containing this Entry.
 896     * If this Entry is the root of its filesystem, its parent is itself.
 897     */
 898    private JSONObject getParent(String baseURLstr) throws JSONException, IOException {
 899        try {
 900        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
 901        	Filesystem fs = this.filesystemForURL(inputURL);
 902        	if (fs == null) {
 903        		throw new MalformedURLException("No installed handlers for this URL");
 904        	}
 905        	return fs.getParentForLocalURL(inputURL);
 906
 907        } catch (IllegalArgumentException e) {
 908            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
 909            mue.initCause(e);
 910        	throw mue;
 911        }
 912    }
 913
 914    /**
 915     * Returns a File that represents the current state of the file that this FileEntry represents.
 916     *
 917     * @return returns a JSONObject represent a W3C File object
 918     */
 919    private JSONObject getFileMetadata(String baseURLstr) throws FileNotFoundException, JSONException, MalformedURLException {
 920        try {
 921        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(baseURLstr);
 922        	Filesystem fs = this.filesystemForURL(inputURL);
 923        	if (fs == null) {
 924        		throw new MalformedURLException("No installed handlers for this URL");
 925        	}
 926        	return fs.getFileMetadataForLocalURL(inputURL);
 927
 928        } catch (IllegalArgumentException e) {
 929            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
 930            mue.initCause(e);
 931        	throw mue;
 932        }
 933    }
 934
 935    /**
 936     * Requests a filesystem in which to store application data.
 937     *
 938     * @param type of file system requested
 939     * @param requiredSize required free space in the file system in bytes
 940     * @param callbackContext context for returning the result or error
 941     * @throws JSONException
 942     */
 943    private void requestFileSystem(int type, long requiredSize, final CallbackContext callbackContext) throws JSONException {
 944        Filesystem rootFs = null;
 945        try {
 946            rootFs = this.filesystems.get(type);
 947        } catch (ArrayIndexOutOfBoundsException e) {
 948            // Pass null through
 949        }
 950        if (rootFs == null) {
 951            callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.NOT_FOUND_ERR));
 952        } else {
 953            // If a nonzero required size was specified, check that the retrieved filesystem has enough free space.
 954            long availableSize = 0;
 955            if (requiredSize > 0) {
 956                availableSize = rootFs.getFreeSpaceInBytes();
 957            }
 958
 959            if (availableSize < requiredSize) {
 960                callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR, FileUtils.QUOTA_EXCEEDED_ERR));
 961            } else {
 962                JSONObject fs = new JSONObject();
 963                fs.put("name", rootFs.name);
 964                fs.put("root", rootFs.getRootEntry());
 965                callbackContext.success(fs);
 966            }
 967        }
 968    }
 969
 970    /**
 971     * Requests a filesystem in which to store application data.
 972     *
 973     * @return a JSONObject representing the file system
 974     */
 975    private JSONArray requestAllFileSystems() throws IOException, JSONException {
 976        JSONArray ret = new JSONArray();
 977        for (Filesystem fs : filesystems) {
 978            ret.put(fs.getRootEntry());
 979        }
 980        return ret;
 981    }
 982
 983    private static String toDirUrl(File f) {
 984        return Uri.fromFile(f).toString() + '/';
 985    }
 986
 987    private JSONObject requestAllPaths() throws JSONException {
 988        Context context = cordova.getActivity();
 989        JSONObject ret = new JSONObject();
 990        ret.put("applicationDirectory", "file:///android_asset/");
 991        ret.put("applicationStorageDirectory", toDirUrl(context.getFilesDir().getParentFile()));
 992        ret.put("dataDirectory", toDirUrl(context.getFilesDir()));
 993        ret.put("cacheDirectory", toDirUrl(context.getCacheDir()));
 994        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
 995          try {
 996            ret.put("externalApplicationStorageDirectory", toDirUrl(context.getExternalFilesDir(null).getParentFile()));
 997            ret.put("externalDataDirectory", toDirUrl(context.getExternalFilesDir(null)));
 998            ret.put("externalCacheDirectory", toDirUrl(context.getExternalCacheDir()));
 999            ret.put("externalRootDirectory", toDirUrl(Environment.getExternalStorageDirectory()));
1000          }
1001          catch(NullPointerException e) {
1002            /* If external storage is unavailable, context.getExternal* returns null */
1003              LOG.d(LOG_TAG, "Unable to access these paths, most liklely due to USB storage");
1004          }
1005        }
1006        return ret;
1007    }
1008
1009   /**
1010     * Returns a JSON object representing the given File. Internal APIs should be modified
1011     * to use URLs instead of raw FS paths wherever possible, when interfacing with this plugin.
1012     *
1013     * @param file the File to convert
1014     * @return a JSON representation of the given File
1015     * @throws JSONException
1016     */
1017    public JSONObject getEntryForFile(File file) throws JSONException {
1018        JSONObject entry;
1019
1020        for (Filesystem fs : filesystems) {
1021             entry = fs.makeEntryForFile(file);
1022             if (entry != null) {
1023                 return entry;
1024             }
1025        }
1026        return null;
1027    }
1028
1029    /**
1030     * Returns a JSON object representing the given File. Deprecated, as this is only used by
1031     * FileTransfer, and because it is a static method that should really be an instance method,
1032     * since it depends on the actual filesystem roots in use. Internal APIs should be modified
1033     * to use URLs instead of raw FS paths wherever possible, when interfacing with this plugin.
1034     *
1035     * @param file the File to convert
1036     * @return a JSON representation of the given File
1037     * @throws JSONException
1038     */
1039    @Deprecated
1040    public static JSONObject getEntry(File file) throws JSONException {
1041 		if (getFilePlugin() != null) {
1042             return getFilePlugin().getEntryForFile(file);
1043		}
1044		return null;
1045    }
1046
1047    /**
1048     * Read the contents of a file.
1049     * This is done in a background thread; the result is sent to the callback.
1050     *
1051     * @param start             Start position in the file.
1052     * @param end               End position to stop at (exclusive).
1053     * @param callbackContext   The context through which to send the result.
1054     * @param encoding          The encoding to return contents as.  Typical value is UTF-8. (see http://www.iana.org/assignments/character-sets)
1055     * @param resultType        The desired type of data to send to the callback.
1056     * @return                  Contents of file.
1057     */
1058    public void readFileAs(final String srcURLstr, final int start, final int end, final CallbackContext callbackContext, final String encoding, final int resultType) throws MalformedURLException {
1059        try {
1060        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(srcURLstr);
1061        	Filesystem fs = this.filesystemForURL(inputURL);
1062        	if (fs == null) {
1063        		throw new MalformedURLException("No installed handlers for this URL");
1064        	}
1065
1066            fs.readFileAtURL(inputURL, start, end, new Filesystem.ReadFileCallback() {
1067                public void handleData(InputStream inputStream, String contentType) {
1068            		try {
1069                        ByteArrayOutputStream os = new ByteArrayOutputStream();
1070                        final int BUFFER_SIZE = 8192;
1071                        byte[] buffer = new byte[BUFFER_SIZE];
1072
1073                        for (;;) {
1074                            int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
1075
1076                            if (bytesRead <= 0) {
1077                                break;
1078                            }
1079                            os.write(buffer, 0, bytesRead);
1080                        }
1081
1082            			PluginResult result;
1083            			switch (resultType) {
1084            			case PluginResult.MESSAGE_TYPE_STRING:
1085                            result = new PluginResult(PluginResult.Status.OK, os.toString(encoding));
1086            				break;
1087            			case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
1088                            result = new PluginResult(PluginResult.Status.OK, os.toByteArray());
1089            				break;
1090            			case PluginResult.MESSAGE_TYPE_BINARYSTRING:
1091                            result = new PluginResult(PluginResult.Status.OK, os.toByteArray(), true);
1092            				break;
1093            			default: // Base64.
1094                        byte[] base64 = Base64.encode(os.toByteArray(), Base64.NO_WRAP);
1095            			String s = "data:" + contentType + ";base64," + new String(base64, "US-ASCII");
1096            			result = new PluginResult(PluginResult.Status.OK, s);
1097            			}
1098
1099            			callbackContext.sendPluginResult(result);
1100            		} catch (IOException e) {
1101            			LOG.d(LOG_TAG, e.getLocalizedMessage());
1102            			callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR));
1103                    }
1104            	}
1105            });
1106
1107
1108        } catch (IllegalArgumentException e) {
1109            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
1110            mue.initCause(e);
1111        	throw mue;
1112        } catch (FileNotFoundException e) {
1113        	callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_FOUND_ERR));
1114        } catch (IOException e) {
1115        	LOG.d(LOG_TAG, e.getLocalizedMessage());
1116        	callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.IO_EXCEPTION, NOT_READABLE_ERR));
1117        }
1118    }
1119
1120
1121    /**
1122     * Write contents of file.
1123     *
1124     * @param data				The contents of the file.
1125     * @param offset			The position to begin writing the file.
1126     * @param isBinary          True if the file contents are base64-encoded binary data
1127     */
1128    /**/
1129    public long write(String srcURLstr, String data, int offset, boolean isBinary) throws FileNotFoundException, IOException, NoModificationAllowedException {
1130        try {
1131        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(srcURLstr);
1132        	Filesystem fs = this.filesystemForURL(inputURL);
1133        	if (fs == null) {
1134        		throw new MalformedURLException("No installed handlers for this URL");
1135        	}
1136
1137            long x = fs.writeToFileAtURL(inputURL, data, offset, isBinary); LOG.d("TEST",srcURLstr + ": "+x); return x;
1138        } catch (IllegalArgumentException e) {
1139            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
1140            mue.initCause(e);
1141        	throw mue;
1142        }
1143
1144    }
1145
1146    /**
1147     * Truncate the file to size
1148     */
1149    private long truncateFile(String srcURLstr, long size) throws FileNotFoundException, IOException, NoModificationAllowedException {
1150        try {
1151        	LocalFilesystemURL inputURL = LocalFilesystemURL.parse(srcURLstr);
1152        	Filesystem fs = this.filesystemForURL(inputURL);
1153        	if (fs == null) {
1154        		throw new MalformedURLException("No installed handlers for this URL");
1155        	}
1156
1157            return fs.truncateFileAtURL(inputURL, size);
1158        } catch (IllegalArgumentException e) {
1159            MalformedURLException mue = new MalformedURLException("Unrecognized filesystem URL");
1160            mue.initCause(e);
1161        	throw mue;
1162        }
1163    }
1164
1165
1166    /*
1167     * Handle the response
1168     */
1169
1170    public void onRequestPermissionResult(int requestCode, String[] permissions,
1171                                          int[] grantResults) throws JSONException {
1172
1173        final PendingRequests.Request req = pendingRequests.getAndRemove(requestCode);
1174        if (req != null) {
1175            for(int r:grantResults)
1176            {
1177                if(r == PackageManager.PERMISSION_DENIED)
1178                {
1179                    req.getCallbackContext().sendPluginResult(new PluginResult(PluginResult.Status.ERROR, SECURITY_ERR));
1180                    return;
1181                }
1182            }
1183            switch(req.getAction())
1184            {
1185                case ACTION_GET_FILE:
1186                    threadhelper( new FileOp( ){
1187                        public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
1188                            String dirname = args.getString(0);
1189
1190                            String path = args.getString(1);
1191                            JSONObject obj = getFile(dirname, path, args.optJSONObject(2), false);
1192                            req.getCallbackContext().success(obj);
1193                        }
1194                    }, req.getRawArgs(), req.getCallbackContext());
1195                    break;
1196                case ACTION_GET_DIRECTORY:
1197                    threadhelper( new FileOp( ){
1198                        public void run(JSONArray args) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
1199                            String dirname = args.getString(0);
1200
1201                            String path = args.getString(1);
1202                            JSONObject obj = getFile(dirname, path, args.optJSONObject(2), true);
1203                            req.getCallbackContext().success(obj);
1204                        }
1205                    }, req.getRawArgs(), req.getCallbackContext());
1206                    break;
1207                case ACTION_WRITE:
1208                    threadhelper( new FileOp( ){
1209                        public void run(JSONArray args) throws JSONException, FileNotFoundException, IOException, NoModificationAllowedException {
1210                            String fname=args.getString(0);
1211                            String data=args.getString(1);
1212                            int offset=args.getInt(2);
1213                            Boolean isBinary=args.getBoolean(3);
1214                            long fileSize = write(fname, data, offset, isBinary);
1215                            req.getCallbackContext().sendPluginResult(new PluginResult(PluginResult.Status.OK, fileSize));
1216                        }
1217                    }, req.getRawArgs(), req.getCallbackContext());
1218                    break;
1219            }
1220        } else {
1221           LOG.d(LOG_TAG, "Received permission callback for unknown request code");
1222        }
1223    }
1224}