Crash when selecting file from downloads

HB. :

I'm using this fileutils class when selecting a file from the device:

public class FileUtils {
private FileUtils() {
}

private static final String TAG = "FileUtils";
private static final boolean DEBUG = false;

private static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

private static boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

private static boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}


private static boolean isGooglePhotosUri(Uri uri) {
    return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}

private static String getDataColumn(Context context, Uri uri, String selection,
                                    String[] selectionArgs) {

    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {
            column
    };

    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                null);
        if (cursor != null && cursor.moveToFirst()) {
            if (DEBUG)
                DatabaseUtils.dumpCursor(cursor);

            final int column_index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(column_index);
        }
    } finally {
        if (cursor != null)
            cursor.close();
    }
    return null;
}

@RequiresApi(api = Build.VERSION_CODES.KITKAT)
public static String getPath(final Context context, final Uri uri) {

    if (DEBUG)
        Log.d(TAG + " File -",
                "Authority: " + uri.getAuthority() +
                        ", Fragment: " + uri.getFragment() +
                        ", Port: " + uri.getPort() +
                        ", Query: " + uri.getQuery() +
                        ", Scheme: " + uri.getScheme() +
                        ", Host: " + uri.getHost() +
                        ", Segments: " + uri.getPathSegments().toString()
        );

    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {

        //// ExternalStorageProvider
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            // This is for checking Main Memory
            if ("primary".equalsIgnoreCase(type)) {
                if (split.length > 1) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                } else {
                    return Environment.getExternalStorageDirectory() + "/";
                }
                // This is for checking SD Card
            } else {
                return "storage" + "/" + docId.replace(":", "/");
            }

        }
        // DownloadsProvider
        else if (isDownloadsDocument(uri)) {
            final String id = DocumentsContract.getDocumentId(uri);
            if (id.startsWith("raw:")) {
                String[] data = new String[2];
                data[0] = id.replaceFirst("raw:", "");
                data[1] = null;
                return data[0];
            }
            final Uri contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

            return getDataColumn(context, contentUri, null, null);
        }
        // MediaProvider
        else if (isMediaDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }

            final String selection = "_id=?";
            final String[] selectionArgs = new String[]{
                    split[1]
            };

            return getDataColumn(context, contentUri, selection, selectionArgs);
        }
    }
    // MediaStore (and general)
    else if ("content".equalsIgnoreCase(uri.getScheme())) {

        // Return the remote address
        if (isGooglePhotosUri(uri))
            return uri.getLastPathSegment();

        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;
}

I call this class in onActivityResult like this:

String sourcePath = FileUtils.getPath(this, data.getData());

For some reason I'm getting the following crash:

Caused by java.lang.IllegalArgumentException: Unknown URI: content://downloads/public_downloads/230
   at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:165)
   at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:135)
   at android.content.ContentProviderProxy.query(ContentProviderNative.java:418)
   at android.content.ContentResolver.query(ContentResolver.java:760)
   at android.content.ContentResolver.query(ContentResolver.java:710)
   at android.content.ContentResolver.query(ContentResolver.java:668)
   at com.HBiSoft.ProGolf.Utils.FileUtils.getDataColumn(FileUtils.java:50)
   at com.HBiSoft.ProGolf.Utils.FileUtils.getPath(FileUtils.java:116)
   at com.HBiSoft.ProGolf.MainActivity.onActivityResult(MainActivity.java:652)
   at android.app.Activity.dispatchActivityResult(Activity.java:7638)
   at android.app.ActivityThread.deliverResults(ActivityThread.java:4515)
   at android.app.ActivityThread.handleSendResult(ActivityThread.java:4563)
   at android.app.ActivityThread.-wrap21(Unknown Source)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1779)
   at android.os.Handler.dispatchMessage(Handler.java:106)
   at android.os.Looper.loop(Looper.java:164)
   at android.app.ActivityThread.main(ActivityThread.java:7000)
   at java.lang.reflect.Method.invoke(Method.java)
   at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:441)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1408)

the crash only happens when selecting a file from the downloads folder.

Can someone please help me resolve this?

HB. :

I have created a gist that solves this issue, here is the link.


IMPORTANT!!

When you select a file from Google Drive or Dropbox then you will get the following error:

Caused by java.lang.IllegalArgumentException: column '_data' does not exist

To resolve this you actually have to make a temporary copy of the file you are trying select.

First you have to check if the content:// uri is from Google Drive, you can do this by doing the following:

public boolean isGoogleDrive(Uri uri) {
    return String.valueOf(uri).toLowerCase().contains("com.google.android.apps");
}

This will return true if it is a file from Google Drive. If it is, I will call a AsyncTask, as shown below:

//The data.getData() below refers to the uri you get in onActivityResult
if (isGoogleDrive(data.getData())) {
    DownloadAsyncTask asyntask = new DownloadAsyncTask(data.getData(), this);
    asyntask.execute();
    asyntask.callback = this;
}

In the AsyncTask we will use the Uri to open a InputStream and get the byte data back which we will use to make a copy of the file.

Here is the AsyncTask class (I've added comments to make it more understandable):

class DownloadAsyncTask extends AsyncTask<Uri, Void, String> {
    private Uri mUri;
    CallBackTask callback;
    Context mContext;
    private AlertDialog mdialog;

    DownloadAsyncTask(Uri uri, Context context) {
        this.mUri = uri;
        mContext = context;
    }

    // In the onPreExecute() I'm displaying a custom dialog, this is not necessary, but recommended for when the user selects a large file
    @Override
    protected void onPreExecute() {
        final AlertDialog.Builder mPro = new AlertDialog.Builder(new ContextThemeWrapper(mContext, R.style.myDialog));
        @SuppressLint("InflateParams") 
        //Get reference to dialog layout
        final View mPView = LayoutInflater.from(mContext).inflate(R.layout.dialog, null);
        //Get reference to dialog title
        final TextView title = mPView.findViewById(R.id.txtTitle);
        //Get reference to dialog description
        final TextView desc = mPView.findViewById(R.id.txtDesc);

        //Set title text
        title.setText("Please wait..");
        //Set description text
        desc.setText("Drive files needs to be imported, this might take some time depending on the file size.");

        mPro.setView(mPView);
        mdialog = mPro.create();
        mdialog.show();

    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    protected String doInBackground(Uri... params) {
        //This will be the file we will use (the one that will be copied)
        File file = null;
        try {
            //Create a temporary folder where the copy will be saved to
            File temp_folder = mContext.getExternalFilesDir("TempFolder");

            //Use ContentResolver to get the name of the original name
            //Create a cursor and pass the Uri to it
            Cursor cursor = mContext.getContentResolver().query(mUri, null, null, null, null);
            //Check that the cursor is not null
            assert cursor != null;
            int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
            cursor.moveToFirst();
            //Get the file name
            String filename = cursor.getString(nameIndex);
            //Close the cursor
            cursor.close();

            //open a InputStream by passing it the Uri
            //We have to do this in a try/catch
            InputStream is = null;
            try {
                is = mContext.getContentResolver().openInputStream(mUri);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }

            //We now have a folder and a file name, now we can create a file
            file = new File(temp_folder + "/" + filename);

            //We can now use a BufferedInputStream to pass the InputStream we opened above to it
            BufferedInputStream bis = new BufferedInputStream(is);
            //We will write the byte data to the FileOutputStream, but we first have to create it
            FileOutputStream fos = new FileOutputStream(file);

            byte data[] = new byte[1024];
            long total = 0;
            int count;
            //Below we will read all the byte data and write it to the FileOutputStream
            while ((count = bis.read(data)) != -1) {
                total += count;
                fos.write(data, 0, count);
            }
            //The FileOutputStream is done and the file is created and we can clean and close it
            fos.flush();
            fos.close();

        } catch (IOException e) {
            Log.e("IOException = ", String.valueOf(e));
        }

        //Finally we can pass the path of the file we have copied
        return file.getAbsolutePath();


    }

    protected void onPostExecute(String result) {
        //We are done and can cancel the dialog
        if (mdialog != null && mdialog.isShowing()) {
            mdialog.cancel();
        }
        //I'm using a callback to let my Activity know that the AsyncTask is done. I pass the path along.
        callback.getResultFromAsynTask(result);


    }
}

You will see that in the onPostExecute() above I have callback.getResultFromAsynTask(result);. As I mentioned in the comment, I'm using a callback method to let the Activity know that I'm done and I pass the path to the callback.

In you Activity you have to implement the callback, like this:

public class MainActivity extends AppCompatActivity implements CallBackTask {

and the CallBackTask will look like this:

interface CallBackTask {
    void getResultFromAsynTask(String result);
 }

now you have to implement it in your Activity to get the result in your Activity:

@Override
public void getResultFromAsynTask(String result) {
    // Do what you need with the result like starting your new Activity and passing the path
    final Intent intent = new Intent();
    intent.setClass(MainActivity.this, Player.class);
    intent.putExtra("path", result);
    startActivity(intent);
}

GREAT, you now have a Uri from a File://(instead of content://) that you have temporarily copied. But, don't forget to delete the file when you are done with it, otherwise your application size will keep growing bigger.

Below, I will delete the TempFolder we created earlier, I will be doing this in the Activities onBackPressed (this should be done in onDestroy() as well):

@Override
public void onBackPressed() {
    super.onBackPressed();
    File dir = getBaseContext().getExternalFilesDir("TempFolder");
    deleteRecursive(dir);
}

void deleteRecursive(File fileOrDirectory) {
    if (fileOrDirectory.isDirectory())
        for (File child : fileOrDirectory.listFiles())
            deleteRecursive(child);

    fileOrDirectory.delete();
}

This is a lot read BUT... By doing it this way, you will NEVER have issues with any content:// Uri's.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=101986&siteId=1