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

393 lines
26 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;
// NotesProvider类继承自ContentProvider用于在安卓系统中作为内容提供器对外提供对笔记相关数据的增删改查等操作接口
// 并根据不同的Uri来区分操作的是笔记数据note表还是笔记相关的具体数据data表等不同情况。
public class NotesProvider extends ContentProvider {
// UriMatcher用于匹配传入的Uri根据不同的Uri模式返回对应的匹配码方便后续根据不同的请求进行相应处理
private static final UriMatcher mMatcher;
// 用于辅助操作数据库比如创建表、执行SQL语句等是对SQLite数据库操作的封装类实例
private NotesDatabaseHelper mHelper;
// 用于日志输出的标签方便在Logcat中查看该类相关操作的日志信息
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;
// 静态代码块用于初始化UriMatcher添加各种不同的Uri模式及其对应的匹配码以便后续根据传入的Uri进行匹配判断
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 匹配"note"路径的Uri对应匹配码为URI_NOTE通常用于操作整个note表比如查询所有笔记等
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
// 匹配"note/#"路径的Uri其中#表示数字即具体的笔记ID对应匹配码为URI_NOTE_ITEM用于操作单个笔记记录
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
// 匹配"data"路径的Uri对应匹配码为URI_DATA用于操作整个data表存储笔记相关具体数据的表
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
// 匹配"data/#"路径的Uri#表示具体的数据记录ID对应匹配码为URI_DATA_ITEM用于操作单个数据记录
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
// 匹配"search"路径的Uri对应匹配码为URI_SEARCH用于执行搜索相关操作具体搜索逻辑在query方法中根据此匹配码处理
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
// 匹配SearchManager.SUGGEST_URI_PATH_QUERY路径的Uri对应匹配码为URI_SEARCH_SUGGEST用于处理搜索建议相关操作
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
// 匹配SearchManager.SUGGEST_URI_PATH_QUERY + "/*"路径的Uri*表示通配符通常是具体的搜索词等同样对应匹配码为URI_SEARCH_SUGGEST用于处理搜索建议相关操作
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
}
/**
* x'0A' represents the '\n' character in sqlite. For title and content in the search result,
* we will trim '\n' and white space in order to show more information.
* 定义用于搜索结果的投影Projection字符串指定从note表中查询出哪些列以及如何对这些列进行处理后返回
* 例如将笔记的ID列重复使用一份作为原始ID另一份作为搜索建议中传递额外数据的列同时对笔记摘要SNIPPET列进行处理
* 去除其中的换行符('\n'在SQLite中用x'0A'表示)和空白字符,并将处理后的结果用于搜索建议的文本显示列等。
*/
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_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;
// 定义用于搜索笔记摘要内容的查询语句字符串指定从note表中查询符合特定条件摘要列包含搜索词、不在回收站、类型为普通笔记等的数据
// 并按照之前定义的NOTES_SEARCH_PROJECTION投影来返回结果后续在处理搜索相关操作时会使用此查询语句进行数据库查询。
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;
// 当ContentProvider被创建时调用的方法在这里获取NotesDatabaseHelper的实例用于后续数据库操作返回true表示初始化成功。
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
// 根据传入的Uri等参数执行查询操作的方法根据不同的Uri匹配情况从相应的数据库表中查询数据并返回结果集Cursor
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null;
// 获取可读的SQLite数据库实例用于执行查询操作不会对数据库进行写操作。
SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null;
// 使用UriMatcher匹配传入的Uri根据匹配结果执行不同的查询逻辑。
switch (mMatcher.match(uri)) {
// 如果匹配码为URI_NOTE表示查询整个note表直接使用提供的投影、筛选条件等参数进行查询。
case URI_NOTE:
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break;
// 如果匹配码为URI_NOTE_ITEM表示查询单个笔记记录先从Uri中获取笔记的ID然后在查询条件中添加ID匹配条件后进行查询。
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
// 如果匹配码为URI_DATA表示查询整个data表同样使用给定参数进行查询。
case URI_DATA:
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder);
break;
// 如果匹配码为URI_DATA_ITEM表示查询单个数据记录先获取数据记录的ID再添加ID匹配条件后进行查询。
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
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;
// 如果匹配码是URI_SEARCH_SUGGEST且Uri的路径段数量大于1表示有具体的搜索词部分则获取路径段中的搜索词。
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1);
}
} else {
// 如果匹配码是URI_SEARCH则从Uri的查询参数中获取名为"pattern"的参数作为搜索词。
searchString = uri.getQueryParameter("pattern");
}
// 如果搜索词为空没有提供搜索词则直接返回null表示无查询结果。
if (TextUtils.isEmpty(searchString)) {
return null;
}
try {
// 对搜索词进行格式化,添加通配符(%),用于构建模糊查询的 LIKE 语句,方便查找包含搜索词的笔记摘要内容。
searchString = String.format("%%%s%%", searchString);
// 使用格式化后的搜索词作为参数执行定义好的NOTES_SNIPPET_SEARCH_QUERY查询语句进行原始查询获取搜索结果集。
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[]{searchString});
} catch (IllegalStateException ex) {
// 如果在执行查询过程中出现非法状态异常(比如数据库未正确打开等情况),则在日志中记录异常信息。
Log.e(TAG, "got exception: " + ex.toString());
}
break;
// 如果Uri不匹配任何已知的模式则抛出异常表示未知的Uri请求。
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果查询结果集不为空设置其通知Uri用于当对应的数据发生变化时接收通知以便更新相关的UI等操作。
if (c!= null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
// 根据传入的Uri和ContentValues执行插入数据操作的方法根据不同的Uri将数据插入到相应的数据库表中并返回插入后数据的Uri包含新插入数据的ID
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase();
long dataId = 0, noteId = 0, insertedId = 0;
// 根据Uri的匹配情况决定将数据插入到哪个表中。
switch (mMatcher.match(uri)) {
// 如果匹配码为URI_NOTE表示插入数据到note表执行插入操作并获取插入数据的ID。
case URI_NOTE:
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break;
// 如果匹配码为URI_DATA表示插入数据到data表先检查是否包含关联的笔记ID如果没有则记录日志提示错误格式然后执行插入操作并获取插入数据的ID。
case URI_DATA:
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;
// 如果Uri不匹配已知的插入模式则抛出异常表示未知的Uri请求。
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果插入到note表的操作成功noteId大于0通知与笔记相关的Uri数据发生了变化以便相关观察者如UI组件等进行更新。
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// 如果插入到data表的操作成功dataId大于0通知与数据相关的Uri数据发生了变化。
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
// 返回插入数据后对应的Uri通过在原始Uri基础上添加新插入数据的ID构建而成方便后续操作如获取插入的数据等
return ContentUris.withAppendedId(uri, insertedId);
}
// 根据传入的Uri、筛选条件等执行删除数据操作的方法根据不同的Uri从相应的数据库表中删除符合条件的数据并返回删除的行数。
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean deleteData = false;
// 根据Uri的匹配情况决定从哪个表中删除数据以及具体的删除逻辑。
switch (mMatcher.match(uri)) {
// 如果匹配码为URI_NOTE表示从note表中删除数据先完善筛选条件确保ID大于0等然后执行删除操作并获取删除的行数。
case URI_NOTE:
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs);
break;
// 如果匹配码为URI_NOTE_ITEM表示删除单个笔记记录先从Uri中获取笔记的ID然后判断ID是否合法小于等于0的可能是系统文件夹不允许删除
// 如果合法则根据ID和其他筛选条件执行删除操作并获取删除的行数。
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
/**
* ID that smaller than 0 is system folder which is not allowed to
* trash
*/
long noteId = Long.valueOf(id);
if (noteId <= 0) {
break;
}
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
break;
// 如果匹配码为URI_DATA表示从data表中删除数据执行删除操作并记录要通知数据相关变化后续根据此标记通知相应Uri获取删除的行数。
case URI_DATA:
count = db.delete(TABLE.DATA, selection, selectionArgs);
deleteData = true;
break;
// 如果匹配码为URI_DATA_ITEM表示删除单个数据记录先获取数据记录的ID然后根据ID和其他筛选条件执行删除操作并获取删除的行数同时记录要通知数据相关变化。
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
deleteData = true;
break;
// 如果Uri不匹配已知的删除模式则抛出异常表示未知的Uri请求。
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果删除操作成功删除的行数大于0根据是否是删除数据相关操作决定通知相应的Uri数据发生了变化。
if (count > 0) {
if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
// 根据传入的Uri、ContentValues和筛选条件等执行更新数据操作的方法根据不同的Uri更新相应数据库表中符合条件的数据并返回更新的行数。
@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;
// 根据Uri的匹配情况决定更新哪个表中的数据以及具体的更新逻辑。
switch (mMatcher.match(uri)) {
// 如果匹配码为
// 在匹配码为URI_NOTE的情况下即针对整个note表进行更新操作时的逻辑处理
case URI_NOTE:
// 调用increaseNoteVersion方法来更新笔记的版本号此处传入 -1 可能表示针对所有笔记进行版本号更新相关操作(具体需结合业务逻辑)
increaseNoteVersion(-1, selection, selectionArgs);
// 执行实际的更新操作使用提供的ContentValues包含要更新的数据、筛选条件及参数对TABLE.NOTE即note表进行更新并获取更新的行数
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
// 在匹配码为URI_NOTE_ITEM的情况下即针对单个笔记记录进行更新操作时的逻辑处理
case URI_NOTE_ITEM:
// 从传入的Uri路径段中获取笔记的ID具体笔记的标识路径格式如 "note/[具体ID]"这里获取第二个路径段索引为1作为ID
id = uri.getPathSegments().get(1);
// 调用increaseNoteVersion方法来更新对应笔记的版本号传入获取到的笔记ID以便针对该特定笔记进行版本号更新相关操作
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
// 执行实际的更新操作使用提供的ContentValues、添加了笔记ID匹配条件确保更新指定的笔记以及其他筛选条件和参数对TABLE.NOTE进行更新并获取更新的行数
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
break;
// 在匹配码为URI_DATA的情况下即针对整个data表进行更新操作时的逻辑处理
case URI_DATA:
// 直接执行更新操作使用提供的ContentValues、筛选条件及参数对TABLE.DATA即存储笔记相关具体数据的表进行更新并获取更新的行数
count = db.update(TABLE.DATA, values, selection, selectionArgs);
// 标记此次更新操作涉及到了data表后续用于判断是否需要通知相应的内容变化
updateData = true;
break;
// 在匹配码为URI_DATA_ITEM的情况下即针对单个数据记录进行更新操作时的逻辑处理
case URI_DATA_ITEM:
// 从传入的Uri路径段中获取数据记录的ID路径格式如 "data/[具体ID]"获取第二个路径段作为ID
id = uri.getPathSegments().get(1);
// 执行实际的更新操作使用提供的ContentValues、添加了数据记录ID匹配条件以及其他筛选条件和参数对TABLE.DATA进行更新并获取更新的行数
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
// 标记此次更新操作涉及到了data表后续用于判断是否需要通知相应的内容变化
updateData = true;
break;
// 如果Uri的匹配码不属于上述任何已知的情况则抛出异常表示这是一个未知的Uri请求无法进行相应的更新操作
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果更新操作成功即更新的行数大于0表示数据有了变化
if (count > 0) {
// 如果此次更新操作涉及到了data表updateData为true则通知与笔记内容相关的UriNotes.CONTENT_NOTE_URI发生了数据变化
// 以便相关的观察者例如UI组件等依赖这些数据的部分能够做出相应的响应比如刷新显示等操作
if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
// 无论是否涉及data表更新都通知传入的当前操作的Uri发生了数据变化用于更细粒度的通知机制
// 比如特定的查询界面只关注对应Uri的数据变化情况这样可以精准地触发相关更新逻辑
getContext().getContentResolver().notifyChange(uri, null);
}
// 返回更新操作实际影响的行数,方便调用者了解更新操作的执行情况
return count;
}
// 此方法用于解析传入的筛选条件selection字符串根据其是否为空进行处理
// 如果筛选条件不为空,返回在原筛选条件基础上添加 " AND (" 和 ")" 的字符串用于构建更完整的SQL筛选语句逻辑
// 如果筛选条件为空,则返回空字符串,表示不需要添加额外的筛选逻辑连接部分
private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection)? " AND (" + selection + ')' : "");
}
// 此方法用于增加笔记的版本号通过构建并执行一个SQL的UPDATE语句来实现
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
// 创建一个初始容量为120的可变字符串StringBuilder对象用于动态构建SQL语句方便后续拼接字符串内容
StringBuilder sql = new StringBuilder(120);
// 开始构建UPDATE语句指定要更新的表为TABLE.NOTE即note表
sql.append("UPDATE ");
sql.append(TABLE.NOTE);
// 指定要更新的列以及更新的方式这里是将NoteColumns.VERSION列的值在原基础上加1实现版本号增加的操作
sql.append(" SET ");
sql.append(NoteColumns.VERSION);
sql.append("=" + NoteColumns.VERSION + "+1 ");
// 如果传入的笔记ID大于0表示针对特定笔记进行操作或者筛选条件不为空表示有额外的筛选条件限制更新范围则需要添加WHERE子句来明确更新的范围
if (id > 0 ||!TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
}
// 如果传入的笔记ID大于0在WHERE子句中添加根据笔记ID进行匹配的条件确保只更新指定ID的笔记版本号
if (id > 0) {
sql.append(NoteColumns.ID + "=" + String.valueOf(id));
}
// 如果筛选条件不为空,进一步处理筛选条件字符串
if (!TextUtils.isEmpty(selection)) {
// 如果笔记ID大于0先调用parseSelection方法对筛选条件进行处理添加逻辑连接部分否则直接使用原筛选条件
String selectString = id > 0? parseSelection(selection) : selection;
// 遍历筛选条件参数数组,将筛选条件字符串中的占位符("?")依次替换为实际的参数值,以构建完整可用的筛选条件语句
for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args);
}
// 将处理好的完整筛选条件添加到SQL语句的WHERE子句部分
sql.append(selectString);
}
// 使用NotesDatabaseHelper获取可写的数据库实例并执行构建好的SQL语句实现更新笔记版本号的操作
mHelper.getWritableDatabase().execSQL(sql.toString());
}
// 按照ContentProvider规范此方法应该返回给定Uri对应的MIME类型但此处是个空实现TODO部分可能需要后续根据具体需求完善
// 返回值用于向外界表明对应Uri所代表的数据格式等信息以便其他组件如调用ContentResolver查询数据时能正确处理数据
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
}