Sunday, August 4, 2013

media play (using Intent.ACTION_VIEW) and share to YouTube (using Intent.ACTION_SEND) breaks on 4.3

Its a common practice to convert file URL to Content URI this way:

ContentValues content = new ContentValues(4);
content.put(Video.VideoColumns.TITLE, outputFile);
content.put(Video.VideoColumns.DATE_ADDED,System.currentTimeMillis() / 1000);
content.put(Video.Media.MIME_TYPE, "video/mp4");
content.put(MediaStore.Video.Media.DATA, outputFileURL);
ContentResolver resolver = getContentResolver();
Uri uri = resolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,content);

In 4.3 this does not work anymore.
Here is the reason:

08-02 23:11:54.524: E/SQLiteDatabase(11214): Error inserting bucket_id=1643208667 media_type=3 title=out.mp4 storage_id=65537 mime_type=video/mp4 date_modified=1375474313 _display_name=out.mp4 date_added=1375474314 parent=1184 _size=325469 format=12299 _data=/storage/emulated/0/compressed/out.mp4 bucket_display_name=compressed
08-02 23:11:54.524: E/SQLiteDatabase(11214): android.database.sqlite.SQLiteConstraintException: column _data is not unique (code 19)

You can see from the log that the column _data must be unique.
Google changed the functionality here, and without any warning lots of apps will break.

The solution is simple, instead of inserting and getting the content URI, you should query.
Here is a function that does it:

public static String getVideoContentUriFromFilePath(Context ctx, String filePath) {

ContentResolver contentResolver = ctx.getContentResolver();
String videoUriStr = null;
   long videoId = -1;
   Log.d(Prefs.TAG,"Loading file " + filePath);

   // This returns us content://media/external/videos/media (or something like that)
   // I pass in "external" because that's the MediaStore's name for the external
   // storage on my device (the other possibility is "internal")
   Uri videosUri = MediaStore.Video.Media.getContentUri("external");

   Log.d(Prefs.TAG,"videosUri = " + videosUri.toString());

   String[] projection = {MediaStore.Video.VideoColumns._ID};

   // TODO This will break if we have no matching item in the MediaStore.
   Cursor cursor = contentResolver.query(videosUri, projection, MediaStore.Video.VideoColumns.DATA + " LIKE ?", new String[] { filePath }, null);

   int columnIndex = cursor.getColumnIndex(projection[0]);
   videoId = cursor.getLong(columnIndex);

   Log.d(Prefs.TAG,"Video ID is " + videoId);
   if (videoId != -1 ) videoUriStr = videosUri.toString() + "/" + videoId;
   return videoUriStr;