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.
xiaomi-Notes/NotesProvider.java

383 lines
18 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.

/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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;
public class NotesProvider extends ContentProvider {
// UriMatcher用于根据URI识别请求的类型
private static final UriMatcher mMatcher;
// 数据库助手类用于操作SQLite数据库
private NotesDatabaseHelper mHelper;
// 日志标签
private static final String TAG = "NotesProvider";
// 定义URI匹配的常量
private static final int URI_NOTE = 1; // 匹配"note" URI
private static final int URI_NOTE_ITEM = 2; // 匹配"note/#" URI#表示ID
private static final int URI_DATA = 3; // 匹配"data" URI
private static final int URI_DATA_ITEM = 4; // 匹配"data/#" URI#表示ID
private static final int URI_SEARCH = 5; // 匹配"search" URI用于搜索功能
private static final int URI_SEARCH_SUGGEST = 6; // 匹配搜索建议的URI
// 静态代码块初始化UriMatcher
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH); // 初始化UriMatcher默认为NO_MATCH
// 添加不同的URI模式并与对应的常量匹配
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); // 匹配 "note" URI
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); // 匹配 "note/#" URI#是ID占位符
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); // 匹配 "data" URI
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); // 匹配 "data/#" URI#是ID占位符
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); // 匹配 "search" URI
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); // 匹配搜索建议URI
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); // 匹配搜索建议带查询字符串的URI
}
/**
* 为了在搜索结果中展示更多信息,去除标题和内容中的换行符('\n')以及多余的空格。
* x'0A'表示SQLite中的换行符('\n'),我们会去掉这些字符,确保搜索结果展示更加紧凑。
*/
// 定义一个搜索投影,用于处理搜索结果中的显示字段
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," // 查询笔记的ID
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," // 用ID作为搜索建议的附加数据
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," // 去除换行符并去掉多余空格作为搜索文本1
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," // 同样处理第二个文本字段
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," // 设置搜索结果的图标
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," // 搜索建议的操作,设置为查看
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; // 搜索建议的内容类型
// 定义搜索查询SQL语句
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION
+ " FROM " + TABLE.NOTE // 查询Notes表中的数据
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?" // 根据笔记内容的摘要SNIPPET进行模糊查询
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER // 排除属于垃圾箱的笔记
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; // 仅返回普通笔记,而不是其他类型(如语音、图片等)
@Override
public boolean onCreate() {
// 初始化数据库助手类
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true; // 成功创建ContentProvider
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// 定义一个Cursor对象用于返回查询结果
Cursor c = null;
// 获取可读的SQLiteDatabase实例
SQLiteDatabase db = mHelper.getReadableDatabase();
// 用于存储从URI中获取的ID
String id = null;
// 根据URI路径匹配不同的查询请求
switch (mMatcher.match(uri)) {
// 匹配URI_NOTE表示查询所有笔记
case URI_NOTE:
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder);
break;
// 匹配URI_NOTE_ITEM表示查询单个笔记根据ID查询
case URI_NOTE_ITEM:
// 从URI路径中获取笔记的ID
id = uri.getPathSegments().get(1);
// 执行查询条件是笔记ID与URI中提供的ID匹配并且可以追加其他选择条件
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + parseSelection(selection),
selectionArgs, null, null, sortOrder);
break;
// 匹配URI_DATA表示查询所有数据
case URI_DATA:
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder);
break;
// 匹配URI_DATA_ITEM表示查询单条数据根据ID查询
case URI_DATA_ITEM:
// 从URI路径中获取数据的ID
id = uri.getPathSegments().get(1);
// 执行查询条件是数据ID与URI中提供的ID匹配并且可以追加其他选择条件
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + parseSelection(selection),
selectionArgs, null, null, sortOrder);
break;
// 匹配URI_SEARCH和URI_SEARCH_SUGGEST表示处理搜索请求
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;
// 如果是搜索建议的URIURI_SEARCH_SUGGEST则从URI路径中提取查询字符串
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1);
}
} else {
// 否则,从查询参数中提取搜索模式("pattern"
searchString = uri.getQueryParameter("pattern");
}
// 如果没有提供搜索字符串则返回null
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;
// 如果没有匹配的URI路径抛出非法参数异常
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果查询成功并返回了Cursor对象设置通知URI
if (c != null) {
// 通知ContentResolver该URI的内容发生了变化ContentResolver会根据这个信息更新UI
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; // 初始化id变量插入的ID将存储在这些变量中
// 根据URI路径匹配来判断要插入的数据类型
switch (mMatcher.match(uri)) {
// 如果是URI_NOTE插入一条新的笔记
case URI_NOTE:
insertedId = noteId = db.insert(TABLE.NOTE, null, values); // 向笔记表插入数据返回插入的ID
break;
// 如果是URI_DATA插入一条新的数据项
case URI_DATA:
// 检查values中是否包含Note ID这是数据与笔记的关联键
if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID); // 获取关联的笔记ID
} else {
// 如果没有提供笔记ID则打印日志
Log.d(TAG, "Wrong data format without note id:" + values.toString());
}
// 向数据表插入数据返回插入的ID
insertedId = dataId = db.insert(TABLE.DATA, null, values);
break;
// 如果URI未知抛出非法参数异常
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果插入的笔记ID大于0通知ContentResolver该笔记数据已发生变化
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// 如果插入的数据ID大于0通知ContentResolver该数据项已发生变化
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
// 返回插入的记录的URI通常是ContentProvider的URI附加上插入的ID
return ContentUris.withAppendedId(uri, insertedId);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0; // 记录删除的行数
String id = null; // 存储从URI中提取的ID
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写的数据库实例
boolean deleteData = false; // 标记是否删除了数据项
// 根据URI路径匹配来判断删除的对象类型
switch (mMatcher.match(uri)) {
// 删除所有笔记
case URI_NOTE:
// 在原有的选择条件上附加“ID > 0”的限制确保不会删除系统保留的笔记
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs); // 删除笔记表中的数据
break;
// 删除单个笔记根据ID
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1); // 从URI路径中获取笔记ID
long noteId = Long.valueOf(id); // 转换为long类型的ID
if (noteId <= 0) {
break; // 如果ID小于等于0跳过删除操作
}
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); // 删除指定ID的笔记
break;
// 删除所有数据项
case URI_DATA:
count = db.delete(TABLE.DATA, selection, selectionArgs); // 删除数据表中的数据
deleteData = true; // 标记删除的是数据项
break;
// 删除单个数据项根据ID
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1); // 从URI路径中获取数据ID
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); // 删除指定ID的数据项
deleteData = true; // 标记删除的是数据项
break;
// 如果URI无法匹配抛出非法参数异常
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); // 通知ContentResolver相关数据已变化
}
return count; // 返回删除的行数
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0; // 记录更新的行数
String id = null; // 用于存储从URI中提取的ID
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写的数据库实例
boolean updateData = false; // 标记是否更新了数据项
// 根据URI匹配来决定更新操作的具体内容
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 对笔记表进行更新时调用increaseNoteVersion方法更新笔记版本号
increaseNoteVersion(-1, selection, selectionArgs);
count = db.update(TABLE.NOTE, values, selection, selectionArgs); // 执行更新操作
break;
case URI_NOTE_ITEM:
// 更新指定ID的笔记项
id = uri.getPathSegments().get(1); // 从URI路径中获取笔记ID
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); // 更新笔记版本号
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs); // 执行更新操作
break;
case URI_DATA:
// 更新所有数据项
count = db.update(TABLE.DATA, values, selection, selectionArgs); // 执行更新操作
updateData = true; // 标记更新了数据项
break;
case URI_DATA_ITEM:
// 更新指定ID的数据项
id = uri.getPathSegments().get(1); // 从URI路径中获取数据ID
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs); // 执行更新操作
updateData = true; // 标记更新了数据项
break;
default:
// 如果URI无法匹配抛出异常
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果更新成功即有行被更新则通知ContentResolver相关数据已变化
if (count > 0) {
// 如果更新的是数据项,通知相关笔记数据变化
if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
// 通知ContentResolverURI发生变化触发UI更新
getContext().getContentResolver().notifyChange(uri, null);
}
return count; // 返回更新的行数
}
//该方法用于解析传入的selection条件并确保如果selection不为空则将其包裹在" AND (...)"内。这主要是为了构建更新时的SQL条件。
private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
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 "); // 增加版本号
// 如果指定了ID或selection则追加WHERE条件
if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
}
if (id > 0) {
sql.append(NoteColumns.ID + "=" + String.valueOf(id)); // 使用指定的ID
}
if (!TextUtils.isEmpty(selection)) {
String selectString = id > 0 ? parseSelection(selection) : selection;
for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args); // 替换选择条件中的占位符
}
sql.append(selectString); // 将最终的selection添加到SQL中
}
// 执行更新SQL语句
mHelper.getWritableDatabase().execSQL(sql.toString());
}