前言:
前面两篇文章我们分别介绍了MediaScanner&MediaProvider的概述:https://blog.csdn.net/cheriyou_/article/details/1025859 还有MediaScanner的具体实现https://blog.csdn.net/cheriyou_/article/details/90772051。在第二篇文章中,我们介绍了无论是需要在数据库中插入还是改动文件信息,最终调用的都是MediaProvider的函数。MediaProvider最终是怎么和数据库交互的呢?本文将继续研究探索。
external.db数据库的内容:
1. tables


2. 每个table的参数


CREATE TABLE "files" ( // files表格的内容太多,用定义描述 "
_id" INTEGER, "_data" BLOB UNIQUE COLLATE NOCASE, "_size" INTEGER, "format" INTEGER, "parent" INTEGER, "date_added" INTEGER, "date_modified" INTEGER, "mime_type" TEXT, "title" TEXT, "description" TEXT, "_display_name" TEXT, "picasa_id" TEXT, "orientation" INTEGER, "latitude" DOUBLE, "longitude" DOUBLE, "datetaken" INTEGER, "mini_thumb_magic" INTEGER, "bucket_id" TEXT, "bucket_display_name" TEXT, "isprivate" INTEGER, "title_key" TEXT, "artist_id" INTEGER, "album_id" INTEGER, "composer" TEXT, "track" INTEGER, "year" INTEGER CHECK("year" != 0), "is_ringtone" INTEGER, "is_music" INTEGER, "is_alarm" INTEGER, "is_notification" INTEGER, "is_podcast" INTEGER, "album_artist" TEXT, "duration" INTEGER, "bookmark" INTEGER, "artist" TEXT, "album" TEXT, "resolution" TEXT, "tags" TEXT, "category" TEXT, "language" TEXT, "mini_thumb_data" TEXT, "name" TEXT, "media_type" INTEGER, "old_id" INTEGER, "is_drm" INTEGER, "width" INTEGER, "height" INTEGER, "title_resource_uri" TEXT, PRIMARY KEY("_id" AUTOINCREMENT) );
MediaProvider的基本操作:
对数据库的操作,一般都分为4个部分: 增(insert)、删(delete)、改(update)、查(query)。下面详细介绍MediaProvider中每个部分的具体处理。
1. insert
// packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
int match = URI_MATCHER.match(uri);
ArrayList<Long> notifyRowIds = new ArrayList<Long>();
Uri newUri = insertInternal(uri, match, initialValues, notifyRowIds);
if (newUri != null && match != MTP_OBJECTS) {
getContext().getContentResolver().notifyChange(uri, null, match != MEDIA_SCANNER
? ContentResolver.NOTIFY_SKIP_NOTIFY_FOR_DESCENDANTS : 0);
if (match != MEDIA_SCANNER) {
getContext().getContentResolver().notifyChange(newUri, null, 0);
}
}
return newUri;
}
private Uri insertInternal(Uri uri, int match, ContentValues initialValues,
ArrayList<Long> notifyRowIds) {
final String volumeName = getVolumeName(uri);
long rowId;
if (match == MEDIA_SCANNER) { // 如果是MEDIA_SCANNER,不用insert数据,直接返回MediaStore.getMediaScannerUri()即可。
mMediaScannerVolume = initialValues.getAsString(MediaStore.MEDIA_SCANNER_VOLUME);
DatabaseHelper database = getDatabaseForUri(
Uri.parse("content://media/" + mMediaScannerVolume + "/audio"));
if (database == null) {
Log.w(TAG, "no database for scanned volume " + mMediaScannerVolume);
} else {
database.mScanStartTime = SystemClock.currentTimeMicro();
}
return MediaStore.getMediaScannerUri();
}
String genre = null;
String path = null;
if (initialValues != null) {
genre = initialValues.getAsString(Audio.AudioColumns.GENRE);
initialValues.remove(Audio.AudioColumns.GENRE);
path = initialValues.getAsString(MediaStore.MediaColumns.DATA);
}
Uri newUri = null;
DatabaseHelper helper = getDatabaseForUri(uri); // 根据输入的uri取出对应的DatabaseHelper对象.
if (helper == null && match != VOLUMES && match != MTP_CONNECTED) {
throw new UnsupportedOperationException(
"Unknown URI: " + uri);
}
SQLiteDatabase db = ((match == VOLUMES || match == MTP_CONNECTED) ? null
: helper.getWritableDatabase()); // 获取SQLiteDatabase
switch (match) { // 此处会根据需要写入的类型做不同处理。仅列举几个典型的类型
case IMAGES_MEDIA: { // 如果是图片媒体信息
rowId = insertFile(helper, uri, initialValues,
FileColumns.MEDIA_TYPE_IMAGE, true, notifyRowIds); // 先调用insertFile
if (rowId > 0) {
MediaDocumentsProvider.onMediaStoreInsert(
getContext(), volumeName, FileColumns.MEDIA_TYPE_IMAGE, rowId);
newUri = ContentUris.withAppendedId(
Images.Media.getContentUri(volumeName), rowId);
}
break;
}
case IMAGES_THUMBNAILS: {// 如果是图片缩略图
ContentValues values = ensureFile(helper.mInternal, initialValues, ".jpg",
"DCIM/.thumbnails");
helper.mNumInserts++;
rowId = db.insert("thumbnails", "name", values); // 调用db.insert
if (rowId > 0) {
newUri = ContentUris.withAppendedId(Images.Thumbnails.
getContentUri(volumeName), rowId);
}
break;
}
case VIDEO_THUMBNAILS: {
ContentValues values = ensureFile(helper.mInternal, initialValues, ".jpg",
"DCIM/.thumbnails");
helper.mNumInserts++;
rowId = db.insert("videothumbnails", "name", values);
if (rowId > 0) {
newUri = ContentUris.withAppendedId(Video.Thumbnails.
getContentUri(volumeName), rowId);
}
break;
}
case VIDEO_MEDIA: {
rowId = insertFile(helper, uri, initialValues,
FileColumns.MEDIA_TYPE_VIDEO, true, notifyRowIds);
if (rowId > 0) {
MediaDocumentsProvider.onMediaStoreInsert(
getContext(), volumeName, FileColumns.MEDIA_TYPE_VIDEO, rowId);
newUri = ContentUris.withAppendedId(
Video.Media.getContentUri(volumeName), rowId);
}
break;
}
default:
throw new UnsupportedOperationException("Invalid URI " + uri);
}
if (path != null && path.toLowerCase(Locale.US).endsWith("/.nomedia")) {
// need to set the media_type of all the files below this folder to 0
processNewNoMediaPath(helper, db, path);
}
return newUri;
}
// frameworks/base/core/java/android/database/sqlite/SQLiteDatabase.java
public long insert(String table, String nullColumnHack, ContentValues values) {
try {
return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + values, e);
return -1;
}
}
public long insertWithOnConflict(String table, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
acquireReference();
try {
StringBuilder sql = new StringBuilder(); // 创建一个StringBuilder对象,把需要的信息写入
sql.append("INSERT");
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(" INTO ");
sql.append(table); // 本次insert的目标table
sql.append('(');
Object[] bindArgs = null;
int size = (initialValues != null && !initialValues.isEmpty())
? initialValues.size() : 0;
if (size > 0) {
bindArgs = new Object[size];
int i = 0;
for (String colName : initialValues.keySet()) {
sql.append((i > 0) ? "," : "");
sql.append(colName);
bindArgs[i++] = initialValues.get(colName);
}
sql.append(')');
sql.append(" VALUES (");
for (i = 0; i < size; i++) {
sql.append((i > 0) ? ",?" : "?");
}
} else {
sql.append(nullColumnHack + ") VALUES (NULL");
}
sql.append(')');
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
try {
return statement.executeInsert(); // 插入数据
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
// frameworks/base/core/java/android/database/sqlite/SQLiteStatement.java
public long executeInsert() {
acquireReference(); // 判断mReferenceCount是否<=0。若是则返回错误,否则mReferenceCount++
try {
return getSession().executeForLastInsertedRowId(
getSql(), getBindArgs(), getConnectionFlags(), null);
} catch (SQLiteDatabaseCorruptException ex) {
onCorruption();
throw ex;
} finally {
releaseReference();
}
}我们看到SQLiteStatement这里就不继续往下看了。SQLiteStatement是安卓的对SQLite优化之后的接口,其插入等操作都比SQLite响应速度快。
2. delete
// packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
public int delete(Uri uri, String userWhere, String[] whereArgs) {
uri = safeUncanonicalize(uri);
int count;
int match = URI_MATCHER.match(uri);
// handle MEDIA_SCANNER before calling getDatabaseForUri()
if (match == MEDIA_SCANNER) {
......
return 1;
}
if (match == VOLUMES_ID) {
detachVolume(uri);
count = 1;
} else if (match == MTP_CONNECTED) {
......
} else {
final String volumeName = getVolumeName(uri);
final boolean isExternal = "external".equals(volumeName);
DatabaseHelper database = getDatabaseForUri(uri);
database.mNumDeletes++;
SQLiteDatabase db = database.getWritableDatabase();
TableAndWhere tableAndWhere = getTableAndWhere(uri, match, userWhere);
if (tableAndWhere.table.equals("files")) {
String deleteparam = uri.getQueryParameter(MediaStore.PARAM_DELETE_DATA);
if (deleteparam == null || ! deleteparam.equals("false")) {
database.mNumQueries++;
Cursor c = db.query(tableAndWhere.table,
sMediaTypeDataId,
tableAndWhere.where, whereArgs,
null /* groupBy */, null /* having */, null /* orderBy */);
String [] idvalue = new String[] { "" };
String [] playlistvalues = new String[] { "", "" };
MiniThumbFile imageMicroThumbs = null;
MiniThumbFile videoMicroThumbs = null;
try {
while (c.moveToNext()) {
final int mediaType = c.getInt(0);
final String data = c.getString(1);
final long id = c.getLong(2);
if (mediaType == FileColumns.MEDIA_TYPE_IMAGE) {
deleteIfAllowed(uri, data);
MediaDocumentsProvider.onMediaStoreDelete(getContext(),
volumeName, FileColumns.MEDIA_TYPE_IMAGE, id);
idvalue[0] = String.valueOf(id);
database.mNumQueries++;
Cursor cc = db.query("thumbnails", sDataOnlyColumn,
"image_id=?", idvalue,
null /* groupBy */, null /* having */,
null /* orderBy */);
try {
while (cc.moveToNext()) {
deleteIfAllowed(uri, cc.getString(0));
}
database.mNumDeletes++;
db.delete("thumbnails", "image_id=?", idvalue);
} finally {
IoUtils.closeQuietly(cc);
}
if (isExternal) {
if (imageMicroThumbs == null) {
imageMicroThumbs = MiniThumbFile.instance(
Images.Media.EXTERNAL_CONTENT_URI);
}
imageMicroThumbs.eraseMiniThumb(id);
}
} else if (mediaType == FileColumns.MEDIA_TYPE_VIDEO) {
deleteIfAllowed(uri, data);
MediaDocumentsProvider.onMediaStoreDelete(getContext(),
volumeName, FileColumns.MEDIA_TYPE_VIDEO, id);
idvalue[0] = String.valueOf(id);
database.mNumQueries++;
Cursor cc = db.query("videothumbnails", sDataOnlyColumn,
"video_id=?", idvalue, null, null, null);
try {
while (cc.moveToNext()) {
deleteIfAllowed(uri, cc.getString(0));
}
database.mNumDeletes++;
db.delete("videothumbnails", "video_id=?", idvalue);
} finally {
IoUtils.closeQuietly(cc);
}
if (isExternal) {
if (videoMicroThumbs == null) {
videoMicroThumbs = MiniThumbFile.instance(
Video.Media.EXTERNAL_CONTENT_URI);
}
videoMicroThumbs.eraseMiniThumb(id);
}
} else if (mediaType == FileColumns.MEDIA_TYPE_AUDIO) {
if (!database.mInternal) {
MediaDocumentsProvider.onMediaStoreDelete(getContext(),
volumeName, FileColumns.MEDIA_TYPE_AUDIO, id);
idvalue[0] = String.valueOf(id);
database.mNumDeletes += 2; // also count the one below
db.delete("audio_genres_map", "audio_id=?", idvalue);
// for each playlist that the item appears in, move
// all the items behind it forward by one
Cursor cc = db.query("audio_playlists_map",
sPlaylistIdPlayOrder,
"audio_id=?", idvalue, null, null, null);
try {
while (cc.moveToNext()) {
playlistvalues[0] = "" + cc.getLong(0);
playlistvalues[1] = "" + cc.getInt(1);
database.mNumUpdates++;
db.execSQL("UPDATE audio_playlists_map" +
" SET play_order=play_order-1" +
" WHERE playlist_id=? AND play_order>?",
playlistvalues);
}
db.delete("audio_playlists_map", "audio_id=?", idvalue);
} finally {
IoUtils.closeQuietly(cc);
}
}
} else if (mediaType == FileColumns.MEDIA_TYPE_PLAYLIST) {
// TODO, maybe: remove the audio_playlists_cleanup trigger and
// implement functionality here (clean up the playlist map)
}
}
} finally {
IoUtils.closeQuietly(c);
if (imageMicroThumbs != null) {
imageMicroThumbs.deactivate();
}
if (videoMicroThumbs != null) {
videoMicroThumbs.deactivate();
}
}
// Do not allow deletion if the file/object is referenced as parent
// by some other entries. It could cause database corruption.
if (!TextUtils.isEmpty(tableAndWhere.where)) {
tableAndWhere.where =
"(" + tableAndWhere.where + ")" +
" AND (_id NOT IN (SELECT parent FROM files" +
" WHERE NOT (" + tableAndWhere.where + ")))";
} else {
tableAndWhere.where = ID_NOT_PARENT_CLAUSE;
}
}db
}
switch (match) {
case MTP_OBJECTS:
case MTP_OBJECTS_ID:
database.mNumDeletes++;
count = db.delete("files", tableAndWhere.where, whereArgs);
break;
case AUDIO_GENRES_ID_MEMBERS:
database.mNumDeletes++;
count = db.delete("audio_genres_map",
tableAndWhere.where, whereArgs);
break;
case IMAGES_THUMBNAILS_ID:
case IMAGES_THUMBNAILS:
case VIDEO_THUMBNAILS_ID:
case VIDEO_THUMBNAILS:
// Delete the referenced files first.
Cursor c = db.query(tableAndWhere.table,
sDataOnlyColumn,
tableAndWhere.where, whereArgs, null, null, null);
if (c != null) {
try {
while (c.moveToNext()) {
deleteIfAllowed(uri, c.getString(0));
}
} finally {
IoUtils.closeQuietly(c);
}
}
database.mNumDeletes++;
count = db.delete(tableAndWhere.table,
tableAndWhere.where, whereArgs);
break;
default:
database.mNumDeletes++;
count = db.delete(tableAndWhere.table,
tableAndWhere.where, whereArgs);
break;
}
Uri notifyUri = Uri.parse("content://" + MediaStore.AUTHORITY + "/" + volumeName);
getContext().getContentResolver().notifyChange(notifyUri, null);
}
return count;
}
// frameworks/base/core/java/android/database/sqlite/SQLiteDatabase.java
public int delete(String table, String whereClause, String[] whereArgs) {
acquireReference();
try {
SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table +
(!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs); // 构造SQL删除命令
try {
return statement.executeUpdateDelete(); // 删除executeUpdateDelete
} finally {
statement.close();
}
} finally {
releaseReference();
}
}3. update
// packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
@Override
public int update(Uri uri, ContentValues initialValues, String userWhere,
String[] whereArgs) {
uri = safeUncanonicalize(uri);
int count;
int match = URI_MATCHER.match(uri);
DatabaseHelper helper = getDatabaseForUri(uri);
helper.mNumUpdates++;
SQLiteDatabase db = helper.getWritableDatabase();
TableAndWhere tableAndWhere = getTableAndWhere(uri, match, userWhere); // 获取tableAndWhere
if (initialValues.containsKey(FileColumns.MEDIA_TYPE)) {
long newMediaType = initialValues.getAsLong(FileColumns.MEDIA_TYPE);
helper.mNumQueries++;
Cursor cursor = db.query(tableAndWhere.table, sMediaTableColumns,
tableAndWhere.where, whereArgs, null, null, null); // 通过query获取cursor
try {
while (cursor != null && cursor.moveToNext()) {
long curMediaType = cursor.getLong(1);
if (curMediaType == FileColumns.MEDIA_TYPE_IMAGE &&
newMediaType != FileColumns.MEDIA_TYPE_IMAGE) { // 把image类型改成别的类型
Log.i(TAG, "need to remove image thumbnail for id " + cursor.getString(0));
removeThumbnailFor(Images.Media.EXTERNAL_CONTENT_URI,
db, cursor.getLong(0));
} else if (curMediaType == FileColumns.MEDIA_TYPE_VIDEO &&
newMediaType != FileColumns.MEDIA_TYPE_VIDEO) { // 把video类型改成别的类型
Log.i(TAG, "need to remove video thumbnail for id " + cursor.getString(0));
removeThumbnailFor(Video.Media.EXTERNAL_CONTENT_URI,
db, cursor.getLong(0));
}
}
} finally {
IoUtils.closeQuietly(cursor);
}
}
if ((match == MTP_OBJECTS || match == MTP_OBJECTS_ID || match == FILES_DIRECTORY)
&& initialValues != null
// Is a rename operation
&& ((initialValues.size() == 1 && initialValues.containsKey(FileColumns.DATA))
// Is a move operation
|| (initialValues.size() == 2 && initialValues.containsKey(FileColumns.DATA)
&& initialValues.containsKey(FileColumns.PARENT)))) {
String oldPath = null;
String newPath = initialValues.getAsString(MediaStore.MediaColumns.DATA);
mDirectoryCache.remove(newPath);
// MtpDatabase will rename the directory first, so we test the new file name
File f = new File(newPath);
if (newPath != null && f.isDirectory()) {
helper.mNumQueries++;
Cursor cursor = db.query(tableAndWhere.table, PATH_PROJECTION,
userWhere, whereArgs, null, null, null); // 通过query获取cursor
try {
if (cursor != null && cursor.moveToNext()) {
oldPath = cursor.getString(1);
}
} finally {
IoUtils.closeQuietly(cursor);
}
if (oldPath != null) {
mDirectoryCache.remove(oldPath);
// first rename the row for the directory
helper.mNumUpdates++;
count = db.update(tableAndWhere.table, initialValues,
tableAndWhere.where, whereArgs); // 调用update
if (count > 0) {
// update the paths of any files and folders contained in the directory
Object[] bindArgs = new Object[] {
newPath,
oldPath.length() + 1,
oldPath + "/",
oldPath + "0",
// update bucket_display_name and bucket_id based on new path
f.getName(),
f.toString().toLowerCase().hashCode()
};
helper.mNumUpdates++;
db.execSQL("UPDATE files SET _data=?1||SUBSTR(_data, ?2)" +
// also update bucket_display_name
",bucket_display_name=?5" +
",bucket_id=?6" +
" WHERE _data >= ?3 AND _data < ?4;",
bindArgs); // 调用execSQL,最终调用的是SQLiteStatement::executeUpdateDelete()
}
if (count > 0 && !db.inTransaction()) {
getContext().getContentResolver().notifyChange(uri, null);
}
if (f.getName().startsWith(".")) {
// the new directory name is hidden
processNewNoMediaPath(helper, db, newPath);
}
return count;
}
} else if (newPath.toLowerCase(Locale.US).endsWith("/.nomedia")) {
processNewNoMediaPath(helper, db, newPath);
}
}
switch (match) {
case AUDIO_MEDIA:
case AUDIO_MEDIA_ID:
{
......
}
break;
case IMAGES_MEDIA:
case IMAGES_MEDIA_ID:
case VIDEO_MEDIA:
case VIDEO_MEDIA_ID:
{
ContentValues values = new ContentValues(initialValues);
values.remove(ImageColumns.BUCKET_ID);
values.remove(ImageColumns.BUCKET_DISPLAY_NAME);
String data = values.getAsString(MediaColumns.DATA);
if (data != null) {
computeBucketValues(data, values);
}
computeTakenTime(values);
helper.mNumUpdates++;
count = db.update(tableAndWhere.table, values,
tableAndWhere.where, whereArgs);
if (count > 0 && values.getAsString(MediaStore.MediaColumns.DATA) != null) {
helper.mNumQueries++;
Cursor c = db.query(tableAndWhere.table,
READY_FLAG_PROJECTION, tableAndWhere.where,
whereArgs, null, null, null);
if (c != null) {
try {
while (c.moveToNext()) {
long magic = c.getLong(2);
if (magic == 0) {
requestMediaThumbnail(c.getString(1), uri,
MediaThumbRequest.PRIORITY_NORMAL, 0);
}
}
} finally {
IoUtils.closeQuietly(c);
}
}
}
}
break;
default:
helper.mNumUpdates++;
count = db.update(tableAndWhere.table, initialValues,
tableAndWhere.where, whereArgs);
break;
}
if (count > 0 && !db.inTransaction()) {
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}4. query
// packages/providers/MediaProvider/src/com/android/providers/media/MediaProvider.java
public Cursor query(Uri uri, String[] projectionIn, String selection,
String[] selectionArgs, String sort) {
uri = safeUncanonicalize(uri);
int table = URI_MATCHER.match(uri);
List<String> prependArgs = new ArrayList<String>();
if (table == MEDIA_SCANNER) {
if (mMediaScannerVolume == null) {
return null;
} else {
// create a cursor to return volume currently being scanned by the media scanner
MatrixCursor c = new MatrixCursor(new String[] {MediaStore.MEDIA_SCANNER_VOLUME});
c.addRow(new String[] {mMediaScannerVolume});
return c;
}
}
String groupBy = null;
DatabaseHelper helper = getDatabaseForUri(uri);
helper.mNumQueries++;
SQLiteDatabase db = helper.getReadableDatabase();
if (db == null) return null;
SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); // 构造查询命令
String limit = uri.getQueryParameter("limit");
String filter = uri.getQueryParameter("filter");
String [] keywords = null;
if (filter != null) {
filter = Uri.decode(filter).trim();
if (!TextUtils.isEmpty(filter)) {
String [] searchWords = filter.split(" ");
keywords = new String[searchWords.length];
for (int i = 0; i < searchWords.length; i++) {
String key = MediaStore.Audio.keyFor(searchWords[i]);
key = key.replace("\\", "\\\\");
key = key.replace("%", "\\%");
key = key.replace("_", "\\_");
keywords[i] = key;
}
}
}
if (uri.getQueryParameter("distinct") != null) {
qb.setDistinct(true);
}
boolean hasThumbnailId = false;
if (table == IMAGES_MEDIA || table == IMAGES_MEDIA_ID || table == IMAGES_THUMBNAILS_ID
|| table == IMAGES_THUMBNAILS || table == VIDEO_MEDIA || table == VIDEO_MEDIA_ID
|| table == VIDEO_THUMBNAILS || table == VIDEO_THUMBNAILS_ID) {
if (1 == Settings.Secure.getInt(getContext().getContentResolver(),
ExtraSettings.Secure.PRIVACY_MODE_ENABLED, 0)) {
return null;
}
}
switch (table) {
case VIDEO_MEDIA:
qb.setTables("video");
break;
case VIDEO_MEDIA_ID:
qb.setTables("video");
qb.appendWhere("_id=?");
prependArgs.add(uri.getPathSegments().get(3));
break;
case VIDEO_THUMBNAILS_ID:
hasThumbnailId = true;
case VIDEO_THUMBNAILS:
if (!queryThumbnail(qb, uri, "videothumbnails", "video_id", hasThumbnailId)) {
return null;
}
break;
default:
throw new IllegalStateException("Unknown URL: " + uri.toString());
}
Cursor c = qb.query(db, projectionIn, selection,
combine(prependArgs, selectionArgs), groupBy, null, sort, limit); // 获取Cursor
if (c != null) {
String nonotify = uri.getQueryParameter("nonotify");
if (nonotify == null || !nonotify.equals("1")) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
}
return c;
}