diff --git a/doc/小米便签开源代码的泛读报告.docx b/doc/小米便签开源代码的泛读报告.docx deleted file mode 100644 index aa536bf..0000000 Binary files a/doc/小米便签开源代码的泛读报告.docx and /dev/null differ diff --git a/doc/小米便签开源代码的泛读报告_樊灿宇.docx b/doc/小米便签开源代码的泛读报告_樊灿宇.docx deleted file mode 100644 index 6ebd0b2..0000000 Binary files a/doc/小米便签开源代码的泛读报告_樊灿宇.docx and /dev/null differ diff --git a/doc/小米便签开源代码的泛读报告_粟嘉政.docx b/doc/小米便签开源代码的泛读报告_粟嘉政.docx deleted file mode 100644 index 07d9c7d..0000000 Binary files a/doc/小米便签开源代码的泛读报告_粟嘉政.docx and /dev/null differ diff --git a/doc/开源软件泛读、标注和维护报告文档.docx b/doc/开源软件泛读、标注和维护报告文档.docx new file mode 100644 index 0000000..8412314 Binary files /dev/null and b/doc/开源软件泛读、标注和维护报告文档.docx differ diff --git a/doc/操作.docx b/doc/操作.docx deleted file mode 100644 index 841b92b..0000000 Binary files a/doc/操作.docx and /dev/null differ diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/MainActivity.java b/src/Notes-master/app/src/main/java/net/micode/notes/MainActivity.java index 8091753..575d729 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/MainActivity.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/MainActivity.java @@ -7,14 +7,23 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; - +/** + * 主活动类,作为应用的入口界面 + * 继承自AppCompatActivity,适配不同Android版本 + */ public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { + // 调用父类的onCreate方法,必须执行,保证Activity的基础初始化逻辑完成 super.onCreate(savedInstanceState); + + // 启用边缘到边缘显示,让内容延伸到系统栏后面 EdgeToEdge.enable(this); + setContentView(R.layout.activity_main); + + // 设置窗口边距适配,处理系统栏(状态栏、导航栏)的遮挡问题 ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/data/Contact.java b/src/Notes-master/app/src/main/java/net/micode/notes/data/Contact.java index d97ac5d..405ccc8 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/data/Contact.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/data/Contact.java @@ -26,9 +26,10 @@ import android.util.Log; import java.util.HashMap; public class Contact { + // 联系人缓存,用于存储电话号码到联系人姓名的映射,避免重复查询 private static HashMap sContactCache; private static final String TAG = "Contact"; - + // 这个语句用于根据电话号码查找对应的联系人姓名 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 " @@ -40,13 +41,14 @@ public class Contact { if(sContactCache == null) { sContactCache = new HashMap(); } - + // 首先检查缓存中是否已有该电话号码的联系人信息 if(sContactCache.containsKey(phoneNumber)) { return sContactCache.get(phoneNumber); } String selection = CALLER_ID_SELECTION.replace("+", PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + // 执行查询:通过ContentResolver查询联系人数据库 Cursor cursor = context.getContentResolver().query( Data.CONTENT_URI, new String [] { Phone.DISPLAY_NAME }, diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/data/Notes.java b/src/Notes-master/app/src/main/java/net/micode/notes/data/Notes.java index f240604..7814d00 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/data/Notes.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/data/Notes.java @@ -17,263 +17,405 @@ package net.micode.notes.data; import android.net.Uri; + +/** + * 笔记应用核心数据定义类 + * 统一管理笔记、文件夹的类型常量、Intent跳转参数、数据库列名以及内容提供者Uri + */ public class Notes { + /** + * 内容提供者(ContentProvider)的授权名称 + * 作为ContentProvider的唯一标识,用于关联应用的内容提供者组件 + */ public static final String AUTHORITY = "micode_notes"; + + /** + * 日志打印标签 + * 在使用Android Log类打印日志时,用于标识日志所属模块 + */ public static final String TAG = "Notes"; + + //数据类型:普通笔记 + public static final int TYPE_NOTE = 0; + + + //数据类型:文件夹 + public static final int TYPE_FOLDER = 1; + + //数据类型:系统内置类型 + public static final int TYPE_SYSTEM = 2; /** - * Following IDs are system folders' identifiers - * {@link Notes#ID_ROOT_FOLDER } is default folder - * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder - * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records + * 以下是系统内置文件夹的固定ID + * {@link Notes#ID_ROOT_FOLDER }:默认根文件夹(所有普通笔记的默认归属) + * {@link Notes#ID_TEMPARAY_FOLDER }:临时文件夹(存放无归属的笔记) + * {@link Notes#ID_CALL_RECORD_FOLDER}:通话记录文件夹(专门存储通话记录衍生的笔记) */ - public static final int ID_ROOT_FOLDER = 0; - public static final int ID_TEMPARAY_FOLDER = -1; - public static final int ID_CALL_RECORD_FOLDER = -2; - public static final int ID_TRASH_FOLER = -3; + public static final int ID_ROOT_FOLDER = 0; // 根文件夹ID + public static final int ID_TEMPARAY_FOLDER = -1; // 临时文件夹ID + public static final int ID_CALL_RECORD_FOLDER = -2; // 通话记录文件夹ID + public static final int ID_TRASH_FOLER = -3; // 回收站文件夹ID(存放删除的笔记/文件夹) + /** + * Intent跳转额外参数:笔记提醒日期 + * 用于在页面间传递笔记的提醒时间戳 + */ public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; + + /** + * Intent跳转额外参数:笔记背景颜色ID + * 用于在页面间传递笔记的背景色标识 + */ public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; + + /** + * Intent跳转额外参数:桌面小组件ID + * 用于传递笔记关联的桌面小组件标识 + */ 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"; + + /** + * Intent跳转额外参数:文件夹ID + * 用于传递笔记所属的文件夹标识 + */ 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; + /** + * 数据类型常量内部类 + * 统一管理不同类型笔记的内容项类型标识(Content Item Type) + */ public static class DataConstants { + /** + * 文本笔记的内容项类型标识 + */ public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; + + /** + * 通话笔记的内容项类型标识 + */ public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; } /** - * Uri to query all notes and folders + * 内容提供者Uri:查询所有笔记和文件夹 + * 用于通过ContentResolver操作(查询/新增/修改/删除)所有笔记和文件夹数据 */ public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); /** - * Uri to query data + * 内容提供者Uri:查询笔记关联数据 + * 用于通过ContentResolver操作笔记的附属数据(如文本内容、通话信息等) */ public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); + /** + * 笔记/文件夹数据表的列名接口 + * 定义了笔记和文件夹在数据库中对应的所有字段名称及字段类型 + */ public interface NoteColumns { /** - * The unique ID for a row - *

Type: INTEGER (long)

+ * 数据表行唯一标识ID + *

字段类型: INTEGER (long) 长整型

*/ public static final String ID = "_id"; /** - * The parent's id for note or folder - *

Type: INTEGER (long)

+ * 父级ID + * 标识笔记/文件夹所属的父文件夹ID + *

字段类型: INTEGER (long) 长整型

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

Type: INTEGER (long)

+ * 创建时间戳 + * 记录笔记/文件夹的创建时间(毫秒级) + *

字段类型: INTEGER (long) 长整型

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

Type: INTEGER (long)

+ * 最后修改时间戳 + * 记录笔记/文件夹的最新修改时间(毫秒级) + *

字段类型: INTEGER (long) 长整型

*/ public static final String MODIFIED_DATE = "modified_date"; - /** - * Alert date - *

Type: INTEGER (long)

+ * 提醒时间戳 + * 记录笔记的提醒触发时间(毫秒级,文件夹无此字段实际意义) + *

字段类型: INTEGER (long) 长整型

*/ public static final String ALERTED_DATE = "alert_date"; /** - * Folder's name or text content of note - *

Type: TEXT

+ * 摘要/名称 + * 文件夹:存储文件夹名称;笔记:存储笔记文本内容的摘要 + *

字段类型: TEXT 字符串类型

*/ public static final String SNIPPET = "snippet"; /** - * Note's widget id - *

Type: INTEGER (long)

+ * 小组件ID + * 记录笔记关联的桌面小组件唯一标识(无关联时为无效值) + *

字段类型: INTEGER (long) 长整型

*/ public static final String WIDGET_ID = "widget_id"; /** - * Note's widget type - *

Type: INTEGER (long)

+ * 小组件类型 + * 记录笔记关联的桌面小组件尺寸类型(对应TYPE_WIDGET_2X等常量) + *

字段类型: 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

+ * 是否有附件 + * 标识笔记是否包含附件(1:有附件;0:无附件,仅对笔记有效) + *

字段类型: 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

+ * 类型标识 + * 区分当前数据是笔记、文件夹还是系统类型(对应TYPE_NOTE等常量) + *

字段类型: 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

+ * 本地修改标识 + * 标识本地数据是否已修改未同步(1:已修改;0:未修改) + *

字段类型: INTEGER 整型

*/ public static final String LOCAL_MODIFIED = "local_modified"; /** - * Original parent id before moving into temporary folder - *

Type : INTEGER

+ * 原始父级ID + * 记录笔记移入临时文件夹之前的原始父文件夹ID + *

字段类型: INTEGER 整型

*/ public static final String ORIGIN_PARENT_ID = "origin_parent_id"; /** - * The gtask id - *

Type : TEXT

+ * GTask任务ID + * 关联谷歌任务(GTask)的唯一标识 + *

字段类型: TEXT 字符串类型

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

Type : INTEGER (long)

+ * 版本号 + * 数据版本标识,用于解决数据同步时的冲突问题 + *

字段类型: INTEGER (long) 长整型

*/ public static final String VERSION = "version"; } + /** + * 笔记关联数据表的列名接口 + * 定义了笔记附属数据(文本内容、通话信息等)在数据库中的字段名称及类型 + */ public interface DataColumns { /** - * The unique ID for a row - *

Type: INTEGER (long)

+ * 数据表行唯一标识ID + *

字段类型: INTEGER (long) 长整型

*/ public static final String ID = "_id"; /** - * The MIME type of the item represented by this row. - *

Type: Text

+ * MIME类型 + * 区分当前数据的类型(如文本笔记、通话笔记) + *

字段类型: TEXT 字符串类型

*/ public static final String MIME_TYPE = "mime_type"; /** - * The reference id to note that this data belongs to - *

Type: INTEGER (long)

+ * 关联笔记ID + * 标识当前数据所属的笔记唯一ID + *

字段类型: INTEGER (long) 长整型

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

Type: INTEGER (long)

+ * 创建时间戳 + * 记录当前附属数据的创建时间(毫秒级) + *

字段类型: INTEGER (long) 长整型

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

Type: INTEGER (long)

+ * 最后修改时间戳 + * 记录当前附属数据的最新修改时间(毫秒级) + *

字段类型: INTEGER (long) 长整型

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

Type: TEXT

+ * 数据内容 + * 存储笔记的核心附属内容(如文本笔记的完整文本) + *

字段类型: TEXT 字符串类型

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

Type: INTEGER

+ * 通用整型数据字段1 + * 含义随MIME_TYPE变化,专门存储整型数据 + *

字段类型: INTEGER 整型

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

Type: INTEGER

+ * 通用整型数据字段2 + * 含义随MIME_TYPE变化,专门存储整型数据 + *

字段类型: INTEGER 整型

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

Type: TEXT

+ * 通用字符串数据字段3 + * 含义随MIME_TYPE变化,专门存储字符串数据 + *

字段类型: TEXT 字符串类型

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

Type: TEXT

+ * 通用字符串数据字段4 + * 含义随MIME_TYPE变化,专门存储字符串数据 + *

字段类型: TEXT 字符串类型

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

Type: TEXT

+ * 通用字符串数据字段5 + * 含义随MIME_TYPE变化,专门存储字符串数据 + *

字段类型: TEXT 字符串类型

*/ public static final String DATA5 = "data5"; } + /** + * 文本笔记数据定义内部类 + * 继承自DataColumns,专门描述文本笔记的专属常量和内容提供者Uri + */ public static final class TextNote implements DataColumns { /** - * Mode to indicate the text in check list mode or not - *

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

+ * 文本笔记模式标识 + * 映射到DATA1字段,用于标识文本笔记是否为复选列表模式 + *

字段类型: Integer (1:复选列表模式;0:普通文本模式)

*/ public static final String MODE = DATA1; + /** + * 复选列表模式标识值 + * 表示当前文本笔记为复选列表格式 + */ public static final int MODE_CHECK_LIST = 1; + /** + * 文本笔记批量查询的MIME类型 + * 用于ContentResolver批量查询文本笔记数据时的类型标识 + */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; + /** + * 文本笔记单个查询的MIME类型 + * 用于ContentResolver查询单个文本笔记数据时的类型标识 + */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; + /** + * 文本笔记的内容提供者Uri + * 用于ContentResolver操作(增删改查)文本笔记数据 + */ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); } + /** + * 通话笔记数据定义内部类 + * 继承自DataColumns,专门描述通话记录笔记的专属常量和内容提供者Uri + */ public static final class CallNote implements DataColumns { /** - * Call date for this record - *

Type: INTEGER (long)

+ * 通话时间 + * 映射到DATA1字段,存储通话发生的时间戳(毫秒级) + *

字段类型: INTEGER (long) 长整型

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

Type: TEXT

+ * 手机号码 + * 映射到DATA3字段,存储通话对应的手机号码 + *

字段类型: TEXT 字符串类型

*/ public static final String PHONE_NUMBER = DATA3; + /** + * 通话笔记批量查询的MIME类型 + * 用于ContentResolver批量查询通话笔记数据时的类型标识 + */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; + /** + * 通话笔记单个查询的MIME类型 + * 用于ContentResolver查询单个通话笔记数据时的类型标识 + */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; + /** + * 通话笔记的内容提供者Uri + * 用于ContentResolver操作(增删改查)通话笔记数据 + */ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index ffe5d57..395a955 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java @@ -26,198 +26,275 @@ 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 { +/** + * 笔记应用数据库帮助类 + * 继承自SQLiteOpenHelper,负责数据库的创建、版本升级、表/触发器/索引的构建 + * 同时提供单例实例,保证全局数据库操作的一致性 + */ +public class NotesDatabaseHelper extends SQLiteOpenHelper {// 继承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"; + /** + * 单例实例对象 + * 保证全局只有一个NotesDatabaseHelper实例,避免数据库连接冲突 + */ private static NotesDatabaseHelper mInstance; + /** + * 创建NOTE表的SQL语句 + * NOTE表用于存储笔记和文件夹的核心信息,包含主键、父子关系、时间戳、样式等字段 + * 所有字段均设置了默认值,保证数据完整性 + */ private static final String CREATE_NOTE_TABLE_SQL = - "CREATE TABLE " + TABLE.NOTE + "(" + - NoteColumns.ID + " INTEGER PRIMARY KEY," + - NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + - NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + - NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + - NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + - ")"; + "CREATE TABLE " + TABLE.NOTE + "(" + + NoteColumns.ID + " INTEGER PRIMARY KEY," + // 主键ID,自增唯一标识 + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 父文件夹ID,默认0 + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 提醒时间戳,默认0(无提醒) + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + // 背景色ID,默认0(默认颜色) + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间,默认当前时间戳 + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + // 是否有附件,默认0(无附件) + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 最后修改时间,默认当前时间戳 + NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + // 文件夹下笔记数量,默认0 + NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + // 文件夹名称/笔记摘要,默认空字符串 + NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + // 类型(笔记/文件夹/系统),默认0(普通笔记) + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + // 关联小组件ID,默认0(无关联) + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 关联小组件类型,默认-1(无效类型) + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步ID,默认0(未同步) + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + // 本地修改标识,默认0(未修改) + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 原始父文件夹ID,默认0 + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // 谷歌任务ID,默认空字符串 + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 数据版本号,默认0 + ")"; + /** + * 创建DATA表的SQL语句 + * DATA表用于存储笔记的附属数据,关联NOTE表,包含MIME类型、内容、通用数据字段等 + */ private static final String CREATE_DATA_TABLE_SQL = - "CREATE TABLE " + TABLE.DATA + "(" + - DataColumns.ID + " INTEGER PRIMARY KEY," + - DataColumns.MIME_TYPE + " TEXT NOT NULL," + - DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA1 + " INTEGER," + - DataColumns.DATA2 + " INTEGER," + - DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + - ")"; + "CREATE TABLE " + TABLE.DATA + "(" + + DataColumns.ID + " INTEGER PRIMARY KEY," + // 主键ID,自增唯一标识 + DataColumns.MIME_TYPE + " TEXT NOT NULL," + // MIME类型,区分数据类型(文本/通话等) + DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 关联的笔记ID,默认0 + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间戳 + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 最后修改时间戳 + DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + // 核心数据内容,默认空字符串 + DataColumns.DATA1 + " INTEGER," + // 通用整型字段1 + DataColumns.DATA2 + " INTEGER," + // 通用整型字段2 + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + // 通用字符串字段3 + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + // 通用字符串字段4 + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + // 通用字符串字段5 + ")"; + /** + * 创建DATA表note_id索引的SQL语句 + * 索引作用:提升通过笔记ID(note_id)查询附属数据的查询效率,避免全表扫描 + */ private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = - "CREATE INDEX IF NOT EXISTS note_id_index ON " + - TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; + "CREATE INDEX IF NOT EXISTS note_id_index ON " + + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; /** - * Increase folder's note count when move note to the folder + * 触发器:更新笔记父文件夹时,增加新父文件夹的笔记数量 + * 触发时机:NOTE表的PARENT_ID字段更新后 + * 执行操作:将新父文件夹的notes_count字段值+1 */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER increase_folder_count_on_update "+ - " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + - " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + - " END"; + "CREATE TRIGGER increase_folder_count_on_update "+ + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " END"; /** - * Decrease folder's note count when move note from folder + * 触发器:更新笔记父文件夹时,减少旧父文件夹的笔记数量 + * 触发时机:NOTE表的PARENT_ID字段更新后 + * 执行操作:将旧父文件夹的notes_count字段值-1(需保证数量大于0) */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER decrease_folder_count_on_update " + - " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + - " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + - " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + - " END"; + "CREATE TRIGGER decrease_folder_count_on_update " + + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + + " END"; /** - * Increase folder's note count when insert new note to the folder + * 触发器:插入新笔记时,增加对应父文件夹的笔记数量 + * 触发时机:NOTE表插入新数据后 + * 执行操作:将新笔记父文件夹的notes_count字段值+1 */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = - "CREATE TRIGGER increase_folder_count_on_insert " + - " AFTER INSERT ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + - " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + - " END"; + "CREATE TRIGGER increase_folder_count_on_insert " + + " AFTER INSERT ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " END"; /** - * Decrease folder's note count when delete note from the folder + * 触发器:删除笔记时,减少对应父文件夹的笔记数量 + * 触发时机:NOTE表删除数据后 + * 执行操作:将被删笔记父文件夹的notes_count字段值-1(需保证数量大于0) */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = - "CREATE TRIGGER decrease_folder_count_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + - " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + - " AND " + NoteColumns.NOTES_COUNT + ">0;" + - " END"; + "CREATE TRIGGER decrease_folder_count_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0;" + + " END"; /** - * Update note's content when insert data with type {@link DataConstants#NOTE} + * 触发器:插入文本笔记数据时,更新对应笔记的摘要内容 + * 触发时机:DATA表插入数据后,且数据类型为文本笔记 + * 执行操作:将笔记的snippet字段更新为文本笔记的content内容 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = - "CREATE TRIGGER update_note_content_on_insert " + - " AFTER INSERT ON " + TABLE.DATA + - " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + - " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + - " END"; + "CREATE TRIGGER update_note_content_on_insert " + + " AFTER INSERT ON " + TABLE.DATA + + " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END"; /** - * Update note's content when data with {@link DataConstants#NOTE} type has changed + * 触发器:更新文本笔记数据时,更新对应笔记的摘要内容 + * 触发时机:DATA表更新数据后,且原数据类型为文本笔记 + * 执行操作:将笔记的snippet字段更新为文本笔记的新content内容 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER update_note_content_on_update " + - " AFTER UPDATE ON " + TABLE.DATA + - " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + - " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + - " END"; + "CREATE TRIGGER update_note_content_on_update " + + " AFTER UPDATE ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END"; /** - * Update note's content when data with {@link DataConstants#NOTE} type has deleted + * 触发器:删除文本笔记数据时,清空对应笔记的摘要内容 + * 触发时机:DATA表删除数据后,且原数据类型为文本笔记 + * 执行操作:将对应笔记的snippet字段置为空字符串 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = - "CREATE TRIGGER update_note_content_on_delete " + - " AFTER delete ON " + TABLE.DATA + - " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=''" + - " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + - " END"; + "CREATE TRIGGER update_note_content_on_delete " + + " AFTER delete ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=''" + + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + + " END"; /** - * Delete datas belong to note which has been deleted + * 触发器:删除笔记时,级联删除该笔记对应的所有附属数据 + * 触发时机:NOTE表删除数据后 + * 执行操作:删除DATA表中该笔记对应的所有关联数据,避免数据冗余 */ private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = - "CREATE TRIGGER delete_data_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN" + - " DELETE FROM " + TABLE.DATA + - " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + - " END"; + "CREATE TRIGGER delete_data_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.DATA + + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + + " END"; /** - * Delete notes belong to folder which has been deleted + * 触发器:删除文件夹时,级联删除该文件夹下的所有笔记/子文件夹 + * 触发时机:NOTE表删除数据后(删除的是文件夹) + * 执行操作:删除NOTE表中父文件夹为该文件夹的所有数据 */ private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = - "CREATE TRIGGER folder_delete_notes_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN" + - " DELETE FROM " + TABLE.NOTE + - " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + - " END"; + "CREATE TRIGGER folder_delete_notes_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END"; /** - * Move notes belong to folder which has been moved to trash folder + * 触发器:文件夹移入回收站时,级联移动其下所有笔记/子文件夹到回收站 + * 触发时机:NOTE表更新数据后,且新父文件夹为回收站 + * 执行操作:将该文件夹下所有数据的父文件夹ID更新为回收站ID */ private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = - "CREATE TRIGGER folder_move_notes_on_trash " + - " AFTER UPDATE ON " + TABLE.NOTE + - " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + - " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + - " END"; + "CREATE TRIGGER folder_move_notes_on_trash " + + " AFTER UPDATE ON " + TABLE.NOTE + + " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END"; + /** + * 构造方法 + * @param context 上下文对象,用于初始化SQLiteOpenHelper + */ public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } + /** + * 创建NOTE表及相关资源 + * @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"); + db.execSQL(CREATE_NOTE_TABLE_SQL); // 执行创建NOTE表的SQL + reCreateNoteTableTriggers(db); // 重建NOTE表相关触发器 + createSystemFolder(db); // 初始化系统内置文件夹 + Log.d(TAG, "note table has been created"); // 打印日志,标识表创建完成 } + /** + * 重建NOTE表相关触发器 + * 先删除旧触发器(避免重复创建异常),再创建新触发器 + * @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,6 +303,7 @@ 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); @@ -235,18 +313,23 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); } + /** + * 初始化系统内置文件夹 + * 插入4个系统文件夹:通话记录文件夹、根文件夹、临时文件夹、回收站文件夹 + * @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); @@ -254,7 +337,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.insert(TABLE.NOTE, null, values); /** - * temporary folder which is used for moving note + * 插入临时文件夹(用于存放无归属的笔记) */ values.clear(); values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); @@ -262,7 +345,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.insert(TABLE.NOTE, null, values); /** - * create trash folder + * 插入回收站文件夹(存放删除的笔记/文件夹) */ values.clear(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); @@ -270,23 +353,40 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.insert(TABLE.NOTE, null, values); } + /** + * 创建DATA表及相关资源 + * @param db SQLite数据库实例 + */ 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"); + db.execSQL(CREATE_DATA_TABLE_SQL); // 执行创建DATA表的SQL + reCreateDataTableTriggers(db); // 重建DATA表相关触发器 + db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建note_id索引 + Log.d(TAG, "data table has been created"); // 打印日志,标识表创建完成 } + /** + * 重建DATA表相关触发器 + * 先删除旧触发器,再创建新触发器,避免重复创建异常 + * @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); } + /** + * 获取NotesDatabaseHelper单例实例 + * 同步方法,保证多线程环境下实例的唯一性 + * @param context 上下文对象 + * @return NotesDatabaseHelper单例实例 + */ static synchronized NotesDatabaseHelper getInstance(Context context) { if (mInstance == null) { mInstance = new NotesDatabaseHelper(context); @@ -294,45 +394,67 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { return mInstance; } + /** + * 数据库首次创建时调用 + * 依次创建NOTE表和DATA表 + * @param db SQLite数据库实例 + */ @Override public void onCreate(SQLiteDatabase db) { createNoteTable(db); createDataTable(db); } + /** + * 数据库版本升级时调用 + * 根据旧版本号逐步升级到新版本,处理表结构变更和触发器重建 + * @param db SQLite数据库实例 + * @param oldVersion 旧数据库版本号 + * @param newVersion 新数据库版本号 + */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - boolean reCreateTriggers = false; - boolean skipV2 = false; + boolean reCreateTriggers = false; // 是否需要重建触发器标识 + boolean skipV2 = false; // 是否跳过版本2升级标识 + // 从版本1升级到版本2 if (oldVersion == 1) { upgradeToV2(db); - skipV2 = true; // this upgrade including the upgrade from v2 to v3 + skipV2 = true; // 版本1的升级已包含版本2到3的内容 oldVersion++; } + // 从版本2升级到版本3(未跳过的情况) if (oldVersion == 2 && !skipV2) { upgradeToV3(db); - reCreateTriggers = true; + reCreateTriggers = true; // 该版本升级需要重建触发器 oldVersion++; } + // 从版本3升级到版本4 if (oldVersion == 3) { upgradeToV4(db); oldVersion++; } + // 若需要重建触发器,则执行重建操作 if (reCreateTriggers) { reCreateNoteTableTriggers(db); reCreateDataTableTriggers(db); } + // 升级完成后校验版本号,不一致则抛出异常 if (oldVersion != newVersion) { throw new IllegalStateException("Upgrade notes database to version " + newVersion + "fails"); } } + /** + * 升级数据库到版本2 + * 删除旧表,重新创建NOTE表和DATA表及相关资源 + * @param db SQLite数据库实例 + */ private void upgradeToV2(SQLiteDatabase db) { db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); @@ -340,23 +462,35 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { createDataTable(db); } + /** + * 升级数据库到版本3 + * 1. 删除无用触发器 + * 2. 给NOTE表添加GTASK_ID字段 + * 3. 插入回收站系统文件夹 + * @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 + // 给NOTE表添加gtask_id字段 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''"); - // add a trash system folder + // 添加回收站系统文件夹 ContentValues values = new ContentValues(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } + /** + * 升级数据库到版本4 + * 给NOTE表添加VERSION字段(数据版本号,用于同步冲突处理) + * @param db SQLite数据库实例 + */ private void upgradeToV4(SQLiteDatabase db) { db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesProvider.java b/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesProvider.java index edb0a60..5fe86ea 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesProvider.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/data/NotesProvider.java @@ -34,22 +34,24 @@ import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.NotesDatabaseHelper.TABLE; - +/** + * 笔记应用内容提供者 + * 提供笔记/数据的CRUD操作及搜索功能,对外暴露数据访问接口 + */ public class NotesProvider extends ContentProvider { - 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 UriMatcher mMatcher; // Uri匹配器,用于区分不同操作类型 + private NotesDatabaseHelper mHelper; // 数据库帮助类实例 + private static final String TAG = "NotesProvider"; // 日志标签 - private static final int URI_SEARCH = 5; - private static final int URI_SEARCH_SUGGEST = 6; + // Uri匹配对应的常量标识 + private static final int URI_NOTE = 1; // 笔记列表 + private static final int URI_NOTE_ITEM = 2; // 单个笔记 + private static final int URI_DATA = 3; // 数据列表 + private static final int URI_DATA_ITEM = 4; // 单个数据 + private static final int URI_SEARCH = 5; // 笔记搜索 + private static final int URI_SEARCH_SUGGEST = 6; // 搜索建议 + // 静态代码块:初始化Uri匹配规则 static { mMatcher = new UriMatcher(UriMatcher.NO_MATCH); mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); @@ -65,53 +67,56 @@ public class NotesProvider extends ContentProvider { * x'0A' represents the '\n' character in sqlite. For title and content in the search result, * we will trim '\n' and white space in order to show more information. */ + // 搜索结果投影字段:构建搜索建议所需返回数据 private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," - + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," - + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," - + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," - + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," - + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," - + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; + + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," + + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," + + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; + // 笔记搜索查询SQL:过滤回收站数据,仅查询普通笔记 private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION - + " FROM " + TABLE.NOTE - + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" - + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER - + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + + " FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; @Override public boolean onCreate() { - mHelper = NotesDatabaseHelper.getInstance(getContext()); + mHelper = NotesDatabaseHelper.getInstance(getContext()); // 初始化数据库帮助类单例 return true; } @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { + String sortOrder) { Cursor c = null; - SQLiteDatabase db = mHelper.getReadableDatabase(); + 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: + case URI_NOTE_ITEM: // 查询单个笔记 id = uri.getPathSegments().get(1); c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder); break; - case URI_DATA: + case URI_DATA: // 查询所有附属数据 c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder); break; - case URI_DATA_ITEM: + case URI_DATA_ITEM: // 查询单个附属数据 id = uri.getPathSegments().get(1); c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder); break; - case URI_SEARCH: - case URI_SEARCH_SUGGEST: + 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"); @@ -141,6 +146,7 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } + // 设置Uri通知,数据变化时触发监听 if (c != null) { c.setNotificationUri(getContext().getContentResolver(), uri); } @@ -149,13 +155,14 @@ public class NotesProvider extends ContentProvider { @Override public Uri insert(Uri uri, ContentValues values) { - SQLiteDatabase db = mHelper.getWritableDatabase(); + 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,18 +173,15 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } - // Notify the note uri + // 发送数据变化通知 if (noteId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); } - - // Notify the data uri if (dataId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); } - return ContentUris.withAppendedId(uri, insertedId); } @@ -187,17 +191,14 @@ public class NotesProvider extends ContentProvider { String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean deleteData = false; + // 根据Uri匹配删除对应数据 switch (mMatcher.match(uri)) { - case URI_NOTE: + 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 - */ long noteId = Long.valueOf(id); if (noteId <= 0) { break; @@ -205,11 +206,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 +219,7 @@ 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); @@ -233,22 +235,23 @@ public class NotesProvider extends ContentProvider { String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean updateData = false; + // 根据Uri匹配更新对应数据 switch (mMatcher.match(uri)) { - case URI_NOTE: + 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); 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); @@ -257,7 +260,7 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } - + // 发送数据变化通知 if (count > 0) { if (updateData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); @@ -267,10 +270,12 @@ public class NotesProvider extends ContentProvider { return count; } + // 拼接查询条件:处理非空selection,添加AND连接 private String parseSelection(String selection) { return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); } + // 自增笔记版本号:用于数据同步冲突处理 private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); @@ -302,4 +307,4 @@ public class NotesProvider extends ContentProvider { return null; } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/MetaData.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/MetaData.java index 3a2050b..c2a5897 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/MetaData.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/MetaData.java @@ -26,10 +26,11 @@ import org.json.JSONObject; public class MetaData extends Task { + // 日志标签,使用类名作为标签 private final static String TAG = MetaData.class.getSimpleName(); - + // 存储关联的Google Task ID private String mRelatedGid = null; - + //把某个编号和相关信息打包,存成一个特殊格式的备注,并给任务改个专用名 public void setMeta(String gid, JSONObject metaInfo) { try { metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); @@ -37,6 +38,7 @@ public class MetaData extends Task { Log.e(TAG, "failed to put related gid"); } setNotes(metaInfo.toString()); + // 设置任务名称为元数据特定的名称 setName(GTaskStringUtils.META_NOTE_NAME); } @@ -51,12 +53,16 @@ public class MetaData extends Task { @Override public void setContentByRemoteJSON(JSONObject js) { + // 调用父类的解析方法 super.setContentByRemoteJSON(js); + // 如果notes字段存在,则从中解析关联的GTask ID if (getNotes() != null) { try { JSONObject metaInfo = new JSONObject(getNotes().trim()); + // 从JSON中提取关联的GTask ID mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); } catch (JSONException e) { + // 解析失败时记录警告并清空关联ID Log.w(TAG, "failed to get related gid"); mRelatedGid = null; } diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Node.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Node.java index 63950e0..06fa13b 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Node.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Node.java @@ -19,7 +19,10 @@ package net.micode.notes.gtask.data; import android.database.Cursor; import org.json.JSONObject; - +/** + * 定义了GTask同步过程中通用的节点属性(唯一标识、名称、修改时间、删除标记)、 + * 是本地笔记与远程GTask数据同步的核心,子类需实现具体的同步逻辑。 + */ public abstract class Node { public static final int SYNC_ACTION_NONE = 0; @@ -38,15 +41,21 @@ public abstract class Node { public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; public static final int SYNC_ACTION_ERROR = 8; - + //GTask节点唯一标识(Gid,Google Tasks体系的全局唯一ID) private String mGid; - + //节点名称(对应GTask标题/本地笔记标题) private String mName; - + //最后修改时间戳 private long mLastModified; - + //删除标记 private boolean mDeleted; - + /** + * 构造方法:初始化节点默认属性 + * - Gid:null(未关联远程GTask) + * - 名称:空字符串 + * - 最后修改时间:0(未修改) + * - 删除标记:false(正常状态) + */ public Node() { mGid = null; mName = ""; diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlData.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlData.java index d3ec3be..c62e085 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlData.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlData.java @@ -36,10 +36,11 @@ import org.json.JSONObject; public class SqlData { + // 日志标签,使用类名作为标签 private static final String TAG = SqlData.class.getSimpleName(); - + // 表示未初始化的ID值 private static final int INVALID_ID = -99999; - + //指定要查询的列 public static final String[] PROJECTION_DATA = new String[] { DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, DataColumns.DATA3 @@ -56,7 +57,7 @@ public class SqlData { public static final int DATA_CONTENT_DATA_3_COLUMN = 4; private ContentResolver mContentResolver; - + //返回true表示是新建数据,false表示已存在 private boolean mIsCreate; private long mDataId; @@ -68,9 +69,11 @@ public class SqlData { private long mDataContentData1; private String mDataContentData3; - + // 存储数据字段的差异值 private ContentValues mDiffDataValues; - + /** + * 构造函数 - 用于创建新的SqlData对象 + */ public SqlData(Context context) { mContentResolver = context.getContentResolver(); mIsCreate = true; @@ -88,7 +91,9 @@ public class SqlData { loadFromCursor(c); mDiffDataValues = new ContentValues(); } - + /** + * 从数据库cursor加载数据到对象字段 + */ private void loadFromCursor(Cursor c) { mDataId = c.getLong(DATA_ID_COLUMN); mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); @@ -98,31 +103,32 @@ public class SqlData { } public void setContent(JSONObject js) throws JSONException { + // 处理数据ID long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; if (mIsCreate || mDataId != dataId) { mDiffDataValues.put(DataColumns.ID, dataId); } mDataId = dataId; - + // 处理MIME类型 String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE) : DataConstants.NOTE; if (mIsCreate || !mDataMimeType.equals(dataMimeType)) { mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); } mDataMimeType = dataMimeType; - + // 处理数据内容 String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : ""; if (mIsCreate || !mDataContent.equals(dataContent)) { mDiffDataValues.put(DataColumns.CONTENT, dataContent); } mDataContent = dataContent; - + //处理数据字段1 long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0; if (mIsCreate || mDataContentData1 != dataContentData1) { mDiffDataValues.put(DataColumns.DATA1, dataContentData1); } mDataContentData1 = dataContentData1; - + //处理数据字段3 String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : ""; if (mIsCreate || !mDataContentData3.equals(dataContentData3)) { mDiffDataValues.put(DataColumns.DATA3, dataContentData3); @@ -148,24 +154,27 @@ public class SqlData { if (mIsCreate) { if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { - mDiffDataValues.remove(DataColumns.ID); + mDiffDataValues.remove(DataColumns.ID);// 移除无效ID } mDiffDataValues.put(DataColumns.NOTE_ID, noteId); Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); try { + // 从插入返回的URI中提取新创建的ID mDataId = Long.valueOf(uri.getPathSegments().get(1)); } catch (NumberFormatException e) { Log.e(TAG, "Get note id error :" + e.toString()); throw new ActionFailureException("create note failed"); } } else { + // 更新现有数据的情况 if (mDiffDataValues.size() > 0) { int result = 0; if (!validateVersion) { result = mContentResolver.update(ContentUris.withAppendedId( Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); } else { + // 验证版本号更新 result = mContentResolver.update(ContentUris.withAppendedId( Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, " ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE @@ -179,8 +188,8 @@ public class SqlData { } } - mDiffDataValues.clear(); - mIsCreate = false; + mDiffDataValues.clear();// 清除差异值 + mIsCreate = false;// 标记为已提交 } public long getId() { diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java index 79a4095..c99409e 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java @@ -36,13 +36,19 @@ import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; - +/** + * 本地笔记数据库操作核心类(适配GTask同步) + * 封装笔记主表(note)的字段属性、数据库操作 + */ public class SqlNote { private static final String TAG = SqlNote.class.getSimpleName(); private static final int INVALID_ID = -99999; - + /** + * 笔记主表(note)查询投影字段数组 + * 定义查询笔记时需要加载的所有字段,与NoteColumns一一对应,用于Cursor取值 + */ 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, @@ -121,7 +127,10 @@ public class SqlNote { private ContentValues mDiffNoteValues; private ArrayList mDataList; - + /** + * 初始化笔记默认属性,如背景色为默认值、创建/修改时间为当前时间、类型为普通笔记 + * 构造方法重载 + */ public SqlNote(Context context) { mContext = context; mContentResolver = context.getContentResolver(); @@ -142,7 +151,10 @@ public class SqlNote { mDiffNoteValues = new ContentValues(); mDataList = new ArrayList(); } - + /** + * 从数据库Cursor加载已有笔记 + * 从Cursor中解析笔记主表字段,并加载附属数据 + */ public SqlNote(Context context, Cursor c) { mContext = context; mContentResolver = context.getContentResolver(); @@ -153,7 +165,10 @@ public class SqlNote { loadDataContent(); mDiffNoteValues = new ContentValues(); } - + /** + * 通过笔记ID加载已有笔记 + * 根据ID查询数据库获取Cursor,再加载笔记属性和附属数据 + */ public SqlNote(Context context, long id) { mContext = context; mContentResolver = context.getContentResolver(); @@ -165,7 +180,7 @@ public class SqlNote { mDiffNoteValues = new ContentValues(); } - + //按笔记ID查询数据库,加载笔记主表属性 private void loadFromCursor(long id) { Cursor c = null; try { @@ -184,7 +199,7 @@ public class SqlNote { c.close(); } } - + //从Cursor解析笔记主表字段到成员变量 private void loadFromCursor(Cursor c) { mId = c.getLong(ID_COLUMN); mAlertDate = c.getLong(ALERTED_DATE_COLUMN); @@ -199,7 +214,7 @@ public class SqlNote { mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); mVersion = c.getLong(VERSION_COLUMN); } - + //加载笔记附属数据,从data表查询并封装为SqlData对象 private void loadDataContent() { Cursor c = null; mDataList.clear(); @@ -213,6 +228,7 @@ public class SqlNote { Log.w(TAG, "it seems that the note has not data"); return; } + // while (c.moveToNext()) { SqlData data = new SqlData(mContext, c); mDataList.add(data); @@ -232,7 +248,7 @@ public class SqlNote { if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { Log.w(TAG, "cannot set system folder"); } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { - // for folder we can only update the snnipet and type + // 对文件夹,仅更新名称和类型 String snippet = note.has(NoteColumns.SNIPPET) ? note .getString(NoteColumns.SNIPPET) : ""; if (mIsCreate || !mSnippet.equals(snippet)) { @@ -247,6 +263,7 @@ public class SqlNote { } mType = type; } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { + //对所有普通笔记,更新所有字段 + 处理附属数据 JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID; if (mIsCreate || mId != id) { @@ -330,7 +347,7 @@ public class SqlNote { mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent); } mOriginParent = originParent; - + // 处理附属数据列表 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); SqlData sqlData = null; @@ -358,7 +375,10 @@ public class SqlNote { } return true; } - + /** + * 将笔记内容序列化为JSON对象,适配GTask同步格式 + * @return 封装笔记数据的JSON对象;新建笔记未提交时返回null,解析异常时返回null + */ public JSONObject getContent() { try { JSONObject js = new JSONObject(); @@ -463,10 +483,12 @@ public class SqlNote { } } } else { + // 更新笔记:验证ID有效性 if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) { Log.e(TAG, "No such note"); throw new IllegalStateException("Try to update note with invalid id"); } + // 更新主表(仅当有变更时) if (mDiffNoteValues.size() > 0) { mVersion ++; int result = 0; @@ -486,7 +508,7 @@ public class SqlNote { Log.w(TAG, "there is no update. maybe user updates note when syncing"); } } - + // 更新普通笔记附属数据 if (mType == Notes.TYPE_NOTE) { for (SqlData sqlData : mDataList) { sqlData.commit(mId, validateVersion, mVersion); diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Task.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Task.java index 6a19454..89c114d 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Task.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/Task.java @@ -33,18 +33,21 @@ import org.json.JSONObject; public class Task extends Node { + // 使用类名作为日志标签 private static final String TAG = Task.class.getSimpleName(); - + // 任务完成状态 private boolean mCompleted; private String mNotes; private JSONObject mMetaInfo; - + // 前一个兄弟任务 private Task mPriorSibling; private TaskList mParent; - + /** + * 初始化任务对象 + */ public Task() { super(); mCompleted = false; @@ -53,22 +56,24 @@ public class Task extends Node { mParent = null; mMetaInfo = null; } - + /** + * 生成创建任务的JSON动作 + * 用于向Google Tasks API发送创建请求 + */ public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); try { - // action_type + //设置动作类型 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - // action_id + // 设置动作id,用于标识 js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // index + // 设置索引 js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); - // entity_delta JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); @@ -79,17 +84,13 @@ public class Task extends Node { } js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - // parent_id js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); - // dest_parent_type js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_GROUP); - // list_id js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); - // prior_sibling_id if (mPriorSibling != null) { js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); } @@ -97,6 +98,7 @@ public class Task extends Node { } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); + // 抛出应用层的异常 throw new ActionFailureException("fail to generate task-create jsonobject"); } @@ -107,17 +109,15 @@ public class Task extends Node { JSONObject js = new JSONObject(); try { - // action_type + // 设置动作类型 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - // action_id + // 设置动作id js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // id js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - // entity_delta JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); if (getNotes() != null) { @@ -138,32 +138,26 @@ public class Task extends Node { public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { - // id if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); } - // last_modified if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); } - // name if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); } - // notes if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); } - // deleted if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); } - // completed if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); } @@ -208,7 +202,6 @@ public class Task extends Node { String name = getName(); try { if (mMetaInfo == null) { - // new task created from web if (name == null) { Log.w(TAG, "the note seems to be an empty one"); return null; @@ -225,7 +218,7 @@ public class Task extends Node { js.put(GTaskStringUtils.META_HEAD_NOTE, note); return js; } else { - // synced task + // 同步任务 JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); @@ -282,22 +275,19 @@ public class Task extends Node { } if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side return SYNC_ACTION_NONE; } else { - // apply remote to local + // 应用远程更新 return SYNC_ACTION_UPDATE_LOCAL; } } else { - // validate gtask id if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { Log.e(TAG, "gtask id doesn't match"); return SYNC_ACTION_ERROR; } if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only + // 仅允许本地更改 return SYNC_ACTION_UPDATE_REMOTE; } else { return SYNC_ACTION_UPDATE_CONFLICT; diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/TaskList.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/TaskList.java index 4ea21c5..7878ae3 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/TaskList.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/data/TaskList.java @@ -47,17 +47,16 @@ public class TaskList extends Node { JSONObject js = new JSONObject(); try { - // action_type + // 动作类型 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - // action_id + // 动作id js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // index + // 索引 js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); - // entity_delta JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); @@ -78,17 +77,15 @@ public class TaskList extends Node { JSONObject js = new JSONObject(); try { - // action_type + // 动作类型 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - // action_id + // 动作id js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // id js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - // entity_delta JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); @@ -111,12 +108,11 @@ public class TaskList extends Node { setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); } - // last_modified + // 时间戳 if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); } - // name if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); } @@ -186,25 +182,23 @@ public class TaskList extends Node { public int getSyncAction(Cursor c) { try { if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update + // 没有本地更新 if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side return SYNC_ACTION_NONE; } else { - // apply remote to local + // 应用同步 return SYNC_ACTION_UPDATE_LOCAL; } } else { - // validate gtask id if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { Log.e(TAG, "gtask id doesn't match"); return SYNC_ACTION_ERROR; } if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only + // 仅限本地修改 return SYNC_ACTION_UPDATE_REMOTE; } else { - // for folder conflicts, just apply local modification + // 对于文件夹冲突,只需应用本地修改 return SYNC_ACTION_UPDATE_REMOTE; } } @@ -225,7 +219,7 @@ public class TaskList extends Node { if (task != null && !mChildren.contains(task)) { ret = mChildren.add(task); if (ret) { - // need to set prior sibling and parent + task.setPriorSibling(mChildren.isEmpty() ? null : mChildren .get(mChildren.size() - 1)); task.setParent(this); @@ -244,7 +238,7 @@ public class TaskList extends Node { if (task != null && pos == -1) { mChildren.add(index, task); - // update the task list + // 更新任务列表 Task preTask = null; Task afterTask = null; if (index != 0) @@ -267,11 +261,11 @@ public class TaskList extends Node { ret = mChildren.remove(task); if (ret) { - // reset prior sibling and parent + // 重置父节点和兄弟节点 task.setPriorSibling(null); task.setParent(null); - // update the task list + // 更新任务列表 if (index != mChildren.size()) { mChildren.get(index).setPriorSibling( index == 0 ? null : mChildren.get(index - 1)); diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java index 15504be..3daed4f 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java @@ -15,13 +15,18 @@ */ package net.micode.notes.gtask.exception; - +/** + * GTask同步动作执行失败的运行时异常类 + */ public class ActionFailureException extends RuntimeException { + //序列化版本号 private static final long serialVersionUID = 4425249765923293627L; + //创建无异常消息的ActionFailureException实例 public ActionFailureException() { super(); } + //创建包含指定错误消息的ActionFailureException实例 public ActionFailureException(String paramString) { super(paramString); diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java index b08cfb1..53e4ab5 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java @@ -15,14 +15,22 @@ */ package net.micode.notes.gtask.exception; - +/** + * 网络失败异常类 + * 用于表示与网络相关的操作失败 + */ public class NetworkFailureException extends Exception { private static final long serialVersionUID = 2107610287180234136L; - + /** + * 默认构造函数 + * 创建一个没有详细错误信息的网络失败异常 + */ public NetworkFailureException() { super(); } - + /** + * 带有错误信息的构造函数 + */ public NetworkFailureException(String paramString) { super(paramString); } diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java index 9fe4c21..fa0d5d8 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java @@ -28,31 +28,44 @@ import net.micode.notes.R; import net.micode.notes.ui.NotesListActivity; import net.micode.notes.ui.NotesPreferenceActivity; - +/** + * GTask同步异步任务类 + * 1. 后台执行同步(登录验证、数据同步); + * 2. 实时发布同步进度并通过系统通知展示; + * 3. 同步完成后根据结果(成功/网络错误/内部错误/取消)展示对应通知; + */ 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; private GTaskManager mTaskManager; private OnCompleteListener mOnCompleteListener; - + /** + * 初始化GTask同步异步任务 + */ public GTaskASyncTask(Context context, OnCompleteListener listener) { mContext = context; mOnCompleteListener = listener; + // 从系统服务中获取通知管理器,用于任务状态通知 mNotifiManager = (NotificationManager) mContext .getSystemService(Context.NOTIFICATION_SERVICE); mTaskManager = GTaskManager.getInstance(); } - + /** + * 取消正在进行的GTask同步 + */ public void cancelSync() { mTaskManager.cancelSync(); } @@ -81,6 +94,12 @@ public class GTaskASyncTask extends AsyncTask { // pendingIntent); // mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); // } + /** + * 构建并显示GTask同步相关通知 + * 区分不同场景的通知跳转逻辑: + * - 非成功(失败/取消):跳转至笔记偏好设置页面(GTask账号配置); + * - 成功:跳转至笔记列表页面; + */ private void showNotification(int tickerId, String content) { PendingIntent pendingIntent; if (tickerId != R.string.ticker_success) { @@ -90,6 +109,7 @@ private void showNotification(int tickerId, String content) { pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE); } + // 构建通知 Notification.Builder builder = new Notification.Builder(mContext) .setAutoCancel(true) .setContentTitle(mContext.getString(R.string.app_name)) @@ -114,7 +134,13 @@ private void showNotification(int tickerId, String content) { ((GTaskSyncService) mContext).sendBroadcast(progress[0]); } } - + /** + * 主线程同步完成回调 + * 1. 成功:展示成功通知,记录最后同步时间; + * 2. 网络错误:展示网络错误通知; + * 3. 内部错误:展示内部错误通知; + * 4. 取消:展示同步取消通知; + */ @Override protected void onPostExecute(Integer result) { if (result == GTaskManager.STATE_SUCCESS) { diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java index c67dfdf..d7801aa 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java @@ -110,14 +110,14 @@ public class GTaskClient { } public boolean login(Activity activity) { - // we suppose that the cookie would expire after 5 minutes - // then we need to re-login + // 假设Cookie在5分钟后过期 + // 如果超过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 +136,7 @@ public class GTaskClient { return false; } - // login with custom domain if necessary + // 如果必要,使用自定义域名登录 if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() .endsWith("googlemail.com"))) { StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); @@ -151,7 +151,7 @@ public class GTaskClient { } } - // try to login with google official url + // 如果自定义域名登录失败,尝试使用Google官方URL登录 if (!mLoggedin) { mGetUrl = GTASK_GET_URL; mPostUrl = GTASK_POST_URL; @@ -163,7 +163,7 @@ public class GTaskClient { mLoggedin = true; return true; } - + //登录Google账户并获取授权令牌 private String loginGoogleAccount(Activity activity, boolean invalidateToken) { String authToken; AccountManager accountManager = AccountManager.get(activity); @@ -189,7 +189,7 @@ public class GTaskClient { return null; } - // get the token now + // 获取token AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); try { @@ -206,7 +206,10 @@ public class GTaskClient { return authToken; } - + /** + * 尝试登录Google Tasks服务 + * 包含令牌过期时的重试逻辑 + */ private boolean tryToLoginGtask(Activity activity, String authToken) { if (!loginGtask(authToken)) { // maybe the auth token is out of date, now let's invalidate the @@ -224,7 +227,11 @@ public class GTaskClient { } return true; } - + /** + * 实际执行Google Tasks登录 + * + * @return 登录成功返回true,否则返回false + */ private boolean loginGtask(String authToken) { int timeoutConnection = 10000; int timeoutSocket = 15000; @@ -236,14 +243,15 @@ 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) { @@ -336,7 +344,7 @@ public class GTaskClient { UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); httpPost.setEntity(entity); - // execute the post + // 执行请求 HttpResponse response = mHttpClient.execute(httpPost); String jsString = getResponseContent(response.getEntity()); return new JSONObject(jsString); @@ -392,14 +400,11 @@ public class GTaskClient { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list actionList.put(tasklist.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client version jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - // post JSONObject jsResponse = postRequest(jsPost); JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( GTaskStringUtils.GTASK_JSON_RESULTS).get(0); @@ -417,10 +422,8 @@ public class GTaskClient { 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); @@ -435,8 +438,6 @@ 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 if (mUpdateArray != null && mUpdateArray.length() > 10) { commitUpdate(); } @@ -455,26 +456,21 @@ public class GTaskClient { JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); - // action_list action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); if (preParent == curParent && task.getPriorSibling() != null) { - // put prioring_sibing_id only if moving within the tasklist and - // it is not the first one action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); } action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); if (preParent != curParent) { - // put the dest_list only if moving between tasklists action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); } actionList.put(action); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); postRequest(jsPost); @@ -492,12 +488,10 @@ public class GTaskClient { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list node.setDeleted(true); actionList.put(node.getUpdateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); postRequest(jsPost); @@ -520,7 +514,6 @@ public class GTaskClient { HttpResponse response = null; response = mHttpClient.execute(httpGet); - // get the task list String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -554,7 +547,6 @@ public class GTaskClient { JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); - // action_list action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); @@ -563,7 +555,6 @@ public class GTaskClient { actionList.put(action); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); JSONObject jsResponse = postRequest(jsPost); diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java index d2b4082..b838ac8 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java @@ -49,8 +49,9 @@ import java.util.Map; 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; @@ -72,6 +73,7 @@ public class GTaskManager { private boolean mSyncing; private boolean mCancelled; + // 数据存储结构 private HashMap mGTaskListHashMap; @@ -86,7 +88,7 @@ public class GTaskManager { private HashMap mGidToNid; private HashMap mNidToGid; - + //初始化所有集合容器与同步状态标识 private GTaskManager() { mSyncing = false; mCancelled = false; @@ -107,19 +109,23 @@ public class GTaskManager { } 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; } + // 初始化同步环境 mContext = context; mContentResolver = mContext.getContentResolver(); mSyncing = true; mCancelled = false; + + mGTaskListHashMap.clear(); mGTaskHashMap.clear(); mMetaHashMap.clear(); @@ -130,19 +136,16 @@ public class GTaskManager { try { GTaskClient client = GTaskClient.getInstance(); client.resetUpdateArray(); - - // login google task + // 若未取消同步,执行Google账号登录 if (!mCancelled) { if (!client.login(mActivity)) { throw new NetworkFailureException("login google task failed"); } } - // get the task list from 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 +171,10 @@ public class GTaskManager { return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; } + /** + * 定义任务清单, + * @throws NetworkFailureException + */ private void initGTaskList() throws NetworkFailureException { if (mCancelled) return; @@ -175,7 +182,6 @@ public class GTaskManager { try { JSONArray jsTaskLists = client.getTaskLists(); - // init meta list first mMetaList = null; for (int i = 0; i < jsTaskLists.length(); i++) { JSONObject object = jsTaskLists.getJSONObject(i); @@ -187,7 +193,6 @@ public class GTaskManager { 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 +208,6 @@ public class GTaskManager { } } - // create meta list if not existed if (mMetaList == null) { mMetaList = new TaskList(); mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX @@ -211,7 +215,6 @@ 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); @@ -225,7 +228,6 @@ 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); @@ -246,7 +248,11 @@ public class GTaskManager { throw new ActionFailureException("initGTaskList: handing JSONObject failed"); } } - + /** + * 核心内容同步方法,处理本地与远程的笔记/文件夹数据同步 + * 流程:处理回收站数据 -> 同步文件夹 -> 同步普通笔记 -> 处理远程新增节点 -> 批量删除本地待删数据 -> 提交更新 -> 刷新同步ID + * @throws NetworkFailureException 网络请求失败时抛出该异常 + */ private void syncContent() throws NetworkFailureException { int syncType; Cursor c = null; @@ -259,7 +265,6 @@ 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 +291,8 @@ 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 +309,8 @@ public class GTaskManager { syncType = node.getSyncAction(c); } else { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add syncType = Node.SYNC_ACTION_ADD_REMOTE; } else { - // remote delete syncType = Node.SYNC_ACTION_DEL_LOCAL; } } @@ -326,7 +327,6 @@ public class GTaskManager { } } - // go through remaining items Iterator> iter = mGTaskHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -334,23 +334,23 @@ public class GTaskManager { doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); } - // mCancelled can be set by another thread, so we neet to check one by - // one - // clear local delete table if (!mCancelled) { if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { throw new ActionFailureException("failed to batch-delete local deleted notes"); } } - // refresh local sync id if (!mCancelled) { GTaskClient.getInstance().commitUpdate(); refreshLocalSyncId(); } } - + /** + * 同步文件夹数据(系统根文件夹、通话记录文件夹、自定义文件夹) + * 处理文件夹的新增、更新、删除同步逻辑,提交文件夹相关更新 + * @throws NetworkFailureException 网络请求失败时抛出该异常 + */ private void syncFolder() throws NetworkFailureException { Cursor c = null; String gid; @@ -361,7 +361,6 @@ 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); @@ -390,7 +389,6 @@ public class GTaskManager { } } - // for call-note folder try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", new String[] { @@ -404,8 +402,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 +421,6 @@ public class GTaskManager { } } - // for local existing folders try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type=? AND parent_id<>?)", new String[] { @@ -460,7 +456,6 @@ public class GTaskManager { } } - // for remote add folders Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -475,7 +470,14 @@ public class GTaskManager { if (!mCancelled) GTaskClient.getInstance().commitUpdate(); } - + /** + * 根据同步类型执行具体的同步操作(核心分发方法) + * 支持新增/删除/更新(本地/远程)、冲突处理等所有同步场景 + * @param syncType 同步类型(对应Node类的SYNC_ACTION_XXX常量) + * @param node Google Tasks远程节点对象 + * @param c 本地笔记数据游标 + * @throws NetworkFailureException 网络请求失败时抛出该异常 + */ private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -521,7 +523,11 @@ public class GTaskManager { throw new ActionFailureException("unkown sync action type"); } } - + /** + * 将Google Tasks远程节点新增到本地MIUI笔记 + * @param node 远程Google Tasks节点(任务清单/单个任务) + * @throws NetworkFailureException 网络请求失败或数据操作失败时抛出该异常 + */ private void addLocalNode(Node node) throws NetworkFailureException { if (mCancelled) { return; @@ -584,25 +590,26 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); } - // create the local node sqlNote.setGtaskId(node.getGid()); sqlNote.commit(false); - // update gid-nid mapping mGidToNid.put(node.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), node.getGid()); - // update meta updateRemoteMeta(node.getGid(), sqlNote); } - + /** + * 用Google Tasks远程节点更新本地MIUI笔记数据 + * @param node 远程Google Tasks节点 + * @param c 本地笔记数据游标 + * @throws NetworkFailureException 网络请求失败或数据操作失败时抛出该异常 + */ private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; } SqlNote sqlNote; - // update the note locally sqlNote = new SqlNote(mContext, c); sqlNote.setContent(node.getLocalJSONFromContent()); @@ -615,10 +622,14 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); sqlNote.commit(true); - // update meta info updateRemoteMeta(node.getGid(), sqlNote); } - + /** + * 将本地MIUI笔记节点新增到Google Tasks远程 + * @param node Google Tasks节点(可为null,用于新建) + * @param c 本地笔记数据游标 + * @throws NetworkFailureException 网络请求失败时抛出该异常 + */ private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -627,7 +638,6 @@ 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 +652,10 @@ public class GTaskManager { GTaskClient.getInstance().createTask(task); n = (Node) task; - // add meta updateRemoteMeta(task.getGid(), sqlNote); } else { TaskList tasklist = null; - // we need to skip folder if it has already existed String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) folderName += GTaskStringUtils.FOLDER_DEFAULT; @@ -671,7 +679,6 @@ public class GTaskManager { } } - // no match we can add now if (tasklist == null) { tasklist = new TaskList(); tasklist.setContentByLocalJSON(sqlNote.getContent()); @@ -681,17 +688,20 @@ public class GTaskManager { n = (Node) tasklist; } - // update local note sqlNote.setGtaskId(n.getGid()); sqlNote.commit(false); sqlNote.resetLocalModified(); sqlNote.commit(true); - // gid-id mapping mGidToNid.put(n.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), n.getGid()); } - + /** + * 用本地MIUI笔记数据更新Google Tasks远程节点 + * @param node 远程Google Tasks节点 + * @param c 本地笔记数据游标 + * @throws NetworkFailureException 网络请求失败时抛出该异常 + */ private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -699,14 +709,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 if (sqlNote.isNoteType()) { Task task = (Task) node; TaskList preParentList = task.getParent(); @@ -725,11 +732,16 @@ public class GTaskManager { } } - // clear local modified flag sqlNote.resetLocalModified(); sqlNote.commit(true); } - + /** + * 更新远程元数据,关联Google Tasks Gid与本地笔记内容 + * 元数据用于记录本地与远程的数据关联信息,确保同步一致性 + * @param gid Google Tasks节点Gid + * @param sqlNote 本地笔记SqlNote对象 + * @throws NetworkFailureException 网络请求失败时抛出该异常 + */ private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { if (sqlNote != null && sqlNote.isNoteType()) { MetaData metaData = mMetaHashMap.get(gid); @@ -745,13 +757,16 @@ public class GTaskManager { } } } - + /** + * 刷新本地笔记的同步ID(将远程节点的最后修改时间作为本地同步ID) + * 确保本地与远程的同步版本一致,避免重复同步 + * @throws NetworkFailureException 网络请求失败时抛出该异常 + */ private void refreshLocalSyncId() throws NetworkFailureException { if (mCancelled) { return; } - // get the latest gtask list mGTaskHashMap.clear(); mGTaskListHashMap.clear(); mMetaHashMap.clear(); diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java index cca36f7..d6166e1 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java @@ -22,12 +22,17 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; - +/** + * GTask(谷歌任务)同步服务类,继承自 Android 系统 Service,用于在后台处理笔记与 GTask 的同步逻辑。 + * 1. 启动/取消 GTask 同步操作 + * 2. 通过广播通知外部组件同步状态和同步进度 + * 3. 管理同步异步任务的生命周期 + */ public class GTaskSyncService extends Service { public final static String ACTION_STRING_NAME = "sync_action_type"; - + //同步操作类型:启动 GTask 同步 public final static int ACTION_START_SYNC = 0; - + //同步操作类型:启取消GTask 同步 public final static int ACTION_CANCEL_SYNC = 1; public final static int ACTION_INVALID = 2; @@ -41,7 +46,11 @@ public class GTaskSyncService extends Service { private static GTaskASyncTask mSyncTask = null; private static String mSyncProgress = ""; - + /** + * 1. 检查异步任务实例是否为空(避免重复启动) + * 2. 若为空则创建新的异步任务,设置完成回调(任务结束后清空实例、发送空进度广播、停止服务) + * 3. 发送初始同步状态广播,执行异步任务 + */ private void startSync() { if (mSyncTask == null) { mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { @@ -66,7 +75,7 @@ public class GTaskSyncService extends Service { public void onCreate() { mSyncTask = null; } - + //解析 Intent 中的同步操作类型,执行对应操作(启动/取消同步) @Override public int onStartCommand(Intent intent, int flags, int startId) { Bundle bundle = intent.getExtras(); @@ -96,7 +105,7 @@ public class GTaskSyncService extends Service { public IBinder onBind(Intent intent) { return null; } - + //发送同步状态广播,通知外部组件当前同步状态和进度 public void sendBroadcast(String msg) { mSyncProgress = msg; Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/model/Note.java b/src/Notes-master/app/src/main/java/net/micode/notes/model/Note.java index 6706cf6..a3a8d44 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/model/Note.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/model/Note.java @@ -33,16 +33,21 @@ 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"; + 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(用于新增笔记入库,生成唯一标识) public static synchronized long getNewNoteId(Context context, long folderId) { - // Create a new note in the database + // 构建新笔记基础参数 ContentValues values = new ContentValues(); long createdTime = System.currentTimeMillis(); values.put(NoteColumns.CREATED_DATE, createdTime); @@ -65,41 +70,50 @@ public class Note { return noteId; } + // 构造方法:初始化笔记差异值和附属数据 public Note() { mNoteDiffValues = new ContentValues(); mNoteData = new NoteData(); } + // 设置笔记核心字段值(标记本地修改+更新修改时间) public void setNoteValue(String key, String value) { mNoteDiffValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + // 设置文本笔记附属数据 public void setTextData(String key, String value) { mNoteData.setTextData(key, value); } + // 设置文本笔记数据ID public void setTextDataId(long id) { mNoteData.setTextDataId(id); } + // 获取文本笔记数据ID public long getTextDataId() { return mNoteData.mTextDataId; } + // 设置通话笔记数据ID public void setCallDataId(long id) { mNoteData.setCallDataId(id); } + // 设置通话笔记附属数据 public void setCallData(String key, String value) { mNoteData.setCallData(key, value); } + // 判断笔记是否存在本地未同步修改 public boolean isLocalModified() { return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); } + // 同步笔记数据到数据库(包含基础信息+附属数据) public boolean syncNote(Context context, long noteId) { if (noteId <= 0) { throw new IllegalArgumentException("Wrong note id:" + noteId); @@ -114,6 +128,7 @@ public class Note { * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the * note data info */ + // 更新笔记基础信息 if (context.getContentResolver().update( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, null) == 0) { @@ -122,6 +137,7 @@ public class Note { } mNoteDiffValues.clear(); + // 推送附属数据到内容提供者 if (mNoteData.isLocalModified() && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { return false; @@ -130,17 +146,18 @@ public class Note { return true; } + /** + * 笔记附属数据管理内部类 + * 处理文本/通话笔记的附属数据存储、ID管理及同步逻辑 + */ private class NoteData { - private long mTextDataId; - - private ContentValues mTextDataValues; - - private long mCallDataId; - - private ContentValues mCallDataValues; - - private static final String TAG = "NoteData"; + private long mTextDataId; // 文本笔记数据ID + private ContentValues mTextDataValues; // 文本笔记数据差异值 + private long mCallDataId; // 通话笔记数据ID + private ContentValues mCallDataValues; // 通话笔记数据差异值 + private static final String TAG = "NoteData"; // 日志标签 + // 构造方法:初始化附属数据存储 public NoteData() { mTextDataValues = new ContentValues(); mCallDataValues = new ContentValues(); @@ -148,10 +165,12 @@ public class Note { mCallDataId = 0; } + // 判断附属数据是否存在本地未同步修改 boolean isLocalModified() { return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; } + // 设置文本笔记数据ID(校验合法性) void setTextDataId(long id) { if(id <= 0) { throw new IllegalArgumentException("Text data id should larger than 0"); @@ -159,6 +178,7 @@ public class Note { mTextDataId = id; } + // 设置通话笔记数据ID(校验合法性) void setCallDataId(long id) { if (id <= 0) { throw new IllegalArgumentException("Call data id should larger than 0"); @@ -166,18 +186,21 @@ public class Note { mCallDataId = id; } + // 设置通话笔记附属数据(标记笔记本地修改) void setCallData(String key, String value) { mCallDataValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + // 设置文本笔记附属数据(标记笔记本地修改) void setTextData(String key, String value) { mTextDataValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + // 将附属数据推送(插入/更新)到内容提供者,支持批量操作 Uri pushIntoContentResolver(Context context, long noteId) { /** * Check for safety @@ -189,6 +212,7 @@ public class Note { ArrayList operationList = new ArrayList(); ContentProviderOperation.Builder builder = null; + // 处理文本笔记数据:无ID则插入,有ID则更新 if(mTextDataValues.size() > 0) { mTextDataValues.put(DataColumns.NOTE_ID, noteId); if (mTextDataId == 0) { @@ -211,6 +235,7 @@ public class Note { mTextDataValues.clear(); } + // 处理通话笔记数据:无ID则插入,有ID则更新 if(mCallDataValues.size() > 0) { mCallDataValues.put(DataColumns.NOTE_ID, noteId); if (mCallDataId == 0) { @@ -233,6 +258,7 @@ public class Note { mCallDataValues.clear(); } + // 执行批量更新操作 if (operationList.size() > 0) { try { ContentProviderResult[] results = context.getContentResolver().applyBatch( @@ -250,4 +276,4 @@ public class Note { return null; } } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/model/WorkingNote.java b/src/Notes-master/app/src/main/java/net/micode/notes/model/WorkingNote.java index be081e4..ad0afe7 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/model/WorkingNote.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/model/WorkingNote.java @@ -31,37 +31,27 @@ import net.micode.notes.data.Notes.NoteColumns; 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 - private long mNoteId; - // Note content - private String mContent; - // Note mode - private int mMode; - - private long mAlertDate; - - private long mModifiedDate; - - private int mBgColorId; - - private int mWidgetId; - - private int mWidgetType; - - private long mFolderId; - - private Context mContext; - - private static final String TAG = "WorkingNote"; - - private boolean mIsDeleted; - - private NoteSettingChangedListener mNoteSettingStatusListener; - + private Note mNote; // 关联的笔记模型 + private long mNoteId; // 笔记ID + private String mContent; // 笔记文本内容 + private int mMode; // 笔记模式(普通/复选列表) + private long mAlertDate; // 提醒时间 + private long mModifiedDate; // 修改时间 + private int mBgColorId; // 背景色ID + private int mWidgetId; // 关联小组件ID + private int mWidgetType; // 关联小组件类型 + private long mFolderId; // 所属文件夹ID + private Context mContext; // 上下文对象 + private static final String TAG = "WorkingNote"; // 日志标签 + private boolean mIsDeleted; // 是否标记为删除 + private NoteSettingChangedListener mNoteSettingStatusListener; // 笔记设置变更监听器 + + // 笔记附属数据查询投影字段 public static final String[] DATA_PROJECTION = new String[] { DataColumns.ID, DataColumns.CONTENT, @@ -72,6 +62,7 @@ public class WorkingNote { DataColumns.DATA4, }; + // 笔记基础信息查询投影字段 public static final String[] NOTE_PROJECTION = new String[] { NoteColumns.PARENT_ID, NoteColumns.ALERTED_DATE, @@ -81,27 +72,21 @@ public class WorkingNote { NoteColumns.MODIFIED_DATE }; + // 附属数据列索引常量 private static final int DATA_ID_COLUMN = 0; - private static final int DATA_CONTENT_COLUMN = 1; - private static final int DATA_MIME_TYPE_COLUMN = 2; - private static final int DATA_MODE_COLUMN = 3; + // 笔记基础信息列索引常量 private static final int NOTE_PARENT_ID_COLUMN = 0; - private static final int NOTE_ALERTED_DATE_COLUMN = 1; - private static final int NOTE_BG_COLOR_ID_COLUMN = 2; - private static final int NOTE_WIDGET_ID_COLUMN = 3; - private static final int NOTE_WIDGET_TYPE_COLUMN = 4; - private static final int NOTE_MODIFIED_DATE_COLUMN = 5; - // New note construct + // 私有构造:新建笔记 private WorkingNote(Context context, long folderId) { mContext = context; mAlertDate = 0; @@ -114,16 +99,17 @@ public class WorkingNote { mWidgetType = Notes.TYPE_WIDGET_INVALIDE; } - // Existing note construct + // 私有构造:已有笔记 private WorkingNote(Context context, long noteId, long folderId) { mContext = context; mNoteId = noteId; mFolderId = folderId; mIsDeleted = false; mNote = new Note(); - loadNote(); + loadNote(); // 加载笔记数据 } + // 加载笔记基础信息 private void loadNote() { Cursor cursor = mContext.getContentResolver().query( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, @@ -143,13 +129,14 @@ public class WorkingNote { Log.e(TAG, "No note with id:" + mNoteId); throw new IllegalArgumentException("Unable to find note with id " + mNoteId); } - loadNoteData(); + loadNoteData(); // 加载笔记附属数据 } + // 加载笔记附属数据(文本/通话) private void loadNoteData() { Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { - String.valueOf(mNoteId) + String.valueOf(mNoteId) }, null); if (cursor != null) { @@ -174,8 +161,9 @@ public class WorkingNote { } } + // 创建空工作笔记(静态工厂方法) public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, - int widgetType, int defaultBgColorId) { + int widgetType, int defaultBgColorId) { WorkingNote note = new WorkingNote(context, folderId); note.setBgColorId(defaultBgColorId); note.setWidgetId(widgetId); @@ -183,10 +171,12 @@ public class WorkingNote { return note; } + // 加载已有工作笔记(静态工厂方法) public static WorkingNote load(Context context, long id) { return new WorkingNote(context, id, 0); } + // 保存笔记(核心方法:判断是否值得保存、新增/更新笔记、通知小组件变更) public synchronized boolean saveNote() { if (isWorthSaving()) { if (!existInDatabase()) { @@ -196,11 +186,8 @@ public class WorkingNote { } } - mNote.syncNote(mContext, mNoteId); + 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 && mNoteSettingStatusListener != null) { @@ -212,10 +199,12 @@ public class WorkingNote { } } + // 判断笔记是否已存在于数据库 public boolean existInDatabase() { return mNoteId > 0; } + // 判断笔记是否值得保存(过滤无效状态) private boolean isWorthSaving() { if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) || (existInDatabase() && !mNote.isLocalModified())) { @@ -225,10 +214,12 @@ public class WorkingNote { } } + // 设置笔记设置变更监听器 public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { mNoteSettingStatusListener = l; } + // 设置提醒时间 public void setAlertDate(long date, boolean set) { if (date != mAlertDate) { mAlertDate = date; @@ -239,14 +230,16 @@ public class WorkingNote { } } + // 标记笔记为已删除 public void markDeleted(boolean mark) { mIsDeleted = mark; if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onWidgetChanged(); + mNoteSettingStatusListener.onWidgetChanged(); } } + // 设置笔记背景色ID public void setBgColorId(int id) { if (id != mBgColorId) { mBgColorId = id; @@ -257,6 +250,7 @@ public class WorkingNote { } } + // 设置笔记模式(普通/复选列表) public void setCheckListMode(int mode) { if (mMode != mode) { if (mNoteSettingStatusListener != null) { @@ -267,6 +261,7 @@ public class WorkingNote { } } + // 设置小组件类型 public void setWidgetType(int type) { if (type != mWidgetType) { mWidgetType = type; @@ -274,6 +269,7 @@ public class WorkingNote { } } + // 设置小组件ID public void setWidgetId(int id) { if (id != mWidgetId) { mWidgetId = id; @@ -281,6 +277,7 @@ public class WorkingNote { } } + // 设置笔记文本内容 public void setWorkingText(String text) { if (!TextUtils.equals(mContent, text)) { mContent = text; @@ -288,81 +285,85 @@ public class WorkingNote { } } + // 转换为通话笔记 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)); } + // 判断是否设置了提醒 public boolean hasClockAlert() { return (mAlertDate > 0 ? true : false); } + // 获取笔记文本内容 public String getContent() { return mContent; } + // 获取提醒时间 public long getAlertDate() { return mAlertDate; } + // 获取修改时间 public long getModifiedDate() { return mModifiedDate; } + // 获取背景色资源ID public int getBgColorResId() { return NoteBgResources.getNoteBgResource(mBgColorId); } + // 获取背景色ID public int getBgColorId() { return mBgColorId; } + // 获取标题背景资源ID public int getTitleBgResId() { return NoteBgResources.getNoteTitleBgResource(mBgColorId); } + // 获取笔记模式 public int getCheckListMode() { return mMode; } + // 获取笔记ID public long getNoteId() { return mNoteId; } + // 获取所属文件夹ID public long getFolderId() { return mFolderId; } + // 获取小组件ID public int getWidgetId() { return mWidgetId; } + // 获取小组件类型 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 - */ 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 - */ + void onCheckListModeChanged(int oldMode, int newMode); } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/tool/BackupUtils.java b/src/Notes-master/app/src/main/java/net/micode/notes/tool/BackupUtils.java index 39f6ec4..2bd5c6a 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/tool/BackupUtils.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/tool/BackupUtils.java @@ -35,12 +35,15 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; - +/** + * 笔记备份工具类 + * 负责将笔记数据导出为可读文本格式并存储到SD卡,提供备份状态反馈 + */ public class BackupUtils { private static final String TAG = "BackupUtils"; - // Singleton stuff - private static BackupUtils sInstance; + private static BackupUtils sInstance; // 单例实例 + // 获取单例对象 public static synchronized BackupUtils getInstance(Context context) { if (sInstance == null) { sInstance = new BackupUtils(context); @@ -48,44 +51,49 @@ public class BackupUtils { return sInstance; } - /** - * 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 + // backup文件不存在 public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; - // The data is not well formated, may be changed by other programs + // state数据损坏 public static final int STATE_DATA_DESTROIED = 2; - // Some run-time exception which causes restore or backup fails + // state系统错误 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; // 文本导出工具实例 private BackupUtils(Context context) { mTextExport = new TextExport(context); } + // 判断外部存储(SD卡)是否可用 private static boolean externalStorageAvailable() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } + // 执行笔记文本导出 public int exportToText() { return mTextExport.exportToText(); } + // 获取导出文本文件名 public String getExportedTextFileName() { return mTextExport.mFileName; } + // 获取导出文本文件目录 public String getExportedTextFileDir() { return mTextExport.mFileDirectory; } + /** + * 文本导出内部类 + * 封装笔记文本导出的具体逻辑(文件夹/单个笔记导出、文件创建) + */ private static class TextExport { + // 笔记基础信息查询投影字段 private static final String[] NOTE_PROJECTION = { NoteColumns.ID, NoteColumns.MODIFIED_DATE, @@ -94,11 +102,10 @@ public class BackupUtils { }; 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, @@ -109,21 +116,18 @@ public class BackupUtils { }; 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 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; // 导出文件目录 public TextExport(Context context) { TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); @@ -132,6 +136,7 @@ public class BackupUtils { mFileDirectory = ""; } + // 获取指定格式模板 private String getFormat(int id) { return TEXT_FORMAT[id]; } @@ -139,11 +144,12 @@ public class BackupUtils { /** * Export the folder identified by folder id to text */ + // 导出指定文件夹下的所有笔记到文本流 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) { @@ -155,20 +161,18 @@ public class BackupUtils { notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); // Query data belong to this note String noteId = notesCursor.getString(NOTE_COLUMN_ID); - exportNoteToText(noteId, ps); + exportNoteToText(noteId, ps); // 导出单个笔记 } while (notesCursor.moveToNext()); } notesCursor.close(); } } - /** - * Export note identified by id to a print stream - */ + // 导出单个笔记的详细内容到文本流 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 +180,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 +189,15 @@ public class BackupUtils { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), phoneNumber)); } - // Print call date ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat .format(mContext.getString(R.string.format_datetime_mdhm), callDate))); - // Print call attachment location if (!TextUtils.isEmpty(location)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), location)); } } else if (DataConstants.NOTE.equals(mimeType)) { + // 导出普通文本笔记内容 String content = dataCursor.getString(DATA_COLUMN_CONTENT); if (!TextUtils.isEmpty(content)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), @@ -205,7 +208,7 @@ public class BackupUtils { } dataCursor.close(); } - // print a line separator between note + // 笔记之间添加分隔符 try { ps.write(new byte[] { Character.LINE_SEPARATOR, Character.LETTER_NUMBER @@ -215,9 +218,7 @@ public class BackupUtils { } } - /** - * Note will be exported as text which is user readable - */ + // 核心导出方法:导出所有文件夹及根目录笔记 public int exportToText() { if (!externalStorageAvailable()) { Log.d(TAG, "Media was not mounted"); @@ -229,7 +230,7 @@ public class BackupUtils { 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 +241,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 +258,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 +271,6 @@ 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()); @@ -282,9 +282,7 @@ public class BackupUtils { return STATE_SUCCESS; } - /** - * Get a print stream pointed to the file {@generateExportedTextFile} - */ + // 获取文本导出的打印流,关联SD卡上的目标文件 private PrintStream getExportToTextPrintStream() { File file = generateFileMountedOnSDcard(mContext, R.string.file_path, R.string.file_name_txt_format); @@ -309,9 +307,7 @@ public class BackupUtils { } } - /** - * Generate the text file to store imported data - */ + // 在SD卡上生成导出文件(含目录创建、按时间命名文件) private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { StringBuilder sb = new StringBuilder(); sb.append(Environment.getExternalStorageDirectory()); @@ -325,10 +321,10 @@ public class BackupUtils { try { if (!filedir.exists()) { - filedir.mkdir(); + filedir.mkdir(); // 创建目录 } if (!file.exists()) { - file.createNewFile(); + file.createNewFile(); // 创建文件 } return file; } catch (SecurityException e) { @@ -339,6 +335,4 @@ public class BackupUtils { return null; } -} - - +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/tool/DataUtils.java b/src/Notes-master/app/src/main/java/net/micode/notes/tool/DataUtils.java index 2a14982..046b19c 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/tool/DataUtils.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/tool/DataUtils.java @@ -34,9 +34,14 @@ import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; import java.util.ArrayList; import java.util.HashSet; - +/** + * 数据库操作工具类,封装了笔记应用中常用的数据库操作方法 + */ public class DataUtils { + // 日志标签 public static final String TAG = "DataUtils"; + + // 批量删除笔记(过滤系统根文件夹,避免误删) public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { if (ids == null) { Log.d(TAG, "the ids is null"); @@ -46,18 +51,20 @@ public class DataUtils { Log.d(TAG, "no id is in the hashset"); return true; } - + // 构建批量删除操作列表 ArrayList operationList = new ArrayList(); for (long id : ids) { if(id == Notes.ID_ROOT_FOLDER) { Log.e(TAG, "Don't delete system folder root"); continue; } + // 创建删除操作 ContentProviderOperation.Builder builder = ContentProviderOperation .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); operationList.add(builder.build()); } try { + // 执行批量操作 ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); if (results == null || results.length == 0 || results[0] == null) { Log.d(TAG, "delete notes failed, ids:" + ids.toString()); @@ -72,6 +79,7 @@ public class DataUtils { return false; } + // 移动单个笔记到指定文件夹(记录原始文件夹ID+标记本地修改) public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { ContentValues values = new ContentValues(); values.put(NoteColumns.PARENT_ID, desFolderId); @@ -80,14 +88,16 @@ public class DataUtils { resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); } + // 批量移动笔记到指定文件夹 public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids, - long folderId) { + long folderId) { if (ids == null) { Log.d(TAG, "the ids is null"); return true; } ArrayList operationList = new ArrayList(); + // 创建更新操作,修改笔记的父文件夹ID for (long id : ids) { ContentProviderOperation.Builder builder = ContentProviderOperation .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); @@ -111,10 +121,10 @@ public class DataUtils { return false; } - /** - * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} - */ + + // 获取用户自定义文件夹数量(排除系统文件夹和回收站) public static int getUserFolderCount(ContentResolver resolver) { + // 查询类型为文件夹且不在回收站中的记录数量 Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, new String[] { "COUNT(*)" }, NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", @@ -136,6 +146,7 @@ public class DataUtils { return count; } + // 判断指定类型的笔记/文件夹是否在数据库中可见(非回收站) public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, @@ -153,6 +164,7 @@ public class DataUtils { return exist; } + // 判断笔记是否存在于数据库中 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 +179,7 @@ public class DataUtils { return exist; } + // 判断笔记附属数据是否存在于数据库中 public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null, null, null, null); @@ -181,11 +194,12 @@ public class DataUtils { return exist; } + // 检查可见文件夹名称是否已存在(避免重复创建) public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + - " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + - " AND " + NoteColumns.SNIPPET + "=?", + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.SNIPPET + "=?", new String[] { name }, null); boolean exist = false; if(cursor != null) { @@ -197,6 +211,7 @@ public class DataUtils { return exist; } + // 获取指定文件夹下关联的所有小组件信息 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 +239,7 @@ public class DataUtils { return set; } + // 根据笔记ID获取对应的通话号码 public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, new String [] { CallNote.PHONE_NUMBER }, @@ -243,11 +259,12 @@ public class DataUtils { return ""; } + // 根据手机号和通话时间获取对应的笔记ID public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, new String [] { CallNote.NOTE_ID }, CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL(" - + CallNote.PHONE_NUMBER + ",?)", + + CallNote.PHONE_NUMBER + ",?)", new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber }, null); @@ -264,6 +281,7 @@ public class DataUtils { return 0; } + // 根据笔记ID获取笔记摘要 public static String getSnippetById(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, new String [] { NoteColumns.SNIPPET }, @@ -282,6 +300,7 @@ public class DataUtils { throw new IllegalArgumentException("Note is not found with id: " + noteId); } + // 格式化笔记摘要(去除首尾空格,截取首行内容) public static String getFormattedSnippet(String snippet) { if (snippet != null) { snippet = snippet.trim(); @@ -292,4 +311,4 @@ public class DataUtils { } return snippet; } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/tool/GTaskStringUtils.java b/src/Notes-master/app/src/main/java/net/micode/notes/tool/GTaskStringUtils.java index 666b729..1806554 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/tool/GTaskStringUtils.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/tool/GTaskStringUtils.java @@ -17,13 +17,14 @@ package net.micode.notes.tool; public class GTaskStringUtils { - + //本类定义了Google Tasks API通信中使用的所有JSON键名和常量值 + // 操作相关的JSON键 public final static String GTASK_JSON_ACTION_ID = "action_id"; public final static String GTASK_JSON_ACTION_LIST = "action_list"; public final static String GTASK_JSON_ACTION_TYPE = "action_type"; - + // 操作类型值 public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; @@ -31,7 +32,7 @@ public class GTaskStringUtils { public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; - + // 实体属性相关的JSON键 public final static String GTASK_JSON_CREATOR_ID = "creator_id"; public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; @@ -57,19 +58,19 @@ public class GTaskStringUtils { public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; public final static String GTASK_JSON_GET_DELETED = "get_deleted"; - + // ID和索引相关的JSON键 public final static String GTASK_JSON_ID = "id"; public final static String GTASK_JSON_INDEX = "index"; public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; - + // 同步相关的JSON键 public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; public final static String GTASK_JSON_LIST_ID = "list_id"; public final static String GTASK_JSON_LISTS = "lists"; - + // 内容相关的JSON键 public final static String GTASK_JSON_NAME = "name"; public final static String GTASK_JSON_NEW_ID = "new_id"; @@ -79,7 +80,7 @@ public class GTaskStringUtils { public final static String GTASK_JSON_PARENT_ID = "parent_id"; public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; - + // 响应相关的JSON键 public final static String GTASK_JSON_RESULTS = "results"; public final static String GTASK_JSON_SOURCE_LIST = "source_list"; diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/tool/ResourceParser.java b/src/Notes-master/app/src/main/java/net/micode/notes/tool/ResourceParser.java index 1ad3ad6..632b730 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/tool/ResourceParser.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/tool/ResourceParser.java @@ -23,7 +23,7 @@ import net.micode.notes.R; import net.micode.notes.ui.NotesPreferenceActivity; public class ResourceParser { - +//定义字体颜色,大小 public static final int YELLOW = 0; public static final int BLUE = 1; public static final int WHITE = 2; @@ -66,10 +66,13 @@ public class ResourceParser { } public static int getDefaultBgId(Context context) { + // 检查用户偏好设置,是否启用了随机背景颜色功能 if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { + //启用:生成随机索引,返回随机选择的背景资源ID return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length); } else { + //未启用:返回默认的背景颜色资源ID return BG_DEFAULT_COLOR; } } @@ -156,6 +159,7 @@ public class ResourceParser { public static class TextAppearanceResources { private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { + //字体大小组 R.style.TextAppearanceNormal, R.style.TextAppearanceMedium, R.style.TextAppearanceLarge, @@ -163,14 +167,11 @@ public class ResourceParser { }; public static int getTexAppearanceResource(int id) { - /** - * HACKME: Fix bug of store the resource id in shared preference. - * The id may larger than the length of resources, in this case, - * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} - */ + if (id >= TEXTAPPEARANCE_RESOURCES.length) { return BG_DEFAULT_FONT_SIZE; } + // 正常情况返回对应索引的样式资源 return TEXTAPPEARANCE_RESOURCES[id]; } diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java b/src/Notes-master/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java index 85723be..7693d7f 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java @@ -39,12 +39,15 @@ import net.micode.notes.tool.DataUtils; import java.io.IOException; - +/** + * 笔记提醒弹窗页面 + * 负责展示笔记提醒内容、播放系统闹钟铃声,并提供操作入口 + */ public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { - private long mNoteId; - private String mSnippet; - private static final int SNIPPET_PREW_MAX_LEN = 60; - MediaPlayer mPlayer; + private long mNoteId; // 提醒对应的笔记ID + private String mSnippet; // 笔记摘要(用于弹窗展示) + private static final int SNIPPET_PREW_MAX_LEN = 60; // 摘要展示最大长度 + MediaPlayer mPlayer; // 媒体播放器(播放提醒铃声) @Override protected void onCreate(Bundle savedInstanceState) { @@ -52,8 +55,9 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD requestWindowFeature(Window.FEATURE_NO_TITLE); final Window win = getWindow(); - win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); // 锁屏时显示窗口 + // 屏幕熄灭时,唤醒屏幕并保持常亮 if (!isScreenOn()) { win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON @@ -64,6 +68,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD Intent intent = getIntent(); try { + // 获取Intent中的笔记ID并格式化摘要 mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, @@ -75,6 +80,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } mPlayer = new MediaPlayer(); + // 校验笔记是否可见(非回收站),可见则弹窗+播放铃声 if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { showActionDialog(); playAlarmSound(); @@ -83,17 +89,20 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + // 判断屏幕是否处于点亮状态 private boolean isScreenOn() { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); return pm.isScreenOn(); } + // 播放系统默认闹钟提醒铃声(循环播放) private void playAlarmSound() { Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); int silentModeStreams = Settings.System.getInt(getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + // 设置音频流类型 if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { mPlayer.setAudioStreamType(silentModeStreams); } else { @@ -102,37 +111,37 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD try { mPlayer.setDataSource(this, url); mPlayer.prepare(); - mPlayer.setLooping(true); + mPlayer.setLooping(true); // 循环播放 mPlayer.start(); } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (SecurityException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalStateException e) { - // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } + // 显示笔记提醒操作弹窗(展示摘要,提供确定/进入编辑按钮) private void showActionDialog() { AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setTitle(R.string.app_name); dialog.setMessage(mSnippet); dialog.setPositiveButton(R.string.notealert_ok, this); + // 屏幕点亮时显示“进入”按钮 if (isScreenOn()) { dialog.setNegativeButton(R.string.notealert_enter, this); } dialog.show().setOnDismissListener(this); } + // 弹窗按钮点击事件处理 public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_NEGATIVE: + // 跳转到笔记编辑页面 Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, mNoteId); @@ -143,11 +152,13 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + // 弹窗消失事件:停止铃声并关闭页面 public void onDismiss(DialogInterface dialog) { stopAlarmSound(); finish(); } + // 停止播放铃声并释放媒体播放器资源 private void stopAlarmSound() { if (mPlayer != null) { mPlayer.stop(); @@ -155,4 +166,4 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD mPlayer = null; } } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java b/src/Notes-master/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java index f221202..f31fd45 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java @@ -40,7 +40,10 @@ public class AlarmInitReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + //获取当前时间 long currentDate = System.currentTimeMillis(); + + //查询条件:提醒日期 + ">? AND " + 当前时间 AND 笔记类型 + "=" + TYPE_NOTE(普通笔记) Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/ui/DateTimePicker.java b/src/Notes-master/app/src/main/java/net/micode/notes/ui/DateTimePicker.java index 496b0cd..31710ef 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/ui/DateTimePicker.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/ui/DateTimePicker.java @@ -21,17 +21,22 @@ import java.util.Calendar; import net.micode.notes.R; - import android.content.Context; import android.text.format.DateFormat; import android.view.View; import android.widget.FrameLayout; import android.widget.NumberPicker; +/** + * 自定义日期时间选择器控件 + * 支持12/24小时制切换,提供日期、小时、分钟、上午/下午选择功能,及时间变更回调 + */ public class DateTimePicker extends FrameLayout { + // 默认启用状态 private static final boolean DEFAULT_ENABLE_STATE = true; + // 时间/日期相关常量定义 private static final int HOURS_IN_HALF_DAY = 12; private static final int HOURS_IN_ALL_DAY = 24; private static final int DAYS_IN_ALL_WEEK = 7; @@ -46,24 +51,21 @@ public class DateTimePicker extends FrameLayout { private static final int AMPM_SPINNER_MIN_VAL = 0; private static final int AMPM_SPINNER_MAX_VAL = 1; - private final NumberPicker mDateSpinner; - private final NumberPicker mHourSpinner; - private final NumberPicker mMinuteSpinner; - private final NumberPicker mAmPmSpinner; - private Calendar mDate; - - private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; - - private boolean mIsAm; + private final NumberPicker mDateSpinner; // 日期选择器 + private final NumberPicker mHourSpinner; // 小时选择器 + private final NumberPicker mMinuteSpinner; // 分钟选择器 + private final NumberPicker mAmPmSpinner; // 上午/下午选择器 + private Calendar mDate; // 当前选中的日期时间 - private boolean mIs24HourView; + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; // 日期展示文本数组 - private boolean mIsEnabled = DEFAULT_ENABLE_STATE; - - private boolean mInitialising; - - private OnDateTimeChangedListener mOnDateTimeChangedListener; + private boolean mIsAm; // 是否为上午 + private boolean mIs24HourView; // 是否为24小时制 + private boolean mIsEnabled = DEFAULT_ENABLE_STATE; // 控件启用状态 + private boolean mInitialising; // 是否处于初始化状态 + private OnDateTimeChangedListener mOnDateTimeChangedListener; // 时间变更回调监听器 + // 日期选择器数值变更监听:处理日期切换逻辑 private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { @@ -73,6 +75,7 @@ public class DateTimePicker extends FrameLayout { } }; + // 小时选择器数值变更监听:处理小时切换及跨天/上午下午切换逻辑 private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { @@ -115,6 +118,7 @@ public class DateTimePicker extends FrameLayout { } }; + // 分钟选择器数值变更监听:处理分钟切换及跨小时逻辑 private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { @@ -144,6 +148,7 @@ public class DateTimePicker extends FrameLayout { } }; + // 上午/下午选择器数值变更监听:处理上午下午切换及小时偏移逻辑 private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { @@ -158,19 +163,25 @@ public class DateTimePicker extends FrameLayout { } }; + /** + * 日期时间变更回调接口 + */ public interface OnDateTimeChangedListener { void onDateTimeChanged(DateTimePicker view, int year, int month, - int dayOfMonth, int hourOfDay, int minute); + int dayOfMonth, int hourOfDay, int minute); } + // 构造方法:默认使用当前时间 public DateTimePicker(Context context) { this(context, System.currentTimeMillis()); } + // 构造方法:指定初始时间 public DateTimePicker(Context context, long date) { this(context, date, DateFormat.is24HourFormat(context)); } + // 核心构造方法:初始化控件、状态及监听 public DateTimePicker(Context context, long date, boolean is24HourView) { super(context); mDate = Calendar.getInstance(); @@ -198,19 +209,18 @@ public class DateTimePicker extends FrameLayout { mAmPmSpinner.setDisplayedValues(stringsForAmPm); mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); - // update controls to initial state + // 更新控件至初始状态 updateDateControl(); updateHourControl(); updateAmPmControl(); set24HourView(is24HourView); - // set to current time + // 设置初始时间 setCurrentDate(date); setEnabled(isEnabled()); - // set the content descriptions mInitialising = false; } @@ -233,18 +243,16 @@ public class DateTimePicker extends FrameLayout { } /** - * Get the current date in millis - * - * @return the current date in millis + * 获取当前选中日期的时间戳 + * @return 时间戳(毫秒) */ public long getCurrentDateInTimeMillis() { return mDate.getTimeInMillis(); } /** - * Set the current date - * - * @param date The current date in millis + * 设置当前选中日期(通过时间戳) + * @param date 时间戳(毫秒) */ public void setCurrentDate(long date) { Calendar cal = Calendar.getInstance(); @@ -254,16 +262,10 @@ public class DateTimePicker extends FrameLayout { } /** - * Set the current date - * - * @param year The current year - * @param month The current month - * @param dayOfMonth The current dayOfMonth - * @param hourOfDay The current hourOfDay - * @param minute The current minute + * 设置当前选中日期(通过年月日时分) */ public void setCurrentDate(int year, int month, - int dayOfMonth, int hourOfDay, int minute) { + int dayOfMonth, int hourOfDay, int minute) { setCurrentYear(year); setCurrentMonth(month); setCurrentDay(dayOfMonth); @@ -272,18 +274,14 @@ public class DateTimePicker extends FrameLayout { } /** - * Get current year - * - * @return The current year + * 获取当前年份 */ public int getCurrentYear() { return mDate.get(Calendar.YEAR); } /** - * Set current year - * - * @param year The current year + * 设置当前年份 */ public void setCurrentYear(int year) { if (!mInitialising && year == getCurrentYear()) { @@ -295,18 +293,14 @@ public class DateTimePicker extends FrameLayout { } /** - * Get current month in the year - * - * @return The current month in the year + * 获取当前月份 */ public int getCurrentMonth() { return mDate.get(Calendar.MONTH); } /** - * Set current month in the year - * - * @param month The month in the year + * 设置当前月份 */ public void setCurrentMonth(int month) { if (!mInitialising && month == getCurrentMonth()) { @@ -318,18 +312,14 @@ public class DateTimePicker extends FrameLayout { } /** - * Get current day of the month - * - * @return The day of the month + * 获取当前日期(当月第几天) */ public int getCurrentDay() { return mDate.get(Calendar.DAY_OF_MONTH); } /** - * Set current day of the month - * - * @param dayOfMonth The day of the month + * 设置当前日期(当月第几天) */ public void setCurrentDay(int dayOfMonth) { if (!mInitialising && dayOfMonth == getCurrentDay()) { @@ -341,13 +331,13 @@ public class DateTimePicker extends FrameLayout { } /** - * Get current hour in 24 hour mode, in the range (0~23) - * @return The current hour in 24 hour mode + * 获取当前小时(24小时制,0~23) */ public int getCurrentHourOfDay() { return mDate.get(Calendar.HOUR_OF_DAY); } + // 获取当前小时(适配12/24小时制) private int getCurrentHour() { if (mIs24HourView){ return getCurrentHourOfDay(); @@ -362,9 +352,7 @@ public class DateTimePicker extends FrameLayout { } /** - * Set current hour in 24 hour mode, in the range (0~23) - * - * @param hourOfDay + * 设置当前小时(24小时制,0~23) */ public void setCurrentHour(int hourOfDay) { if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { @@ -390,16 +378,14 @@ public class DateTimePicker extends FrameLayout { } /** - * Get currentMinute - * - * @return The Current Minute + * 获取当前分钟 */ public int getCurrentMinute() { return mDate.get(Calendar.MINUTE); } /** - * Set current minute + * 设置当前分钟 */ public void setCurrentMinute(int minute) { if (!mInitialising && minute == getCurrentMinute()) { @@ -411,16 +397,15 @@ public class DateTimePicker extends FrameLayout { } /** - * @return true if this is in 24 hour view else false. + * 判断是否为24小时制 */ public boolean is24HourView () { return mIs24HourView; } /** - * Set whether in 24 hour or AM/PM mode. - * - * @param is24HourView True for 24 hour mode. False for AM/PM mode. + * 切换12/24小时制 + * @param is24HourView true为24小时制,false为12小时制 */ public void set24HourView(boolean is24HourView) { if (mIs24HourView == is24HourView) { @@ -434,6 +419,7 @@ public class DateTimePicker extends FrameLayout { updateAmPmControl(); } + // 更新日期选择器的展示内容 private void updateDateControl() { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(mDate.getTimeInMillis()); @@ -448,6 +434,7 @@ public class DateTimePicker extends FrameLayout { mDateSpinner.invalidate(); } + // 更新上午/下午选择器的显示状态 private void updateAmPmControl() { if (mIs24HourView) { mAmPmSpinner.setVisibility(View.GONE); @@ -458,6 +445,7 @@ public class DateTimePicker extends FrameLayout { } } + // 更新小时选择器的取值范围(适配12/24小时制) private void updateHourControl() { if (mIs24HourView) { mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); @@ -469,17 +457,17 @@ public class DateTimePicker extends FrameLayout { } /** - * Set the callback that indicates the 'Set' button has been pressed. - * @param callback the callback, if null will do nothing + * 设置日期时间变更回调监听器 */ public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { mOnDateTimeChangedListener = callback; } + // 触发日期时间变更回调 private void onDateTimeChanged() { if (mOnDateTimeChangedListener != null) { mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); } } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java b/src/Notes-master/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java index 2c47ba4..72d220c 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java @@ -29,24 +29,34 @@ import android.content.DialogInterface.OnClickListener; import android.text.format.DateFormat; import android.text.format.DateUtils; +/** + * 日期时间选择对话框 + * 封装DateTimePicker控件,以弹窗形式提供日期时间选择功能,并支持选择结果回调 + */ public class DateTimePickerDialog extends AlertDialog implements OnClickListener { - private Calendar mDate = Calendar.getInstance(); - private boolean mIs24HourView; - private OnDateTimeSetListener mOnDateTimeSetListener; - private DateTimePicker mDateTimePicker; + private Calendar mDate = Calendar.getInstance(); // 当前选中的日期时间 + private boolean mIs24HourView; // 是否使用24小时制显示 + private OnDateTimeSetListener mOnDateTimeSetListener; // 时间选择完成回调监听器 + private DateTimePicker mDateTimePicker; // 日期时间选择器控件 + /** + * 时间选择完成回调接口 + */ public interface OnDateTimeSetListener { void OnDateTimeSet(AlertDialog dialog, long date); } + // 构造方法:初始化弹窗,绑定选择器并设置初始时间 public DateTimePickerDialog(Context context, long date) { super(context); mDateTimePicker = new DateTimePicker(context); - setView(mDateTimePicker); + setView(mDateTimePicker); // 将时间选择器设置为弹窗内容视图 + + // 设置时间变更监听:同步更新选中日期并刷新弹窗标题 mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { public void onDateTimeChanged(DateTimePicker view, int year, int month, - int dayOfMonth, int hourOfDay, int minute) { + int dayOfMonth, int hourOfDay, int minute) { mDate.set(Calendar.YEAR, year); mDate.set(Calendar.MONTH, month); mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); @@ -55,32 +65,43 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener updateTitle(mDate.getTimeInMillis()); } }); + + // 初始化选中时间(清空秒数) mDate.setTimeInMillis(date); mDate.set(Calendar.SECOND, 0); mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); + + // 设置弹窗按钮(确定/取消) setButton(context.getString(R.string.datetime_dialog_ok), this); setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); + + // 设置小时显示模式(跟随系统默认) set24HourView(DateFormat.is24HourFormat(this.getContext())); + // 初始化弹窗标题 updateTitle(mDate.getTimeInMillis()); } + // 设置是否使用24小时制显示时间 public void set24HourView(boolean is24HourView) { mIs24HourView = is24HourView; } + // 设置时间选择完成的回调监听器 public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { mOnDateTimeSetListener = callBack; } + // 更新弹窗标题,展示当前选中的日期时间文本 private void updateTitle(long date) { int flag = - DateUtils.FORMAT_SHOW_YEAR | - DateUtils.FORMAT_SHOW_DATE | - DateUtils.FORMAT_SHOW_TIME; + DateUtils.FORMAT_SHOW_YEAR | + DateUtils.FORMAT_SHOW_DATE | + DateUtils.FORMAT_SHOW_TIME; flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); } + // 弹窗确定按钮点击事件:触发时间选择完成回调 public void onClick(DialogInterface arg0, int arg1) { if (mOnDateTimeSetListener != null) { mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/ui/DropdownMenu.java b/src/Notes-master/app/src/main/java/net/micode/notes/ui/DropdownMenu.java index 613dc74..37334a9 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/ui/DropdownMenu.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/ui/DropdownMenu.java @@ -27,17 +27,29 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import net.micode.notes.R; +/** + * 下拉菜单封装类 + * 绑定触发按钮与PopupMenu,简化下拉菜单的创建、显示及事件监听操作 + */ public class DropdownMenu { - private Button mButton; - private PopupMenu mPopupMenu; - private Menu mMenu; + private Button mButton; // 触发下拉菜单的按钮控件 + private PopupMenu mPopupMenu; // 系统弹窗菜单实例 + private Menu mMenu; // 下拉菜单对应的菜单对象 + /** + * 构造方法:初始化下拉菜单 + * @param context 上下文对象 + * @param button 触发下拉菜单的按钮 + * @param menuId 菜单布局资源ID + */ public DropdownMenu(Context context, Button button, int menuId) { mButton = button; - mButton.setBackgroundResource(R.drawable.dropdown_icon); - mPopupMenu = new PopupMenu(context, mButton); + mButton.setBackgroundResource(R.drawable.dropdown_icon); // 设置按钮背景样式 + mPopupMenu = new PopupMenu(context, mButton); // 绑定按钮创建弹窗菜单 mMenu = mPopupMenu.getMenu(); + // 加载菜单布局到弹窗中 mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + // 设置按钮点击事件:点击时显示下拉菜单 mButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { mPopupMenu.show(); @@ -45,17 +57,30 @@ public class DropdownMenu { }); } + /** + * 设置下拉菜单项的点击监听器 + * @param listener 菜单项点击回调 + */ public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { if (mPopupMenu != null) { mPopupMenu.setOnMenuItemClickListener(listener); } } + /** + * 根据菜单项ID查找对应的菜单项 + * @param id 菜单项资源ID + * @return 对应的MenuItem对象 + */ public MenuItem findItem(int id) { return mMenu.findItem(id); } + /** + * 设置下拉菜单触发按钮的文本标题 + * @param title 要显示的文本内容 + */ public void setTitle(CharSequence title) { mButton.setText(title); } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java b/src/Notes-master/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java index 96b77da..f595de1 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java @@ -28,53 +28,85 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; - +/** + * 文件夹列表适配器 + * 继承CursorAdapter,用于将文件夹的Cursor数据绑定到列表视图,展示文件夹名称 + */ public class FoldersListAdapter extends CursorAdapter { + // 文件夹查询投影字段(ID和名称) public static final String [] PROJECTION = { - NoteColumns.ID, - NoteColumns.SNIPPET + NoteColumns.ID, + NoteColumns.SNIPPET }; - public static final int ID_COLUMN = 0; - public static final int NAME_COLUMN = 1; + // 投影字段对应的列索引 + public static final int ID_COLUMN = 0; // 文件夹ID列索引 + public static final int NAME_COLUMN = 1; // 文件夹名称列索引 + /** + * 构造方法:初始化文件夹列表适配器 + * @param context 上下文对象 + * @param c 存储文件夹数据的Cursor + */ public FoldersListAdapter(Context context, Cursor c) { super(context, c); // TODO Auto-generated constructor stub } + /** + * 创建新的列表项视图 + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new FolderListItem(context); } + /** + * 绑定文件夹数据到列表项视图 + * 特殊处理根文件夹,显示预设文本而非数据库中的名称 + */ @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof FolderListItem) { + // 判断是否为根文件夹,设置对应显示名称 String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); ((FolderListItem) view).bind(folderName); } } + /** + * 获取指定位置的文件夹名称 + * @param context 上下文对象 + * @param position 列表项位置 + * @return 文件夹显示名称 + */ public String getFolderName(Context context, int position) { Cursor cursor = (Cursor) getItem(position); return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); } + /** + * 文件夹列表项布局封装类 + * 承载单个文件夹的显示布局,包含名称文本视图 + */ private class FolderListItem extends LinearLayout { - private TextView mName; + private TextView mName; // 文件夹名称文本视图 public FolderListItem(Context context) { super(context); - inflate(context, R.layout.folder_list_item, this); - mName = (TextView) findViewById(R.id.tv_folder_name); + inflate(context, R.layout.folder_list_item, this); // 加载列表项布局 + mName = (TextView) findViewById(R.id.tv_folder_name); // 获取名称文本控件 } + /** + * 绑定文件夹名称到文本视图 + * @param name 文件夹显示名称 + */ public void bind(String name) { mName.setText(name); } } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java index 96a9ff8..2294c51 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -1,19 +1,3 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package net.micode.notes.ui; import android.app.Activity; @@ -71,19 +55,24 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; - +/** + * 笔记编辑页面 + * 负责笔记的创建、编辑、保存、删除、设置提醒等核心功能 + */ public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { + /** + * 头部视图持有者 + * 缓存头部布局控件,提升控件获取效率 + */ private class HeadViewHolder { - public TextView tvModified; - - public ImageView ivAlertIcon; - - public TextView tvAlertDate; - - public ImageView ibSetBgColor; + public TextView tvModified; // 笔记修改时间文本 + public ImageView ivAlertIcon; // 提醒图标 + public TextView tvAlertDate; // 提醒时间文本 + public ImageView ibSetBgColor; // 背景颜色设置按钮 } + // 背景颜色按钮与对应颜色资源的映射表 private static final Map sBgSelectorBtnsMap = new HashMap(); static { sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); @@ -93,6 +82,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); } + // 背景颜色与对应选中状态控件的映射表 private static final Map sBgSelectorSelectionMap = new HashMap(); static { sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); @@ -102,6 +92,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } + // 字体大小按钮与对应字体资源的映射表 private static final Map sFontSizeBtnsMap = new HashMap(); static { sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); @@ -110,6 +101,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); } + // 字体大小与对应选中状态控件的映射表 private static final Map sFontSelectorSelectionMap = new HashMap(); static { sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); @@ -119,41 +111,31 @@ public class NoteEditActivity extends Activity implements OnClickListener, } private static final String TAG = "NoteEditActivity"; - - private HeadViewHolder mNoteHeaderHolder; - - private View mHeadViewPanel; - - private View mNoteBgColorSelector; - - private View mFontSizeSelector; - - private EditText mNoteEditor; - - private View mNoteEditorPanel; - - private WorkingNote mWorkingNote; - - private SharedPreferences mSharedPrefs; - private int mFontSizeId; - private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; - private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; public static final String TAG_CHECKED = String.valueOf('\u221A'); public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); - private LinearLayout mEditTextList; - - private String mUserQuery; - private Pattern mPattern; + private HeadViewHolder mNoteHeaderHolder; // 头部视图持有者实例 + private View mHeadViewPanel; // 头部布局面板 + private View mNoteBgColorSelector; // 背景颜色选择器面板 + private View mFontSizeSelector; // 字体大小选择器面板 + private EditText mNoteEditor; // 普通笔记编辑框 + private View mNoteEditorPanel; // 笔记编辑面板 + private WorkingNote mWorkingNote; // 工作笔记实例,承载当前编辑笔记数据 + private SharedPreferences mSharedPrefs; // 共享偏好,存储用户配置(如字体大小) + private int mFontSizeId; // 当前字体大小资源ID + private LinearLayout mEditTextList; // 复选列表模式下的编辑项列表 + private String mUserQuery; // 搜索查询关键词 + private Pattern mPattern; // 搜索关键词匹配正则 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.note_edit); + // 首次创建且无保存状态时,初始化页面状态 if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; @@ -161,13 +143,10 @@ public class NoteEditActivity extends Activity implements OnClickListener, initResources(); } - /** - * Current activity may be killed when the memory is low. Once it is killed, for another time - * user load this activity, we should restore the former state - */ @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); + // 恢复被系统回收的页面状态 if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); @@ -179,24 +158,25 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 初始化页面状态 + * @param intent 页面跳转意图 + * @return 初始化是否成功 + */ private boolean initActivityState(Intent intent) { - /** - * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, - * then jump to the NotesListActivity - */ mWorkingNote = null; + // 查看已有笔记 if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); mUserQuery = ""; - /** - * Starting from the searched result - */ + // 从搜索结果跳转 if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); } + // 笔记不存在则跳转至列表页 if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { Intent jump = new Intent(this, NotesListActivity.class); startActivity(jump); @@ -211,11 +191,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } } + // 隐藏软键盘 getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); - } else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { - // New note + } + // 新建/编辑笔记 + else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); @@ -224,7 +206,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, ResourceParser.getDefaultBgId(this)); - // Parse call-record note + // 处理通话记录笔记 String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); if (callDate != 0 && phoneNumber != null) { @@ -232,6 +214,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, Log.w(TAG, "The call record number is null"); } long noteId = 0; + // 已有对应通话笔记则加载,否则创建新笔记 if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), phoneNumber, callDate)) > 0) { mWorkingNote = WorkingNote.load(this, noteId); @@ -249,7 +232,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId); } - + // 显示软键盘 getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); @@ -268,39 +251,50 @@ public class NoteEditActivity extends Activity implements OnClickListener, initNoteScreen(); } + /** + * 初始化笔记显示界面 + */ private void initNoteScreen() { mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); + // 复选列表模式 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { switchToListMode(mWorkingNote.getContent()); - } else { + } + // 普通编辑模式 + else { mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); mNoteEditor.setSelection(mNoteEditor.getText().length()); } + // 隐藏所有背景选中状态 for (Integer id : sBgSelectorSelectionMap.keySet()) { findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); } + // 设置页面背景 mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + // 设置修改时间 mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR)); - /** - * TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker - * is not ready - */ showAlertHeader(); } + /** + * 显示提醒头部信息 + */ private void showAlertHeader() { if (mWorkingNote.hasClockAlert()) { long time = System.currentTimeMillis(); + // 提醒已过期 if (time > mWorkingNote.getAlertDate()) { mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); - } else { + } + // 显示相对时间 + else { mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); } @@ -321,11 +315,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - /** - * For new note without note id, we should firstly save it to - * generate a id. If the editing note is not worth saving, there - * is no id which is equivalent to create new note - */ + // 新笔记先保存生成ID if (!mWorkingNote.existInDatabase()) { saveNote(); } @@ -335,12 +325,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, @Override public boolean dispatchTouchEvent(MotionEvent ev) { + // 点击外部隐藏背景选择器 if (mNoteBgColorSelector.getVisibility() == View.VISIBLE && !inRangeOfView(mNoteBgColorSelector, ev)) { mNoteBgColorSelector.setVisibility(View.GONE); return true; } - + // 点击外部隐藏字体大小选择器 if (mFontSizeSelector.getVisibility() == View.VISIBLE && !inRangeOfView(mFontSizeSelector, ev)) { mFontSizeSelector.setVisibility(View.GONE); @@ -349,6 +340,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, return super.dispatchTouchEvent(ev); } + /** + * 判断触摸点是否在视图范围内 + * @param view 目标视图 + * @param ev 触摸事件 + * @return 是否在范围内 + */ private boolean inRangeOfView(View view, MotionEvent ev) { int []location = new int[2]; view.getLocationOnScreen(location); @@ -358,11 +355,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, || ev.getX() > (x + view.getWidth()) || ev.getY() < y || ev.getY() > (y + view.getHeight())) { - return false; - } + return false; + } return true; } + /** + * 初始化页面资源和控件 + */ private void initResources() { mHeadViewPanel = findViewById(R.id.note_title); mNoteHeaderHolder = new HeadViewHolder(); @@ -371,26 +371,27 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); + mNoteEditor = (EditText) findViewById(R.id.note_edit_view); mNoteEditorPanel = findViewById(R.id.sv_note_edit); mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); + + // 初始化背景颜色选择按钮 for (int id : sBgSelectorBtnsMap.keySet()) { ImageView iv = (ImageView) findViewById(id); iv.setOnClickListener(this); } mFontSizeSelector = findViewById(R.id.font_size_selector); + // 初始化字体大小选择按钮 for (int id : sFontSizeBtnsMap.keySet()) { View view = findViewById(id); view.setOnClickListener(this); }; + mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); - /** - * HACKME: Fix bug of store the resource id in shared preference. - * The id may larger than the length of resources, in this case, - * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} - */ + // 字体资源ID异常时重置为默认值 if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; } @@ -400,14 +401,19 @@ public class NoteEditActivity extends Activity implements OnClickListener, @Override protected void onPause() { super.onPause(); + // 暂停时保存笔记 if(saveNote()) { Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); } clearSettingState(); } + /** + * 更新桌面小组件 + */ private void updateWidget() { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + // 区分2x和4x小组件 if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { intent.setClass(this, NoteWidgetProvider_2x.class); } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { @@ -418,33 +424,42 @@ public class NoteEditActivity extends Activity implements OnClickListener, } intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { - mWorkingNote.getWidgetId() + mWorkingNote.getWidgetId() }); sendBroadcast(intent); setResult(RESULT_OK, intent); } + @Override public void onClick(View v) { int id = v.getId(); + // 显示背景颜色选择器 if (id == R.id.btn_set_bg_color) { mNoteBgColorSelector.setVisibility(View.VISIBLE); findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - - View.VISIBLE); - } else if (sBgSelectorBtnsMap.containsKey(id)) { + View.VISIBLE); + } + // 选择背景颜色 + else if (sBgSelectorBtnsMap.containsKey(id)) { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.GONE); mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); mNoteBgColorSelector.setVisibility(View.GONE); - } else if (sFontSizeBtnsMap.containsKey(id)) { + } + // 选择字体大小 + else if (sFontSizeBtnsMap.containsKey(id)) { findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); mFontSizeId = sFontSizeBtnsMap.get(id); mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); + // 刷新复选列表模式字体 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { getWorkingText(); switchToListMode(mWorkingNote.getContent()); - } else { + } + // 刷新普通模式字体 + else { mNoteEditor.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); } @@ -454,6 +469,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, @Override public void onBackPressed() { + // 先关闭设置面板再返回 if(clearSettingState()) { return; } @@ -462,6 +478,10 @@ public class NoteEditActivity extends Activity implements OnClickListener, super.onBackPressed(); } + /** + * 清除设置面板状态(隐藏背景/字体选择器) + * @return 是否执行了清除操作 + */ private boolean clearSettingState() { if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { mNoteBgColorSelector.setVisibility(View.GONE); @@ -473,6 +493,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } + @Override public void onBackgroundColorChanged() { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.VISIBLE); @@ -487,16 +508,19 @@ public class NoteEditActivity extends Activity implements OnClickListener, } clearSettingState(); menu.clear(); + // 通话记录笔记和普通笔记加载不同菜单 if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { getMenuInflater().inflate(R.menu.call_note_edit, menu); } else { getMenuInflater().inflate(R.menu.note_edit, menu); } + // 切换列表模式/普通模式菜单文字 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); } else { menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); } + // 隐藏已设置提醒/删除提醒菜单 if (mWorkingNote.hasClockAlert()) { menu.findItem(R.id.menu_alert).setVisible(false); } else { @@ -512,6 +536,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, createNewNote(); break; case R.id.menu_delete: + // 弹出删除确认对话框 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); @@ -531,6 +556,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); break; case R.id.menu_list_mode: + // 切换复选列表/普通模式 mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? TextNote.MODE_CHECK_LIST : 0); break; @@ -553,6 +579,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + /** + * 设置笔记提醒 + */ private void setReminder() { DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); d.setOnDateTimeSetListener(new OnDateTimeSetListener() { @@ -564,8 +593,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, } /** - * Share note to apps that support {@link Intent#ACTION_SEND} action - * and {@text/plain} type + * 分享笔记内容 + * @param context 上下文 + * @param info 笔记内容 */ private void sendTo(Context context, String info) { Intent intent = new Intent(Intent.ACTION_SEND); @@ -574,11 +604,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, context.startActivity(intent); } + /** + * 创建新笔记 + */ private void createNewNote() { - // Firstly, save current editing notes saveNote(); - - // For safety, start a new NoteEditActivity finish(); Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); @@ -586,6 +616,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, startActivity(intent); } + /** + * 删除当前笔记 + */ private void deleteCurrentNote() { if (mWorkingNote.existInDatabase()) { HashSet ids = new HashSet(); @@ -595,6 +628,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, } else { Log.d(TAG, "Wrong note id, should not happen"); } + // 非同步模式直接删除,同步模式移至回收站 if (!isSyncMode()) { if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { Log.e(TAG, "Delete Note error"); @@ -608,15 +642,17 @@ public class NoteEditActivity extends Activity implements OnClickListener, mWorkingNote.markDeleted(true); } + /** + * 判断是否为同步模式 + * @return 是否开启同步 + */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + @Override public void onClockAlertChanged(long date, boolean set) { - /** - * User could set clock to an unsaved note, so before setting the - * alert clock, we should save the note first - */ + // 未保存的笔记先保存生成ID if (!mWorkingNote.existInDatabase()) { saveNote(); } @@ -626,32 +662,33 @@ public class NoteEditActivity extends Activity implements OnClickListener, PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); showAlertHeader(); + // 取消提醒 if(!set) { alarmManager.cancel(pendingIntent); - } else { + } + // 设置提醒 + else { alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); } } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ Log.e(TAG, "Clock alert setting error"); showToast(R.string.error_note_empty_for_clock); } } + @Override public void onWidgetChanged() { updateWidget(); } + @Override public void onEditTextDelete(int index, String text) { int childCount = mEditTextList.getChildCount(); if (childCount == 1) { return; } + // 更新编辑项索引 for (int i = index + 1; i < childCount; i++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) .setIndex(i - 1); @@ -659,6 +696,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, mEditTextList.removeViewAt(index); NoteEditText edit = null; + // 获取焦点并拼接文本 if(index == 0) { edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( R.id.et_edit_text); @@ -672,10 +710,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, edit.setSelection(length); } + @Override public void onEditTextEnter(int index, String text) { - /** - * Should not happen, check for debug - */ if(index > mEditTextList.getChildCount()) { Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); } @@ -685,12 +721,17 @@ public class NoteEditActivity extends Activity implements OnClickListener, NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); edit.requestFocus(); edit.setSelection(0); + // 更新编辑项索引 for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) .setIndex(i); } } + /** + * 切换到复选列表模式 + * @param text 笔记内容 + */ private void switchToListMode(String text) { mEditTextList.removeAllViews(); String[] items = text.split("\n"); @@ -708,6 +749,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, mEditTextList.setVisibility(View.VISIBLE); } + /** + * 高亮显示搜索关键词 + * @param fullText 完整文本 + * @param userQuery 搜索关键词 + * @return 带高亮的 Spannable + */ private Spannable getHighlightQueryResult(String fullText, String userQuery) { SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); if (!TextUtils.isEmpty(userQuery)) { @@ -725,11 +772,18 @@ public class NoteEditActivity extends Activity implements OnClickListener, return spannable; } + /** + * 获取复选列表项视图 + * @param item 列表项文本 + * @param index 列表项索引 + * @return 列表项视图 + */ private View getListItem(String item, int index) { View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); + // 复选框状态变更监听 cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { @@ -740,6 +794,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, } }); + // 初始化复选框状态和文本 if (item.startsWith(TAG_CHECKED)) { cb.setChecked(true); edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); @@ -756,11 +811,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, return view; } + @Override public void onTextChange(int index, boolean hasText) { if (index >= mEditTextList.getChildCount()) { Log.e(TAG, "Wrong index, should not happen"); return; } + // 有文本显示复选框,无文本隐藏 if(hasText) { mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); } else { @@ -768,10 +825,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + @Override public void onCheckListModeChanged(int oldMode, int newMode) { + // 切换到复选列表模式 if (newMode == TextNote.MODE_CHECK_LIST) { switchToListMode(mNoteEditor.getText().toString()); - } else { + } + // 切换到普通模式 + else { if (!getWorkingText()) { mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", "")); @@ -782,8 +843,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 获取当前编辑文本内容 + * @return 是否存在已勾选的复选项 + */ private boolean getWorkingText() { boolean hasChecked = false; + // 复选列表模式 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < mEditTextList.getChildCount(); i++) { @@ -799,34 +865,32 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } mWorkingNote.setWorkingText(sb.toString()); - } else { + } + // 普通编辑模式 + else { mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); } return hasChecked; } + /** + * 保存笔记 + * @return 保存是否成功 + */ private boolean saveNote() { getWorkingText(); boolean saved = mWorkingNote.saveNote(); if (saved) { - /** - * There are two modes from List view to edit view, open one note, - * create/edit a node. Opening node requires to the original - * position in the list when back from edit view, while creating a - * new node requires to the top of the list. This code - * {@link #RESULT_OK} is used to identify the create/edit state - */ setResult(RESULT_OK); } return saved; } + /** + * 添加笔记快捷方式到桌面 + */ private void sendToDesktop() { - /** - * Before send message to home, we should make sure that current - * editing note is exists in databases. So, for new note, firstly - * save it - */ + // 新笔记先保存 if (!mWorkingNote.existInDatabase()) { saveNote(); } @@ -846,16 +910,16 @@ public class NoteEditActivity extends Activity implements OnClickListener, showToast(R.string.info_note_enter_desktop); sendBroadcast(sender); } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ Log.e(TAG, "Send to desktop error"); showToast(R.string.error_note_empty_for_send_to_desktop); } } + /** + * 生成快捷方式标题 + * @param content 笔记内容 + * @return 截取后的快捷方式标题 + */ private String makeShortcutIconTitle(String content) { content = content.replace(TAG_CHECKED, ""); content = content.replace(TAG_UNCHECKED, ""); @@ -863,11 +927,20 @@ public class NoteEditActivity extends Activity implements OnClickListener, SHORTCUT_ICON_TITLE_MAX_LEN) : content; } + /** + * 显示短时长吐司 + * @param resId 字符串资源ID + */ private void showToast(int resId) { showToast(resId, Toast.LENGTH_SHORT); } + /** + * 显示指定时长吐司 + * @param resId 字符串资源ID + * @param duration 显示时长 + */ private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).show(); } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NoteEditText.java b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NoteEditText.java index 2afe2a8..71196b9 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NoteEditText.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NoteEditText.java @@ -1,19 +1,3 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package net.micode.notes.ui; import android.content.Context; @@ -37,15 +21,21 @@ import net.micode.notes.R; import java.util.HashMap; import java.util.Map; +/** + * 自定义笔记编辑框 + * 扩展EditText功能,支持URL链接跳转、按键事件回调、焦点变化监听等 + */ public class NoteEditText extends EditText { private static final String TAG = "NoteEditText"; - private int mIndex; - private int mSelectionStartBeforeDelete; + private int mIndex; // 编辑项索引(用于复选列表模式) + private int mSelectionStartBeforeDelete; // 删除操作前的光标起始位置 + // 链接协议常量 private static final String SCHEME_TEL = "tel:" ; private static final String SCHEME_HTTP = "http:" ; private static final String SCHEME_EMAIL = "mailto:" ; + // 协议与对应菜单文字资源的映射表 private static final Map sSchemaActionResMap = new HashMap(); static { sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); @@ -54,38 +44,51 @@ public class NoteEditText extends EditText { } /** - * Call by the {@link NoteEditActivity} to delete or add edit text + * 文本变化回调接口 + * 用于通知父组件执行删除/添加编辑项、文本状态变更操作 */ public interface OnTextViewChangeListener { /** - * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens - * and the text is null + * 文本为空时触发删除键,通知删除当前编辑项 + * @param index 编辑项索引 + * @param text 编辑项文本 */ void onEditTextDelete(int index, String text); /** - * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} - * happen + * 触发回车键时,通知在当前编辑项后添加新项 + * @param index 编辑项索引 + * @param text 编辑项文本 */ void onEditTextEnter(int index, String text); /** - * Hide or show item option when text change + * 文本变化时,通知隐藏/显示对应选项 + * @param index 编辑项索引 + * @param hasText 是否包含文本 */ void onTextChange(int index, boolean hasText); } - private OnTextViewChangeListener mOnTextViewChangeListener; + private OnTextViewChangeListener mOnTextViewChangeListener; // 回调监听实例 public NoteEditText(Context context) { super(context, null); mIndex = 0; } + /** + * 设置编辑项索引 + * @param index 索引值 + */ public void setIndex(int index) { mIndex = index; } + /** + * 设置文本变化回调监听 + * @param listener 回调实例 + */ public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { mOnTextViewChangeListener = listener; } @@ -96,14 +99,15 @@ public class NoteEditText extends EditText { public NoteEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - // TODO Auto-generated constructor stub } + /** + * 处理触摸事件,定位光标到触摸位置 + */ @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - int x = (int) event.getX(); int y = (int) event.getY(); x -= getTotalPaddingLeft(); @@ -117,10 +121,12 @@ public class NoteEditText extends EditText { Selection.setSelection(getText(), off); break; } - return super.onTouchEvent(event); } + /** + * 处理按键按下事件 + */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { @@ -130,6 +136,7 @@ public class NoteEditText extends EditText { } break; case KeyEvent.KEYCODE_DEL: + // 记录删除前的光标位置 mSelectionStartBeforeDelete = getSelectionStart(); break; default: @@ -138,11 +145,15 @@ public class NoteEditText extends EditText { return super.onKeyDown(keyCode, event); } + /** + * 处理按键抬起事件,分发删除/回车回调 + */ @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch(keyCode) { case KeyEvent.KEYCODE_DEL: if (mOnTextViewChangeListener != null) { + // 光标在起始位置且非首个编辑项时,触发删除回调 if (0 == mSelectionStartBeforeDelete && mIndex != 0) { mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); return true; @@ -156,6 +167,7 @@ public class NoteEditText extends EditText { int selectionStart = getSelectionStart(); String text = getText().subSequence(selectionStart, length()).toString(); setText(getText().subSequence(0, selectionStart)); + // 触发添加编辑项回调 mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); } else { Log.d(TAG, "OnTextViewChangeListener was not seted"); @@ -167,6 +179,9 @@ public class NoteEditText extends EditText { return super.onKeyUp(keyCode, event); } + /** + * 处理焦点变化事件,通知文本状态变更 + */ @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { if (mOnTextViewChangeListener != null) { @@ -179,6 +194,9 @@ public class NoteEditText extends EditText { super.onFocusChanged(focused, direction, previouslyFocusedRect); } + /** + * 创建上下文菜单,支持URL链接跳转功能 + */ @Override protected void onCreateContextMenu(ContextMenu menu) { if (getText() instanceof Spanned) { @@ -188,9 +206,11 @@ public class NoteEditText extends EditText { int min = Math.min(selStart, selEnd); int max = Math.max(selStart, selEnd); + // 获取选中区域内的URL链接 final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); if (urls.length == 1) { int defaultResId = 0; + // 匹配链接协议,获取对应菜单文字 for(String schema: sSchemaActionResMap.keySet()) { if(urls[0].getURL().indexOf(schema) >= 0) { defaultResId = sSchemaActionResMap.get(schema); @@ -198,14 +218,15 @@ public class NoteEditText extends EditText { } } + // 默认菜单文字 if (defaultResId == 0) { defaultResId = R.string.note_link_other; } + // 添加跳转菜单并设置点击事件 menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { - // goto a new intent urls[0].onClick(NoteEditText.this); return true; } @@ -214,4 +235,4 @@ public class NoteEditText extends EditText { } super.onCreateContextMenu(menu); } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NoteItemData.java b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NoteItemData.java index 0f5a878..f4c0201 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NoteItemData.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NoteItemData.java @@ -1,19 +1,3 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package net.micode.notes.ui; import android.content.Context; @@ -25,57 +9,69 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.tool.DataUtils; - +/** + * 笔记条目数据封装类 + * 用于存储笔记/文件夹的核心信息,从数据库Cursor中解析并初始化数据 + */ public class NoteItemData { + // 数据库查询投影字段,包含笔记/文件夹的所有核心属性 static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.ALERTED_DATE, - NoteColumns.BG_COLOR_ID, - NoteColumns.CREATED_DATE, - NoteColumns.HAS_ATTACHMENT, - NoteColumns.MODIFIED_DATE, - NoteColumns.NOTES_COUNT, - NoteColumns.PARENT_ID, - NoteColumns.SNIPPET, - NoteColumns.TYPE, - NoteColumns.WIDGET_ID, - NoteColumns.WIDGET_TYPE, + NoteColumns.ID, + NoteColumns.ALERTED_DATE, + NoteColumns.BG_COLOR_ID, + NoteColumns.CREATED_DATE, + NoteColumns.HAS_ATTACHMENT, + NoteColumns.MODIFIED_DATE, + NoteColumns.NOTES_COUNT, + NoteColumns.PARENT_ID, + NoteColumns.SNIPPET, + NoteColumns.TYPE, + NoteColumns.WIDGET_ID, + NoteColumns.WIDGET_TYPE, }; - private static final int ID_COLUMN = 0; - private static final int ALERTED_DATE_COLUMN = 1; - private static final int BG_COLOR_ID_COLUMN = 2; - private static final int CREATED_DATE_COLUMN = 3; - private static final int HAS_ATTACHMENT_COLUMN = 4; - private static final int MODIFIED_DATE_COLUMN = 5; - private static final int NOTES_COUNT_COLUMN = 6; - private static final int PARENT_ID_COLUMN = 7; - private static final int SNIPPET_COLUMN = 8; - private static final int TYPE_COLUMN = 9; - private static final int WIDGET_ID_COLUMN = 10; - private static final int WIDGET_TYPE_COLUMN = 11; - - private long mId; - private long mAlertDate; - private int mBgColorId; - private long mCreatedDate; - private boolean mHasAttachment; - private long mModifiedDate; - private int mNotesCount; - private long mParentId; - private String mSnippet; - private int mType; - private int mWidgetId; - private int mWidgetType; - private String mName; - private String mPhoneNumber; - - private boolean mIsLastItem; - private boolean mIsFirstItem; - private boolean mIsOnlyOneItem; - private boolean mIsOneNoteFollowingFolder; - private boolean mIsMultiNotesFollowingFolder; - + // 投影字段对应的列索引 + private static final int ID_COLUMN = 0; // 笔记/文件夹ID列 + private static final int ALERTED_DATE_COLUMN = 1; // 提醒时间列 + private static final int BG_COLOR_ID_COLUMN = 2; // 背景颜色ID列 + private static final int CREATED_DATE_COLUMN = 3; // 创建时间列 + private static final int HAS_ATTACHMENT_COLUMN = 4; // 是否有附件列 + private static final int MODIFIED_DATE_COLUMN = 5; // 修改时间列 + private static final int NOTES_COUNT_COLUMN = 6; // 笔记数量列(文件夹专用) + private static final int PARENT_ID_COLUMN = 7; // 父文件夹ID列 + private static final int SNIPPET_COLUMN = 8; // 笔记内容摘要列 + private static final int TYPE_COLUMN = 9; // 类型列(笔记/文件夹) + private static final int WIDGET_ID_COLUMN = 10; // 桌面组件ID列 + private static final int WIDGET_TYPE_COLUMN = 11; // 桌面组件类型列 + + // 核心数据字段 + private long mId; // 笔记/文件夹ID + private long mAlertDate; // 提醒时间戳 + private int mBgColorId; // 背景颜色资源ID + private long mCreatedDate; // 创建时间戳 + private boolean mHasAttachment; // 是否包含附件 + private long mModifiedDate; // 修改时间戳 + private int mNotesCount; // 文件夹下的笔记数量 + private long mParentId; // 父文件夹ID + private String mSnippet; // 笔记内容摘要 + private int mType; // 类型(笔记/文件夹) + private int mWidgetId; // 桌面组件ID + private int mWidgetType; // 桌面组件类型 + private String mName; // 通话记录联系人姓名 + private String mPhoneNumber; // 通话记录电话号码 + + // 条目位置状态字段 + private boolean mIsLastItem; // 是否为列表最后一个条目 + private boolean mIsFirstItem; // 是否为列表第一个条目 + private boolean mIsOnlyOneItem; // 是否为列表唯一条目 + private boolean mIsOneNoteFollowingFolder; // 是否为文件夹后紧跟单个笔记 + private boolean mIsMultiNotesFollowingFolder;// 是否为文件夹后紧跟多个笔记 + + /** + * 构造方法:从Cursor中解析并初始化笔记/文件夹数据 + * @param context 上下文对象 + * @param cursor 数据库查询游标 + */ public NoteItemData(Context context, Cursor cursor) { mId = cursor.getLong(ID_COLUMN); mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); @@ -86,6 +82,7 @@ public class NoteItemData { mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); mParentId = cursor.getLong(PARENT_ID_COLUMN); mSnippet = cursor.getString(SNIPPET_COLUMN); + // 移除复选列表标记符 mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( NoteEditActivity.TAG_UNCHECKED, ""); mType = cursor.getInt(TYPE_COLUMN); @@ -93,6 +90,7 @@ public class NoteItemData { mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); mPhoneNumber = ""; + // 若是通话记录文件夹下的笔记,获取电话号码和联系人姓名 if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); if (!TextUtils.isEmpty(mPhoneNumber)) { @@ -106,9 +104,15 @@ public class NoteItemData { if (mName == null) { mName = ""; } + // 检查条目位置状态 checkPostion(cursor); } + /** + * 检查当前条目在Cursor中的位置及关联状态 + * 初始化首条、末条、文件夹关联等状态标记 + * @param cursor 数据库查询游标 + */ private void checkPostion(Cursor cursor) { mIsLastItem = cursor.isLast() ? true : false; mIsFirstItem = cursor.isFirst() ? true : false; @@ -116,6 +120,7 @@ public class NoteItemData { mIsMultiNotesFollowingFolder = false; mIsOneNoteFollowingFolder = false; + // 判断是否为文件夹后紧跟的笔记 if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { int position = cursor.getPosition(); if (cursor.moveToPrevious()) { @@ -127,6 +132,7 @@ public class NoteItemData { mIsOneNoteFollowingFolder = true; } } + // 恢复游标位置 if (!cursor.moveToNext()) { throw new IllegalStateException("cursor move to previous but can't move back"); } @@ -134,91 +140,113 @@ public class NoteItemData { } } + // 是否为文件夹后紧跟单个笔记 public boolean isOneFollowingFolder() { return mIsOneNoteFollowingFolder; } + // 是否为文件夹后紧跟多个笔记 public boolean isMultiFollowingFolder() { return mIsMultiNotesFollowingFolder; } + // 是否为列表最后一个条目 public boolean isLast() { return mIsLastItem; } + // 获取通话记录联系人姓名 public String getCallName() { return mName; } + // 是否为列表第一个条目 public boolean isFirst() { return mIsFirstItem; } + // 是否为列表唯一条目 public boolean isSingle() { return mIsOnlyOneItem; } + // 获取条目ID(笔记/文件夹ID) public long getId() { return mId; } + // 获取提醒时间戳 public long getAlertDate() { return mAlertDate; } + // 获取创建时间戳 public long getCreatedDate() { return mCreatedDate; } + // 是否包含附件 public boolean hasAttachment() { return mHasAttachment; } + // 获取修改时间戳 public long getModifiedDate() { return mModifiedDate; } + // 获取背景颜色ID public int getBgColorId() { return mBgColorId; } + // 获取父文件夹ID public long getParentId() { return mParentId; } + // 获取文件夹下的笔记数量 public int getNotesCount() { return mNotesCount; } + // 获取文件夹ID(同getParentId) public long getFolderId () { return mParentId; } + // 获取条目类型(笔记/文件夹) public int getType() { return mType; } + // 获取桌面组件类型 public int getWidgetType() { return mWidgetType; } + // 获取桌面组件ID public int getWidgetId() { return mWidgetId; } + // 获取笔记内容摘要 public String getSnippet() { return mSnippet; } + // 是否设置了提醒 public boolean hasAlert() { return (mAlertDate > 0); } + // 是否为通话记录笔记 public boolean isCallRecord() { return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); } + // 从Cursor中获取笔记/文件夹类型 public static int getNoteType(Cursor cursor) { return cursor.getInt(TYPE_COLUMN); } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesListActivity.java b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesListActivity.java index e843aec..89b2099 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesListActivity.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesListActivity.java @@ -1,19 +1,3 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package net.micode.notes.ui; import android.app.Activity; @@ -78,77 +62,75 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashSet; +/** + * 笔记列表主页面 + * 负责展示笔记/文件夹列表、处理新建/删除/移动笔记/文件夹、批量操作等核心功能 + */ public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { + // 异步查询标记:笔记列表查询 private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; - + // 异步查询标记:文件夹列表查询 private static final int FOLDER_LIST_QUERY_TOKEN = 1; - + // 文件夹上下文菜单:删除 private static final int MENU_FOLDER_DELETE = 0; - + // 文件夹上下文菜单:查看 private static final int MENU_FOLDER_VIEW = 1; - + // 文件夹上下文菜单:重命名 private static final int MENU_FOLDER_CHANGE_NAME = 2; - + // 偏好设置:首次使用引导标记 private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; - - private enum ListEditState { - NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER - }; - - private ListEditState mState; - - private BackgroundQueryHandler mBackgroundQueryHandler; - - private NotesListAdapter mNotesListAdapter; - - private ListView mNotesListView; - - private Button mAddNewNote; - - private boolean mDispatch; - - private int mOriginY; - - private int mDispatchY; - - private TextView mTitleBar; - - private long mCurrentFolderId; - - private ContentResolver mContentResolver; - - private ModeCallback mModeCallBack; - + // 请求码:打开已有笔记 + private final static int REQUEST_CODE_OPEN_NODE = 102; + // 请求码:新建笔记 + private final static int REQUEST_CODE_NEW_NODE = 103; + // 日志标记 private static final String TAG = "NotesListActivity"; - + // 列表滚动速率 public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; - - private NoteItemData mFocusNoteDataItem; - + // 普通文件夹查询条件 private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; - + // 根文件夹查询条件 private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0)"; - private final static int REQUEST_CODE_OPEN_NODE = 102; - private final static int REQUEST_CODE_NEW_NODE = 103; + /** + * 列表编辑状态枚举 + * NOTE_LIST:根目录笔记/文件夹列表 + * SUB_FOLDER:子文件夹内笔记列表 + * CALL_RECORD_FOLDER:通话记录文件夹内列表 + */ + private enum ListEditState { + NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER + }; + + private ListEditState mState; // 当前列表编辑状态 + private BackgroundQueryHandler mBackgroundQueryHandler; // 异步查询处理器 + private NotesListAdapter mNotesListAdapter; // 笔记列表适配器 + private ListView mNotesListView; // 笔记列表视图 + private Button mAddNewNote; // 新建笔记按钮 + private boolean mDispatch; // 触摸事件透传标记 + private int mOriginY; // 触摸事件原始Y坐标 + private int mDispatchY; // 触摸事件透传Y坐标 + private TextView mTitleBar; // 标题栏(子文件夹/通话记录文件夹显示) + private long mCurrentFolderId; // 当前所在文件夹ID + private ContentResolver mContentResolver; // 内容解析器 + private ModeCallback mModeCallBack; // 多选模式回调 + private NoteItemData mFocusNoteDataItem; // 长按选中的笔记/文件夹数据 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.note_list); initResources(); - - /** - * Insert an introduction when user firstly use this application - */ + // 首次使用时添加引导笔记 setAppInfoFromRawRes(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // 笔记编辑完成后刷新列表 if (resultCode == RESULT_OK && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { mNotesListAdapter.changeCursor(null); @@ -157,13 +139,17 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 首次使用应用时,添加引导笔记 + * 从raw资源中读取引导内容并创建笔记 + */ private void setAppInfoFromRawRes() { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { StringBuilder sb = new StringBuilder(); InputStream in = null; try { - in = getResources().openRawResource(R.raw.introduction); + in = getResources().openRawResource(R.raw.introduction); if (in != null) { InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr); @@ -184,7 +170,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt try { in.close(); } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } @@ -206,14 +191,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override protected void onStart() { super.onStart(); + // 启动异步查询加载笔记列表 startAsyncNotesListQuery(); } + /** + * 初始化页面资源和控件 + */ private void initResources() { mContentResolver = this.getContentResolver(); mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); mCurrentFolderId = Notes.ID_ROOT_FOLDER; mNotesListView = (ListView) findViewById(R.id.notes_list); + // 添加列表底部视图 mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), null, false); mNotesListView.setOnItemClickListener(new OnListItemClickListener()); @@ -231,15 +221,20 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mModeCallBack = new ModeCallback(); } + /** + * 多选模式回调类 + * 处理笔记批量选择、删除、移动等操作 + */ private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { - private DropdownMenu mDropDownMenu; - private ActionMode mActionMode; - private MenuItem mMoveMenu; + private DropdownMenu mDropDownMenu; // 下拉菜单(全选/取消全选) + private ActionMode mActionMode; // 动作模式 + private MenuItem mMoveMenu; // 移动菜单选项 public boolean onCreateActionMode(ActionMode mode, Menu menu) { getMenuInflater().inflate(R.menu.note_list_options, menu); menu.findItem(R.id.delete).setOnMenuItemClickListener(this); mMoveMenu = menu.findItem(R.id.move); + // 通话记录文件夹或无用户文件夹时,隐藏移动选项 if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER || DataUtils.getUserFolderCount(mContentResolver) == 0) { mMoveMenu.setVisible(false); @@ -252,6 +247,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mNotesListView.setLongClickable(false); mAddNewNote.setVisibility(View.GONE); + // 设置自定义动作模式视图(下拉菜单) View customView = LayoutInflater.from(NotesListActivity.this).inflate( R.layout.note_list_dropdown_menu, null); mode.setCustomView(customView); @@ -264,14 +260,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt updateMenu(); return true; } - }); return true; } + /** + * 更新多选模式菜单状态(选中数量、全选状态) + */ private void updateMenu() { int selectedCount = mNotesListAdapter.getSelectedCount(); - // Update dropdown menu String format = getResources().getString(R.string.menu_select_title, selectedCount); mDropDownMenu.setTitle(format); MenuItem item = mDropDownMenu.findItem(R.id.action_select_all); @@ -287,16 +284,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - // TODO Auto-generated method stub return false; } public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - // TODO Auto-generated method stub return false; } public void onDestroyActionMode(ActionMode mode) { + // 退出多选模式,恢复页面状态 mNotesListAdapter.setChoiceMode(false); mNotesListView.setLongClickable(true); mAddNewNote.setVisibility(View.VISIBLE); @@ -307,7 +303,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { + boolean checked) { mNotesListAdapter.setCheckedItem(position, checked); updateMenu(); } @@ -321,22 +317,24 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt switch (item.getItemId()) { case R.id.delete: + // 弹出批量删除确认对话框 AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); builder.setMessage(getString(R.string.alert_message_delete_notes, - mNotesListAdapter.getSelectedCount())); + mNotesListAdapter.getSelectedCount())); builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int which) { - batchDelete(); - } - }); + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + batchDelete(); + } + }); builder.setNegativeButton(android.R.string.cancel, null); builder.show(); break; case R.id.move: + // 查询目标文件夹列表 startQueryDestinationFolders(); break; default: @@ -346,6 +344,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 新建笔记按钮触摸事件处理器 + * 处理按钮透明区域的触摸事件透传,兼容列表滚动 + */ private class NewNoteOnTouchListener implements OnTouchListener { public boolean onTouch(View v, MotionEvent event) { @@ -356,22 +358,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt int newNoteViewHeight = mAddNewNote.getHeight(); int start = screenHeight - newNoteViewHeight; int eventY = start + (int) event.getY(); - /** - * Minus TitleBar's height - */ + // 子文件夹状态下,扣除标题栏高度 if (mState == ListEditState.SUB_FOLDER) { eventY -= mTitleBar.getHeight(); start -= mTitleBar.getHeight(); } - /** - * HACKME:When click the transparent part of "New Note" button, dispatch - * the event to the list view behind this button. The transparent part of - * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel) - * and the line top of the button. The coordinate based on left of the "New - * Note" button. The 94 represents maximum height of the transparent part. - * Notice that, if the background of the button changes, the formula should - * also change. This is very bad, just for the UI designer's strong requirement. - */ + // 透明区域触摸事件透传到列表 if (event.getY() < (event.getX() * (-0.12) + 94)) { View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1 - mNotesListView.getFooterViewsCount()); @@ -408,15 +400,22 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }; + /** + * 启动异步查询,加载笔记/文件夹列表 + */ private void startAsyncNotesListQuery() { String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION : NORMAL_SELECTION; mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] { - String.valueOf(mCurrentFolderId) + String.valueOf(mCurrentFolderId) }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); } + /** + * 异步查询处理器 + * 处理笔记/文件夹列表的异步查询回调 + */ private final class BackgroundQueryHandler extends AsyncQueryHandler { public BackgroundQueryHandler(ContentResolver contentResolver) { super(contentResolver); @@ -426,10 +425,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt protected void onQueryComplete(int token, Object cookie, Cursor cursor) { switch (token) { case FOLDER_NOTE_LIST_QUERY_TOKEN: + // 更新笔记列表数据 mNotesListAdapter.changeCursor(cursor); break; case FOLDER_LIST_QUERY_TOKEN: if (cursor != null && cursor.getCount() > 0) { + // 显示文件夹选择菜单 showFolderListMenu(cursor); } else { Log.e(TAG, "Query folder failed"); @@ -441,6 +442,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 显示文件夹选择菜单(用于笔记移动) + * @param cursor 文件夹数据游标 + */ private void showFolderListMenu(Cursor cursor) { AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(R.string.menu_title_select_folder); @@ -462,6 +467,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt builder.show(); } + /** + * 新建笔记 + * 跳转到笔记编辑页面 + */ private void createNewNote() { Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); @@ -469,20 +478,22 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); } + /** + * 批量删除笔记 + * 非同步模式直接删除,同步模式移至回收站 + */ private void batchDelete() { new AsyncTask>() { protected HashSet doInBackground(Void... unused) { HashSet widgets = mNotesListAdapter.getSelectedWidget(); if (!isSyncMode()) { - // if not synced, delete notes directly - if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter + // 非同步模式:直接删除 + if (!DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter .getSelectedItemIds())) { - } else { Log.e(TAG, "Delete notes error, should not happens"); } } else { - // in sync mode, we'll move the deleted note into the trash - // folder + // 同步模式:移至回收站 if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter .getSelectedItemIds(), Notes.ID_TRASH_FOLER)) { Log.e(TAG, "Move notes to trash folder error, should not happens"); @@ -493,6 +504,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override protected void onPostExecute(HashSet widgets) { + // 更新关联的桌面小组件 if (widgets != null) { for (AppWidgetAttribute widget : widgets) { if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID @@ -506,6 +518,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }.execute(); } + /** + * 删除文件夹 + * @param folderId 要删除的文件夹ID + */ private void deleteFolder(long folderId) { if (folderId == Notes.ID_ROOT_FOLDER) { Log.e(TAG, "Wrong folder id, should not happen " + folderId); @@ -517,12 +533,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver, folderId); if (!isSyncMode()) { - // if not synced, delete folder directly + // 非同步模式:直接删除 DataUtils.batchDeleteNotes(mContentResolver, ids); } else { - // in sync mode, we'll move the deleted folder into the trash folder + // 同步模式:移至回收站 DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER); } + // 更新关联的桌面小组件 if (widgets != null) { for (AppWidgetAttribute widget : widgets) { if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID @@ -533,6 +550,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 打开已有笔记 + * @param data 笔记数据 + */ private void openNode(NoteItemData data) { Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_VIEW); @@ -540,15 +561,21 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); } + /** + * 打开文件夹 + * @param data 文件夹数据 + */ private void openFolder(NoteItemData data) { mCurrentFolderId = data.getId(); startAsyncNotesListQuery(); + // 更新页面状态 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mState = ListEditState.CALL_RECORD_FOLDER; mAddNewNote.setVisibility(View.GONE); } else { mState = ListEditState.SUB_FOLDER; } + // 设置标题栏 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mTitleBar.setText(R.string.call_record_folder_name); } else { @@ -557,6 +584,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mTitleBar.setVisibility(View.VISIBLE); } + @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_new_note: @@ -567,6 +595,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 显示软键盘 + */ private void showSoftInput() { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (inputMethodManager != null) { @@ -574,16 +605,25 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 隐藏软键盘 + * @param view 关联视图 + */ private void hideSoftInput(View view) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); } + /** + * 显示新建/修改文件夹对话框 + * @param create true:新建文件夹;false:修改文件夹 + */ private void showCreateOrModifyFolderDialog(final boolean create) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); final EditText etName = (EditText) view.findViewById(R.id.et_foler_name); showSoftInput(); + // 修改文件夹:填充原有名称 if (!create) { if (mFocusNoteDataItem != null) { etName.setText(mFocusNoteDataItem.getSnippet()); @@ -610,12 +650,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt public void onClick(View v) { hideSoftInput(etName); String name = etName.getText().toString(); + // 检查文件夹名称是否已存在 if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name), Toast.LENGTH_LONG).show(); etName.setSelection(0, etName.length()); return; } + // 修改文件夹名称 if (!create) { if (!TextUtils.isEmpty(name)) { ContentValues values = new ContentValues(); @@ -624,10 +666,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt values.put(NoteColumns.LOCAL_MODIFIED, 1); mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID + "=?", new String[] { - String.valueOf(mFocusNoteDataItem.getId()) + String.valueOf(mFocusNoteDataItem.getId()) }); } - } else if (!TextUtils.isEmpty(name)) { + } + // 新建文件夹 + else if (!TextUtils.isEmpty(name)) { ContentValues values = new ContentValues(); values.put(NoteColumns.SNIPPET, name); values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); @@ -637,16 +681,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } }); + // 文本为空时禁用确认按钮 if (TextUtils.isEmpty(etName.getText())) { positive.setEnabled(false); } - /** - * When the name edit text is null, disable the positive button - */ etName.addTextChangedListener(new TextWatcher() { public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // TODO Auto-generated method stub - } public void onTextChanged(CharSequence s, int start, int before, int count) { @@ -658,14 +698,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } public void afterTextChanged(Editable s) { - // TODO Auto-generated method stub - } }); } @Override public void onBackPressed() { + // 返回上级目录逻辑 switch (mState) { case SUB_FOLDER: mCurrentFolderId = Notes.ID_ROOT_FOLDER; @@ -688,6 +727,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 更新桌面小组件 + * @param appWidgetId 小组件ID + * @param appWidgetType 小组件类型 + */ private void updateWidget(int appWidgetId, int appWidgetType) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); if (appWidgetType == Notes.TYPE_WIDGET_2X) { @@ -700,13 +744,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { - appWidgetId + appWidgetId }); sendBroadcast(intent); setResult(RESULT_OK, intent); } + /** + * 文件夹上下文菜单创建监听器 + */ private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { if (mFocusNoteDataItem != null) { @@ -737,6 +784,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt openFolder(mFocusNoteDataItem); break; case MENU_FOLDER_DELETE: + // 弹出文件夹删除确认对话框 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); @@ -763,9 +811,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override public boolean onPrepareOptionsMenu(Menu menu) { menu.clear(); + // 根据当前状态加载对应菜单 if (mState == ListEditState.NOTE_LIST) { getMenuInflater().inflate(R.menu.note_list, menu); - // set sync or sync_cancel menu.findItem(R.id.menu_sync).setTitle( GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync); } else if (mState == ListEditState.SUB_FOLDER) { @@ -781,15 +829,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.menu_new_folder: { + case R.id.menu_new_folder: + // 显示新建文件夹对话框 showCreateOrModifyFolderDialog(true); break; - } - case R.id.menu_export_text: { + case R.id.menu_export_text: + // 导出笔记为文本 exportNoteToText(); break; - } - case R.id.menu_sync: { + case R.id.menu_sync: + // 同步/取消同步 if (isSyncMode()) { if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) { GTaskSyncService.startSync(this); @@ -800,16 +849,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt startPreferenceActivity(); } break; - } - case R.id.menu_setting: { + case R.id.menu_setting: + // 打开设置页面 startPreferenceActivity(); break; - } - case R.id.menu_new_note: { + case R.id.menu_new_note: + // 新建笔记 createNewNote(); break; - } case R.id.menu_search: + // 打开搜索 onSearchRequested(); break; default: @@ -824,6 +873,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return true; } + /** + * 导出笔记为文本文件 + */ private void exportNoteToText() { final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); new AsyncTask() { @@ -835,6 +887,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt @Override protected void onPostExecute(Integer result) { + // 根据导出结果显示对应提示 if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(NotesListActivity.this @@ -866,21 +919,33 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }.execute(); } + /** + * 判断是否开启同步模式 + * @return 是否开启同步 + */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + /** + * 打开设置页面 + */ private void startPreferenceActivity() { Activity from = getParent() != null ? getParent() : this; Intent intent = new Intent(from, NotesPreferenceActivity.class); from.startActivityIfNeeded(intent, -1); } + /** + * 列表项点击监听器 + * 处理笔记/文件夹的点击事件 + */ private class OnListItemClickListener implements OnItemClickListener { public void onItemClick(AdapterView parent, View view, int position, long id) { if (view instanceof NotesListItem) { NoteItemData item = ((NotesListItem) view).getItemData(); + // 多选模式下,切换选中状态 if (mNotesListAdapter.isInChoiceMode()) { if (item.getType() == Notes.TYPE_NOTE) { position = position - mNotesListView.getHeaderViewsCount(); @@ -890,6 +955,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return; } + // 根据当前状态处理点击事件 switch (mState) { case NOTE_LIST: if (item.getType() == Notes.TYPE_FOLDER @@ -917,10 +983,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } + /** + * 查询目标文件夹列表(用于笔记移动) + */ private void startQueryDestinationFolders() { String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; selection = (mState == ListEditState.NOTE_LIST) ? selection: - "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; + "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, null, @@ -935,9 +1004,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt NoteColumns.MODIFIED_DATE + " DESC"); } + @Override public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { if (view instanceof NotesListItem) { mFocusNoteDataItem = ((NotesListItem) view).getItemData(); + // 笔记长按:进入多选模式 if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { if (mNotesListView.startActionMode(mModeCallBack) != null) { mModeCallBack.onItemCheckedStateChanged(null, position, id, true); @@ -945,10 +1016,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } else { Log.e(TAG, "startActionMode fails"); } - } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { + } + // 文件夹长按:创建上下文菜单 + else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); } } return false; } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java index 51c9cb9..e705cea 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java @@ -1,19 +1,3 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package net.micode.notes.ui; import android.content.Context; @@ -30,19 +14,30 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; - +/** + * 笔记列表游标适配器 + * 负责将数据库游标(Cursor)数据与列表项视图绑定,并管理笔记的多选状态 + */ public class NotesListAdapter extends CursorAdapter { - private static final String TAG = "NotesListAdapter"; - private Context mContext; - private HashMap mSelectedIndex; - private int mNotesCount; - private boolean mChoiceMode; - + private static final String TAG = "NotesListAdapter"; // 日志标记 + private Context mContext; // 上下文对象 + private HashMap mSelectedIndex; // 选中项位置与选中状态的映射表 + private int mNotesCount; // 笔记总数(排除文件夹类型) + private boolean mChoiceMode; // 是否开启多选模式标记 + + /** + * 桌面组件属性封装类 + * 存储桌面小组件的ID和类型信息 + */ public static class AppWidgetAttribute { - public int widgetId; - public int widgetType; + public int widgetId; // 桌面组件ID + public int widgetType; // 桌面组件类型 }; + /** + * 构造方法:初始化笔记列表适配器 + * @param context 上下文对象 + */ public NotesListAdapter(Context context) { super(context, null); mSelectedIndex = new HashMap(); @@ -50,11 +45,24 @@ public class NotesListAdapter extends CursorAdapter { mNotesCount = 0; } + /** + * 创建新的列表项视图 + * @param context 上下文 + * @param cursor 数据游标 + * @param parent 父视图容器 + * @return 笔记列表项视图(NotesListItem实例) + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new NotesListItem(context); } + /** + * 绑定游标数据到列表项视图 + * @param view 列表项视图 + * @param context 上下文 + * @param cursor 数据游标 + */ @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof NotesListItem) { @@ -64,24 +72,42 @@ public class NotesListAdapter extends CursorAdapter { } } + /** + * 设置指定位置项的选中状态 + * @param position 列表项位置 + * @param checked 选中状态(true=选中,false=未选中) + */ public void setCheckedItem(final int position, final boolean checked) { mSelectedIndex.put(position, checked); - notifyDataSetChanged(); + notifyDataSetChanged(); // 通知列表数据刷新 } + /** + * 判断是否处于多选模式 + * @return 多选模式状态 + */ public boolean isInChoiceMode() { return mChoiceMode; } + /** + * 设置多选模式 + * @param mode 多选模式状态(true=开启,false=关闭) + */ public void setChoiceMode(boolean mode) { - mSelectedIndex.clear(); + mSelectedIndex.clear(); // 清空原有选中状态 mChoiceMode = mode; } + /** + * 全选/取消全选所有笔记项(排除文件夹) + * @param checked 选中状态(true=全选,false=取消全选) + */ public void selectAll(boolean checked) { Cursor cursor = getCursor(); for (int i = 0; i < getCount(); i++) { if (cursor.moveToPosition(i)) { + // 仅处理笔记类型,跳过文件夹 if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { setCheckedItem(i, checked); } @@ -89,6 +115,10 @@ public class NotesListAdapter extends CursorAdapter { } } + /** + * 获取所有选中项的ID集合 + * @return 选中项ID哈希集合 + */ public HashSet getSelectedItemIds() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -101,10 +131,13 @@ public class NotesListAdapter extends CursorAdapter { } } } - return itemSet; } + /** + * 获取所有选中笔记关联的桌面组件属性集合 + * @return 桌面组件属性哈希集合 + */ public HashSet getSelectedWidget() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -116,9 +149,7 @@ public class NotesListAdapter extends CursorAdapter { widget.widgetId = item.getWidgetId(); widget.widgetType = item.getWidgetType(); itemSet.add(widget); - /** - * Don't close cursor here, only the adapter could close it - */ + // 注意:此处不要关闭游标,仅适配器有权限管理游标生命周期 } else { Log.e(TAG, "Invalid cursor"); return null; @@ -128,6 +159,10 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + /** + * 统计选中项的数量 + * @return 选中项总数 + */ public int getSelectedCount() { Collection values = mSelectedIndex.values(); if (null == values) { @@ -143,11 +178,20 @@ public class NotesListAdapter extends CursorAdapter { return count; } + /** + * 判断是否所有笔记项都已被选中 + * @return 全选状态 + */ public boolean isAllSelected() { int checkedCount = getSelectedCount(); return (checkedCount != 0 && checkedCount == mNotesCount); } + /** + * 判断指定位置的项是否被选中 + * @param position 列表项位置 + * @return 选中状态 + */ public boolean isSelectedItem(final int position) { if (null == mSelectedIndex.get(position)) { return false; @@ -155,23 +199,37 @@ public class NotesListAdapter extends CursorAdapter { return mSelectedIndex.get(position); } + /** + * 数据内容变化时的回调方法 + * 重新计算笔记总数 + */ @Override protected void onContentChanged() { super.onContentChanged(); calcNotesCount(); } + /** + * 更换数据游标时的回调方法 + * 重新计算笔记总数 + * @param cursor 新的数据库游标 + */ @Override public void changeCursor(Cursor cursor) { super.changeCursor(cursor); calcNotesCount(); } + /** + * 计算列表中的笔记总数(排除文件夹类型) + * 用于判断全选状态 + */ private void calcNotesCount() { mNotesCount = 0; for (int i = 0; i < getCount(); i++) { Cursor c = (Cursor) getItem(i); if (c != null) { + // 仅统计笔记类型,跳过文件夹 if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { mNotesCount++; } @@ -181,4 +239,4 @@ public class NotesListAdapter extends CursorAdapter { } } } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesListItem.java b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesListItem.java index 1221e80..179f5d2 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesListItem.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesListItem.java @@ -1,19 +1,3 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package net.micode.notes.ui; import android.content.Context; @@ -29,18 +13,26 @@ import net.micode.notes.data.Notes; import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser.NoteItemBgResources; - +/** + * 笔记列表项视图 + * 封装单个笔记/文件夹的UI展示,负责数据与视图的绑定及样式渲染 + */ public class NotesListItem extends LinearLayout { - private ImageView mAlert; - private TextView mTitle; - private TextView mTime; - private TextView mCallName; - private NoteItemData mItemData; - private CheckBox mCheckBox; + private ImageView mAlert; // 提醒图标/通话记录图标 + private TextView mTitle; // 标题文本(笔记摘要/文件夹名称) + private TextView mTime; // 修改时间文本 + private TextView mCallName; // 通话记录联系人姓名 + private NoteItemData mItemData; // 当前列表项绑定的笔记/文件夹数据 + private CheckBox mCheckBox; // 多选模式复选框 + /** + * 构造方法:初始化列表项视图 + * @param context 上下文对象 + */ public NotesListItem(Context context) { super(context); - inflate(context, R.layout.note_item, this); + inflate(context, R.layout.note_item, this); // 加载列表项布局 + // 绑定UI控件 mAlert = (ImageView) findViewById(R.id.iv_alert_icon); mTitle = (TextView) findViewById(R.id.tv_title); mTime = (TextView) findViewById(R.id.tv_time); @@ -48,7 +40,15 @@ public class NotesListItem extends LinearLayout { mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); } + /** + * 绑定笔记/文件夹数据到UI控件 + * @param context 上下文 + * @param data 笔记/文件夹数据 + * @param choiceMode 是否开启多选模式 + * @param checked 当前项是否被选中 + */ public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { + // 处理多选模式复选框显示状态 if (choiceMode && data.getType() == Notes.TYPE_NOTE) { mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setChecked(checked); @@ -57,6 +57,7 @@ public class NotesListItem extends LinearLayout { } mItemData = data; + // 通话记录文件夹样式渲染 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mCallName.setVisibility(View.GONE); mAlert.setVisibility(View.VISIBLE); @@ -64,28 +65,37 @@ public class NotesListItem extends LinearLayout { mTitle.setText(context.getString(R.string.call_record_folder_name) + context.getString(R.string.format_folder_files_count, data.getNotesCount())); mAlert.setImageResource(R.drawable.call_record); - } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { + } + // 通话记录笔记样式渲染 + else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { mCallName.setVisibility(View.VISIBLE); mCallName.setText(data.getCallName()); mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + // 显示提醒图标 if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); mAlert.setVisibility(View.VISIBLE); } else { mAlert.setVisibility(View.GONE); } - } else { + } + // 普通文件夹/笔记样式渲染 + else { mCallName.setVisibility(View.GONE); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); + // 普通文件夹 if (data.getType() == Notes.TYPE_FOLDER) { mTitle.setText(data.getSnippet() + context.getString(R.string.format_folder_files_count, - data.getNotesCount())); + data.getNotesCount())); mAlert.setVisibility(View.GONE); - } else { + } + // 普通笔记 + else { mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + // 显示提醒图标 if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); mAlert.setVisibility(View.VISIBLE); @@ -94,13 +104,19 @@ public class NotesListItem extends LinearLayout { } } } + // 设置相对修改时间 mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); - + // 设置列表项背景 setBackground(data); } + /** + * 根据笔记类型和位置状态设置对应背景资源 + * @param data 笔记/文件夹数据 + */ private void setBackground(NoteItemData data) { int id = data.getBgColorId(); + // 笔记类型背景区分(单个、首条、末条、普通) if (data.getType() == Notes.TYPE_NOTE) { if (data.isSingle() || data.isOneFollowingFolder()) { setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); @@ -111,12 +127,18 @@ public class NotesListItem extends LinearLayout { } else { setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); } - } else { + } + // 文件夹类型统一背景 + else { setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } } + /** + * 获取当前列表项绑定的笔记/文件夹数据 + * @return 笔记/文件夹数据 + */ public NoteItemData getItemData() { return mItemData; } -} +} \ No newline at end of file diff --git a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java index 07c5f7e..201b91d 100644 --- a/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/src/Notes-master/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java @@ -1,19 +1,3 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package net.micode.notes.ui; import android.accounts.Account; @@ -47,43 +31,42 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.gtask.remote.GTaskSyncService; - +/** + * 笔记设置页面 + * 负责同步账号配置、手动同步操作、偏好设置管理及同步状态展示 + */ public class NotesPreferenceActivity extends PreferenceActivity { - public static final String PREFERENCE_NAME = "notes_preferences"; - - public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; - - public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; + public static final String PREFERENCE_NAME = "notes_preferences"; // 偏好设置文件名 + public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; // 同步账号名称偏好键 + public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; // 最后同步时间偏好键 + public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; // 背景颜色设置偏好键 - public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; + private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; // 同步账号偏好分类键 + private static final String AUTHORITIES_FILTER_KEY = "authorities"; // 账号权限过滤键 - private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; - - private static final String AUTHORITIES_FILTER_KEY = "authorities"; - - private PreferenceCategory mAccountCategory; - - private GTaskReceiver mReceiver; - - private Account[] mOriAccounts; - - private boolean mHasAddedAccount; + private PreferenceCategory mAccountCategory; // 账号设置偏好分类 + private GTaskReceiver mReceiver; // 同步状态广播接收器 + private Account[] mOriAccounts; // 原始谷歌账号数组 + private boolean mHasAddedAccount; // 是否新增账号标记 @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - /* using the app icon for navigation */ + // 设置ActionBar返回按钮 getActionBar().setDisplayHomeAsUpEnabled(true); + // 加载偏好设置资源 addPreferencesFromResource(R.xml.preferences); mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); mReceiver = new GTaskReceiver(); + // 注册同步服务广播过滤器 IntentFilter filter = new IntentFilter(); filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); registerReceiver(mReceiver, filter); mOriAccounts = null; + // 添加设置页面头部视图 View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); getListView().addHeaderView(header, null, true); } @@ -92,8 +75,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { protected void onResume() { super.onResume(); - // need to set sync account automatically if user has added a new - // account + // 检测是否新增账号,自动配置同步账号 if (mHasAddedAccount) { Account[] accounts = getGoogleAccounts(); if (mOriAccounts != null && accounts.length > mOriAccounts.length) { @@ -113,17 +95,22 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + // 刷新页面UI refreshUI(); } @Override protected void onDestroy() { + // 注销广播接收器,释放资源 if (mReceiver != null) { unregisterReceiver(mReceiver); } super.onDestroy(); } + /** + * 加载账号选择偏好项 + */ private void loadAccountPreference() { mAccountCategory.removeAll(); @@ -135,16 +122,15 @@ public class NotesPreferenceActivity extends PreferenceActivity { public boolean onPreferenceClick(Preference preference) { if (!GTaskSyncService.isSyncing()) { if (TextUtils.isEmpty(defaultAccount)) { - // the first time to set account + // 首次设置账号,显示账号选择对话框 showSelectAccountAlertDialog(); } else { - // if the account has already been set, we need to promp - // user about the risk + // 已设置账号,显示更换账号确认对话框 showChangeAccountConfirmAlertDialog(); } } else { Toast.makeText(NotesPreferenceActivity.this, - R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) + R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) .show(); } return true; @@ -154,11 +140,14 @@ public class NotesPreferenceActivity extends PreferenceActivity { mAccountCategory.addPreference(accountPref); } + /** + * 加载同步按钮及最后同步时间展示 + */ private void loadSyncButton() { Button syncButton = (Button) findViewById(R.id.preference_sync_button); TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); - // set button state + // 根据同步状态设置按钮样式和点击事件 if (GTaskSyncService.isSyncing()) { syncButton.setText(getString(R.string.preferences_button_sync_cancel)); syncButton.setOnClickListener(new View.OnClickListener() { @@ -174,9 +163,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { } }); } + // 未设置账号时禁用同步按钮 syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); - // set last sync time + // 设置最后同步时间展示 if (GTaskSyncService.isSyncing()) { lastSyncTimeView.setText(GTaskSyncService.getProgressString()); lastSyncTimeView.setVisibility(View.VISIBLE); @@ -193,14 +183,21 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 刷新页面UI(账号偏好+同步按钮) + */ private void refreshUI() { loadAccountPreference(); loadSyncButton(); } + /** + * 显示账号选择对话框(支持选择已有账号/新增账号) + */ private void showSelectAccountAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + // 设置自定义对话框标题 View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); @@ -216,6 +213,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { mOriAccounts = accounts; mHasAddedAccount = false; + // 展示已有谷歌账号列表 if (accounts.length > 0) { CharSequence[] items = new CharSequence[accounts.length]; final CharSequence[] itemMapping = items; @@ -237,6 +235,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + // 添加新增账号视图 View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); dialogBuilder.setView(addAccountView); @@ -246,7 +245,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { mHasAddedAccount = true; Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { - "gmail-ls" + "gmail-ls" }); startActivityForResult(intent, -1); dialog.dismiss(); @@ -254,9 +253,13 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + /** + * 显示更换账号确认对话框(更换/移除/取消) + */ private void showChangeAccountConfirmAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + // 设置自定义对话框标题 View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, @@ -265,6 +268,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); dialogBuilder.setCustomTitle(titleView); + // 对话框选项 CharSequence[] menuItemArray = new CharSequence[] { getString(R.string.preferences_menu_change_account), getString(R.string.preferences_menu_remove_account), @@ -283,11 +287,19 @@ public class NotesPreferenceActivity extends PreferenceActivity { dialogBuilder.show(); } + /** + * 获取设备上所有谷歌账号 + * @return 谷歌账号数组 + */ private Account[] getGoogleAccounts() { AccountManager accountManager = AccountManager.get(this); return accountManager.getAccountsByType("com.google"); } + /** + * 设置同步账号,并清理原有同步信息 + * @param account 账号名称 + */ private void setSyncAccount(String account) { if (!getSyncAccountName(this).equals(account)) { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -299,10 +311,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { } editor.commit(); - // clean up last sync time + // 清空最后同步时间 setLastSyncTime(this, 0); - // clean up local gtask related info + // 异步清理本地同步相关数据 new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); @@ -318,6 +330,9 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + /** + * 移除同步账号,并清理相关偏好设置和本地数据 + */ private void removeSyncAccount() { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); @@ -329,7 +344,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { } editor.commit(); - // clean up local gtask related info + // 异步清理本地同步相关数据 new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); @@ -340,12 +355,22 @@ public class NotesPreferenceActivity extends PreferenceActivity { }).start(); } + /** + * 获取已保存的同步账号名称 + * @param context 上下文 + * @return 账号名称 + */ public static String getSyncAccountName(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); } + /** + * 设置最后同步时间 + * @param context 上下文 + * @param time 时间戳 + */ public static void setLastSyncTime(Context context, long time) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -354,12 +379,21 @@ public class NotesPreferenceActivity extends PreferenceActivity { editor.commit(); } + /** + * 获取最后同步时间 + * @param context 上下文 + * @return 时间戳 + */ public static long getLastSyncTime(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); } + /** + * 同步状态广播接收器 + * 接收同步服务广播,刷新UI并更新同步进度 + */ private class GTaskReceiver extends BroadcastReceiver { @Override @@ -370,13 +404,16 @@ public class NotesPreferenceActivity extends PreferenceActivity { syncStatus.setText(intent .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); } - } } + /** + * 处理ActionBar菜单点击事件 + */ public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: + // 返回笔记列表首页 Intent intent = new Intent(this, NotesListActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); @@ -385,4 +422,4 @@ public class NotesPreferenceActivity extends PreferenceActivity { return false; } } -} +} \ No newline at end of file