diff --git a/app/src/main/java/net/micode/notes/data/Notes.java b/app/src/main/java/net/micode/notes/data/Notes.java index f240604..df095a0 100644 --- a/app/src/main/java/net/micode/notes/data/Notes.java +++ b/app/src/main/java/net/micode/notes/data/Notes.java @@ -17,24 +17,36 @@ package net.micode.notes.data; import android.net.Uri; + +/** + * 便签数据库模式定义类 + * + * 定义了应用数据结构、URI、表结构和常量,是数据层的核心类。 + * 包含便签和便签内容相关的所有表定义和数据类型定义。 + */ public class Notes { + // ContentProvider的Authority,用于URI标识 public static final String AUTHORITY = "micode_notes"; public static final String TAG = "Notes"; - public static final int TYPE_NOTE = 0; - public static final int TYPE_FOLDER = 1; - public static final int TYPE_SYSTEM = 2; + + // 便签类型常量 + public static final int TYPE_NOTE = 0; // 普通便签 + public static final int TYPE_FOLDER = 1; // 文件夹 + public static final int TYPE_SYSTEM = 2; // 系统文件夹 /** - * Following IDs are system folders' identifiers - * {@link Notes#ID_ROOT_FOLDER } is default folder - * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder - * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records + * 系统文件夹ID定义 + * + * {@link Notes#ID_ROOT_FOLDER} 是默认根文件夹 + * {@link Notes#ID_TEMPARAY_FOLDER} 是临时文件夹,用于不属于任何文件夹的便签 + * {@link Notes#ID_CALL_RECORD_FOLDER} 用于存储通话记录 */ - public static final int ID_ROOT_FOLDER = 0; - public static final int ID_TEMPARAY_FOLDER = -1; - public static final int ID_CALL_RECORD_FOLDER = -2; - public static final int ID_TRASH_FOLER = -3; + public static final int ID_ROOT_FOLDER = 0; // 根文件夹ID + public static final int ID_TEMPARAY_FOLDER = -1; // 临时文件夹ID + public static final int ID_CALL_RECORD_FOLDER = -2; // 通话记录文件夹ID + public static final int ID_TRASH_FOLER = -3; // 垃圾箱文件夹ID + // Intent Extra常量,用于Activity间传递数据 public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; @@ -42,238 +54,277 @@ public class Notes { public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; - public static final int TYPE_WIDGET_INVALIDE = -1; - public static final int TYPE_WIDGET_2X = 0; - public static final int TYPE_WIDGET_4X = 1; + // 桌面小部件类型常量 + public static final int TYPE_WIDGET_INVALIDE = -1; // 无效的小部件类型 + public static final int TYPE_WIDGET_2X = 0; // 2x小部件 + public static final int TYPE_WIDGET_4X = 1; // 4x小部件 + /** + * 数据类型常量类 + * 定义便签内容的数据类型 + */ public static class DataConstants { public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; } /** - * Uri to query all notes and folders + * 便签表内容URI + * 用于查询所有便签和文件夹 */ public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); /** - * Uri to query data + * 数据表内容URI + * 用于查询便签内容数据 */ public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); + /** + * 便签表列定义接口 + * 定义便签表的所有列名 + */ public interface NoteColumns { /** - * The unique ID for a row - *
Type: INTEGER (long)
+ * 便签ID,主键 + *类型: INTEGER (long)
*/ public static final String ID = "_id"; /** - * The parent's id for note or folder - *Type: INTEGER (long)
+ * 父级ID,表示便签或文件夹所属的文件夹ID + *类型: INTEGER (long)
*/ public static final String PARENT_ID = "parent_id"; /** - * Created data for note or folder - *Type: INTEGER (long)
+ * 创建日期,便签或文件夹的创建时间 + *类型: INTEGER (long)
*/ public static final String CREATED_DATE = "created_date"; /** - * Latest modified date - *Type: INTEGER (long)
+ * 最后修改日期,便签的最后修改时间 + *类型: INTEGER (long)
*/ public static final String MODIFIED_DATE = "modified_date"; /** - * Alert date - *Type: INTEGER (long)
+ * 提醒日期,便签的提醒时间 + *类型: INTEGER (long)
*/ public static final String ALERTED_DATE = "alert_date"; /** - * Folder's name or text content of note - *Type: TEXT
+ * 便签内容片段或文件夹名称 + *类型: TEXT
*/ public static final String SNIPPET = "snippet"; /** - * Note's widget id - *Type: INTEGER (long)
+ * 便签关联的桌面小部件ID + *类型: INTEGER (long)
*/ public static final String WIDGET_ID = "widget_id"; /** - * Note's widget type - *Type: INTEGER (long)
+ * 便签关联的桌面小部件类型 + *类型: INTEGER (long)
*/ public static final String WIDGET_TYPE = "widget_type"; /** - * Note's background color's id - *Type: INTEGER (long)
+ * 便签背景颜色ID + *类型: INTEGER (long)
*/ public static final String BG_COLOR_ID = "bg_color_id"; /** - * For text note, it doesn't has attachment, for multi-media - * note, it has at least one attachment - *Type: INTEGER
+ * 便签是否有附件标记 + * 对于文本便签,没有附件;对于多媒体便签,至少有一个附件 + *类型: INTEGER
*/ public static final String HAS_ATTACHMENT = "has_attachment"; /** - * Folder's count of notes - *Type: INTEGER (long)
+ * 文件夹包含的便签数量 + *类型: INTEGER (long)
*/ public static final String NOTES_COUNT = "notes_count"; /** - * The file type: folder or note - *Type: INTEGER
+ * 便签类型:文件夹或便签 + *类型: INTEGER
*/ public static final String TYPE = "type"; /** - * The last sync id - *Type: INTEGER (long)
+ * 最后同步ID + *类型: INTEGER (long)
*/ public static final String SYNC_ID = "sync_id"; /** - * Sign to indicate local modified or not - *Type: INTEGER
+ * 本地修改标记 + *类型: INTEGER
*/ public static final String LOCAL_MODIFIED = "local_modified"; /** - * Original parent id before moving into temporary folder - *Type : INTEGER
+ * 移入临时文件夹前的原始父ID + *类型 : INTEGER
*/ public static final String ORIGIN_PARENT_ID = "origin_parent_id"; /** - * The gtask id - *Type : TEXT
+ * Google Task ID + *类型 : TEXT
*/ public static final String GTASK_ID = "gtask_id"; /** - * The version code - *Type : INTEGER (long)
+ * 版本号 + *类型 : INTEGER (long)
*/ public static final String VERSION = "version"; } + /** + * 数据表列定义接口 + * 定义存储便签内容的数据表列名 + */ public interface DataColumns { /** - * The unique ID for a row - *Type: INTEGER (long)
+ * 数据ID,主键 + *类型: INTEGER (long)
*/ public static final String ID = "_id"; /** - * The MIME type of the item represented by this row. - *Type: Text
+ * 数据MIME类型 + *类型: Text
*/ public static final String MIME_TYPE = "mime_type"; /** - * The reference id to note that this data belongs to - *Type: INTEGER (long)
+ * 关联的便签ID,表示该数据属于哪个便签 + *类型: INTEGER (long)
*/ public static final String NOTE_ID = "note_id"; /** - * Created data for note or folder - *Type: INTEGER (long)
+ * 创建日期 + *类型: INTEGER (long)
*/ public static final String CREATED_DATE = "created_date"; /** - * Latest modified date - *Type: INTEGER (long)
+ * 最后修改日期 + *类型: INTEGER (long)
*/ public static final String MODIFIED_DATE = "modified_date"; /** - * Data's content - *Type: TEXT
+ * 数据内容 + *类型: TEXT
*/ public static final String CONTENT = "content"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *Type: INTEGER
+ * 通用数据列1,具体含义由MIME_TYPE决定,用于整型数据 + *类型: INTEGER
*/ public static final String DATA1 = "data1"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *Type: INTEGER
+ * 通用数据列2,具体含义由MIME_TYPE决定,用于整型数据 + *类型: INTEGER
*/ public static final String DATA2 = "data2"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *Type: TEXT
+ * 通用数据列3,具体含义由MIME_TYPE决定,用于文本类型数据 + *类型: TEXT
*/ public static final String DATA3 = "data3"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *Type: TEXT
+ * 通用数据列4,具体含义由MIME_TYPE决定,用于文本类型数据 + *类型: TEXT
*/ public static final String DATA4 = "data4"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *Type: TEXT
+ * 通用数据列5,具体含义由MIME_TYPE决定,用于文本类型数据 + *类型: TEXT
*/ public static final String DATA5 = "data5"; } + /** + * 文本便签类 + * 定义文本便签相关的数据类型和URI + */ public static final class TextNote implements DataColumns { /** - * Mode to indicate the text in check list mode or not - *Type: Integer 1:check list mode 0: normal mode
+ * 便签模式,用于DATA1列 + * 表示便签是普通模式还是清单模式 + *类型: Integer 1:清单模式 0:普通模式
*/ public static final String MODE = DATA1; - public static final int MODE_CHECK_LIST = 1; - + /** + * 内容类型 + * 用于ContentProvider的getType返回值 + */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; + /** + * 单条内容类型 + * 用于ContentProvider的getType返回值 + */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; + /** + * 文本便签内容URI + */ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); } + /** + * 通话便签类 + * 定义通话记录便签相关的数据类型和URI + */ public static final class CallNote implements DataColumns { /** - * Call date for this record - *Type: INTEGER (long)
+ * 通话日期 + * 用于DATA1列,存储通话时间 */ public static final String CALL_DATE = DATA1; /** - * Phone number for this record - *Type: TEXT
+ * 通话电话号码 + * 用于DATA3列,存储通话电话号码 */ public static final String PHONE_NUMBER = DATA3; + /** + * 内容类型 + * 用于ContentProvider的getType返回值 + */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; + /** + * 单条内容类型 + * 用于ContentProvider的getType返回值 + */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; + /** + * 通话便签内容URI + */ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); } } diff --git a/app/src/main/java/net/micode/notes/data/NotesProvider.java b/app/src/main/java/net/micode/notes/data/NotesProvider.java index edb0a60..dce5320 100644 --- a/app/src/main/java/net/micode/notes/data/NotesProvider.java +++ b/app/src/main/java/net/micode/notes/data/NotesProvider.java @@ -34,36 +34,48 @@ import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.NotesDatabaseHelper.TABLE; - +/** + * 便签内容提供者类 + * + * 实现ContentProvider接口,负责管理便签应用的数据访问和操作。 + * 提供URI匹配、CRUD操作和搜索功能。是应用数据层的关键组件, + * 使其他组件和应用能够以统一方式访问便签数据。 + */ public class NotesProvider extends ContentProvider { + // URI匹配器,用于区分不同的URI请求 private static final UriMatcher mMatcher; + // 数据库帮助类实例 private NotesDatabaseHelper mHelper; private static final String TAG = "NotesProvider"; - 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; + // 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; // 搜索建议 + // 静态初始化块,设置URI匹配规则 static { mMatcher = new UriMatcher(UriMatcher.NO_MATCH); - mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); - mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); - mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); - mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); - mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); - mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); - mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); + // 添加URI匹配规则 + mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); // 匹配所有便签 + mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); // 匹配特定ID的便签 + mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); // 匹配所有数据 + mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); // 匹配特定ID的数据 + mMatcher.addURI(Notes.AUTHORITY, "search", URI_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 } /** - * 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. + * 搜索结果的投影列 + * + * x'0A' 表示SQLite中的'\n'字符。对于搜索结果中的标题和内容, + * 我们将去除'\n'和空格以显示更多信息。 */ private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," @@ -73,45 +85,76 @@ public class NotesProvider extends ContentProvider { + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; + /** + * 便签片段搜索查询SQL + * + * 构建搜索便签内容的SQL查询语句,排除回收站中的便签,只搜索普通便签 + */ 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; + + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" // 使用LIKE进行模糊匹配 + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER // 排除回收站中的便签 + + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; // 只搜索便签类型 + /** + * ContentProvider创建时调用 + * 初始化数据库帮助类 + * + * @return 初始化是否成功 + */ @Override public boolean onCreate() { + // 获取数据库帮助类单例 mHelper = NotesDatabaseHelper.getInstance(getContext()); return true; } + /** + * 查询数据方法 + * 处理不同类型URI的查询请求 + * + * @param uri 查询URI + * @param projection 要返回的列 + * @param selection 查询条件 + * @param selectionArgs 查询参数 + * @param sortOrder 排序方式 + * @return 查询结果游标 + */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Cursor c = null; + // 获取可读数据库 SQLiteDatabase db = mHelper.getReadableDatabase(); String id = null; + + // 根据URI类型执行不同的查询 switch (mMatcher.match(uri)) { case URI_NOTE: + // 查询所有便签 c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder); break; case URI_NOTE_ITEM: - id = uri.getPathSegments().get(1); + // 查询特定ID的便签 + id = uri.getPathSegments().get(1); // 从URI路径获取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 = uri.getPathSegments().get(1); + // 查询特定ID的便签数据 + id = uri.getPathSegments().get(1); // 从URI路径获取ID 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"); @@ -119,19 +162,23 @@ public class NotesProvider extends ContentProvider { String searchString = null; if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { + // 从URI路径获取搜索字符串 if (uri.getPathSegments().size() > 1) { searchString = uri.getPathSegments().get(1); } } else { + // 从URI参数获取搜索模式 searchString = uri.getQueryParameter("pattern"); } + // 空搜索字符串不执行查询 if (TextUtils.isEmpty(searchString)) { return null; } try { - searchString = String.format("%%%s%%", searchString); + // 格式化搜索字符串为SQL LIKE模式 + searchString = String.format("%%%s%%", searchString); // 添加%用于模糊匹配 c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, new String[] { searchString }); } catch (IllegalStateException ex) { @@ -141,23 +188,38 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } + + // 设置内容变更通知 if (c != null) { c.setNotificationUri(getContext().getContentResolver(), uri); } return c; } + /** + * 插入数据方法 + * 处理便签和便签数据的插入操作 + * + * @param uri 插入URI + * @param values 要插入的值 + * @return 插入后的URI + */ @Override public Uri insert(Uri uri, ContentValues values) { + // 获取可写数据库 SQLiteDatabase db = mHelper.getWritableDatabase(); long dataId = 0, noteId = 0, insertedId = 0; + + // 根据URI类型执行不同的插入操作 switch (mMatcher.match(uri)) { case URI_NOTE: + // 插入便签 insertedId = noteId = db.insert(TABLE.NOTE, null, values); break; case URI_DATA: + // 插入便签数据,需要关联到便签 if (values.containsKey(DataColumns.NOTE_ID)) { - noteId = values.getAsLong(DataColumns.NOTE_ID); + noteId = values.getAsLong(DataColumns.NOTE_ID); // 获取关联的便签ID } else { Log.d(TAG, "Wrong data format without note id:" + values.toString()); } @@ -166,140 +228,209 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } - // Notify the note uri + + // 通知便签内容变更 if (noteId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); } - // Notify the data uri + // 通知数据内容变更 if (dataId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); } + // 返回插入项的URI return ContentUris.withAppendedId(uri, insertedId); } + /** + * 删除数据方法 + * 处理便签和便签数据的删除操作 + * + * @param uri 删除URI + * @param selection 删除条件 + * @param selectionArgs 删除参数 + * @return 删除的行数 + */ @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)) { case URI_NOTE: + // 删除便签,ID>0是为了保护系统文件夹 selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; count = db.delete(TABLE.NOTE, selection, selectionArgs); break; case URI_NOTE_ITEM: id = uri.getPathSegments().get(1); /** - * ID that smaller than 0 is system folder which is not allowed to - * trash + * ID小于0的是系统文件夹,不允许删除 */ - long noteId = Long.valueOf(id); - if (noteId <= 0) { - break; + Long noteId = Long.valueOf(id); + if (noteId > 0) { + count = db.delete(TABLE.NOTE, + NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); + } else { + Log.d(TAG, "Skip delete system folder"); } - count = db.delete(TABLE.NOTE, - NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); break; case URI_DATA: + // 删除便签数据 count = db.delete(TABLE.DATA, selection, selectionArgs); - deleteData = true; break; case URI_DATA_ITEM: id = uri.getPathSegments().get(1); count = db.delete(TABLE.DATA, DataColumns.ID + "=" + id + parseSelection(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; } + /** + * 更新数据方法 + * 处理便签和便签数据的更新操作 + * + * @param uri 更新URI + * @param values 要更新的值 + * @param selection 更新条件 + * @param selectionArgs 更新参数 + * @return 更新的行数 + */ @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)) { case URI_NOTE: - increaseNoteVersion(-1, selection, selectionArgs); + // 更新多个便签 + increaseNoteVersion(-1, selection, selectionArgs); // 增加便签版本号 count = db.update(TABLE.NOTE, values, selection, selectionArgs); break; case URI_NOTE_ITEM: + // 更新单个便签 id = uri.getPathSegments().get(1); - increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); + 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 = uri.getPathSegments().get(1); count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id + parseSelection(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; } + /** + * 解析选择条件 + * 将已有条件和新条件合并 + * + * @param selection 原始选择条件 + * @return 合并后的选择条件 + */ private String parseSelection(String selection) { - return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); + return TextUtils.isEmpty(selection) ? "" : " AND (" + selection + ")"; } + /** + * 增加便签版本号 + * 便签变更时更新版本号,用于同步 + * + * @param id 便签ID,-1表示多个便签 + * @param selection 选择条件 + * @param selectionArgs 选择参数 + */ private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { + // 获取可写数据库 + SQLiteDatabase db = mHelper.getWritableDatabase(); StringBuilder sql = new StringBuilder(120); + + // 构建更新SQL语句 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("="); + sql.append(NoteColumns.VERSION); + sql.append("+1 "); + + // 根据ID或条件构建WHERE子句 + if (id != -1) { 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(NoteColumns.ID); + sql.append("="); + sql.append(String.valueOf(id)); + if (!TextUtils.isEmpty(selection)) { + sql.append(parseSelection(selection)); + } + db.execSQL(sql.toString()); + } else if (!TextUtils.isEmpty(selection)) { + sql.append(" WHERE "); + sql.append("("); + sql.append(selection); + sql.append(")"); + if (selectionArgs == null) { + db.execSQL(sql.toString()); + } else { + db.execSQL(sql.toString(), selectionArgs); } - sql.append(selectString); } - - mHelper.getWritableDatabase().execSQL(sql.toString()); } + /** + * 获取内容类型 + * 根据URI返回MIME类型 + * + * @param uri 请求的URI + * @return MIME类型字符串 + */ @Override public String getType(Uri uri) { - // TODO Auto-generated method stub - return null; + // 根据URI类型返回不同的MIME类型 + switch (mMatcher.match(uri)) { + case URI_NOTE: + return Notes.NoteColumns.CONTENT_TYPE; + case URI_NOTE_ITEM: + return Notes.NoteColumns.CONTENT_ITEM_TYPE; + case URI_DATA: + return Notes.DataColumns.CONTENT_TYPE; + case URI_DATA_ITEM: + return Notes.DataColumns.CONTENT_ITEM_TYPE; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + } } - } diff --git a/app/src/main/java/net/micode/notes/model/Note.java b/app/src/main/java/net/micode/notes/model/Note.java index 6706cf6..4a1b0fd 100644 --- a/app/src/main/java/net/micode/notes/model/Note.java +++ b/app/src/main/java/net/micode/notes/model/Note.java @@ -33,25 +33,38 @@ import net.micode.notes.data.Notes.TextNote; import java.util.ArrayList; - +/** + * 便签核心数据模型类 + * + * 负责便签数据的管理和数据库操作,包括便签基本信息和内容数据的存储。 + * 该类是模型层的核心类,提供便签创建、更新和同步功能,与数据库交互。 + */ public class Note { - private ContentValues mNoteDiffValues; - private NoteData mNoteData; + private ContentValues mNoteDiffValues; // 存储便签属性变更 + private NoteData mNoteData; // 存储便签内容数据 private static final String TAG = "Note"; + /** - * Create a new note id for adding a new note to databases + * 创建新便签并返回便签ID + * + * @param context 应用上下文 + * @param folderId 所属文件夹ID + * @return 新创建的便签ID */ public static synchronized long getNewNoteId(Context context, long folderId) { - // Create a new note in the database + // 创建便签基本属性 ContentValues values = new ContentValues(); - long createdTime = System.currentTimeMillis(); + long createdTime = System.currentTimeMillis(); // 获取当前时间作为创建时间 values.put(NoteColumns.CREATED_DATE, createdTime); values.put(NoteColumns.MODIFIED_DATE, createdTime); - values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); - values.put(NoteColumns.LOCAL_MODIFIED, 1); - values.put(NoteColumns.PARENT_ID, folderId); + values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 设置类型为便签 + values.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记为本地已修改 + values.put(NoteColumns.PARENT_ID, folderId); // 设置所属文件夹 + + // 向数据库插入记录 Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); + // 从返回的URI中提取便签ID long noteId = 0; try { noteId = Long.valueOf(uri.getPathSegments().get(1)); @@ -59,69 +72,122 @@ public class Note { Log.e(TAG, "Get note id error :" + e.toString()); noteId = 0; } + + // 验证便签ID有效性 if (noteId == -1) { throw new IllegalStateException("Wrong note id:" + noteId); } return noteId; } + /** + * 构造函数,初始化便签数据容器 + */ public Note() { mNoteDiffValues = new ContentValues(); mNoteData = new NoteData(); } + /** + * 设置便签属性值 + * + * @param key 属性名 + * @param value 属性值 + */ public void setNoteValue(String key, String value) { - mNoteDiffValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + mNoteDiffValues.put(key, value); // 存储属性变更 + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记为已修改 + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 更新修改时间 } + /** + * 设置便签文本内容 + * + * @param key 内容属性名 + * @param value 内容值 + */ public void setTextData(String key, String value) { mNoteData.setTextData(key, value); } + /** + * 设置便签文本数据ID + * + * @param id 文本数据ID + */ public void setTextDataId(long id) { mNoteData.setTextDataId(id); } + /** + * 获取便签文本数据ID + * + * @return 文本数据ID + */ public long getTextDataId() { return mNoteData.mTextDataId; } + /** + * 设置通话记录数据ID + * + * @param id 通话记录数据ID + */ public void setCallDataId(long id) { mNoteData.setCallDataId(id); } + /** + * 设置通话记录数据 + * + * @param key 数据属性名 + * @param value 数据值 + */ public void setCallData(String key, String value) { mNoteData.setCallData(key, value); } + /** + * 检查便签是否有本地修改 + * + * @return 如果有本地修改返回true,否则返回false + */ public boolean isLocalModified() { return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); } + /** + * 将便签数据同步到数据库 + * + * @param context 应用上下文 + * @param noteId 便签ID + * @return 同步成功返回true,失败返回false + */ public boolean syncNote(Context context, long noteId) { + // 验证便签ID有效性 if (noteId <= 0) { throw new IllegalArgumentException("Wrong note id:" + noteId); } + // 如果没有本地修改,不需要同步 if (!isLocalModified()) { return true; } /** - * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and - * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the - * note data info + * 理论上,一旦数据变更,便签应该在LOCAL_MODIFIED和MODIFIED_DATE上更新 + * 为了数据安全,即使更新便签失败,我们也会更新便签数据信息 */ if (context.getContentResolver().update( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, null) == 0) { Log.e(TAG, "Update note error, should not happen"); - // Do not return, fall through + // 不返回,继续执行 } + // 清空变更缓存 mNoteDiffValues.clear(); + // 同步便签内容数据 if (mNoteData.isLocalModified() && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { return false; @@ -130,17 +196,21 @@ public class Note { return true; } + /** + * 便签数据内部类 + * + * 管理便签的文本内容和通话记录数据,处理与数据库的交互 + */ private class NoteData { - private long mTextDataId; - - private ContentValues mTextDataValues; - - private long mCallDataId; - - private ContentValues mCallDataValues; - + private long mTextDataId; // 文本数据ID + private ContentValues mTextDataValues; // 文本数据变更 + private long mCallDataId; // 通话记录数据ID + private ContentValues mCallDataValues; // 通话记录数据变更 private static final String TAG = "NoteData"; + /** + * 构造函数,初始化数据容器 + */ public NoteData() { mTextDataValues = new ContentValues(); mCallDataValues = new ContentValues(); @@ -148,10 +218,20 @@ public class Note { mCallDataId = 0; } + /** + * 检查数据是否有本地修改 + * + * @return 如果有本地修改返回true,否则返回false + */ boolean isLocalModified() { return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; } + /** + * 设置文本数据ID + * + * @param id 文本数据ID + */ void setTextDataId(long id) { if(id <= 0) { throw new IllegalArgumentException("Text data id should larger than 0"); @@ -159,6 +239,11 @@ public class Note { mTextDataId = id; } + /** + * 设置通话记录数据ID + * + * @param id 通话记录数据ID + */ void setCallDataId(long id) { if (id <= 0) { throw new IllegalArgumentException("Call data id should larger than 0"); @@ -166,32 +251,52 @@ public class Note { mCallDataId = id; } + /** + * 设置通话记录数据 + * + * @param key 数据属性名 + * @param value 数据值 + */ void setCallData(String key, String value) { - mCallDataValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + mCallDataValues.put(key, value); // 存储通话记录数据 + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记便签为已修改 + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 更新修改时间 } + /** + * 设置文本数据 + * + * @param key 数据属性名 + * @param value 数据值 + */ void setTextData(String key, String value) { - mTextDataValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + mTextDataValues.put(key, value); // 存储文本数据 + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记便签为已修改 + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 更新修改时间 } + /** + * 将数据推送到内容提供者(数据库) + * + * @param context 应用上下文 + * @param noteId 便签ID + * @return 成功返回URI,失败返回null + */ Uri pushIntoContentResolver(Context context, long noteId) { - /** - * Check for safety - */ + // 安全检查 if (noteId <= 0) { throw new IllegalArgumentException("Wrong note id:" + noteId); } + // 创建批量操作列表 ArrayList