From 2e7ba35d9db0116ebf28690c049cf48ca03caa6c Mon Sep 17 00:00:00 2001 From: chroe <454604461@qq.com> Date: Tue, 23 Dec 2025 21:05:51 +0800 Subject: [PATCH 1/2] =?UTF-8?q?data=E5=8C=85=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/net/micode/notes/data/Contact.java | 29 +- src/net/micode/notes/data/Notes.java | 268 ++++++++++++------ .../notes/data/NotesDatabaseHelper.java | 216 ++++++++++---- src/net/micode/notes/data/NotesProvider.java | 205 +++++++++++--- 4 files changed, 525 insertions(+), 193 deletions(-) diff --git a/src/net/micode/notes/data/Contact.java b/src/net/micode/notes/data/Contact.java index d97ac5d..ce26d32 100644 --- a/src/net/micode/notes/data/Contact.java +++ b/src/net/micode/notes/data/Contact.java @@ -25,10 +25,24 @@ import android.util.Log; import java.util.HashMap; +/** + * 联系人工具类,用于通过电话号码获取联系人姓名 + */ public class Contact { + /** + * 联系人缓存,用于存储已查询的电话号码和联系人姓名映射关系 + */ private static HashMap sContactCache; + /** + * 日志标签 + */ private static final String TAG = "Contact"; + /** + * 联系人查询条件,用于匹配电话号码对应的联系人 + * PHONE_NUMBERS_EQUAL(18874902291,?) AND Data.MIMETYPE ='Phone.CONTENT_ITEM_TYPE' + * AND Data.RAW_CONTACT_ID IN (SELECT raw_contact_id FROM phone_lookup WHERE min_match = '+') + */ 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 " @@ -36,26 +50,38 @@ public class Contact { + " FROM phone_lookup" + " WHERE min_match = '+')"; + /** + * 根据电话号码获取联系人姓名 + * @param context 上下文对象 + * @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, + Data.CONTENT_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; @@ -63,6 +89,7 @@ public class Contact { Log.e(TAG, " Cursor get string error " + e.toString()); return null; } finally { + // 关闭游标 cursor.close(); } } else { diff --git a/src/net/micode/notes/data/Notes.java b/src/net/micode/notes/data/Notes.java index f240604..913e2b1 100644 --- a/src/net/micode/notes/data/Notes.java +++ b/src/net/micode/notes/data/Notes.java @@ -17,263 +17,343 @@ package net.micode.notes.data; import android.net.Uri; + +/** + * 小米便签应用的核心数据常量和接口定义类 + * 包含便签、文件夹的类型定义、系统文件夹ID、数据库列名、内容URI等 + * 是整个应用的数据模型基础,被各个组件广泛使用 + */ public class Notes { + /** + * ContentProvider的授权字符串,用于标识便签内容提供者 + */ 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; + + /** + * 便签类型常量 + * - TYPE_NOTE:普通便签 + * - TYPE_FOLDER:文件夹 + * - TYPE_SYSTEM:系统文件夹 + */ + public static final int TYPE_NOTE = 0; // 普通便签类型 + public static final int TYPE_FOLDER = 1; // 文件夹类型 + public static final int TYPE_SYSTEM = 2; // 系统文件夹类型 /** - * Following IDs are system folders' identifiers - * {@link Notes#ID_ROOT_FOLDER } is default folder - * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder - * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records + * 系统文件夹ID常量 + * - ID_ROOT_FOLDER:根文件夹,默认文件夹,用于存储普通便签 + * - ID_TEMPARAY_FOLDER:临时文件夹,用于存储没有归属文件夹的便签 + * - ID_CALL_RECORD_FOLDER:通话记录文件夹,用于存储通话记录 + * - 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 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 ID_ROOT_FOLDER = 0; // 根文件夹ID + public static final int ID_TEMPARAY_FOLDER = -1; // 临时文件夹ID + public static final int ID_CALL_RECORD_FOLDER = -2; // 通话记录文件夹ID + public static final int ID_TRASH_FOLER = -3; // 回收站文件夹ID + /** + * Intent额外数据常量,用于组件间传递参数 + * - INTENT_EXTRA_ALERT_DATE:提醒日期 + * - INTENT_EXTRA_BACKGROUND_ID:背景颜色ID + * - INTENT_EXTRA_WIDGET_ID:小部件ID + * - INTENT_EXTRA_WIDGET_TYPE:小部件类型 + * - INTENT_EXTRA_FOLDER_ID:文件夹ID + * - INTENT_EXTRA_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"; // 通话日期 + + /** + * 小部件类型常量 + * - TYPE_WIDGET_INVALIDE:无效小部件 + * - TYPE_WIDGET_2X:2x2大小的小部件 + * - TYPE_WIDGET_4X:4x4大小的小部件 + */ + public static final int TYPE_WIDGET_INVALIDE = -1; // 无效小部件类型 + public static final int TYPE_WIDGET_2X = 0; // 2x2小部件类型 + public static final int TYPE_WIDGET_4X = 1; // 4x4小部件类型 + + /** + * 数据类型常量类,定义了不同类型的便签数据 + * - NOTE:普通文本便签数据 + * - CALL_NOTE:通话记录便签数据 + */ 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; // 普通文本便签数据类型 + public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; // 通话记录便签数据类型 } /** - * Uri to query all notes and folders + * 用于查询所有便签和文件夹的URI + * 对应NoteProvider中的note表查询 */ public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); /** - * Uri to query data + * 用于查询便签详细数据的URI + * 对应NoteProvider中的data表查询 */ public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); + /** + * Note表的列名接口定义 + * 定义了便签和文件夹表的所有字段 + */ public interface NoteColumns { /** - * The unique ID for a row - *

Type: INTEGER (long)

+ * 行的唯一标识符 + *

类型: 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 Tasks同步 + *

类型 : TEXT

*/ public static final String GTASK_ID = "gtask_id"; /** - * The version code - *

Type : INTEGER (long)

+ * 版本号,用于跟踪便签的版本变化 + *

类型 : INTEGER (long)

*/ public static final String VERSION = "version"; } + /** + * Data表的列名接口定义 + * 定义了便签详细数据表的所有字段 + * 支持多种类型的便签数据,如普通文本便签、通话记录便签等 + */ public interface DataColumns { /** - * The unique ID for a row - *

Type: INTEGER (long)

+ * 行的唯一标识符 + *

类型: 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 + * 用于关联Data表和Note表 + *

类型: INTEGER (long)

*/ public static final String NOTE_ID = "note_id"; /** - * Created data for note or folder - *

Type: INTEGER (long)

+ * 数据的创建时间 + *

类型: INTEGER (long)

*/ public static final String CREATED_DATE = "created_date"; /** - * Latest modified date - *

Type: INTEGER (long)

+ * 数据的最后修改时间 + *

类型: INTEGER (long)

*/ public static final String MODIFIED_DATE = "modified_date"; /** - * Data's content - *

Type: TEXT

+ * 数据的内容 + *

类型: TEXT

*/ public static final String CONTENT = "content"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *

Type: INTEGER

+ * 通用数据列1,具体含义由MIME类型决定,用于整数类型数据 + *

类型: INTEGER

*/ public static final String DATA1 = "data1"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *

Type: INTEGER

+ * 通用数据列2,具体含义由MIME类型决定,用于整数类型数据 + *

类型: INTEGER

*/ public static final String DATA2 = "data2"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

+ * 通用数据列3,具体含义由MIME类型决定,用于文本类型数据 + *

类型: TEXT

*/ public static final String DATA3 = "data3"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

+ * 通用数据列4,具体含义由MIME类型决定,用于文本类型数据 + *

类型: TEXT

*/ public static final String DATA4 = "data4"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

+ * 通用数据列5,具体含义由MIME类型决定,用于文本类型数据 + *

类型: TEXT

*/ public static final String DATA5 = "data5"; } + /** + * 普通文本便签的数据类型定义 + * 实现了DataColumns接口,定义了文本便签特有的字段和常量 + */ 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

+ * 文本便签的模式标识,用于指示文本是否为 checklist 模式 + *

类型: Integer 1:checklist模式 0:普通模式

+ * 映射到Data表的DATA1字段 */ public static final String MODE = DATA1; + /** + * Checklist模式常量 + */ public static final int MODE_CHECK_LIST = 1; + /** + * 文本便签集合的MIME类型 + */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; + /** + * 单个文本便签的MIME类型 + */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; + /** + * 用于查询文本便签的URI + */ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); } + /** + * 通话记录便签的数据类型定义 + * 实现了DataColumns接口,定义了通话记录便签特有的字段和常量 + */ public static final class CallNote implements DataColumns { /** - * Call date for this record - *

Type: INTEGER (long)

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

类型: INTEGER (long)

+ * 映射到Data表的DATA1字段 */ public static final String CALL_DATE = DATA1; /** - * Phone number for this record - *

Type: TEXT

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

类型: TEXT

+ * 映射到Data表的DATA3字段 */ public static final String PHONE_NUMBER = DATA3; + /** + * 通话记录便签集合的MIME类型 + */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; + /** + * 单个通话记录便签的MIME类型 + */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; + /** + * 用于查询通话记录便签的URI + */ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); } } diff --git a/src/net/micode/notes/data/NotesDatabaseHelper.java b/src/net/micode/notes/data/NotesDatabaseHelper.java index ffe5d57..2a1c98e 100644 --- a/src/net/micode/notes/data/NotesDatabaseHelper.java +++ b/src/net/micode/notes/data/NotesDatabaseHelper.java @@ -27,21 +27,51 @@ import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.NoteColumns; +/** + * 小米便签数据库帮助类 + * 负责创建、升级数据库,管理表结构和触发器 + */ public class NotesDatabaseHelper extends SQLiteOpenHelper { + // 数据库名称 private static final String DB_NAME = "note.db"; - + + // 数据库版本号 private static final int DB_VERSION = 4; - + + // 数据库表名定义 public interface TABLE { + // 笔记表:存储笔记和文件夹的基本信息 public static final String NOTE = "note"; - + + // 数据表:存储笔记的详细内容,支持多种类型的笔记数据 public static final String DATA = "data"; } - + + // 日志标签 private static final String TAG = "NotesDatabaseHelper"; + // 单例实例,确保整个应用只有一个数据库帮助类实例 private static NotesDatabaseHelper mInstance; - + + // 创建笔记表的SQL语句 + // 笔记表存储笔记和文件夹的基本信息,包括: + // - ID:唯一标识符 + // - PARENT_ID:父文件夹ID + // - ALERTED_DATE:提醒日期 + // - BG_COLOR_ID:背景颜色ID + // - CREATED_DATE:创建日期 + // - HAS_ATTACHMENT:是否有附件 + // - MODIFIED_DATE:修改日期 + // - NOTES_COUNT:子笔记数量(仅文件夹使用) + // - SNIPPET:笔记内容片段或文件夹名称 + // - TYPE:类型(笔记、文件夹、系统文件夹) + // - WIDGET_ID:关联的小部件ID + // - WIDGET_TYPE:小部件类型 + // - SYNC_ID:同步ID + // - LOCAL_MODIFIED:本地是否修改 + // - ORIGIN_PARENT_ID:原始父文件夹ID + // - GTASK_ID:Google任务ID + // - VERSION:版本号 private static final String CREATE_NOTE_TABLE_SQL = "CREATE TABLE " + TABLE.NOTE + "(" + NoteColumns.ID + " INTEGER PRIMARY KEY," + @@ -63,6 +93,15 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + ")"; + // 创建数据表的SQL语句 + // 数据表存储笔记的详细内容,支持多种类型的笔记数据,包括: + // - ID:唯一标识符 + // - MIME_TYPE:数据类型,如文本笔记、通话笔记等 + // - NOTE_ID:关联的笔记ID + // - CREATED_DATE:创建日期 + // - MODIFIED_DATE:修改日期 + // - CONTENT:数据内容 + // - DATA1, DATA2, DATA3, DATA4, DATA5:扩展字段,根据不同数据类型存储不同信息 private static final String CREATE_DATA_TABLE_SQL = "CREATE TABLE " + TABLE.DATA + "(" + DataColumns.ID + " INTEGER PRIMARY KEY," + @@ -83,7 +122,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; /** - * Increase folder's note count when move note to the folder + * 当将笔记移动到文件夹时,增加文件夹的笔记计数 */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER increase_folder_count_on_update "+ @@ -95,7 +134,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Decrease folder's note count when move note from folder + * 当将笔记从文件夹移出时,减少文件夹的笔记计数 */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_update " + @@ -108,7 +147,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Increase folder's note count when insert new note to the folder + * 当向文件夹插入新笔记时,增加文件夹的笔记计数 */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = "CREATE TRIGGER increase_folder_count_on_insert " + @@ -120,7 +159,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Decrease folder's note count when delete note from the folder + * 当从文件夹删除笔记时,减少文件夹的笔记计数 */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_delete " + @@ -133,7 +172,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Update note's content when insert data with type {@link DataConstants#NOTE} + * 当插入类型为普通笔记的数据时,更新笔记内容 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = "CREATE TRIGGER update_note_content_on_insert " + @@ -146,7 +185,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " 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 " + @@ -159,7 +198,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " 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 " + @@ -172,7 +211,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " 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 " + @@ -183,7 +222,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " 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 " + @@ -194,7 +233,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " 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 " + @@ -210,14 +249,27 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { super(context, DB_NAME, null, DB_VERSION); } + /** + * 创建笔记表及其相关触发器和系统文件夹 + * @param db SQLite数据库实例 + */ public void createNoteTable(SQLiteDatabase db) { + // 创建笔记表 db.execSQL(CREATE_NOTE_TABLE_SQL); + // 重新创建笔记表相关触发器 reCreateNoteTableTriggers(db); + // 创建系统文件夹 createSystemFolder(db); + // 记录日志 Log.d(TAG, "note table has been created"); } + /** + * 重新创建笔记表相关触发器,确保触发器的正确性和一致性 + * @param db SQLite数据库实例 + */ 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"); @@ -226,136 +278,194 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { 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); + // 创建新的触发器 + 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); // 将文件夹移到回收站时,将其所有子笔记也移到回收站 } + /** + * 创建系统文件夹 + * 系统文件夹用于管理不同类型的笔记,包括: + * - 通话记录文件夹:存储通话记录 + * - 根文件夹:默认文件夹,用于存储普通笔记 + * - 临时文件夹:用于移动笔记时的临时存储 + * - 回收站文件夹:用于存储被删除的笔记和文件夹 + * @param db SQLite数据库实例 + */ private void createSystemFolder(SQLiteDatabase db) { ContentValues values = new ContentValues(); - /** - * call record foler for call notes - */ + // 创建通话记录文件夹:用于存储通话记录 values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - /** - * root folder which is default folder - */ + // 创建根文件夹:默认文件夹,用于存储普通笔记 values.clear(); values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - /** - * temporary folder which is used for moving note - */ + // 创建临时文件夹:用于移动笔记时的临时存储 values.clear(); values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - /** - * create trash folder - */ + // 创建回收站文件夹:用于存储被删除的笔记和文件夹 values.clear(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } + /** + * 获取NotesDatabaseHelper的单例实例 + * 使用同步方法确保线程安全,避免多线程环境下创建多个实例 + * @param context 上下文对象 + * @return NotesDatabaseHelper的单例实例 + */ + static synchronized NotesDatabaseHelper getInstance(Context context) { + if (mInstance == null) { + mInstance = new NotesDatabaseHelper(context); + } + return mInstance; + } + + /** + * 创建数据表及其相关触发器和索引 + * @param db SQLite数据库实例 + */ public void createDataTable(SQLiteDatabase db) { + // 创建数据表 db.execSQL(CREATE_DATA_TABLE_SQL); + // 重新创建数据表相关触发器 reCreateDataTableTriggers(db); + // 为data表的note_id字段创建索引,提高查询效率 db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); + // 记录日志 Log.d(TAG, "data table has been created"); } + /** + * 重新创建数据表相关触发器,确保触发器的正确性和一致性 + * @param db SQLite数据库实例 + */ 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; + // 创建新的数据表触发器 + 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); // 删除文本笔记数据时清空笔记内容 } + /** + * 创建数据库时调用,初始化数据库表结构和数据 + * @param db SQLite数据库实例 + */ @Override public void onCreate(SQLiteDatabase db) { + // 创建笔记表及其相关触发器和系统文件夹 createNoteTable(db); + // 创建数据表及其相关触发器和索引 createDataTable(db); } + /** + * 数据库升级时调用,处理不同版本之间的升级逻辑 + * @param db SQLite数据库实例 + * @param oldVersion 旧数据库版本号 + * @param newVersion 新数据库版本号 + * @throws IllegalStateException 升级失败时抛出异常 + */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - boolean reCreateTriggers = false; - boolean skipV2 = false; + boolean reCreateTriggers = false; // 是否需要重新创建触发器 + boolean skipV2 = false; // 是否跳过v2版本升级 + // 从v1升级到v2 if (oldVersion == 1) { upgradeToV2(db); - skipV2 = true; // this upgrade including the upgrade from v2 to v3 + skipV2 = true; // v2到v3的升级已包含在v1到v2的升级中 oldVersion++; } + // 从v2升级到v3 if (oldVersion == 2 && !skipV2) { upgradeToV3(db); - reCreateTriggers = true; + reCreateTriggers = true; // v3升级需要重新创建触发器 oldVersion++; } + // 从v3升级到v4 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"); + throw new IllegalStateException("Upgrade notes database to version " + newVersion + " fails"); } } + /** + * 将数据库从v1升级到v2 + * 此版本升级重建了整个数据库结构 + * @param db SQLite数据库实例 + */ private void upgradeToV2(SQLiteDatabase db) { + // 删除旧的笔记表和数据表 db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); + // 创建新的笔记表 createNoteTable(db); + // 创建新的数据表 createDataTable(db); } + /** + * 将数据库从v2升级到v3 + * 此版本升级添加了Google任务ID字段和回收站文件夹 + * @param db SQLite数据库实例 + */ 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); db.insert(TABLE.NOTE, null, values); } + /** + * 将数据库从v3升级到v4 + * 此版本升级添加了版本号字段,用于记录笔记的版本 + * @param db SQLite数据库实例 + */ private void upgradeToV4(SQLiteDatabase db) { + // 为笔记表添加版本号字段 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); } diff --git a/src/net/micode/notes/data/NotesProvider.java b/src/net/micode/notes/data/NotesProvider.java index edb0a60..da10884 100644 --- a/src/net/micode/notes/data/NotesProvider.java +++ b/src/net/micode/notes/data/NotesProvider.java @@ -35,35 +35,65 @@ import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.NotesDatabaseHelper.TABLE; +/** + * 小米便签内容提供者 + * 实现了ContentProvider接口,提供便签数据的增删改查功能 + * 支持便签和文件夹的管理,以及搜索和搜索建议功能 + */ 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类型常量,用于标识不同的请求类型 + * - URI_NOTE:查询所有便签和文件夹 + * - URI_NOTE_ITEM:查询单个便签或文件夹 + * - URI_DATA:查询所有便签数据 + * - URI_DATA_ITEM:查询单个便签数据 + * - URI_SEARCH:搜索便签 + * - URI_SEARCH_SUGGEST:搜索建议 + */ + 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; // 搜索建议 static { + // 初始化URI匹配器,设置各种URI模式对应的处理类型 mMatcher = new UriMatcher(UriMatcher.NO_MATCH); - mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); - mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); - mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); - mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); - mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); - mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); - mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); + mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); // 匹配所有便签和文件夹 + mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); // 匹配单个便签或文件夹 + mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); // 匹配所有便签数据 + mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); // 匹配单个便签数据 + mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); // 匹配搜索请求 + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); // 匹配搜索建议 + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); // 匹配带参数的搜索建议 } /** - * x'0A' represents the '\n' character in sqlite. For title and content in the search result, - * we will trim '\n' and white space in order to show more information. + * 搜索结果的投影列定义 + * x'0A'代表SQLite中的'\n'字符。在搜索结果中,我们会去除'\n'和空白字符以显示更多信息 + * 定义了搜索建议需要的列: + * - ID:便签ID + * - SUGGEST_COLUMN_INTENT_EXTRA_DATA:用于传递便签ID + * - SUGGEST_COLUMN_TEXT_1和TEXT_2:搜索建议显示的文本 + * - SUGGEST_COLUMN_ICON_1:搜索建议显示的图标 + * - SUGGEST_COLUMN_INTENT_ACTION:点击搜索建议时的动作 + * - SUGGEST_COLUMN_INTENT_DATA:点击搜索建议时的数据类型 */ private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," @@ -73,65 +103,94 @@ public class NotesProvider extends ContentProvider { + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; + /** + * 便签搜索查询语句 + * 用于根据关键词搜索便签,只搜索未删除的普通便签 + */ private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION + " FROM " + TABLE.NOTE - + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" - + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER - + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" // 模糊匹配关键词 + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER // 排除回收站中的便签 + + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; // 只搜索普通便签 + /** + * 内容提供者创建时调用,初始化数据库帮助类实例 + * @return 初始化是否成功,始终返回true + */ @Override public boolean onCreate() { + // 获取数据库帮助类的单例实例 mHelper = NotesDatabaseHelper.getInstance(getContext()); return true; } + /** + * 查询便签数据 + * @param uri 请求的URI + * @param projection 返回的列 + * @param selection 查询条件 + * @param selectionArgs 查询条件参数 + * @param sortOrder 排序方式 + * @return 查询结果游标 + * @throws IllegalArgumentException 当请求的URI无效或参数不符合要求时抛出 + */ @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: + case URI_NOTE: // 查询所有便签和文件夹 c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder); break; - case URI_NOTE_ITEM: - id = uri.getPathSegments().get(1); + case URI_NOTE_ITEM: // 查询单个便签或文件夹 + id = uri.getPathSegments().get(1); // 从URI中获取ID c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder); break; - case URI_DATA: + case URI_DATA: // 查询所有便签数据 c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder); break; - case URI_DATA_ITEM: - id = uri.getPathSegments().get(1); + case URI_DATA_ITEM: // 查询单个便签数据 + id = uri.getPathSegments().get(1); // 从URI中获取ID c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder); break; - case URI_SEARCH: - case URI_SEARCH_SUGGEST: + 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) { + // 从URI路径中获取搜索关键词 if (uri.getPathSegments().size() > 1) { searchString = uri.getPathSegments().get(1); } } else { + // 从URI查询参数中获取搜索关键词 searchString = uri.getQueryParameter("pattern"); } + // 如果搜索关键词为空,返回null if (TextUtils.isEmpty(searchString)) { return null; } try { + // 构建模糊查询字符串 searchString = String.format("%%%s%%", searchString); + // 执行搜索查询 c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, new String[] { searchString }); } catch (IllegalStateException ex) { @@ -141,21 +200,33 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } + + // 设置内容变更通知URI,当数据发生变化时通知观察者 if (c != null) { c.setNotificationUri(getContext().getContentResolver(), uri); } return c; } + /** + * 插入便签数据 + * @param uri 请求的URI + * @param values 要插入的数据 + * @return 插入数据的URI,包含新插入数据的ID + * @throws IllegalArgumentException 当请求的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: + case URI_NOTE: // 插入便签或文件夹 insertedId = noteId = db.insert(TABLE.NOTE, null, values); break; - case URI_DATA: + case URI_DATA: // 插入便签数据 if (values.containsKey(DataColumns.NOTE_ID)) { noteId = values.getAsLong(DataColumns.NOTE_ID); } else { @@ -166,37 +237,49 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } - // Notify the note uri + + // 通知便签URI数据变更 if (noteId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); } - // Notify the data uri + // 通知数据URI数据变更 if (dataId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); } + // 返回包含新插入数据ID的URI return ContentUris.withAppendedId(uri, insertedId); } + /** + * 删除便签数据 + * @param uri 请求的URI + * @param selection 删除条件 + * @param selectionArgs 删除条件参数 + * @return 删除的行数 + * @throws IllegalArgumentException 当请求的URI无效时抛出 + */ @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int count = 0; String id = null; + // 获取可写数据库连接 SQLiteDatabase db = mHelper.getWritableDatabase(); boolean deleteData = false; + + // 根据URI类型执行不同的删除逻辑 switch (mMatcher.match(uri)) { - case URI_NOTE: - selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; + case URI_NOTE: // 删除便签或文件夹 + selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; // 排除系统文件夹 count = db.delete(TABLE.NOTE, selection, selectionArgs); break; - case URI_NOTE_ITEM: + case URI_NOTE_ITEM: // 删除单个便签或文件夹 id = uri.getPathSegments().get(1); /** - * ID that smaller than 0 is system folder which is not allowed to - * trash + * ID小于0的是系统文件夹,不允许删除 */ long noteId = Long.valueOf(id); if (noteId <= 0) { @@ -205,11 +288,11 @@ public class NotesProvider extends ContentProvider { count = db.delete(TABLE.NOTE, NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); break; - case URI_DATA: + case URI_DATA: // 删除便签数据 count = db.delete(TABLE.DATA, selection, selectionArgs); deleteData = true; break; - case URI_DATA_ITEM: + case URI_DATA_ITEM: // 删除单个便签数据 id = uri.getPathSegments().get(1); count = db.delete(TABLE.DATA, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); @@ -218,6 +301,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,28 +312,40 @@ public class NotesProvider extends ContentProvider { return count; } + /** + * 更新便签数据 + * @param uri 请求的URI + * @param values 要更新的数据 + * @param selection 更新条件 + * @param selectionArgs 更新条件参数 + * @return 更新的行数 + * @throws IllegalArgumentException 当请求的URI无效时抛出 + */ @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int count = 0; String id = null; + // 获取可写数据库连接 SQLiteDatabase db = mHelper.getWritableDatabase(); boolean updateData = false; + + // 根据URI类型执行不同的更新逻辑 switch (mMatcher.match(uri)) { - case URI_NOTE: - increaseNoteVersion(-1, selection, selectionArgs); + case URI_NOTE: // 更新便签或文件夹 + increaseNoteVersion(-1, selection, selectionArgs); // 增加便签版本号 count = db.update(TABLE.NOTE, values, selection, selectionArgs); break; - case URI_NOTE_ITEM: + case URI_NOTE_ITEM: // 更新单个便签或文件夹 id = uri.getPathSegments().get(1); - increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); + increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); // 增加便签版本号 count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); break; - case URI_DATA: + case URI_DATA: // 更新便签数据 count = db.update(TABLE.DATA, values, selection, selectionArgs); updateData = true; break; - case URI_DATA_ITEM: + case URI_DATA_ITEM: // 更新单个便签数据 id = uri.getPathSegments().get(1); count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); @@ -258,6 +355,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,17 +365,28 @@ 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 "); sql.append(TABLE.NOTE); sql.append(" SET "); sql.append(NoteColumns.VERSION); - sql.append("=" + NoteColumns.VERSION + "+1 "); + sql.append("=" + NoteColumns.VERSION + "+1 "); // 版本号自增1 if (id > 0 || !TextUtils.isEmpty(selection)) { sql.append(" WHERE "); @@ -293,9 +402,15 @@ public class NotesProvider extends ContentProvider { sql.append(selectString); } + // 执行SQL语句 mHelper.getWritableDatabase().execSQL(sql.toString()); } + /** + * 获取URI对应的MIME类型 + * @param uri 请求的URI + * @return MIME类型,目前未实现,返回null + */ @Override public String getType(Uri uri) { // TODO Auto-generated method stub -- 2.34.1 From aaf3d69fc058ffbd7b37dc94a59c83adb5341a61 Mon Sep 17 00:00:00 2001 From: chroe <454604461@qq.com> Date: Tue, 23 Dec 2025 21:12:59 +0800 Subject: [PATCH 2/2] =?UTF-8?q?gtask=E5=8C=85=EF=BC=8Cmodel=E5=8C=85?= =?UTF-8?q?=EF=BC=8Ctool=E5=8C=85=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/net/micode/notes/gtask/data/MetaData.java | 69 +++- src/net/micode/notes/gtask/data/Node.java | 148 ++++++-- src/net/micode/notes/gtask/data/SqlData.java | 122 +++++-- src/net/micode/notes/gtask/data/SqlNote.java | 252 +++++++++----- src/net/micode/notes/gtask/data/Task.java | 140 +++++++- src/net/micode/notes/gtask/data/TaskList.java | 183 +++++++++- .../exception/ActionFailureException.java | 32 +- .../exception/NetworkFailureException.java | 32 +- .../notes/gtask/remote/GTaskASyncTask.java | 79 ++++- .../notes/gtask/remote/GTaskClient.java | 133 +++++++- .../notes/gtask/remote/GTaskManager.java | 96 +++++- .../notes/gtask/remote/GTaskSyncService.java | 39 +++ src/net/micode/notes/model/Note.java | 208 +++++++++--- src/net/micode/notes/model/WorkingNote.java | 321 ++++++++++++++---- src/net/micode/notes/tool/BackupUtils.java | 197 +++++++++-- src/net/micode/notes/tool/DataUtils.java | 145 +++++++- .../micode/notes/tool/GTaskStringUtils.java | 154 +++++++++ src/net/micode/notes/tool/ResourceParser.java | 212 ++++++++++++ 18 files changed, 2261 insertions(+), 301 deletions(-) diff --git a/src/net/micode/notes/gtask/data/MetaData.java b/src/net/micode/notes/gtask/data/MetaData.java index 3a2050b..b4d8817 100644 --- a/src/net/micode/notes/gtask/data/MetaData.java +++ b/src/net/micode/notes/gtask/data/MetaData.java @@ -25,11 +25,27 @@ import org.json.JSONException; import org.json.JSONObject; +/** + * MetaData - 元数据类 + *

+ * 继承自Task类,用于存储Google Task的元数据信息 + * 主要用于关联本地便签和Google Task + *

+ */ public class MetaData extends Task { - private final static String TAG = MetaData.class.getSimpleName(); + private final static String TAG = MetaData.class.getSimpleName(); // 日志标签 - private String mRelatedGid = null; + private String mRelatedGid = null; // 关联的全局唯一标识符 + /** + * 设置元数据 + *

+ * 将元数据信息存储到Task的notes字段中,并设置名称为META_NOTE_NAME + *

+ * + * @param gid 关联的全局唯一标识符 + * @param metaInfo 元数据JSON对象 + */ public void setMeta(String gid, JSONObject metaInfo) { try { metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); @@ -40,15 +56,36 @@ public class MetaData extends Task { setName(GTaskStringUtils.META_NOTE_NAME); } + /** + * 获取关联的全局唯一标识符 + * + * @return 关联的全局唯一标识符 + */ public String getRelatedGid() { return mRelatedGid; } + /** + * 判断元数据是否值得保存 + *

+ * 只要notes字段不为null就值得保存 + *

+ * + * @return 是否值得保存 + */ @Override public boolean isWorthSaving() { return getNotes() != null; } + /** + * 从远程JSON对象设置元数据内容 + *

+ * 调用父类方法后,解析notes字段获取关联的GID + *

+ * + * @param js 远程JSON对象 + */ @Override public void setContentByRemoteJSON(JSONObject js) { super.setContentByRemoteJSON(js); @@ -63,17 +100,45 @@ public class MetaData extends Task { } } + /** + * 从本地JSON对象设置元数据内容 + *

+ * 该方法不应该被调用,会抛出IllegalAccessError + *

+ * + * @param js 本地JSON对象 + * @throws IllegalAccessError 调用该方法时抛出 + */ @Override public void setContentByLocalJSON(JSONObject js) { // this function should not be called throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); } + /** + * 从元数据内容生成本地JSON对象 + *

+ * 该方法不应该被调用,会抛出IllegalAccessError + *

+ * + * @return 本地JSON对象 + * @throws IllegalAccessError 调用该方法时抛出 + */ @Override public JSONObject getLocalJSONFromContent() { throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); } + /** + * 获取同步操作类型 + *

+ * 该方法不应该被调用,会抛出IllegalAccessError + *

+ * + * @param c 游标对象 + * @return 同步操作类型 + * @throws IllegalAccessError 调用该方法时抛出 + */ @Override public int getSyncAction(Cursor c) { throw new IllegalAccessError("MetaData:getSyncAction should not be called"); diff --git a/src/net/micode/notes/gtask/data/Node.java b/src/net/micode/notes/gtask/data/Node.java index 63950e0..34a54b0 100644 --- a/src/net/micode/notes/gtask/data/Node.java +++ b/src/net/micode/notes/gtask/data/Node.java @@ -20,33 +20,38 @@ import android.database.Cursor; import org.json.JSONObject; +/** + * Node - 节点抽象类 + *

+ * 定义了Google Task同步的基本节点类,包含同步操作常量和抽象方法 + * 子类需要实现具体的创建、更新和同步逻辑 + *

+ */ 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; // 全局唯一标识符 + private String mName; // 节点名称 + private long mLastModified; // 最后修改时间 + private boolean mDeleted; // 是否已删除 + + /** + * 构造方法 + *

+ * 初始化节点的默认值 + *

+ */ public Node() { mGid = null; mName = ""; @@ -54,46 +59,137 @@ public abstract class Node { mDeleted = false; } + /** + * 获取创建操作的JSON对象 + *

+ * 子类需要实现生成创建节点的JSON操作 + *

+ * + * @param actionId 操作ID + * @return 创建操作的JSON对象 + */ public abstract JSONObject getCreateAction(int actionId); + /** + * 获取更新操作的JSON对象 + *

+ * 子类需要实现生成更新节点的JSON操作 + *

+ * + * @param actionId 操作ID + * @return 更新操作的JSON对象 + */ public abstract JSONObject getUpdateAction(int actionId); + /** + * 从远程JSON对象设置节点内容 + *

+ * 子类需要实现从远程JSON对象解析并设置节点内容 + *

+ * + * @param js 远程JSON对象 + */ public abstract void setContentByRemoteJSON(JSONObject js); + /** + * 从本地JSON对象设置节点内容 + *

+ * 子类需要实现从本地JSON对象解析并设置节点内容 + *

+ * + * @param js 本地JSON对象 + */ public abstract void setContentByLocalJSON(JSONObject js); + /** + * 从节点内容生成本地JSON对象 + *

+ * 子类需要实现将节点内容转换为本地JSON对象 + *

+ * + * @return 本地JSON对象 + */ public abstract JSONObject getLocalJSONFromContent(); + /** + * 获取同步操作类型 + *

+ * 子类需要实现根据游标数据判断同步操作类型 + *

+ * + * @param c 游标对象,包含本地数据 + * @return 同步操作类型 + */ public abstract int getSyncAction(Cursor c); + /** + * 设置全局唯一标识符 + * + * @param gid 全局唯一标识符 + */ public void setGid(String gid) { this.mGid = gid; } + /** + * 设置节点名称 + * + * @param name 节点名称 + */ public void setName(String name) { this.mName = name; } + /** + * 设置最后修改时间 + * + * @param lastModified 最后修改时间 + */ public void setLastModified(long lastModified) { this.mLastModified = lastModified; } + /** + * 设置是否已删除 + * + * @param deleted 是否已删除 + */ public void setDeleted(boolean deleted) { this.mDeleted = deleted; } + /** + * 获取全局唯一标识符 + * + * @return 全局唯一标识符 + */ public String getGid() { return this.mGid; } + /** + * 获取节点名称 + * + * @return 节点名称 + */ public String getName() { return this.mName; } + /** + * 获取最后修改时间 + * + * @return 最后修改时间 + */ public long getLastModified() { return this.mLastModified; } + /** + * 获取是否已删除 + * + * @return 是否已删除 + */ public boolean getDeleted() { return this.mDeleted; } diff --git a/src/net/micode/notes/gtask/data/SqlData.java b/src/net/micode/notes/gtask/data/SqlData.java index d3ec3be..7deff7e 100644 --- a/src/net/micode/notes/gtask/data/SqlData.java +++ b/src/net/micode/notes/gtask/data/SqlData.java @@ -35,42 +35,55 @@ import org.json.JSONException; import org.json.JSONObject; +/** + * SqlData - 数据库便签数据类 + *

+ * 用于操作数据库中的便签数据内容,包括数据的创建、更新、查询和同步 + * 支持便签数据的JSON序列化和反序列化 + *

+ */ public class SqlData { - private static final String TAG = SqlData.class.getSimpleName(); + private static final String TAG = SqlData.class.getSimpleName(); // 日志标签 - private static final int INVALID_ID = -99999; + private static final int INVALID_ID = -99999; // 无效ID常量 + /** + * 数据查询投影数组 + *

+ * 定义了查询便签数据时返回的列 + *

+ */ public static final String[] PROJECTION_DATA = new String[] { DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, DataColumns.DATA3 }; - 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; - + /** + * 查询投影列索引常量 + */ + public static final int DATA_ID_COLUMN = 0; // 数据ID列索引 + public static final int DATA_MIME_TYPE_COLUMN = 1; // 数据MIME类型列索引 + public static final int DATA_CONTENT_COLUMN = 2; // 数据内容列索引 + public static final int DATA_CONTENT_DATA_1_COLUMN = 3; // 数据内容DATA1列索引 + public static final int DATA_CONTENT_DATA_3_COLUMN = 4; // 数据内容DATA3列索引 + + private ContentResolver mContentResolver; // 内容解析器 + private boolean mIsCreate; // 是否为新建数据 + private long mDataId; // 数据ID + private String mDataMimeType; // 数据MIME类型 + private String mDataContent; // 数据内容 + private long mDataContentData1; // 数据内容DATA1 + private String mDataContentData3; // 数据内容DATA3 + private ContentValues mDiffDataValues; // 差异值集合,用于更新操作 + + /** + * 构造方法 + *

+ * 创建一个新的数据实例,初始化默认值 + *

+ * + * @param context 上下文对象 + */ public SqlData(Context context) { mContentResolver = context.getContentResolver(); mIsCreate = true; @@ -82,6 +95,15 @@ public class SqlData { mDiffDataValues = new ContentValues(); } + /** + * 构造方法 + *

+ * 从游标创建 SqlData 对象,加载数据 + *

+ * + * @param context 上下文对象 + * @param c 包含数据的游标 + */ public SqlData(Context context, Cursor c) { mContentResolver = context.getContentResolver(); mIsCreate = false; @@ -89,6 +111,14 @@ public class SqlData { mDiffDataValues = new ContentValues(); } + /** + * 从游标加载数据 + *

+ * 从游标中提取数据,设置到对象的各个字段中 + *

+ * + * @param c 包含数据的游标 + */ private void loadFromCursor(Cursor c) { mDataId = c.getLong(DATA_ID_COLUMN); mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); @@ -97,6 +127,15 @@ public class SqlData { mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); } + /** + * 从JSON对象设置数据内容 + *

+ * 解析JSON对象,提取数据并设置到对象的各个字段中 + *

+ * + * @param js 包含数据的JSON对象 + * @throws JSONException 解析JSON对象失败时抛出 + */ public void setContent(JSONObject js) throws JSONException { long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; if (mIsCreate || mDataId != dataId) { @@ -130,6 +169,16 @@ public class SqlData { mDataContentData3 = dataContentData3; } + /** + * 将数据内容转换为JSON对象 + *

+ * 将数据的各个字段转换为JSON格式 + * 新建数据返回null + *

+ * + * @return 包含数据内容的JSON对象,失败返回null + * @throws JSONException 生成JSON对象失败时抛出 + */ public JSONObject getContent() throws JSONException { if (mIsCreate) { Log.e(TAG, "it seems that we haven't created this in database yet"); @@ -144,6 +193,18 @@ public class SqlData { return js; } + /** + * 提交数据到数据库 + *

+ * 根据mIsCreate标志判断是插入新数据还是更新现有数据 + * 支持版本验证,防止并发更新冲突 + *

+ * + * @param noteId 所属便签ID + * @param validateVersion 是否验证版本 + * @param version 版本号 + * @throws ActionFailureException 插入数据失败时抛出 + */ public void commit(long noteId, boolean validateVersion, long version) { if (mIsCreate) { @@ -183,6 +244,11 @@ public class SqlData { mIsCreate = false; } + /** + * 获取数据ID + * + * @return 数据ID + */ public long getId() { return mDataId; } diff --git a/src/net/micode/notes/gtask/data/SqlNote.java b/src/net/micode/notes/gtask/data/SqlNote.java index 79a4095..bdd6dc7 100644 --- a/src/net/micode/notes/gtask/data/SqlNote.java +++ b/src/net/micode/notes/gtask/data/SqlNote.java @@ -38,11 +38,24 @@ import org.json.JSONObject; import java.util.ArrayList; +/** + * SqlNote - 数据库便签类 + *

+ * 用于操作数据库中的便签数据,包括便签的创建、更新、查询和同步 + * 支持便签内容的JSON序列化和反序列化 + *

+ */ public class SqlNote { - private static final String TAG = SqlNote.class.getSimpleName(); + private static final String TAG = SqlNote.class.getSimpleName(); // 日志标签 - private static final int INVALID_ID = -99999; + private static final int INVALID_ID = -99999; // 无效ID常量 + /** + * 便签查询投影数组 + *

+ * 定义了查询便签时返回的列 + *

+ */ public static final String[] PROJECTION_NOTE = new String[] { NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID, NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE, @@ -52,76 +65,54 @@ public class SqlNote { NoteColumns.VERSION }; - public static final int ID_COLUMN = 0; - - public static final int ALERTED_DATE_COLUMN = 1; - - public static final int BG_COLOR_ID_COLUMN = 2; - - public static final int CREATED_DATE_COLUMN = 3; - - public static final int HAS_ATTACHMENT_COLUMN = 4; - - public static final int MODIFIED_DATE_COLUMN = 5; - - public static final int NOTES_COUNT_COLUMN = 6; - - public static final int PARENT_ID_COLUMN = 7; - - public static final int SNIPPET_COLUMN = 8; - - public static final int TYPE_COLUMN = 9; - - public static final int WIDGET_ID_COLUMN = 10; - - public static final int WIDGET_TYPE_COLUMN = 11; - - public static final int SYNC_ID_COLUMN = 12; - - public static final int LOCAL_MODIFIED_COLUMN = 13; - - public static final int ORIGIN_PARENT_ID_COLUMN = 14; - - public static final int GTASK_ID_COLUMN = 15; - - 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; - + /** + * 查询投影列索引常量 + */ + public static final int ID_COLUMN = 0; // ID列索引 + public static final int ALERTED_DATE_COLUMN = 1; // 提醒日期列索引 + public static final int BG_COLOR_ID_COLUMN = 2; // 背景颜色ID列索引 + public static final int CREATED_DATE_COLUMN = 3; // 创建日期列索引 + public static final int HAS_ATTACHMENT_COLUMN = 4; // 是否有附件列索引 + public static final int MODIFIED_DATE_COLUMN = 5; // 修改日期列索引 + public static final int NOTES_COUNT_COLUMN = 6; // 便签数量列索引 + public static final int PARENT_ID_COLUMN = 7; // 父ID列索引 + public static final int SNIPPET_COLUMN = 8; // 摘要列索引 + public static final int TYPE_COLUMN = 9; // 类型列索引 + public static final int WIDGET_ID_COLUMN = 10; // 小部件ID列索引 + public static final int WIDGET_TYPE_COLUMN = 11; // 小部件类型列索引 + public static final int SYNC_ID_COLUMN = 12; // 同步ID列索引 + public static final int LOCAL_MODIFIED_COLUMN = 13; // 本地修改标记列索引 + public static final int ORIGIN_PARENT_ID_COLUMN = 14; // 原始父ID列索引 + public static final int GTASK_ID_COLUMN = 15; // Google Task ID列索引 + public static final int VERSION_COLUMN = 16; // 版本号列索引 + + private Context mContext; // 上下文对象 + private ContentResolver mContentResolver; // 内容解析器 + private boolean mIsCreate; // 是否为新建便签 + private long mId; // 便签ID + private long mAlertDate; // 提醒日期 + private int mBgColorId; // 背景颜色ID + private long mCreatedDate; // 创建日期 + private int mHasAttachment; // 是否有附件 + private long mModifiedDate; // 修改日期 + private long mParentId; // 父ID + private String mSnippet; // 摘要 + private int mType; // 类型 + private int mWidgetId; // 小部件ID + private int mWidgetType; // 小部件类型 + private long mOriginParent; // 原始父ID + private long mVersion; // 版本号 + private ContentValues mDiffNoteValues; // 差异值集合,用于更新操作 + private ArrayList mDataList; // 便签内容数据列表 + + /** + * 构造方法 + *

+ * 创建一个新的便签实例,初始化默认值 + *

+ * + * @param context 上下文对象 + */ public SqlNote(Context context) { mContext = context; mContentResolver = context.getContentResolver(); @@ -143,6 +134,15 @@ public class SqlNote { mDataList = new ArrayList(); } + /** + * 构造方法 + *

+ * 从游标创建 SqlNote 对象,加载便签数据 + *

+ * + * @param context 上下文对象 + * @param c 包含便签数据的游标 + */ public SqlNote(Context context, Cursor c) { mContext = context; mContentResolver = context.getContentResolver(); @@ -154,6 +154,15 @@ public class SqlNote { mDiffNoteValues = new ContentValues(); } + /** + * 构造方法 + *

+ * 根据便签ID创建 SqlNote 对象,从数据库加载便签数据 + *

+ * + * @param context 上下文对象 + * @param id 便签ID + */ public SqlNote(Context context, long id) { mContext = context; mContentResolver = context.getContentResolver(); @@ -166,6 +175,14 @@ public class SqlNote { } + /** + * 根据ID从数据库加载便签数据 + *

+ * 通过内容解析器查询指定ID的便签数据,并调用loadFromCursor(Cursor c)方法加载数据 + *

+ * + * @param id 要加载的便签ID + */ private void loadFromCursor(long id) { Cursor c = null; try { @@ -185,6 +202,14 @@ public class SqlNote { } } + /** + * 从游标加载便签数据 + *

+ * 从游标中提取便签数据,设置到对象的各个字段中 + *

+ * + * @param c 包含便签数据的游标 + */ private void loadFromCursor(Cursor c) { mId = c.getLong(ID_COLUMN); mAlertDate = c.getLong(ALERTED_DATE_COLUMN); @@ -200,6 +225,12 @@ public class SqlNote { mVersion = c.getLong(VERSION_COLUMN); } + /** + * 加载便签内容数据 + *

+ * 查询并加载便签的内容数据,将数据添加到mDataList中 + *

+ */ private void loadDataContent() { Cursor c = null; mDataList.clear(); @@ -226,6 +257,16 @@ public class SqlNote { } } + /** + * 从JSON对象设置便签内容 + *

+ * 解析JSON对象,提取便签数据并设置到对象的各个字段中 + * 支持系统文件夹、普通文件夹和普通便签三种类型 + *

+ * + * @param js 包含便签数据的JSON对象 + * @return 设置成功返回true,失败返回false + */ public boolean setContent(JSONObject js) { try { JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); @@ -359,6 +400,15 @@ public class SqlNote { return true; } + /** + * 将便签内容转换为JSON对象 + *

+ * 将便签的各个字段转换为JSON格式,支持普通便签、文件夹和系统文件夹三种类型 + * 新建便签返回null + *

+ * + * @return 包含便签内容的JSON对象,失败返回null + */ public JSONObject getContent() { try { JSONObject js = new JSONObject(); @@ -407,39 +457,91 @@ public class SqlNote { return null; } + /** + * 设置父ID + * + * @param id 父ID + */ public void setParentId(long id) { mParentId = id; mDiffNoteValues.put(NoteColumns.PARENT_ID, id); } + /** + * 设置Google Task ID + * + * @param gid Google Task ID + */ public void setGtaskId(String gid) { mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); } + /** + * 设置同步ID + * + * @param syncId 同步ID + */ public void setSyncId(long syncId) { mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); } + /** + * 重置本地修改标记 + *

+ * 将本地修改标记设置为0,表示没有本地修改 + *

+ */ public void resetLocalModified() { mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); } + /** + * 获取便签ID + * + * @return 便签ID + */ public long getId() { return mId; } + /** + * 获取父ID + * + * @return 父ID + */ public long getParentId() { return mParentId; } + /** + * 获取便签摘要 + * + * @return 便签摘要 + */ public String getSnippet() { return mSnippet; } + /** + * 判断是否为普通便签类型 + * + * @return 是否为普通便签 + */ public boolean isNoteType() { return mType == Notes.TYPE_NOTE; } + /** + * 提交便签数据到数据库 + *

+ * 根据mIsCreate标志判断是插入新便签还是更新现有便签 + * 支持版本验证,防止并发更新冲突 + *

+ * + * @param validateVersion 是否验证版本 + * @throws ActionFailureException 插入便签失败时抛出 + * @throws IllegalStateException 更新无效ID的便签时抛出 + */ public void commit(boolean validateVersion) { if (mIsCreate) { if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) { @@ -471,13 +573,11 @@ public class SqlNote { mVersion ++; int result = 0; if (!validateVersion) { - result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?)", new String[] { + result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" + NoteColumns.ID + "=?)", new String[] { String.valueOf(mId) }); } else { - result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", + result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", new String[] { String.valueOf(mId), String.valueOf(mVersion) }); diff --git a/src/net/micode/notes/gtask/data/Task.java b/src/net/micode/notes/gtask/data/Task.java index 6a19454..a92f601 100644 --- a/src/net/micode/notes/gtask/data/Task.java +++ b/src/net/micode/notes/gtask/data/Task.java @@ -32,19 +32,32 @@ import org.json.JSONException; import org.json.JSONObject; +/** + * Task - 任务类 + *

+ * 表示Google Task中的单个任务,继承自Node类 + * 负责处理任务的创建、更新和同步操作 + *

+ */ public class Task extends Node { - private static final String TAG = Task.class.getSimpleName(); + private static final String TAG = Task.class.getSimpleName(); // 日志标签 - private boolean mCompleted; + private boolean mCompleted; // 任务是否已完成 - private String mNotes; + private String mNotes; // 任务备注 - private JSONObject mMetaInfo; + private JSONObject mMetaInfo; // 任务元信息 - private Task mPriorSibling; + private Task mPriorSibling; // 前序兄弟任务 - private TaskList mParent; + private TaskList mParent; // 父任务列表 + /** + * 构造函数 + *

+ * 初始化任务,设置默认值 + *

+ */ public Task() { super(); mCompleted = false; @@ -54,6 +67,16 @@ public class Task extends Node { mMetaInfo = null; } + /** + * 获取创建任务的JSON操作 + *

+ * 生成创建任务的JSON对象,包含操作类型、操作ID、索引、实体信息、父任务ID等 + *

+ * + * @param actionId 操作ID + * @return 创建任务的JSON对象 + * @throws ActionFailureException 生成JSON对象失败时抛出 + */ public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); @@ -103,6 +126,16 @@ public class Task extends Node { return js; } + /** + * 获取更新任务的JSON操作 + *

+ * 生成更新任务的JSON对象,包含操作类型、操作ID、任务ID和实体信息 + *

+ * + * @param actionId 操作ID + * @return 更新任务的JSON对象 + * @throws ActionFailureException 生成JSON对象失败时抛出 + */ public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); @@ -135,6 +168,15 @@ public class Task extends Node { return js; } + /** + * 从远程JSON对象设置任务内容 + *

+ * 解析远程JSON对象,提取任务的ID、最后修改时间、名称、备注、删除状态和完成状态 + *

+ * + * @param js 远程JSON对象 + * @throws ActionFailureException 解析JSON对象失败时抛出 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { @@ -175,6 +217,14 @@ public class Task extends Node { } } + /** + * 从本地JSON对象设置任务内容 + *

+ * 解析本地JSON对象,提取便签内容设置为任务名称 + *

+ * + * @param js 本地JSON对象 + */ public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) || !js.has(GTaskStringUtils.META_HEAD_DATA)) { @@ -204,6 +254,14 @@ public class Task extends Node { } } + /** + * 从任务内容生成本地JSON对象 + *

+ * 根据任务名称和元信息生成本地JSON对象,包含便签类型和内容信息 + *

+ * + * @return 本地JSON对象,生成失败返回null + */ public JSONObject getLocalJSONFromContent() { String name = getName(); try { @@ -247,6 +305,14 @@ public class Task extends Node { } } + /** + * 设置任务的元信息 + *

+ * 将MetaData对象中的notes字符串解析为JSONObject,设置为任务的元信息 + *

+ * + * @param metaData 包含元信息的MetaData对象 + */ public void setMetaInfo(MetaData metaData) { if (metaData != null && metaData.getNotes() != null) { try { @@ -258,6 +324,20 @@ 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; @@ -311,39 +391,87 @@ public class Task extends Node { return SYNC_ACTION_ERROR; } + /** + * 判断任务是否值得保存 + *

+ * 如果任务有元信息,或者名称不为空,或者备注不为空,则值得保存 + *

+ * + * @return 是否值得保存 + */ public boolean isWorthSaving() { return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) || (getNotes() != null && getNotes().trim().length() > 0); } + /** + * 设置任务完成状态 + * + * @param completed 是否完成 + */ public void setCompleted(boolean completed) { this.mCompleted = completed; } + /** + * 设置任务备注 + * + * @param notes 任务备注 + */ public void setNotes(String notes) { this.mNotes = notes; } + /** + * 设置前序兄弟任务 + * + * @param priorSibling 前序兄弟任务 + */ public void setPriorSibling(Task priorSibling) { this.mPriorSibling = priorSibling; } + /** + * 设置父任务列表 + * + * @param parent 父任务列表 + */ public void setParent(TaskList parent) { this.mParent = parent; } + /** + * 获取任务完成状态 + * + * @return 是否完成 + */ public boolean getCompleted() { return this.mCompleted; } + /** + * 获取任务备注 + * + * @return 任务备注 + */ public String getNotes() { return this.mNotes; } + /** + * 获取前序兄弟任务 + * + * @return 前序兄弟任务 + */ public Task getPriorSibling() { return this.mPriorSibling; } + /** + * 获取父任务列表 + * + * @return 父任务列表 + */ public TaskList getParent() { return this.mParent; } diff --git a/src/net/micode/notes/gtask/data/TaskList.java b/src/net/micode/notes/gtask/data/TaskList.java index 4ea21c5..04482c2 100644 --- a/src/net/micode/notes/gtask/data/TaskList.java +++ b/src/net/micode/notes/gtask/data/TaskList.java @@ -30,19 +30,42 @@ import org.json.JSONObject; import java.util.ArrayList; +/** + * TaskList - 任务列表类 + *

+ * 表示Google Task中的任务列表,继承自Node类,包含多个Task子项 + * 负责处理任务列表的创建、更新和同步操作 + *

+ */ public class TaskList extends Node { - private static final String TAG = TaskList.class.getSimpleName(); + private static final String TAG = TaskList.class.getSimpleName(); // 日志标签 - private int mIndex; + private int mIndex; // 任务列表索引 - private ArrayList mChildren; + private ArrayList mChildren; // 子任务列表 + /** + * 构造函数 + *

+ * 初始化任务列表,创建子任务集合 + *

+ */ public TaskList() { super(); mChildren = new ArrayList(); mIndex = 1; } + /** + * 获取创建任务列表的JSON操作 + *

+ * 生成创建任务列表的JSON对象,包含操作类型、操作ID、索引和实体信息 + *

+ * + * @param actionId 操作ID + * @return 创建任务列表的JSON对象 + * @throws ActionFailureException 生成JSON对象失败时抛出 + */ public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); @@ -74,6 +97,16 @@ public class TaskList extends Node { return js; } + /** + * 获取更新任务列表的JSON操作 + *

+ * 生成更新任务列表的JSON对象,包含操作类型、操作ID、任务列表ID和实体信息 + *

+ * + * @param actionId 操作ID + * @return 更新任务列表的JSON对象 + * @throws ActionFailureException 生成JSON对象失败时抛出 + */ public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); @@ -103,6 +136,15 @@ public class TaskList extends Node { return js; } + /** + * 从远程JSON对象设置任务列表内容 + *

+ * 解析远程JSON对象,提取任务列表的ID、最后修改时间和名称 + *

+ * + * @param js 远程JSON对象 + * @throws ActionFailureException 解析JSON对象失败时抛出 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { @@ -129,6 +171,14 @@ public class TaskList extends Node { } } + /** + * 从本地JSON对象设置任务列表内容 + *

+ * 解析本地JSON对象,根据便签类型设置任务列表名称 + *

+ * + * @param js 本地JSON对象 + */ public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); @@ -157,6 +207,14 @@ public class TaskList extends Node { } } + /** + * 从任务列表内容生成本地JSON对象 + *

+ * 根据任务列表名称生成本地JSON对象,包含便签类型和名称信息 + *

+ * + * @return 本地JSON对象,生成失败返回null + */ public JSONObject getLocalJSONFromContent() { try { JSONObject js = new JSONObject(); @@ -183,6 +241,19 @@ 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) { @@ -216,10 +287,27 @@ public class TaskList extends Node { return SYNC_ACTION_ERROR; } + /** + * 获取子任务数量 + *

+ * 返回当前任务列表中的子任务数量 + *

+ * + * @return 子任务数量 + */ public int getChildTaskCount() { return mChildren.size(); } + /** + * 添加子任务 + *

+ * 将任务添加到子任务列表末尾,并设置其前序兄弟和父任务 + *

+ * + * @param task 要添加的子任务 + * @return 是否添加成功 + */ public boolean addChildTask(Task task) { boolean ret = false; if (task != null && !mChildren.contains(task)) { @@ -234,6 +322,16 @@ public class TaskList extends Node { return ret; } + /** + * 在指定位置添加子任务 + *

+ * 将任务添加到子任务列表的指定位置,并更新相关任务的前序兄弟关系 + *

+ * + * @param task 要添加的子任务 + * @param index 要添加的位置索引 + * @return 是否添加成功 + */ public boolean addChildTask(Task task, int index) { if (index < 0 || index > mChildren.size()) { Log.e(TAG, "add child task: invalid index"); @@ -260,6 +358,15 @@ public class TaskList extends Node { return true; } + /** + * 移除子任务 + *

+ * 从子任务列表中移除指定任务,并更新相关任务的前序兄弟关系 + *

+ * + * @param task 要移除的子任务 + * @return 是否移除成功 + */ public boolean removeChildTask(Task task) { boolean ret = false; int index = mChildren.indexOf(task); @@ -281,6 +388,16 @@ public class TaskList extends Node { return ret; } + /** + * 移动子任务到指定位置 + *

+ * 将指定任务移动到子任务列表的指定位置,更新相关任务的前序兄弟关系 + *

+ * + * @param task 要移动的子任务 + * @param index 目标位置索引 + * @return 是否移动成功 + */ public boolean moveChildTask(Task task, int index) { if (index < 0 || index >= mChildren.size()) { @@ -299,6 +416,15 @@ public class TaskList extends Node { return (removeChildTask(task) && addChildTask(task, index)); } + /** + * 根据GID查找子任务 + *

+ * 在子任务列表中查找具有指定GID的任务 + *

+ * + * @param gid 要查找的任务GID + * @return 找到的任务,未找到返回null + */ public Task findChildTaskByGid(String gid) { for (int i = 0; i < mChildren.size(); i++) { Task t = mChildren.get(i); @@ -309,10 +435,28 @@ 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 +465,15 @@ public class TaskList extends Node { return mChildren.get(index); } + /** + * 根据GID获取子任务 + *

+ * 在子任务列表中查找具有指定GID的任务 + *

+ * + * @param gid 要查找的任务GID + * @return 找到的任务,未找到返回null + */ public Task getChilTaskByGid(String gid) { for (Task task : mChildren) { if (task.getGid().equals(gid)) @@ -329,14 +482,38 @@ public class TaskList extends Node { return null; } + /** + * 获取子任务列表 + *

+ * 返回当前任务列表的所有子任务 + *

+ * + * @return 子任务列表 + */ public ArrayList getChildTaskList() { return this.mChildren; } + /** + * 设置任务列表索引 + *

+ * 设置当前任务列表的索引值 + *

+ * + * @param index 要设置的索引值 + */ public void setIndex(int index) { this.mIndex = index; } + /** + * 获取任务列表索引 + *

+ * 返回当前任务列表的索引值 + *

+ * + * @return 当前任务列表的索引值 + */ public int getIndex() { return this.mIndex; } diff --git a/src/net/micode/notes/gtask/exception/ActionFailureException.java b/src/net/micode/notes/gtask/exception/ActionFailureException.java index 15504be..be5c3df 100644 --- a/src/net/micode/notes/gtask/exception/ActionFailureException.java +++ b/src/net/micode/notes/gtask/exception/ActionFailureException.java @@ -16,17 +16,47 @@ package net.micode.notes.gtask.exception; +/** + * ActionFailureException - 操作失败异常类 + *

+ * 用于表示Google Task同步过程中发生的操作失败异常 + * 继承自RuntimeException类,提供了三种构造方法 + *

+ */ public class ActionFailureException extends RuntimeException { - private static final long serialVersionUID = 4425249765923293627L; + private static final long serialVersionUID = 4425249765923293627L; // 序列化版本号 + /** + * 构造方法 + *

+ * 创建一个不带详细消息的ActionFailureException实例 + *

+ */ public ActionFailureException() { super(); } + /** + * 构造方法 + *

+ * 使用指定的详细消息创建ActionFailureException实例 + *

+ * + * @param paramString 详细错误消息 + */ public ActionFailureException(String paramString) { super(paramString); } + /** + * 构造方法 + *

+ * 使用指定的详细消息和原因创建ActionFailureException实例 + *

+ * + * @param paramString 详细错误消息 + * @param paramThrowable 导致此异常的原因 + */ public ActionFailureException(String paramString, Throwable paramThrowable) { super(paramString, paramThrowable); } diff --git a/src/net/micode/notes/gtask/exception/NetworkFailureException.java b/src/net/micode/notes/gtask/exception/NetworkFailureException.java index b08cfb1..31f5583 100644 --- a/src/net/micode/notes/gtask/exception/NetworkFailureException.java +++ b/src/net/micode/notes/gtask/exception/NetworkFailureException.java @@ -16,17 +16,47 @@ package net.micode.notes.gtask.exception; +/** + * NetworkFailureException - 网络失败异常类 + *

+ * 用于表示Google Task同步过程中发生的网络连接失败异常 + * 继承自Exception类,提供了三种构造方法 + *

+ */ public class NetworkFailureException extends Exception { - private static final long serialVersionUID = 2107610287180234136L; + private static final long serialVersionUID = 2107610287180234136L; // 序列化版本号 + /** + * 构造方法 + *

+ * 创建一个不带详细消息的NetworkFailureException实例 + *

+ */ public NetworkFailureException() { super(); } + /** + * 构造方法 + *

+ * 使用指定的详细消息创建NetworkFailureException实例 + *

+ * + * @param paramString 详细错误消息 + */ public NetworkFailureException(String paramString) { super(paramString); } + /** + * 构造方法 + *

+ * 使用指定的详细消息和原因创建NetworkFailureException实例 + *

+ * + * @param paramString 详细错误消息 + * @param paramThrowable 导致此异常的原因 + */ public NetworkFailureException(String paramString, Throwable paramThrowable) { super(paramString, paramThrowable); } diff --git a/src/net/micode/notes/gtask/remote/GTaskASyncTask.java b/src/net/micode/notes/gtask/remote/GTaskASyncTask.java index b3b61e7..5a0ac60 100644 --- a/src/net/micode/notes/gtask/remote/GTaskASyncTask.java +++ b/src/net/micode/notes/gtask/remote/GTaskASyncTask.java @@ -29,22 +29,53 @@ import net.micode.notes.ui.NotesListActivity; import net.micode.notes.ui.NotesPreferenceActivity; +/** + * Google Task 异步同步任务类 + * 继承自 AsyncTask,用于在后台执行 Google Task 同步操作,并通过通知向用户显示同步状态 + */ public class GTaskASyncTask extends AsyncTask { + /** + * Google Task 同步通知的唯一标识符 + */ private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; + /** + * 同步完成监听器接口 + * 用于在同步完成时回调通知调用者 + */ public interface OnCompleteListener { + /** + * 同步完成时调用 + */ void onComplete(); } + /** + * 应用上下文 + */ private Context mContext; + /** + * 通知管理器,用于显示同步状态通知 + */ private NotificationManager mNotifiManager; + /** + * Google Task 管理器实例,用于执行实际的同步操作 + */ private GTaskManager mTaskManager; + /** + * 同步完成监听器 + */ private OnCompleteListener mOnCompleteListener; + /** + * 构造函数 + * @param context 应用上下文 + * @param listener 同步完成监听器 + */ public GTaskASyncTask(Context context, OnCompleteListener listener) { mContext = context; mOnCompleteListener = listener; @@ -53,64 +84,108 @@ 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) { + // 创建通知实例,设置图标、标题和时间 Notification notification = new Notification(R.drawable.notification, mContext .getString(tickerId), System.currentTimeMillis()); - notification.defaults = Notification.DEFAULT_LIGHTS; - notification.flags = Notification.FLAG_AUTO_CANCEL; + notification.defaults = Notification.DEFAULT_LIGHTS; // 使用默认灯光效果 + notification.flags = Notification.FLAG_AUTO_CANCEL; // 点击后自动取消 + + // 根据通知类型设置不同的跳转意图 PendingIntent pendingIntent; if (tickerId != R.string.ticker_success) { + // 同步失败时跳转到设置页面 pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesPreferenceActivity.class), 0); } else { + // 同步成功时跳转到笔记列表页面 pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesListActivity.class), 0); } + + // 设置通知的详细信息并显示 notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, pendingIntent); 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]); + // 如果上下文是同步服务,则发送广播通知进度 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)); } else if (result == GTaskManager.STATE_INTERNAL_ERROR) { + // 内部错误 showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); } else if (result == GTaskManager.STATE_SYNC_CANCELLED) { + // 同步取消 showNotification(R.string.ticker_cancel, mContext .getString(R.string.error_sync_cancelled)); } + + // 如果设置了完成监听器,则在新线程中调用 if (mOnCompleteListener != null) { new Thread(new Runnable() { diff --git a/src/net/micode/notes/gtask/remote/GTaskClient.java b/src/net/micode/notes/gtask/remote/GTaskClient.java index c67dfdf..3b25ecc 100644 --- a/src/net/micode/notes/gtask/remote/GTaskClient.java +++ b/src/net/micode/notes/gtask/remote/GTaskClient.java @@ -61,35 +61,85 @@ import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; +/** + * Google Task 客户端类 + * 用于与 Google Task 服务进行交互,处理任务和任务列表的创建、更新、删除等操作 + * 采用单例模式设计,确保全局只有一个实例 + */ public class GTaskClient { + /** + * 日志标签 + */ private static final String TAG = GTaskClient.class.getSimpleName(); + /** + * Google Task 基础 URL + */ private static final String GTASK_URL = "https://mail.google.com/tasks/"; + /** + * Google Task GET 请求 URL + */ private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; + /** + * Google Task POST 请求 URL + */ private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; + /** + * GTaskClient 单例实例 + */ private static GTaskClient mInstance = null; + /** + * HTTP 客户端,用于发送请求到 Google Task 服务器 + */ private DefaultHttpClient mHttpClient; + /** + * 当前使用的 GET 请求 URL + */ private String mGetUrl; + /** + * 当前使用的 POST 请求 URL + */ private String mPostUrl; + /** + * 客户端版本,用于与 Google Task 服务器通信 + */ private long mClientVersion; + /** + * 登录状态标记 + */ private boolean mLoggedin; + /** + * 上次登录时间,用于判断登录是否过期 + */ private long mLastLoginTime; + /** + * 操作 ID,用于标识每个请求操作 + */ private int mActionId; + /** + * 同步账户信息 + */ private Account mAccount; + /** + * 更新数组,用于批量提交更新操作 + */ private JSONArray mUpdateArray; + /** + * 私有构造函数,防止外部实例化 + */ private GTaskClient() { mHttpClient = null; mGetUrl = GTASK_GET_URL; @@ -102,6 +152,10 @@ public class GTaskClient { mUpdateArray = null; } + /** + * 获取 GTaskClient 单例实例 + * @return GTaskClient 实例 + */ public static synchronized GTaskClient getInstance() { if (mInstance == null) { mInstance = new GTaskClient(); @@ -109,15 +163,19 @@ public class GTaskClient { return mInstance; } + /** + * 登录 Google Task 服务 + * @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 + // 假设登录 cookie 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 +194,7 @@ public class GTaskClient { return false; } - // login with custom domain if necessary + // 如果是自定义域名,使用自定义 URL 登录 if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() .endsWith("googlemail.com"))) { StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); @@ -151,7 +209,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 +222,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); @@ -189,7 +253,7 @@ public class GTaskClient { return null; } - // get the token now + // 获取认证令牌 AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); try { @@ -207,10 +271,16 @@ public class GTaskClient { return authToken; } + /** + * 尝试登录 Google Task 服务 + * 如果首次登录失败,会尝试使令牌失效并重新登录 + * @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,6 +295,11 @@ public class GTaskClient { return true; } + /** + * 登录 Google Task 服务 + * @param authToken 认证令牌 + * @return 登录是否成功 + */ private boolean loginGtask(String authToken) { int timeoutConnection = 10000; int timeoutSocket = 15000; @@ -236,14 +311,14 @@ public class GTaskClient { mHttpClient.setCookieStore(localBasicCookieStore); HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); - // login gtask + // 登录 Google Task try { String loginUrl = mGetUrl + "?auth=" + authToken; HttpGet httpGet = new HttpGet(loginUrl); HttpResponse response = null; response = mHttpClient.execute(httpGet); - // get the cookie now + // 获取认证 Cookie List cookies = mHttpClient.getCookieStore().getCookies(); boolean hasAuthCookie = false; for (Cookie cookie : cookies) { @@ -255,7 +330,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 +347,7 @@ 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 +355,18 @@ 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 +374,12 @@ public class GTaskClient { return httpPost; } + /** + * 从 HTTP 响应实体中获取内容,支持多种编码格式 + * @param entity HTTP 响应实体 + * @return 响应内容字符串 + * @throws IOException IO 异常 + */ private String getResponseContent(HttpEntity entity) throws IOException { String contentEncoding = null; if (entity.getContentEncoding() != null) { @@ -323,6 +412,12 @@ public class GTaskClient { } } + /** + * 发送 HTTP POST 请求到 Google Task 服务器 + * @param js 请求数据的 JSON 对象 + * @return 服务器响应的 JSON 对象 + * @throws NetworkFailureException 网络请求失败时抛出 + */ private JSONObject postRequest(JSONObject js) throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); @@ -360,6 +455,11 @@ public class GTaskClient { } } + /** + * 创建新的 Google Task + * @param task 要创建的任务对象 + * @throws NetworkFailureException 网络请求失败时抛出 + */ public void createTask(Task task) throws NetworkFailureException { commitUpdate(); try { @@ -386,6 +486,11 @@ public class GTaskClient { } } + /** + * 创建新的 Google Task 列表 + * @param tasklist 要创建的任务列表对象 + * @throws NetworkFailureException 网络请求失败时抛出 + */ public void createTaskList(TaskList tasklist) throws NetworkFailureException { commitUpdate(); try { @@ -412,6 +517,10 @@ public class GTaskClient { } } + /** + * 提交更新到 Google Task 服务器 + * @throws NetworkFailureException 网络请求失败时抛出 + */ public void commitUpdate() throws NetworkFailureException { if (mUpdateArray != null) { try { diff --git a/src/net/micode/notes/gtask/remote/GTaskManager.java b/src/net/micode/notes/gtask/remote/GTaskManager.java index d2b4082..b03ba6c 100644 --- a/src/net/micode/notes/gtask/remote/GTaskManager.java +++ b/src/net/micode/notes/gtask/remote/GTaskManager.java @@ -48,45 +48,109 @@ import java.util.Iterator; import java.util.Map; +/** + * Google Task 管理器类 + * 负责处理 Google Task 与本地笔记之间的同步操作 + */ public class GTaskManager { + /** + * 日志标签 + */ private static final String TAG = GTaskManager.class.getSimpleName(); + /** + * 同步成功状态码 + */ public static final int STATE_SUCCESS = 0; + /** + * 网络错误状态码 + */ public static final int STATE_NETWORK_ERROR = 1; + /** + * 内部错误状态码 + */ public static final int STATE_INTERNAL_ERROR = 2; + /** + * 同步进行中状态码 + */ public static final int STATE_SYNC_IN_PROGRESS = 3; + /** + * 同步取消状态码 + */ public static final int STATE_SYNC_CANCELLED = 4; + /** + * GTaskManager 单例实例 + */ private static GTaskManager mInstance = null; + /** + * 用于获取认证令牌的 Activity 上下文 + */ private Activity mActivity; + /** + * 应用上下文 + */ private Context mContext; + /** + * 内容解析器,用于访问本地数据库 + */ private ContentResolver mContentResolver; + /** + * 同步状态标志,表示是否正在进行同步 + */ private boolean mSyncing; + /** + * 取消标志,表示是否已取消同步 + */ private boolean mCancelled; + /** + * Google Task 列表哈希映射,键为列表的 GID + */ private HashMap mGTaskListHashMap; + /** + * Google Task 节点哈希映射,键为节点的 GID + */ private HashMap mGTaskHashMap; + /** + * 元数据哈希映射,键为关联的 GID + */ private HashMap mMetaHashMap; + /** + * 元数据列表 + */ private TaskList mMetaList; + /** + * 本地删除的笔记 ID 集合 + */ private HashSet mLocalDeleteIdMap; + /** + * GID 到本地 ID 的映射 + */ private HashMap mGidToNid; + /** + * 本地 ID 到 GID 的映射 + */ private HashMap mNidToGid; + /** + * 私有构造函数,用于初始化 GTaskManager 实例 + */ private GTaskManager() { mSyncing = false; mCancelled = false; @@ -99,6 +163,10 @@ public class GTaskManager { mNidToGid = new HashMap(); } + /** + * 获取 GTaskManager 单例实例 + * @return GTaskManager 单例实例 + */ public static synchronized GTaskManager getInstance() { if (mInstance == null) { mInstance = new GTaskManager(); @@ -106,11 +174,21 @@ public class GTaskManager { return mInstance; } + /** + * 设置用于获取认证令牌的 Activity 上下文 + * @param activity Activity 上下文 + */ public synchronized void setActivityContext(Activity activity) { - // used for getting authtoken + // 用于获取认证令牌 mActivity = activity; } + /** + * 执行 Google Task 同步操作 + * @param context 应用上下文 + * @param asyncTask 异步任务实例,用于发布同步进度 + * @return 同步结果状态码 + */ public int sync(Context context, GTaskASyncTask asyncTask) { if (mSyncing) { Log.d(TAG, "Sync is in progress"); @@ -131,18 +209,18 @@ public class GTaskManager { GTaskClient client = GTaskClient.getInstance(); client.resetUpdateArray(); - // login google task + // 登录 Google Task 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) { @@ -168,6 +246,11 @@ public class GTaskManager { return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; } + /** + * 初始化 Google Task 列表 + * 从 Google 服务器获取任务列表和任务,并初始化本地映射 + * @throws NetworkFailureException 网络请求失败时抛出 + */ private void initGTaskList() throws NetworkFailureException { if (mCancelled) return; @@ -247,6 +330,11 @@ public class GTaskManager { } } + /** + * 同步内容 + * 处理本地删除的笔记、同步文件夹和笔记,并更新本地同步 ID + * @throws NetworkFailureException 网络请求失败时抛出 + */ private void syncContent() throws NetworkFailureException { int syncType; Cursor c = null; diff --git a/src/net/micode/notes/gtask/remote/GTaskSyncService.java b/src/net/micode/notes/gtask/remote/GTaskSyncService.java index cca36f7..6c89043 100644 --- a/src/net/micode/notes/gtask/remote/GTaskSyncService.java +++ b/src/net/micode/notes/gtask/remote/GTaskSyncService.java @@ -23,25 +23,60 @@ import android.content.Intent; import android.os.Bundle; import android.os.IBinder; +/** + * Google Task 同步服务类 + * 继承自 Service,用于管理 Google Task 同步任务的启动、取消和状态广播 + */ public class GTaskSyncService extends Service { + /** + * 操作类型的 Intent 额外数据键名 + */ public final static String ACTION_STRING_NAME = "sync_action_type"; + /** + * 启动同步操作类型 + */ public final static int ACTION_START_SYNC = 0; + /** + * 取消同步操作类型 + */ public final static int ACTION_CANCEL_SYNC = 1; + /** + * 无效操作类型 + */ public final static int ACTION_INVALID = 2; + /** + * 同步服务广播的名称 + */ public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; + /** + * 同步服务广播中表示是否正在同步的键名 + */ public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; + /** + * 同步服务广播中表示同步进度消息的键名 + */ public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; + /** + * 当前正在执行的同步任务实例 + */ private static GTaskASyncTask mSyncTask = null; + /** + * 当前同步进度消息 + */ private static String mSyncProgress = ""; + /** + * 启动同步任务 + * 如果当前没有正在执行的同步任务,则创建新的同步任务并执行 + */ private void startSync() { if (mSyncTask == null) { mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { @@ -56,6 +91,10 @@ public class GTaskSyncService extends Service { } } + /** + * 取消同步任务 + * 如果当前有正在执行的同步任务,则取消它 + */ private void cancelSync() { if (mSyncTask != null) { mSyncTask.cancelSync(); diff --git a/src/net/micode/notes/model/Note.java b/src/net/micode/notes/model/Note.java index 6706cf6..d13e7f1 100644 --- a/src/net/micode/notes/model/Note.java +++ b/src/net/micode/notes/model/Note.java @@ -34,12 +34,34 @@ import net.micode.notes.data.Notes.TextNote; import java.util.ArrayList; +/** + * 小米便签的业务逻辑模型类 + * 用于管理便签的数据和操作,包括创建、修改、同步等 + * 支持普通文本便签和通话记录便签 + */ 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 当创建便签失败时抛出 */ public static synchronized long getNewNoteId(Context context, long folderId) { // Create a new note in the database @@ -65,93 +87,168 @@ public class Note { return noteId; } + /** + * 构造方法,初始化便签对象 + */ public Note() { - mNoteDiffValues = new ContentValues(); - mNoteData = new NoteData(); + mNoteDiffValues = new ContentValues(); // 初始化便签差异值 + mNoteData = new NoteData(); // 初始化便签数据 } + /** + * 设置便签的属性值 + * @param key 属性名 + * @param value 属性值 + */ public void setNoteValue(String key, String value) { - mNoteDiffValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + mNoteDiffValues.put(key, value); // 存储属性变化 + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记为本地修改 + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 更新修改时间 } + /** + * 设置文本便签的数据 + * @param key 数据键 + * @param value 数据值 + */ public void setTextData(String key, String value) { mNoteData.setTextData(key, value); } + /** + * 设置文本便签的数据ID + * @param id 文本便签数据ID + */ public void setTextDataId(long id) { mNoteData.setTextDataId(id); } + /** + * 获取文本便签的数据ID + * @return 文本便签数据ID + */ public long getTextDataId() { return mNoteData.mTextDataId; } + /** + * 设置通话记录便签的数据ID + * @param id 通话记录便签数据ID + */ public void setCallDataId(long id) { mNoteData.setCallDataId(id); } + /** + * 设置通话记录便签的数据 + * @param key 数据键 + * @param value 数据值 + */ public void setCallData(String key, String value) { mNoteData.setCallData(key, value); } + /** + * 检查便签是否在本地被修改 + * @return 如果便签在本地被修改则返回true,否则返回false + */ public boolean isLocalModified() { return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); } + /** + * 将便签的修改同步到数据库 + * @param context 上下文对象 + * @param noteId 便签ID + * @return 同步是否成功 + * @throws IllegalArgumentException 当便签ID无效时抛出 + */ public boolean syncNote(Context context, long noteId) { 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(); + mNoteDiffValues.clear(); // 清空便签差异值 + // 如果便签数据被修改,将数据同步到数据库 if (mNoteData.isLocalModified() && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { - return false; + return false; // 数据同步失败,返回false } - return true; + 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(); - mTextDataId = 0; - mCallDataId = 0; + mTextDataValues = new ContentValues(); // 初始化文本便签差异值 + mCallDataValues = new ContentValues(); // 初始化通话记录便签差异值 + mTextDataId = 0; // 初始化文本便签数据ID + mCallDataId = 0; // 初始化通话记录便签数据ID } + /** + * 检查便签数据是否在本地被修改 + * @return 如果便签数据在本地被修改则返回true,否则返回false + */ boolean isLocalModified() { return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; } + /** + * 设置文本便签的数据ID + * @param id 文本便签数据ID + * @throws IllegalArgumentException 当ID小于等于0时抛出 + */ void setTextDataId(long id) { if(id <= 0) { throw new IllegalArgumentException("Text data id should larger than 0"); @@ -159,6 +256,11 @@ public class Note { mTextDataId = id; } + /** + * 设置通话记录便签的数据ID + * @param id 通话记录便签数据ID + * @throws IllegalArgumentException 当ID小于等于0时抛出 + */ void setCallDataId(long id) { if (id <= 0) { throw new IllegalArgumentException("Call data id should larger than 0"); @@ -166,88 +268,112 @@ public class Note { mCallDataId = id; } + /** + * 设置通话记录便签的数据 + * @param key 数据键 + * @param value 数据值 + */ void setCallData(String key, String value) { - mCallDataValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + mCallDataValues.put(key, value); // 存储通话记录数据变化 + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记为本地修改 + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 更新修改时间 } + /** + * 设置文本便签的数据 + * @param key 数据键 + * @param value 数据值 + */ void setTextData(String key, String value) { - mTextDataValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + mTextDataValues.put(key, value); // 存储文本数据变化 + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记为本地修改 + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 更新修改时间 } + /** + * 将便签数据推送到ContentResolver + * @param context 上下文对象 + * @param noteId 便签ID + * @return 操作成功返回null,失败返回null + * @throws IllegalArgumentException 当便签ID无效时抛出 + */ Uri pushIntoContentResolver(Context context, long noteId) { /** - * Check for safety + * 安全性检查 */ if (noteId <= 0) { throw new IllegalArgumentException("Wrong note id:" + noteId); } + // 创建ContentProvider操作列表,用于批量处理数据操作 ArrayList operationList = new ArrayList(); ContentProviderOperation.Builder builder = null; + // 处理文本便签数据 if(mTextDataValues.size() > 0) { - mTextDataValues.put(DataColumns.NOTE_ID, noteId); + mTextDataValues.put(DataColumns.NOTE_ID, noteId); // 设置便签ID if (mTextDataId == 0) { - mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); - Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, - mTextDataValues); + // 插入新的文本便签数据 + mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); // 设置MIME类型 + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mTextDataValues); try { - setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); + setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); // 获取新插入的数据ID } catch (NumberFormatException e) { Log.e(TAG, "Insert new text data fail with noteId" + noteId); mTextDataValues.clear(); - return null; + return null; // 插入失败,返回null } } else { + // 更新现有文本便签数据 builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( Notes.CONTENT_DATA_URI, mTextDataId)); builder.withValues(mTextDataValues); operationList.add(builder.build()); } - mTextDataValues.clear(); + mTextDataValues.clear(); // 清空文本便签差异值 } + // 处理通话记录便签数据 if(mCallDataValues.size() > 0) { - mCallDataValues.put(DataColumns.NOTE_ID, noteId); + mCallDataValues.put(DataColumns.NOTE_ID, noteId); // 设置便签ID if (mCallDataId == 0) { - mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); - Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, - mCallDataValues); + // 插入新的通话记录便签数据 + mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); // 设置MIME类型 + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mCallDataValues); try { - setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); + setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); // 获取新插入的数据ID } catch (NumberFormatException e) { Log.e(TAG, "Insert new call data fail with noteId" + noteId); mCallDataValues.clear(); - return null; + return null; // 插入失败,返回null } } else { + // 更新现有通话记录便签数据 builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( Notes.CONTENT_DATA_URI, mCallDataId)); builder.withValues(mCallDataValues); operationList.add(builder.build()); } - mCallDataValues.clear(); + mCallDataValues.clear(); // 清空通话记录便签差异值 } + // 如果有操作需要执行,应用批量操作 if (operationList.size() > 0) { try { ContentProviderResult[] results = context.getContentResolver().applyBatch( Notes.AUTHORITY, operationList); + // 返回操作结果,成功返回便签URI,失败返回null return (results == null || results.length == 0 || results[0] == null) ? null : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); } catch (RemoteException e) { Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - return null; + return null; // 远程异常,返回null } catch (OperationApplicationException e) { Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - return null; + return null; // 操作应用异常,返回null } } - return null; + return null; // 没有操作需要执行,返回null } } } diff --git a/src/net/micode/notes/model/WorkingNote.java b/src/net/micode/notes/model/WorkingNote.java index be081e4..91c98fd 100644 --- a/src/net/micode/notes/model/WorkingNote.java +++ b/src/net/micode/notes/model/WorkingNote.java @@ -32,105 +32,170 @@ import net.micode.notes.data.Notes.TextNote; import net.micode.notes.tool.ResourceParser.NoteBgResources; +/** + * 小米便签的工作便签类 + * 用于管理便签的编辑状态和设置 + * 是便签编辑界面和数据库之间的桥梁 + */ public class WorkingNote { - // Note for the working note + /** + * 便签对象,用于处理便签的数据操作 + */ private Note mNote; - // Note Id + + /** + * 便签ID + */ private long mNoteId; - // Note content + + /** + * 便签内容 + */ private String mContent; - // Note mode + + /** + * 便签模式:0-普通模式,1- checklist模式 + */ 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, - DataColumns.MIME_TYPE, - DataColumns.DATA1, - DataColumns.DATA2, - DataColumns.DATA3, - DataColumns.DATA4, + DataColumns.ID, // 数据ID + DataColumns.CONTENT, // 数据内容 + DataColumns.MIME_TYPE, // 数据类型 + DataColumns.DATA1, // 扩展数据1 + DataColumns.DATA2, // 扩展数据2 + DataColumns.DATA3, // 扩展数据3 + DataColumns.DATA4, // 扩展数据4 }; + /** + * 便签投影数组,用于从数据库中查询便签基本信息 + */ public static final String[] NOTE_PROJECTION = new String[] { - NoteColumns.PARENT_ID, - NoteColumns.ALERTED_DATE, - NoteColumns.BG_COLOR_ID, - NoteColumns.WIDGET_ID, - NoteColumns.WIDGET_TYPE, - NoteColumns.MODIFIED_DATE + NoteColumns.PARENT_ID, // 父文件夹ID + NoteColumns.ALERTED_DATE, // 提醒日期 + NoteColumns.BG_COLOR_ID, // 背景颜色ID + NoteColumns.WIDGET_ID, // 小部件ID + NoteColumns.WIDGET_TYPE, // 小部件类型 + 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 + /** + * 数据投影列索引 + */ + private static final int DATA_ID_COLUMN = 0; // 数据ID列索引 + 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; // 父文件夹ID列索引 + private static final int NOTE_ALERTED_DATE_COLUMN = 1; // 提醒日期列索引 + private static final int NOTE_BG_COLOR_ID_COLUMN = 2; // 背景颜色ID列索引 + private static final int NOTE_WIDGET_ID_COLUMN = 3; // 小部件ID列索引 + private static final int NOTE_WIDGET_TYPE_COLUMN = 4; // 小部件类型列索引 + private static final int NOTE_MODIFIED_DATE_COLUMN = 5; // 修改日期列索引 + + /** + * 构造方法,创建一个新的便签 + * @param context 上下文对象 + * @param folderId 文件夹ID,新便签将存储在此文件夹中 + */ private WorkingNote(Context context, long folderId) { mContext = context; - mAlertDate = 0; - mModifiedDate = System.currentTimeMillis(); - mFolderId = folderId; - mNote = new Note(); - mNoteId = 0; - mIsDeleted = false; - mMode = 0; - mWidgetType = Notes.TYPE_WIDGET_INVALIDE; + mAlertDate = 0; // 初始化为没有提醒 + mModifiedDate = System.currentTimeMillis(); // 初始化修改时间为当前时间 + mFolderId = folderId; // 设置文件夹ID + mNote = new Note(); // 创建便签对象 + mNoteId = 0; // 新便签ID为0 + mIsDeleted = false; // 初始化为未删除 + mMode = 0; // 初始化为普通模式 + 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; - mFolderId = folderId; - mIsDeleted = false; - mNote = new Note(); - loadNote(); + mNoteId = noteId; // 设置便签ID + mFolderId = folderId; // 设置文件夹ID + mIsDeleted = false; // 初始化为未删除 + mNote = new Note(); // 创建便签对象 + 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 +208,15 @@ public class WorkingNote { Log.e(TAG, "No note with id:" + mNoteId); throw new IllegalArgumentException("Unable to find note with id " + mNoteId); } - loadNoteData(); + 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) @@ -157,10 +227,12 @@ public class WorkingNote { 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 +246,15 @@ public class WorkingNote { } } + /** + * 创建一个空便签 + * @param context 上下文对象 + * @param folderId 文件夹ID + * @param widgetId 小部件ID + * @param widgetType 小部件类型 + * @param defaultBgColorId 默认背景颜色ID + * @return 空便签对象 + */ public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, int widgetType, int defaultBgColorId) { WorkingNote note = new WorkingNote(context, folderId); @@ -183,12 +264,23 @@ public class WorkingNote { return note; } + /** + * 加载指定ID的便签 + * @param context 上下文对象 + * @param id 便签ID + * @return 加载的便签对象 + */ public static WorkingNote load(Context context, long id) { return new WorkingNote(context, id, 0); } + /** + * 保存便签 + * @return 保存是否成功 + */ 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 +288,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 +305,23 @@ public class WorkingNote { } } + /** + * 检查便签是否存在于数据库中 + * @return 如果便签ID大于0,则返回true,否则返回false + */ public boolean existInDatabase() { return mNoteId > 0; } + /** + * 检查便签是否值得保存 + * @return 如果便签值得保存则返回true,否则返回false + */ private boolean isWorthSaving() { + // 以下情况不保存: + // 1. 便签被标记为删除 + // 2. 便签不存在于数据库中且内容为空 + // 3. 便签存在于数据库中但没有被修改 if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) || (existInDatabase() && !mNote.isLocalModified())) { return false; @@ -225,31 +330,51 @@ 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; mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); } + // 通知监听器提醒日期变化 if (mNoteSettingStatusListener != null) { mNoteSettingStatusListener.onClockAlertChanged(date, set); } } + /** + * 标记便签为删除或恢复删除 + * @param mark 是否标记为删除 + */ public void markDeleted(boolean mark) { mIsDeleted = mark; + // 如果便签关联了小部件,通知监听器小部件变化 if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { mNoteSettingStatusListener.onWidgetChanged(); } } + /** + * 设置背景颜色ID + * @param id 背景颜色ID + */ public void setBgColorId(int id) { if (id != mBgColorId) { mBgColorId = id; + // 通知监听器背景颜色变化 if (mNoteSettingStatusListener != null) { mNoteSettingStatusListener.onBackgroundColorChanged(); } @@ -257,8 +382,13 @@ public class WorkingNote { } } + /** + * 设置便签模式(普通模式或checklist模式) + * @param mode 模式:0-普通模式,1-checklist模式 + */ public void setCheckListMode(int mode) { if (mMode != mode) { + // 通知监听器模式变化 if (mNoteSettingStatusListener != null) { mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); } @@ -267,6 +397,10 @@ public class WorkingNote { } } + /** + * 设置小部件类型 + * @param type 小部件类型 + */ public void setWidgetType(int type) { if (type != mWidgetType) { mWidgetType = type; @@ -274,6 +408,10 @@ public class WorkingNote { } } + /** + * 设置小部件ID + * @param id 小部件ID + */ public void setWidgetId(int id) { if (id != mWidgetId) { mWidgetId = id; @@ -281,6 +419,10 @@ public class WorkingNote { } } + /** + * 设置便签内容 + * @param text 便签内容 + */ public void setWorkingText(String text) { if (!TextUtils.equals(mContent, text)) { mContent = text; @@ -288,80 +430,139 @@ 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 便签模式:0-普通模式,1-checklist模式 + */ 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 + * 当在checklist模式和普通模式之间切换时调用 + * @param oldMode 切换前的模式 + * @param newMode 切换后的模式 */ void onCheckListModeChanged(int oldMode, int newMode); } diff --git a/src/net/micode/notes/tool/BackupUtils.java b/src/net/micode/notes/tool/BackupUtils.java index 39f6ec4..7a98c81 100644 --- a/src/net/micode/notes/tool/BackupUtils.java +++ b/src/net/micode/notes/tool/BackupUtils.java @@ -36,11 +36,31 @@ import java.io.IOException; import java.io.PrintStream; +/** + * BackupUtils - 备份工具类 + *

+ * 负责便签数据的备份和导出功能,使用单例模式实现 + * 支持将便签导出为文本文件,存储到SD卡 + *

+ * + * @author MiCode Open Source Community + * @version 1.0 + */ public class BackupUtils { - private static final String TAG = "BackupUtils"; - // Singleton stuff + private static final String TAG = "BackupUtils"; // 日志标签 + + // 单例模式实现 private static BackupUtils sInstance; + /** + * 获取单例实例 + *

+ * 线程安全的单例获取方法 + *

+ * + * @param context 上下文对象 + * @return BackupUtils实例 + */ public static synchronized BackupUtils getInstance(Context context) { if (sInstance == null) { sInstance = new BackupUtils(context); @@ -49,43 +69,91 @@ 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; + private TextExport mTextExport; // 文本导出实例 + /** + * 构造函数(私有,单例模式) + *

+ * 初始化文本导出实例 + *

+ * + * @param context 上下文对象 + */ private BackupUtils(Context context) { mTextExport = new TextExport(context); } + /** + * 外部存储是否可用 + *

+ * 检查SD卡是否已挂载 + *

+ * + * @return 是否可用 + */ private static boolean externalStorageAvailable() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } + /** + * 导出为文本文件 + *

+ * 将便签数据导出到SD卡上的文本文件 + *

+ * + * @return 导出状态 + */ public int exportToText() { return mTextExport.exportToText(); } + /** + * 获取导出的文本文件名 + *

+ * 返回最近一次导出的文本文件名 + *

+ * + * @return 文件名 + */ public String getExportedTextFileName() { return mTextExport.mFileName; } + /** + * 获取导出的文本文件目录 + *

+ * 返回最近一次导出的文本文件目录 + *

+ * + * @return 文件目录 + */ public String getExportedTextFileDir() { return mTextExport.mFileDirectory; } + /** + * TextExport - 文本导出内部类 + *

+ * 负责将便签数据导出为可读文本格式,处理数据库查询、数据格式化和文件写入 + *

+ */ private static class TextExport { + /** + * 便签查询投影列 + */ private static final String[] NOTE_PROJECTION = { NoteColumns.ID, NoteColumns.MODIFIED_DATE, @@ -93,12 +161,14 @@ 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,23 +178,31 @@ 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; + 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; @@ -132,28 +210,43 @@ public class BackupUtils { 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 + folderId }, null); 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,12 +256,19 @@ public class BackupUtils { } /** - * Export note identified by id to a print stream + * 将指定便签导出为文本 + *

+ * 查询指定便签的详细数据,并导出到打印流 + *

+ * + * @param noteId 便签ID + * @param ps 打印流对象 */ private void exportNoteToText(String noteId, PrintStream ps) { + // 查询该便签的数据 Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { - noteId + noteId }, null); if (dataCursor != null) { @@ -176,7 +276,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 +285,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,10 +306,11 @@ public class BackupUtils { } dataCursor.close(); } - // print a line separator between note + + // 打印便签之间的分隔线 try { ps.write(new byte[] { - Character.LINE_SEPARATOR, Character.LETTER_NUMBER + Character.LINE_SEPARATOR, Character.LINE_SEPARATOR }); } catch (IOException e) { Log.e(TAG, e.toString()); @@ -216,20 +318,28 @@ public class BackupUtils { } /** - * Note will be exported as text which is user readable + * 将便签导出为可读文本 + *

+ * 执行实际的导出操作,包括检查SD卡状态、创建文件和写入数据 + *

+ * + * @return 导出状态 */ public int exportToText() { + // 检查SD卡是否可用 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 +350,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 +367,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 +380,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,7 +393,12 @@ public class BackupUtils { } /** - * Get a print stream pointed to the file {@generateExportedTextFile} + * 获取文本导出的打印流 + *

+ * 创建导出文件,并获取对应的打印流 + *

+ * + * @return 打印流对象,失败则返回null */ private PrintStream getExportToTextPrintStream() { File file = generateFileMountedOnSDcard(mContext, R.string.file_path, @@ -310,7 +425,15 @@ public class BackupUtils { } /** - * Generate the text file to store imported data + * 在SD卡上生成文件 + *

+ * 创建文件夹和文件,用于存储导出的数据 + *

+ * + * @param context 上下文对象 + * @param filePathResId 文件路径资源ID + * @param fileNameFormatResId 文件名格式资源ID + * @return 创建的文件对象,失败则返回null */ private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { StringBuilder sb = new StringBuilder(); diff --git a/src/net/micode/notes/tool/DataUtils.java b/src/net/micode/notes/tool/DataUtils.java index 2a14982..5a8b867 100644 --- a/src/net/micode/notes/tool/DataUtils.java +++ b/src/net/micode/notes/tool/DataUtils.java @@ -35,8 +35,29 @@ import java.util.ArrayList; import java.util.HashSet; +/** + * DataUtils - 数据工具类 + *

+ * 提供便签数据的各种操作方法,包括批量删除、移动、查询、验证等 + * 所有方法均为静态方法,方便直接调用 + *

+ * + * @author MiCode Open Source Community + * @version 1.0 + */ public class DataUtils { - public static final String TAG = "DataUtils"; + public static final String TAG = "DataUtils"; // 日志标签 + + /** + * 批量删除便签 + *

+ * 通过ContentProvider批量删除指定ID的便签 + *

+ * + * @param resolver ContentResolver对象 + * @param ids 要删除的便签ID集合 + * @return 删除是否成功 + */ public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { if (ids == null) { Log.d(TAG, "the ids is null"); @@ -72,6 +93,17 @@ public class DataUtils { return false; } + /** + * 移动便签到指定文件夹 + *

+ * 更新便签的父文件夹ID,并标记为本地修改 + *

+ * + * @param resolver ContentResolver对象 + * @param id 便签ID + * @param srcFolderId 源文件夹ID + * @param desFolderId 目标文件夹ID + */ public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { ContentValues values = new ContentValues(); values.put(NoteColumns.PARENT_ID, desFolderId); @@ -80,6 +112,17 @@ public class DataUtils { resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); } + /** + * 批量移动便签到指定文件夹 + *

+ * 通过ContentProvider批量更新便签的父文件夹ID + *

+ * + * @param resolver ContentResolver对象 + * @param ids 要移动的便签ID集合 + * @param folderId 目标文件夹ID + * @return 移动是否成功 + */ public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids, long folderId) { if (ids == null) { @@ -112,7 +155,13 @@ public class DataUtils { } /** - * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} + * 获取用户文件夹数量 + *

+ * 获取除系统文件夹外的所有文件夹数量 + *

+ * + * @param resolver ContentResolver对象 + * @return 用户文件夹数量 */ public static int getUserFolderCount(ContentResolver resolver) { Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, @@ -136,6 +185,17 @@ public class DataUtils { return count; } + /** + * 检查便签是否在数据库中可见 + *

+ * 检查指定类型的便签是否存在于数据库中且不在回收站 + *

+ * + * @param resolver ContentResolver对象 + * @param noteId 便签ID + * @param type 便签类型 + * @return 是否可见 + */ public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, @@ -153,6 +213,16 @@ public class DataUtils { return exist; } + /** + * 检查便签是否存在于数据库中 + *

+ * 检查指定ID的便签是否存在于数据库中 + *

+ * + * @param resolver ContentResolver对象 + * @param noteId 便签ID + * @return 是否存在 + */ public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, null, null, null); @@ -167,6 +237,16 @@ public class DataUtils { return exist; } + /** + * 检查数据是否存在于数据库中 + *

+ * 检查指定ID的数据是否存在于数据库中 + *

+ * + * @param resolver ContentResolver对象 + * @param dataId 数据ID + * @return 是否存在 + */ public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null, null, null, null); @@ -181,6 +261,16 @@ public class DataUtils { return exist; } + /** + * 检查可见文件夹名称是否存在 + *

+ * 检查除回收站外的文件夹中是否已存在指定名称的文件夹 + *

+ * + * @param resolver ContentResolver对象 + * @param name 文件夹名称 + * @return 是否存在 + */ public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + @@ -197,6 +287,16 @@ public class DataUtils { return exist; } + /** + * 获取文件夹下的便签小部件 + *

+ * 查询指定文件夹下所有便签关联的小部件信息 + *

+ * + * @param resolver ContentResolver对象 + * @param folderId 文件夹ID + * @return 小部件属性集合 + */ public static HashSet getFolderNoteWidget(ContentResolver resolver, long folderId) { Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, @@ -224,6 +324,16 @@ public class DataUtils { return set; } + /** + * 根据便签ID获取电话号码 + *

+ * 查询指定通话便签的电话号码 + *

+ * + * @param resolver ContentResolver对象 + * @param noteId 便签ID + * @return 电话号码,不存在则返回空字符串 + */ public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, new String [] { CallNote.PHONE_NUMBER }, @@ -243,6 +353,17 @@ public class DataUtils { return ""; } + /** + * 根据电话号码和通话日期获取便签ID + *

+ * 查询指定电话号码和通话日期对应的通话便签ID + *

+ * + * @param resolver ContentResolver对象 + * @param phoneNumber 电话号码 + * @param callDate 通话日期 + * @return 便签ID,不存在则返回0 + */ public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, new String [] { CallNote.NOTE_ID }, @@ -264,6 +385,17 @@ public class DataUtils { return 0; } + /** + * 根据便签ID获取摘要 + *

+ * 查询指定便签的摘要内容 + *

+ * + * @param resolver ContentResolver对象 + * @param noteId 便签ID + * @return 便签摘要 + * @throws IllegalArgumentException 当便签不存在时抛出 + */ public static String getSnippetById(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, new String [] { NoteColumns.SNIPPET }, @@ -282,6 +414,15 @@ public class DataUtils { throw new IllegalArgumentException("Note is not found with id: " + noteId); } + /** + * 格式化摘要 + *

+ * 去除摘要前后空格,并只保留第一行 + *

+ * + * @param snippet 原始摘要 + * @return 格式化后的摘要 + */ public static String getFormattedSnippet(String snippet) { if (snippet != null) { snippet = snippet.trim(); diff --git a/src/net/micode/notes/tool/GTaskStringUtils.java b/src/net/micode/notes/tool/GTaskStringUtils.java index 666b729..8ffaa78 100644 --- a/src/net/micode/notes/tool/GTaskStringUtils.java +++ b/src/net/micode/notes/tool/GTaskStringUtils.java @@ -16,98 +16,252 @@ package net.micode.notes.tool; +/** + * GTaskStringUtils - GTask字符串工具类 + *

+ * 包含GTask(Google Task)同步功能所需的所有JSON字段名和常量定义 + * 用于便签数据与Google Task服务之间的同步通信 + *

+ * + * @author MiCode Open Source Community + * @version 1.0 + */ public class GTaskStringUtils { + // ------------------- GTask JSON字段名常量 ------------------- + + /** + * JSON字段 - 操作ID + */ public final static String GTASK_JSON_ACTION_ID = "action_id"; + /** + * JSON字段 - 操作列表 + */ public final static String GTASK_JSON_ACTION_LIST = "action_list"; + /** + * JSON字段 - 操作类型 + */ public final static String GTASK_JSON_ACTION_TYPE = "action_type"; + /** + * JSON字段值 - 操作类型:创建 + */ public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; + /** + * JSON字段值 - 操作类型:获取所有 + */ public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; + /** + * JSON字段值 - 操作类型:移动 + */ public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; + /** + * JSON字段值 - 操作类型:更新 + */ public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; + /** + * JSON字段 - 创建者ID + */ public final static String GTASK_JSON_CREATOR_ID = "creator_id"; + /** + * JSON字段 - 子实体 + */ public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; + /** + * JSON字段 - 客户端版本 + */ public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; + /** + * JSON字段 - 是否已完成 + */ public final static String GTASK_JSON_COMPLETED = "completed"; + /** + * JSON字段 - 当前列表ID + */ public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; + /** + * JSON字段 - 默认列表ID + */ public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; + /** + * JSON字段 - 是否已删除 + */ public final static String GTASK_JSON_DELETED = "deleted"; + /** + * JSON字段 - 目标列表 + */ public final static String GTASK_JSON_DEST_LIST = "dest_list"; + /** + * JSON字段 - 目标父项 + */ public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; + /** + * JSON字段 - 目标父项类型 + */ public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; + /** + * JSON字段 - 实体增量 + */ public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; + /** + * JSON字段 - 实体类型 + */ public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; + /** + * JSON字段 - 获取已删除项 + */ public final static String GTASK_JSON_GET_DELETED = "get_deleted"; + /** + * JSON字段 - ID + */ public final static String GTASK_JSON_ID = "id"; + /** + * JSON字段 - 索引 + */ public final static String GTASK_JSON_INDEX = "index"; + /** + * JSON字段 - 最后修改时间 + */ public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; + /** + * JSON字段 - 最新同步点 + */ public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; + /** + * JSON字段 - 列表ID + */ public final static String GTASK_JSON_LIST_ID = "list_id"; + /** + * JSON字段 - 列表 + */ public final static String GTASK_JSON_LISTS = "lists"; + /** + * JSON字段 - 名称 + */ public final static String GTASK_JSON_NAME = "name"; + /** + * JSON字段 - 新ID + */ public final static String GTASK_JSON_NEW_ID = "new_id"; + /** + * JSON字段 - 备注 + */ public final static String GTASK_JSON_NOTES = "notes"; + /** + * JSON字段 - 父项ID + */ public final static String GTASK_JSON_PARENT_ID = "parent_id"; + /** + * JSON字段 - 前一个兄弟项ID + */ public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; + /** + * JSON字段 - 结果 + */ public final static String GTASK_JSON_RESULTS = "results"; + /** + * JSON字段 - 源列表 + */ public final static String GTASK_JSON_SOURCE_LIST = "source_list"; + /** + * JSON字段 - 任务 + */ public final static String GTASK_JSON_TASKS = "tasks"; + /** + * JSON字段 - 类型 + */ public final static String GTASK_JSON_TYPE = "type"; + /** + * JSON字段值 - 类型:组 + */ public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; + /** + * JSON字段值 - 类型:任务 + */ public final static String GTASK_JSON_TYPE_TASK = "TASK"; + /** + * JSON字段 - 用户 + */ public final static String GTASK_JSON_USER = "user"; + // ------------------- 文件夹相关常量 ------------------- + + /** + * MIUI文件夹前缀 + */ public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; + /** + * 默认文件夹名称 + */ public final static String FOLDER_DEFAULT = "Default"; + /** + * 通话记录文件夹名称 + */ public final static String FOLDER_CALL_NOTE = "Call_Note"; + /** + * 元数据文件夹名称 + */ public final static String FOLDER_META = "METADATA"; + // ------------------- 元数据相关常量 ------------------- + + /** + * 元数据头 - GTask ID + */ public final static String META_HEAD_GTASK_ID = "meta_gid"; + /** + * 元数据头 - 便签 + */ public final static String META_HEAD_NOTE = "meta_note"; + /** + * 元数据头 - 数据 + */ public final static String META_HEAD_DATA = "meta_data"; + /** + * 元数据便签名称 + */ public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; } diff --git a/src/net/micode/notes/tool/ResourceParser.java b/src/net/micode/notes/tool/ResourceParser.java index 1ad3ad6..402dab1 100644 --- a/src/net/micode/notes/tool/ResourceParser.java +++ b/src/net/micode/notes/tool/ResourceParser.java @@ -22,24 +22,87 @@ import android.preference.PreferenceManager; import net.micode.notes.R; import net.micode.notes.ui.NotesPreferenceActivity; +/** + * ResourceParser - 资源解析工具类 + *

+ * 提供便签背景颜色、字体大小等常量定义,以及背景资源获取方法 + * 包含了便签编辑背景、列表背景、小部件背景等资源的管理 + *

+ * + * @author MiCode Open Source Community + * @version 1.0 + */ public class ResourceParser { + // ------------------- 颜色常量 ------------------- + + /** + * 颜色常量 - 黄色 + */ public static final int YELLOW = 0; + + /** + * 颜色常量 - 蓝色 + */ public static final int BLUE = 1; + + /** + * 颜色常量 - 白色 + */ public static final int WHITE = 2; + + /** + * 颜色常量 - 绿色 + */ public static final int GREEN = 3; + + /** + * 颜色常量 - 红色 + */ public static final int RED = 4; + /** + * 默认背景颜色 + */ public static final int BG_DEFAULT_COLOR = YELLOW; + // ------------------- 字体大小常量 ------------------- + + /** + * 字体大小 - 小 + */ public static final int TEXT_SMALL = 0; + + /** + * 字体大小 - 中 + */ public static final int TEXT_MEDIUM = 1; + + /** + * 字体大小 - 大 + */ public static final int TEXT_LARGE = 2; + + /** + * 字体大小 - 超大 + */ public static final int TEXT_SUPER = 3; + /** + * 默认字体大小 + */ public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; + /** + * NoteBgResources - 便签背景资源类 + *

+ * 管理便签编辑界面的背景资源,包括便签主体和标题的背景 + *

+ */ public static class NoteBgResources { + /** + * 便签编辑背景资源数组 + */ private final static int [] BG_EDIT_RESOURCES = new int [] { R.drawable.edit_yellow, R.drawable.edit_blue, @@ -48,6 +111,9 @@ public class ResourceParser { R.drawable.edit_red }; + /** + * 便签编辑标题背景资源数组 + */ private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { R.drawable.edit_title_yellow, R.drawable.edit_title_blue, @@ -56,15 +122,42 @@ public class ResourceParser { R.drawable.edit_title_red }; + /** + * 获取便签背景资源 + *

+ * 根据颜色ID获取对应的便签编辑背景资源 + *

+ * + * @param id 颜色ID + * @return 背景资源ID + */ public static int getNoteBgResource(int id) { return BG_EDIT_RESOURCES[id]; } + /** + * 获取便签标题背景资源 + *

+ * 根据颜色ID获取对应的便签编辑标题背景资源 + *

+ * + * @param id 颜色ID + * @return 标题背景资源ID + */ public static int getNoteTitleBgResource(int id) { return BG_EDIT_TITLE_RESOURCES[id]; } } + /** + * 获取默认背景ID + *

+ * 根据用户偏好设置获取默认背景颜色ID,如果启用随机背景,则返回随机ID + *

+ * + * @param context 上下文对象 + * @return 默认背景颜色ID + */ public static int getDefaultBgId(Context context) { if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { @@ -74,7 +167,16 @@ public class ResourceParser { } } + /** + * NoteItemBgResources - 便签列表项背景资源类 + *

+ * 管理便签列表项的背景资源,包括列表中的第一个项、中间项、最后一个项和单项 + *

+ */ public static class NoteItemBgResources { + /** + * 列表第一项背景资源数组 + */ private final static int [] BG_FIRST_RESOURCES = new int [] { R.drawable.list_yellow_up, R.drawable.list_blue_up, @@ -83,6 +185,9 @@ public class ResourceParser { R.drawable.list_red_up }; + /** + * 列表中间项背景资源数组 + */ private final static int [] BG_NORMAL_RESOURCES = new int [] { R.drawable.list_yellow_middle, R.drawable.list_blue_middle, @@ -91,6 +196,9 @@ public class ResourceParser { R.drawable.list_red_middle }; + /** + * 列表最后一项背景资源数组 + */ private final static int [] BG_LAST_RESOURCES = new int [] { R.drawable.list_yellow_down, R.drawable.list_blue_down, @@ -99,6 +207,9 @@ public class ResourceParser { R.drawable.list_red_down, }; + /** + * 列表单项背景资源数组 + */ private final static int [] BG_SINGLE_RESOURCES = new int [] { R.drawable.list_yellow_single, R.drawable.list_blue_single, @@ -107,28 +218,81 @@ public class ResourceParser { R.drawable.list_red_single }; + /** + * 获取列表第一项背景资源 + *

+ * 根据颜色ID获取对应的列表第一项背景资源 + *

+ * + * @param id 颜色ID + * @return 背景资源ID + */ public static int getNoteBgFirstRes(int id) { return BG_FIRST_RESOURCES[id]; } + /** + * 获取列表最后一项背景资源 + *

+ * 根据颜色ID获取对应的列表最后一项背景资源 + *

+ * + * @param id 颜色ID + * @return 背景资源ID + */ public static int getNoteBgLastRes(int id) { return BG_LAST_RESOURCES[id]; } + /** + * 获取列表单项背景资源 + *

+ * 根据颜色ID获取对应的列表单项背景资源 + *

+ * + * @param id 颜色ID + * @return 背景资源ID + */ public static int getNoteBgSingleRes(int id) { return BG_SINGLE_RESOURCES[id]; } + /** + * 获取列表中间项背景资源 + *

+ * 根据颜色ID获取对应的列表中间项背景资源 + *

+ * + * @param id 颜色ID + * @return 背景资源ID + */ public static int getNoteBgNormalRes(int id) { return BG_NORMAL_RESOURCES[id]; } + /** + * 获取文件夹背景资源 + *

+ * 获取文件夹项的背景资源 + *

+ * + * @return 文件夹背景资源ID + */ public static int getFolderBgRes() { return R.drawable.list_folder; } } + /** + * WidgetBgResources - 小部件背景资源类 + *

+ * 管理便签小部件的背景资源,包括2x和4x大小的小部件 + *

+ */ public static class WidgetBgResources { + /** + * 2x小部件背景资源数组 + */ private final static int [] BG_2X_RESOURCES = new int [] { R.drawable.widget_2x_yellow, R.drawable.widget_2x_blue, @@ -137,10 +301,22 @@ public class ResourceParser { R.drawable.widget_2x_red, }; + /** + * 获取2x小部件背景资源 + *

+ * 根据颜色ID获取对应的2x小部件背景资源 + *

+ * + * @param id 颜色ID + * @return 背景资源ID + */ public static int getWidget2xBgResource(int id) { return BG_2X_RESOURCES[id]; } + /** + * 4x小部件背景资源数组 + */ private final static int [] BG_4X_RESOURCES = new int [] { R.drawable.widget_4x_yellow, R.drawable.widget_4x_blue, @@ -149,12 +325,30 @@ public class ResourceParser { R.drawable.widget_4x_red }; + /** + * 获取4x小部件背景资源 + *

+ * 根据颜色ID获取对应的4x小部件背景资源 + *

+ * + * @param id 颜色ID + * @return 背景资源ID + */ public static int getWidget4xBgResource(int id) { return BG_4X_RESOURCES[id]; } } + /** + * TextAppearanceResources - 文本外观资源类 + *

+ * 管理文本外观资源,包括不同字体大小的样式 + *

+ */ public static class TextAppearanceResources { + /** + * 文本外观资源数组 + */ private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { R.style.TextAppearanceNormal, R.style.TextAppearanceMedium, @@ -162,6 +356,16 @@ public class ResourceParser { R.style.TextAppearanceSuper }; + /** + * 获取文本外观资源 + *

+ * 根据字体大小ID获取对应的文本外观资源 + * 修复了共享偏好设置中存储资源ID可能超出范围的问题 + *

+ * + * @param id 字体大小ID + * @return 文本外观资源ID + */ public static int getTexAppearanceResource(int id) { /** * HACKME: Fix bug of store the resource id in shared preference. @@ -174,6 +378,14 @@ public class ResourceParser { return TEXTAPPEARANCE_RESOURCES[id]; } + /** + * 获取资源数组大小 + *

+ * 返回文本外观资源数组的长度 + *

+ * + * @return 资源数组大小 + */ public static int getResourcesSize() { return TEXTAPPEARANCE_RESOURCES.length; } -- 2.34.1