diff --git a/src/main/java/net/micode/notes/data/Contact.java b/src/main/java/net/micode/notes/data/Contact.java index d97ac5d..6a4f1d5 100644 --- a/src/main/java/net/micode/notes/data/Contact.java +++ b/src/main/java/net/micode/notes/data/Contact.java @@ -25,10 +25,25 @@ import android.util.Log; import java.util.HashMap; +/** + * 联系人工具类,用于根据电话号码获取联系人姓名 + *
+ * 该类提供了获取联系人信息的静态方法,并使用缓存机制提高查询效率 + *
+ */ public class Contact { + /** + * 联系人缓存,用于存储已查询过的电话号码和对应联系人姓名 + */ private static HashMap+ * 该方法会先检查缓存,如果缓存中存在则直接返回,否则查询联系人数据库 + *
+ * @param context 上下文对象 + * @param phoneNumber 电话号码 + * @return 联系人姓名,若未找到则返回null + */ public static String getContact(Context context, String phoneNumber) { if(sContactCache == null) { sContactCache = new HashMap+ * 该类包含了便签应用的核心数据定义,包括便签类型、系统文件夹ID、意图额外参数、 + * 组件类型、以及各种URI定义。同时还定义了便签表和数据表的列名,以及文本便签和 + * 通话便签的具体数据结构。 + *
+ */ public class Notes { + /** + * 内容提供者的授权名称,用于构建内容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; /** - * 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 + * 以下是系统文件夹的标识符 + * {@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; + /** + * 回收站文件夹ID,用于存储被删除的便签和文件夹 + */ public static final int ID_TRASH_FOLER = -3; + /** + * 意图额外参数:提醒日期 + */ public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; + /** + * 意图额外参数:背景颜色ID + */ public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; + /** + * 意图额外参数:组件ID + */ public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; + /** + * 意图额外参数:组件类型 + */ public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; + /** + * 意图额外参数:文件夹ID + */ 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; + /** + * 组件类型:2x2大小的便签组件 + */ public static final int TYPE_WIDGET_2X = 0; + /** + * 组件类型:4x4大小的便签组件 + */ public static final int TYPE_WIDGET_4X = 1; + /** + * 数据类型常量类,定义了不同类型便签的数据项类型 + */ 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 + *类型: 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
+ * GTask 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
+ * 通用数据列,含义由{@link # MIMETYPE}决定,用于整数数据类型 + *类型: INTEGER
*/ public static final String DATA1 = "data1"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *Type: INTEGER
+ * 通用数据列,含义由{@link # MIMETYPE}决定,用于整数数据类型 + *类型: INTEGER
*/ public static final String DATA2 = "data2"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *Type: TEXT
+ * 通用数据列,含义由{@link # MIMETYPE}决定,用于文本数据类型 + *类型: TEXT
*/ public static final String DATA3 = "data3"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *Type: TEXT
+ * 通用数据列,含义由{@link # MIMETYPE}决定,用于文本数据类型 + *类型: TEXT
*/ public static final String DATA4 = "data4"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *Type: TEXT
+ * 通用数据列,含义由{@link # MIMETYPE}决定,用于文本数据类型 + *类型: TEXT
*/ public static final String DATA5 = "data5"; } 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
+ * 指示文本是否处于清单模式 + *类型: Integer 1:清单模式 0:普通模式
*/ public static final String MODE = DATA1; + /** + * 清单模式 + */ public static final int MODE_CHECK_LIST = 1; + /** + * 文本便签的目录MIME类型 + */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; + /** + * 文本便签的单项MIME类型 + */ 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"); } public static final class CallNote implements DataColumns { /** - * Call date for this record - *Type: INTEGER (long)
+ * 此记录的通话日期 + *类型: INTEGER (long)
*/ public static final String CALL_DATE = DATA1; /** - * Phone number for this record - *Type: TEXT
+ * 此记录的电话号码 + *类型: TEXT
*/ public static final String PHONE_NUMBER = DATA3; + /** + * 通话便签的目录MIME类型 + */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; + /** + * 通话便签的单项MIME类型 + */ 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/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index ffe5d57..f6b9c37 100644 --- a/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java +++ b/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java @@ -27,189 +27,219 @@ import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.NoteColumns; +/** + * 笔记数据库帮助类,负责数据库的创建、升级及触发器管理 + * 采用单例模式确保数据库连接的唯一性,封装了表结构定义、系统文件夹初始化等核心操作 + */ public class NotesDatabaseHelper extends SQLiteOpenHelper { + // 数据库文件名 private static final String DB_NAME = "note.db"; - + // 数据库版本号,用于升级控制 private static final int DB_VERSION = 4; + /** + * 数据库表名常量定义 + */ public interface TABLE { + // 笔记表,存储笔记和文件夹的元数据 public static final String NOTE = "note"; - + // 数据表,存储笔记的具体内容(如文本、通话记录等) public static final String DATA = "data"; } + // 日志标签 private static final String TAG = "NotesDatabaseHelper"; - + // 单例实例 private static NotesDatabaseHelper mInstance; + /** + * 笔记表创建SQL语句 + * 包含笔记/文件夹的基本属性:ID、父文件夹ID、提醒时间、背景色、创建/修改时间等 + */ private static final String CREATE_NOTE_TABLE_SQL = - "CREATE TABLE " + TABLE.NOTE + "(" + - NoteColumns.ID + " INTEGER PRIMARY KEY," + - NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + - NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + - NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + - NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + - ")"; + "CREATE TABLE " + TABLE.NOTE + "(" + + NoteColumns.ID + " INTEGER PRIMARY KEY," + + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + + NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + + ")"; + /** + * 数据表创建SQL语句 + * 存储笔记的具体内容,支持多种类型(文本、通话记录等),通过MIME_TYPE区分 + */ private static final String CREATE_DATA_TABLE_SQL = - "CREATE TABLE " + TABLE.DATA + "(" + - DataColumns.ID + " INTEGER PRIMARY KEY," + - DataColumns.MIME_TYPE + " TEXT NOT NULL," + - DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA1 + " INTEGER," + - DataColumns.DATA2 + " INTEGER," + - DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + - ")"; + "CREATE TABLE " + TABLE.DATA + "(" + + DataColumns.ID + " INTEGER PRIMARY KEY," + + DataColumns.MIME_TYPE + " TEXT NOT NULL," + + DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + + DataColumns.DATA1 + " INTEGER," + + DataColumns.DATA2 + " INTEGER," + + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + + ")"; + /** + * 为数据表的NOTE_ID字段创建索引 + * 优化根据笔记ID查询数据的性能 + */ private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = - "CREATE INDEX IF NOT EXISTS note_id_index ON " + - TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; + "CREATE INDEX IF NOT EXISTS note_id_index ON " + + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; /** - * Increase folder's note count when move note to the folder + * 触发器:更新笔记父文件夹时,增加新父文件夹的笔记计数 */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER increase_folder_count_on_update "+ - " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + - " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + - " END"; + "CREATE TRIGGER increase_folder_count_on_update "+ + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " END"; /** - * Decrease folder's note count when move note from folder + * 触发器:更新笔记父文件夹时,减少旧父文件夹的笔记计数 */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER decrease_folder_count_on_update " + - " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + - " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + - " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + - " END"; + "CREATE TRIGGER decrease_folder_count_on_update " + + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + + " END"; /** - * Increase folder's note count when insert new note to the folder + * 触发器:插入新笔记时,增加其父文件夹的笔记计数 */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = - "CREATE TRIGGER increase_folder_count_on_insert " + - " AFTER INSERT ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + - " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + - " END"; + "CREATE TRIGGER increase_folder_count_on_insert " + + " AFTER INSERT ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " END"; /** - * Decrease folder's note count when delete note from the folder + * 触发器:删除笔记时,减少其父文件夹的笔记计数 */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = - "CREATE TRIGGER decrease_folder_count_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + - " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + - " AND " + NoteColumns.NOTES_COUNT + ">0;" + - " END"; + "CREATE TRIGGER decrease_folder_count_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0;" + + " END"; /** - * Update note's content when insert data with type {@link DataConstants#NOTE} + * 触发器:插入文本笔记数据时,更新对应笔记的摘要内容 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = - "CREATE TRIGGER update_note_content_on_insert " + - " AFTER INSERT ON " + TABLE.DATA + - " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + - " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + - " END"; + "CREATE TRIGGER update_note_content_on_insert " + + " AFTER INSERT ON " + TABLE.DATA + + " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END"; /** - * Update note's content when data with {@link DataConstants#NOTE} type has changed + * 触发器:更新文本笔记数据时,同步更新对应笔记的摘要内容 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER update_note_content_on_update " + - " AFTER UPDATE ON " + TABLE.DATA + - " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + - " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + - " END"; + "CREATE TRIGGER update_note_content_on_update " + + " AFTER UPDATE ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END"; /** - * Update note's content when data with {@link DataConstants#NOTE} type has deleted + * 触发器:删除文本笔记数据时,清空对应笔记的摘要内容 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = - "CREATE TRIGGER update_note_content_on_delete " + - " AFTER delete ON " + TABLE.DATA + - " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=''" + - " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + - " END"; + "CREATE TRIGGER update_note_content_on_delete " + + " AFTER delete ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=''" + + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + + " END"; /** - * Delete datas belong to note which has been deleted + * 触发器:删除笔记时,级联删除其关联的数据记录 */ private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = - "CREATE TRIGGER delete_data_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN" + - " DELETE FROM " + TABLE.DATA + - " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + - " END"; + "CREATE TRIGGER delete_data_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.DATA + + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + + " END"; /** - * Delete notes belong to folder which has been deleted + * 触发器:删除文件夹时,级联删除其包含的所有笔记 */ private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = - "CREATE TRIGGER folder_delete_notes_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN" + - " DELETE FROM " + TABLE.NOTE + - " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + - " END"; + "CREATE TRIGGER folder_delete_notes_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END"; /** - * Move notes belong to folder which has been moved to trash folder + * 触发器:当文件夹被移到回收站时,其包含的所有笔记也自动移到回收站 */ private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = - "CREATE TRIGGER folder_move_notes_on_trash " + - " AFTER UPDATE ON " + TABLE.NOTE + - " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + - " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + - " END"; + "CREATE TRIGGER folder_move_notes_on_trash " + + " AFTER UPDATE ON " + TABLE.NOTE + + " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END"; + /** + * 私有构造方法,防止外部实例化 + * @param context 上下文对象 + */ public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } + /** + * 创建笔记表并初始化相关触发器和系统文件夹 + * @param db 数据库实例 + */ public void createNoteTable(SQLiteDatabase db) { db.execSQL(CREATE_NOTE_TABLE_SQL); reCreateNoteTableTriggers(db); @@ -217,6 +247,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { Log.d(TAG, "note table has been created"); } + /** + * 重新创建笔记表的所有触发器(先删除旧触发器再创建新的) + * @param db 数据库实例 + */ private void reCreateNoteTableTriggers(SQLiteDatabase db) { db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); @@ -235,41 +269,41 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); } + /** + * 初始化系统文件夹(通话记录文件夹、根目录、临时文件夹、回收站) + * @param db 数据库实例 + */ private void createSystemFolder(SQLiteDatabase db) { ContentValues values = new ContentValues(); - /** - * call record foler for call notes - */ + // 通话记录文件夹:存储通话笔记 values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - /** - * root folder which is default folder - */ + // 根目录:默认文件夹 values.clear(); values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - /** - * temporary folder which is used for moving note - */ + // 临时文件夹:用于移动笔记的中间存储 values.clear(); values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - /** - * create trash folder - */ + // 回收站:存储被删除的笔记和文件夹 values.clear(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } + /** + * 创建数据表并初始化相关触发器和索引 + * @param db 数据库实例 + */ public void createDataTable(SQLiteDatabase db) { db.execSQL(CREATE_DATA_TABLE_SQL); reCreateDataTableTriggers(db); @@ -277,6 +311,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { Log.d(TAG, "data table has been created"); } + /** + * 重新创建数据表的所有触发器(先删除旧触发器再创建新的) + * @param db 数据库实例 + */ private void reCreateDataTableTriggers(SQLiteDatabase db) { db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); @@ -287,6 +325,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); } + /** + * 获取单例实例 + * @param context 上下文对象 + * @return 数据库帮助类实例 + */ static synchronized NotesDatabaseHelper getInstance(Context context) { if (mInstance == null) { mInstance = new NotesDatabaseHelper(context); @@ -294,45 +337,64 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { return mInstance; } + /** + * 数据库首次创建时调用,初始化表结构 + * @param db 数据库实例 + */ @Override public void onCreate(SQLiteDatabase db) { createNoteTable(db); createDataTable(db); } + /** + * 数据库版本升级时调用 + * @param db 数据库实例 + * @param oldVersion 旧版本号 + * @param newVersion 新版本号 + */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { boolean reCreateTriggers = false; boolean skipV2 = false; + // 版本1升级到版本2 if (oldVersion == 1) { upgradeToV2(db); - skipV2 = true; // this upgrade including the upgrade from v2 to v3 + skipV2 = true; // 此升级包含了从v2到v3的升级 oldVersion++; } + // 版本2升级到版本3(跳过已包含在v1->v2中的情况) if (oldVersion == 2 && !skipV2) { upgradeToV3(db); reCreateTriggers = true; oldVersion++; } + // 版本3升级到版本4 if (oldVersion == 3) { upgradeToV4(db); oldVersion++; } + // 如果需要,重新创建触发器 if (reCreateTriggers) { reCreateNoteTableTriggers(db); reCreateDataTableTriggers(db); } + // 验证升级是否完成 if (oldVersion != newVersion) { throw new IllegalStateException("Upgrade notes database to version " + newVersion + "fails"); } } + /** + * 升级到版本2:删除旧表并重新创建(结构调整) + * @param db 数据库实例 + */ private void upgradeToV2(SQLiteDatabase db) { db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); @@ -340,23 +402,31 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { createDataTable(db); } + /** + * 升级到版本3:添加GTask ID字段和回收站文件夹 + * @param db 数据库实例 + */ private void upgradeToV3(SQLiteDatabase db) { - // drop unused triggers + // 删除未使用的触发器 db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); - // add a column for gtask id + // 添加GTask ID字段,用于同步 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''"); - // add a trash system folder + // 添加回收站系统文件夹 ContentValues values = new ContentValues(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } + /** + * 升级到版本4:添加版本号字段,用于数据同步控制 + * @param db 数据库实例 + */ private void upgradeToV4(SQLiteDatabase db) { db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); } -} +} \ No newline at end of file diff --git a/src/main/java/net/micode/notes/data/NotesProvider.java b/src/main/java/net/micode/notes/data/NotesProvider.java index edb0a60..678534f 100644 --- a/src/main/java/net/micode/notes/data/NotesProvider.java +++ b/src/main/java/net/micode/notes/data/NotesProvider.java @@ -16,7 +16,6 @@ package net.micode.notes.data; - import android.app.SearchManager; import android.content.ContentProvider; import android.content.ContentUris; @@ -34,90 +33,137 @@ import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.NotesDatabaseHelper.TABLE; - +/** + * 便签应用的内容提供者(ContentProvider) + * 负责管理便签数据的CRUD操作,包括搜索功能 + */ public class NotesProvider extends ContentProvider { + /** + * URI匹配器,用于匹配不同的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匹配类型常量定义,用于区分不同的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匹配规则 + * 为不同类型的URI请求配置对应的匹配类型 + */ static { mMatcher = new UriMatcher(UriMatcher.NO_MATCH); + // 匹配便签表的URI mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); + // 匹配数据表的URI mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); + // 匹配搜索相关的URI 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); } /** - * 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 + "," - + "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; + + 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; + /** + * 便签片段搜索查询的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; + + " FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + /** + * 初始化内容提供者 + * @return 如果初始化成功返回true,否则返回false + */ @Override public boolean onCreate() { mHelper = NotesDatabaseHelper.getInstance(getContext()); return true; } + /** + * 查询数据 + * @param uri 要查询的URI + * @param projection 要返回的列,null表示所有列 + * @param selection 查询条件,null表示无条件 + * @param selectionArgs 查询条件参数 + * @param sortOrder 排序方式,null表示默认排序 + * @return 包含查询结果的Cursor对象 + */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { + 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); 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); 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; + // 根据URI类型获取搜索关键词 if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { if (uri.getPathSegments().size() > 1) { searchString = uri.getPathSegments().get(1); @@ -126,11 +172,13 @@ public class NotesProvider extends ContentProvider { searchString = uri.getQueryParameter("pattern"); } + // 如果搜索关键词为空,返回null if (TextUtils.isEmpty(searchString)) { return null; } try { + // 构造模糊查询字符串 searchString = String.format("%%%s%%", searchString); c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, new String[] { searchString }); @@ -141,21 +189,33 @@ 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); } else { @@ -166,37 +226,49 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } - // Notify the note uri + + // 通知便签URI的数据变化 if (noteId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); } - // Notify the data uri + // 通知数据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 删除条件,null表示无条件 + * @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) { @@ -206,10 +278,12 @@ public class NotesProvider extends ContentProvider { 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); @@ -218,37 +292,55 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } + + // 如果成功删除数据,发送数据变化通知 if (count > 0) { if (deleteData) { + // 如果删除的是数据表记录,通知便签URI数据变化 getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); } + // 通知当前URI数据变化 getContext().getContentResolver().notifyChange(uri, null); } return count; } + /** + * 更新数据 + * @param uri 要更新数据的URI + * @param values 更新的数据值 + * @param selection 更新条件,null表示无条件 + * @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); count = db.update(TABLE.NOTE, values, selection, selectionArgs); break; case URI_NOTE_ITEM: + // 更新单条便签记录,增加版本号 id = uri.getPathSegments().get(1); 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); @@ -258,19 +350,35 @@ public class NotesProvider extends ContentProvider { throw new IllegalArgumentException("Unknown URI " + uri); } + // 如果成功更新数据,发送数据变化通知 if (count > 0) { if (updateData) { + // 如果更新的是数据表记录,通知便签URI数据变化 getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); } + // 通知当前URI数据变化 getContext().getContentResolver().notifyChange(uri, null); } return count; } + /** + * 解析查询/更新条件 + * 将原始条件转换为SQL语句中的AND条件 + * @param selection 原始条件字符串 + * @return 格式化后的条件字符串 + */ private String parseSelection(String selection) { return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); } + /** + * 增加便签版本号 + * 当便签被修改时,调用此方法增加版本号 + * @param id 便签ID,如果id>0则更新指定便签,否则更新所有匹配条件的便签 + * @param selection 更新条件 + * @param selectionArgs 更新条件参数 + */ private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); @@ -279,6 +387,7 @@ public class NotesProvider extends ContentProvider { sql.append(NoteColumns.VERSION); sql.append("=" + NoteColumns.VERSION + "+1 "); + // 构造WHERE子句 if (id > 0 || !TextUtils.isEmpty(selection)) { sql.append(" WHERE "); } @@ -287,19 +396,25 @@ public class NotesProvider extends ContentProvider { } 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()); } + /** + * 返回指定URI的MIME类型 + * @param uri 要获取MIME类型的URI + * @return MIME类型字符串 + */ @Override public String getType(Uri uri) { - // TODO Auto-generated method stub + // TODO: 实现此方法,返回合适的MIME类型 return null; } - -} +} \ No newline at end of file diff --git a/src/main/java/net/micode/notes/gtask/data/MetaData.java b/src/main/java/net/micode/notes/gtask/data/MetaData.java index 3a2050b..2a39a68 100644 --- a/src/main/java/net/micode/notes/gtask/data/MetaData.java +++ b/src/main/java/net/micode/notes/gtask/data/MetaData.java @@ -24,37 +24,84 @@ import net.micode.notes.tool.GTaskStringUtils; import org.json.JSONException; import org.json.JSONObject; - +/** + * MetaData类是Task的子类,用于存储与Google Tasks同步相关的元数据信息。 + *+ * 该类主要用于在GTask同步过程中存储任务的关联信息,将元数据以JSON格式存储在 + * Task的notes字段中,并提供了相应的解析和访问方法。 + *
+ */ public class MetaData extends Task { private final static String TAG = MetaData.class.getSimpleName(); + /** + * 与当前元数据相关联的Google Task ID + */ private String mRelatedGid = null; + /** + * 设置元数据信息 + *+ * 将Google Task ID添加到元数据JSON对象中,并将其存储在Task的notes字段中。 + * 同时设置任务名称为元数据标识名称。 + *
+ * + * @param gid 与元数据关联的Google Task ID + * @param metaInfo 包含元数据信息的JSON对象 + */ public void setMeta(String gid, JSONObject metaInfo) { try { + // 将关联的Google Task ID添加到元数据中 metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); } catch (JSONException e) { Log.e(TAG, "failed to put related gid"); } + // 将元数据JSON对象转换为字符串并存储在notes字段中 setNotes(metaInfo.toString()); + // 设置任务名称为元数据标识名称 setName(GTaskStringUtils.META_NOTE_NAME); } + /** + * 获取与当前元数据相关联的Google Task ID + * + * @return 关联的Google Task ID,如果没有则返回null + */ public String getRelatedGid() { return mRelatedGid; } + /** + * 判断当前元数据是否值得保存 + *+ * 只有当notes字段不为null时,元数据才值得保存 + *
+ * + * @return 如果notes字段不为null则返回true,否则返回false + */ @Override public boolean isWorthSaving() { return getNotes() != null; } + /** + * 从远程JSON数据设置内容 + *+ * 从远程获取的JSON数据中解析元信息,并提取关联的Google Task ID + *
+ * + * @param js 包含远程数据的JSON对象 + */ @Override public void setContentByRemoteJSON(JSONObject js) { + // 调用父类方法设置基本内容 super.setContentByRemoteJSON(js); + // 如果notes字段不为null,则解析元数据 if (getNotes() != null) { try { + // 解析notes字段中的JSON元数据 JSONObject metaInfo = new JSONObject(getNotes().trim()); + // 提取关联的Google Task ID mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); } catch (JSONException e) { Log.w(TAG, "failed to get related gid"); @@ -63,17 +110,45 @@ public class MetaData extends Task { } } + /** + * 从本地JSON数据设置内容(不支持) + *+ * 该方法不应该被调用,因为MetaData主要用于处理远程同步数据 + *
+ * + * @param js 本地JSON对象 + * @throws IllegalAccessError 总是抛出此异常,表示该方法不应该被调用 + */ @Override public void setContentByLocalJSON(JSONObject js) { - // this function should not be called + // 此方法不应该被调用 throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); } + /** + * 从内容获取本地JSON数据(不支持) + *+ * 该方法不应该被调用,因为MetaData主要用于处理远程同步数据 + *
+ * + * @return 本地JSON对象 + * @throws IllegalAccessError 总是抛出此异常,表示该方法不应该被调用 + */ @Override public JSONObject getLocalJSONFromContent() { throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); } + /** + * 获取同步操作(不支持) + *+ * 该方法不应该被调用,因为MetaData主要用于处理远程同步数据 + *
+ * + * @param c 本地数据库游标 + * @return 同步操作类型 + * @throws IllegalAccessError 总是抛出此异常,表示该方法不应该被调用 + */ @Override public int getSyncAction(Cursor c) { throw new IllegalAccessError("MetaData:getSyncAction should not be called"); diff --git a/src/main/java/net/micode/notes/gtask/data/Node.java b/src/main/java/net/micode/notes/gtask/data/Node.java index 63950e0..dea45d5 100644 --- a/src/main/java/net/micode/notes/gtask/data/Node.java +++ b/src/main/java/net/micode/notes/gtask/data/Node.java @@ -20,33 +20,82 @@ import android.database.Cursor; import org.json.JSONObject; +/** + * Node类是GTask同步系统中的抽象基类,定义了同步节点的基本属性和操作接口。 + *+ * 该类为所有参与GTask同步的实体(如Task、TaskList等)提供了统一的接口和基础属性, + * 包括唯一标识符、名称、最后修改时间、删除状态等,并定义了同步操作所需的抽象方法。 + *
+ */ public abstract class Node { + /** + * 同步操作:无操作 + */ public static final int SYNC_ACTION_NONE = 0; + /** + * 同步操作:向远程添加节点 + */ public static final int SYNC_ACTION_ADD_REMOTE = 1; + /** + * 同步操作:向本地添加节点 + */ public static final int SYNC_ACTION_ADD_LOCAL = 2; + /** + * 同步操作:从远程删除节点 + */ public static final int SYNC_ACTION_DEL_REMOTE = 3; + /** + * 同步操作:从本地删除节点 + */ public static final int SYNC_ACTION_DEL_LOCAL = 4; + /** + * 同步操作:更新远程节点 + */ public static final int SYNC_ACTION_UPDATE_REMOTE = 5; + /** + * 同步操作:更新本地节点 + */ public static final int SYNC_ACTION_UPDATE_LOCAL = 6; + /** + * 同步操作:更新冲突(本地和远程都有修改) + */ public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; + /** + * 同步操作:同步错误 + */ public static final int SYNC_ACTION_ERROR = 8; + /** + * 节点的唯一标识符(Google Task ID) + */ private String mGid; + /** + * 节点的名称 + */ private String mName; + /** + * 节点的最后修改时间戳 + */ private long mLastModified; + /** + * 节点的删除状态 + */ private boolean mDeleted; + /** + * 构造函数,初始化节点的默认值 + */ public Node() { mGid = null; mName = ""; @@ -54,46 +103,119 @@ public abstract class Node { mDeleted = false; } + /** + * 获取创建节点的同步操作JSON对象 + * + * @param actionId 操作ID + * @return 创建操作的JSON对象 + */ public abstract JSONObject getCreateAction(int actionId); + /** + * 获取更新节点的同步操作JSON对象 + * + * @param actionId 操作ID + * @return 更新操作的JSON对象 + */ public abstract JSONObject getUpdateAction(int actionId); + /** + * 从远程JSON数据设置节点内容 + * + * @param js 包含远程数据的JSON对象 + */ public abstract void setContentByRemoteJSON(JSONObject js); + /** + * 从本地JSON数据设置节点内容 + * + * @param js 包含本地数据的JSON对象 + */ public abstract void setContentByLocalJSON(JSONObject js); + /** + * 从节点内容获取本地JSON数据 + * + * @return 包含本地数据的JSON对象 + */ public abstract JSONObject getLocalJSONFromContent(); + /** + * 根据本地数据库游标获取同步操作类型 + * + * @param c 本地数据库游标 + * @return 同步操作类型(参考SYNC_ACTION_*常量) + */ public abstract int getSyncAction(Cursor c); + /** + * 设置节点的唯一标识符 + * + * @param gid 节点的唯一标识符(Google Task ID) + */ public void setGid(String gid) { this.mGid = gid; } + /** + * 设置节点的名称 + * + * @param name 节点的名称 + */ public void setName(String name) { this.mName = name; } + /** + * 设置节点的最后修改时间戳 + * + * @param lastModified 最后修改时间戳 + */ public void setLastModified(long lastModified) { this.mLastModified = lastModified; } + /** + * 设置节点的删除状态 + * + * @param deleted 是否删除 + */ public void setDeleted(boolean deleted) { this.mDeleted = deleted; } + /** + * 获取节点的唯一标识符 + * + * @return 节点的唯一标识符(Google Task ID) + */ public String getGid() { return this.mGid; } + /** + * 获取节点的名称 + * + * @return 节点的名称 + */ public String getName() { return this.mName; } + /** + * 获取节点的最后修改时间戳 + * + * @return 最后修改时间戳 + */ public long getLastModified() { return this.mLastModified; } + /** + * 获取节点的删除状态 + * + * @return 是否删除 + */ public boolean getDeleted() { return this.mDeleted; } diff --git a/src/main/java/net/micode/notes/gtask/data/SqlData.java b/src/main/java/net/micode/notes/gtask/data/SqlData.java index d3ec3be..3d537ca 100644 --- a/src/main/java/net/micode/notes/gtask/data/SqlData.java +++ b/src/main/java/net/micode/notes/gtask/data/SqlData.java @@ -35,42 +35,131 @@ import org.json.JSONException; import org.json.JSONObject; +/** + * SqlData类是小米笔记应用中GTask同步模块的核心数据处理类,负责本地SQL数据的CRUD操作。CRUD 是数据库操作的四种基本功能缩写: + +- C(Create):创建新笔记数据 +- R(Read):读取现有笔记数据 +- U(Update):更新笔记数据 +- D(Delete):删除笔记数据 + *+ * 在Notes与Google Tasks的同步过程中,该类扮演着桥梁角色: + * 1. 接收并解析来自GTask的JSON格式数据 + * 2. 将数据转换为本地数据库可存储的格式 + * 3. 通过ContentResolver与本地ContentProvider交互,执行数据的插入、更新和删除操作 + * 4. 支持数据差异跟踪,仅提交修改的字段以优化性能 + * 5. 提供版本验证机制,避免同步冲突 + *
+ *+ * 该类是同步流程中的关键组件,确保本地笔记数据与Google Tasks之间的数据一致性和完整性。 + *
+ */ public class SqlData { private static final String TAG = SqlData.class.getSimpleName(); + /** + * 无效ID常量,用于标记未初始化或无效的数据ID + * 在同步过程中用于区分新创建的数据和已存在的数据 + */ private static final int INVALID_ID = -99999; + /** + * 数据库查询投影列,定义了从数据库中获取的数据字段 + * 包含数据ID、MIME类型、内容以及扩展字段DATA1和DATA3 + */ public static final String[] PROJECTION_DATA = new String[] { DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, DataColumns.DATA3 }; + /** + * 数据ID列在投影中的索引位置 + */ public static final int DATA_ID_COLUMN = 0; + /** + * 数据MIME类型列在投影中的索引位置 + * 用于区分不同类型的数据(如文本笔记、通话记录等) + */ public static final int DATA_MIME_TYPE_COLUMN = 1; + /** + * 数据内容列在投影中的索引位置 + * 存储笔记的主要内容 + */ public static final int DATA_CONTENT_COLUMN = 2; + /** + * 数据内容DATA1字段在投影中的索引位置 + * 用于存储整数类型的扩展数据(如笔记模式、通话日期等) + */ public static final int DATA_CONTENT_DATA_1_COLUMN = 3; + /** + * 数据内容DATA3字段在投影中的索引位置 + * 用于存储字符串类型的扩展数据(如电话号码等) + */ public static final int DATA_CONTENT_DATA_3_COLUMN = 4; + /** + * 用于访问ContentProvider的ContentResolver实例 + * 是与本地数据库交互的核心接口 + */ private ContentResolver mContentResolver; + /** + * 标记是否为新建数据 + * true表示该数据需要插入数据库,false表示需要更新数据库 + */ private boolean mIsCreate; + /** + * 数据ID + * 唯一标识数据库中的数据记录 + */ private long mDataId; + /** + * 数据MIME类型 + * 定义数据的类型,如文本笔记(DataConstants.NOTE)或通话记录 + */ private String mDataMimeType; + /** + * 数据内容 + * 存储笔记的主要文本内容 + */ private String mDataContent; + /** + * 数据内容的DATA1字段 + * 根据MIME类型不同存储不同的整数数据,如: + * - 对于文本笔记:存储笔记模式(普通模式或 checklist 模式) + * - 对于通话记录:存储通话日期 + */ private long mDataContentData1; + /** + * 数据内容的DATA3字段 + * 根据MIME类型不同存储不同的字符串数据,如: + * - 对于通话记录:存储电话号码 + */ private String mDataContentData3; + /** + * 用于跟踪数据变化的ContentValues对象 + * 只包含已修改的字段,用于优化数据库更新操作 + */ private ContentValues mDiffDataValues; + /** + * 构造函数,用于创建新的SqlData实例(用于插入新数据) + *+ * 初始化所有字段为默认值,并将mIsCreate标记设置为true + *
+ * + * @param context 上下文对象,用于获取ContentResolver实例 + */ public SqlData(Context context) { mContentResolver = context.getContentResolver(); mIsCreate = true; @@ -82,6 +171,15 @@ public class SqlData { mDiffDataValues = new ContentValues(); } + /** + * 构造函数,用于从数据库Cursor加载SqlData实例(用于更新现有数据) + *+ * 从Cursor中读取数据并初始化所有字段,将mIsCreate标记设置为false + *
+ * + * @param context 上下文对象,用于获取ContentResolver实例 + * @param c 数据库Cursor对象,包含要加载的数据记录 + */ public SqlData(Context context, Cursor c) { mContentResolver = context.getContentResolver(); mIsCreate = false; @@ -89,6 +187,14 @@ public class SqlData { mDiffDataValues = new ContentValues(); } + /** + * 从数据库Cursor中加载数据到当前SqlData实例 + *+ * 根据PROJECTION_DATA中定义的列顺序,从Cursor中读取对应字段的值 + *
+ * + * @param c 包含数据的Cursor对象,必须指向有效的数据行 + */ private void loadFromCursor(Cursor c) { mDataId = c.getLong(DATA_ID_COLUMN); mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); @@ -97,6 +203,16 @@ public class SqlData { mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); } + /** + * 从JSON对象设置数据内容,并跟踪数据变化 + *+ * 该方法是同步过程中的关键方法,用于将从GTask获取的JSON数据转换为本地数据格式 + * 同时会跟踪数据变化,仅将修改的字段记录到mDiffDataValues中 + *
+ * + * @param js 包含GTask数据的JSON对象 + * @throws JSONException 如果JSON解析失败 + */ public void setContent(JSONObject js) throws JSONException { long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; if (mIsCreate || mDataId != dataId) { @@ -130,6 +246,16 @@ public class SqlData { mDataContentData3 = dataContentData3; } + /** + * 获取当前数据的JSON表示 + *+ * 将本地数据转换为JSON格式,用于与GTask进行数据交换 + * 注意:只有已提交到数据库的数据才能获取JSON表示 + *
+ * + * @return 包含当前数据的JSON对象,如果数据未提交到数据库则返回null + * @throws JSONException 如果JSON构建失败 + */ public JSONObject getContent() throws JSONException { if (mIsCreate) { Log.e(TAG, "it seems that we haven't created this in database yet"); @@ -144,6 +270,20 @@ public class SqlData { return js; } + /** + * 将数据提交到本地数据库 + *+ * 这是SqlData类的核心方法,根据mIsCreate标记执行插入或更新操作: + * 1. 如果是新数据(mIsCreate=true),执行插入操作 + * 2. 如果是现有数据(mIsCreate=false),仅当有数据变化时执行更新操作 + * 3. 支持版本验证机制,避免在同步过程中出现数据冲突 + *
+ * + * @param noteId 所属的Note ID,用于关联到具体的笔记 + * @param validateVersion 是否验证版本,true表示需要进行版本检查 + * @param version 版本号,用于版本验证 + * @throws ActionFailureException 如果数据提交失败 + */ public void commit(long noteId, boolean validateVersion, long version) { if (mIsCreate) { @@ -183,6 +323,14 @@ public class SqlData { mIsCreate = false; } + /** + * 获取数据ID + *+ * 返回当前数据的唯一标识符,用于在数据库中定位该记录 + *
+ * + * @return 数据ID,如果数据未提交到数据库则返回INVALID_ID + */ public long getId() { return mDataId; } diff --git a/src/main/java/net/micode/notes/gtask/data/SqlNote.java b/src/main/java/net/micode/notes/gtask/data/SqlNote.java index 79a4095..ad39ff5 100644 --- a/src/main/java/net/micode/notes/gtask/data/SqlNote.java +++ b/src/main/java/net/micode/notes/gtask/data/SqlNote.java @@ -38,11 +38,29 @@ import org.json.JSONObject; import java.util.ArrayList; +/** + * SqlNote类是小米笔记应用中GTask同步模块的核心类,负责本地笔记数据的CRUD操作。 + *+ * 在Notes与Google Tasks的同步过程中,该类扮演着重要角色: + * 1. 封装本地笔记数据模型,支持笔记和文件夹两种类型 + * 2. 通过ContentResolver与本地ContentProvider交互,执行数据的插入、更新和删除操作 + * 3. 支持将JSON格式的GTask数据转换为本地数据模型 + * 4. 支持将本地数据模型转换为JSON格式以便与GTask同步 + * 5. 实现数据差异跟踪,仅提交修改的字段以优化性能 + * 6. 提供版本验证机制,避免同步冲突 + *
+ */ public class SqlNote { private static final String TAG = SqlNote.class.getSimpleName(); + /** + * 无效的ID值,用于初始化和错误检查 + */ private static final int INVALID_ID = -99999; +/** + * 查询笔记时使用的投影列,包含笔记的所有核心字段 + */ public static final String[] PROJECTION_NOTE = new String[] { NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID, NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE, @@ -52,76 +70,186 @@ public class SqlNote { NoteColumns.VERSION }; +/** + * 投影列索引:笔记ID + */ public static final int ID_COLUMN = 0; + /** + * 投影列索引:提醒日期 + */ public static final int ALERTED_DATE_COLUMN = 1; + /** + * 投影列索引:背景颜色ID + */ public static final int BG_COLOR_ID_COLUMN = 2; + /** + * 投影列索引:创建日期 + */ public static final int CREATED_DATE_COLUMN = 3; + /** + * 投影列索引:是否有附件 + */ public static final int HAS_ATTACHMENT_COLUMN = 4; + /** + * 投影列索引:修改日期 + */ public static final int MODIFIED_DATE_COLUMN = 5; + /** + * 投影列索引:笔记数量 + */ public static final int NOTES_COUNT_COLUMN = 6; + /** + * 投影列索引:父文件夹ID + */ public static final int PARENT_ID_COLUMN = 7; + /** + * 投影列索引:笔记摘要 + */ public static final int SNIPPET_COLUMN = 8; + /** + * 投影列索引:笔记类型(笔记或文件夹) + */ public static final int TYPE_COLUMN = 9; + /** + * 投影列索引:小部件ID + */ public static final int WIDGET_ID_COLUMN = 10; + /** + * 投影列索引:小部件类型 + */ public static final int WIDGET_TYPE_COLUMN = 11; + /** + * 投影列索引:同步ID + */ public static final int SYNC_ID_COLUMN = 12; + /** + * 投影列索引:本地修改标记 + */ public static final int LOCAL_MODIFIED_COLUMN = 13; + /** + * 投影列索引:原始父文件夹ID + */ public static final int ORIGIN_PARENT_ID_COLUMN = 14; + /** + * 投影列索引:Google Task ID + */ public static final int GTASK_ID_COLUMN = 15; + /** + * 投影列索引:版本号 + */ public static final int VERSION_COLUMN = 16; + /** + * 上下文对象,用于访问系统服务 + */ private Context mContext; + /** + * ContentResolver对象,用于与ContentProvider交互 + */ private ContentResolver mContentResolver; + /** + * 标记是否为新创建的笔记 + */ private boolean mIsCreate; + /** + * 笔记ID + */ private long mId; + /** + * 提醒日期 + */ private long mAlertDate; + /** + * 背景颜色ID + */ private int mBgColorId; + /** + * 创建日期 + */ private long mCreatedDate; + /** + * 是否有附件(0:无,1:有) + */ private int mHasAttachment; + /** + * 修改日期 + */ private long mModifiedDate; + /** + * 父文件夹ID + */ private long mParentId; + /** + * 笔记摘要 + */ private String mSnippet; + /** + * 笔记类型(笔记或文件夹) + */ private int mType; + /** + * 小部件ID + */ private int mWidgetId; + /** + * 小部件类型 + */ private int mWidgetType; + /** + * 原始父文件夹ID + */ private long mOriginParent; + /** + * 版本号,用于避免同步冲突 + */ private long mVersion; + /** + * 记录笔记数据的变更,仅提交修改的字段以优化性能 + */ private ContentValues mDiffNoteValues; + /** + * 笔记内容列表,每个SqlData对象代表笔记的一个内容部分 + */ private ArrayList+ * 在小米笔记与Google Tasks的同步过程中,该类负责: + * 1. 封装Google Tasks任务的数据模型 + * 2. 实现任务的创建、更新等同步操作 + * 3. 处理本地数据与Google Tasks数据之间的转换 + * 4. 管理任务的层次结构和关系 + *
+ */ public class Task extends Node { private static final String TAG = Task.class.getSimpleName(); + /** + * 任务是否已完成 + */ private boolean mCompleted; + /** + * 任务的备注信息 + */ private String mNotes; + /** + * 任务的元信息,包含与本地笔记的关联数据 + */ private JSONObject mMetaInfo; + /** + * 任务的前一个兄弟节点(在同一父任务列表中的前一个任务) + */ private Task mPriorSibling; + /** + * 任务的父任务列表 + */ private TaskList mParent; +/** + * 构造函数,创建一个新的Task对象 + */ public Task() { super(); mCompleted = false; @@ -54,6 +82,16 @@ public class Task extends Node { mMetaInfo = null; } +/** + * 获取创建任务的JSON动作 + *+ * 该方法生成用于向Google Tasks服务器发送创建任务请求的JSON数据 + *
+ * + * @param actionId 动作ID,用于标识当前操作 + * @return 包含创建任务信息的JSON对象 + * @throws ActionFailureException 如果生成JSON对象失败 + */ public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); @@ -103,6 +141,16 @@ public class Task extends Node { return js; } +/** + * 获取更新任务的JSON动作 + *+ * 该方法生成用于向Google Tasks服务器发送更新任务请求的JSON数据 + *
+ * + * @param actionId 动作ID,用于标识当前操作 + * @return 包含更新任务信息的JSON对象 + * @throws ActionFailureException 如果生成JSON对象失败 + */ public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); @@ -135,6 +183,12 @@ public class Task extends Node { return js; } +/** + * 从Google Tasks服务器返回的JSON数据设置任务内容 + * + * @param js 包含Google Tasks任务数据的JSON对象 + * @throws ActionFailureException 如果解析JSON数据失败 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { @@ -175,6 +229,11 @@ public class Task extends Node { } } +/** + * 从本地JSON数据设置任务内容 + * + * @param js 包含本地笔记数据的JSON对象 + */ public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) || !js.has(GTaskStringUtils.META_HEAD_DATA)) { @@ -204,6 +263,14 @@ public class Task extends Node { } } +/** + * 获取任务内容的本地JSON表示 + *+ * 该方法将任务数据转换为本地笔记系统使用的JSON格式 + *
+ * + * @return 包含任务数据的本地JSON对象,如果转换失败返回null + */ public JSONObject getLocalJSONFromContent() { String name = getName(); try { @@ -247,6 +314,14 @@ public class Task extends Node { } } +/** + * 设置任务的元信息 + *+ * 元信息包含任务与本地笔记的关联数据 + *
+ * + * @param metaData 包含元信息的MetaData对象 + */ public void setMetaInfo(MetaData metaData) { if (metaData != null && metaData.getNotes() != null) { try { @@ -258,6 +333,15 @@ public class Task extends Node { } } +/** + * 获取任务的同步动作类型 + *+ * 该方法根据本地数据和远程数据的状态,确定需要执行的同步操作类型 + *
+ * + * @param c 包含本地笔记数据的Cursor对象 + * @return 同步动作类型,取值为SYNC_ACTION_*常量之一 + */ public int getSyncAction(Cursor c) { try { JSONObject noteInfo = null; @@ -311,39 +395,87 @@ public class Task extends Node { return SYNC_ACTION_ERROR; } +/** + * 判断任务是否值得保存 + *+ * 如果任务有元信息、名称或备注,则认为值得保存 + *
+ * + * @return 任务值得保存返回true,否则返回false + */ public boolean isWorthSaving() { return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) || (getNotes() != null && getNotes().trim().length() > 0); } +/** + * 设置任务的完成状态 + * + * @param completed 任务是否已完成 + */ public void setCompleted(boolean completed) { this.mCompleted = completed; } +/** + * 设置任务的备注信息 + * + * @param notes 任务的备注信息 + */ public void setNotes(String notes) { this.mNotes = notes; } +/** + * 设置任务的前一个兄弟节点 + * + * @param priorSibling 前一个兄弟任务 + */ public void setPriorSibling(Task priorSibling) { this.mPriorSibling = priorSibling; } +/** + * 设置任务的父任务列表 + * + * @param parent 父任务列表 + */ public void setParent(TaskList parent) { this.mParent = parent; } +/** + * 获取任务的完成状态 + * + * @return 任务是否已完成 + */ public boolean getCompleted() { return this.mCompleted; } +/** + * 获取任务的备注信息 + * + * @return 任务的备注信息 + */ public String getNotes() { return this.mNotes; } +/** + * 获取任务的前一个兄弟节点 + * + * @return 前一个兄弟任务 + */ public Task getPriorSibling() { return this.mPriorSibling; } +/** + * 获取任务的父任务列表 + * + * @return 父任务列表 + */ public TaskList getParent() { return this.mParent; } diff --git a/src/main/java/net/micode/notes/gtask/data/TaskList.java b/src/main/java/net/micode/notes/gtask/data/TaskList.java index 4ea21c5..1cfab78 100644 --- a/src/main/java/net/micode/notes/gtask/data/TaskList.java +++ b/src/main/java/net/micode/notes/gtask/data/TaskList.java @@ -30,19 +30,48 @@ import org.json.JSONObject; import java.util.ArrayList; +/** + * TaskList类是Node类的子类,用于表示Google Tasks中的任务列表。 + *+ * 在小米笔记与Google Tasks的同步过程中,该类负责: + * 1. 封装Google Tasks任务列表的数据模型 + * 2. 管理任务列表中的子任务(Task对象) + * 3. 实现任务列表的创建、更新等同步操作 + * 4. 处理本地文件夹与Google Tasks列表之间的转换 + *
+ */ public class TaskList extends Node { private static final String TAG = TaskList.class.getSimpleName(); + /** + * 任务列表的索引位置 + */ private int mIndex; + /** + * 任务列表中的子任务列表 + */ private ArrayList+ * 该方法生成用于向Google Tasks服务器发送创建任务列表请求的JSON数据 + *
+ * + * @param actionId 动作ID,用于标识当前操作 + * @return 包含创建任务列表信息的JSON对象 + * @throws ActionFailureException 如果生成JSON对象失败 + */ public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); @@ -74,6 +103,16 @@ public class TaskList extends Node { return js; } +/** + * 获取更新任务列表的JSON动作 + *+ * 该方法生成用于向Google Tasks服务器发送更新任务列表请求的JSON数据 + *
+ * + * @param actionId 动作ID,用于标识当前操作 + * @return 包含更新任务列表信息的JSON对象 + * @throws ActionFailureException 如果生成JSON对象失败 + */ public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); @@ -103,6 +142,12 @@ public class TaskList extends Node { return js; } +/** + * 从Google Tasks服务器返回的JSON数据设置任务列表内容 + * + * @param js 包含Google Tasks任务列表数据的JSON对象 + * @throws ActionFailureException 如果解析JSON数据失败 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { @@ -129,6 +174,14 @@ public class TaskList extends Node { } } +/** + * 从本地JSON数据设置任务列表内容 + *+ * 根据本地文件夹类型设置任务列表名称,支持普通文件夹、系统文件夹等 + *
+ * + * @param js 包含本地文件夹数据的JSON对象 + */ public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); @@ -157,6 +210,14 @@ public class TaskList extends Node { } } +/** + * 获取任务列表内容的本地JSON表示 + *+ * 该方法将任务列表数据转换为本地文件夹系统使用的JSON格式 + *
+ * + * @return 包含任务列表数据的本地JSON对象,如果转换失败返回null + */ public JSONObject getLocalJSONFromContent() { try { JSONObject js = new JSONObject(); @@ -183,6 +244,15 @@ public class TaskList extends Node { } } +/** + * 获取任务列表的同步动作类型 + *+ * 该方法根据本地数据和远程数据的状态,确定需要执行的同步操作类型 + *
+ * + * @param c 包含本地文件夹数据的Cursor对象 + * @return 同步动作类型,取值为SYNC_ACTION_*常量之一 + */ public int getSyncAction(Cursor c) { try { if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { @@ -216,10 +286,21 @@ public class TaskList extends Node { return SYNC_ACTION_ERROR; } +/** + * 获取任务列表中的子任务数量 + * + * @return 子任务数量 + */ public int getChildTaskCount() { return mChildren.size(); } +/** + * 向任务列表中添加子任务 + * + * @param task 要添加的子任务 + * @return 添加成功返回true,否则返回false + */ public boolean addChildTask(Task task) { boolean ret = false; if (task != null && !mChildren.contains(task)) { @@ -234,6 +315,13 @@ public class TaskList extends Node { return ret; } +/** + * 在指定位置向任务列表中添加子任务 + * + * @param task 要添加的子任务 + * @param index 要添加的位置索引 + * @return 添加成功返回true,否则返回false + */ public boolean addChildTask(Task task, int index) { if (index < 0 || index > mChildren.size()) { Log.e(TAG, "add child task: invalid index"); @@ -260,6 +348,12 @@ public class TaskList extends Node { return true; } +/** + * 从任务列表中移除子任务 + * + * @param task 要移除的子任务 + * @return 移除成功返回true,否则返回false + */ public boolean removeChildTask(Task task) { boolean ret = false; int index = mChildren.indexOf(task); @@ -281,6 +375,13 @@ public class TaskList extends Node { return ret; } +/** + * 移动任务列表中的子任务到指定位置 + * + * @param task 要移动的子任务 + * @param index 目标位置索引 + * @return 移动成功返回true,否则返回false + */ public boolean moveChildTask(Task task, int index) { if (index < 0 || index >= mChildren.size()) { @@ -299,6 +400,12 @@ public class TaskList extends Node { return (removeChildTask(task) && addChildTask(task, index)); } +/** + * 根据Google Task ID查找子任务 + * + * @param gid Google Task ID + * @return 找到的子任务,如果找不到返回null + */ public Task findChildTaskByGid(String gid) { for (int i = 0; i < mChildren.size(); i++) { Task t = mChildren.get(i); @@ -309,10 +416,22 @@ public class TaskList extends Node { return null; } +/** + * 获取子任务在任务列表中的索引位置 + * + * @param task 要查找的子任务 + * @return 子任务的索引位置,如果不存在返回-1 + */ public int getChildTaskIndex(Task task) { return mChildren.indexOf(task); } +/** + * 根据索引位置获取子任务 + * + * @param index 要获取的子任务的索引位置 + * @return 索引位置对应的子任务,如果索引无效返回null + */ public Task getChildTaskByIndex(int index) { if (index < 0 || index >= mChildren.size()) { Log.e(TAG, "getTaskByIndex: invalid index"); @@ -321,6 +440,15 @@ public class TaskList extends Node { return mChildren.get(index); } +/** + * 根据Google Task ID获取子任务 + *+ * 注意:该方法与findChildTaskByGid功能相同,名称可能存在拼写错误("ChilTask"应为"ChildTask") + *
+ * + * @param gid Google Task ID + * @return 找到的子任务,如果找不到返回null + */ public Task getChilTaskByGid(String gid) { for (Task task : mChildren) { if (task.getGid().equals(gid)) @@ -329,14 +457,29 @@ public class TaskList extends Node { return null; } +/** + * 获取任务列表中的所有子任务 + * + * @return 包含所有子任务的ArrayList + */ public ArrayList+ * 当GTask同步操作(如创建、更新、删除任务或任务列表)执行失败时抛出此异常 + *
+ */ public class ActionFailureException extends RuntimeException { private static final long serialVersionUID = 4425249765923293627L; diff --git a/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java b/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java index b08cfb1..b9f564d 100644 --- a/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java +++ b/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java @@ -16,6 +16,12 @@ package net.micode.notes.gtask.exception; +/** + * NetworkFailureException是GTask同步过程中用于表示网络操作失败的自定义检查型异常 + *+ * 当GTask同步操作(如网络请求、数据传输)因网络问题(如网络连接中断、超时等)失败时抛出此异常 + *
+ */ public class NetworkFailureException extends Exception { private static final long serialVersionUID = 2107610287180234136L; diff --git a/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java b/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java index ee352dc..9e52a69 100644 --- a/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java +++ b/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java @@ -29,22 +29,59 @@ import net.micode.notes.ui.NotesListActivity; import net.micode.notes.ui.NotesPreferenceActivity; +/** + * GTaskASyncTask是用于在后台执行GTask同步操作的异步任务类 + *+ * 该类负责: + * 1. 在后台线程执行GTask同步操作 + * 2. 显示同步进度通知 + * 3. 处理同步结果并显示相应通知 + * 4. 支持同步取消功能 + *
+ */ public class GTaskASyncTask extends AsyncTask+ * 该类提供了以下主要功能: + * 1. Google账号登录和认证 + * 2. 创建、更新、删除任务 + * 3. 创建、更新、删除任务列表 + * 4. 移动任务(在同一任务列表内或不同任务列表之间) + * 5. 获取任务列表和任务数据 + *
+ *+ * 该类采用单例模式设计,确保应用中只有一个GTask客户端实例 + *
+ */ public class GTaskClient { + /** + * 日志标签 + */ private static final String TAG = GTaskClient.class.getSimpleName(); + /** + * Google Tasks基础URL + */ private static final String GTASK_URL = "https://mail.google.com/tasks/"; + /** + * Google Tasks GET请求URL + */ private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; + /** + * Google Tasks POST请求URL + */ private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; + /** + * GTaskClient单例实例 + */ private static GTaskClient mInstance = null; + /** + * HTTP客户端实例,用于与Google Tasks服务器进行通信 + */ private DefaultHttpClient mHttpClient; + /** + * 当前使用的GET请求URL + */ private String mGetUrl; + /** + * 当前使用的POST请求URL + */ private String mPostUrl; + /** + * Google Tasks客户端版本号 + */ private long mClientVersion; + /** + * 登录状态标志 + */ private boolean mLoggedin; + /** + * 上次登录时间 + */ private long mLastLoginTime; + /** + * 操作ID,用于标识不同的同步操作 + */ private int mActionId; + /** + * 当前同步的Google账号 + */ private Account mAccount; + /** + * 更新操作数组,用于批量提交更新操作 + */ private JSONArray mUpdateArray; + /** + * GTaskClient构造函数(私有,用于单例模式) + */ private GTaskClient() { mHttpClient = null; mGetUrl = GTASK_GET_URL; @@ -102,6 +161,11 @@ public class GTaskClient { mUpdateArray = null; } + /** + * 获取GTaskClient单例实例 + * + * @return GTaskClient单例实例 + */ public static synchronized GTaskClient getInstance() { if (mInstance == null) { mInstance = new GTaskClient(); @@ -109,6 +173,16 @@ public class GTaskClient { return mInstance; } + /** + * 登录Google Tasks账号 + *+ * 如果距离上次登录时间不超过5分钟,则不需要重新登录 + * 如果账号发生变化,需要重新登录 + *
+ * + * @param activity 调用登录的Activity + * @return 登录成功返回true,失败返回false + */ public boolean login(Activity activity) { // we suppose that the cookie would expire after 5 minutes // then we need to re-login @@ -164,6 +238,13 @@ public class GTaskClient { return true; } + /** + * 登录Google账号,获取认证令牌 + * + * @param activity 调用登录的Activity + * @param invalidateToken 是否使当前令牌失效 + * @return 认证令牌字符串,失败返回null + */ private String loginGoogleAccount(Activity activity, boolean invalidateToken) { String authToken; AccountManager accountManager = AccountManager.get(activity); @@ -207,6 +288,16 @@ public class GTaskClient { return authToken; } + /** + * 尝试登录Google Tasks + *+ * 如果登录失败,会尝试使令牌失效并重新登录 + *
+ * + * @param activity 调用登录的Activity + * @param authToken 认证令牌 + * @return 登录成功返回true,失败返回false + */ private boolean tryToLoginGtask(Activity activity, String authToken) { if (!loginGtask(authToken)) { // maybe the auth token is out of date, now let's invalidate the @@ -225,6 +316,12 @@ public class GTaskClient { return true; } + /** + * 使用认证令牌登录Google Tasks + * + * @param authToken 认证令牌 + * @return 登录成功返回true,失败返回false + */ private boolean loginGtask(String authToken) { int timeoutConnection = 10000; int timeoutSocket = 15000; @@ -280,10 +377,20 @@ public class GTaskClient { return true; } + /** + * 获取下一个操作ID + * + * @return 操作ID + */ private int getActionId() { return mActionId++; } + /** + * 创建HTTP POST请求对象 + * + * @return HttpPost请求对象 + */ private HttpPost createHttpPost() { HttpPost httpPost = new HttpPost(mPostUrl); httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); @@ -291,6 +398,16 @@ public class GTaskClient { return httpPost; } + /** + * 获取HTTP响应内容 + *+ * 支持gzip和deflate压缩格式 + *
+ * + * @param entity HTTP响应实体 + * @return 响应内容字符串 + * @throws IOException 如果读取响应内容失败 + */ private String getResponseContent(HttpEntity entity) throws IOException { String contentEncoding = null; if (entity.getContentEncoding() != null) { @@ -323,6 +440,14 @@ public class GTaskClient { } } + /** + * 发送POST请求到Google Tasks服务器 + * + * @param js 请求内容的JSON对象 + * @return 响应内容的JSON对象 + * @throws NetworkFailureException 如果网络请求失败 + * @throws ActionFailureException 如果请求格式或响应解析失败 + */ private JSONObject postRequest(JSONObject js) throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); @@ -360,6 +485,13 @@ public class GTaskClient { } } + /** + * 创建新任务 + * + * @param task 要创建的任务对象 + * @throws NetworkFailureException 如果网络请求失败 + * @throws ActionFailureException 如果任务创建失败 + */ public void createTask(Task task) throws NetworkFailureException { commitUpdate(); try { @@ -386,6 +518,13 @@ public class GTaskClient { } } + /** + * 创建新任务列表 + * + * @param tasklist 要创建的任务列表对象 + * @throws NetworkFailureException 如果网络请求失败 + * @throws ActionFailureException 如果任务列表创建失败 + */ public void createTaskList(TaskList tasklist) throws NetworkFailureException { commitUpdate(); try { @@ -412,6 +551,12 @@ public class GTaskClient { } } + /** + * 提交批量更新操作 + * + * @throws NetworkFailureException 如果网络请求失败 + * @throws ActionFailureException 如果更新提交失败 + */ public void commitUpdate() throws NetworkFailureException { if (mUpdateArray != null) { try { @@ -433,6 +578,16 @@ public class GTaskClient { } } + /** + * 添加节点更新操作到批量更新数组 + *+ * 如果更新数组中的操作数量超过10个,则立即提交更新 + *
+ * + * @param node 要更新的节点对象 + * @throws NetworkFailureException 如果网络请求失败 + * @throws ActionFailureException 如果更新操作添加失败 + */ public void addUpdateNode(Node node) throws NetworkFailureException { if (node != null) { // too many update items may result in an error @@ -447,6 +602,18 @@ public class GTaskClient { } } + /** + * 移动任务 + *+ * 支持在同一任务列表内移动或在不同任务列表之间移动 + *
+ * + * @param task 要移动的任务 + * @param preParent 任务原来的父任务列表 + * @param curParent 任务现在的父任务列表 + * @throws NetworkFailureException 如果网络请求失败 + * @throws ActionFailureException 如果任务移动失败 + */ public void moveTask(Task task, TaskList preParent, TaskList curParent) throws NetworkFailureException { commitUpdate(); @@ -486,6 +653,13 @@ public class GTaskClient { } } + /** + * 删除节点(任务或任务列表) + * + * @param node 要删除的节点对象 + * @throws NetworkFailureException 如果网络请求失败 + * @throws ActionFailureException 如果节点删除失败 + */ public void deleteNode(Node node) throws NetworkFailureException { commitUpdate(); try { @@ -509,6 +683,13 @@ public class GTaskClient { } } + /** + * 获取所有任务列表 + * + * @return 包含所有任务列表的JSON数组 + * @throws NetworkFailureException 如果网络请求失败 + * @throws ActionFailureException 如果获取任务列表失败 + */ public JSONArray getTaskLists() throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); @@ -547,6 +728,14 @@ public class GTaskClient { } } + /** + * 获取指定任务列表中的所有任务 + * + * @param listGid 任务列表的Google ID + * @return 包含任务列表中所有任务的JSON数组 + * @throws NetworkFailureException 如果网络请求失败 + * @throws ActionFailureException 如果获取任务失败 + */ public JSONArray getTaskList(String listGid) throws NetworkFailureException { commitUpdate(); try { @@ -575,10 +764,18 @@ public class GTaskClient { } } + /** + * 获取当前同步的Google账号 + * + * @return 当前同步的Google账号 + */ public Account getSyncAccount() { return mAccount; } + /** + * 重置更新数组 + */ public void resetUpdateArray() { mUpdateArray = null; } diff --git a/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java b/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java index d2b4082..09c7b79 100644 --- a/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java +++ b/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java @@ -48,45 +48,112 @@ import java.util.Iterator; import java.util.Map; +/** + * GTaskManager是GTask同步模块的核心管理类,负责协调本地笔记与Google Tasks服务器之间的同步 + *+ * 该类采用单例模式,提供以下主要功能: + * 1. 管理同步状态和进度 + * 2. 初始化远程任务列表 + * 3. 同步本地和远程内容(文件夹和笔记) + * 4. 处理同步冲突和错误 + *
+ */ public class GTaskManager { private static final String TAG = GTaskManager.class.getSimpleName(); + /** + * 同步成功状态 + */ public static final int STATE_SUCCESS = 0; + /** + * 网络错误状态 + */ public static final int STATE_NETWORK_ERROR = 1; + /** + * 内部错误状态 + */ public static final int STATE_INTERNAL_ERROR = 2; + /** + * 同步进行中状态 + */ public static final int STATE_SYNC_IN_PROGRESS = 3; + /** + * 同步已取消状态 + */ public static final int STATE_SYNC_CANCELLED = 4; + /** + * GTaskManager的单例实例 + */ private static GTaskManager mInstance = null; + /** + * 用于Google登录的Activity实例 + */ private Activity mActivity; + /** + * 应用上下文 + */ private Context mContext; + /** + * 内容解析器,用于访问本地数据库 + */ private ContentResolver mContentResolver; + /** + * 同步状态标志,表示是否正在进行同步 + */ private boolean mSyncing; + /** + * 取消标志,表示同步是否已被取消 + */ private boolean mCancelled; + /** + * 远程任务列表的HashMap,键为任务列表的GID + */ private HashMap+ * 该服务提供了以下主要功能: + * 1. 启动GTask同步操作 + * 2. 取消正在进行的同步操作 + * 3. 通过广播通知同步状态和进度 + *
+ */ public class GTaskSyncService extends Service { + /** + * 同步操作类型的Intent参数名称 + */ public final static String ACTION_STRING_NAME = "sync_action_type"; + /** + * 启动同步操作的常量 + */ public final static int ACTION_START_SYNC = 0; + /** + * 取消同步操作的常量 + */ public final static int ACTION_CANCEL_SYNC = 1; + /** + * 无效操作的常量 + */ public final static int ACTION_INVALID = 2; + /** + * GTask同步服务广播的Intent名称 + */ public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; + /** + * 广播中表示同步状态的参数名称 + */ public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; + /** + * 广播中表示同步进度的参数名称 + */ public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; + /** + * 当前正在执行的同步任务实例 + */ private static GTaskASyncTask mSyncTask = null; + /** + * 当前同步进度信息 + */ private static String mSyncProgress = ""; + /** + * 启动同步操作 + */ private void startSync() { if (mSyncTask == null) { mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { @@ -56,6 +95,9 @@ public class GTaskSyncService extends Service { } } + /** + * 取消正在进行的同步操作 + */ private void cancelSync() { if (mSyncTask != null) { mSyncTask.cancelSync(); @@ -93,10 +135,15 @@ public class GTaskSyncService extends Service { } } + @Override public IBinder onBind(Intent intent) { return null; } + /** + * 发送同步状态广播 + * @param msg 同步进度消息 + */ public void sendBroadcast(String msg) { mSyncProgress = msg; Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); @@ -105,6 +152,10 @@ public class GTaskSyncService extends Service { sendBroadcast(intent); } + /** + * 启动GTask同步服务 + * @param activity 调用该方法的Activity实例 + */ public static void startSync(Activity activity) { GTaskManager.getInstance().setActivityContext(activity); Intent intent = new Intent(activity, GTaskSyncService.class); @@ -112,16 +163,28 @@ public class GTaskSyncService extends Service { activity.startService(intent); } + /** + * 取消GTask同步服务 + * @param context 调用该方法的上下文 + */ public static void cancelSync(Context context) { Intent intent = new Intent(context, GTaskSyncService.class); intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); context.startService(intent); } + /** + * 检查是否正在进行同步 + * @return 如果正在同步返回true,否则返回false + */ public static boolean isSyncing() { return mSyncTask != null; } + /** + * 获取当前同步进度信息 + * @return 当前同步进度字符串 + */ public static String getProgressString() { return mSyncProgress; } diff --git a/src/main/java/net/micode/notes/tool/BackupUtils.java b/src/main/java/net/micode/notes/tool/BackupUtils.java index 39f6ec4..8f2e96e 100644 --- a/src/main/java/net/micode/notes/tool/BackupUtils.java +++ b/src/main/java/net/micode/notes/tool/BackupUtils.java @@ -36,11 +36,32 @@ import java.io.IOException; import java.io.PrintStream; +/** + * 备份工具类,负责将笔记导出为文本文件。 + *+ * 该类采用单例模式实现,提供了将笔记内容导出为用户可读文本格式的功能, + * 支持将不同文件夹的笔记分类导出,并处理SD卡状态检查和文件生成等操作。 + *
+ */ public class BackupUtils { - private static final String TAG = "BackupUtils"; - // Singleton stuff + /** + * 日志标签 + */ + private static final String TAG = BackupUtils.class.getSimpleName(); + /** + * 单例实例 + */ private static BackupUtils sInstance; + /** + * 获取BackupUtils的单例实例 + *+ * 如果实例不存在,则创建一个新实例并返回;否则返回已存在的实例。 + *
+ * + * @param context 应用上下文 + * @return BackupUtils的单例实例 + */ public static synchronized BackupUtils getInstance(Context context) { if (sInstance == null) { sInstance = new BackupUtils(context); @@ -49,42 +70,108 @@ public class BackupUtils { } /** - * Following states are signs to represents backup or restore - * status + * 备份或恢复操作的状态常量 + *+ * 这些状态常量用于表示备份或恢复操作的执行结果和状态。 + *
+ */ + /** + * SD卡未挂载状态 */ - // Currently, the sdcard is not mounted public static final int STATE_SD_CARD_UNMOUONTED = 0; - // The backup file not exist + + /** + * 备份文件不存在状态 + */ public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; - // The data is not well formated, may be changed by other programs + + /** + * 数据格式损坏状态,可能被其他程序修改 + */ public static final int STATE_DATA_DESTROIED = 2; - // Some run-time exception which causes restore or backup fails + + /** + * 运行时异常导致备份或恢复失败状态 + */ public static final int STATE_SYSTEM_ERROR = 3; - // Backup or restore success + + /** + * 备份或恢复成功状态 + */ public static final int STATE_SUCCESS = 4; + /** + * 文本导出实例,负责实际的文本导出操作 + */ private TextExport mTextExport; + /** + * 私有构造方法,初始化BackupUtils实例 + *+ * 创建TextExport实例用于文本导出操作。 + *
+ * + * @param context 应用上下文 + */ private BackupUtils(Context context) { mTextExport = new TextExport(context); } + /** + * 检查外部存储是否可用 + *+ * 判断SD卡是否已挂载并可读写。 + *
+ * + * @return 如果外部存储可用返回true,否则返回false + */ private static boolean externalStorageAvailable() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } + /** + * 将笔记导出为文本文件 + *+ * 调用内部TextExport实例的exportToText方法执行实际导出操作。 + *
+ * + * @return 导出操作的状态码,可能的值为STATE_SD_CARD_UNMOUONTED、STATE_SYSTEM_ERROR或STATE_SUCCESS + */ public int exportToText() { return mTextExport.exportToText(); } + /** + * 获取导出的文本文件名 + *+ * 返回最近一次导出操作生成的文本文件名。 + *
+ * + * @return 导出的文本文件名 + */ public String getExportedTextFileName() { return mTextExport.mFileName; } + /** + * 获取导出的文本文件目录 + *+ * 返回导出文本文件的存储目录路径。 + *
+ * + * @return 导出的文本文件目录路径 + */ public String getExportedTextFileDir() { return mTextExport.mFileDirectory; } + /** + * 文本导出内部类,负责实际的笔记文本导出操作。 + *+ * 该内部类实现了将笔记内容导出为用户可读文本格式的功能, + * 支持按文件夹分类导出,并处理文件生成和内容格式化等操作。 + *
+ */ private static class TextExport { private static final String[] NOTE_PROJECTION = { NoteColumns.ID, @@ -116,15 +203,35 @@ public class BackupUtils { private static final int DATA_COLUMN_PHONE_NUMBER = 4; + /** + * 文本格式化字符串数组,从资源文件中加载 + */ private final String [] TEXT_FORMAT; private static final int FORMAT_FOLDER_NAME = 0; private static final int FORMAT_NOTE_DATE = 1; private static final int FORMAT_NOTE_CONTENT = 2; + /** + * 应用上下文 + */ private Context mContext; + /** + * 导出的文件名 + */ private String mFileName; + /** + * 导出的文件目录 + */ private String mFileDirectory; + /** + * 构造方法,初始化TextExport实例 + *+ * 从资源文件中加载文本格式化字符串,并初始化上下文和文件信息。 + *
+ * + * @param context 应用上下文 + */ public TextExport(Context context) { TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); mContext = context; @@ -132,12 +239,27 @@ public class BackupUtils { mFileDirectory = ""; } + /** + * 获取指定ID的文本格式化字符串 + *+ * 从TEXT_FORMAT数组中获取指定索引的格式化字符串。 + *
+ * + * @param id 格式化字符串的索引 + * @return 对应的格式化字符串 + */ private String getFormat(int id) { return TEXT_FORMAT[id]; } /** - * Export the folder identified by folder id to text + * 将指定文件夹的笔记导出为文本 + *+ * 查询并导出指定文件夹下的所有笔记,包括每个笔记的修改日期和内容。 + *
+ * + * @param folderId 文件夹ID + * @param ps 输出流,用于写入导出的文本内容 */ private void exportFolderToText(String folderId, PrintStream ps) { // Query notes belong to this folder @@ -163,7 +285,13 @@ public class BackupUtils { } /** - * Export note identified by id to a print stream + * 将指定笔记导出到打印流 + *+ * 查询并导出指定笔记的详细内容,区分文本笔记和通话笔记的不同格式。 + *
+ * + * @param noteId 笔记ID + * @param ps 打印流,用于写入导出的笔记内容 */ private void exportNoteToText(String noteId, PrintStream ps) { Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, @@ -216,7 +344,12 @@ public class BackupUtils { } /** - * Note will be exported as text which is user readable + * 将笔记导出为用户可读的文本格式 + *+ * 检查SD卡状态,创建输出文件,然后按文件夹分类导出所有笔记内容。 + *
+ * + * @return 导出操作的状态码,可能的值为STATE_SD_CARD_UNMOUONTED、STATE_SYSTEM_ERROR或STATE_SUCCESS */ public int exportToText() { if (!externalStorageAvailable()) { @@ -283,7 +416,12 @@ public class BackupUtils { } /** - * Get a print stream pointed to the file {@generateExportedTextFile} + * 获取用于导出文本的打印流 + *+ * 创建并返回指向导出文本文件的打印流,用于写入导出的笔记内容。 + *
+ * + * @return 指向导出文本文件的打印流,如果创建失败返回null */ private PrintStream getExportToTextPrintStream() { File file = generateFileMountedOnSDcard(mContext, R.string.file_path, @@ -310,7 +448,15 @@ public class BackupUtils { } /** - * Generate the text file to store imported data + * 在SD卡上生成用于存储导入数据的文本文件 + *+ * 根据指定的文件路径和文件名格式,在SD卡上创建相应的目录和文件。 + *
+ * + * @param context 应用上下文 + * @param filePathResId 文件路径资源ID + * @param fileNameFormatResId 文件名格式资源ID + * @return 创建的文件对象,如果创建失败返回null */ private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { StringBuilder sb = new StringBuilder(); diff --git a/src/main/java/net/micode/notes/tool/DataUtils.java b/src/main/java/net/micode/notes/tool/DataUtils.java index 2a14982..49d5a18 100644 --- a/src/main/java/net/micode/notes/tool/DataUtils.java +++ b/src/main/java/net/micode/notes/tool/DataUtils.java @@ -35,8 +35,29 @@ import java.util.ArrayList; import java.util.HashSet; +/** + * 数据工具类,提供笔记数据的批量操作和查询功能。 + *+ * 该类包含了笔记数据的批量删除、移动、查询等静态方法, + * 主要用于处理笔记应用中的数据操作,与ContentResolver进行交互。 + *
+ */ public class DataUtils { - public static final String TAG = "DataUtils"; + /** + * 日志标签 + */ + public static final String TAG = DataUtils.class.getSimpleName(); + /** + * 批量删除笔记 + *+ * 根据提供的笔记ID集合,批量删除对应的笔记。 + * 系统根文件夹(ID_ROOT_FOLDER)不会被删除。 + *
+ * + * @param resolver ContentResolver实例,用于与内容提供者交互 + * @param ids 要删除的笔记ID集合 + * @return 删除成功返回true,否则返回false + */ public static boolean batchDeleteNotes(ContentResolver resolver, HashSet+ * 更新笔记的父文件夹ID和原始父文件夹ID,并标记为本地修改。 + *
+ * + * @param resolver ContentResolver实例,用于与内容提供者交互 + * @param id 要移动的笔记ID + * @param srcFolderId 源文件夹ID + * @param desFolderId 目标文件夹ID + */ public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { ContentValues values = new ContentValues(); values.put(NoteColumns.PARENT_ID, desFolderId); @@ -80,6 +112,17 @@ public class DataUtils { resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); } + /** + * 批量将笔记移动到指定文件夹 + *+ * 根据提供的笔记ID集合,批量更新笔记的父文件夹ID。 + *
+ * + * @param resolver ContentResolver实例,用于与内容提供者交互 + * @param ids 要移动的笔记ID集合 + * @param folderId 目标文件夹ID + * @return 移动成功返回true,否则返回false + */ public static boolean batchMoveToFolder(ContentResolver resolver, HashSet+ * 查询并返回除系统文件夹外的所有用户文件夹数量。 + *
+ * + * @param resolver ContentResolver实例,用于与内容提供者交互 + * @return 用户文件夹数量 */ public static int getUserFolderCount(ContentResolver resolver) { Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, @@ -136,6 +185,17 @@ public class DataUtils { return count; } + /** + * 检查笔记在数据库中是否可见 + *+ * 检查指定ID和类型的笔记是否存在于数据库中,且不在垃圾箱中。 + *
+ * + * @param resolver ContentResolver实例,用于与内容提供者交互 + * @param noteId 笔记ID + * @param type 笔记类型 + * @return 可见返回true,否则返回false + */ public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, @@ -153,6 +213,16 @@ public class DataUtils { return exist; } + /** + * 检查笔记是否存在于数据库中 + *+ * 根据笔记ID检查笔记是否存在于数据库中。 + *
+ * + * @param resolver ContentResolver实例,用于与内容提供者交互 + * @param noteId 笔记ID + * @return 存在返回true,否则返回false + */ public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, null, null, null); @@ -167,6 +237,16 @@ public class DataUtils { return exist; } + /** + * 检查数据是否存在于数据库中 + *+ * 根据数据ID检查数据是否存在于数据库中。 + *
+ * + * @param resolver ContentResolver实例,用于与内容提供者交互 + * @param dataId 数据ID + * @return 存在返回true,否则返回false + */ public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null, null, null, null); @@ -181,6 +261,16 @@ public class DataUtils { return exist; } + /** + * 检查可见文件夹名称是否存在 + *+ * 检查指定名称的文件夹是否存在于数据库中,且不在垃圾箱中。 + *
+ * + * @param resolver ContentResolver实例,用于与内容提供者交互 + * @param name 文件夹名称 + * @return 存在返回true,否则返回false + */ public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + @@ -197,6 +287,16 @@ public class DataUtils { return exist; } + /** + * 获取文件夹中笔记的小部件属性 + *+ * 查询并返回指定文件夹下所有笔记的小部件ID和类型。 + *
+ * + * @param resolver ContentResolver实例,用于与内容提供者交互 + * @param folderId 文件夹ID + * @return 小部件属性集合,如果没有则返回null + */ public static HashSet+ * 查询并返回通话笔记中存储的电话号码。 + *
+ * + * @param resolver ContentResolver实例,用于与内容提供者交互 + * @param noteId 通话笔记ID + * @return 电话号码,如果没有则返回空字符串 + */ public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, new String [] { CallNote.PHONE_NUMBER }, @@ -243,6 +353,17 @@ public class DataUtils { return ""; } + /** + * 根据电话号码和通话日期获取笔记ID + *+ * 查询并返回匹配指定电话号码和通话日期的通话笔记ID。 + *
+ * + * @param resolver ContentResolver实例,用于与内容提供者交互 + * @param phoneNumber 电话号码 + * @param callDate 通话日期时间戳 + * @return 笔记ID,如果没有找到则返回0 + */ public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, new String [] { CallNote.NOTE_ID }, @@ -264,6 +385,18 @@ public class DataUtils { return 0; } + /** + * 根据笔记ID获取摘要 + *+ * 查询并返回指定笔记ID的摘要内容。 + * 如果笔记不存在,将抛出IllegalArgumentException异常。 + *
+ * + * @param resolver ContentResolver实例,用于与内容提供者交互 + * @param noteId 笔记ID + * @return 笔记摘要 + * @throws IllegalArgumentException 如果笔记不存在 + */ public static String getSnippetById(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, new String [] { NoteColumns.SNIPPET }, @@ -282,6 +415,15 @@ public class DataUtils { throw new IllegalArgumentException("Note is not found with id: " + noteId); } + /** + * 格式化笔记摘要 + *+ * 对笔记摘要进行格式化处理,去除首尾空格,并只保留第一行内容。 + *
+ * + * @param snippet 原始摘要内容 + * @return 格式化后的摘要内容 + */ public static String getFormattedSnippet(String snippet) { if (snippet != null) { snippet = snippet.trim(); diff --git a/src/main/java/net/micode/notes/tool/GTaskStringUtils.java b/src/main/java/net/micode/notes/tool/GTaskStringUtils.java index 666b729..e9e7aab 100644 --- a/src/main/java/net/micode/notes/tool/GTaskStringUtils.java +++ b/src/main/java/net/micode/notes/tool/GTaskStringUtils.java @@ -16,98 +16,151 @@ package net.micode.notes.tool; +/** + * GTask字符串常量工具类,定义了与GTask服务交互时使用的各种JSON字段名和文件夹名称。 + *+ * 该类包含了与Google任务服务(GTask)进行JSON数据交换时所需的所有字符串常量, + * 包括动作类型、实体类型、字段名称以及文件夹命名规则等。 + *
+ */ public class GTaskStringUtils { + /** 动作ID字段名 */ public final static String GTASK_JSON_ACTION_ID = "action_id"; + /** 动作列表字段名 */ public final static String GTASK_JSON_ACTION_LIST = "action_list"; + /** 动作类型字段名 */ public final static String GTASK_JSON_ACTION_TYPE = "action_type"; + /** 创建动作类型 */ public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; + /** 获取所有数据动作类型 */ public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; + /** 移动动作类型 */ public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; + /** 更新动作类型 */ public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; + /** 创建者ID字段名 */ public final static String GTASK_JSON_CREATOR_ID = "creator_id"; + /** 子实体字段名 */ public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; + /** 客户端版本字段名 */ public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; + /** 完成状态字段名 */ public final static String GTASK_JSON_COMPLETED = "completed"; + /** 当前列表ID字段名 */ public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; + /** 默认列表ID字段名 */ public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; + /** 删除状态字段名 */ public final static String GTASK_JSON_DELETED = "deleted"; + /** 目标列表字段名 */ public final static String GTASK_JSON_DEST_LIST = "dest_list"; + /** 目标父级字段名 */ public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; + /** 目标父级类型字段名 */ public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; + /** 实体增量字段名 */ public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; + /** 实体类型字段名 */ public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; + /** 获取已删除数据字段名 */ public final static String GTASK_JSON_GET_DELETED = "get_deleted"; + /** ID字段名 */ public final static String GTASK_JSON_ID = "id"; + /** 索引字段名 */ public final static String GTASK_JSON_INDEX = "index"; + /** 最后修改时间字段名 */ public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; + /** 最新同步点字段名 */ public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; + /** 列表ID字段名 */ public final static String GTASK_JSON_LIST_ID = "list_id"; + /** 列表集合字段名 */ public final static String GTASK_JSON_LISTS = "lists"; + /** 名称字段名 */ public final static String GTASK_JSON_NAME = "name"; + /** 新ID字段名 */ public final static String GTASK_JSON_NEW_ID = "new_id"; + /** 笔记字段名 */ public final static String GTASK_JSON_NOTES = "notes"; + /** 父级ID字段名 */ public final static String GTASK_JSON_PARENT_ID = "parent_id"; + /** 前一个兄弟节点ID字段名 */ public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; + /** 结果集合字段名 */ public final static String GTASK_JSON_RESULTS = "results"; + /** 源列表字段名 */ public final static String GTASK_JSON_SOURCE_LIST = "source_list"; + /** 任务集合字段名 */ public final static String GTASK_JSON_TASKS = "tasks"; + /** 类型字段名 */ public final static String GTASK_JSON_TYPE = "type"; + /** 分组类型 */ public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; + /** 任务类型 */ public final static String GTASK_JSON_TYPE_TASK = "TASK"; + /** 用户字段名 */ public final static String GTASK_JSON_USER = "user"; + /** MIUI笔记文件夹前缀 */ public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; + /** 默认文件夹名称 */ public final static String FOLDER_DEFAULT = "Default"; + /** 通话笔记文件夹名称 */ public final static String FOLDER_CALL_NOTE = "Call_Note"; + /** 元数据文件夹名称 */ public final static String FOLDER_META = "METADATA"; + /** GTask ID元数据头 */ public final static String META_HEAD_GTASK_ID = "meta_gid"; + /** 笔记元数据头 */ public final static String META_HEAD_NOTE = "meta_note"; + /** 数据元数据头 */ public final static String META_HEAD_DATA = "meta_data"; + /** 元数据笔记名称 */ public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; } diff --git a/src/main/java/net/micode/notes/tool/ResourceParser.java b/src/main/java/net/micode/notes/tool/ResourceParser.java index 1ad3ad6..1c3c73a 100644 --- a/src/main/java/net/micode/notes/tool/ResourceParser.java +++ b/src/main/java/net/micode/notes/tool/ResourceParser.java @@ -22,24 +22,46 @@ import android.preference.PreferenceManager; import net.micode.notes.R; import net.micode.notes.ui.NotesPreferenceActivity; +/** + * 资源解析工具类,管理笔记应用中使用的各种资源配置和常量。 + *+ * 该类包含了笔记的背景颜色、字体大小等常量定义,以及管理笔记背景、 + * 列表项背景、小部件背景和文本外观等资源的内部类。 + *
+ */ public class ResourceParser { + /** 黄色背景ID */ public static final int YELLOW = 0; + /** 蓝色背景ID */ public static final int BLUE = 1; + /** 白色背景ID */ public static final int WHITE = 2; + /** 绿色背景ID */ public static final int GREEN = 3; + /** 红色背景ID */ public static final int RED = 4; + /** 默认背景颜色ID */ public static final int BG_DEFAULT_COLOR = YELLOW; + /** 小字体大小ID */ public static final int TEXT_SMALL = 0; + /** 中字体大小ID */ public static final int TEXT_MEDIUM = 1; + /** 大字体大小ID */ public static final int TEXT_LARGE = 2; + /** 超大字体大小ID */ public static final int TEXT_SUPER = 3; + /** 默认字体大小ID */ public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; + /** + * 笔记背景资源管理类,提供笔记编辑界面的背景和标题背景资源。 + */ public static class NoteBgResources { + /** 笔记编辑背景资源数组 */ private final static int [] BG_EDIT_RESOURCES = new int [] { R.drawable.edit_yellow, R.drawable.edit_blue, @@ -48,6 +70,7 @@ public class ResourceParser { R.drawable.edit_red }; + /** 笔记编辑标题背景资源数组 */ private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { R.drawable.edit_title_yellow, R.drawable.edit_title_blue, @@ -56,15 +79,36 @@ public class ResourceParser { R.drawable.edit_title_red }; + /** + * 获取笔记背景资源ID + * + * @param id 背景颜色ID + * @return 对应的背景资源ID + */ public static int getNoteBgResource(int id) { return BG_EDIT_RESOURCES[id]; } + /** + * 获取笔记标题背景资源ID + * + * @param id 背景颜色ID + * @return 对应的标题背景资源ID + */ public static int getNoteTitleBgResource(int id) { return BG_EDIT_TITLE_RESOURCES[id]; } } + /** + * 获取默认的笔记背景颜色ID + *+ * 根据用户设置决定返回固定默认颜色还是随机颜色。 + *
+ * + * @param context 上下文对象 + * @return 默认的背景颜色ID + */ public static int getDefaultBgId(Context context) { if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { @@ -74,7 +118,11 @@ public class ResourceParser { } } + /** + * 笔记列表项背景资源管理类,提供笔记列表中不同位置项的背景资源。 + */ public static class NoteItemBgResources { + /** 列表第一项背景资源数组 */ private final static int [] BG_FIRST_RESOURCES = new int [] { R.drawable.list_yellow_up, R.drawable.list_blue_up, @@ -83,6 +131,7 @@ public class ResourceParser { R.drawable.list_red_up }; + /** 列表中间项背景资源数组 */ private final static int [] BG_NORMAL_RESOURCES = new int [] { R.drawable.list_yellow_middle, R.drawable.list_blue_middle, @@ -91,6 +140,7 @@ public class ResourceParser { R.drawable.list_red_middle }; + /** 列表最后一项背景资源数组 */ private final static int [] BG_LAST_RESOURCES = new int [] { R.drawable.list_yellow_down, R.drawable.list_blue_down, @@ -99,6 +149,7 @@ public class ResourceParser { R.drawable.list_red_down, }; + /** 列表单独项背景资源数组 */ private final static int [] BG_SINGLE_RESOURCES = new int [] { R.drawable.list_yellow_single, R.drawable.list_blue_single, @@ -107,28 +158,61 @@ public class ResourceParser { R.drawable.list_red_single }; + /** + * 获取列表第一项背景资源ID + * + * @param id 背景颜色ID + * @return 对应的第一项背景资源ID + */ public static int getNoteBgFirstRes(int id) { return BG_FIRST_RESOURCES[id]; } + /** + * 获取列表最后一项背景资源ID + * + * @param id 背景颜色ID + * @return 对应的最后一项背景资源ID + */ public static int getNoteBgLastRes(int id) { return BG_LAST_RESOURCES[id]; } + /** + * 获取列表单独项背景资源ID + * + * @param id 背景颜色ID + * @return 对应的单独项背景资源ID + */ public static int getNoteBgSingleRes(int id) { return BG_SINGLE_RESOURCES[id]; } + /** + * 获取列表中间项背景资源ID + * + * @param id 背景颜色ID + * @return 对应的中间项背景资源ID + */ public static int getNoteBgNormalRes(int id) { return BG_NORMAL_RESOURCES[id]; } + /** + * 获取文件夹背景资源ID + * + * @return 文件夹背景资源ID + */ public static int getFolderBgRes() { return R.drawable.list_folder; } } + /** + * 小部件背景资源管理类,提供不同尺寸小部件的背景资源。 + */ public static class WidgetBgResources { + /** 2x尺寸小部件背景资源数组 */ private final static int [] BG_2X_RESOURCES = new int [] { R.drawable.widget_2x_yellow, R.drawable.widget_2x_blue, @@ -137,10 +221,17 @@ public class ResourceParser { R.drawable.widget_2x_red, }; + /** + * 获取2x尺寸小部件背景资源ID + * + * @param id 背景颜色ID + * @return 对应的2x尺寸小部件背景资源ID + */ public static int getWidget2xBgResource(int id) { return BG_2X_RESOURCES[id]; } + /** 4x尺寸小部件背景资源数组 */ private final static int [] BG_4X_RESOURCES = new int [] { R.drawable.widget_4x_yellow, R.drawable.widget_4x_blue, @@ -149,12 +240,22 @@ public class ResourceParser { R.drawable.widget_4x_red }; + /** + * 获取4x尺寸小部件背景资源ID + * + * @param id 背景颜色ID + * @return 对应的4x尺寸小部件背景资源ID + */ public static int getWidget4xBgResource(int id) { return BG_4X_RESOURCES[id]; } } + /** + * 文本外观资源管理类,提供不同字体大小的文本外观资源。 + */ public static class TextAppearanceResources { + /** 文本外观资源数组 */ private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { R.style.TextAppearanceNormal, R.style.TextAppearanceMedium, @@ -162,6 +263,15 @@ public class ResourceParser { R.style.TextAppearanceSuper }; + /** + * 获取文本外观资源ID + *+ * 如果ID超出资源数组范围,返回默认字体大小的资源ID。 + *
+ * + * @param id 字体大小ID + * @return 对应的文本外观资源ID + */ public static int getTexAppearanceResource(int id) { /** * HACKME: Fix bug of store the resource id in shared preference. @@ -174,6 +284,11 @@ public class ResourceParser { return TEXTAPPEARANCE_RESOURCES[id]; } + /** + * 获取文本外观资源数组的大小 + * + * @return 文本外观资源数组的大小 + */ public static int getResourcesSize() { return TEXTAPPEARANCE_RESOURCES.length; }