From b7ddca9ff8e0023f70796a50dff68a9f5c7d1d26 Mon Sep 17 00:00:00 2001 From: HYF <2173636536@qq.com> Date: Sat, 31 Aug 2024 11:13:06 +0800 Subject: [PATCH 1/4] xinyajie.txt --- xinyajie.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 xinyajie.txt diff --git a/xinyajie.txt b/xinyajie.txt new file mode 100644 index 0000000..e69de29 From db432618c6e66ee98e5b44b415312d4bbffbf0d3 Mon Sep 17 00:00:00 2001 From: XYJ <1185560693@qq.com> Date: Thu, 17 Oct 2024 20:58:09 +0800 Subject: [PATCH 2/4] 10.17-xinyajie --- README.md | 0 ...小米便签开源代码的泛读报告.docx | Bin 16698 -> 0 bytes ...签开源代码的质量分析报告 .docx | Bin 13911 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 README.md delete mode 100644 doc/小米便签开源代码的泛读报告.docx delete mode 100644 doc/小米便签开源代码的质量分析报告 .docx diff --git a/README.md b/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/doc/小米便签开源代码的泛读报告.docx b/doc/小米便签开源代码的泛读报告.docx deleted file mode 100644 index 92cdf698ed0aae8d6f97df93d97d27e6fe08e660..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16698 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 F{{gUsbC>`C diff --git a/doc/小米便签开源代码的质量分析报告 .docx b/doc/小米便签开源代码的质量分析报告 .docx deleted file mode 100644 index 45f911a6901431655901987f1bdf9f2d53d75f8f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13911 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 From 51814cff355831a5bf9792c768014989dd3282e8 Mon Sep 17 00:00:00 2001 From: XYJ <1185560693@qq.com> Date: Wed, 25 Dec 2024 15:29:38 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E4=BF=A1=E4=BA=9A=E6=9D=B0=20data=E5=8C=85?= =?UTF-8?q?=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 | 30 ++++- .../src/net/micode/notes/data/Notes.java | 106 +++++++++++------- .../notes/data/NotesDatabaseHelper.java | 77 ++++++------- .../net/micode/notes/data/NotesProvider.java | 76 +++++++++---- 4 files changed, 184 insertions(+), 105 deletions(-) diff --git a/src/Notes-master/src/net/micode/notes/data/Contact.java b/src/Notes-master/src/net/micode/notes/data/Contact.java index d97ac5d..286aff4 100644 --- a/src/Notes-master/src/net/micode/notes/data/Contact.java +++ b/src/Notes-master/src/net/micode/notes/data/Contact.java @@ -25,47 +25,67 @@ import android.util.Log; import java.util.HashMap; +// 该类用于获取与给定电话号码对应的联系人姓名,通过缓存已查询结果来提高性能 + public class Contact { + // 该类用于获取与给定电话号码对应的联系人姓名,通过缓存已查询结果来提高性能 private static HashMap sContactCache; + // 日志标签,用于在 Log 输出中标识该类的相关信息 private static final String TAG = "Contact"; - + // 查询条件字符串,用于从联系人数据库中筛选出匹配电话号码的记录 + // 它通过比较电话号码和联系人数据中的电话号码,确保数据类型为电话类型,并限制在特定的 raw_contact_id 范围内进行查询 + // 其中,PHONE_NUMBERS_EQUAL 是一个函数,用于比较电话号码是否相等,这里使用了占位符 '?',后续会替换为实际的电话号码 + // Data.MIMETYPE 用于指定数据类型,Phone.CONTENT_ITEM_TYPE 表示电话类型的数据 + // Data.RAW_CONTACT_ID 用于指定原始联系人 ID,这里通过子查询从 phone_lookup 表中获取满足 min_match = '+' 条件的 raw_contact_id 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 " + "(SELECT raw_contact_id " + " FROM phone_lookup" + " WHERE min_match = '+')"; - + // 根据给定的上下文和电话号码获取联系人姓名的静态方法 public static String getContact(Context context, String phoneNumber) { + // 如果缓存为空,则创建一个新的 HashMap 用于缓存 if(sContactCache == null) { sContactCache = new HashMap(); } - + // 首先检查缓存中是否已经存在该电话号码对应的联系人姓名,如果存在则直接返回缓存中的结果 if(sContactCache.containsKey(phoneNumber)) { return sContactCache.get(phoneNumber); } - + // 替换查询条件中的 '+' 为电话号码的最小匹配格式,以便进行更准确的查询 String selection = CALLER_ID_SELECTION.replace("+", PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + // 使用 ContentResolver 查询联系人数据库,获取匹配电话号码的联系人姓名 + // 查询的 URI 为 Data.CONTENT_URI,表示联系人数据的通用 URI + // 查询的列仅包含 Phone.DISPLAY_NAME,即联系人的显示名称 + // 选择条件为上面生成的 selection,选择参数为实际的电话号码 + // 排序方式为默认,这里传入 null Cursor cursor = context.getContentResolver().query( Data.CONTENT_URI, new String [] { Phone.DISPLAY_NAME }, selection, new String[] { phoneNumber }, null); - + // 如果查询结果不为空且游标能够移动到第一条记录,则表示找到了匹配的联系人 if (cursor != null && cursor.moveToFirst()) { try { + // 从游标中获取联系人姓名,索引为 0,因为查询结果只包含一列联系人姓名 String name = cursor.getString(0); + // 将查询到的电话号码和对应的联系人姓名存入缓存 sContactCache.put(phoneNumber, name); + // 返回联系人姓名 return name; } catch (IndexOutOfBoundsException e) { + // 如果在获取游标数据时发生越界异常,则记录错误日志,并返回 null Log.e(TAG, " Cursor get string error " + e.toString()); return null; } finally { + // 无论是否发生异常,都需要关闭游标,释放资源 cursor.close(); } } else { + // 如果没有找到匹配的联系人,则记录调试日志,并返回 null Log.d(TAG, "No contact matched with number:" + phoneNumber); return null; } diff --git a/src/Notes-master/src/net/micode/notes/data/Notes.java b/src/Notes-master/src/net/micode/notes/data/Notes.java index f240604..827493e 100644 --- a/src/Notes-master/src/net/micode/notes/data/Notes.java +++ b/src/Notes-master/src/net/micode/notes/data/Notes.java @@ -17,12 +17,20 @@ package net.micode.notes.data; import android.net.Uri; + +// 该类用于定义笔记应用程序中的各种常量和数据结构相关信息 public class Notes { + // 定义笔记应用的授权(Authority),用于 Content Provider 的标识 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; + // 系统文件夹的标识符 + // ID_ROOT_FOLDER 是默认文件夹的 ID /** * Following IDs are system folders' identifiers @@ -31,179 +39,194 @@ public class Notes { * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records */ public static final int ID_ROOT_FOLDER = 0; + // ID_TEMPARAY_FOLDER 用于存放不属于任何文件夹的笔记 public static final int ID_TEMPARAY_FOLDER = -1; + // ID_CALL_RECORD_FOLDER 用于存储通话记录的文件夹 ID public static final int ID_CALL_RECORD_FOLDER = -2; + // ID_TRASH_FOLER 垃圾桶文件夹 ID public static final int ID_TRASH_FOLER = -3; + // 用于传递提醒日期的 Intent 额外数据键 public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; + // 用于传递背景颜色 ID 的 Intent 额外数据键 public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; + // 用于传递小部件 ID 的 Intent 额外数据键 public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; + // 用于传递小部件类型的 Intent 额外数据键 public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; + // 用于传递文件夹 ID 的 Intent 额外数据键 public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; + // 用于传递通话日期的 Intent 额外数据键 public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; - + // 无效的小部件类型 public static final int TYPE_WIDGET_INVALIDE = -1; + // 2x 小部件类型 public static final int TYPE_WIDGET_2X = 0; + // 4x 小部件类型 public static final int TYPE_WIDGET_4X = 1; - + // 内部类,用于定义数据常量 public static class DataConstants { + // 文本笔记的 Content Item Type public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; + // 通话笔记的 Content Item Type public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; } - + // 用于查询所有笔记和文件夹的 Uri /** * Uri to query all notes and folders */ public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); - + // 用于查询数据的 Uri /** * Uri to query data */ public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); - + // 定义笔记列的接口 public interface NoteColumns { + // 行的唯一 ID,类型为长整型整数 /** * The unique ID for a row *

Type: INTEGER (long)

*/ public static final String ID = "_id"; - + // 笔记或文件夹的父 ID,类型为长整型整数 /** * The parent's id for note or folder *

Type: INTEGER (long)

*/ public static final String PARENT_ID = "parent_id"; - + // 笔记或文件夹的创建日期,类型为长整型整数 /** * Created data for note or folder *

Type: INTEGER (long)

*/ public static final String CREATED_DATE = "created_date"; - + // 最新修改日期,类型为长整型整数 /** * Latest modified date *

Type: INTEGER (long)

*/ public static final String MODIFIED_DATE = "modified_date"; - + // 提醒日期,类型为长整型整数 /** * Alert date *

Type: INTEGER (long)

*/ public static final String ALERTED_DATE = "alert_date"; - + // 文件夹名称或笔记的文本内容,类型为文本 /** * Folder's name or text content of note *

Type: TEXT

*/ public static final String SNIPPET = "snippet"; - + // 笔记的小部件 ID,类型为长整型整数 /** * Note's widget id *

Type: INTEGER (long)

*/ public static final String WIDGET_ID = "widget_id"; - + // 笔记的小部件类型,类型为长整型整数 /** * Note's widget type *

Type: INTEGER (long)

*/ public static final String WIDGET_TYPE = "widget_type"; - + // 笔记的背景颜色 ID,类型为长整型整数 /** * Note's background color's id *

Type: INTEGER (long)

*/ public static final String BG_COLOR_ID = "bg_color_id"; - + // 对于文本笔记,无附件时为 0,多媒体笔记至少有一个附件时为 1,类型为整数 /** * For text note, it doesn't has attachment, for multi-media * note, it has at least one attachment *

Type: INTEGER

*/ public static final String HAS_ATTACHMENT = "has_attachment"; - + // 文件夹中的笔记数量,类型为长整型整数 /** * Folder's count of notes *

Type: INTEGER (long)

*/ public static final String NOTES_COUNT = "notes_count"; - + // 文件类型(文件夹或笔记),类型为整数 /** * The file type: folder or note *

Type: INTEGER

*/ public static final String TYPE = "type"; - + // 最后同步 ID,类型为长整型整数 /** * The last sync id *

Type: INTEGER (long)

*/ public static final String SYNC_ID = "sync_id"; - + // 表示本地是否修改的标志,类型为整数 /** * Sign to indicate local modified or not *

Type: INTEGER

*/ public static final String LOCAL_MODIFIED = "local_modified"; - + // 移动到临时文件夹之前的原始父 ID,类型为整数 /** * Original parent id before moving into temporary folder *

Type : INTEGER

*/ public static final String ORIGIN_PARENT_ID = "origin_parent_id"; - + // gtask ID,类型为文本 /** * The gtask id *

Type : TEXT

*/ public static final String GTASK_ID = "gtask_id"; - + // 版本代码,类型为长整型整数 /** * The version code *

Type : INTEGER (long)

*/ public static final String VERSION = "version"; } - + // 定义数据列的接口 public interface DataColumns { + // 行的唯一 ID,类型为长整型整数 /** * The unique ID for a row *

Type: INTEGER (long)

*/ public static final String ID = "_id"; - + // 此行表示的项目的 MIME 类型,类型为文本 /** * The MIME type of the item represented by this row. *

Type: Text

*/ public static final String MIME_TYPE = "mime_type"; - + // 此数据所属笔记的引用 ID,类型为长整型整数 /** * The reference id to note that this data belongs to *

Type: INTEGER (long)

*/ public static final String NOTE_ID = "note_id"; - + // 笔记或文件夹的创建日期,类型为长整型整数 /** * Created data for note or folder *

Type: INTEGER (long)

*/ public static final String CREATED_DATE = "created_date"; - + // 最新修改日期,类型为长整型整数 /** * Latest modified date *

Type: INTEGER (long)

*/ public static final String MODIFIED_DATE = "modified_date"; - + // 数据内容,类型为文本 /** * Data's content *

Type: TEXT

*/ public static final String CONTENT = "content"; - + // 通用数据列,含义取决于 MIME_TYPE,用于整数数据类型 /** * Generic data column, the meaning is {@link #MIMETYPE} specific, used for @@ -211,28 +234,28 @@ public class Notes { *

Type: INTEGER

*/ public static final String DATA1 = "data1"; - + // 通用数据列,含义取决于 MIME_TYPE,用于整数数据类型 /** * Generic data column, the meaning is {@link #MIMETYPE} specific, used for * integer data type *

Type: INTEGER

*/ public static final String DATA2 = "data2"; - + // 通用数据列,含义取决于 MIME_TYPE,用于文本数据类型 /** * Generic data column, the meaning is {@link #MIMETYPE} specific, used for * TEXT data type *

Type: TEXT

*/ public static final String DATA3 = "data3"; - + // 通用数据列,含义取决于 MIME_TYPE,用于文本数据类型 /** * Generic data column, the meaning is {@link #MIMETYPE} specific, used for * TEXT data type *

Type: TEXT

*/ public static final String DATA4 = "data4"; - + // 通用数据列,含义取决于 MIME_TYPE,用于文本数据类型 /** * Generic data column, the meaning is {@link #MIMETYPE} specific, used for * TEXT data type @@ -240,8 +263,10 @@ public class Notes { */ public static final String DATA5 = "data5"; } + // 文本笔记类,实现 DataColumns 接口 public static final class TextNote implements DataColumns { + // 表示文本是否处于清单模式的模式,1 为清单模式,0 为正常模式,类型为整数 /** * Mode to indicate the text in check list mode or not *

Type: Integer 1:check list mode 0: normal mode

@@ -249,31 +274,32 @@ public class Notes { public static final String MODE = DATA1; public static final int MODE_CHECK_LIST = 1; - + // 文本笔记的 Content Type(目录) public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; - + // 文本笔记的 Content Item Type(项目) public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; - + // 文本笔记的 Content 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)

*/ public static final String CALL_DATE = DATA1; - + // 通话记录的电话号码,类型为文本 /** * Phone number for this record *

Type: TEXT

*/ public static final String PHONE_NUMBER = DATA3; - + // 通话笔记的 Content Type(目录) public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; - + // 通话笔记的 Content Item Type(项目) public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; - + // 通话笔记的 Content Uri public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); } } diff --git a/src/Notes-master/src/net/micode/notes/data/NotesDatabaseHelper.java b/src/Notes-master/src/net/micode/notes/data/NotesDatabaseHelper.java index ffe5d57..59c388c 100644 --- a/src/Notes-master/src/net/micode/notes/data/NotesDatabaseHelper.java +++ b/src/Notes-master/src/net/micode/notes/data/NotesDatabaseHelper.java @@ -21,27 +21,28 @@ import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; - +// 该类继承自 SQLiteOpenHelper,用于管理笔记应用的数据库创建、升级和相关操作 import net.micode.notes.data.Notes.DataColumns; 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 语句 private static final String CREATE_NOTE_TABLE_SQL = "CREATE TABLE " + TABLE.NOTE + "(" + NoteColumns.ID + " INTEGER PRIMARY KEY," + @@ -62,7 +63,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + ")"; - + // 创建数据表的 SQL 语句 private static final String CREATE_DATA_TABLE_SQL = "CREATE TABLE " + TABLE.DATA + "(" + DataColumns.ID + " INTEGER PRIMARY KEY," + @@ -77,11 +78,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + ")"; - + // 在数据表上创建基于 note_id 的索引 SQL 语句,如果不存在则创建 private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = "CREATE INDEX IF NOT EXISTS note_id_index ON " + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; - + // 当笔记移动到文件夹时增加文件夹笔记数量的触发器 SQL 语句 /** * Increase folder's note count when move note to the folder */ @@ -93,7 +94,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " END"; - + // 当笔记从文件夹移出时减少文件夹笔记数量的触发器 SQL 语句 /** * Decrease folder's note count when move note from folder */ @@ -106,7 +107,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + " END"; - + // 当在文件夹中插入新笔记时增加文件夹笔记数量的触发器 SQL 语句 /** * Increase folder's note count when insert new note to the folder */ @@ -118,7 +119,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " END"; - + // 当从文件夹中删除笔记时减少文件夹笔记数量的触发器 SQL 语句 /** * Decrease folder's note count when delete note from the folder */ @@ -131,7 +132,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + " AND " + NoteColumns.NOTES_COUNT + ">0;" + " END"; - + // 当插入类型为笔记的数据时更新笔记内容的触发器 SQL 语句 /** * Update note's content when insert data with type {@link DataConstants#NOTE} */ @@ -144,7 +145,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " END"; - + // 当笔记类型的数据更新时更新笔记内容的触发器 SQL 语句 /** * Update note's content when data with {@link DataConstants#NOTE} type has changed */ @@ -157,7 +158,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " END"; - + // 当笔记类型的数据删除时更新笔记内容的触发器 SQL 语句 /** * Update note's content when data with {@link DataConstants#NOTE} type has deleted */ @@ -170,7 +171,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " SET " + NoteColumns.SNIPPET + "=''" + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + " END"; - + // 当笔记被删除时删除其关联数据的触发器 SQL 语句 /** * Delete datas belong to note which has been deleted */ @@ -181,7 +182,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " DELETE FROM " + TABLE.DATA + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + " END"; - + // 当文件夹被删除时删除其关联笔记的触发器 SQL 语句 /** * Delete notes belong to folder which has been deleted */ @@ -192,7 +193,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " DELETE FROM " + TABLE.NOTE + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " END"; - + // 当文件夹被移动到垃圾桶文件夹时移动其关联笔记的触发器 SQL 语句 /** * Move notes belong to folder which has been moved to trash folder */ @@ -205,18 +206,18 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " END"; - + // 构造函数,调用父类 SQLiteOpenHelper 的构造函数,传入上下文、数据库名称、游标工厂(这里为 null)和版本号 public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } - + // 创建笔记表的方法,执行创建表的 SQL 语句,重新创建触发器,并创建系统文件夹 public void createNoteTable(SQLiteDatabase db) { db.execSQL(CREATE_NOTE_TABLE_SQL); reCreateNoteTableTriggers(db); createSystemFolder(db); Log.d(TAG, "note table has been created"); } - + // 重新创建笔记表触发器的方法,先删除已存在的相关触发器,再创建新的触发器 private void reCreateNoteTableTriggers(SQLiteDatabase db) { db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); @@ -234,17 +235,17 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); } - + // 创建系统文件夹的方法,向笔记表中插入通话记录文件夹、根文件夹、临时文件夹和垃圾桶文件夹的记录 private void createSystemFolder(SQLiteDatabase db) { ContentValues values = new ContentValues(); - + // 通话记录文件夹 /** * call record foler for call notes */ 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 */ @@ -252,7 +253,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { 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 */ @@ -260,7 +261,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - + // 垃圾桶文件夹 /** * create trash folder */ @@ -269,14 +270,14 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } - + // 创建数据表的方法,执行创建表的 SQL 语句,重新创建触发器,并创建索引 public void createDataTable(SQLiteDatabase db) { db.execSQL(CREATE_DATA_TABLE_SQL); reCreateDataTableTriggers(db); db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); Log.d(TAG, "data table has been created"); } - + // 重新创建数据表触发器的方法,先删除已存在的相关触发器,再创建新的触发器 private void reCreateDataTableTriggers(SQLiteDatabase db) { db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); @@ -286,20 +287,20 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); } - + // 单例模式获取实例的方法,确保只有一个 NotesDatabaseHelper 实例存在 static synchronized NotesDatabaseHelper getInstance(Context context) { if (mInstance == null) { mInstance = new NotesDatabaseHelper(context); } return mInstance; } - + // 重写 onCreate 方法,在数据库首次创建时调用,创建笔记表和数据表 @Override public void onCreate(SQLiteDatabase db) { createNoteTable(db); createDataTable(db); } - + // 重写 onUpgrade 方法,在数据库版本升级时调用,根据不同的旧版本执行相应的升级操作 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { boolean reCreateTriggers = false; @@ -307,7 +308,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { if (oldVersion == 1) { upgradeToV2(db); - skipV2 = true; // this upgrade including the upgrade from v2 to v3 + skipV2 = true; // 此次升级包含从 v2 到 v3 的升级 oldVersion++; } @@ -332,29 +333,29 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { + "fails"); } } - + // 从版本 1 升级到版本 2 的方法,删除旧表并重新创建新表 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); } - + // 从版本 2 升级到版本 3 的方法,删除无用触发器,添加 gtask_id 列,并添加垃圾桶系统文件夹 private void upgradeToV3(SQLiteDatabase db) { - // drop unused triggers + // 删除无用触发器 db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); - // add a column for gtask id + // 添加 gtask_id 列 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''"); - // add a trash system folder + // 添加垃圾桶系统文件夹 ContentValues values = new ContentValues(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } - + // 从版本 3 升级到版本 4 的方法,添加 version 列 private void upgradeToV4(SQLiteDatabase db) { db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); diff --git a/src/Notes-master/src/net/micode/notes/data/NotesProvider.java b/src/Notes-master/src/net/micode/notes/data/NotesProvider.java index edb0a60..47889d8 100644 --- a/src/Notes-master/src/net/micode/notes/data/NotesProvider.java +++ b/src/Notes-master/src/net/micode/notes/data/NotesProvider.java @@ -34,14 +34,15 @@ import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.NotesDatabaseHelper.TABLE; - +// 该类继承自 ContentProvider,用于在 Android 系统中提供笔记数据的增删改查功能,并处理搜索相关操作 public class NotesProvider extends ContentProvider { + // 用于匹配不同的 Uri 模式的 UriMatcher 实例 private static final UriMatcher mMatcher; - + // 数据库帮助类实例,用于操作数据库 private NotesDatabaseHelper mHelper; - + // 日志标签,用于记录日志信息 private static final String TAG = "NotesProvider"; - + // 定义不同 Uri 模式的匹配码 private static final int URI_NOTE = 1; private static final int URI_NOTE_ITEM = 2; private static final int URI_DATA = 3; @@ -49,7 +50,7 @@ public class NotesProvider extends ContentProvider { private static final int URI_SEARCH = 5; private static final int URI_SEARCH_SUGGEST = 6; - + // 静态代码块,用于初始化 UriMatcher,添加各种 Uri 模式的匹配规则 static { mMatcher = new UriMatcher(UriMatcher.NO_MATCH); mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); @@ -60,7 +61,8 @@ public class NotesProvider extends ContentProvider { mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); } - + // 搜索结果投影字符串,用于定义搜索结果中返回的列信息 + // 包括笔记 ID、将笔记 ID 作为意图额外数据、修剪和替换换行符后的笔记摘要作为搜索建议的文本列、图标资源 ID、意图动作和意图数据类型 /** * 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. @@ -72,52 +74,61 @@ public class NotesProvider extends ContentProvider { + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; - + // 搜索查询语句,用于从笔记表中查询符合搜索条件的笔记信息 + // 搜索条件是笔记摘要包含指定字符串,且笔记不在垃圾桶文件夹中,并且是普通笔记类型 private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION + " FROM " + TABLE.NOTE + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; - + // 重写 ContentProvider 的 onCreate 方法,在 ContentProvider 启动时调用 + // 用于获取 NotesDatabaseHelper 的单例实例 @Override public boolean onCreate() { mHelper = NotesDatabaseHelper.getInstance(getContext()); return true; } - + // 重写 ContentProvider 的 query 方法,用于处理数据查询请求 @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Cursor c = null; SQLiteDatabase db = mHelper.getReadableDatabase(); String id = null; + // 根据 Uri 匹配码执行不同的查询操作 switch (mMatcher.match(uri)) { case URI_NOTE: + // 查询笔记表,返回符合条件的游标 c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder); break; case URI_NOTE_ITEM: + // 获取路径片段中的笔记 ID,查询指定 ID 的笔记 id = uri.getPathSegments().get(1); c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder); break; case URI_DATA: + // 查询数据表,返回符合条件的游标 c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder); break; case URI_DATA_ITEM: + // 获取路径片段中的数据 ID,查询指定 ID 的数据 id = uri.getPathSegments().get(1); c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder); break; case URI_SEARCH: case URI_SEARCH_SUGGEST: + // 检查是否指定了不允许的参数,如果指定则抛出异常 if (sortOrder != null || projection != null) { throw new IllegalArgumentException( "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); } String searchString = null; + // 根据 Uri 匹配码获取搜索字符串 if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { if (uri.getPathSegments().size() > 1) { searchString = uri.getPathSegments().get(1); @@ -125,12 +136,13 @@ public class NotesProvider extends ContentProvider { } else { 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 }); @@ -139,61 +151,71 @@ public class NotesProvider extends ContentProvider { } break; default: + // 如果 Uri 不匹配任何已知模式,则抛出异常 throw new IllegalArgumentException("Unknown URI " + uri); } + // 如果游标不为空,则设置通知 Uri,以便在数据变化时接收通知 if (c != null) { c.setNotificationUri(getContext().getContentResolver(), uri); } return c; } - + // 重写 ContentProvider 的 insert 方法,用于处理数据插入请求 @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: + // 插入笔记数据,获取插入的笔记 ID insertedId = noteId = db.insert(TABLE.NOTE, null, values); break; case URI_DATA: + // 获取数据所属的笔记 ID,如果不存在则记录错误日志 if (values.containsKey(DataColumns.NOTE_ID)) { noteId = values.getAsLong(DataColumns.NOTE_ID); } else { Log.d(TAG, "Wrong data format without note id:" + values.toString()); } + // 插入数据,获取插入的数据 ID insertedId = dataId = db.insert(TABLE.DATA, null, values); break; default: + // 如果 Uri 不匹配任何已知模式,则抛出异常 throw new IllegalArgumentException("Unknown URI " + uri); } - // Notify the note uri + // 如果插入了笔记数据,则通知笔记 Uri 数据变化 if (noteId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); } - - // Notify the data uri + // 如果插入了数据,则通知数据 Uri 数据变化 if (dataId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); } - + // 返回插入数据后的 Uri,包含插入的 ID return ContentUris.withAppendedId(uri, insertedId); } - + // 重写 ContentProvider 的 delete 方法,用于处理数据删除请求 @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean deleteData = false; + // 根据 Uri 匹配码执行不同的删除操作 switch (mMatcher.match(uri)) { case URI_NOTE: + // 添加额外的条件,确保只删除有效 ID 的笔记 selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; count = db.delete(TABLE.NOTE, selection, selectionArgs); break; case URI_NOTE_ITEM: + // 获取路径片段中的笔记 ID id = uri.getPathSegments().get(1); + // 如果笔记 ID 小于等于 0,则不允许删除(可能是系统文件夹) /** * ID that smaller than 0 is system folder which is not allowed to * trash @@ -206,18 +228,22 @@ public class NotesProvider extends ContentProvider { NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); break; case URI_DATA: + // 删除数据,设置标志位 count = db.delete(TABLE.DATA, selection, selectionArgs); deleteData = true; break; case URI_DATA_ITEM: + // 获取路径片段中的数据 ID id = uri.getPathSegments().get(1); count = db.delete(TABLE.DATA, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); deleteData = true; break; default: + // 如果 Uri 不匹配任何已知模式,则抛出异常 throw new IllegalArgumentException("Unknown URI " + uri); } + // 如果删除了数据且有数据被删除,则通知笔记 Uri 和删除的 Uri 数据变化 if (count > 0) { if (deleteData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); @@ -226,38 +252,44 @@ public class NotesProvider extends ContentProvider { } return count; } - + // 重写 ContentProvider 的 update 方法,用于处理数据更新请求 @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean updateData = false; + // 根据 Uri 匹配码执行不同的更新操作 switch (mMatcher.match(uri)) { case URI_NOTE: + // 增加笔记版本号,执行更新操作 increaseNoteVersion(-1, selection, selectionArgs); count = db.update(TABLE.NOTE, values, selection, selectionArgs); break; case URI_NOTE_ITEM: + // 获取路径片段中的笔记 ID,增加笔记版本号,执行更新操作 id = uri.getPathSegments().get(1); increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); break; case URI_DATA: + // 更新数据,设置标志位 count = db.update(TABLE.DATA, values, selection, selectionArgs); updateData = true; break; case URI_DATA_ITEM: + // 获取路径片段中的数据 ID,更新数据 id = uri.getPathSegments().get(1); count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); updateData = true; break; default: + // 如果 Uri 不匹配任何已知模式,则抛出异常 throw new IllegalArgumentException("Unknown URI " + uri); } - + // 如果更新了数据且有数据被更新,则通知笔记 Uri 和更新的 Uri 数据变化 if (count > 0) { if (updateData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); @@ -266,11 +298,11 @@ public class NotesProvider extends ContentProvider { } return count; } - + // 辅助方法,用于解析选择条件,添加括号和 AND 连接符 private String parseSelection(String selection) { return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); } - + // 增加笔记版本号的方法,根据指定的笔记 ID 和选择条件构建更新 SQL 语句并执行 private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); @@ -295,10 +327,10 @@ public class NotesProvider extends ContentProvider { mHelper.getWritableDatabase().execSQL(sql.toString()); } - + // 重写 ContentProvider 的 getType 方法,目前未实现,返回 null @Override public String getType(Uri uri) { - // TODO Auto-generated method stub + // TODO Auto-generated method stub return null; } From 8dd7a9b8308fc6d7bb353647115de88d8a455cb9 Mon Sep 17 00:00:00 2001 From: XYJ <1185560693@qq.com> Date: Wed, 25 Dec 2024 16:46:24 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=BF=A1=E4=BA=9A=E6=9D=B0=20gtask?= =?UTF-8?q?=E5=8C=85remote=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notes/gtask/remote/GTaskASyncTask.java | 45 +++-- .../notes/gtask/remote/GTaskClient.java | 185 ++++++++++-------- .../notes/gtask/remote/GTaskManager.java | 170 ++++++++-------- .../notes/gtask/remote/GTaskSyncService.java | 65 ++++-- 4 files changed, 276 insertions(+), 189 deletions(-) diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskASyncTask.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskASyncTask.java index b3b61e7..bdb66ef 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskASyncTask.java +++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskASyncTask.java @@ -28,47 +28,53 @@ import net.micode.notes.R; import net.micode.notes.ui.NotesListActivity; import net.micode.notes.ui.NotesPreferenceActivity; - +// 该类继承自 AsyncTask,用于在后台执行与 GTask 同步相关的任务,并在任务执行过程中更新进度、处理结果并显示通知 public class GTaskASyncTask extends AsyncTask { - + // 用于标识 GTask 同步通知的 ID private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; - + // 定义任务完成监听器接口 public interface OnCompleteListener { void onComplete(); } - + // 上下文对象,用于获取系统服务和资源 private Context mContext; - + // 通知管理器,用于显示通知 private NotificationManager mNotifiManager; - + // GTask 管理器实例,用于执行同步操作 private GTaskManager mTaskManager; - + // 任务完成监听器实例 private OnCompleteListener mOnCompleteListener; - + // 构造函数,初始化相关成员变量 public GTaskASyncTask(Context context, OnCompleteListener listener) { mContext = context; mOnCompleteListener = listener; + // 获取通知管理器服务 mNotifiManager = (NotificationManager) mContext .getSystemService(Context.NOTIFICATION_SERVICE); + // 获取 GTask 管理器单例实例 mTaskManager = GTaskManager.getInstance(); } - + // 取消同步的方法,调用 GTask 管理器的取消同步方法 public void cancelSync() { mTaskManager.cancelSync(); } - + // 发布进度的方法,调用父类的 publishProgress 方法传递进度信息 public void publishProgess(String message) { publishProgress(new String[] { message }); } - + // 显示通知的私有方法,根据给定的 tickerId 和内容创建并显示通知 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; PendingIntent pendingIntent; + // 根据 tickerId 设置不同的点击意图 if (tickerId != R.string.ticker_success) { pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesPreferenceActivity.class), 0); @@ -77,32 +83,40 @@ public class GTaskASyncTask extends AsyncTask { 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); } - + // 重写 doInBackground 方法,在后台线程执行同步任务 @Override protected Integer doInBackground(Void... unused) { + // 发布登录进度通知 publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity .getSyncAccountName(mContext))); + // 调用 GTask 管理器的同步方法并返回结果 return mTaskManager.sync(mContext, this); } - + // 重写 onProgressUpdate 方法,在主线程更新进度通知 @Override protected void onProgressUpdate(String... progress) { + // 显示同步进度通知 showNotification(R.string.ticker_syncing, progress[0]); + // 如果上下文是 GTaskSyncService,则发送广播传递进度信息 if (mContext instanceof GTaskSyncService) { ((GTaskSyncService) mContext).sendBroadcast(progress[0]); } } - + // 重写 onPostExecute 方法,在主线程处理同步任务结果 @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()); + // 设置最后同步时间 + 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) { @@ -111,6 +125,7 @@ public class GTaskASyncTask extends AsyncTask { showNotification(R.string.ticker_cancel, mContext .getString(R.string.error_sync_cancelled)); } + // 如果有任务完成监听器,则在新线程中触发监听器的 onComplete 方法 if (mOnCompleteListener != null) { new Thread(new Runnable() { diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskClient.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskClient.java index c67dfdf..eb5ab19 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskClient.java +++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskClient.java @@ -59,37 +59,39 @@ import java.util.List; import java.util.zip.GZIPInputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; - +// 该类是与 Google Tasks(GTask)进行交互的客户端类,负责处理登录、任务和任务列表的创建、更新、移动、删除以及获取等操作 public class GTaskClient { + // 日志标签,用于记录类的相关操作信息 private static final String TAG = GTaskClient.class.getSimpleName(); - + // Google Tasks 的基础 URL private static final String GTASK_URL = "https://mail.google.com/tasks/"; - + // 用于获取任务数据的 URL private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; - + // 用于提交任务相关操作的 URL private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; - + // 单例模式实例 private static GTaskClient mInstance = null; - + // HTTP 客户端实例,用于发送 HTTP 请求 private DefaultHttpClient mHttpClient; - + // 当前使用的获取任务数据的 URL private String mGetUrl; - + // 当前使用的提交任务操作的 URL private String mPostUrl; + // 客户端版本号 private long mClientVersion; - + // 登录状态 private boolean mLoggedin; - + // 上次登录时间 private long mLastLoginTime; - + // 操作 ID,用于唯一标识每个操作 private int mActionId; - + // 同步的 Google 账户 private Account mAccount; - + // 用于存储待提交的更新操作数组 private JSONArray mUpdateArray; - + // 私有构造函数,初始化成员变量 private GTaskClient() { mHttpClient = null; mGetUrl = GTASK_GET_URL; @@ -101,23 +103,21 @@ public class GTaskClient { mAccount = null; mUpdateArray = null; } - + // 单例模式获取实例的方法 public static synchronized GTaskClient getInstance() { if (mInstance == null) { mInstance = new GTaskClient(); } return mInstance; } - + // 登录方法,根据账户状态和设置进行登录操作 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))) { @@ -130,13 +130,13 @@ public class GTaskClient { } mLastLoginTime = System.currentTimeMillis(); + // 登录 Google 账户获取授权令牌 String authToken = loginGoogleAccount(activity, false); if (authToken == null) { Log.e(TAG, "login google account failed"); return false; } - - // login with custom domain if necessary + // 如果账户不是 gmail.com 或 googlemail.com 结尾,使用自定义域名登录 GTask if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() .endsWith("googlemail.com"))) { StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); @@ -150,8 +150,8 @@ public class GTaskClient { mLoggedin = true; } } - - // try to login with google official url + // 使用 Google 官方 URL 登录 GTask + if (!mLoggedin) { mGetUrl = GTASK_GET_URL; mPostUrl = GTASK_POST_URL; @@ -159,21 +159,23 @@ public class GTaskClient { return false; } } - + mLoggedin = true; return true; } - + // 登录 Google 账户的私有方法 private String loginGoogleAccount(Activity activity, boolean invalidateToken) { String authToken; + // 获取账户管理器实例 AccountManager accountManager = AccountManager.get(activity); + // 获取所有 Google 账户 Account[] accounts = accountManager.getAccountsByType("com.google"); if (accounts.length == 0) { Log.e(TAG, "there is no available google account"); return null; } - + // 获取同步账户名称 String accountName = NotesPreferenceActivity.getSyncAccountName(activity); Account account = null; for (Account a : accounts) { @@ -188,8 +190,7 @@ public class GTaskClient { Log.e(TAG, "unable to get an account with the same name in the settings"); return null; } - - // get the token now + // 获取授权令牌 AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); try { @@ -206,9 +207,10 @@ public class GTaskClient { return authToken; } - + // 尝试登录 GTask 的私有方法 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); @@ -224,10 +226,11 @@ public class GTaskClient { } return true; } - + // 登录 GTask 的私有方法,获取客户端版本号和设置 HTTP 客户端相关参数 private boolean loginGtask(String authToken) { int timeoutConnection = 10000; int timeoutSocket = 15000; + // 设置 HTTP 请求参数 HttpParams httpParameters = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); @@ -236,14 +239,14 @@ public class GTaskClient { mHttpClient.setCookieStore(localBasicCookieStore); HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); - // login gtask + // 登录 GTask 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 +258,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 = ")}"; @@ -279,18 +282,18 @@ public class GTaskClient { return true; } - + // 获取操作 ID 的方法,每次调用后自增 private int getActionId() { return mActionId++; } - + // 创建用于提交任务操作的 HttpPost 对象的方法 private HttpPost createHttpPost() { HttpPost httpPost = new HttpPost(mPostUrl); httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); httpPost.setHeader("AT", "1"); return httpPost; } - + // 从 HTTP 响应实体中获取响应内容的方法,处理不同的编码格式 private String getResponseContent(HttpEntity entity) throws IOException { String contentEncoding = null; if (entity.getContentEncoding() != null) { @@ -322,7 +325,7 @@ public class GTaskClient { input.close(); } } - + // 提交 POST 请求的方法,将 JSON 数据发送到 GTask 服务器并处理响应 private JSONObject postRequest(JSONObject js) throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); @@ -336,7 +339,7 @@ public class GTaskClient { UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); httpPost.setEntity(entity); - // execute the post + // 执行 POST 请求 HttpResponse response = mHttpClient.execute(httpPost); String jsString = getResponseContent(response.getEntity()); return new JSONObject(jsString); @@ -359,21 +362,21 @@ public class GTaskClient { throw new ActionFailureException("error occurs when posting request"); } } - + // 创建任务的方法,将任务创建操作提交到 GTask 服务器 public void createTask(Task task) throws NetworkFailureException { commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list + // 添加任务创建操作到操作列表 actionList.put(task.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 设置客户端版本号 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - // post + // 提交请求并处理响应,设置任务的全局 ID JSONObject jsResponse = postRequest(jsPost); JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( GTaskStringUtils.GTASK_JSON_RESULTS).get(0); @@ -385,21 +388,21 @@ public class GTaskClient { throw new ActionFailureException("create task: handing jsonobject failed"); } } - + // 创建任务列表的方法,将任务列表创建操作提交到 GTask 服务器 public void createTaskList(TaskList tasklist) throws NetworkFailureException { commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list + // 添加任务列表创建操作到操作列表 actionList.put(tasklist.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client version + // 设置客户端版本号 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - // post + // 提交请求并处理响应,设置任务列表的全局 ID JSONObject jsResponse = postRequest(jsPost); JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( GTaskStringUtils.GTASK_JSON_RESULTS).get(0); @@ -411,16 +414,16 @@ public class GTaskClient { throw new ActionFailureException("create tasklist: handing jsonobject failed"); } } - + // 提交更新操作的方法,将存储的更新操作数组提交到 GTask 服务器 public void commitUpdate() throws NetworkFailureException { if (mUpdateArray != null) { try { JSONObject jsPost = new JSONObject(); - // action_list + // 设置操作列表 jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); - // client_version + // 设置客户端版本号 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); postRequest(jsPost); @@ -432,11 +435,10 @@ public class GTaskClient { } } } - + // 添加更新节点操作的方法,将节点更新操作添加到更新操作数组 public void addUpdateNode(Node node) throws NetworkFailureException { if (node != null) { - // too many update items may result in an error - // set max to 10 items + // 为避免更新项过多导致错误,设置最大更新项为 10 if (mUpdateArray != null && mUpdateArray.length() > 10) { commitUpdate(); } @@ -446,81 +448,98 @@ public class GTaskClient { mUpdateArray.put(node.getUpdateAction(getActionId())); } } - + // 移动任务的方法,将任务移动操作提交到 GTask 服务器 public void moveTask(Task task, TaskList preParent, TaskList curParent) throws NetworkFailureException { + // 先提交之前可能存在的更新操作 commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); - // action_list + // 设置操作类型为移动任务 action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); - action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); - action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); - if (preParent == curParent && task.getPriorSibling() != null) { + // 设置操作 ID + action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); + // 设置要移动的任务的 ID + action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); + // 如果源任务列表和目标任务列表相同且任务有前驱兄弟节点,则设置前驱兄弟节点 ID + if (preParent == curParent && task.getPriorSibling() != null) { // put prioring_sibing_id only if moving within the tasklist and // it is not the first one action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); } + // 设置源任务列表 ID action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); + // 设置目标父任务列表 ID action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); + // 如果源任务列表和目标任务列表不同,则设置目标任务列表 ID if (preParent != curParent) { // put the dest_list only if moving between tasklists action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); } + // 将操作添加到操作列表 actionList.put(action); + // 将操作列表添加到提交的 JSON 对象中 jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 设置客户端版本号 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - + // 提交请求 postRequest(jsPost); } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); + // 如果 JSON 处理出现问题,抛出操作失败异常 throw new ActionFailureException("move task: handing jsonobject failed"); } } - + // 删除节点的方法,接受要删除的节点作为参数,并可能抛出网络异常 public void deleteNode(Node node) throws NetworkFailureException { + // 先提交之前可能存在的更新操作 commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list + // 设置节点为已删除状态 node.setDeleted(true); + // 将节点的更新操作添加到操作列表 actionList.put(node.getUpdateAction(getActionId())); + // 将操作列表添加到提交的 JSON 对象中 jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 设置客户端版本号 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - + // 提交请求并删除后将更新数组置空 postRequest(jsPost); mUpdateArray = null; } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); + // 如果 JSON 处理出现问题,抛出操作失败异常 throw new ActionFailureException("delete node: handing jsonobject failed"); } } - + // 获取所有任务列表的方法,可能抛出网络异常 public JSONArray getTaskLists() throws NetworkFailureException { + // 检查是否已登录,未登录则抛出异常 if (!mLoggedin) { Log.e(TAG, "please login first"); throw new ActionFailureException("not logged in"); } try { + // 创建 HTTP GET 请求 HttpGet httpGet = new HttpGet(mGetUrl); HttpResponse response = null; + // 执行请求 response = mHttpClient.execute(httpGet); - // get the task list + // 获取响应内容并解析出任务列表的 JSON 数组 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -535,50 +554,60 @@ public class GTaskClient { } catch (ClientProtocolException e) { Log.e(TAG, e.toString()); e.printStackTrace(); + // 如果 HTTP 协议出现问题,抛出网络异常 throw new NetworkFailureException("gettasklists: httpget failed"); } catch (IOException e) { Log.e(TAG, e.toString()); e.printStackTrace(); + // 如果 I/O 出现问题,抛出网络异常 throw new NetworkFailureException("gettasklists: httpget failed"); } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); + // 如果 JSON 解析出现问题,抛出操作失败异常 throw new ActionFailureException("get task lists: handing jasonobject failed"); } } - + // 根据任务列表 ID 获取任务列表的方法,可能抛出网络异常 public JSONArray getTaskList(String listGid) throws NetworkFailureException { + // 先提交之前可能存在的更新操作 commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); - // action_list + // 设置操作类型为获取所有任务 action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); - action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); - action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); - action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); - actionList.put(action); - jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - - // client_version + // 设置操作 ID + action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); + // 设置任务列表 ID + action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); + // 设置不获取已删除任务 + action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); + // 将操作添加到操作列表 + actionList.put(action); + // 将操作列表添加到提交的 JSON 对象中 + jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + + // 设置客户端版本号 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - + // 提交请求并获取响应中的任务列表 JSON 数组 JSONObject jsResponse = postRequest(jsPost); return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); + // 如果 JSON 处理出现问题,抛出操作失败异常 throw new ActionFailureException("get task list: handing jsonobject failed"); } } - + // 获取同步账户的方法 public Account getSyncAccount() { return mAccount; } - + // 重置更新数组的方法,将更新数组置空 public void resetUpdateArray() { mUpdateArray = null; } diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java index d2b4082..c32e8cf 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java +++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java @@ -47,10 +47,10 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; - +// GTaskManager类用于管理与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; @@ -60,33 +60,33 @@ public class GTaskManager { 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; - + // 用于访问内容提供者的ContentResolver private ContentResolver mContentResolver; - + // 表示是否正在同步 private boolean mSyncing; - + // 表示同步是否已取消 private boolean mCancelled; - + // 存储Google Task列表的哈希表,键为任务列表的全局唯一标识符(gid),值为TaskList对象 private HashMap mGTaskListHashMap; - + // 存储Google Task节点(任务或任务列表)的哈希表,键为节点的gid,值为Node对象 private HashMap mGTaskHashMap; - + // 存储元数据的哈希表,键为相关的gid,值为MetaData对象 private HashMap mMetaHashMap; - + // 元数据列表 private TaskList mMetaList; - + // 存储本地已删除笔记的ID集合 private HashSet mLocalDeleteIdMap; - + // 用于将Google Task的gid映射到本地笔记的ID private HashMap mGidToNid; - + // 用于将本地笔记的ID映射到Google Task的gid private HashMap mNidToGid; - + // 私有构造函数,用于初始化各种数据结构和变量 private GTaskManager() { mSyncing = false; mCancelled = false; @@ -98,21 +98,22 @@ public class GTaskManager { mGidToNid = new HashMap(); mNidToGid = new HashMap(); } - + // 获取GTaskManager的单例实例 public static synchronized GTaskManager getInstance() { if (mInstance == null) { mInstance = new GTaskManager(); } return mInstance; } - + // 设置Activity上下文,主要用于获取认证令牌 public synchronized void setActivityContext(Activity activity) { // used for getting authtoken mActivity = activity; } - + // 执行同步操作的主要方法 public int sync(Context context, GTaskASyncTask asyncTask) { if (mSyncing) { + // 如果同步已经在进行中,记录日志并返回相应状态码 Log.d(TAG, "Sync is in progress"); return STATE_SYNC_IN_PROGRESS; } @@ -120,6 +121,7 @@ public class GTaskManager { mContentResolver = mContext.getContentResolver(); mSyncing = true; mCancelled = false; + // 清除之前同步过程中存储的数据 mGTaskListHashMap.clear(); mGTaskHashMap.clear(); mMetaHashMap.clear(); @@ -131,24 +133,26 @@ 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 Task列表并进行初始化 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) { + // 处理网络故障异常,记录日志并返回相应状态码 Log.e(TAG, e.toString()); return STATE_NETWORK_ERROR; } catch (ActionFailureException e) { + // 处理操作失败异常,记录日志并返回相应状态码 Log.e(TAG, e.toString()); return STATE_INTERNAL_ERROR; } catch (Exception e) { @@ -156,6 +160,7 @@ public class GTaskManager { e.printStackTrace(); return STATE_INTERNAL_ERROR; } finally { + // 无论同步是否成功,都清除相关数据并重置同步状态 mGTaskListHashMap.clear(); mGTaskHashMap.clear(); mMetaHashMap.clear(); @@ -164,7 +169,7 @@ public class GTaskManager { mNidToGid.clear(); mSyncing = false; } - + // 初始化Google Task列表的方法 return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; } @@ -173,21 +178,22 @@ public class GTaskManager { return; GTaskClient client = GTaskClient.getInstance(); try { + // 从Google获取任务列表的JSON数组 JSONArray jsTaskLists = client.getTaskLists(); - // init meta list first + // 初始化元数据列表 mMetaList = null; for (int i = 0; i < jsTaskLists.length(); i++) { JSONObject object = jsTaskLists.getJSONObject(i); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); - + // 查找元数据文件夹 if (name .equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { mMetaList = new TaskList(); mMetaList.setContentByRemoteJSON(object); - // load meta data + // 加载元数据 JSONArray jsMetas = client.getTaskList(gid); for (int j = 0; j < jsMetas.length(); j++) { object = (JSONObject) jsMetas.getJSONObject(j); @@ -203,7 +209,7 @@ public class GTaskManager { } } - // create meta list if not existed + // 如果元数据列表不存在,则创建一个新的元数据列表 if (mMetaList == null) { mMetaList = new TaskList(); mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX @@ -211,12 +217,12 @@ public class GTaskManager { GTaskClient.getInstance().createTaskList(mMetaList); } - // init task list + // 初始化任务列表和任务 for (int i = 0; i < jsTaskLists.length(); i++) { JSONObject object = jsTaskLists.getJSONObject(i); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); - + // 处理以特定前缀开头且不是元数据文件夹的任务列表 if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { @@ -225,7 +231,7 @@ public class GTaskManager { mGTaskListHashMap.put(gid, tasklist); mGTaskHashMap.put(gid, tasklist); - // load tasks + // 加载任务列表中的任务 JSONArray jsTasks = client.getTaskList(gid); for (int j = 0; j < jsTasks.length(); j++) { object = (JSONObject) jsTasks.getJSONObject(j); @@ -241,12 +247,13 @@ public class GTaskManager { } } } catch (JSONException e) { + // 处理JSON解析异常,记录日志、打印堆栈信息并抛出操作失败异常 Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("initGTaskList: handing JSONObject failed"); } } - + // 执行内容同步的方法 private void syncContent() throws NetworkFailureException { int syncType; Cursor c = null; @@ -259,7 +266,7 @@ public class GTaskManager { return; } - // for local deleted note + // 处理本地已删除的笔记 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type<>? AND parent_id=?)", new String[] { @@ -286,10 +293,10 @@ public class GTaskManager { } } - // sync folder first + // 同步文件夹 syncFolder(); - // for note existing in database + // 处理数据库中存在的笔记 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type=? AND parent_id<>?)", new String[] { @@ -306,10 +313,10 @@ public class GTaskManager { syncType = node.getSyncAction(c); } else { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add + // 本地新增 syncType = Node.SYNC_ACTION_ADD_REMOTE; } else { - // remote delete + // 远程删除 syncType = Node.SYNC_ACTION_DEL_LOCAL; } } @@ -326,14 +333,14 @@ public class GTaskManager { } } - // go through remaining items + // 处理剩余的未同步节点 Iterator> iter = mGTaskHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); node = entry.getValue(); doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); } - + // 检查同步是否取消,如果未取消则执行本地删除操作和提交更新操作 // mCancelled can be set by another thread, so we neet to check one by // one // clear local delete table @@ -342,15 +349,14 @@ public class GTaskManager { throw new ActionFailureException("failed to batch-delete local deleted notes"); } } - - // refresh local sync id + // 检查同步是否取消,如果未取消则提交更新并刷新本地同步ID if (!mCancelled) { GTaskClient.getInstance().commitUpdate(); refreshLocalSyncId(); } } - + // 同步文件夹的方法 private void syncFolder() throws NetworkFailureException { Cursor c = null; String gid; @@ -361,7 +367,7 @@ public class GTaskManager { return; } - // for root folder + // 处理根文件夹 try { c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); @@ -373,7 +379,7 @@ public class GTaskManager { mGTaskHashMap.remove(gid); mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); - // for system folder, only update remote name if necessary + // 对于系统文件夹,仅在必要时更新远程名称 if (!node.getName().equals( GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); @@ -390,7 +396,7 @@ public class GTaskManager { } } - // for call-note folder + // 处理通话记录文件夹 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", new String[] { @@ -404,8 +410,7 @@ public class GTaskManager { mGTaskHashMap.remove(gid); mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); - // for system folder, only update remote name if - // necessary + // 对于系统文件夹,仅在必要时更新远程名称 if (!node.getName().equals( GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) @@ -424,7 +429,7 @@ public class GTaskManager { } } - // for local existing folders + // 处理本地现有文件夹 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type=? AND parent_id<>?)", new String[] { @@ -441,10 +446,10 @@ public class GTaskManager { syncType = node.getSyncAction(c); } else { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add + // 本地新增文件夹 syncType = Node.SYNC_ACTION_ADD_REMOTE; } else { - // remote delete + // 远程删除文件夹 syncType = Node.SYNC_ACTION_DEL_LOCAL; } } @@ -460,7 +465,7 @@ public class GTaskManager { } } - // for remote add folders + // 处理远程新增文件夹 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -475,7 +480,7 @@ public class GTaskManager { if (!mCancelled) GTaskClient.getInstance().commitUpdate(); } - + // 根据同步类型执行具体同步操作的方法 private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -484,12 +489,15 @@ public class GTaskManager { MetaData meta; switch (syncType) { case Node.SYNC_ACTION_ADD_LOCAL: + // 本地新增节点操作 addLocalNode(node); break; case Node.SYNC_ACTION_ADD_REMOTE: + // 远程新增节点操作 addRemoteNode(node, c); break; case Node.SYNC_ACTION_DEL_LOCAL: + // 本地删除节点操作 meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); if (meta != null) { GTaskClient.getInstance().deleteNode(meta); @@ -497,6 +505,7 @@ public class GTaskManager { mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); break; case Node.SYNC_ACTION_DEL_REMOTE: + // 远程删除节点操作 meta = mMetaHashMap.get(node.getGid()); if (meta != null) { GTaskClient.getInstance().deleteNode(meta); @@ -504,24 +513,28 @@ public class GTaskManager { GTaskClient.getInstance().deleteNode(node); break; case Node.SYNC_ACTION_UPDATE_LOCAL: + // 本地更新节点操作 updateLocalNode(node, c); break; case Node.SYNC_ACTION_UPDATE_REMOTE: + // 远程更新节点操作 updateRemoteNode(node, c); break; case Node.SYNC_ACTION_UPDATE_CONFLICT: - // merging both modifications maybe a good idea - // right now just use local update simply + // 处理更新冲突,目前简单地使用本地更新 + // 可能合并双方修改会是更好的做法,但目前仅做简单处理 updateRemoteNode(node, c); break; case Node.SYNC_ACTION_NONE: + // 无需同步操作 break; case Node.SYNC_ACTION_ERROR: default: + // 未知同步操作类型,抛出异常 throw new ActionFailureException("unkown sync action type"); } } - + // 本地新增节点的具体实现方法 private void addLocalNode(Node node) throws NetworkFailureException { if (mCancelled) { return; @@ -549,7 +562,7 @@ public class GTaskManager { if (note.has(NoteColumns.ID)) { long id = note.getLong(NoteColumns.ID); if (DataUtils.existInNoteDatabase(mContentResolver, id)) { - // the id is not available, have to create a new one + // 如果本地数据库中已存在该笔记ID,则移除该ID,重新生成新的笔记 note.remove(NoteColumns.ID); } } @@ -562,8 +575,7 @@ public class GTaskManager { if (data.has(DataColumns.ID)) { long dataId = data.getLong(DataColumns.ID); if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { - // the data id is not available, have to create - // a new one + // 如果本地数据库中已存在该数据ID,则移除该ID,重新生成新的数据 data.remove(DataColumns.ID); } } @@ -584,25 +596,25 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); } - // create the local node + // 创建本地节点 sqlNote.setGtaskId(node.getGid()); sqlNote.commit(false); - // update gid-nid mapping + // 更新gid - nid映射 mGidToNid.put(node.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), node.getGid()); - // update meta + // 更新远程元数据 updateRemoteMeta(node.getGid(), sqlNote); } - + // 本地更新节点的具体实现方法 private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; } SqlNote sqlNote; - // update the note locally + // 更新本地笔记 sqlNote = new SqlNote(mContext, c); sqlNote.setContent(node.getLocalJSONFromContent()); @@ -615,10 +627,10 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); sqlNote.commit(true); - // update meta info + // 更新远程元数据 updateRemoteMeta(node.getGid(), sqlNote); } - + // 远程新增节点的具体实现方法 private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -627,7 +639,7 @@ public class GTaskManager { SqlNote sqlNote = new SqlNote(mContext, c); Node n; - // update remotely + // 远程更新操作 if (sqlNote.isNoteType()) { Task task = new Task(); task.setContentByLocalJSON(sqlNote.getContent()); @@ -642,12 +654,12 @@ public class GTaskManager { GTaskClient.getInstance().createTask(task); n = (Node) task; - // add meta + // 添加元数据 updateRemoteMeta(task.getGid(), sqlNote); } else { TaskList tasklist = null; - // we need to skip folder if it has already existed + // 检查文件夹是否已存在,如果存在则获取对应的TaskList对象 String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) folderName += GTaskStringUtils.FOLDER_DEFAULT; @@ -671,7 +683,7 @@ public class GTaskManager { } } - // no match we can add now + // 如果未找到匹配的文件夹,则创建新的TaskList对象 if (tasklist == null) { tasklist = new TaskList(); tasklist.setContentByLocalJSON(sqlNote.getContent()); @@ -681,17 +693,17 @@ public class GTaskManager { n = (Node) tasklist; } - // update local note + // 更新本地笔记 sqlNote.setGtaskId(n.getGid()); sqlNote.commit(false); sqlNote.resetLocalModified(); sqlNote.commit(true); - // gid-id mapping + // 更新gid - id映射 mGidToNid.put(n.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), n.getGid()); } - + // 远程更新节点的具体实现方法 private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -699,11 +711,11 @@ public class GTaskManager { SqlNote sqlNote = new SqlNote(mContext, c); - // update remotely + // 远程更新节点内容 node.setContentByLocalJSON(sqlNote.getContent()); GTaskClient.getInstance().addUpdateNode(node); - // update meta + // 更新远程元数据 updateRemoteMeta(node.getGid(), sqlNote); // move task if necessary @@ -725,11 +737,11 @@ public class GTaskManager { } } - // clear local modified flag + // 清除本地修改标志 sqlNote.resetLocalModified(); sqlNote.commit(true); } - + // 更新远程元数据的方法 private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { if (sqlNote != null && sqlNote.isNoteType()) { MetaData metaData = mMetaHashMap.get(gid); @@ -745,13 +757,13 @@ public class GTaskManager { } } } - + // 刷新本地同步ID的方法 private void refreshLocalSyncId() throws NetworkFailureException { if (mCancelled) { return; } - // get the latest gtask list + // 重新获取最新的Google Task列表,清除之前的数据缓存 mGTaskHashMap.clear(); mGTaskListHashMap.clear(); mMetaHashMap.clear(); @@ -789,11 +801,11 @@ public class GTaskManager { } } } - + // 获取同步账户名称的方法 public String getSyncAccount() { return GTaskClient.getInstance().getSyncAccount().name; } - + // 取消同步操作的方法 public void cancelSync() { mCancelled = true; } diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskSyncService.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskSyncService.java index cca36f7..0f305c6 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskSyncService.java +++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskSyncService.java @@ -22,70 +22,89 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; - +// GTaskSyncService类是一个Android服务,用于管理Google Task的同步任务 public class GTaskSyncService extends Service { + // 定义用于表示同步操作类型的字符串常量 public final static String ACTION_STRING_NAME = "sync_action_type"; - + // 表示开始同步的操作类型值 public final static int ACTION_START_SYNC = 0; - + // 表示取消同步的操作类型值 public final static int ACTION_CANCEL_SYNC = 1; - + // 表示无效操作类型的默认值 public final static int ACTION_INVALID = 2; - + // 定义用于广播同步服务相关信息的动作名称 public final static 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"; - + // 存储当前正在执行的同步任务实例,初始化为null private static GTaskASyncTask mSyncTask = null; - + // 存储同步进度消息,初始化为空字符串 private static String mSyncProgress = ""; - + // 启动同步任务的方法 private void startSync() { + // 如果当前没有正在执行的同步任务 if (mSyncTask == null) { + // 创建一个新的GTaskASyncTask实例,并传入当前服务实例和一个完成监听器 mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { + // 当同步任务完成时调用的方法 public void onComplete() { + // 将当前同步任务实例设置为null,表示任务已完成 mSyncTask = null; + // 发送一个空消息的广播 sendBroadcast(""); + // 停止当前服务 stopSelf(); } }); + // 发送一个空消息的广播,可能用于通知相关组件同步任务即将开始 sendBroadcast(""); + // 执行同步任务 mSyncTask.execute(); } } - + // 取消同步任务的方法 private void cancelSync() { + // 如果当前有正在执行的同步任务 if (mSyncTask != null) { + // 调用同步任务的取消同步方法 mSyncTask.cancelSync(); } } - + // 服务创建时调用的方法,将同步任务实例设置为null @Override public void onCreate() { mSyncTask = null; } - + // 服务接收到启动命令时调用的方法 @Override public int onStartCommand(Intent intent, int flags, int startId) { + // 获取启动意图中的额外数据 Bundle bundle = intent.getExtras(); + // 如果额外数据不为空且包含同步操作类型键 if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { + // 根据操作类型值执行相应操作 switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { case ACTION_START_SYNC: + // 启动同步任务 startSync(); break; case ACTION_CANCEL_SYNC: + // 取消同步任务 cancelSync(); break; default: break; } + // 返回粘性服务标志,表示服务在被异常终止后会尝试重新启动 return START_STICKY; } + // 如果不满足上述条件,则调用父类的onStartCommand方法 return super.onStartCommand(intent, flags, startId); } + // 系统内存不足时调用的方法,如果有正在执行的同步任务则取消它 @Override public void onLowMemory() { if (mSyncTask != null) { @@ -93,35 +112,47 @@ public class GTaskSyncService extends Service { } } + // 服务绑定方法,返回null表示不支持绑定操作 public IBinder onBind(Intent intent) { return null; } + // 发送广播的方法,用于向感兴趣的组件发送同步服务的相关信息 public void sendBroadcast(String msg) { + // 更新同步进度消息 mSyncProgress = msg; + // 创建一个意图,指定广播动作名称 Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); + // 将是否正在同步的状态添加到意图的额外数据中 intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); + // 将同步进度消息添加到意图的额外数据中 intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); + // 发送广播 sendBroadcast(intent); } - + // 静态方法,用于启动同步任务,在Activity中调用 public static void startSync(Activity activity) { + // 设置同步任务的Activity上下文 GTaskManager.getInstance().setActivityContext(activity); + // 创建一个启动当前服务的意图,并指定操作类型为开始同步 Intent intent = new Intent(activity, GTaskSyncService.class); intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); + // 启动服务 activity.startService(intent); } - + // 静态方法,用于取消同步任务,在Context中调用 public static void cancelSync(Context context) { + // 创建一个启动当前服务的意图,并指定操作类型为取消同步 Intent intent = new Intent(context, GTaskSyncService.class); intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); + // 启动服务 context.startService(intent); } - + // 静态方法,用于检查是否正在进行同步任务 public static boolean isSyncing() { return mSyncTask != null; } - + // 静态方法,用于获取同步进度消息 public static String getProgressString() { return mSyncProgress; }