Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/redis/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Android 为手机上的照片编写自定义内容提供商(第2部分)_Android_Android Contentprovider - Fatal编程技术网

Android 为手机上的照片编写自定义内容提供商(第2部分)

Android 为手机上的照片编写自定义内容提供商(第2部分),android,android-contentprovider,Android,Android Contentprovider,我正在为我的应用程序开发一个自定义内容提供商。这是我学习安卓应用程序课程的一部分,所以请不要期望做这一切的理由太棒;-)这里的重点是让我了解CP 我有一个问题,但我想我已经设法把我的问题简化了很多。所以,我正在开发一个“画廊应用程序”。由于我不知道手机上的缩略图是如何存储的,也不知道存储在哪里,所以我决定只使用MediaStore.images.Thumbnails访问缩略图,并在我的GridView中显示它们 然而,为了满足上述课程的要求,我将编写一个“PhotoProvider”来加载Det

我正在为我的应用程序开发一个自定义内容提供商。这是我学习安卓应用程序课程的一部分,所以请不要期望做这一切的理由太棒;-)这里的重点是让我了解CP

我有一个问题,但我想我已经设法把我的问题简化了很多。所以,我正在开发一个“画廊应用程序”。由于我不知道手机上的缩略图是如何存储的,也不知道存储在哪里,所以我决定只使用MediaStore.images.Thumbnails访问缩略图,并在我的GridView中显示它们

然而,为了满足上述课程的要求,我将编写一个“PhotoProvider”来加载DetailActivity中的单张全屏照片。此外,为了使这一点有一定的意义,而不仅仅是作为MediaStore.Media.Images的包装器,我将使用JPEG文件中的一些EXIF标记扩展该数据

找到了一个新的类,并继续讨论类中提供的源代码,我提出了以下类。也许你想看看,帮我解决一下(给我指出正确的方向)

我将支持的URI是
context://AUTH/photo/#
,以获取特定图像(从缩略图中给定
图像\u ID
)。此外,为了让它更有趣,我还希望能够将数据写入EXIF标记
UserComment
context://AUTH/photo/#/comment/*
(注释字符串是最后一个参数)。这看起来合理吗

一些问题:(更新)

  • getType()
    令人困惑。同样,这是应用程序课程的贷款。当返回图像时,我想类型应该总是
    image/jpeg
    (或者PNG)
  • Edit:了解更多信息,我现在了解到从
    insert()
    方法返回的URI是可选的,但是返回新(插入的)数据的“链接”(即URI)通常很有用!对于我的情况,在更新EXIF标记之后,我可以返回
    null
    ,或者返回一个URI到编辑的照片:
    context://AUTH/photo/7271
    (其中7271模拟照片ID)

    下面是我的(未完成的!)代码。请看一看,特别是
    query()
    insert()
    函数:-)

    PhotoContract.java PhotoProvider.java:
    package com.example.android.galleri.app.data;
    导入android.content.ContentProvider;
    导入android.content.ContentValues;
    导入android.content.UriMatcher;
    导入android.database.Cursor;
    导入android.database.MatrixCursor;
    导入android.graphics.Bitmap;
    导入android.graphics.BitmapFactory;
    导入android.media.ExifInterface;
    导入android.net.Uri;
    导入android.provider.MediaStore;
    导入android.support.v4.content.CursorLoader;
    导入android.util.Log;
    导入java.io.IOException;
    公共类PhotoProvider扩展了ContentProvider{
    //此内容提供程序使用的URI匹配器。
    私有静态最终UriMatcher sUriMatcher=buildUriMatcher();
    静态最终int照片=100;
    静态最终int照片集注释=200;
    静态UriMatcher buildUriMatcher(){
    //1)传递到构造函数中的代码表示要为根返回的代码
    //在这种情况下,通常使用NO_MATCH作为代码。请在下面添加构造函数。
    最终UriMatcher匹配器=新UriMatcher(UriMatcher.NO_匹配);
    最终字符串权限=PhotoContract.CONTENT\u权限;
    //2)使用addURI函数匹配每个类型
    //WeatherContract帮助定义UriMatcher的类型。
    //匹配照片/表示任何照片ID
    matcher.addURI(authority,PhotoContract.PATH_PHOTO+“/#”,PHOTO);
    //匹配照片//注释/
    matcher.addURI(authority,PhotoContract.PATH_PHOTO+“/#/”+PhotoContract.PATH_COMMENT+“/*”,PHOTO_SET_COMMENT);
    //3)退回新的匹配器!
    返回匹配器;
    }
    @凌驾
    公共字符串getType(Uri){
    //使用Uri匹配器确定这是什么类型的Uri。
    final int match=sUriMatcher.match(uri);
    开关(匹配){
    案例照片\u集\u注释:
    返回PhotoContract.PhotoEntry.CONTENT\u类型;
    案例照片:
    返回PhotoContract.PhotoEntry.CONTENT\u类型;
    违约:
    抛出新的UnsupportedOperationException(“未知uri:+uri”);
    }
    }
    @凌驾
    公共布尔onCreate(){
    返回true;//够了吗?
    }
    @凌驾
    公共游标查询(Uri Uri、字符串[]投影、字符串选择、字符串[]selectionArgs、字符串排序器){
    MatrixCursor retCursor=新MatrixCursor(投影);
    //通过MediaStore打开指定的映像以获取基本列
    //然后通过ExiFinInterface打开图像文件以获取详细信息列
    Long IMAGE_ID=PhotoContract.PhotoEntry.getImageIdFromUri(uri);
    //Uri baseUri=Uri.parse(“content://media/external/images/media");
    Uri baseUri=MediaStore.Images.Media.EXTERNAL\u CONTENT\u Uri;
    baseUri=Uri.withAppendedPath(baseUri,“+IMAGE_ID);
    字符串[]MS_投影={
    MediaStore.Images.Media.DISPLAY\u名称,
    MediaStore.Images.Media.DATA,
    MediaStore.Images.Media.DESCRIPTION,
    MediaStore.Images.Media.DATE_拍摄,
    MediaStore.Images.Media.DATE_已添加,
    MediaStore.Images.Media.TITLE,
    MediaStore.Images.Media.SIZE,
    MediaStore.Images.Media.ORIENTATION};
    // http://androidsnippets.com/get-file-path-of-gallery-image
    游标c=getContext().getContentResolver().query(baseUri,MS_投影,null,null);
    //将字段(我们需要的字段——假设现在我们需要所有字段,顺序相同)转储到MatrixCursor中
    Object[]行=新对象[projection.length];
    R
    
    package com.example.android.galleri.app.data;
    
    import android.content.ContentResolver;
    import android.content.ContentUris;
    import android.net.Uri;
    import android.provider.BaseColumns;
    import android.provider.MediaStore;
    
    public class PhotoContract {
    
        public static final String CONTENT_AUTHORITY = "no.myapp.android.galleri.app";
    
        public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
    
        public static final String PATH_PHOTO = "photo";
        public static final String PATH_COMMENT = "comment";
    
        public static final class PhotoEntry {
    
            public static final Uri CONTENT_URI =
                    BASE_CONTENT_URI.buildUpon().appendPath(PATH_PHOTO).build();
    
            public static final String COLUMN_DISPLAY_NAME = MediaStore.Images.Media.DISPLAY_NAME;
            public static final String COLUMN_DATA = MediaStore.Images.Media.DATA;
            public static final String COLUMN_DESC = MediaStore.Images.Media.DESCRIPTION;
            public static final String COLUMN_DATE_TAKEN = MediaStore.Images.Media.DATE_TAKEN;
            public static final String COLUMN_DATE_ADDED = MediaStore.Images.Media.DATE_ADDED;
            public static final String COLUMN_TITLE = MediaStore.Images.Media.TITLE;
            public static final String COLUMN_SIZE = MediaStore.Images.Media.SIZE;
            public static final String COLUMN_ORIENTATION = MediaStore.Images.Media.ORIENTATION;
            public static final String COLUMN_EXIF_COMMENT = "UserComment";
            public static final String COLUMN_EXIF_AUTHOR = "Author";
    
            // should these simply be image/png??
            public static final String CONTENT_TYPE =
                    ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
            public static final String CONTENT_ITEM_TYPE =
                    ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
    
            // makes an URI to a specific image_id
            public static final Uri buildPhotoWithId(Long photo_id) {
                return CONTENT_URI.buildUpon().appendPath(Long.toString(photo_id)).build();
            }
    
            // since we will "redirect" the URI towards MediaStore, we need to be able to extract IMAGE_ID
            public static Long getImageIdFromUri(Uri uri) {
                return Long.parseLong(uri.getPathSegments().get(1));  // TODO: is it position 1??
            }
    
            // get comment to set in EXIF tag
            public static String getCommentFromUri(Uri uri) {
                return uri.getPathSegments().get(2);  // TODO: is it position 2??
            }
        }
    }
    
    package com.example.android.galleri.app.data;
    
    import android.content.ContentProvider;
    import android.content.ContentValues;
    import android.content.UriMatcher;
    import android.database.Cursor;
    import android.database.MatrixCursor;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.media.ExifInterface;
    import android.net.Uri;
    import android.provider.MediaStore;
    import android.support.v4.content.CursorLoader;
    import android.util.Log;
    
    import java.io.IOException;
    
    
    public class PhotoProvider extends ContentProvider {
    
        // The URI Matcher used by this content provider.
        private static final UriMatcher sUriMatcher = buildUriMatcher();
    
        static final int PHOTO = 100;
        static final int PHOTO_SET_COMMENT = 200;
    
        static UriMatcher buildUriMatcher() {
            // 1) The code passed into the constructor represents the code to return for the root
            // URI.  It's common to use NO_MATCH as the code for this case. Add the constructor below.
            final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
            final String authority = PhotoContract.CONTENT_AUTHORITY;
    
            // 2) Use the addURI function to match each of the types.  Use the constants from
            // WeatherContract to help define the types to the UriMatcher.
    
            // matches photo/<any number> meaning any photo ID
            matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#", PHOTO);
    
            // matches photo/<photo id>/comment/<any comment>
            matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#/" + PhotoContract.PATH_COMMENT + "/*", PHOTO_SET_COMMENT);
    
            // 3) Return the new matcher!
            return matcher;
        }
    
    
        @Override
        public String getType(Uri uri) {
    
            // Use the Uri Matcher to determine what kind of URI this is.
            final int match = sUriMatcher.match(uri);
    
            switch (match) {
                case PHOTO_SET_COMMENT:
                    return PhotoContract.PhotoEntry.CONTENT_TYPE;
                case PHOTO:
                    return PhotoContract.PhotoEntry.CONTENT_TYPE;
                default:
                    throw new UnsupportedOperationException("Unknown uri: " + uri);
            }
        }
    
    
        @Override
        public boolean onCreate() {
            return true;  // enough?
        }
    
    
        @Override
        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
            MatrixCursor retCursor = new MatrixCursor(projection);
    
            // open the specified image through the MediaStore to get base columns
            // then open image file through ExifInterface to get detail columns
            Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
    
            //Uri baseUri = Uri.parse("content://media/external/images/media");
            Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);
    
            String[] MS_projection = {
                    MediaStore.Images.Media.DISPLAY_NAME,
                    MediaStore.Images.Media.DATA,
                    MediaStore.Images.Media.DESCRIPTION,
                    MediaStore.Images.Media.DATE_TAKEN,
                    MediaStore.Images.Media.DATE_ADDED,
                    MediaStore.Images.Media.TITLE,
                    MediaStore.Images.Media.SIZE,
                    MediaStore.Images.Media.ORIENTATION};
    
            // http://androidsnippets.com/get-file-path-of-gallery-image
            Cursor c = getContext().getContentResolver().query(baseUri, MS_projection, null, null, null);
    
            // dump fields (the ones we want -- assuming for now we want ALL fields, in SAME ORDER) into MatrixCursor
            Object[] row = new Object[projection.length];
            row[0] = c.getString(0);  // DISPLAY_NAME
            row[1] = c.getBlob(1);  // etc
            row[2] = c.getString(2);
            row[3] = c.getLong(3);
            row[4] = c.getLong(4);
            row[5] = c.getString(5);
            row[6] = c.getInt(6);
            row[7] = c.getInt(7);
    
            // NB! Extra +2 fields, for EXIF data.
            try {
                ExifInterface exif = new ExifInterface((String)row[1]);
                row[8] = exif.getAttribute("UserComment");
                row[9] = exif.getAttribute("Author");
    
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            retCursor.addRow(row);
    
            return retCursor;
        }
    
        @Override
        public Uri insert(Uri uri, ContentValues values) {
            String comment_to_set = PhotoContract.PhotoEntry.getCommentFromUri(uri);
            Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
    
            Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);
    
            // get DATA (path/filename) from MediaStore -- only need that specific piece of information
            String[] MS_projection = {MediaStore.Images.Media.DATA};
    
            // http://androidsnippets.com/get-file-path-of-gallery-image
            Cursor c = getContext().getContentResolver().query(baseUri, MS_projection, null, null, null);
            String thumbData = c.getString(0);
    
            try {
                ExifInterface exif = new ExifInterface(thumbData);
    
                exif.setAttribute("UserComment", comment_to_set);
                exif.saveAttributes();
    
            } catch (IOException e) {
                e.printStackTrace();
            }
            return PhotoContract.PhotoEntry.buildPhotoWithId(IMAGE_ID);  // return URI to this specific image
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            return 0;
        }
    
        @Override
        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
            return 0;
        }
    }
    
    import android.content.ContentResolver;
    import android.content.ContentUris;
    import android.net.Uri;
    import android.provider.BaseColumns;
    import android.provider.MediaStore;
    
    public class PhotoContract {
    
        public static final String CONTENT_AUTHORITY = "com.example.android.myFunkyApp.app";
    
        public static final Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY);
    
        public static final String PATH_PHOTO = "photo";
        public static final String PATH_COMMENT = "comment";
        //public static final String PATH_AUTHOR = "author";
    
        public static final class ThumbEntry {
            public static final String COLUMN_THUMB_ID = MediaStore.Images.Thumbnails._ID;
            public static final String COLUMN_DATA = MediaStore.Images.Thumbnails.DATA;
            public static final String COLUMN_IMAGE_ID = MediaStore.Images.Thumbnails.IMAGE_ID;
        }
    
        public static final class PhotoEntry {
    
            public static final Uri CONTENT_URI =
                    BASE_CONTENT_URI.buildUpon().appendPath(PATH_PHOTO).build();
    
            public static final String COLUMN_IMAGE_ID = MediaStore.Images.Media._ID;
            public static final String COLUMN_DISPLAY_NAME = MediaStore.Images.Media.DISPLAY_NAME;
            public static final String COLUMN_DATA = MediaStore.Images.Media.DATA;
            public static final String COLUMN_DESC = MediaStore.Images.Media.DESCRIPTION;
            public static final String COLUMN_DATE_TAKEN = MediaStore.Images.Media.DATE_TAKEN;
            public static final String COLUMN_DATE_ADDED = MediaStore.Images.Media.DATE_ADDED;
            public static final String COLUMN_TITLE = MediaStore.Images.Media.TITLE;
            public static final String COLUMN_SIZE = MediaStore.Images.Media.SIZE;
            public static final String COLUMN_ORIENTATION = MediaStore.Images.Media.ORIENTATION;
            public static final String COLUMN_EXIF_COMMENT = "UserComment";
            //public static final String COLUMN_EXIF_AUTHOR = "Author";
    
            public static final String CONTENT_TYPE =
                    ContentResolver.CURSOR_DIR_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
            public static final String CONTENT_ITEM_TYPE =
                    ContentResolver.CURSOR_ITEM_BASE_TYPE + "/" + CONTENT_AUTHORITY + "/" + PATH_PHOTO;
    
            // makes an URI to a specific image_id
            public static final Uri buildPhotoUriWithId(Long photo_id) {
                return CONTENT_URI.buildUpon().appendPath(Long.toString(photo_id)).build();
            }
    
            // since we will "redirect" the URI towards MediaStore, we need to be able to extract IMAGE_ID
            public static Long getImageIdFromUri(Uri uri) {
                return Long.parseLong(uri.getPathSegments().get(1));  // always in position 1
            }
    
        }
    }
    
    import android.content.ContentProvider;
    import android.content.ContentValues;
    import android.content.UriMatcher;
    import android.database.AbstractWindowedCursor;
    import android.database.Cursor;
    import android.database.CursorWindow;
    import android.database.CursorWrapper;
    import android.database.MatrixCursor;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.media.ExifInterface;
    import android.net.Uri;
    import android.provider.MediaStore;
    import android.support.v4.content.CursorLoader;
    import android.util.Log;
    
    import java.io.IOException;
    import java.lang.reflect.Field;
    import java.util.Arrays;
    import java.util.List;
    
    public class PhotoProvider extends ContentProvider {
    
        // The URI Matcher used by this content provider.
        private static final UriMatcher sUriMatcher = buildUriMatcher();
    
        static final int PHOTO = 100;
        static final int PHOTO_COMMENT = 101;
        static final int PHOTO_AUTHOR = 102;
    
        static UriMatcher buildUriMatcher() {
            final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
            final String authority = PhotoContract.CONTENT_AUTHORITY;
    
            // matches photo/<any number> meaning any photo ID
            matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#", PHOTO);
    
            // matches photo/<photo id>/comment/ (comment text in ContentValues)
            matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#/" + PhotoContract.PATH_COMMENT, PHOTO_COMMENT);
            // matches photo/<photo id>/author/ (author name in ContentValues)
            //matcher.addURI(authority, PhotoContract.PATH_PHOTO + "/#/" + PhotoContract.PATH_AUTHOR, PHOTO_AUTHOR);
    
            return matcher;
        }
    
    
    
        @Override
        public String getType(Uri uri) {
    
            // Use the Uri Matcher to determine what kind of URI this is.
            final int match = sUriMatcher.match(uri);
    
            // Note: We always return single row of data, so content-type is always "a dir"
            switch (match) {
                case PHOTO_COMMENT:
                    return PhotoContract.PhotoEntry.CONTENT_TYPE;
                case PHOTO_AUTHOR:
                    return PhotoContract.PhotoEntry.CONTENT_TYPE;
                case PHOTO:
                    return PhotoContract.PhotoEntry.CONTENT_TYPE;
                default:
                    throw new UnsupportedOperationException("Unknown uri: " + uri);
            }
        }
    
    
        @Override
        public boolean onCreate() {
            return true;
        }
    
    
        @Override
        public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
            MatrixCursor retCursor = new MatrixCursor(projection);
    
            // open the specified image through the MediaStore to get base columns
            // then open image file through ExifInterface to get detail columns
            Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
            Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);
    
            // http://androidsnippets.com/get-file-path-of-gallery-image
            // run query against MediaStore, projection = null means "get all fields"
            Cursor c = getContext().getContentResolver().query(baseUri, null, null, null, null);
            if (!c.moveToFirst()) {
                return null;
            }
            // match returned fields against projection, and copy into row[]
            Object[] row = new Object[projection.length];
    
            int i = 0;
    
            /* // Cursor.getType() Requires API level > 10...
            for (String colName : projection) {
                int idx = c.getColumnIndex(colName);
                if (idx <= 0) return null; // ERROR
                int colType = c.getType(idx);
                switch (colType) {
                    case Cursor.FIELD_TYPE_INTEGER: {
                        row[i++] = c.getLong(idx);
                        break;
                    }
                    case Cursor.FIELD_TYPE_FLOAT: {
                        row[i++] = c.getFloat(idx);
                        break;
                    }
                    case Cursor.FIELD_TYPE_STRING: {
                        row[i++] = c.getString(idx);
                        break;
                    }
                    case Cursor.FIELD_TYPE_BLOB: {
                        row[i++] = c.getBlob(idx);
                        break;
                    }
                }
            }
            */
    
            //http://stackoverflow.com/questions/11658239/cursor-gettype-for-api-level-11
            CursorWrapper cw = (CursorWrapper)c;
            Class<?> cursorWrapper = CursorWrapper.class;
            Field mCursor = null;
            try {
                mCursor = cursorWrapper.getDeclaredField("mCursor");
                mCursor.setAccessible(true);
                AbstractWindowedCursor abstractWindowedCursor = (AbstractWindowedCursor)mCursor.get(cw);
                CursorWindow cursorWindow = abstractWindowedCursor.getWindow();
                int pos = abstractWindowedCursor.getPosition();
                // NB! Expect resulting cursor to contain data in same order as projection!
                for (String colName : projection) {
                    int idx = c.getColumnIndex(colName);
    
                    // simple solution: If column name NOT FOUND in MediaStore, assume it's an EXIF tag
                    // and skip
                    if (idx >= 0) {
                        if (cursorWindow.isNull(pos, idx)) {
                            //Cursor.FIELD_TYPE_NULL
                            row[i++] = null;
                        } else if (cursorWindow.isLong(pos, idx)) {
                            //Cursor.FIELD_TYPE_INTEGER
                            row[i++] = c.getLong(idx);
                        } else if (cursorWindow.isFloat(pos, idx)) {
                            //Cursor.FIELD_TYPE_FLOAT
                            row[i++] = c.getFloat(idx);
                        } else if (cursorWindow.isString(pos, idx)) {
                            //Cursor.FIELD_TYPE_STRING
                            row[i++] = c.getString(idx);
                        } else if (cursorWindow.isBlob(pos, idx)) {
                            //Cursor.FIELD_TYPE_BLOB
                            row[i++] = c.getBlob(idx);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            // have now handled the first i fields in projection. If there are any more, we expect
            // these to be valid EXIF tags. Should obviously make this more robust...
            try {
                ExifInterface exif = new ExifInterface((String) row[2]);
                while (i < projection.length) {
                    row[i] = exif.getAttribute("UserComment"); //projection[i]);  // String (or null)
                    i++;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
    
            retCursor.addRow(row);
            return retCursor;
        }
    
    
        @Override
        public Uri insert(Uri uri, ContentValues values) {
            return null;
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            return 0;
        }
    
        @Override
        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
            // URI identifies IMAGE_ID and which EXIF tag to set; content://AUTH/photo/<image_id>/comment or /author
    
            // first, get IMAGE_ID and prepare URI (to get file path from MediaStore)
            Long IMAGE_ID = PhotoContract.PhotoEntry.getImageIdFromUri(uri);
            Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            baseUri = Uri.withAppendedPath(baseUri, ""+ IMAGE_ID);
    
            // get DATA (path/filename) from MediaStore -- only need that specific piece of information
            String[] MS_projection = {MediaStore.Images.Media.DATA};
    
            // http://androidsnippets.com/get-file-path-of-gallery-image
            Cursor c = getContext().getContentResolver().query(baseUri, MS_projection, null, null, null);
            if (!c.moveToFirst()) return -1;  // can't get image path/filename...
            String thumbData = c.getString(0);
    
            // then, use URIMatcher to identify the "operation" (i.e., which tag to set)
            final int match = sUriMatcher.match(uri);
    
            String EXIF_tag;
            switch (match) {
                case PHOTO_COMMENT: {
                    EXIF_tag = "UserComment";
                    break;
                }
                case PHOTO_AUTHOR: {
                    EXIF_tag = "Author";
                    break;
                }
                default:
                    throw new UnsupportedOperationException("Unknown uri: " + uri);
            }
            if (EXIF_tag == null) return -1;
    
            // finally, set tag in image
            try {
                ExifInterface exif = new ExifInterface(thumbData);
                String textToSet = values.get(EXIF_tag).toString();
    
                if (textToSet == null)
                    throw new IOException("Can't retrieve text to set from ContentValues, on key = " + EXIF_tag);
                if (textToSet.length() > 48)
                    throw new IOException("Data too long (" + textToSet.length() + "), on key = " + EXIF_tag);
    
                exif.setAttribute(EXIF_tag, textToSet);
                exif.saveAttributes();
    
            } catch (IOException e) {
                e.printStackTrace();
            }
            return 1; // 1 image updated
        }
    }