You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
test/src/data/NotesProvider.java

322 lines
14 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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);
// 匹配便签列表URIcontent://micode_notes/note
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
// 匹配单个便签URIcontent://micode_notes/note/123
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
// 匹配便签数据列表URIcontent://micode_notes/data
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
// 匹配单个便签数据URIcontent://micode_notes/data/456
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
// 匹配搜索URIcontent://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;
}
}