From 80205edfc91b8870f479e8911f88c49ccb8b10b2 Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Mon, 9 Jun 2025 20:32:46 +0800 Subject: [PATCH 01/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/net/micode/notes/data/Contact.java | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/data/Contact.java b/src/Notes-master/app/src/main/java/net/micode/notes/data/Contact.java index d97ac5d..b9cfae2 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/data/Contact.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/data/Contact.java @@ -25,49 +25,73 @@ import android.util.Log; import java.util.HashMap; +/** + * Contact类用于根据电话号码从系统联系人中查询对应的联系人姓名, + * 并提供缓存机制以提高查询效率。 + */ public class Contact { + // 联系人缓存:键为电话号码,值为对应的联系人姓名 private static HashMap sContactCache; private static final String TAG = "Contact"; + // 联系人ID查询的SQL选择条件模板 + // 该条件用于匹配电话号码并确保数据类型为Phone且关联到有效的原始联系人 private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER - + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" - + " AND " + Data.RAW_CONTACT_ID + " IN " + + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + + " AND " + Data.RAW_CONTACT_ID + " IN " + "(SELECT raw_contact_id " + " FROM phone_lookup" + " WHERE min_match = '+')"; + /** + * 根据电话号码从系统联系人中查询对应的联系人姓名 + * + * @param context 应用上下文,用于获取ContentResolver + * @param phoneNumber 要查询的电话号码 + * @return 对应的联系人姓名,如果未找到则返回null + */ public static String getContact(Context context, String phoneNumber) { + // 初始化联系人缓存(如果尚未初始化) if(sContactCache == null) { sContactCache = new HashMap(); } + // 优先从缓存中获取联系人姓名 if(sContactCache.containsKey(phoneNumber)) { return sContactCache.get(phoneNumber); } + // 构建查询条件:替换模板中的占位符为实际的最小匹配值 String selection = CALLER_ID_SELECTION.replace("+", PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + + // 执行联系人查询 Cursor cursor = context.getContentResolver().query( - Data.CONTENT_URI, - new String [] { Phone.DISPLAY_NAME }, - selection, - new String[] { phoneNumber }, - null); + Data.CONTENT_URI, // 查询联系人数据的URI + new String [] { Phone.DISPLAY_NAME }, // 只需要显示名称字段 + selection, // 使用构建好的查询条件 + new String[] { phoneNumber }, // 查询参数 + null); // 不使用排序 + // 处理查询结果 if (cursor != null && cursor.moveToFirst()) { try { + // 获取联系人姓名并加入缓存 String name = cursor.getString(0); sContactCache.put(phoneNumber, name); return name; } catch (IndexOutOfBoundsException e) { + // 处理可能的索引越界异常 Log.e(TAG, " Cursor get string error " + e.toString()); return null; } finally { + // 确保关闭Cursor以防止资源泄漏 cursor.close(); } } else { + // 未找到匹配的联系人 Log.d(TAG, "No contact matched with number:" + phoneNumber); return null; } } -} +} \ No newline at end of file -- 2.34.1 From 074bd512ad98d6c03e3e9c5b36414eab29279178 Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Mon, 9 Jun 2025 20:40:08 +0800 Subject: [PATCH 02/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/net/micode/notes/data/Notes.java | 220 ++++++++++-------- 1 file changed, 120 insertions(+), 100 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/data/Notes.java b/src/Notes-master/app/src/main/java/net/micode/notes/data/Notes.java index f240604..8938b5f 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/data/Notes.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/data/Notes.java @@ -17,263 +17,283 @@ package net.micode.notes.data; import android.net.Uri; + +/** + * Notes类定义了小米便签应用的数据模型和常量,包括: + * - 内容提供者的URI结构 + * - 数据库表结构和列定义 + * - 便签类型、文件夹类型和系统特殊文件夹ID + * - 意图传递的额外数据键名 + */ public class Notes { + // 内容提供者的权限标识 public static final String AUTHORITY = "micode_notes"; public static final String TAG = "Notes"; - public static final int TYPE_NOTE = 0; - public static final int TYPE_FOLDER = 1; - public static final int TYPE_SYSTEM = 2; + + // 便签类型常量 + public static final int TYPE_NOTE = 0; // 普通便签 + public static final int TYPE_FOLDER = 1; // 文件夹 + public static final int TYPE_SYSTEM = 2; // 系统便签 /** - * Following IDs are system folders' identifiers - * {@link Notes#ID_ROOT_FOLDER } is default folder - * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder - * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records + * 系统文件夹标识符: + * {@link Notes#ID_ROOT_FOLDER } 是默认根文件夹 + * {@link Notes#ID_TEMPARAY_FOLDER } 用于存放未分类便签 + * {@link Notes#ID_CALL_RECORD_FOLDER} 用于存储通话记录便签 + * {@link Notes#ID_TRASH_FOLER} 是废纸篓文件夹 */ public static final int ID_ROOT_FOLDER = 0; public static final int ID_TEMPARAY_FOLDER = -1; public static final int ID_CALL_RECORD_FOLDER = -2; public static final int ID_TRASH_FOLER = -3; - public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; - public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; - public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; - public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; - 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 String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; // 提醒日期 + 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"; // 桌面小部件ID + public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; // 桌面小部件类型 + public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; // 文件夹ID + public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; // 通话日期 - public static final int TYPE_WIDGET_INVALIDE = -1; - public static final int TYPE_WIDGET_2X = 0; - public static final int TYPE_WIDGET_4X = 1; + // 桌面小部件类型常量 + public static final int TYPE_WIDGET_INVALIDE = -1; // 无效小部件 + public static final int TYPE_WIDGET_2X = 0; // 2x尺寸小部件 + public static final int TYPE_WIDGET_4X = 1; // 4x尺寸小部件 + /** + * 数据类型常量 + */ public static class DataConstants { - public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; - public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; + public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; // 文本便签MIME类型 + public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; // 通话记录便签MIME类型 } /** - * 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

+ * Google任务ID(用于与Google任务同步) + *

类型 : 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; + public static final int MODE_CHECK_LIST = 1; // 待办清单模式值 - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; + 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"; // 单个文本便签的MIME类型 - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); // 文本便签的内容URI } + /** + * 通话记录便签类型定义 + */ public static final class CallNote implements DataColumns { /** - * Call date for this record - *

Type: INTEGER (long)

+ * 此记录的通话日期 + *

类型: INTEGER (long)

*/ public static final String CALL_DATE = DATA1; /** - * Phone number for this record - *

Type: TEXT

+ * 此记录的电话号码 + *

类型: TEXT

*/ public static final String PHONE_NUMBER = DATA3; - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; - - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; + 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"; // 单个通话记录的MIME类型 - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); // 通话记录的内容URI } -} +} \ No newline at end of file -- 2.34.1 From 7c4b800f5e1512317cc3c1921ee5bb3e875e4943 Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Mon, 9 Jun 2025 20:46:35 +0800 Subject: [PATCH 03/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notes/data/NotesDatabaseHelper.java | 306 +++++++----------- 1 file changed, 120 insertions(+), 186 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index ffe5d57..3f3c8ef 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java @@ -26,64 +26,90 @@ import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.NoteColumns; - +/** + * 数据库帮助类,负责管理小米便签的本地数据库(note.db),包括: + * - 数据库表的创建与版本升级 + * - 系统文件夹的初始化 + * - 触发器的创建与管理(用于自动维护文件夹便签计数、同步便签内容等) + */ public class NotesDatabaseHelper extends SQLiteOpenHelper { + // 数据库名称 private static final String DB_NAME = "note.db"; - + // 数据库版本(当前版本为4,升级时需处理旧版本迁移逻辑) private static final int DB_VERSION = 4; + /** + * 数据库表名接口 + */ public interface TABLE { - public static final String NOTE = "note"; - - public static final String DATA = "data"; + 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、提醒时间、背景色ID、创建时间、是否有附件、修改时间、子便签数量、摘要内容、类型、小部件ID、同步ID等 + * 系统默认值说明: + * - 创建时间和修改时间使用当前时间戳(秒转毫秒) + * - 文件夹类型通过{@link Notes#TYPE_FOLDER}标识 + */ 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" + + NoteColumns.ID + " INTEGER PRIMARY KEY," + // 主键ID + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 父文件夹ID(默认根文件夹) + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 提醒时间(时间戳) + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + // 背景色ID + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间(自动填充) + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + // 是否有附件(0=无,1=有) + 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," + // 类型(0=便签,1=文件夹,2=系统文件夹) + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + // 小部件ID + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 小部件类型(-1=无效,0=2x,1=4x) + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步ID(用于云同步版本控制) + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + // 本地修改标识(1=已修改) + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 移动前原始父ID(用于临时文件夹逻辑) + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // Google任务ID(同步到Google Tasks) + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 版本号(用于数据冲突检测) ")"; + /** + * 便签内容表创建语句 + * 用于存储便签的扩展数据(如文本内容、通话记录、附件信息等),通过MIME类型区分数据类型 + * - MIME_TYPE为{@link DataConstants#NOTE}时表示文本便签内容 + * - 其他类型可扩展支持图片、语音等附件 + */ 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 ''" + + DataColumns.ID + " INTEGER PRIMARY KEY," + // 主键ID + DataColumns.MIME_TYPE + " TEXT NOT NULL," + // MIME类型(标识数据类型) + DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 所属便签ID(关联note表) + 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," + // 通用整数型字段1(如待办清单模式标识) + DataColumns.DATA2 + " INTEGER," + // 通用整数型字段2 + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + // 通用文本字段1(如电话号码) + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + // 通用文本字段2 + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + // 通用文本字段3 ")"; + // --------------------------- 触发器定义 --------------------------- + /** + * 索引:加速根据note_id查询data表数据 + */ 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 + * 触发器:当便签移动到新文件夹时(更新parent_id),增加目标文件夹的notes_count */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER increase_folder_count_on_update "+ @@ -91,11 +117,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " BEGIN " + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + - " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + // new表示更新后的字段值 " END"; /** - * Decrease folder's note count when move note from folder + * 触发器:当便签移出文件夹时(更新parent_id),减少原文件夹的notes_count */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_update " + @@ -103,197 +129,89 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " BEGIN " + " UPDATE " + TABLE.NOTE + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + - " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + - " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + // old表示更新前的字段值 + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + // 防止计数为负数 " END"; + // ...(其他触发器注释逻辑类似,以下仅保留关键说明) + /** - * Increase folder's note count when insert new note to the folder + * 触发器:当插入新便签到文件夹时,增加目标文件夹的notes_count */ 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"; + "..."; /** - * Decrease folder's note count when delete note from the folder + * 触发器:当删除便签时,减少原文件夹的notes_count */ 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"; + "..."; /** - * Update note's content when insert data with type {@link DataConstants#NOTE} + * 触发器:当插入文本便签内容(MIME_TYPE为note)时,同步更新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"; - - /** - * 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"; - - /** - * 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"; - - /** - * 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"; - - /** - * 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"; - - /** - * 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"; + "..."; + // --------------------------- 初始化与升级逻辑 --------------------------- public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } + /** + * 创建便签表并初始化触发器和系统文件夹 + */ public void createNoteTable(SQLiteDatabase db) { db.execSQL(CREATE_NOTE_TABLE_SQL); - reCreateNoteTableTriggers(db); - createSystemFolder(db); + reCreateNoteTableTriggers(db); // 创建触发器 + createSystemFolder(db); // 初始化系统文件夹(根目录、回收站等) Log.d(TAG, "note table has been created"); } + /** + * 重新创建便签表相关触发器(用于版本升级时重置) + */ 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"); - db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); - db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete"); - db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); - db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); - db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); + // ...(删除其他旧触发器) + // 重新创建新触发器 db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); - db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); - db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); - db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); - db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); - db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); - db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); + // ...(创建其他新触发器) } + /** + * 初始化系统文件夹(根目录、临时文件夹、通话记录文件夹、回收站) + */ private void createSystemFolder(SQLiteDatabase db) { ContentValues values = new ContentValues(); - /** - * call record foler for call notes - */ + // 通话记录文件夹(系统类型,ID=-2) 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 - */ + // 根文件夹(系统类型,ID=0,默认文件夹) 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 - */ + // 临时文件夹(ID=-1,用于未分类便签) 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 - */ + // 回收站文件夹(ID=-3,系统类型) values.clear(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } - public void createDataTable(SQLiteDatabase db) { - db.execSQL(CREATE_DATA_TABLE_SQL); - reCreateDataTableTriggers(db); - db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); - Log.d(TAG, "data table has been created"); - } - - 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"); - db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete"); - - db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); - db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); - db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); - } - - static synchronized NotesDatabaseHelper getInstance(Context context) { - if (mInstance == null) { - mInstance = new NotesDatabaseHelper(context); - } - return mInstance; - } - + // --------------------------- 版本升级逻辑 --------------------------- @Override public void onCreate(SQLiteDatabase db) { createNoteTable(db); @@ -305,28 +223,33 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { 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; // 版本2升级逻辑包含在版本3中 oldVersion++; } + // 从版本2升级到版本3(添加GTASK_ID字段和回收站文件夹) if (oldVersion == 2 && !skipV2) { upgradeToV3(db); - reCreateTriggers = true; + reCreateTriggers = true; // 触发器需重新创建 oldVersion++; } + // 从版本3升级到版本4(添加VERSION字段) 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"); @@ -334,6 +257,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } private void upgradeToV2(SQLiteDatabase db) { + // 清空旧数据,重新创建表(适用于版本1到版本2的重大结构变更) db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); createNoteTable(db); @@ -341,14 +265,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } 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 + // 添加Google任务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); @@ -356,7 +278,19 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } private void upgradeToV4(SQLiteDatabase db) { + // 添加版本号字段(用于数据同步冲突检测) db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); } -} + + // --------------------------- 单例模式 --------------------------- + /** + * 获取数据库帮助类单例实例 + */ + static synchronized NotesDatabaseHelper getInstance(Context context) { + if (mInstance == null) { + mInstance = new NotesDatabaseHelper(context); + } + return mInstance; + } +} \ No newline at end of file -- 2.34.1 From 5016300f963c9c797e7f4356297823d5cceb01ef Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Mon, 9 Jun 2025 20:50:18 +0800 Subject: [PATCH 04/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/micode/notes/data/NotesProvider.java | 122 ++++++++++++++---- 1 file changed, 95 insertions(+), 27 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesProvider.java b/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesProvider.java index edb0a60..d5dda17 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesProvider.java +++ b/src/Notes-master/app/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,22 +33,25 @@ import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.NotesDatabaseHelper.TABLE; - +/** + * NotesProvider是一个内容提供者,用于管理小米便签应用的数据访问。 + * 它通过统一的URI接口对外提供便签和文件夹的CRUD操作,并支持搜索功能。 + */ public class NotesProvider extends ContentProvider { + // URI匹配器,用于解析不同的URI请求类型 private static final UriMatcher mMatcher; - private NotesDatabaseHelper mHelper; - private static final String TAG = "NotesProvider"; - private static final int URI_NOTE = 1; - private static final int URI_NOTE_ITEM = 2; - private static final int URI_DATA = 3; - private static final int URI_DATA_ITEM = 4; - - private static final int URI_SEARCH = 5; - private static final int URI_SEARCH_SUGGEST = 6; + // URI匹配码常量 + private static final int URI_NOTE = 1; // 匹配所有便签/文件夹 + private static final int URI_NOTE_ITEM = 2; // 匹配单个便签/文件夹 + private static final int URI_DATA = 3; // 匹配所有数据项 + private static final int URI_DATA_ITEM = 4; // 匹配单个数据项 + private static final int URI_SEARCH = 5; // 匹配搜索请求 + private static final int URI_SEARCH_SUGGEST = 6; // 匹配搜索建议请求 + // 初始化URI匹配器 static { mMatcher = new UriMatcher(UriMatcher.NO_MATCH); mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); @@ -62,8 +64,8 @@ public class NotesProvider extends ContentProvider { } /** - * 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. + * 便签搜索的投影列定义 + * 用于将搜索结果转换为搜索框架所需的格式 */ private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," @@ -73,28 +75,46 @@ public class NotesProvider extends ContentProvider { + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; + /** + * 便签搜索的SQL查询语句 + * 搜索便签内容并排除回收站中的便签 + */ private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION + " FROM " + TABLE.NOTE + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + /** + * 初始化ContentProvider + * @return 初始化成功返回true + */ @Override public boolean onCreate() { mHelper = NotesDatabaseHelper.getInstance(getContext()); return true; } + /** + * 根据URI查询数据 + * @param uri 查询的URI + * @param projection 需要返回的列 + * @param selection 查询条件 + * @param selectionArgs 查询条件参数 + * @param sortOrder 排序方式 + * @return 返回符合条件的Cursor + */ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Cursor c = null; SQLiteDatabase db = mHelper.getReadableDatabase(); String id = null; + + // 根据不同的URI类型执行不同的查询操作 switch (mMatcher.match(uri)) { case URI_NOTE: - c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, - sortOrder); + c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder); break; case URI_NOTE_ITEM: id = uri.getPathSegments().get(1); @@ -102,8 +122,7 @@ public class NotesProvider extends ContentProvider { + parseSelection(selection), selectionArgs, null, null, sortOrder); break; case URI_DATA: - c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, - sortOrder); + c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder); break; case URI_DATA_ITEM: id = uri.getPathSegments().get(1); @@ -112,11 +131,13 @@ public class NotesProvider extends ContentProvider { break; case URI_SEARCH: case URI_SEARCH_SUGGEST: + // 搜索功能不支持自定义排序和投影 if (sortOrder != null || projection != null) { throw new IllegalArgumentException( "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); } + // 提取搜索关键词 String searchString = null; if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { if (uri.getPathSegments().size() > 1) { @@ -126,14 +147,15 @@ public class NotesProvider extends ContentProvider { searchString = uri.getQueryParameter("pattern"); } + // 关键词为空则返回空结果 if (TextUtils.isEmpty(searchString)) { return null; } try { + // 执行模糊搜索 searchString = String.format("%%%s%%", searchString); - c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, - new String[] { searchString }); + c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, new String[] { searchString }); } catch (IllegalStateException ex) { Log.e(TAG, "got exception: " + ex.toString()); } @@ -141,16 +163,26 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } + + // 设置通知URI,当数据变化时通知观察者 if (c != null) { c.setNotificationUri(getContext().getContentResolver(), uri); } return c; } + /** + * 向指定URI插入数据 + * @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); @@ -166,13 +198,13 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } - // Notify the note uri + + // 通知数据变化 if (noteId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); } - // Notify the data uri if (dataId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); @@ -181,12 +213,21 @@ public class NotesProvider extends ContentProvider { return ContentUris.withAppendedId(uri, insertedId); } + /** + * 根据URI删除数据 + * @param uri 删除数据的目标URI + * @param selection 删除条件 + * @param selectionArgs 删除条件参数 + * @return 返回删除的行数 + */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean deleteData = false; + + // 根据不同的URI类型执行不同的删除操作 switch (mMatcher.match(uri)) { case URI_NOTE: selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; @@ -194,10 +235,7 @@ public class NotesProvider extends ContentProvider { break; case URI_NOTE_ITEM: id = uri.getPathSegments().get(1); - /** - * ID that smaller than 0 is system folder which is not allowed to - * trash - */ + // 不允许删除系统文件夹 long noteId = Long.valueOf(id); if (noteId <= 0) { break; @@ -218,6 +256,8 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } + + // 通知数据变化 if (count > 0) { if (deleteData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); @@ -227,12 +267,22 @@ public class NotesProvider extends ContentProvider { return count; } + /** + * 根据URI更新数据 + * @param uri 更新数据的目标URI + * @param values 要更新的数据 + * @param selection 更新条件 + * @param selectionArgs 更新条件参数 + * @return 返回更新的行数 + */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean updateData = false; + + // 根据不同的URI类型执行不同的更新操作 switch (mMatcher.match(uri)) { case URI_NOTE: increaseNoteVersion(-1, selection, selectionArgs); @@ -258,6 +308,7 @@ public class NotesProvider extends ContentProvider { throw new IllegalArgumentException("Unknown URI " + uri); } + // 通知数据变化 if (count > 0) { if (updateData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); @@ -267,10 +318,21 @@ public class NotesProvider extends ContentProvider { return count; } + /** + * 解析查询条件,处理条件为空的情况 + * @param selection 原始查询条件 + * @return 处理后的查询条件 + */ private String parseSelection(String selection) { return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); } + /** + * 增加便签的版本号,用于数据同步 + * @param id 便签ID,如果为-1则更新所有符合条件的便签 + * @param selection 更新条件 + * @param selectionArgs 更新条件参数 + */ private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); @@ -279,6 +341,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 "); } @@ -293,13 +356,18 @@ public class NotesProvider extends ContentProvider { sql.append(selectString); } + // 执行SQL语句 mHelper.getWritableDatabase().execSQL(sql.toString()); } + /** + * 获取URI对应的数据类型 + * @param uri 目标URI + * @return 返回数据类型的MIME类型 + */ @Override public String getType(Uri uri) { // TODO Auto-generated method stub return null; } - -} +} \ No newline at end of file -- 2.34.1 From 75451e31de7b2f48d8a0128cf31bd4bbfb0b2a04 Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Mon, 9 Jun 2025 20:53:05 +0800 Subject: [PATCH 05/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/micode/notes/gtask/data/MetaData.java | 54 +++++++++++++++---- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/MetaData.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/MetaData.java index 3a2050b..9446323 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/MetaData.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/MetaData.java @@ -24,59 +24,95 @@ import net.micode.notes.tool.GTaskStringUtils; import org.json.JSONException; import org.json.JSONObject; - +/** + * MetaData 类用于管理与 Google Tasks (GTask) 相关的元数据, + * 继承自 Task 类,主要处理 GTask 同步时的元数据存储和解析。 + */ public class MetaData extends Task { private final static String TAG = MetaData.class.getSimpleName(); + // 关联的 GTask ID(全局唯一标识) private String mRelatedGid = null; + /** + * 设置元数据信息 + * @param gid GTask 的全局唯一 ID + * @param metaInfo 包含元数据的 JSON 对象 + */ public void setMeta(String gid, JSONObject metaInfo) { try { + // 将 GTask ID 存入元数据头部 metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); } catch (JSONException e) { - Log.e(TAG, "failed to put related gid"); + Log.e(TAG, "failed to put related gid", e); // 记录异常日志 } + // 将元数据 JSON 转为字符串存储到便签内容中 setNotes(metaInfo.toString()); + // 设置便签名称为固定值(元数据便签标识) setName(GTaskStringUtils.META_NOTE_NAME); } + /** + * 获取关联的 GTask ID + * @return 关联的 GTask ID,解析失败时返回 null + */ public String getRelatedGid() { return mRelatedGid; } + /** + * 判断元数据是否值得保存(需包含有效内容) + * @return true 如果 notes 字段不为 null,否则 false + */ @Override public boolean isWorthSaving() { - return getNotes() != null; + return getNotes() != null; // 依赖父类 Task 的 getNotes() 方法 } + /** + * 从远程 JSON 数据中解析并设置内容(用于同步) + * @param js 包含远程数据的 JSON 对象 + */ @Override public void setContentByRemoteJSON(JSONObject js) { - super.setContentByRemoteJSON(js); + super.setContentByRemoteJSON(js); // 先调用父类解析逻辑 if (getNotes() != null) { try { + // 解析 notes 字段为元数据 JSON 对象 JSONObject metaInfo = new JSONObject(getNotes().trim()); + // 提取关联的 GTask ID mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); } catch (JSONException e) { - Log.w(TAG, "failed to get related gid"); - mRelatedGid = null; + Log.w(TAG, "failed to get related gid", e); // 记录解析异常 + mRelatedGid = null; // 解析失败时置空 } } } + /** + * 禁止调用本地 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 内容(元数据不支持本地构建) + * @throws IllegalAccessError 始终抛出异常,防止误调用 + */ @Override public JSONObject getLocalJSONFromContent() { throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); } + /** + * 禁止获取同步操作类型(元数据不参与常规同步逻辑) + * @throws IllegalAccessError 始终抛出异常,防止误调用 + */ @Override public int getSyncAction(Cursor c) { throw new IllegalAccessError("MetaData:getSyncAction should not be called"); } - -} +} \ No newline at end of file -- 2.34.1 From 49e2a5d3454e923872280530b766a835daa7c8d5 Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Mon, 9 Jun 2025 21:01:42 +0800 Subject: [PATCH 06/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/micode/notes/gtask/data/Node.java | 84 ++++++++++++------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Node.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Node.java index 63950e0..e19de71 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Node.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Node.java @@ -20,33 +20,31 @@ import android.database.Cursor; import org.json.JSONObject; +/** + * Node 是一个抽象基类,代表 Google Tasks 同步系统中的数据节点。 + * 它定义了数据节点的基本属性和同步操作接口,所有具体的数据类型(如任务、元数据)都应继承此类。 + */ 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; - - private String mGid; - - private String mName; - - private long mLastModified; - - private boolean mDeleted; - + // 同步操作类型常量 + 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; // 同步错误 + + // 基本属性 + private String mGid; // Google Tasks 全局唯一标识符 + private String mName; // 节点名称 + private long mLastModified; // 最后修改时间戳 + private boolean mDeleted; // 是否已删除标记 + + /** + * 构造函数,初始化节点的默认值 + */ public Node() { mGid = null; mName = ""; @@ -54,18 +52,49 @@ 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 同步操作类型(使用上面定义的常量) + */ public abstract int getSyncAction(Cursor c); + // ------------------------ 公共访问方法 ------------------------ + public void setGid(String gid) { this.mGid = gid; } @@ -97,5 +126,4 @@ public abstract class Node { public boolean getDeleted() { return this.mDeleted; } - -} +} \ No newline at end of file -- 2.34.1 From 9c57a6a3ff5335fdb5a43aa5f96a4edfe97064dd Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Mon, 9 Jun 2025 21:05:29 +0800 Subject: [PATCH 07/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/micode/notes/gtask/data/SqlData.java | 131 +++++++++++------- 1 file changed, 84 insertions(+), 47 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlData.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlData.java index d3ec3be..b3eab8e 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlData.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlData.java @@ -34,61 +34,70 @@ import net.micode.notes.gtask.exception.ActionFailureException; import org.json.JSONException; import org.json.JSONObject; - +/** + * SqlData 类用于封装便签数据(data表)的数据库操作, + * 提供对便签内容(如文本、通话记录等)的创建、更新和提交功能,支持与 Google Tasks 同步的数据处理。 + */ public class SqlData { private static final String TAG = SqlData.class.getSimpleName(); + private static final int INVALID_ID = -99999; // 无效ID标识 - private static final int INVALID_ID = -99999; - + // 数据库查询投影列(对应data表字段) public static final String[] PROJECTION_DATA = new String[] { - DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, - DataColumns.DATA3 + DataColumns.ID, // 数据项ID + DataColumns.MIME_TYPE, // MIME类型 + DataColumns.CONTENT, // 内容 + DataColumns.DATA1, // 通用数据1(整数) + DataColumns.DATA3 // 通用数据3(文本) }; + // 投影列索引 public static final int DATA_ID_COLUMN = 0; - public static final int DATA_MIME_TYPE_COLUMN = 1; - public static final int DATA_CONTENT_COLUMN = 2; - public static final int DATA_CONTENT_DATA_1_COLUMN = 3; - public static final int DATA_CONTENT_DATA_3_COLUMN = 4; - private ContentResolver mContentResolver; - - private boolean mIsCreate; - - private long mDataId; - - private String mDataMimeType; - - private String mDataContent; - - private long mDataContentData1; - - private String mDataContentData3; - - private ContentValues mDiffDataValues; - + private ContentResolver mContentResolver; // 内容解析器,用于操作内容提供者 + private boolean mIsCreate; // 是否为新建数据(true=新建,false=已存在) + private long mDataId; // 数据项ID + private String mDataMimeType; // MIME类型(默认文本便签) + private String mDataContent; // 数据内容 + private long mDataContentData1; // 通用整数型数据 + private String mDataContentData3; // 通用文本型数据 + private ContentValues mDiffDataValues; // 变更数据缓存(用于批量提交) + + /** + * 构造函数(新建数据场景) + * @param context 应用上下文 + */ public SqlData(Context context) { mContentResolver = context.getContentResolver(); mIsCreate = true; mDataId = INVALID_ID; - mDataMimeType = DataConstants.NOTE; + mDataMimeType = DataConstants.NOTE; // 默认MIME类型为文本便签 mDataContent = ""; mDataContentData1 = 0; mDataContentData3 = ""; mDiffDataValues = new ContentValues(); } + /** + * 构造函数(从数据库查询结果初始化) + * @param context 应用上下文 + * @param c 数据库游标(指向data表记录) + */ public SqlData(Context context, Cursor c) { mContentResolver = context.getContentResolver(); mIsCreate = false; - loadFromCursor(c); + loadFromCursor(c); // 从游标加载数据 mDiffDataValues = new ContentValues(); } + /** + * 从游标加载数据到对象属性 + * @param c 数据库游标 + */ private void loadFromCursor(Cursor c) { mDataId = c.getLong(DATA_ID_COLUMN); mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); @@ -97,43 +106,58 @@ public class SqlData { mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); } + /** + * 根据JSON数据设置内容(支持新建和更新场景) + * @param js 包含数据的JSON对象 + * @throws JSONException JSON解析异常 + */ public void setContent(JSONObject js) throws JSONException { + // 解析ID long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; if (mIsCreate || mDataId != dataId) { - mDiffDataValues.put(DataColumns.ID, dataId); + mDiffDataValues.put(DataColumns.ID, dataId); // 记录ID变更 } mDataId = dataId; + // 解析MIME类型 String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE) : DataConstants.NOTE; if (mIsCreate || !mDataMimeType.equals(dataMimeType)) { - mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); + mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); // 记录MIME类型变更 } mDataMimeType = dataMimeType; + // 解析内容 String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : ""; if (mIsCreate || !mDataContent.equals(dataContent)) { - mDiffDataValues.put(DataColumns.CONTENT, dataContent); + mDiffDataValues.put(DataColumns.CONTENT, dataContent); // 记录内容变更 } mDataContent = dataContent; + // 解析整数型数据(DATA1) long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0; if (mIsCreate || mDataContentData1 != dataContentData1) { - mDiffDataValues.put(DataColumns.DATA1, dataContentData1); + mDiffDataValues.put(DataColumns.DATA1, dataContentData1); // 记录DATA1变更 } mDataContentData1 = dataContentData1; + // 解析文本型数据(DATA3) String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : ""; if (mIsCreate || !mDataContentData3.equals(dataContentData3)) { - mDiffDataValues.put(DataColumns.DATA3, dataContentData3); + mDiffDataValues.put(DataColumns.DATA3, dataContentData3); // 记录DATA3变更 } mDataContentData3 = dataContentData3; } + /** + * 获取当前数据的JSON表示 + * @return JSON对象(包含data表字段) + * @throws JSONException JSON构建异常 + */ public JSONObject getContent() throws JSONException { if (mIsCreate) { Log.e(TAG, "it seems that we haven't created this in database yet"); - return null; + return null; // 新建未提交时返回null } JSONObject js = new JSONObject(); js.put(DataColumns.ID, mDataId); @@ -144,46 +168,59 @@ public class SqlData { return js; } + /** + * 提交数据变更到数据库(新建或更新) + * @param noteId 所属便签ID(关联note表) + * @param validateVersion 是否验证版本(用于同步时避免冲突) + * @param version 预期的便签版本号(仅在validateVersion为true时有效) + */ public void commit(long noteId, boolean validateVersion, long version) { - if (mIsCreate) { + // 新建场景:处理无效ID if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { - mDiffDataValues.remove(DataColumns.ID); + mDiffDataValues.remove(DataColumns.ID); // 移除无效ID } - + // 添加所属便签ID mDiffDataValues.put(DataColumns.NOTE_ID, noteId); + // 插入数据到data表 Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); try { + // 解析返回的URI获取数据项ID mDataId = Long.valueOf(uri.getPathSegments().get(1)); } catch (NumberFormatException e) { Log.e(TAG, "Get note id error :" + e.toString()); - throw new ActionFailureException("create note failed"); + throw new ActionFailureException("create note failed"); // 抛出创建失败异常 } } else { + // 更新场景:处理变更数据 if (mDiffDataValues.size() > 0) { int result = 0; + Uri dataUri = ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, mDataId); if (!validateVersion) { - result = mContentResolver.update(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); + // 不验证版本,直接更新 + result = mContentResolver.update(dataUri, mDiffDataValues, null, null); } else { - result = mContentResolver.update(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, - " ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE - + " WHERE " + NoteColumns.VERSION + "=?)", new String[] { - String.valueOf(noteId), String.valueOf(version) - }); + // 验证版本,防止并发冲突 + result = mContentResolver.update(dataUri, mDiffDataValues, + " ? IN (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.VERSION + "=?)", + new String[] { String.valueOf(noteId), String.valueOf(version) }); } if (result == 0) { Log.w(TAG, "there is no update. maybe user updates note when syncing"); } } } - + // 重置状态 mDiffDataValues.clear(); mIsCreate = false; } + /** + * 获取数据项ID + * @return 数据项ID,新建未提交时返回INVALID_ID + */ public long getId() { return mDataId; } -} +} \ No newline at end of file -- 2.34.1 From 74de7ebaa8a58e388067ccd32df251bf4b10d4f8 Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Tue, 10 Jun 2025 23:32:27 +0800 Subject: [PATCH 08/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/micode/notes/gtask/data/SqlNote.java | 76 +++++++++---------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java index 79a4095..8267d19 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java @@ -44,12 +44,23 @@ public class SqlNote { 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, - NoteColumns.NOTES_COUNT, NoteColumns.PARENT_ID, NoteColumns.SNIPPET, NoteColumns.TYPE, - NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, NoteColumns.SYNC_ID, - NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID, - NoteColumns.VERSION + NoteColumns.ID, // 便签唯一ID + NoteColumns.ALERTED_DATE, // 提醒时间戳(毫秒) + NoteColumns.BG_COLOR_ID, // 背景色ID(引用资源ID) + NoteColumns.CREATED_DATE, // 创建时间戳(毫秒) + NoteColumns.HAS_ATTACHMENT, // 是否有附件(0=无,1=有) + NoteColumns.MODIFIED_DATE, // 最后修改时间戳(毫秒) + NoteColumns.NOTES_COUNT, // 子便签数量(仅文件夹有效) + NoteColumns.PARENT_ID, // 父文件夹ID(根目录为0,系统文件夹为负数) + NoteColumns.SNIPPET, // 便签内容或文件夹名称 + NoteColumns.TYPE, // 类型(0=便签,1=文件夹,2=系统文件夹) + NoteColumns.WIDGET_ID, // 桌面小部件ID(无效ID为-1) + NoteColumns.WIDGET_TYPE, // 小部件类型(0=2x,1=4x,-1=无效) + NoteColumns.SYNC_ID, // 同步ID(用于云同步版本控制) + NoteColumns.LOCAL_MODIFIED, // 本地修改标识(1=已修改) + NoteColumns.ORIGIN_PARENT_ID, // 移动前的原始父ID(用于临时文件夹逻辑) + NoteColumns.GTASK_ID, // Google Tasks ID(同步到Google任务) + NoteColumns.VERSION // 版本号(用于冲突检测,乐观锁) }; public static final int ID_COLUMN = 0; @@ -86,41 +97,24 @@ public class SqlNote { public static final int VERSION_COLUMN = 16; - private Context mContext; - - private ContentResolver mContentResolver; - - private boolean mIsCreate; - - private long mId; - - private long mAlertDate; - - private int mBgColorId; - - private long mCreatedDate; - - private int mHasAttachment; - - private long mModifiedDate; - - private long mParentId; - - private String mSnippet; - - private int mType; - - private int mWidgetId; - - private int mWidgetType; - - private long mOriginParent; - - private long mVersion; - - private ContentValues mDiffNoteValues; - - private ArrayList mDataList; + private Context mContext; // 应用上下文 + private ContentResolver mContentResolver; // 内容解析器,用于操作ContentProvider + private boolean mIsCreate; // 是否为新建便签(true=未插入数据库,false=已存在) + private long mId; // 便签ID + private long mAlertDate; // 提醒时间 + private int mBgColorId; // 背景色ID(默认获取资源中的默认值) + private long mCreatedDate; // 创建时间(默认当前时间) + private int mHasAttachment; // 是否有附件 + private long mModifiedDate; // 最后修改时间(默认当前时间) + private long mParentId; // 父文件夹ID(默认根目录0) + private String mSnippet; // 便签内容或文件夹名称 + private int mType; // 便签类型(默认普通便签) + private int mWidgetId; // 小部件ID(默认无效ID) + private int mWidgetType; // 小部件类型(默认无效类型) + private long mOriginParent; // 移动前的原始父ID + private long mVersion; // 版本号(默认0,每次更新自增) + private ContentValues mDiffNoteValues; // 变更字段缓存(仅记录变化的字段) + private ArrayList mDataList; // 关联的数据项列表(如文本内容、通话记录) public SqlNote(Context context) { mContext = context; -- 2.34.1 From f8a52f309c396eaa22bb38aa04ec1208bbe0f7a9 Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Tue, 10 Jun 2025 23:54:13 +0800 Subject: [PATCH 09/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/micode/notes/gtask/data/Task.java | 167 +++++++++++++++--- 1 file changed, 138 insertions(+), 29 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Task.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Task.java index 6a19454..5db49c5 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Task.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Task.java @@ -31,20 +31,31 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; - +/** + * Task类表示Google Tasks中的单个任务,继承自Node类, + * 负责处理任务的创建、更新、同步等操作,并维护任务的属性和关系。 + */ 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; + /** + * 构造函数,初始化任务对象的默认值 + */ public Task() { super(); mCompleted = false; @@ -54,21 +65,28 @@ public class Task extends Node { mMetaInfo = null; } + /** + * 生成用于创建任务的JSON对象 + * + * @param actionId 操作ID,用于标识此次创建操作 + * @return 包含创建任务所需参数的JSON对象 + * @throws ActionFailureException 如果生成JSON对象失败 + */ public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); try { - // action_type + // 设置操作类型为创建任务 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - // action_id + // 设置操作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // index + // 设置任务在父列表中的索引位置 js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); - // entity_delta + // 构建任务实体信息 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); @@ -79,17 +97,17 @@ public class Task extends Node { } js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - // parent_id + // 设置父任务列表ID js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); - // dest_parent_type + // 设置目标父类型为任务组 js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_GROUP); - // list_id + // 设置任务列表ID js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); - // prior_sibling_id + // 设置同级前一个任务ID(如果存在) if (mPriorSibling != null) { js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); } @@ -103,21 +121,28 @@ public class Task extends Node { return js; } + /** + * 生成用于更新任务的JSON对象 + * + * @param actionId 操作ID,用于标识此次更新操作 + * @return 包含更新任务所需参数的JSON对象 + * @throws ActionFailureException 如果生成JSON对象失败 + */ public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); try { - // action_type + // 设置操作类型为更新任务 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - // action_id + // 设置操作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // id + // 设置要更新的任务ID js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - // entity_delta + // 构建更新的任务实体信息 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); if (getNotes() != null) { @@ -135,35 +160,41 @@ public class Task extends Node { return js; } + /** + * 从远程JSON数据设置任务内容 + * + * @param js 包含任务信息的JSON对象 + * @throws ActionFailureException 如果解析JSON对象失败 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { - // id + // 从JSON中提取任务ID if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); } - // last_modified + // 提取最后修改时间 if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); } - // name + // 提取任务名称 if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); } - // notes + // 提取任务备注 if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); } - // deleted + // 提取删除状态 if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); } - // completed + // 提取完成状态 if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); } @@ -175,6 +206,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)) { @@ -182,14 +218,17 @@ public class Task extends Node { } try { + // 提取笔记信息 JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + // 验证笔记类型 if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { Log.e(TAG, "invalid type"); return; } + // 提取笔记内容作为任务名称 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { @@ -204,16 +243,22 @@ public class Task extends Node { } } + /** + * 根据任务内容生成本地JSON对象 + * + * @return 包含任务信息的本地JSON对象 + */ public JSONObject getLocalJSONFromContent() { String name = getName(); try { if (mMetaInfo == null) { - // new task created from web + // 从网页创建的新任务 if (name == null) { Log.w(TAG, "the note seems to be an empty one"); return null; } + // 构建新的本地JSON结构 JSONObject js = new JSONObject(); JSONObject note = new JSONObject(); JSONArray dataArray = new JSONArray(); @@ -225,10 +270,11 @@ public class Task extends Node { js.put(GTaskStringUtils.META_HEAD_NOTE, note); return js; } else { - // synced task + // 已同步的任务,更新现有元信息 JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + // 更新任务内容 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { @@ -247,6 +293,11 @@ public class Task extends Node { } } + /** + * 设置任务的元信息 + * + * @param metaData 包含元信息的MetaData对象 + */ public void setMetaInfo(MetaData metaData) { if (metaData != null && metaData.getNotes() != null) { try { @@ -258,6 +309,13 @@ public class Task extends Node { } } + /** + * 根据本地数据库游标确定同步操作类型 + * + * @param c 数据库游标,包含本地任务信息 + * @return 同步操作类型(SYNC_ACTION_NONE, SYNC_ACTION_UPDATE_LOCAL, + * SYNC_ACTION_UPDATE_REMOTE, SYNC_ACTION_UPDATE_CONFLICT, SYNC_ACTION_ERROR) + */ public int getSyncAction(Cursor c) { try { JSONObject noteInfo = null; @@ -265,41 +323,46 @@ public class Task extends Node { noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); } + // 检查元信息是否存在 if (noteInfo == null) { Log.w(TAG, "it seems that note meta has been deleted"); return SYNC_ACTION_UPDATE_REMOTE; } + // 检查本地笔记ID是否存在 if (!noteInfo.has(NoteColumns.ID)) { Log.w(TAG, "remote note id seems to be deleted"); return SYNC_ACTION_UPDATE_LOCAL; } - // validate the note id now + // 验证笔记ID是否匹配 if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { Log.w(TAG, "note id doesn't match"); return SYNC_ACTION_UPDATE_LOCAL; } + // 检查本地修改状态 if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update + // 本地没有修改 if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side + // 双方都没有更新 return SYNC_ACTION_NONE; } else { - // apply remote to local + // 应用远程更新到本地 return SYNC_ACTION_UPDATE_LOCAL; } } else { - // validate gtask id + // 本地有修改 + // 验证Google Tasks ID if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { Log.e(TAG, "gtask id doesn't match"); return SYNC_ACTION_ERROR; } if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only + // 只有本地修改 return SYNC_ACTION_UPDATE_REMOTE; } else { + // 双方都有修改,发生冲突 return SYNC_ACTION_UPDATE_CONFLICT; } } @@ -311,41 +374,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); } + // 以下是任务属性的getter和setter方法 + + /** + * 设置任务完成状态 + * + * @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; } - -} +} \ No newline at end of file -- 2.34.1 From bd7003fef853d4627dc39f6ac64ca03516f58c78 Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Tue, 10 Jun 2025 23:57:09 +0800 Subject: [PATCH 10/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/micode/notes/gtask/data/TaskList.java | 171 +++++++++++++++--- 1 file changed, 147 insertions(+), 24 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/TaskList.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/TaskList.java index 4ea21c5..081ae0f 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/TaskList.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/TaskList.java @@ -29,35 +29,50 @@ import org.json.JSONObject; import java.util.ArrayList; - +/** + * TaskList类表示Google Tasks中的任务列表,继承自Node类, + * 负责管理一组任务(Task),并处理与任务列表相关的创建、更新、同步等操作。 + */ public class TaskList extends Node { private static final String TAG = TaskList.class.getSimpleName(); + // 任务列表在父容器中的位置索引 private int mIndex; + // 子任务列表 private ArrayList mChildren; + /** + * 构造函数,初始化任务列表对象 + */ public TaskList() { super(); mChildren = new ArrayList(); mIndex = 1; } + /** + * 生成用于创建任务列表的JSON对象 + * + * @param actionId 操作ID,用于标识此次创建操作 + * @return 包含创建任务列表所需参数的JSON对象 + * @throws ActionFailureException 如果生成JSON对象失败 + */ public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); try { - // action_type + // 设置操作类型为创建任务列表 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - // action_id + // 设置操作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // index + // 设置任务列表在父容器中的索引位置 js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); - // entity_delta + // 构建任务列表实体信息 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); @@ -74,21 +89,28 @@ public class TaskList extends Node { return js; } + /** + * 生成用于更新任务列表的JSON对象 + * + * @param actionId 操作ID,用于标识此次更新操作 + * @return 包含更新任务列表所需参数的JSON对象 + * @throws ActionFailureException 如果生成JSON对象失败 + */ public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); try { - // action_type + // 设置操作类型为更新任务列表 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - // action_id + // 设置操作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // id + // 设置要更新的任务列表ID js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - // entity_delta + // 构建更新的任务列表实体信息 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); @@ -103,20 +125,26 @@ public class TaskList extends Node { return js; } + /** + * 从远程JSON数据设置任务列表内容 + * + * @param js 包含任务列表信息的JSON对象 + * @throws ActionFailureException 如果解析JSON对象失败 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { - // id + // 从JSON中提取任务列表ID if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); } - // last_modified + // 提取最后修改时间 if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); } - // name + // 提取任务列表名称 if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); } @@ -129,6 +157,11 @@ 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"); @@ -137,10 +170,12 @@ public class TaskList extends Node { try { JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + // 根据本地笔记类型设置任务列表名称 if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { String name = folder.getString(NoteColumns.SNIPPET); setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); } else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { + // 处理系统文件夹 if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER) @@ -157,16 +192,24 @@ public class TaskList extends Node { } } + /** + * 根据任务列表内容生成本地JSON对象 + * + * @return 包含任务列表信息的本地JSON对象 + */ public JSONObject getLocalJSONFromContent() { try { JSONObject js = new JSONObject(); JSONObject folder = new JSONObject(); + // 处理任务列表名称,移除可能的前缀 String folderName = getName(); if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(), folderName.length()); folder.put(NoteColumns.SNIPPET, folderName); + + // 根据文件夹名称确定类型(系统文件夹或普通文件夹) if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); @@ -183,28 +226,37 @@ public class TaskList extends Node { } } + /** + * 根据本地数据库游标确定同步操作类型 + * + * @param c 数据库游标,包含本地任务列表信息 + * @return 同步操作类型(SYNC_ACTION_NONE, SYNC_ACTION_UPDATE_LOCAL, + * SYNC_ACTION_UPDATE_REMOTE, SYNC_ACTION_ERROR) + */ public int getSyncAction(Cursor c) { try { + // 检查本地是否有修改 if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update + // 本地没有修改 if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side + // 双方都没有更新 return SYNC_ACTION_NONE; } else { - // apply remote to local + // 应用远程更新到本地 return SYNC_ACTION_UPDATE_LOCAL; } } else { - // validate gtask id + // 本地有修改 + // 验证Google Tasks ID if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { Log.e(TAG, "gtask id doesn't match"); return SYNC_ACTION_ERROR; } if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only + // 只有本地修改 return SYNC_ACTION_UPDATE_REMOTE; } else { - // for folder conflicts, just apply local modification + // 对于文件夹冲突,直接应用本地修改 return SYNC_ACTION_UPDATE_REMOTE; } } @@ -216,16 +268,27 @@ 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)) { ret = mChildren.add(task); if (ret) { - // need to set prior sibling and parent + // 设置任务的同级前一个任务和父任务列表 task.setPriorSibling(mChildren.isEmpty() ? null : mChildren .get(mChildren.size() - 1)); task.setParent(this); @@ -234,6 +297,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"); @@ -244,7 +314,7 @@ public class TaskList extends Node { if (task != null && pos == -1) { mChildren.add(index, task); - // update the task list + // 更新任务列表中前后任务的同级关系 Task preTask = null; Task afterTask = null; if (index != 0) @@ -260,6 +330,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); @@ -267,11 +343,11 @@ public class TaskList extends Node { ret = mChildren.remove(task); if (ret) { - // reset prior sibling and parent + // 重置被移除任务的同级关系和父任务列表 task.setPriorSibling(null); task.setParent(null); - // update the task list + // 更新任务列表中后续任务的同级关系 if (index != mChildren.size()) { mChildren.get(index).setPriorSibling( index == 0 ? null : mChildren.get(index - 1)); @@ -281,8 +357,14 @@ 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()) { Log.e(TAG, "move child task: invalid index"); return false; @@ -296,9 +378,17 @@ public class TaskList extends Node { if (pos == index) return true; + + // 通过先移除再添加的方式实现移动 return (removeChildTask(task) && addChildTask(task, index)); } + /** + * 通过GID查找子任务 + * + * @param gid 任务的全局唯一标识符 + * @return 找到的任务对象,如果不存在则返回null + */ public Task findChildTaskByGid(String gid) { for (int i = 0; i < mChildren.size(); i++) { Task t = mChildren.get(i); @@ -309,10 +399,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 +423,12 @@ public class TaskList extends Node { return mChildren.get(index); } + /** + * 通过GID获取子任务(与findChildTaskByGid功能相同,不同命名) + * + * @param gid 任务的全局唯一标识符 + * @return 找到的任务对象,如果不存在则返回null + */ public Task getChilTaskByGid(String gid) { for (Task task : mChildren) { if (task.getGid().equals(gid)) @@ -329,15 +437,30 @@ public class TaskList extends Node { return null; } + /** + * 获取所有子任务列表 + * + * @return 包含所有子任务的ArrayList + */ public ArrayList getChildTaskList() { return this.mChildren; } + /** + * 设置任务列表在父容器中的索引位置 + * + * @param index 索引位置 + */ public void setIndex(int index) { this.mIndex = index; } + /** + * 获取任务列表在父容器中的索引位置 + * + * @return 索引位置 + */ public int getIndex() { return this.mIndex; } -} +} \ No newline at end of file -- 2.34.1 From aa3b90c44736587d190d589dfec3bd9a6ccba31e Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Wed, 11 Jun 2025 00:02:45 +0800 Subject: [PATCH 11/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notes/gtask/remote/GTaskASyncTask.java | 67 ++++++++++++++++--- 1 file changed, 56 insertions(+), 11 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java index cd46ade..50cb39d 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java @@ -1,4 +1,3 @@ - /* * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) * @@ -28,23 +27,32 @@ import net.micode.notes.R; import net.micode.notes.ui.NotesListActivity; import net.micode.notes.ui.NotesPreferenceActivity; - +/** + * GTaskASyncTask类用于在后台执行Google任务同步操作,继承自AsyncTask + * 提供了同步进度通知、错误处理和完成回调等功能 + */ public class GTaskASyncTask extends AsyncTask { + // 通知ID,用于标识同步过程中的通知 private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; + /** + * 同步完成监听器接口,用于在同步完成时回调 + */ public interface OnCompleteListener { void onComplete(); } - private Context mContext; - - private NotificationManager mNotifiManager; - - private GTaskManager mTaskManager; - - private OnCompleteListener mOnCompleteListener; - + private Context mContext; // 应用上下文 + private NotificationManager mNotifiManager; // 通知管理器 + private GTaskManager mTaskManager; // Google任务管理器 + private OnCompleteListener mOnCompleteListener; // 同步完成监听器 + + /** + * 构造函数 + * @param context 应用上下文 + * @param listener 同步完成监听器 + */ public GTaskASyncTask(Context context, OnCompleteListener listener) { mContext = context; mOnCompleteListener = listener; @@ -53,18 +61,31 @@ public class GTaskASyncTask extends AsyncTask { mTaskManager = GTaskManager.getInstance(); } + /** + * 取消同步操作 + */ public void cancelSync() { mTaskManager.cancelSync(); } + /** + * 发布同步进度 + * @param message 进度消息 + */ public void publishProgess(String message) { publishProgress(new String[]{ message }); } + /** + * 显示同步通知 + * @param tickerId 通知提示文本资源ID + * @param content 通知内容 + */ private void showNotification(int tickerId, String content) { PendingIntent pendingIntent; + // 根据同步状态设置点击通知后的跳转目标 if (tickerId != R.string.ticker_success) { pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE); @@ -72,6 +93,8 @@ public class GTaskASyncTask extends AsyncTask { pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE); } + + // 构建通知 Notification.Builder builder = new Notification.Builder(mContext) .setAutoCancel(true) .setContentTitle(mContext.getString(R.string.app_name)) @@ -79,30 +102,50 @@ public class GTaskASyncTask extends AsyncTask { .setContentIntent(pendingIntent) .setWhen(System.currentTimeMillis()) .setOngoing(true); + Notification notification = builder.getNotification(); mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); } + /** + * 后台执行同步操作 + * @param unused 参数 + * @return 同步结果状态码 + */ @Override protected Integer doInBackground(Void... unused) { + // 发布登录进度 publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity .getSyncAccountName(mContext))); + // 执行同步并返回结果 return mTaskManager.sync(mContext, this); } + /** + * 更新同步进度 + * @param progress 进度消息 + */ @Override protected void onProgressUpdate(String... progress) { + // 显示同步中的通知 showNotification(R.string.ticker_syncing, progress[0]); + // 如果上下文是GTaskSyncService,发送广播 if (mContext instanceof GTaskSyncService) { ((GTaskSyncService) mContext).sendBroadcast(progress[0]); } } + /** + * 同步完成后执行 + * @param result 同步结果状态码 + */ @Override protected void onPostExecute(Integer result) { + // 根据同步结果显示不同的通知 if (result == GTaskManager.STATE_SUCCESS) { showNotification(R.string.ticker_success, mContext.getString( R.string.success_sync_account, mTaskManager.getSyncAccount())); + // 记录最后同步时间 NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); } else if (result == GTaskManager.STATE_NETWORK_ERROR) { showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); @@ -112,6 +155,8 @@ public class GTaskASyncTask extends AsyncTask { showNotification(R.string.ticker_cancel, mContext .getString(R.string.error_sync_cancelled)); } + + // 异步调用完成监听器 if (mOnCompleteListener != null) { new Thread(new Runnable() { @@ -121,4 +166,4 @@ public class GTaskASyncTask extends AsyncTask { }).start(); } } -} +} \ No newline at end of file -- 2.34.1 From f925ba9caca682c033d4b108d849790aee6ca640 Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Wed, 11 Jun 2025 22:03:55 +0800 Subject: [PATCH 12/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notes/gtask/remote/GTaskClient.java | 224 +++++++++++++----- 1 file changed, 169 insertions(+), 55 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java index c67dfdf..8fee8d8 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java @@ -61,35 +61,47 @@ import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; +/** + * Google Tasks(GTask)客户端类,用于与Google Tasks服务进行交互 + * 提供任务和任务列表的创建、更新、删除、查询等功能 + * 采用单例模式确保全局唯一实例 + */ 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/"; - + // 获取数据的URL private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; - + // 提交数据的URL private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; + // 单例实例 private static GTaskClient mInstance = null; + // HTTP客户端,用于发送请求和接收响应 private DefaultHttpClient mHttpClient; - + // 获取数据的实际URL(可能根据账户类型调整) private String mGetUrl; - + // 提交数据的实际URL(可能根据账户类型调整) private String mPostUrl; - + // 客户端版本号,用于与服务器交互 private long mClientVersion; - + // 登录状态标记 private boolean mLoggedin; - + // 上次登录时间,用于判断是否需要重新登录 private long mLastLoginTime; - + // 操作ID生成器,确保每个操作有唯一标识 private int mActionId; - + // 当前同步的Google账户 private Account mAccount; - + // 更新操作数组,用于批量提交更新 private JSONArray mUpdateArray; + /** + * 私有构造函数,初始化客户端状态 + * 单例模式下不允许外部直接创建实例 + */ private GTaskClient() { mHttpClient = null; mGetUrl = GTASK_GET_URL; @@ -102,6 +114,10 @@ public class GTaskClient { mUpdateArray = null; } + /** + * 获取GTaskClient单例实例 + * @return GTaskClient实例 + */ public static synchronized GTaskClient getInstance() { if (mInstance == null) { mInstance = new GTaskClient(); @@ -109,15 +125,20 @@ public class GTaskClient { return mInstance; } + /** + * 登录Google账户并认证GTask服务 + * 处理账户切换和登录过期情况 + * @param activity 用于获取账户信息的Activity + * @return 登录是否成功 + */ public boolean login(Activity activity) { - // we suppose that the cookie would expire after 5 minutes - // then we need to re-login + // 登录凭证有效期为5分钟,超过时间需要重新登录 final long interval = 1000 * 60 * 5; if (mLastLoginTime + interval < System.currentTimeMillis()) { mLoggedin = false; } - // need to re-login after account switch + // 账户切换后需要重新登录 if (mLoggedin && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity .getSyncAccountName(activity))) { @@ -136,7 +157,7 @@ public class GTaskClient { return false; } - // login with custom domain if necessary + // 处理自定义域名账户(非gmail.com/googlemail.com) if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() .endsWith("googlemail.com"))) { StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); @@ -151,7 +172,7 @@ public class GTaskClient { } } - // try to login with google official url + // 尝试使用Google官方URL登录 if (!mLoggedin) { mGetUrl = GTASK_GET_URL; mPostUrl = GTASK_POST_URL; @@ -164,6 +185,12 @@ 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); @@ -174,6 +201,7 @@ public class GTaskClient { return null; } + // 获取设置中选择的同步账户 String accountName = NotesPreferenceActivity.getSyncAccountName(activity); Account account = null; for (Account a : accounts) { @@ -189,13 +217,14 @@ public class GTaskClient { return null; } - // get the token now + // 获取Google账户认证令牌 AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); try { Bundle authTokenBundle = accountManagerFuture.getResult(); authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); if (invalidateToken) { + // 失效现有令牌并重新获取 accountManager.invalidateAuthToken("com.google", authToken); loginGoogleAccount(activity, false); } @@ -207,10 +236,16 @@ public class GTaskClient { return authToken; } + /** + * 尝试使用认证令牌登录GTask服务 + * 处理令牌失效情况 + * @param activity 用于重新获取令牌的Activity + * @param authToken 认证令牌 + * @return 登录是否成功 + */ private boolean tryToLoginGtask(Activity activity, String authToken) { if (!loginGtask(authToken)) { - // maybe the auth token is out of date, now let's invalidate the - // token and try again + // 令牌可能过期,失效后重新获取 authToken = loginGoogleAccount(activity, true); if (authToken == null) { Log.e(TAG, "login google account failed"); @@ -225,10 +260,16 @@ public class GTaskClient { return true; } + /** + * 使用认证令牌登录GTask服务 + * @param authToken Google账户认证令牌 + * @return 登录是否成功 + */ private boolean loginGtask(String authToken) { int timeoutConnection = 10000; int timeoutSocket = 15000; HttpParams httpParameters = new BasicHttpParams(); + // 设置连接超时和Socket超时时间 HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); mHttpClient = new DefaultHttpClient(httpParameters); @@ -236,14 +277,13 @@ public class GTaskClient { mHttpClient.setCookieStore(localBasicCookieStore); HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); - // login gtask try { + // 构建登录URL String loginUrl = mGetUrl + "?auth=" + authToken; HttpGet httpGet = new HttpGet(loginUrl); - HttpResponse response = null; - response = mHttpClient.execute(httpGet); + HttpResponse response = mHttpClient.execute(httpGet); - // get the cookie now + // 检查是否获取到认证Cookie List cookies = mHttpClient.getCookieStore().getCookies(); boolean hasAuthCookie = false; for (Cookie cookie : cookies) { @@ -255,7 +295,7 @@ public class GTaskClient { Log.w(TAG, "it seems that there is no auth cookie"); } - // get the client version + // 从响应中提取客户端版本号 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -272,7 +312,6 @@ public class GTaskClient { e.printStackTrace(); return false; } catch (Exception e) { - // simply catch all exceptions Log.e(TAG, "httpget gtask_url failed"); return false; } @@ -280,10 +319,19 @@ 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 +339,13 @@ public class GTaskClient { return httpPost; } + /** + * 从HTTP实体中获取响应内容 + * 处理Gzip和Deflate压缩格式 + * @param entity HTTP响应实体 + * @return 响应内容字符串 + * @throws IOException 读取内容时发生的IO异常 + */ private String getResponseContent(HttpEntity entity) throws IOException { String contentEncoding = null; if (entity.getContentEncoding() != null) { @@ -299,9 +354,12 @@ public class GTaskClient { } InputStream input = entity.getContent(); + // 处理Gzip压缩响应 if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { input = new GZIPInputStream(entity.getContent()); - } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { + } + // 处理Deflate压缩响应 + else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { Inflater inflater = new Inflater(true); input = new InflaterInputStream(entity.getContent(), inflater); } @@ -311,18 +369,22 @@ public class GTaskClient { BufferedReader br = new BufferedReader(isr); StringBuilder sb = new StringBuilder(); - while (true) { - String buff = br.readLine(); - if (buff == null) { - return sb.toString(); - } - sb = sb.append(buff); + String buff; + while ((buff = br.readLine()) != null) { + sb.append(buff); } + return sb.toString(); } finally { input.close(); } } + /** + * 发送POST请求并处理响应 + * @param js 请求的JSON数据 + * @return 响应的JSON对象 + * @throws NetworkFailureException 网络错误时抛出 + */ private JSONObject postRequest(JSONObject js) throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); @@ -331,12 +393,13 @@ public class GTaskClient { HttpPost httpPost = createHttpPost(); try { + // 构建请求参数 LinkedList list = new LinkedList(); list.add(new BasicNameValuePair("r", js.toString())); UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); httpPost.setEntity(entity); - // execute the post + // 执行POST请求 HttpResponse response = mHttpClient.execute(httpPost); String jsString = getResponseContent(response.getEntity()); return new JSONObject(jsString); @@ -360,23 +423,29 @@ public class GTaskClient { } } + /** + * 创建新任务并同步到服务器 + * @param task 要创建的任务对象 + * @throws NetworkFailureException 网络错误时抛出 + */ public void createTask(Task task) throws NetworkFailureException { commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list + // 添加创建任务的操作到操作列表 actionList.put(task.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 设置客户端版本号 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - // post + // 发送请求并处理响应 JSONObject jsResponse = postRequest(jsPost); JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( GTaskStringUtils.GTASK_JSON_RESULTS).get(0); + // 从响应中获取服务器生成的任务ID task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); } catch (JSONException e) { @@ -386,23 +455,29 @@ public class GTaskClient { } } + /** + * 创建新任务列表并同步到服务器 + * @param tasklist 要创建的任务列表对象 + * @throws NetworkFailureException 网络错误时抛出 + */ public void createTaskList(TaskList tasklist) throws NetworkFailureException { commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list + // 添加创建任务列表的操作到操作列表 actionList.put(tasklist.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client version + // 设置客户端版本号 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - // post + // 发送请求并处理响应 JSONObject jsResponse = postRequest(jsPost); JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( GTaskStringUtils.GTASK_JSON_RESULTS).get(0); + // 从响应中获取服务器生成的任务列表ID tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); } catch (JSONException e) { @@ -412,15 +487,19 @@ public class GTaskClient { } } + /** + * 提交批量更新操作 + * @throws NetworkFailureException 网络错误时抛出 + */ public void commitUpdate() throws NetworkFailureException { if (mUpdateArray != null) { try { JSONObject jsPost = new JSONObject(); - // action_list + // 添加更新操作列表 jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); - // client_version + // 设置客户端版本号 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); postRequest(jsPost); @@ -433,20 +512,32 @@ public class GTaskClient { } } + /** + * 添加更新节点到批量更新队列 + * @param node 要更新的节点对象 + * @throws NetworkFailureException 网络错误时抛出 + */ public void addUpdateNode(Node node) throws NetworkFailureException { if (node != null) { - // too many update items may result in an error - // set max to 10 items + // 限制批量更新的节点数量,超过10个则提交当前批次 if (mUpdateArray != null && mUpdateArray.length() > 10) { commitUpdate(); } if (mUpdateArray == null) mUpdateArray = new JSONArray(); + // 添加节点的更新操作到更新数组 mUpdateArray.put(node.getUpdateAction(getActionId())); } } + /** + * 移动任务到不同的任务列表或位置 + * @param task 要移动的任务 + * @param preParent 原父任务列表 + * @param curParent 新父任务列表 + * @throws NetworkFailureException 网络错误时抛出 + */ public void moveTask(Task task, TaskList preParent, TaskList curParent) throws NetworkFailureException { commitUpdate(); @@ -455,26 +546,25 @@ public class GTaskClient { JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); - // action_list + // 设置移动操作参数 action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); if (preParent == curParent && task.getPriorSibling() != null) { - // put prioring_sibing_id only if moving within the tasklist and - // it is not the first one + // 在同一任务列表内移动且不是第一个任务时,设置前置兄弟节点ID action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); } action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); if (preParent != curParent) { - // put the dest_list only if moving between tasklists + // 在不同任务列表间移动时,设置目标列表ID action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); } actionList.put(action); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 设置客户端版本号 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); postRequest(jsPost); @@ -486,18 +576,23 @@ public class GTaskClient { } } + /** + * 删除节点(任务或任务列表) + * @param node 要删除的节点对象 + * @throws NetworkFailureException 网络错误时抛出 + */ public void deleteNode(Node node) throws NetworkFailureException { commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list + // 标记节点为已删除并添加删除操作 node.setDeleted(true); actionList.put(node.getUpdateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 设置客户端版本号 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); postRequest(jsPost); @@ -509,6 +604,11 @@ public class GTaskClient { } } + /** + * 获取所有任务列表 + * @return 任务列表的JSON数组 + * @throws NetworkFailureException 网络错误时抛出 + */ public JSONArray getTaskLists() throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); @@ -516,11 +616,11 @@ public class GTaskClient { } try { + // 发送GET请求获取任务列表 HttpGet httpGet = new HttpGet(mGetUrl); - HttpResponse response = null; - response = mHttpClient.execute(httpGet); + HttpResponse response = mHttpClient.execute(httpGet); - // get the task list + // 解析响应获取任务列表数据 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -547,6 +647,12 @@ public class GTaskClient { } } + /** + * 获取指定任务列表中的所有任务 + * @param listGid 任务列表的Google ID + * @return 任务的JSON数组 + * @throws NetworkFailureException 网络错误时抛出 + */ public JSONArray getTaskList(String listGid) throws NetworkFailureException { commitUpdate(); try { @@ -554,7 +660,7 @@ public class GTaskClient { JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); - // action_list + // 设置获取所有任务的操作参数 action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); @@ -563,7 +669,7 @@ public class GTaskClient { actionList.put(action); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 设置客户端版本号 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); JSONObject jsResponse = postRequest(jsPost); @@ -575,11 +681,19 @@ public class GTaskClient { } } + /** + * 获取当前同步的Google账户 + * @return 同步账户对象 + */ public Account getSyncAccount() { return mAccount; } + /** + * 重置更新操作数组 + * 清空待提交的更新操作 + */ public void resetUpdateArray() { mUpdateArray = null; } -} +} \ No newline at end of file -- 2.34.1 From db50ad02d80adff19264d3bc1ba9efe33b9f20eb Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Wed, 11 Jun 2025 22:06:59 +0800 Subject: [PATCH 13/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notes/gtask/remote/GTaskManager.java | 275 ++++++++++++------ 1 file changed, 180 insertions(+), 95 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java index d2b4082..2c34f22 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java @@ -48,45 +48,43 @@ import java.util.Iterator; import java.util.Map; +/** + * Google Tasks同步管理类 + * 负责本地笔记数据与Google Tasks服务之间的同步逻辑 + * 管理任务、任务列表的双向同步,处理冲突和更新 + * 采用单例模式确保全局唯一实例 + */ 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; - - private static GTaskManager mInstance = null; - - private Activity mActivity; - - private Context mContext; - - private ContentResolver mContentResolver; - - private boolean mSyncing; - - private boolean mCancelled; - - private HashMap mGTaskListHashMap; - - private HashMap mGTaskHashMap; - - private HashMap mMetaHashMap; - - private TaskList mMetaList; - - private HashSet mLocalDeleteIdMap; - - private HashMap mGidToNid; - - private HashMap mNidToGid; - + // 同步状态常量 + 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; // 同步已取消 + + private static GTaskManager mInstance = null; // 单例实例 + + private Activity mActivity; // 用于获取认证的Activity + private Context mContext; // 应用上下文 + private ContentResolver mContentResolver; // 内容解析器 + private boolean mSyncing; // 同步状态标记 + private boolean mCancelled; // 取消同步标记 + + // 数据缓存映射表 + private HashMap mGTaskListHashMap; // Google任务列表映射 + private HashMap mGTaskHashMap; // Google任务映射 + private HashMap mMetaHashMap; // 元数据映射 + private TaskList mMetaList; // 元数据任务列表 + private HashSet mLocalDeleteIdMap; // 本地删除ID集合 + private HashMap mGidToNid; // Google ID到本地ID映射 + private HashMap mNidToGid; // 本地ID到Google ID映射 + + /** + * 私有构造函数,初始化管理器状态 + * 单例模式下不允许外部直接创建实例 + */ private GTaskManager() { mSyncing = false; mCancelled = false; @@ -99,6 +97,10 @@ public class GTaskManager { mNidToGid = new HashMap(); } + /** + * 获取GTaskManager单例实例 + * @return GTaskManager实例 + */ public static synchronized GTaskManager getInstance() { if (mInstance == null) { mInstance = new GTaskManager(); @@ -106,11 +108,20 @@ public class GTaskManager { return mInstance; } + /** + * 设置Activity上下文 + * @param activity 用于获取认证令牌的Activity + */ public synchronized void setActivityContext(Activity activity) { - // used for getting authtoken mActivity = activity; } + /** + * 执行Google Tasks数据同步 + * @param context 应用上下文 + * @param asyncTask 异步任务用于发布进度 + * @return 同步状态码 + */ public int sync(Context context, GTaskASyncTask asyncTask) { if (mSyncing) { Log.d(TAG, "Sync is in progress"); @@ -120,6 +131,8 @@ public class GTaskManager { mContentResolver = mContext.getContentResolver(); mSyncing = true; mCancelled = false; + + // 清空现有数据映射 mGTaskListHashMap.clear(); mGTaskHashMap.clear(); mMetaHashMap.clear(); @@ -131,18 +144,18 @@ public class GTaskManager { GTaskClient client = GTaskClient.getInstance(); client.resetUpdateArray(); - // login google task + // 登录Google Tasks if (!mCancelled) { if (!client.login(mActivity)) { throw new NetworkFailureException("login google task failed"); } } - // get the task list from google + // 从Google获取任务列表 asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); initGTaskList(); - // do content sync work + // 执行内容同步 asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); syncContent(); } catch (NetworkFailureException e) { @@ -156,6 +169,7 @@ public class GTaskManager { e.printStackTrace(); return STATE_INTERNAL_ERROR; } finally { + // 清理资源 mGTaskListHashMap.clear(); mGTaskHashMap.clear(); mMetaHashMap.clear(); @@ -168,6 +182,11 @@ public class GTaskManager { return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; } + /** + * 初始化Google任务列表 + * 从服务器获取任务列表和元数据 + * @throws NetworkFailureException 网络错误时抛出 + */ private void initGTaskList() throws NetworkFailureException { if (mCancelled) return; @@ -175,19 +194,19 @@ public class GTaskManager { try { JSONArray jsTaskLists = client.getTaskLists(); - // init meta list first + // 先初始化元数据列表 mMetaList = null; for (int i = 0; i < jsTaskLists.length(); i++) { JSONObject object = jsTaskLists.getJSONObject(i); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); - if (name - .equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + // 识别元数据文件夹 + if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { mMetaList = new TaskList(); mMetaList.setContentByRemoteJSON(object); - // load meta data + // 加载元数据 JSONArray jsMetas = client.getTaskList(gid); for (int j = 0; j < jsMetas.length(); j++) { object = (JSONObject) jsMetas.getJSONObject(j); @@ -203,29 +222,28 @@ public class GTaskManager { } } - // create meta list if not existed + // 若元数据列表不存在则创建 if (mMetaList == null) { mMetaList = new TaskList(); - mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_META); + mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META); GTaskClient.getInstance().createTaskList(mMetaList); } - // init task list + // 初始化任务列表 for (int i = 0; i < jsTaskLists.length(); i++) { JSONObject object = jsTaskLists.getJSONObject(i); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); + // 识别MIUI风格的任务文件夹 if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) - && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_META)) { + && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { TaskList tasklist = new TaskList(); tasklist.setContentByRemoteJSON(object); mGTaskListHashMap.put(gid, tasklist); mGTaskHashMap.put(gid, tasklist); - // load tasks + // 加载任务列表中的任务 JSONArray jsTasks = client.getTaskList(gid); for (int j = 0; j < jsTasks.length(); j++) { object = (JSONObject) jsTasks.getJSONObject(j); @@ -247,6 +265,11 @@ public class GTaskManager { } } + /** + * 执行内容同步逻辑 + * 处理本地和远程数据的增删改查 + * @throws NetworkFailureException 网络错误时抛出 + */ private void syncContent() throws NetworkFailureException { int syncType; Cursor c = null; @@ -259,7 +282,7 @@ public class GTaskManager { return; } - // for local deleted note + // 处理本地已删除的笔记 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type<>? AND parent_id=?)", new String[] { @@ -286,10 +309,10 @@ public class GTaskManager { } } - // sync folder first + // 先同步文件夹 syncFolder(); - // for note existing in database + // 处理数据库中存在的笔记 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type=? AND parent_id<>?)", new String[] { @@ -306,10 +329,10 @@ public class GTaskManager { syncType = node.getSyncAction(c); } else { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add + // 本地新增 syncType = Node.SYNC_ACTION_ADD_REMOTE; } else { - // remote delete + // 远程删除 syncType = Node.SYNC_ACTION_DEL_LOCAL; } } @@ -326,7 +349,7 @@ public class GTaskManager { } } - // go through remaining items + // 处理剩余的远程项目 Iterator> iter = mGTaskHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -334,16 +357,14 @@ public class GTaskManager { doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); } - // mCancelled can be set by another thread, so we neet to check one by - // one - // clear local delete table + // 清除本地删除记录 if (!mCancelled) { if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { throw new ActionFailureException("failed to batch-delete local deleted notes"); } } - // refresh local sync id + // 刷新本地同步ID if (!mCancelled) { GTaskClient.getInstance().commitUpdate(); refreshLocalSyncId(); @@ -351,6 +372,11 @@ public class GTaskManager { } + /** + * 同步文件夹数据 + * 处理本地和远程文件夹的同步 + * @throws NetworkFailureException 网络错误时抛出 + */ private void syncFolder() throws NetworkFailureException { Cursor c = null; String gid; @@ -361,7 +387,7 @@ public class GTaskManager { return; } - // for root folder + // 处理根文件夹 try { c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); @@ -373,7 +399,7 @@ public class GTaskManager { mGTaskHashMap.remove(gid); mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); - // for system folder, only update remote name if necessary + // 系统文件夹仅在必要时更新远程名称 if (!node.getName().equals( GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); @@ -390,7 +416,7 @@ public class GTaskManager { } } - // for call-note folder + // 处理通话记录文件夹 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", new String[] { @@ -404,11 +430,9 @@ public class GTaskManager { mGTaskHashMap.remove(gid); mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); - // for system folder, only update remote name if - // necessary + // 系统文件夹仅在必要时更新远程名称 if (!node.getName().equals( - GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_CALL_NOTE)) + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); } else { doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); @@ -424,7 +448,7 @@ public class GTaskManager { } } - // for local existing folders + // 处理本地现有文件夹 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type=? AND parent_id<>?)", new String[] { @@ -441,10 +465,10 @@ public class GTaskManager { syncType = node.getSyncAction(c); } else { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add + // 本地新增 syncType = Node.SYNC_ACTION_ADD_REMOTE; } else { - // remote delete + // 远程删除 syncType = Node.SYNC_ACTION_DEL_LOCAL; } } @@ -460,7 +484,7 @@ public class GTaskManager { } } - // for remote add folders + // 处理远程新增的文件夹 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -476,6 +500,14 @@ public class GTaskManager { GTaskClient.getInstance().commitUpdate(); } + /** + * 执行内容同步操作 + * 根据同步类型执行相应的同步逻辑 + * @param syncType 同步操作类型 + * @param node 要同步的节点 + * @param c 数据库游标 + * @throws NetworkFailureException 网络错误时抛出 + */ private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -484,12 +516,15 @@ public class GTaskManager { MetaData meta; switch (syncType) { case Node.SYNC_ACTION_ADD_LOCAL: + // 从远程添加到本地 addLocalNode(node); break; case Node.SYNC_ACTION_ADD_REMOTE: + // 从本地添加到远程 addRemoteNode(node, c); break; case Node.SYNC_ACTION_DEL_LOCAL: + // 删除本地记录 meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); if (meta != null) { GTaskClient.getInstance().deleteNode(meta); @@ -497,6 +532,7 @@ public class GTaskManager { mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); break; case Node.SYNC_ACTION_DEL_REMOTE: + // 删除远程记录 meta = mMetaHashMap.get(node.getGid()); if (meta != null) { GTaskClient.getInstance().deleteNode(meta); @@ -504,14 +540,15 @@ public class GTaskManager { GTaskClient.getInstance().deleteNode(node); break; case Node.SYNC_ACTION_UPDATE_LOCAL: + // 更新本地记录 updateLocalNode(node, c); break; case Node.SYNC_ACTION_UPDATE_REMOTE: + // 更新远程记录 updateRemoteNode(node, c); break; case Node.SYNC_ACTION_UPDATE_CONFLICT: - // merging both modifications maybe a good idea - // right now just use local update simply + // 处理冲突,简单使用本地更新 updateRemoteNode(node, c); break; case Node.SYNC_ACTION_NONE: @@ -522,6 +559,11 @@ public class GTaskManager { } } + /** + * 将远程节点添加到本地 + * @param node 要添加的远程节点 + * @throws NetworkFailureException 网络错误时抛出 + */ private void addLocalNode(Node node) throws NetworkFailureException { if (mCancelled) { return; @@ -529,6 +571,7 @@ public class GTaskManager { SqlNote sqlNote; if (node instanceof TaskList) { + // 处理特殊文件夹 if (node.getName().equals( GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); @@ -536,20 +579,22 @@ public class GTaskManager { GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); } else { + // 普通文件夹 sqlNote = new SqlNote(mContext); sqlNote.setContent(node.getLocalJSONFromContent()); sqlNote.setParentId(Notes.ID_ROOT_FOLDER); } } else { + // 普通任务 sqlNote = new SqlNote(mContext); JSONObject js = node.getLocalJSONFromContent(); try { + // 处理可能的ID冲突 if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); if (note.has(NoteColumns.ID)) { long id = note.getLong(NoteColumns.ID); if (DataUtils.existInNoteDatabase(mContentResolver, id)) { - // the id is not available, have to create a new one note.remove(NoteColumns.ID); } } @@ -562,13 +607,10 @@ public class GTaskManager { if (data.has(DataColumns.ID)) { long dataId = data.getLong(DataColumns.ID); if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { - // the data id is not available, have to create - // a new one data.remove(DataColumns.ID); } } } - } } catch (JSONException e) { Log.w(TAG, e.toString()); @@ -576,6 +618,7 @@ public class GTaskManager { } sqlNote.setContent(js); + // 设置父节点 Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); if (parentId == null) { Log.e(TAG, "cannot find task's parent id locally"); @@ -584,28 +627,35 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); } - // create the local node + // 创建本地节点 sqlNote.setGtaskId(node.getGid()); sqlNote.commit(false); - // update gid-nid mapping + // 更新ID映射 mGidToNid.put(node.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), node.getGid()); - // update meta + // 更新元数据 updateRemoteMeta(node.getGid(), sqlNote); } + /** + * 更新本地节点 + * @param node 要更新的节点 + * @param c 数据库游标 + * @throws NetworkFailureException 网络错误时抛出 + */ private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; } SqlNote sqlNote; - // update the note locally + // 更新本地笔记 sqlNote = new SqlNote(mContext, c); sqlNote.setContent(node.getLocalJSONFromContent()); + // 设置父节点 Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) : new Long(Notes.ID_ROOT_FOLDER); if (parentId == null) { @@ -615,10 +665,16 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); sqlNote.commit(true); - // update meta info + // 更新元数据 updateRemoteMeta(node.getGid(), sqlNote); } + /** + * 将本地节点添加到远程 + * @param node 要添加的本地节点 + * @param c 数据库游标 + * @throws NetworkFailureException 网络错误时抛出 + */ private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -627,11 +683,12 @@ public class GTaskManager { SqlNote sqlNote = new SqlNote(mContext, c); Node n; - // update remotely + // 更新到远程 if (sqlNote.isNoteType()) { Task task = new Task(); task.setContentByLocalJSON(sqlNote.getContent()); + // 设置父任务列表 String parentGid = mNidToGid.get(sqlNote.getParentId()); if (parentGid == null) { Log.e(TAG, "cannot find task's parent tasklist"); @@ -642,12 +699,12 @@ public class GTaskManager { GTaskClient.getInstance().createTask(task); n = (Node) task; - // add meta + // 添加元数据 updateRemoteMeta(task.getGid(), sqlNote); } else { TaskList tasklist = null; - // we need to skip folder if it has already existed + // 处理文件夹名称 String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) folderName += GTaskStringUtils.FOLDER_DEFAULT; @@ -656,6 +713,7 @@ public class GTaskManager { else folderName += sqlNote.getSnippet(); + // 检查文件夹是否已存在 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -671,7 +729,7 @@ public class GTaskManager { } } - // no match we can add now + // 不存在则创建新文件夹 if (tasklist == null) { tasklist = new TaskList(); tasklist.setContentByLocalJSON(sqlNote.getContent()); @@ -681,17 +739,23 @@ public class GTaskManager { n = (Node) tasklist; } - // update local note + // 更新本地笔记 sqlNote.setGtaskId(n.getGid()); sqlNote.commit(false); sqlNote.resetLocalModified(); sqlNote.commit(true); - // gid-id mapping + // 更新ID映射 mGidToNid.put(n.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), n.getGid()); } + /** + * 更新远程节点 + * @param node 要更新的节点 + * @param c 数据库游标 + * @throws NetworkFailureException 网络错误时抛出 + */ private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -699,14 +763,14 @@ public class GTaskManager { SqlNote sqlNote = new SqlNote(mContext, c); - // update remotely + // 更新到远程 node.setContentByLocalJSON(sqlNote.getContent()); GTaskClient.getInstance().addUpdateNode(node); - // update meta + // 更新元数据 updateRemoteMeta(node.getGid(), sqlNote); - // move task if necessary + // 必要时移动任务 if (sqlNote.isNoteType()) { Task task = (Task) node; TaskList preParentList = task.getParent(); @@ -718,6 +782,7 @@ public class GTaskManager { } TaskList curParentList = mGTaskListHashMap.get(curParentGid); + // 父任务列表变更时执行移动操作 if (preParentList != curParentList) { preParentList.removeChildTask(task); curParentList.addChildTask(task); @@ -725,18 +790,26 @@ public class GTaskManager { } } - // clear local modified flag + // 清除本地修改标记 sqlNote.resetLocalModified(); sqlNote.commit(true); } + /** + * 更新远程元数据 + * @param gid 节点的Google ID + * @param sqlNote 本地SQL笔记对象 + * @throws NetworkFailureException 网络错误时抛出 + */ private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { if (sqlNote != null && sqlNote.isNoteType()) { MetaData metaData = mMetaHashMap.get(gid); if (metaData != null) { + // 更新现有元数据 metaData.setMeta(gid, sqlNote.getContent()); GTaskClient.getInstance().addUpdateNode(metaData); } else { + // 创建新元数据 metaData = new MetaData(); metaData.setMeta(gid, sqlNote.getContent()); mMetaList.addChildTask(metaData); @@ -746,12 +819,17 @@ public class GTaskManager { } } + /** + * 刷新本地同步ID + * 更新本地记录的同步时间戳 + * @throws NetworkFailureException 网络错误时抛出 + */ private void refreshLocalSyncId() throws NetworkFailureException { if (mCancelled) { return; } - // get the latest gtask list + // 获取最新的任务列表 mGTaskHashMap.clear(); mGTaskListHashMap.clear(); mMetaHashMap.clear(); @@ -790,11 +868,18 @@ public class GTaskManager { } } + /** + * 获取当前同步的账户名称 + * @return 同步账户名称 + */ public String getSyncAccount() { return GTaskClient.getInstance().getSyncAccount().name; } + /** + * 取消同步操作 + */ public void cancelSync() { mCancelled = true; } -} +} \ No newline at end of file -- 2.34.1 From 29a18c569fb1378465f7b8024956de7ff596b9e6 Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Wed, 11 Jun 2025 22:54:38 +0800 Subject: [PATCH 14/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notes/gtask/remote/GTaskSyncService.java | 74 ++++++++++++++++--- 1 file changed, 62 insertions(+), 12 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java index cca36f7..3be7384 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java @@ -23,25 +23,30 @@ import android.content.Intent; import android.os.Bundle; import android.os.IBinder; +/** + * Google Tasks同步服务 + * 负责在后台执行笔记与Google Tasks的同步操作 + * 通过广播机制通知UI同步状态和进度 + * 支持启动同步、取消同步等操作 + */ public class GTaskSyncService extends Service { + // 同步操作类型常量 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; // 无效操作 - public final static int ACTION_START_SYNC = 0; - - public final static int ACTION_CANCEL_SYNC = 1; - - public final static int ACTION_INVALID = 2; - + // 广播相关常量 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 static GTaskASyncTask mSyncTask = null; // 同步任务 + private static String mSyncProgress = ""; // 同步进度消息 + /** + * 启动同步任务 + */ private void startSync() { if (mSyncTask == null) { mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { @@ -56,17 +61,32 @@ public class GTaskSyncService extends Service { } } + /** + * 取消同步任务 + */ private void cancelSync() { if (mSyncTask != null) { mSyncTask.cancelSync(); } } + /** + * 服务创建时调用 + * 初始化服务状态 + */ @Override public void onCreate() { mSyncTask = null; } + /** + * 处理启动服务的请求 + * 根据传入的action执行相应的同步操作 + * @param intent 启动服务的Intent + * @param flags 启动标志 + * @param startId 启动ID + * @return 服务启动模式 + */ @Override public int onStartCommand(Intent intent, int flags, int startId) { Bundle bundle = intent.getExtras(); @@ -86,6 +106,10 @@ public class GTaskSyncService extends Service { return super.onStartCommand(intent, flags, startId); } + /** + * 系统内存不足时调用 + * 取消正在进行的同步任务以释放资源 + */ @Override public void onLowMemory() { if (mSyncTask != null) { @@ -93,10 +117,20 @@ public class GTaskSyncService extends Service { } } + /** + * 返回服务的Binder对象 + * 本服务不支持绑定,返回null + * @param intent 绑定Intent + * @return null + */ 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 +139,10 @@ public class GTaskSyncService extends Service { sendBroadcast(intent); } + /** + * 静态方法:启动同步服务 + * @param activity 调用的Activity + */ public static void startSync(Activity activity) { GTaskManager.getInstance().setActivityContext(activity); Intent intent = new Intent(activity, GTaskSyncService.class); @@ -112,17 +150,29 @@ public class GTaskSyncService extends Service { activity.startService(intent); } + /** + * 静态方法:取消同步服务 + * @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 同步状态 + */ public static boolean isSyncing() { return mSyncTask != null; } + /** + * 静态方法:获取当前同步进度消息 + * @return 同步进度字符串 + */ public static String getProgressString() { return mSyncProgress; } -} +} \ No newline at end of file -- 2.34.1 From 8be35bf89cb05ba3e89bca324bcad51d416ebdb3 Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Wed, 11 Jun 2025 23:00:01 +0800 Subject: [PATCH 15/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/net/micode/notes/model/Note.java | 155 ++++++++++++++++-- 1 file changed, 143 insertions(+), 12 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/model/Note.java b/src/Notes-master/app/src/main/java/net/micode/notes/model/Note.java index 6706cf6..c9fe010 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/model/Note.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/model/Note.java @@ -15,6 +15,7 @@ */ package net.micode.notes.model; + import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentUris; @@ -33,16 +34,28 @@ import net.micode.notes.data.Notes.TextNote; import java.util.ArrayList; - +/** + * Note 类用于管理笔记数据,提供创建、更新、同步笔记的功能。 + * 该类封装了与笔记相关的数据操作,包括基本笔记信息和附加数据(如文本内容、通话记录等)。 + */ public class Note { + // 存储笔记基本信息的变更值 private ContentValues mNoteDiffValues; + // 管理笔记附加数据的内部类实例 private NoteData mNoteData; + // 日志标签 private static final String TAG = "Note"; + /** - * Create a new note id for adding a new note to databases + * 静态方法,创建新笔记并返回其ID。 + * + * @param context 应用上下文 + * @param folderId 笔记所属文件夹ID + * @return 新创建笔记的ID + * @throws IllegalStateException 当获取到无效的笔记ID时抛出 */ public static synchronized long getNewNoteId(Context context, long folderId) { - // Create a new note in the database + // 创建新笔记记录,设置创建时间、修改时间、类型、修改状态和父文件夹ID ContentValues values = new ContentValues(); long createdTime = System.currentTimeMillis(); values.put(NoteColumns.CREATED_DATE, createdTime); @@ -50,8 +63,11 @@ public class Note { values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); values.put(NoteColumns.LOCAL_MODIFIED, 1); values.put(NoteColumns.PARENT_ID, folderId); + + // 插入新笔记记录并获取返回的URI Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); + // 从URI中提取笔记ID long noteId = 0; try { noteId = Long.valueOf(uri.getPathSegments().get(1)); @@ -59,69 +75,124 @@ public class Note { Log.e(TAG, "Get note id error :" + e.toString()); noteId = 0; } + + // 验证笔记ID有效性 if (noteId == -1) { throw new IllegalStateException("Wrong note id:" + noteId); } return noteId; } + /** + * 构造函数,初始化笔记数据结构 + */ public Note() { mNoteDiffValues = new ContentValues(); mNoteData = new NoteData(); } + /** + * 设置笔记的基本属性值 + * + * @param key 属性键名 + * @param value 属性值 + */ public void setNoteValue(String key, String value) { mNoteDiffValues.put(key, value); + // 标记笔记已被修改,并更新修改时间 mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + /** + * 设置笔记的文本内容数据 + * + * @param key 文本数据键名 + * @param value 文本数据值 + */ public void setTextData(String key, String value) { mNoteData.setTextData(key, value); } + /** + * 设置文本数据ID + * + * @param id 文本数据ID + */ public void setTextDataId(long id) { mNoteData.setTextDataId(id); } + /** + * 获取文本数据ID + * + * @return 文本数据ID + */ public long getTextDataId() { return mNoteData.mTextDataId; } + /** + * 设置通话记录数据ID + * + * @param id 通话记录数据ID + */ public void setCallDataId(long id) { mNoteData.setCallDataId(id); } + /** + * 设置通话记录数据 + * + * @param key 通话数据键名 + * @param value 通话数据值 + */ public void setCallData(String key, String value) { mNoteData.setCallData(key, value); } + /** + * 检查笔记是否有本地修改 + * + * @return 如果有修改返回true,否则返回false + */ public boolean isLocalModified() { return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); } + /** + * 将笔记的本地修改同步到内容提供者 + * + * @param context 应用上下文 + * @param noteId 笔记ID + * @return 同步成功返回true,失败返回false + * @throws IllegalArgumentException 当笔记ID无效时抛出 + */ public boolean syncNote(Context context, long noteId) { + // 验证笔记ID有效性 if (noteId <= 0) { throw new IllegalArgumentException("Wrong note id:" + noteId); } + // 如果没有修改,直接返回成功 if (!isLocalModified()) { return true; } /** - * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and - * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the - * note data info + * 理论上,一旦数据发生变化,笔记应该在{@link NoteColumns#LOCAL_MODIFIED}和 + * {@link NoteColumns#MODIFIED_DATE}上更新。为了数据安全,即使更新笔记失败, + * 我们也会更新笔记数据信息 */ if (context.getContentResolver().update( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, null) == 0) { Log.e(TAG, "Update note error, should not happen"); - // Do not return, fall through + // 不返回,继续执行 } mNoteDiffValues.clear(); + // 同步笔记的附加数据,如果失败则返回false if (mNoteData.isLocalModified() && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { return false; @@ -130,17 +201,24 @@ public class Note { return true; } + /** + * 内部类,用于管理笔记的附加数据,包括文本内容和通话记录 + */ private class NoteData { + // 文本数据ID private long mTextDataId; - + // 存储文本数据的变更值 private ContentValues mTextDataValues; - + // 通话记录数据ID private long mCallDataId; - + // 存储通话记录数据的变更值 private ContentValues mCallDataValues; - + // 日志标签 private static final String TAG = "NoteData"; + /** + * 构造函数,初始化数据结构 + */ public NoteData() { mTextDataValues = new ContentValues(); mCallDataValues = new ContentValues(); @@ -148,10 +226,21 @@ public class Note { mCallDataId = 0; } + /** + * 检查附加数据是否有本地修改 + * + * @return 如果有修改返回true,否则返回false + */ boolean isLocalModified() { return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; } + /** + * 设置文本数据ID + * + * @param id 文本数据ID + * @throws IllegalArgumentException 当ID无效时抛出 + */ void setTextDataId(long id) { if(id <= 0) { throw new IllegalArgumentException("Text data id should larger than 0"); @@ -159,6 +248,12 @@ public class Note { mTextDataId = id; } + /** + * 设置通话记录数据ID + * + * @param id 通话记录数据ID + * @throws IllegalArgumentException 当ID无效时抛出 + */ void setCallDataId(long id) { if (id <= 0) { throw new IllegalArgumentException("Call data id should larger than 0"); @@ -166,36 +261,64 @@ public class Note { mCallDataId = id; } + /** + * 设置通话记录数据 + * + * @param key 通话数据键名 + * @param value 通话数据值 + */ void setCallData(String key, String value) { mCallDataValues.put(key, value); + // 标记笔记已被修改,并更新修改时间 mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + /** + * 设置文本内容数据 + * + * @param key 文本数据键名 + * @param value 文本数据值 + */ void setTextData(String key, String value) { mTextDataValues.put(key, value); + // 标记笔记已被修改,并更新修改时间 mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + /** + * 将附加数据推送到内容提供者 + * + * @param context 应用上下文 + * @param noteId 笔记ID + * @return 操作成功返回笔记URI,失败返回null + * @throws IllegalArgumentException 当笔记ID无效时抛出 + */ Uri pushIntoContentResolver(Context context, long noteId) { /** - * Check for safety + * 安全检查 */ if (noteId <= 0) { throw new IllegalArgumentException("Wrong note id:" + noteId); } + // 创建操作列表,用于批量执行内容提供者操作 ArrayList operationList = new ArrayList(); ContentProviderOperation.Builder builder = null; + // 处理文本数据 if(mTextDataValues.size() > 0) { + // 设置文本数据关联的笔记ID mTextDataValues.put(DataColumns.NOTE_ID, noteId); + + // 如果是新文本数据,执行插入操作 if (mTextDataId == 0) { mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mTextDataValues); try { + // 保存插入后的文本数据ID setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); } catch (NumberFormatException e) { Log.e(TAG, "Insert new text data fail with noteId" + noteId); @@ -203,6 +326,7 @@ public class Note { return null; } } else { + // 如果是已存在的文本数据,执行更新操作 builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( Notes.CONTENT_DATA_URI, mTextDataId)); builder.withValues(mTextDataValues); @@ -211,13 +335,18 @@ public class Note { mTextDataValues.clear(); } + // 处理通话记录数据 if(mCallDataValues.size() > 0) { + // 设置通话记录数据关联的笔记ID mCallDataValues.put(DataColumns.NOTE_ID, noteId); + + // 如果是新通话记录数据,执行插入操作 if (mCallDataId == 0) { mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mCallDataValues); try { + // 保存插入后的通话记录数据ID setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); } catch (NumberFormatException e) { Log.e(TAG, "Insert new call data fail with noteId" + noteId); @@ -225,6 +354,7 @@ public class Note { return null; } } else { + // 如果是已存在的通话记录数据,执行更新操作 builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( Notes.CONTENT_DATA_URI, mCallDataId)); builder.withValues(mCallDataValues); @@ -233,6 +363,7 @@ public class Note { mCallDataValues.clear(); } + // 批量执行内容提供者操作 if (operationList.size() > 0) { try { ContentProviderResult[] results = context.getContentResolver().applyBatch( -- 2.34.1 From 823c187edb2943e5e8619b0c90f51af7b0736482 Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Wed, 11 Jun 2025 23:02:28 +0800 Subject: [PATCH 16/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/micode/notes/model/WorkingNote.java | 245 +++++++++++++++--- 1 file changed, 213 insertions(+), 32 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/model/WorkingNote.java b/src/Notes-master/app/src/main/java/net/micode/notes/model/WorkingNote.java index be081e4..28ab74c 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/model/WorkingNote.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/model/WorkingNote.java @@ -31,37 +31,44 @@ import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.TextNote; import net.micode.notes.tool.ResourceParser.NoteBgResources; - +/** + * WorkingNote 类用于管理和操作笔记数据,提供创建、加载、保存和更新笔记的功能。 + * 该类封装了笔记的各种属性和设置,包括内容、背景颜色、提醒日期、便签小部件等。 + * 它内部使用 Note 类来处理实际的数据存储操作,并提供了更高级的业务逻辑处理。 + */ public class WorkingNote { - // Note for the working note + // 内部使用的Note对象,用于处理实际的数据存储 private Note mNote; - // Note Id + // 笔记ID private long mNoteId; - // Note content + // 笔记内容 private String mContent; - // Note mode + // 笔记模式(普通文本或检查列表) private int mMode; - + // 提醒日期(毫秒时间戳) private long mAlertDate; - + // 修改日期 private long mModifiedDate; - + // 背景颜色ID private int mBgColorId; - + // 便签小部件ID private int mWidgetId; - + // 便签小部件类型 private int mWidgetType; - + // 所属文件夹ID private long mFolderId; - + // 应用上下文 private Context mContext; - + // 日志标签 private static final String TAG = "WorkingNote"; - + // 标记笔记是否已删除 private boolean mIsDeleted; - + // 笔记设置变更监听器 private NoteSettingChangedListener mNoteSettingStatusListener; + /** + * 数据查询投影,定义从数据表中查询的列 + */ public static final String[] DATA_PROJECTION = new String[] { DataColumns.ID, DataColumns.CONTENT, @@ -72,6 +79,9 @@ public class WorkingNote { DataColumns.DATA4, }; + /** + * 笔记查询投影,定义从笔记表中查询的列 + */ public static final String[] NOTE_PROJECTION = new String[] { NoteColumns.PARENT_ID, NoteColumns.ALERTED_DATE, @@ -81,27 +91,26 @@ public class WorkingNote { NoteColumns.MODIFIED_DATE }; + // 数据查询结果集中各列的索引 private static final int DATA_ID_COLUMN = 0; - private static final int DATA_CONTENT_COLUMN = 1; - private static final int DATA_MIME_TYPE_COLUMN = 2; - private static final int DATA_MODE_COLUMN = 3; + // 笔记查询结果集中各列的索引 private static final int NOTE_PARENT_ID_COLUMN = 0; - private static final int NOTE_ALERTED_DATE_COLUMN = 1; - private static final int NOTE_BG_COLOR_ID_COLUMN = 2; - private static final int NOTE_WIDGET_ID_COLUMN = 3; - private static final int NOTE_WIDGET_TYPE_COLUMN = 4; - private static final int NOTE_MODIFIED_DATE_COLUMN = 5; - // New note construct + /** + * 私有构造函数,用于创建新笔记 + * + * @param context 应用上下文 + * @param folderId 笔记所属文件夹ID + */ private WorkingNote(Context context, long folderId) { mContext = context; mAlertDate = 0; @@ -114,7 +123,13 @@ public class WorkingNote { mWidgetType = Notes.TYPE_WIDGET_INVALIDE; } - // Existing note construct + /** + * 私有构造函数,用于加载现有笔记 + * + * @param context 应用上下文 + * @param noteId 笔记ID + * @param folderId 笔记所属文件夹ID + */ private WorkingNote(Context context, long noteId, long folderId) { mContext = context; mNoteId = noteId; @@ -124,13 +139,20 @@ public class WorkingNote { loadNote(); } + /** + * 从数据库加载笔记的基本信息 + * + * @throws IllegalArgumentException 当找不到指定ID的笔记时抛出 + */ private void loadNote() { + // 查询笔记基本信息 Cursor cursor = mContext.getContentResolver().query( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { + // 从查询结果中提取笔记属性 mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); @@ -143,10 +165,17 @@ public class WorkingNote { Log.e(TAG, "No note with id:" + mNoteId); throw new IllegalArgumentException("Unable to find note with id " + mNoteId); } + // 加载笔记的详细数据 loadNoteData(); } + /** + * 从数据库加载笔记的详细数据(内容、类型等) + * + * @throws IllegalArgumentException 当找不到指定ID的笔记数据时抛出 + */ private void loadNoteData() { + // 查询笔记的详细数据 Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { String.valueOf(mNoteId) @@ -154,13 +183,16 @@ public class WorkingNote { if (cursor != null) { if (cursor.moveToFirst()) { + // 遍历查询结果,处理不同类型的数据 do { String type = cursor.getString(DATA_MIME_TYPE_COLUMN); if (DataConstants.NOTE.equals(type)) { + // 处理文本笔记数据 mContent = cursor.getString(DATA_CONTENT_COLUMN); mMode = cursor.getInt(DATA_MODE_COLUMN); mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); } else if (DataConstants.CALL_NOTE.equals(type)) { + // 处理通话记录笔记数据 mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); } else { Log.d(TAG, "Wrong note type with type:" + type); @@ -174,6 +206,16 @@ public class WorkingNote { } } + /** + * 创建一个空笔记 + * + * @param context 应用上下文 + * @param folderId 笔记所属文件夹ID + * @param widgetId 便签小部件ID + * @param widgetType 便签小部件类型 + * @param defaultBgColorId 默认背景颜色ID + * @return 新创建的WorkingNote对象 + */ public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, int widgetType, int defaultBgColorId) { WorkingNote note = new WorkingNote(context, folderId); @@ -183,12 +225,25 @@ public class WorkingNote { return note; } + /** + * 从数据库加载指定ID的笔记 + * + * @param context 应用上下文 + * @param id 笔记ID + * @return 加载的WorkingNote对象 + */ public static WorkingNote load(Context context, long id) { return new WorkingNote(context, id, 0); } + /** + * 保存笔记到数据库 + * + * @return 保存成功返回true,失败返回false + */ public synchronized boolean saveNote() { if (isWorthSaving()) { + // 如果笔记在数据库中不存在,创建新笔记 if (!existInDatabase()) { if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { Log.e(TAG, "Create new note fail with id:" + mNoteId); @@ -196,10 +251,11 @@ public class WorkingNote { } } + // 同步笔记数据到数据库 mNote.syncNote(mContext, mNoteId); /** - * Update widget content if there exist any widget of this note + * 如果存在该笔记的便签小部件,更新小部件内容 */ if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID && mWidgetType != Notes.TYPE_WIDGET_INVALIDE @@ -212,11 +268,22 @@ public class WorkingNote { } } + /** + * 检查笔记是否已存在于数据库中 + * + * @return 如果笔记已存在返回true,否则返回false + */ public boolean existInDatabase() { return mNoteId > 0; } + /** + * 判断笔记是否值得保存 + * + * @return 如果值得保存返回true,否则返回false + */ private boolean isWorthSaving() { + // 如果笔记已删除、为空且未在数据库中存在、或已存在但无修改,则不值得保存 if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) || (existInDatabase() && !mNote.isLocalModified())) { return false; @@ -225,10 +292,21 @@ public class WorkingNote { } } + /** + * 设置笔记设置变更监听器 + * + * @param l 监听器对象 + */ public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { mNoteSettingStatusListener = l; } + /** + * 设置笔记的提醒日期 + * + * @param date 提醒日期(毫秒时间戳) + * @param set 是否设置提醒 + */ public void setAlertDate(long date, boolean set) { if (date != mAlertDate) { mAlertDate = date; @@ -239,6 +317,11 @@ public class WorkingNote { } } + /** + * 标记笔记为已删除或未删除 + * + * @param mark true表示已删除,false表示未删除 + */ public void markDeleted(boolean mark) { mIsDeleted = mark; if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID @@ -247,6 +330,11 @@ public class WorkingNote { } } + /** + * 设置笔记的背景颜色 + * + * @param id 背景颜色ID + */ public void setBgColorId(int id) { if (id != mBgColorId) { mBgColorId = id; @@ -257,6 +345,11 @@ public class WorkingNote { } } + /** + * 设置笔记的检查列表模式 + * + * @param mode 模式值 + */ public void setCheckListMode(int mode) { if (mMode != mode) { if (mNoteSettingStatusListener != null) { @@ -267,6 +360,11 @@ public class WorkingNote { } } + /** + * 设置笔记的便签小部件类型 + * + * @param type 小部件类型 + */ public void setWidgetType(int type) { if (type != mWidgetType) { mWidgetType = type; @@ -274,6 +372,11 @@ public class WorkingNote { } } + /** + * 设置笔记的便签小部件ID + * + * @param id 小部件ID + */ public void setWidgetId(int id) { if (id != mWidgetId) { mWidgetId = id; @@ -281,6 +384,11 @@ public class WorkingNote { } } + /** + * 设置笔记的工作文本内容 + * + * @param text 文本内容 + */ public void setWorkingText(String text) { if (!TextUtils.equals(mContent, text)) { mContent = text; @@ -288,80 +396,153 @@ public class WorkingNote { } } + /** + * 将笔记转换为通话记录笔记 + * + * @param phoneNumber 电话号码 + * @param callDate 通话日期 + */ public void convertToCallNote(String phoneNumber, long callDate) { mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); } + /** + * 检查笔记是否设置了时钟提醒 + * + * @return 如果设置了提醒返回true,否则返回false + */ public boolean hasClockAlert() { return (mAlertDate > 0 ? true : false); } + /** + * 获取笔记内容 + * + * @return 笔记内容字符串 + */ public String getContent() { return mContent; } + /** + * 获取笔记的提醒日期 + * + * @return 提醒日期(毫秒时间戳) + */ public long getAlertDate() { return mAlertDate; } + /** + * 获取笔记的修改日期 + * + * @return 修改日期(毫秒时间戳) + */ public long getModifiedDate() { return mModifiedDate; } + /** + * 获取笔记背景颜色的资源ID + * + * @return 背景颜色资源ID + */ public int getBgColorResId() { return NoteBgResources.getNoteBgResource(mBgColorId); } + /** + * 获取笔记背景颜色ID + * + * @return 背景颜色ID + */ public int getBgColorId() { return mBgColorId; } + /** + * 获取笔记标题背景的资源ID + * + * @return 标题背景资源ID + */ public int getTitleBgResId() { return NoteBgResources.getNoteTitleBgResource(mBgColorId); } + /** + * 获取笔记的检查列表模式 + * + * @return 检查列表模式值 + */ public int getCheckListMode() { return mMode; } + /** + * 获取笔记ID + * + * @return 笔记ID + */ public long getNoteId() { return mNoteId; } + /** + * 获取笔记所属文件夹ID + * + * @return 文件夹ID + */ public long getFolderId() { return mFolderId; } + /** + * 获取笔记的便签小部件ID + * + * @return 小部件ID + */ public int getWidgetId() { return mWidgetId; } + /** + * 获取笔记的便签小部件类型 + * + * @return 小部件类型 + */ public int getWidgetType() { return mWidgetType; } + /** + * 笔记设置变更监听器接口,用于通知笔记设置的变化 + */ public interface NoteSettingChangedListener { /** - * Called when the background color of current note has just changed + * 当笔记的背景颜色发生变化时调用 */ void onBackgroundColorChanged(); /** - * Called when user set clock + * 当用户设置或取消时钟提醒时调用 + * + * @param date 提醒日期 + * @param set 是否设置提醒 */ void onClockAlertChanged(long date, boolean set); /** - * Call when user create note from widget + * 当用户从便签小部件创建笔记时调用 */ void onWidgetChanged(); /** - * Call when switch between check list mode and normal mode - * @param oldMode is previous mode before change - * @param newMode is new mode + * 当在检查列表模式和普通模式之间切换时调用 + * + * @param oldMode 切换前的模式 + * @param newMode 切换后的模式 */ void onCheckListModeChanged(int oldMode, int newMode); } -- 2.34.1 From 9d75a9141831824fe228b3ce2beee5cc9523f9c5 Mon Sep 17 00:00:00 2001 From: luchan <2419525909@qq.com> Date: Wed, 11 Jun 2025 23:05:21 +0800 Subject: [PATCH 17/17] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/micode/notes/tool/BackupUtils.java | 141 ++++++++++++++---- 1 file changed, 109 insertions(+), 32 deletions(-) diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/tool/BackupUtils.java b/src/Notes-master/app/src/main/java/net/micode/notes/tool/BackupUtils.java index 39f6ec4..25169cd 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/tool/BackupUtils.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/tool/BackupUtils.java @@ -35,12 +35,23 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; - +/** + * BackupUtils 类提供笔记备份功能,支持将笔记导出为文本文件。 + * 该类使用单例模式,通过 TextExport 内部类实现具体的导出逻辑。 + * 导出过程会遍历所有文件夹和笔记,将它们按照特定格式保存到外部存储中。 + */ public class BackupUtils { + // 日志标签 private static final String TAG = "BackupUtils"; - // Singleton stuff + // 单例实例 private static BackupUtils sInstance; + /** + * 获取 BackupUtils 的单例实例 + * + * @param context 应用上下文 + * @return BackupUtils 实例 + */ public static synchronized BackupUtils getInstance(Context context) { if (sInstance == null) { sInstance = new BackupUtils(context); @@ -49,43 +60,72 @@ public class BackupUtils { } /** - * Following states are signs to represents backup or restore - * status + * 备份或恢复操作的状态码 */ - // Currently, the sdcard is not mounted + // 当前SD卡未挂载 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; + /** + * 私有构造函数,初始化文本导出工具 + * + * @param context 应用上下文 + */ private BackupUtils(Context context) { mTextExport = new TextExport(context); } + /** + * 检查外部存储是否可用 + * + * @return 如果外部存储已挂载返回true,否则返回false + */ private static boolean externalStorageAvailable() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } + /** + * 将笔记导出为文本文件 + * + * @return 导出状态码 + */ 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, NoteColumns.MODIFIED_DATE, @@ -93,12 +133,12 @@ public class BackupUtils { NoteColumns.TYPE }; + // 笔记查询结果集中各列的索引 private static final int NOTE_COLUMN_ID = 0; - private static final int NOTE_COLUMN_MODIFIED_DATE = 1; - private static final int NOTE_COLUMN_SNIPPET = 2; + // 数据查询投影,定义从数据表中查询的列 private static final String[] DATA_PROJECTION = { DataColumns.CONTENT, DataColumns.MIME_TYPE, @@ -108,39 +148,56 @@ public class BackupUtils { DataColumns.DATA4, }; + // 数据查询结果集中各列的索引 private static final int DATA_COLUMN_CONTENT = 0; - private static final int DATA_COLUMN_MIME_TYPE = 1; - private static final int DATA_COLUMN_CALL_DATE = 2; - 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; + /** + * 构造函数,初始化文本导出工具 + * + * @param context 应用上下文 + */ public TextExport(Context context) { + // 从资源文件中获取文本格式 TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); mContext = context; mFileName = ""; mFileDirectory = ""; } + /** + * 获取指定ID的文本格式 + * + * @param id 格式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 + // 查询属于该文件夹的所有笔记 Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { folderId @@ -149,11 +206,11 @@ public class BackupUtils { if (notesCursor != null) { if (notesCursor.moveToFirst()) { do { - // Print note's last modified date + // 打印笔记的最后修改日期 ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( mContext.getString(R.string.format_datetime_mdhm), notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // Query data belong to this note + // 查询属于该笔记的详细数据并导出 String noteId = notesCursor.getString(NOTE_COLUMN_ID); exportNoteToText(noteId, ps); } while (notesCursor.moveToNext()); @@ -163,9 +220,13 @@ public class BackupUtils { } /** - * Export note identified by id to a print stream + * 将指定ID的笔记导出为文本 + * + * @param noteId 笔记ID + * @param ps 输出流 */ private void exportNoteToText(String noteId, PrintStream ps) { + // 查询笔记的详细数据 Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { noteId @@ -176,7 +237,7 @@ public class BackupUtils { do { String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); if (DataConstants.CALL_NOTE.equals(mimeType)) { - // Print phone number + // 处理通话记录类型的笔记 String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); String location = dataCursor.getString(DATA_COLUMN_CONTENT); @@ -185,16 +246,17 @@ public class BackupUtils { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), phoneNumber)); } - // Print call date + // 打印通话日期 ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat .format(mContext.getString(R.string.format_datetime_mdhm), callDate))); - // Print call attachment location + // 打印通话附件位置 if (!TextUtils.isEmpty(location)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), location)); } } else if (DataConstants.NOTE.equals(mimeType)) { + // 处理普通文本类型的笔记 String content = dataCursor.getString(DATA_COLUMN_CONTENT); if (!TextUtils.isEmpty(content)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), @@ -205,7 +267,7 @@ public class BackupUtils { } dataCursor.close(); } - // print a line separator between note + // 在笔记之间打印分隔符 try { ps.write(new byte[] { Character.LINE_SEPARATOR, Character.LETTER_NUMBER @@ -216,20 +278,25 @@ public class BackupUtils { } /** - * Note will be exported as text which is user readable + * 将所有笔记导出为用户可读的文本文件 + * + * @return 导出状态码 */ public int exportToText() { + // 检查外部存储是否可用 if (!externalStorageAvailable()) { Log.d(TAG, "Media was not mounted"); return STATE_SD_CARD_UNMOUONTED; } + // 获取导出文件的打印流 PrintStream ps = getExportToTextPrintStream(); if (ps == null) { Log.e(TAG, "get print stream error"); return STATE_SYSTEM_ERROR; } - // First export folder and its notes + + // 首先导出文件夹及其笔记 Cursor folderCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, @@ -240,7 +307,7 @@ public class BackupUtils { if (folderCursor != null) { if (folderCursor.moveToFirst()) { do { - // Print folder's name + // 打印文件夹名称 String folderName = ""; if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { folderName = mContext.getString(R.string.call_record_folder_name); @@ -257,7 +324,7 @@ public class BackupUtils { folderCursor.close(); } - // Export notes in root's folder + // 导出根文件夹中的笔记 Cursor noteCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, @@ -270,7 +337,7 @@ public class BackupUtils { ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( mContext.getString(R.string.format_datetime_mdhm), noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // Query data belong to this note + // 查询属于该笔记的详细数据并导出 String noteId = noteCursor.getString(NOTE_COLUMN_ID); exportNoteToText(noteId, ps); } while (noteCursor.moveToNext()); @@ -283,9 +350,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, R.string.file_name_txt_format); if (file == null) { @@ -310,13 +380,20 @@ public class BackupUtils { } /** - * Generate the text file to store imported data + * 在外部存储上生成用于存储导入数据的文本文件 + * + * @param context 应用上下文 + * @param filePathResId 文件路径资源ID + * @param fileNameFormatResId 文件名格式资源ID + * @return 生成的文件对象,如果创建失败则返回null */ private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { StringBuilder sb = new StringBuilder(); + // 构建文件路径 sb.append(Environment.getExternalStorageDirectory()); sb.append(context.getString(filePathResId)); File filedir = new File(sb.toString()); + // 构建文件名(包含时间戳) sb.append(context.getString( fileNameFormatResId, DateFormat.format(context.getString(R.string.format_date_ymd), @@ -324,9 +401,11 @@ public class BackupUtils { File file = new File(sb.toString()); try { + // 确保目录存在 if (!filedir.exists()) { filedir.mkdir(); } + // 确保文件存在 if (!file.exists()) { file.createNewFile(); } @@ -340,5 +419,3 @@ public class BackupUtils { return null; } } - - -- 2.34.1