@Override
  public String createDocument(String docId, String mimeType, String displayName)
      throws FileNotFoundException {
    displayName = FileUtils.buildValidFatFilename(displayName);

    final File parent = getFileForDocId(docId);
    if (!parent.isDirectory()) {
      throw new IllegalArgumentException("Parent document isn't a directory");
    }

    final File file = FileUtils.buildUniqueFile(parent, mimeType, displayName);
    if (Document.MIME_TYPE_DIR.equals(mimeType)) {
      if (!file.mkdir()) {
        throw new IllegalStateException("Failed to mkdir " + file);
      }
    } else {
      try {
        if (!file.createNewFile()) {
          throw new IllegalStateException("Failed to touch " + file);
        }
      } catch (IOException e) {
        throw new IllegalStateException("Failed to touch " + file + ": " + e);
      }
    }

    return getDocIdForFile(file);
  }
  public SortingCursorWrapper(Cursor cursor, int sortOrder) {
    mCursor = cursor;

    final int count = cursor.getCount();
    mPosition = new int[count];
    switch (sortOrder) {
      case SORT_ORDER_DISPLAY_NAME:
        mValueString = new String[count];
        mValueLong = null;
        break;
      case SORT_ORDER_LAST_MODIFIED:
      case SORT_ORDER_SIZE:
        mValueString = null;
        mValueLong = new long[count];
        break;
      default:
        throw new IllegalArgumentException();
    }

    cursor.moveToPosition(-1);
    for (int i = 0; i < count; i++) {
      cursor.moveToNext();
      mPosition[i] = i;

      switch (sortOrder) {
        case SORT_ORDER_DISPLAY_NAME:
          final String mimeType =
              cursor.getString(cursor.getColumnIndex(Document.COLUMN_MIME_TYPE));
          final String displayName =
              cursor.getString(cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME));
          if (Document.MIME_TYPE_DIR.equals(mimeType)) {
            mValueString[i] = '\001' + displayName;
          } else {
            mValueString[i] = displayName;
          }
          break;
        case SORT_ORDER_LAST_MODIFIED:
          mValueLong[i] = cursor.getLong(cursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED));
          break;
        case SORT_ORDER_SIZE:
          mValueLong[i] = cursor.getLong(cursor.getColumnIndex(Document.COLUMN_SIZE));
          break;
      }
    }

    switch (sortOrder) {
      case SORT_ORDER_DISPLAY_NAME:
        synchronized (SortingCursorWrapper.class) {
          binarySort(mPosition, mValueString);
        }
        break;
      case SORT_ORDER_LAST_MODIFIED:
      case SORT_ORDER_SIZE:
        binarySort(mPosition, mValueLong);
        break;
    }
  }
 public final ArrayList<SAFItem> getChildren(Uri u) {
   Cursor c = null;
   try {
     try {
       ContentResolver cr = ctx.getContentResolver();
       String document_id = DocumentsContract.getDocumentId(u);
       Uri children_uri = DocumentsContract.buildChildDocumentsUriUsingTree(u, document_id);
       // Log.d( TAG, "Children URI:" + children_uri );
       final String[] projection = {
         Document.COLUMN_DOCUMENT_ID,
         Document.COLUMN_DISPLAY_NAME,
         Document.COLUMN_LAST_MODIFIED,
         Document.COLUMN_MIME_TYPE,
         Document.COLUMN_SIZE
       };
       c = cr.query(children_uri, projection, null, null, null);
     } catch (SecurityException e) {
       Log.w(TAG, "Security error on " + u.toString(), e);
       return null;
     } catch (Exception e) {
       Log.e(TAG, u.toString(), e);
     }
     if (c != null) {
       ArrayList<SAFItem> tmp_list = new ArrayList<SAFItem>();
       if (c.getCount() == 0) return tmp_list;
       int ici = c.getColumnIndex(Document.COLUMN_DOCUMENT_ID);
       int nci = c.getColumnIndex(Document.COLUMN_DISPLAY_NAME);
       int sci = c.getColumnIndex(Document.COLUMN_SIZE);
       int mci = c.getColumnIndex(Document.COLUMN_MIME_TYPE);
       int dci = c.getColumnIndex(Document.COLUMN_LAST_MODIFIED);
       c.moveToFirst();
       do {
         SAFItem item = new SAFItem();
         String id = c.getString(ici);
         item.origin = DocumentsContract.buildDocumentUriUsingTree(u, id);
         item.attr = c.getString(mci);
         item.dir = Document.MIME_TYPE_DIR.equals(item.attr);
         item.name = (item.dir ? "/" : "") + c.getString(nci);
         item.size = c.getLong(sci);
         item.date = new Date(c.getLong(dci));
         if (item.dir) item.size = -1;
         tmp_list.add(item);
       } while (c.moveToNext());
       return tmp_list;
     }
   } catch (Exception e) {
     Log.e(TAG, "Failed cursor processing for " + u.toString(), e);
   } finally {
     if (c != null) c.close();
   }
   return null;
 }
  @Override
  public String createDocument(String docId, String mimeType, String displayName)
      throws FileNotFoundException {
    if (Document.MIME_TYPE_DIR.equals(mimeType)) {
      throw new FileNotFoundException("Directory creation not supported");
    }

    final File parent =
        Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
    parent.mkdirs();

    // Delegate to real provider
    final long token = Binder.clearCallingIdentity();
    try {
      displayName = removeExtension(mimeType, displayName);
      File file = new File(parent, addExtension(mimeType, displayName));

      // If conflicting file, try adding counter suffix
      int n = 0;
      while (file.exists() && n++ < 32) {
        file = new File(parent, addExtension(mimeType, displayName + " (" + n + ")"));
      }

      try {
        if (!file.createNewFile()) {
          throw new IllegalStateException("Failed to touch " + file);
        }
      } catch (IOException e) {
        throw new IllegalStateException("Failed to touch " + file + ": " + e);
      }

      return Long.toString(
          mDm.addCompletedDownloadTransfer(
              file.getName(),
              file.getName(),
              false,
              mimeType,
              file.getAbsolutePath(),
              0L,
              false,
              true));
    } finally {
      Binder.restoreCallingIdentity(token);
    }
  }
    private final int copyFiles(File[] list, Uri dest) throws InterruptedException {
      File file = null;
      for (int i = 0; i < list.length; i++) {
        InputStream is = null;
        OutputStream os = null;
        file = list[i];
        if (file == null) {
          error(ctx.getString(R.string.unkn_err));
          break;
        }
        Uri dest_uri = null;
        try {
          if (isStopReq()) {
            error(ctx.getString(R.string.canceled));
            break;
          }
          String fn = file.getName();
          String to_append = "%2f" + Utils.escapePath(fn);
          dest_uri = dest.buildUpon().encodedPath(dest.getEncodedPath() + to_append).build();
          String mime = getMime(dest_uri);
          if (file.isDirectory()) {
            if (depth++ > 40) {
              error(ctx.getString(R.string.too_deep_hierarchy));
              break;
            }
            if (mime != null) {
              if (!Document.MIME_TYPE_DIR.equals(mime)) {
                error(ctx.getString(R.string.cant_md));
                break;
              }
            } else {
              DocumentsContract.createDocument(cr, dest, Document.MIME_TYPE_DIR, fn);
            }
            copyFiles(file.listFiles(), dest_uri);
            if (errMsg != null) break;
            depth--;
            counter++;
          } else {
            if (mime != null) {
              int res = askOnFileExist(ctx.getString(R.string.file_exist, fn), commander);
              if (res == Commander.SKIP) continue;
              if (res == Commander.ABORT) break;
              if (res == Commander.REPLACE) {
                File dest_file = new File(getPath(dest_uri, false));
                if (dest_file.equals(file)) {
                  Log.w(TAG, "Not going to copy file to itself");
                  continue;
                }
                Log.v(TAG, "Overwritting file " + fn);
                DocumentsContract.deleteDocument(cr, dest_uri);
              }
            } else mime = Utils.getMimeByExt(Utils.getFileExt(fn));
            dest_uri = DocumentsContract.createDocument(cr, dest, mime, fn);
            if (dest_uri == null) {
              error(ctx.getString(R.string.cant_create, fn, ""));
              break;
            }
            String dest_path = dest_uri.getPath();
            if (dest_path.indexOf(fn, dest_path.length() - fn.length() - 1) < 0) // SAF suxx
            dest_uri = DocumentsContract.renameDocument(cr, dest_uri, fn);

            is = new FileInputStream(file);
            os = cr.openOutputStream(dest_uri);
            long copied = 0, size = file.length();

            long start_time = 0;
            int speed = 0;
            int so_far = (int) (totalBytes * conv);

            String sz_s = Utils.getHumanSize(size);
            int fnl = fn.length();
            String rep_s =
                ctx.getString(
                    R.string.copying, fnl > CUT_LEN ? "\u2026" + fn.substring(fnl - CUT_LEN) : fn);
            int n = 0;
            long nn = 0;

            while (true) {
              if (nn == 0) {
                start_time = System.currentTimeMillis();
                sendProgress(
                    rep_s + sizeOfsize(copied, sz_s), so_far, (int) (totalBytes * conv), speed);
              }
              n = is.read(buf);
              if (n < 0) {
                long time_delta = System.currentTimeMillis() - start_time;
                if (time_delta > 0) {
                  speed = (int) (MILLI * nn / time_delta);
                  sendProgress(
                      rep_s + sizeOfsize(copied, sz_s), so_far, (int) (totalBytes * conv), speed);
                }
                break;
              }
              os.write(buf, 0, n);
              nn += n;
              copied += n;
              totalBytes += n;
              if (isStopReq()) {
                Log.d(TAG, "Interrupted!");
                error(ctx.getString(R.string.canceled));
                return counter;
              }
              long time_delta = System.currentTimeMillis() - start_time;
              if (time_delta > DELAY) {
                speed = (int) (MILLI * nn / time_delta);
                // Log.v( TAG, "bytes: " + nn + " time: " + time_delta + " speed: " + speed );
                nn = 0;
              }
            }
            is.close();
            os.close();
            is = null;
            os = null;
            /*
            ContentValues cv = new ContentValues();
            cv.put( Document.COLUMN_LAST_MODIFIED, file.lastModified() );
            cr.update( dest_uri, cv, null, null ); //throws..
            */
            if (i >= list.length - 1)
              sendProgress(
                  ctx.getString(R.string.copied_f, fn) + sizeOfsize(copied, sz_s),
                  (int) (totalBytes * conv));
            counter++;
          }
          if (move) {
            if (!file.delete()) {
              sendProgress(ctx.getString(R.string.cant_del, fn), -1);
              delerr_counter++;
            }
          }
        } catch (Exception e) {
          Log.e(TAG, "", e);
          error(ctx.getString(R.string.rtexcept, file.getAbsolutePath(), e.getMessage()));
        } finally {
          try {
            if (is != null) is.close();
            if (os != null) os.close();
          } catch (IOException e) {
            error(ctx.getString(R.string.acc_err, file.getAbsolutePath(), e.getMessage()));
          }
        }
      }
      return counter;
    }