crashes when selecting files from downloads
I use this fileutils class when selecting files 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 like this in onActivityResult
:
String sourcePath = FileUtils.getPath(this, data.getData());
For some reason I’m experiencing 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 occurs when a file is selected from the download folder.
Can someone help me with this?
Solution
I’ve created a gist
to fix this, here is link
Important!!
When you select a file from Google Drive or Dropbox, you get the following error message:
Caused by java.lang.IllegalArgumentException: column '_data' does not exist
To fix this, you actually have to make a temporary copy of the file you are trying to select.
First you have to check if the content:// URI is from Google Drive, which you can do 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 yes, I’ll call an AsyncTask
like this:
//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 AsyncTask
, we will use the Uri
to open an InputStream
and retrieve the file of the byte data that we will use to make the copy.
This is the AsyncTask
class (I added comments to make it easier to understand):
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 can see that in onPostExecute()
above I have callback.getResultFromAsynTask(result);
。 As I mentioned in the comment, I’m using the callback method to let the activity know I’m done and pass the path to the callback.
In your activity, you must implement callbacks as follows:
public class MainActivity extends AppCompatActivity implements CallBackTask {
The CallBackTask
will look like this:
interface CallBackTask {
void getResultFromAsynTask(String result);
}
Now you have to implement it in your activity to get results 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);
}
Awesome, you now have a URI
from File://
(instead of content://
) that you copied temporarily. However, don’t forget to delete the files when you are done using them or your app will get bigger and bigger.
Below, I’ll
delete the TempFolder
we created earlier, which I’ll do in Activity onBackPressed
(this should also be done in onDestroy()):
@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();
}
Read a lot, but …
In this way, you will never have any problems with content://
URIs
.