package net.micode.notes.data; import android.app.SearchManager; import android.content.ContentProvider; import android.content.ContentUris; import android.content.ContentValues; import android.content.Intent; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.net.Uri; import android.text.TextUtils; import android.util.Log; import net.micode.notes.R; import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.NotesDatabaseHelper.TABLE; /** * 便签内容提供者,实现Android ContentProvider接口 * 负责管理便签数据的增删改查,并支持搜索建议功能 */ public class NotesProvider extends ContentProvider { // URI匹配器,用于解析不同的URI请求 private static final UriMatcher mMatcher; // 数据库帮助类实例 private NotesDatabaseHelper mHelper; // 日志标签 private static final String TAG = "NotesProvider"; // URI匹配器的类型常量 private static final int URI_NOTE = 1; // 便签列表 private static final int URI_NOTE_ITEM = 2; // 单个便签 private static final int URI_DATA = 3; // 便签数据列表 private static final int URI_DATA_ITEM = 4; // 单个便签数据 private static final int URI_SEARCH = 5; // 搜索请求 private static final int URI_SEARCH_SUGGEST = 6; // 搜索建议请求 static { // 初始化URI匹配器 mMatcher = new UriMatcher(UriMatcher.NO_MATCH); // 匹配便签列表URI:content://micode_notes/note mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); // 匹配单个便签URI:content://micode_notes/note/123 mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); // 匹配便签数据列表URI:content://micode_notes/data mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); // 匹配单个便签数据URI:content://micode_notes/data/456 mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); // 匹配搜索URI:content://micode_notes/search mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); // 匹配搜索建议URI(带查询词路径) mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); } /** * 搜索投影字段定义 * 包含便签ID、搜索建议文本、图标、点击动作等 * x'0A' 表示SQLite中的换行符,替换为空格并修剪前后空白 */ private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," // 传递便签ID到搜索结果 + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," // 第一行文本(摘要) + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," // 第二行文本(重复摘要,适配UI) + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," // 搜索结果图标资源ID + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," // 点击动作:查看便签 + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; // 数据类型:文本便签 /** * 便签内容搜索查询语句 * 查询条件:摘要包含搜索词、非回收站文件夹、类型为普通便签 */ private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION + " FROM " + TABLE.NOTE + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER // 排除回收站 + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; // 仅普通便签 @Override public boolean onCreate() { // 获取数据库帮助类的单例实例 mHelper = NotesDatabaseHelper.getInstance(getContext()); return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Cursor c = null; SQLiteDatabase db = mHelper.getReadableDatabase(); String id = null; switch (mMatcher.match(uri)) { case URI_NOTE: // 查询便签列表 c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder); break; case URI_NOTE_ITEM: // 获取路径中的便签ID(如note/123中的123) id = uri.getPathSegments().get(1); // 查询单个便签,拼接条件:ID=路径ID + 额外查询条件 c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder); break; case URI_DATA: // 查询便签数据列表 c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder); break; case URI_DATA_ITEM: // 获取路径中的数据ID(如data/456中的456) id = uri.getPathSegments().get(1); // 查询单个便签数据 c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder); break; case URI_SEARCH: case URI_SEARCH_SUGGEST: // 搜索和搜索建议不支持排序、投影等参数 if (sortOrder != null || projection != null) { throw new IllegalArgumentException( "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); } String searchString = null; if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { // 从路径中获取搜索词(如suggest_query/关键词) if (uri.getPathSegments().size() > 1) { searchString = uri.getPathSegments().get(1); } } else { // 从查询参数中获取搜索词(如pattern=关键词) searchString = uri.getQueryParameter("pattern"); } if (TextUtils.isEmpty(searchString)) { return null; } try { // 拼接模糊查询参数(%关键词%) searchString = String.format("%%%s%%", searchString); // 执行原生SQL查询搜索结果 c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, new String[] { searchString }); } catch (IllegalStateException ex) { Log.e(TAG, "got exception: " + ex.toString()); } break; default: throw new IllegalArgumentException("Unknown URI " + uri); } if (c != null) { // 设置查询结果的通知URI,以便数据变化时更新界面 c.setNotificationUri(getContext().getContentResolver(), uri); } return c; } @Override public Uri insert(Uri uri, ContentValues values) { SQLiteDatabase db = mHelper.getWritableDatabase(); long dataId = 0, noteId = 0, insertedId = 0; switch (mMatcher.match(uri)) { case URI_NOTE: // 插入新便签 insertedId = noteId = db.insert(TABLE.NOTE, null, values); break; case URI_DATA: // 插入便签数据时需关联便签ID if (values.containsKey(DataColumns.NOTE_ID)) { noteId = values.getAsLong(DataColumns.NOTE_ID); } else { Log.d(TAG, "Wrong data format without note id:" + values.toString()); } insertedId = dataId = db.insert(TABLE.DATA, null, values); break; default: throw new IllegalArgumentException("Unknown URI " + uri); } // 通知便签数据变化(更新UI) if (noteId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); } if (dataId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); } // 返回插入记录的URI(带ID) return ContentUris.withAppendedId(uri, insertedId); } @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean deleteData = false; switch (mMatcher.match(uri)) { case URI_NOTE: // 删除便签列表,排除系统文件夹(ID>0) selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; count = db.delete(TABLE.NOTE, selection, selectionArgs); break; case URI_NOTE_ITEM: // 删除单个便签,获取路径中的ID id = uri.getPathSegments().get(1); long noteId = Long.valueOf(id); // 系统文件夹(ID≤0)不允许删除 if (noteId <= 0) { break; } count = db.delete(TABLE.NOTE, NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); break; case URI_DATA: case URI_DATA_ITEM: // 删除便签数据 count = db.delete(mMatcher.match(uri) == URI_DATA ? TABLE.DATA : TABLE.DATA, mMatcher.match(uri) == URI_DATA_ITEM ? DataColumns.ID + "=" + id + parseSelection(selection) : selection, selectionArgs); deleteData = true; break; default: throw new IllegalArgumentException("Unknown URI " + uri); } if (count > 0) { // 通知数据变化 if (deleteData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); } getContext().getContentResolver().notifyChange(uri, null); } return count; } @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean updateData = false; switch (mMatcher.match(uri)) { case URI_NOTE: case URI_NOTE_ITEM: // 更新便签时增加版本号(乐观锁机制) increaseNoteVersion(mMatcher.match(uri) == URI_NOTE_ITEM ? Long.valueOf(uri.getPathSegments().get(1)) : -1, selection, selectionArgs); // 执行更新操作 count = db.update(mMatcher.match(uri) == URI_NOTE ? TABLE.NOTE : TABLE.NOTE, values, mMatcher.match(uri) == URI_NOTE_ITEM ? NoteColumns.ID + "=" + uri.getPathSegments().get(1) + parseSelection(selection) : selection, selectionArgs); break; case URI_DATA: case URI_DATA_ITEM: // 更新便签数据 count = db.update(mMatcher.match(uri) == URI_DATA ? TABLE.DATA : TABLE.DATA, values, mMatcher.match(uri) == URI_DATA_ITEM ? DataColumns.ID + "=" + uri.getPathSegments().get(1) + parseSelection(selection) : selection, selectionArgs); updateData = true; break; default: throw new IllegalArgumentException("Unknown URI " + uri); } if (count > 0) { // 通知数据变化 if (updateData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); } getContext().getContentResolver().notifyChange(uri, null); } return count; } /** * 拼接额外查询条件(在原有条件前添加" AND ") * @param selection 原始查询条件 * @return 拼接后的条件字符串 */ private String parseSelection(String selection) { return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); } /** * 增加便签版本号(用于数据同步或乐观锁) * @param id 单个便签ID(-1表示批量更新) * @param selection 批量更新条件 * @param selectionArgs 条件参数 */ private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); sql.append(TABLE.NOTE); sql.append(" SET "); sql.append(NoteColumns.VERSION); sql.append("=" + NoteColumns.VERSION + "+1 "); if (id > 0 || !TextUtils.isEmpty(selection)) { sql.append(" WHERE "); } if (id > 0) { sql.append(NoteColumns.ID + "=" + String.valueOf(id)); } if (!TextUtils.isEmpty(selection)) { // 替换占位符?为实际参数 String selectString = id > 0 ? parseSelection(selection) : selection; for (String args : selectionArgs) { selectString = selectString.replaceFirst("\\?", args); } sql.append(selectString); } // 执行SQL更新 mHelper.getWritableDatabase().execSQL(sql.toString()); } @Override public String getType(Uri uri) { // TODO: 实现获取MIME类型的逻辑(当前未实现) return null; } }