diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/data/Contact.java b/MiNotes-master/app/src/main/java/net/micode/notes/data/Contact.java index 899b1c6..d97ac5d 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/data/Contact.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/data/Contact.java @@ -24,18 +24,11 @@ import android.telephony.PhoneNumberUtils; import android.util.Log; import java.util.HashMap; -//该工具类用于通过电话号码查询联系人姓名,并且会对查询结果进行缓存,从而避免重复查询,提高后续查询的效率。 public class Contact { - // 用于缓存已查询过的电话号码及其对应的联系人姓名 private static HashMap sContactCache; private static final String TAG = "Contact"; - - // 用于查询联系人的 SQL 条件语句,借助 PHONE_NUMBERS_EQUAL 函数 - // 来匹配电话号码,同时会对原始联系人 ID 进行验证 - - private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + " AND " + Data.RAW_CONTACT_ID + " IN " @@ -43,55 +36,36 @@ public class Contact { + " FROM phone_lookup" + " WHERE min_match = '+')"; -//依据给定的电话号码,在联系人数据库中查找对应的联系人姓名。 要是找到了联系人,就会将结果缓存起来,方便后续使用。上下文对象,用于获取 ContentResolver。要查询的电话号码。若找到联系人,返回其显示名称;若未找到或者出现错误,返回 null。 - public static String getContact(Context context, String phoneNumber) { - // 初始化缓存 if(sContactCache == null) { sContactCache = new HashMap(); } - // 先检查缓存,若已有结果则直接返回 - if(sContactCache.containsKey(phoneNumber)) { return sContactCache.get(phoneNumber); } - - // 针对当前电话号码,定制化查询条件 String selection = CALLER_ID_SELECTION.replace("+", PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); - - // 执行联系人数据库查询 Cursor cursor = context.getContentResolver().query( - Data.CONTENT_URI, // 查询的内容 URI - new String [] { Phone.DISPLAY_NAME }, // 要获取的字段 - selection, // 定制后的查询条件 - new String[] { phoneNumber }, // 查询参数 - null); // 不使用排序 - + Data.CONTENT_URI, + new String [] { Phone.DISPLAY_NAME }, + selection, + new String[] { phoneNumber }, + null); - // 处理查询结果 if (cursor != null && cursor.moveToFirst()) { try { - - // 获取联系人姓名并缓存 String name = cursor.getString(0); sContactCache.put(phoneNumber, name); return name; } catch (IndexOutOfBoundsException e) { - -// 处理可能出现的索引越界异常 Log.e(TAG, " Cursor get string error " + e.toString()); return null; } finally { - - // 确保游标资源被关闭 cursor.close(); } } else { - - // 未找到匹配的联系人 Log.d(TAG, "No contact matched with number:" + phoneNumber); return null; } diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/data/Notes.java b/MiNotes-master/app/src/main/java/net/micode/notes/data/Notes.java index 25450be..f240604 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/data/Notes.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/data/Notes.java @@ -17,19 +17,12 @@ package net.micode.notes.data; import android.net.Uri; -/** - * 笔记和文件夹管理的核心数据类 - * 包含数据类型定义、URI路径、数据库字段常量等 - */ - public class Notes { - public static final String AUTHORITY = "micode_notes"; // 内容提供器的 authority(用于ContentProvider标识) - public static final String TAG = "Notes"; // 日志标签 - -// 数据类型常量 - public static final int TYPE_NOTE = 0;// 数据类型常量 - public static final int TYPE_FOLDER = 1; // 文件夹类型 - public static final int TYPE_SYSTEM = 2;// 系统类型(如系统文件夹) + public static final String AUTHORITY = "micode_notes"; + public static final String TAG = "Notes"; + public static final int TYPE_NOTE = 0; + public static final int TYPE_FOLDER = 1; + public static final int TYPE_SYSTEM = 2; /** * Following IDs are system folders' identifiers @@ -42,206 +35,245 @@ public class Notes { public static final int ID_CALL_RECORD_FOLDER = -2; public static final int ID_TRASH_FOLER = -3; + public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; + public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; + public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; + public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; + public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; + public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; - // Intent传递的额外参数键名 - public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date";// 提醒时间 - public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id";// 背景颜色ID - public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; // 桌面小部件ID - public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type";// 小部件类型 - public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; // 文件夹ID - public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; // 通话日期 - - - // 桌面小部件类型常量 - public static final int TYPE_WIDGET_INVALIDE = -1;// 无效类型 - public static final int TYPE_WIDGET_2X = 0;// 2x尺寸小部件 - public static final int TYPE_WIDGET_4X = 1;// 4x尺寸小部件 + public static final int TYPE_WIDGET_INVALIDE = -1; + public static final int TYPE_WIDGET_2X = 0; + public static final int TYPE_WIDGET_4X = 1; public static class DataConstants { - public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; // 文本笔记MIME类型 - public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;// 通话笔记MIME类型 + public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; + public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; } /** - * 数据类型常量内部类 - * 定义不同数据项的MIME类型 + * Uri to query all notes and folders */ - public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); // 通用数据查询URI(包含所有笔记和文件夹) + public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); - - public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); // 具体数据项查询URI(如附件、内容详情) + /** + * Uri to query data + */ + public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); public interface NoteColumns { - + /** + * The unique ID for a row + *

Type: INTEGER (long)

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

Type: INTEGER (long)

+ */ + public static final String PARENT_ID = "parent_id"; - public static final String PARENT_ID = "parent_id";//父级ID(笔记所属文件夹ID,文件夹的父文件夹ID) - - public static final String CREATED_DATE = "created_date";//创建时间(时间戳) - - - public static final String MODIFIED_DATE = "modified_date";//最后修改时间(时间戳) - + /** + * Created data for note or folder + *

Type: INTEGER (long)

+ */ + public static final String CREATED_DATE = "created_date"; - public static final String ALERTED_DATE = "alert_date";//提醒时间(时间戳) + /** + * Latest modified date + *

Type: INTEGER (long)

+ */ + public static final String MODIFIED_DATE = "modified_date"; - public static final String SNIPPET = "snippet";//文件夹名称或笔记内容摘要 + /** + * Alert date + *

Type: INTEGER (long)

+ */ + public static final String ALERTED_DATE = "alert_date"; - public static final String WIDGET_ID = "widget_id";//关联的桌面小部件ID + /** + * Folder's name or text content of note + *

Type: TEXT

+ */ + public static final String SNIPPET = "snippet"; - public static final String WIDGET_TYPE = "widget_type";//桌面小部件类型 + /** + * Note's widget id + *

Type: INTEGER (long)

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

Type: INTEGER (long)

+ */ + public static final String WIDGET_TYPE = "widget_type"; - public static final String BG_COLOR_ID = "bg_color_id";//背景颜色ID + /** + * Note's background color's id + *

Type: 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

*/ public static final String HAS_ATTACHMENT = "has_attachment"; /** - 文件夹内的笔记数量 + * Folder's count of notes + *

Type: INTEGER (long)

*/ public static final String NOTES_COUNT = "notes_count"; /** - 数据类型 + * The file type: folder or note + *

Type: INTEGER

*/ public static final String TYPE = "type"; /** - 最后同步ID + * The last sync id + *

Type: INTEGER (long)

*/ public static final String SYNC_ID = "sync_id"; /** - 本地是否修改过 + * Sign to indicate local modified or not + *

Type: INTEGER

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

Type : INTEGER

*/ public static final String ORIGIN_PARENT_ID = "origin_parent_id"; /** - 关联的Google任务ID + * The gtask id + *

Type : TEXT

*/ public static final String GTASK_ID = "gtask_id"; /** - 数据版本号 + * The version code + *

Type : INTEGER (long)

*/ public static final String VERSION = "version"; } - /** - * 数据项字段接口 - * 定义具体数据项(如附件、内容)的公共字段 - */ + public interface DataColumns { - - public static final String ID = "_id"; // 行唯一标识 + /** + * The unique ID for a row + *

Type: INTEGER (long)

+ */ + public static final String ID = "_id"; /** - MIME类型(标识数据项类型,如文本、图片、音频等) + * The MIME type of the item represented by this row. + *

Type: Text

*/ public static final String MIME_TYPE = "mime_type"; /** - 所属笔记ID(关联到Note表的ID) + * The reference id to note that this data belongs to + *

Type: INTEGER (long)

*/ public static final String NOTE_ID = "note_id"; /** - 创建时间(时间戳) + * Created data for note or folder + *

Type: INTEGER (long)

*/ public static final String CREATED_DATE = "created_date"; /** - 最后修改时间(时间戳) + * Latest modified date + *

Type: INTEGER (long)

*/ public static final String MODIFIED_DATE = "modified_date"; /** - 数据内容(具体内容根据MIME类型而定) + * Data's content + *

Type: TEXT

*/ public static final String CONTENT = "content"; /** - 通用数据字段1(类型由MIME类型决定) + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * integer data type + *

Type: INTEGER

*/ public static final String DATA1 = "data1"; /** - 通用数据字段2(类型由MIME类型决定) + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * integer data type + *

Type: INTEGER

*/ public static final String DATA2 = "data2"; /** - 通用数据字段3(类型由MIME类型决定) + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * TEXT data type + *

Type: TEXT

*/ public static final String DATA3 = "data3"; /** - 通用数据字段4(类型由MIME类型决定) + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * TEXT data type + *

Type: TEXT

*/ public static final String DATA4 = "data4"; /** - 通用数据字段5(类型由MIME类型决定) + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * TEXT data type + *

Type: TEXT

*/ public static final String DATA5 = "data5"; } public static final class TextNote implements DataColumns { /** - * This column stores the note's mode, currently only {@link TextNote#MODE_CHECK_LIST} - * is supported - *

Type: INTEGER

+ * Mode to indicate the text in check list mode or not + *

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

*/ public static final String MODE = DATA1; - /** - * This column stores the bold state of the text - *

Type: INTEGER (boolean)

- */ - public static final String BOLD = DATA4; - public static final int MODE_CHECK_LIST = 1; - // MIME类型定义 - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; - // 文本笔记内容URI - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); } public static final class CallNote implements DataColumns { /** - 通话时间 + * Call date for this record + *

Type: INTEGER (long)

*/ public static final String CALL_DATE = DATA1; /** - 电话号码 + * Phone number for this record + *

Type: TEXT

*/ public static final String PHONE_NUMBER = DATA3; - // MIME类型定义 - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; - // 通话笔记内容URI - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); } } diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/MiNotes-master/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index c9357a4..ffe5d57 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java @@ -28,75 +28,63 @@ import net.micode.notes.data.Notes.NoteColumns; public class NotesDatabaseHelper extends SQLiteOpenHelper { - // 数据库名称 private static final String DB_NAME = "note.db"; - - // 数据库版本号 + private static final int DB_VERSION = 4; - // 表名接口 public interface TABLE { - // 笔记表名 public static final String NOTE = "note"; - - // 数据项表名(如附件、内容详情) + public static final String DATA = "data"; } - // 日志标签 private static final String TAG = "NotesDatabaseHelper"; - - // 单例实例 + private static NotesDatabaseHelper mInstance; - // 创建笔记表的SQL语句 private static final String CREATE_NOTE_TABLE_SQL = "CREATE TABLE " + TABLE.NOTE + "(" + - NoteColumns.ID + " INTEGER PRIMARY KEY," + // 主键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 ''," + // Google任务ID(默认空) - NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 版本号(默认0) + NoteColumns.ID + " INTEGER PRIMARY KEY," + + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + + NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + ")"; - // 创建数据项表的SQL语句 private static final String CREATE_DATA_TABLE_SQL = "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(文本) + 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 ''" + ")"; - // 创建数据项表的笔记ID索引SQL private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = "CREATE INDEX IF NOT EXISTS note_id_index ON " + - TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; // 基于note_id创建索引 + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; /** - * 以下为数据库触发器(用于自动维护数据一致性) + * Increase folder's note count when move note to the folder */ - - // 更新笔记父文件夹时增加目标文件夹笔记数的触发器 private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER increase_folder_count_on_update "+ " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + @@ -106,7 +94,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " END"; - // 更新笔记父文件夹时减少原文件夹笔记数的触发器 + /** + * Decrease folder's note count when move note from folder + */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_update " + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + @@ -117,7 +107,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + " END"; - // 插入新笔记时增加目标文件夹笔记数的触发器 + /** + * Increase folder's note count when insert new note to the folder + */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = "CREATE TRIGGER increase_folder_count_on_insert " + " AFTER INSERT ON " + TABLE.NOTE + @@ -127,7 +119,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " END"; - // 删除笔记时减少原文件夹笔记数的触发器 + /** + * Decrease folder's note count when delete note from the folder + */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_delete " + " AFTER DELETE ON " + TABLE.NOTE + @@ -138,7 +132,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " AND " + NoteColumns.NOTES_COUNT + ">0;" + " END"; - // 插入文本笔记数据时更新笔记内容的触发器 + /** + * Update note's content when insert data with type {@link DataConstants#NOTE} + */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = "CREATE TRIGGER update_note_content_on_insert " + " AFTER INSERT ON " + TABLE.DATA + @@ -149,7 +145,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " END"; - // 更新文本笔记数据时更新笔记内容的触发器 + /** + * Update note's content when data with {@link DataConstants#NOTE} type has changed + */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = "CREATE TRIGGER update_note_content_on_update " + " AFTER UPDATE ON " + TABLE.DATA + @@ -160,7 +158,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " END"; - // 删除文本笔记数据时清空笔记内容的触发器 + /** + * Update note's content when data with {@link DataConstants#NOTE} type has deleted + */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = "CREATE TRIGGER update_note_content_on_delete " + " AFTER delete ON " + TABLE.DATA + @@ -171,7 +171,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + " END"; - // 删除笔记时级联删除相关数据项的触发器 + /** + * Delete datas belong to note which has been deleted + */ private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = "CREATE TRIGGER delete_data_on_delete " + " AFTER DELETE ON " + TABLE.NOTE + @@ -180,7 +182,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + " END"; - // 删除文件夹时级联删除子笔记的触发器 + /** + * Delete notes belong to folder which has been deleted + */ private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = "CREATE TRIGGER folder_delete_notes_on_delete " + " AFTER DELETE ON " + TABLE.NOTE + @@ -189,7 +193,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " END"; - // 文件夹移动到回收站时级联移动子笔记的触发器 + /** + * Move notes belong to folder which has been moved to trash folder + */ private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = "CREATE TRIGGER folder_move_notes_on_trash " + " AFTER UPDATE ON " + TABLE.NOTE + @@ -200,22 +206,18 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " END"; - // 构造方法 public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } - // 创建笔记表(含触发器和系统文件夹) public void createNoteTable(SQLiteDatabase db) { - db.execSQL(CREATE_NOTE_TABLE_SQL); // 执行建表语句 - reCreateNoteTableTriggers(db); // 重建触发器 - createSystemFolder(db); // 创建系统文件夹 - Log.d(TAG, "note table has been created"); // 日志记录 + db.execSQL(CREATE_NOTE_TABLE_SQL); + reCreateNoteTableTriggers(db); + createSystemFolder(db); + Log.d(TAG, "note table has been created"); } - // 重建笔记表相关触发器(先删除旧触发器再创建新触发器) private void reCreateNoteTableTriggers(SQLiteDatabase db) { - // 删除旧触发器 db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); @@ -223,8 +225,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); - - // 创建新触发器 + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); @@ -234,56 +235,58 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); } - // 创建系统文件夹(根文件夹、临时文件夹等) private void createSystemFolder(SQLiteDatabase db) { ContentValues values = new ContentValues(); - // 创建通话记录文件夹 + /** + * call record foler for call notes + */ values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - // 创建根文件夹 + /** + * root folder which is default folder + */ values.clear(); values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - // 创建临时文件夹 + /** + * temporary folder which is used for moving note + */ values.clear(); values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); - // 创建回收站文件夹 + /** + * create trash folder + */ values.clear(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } - // 创建数据项表(含触发器和索引) 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); + reCreateDataTableTriggers(db); + db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); + Log.d(TAG, "data table has been created"); } - // 重建数据项表相关触发器 private void reCreateDataTableTriggers(SQLiteDatabase db) { - // 删除旧触发器 db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete"); - - // 创建新触发器 + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); } - // 获取单例实例 static synchronized NotesDatabaseHelper getInstance(Context context) { if (mInstance == null) { mInstance = new NotesDatabaseHelper(context); @@ -291,84 +294,69 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { return mInstance; } - // 数据库创建回调(首次创建时调用) @Override public void onCreate(SQLiteDatabase db) { - createNoteTable(db); // 创建笔记表 - createDataTable(db); // 创建数据项表 + createNoteTable(db); + createDataTable(db); } - // 数据库升级回调(版本号变更时调用) @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - boolean reCreateTriggers = false; // 是否需要重建触发器 - boolean skipV2 = false; // 是否跳过V2升级步骤 + boolean reCreateTriggers = false; + boolean skipV2 = false; - // 从版本1升级到版本2(全量重建表) if (oldVersion == 1) { upgradeToV2(db); - skipV2 = true; // 标记已完成V2升级(避免重复处理) + skipV2 = true; // this upgrade including the upgrade from v2 to v3 oldVersion++; } - // 从版本2升级到版本3(添加GTASK_ID和回收站文件夹) if (oldVersion == 2 && !skipV2) { upgradeToV3(db); - reCreateTriggers = true; // 需要重建触发器 + reCreateTriggers = true; oldVersion++; } - // 从版本3升级到版本4(添加VERSION字段) if (oldVersion == 3) { upgradeToV4(db); oldVersion++; } - // 按需重建触发器 if (reCreateTriggers) { reCreateNoteTableTriggers(db); reCreateDataTableTriggers(db); } - // 升级失败处理 if (oldVersion != newVersion) { throw new IllegalStateException("Upgrade notes database to version " + newVersion + "fails"); } } - // 升级到版本2(全量重建表) private void upgradeToV2(SQLiteDatabase db) { - // 删除旧表 db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); - // 重新创建表(包含最新结构和触发器) createNoteTable(db); createDataTable(db); } - // 升级到版本3(添加GTASK_ID字段和回收站文件夹) private void upgradeToV3(SQLiteDatabase db) { - // 删除不再使用的触发器(旧版本遗留) + // drop unused triggers db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); - - // 向笔记表添加GTASK_ID字段(用于关联Google任务) + // add a column for gtask id db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''"); - - // 创建回收站系统文件夹(ID为-3) + // 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); // 插入系统文件夹数据 + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); } - // 升级到版本4(添加VERSION字段用于数据版本管理) private void upgradeToV4(SQLiteDatabase db) { - // 向笔记表添加VERSION字段(记录数据版本号,用于升级校验) db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); } -} \ No newline at end of file +} diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/data/NotesProvider.java b/MiNotes-master/app/src/main/java/net/micode/notes/data/NotesProvider.java index 1e54177..edb0a60 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/data/NotesProvider.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/data/NotesProvider.java @@ -14,8 +14,6 @@ * limitations under the License. */ - - package net.micode.notes.data; @@ -37,114 +35,89 @@ import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.NotesDatabaseHelper.TABLE; -/** - * 笔记内容提供器(实现Android ContentProvider接口) - * 负责处理数据的增删改查操作,并支持搜索建议功能 - */ public class NotesProvider extends ContentProvider { - // URI匹配器(用于解析不同的URI请求) private static final UriMatcher mMatcher; - - // 数据库帮助类实例(用于操作数据库) + private NotesDatabaseHelper mHelper; - - // 日志标签 + private static final String TAG = "NotesProvider"; - // URI匹配器常量(对应不同的操作类型) - private static final int URI_NOTE = 1; // 笔记列表URI - private static final int URI_NOTE_ITEM = 2; // 单个笔记URI - private static final int URI_DATA = 3; // 数据项列表URI(如附件) - private static final int URI_DATA_ITEM = 4; // 单个数据项URI - private static final int URI_SEARCH = 5; // 搜索请求URI - 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); - // 匹配笔记列表URI:content://micode_notes/note mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); - // 匹配单个笔记URI:content://micode_notes/note/123 mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); - // 匹配数据项列表URI:content://micode_notes/data mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); - // 匹配单个数据项URI:content://micode_notes/data/456 mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); - // 匹配搜索URI:content://micode_notes/search mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); - // 匹配搜索建议URI(带查询词):content://micode_notes/suggest_query/xxx mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); } /** - * 搜索投影定义(用于构建搜索结果的字段) - * - 替换换行符x'0A'为空格,并修剪空白字符 - * - 关联SearchManager所需的字段(如搜索建议文本、图标、意图等) + * 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 + "," // 传递笔记ID到意图 - + "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 + "," // 搜索结果图标资源ID - + "'" + 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; - // 搜索查询语句(查询包含关键词的笔记内容,排除回收站和非笔记类型) private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION + " FROM " + TABLE.NOTE - + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" // 模糊匹配搜索词 - + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER // 排除回收站中的笔记 - + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; // 仅查询普通笔记类型 + + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; - // ContentProvider初始化方法(创建数据库帮助类实例) @Override public boolean onCreate() { - mHelper = NotesDatabaseHelper.getInstance(getContext()); // 获取数据库帮助类单例 + mHelper = NotesDatabaseHelper.getInstance(getContext()); return true; } - // 查询数据方法(处理不同URI的查询请求) @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { Cursor c = null; - SQLiteDatabase db = mHelper.getReadableDatabase(); // 获取可读数据库 - String id = null; // 用于存储URI中的ID部分 - + SQLiteDatabase db = mHelper.getReadableDatabase(); + String id = null; switch (mMatcher.match(uri)) { case URI_NOTE: - // 查询笔记列表 - c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder); + c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, + sortOrder); break; case URI_NOTE_ITEM: - // 查询单个笔记(从URI路径中提取ID) id = uri.getPathSegments().get(1); - c = db.query(TABLE.NOTE, projection, - NoteColumns.ID + "=" + id + parseSelection(selection), // 添加ID条件和附加查询条件 - selectionArgs, null, null, sortOrder); + c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder); break; case URI_DATA: - // 查询数据项列表(如附件) - c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder); + c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, + sortOrder); break; case URI_DATA_ITEM: - // 查询单个数据项(从URI路径中提取ID) id = uri.getPathSegments().get(1); - c = db.query(TABLE.DATA, projection, - DataColumns.ID + "=" + id + parseSelection(selection), // 添加ID条件和附加查询条件 - selectionArgs, null, null, sortOrder); + c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder); break; case URI_SEARCH: case URI_SEARCH_SUGGEST: - // 处理搜索和搜索建议请求 if (sortOrder != null || projection != null) { throw new IllegalArgumentException( - "搜索查询不支持排序或投影参数"); + "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); } String searchString = null; - // 从URI中提取搜索词(根据不同的URI类型) if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { if (uri.getPathSegments().size() > 1) { searchString = uri.getPathSegments().get(1); @@ -154,186 +127,179 @@ public class NotesProvider extends ContentProvider { } if (TextUtils.isEmpty(searchString)) { - return null; // 空搜索词返回空结果 + return null; } try { - // 构造模糊查询参数(前后添加通配符) searchString = String.format("%%%s%%", searchString); - // 执行原生SQL查询(使用预定义的搜索语句) - c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, new String[] { searchString }); + c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, + new String[] { searchString }); } catch (IllegalStateException ex) { - Log.e(TAG, "搜索查询异常: " + ex.toString()); + Log.e(TAG, "got exception: " + ex.toString()); } break; default: - throw new IllegalArgumentException("未知URI: " + uri); + throw new IllegalArgumentException("Unknown URI " + uri); } - if (c != null) { - // 设置内容变更通知(当数据变化时通知观察者) c.setNotificationUri(getContext().getContentResolver(), uri); } return c; } - // 插入数据方法(处理不同URI的插入请求) @Override public Uri insert(Uri uri, ContentValues values) { - SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库 - long dataId = 0, noteId = 0, insertedId = 0; // 插入记录的ID - + SQLiteDatabase db = mHelper.getWritableDatabase(); + long dataId = 0, noteId = 0, insertedId = 0; switch (mMatcher.match(uri)) { case URI_NOTE: - // 插入新笔记 insertedId = noteId = db.insert(TABLE.NOTE, null, values); break; case URI_DATA: - // 插入新数据项(需关联笔记ID) if (values.containsKey(DataColumns.NOTE_ID)) { - noteId = values.getAsLong(DataColumns.NOTE_ID); // 从参数中获取所属笔记ID + noteId = values.getAsLong(DataColumns.NOTE_ID); } else { - Log.d(TAG, "错误:数据项未包含笔记ID: " + values.toString()); + Log.d(TAG, "Wrong data format without note id:" + values.toString()); } insertedId = dataId = db.insert(TABLE.DATA, null, values); break; default: - throw new IllegalArgumentException("未知URI: " + uri); + throw new IllegalArgumentException("Unknown URI " + uri); } - - // 通知内容变更(根据插入的是笔记还是数据项) + // Notify the note uri if (noteId > 0) { getContext().getContentResolver().notifyChange( - ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); // 通知笔记URI变更 + 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); // 通知数据项URI变更 + ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); } - return ContentUris.withAppendedId(uri, insertedId); // 返回插入记录的URI + return ContentUris.withAppendedId(uri, insertedId); } - // 删除数据方法(处理不同URI的删除请求) @Override public int delete(Uri uri, String selection, String[] selectionArgs) { int count = 0; String id = null; - SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库 - boolean deleteData = false; // 是否删除的是数据项(用于通知逻辑) - + SQLiteDatabase db = mHelper.getWritableDatabase(); + boolean deleteData = false; switch (mMatcher.match(uri)) { case URI_NOTE: - // 删除笔记列表(添加条件:ID>0,排除系统文件夹) selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; count = db.delete(TABLE.NOTE, selection, selectionArgs); break; case URI_NOTE_ITEM: - // 删除单个笔记(从URI中提取ID,排除系统文件夹) 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) { // 系统文件夹ID为负数或0,不允许删除 + if (noteId <= 0) { break; } - count = db.delete(TABLE.NOTE, - NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); + count = db.delete(TABLE.NOTE, + NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); break; case URI_DATA: + count = db.delete(TABLE.DATA, selection, selectionArgs); + deleteData = true; + break; case URI_DATA_ITEM: - // 删除数据项(标记为删除数据项类型) - count = db.delete(TABLE.DATA, - (uri.getPathSegments().size() > 1 ? DataColumns.ID + "=" + uri.getPathSegments().get(1) : selection) - + parseSelection(selection), selectionArgs); + id = uri.getPathSegments().get(1); + count = db.delete(TABLE.DATA, + DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); deleteData = true; break; default: - throw new IllegalArgumentException("未知URI: " + uri); + throw new IllegalArgumentException("Unknown URI " + uri); } - if (count > 0) { - // 通知内容变更(数据项删除时需通知笔记列表,因为可能影响笔记内容) if (deleteData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); } - getContext().getContentResolver().notifyChange(uri, null); // 通知当前URI变更 + getContext().getContentResolver().notifyChange(uri, null); } return count; } - // 更新数据方法(处理不同URI的更新请求) @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int count = 0; String id = null; - SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库 - boolean updateData = false; // 是否更新的是数据项(用于通知逻辑) - + SQLiteDatabase db = mHelper.getWritableDatabase(); + boolean updateData = false; switch (mMatcher.match(uri)) { case URI_NOTE: + increaseNoteVersion(-1, selection, selectionArgs); + count = db.update(TABLE.NOTE, values, selection, selectionArgs); + break; case URI_NOTE_ITEM: - // 更新笔记时增加版本号(用于数据同步或乐观锁) - increaseNoteVersion(uri.getPathSegments().size() > 1 ? - Long.valueOf(uri.getPathSegments().get(1)) : -1, selection, selectionArgs); - // 执行更新操作 - count = db.update(TABLE.NOTE, values, - (uri.getPathSegments().size() > 1 ? NoteColumns.ID + "=" + uri.getPathSegments().get(1) : selection) - + parseSelection(selection), selectionArgs); + id = uri.getPathSegments().get(1); + increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); + count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs); break; case URI_DATA: + count = db.update(TABLE.DATA, values, selection, selectionArgs); + updateData = true; + break; case URI_DATA_ITEM: - // 更新数据项(标记为更新数据项类型) - count = db.update(TABLE.DATA, values, - (uri.getPathSegments().size() > 1 ? DataColumns.ID + "=" + uri.getPathSegments().get(1) : selection) - + parseSelection(selection), selectionArgs); + id = uri.getPathSegments().get(1); + count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs); updateData = true; break; default: - throw new IllegalArgumentException("未知URI: " + uri); + throw new IllegalArgumentException("Unknown URI " + uri); } - + if (count > 0) { - // 通知内容变更(数据项更新时需通知笔记列表,因为可能影响笔记内容) if (updateData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); } - getContext().getContentResolver().notifyChange(uri, null); // 通知当前URI变更 + getContext().getContentResolver().notifyChange(uri, null); } return count; } - // 解析附加查询条件(在主条件后添加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 " + TABLE.NOTE + " SET " + NoteColumns.VERSION + "=" + NoteColumns.VERSION + "+1 "); - - // 构建WHERE条件(支持按ID或自定义条件更新) + sql.append("UPDATE "); + sql.append(TABLE.NOTE); + sql.append(" SET "); + sql.append(NoteColumns.VERSION); + sql.append("=" + NoteColumns.VERSION + "+1 "); + if (id > 0 || !TextUtils.isEmpty(selection)) { sql.append(" WHERE "); - if (id > 0) { - sql.append(NoteColumns.ID + "=" + String.valueOf(id)); - } - if (!TextUtils.isEmpty(selection)) { - // 替换占位符?为实际参数值(简化处理,仅适用于简单场景) - String selectString = id > 0 ? parseSelection(selection) : selection; - for (String args : selectionArgs) { - selectString = selectString.replaceFirst("\\?", args); - } - sql.append(selectString); + } + if (id > 0) { + sql.append(NoteColumns.ID + "=" + String.valueOf(id)); + } + if (!TextUtils.isEmpty(selection)) { + String selectString = id > 0 ? parseSelection(selection) : selection; + for (String args : selectionArgs) { + selectString = selectString.replaceFirst("\\?", args); } + sql.append(selectString); } - mHelper.getWritableDatabase().execSQL(sql.toString()); // 执行SQL语句 + mHelper.getWritableDatabase().execSQL(sql.toString()); } - // 获取数据类型(未实现,可根据需求返回MIME类型) @Override public String getType(Uri uri) { - // TODO: 根据URI返回对应的MIME类型(如vnd.android.cursor.dir/note) + // TODO Auto-generated method stub return null; } -} \ No newline at end of file + +} diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/MetaData.java b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/MetaData.java index e40160b..3a2050b 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/MetaData.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/MetaData.java @@ -25,98 +25,58 @@ import org.json.JSONException; import org.json.JSONObject; -/** - * 元数据任务类(继承自Task类,用于处理Google任务相关的元数据) - * 负责存储和解析与Google任务(GTask)相关的元信息,如关联的GID等 - */ public class MetaData extends Task { - private final static String TAG = MetaData.class.getSimpleName(); // 日志标签(类名) + private final static String TAG = MetaData.class.getSimpleName(); - // 关联的Google任务GID(全局唯一标识符) private String mRelatedGid = null; - /** - * 设置元数据信息 - * @param gid 关联的Google任务GID - * @param metaInfo 元数据JSON对象(用于存储额外信息) - */ public void setMeta(String gid, JSONObject metaInfo) { try { - // 在元数据中添加GTASK_ID字段,值为传入的gid metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); } catch (JSONException e) { - Log.e(TAG, "向元数据中添加关联GID失败"); + Log.e(TAG, "failed to put related gid"); } - // 将元数据JSON转为字符串存储到笔记内容中 setNotes(metaInfo.toString()); - // 设置任务名称为固定值(元数据任务的标识) setName(GTaskStringUtils.META_NOTE_NAME); } - /** - * 获取关联的Google任务GID - * @return 关联的GID,若不存在则返回null - */ public String getRelatedGid() { return mRelatedGid; } - /** - * 判断元数据是否值得保存(当前逻辑:只要笔记内容不为null则保存) - * @return true-值得保存,false-无需保存 - */ @Override public boolean isWorthSaving() { - return getNotes() != null; // 只要存在元数据内容即保存 + return getNotes() != null; } - /** - * 从远程JSON数据中解析并设置元数据内容 - * @param js 远程获取的JSON数据(包含元数据信息) - */ @Override public void setContentByRemoteJSON(JSONObject js) { - super.setContentByRemoteJSON(js); // 调用父类方法处理公共字段 + super.setContentByRemoteJSON(js); if (getNotes() != null) { try { - // 解析存储的元数据JSON字符串 JSONObject metaInfo = new JSONObject(getNotes().trim()); - // 提取关联的GID mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); } catch (JSONException e) { - Log.w(TAG, "从元数据中获取关联GID失败"); - mRelatedGid = null; // 解析失败时清空GID + Log.w(TAG, "failed to get related gid"); + mRelatedGid = null; } } } - /** - * 本地JSON内容设置方法(禁止调用,元数据不支持本地生成) - * @throws IllegalAccessError 始终抛出异常,提示方法不可用 - */ @Override public void setContentByLocalJSON(JSONObject js) { - throw new IllegalAccessError("MetaData:setContentByLocalJSON 方法不应该被调用"); + // this function should not be called + throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); } - /** - * 从内容中获取本地JSON数据方法(禁止调用,元数据不支持本地生成) - * @return 始终抛出异常,提示方法不可用 - * @throws IllegalAccessError 始终抛出异常 - */ @Override public JSONObject getLocalJSONFromContent() { - throw new IllegalAccessError("MetaData:getLocalJSONFromContent 方法不应该被调用"); + throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); } - /** - * 获取同步动作方法(禁止调用,元数据不参与同步动作逻辑) - * @param c 数据库游标(未使用) - * @return 始终抛出异常,提示方法不可用 - * @throws IllegalAccessError 始终抛出异常 - */ @Override public int getSyncAction(Cursor c) { - throw new IllegalAccessError("MetaData:getSyncAction 方法不应该被调用"); + throw new IllegalAccessError("MetaData:getSyncAction should not be called"); } -} \ No newline at end of file + +} diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/Node.java b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/Node.java index 7298b8a..63950e0 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/Node.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/Node.java @@ -20,29 +20,33 @@ import android.database.Cursor; import org.json.JSONObject; -/** - * 同步节点抽象类(所有同步实体的基类) - * 定义同步操作类型常量及公共属性和抽象方法 - */ public abstract class Node { - // 同步操作类型常量(用于标识不同的同步动作) - public static final int SYNC_ACTION_NONE = 0; // 无操作 - public static final int SYNC_ACTION_ADD_REMOTE = 1; // 远程添加 - public static final int SYNC_ACTION_ADD_LOCAL = 2; // 本地添加 - public static final int SYNC_ACTION_DEL_REMOTE = 3; // 远程删除 - public static final int SYNC_ACTION_DEL_LOCAL = 4; // 本地删除 - public static final int SYNC_ACTION_UPDATE_REMOTE = 5; // 远程更新 - public static final int SYNC_ACTION_UPDATE_LOCAL = 6; // 本地更新 - public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; // 更新冲突 - public static final int SYNC_ACTION_ERROR = 8; // 错误状态 - - // 公共属性(所有子类共享的同步相关信息) - private String mGid; // Google任务全局唯一标识符(GID) - private String mName; // 节点名称(如任务标题) - private long mLastModified; // 最后修改时间戳 - private boolean mDeleted; // 是否已删除标记 - - // 构造方法:初始化默认值 + public static final int SYNC_ACTION_NONE = 0; + + public static final int SYNC_ACTION_ADD_REMOTE = 1; + + public static final int SYNC_ACTION_ADD_LOCAL = 2; + + public static final int SYNC_ACTION_DEL_REMOTE = 3; + + public static final int SYNC_ACTION_DEL_LOCAL = 4; + + public static final int SYNC_ACTION_UPDATE_REMOTE = 5; + + public static final int SYNC_ACTION_UPDATE_LOCAL = 6; + + public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; + + public static final int SYNC_ACTION_ERROR = 8; + + private String mGid; + + private String mName; + + private long mLastModified; + + private boolean mDeleted; + public Node() { mGid = null; mName = ""; @@ -50,25 +54,18 @@ public abstract class Node { mDeleted = false; } - // 抽象方法:获取创建操作的JSON(由子类实现具体逻辑) public abstract JSONObject getCreateAction(int actionId); - // 抽象方法:获取更新操作的JSON(由子类实现具体逻辑) public abstract JSONObject getUpdateAction(int actionId); - // 抽象方法:从远程JSON设置内容(由子类实现解析逻辑) public abstract void setContentByRemoteJSON(JSONObject js); - // 抽象方法:从本地JSON设置内容(由子类实现序列化逻辑) public abstract void setContentByLocalJSON(JSONObject js); - // 抽象方法:从内容获取本地JSON(由子类实现反序列化逻辑) public abstract JSONObject getLocalJSONFromContent(); - // 抽象方法:获取同步动作类型(由子类根据数据库游标判断) public abstract int getSyncAction(Cursor c); - // ==================== 公共属性的访问器和修改器 ==================== public void setGid(String gid) { this.mGid = gid; } @@ -100,4 +97,5 @@ public abstract class Node { public boolean getDeleted() { return this.mDeleted; } + } diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/SqlData.java b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/SqlData.java index 572f022..d3ec3be 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/SqlData.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/SqlData.java @@ -34,42 +34,43 @@ import net.micode.notes.gtask.exception.ActionFailureException; import org.json.JSONException; import org.json.JSONObject; -/** - * 数据库数据操作类 - * 用于处理笔记数据项的增删改查操作,支持JSON格式转换 - */ + 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 }; - // 各字段在投影中的索引位置 public static final int DATA_ID_COLUMN = 0; + public static final int DATA_MIME_TYPE_COLUMN = 1; + public static final int DATA_CONTENT_COLUMN = 2; + public static final int DATA_CONTENT_DATA_1_COLUMN = 3; + public static final int DATA_CONTENT_DATA_3_COLUMN = 4; - private ContentResolver mContentResolver; // 内容解析器 - private boolean mIsCreate; // 是否为新建数据 - private long mDataId; // 数据ID - private String mDataMimeType; // MIME类型 - private String mDataContent; // 内容 - private long mDataContentData1; // 附加数据1 - private String mDataContentData3; // 附加数据3 - private ContentValues mDiffDataValues; // 差异值,用于更新操作 - - /** - * 构造函数(创建新数据项) - * @param context 上下文 - */ + private ContentResolver mContentResolver; + + private boolean mIsCreate; + + private long mDataId; + + private String mDataMimeType; + + private String mDataContent; + + private long mDataContentData1; + + private String mDataContentData3; + + private ContentValues mDiffDataValues; + public SqlData(Context context) { mContentResolver = context.getContentResolver(); mIsCreate = true; @@ -81,11 +82,6 @@ public class SqlData { mDiffDataValues = new ContentValues(); } - /** - * 构造函数(从游标加载现有数据项) - * @param context 上下文 - * @param c 游标 - */ public SqlData(Context context, Cursor c) { mContentResolver = context.getContentResolver(); mIsCreate = false; @@ -93,10 +89,6 @@ public class SqlData { mDiffDataValues = new ContentValues(); } - /** - * 从游标加载数据 - * @param c 游标 - */ private void loadFromCursor(Cursor c) { mDataId = c.getLong(DATA_ID_COLUMN); mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); @@ -105,13 +97,7 @@ public class SqlData { mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); } - /** - * 从JSON设置内容 - * @param js JSON对象 - * @throws JSONException JSON解析异常 - */ public void setContent(JSONObject js) throws JSONException { - // 从JSON中获取各字段值,并与当前值比较,记录差异 long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; if (mIsCreate || mDataId != dataId) { mDiffDataValues.put(DataColumns.ID, dataId); @@ -144,14 +130,9 @@ public class SqlData { mDataContentData3 = dataContentData3; } - /** - * 获取内容的JSON表示 - * @return JSON对象 - * @throws JSONException JSON生成异常 - */ public JSONObject getContent() throws JSONException { if (mIsCreate) { - Log.e(TAG, "数据尚未在数据库中创建"); + Log.e(TAG, "it seems that we haven't created this in database yet"); return null; } JSONObject js = new JSONObject(); @@ -163,38 +144,28 @@ public class SqlData { return js; } - /** - * 提交数据到数据库 - * @param noteId 所属笔记ID - * @param validateVersion 是否验证版本 - * @param version 版本号 - */ public void commit(long noteId, boolean validateVersion, long version) { + if (mIsCreate) { - // 新建数据项 if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { - mDiffDataValues.remove(DataColumns.ID); // 移除无效ID + mDiffDataValues.remove(DataColumns.ID); } - mDiffDataValues.put(DataColumns.NOTE_ID, noteId); // 设置所属笔记ID - - // 插入数据 + + mDiffDataValues.put(DataColumns.NOTE_ID, noteId); Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); try { - mDataId = Long.valueOf(uri.getPathSegments().get(1)); // 获取插入后的ID + mDataId = Long.valueOf(uri.getPathSegments().get(1)); } catch (NumberFormatException e) { - Log.e(TAG, "获取数据ID失败: " + e.toString()); - throw new ActionFailureException("创建笔记失败"); + 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 @@ -203,19 +174,15 @@ public class SqlData { }); } if (result == 0) { - Log.w(TAG, "更新失败,可能在同步时有其他更新操作"); + Log.w(TAG, "there is no update. maybe user updates note when syncing"); } } } - mDiffDataValues.clear(); // 清空差异值 - mIsCreate = false; // 标记为已创建 + mDiffDataValues.clear(); + mIsCreate = false; } - /** - * 获取数据ID - * @return 数据ID - */ public long getId() { return mDataId; } diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java index 5d46d01..79a4095 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java @@ -171,7 +171,7 @@ public class SqlNote { try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)", new String[] { - String.valueOf(id) + String.valueOf(id) }, null); if (c != null) { c.moveToNext(); @@ -206,7 +206,7 @@ public class SqlNote { try { c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA, "(note_id=?)", new String[] { - String.valueOf(mId) + String.valueOf(mId) }, null); if (c != null) { if (c.getCount() == 0) { @@ -473,11 +473,11 @@ public class SqlNote { if (!validateVersion) { result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" + NoteColumns.ID + "=?)", new String[] { - String.valueOf(mId) + String.valueOf(mId) }); } else { result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", + + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", new String[] { String.valueOf(mId), String.valueOf(mVersion) }); diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/Task.java b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/Task.java index bd39b66..6a19454 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/Task.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/Task.java @@ -31,18 +31,19 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -/** - * 任务类,继承自Node抽象类,用于表示Google Tasks中的任务 - * 负责处理任务的创建、更新、同步等操作,并与本地数据库中的笔记数据进行映射 - */ + 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; // 父任务列表 + private boolean mCompleted; + + private String mNotes; + + private JSONObject mMetaInfo; + + private Task mPriorSibling; + + private TaskList mParent; public Task() { super(); @@ -53,26 +54,21 @@ public class Task extends Node { mMetaInfo = null; } - /** - * 生成创建任务的JSON请求 - * @param actionId 操作ID - * @return JSON格式的创建任务请求 - */ public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); try { - // 设置操作类型为创建任务 + // action_type js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - // 设置操作ID + // action_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"); @@ -83,17 +79,17 @@ public class Task extends Node { } js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - // 设置父任务列表ID + // 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); - // 设置任务列表ID + // list_id js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); - // 设置前一个兄弟任务ID(如果有) + // prior_sibling_id if (mPriorSibling != null) { js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); } @@ -101,32 +97,27 @@ public class Task extends Node { } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); - throw new ActionFailureException("生成创建任务的JSON对象失败"); + throw new ActionFailureException("fail to generate task-create jsonobject"); } return js; } - /** - * 生成更新任务的JSON请求 - * @param actionId 操作ID - * @return JSON格式的更新任务请求 - */ public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); try { - // 设置操作类型为更新任务 + // action_type js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - // 设置操作ID + // action_id js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // 设置任务ID + // id js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - // 设置要更新的任务实体信息 + // entity_delta JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); if (getNotes() != null) { @@ -138,78 +129,67 @@ public class Task extends Node { } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); - throw new ActionFailureException("生成更新任务的JSON对象失败"); + throw new ActionFailureException("fail to generate task-update jsonobject"); } return js; } - /** - * 从远程JSON数据设置任务内容 - * @param js 包含任务信息的JSON对象 - */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { - // 从JSON中提取并设置任务ID + // 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)); } } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); - throw new ActionFailureException("从JSON对象获取任务内容失败"); + throw new ActionFailureException("fail to get task content from jsonobject"); } } } - /** - * 从本地JSON数据设置任务内容 - * @param js 包含本地笔记信息的JSON对象 - */ public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) || !js.has(GTaskStringUtils.META_HEAD_DATA)) { - Log.w(TAG, "setContentByLocalJSON: 没有可用数据"); + Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); } try { - // 提取笔记和数据信息 JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); - // 确保笔记类型正确 if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { - Log.e(TAG, "无效的笔记类型"); + Log.e(TAG, "invalid type"); return; } - // 从数据中提取任务名称 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { @@ -224,21 +204,16 @@ public class Task extends Node { } } - /** - * 将任务内容转换为本地JSON格式 - * @return 包含任务信息的JSON对象 - */ public JSONObject getLocalJSONFromContent() { String name = getName(); try { if (mMetaInfo == null) { - // 从网页创建的新任务 + // new task created from web if (name == null) { - Log.w(TAG, "该笔记似乎为空"); + Log.w(TAG, "the note seems to be an empty one"); return null; } - // 创建新的JSON对象表示任务 JSONObject js = new JSONObject(); JSONObject note = new JSONObject(); JSONArray dataArray = new JSONArray(); @@ -250,11 +225,10 @@ public class Task extends Node { js.put(GTaskStringUtils.META_HEAD_NOTE, note); return js; } else { - // 已同步的任务,更新现有元数据 + // synced task JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); - // 更新任务名称 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { @@ -273,10 +247,6 @@ public class Task extends Node { } } - /** - * 设置任务的元数据信息 - * @param metaData 元数据对象 - */ public void setMetaInfo(MetaData metaData) { if (metaData != null && metaData.getNotes() != null) { try { @@ -288,11 +258,6 @@ public class Task extends Node { } } - /** - * 根据本地数据库状态确定同步操作类型 - * @param c 包含本地笔记信息的数据库游标 - * @return 同步操作类型(如更新远程、更新本地、冲突等) - */ public int getSyncAction(Cursor c) { try { JSONObject noteInfo = null; @@ -301,42 +266,40 @@ public class Task extends Node { } if (noteInfo == null) { - Log.w(TAG, "笔记元数据似乎已被删除"); + Log.w(TAG, "it seems that note meta has been deleted"); return SYNC_ACTION_UPDATE_REMOTE; } if (!noteInfo.has(NoteColumns.ID)) { - Log.w(TAG, "远程笔记ID似乎已被删除"); + Log.w(TAG, "remote note id seems to be deleted"); return SYNC_ACTION_UPDATE_LOCAL; } - // 验证笔记ID + // validate the note id now if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { - Log.w(TAG, "笔记ID不匹配"); + Log.w(TAG, "note id doesn't match"); return SYNC_ACTION_UPDATE_LOCAL; } - // 判断是否有本地修改 if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // 本地没有更新 + // there is no local update if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // 两边都没有更新 + // no update both side return SYNC_ACTION_NONE; } else { - // 应用远程更新到本地 + // apply remote to local return SYNC_ACTION_UPDATE_LOCAL; } } else { - // 有本地修改,验证GTask ID + // validate gtask id if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { - Log.e(TAG, "GTask ID不匹配"); + 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; } } @@ -348,17 +311,11 @@ public class Task extends Node { return SYNC_ACTION_ERROR; } - /** - * 判断任务是否值得保存 - * @return 如果任务有名称或备注,则返回true,否则返回false - */ public boolean isWorthSaving() { - return mMetaInfo != null || - (getName() != null && getName().trim().length() > 0) || - (getNotes() != null && getNotes().trim().length() > 0); + return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) + || (getNotes() != null && getNotes().trim().length() > 0); } - // Getter和Setter方法 public void setCompleted(boolean completed) { this.mCompleted = completed; } @@ -390,4 +347,5 @@ public class Task extends Node { public TaskList getParent() { return this.mParent; } -} \ No newline at end of file + +} diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/TaskList.java b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/TaskList.java index f634932..4ea21c5 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/TaskList.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/data/TaskList.java @@ -29,292 +29,315 @@ import org.json.JSONObject; import java.util.ArrayList; -/** - * 任务列表类,继承自Node抽象类,代表Google Tasks中的任务列表(如待办事项分组) - * 负责管理任务列表的创建、更新、同步及子任务的层级关系 - */ + public class TaskList extends Node { private static final String TAG = TaskList.class.getSimpleName(); - private int mIndex; // 任务列表在父层级中的排序索引 - private ArrayList mChildren; // 子任务列表 + + private int mIndex; + + private ArrayList mChildren; public TaskList() { super(); - mChildren = new ArrayList<>(); // 初始化子任务集合 - mIndex = 1; // 默认索引为1(可能用于排序) + mChildren = new ArrayList(); + mIndex = 1; } - /** - * 生成创建任务列表的JSON请求 - * @param actionId 操作ID - * @return 创建任务列表的JSON对象 - */ public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); + try { - // 设置操作类型为"创建" - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - // 设置操作ID + // action_type + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); + + // action_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"); // 创建者ID(固定值) - entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, - GTaskStringUtils.GTASK_JSON_TYPE_GROUP); // 实体类型为"任务组" - js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 实体变更数据 + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); + entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_GROUP); + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + } catch (JSONException e) { - Log.e(TAG, "生成创建任务列表JSON失败: " + e.getMessage()); - throw new ActionFailureException("创建任务列表JSON生成失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate tasklist-create jsonobject"); } + return js; } - /** - * 生成更新任务列表的JSON请求 - * @param actionId 操作ID - * @return 更新任务列表的JSON对象 - */ public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); + try { - // 设置操作类型为"更新" - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - // 设置操作ID + // action_type + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); + + // action_id js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // 设置任务列表ID(GID) + + // 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()); // 删除状态 - js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 实体变更数据 + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + } catch (JSONException e) { - Log.e(TAG, "生成更新任务列表JSON失败: " + e.getMessage()); - throw new ActionFailureException("更新任务列表JSON生成失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to generate tasklist-update jsonobject"); } + return js; } - /** - * 从远程JSON数据设置任务列表内容 - * @param js 包含任务列表信息的JSON对象 - */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { - // 提取并设置任务列表ID(GID) + // 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)); } + } catch (JSONException e) { - Log.e(TAG, "解析远程任务列表JSON失败: " + e.getMessage()); - throw new ActionFailureException("远程任务列表内容解析失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("fail to get tasklist content from jsonobject"); } } } - /** - * 从本地JSON数据设置任务列表内容(映射到本地笔记文件夹) - * @param js 包含本地笔记信息的JSON对象 - */ public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { - Log.w(TAG, "本地JSON数据为空或缺少笔记元数据"); - return; + Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); } + try { - JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 提取笔记元数据 - int type = folder.getInt(NoteColumns.TYPE); // 获取笔记类型 + JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - if (type == Notes.TYPE_FOLDER) { - // 普通文件夹:设置名称(添加MIUI前缀) + if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { String name = folder.getString(NoteColumns.SNIPPET); setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); - } else if (type == Notes.TYPE_SYSTEM) { - // 系统文件夹:根据ID设置固定名称 - long folderId = folder.getLong(NoteColumns.ID); - if (folderId == Notes.ID_ROOT_FOLDER) { + } else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { + if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); - } else if (folderId == Notes.ID_CALL_RECORD_FOLDER) { - setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE); - } else { - Log.e(TAG, "无效的系统文件夹ID: " + folderId); - } + else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER) + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_CALL_NOTE); + else + Log.e(TAG, "invalid system folder"); } else { - Log.e(TAG, "无效的笔记类型: " + type); + Log.e(TAG, "error type"); } } catch (JSONException e) { - Log.e(TAG, "解析本地任务列表JSON失败: " + e.getMessage()); + Log.e(TAG, e.toString()); + e.printStackTrace(); } } - /** - * 将任务列表内容转换为本地JSON格式(映射到笔记文件夹) - * @return 包含本地笔记信息的JSON对象 - */ public JSONObject getLocalJSONFromContent() { try { JSONObject js = new JSONObject(); JSONObject folder = new JSONObject(); + String folderName = getName(); + if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) + folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(), + folderName.length()); + folder.put(NoteColumns.SNIPPET, folderName); + if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) + || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) + folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + else + folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - // 移除MIUI前缀(还原本地文件夹名称) - if (folderName.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) { - folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length()); - } + js.put(GTaskStringUtils.META_HEAD_NOTE, folder); - // 设置文件夹类型 - if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) || - folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) { - folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 系统文件夹 - } else { - folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 普通文件夹 - } - folder.put(NoteColumns.SNIPPET, folderName); // 设置文件夹名称 - js.put(GTaskStringUtils.META_HEAD_NOTE, folder); // 封装笔记元数据 return js; } catch (JSONException e) { - Log.e(TAG, "生成本地任务列表JSON失败: " + e.getMessage()); + Log.e(TAG, e.toString()); + e.printStackTrace(); return null; } } - /** - * 根据本地数据库状态确定同步操作类型 - * @param c 数据库游标(指向本地笔记记录) - * @return 同步操作类型(如无操作、更新本地、更新远程等) - */ 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()) { - return SYNC_ACTION_NONE; // 本地与远程一致,无操作 + // no update both side + return SYNC_ACTION_NONE; } else { - return SYNC_ACTION_UPDATE_LOCAL; // 应用远程更新到本地 + // 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不匹配,同步错误"); - return SYNC_ACTION_ERROR; // GID不一致,同步错误 + Log.e(TAG, "gtask id doesn't match"); + return SYNC_ACTION_ERROR; } if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - return SYNC_ACTION_UPDATE_REMOTE; // 仅本地修改,更新远程 + // local modification only + return SYNC_ACTION_UPDATE_REMOTE; } else { - // 文件夹冲突处理策略:强制应用本地修改(可能丢失远程数据) - Log.w(TAG, "文件夹同步冲突,应用本地修改"); + // for folder conflicts, just apply local modification return SYNC_ACTION_UPDATE_REMOTE; } } } catch (Exception e) { - Log.e(TAG, "同步操作判断失败: " + e.getMessage()); - return SYNC_ACTION_ERROR; + Log.e(TAG, e.toString()); + e.printStackTrace(); } + + return SYNC_ACTION_ERROR; } - // ==================== 子任务管理方法 ==================== public int getChildTaskCount() { - return mChildren.size(); // 获取子任务数量 + return mChildren.size(); } public boolean addChildTask(Task task) { - if (task == null || mChildren.contains(task)) return false; - boolean ret = mChildren.add(task); // 添加子任务 - if (ret) { - // 设置子任务的前一个兄弟任务和父列表 - task.setPriorSibling(mChildren.size() > 1 ? mChildren.get(mChildren.size() - 2) : null); - task.setParent(this); + boolean ret = false; + if (task != null && !mChildren.contains(task)) { + ret = mChildren.add(task); + if (ret) { + // need to set prior sibling and parent + task.setPriorSibling(mChildren.isEmpty() ? null : mChildren + .get(mChildren.size() - 1)); + task.setParent(this); + } } return ret; } public boolean addChildTask(Task task, int index) { if (index < 0 || index > mChildren.size()) { - Log.e(TAG, "添加子任务:无效索引"); + Log.e(TAG, "add child task: invalid index"); return false; } - if (task == null || mChildren.indexOf(task) != -1) return false; - mChildren.add(index, task); // 插入到指定索引 - // 更新相邻任务的兄弟关系 - Task preTask = index > 0 ? mChildren.get(index - 1) : null; - Task nextTask = index < mChildren.size() - 1 ? mChildren.get(index + 1) : null; - task.setPriorSibling(preTask); - if (nextTask != null) nextTask.setPriorSibling(task); + + int pos = mChildren.indexOf(task); + if (task != null && pos == -1) { + mChildren.add(index, task); + + // update the task list + Task preTask = null; + Task afterTask = null; + if (index != 0) + preTask = mChildren.get(index - 1); + if (index != mChildren.size() - 1) + afterTask = mChildren.get(index + 1); + + task.setPriorSibling(preTask); + if (afterTask != null) + afterTask.setPriorSibling(task); + } + return true; } public boolean removeChildTask(Task task) { + boolean ret = false; int index = mChildren.indexOf(task); - if (index == -1) return false; - boolean ret = mChildren.remove(task); // 移除子任务 - if (ret) { - task.setPriorSibling(null); // 清空被移除任务的兄弟关系 - task.setParent(null); // 清空父列表引用 - // 更新后续任务的前一个兄弟任务 - if (index < mChildren.size()) { - Task nextTask = mChildren.get(index); - nextTask.setPriorSibling(index > 0 ? mChildren.get(index - 1) : null); + if (index != -1) { + 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)); + } } } return ret; } - public boolean moveChildTask(Task task, int newIndex) { - int oldIndex = mChildren.indexOf(task); - if (oldIndex == -1 || newIndex < 0 || newIndex >= mChildren.size()) { - Log.e(TAG, "移动子任务:无效索引或任务不存在"); + public boolean moveChildTask(Task task, int index) { + + if (index < 0 || index >= mChildren.size()) { + Log.e(TAG, "move child task: invalid index"); + return false; + } + + int pos = mChildren.indexOf(task); + if (pos == -1) { + Log.e(TAG, "move child task: the task should in the list"); return false; } - if (oldIndex == newIndex) return true; - // 先移除再插入实现移动 - return removeChildTask(task) && addChildTask(task, newIndex); + + if (pos == index) + return true; + return (removeChildTask(task) && addChildTask(task, index)); } public Task findChildTaskByGid(String gid) { - for (Task task : mChildren) { - if (task.getGid().equals(gid)) { - return task; // 根据GID查找子任务 + for (int i = 0; i < mChildren.size(); i++) { + Task t = mChildren.get(i); + if (t.getGid().equals(gid)) { + return t; } } return null; } public int getChildTaskIndex(Task task) { - return mChildren.indexOf(task); // 获取子任务在列表中的索引 + return mChildren.indexOf(task); } public Task getChildTaskByIndex(int index) { if (index < 0 || index >= mChildren.size()) { - Log.e(TAG, "获取子任务:无效索引"); + Log.e(TAG, "getTaskByIndex: invalid index"); return null; } - return mChildren.get(index); // 根据索引获取子任务 + return mChildren.get(index); + } + + public Task getChilTaskByGid(String gid) { + for (Task task : mChildren) { + if (task.getGid().equals(gid)) + return task; + } + return null; } public ArrayList getChildTaskList() { - return mChildren; // 获取所有子任务列表 + return this.mChildren; } - // ==================== 索引管理 ==================== public void setIndex(int index) { - this.mIndex = index; // 设置任务列表的排序索引 + this.mIndex = index; } public int getIndex() { - return mIndex; // 获取排序索引 + return this.mIndex; } -} \ No newline at end of file +} diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java index 016f0ea..15504be 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java @@ -16,34 +16,18 @@ package net.micode.notes.gtask.exception; -/** - * 操作失败异常类 - * 继承自RuntimeException,用于标识同步或数据操作过程中的不可恢复错误 - */ public class ActionFailureException extends RuntimeException { - private static final long serialVersionUID = 4425249765923293627L; // 序列化版本号 + private static final long serialVersionUID = 4425249765923293627L; - /** - * 无参构造方法 - */ public ActionFailureException() { super(); } - /** - * 带错误消息的构造方法 - * @param paramString 错误消息字符串 - */ public ActionFailureException(String paramString) { super(paramString); } - /** - * 带错误消息和根源异常的构造方法 - * @param paramString 错误消息字符串 - * @param paramThrowable 根源异常(导致当前异常的原始异常) - */ public ActionFailureException(String paramString, Throwable paramThrowable) { super(paramString, paramThrowable); } -} \ No newline at end of file +} diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java index 0d2996c..b08cfb1 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java @@ -16,34 +16,18 @@ package net.micode.notes.gtask.exception; -/** - * 网络失败异常类 - * 继承自Exception,用于标识网络请求或连接相关的可恢复错误 - */ public class NetworkFailureException extends Exception { - private static final long serialVersionUID = 2107610287180234136L; // 序列化版本号 + private static final long serialVersionUID = 2107610287180234136L; - /** - * 无参构造方法 - */ public NetworkFailureException() { super(); } - /** - * 带错误消息的构造方法 - * @param paramString 错误消息字符串 - */ public NetworkFailureException(String paramString) { super(paramString); } - /** - * 带错误消息和根源异常的构造方法 - * @param paramString 错误消息字符串 - * @param paramThrowable 根源异常(导致当前异常的原始异常,如SocketException) - */ public NetworkFailureException(String paramString, Throwable paramThrowable) { super(paramString, paramThrowable); } -} \ No newline at end of file +} diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java index bca1cb6..56c8e5e 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java @@ -28,136 +28,97 @@ import net.micode.notes.R; import net.micode.notes.ui.NotesListActivity; import net.micode.notes.ui.NotesPreferenceActivity; -/** - * Google Tasks同步异步任务类 - * 负责在后台执行Google Tasks同步操作,并通过通知和回调机制反馈状态 - */ + public class GTaskASyncTask extends AsyncTask { - private static final int GTASK_SYNC_NOTIFICATION_ID = 5234235; // 同步通知的唯一标识 + private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; - // 完成监听器接口,用于同步完成后回调 public interface OnCompleteListener { - void onComplete(); // 同步完成回调方法 + void onComplete(); } - private Context mContext; // 上下文 - private NotificationManager mNotifiManager; // 通知管理器 - private GTaskManager mTaskManager; // Google Tasks管理类实例 - private OnCompleteListener mOnCompleteListener; // 完成监听器 + private Context mContext; + + private NotificationManager mNotifiManager; + + private GTaskManager mTaskManager; + + private OnCompleteListener mOnCompleteListener; - /** - * 构造方法 - * @param context 上下文 - * @param listener 同步完成监听器 - */ public GTaskASyncTask(Context context, OnCompleteListener listener) { mContext = context; mOnCompleteListener = listener; - // 获取通知管理器服务 - mNotifiManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); - // 获取Google Tasks管理类的单例实例 + mNotifiManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); mTaskManager = GTaskManager.getInstance(); } - /** - * 取消同步操作 - */ public void cancelSync() { - mTaskManager.cancelSync(); // 调用GTaskManager的取消同步方法 + mTaskManager.cancelSync(); } - /** - * 发布进度消息(包装为数组以适配AsyncTask的泛型参数) - * @param message 进度消息内容 - */ public void publishProgess(String message) { - publishProgress(new String[]{message}); // 调用父类方法发布进度 + publishProgress(new String[] { + message + }); } - /** - * 显示同步状态通知 - * @param tickerId 通知标题的字符串资源ID - * @param content 通知内容 - */ private void showNotification(int tickerId, String content) { - // 创建通知对象 - Notification notification = new Notification(R.drawable.notification, - mContext.getString(tickerId), System.currentTimeMillis()); - notification.defaults = Notification.DEFAULT_LIGHTS; // 设置默认灯光效果 - notification.flags = Notification.FLAG_AUTO_CANCEL; // 设置通知自动取消 - - // 创建点击通知时的PendingIntent + Notification notification = new Notification(R.drawable.notification, mContext + .getString(tickerId), System.currentTimeMillis()); + notification.defaults = Notification.DEFAULT_LIGHTS; + notification.flags = Notification.FLAG_AUTO_CANCEL; PendingIntent pendingIntent; if (tickerId != R.string.ticker_success) { - // 失败/进行中的通知点击后跳转到设置页 - pendingIntent = PendingIntent.getActivity(mContext, 0, - new Intent(mContext, NotesPreferenceActivity.class), 0); + pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, + NotesPreferenceActivity.class), 0); + } else { - // 成功的通知点击后跳转到笔记列表页 - pendingIntent = PendingIntent.getActivity(mContext, 0, - new Intent(mContext, NotesListActivity.class), 0); + pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, + NotesListActivity.class), 0); } - - // 设置通知内容(已弃用的setLatestEventInfo,改用contentIntent) +// notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, +// pendingIntent); notification.contentIntent = pendingIntent; - // 显示通知 mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); } - /** - * 后台执行同步操作(异步任务核心逻辑) - * @param unused 未使用的参数 - * @return 同步结果状态码(来自GTaskManager的STATE_*常量) - */ @Override protected Integer doInBackground(Void... unused) { - // 发布登录进度消息(显示正在使用的同步账号) - publishProgess(mContext.getString(R.string.sync_progress_login, - NotesPreferenceActivity.getSyncAccountName(mContext))); - // 执行同步操作并返回结果 + publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity + .getSyncAccountName(mContext))); return mTaskManager.sync(mContext, this); } - /** - * 处理进度更新(在主线程执行,用于更新UI或通知) - * @param progress 进度消息数组 - */ @Override protected void onProgressUpdate(String... progress) { - showNotification(R.string.ticker_syncing, progress[0]); // 显示同步中的通知 - // 如果上下文是GTaskSyncService,发送广播通知进度 + showNotification(R.string.ticker_syncing, progress[0]); if (mContext instanceof GTaskSyncService) { ((GTaskSyncService) mContext).sendBroadcast(progress[0]); } } - /** - * 处理后台任务完成(在主线程执行,用于处理结果和通知用户) - * @param result 同步结果状态码 - */ @Override protected void onPostExecute(Integer result) { - // 根据不同的结果状态显示对应的通知 if (result == GTaskManager.STATE_SUCCESS) { - showNotification(R.string.ticker_success, - mContext.getString(R.string.success_sync_account, mTaskManager.getSyncAccount())); - // 记录最后同步时间 + showNotification(R.string.ticker_success, mContext.getString( + R.string.success_sync_account, mTaskManager.getSyncAccount())); NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); } else if (result == GTaskManager.STATE_NETWORK_ERROR) { showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); } else if (result == GTaskManager.STATE_INTERNAL_ERROR) { showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); } else if (result == GTaskManager.STATE_SYNC_CANCELLED) { - showNotification(R.string.ticker_cancel, mContext.getString(R.string.error_sync_cancelled)); + showNotification(R.string.ticker_cancel, mContext + .getString(R.string.error_sync_cancelled)); } - // 在子线程中执行完成回调,避免阻塞主线程 if (mOnCompleteListener != null) { new Thread(new Runnable() { + public void run() { mOnCompleteListener.onComplete(); } }).start(); } } -} \ No newline at end of file +} diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java index f7c6489..c67dfdf 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java @@ -60,31 +60,36 @@ import java.util.zip.GZIPInputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; -/** - * Google Tasks客户端类,负责与Google Tasks服务进行网络通信 - * 处理登录验证、任务/列表的增删改查等操作,封装HTTP请求与响应处理逻辑 - */ + public class GTaskClient { private static final String TAG = GTaskClient.class.getSimpleName(); - private static final String GTASK_URL = "https://mail.google.com/tasks/"; // Google Tasks根URL - private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; // GET请求URL(获取数据) - private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; // POST请求URL(提交操作) - - private static GTaskClient mInstance = null; // 单例实例 - - private DefaultHttpClient mHttpClient; // HTTP客户端 - private String mGetUrl; // 当前GET请求URL(支持自定义域名) - private String mPostUrl; // 当前POST请求URL(支持自定义域名) - private long mClientVersion; // 客户端版本号(由Google Tasks返回) - private boolean mLoggedin; // 登录状态 - private long mLastLoginTime; // 最后登录时间(用于超时控制) - private int mActionId; // 操作ID生成器(保证每次请求唯一) - private Account mAccount; // 当前同步的Google账号 - private JSONArray mUpdateArray; // 批量更新操作队列 - - /** - * 构造方法(私有化,保证单例) - */ + + private static final String GTASK_URL = "https://mail.google.com/tasks/"; + + private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; + + private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; + + private static GTaskClient mInstance = null; + + private DefaultHttpClient mHttpClient; + + private String mGetUrl; + + private String mPostUrl; + + private long mClientVersion; + + private boolean mLoggedin; + + private long mLastLoginTime; + + private int mActionId; + + private Account mAccount; + + private JSONArray mUpdateArray; + private GTaskClient() { mHttpClient = null; mGetUrl = GTASK_GET_URL; @@ -97,10 +102,6 @@ public class GTaskClient { mUpdateArray = null; } - /** - * 获取单例实例 - * @return GTaskClient实例 - */ public static synchronized GTaskClient getInstance() { if (mInstance == null) { mInstance = new GTaskClient(); @@ -108,49 +109,49 @@ public class GTaskClient { return mInstance; } - /** - * 登录Google Tasks - * @param activity 活动上下文(用于获取账号令牌) - * @return 登录是否成功 - */ public boolean login(Activity activity) { - // 登录状态超时控制(5分钟) + // we suppose that the cookie would expire after 5 minutes + // then we need to re-login final long interval = 1000 * 60 * 5; if (mLastLoginTime + interval < System.currentTimeMillis()) { mLoggedin = false; } - // 账号切换后强制重新登录 - if (mLoggedin && !TextUtils.equals(getSyncAccount().name, - NotesPreferenceActivity.getSyncAccountName(activity))) { + + // need to re-login after account switch + if (mLoggedin + && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity + .getSyncAccountName(activity))) { mLoggedin = false; } + if (mLoggedin) { - Log.d(TAG, "已登录"); + Log.d(TAG, "already logged in"); return true; } mLastLoginTime = System.currentTimeMillis(); - String authToken = loginGoogleAccount(activity, false); // 获取授权令牌 + String authToken = loginGoogleAccount(activity, false); if (authToken == null) { - Log.e(TAG, "Google账号登录失败"); + Log.e(TAG, "login google account failed"); return false; } - // 处理自定义域名账号(非gmail/googlemail后缀) - if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || - mAccount.name.toLowerCase().endsWith("googlemail.com"))) { + // 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/"); int index = mAccount.name.indexOf('@') + 1; String suffix = mAccount.name.substring(index); url.append(suffix + "/"); mGetUrl = url.toString() + "ig"; mPostUrl = url.toString() + "r/ig"; - // 尝试使用自定义域名登录 + if (tryToLoginGtask(activity, authToken)) { mLoggedin = true; } } - // 通用域名登录(处理失败或默认情况) + + // try to login with google official url if (!mLoggedin) { mGetUrl = GTASK_GET_URL; mPostUrl = GTASK_POST_URL; @@ -158,116 +159,103 @@ public class GTaskClient { return false; } } + mLoggedin = true; return true; } - /** - * 登录Google账号获取授权令牌 - * @param activity 活动上下文 - * @param invalidateToken 是否失效现有令牌(用于重试) - * @return 授权令牌 - */ private String loginGoogleAccount(Activity activity, boolean invalidateToken) { String authToken; AccountManager accountManager = AccountManager.get(activity); - Account[] accounts = accountManager.getAccountsByType("com.google"); // 获取所有Google账号 + Account[] accounts = accountManager.getAccountsByType("com.google"); if (accounts.length == 0) { - Log.e(TAG, "无可用Google账号"); + Log.e(TAG, "there is no available google account"); return null; } String accountName = NotesPreferenceActivity.getSyncAccountName(activity); Account account = null; - // 查找配置的同步账号 for (Account a : accounts) { if (a.name.equals(accountName)) { account = a; break; } } - if (account == null) { - Log.e(TAG, "无法找到配置的同步账号"); + if (account != null) { + mAccount = account; + } else { + Log.e(TAG, "unable to get an account with the same name in the settings"); return null; } - mAccount = account; - // 获取授权令牌 - AccountManagerFuture future = accountManager.getAuthToken(account, + // get the token now + AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); try { - Bundle bundle = future.getResult(); - authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN); + Bundle authTokenBundle = accountManagerFuture.getResult(); + authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); if (invalidateToken) { - // 失效旧令牌并重新获取(用于处理令牌过期) accountManager.invalidateAuthToken("com.google", authToken); - return loginGoogleAccount(activity, false); + loginGoogleAccount(activity, false); } - return authToken; } catch (Exception e) { - Log.e(TAG, "获取授权令牌失败: " + e.getMessage()); - return null; + Log.e(TAG, "get auth token failed"); + authToken = null; } + + return authToken; } - /** - * 尝试登录Google Tasks(携带授权令牌) - * @param activity 活动上下文 - * @param authToken 授权令牌 - * @return 登录是否成功 - */ private boolean tryToLoginGtask(Activity activity, String authToken) { - if (!loginGtask(authToken)) { // 首次登录尝试 - // 令牌可能过期,失效后重试 + if (!loginGtask(authToken)) { + // maybe the auth token is out of date, now let's invalidate the + // token and try again authToken = loginGoogleAccount(activity, true); if (authToken == null) { - Log.e(TAG, "重新获取令牌失败"); + Log.e(TAG, "login google account failed"); return false; } - if (!loginGtask(authToken)) { // 二次登录尝试 - Log.e(TAG, "二次登录失败"); + + if (!loginGtask(authToken)) { + Log.e(TAG, "login gtask failed"); return false; } } return true; } - /** - * 执行Google Tasks登录请求 - * @param authToken 授权令牌 - * @return 登录是否成功 - */ private boolean loginGtask(String authToken) { - int timeoutConnection = 10000; // 连接超时(10秒) - int timeoutSocket = 15000; // 套接字超时(15秒) + int timeoutConnection = 10000; + int timeoutSocket = 15000; HttpParams httpParameters = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); mHttpClient = new DefaultHttpClient(httpParameters); - BasicCookieStore cookieStore = new BasicCookieStore(); - mHttpClient.setCookieStore(cookieStore); + BasicCookieStore localBasicCookieStore = new BasicCookieStore(); + mHttpClient.setCookieStore(localBasicCookieStore); HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); + // login gtask try { - String loginUrl = mGetUrl + "?auth=" + authToken; // 构造登录URL + String loginUrl = mGetUrl + "?auth=" + authToken; HttpGet httpGet = new HttpGet(loginUrl); - HttpResponse response = mHttpClient.execute(httpGet); // 发送GET请求 + HttpResponse response = null; + response = mHttpClient.execute(httpGet); - // 检查Cookie是否包含GTL认证信息 - List cookies = cookieStore.getCookies(); + // get the cookie now + List cookies = mHttpClient.getCookieStore().getCookies(); boolean hasAuthCookie = false; for (Cookie cookie : cookies) { if (cookie.getName().contains("GTL")) { hasAuthCookie = true; - break; } } if (!hasAuthCookie) { - Log.w(TAG, "未获取到认证Cookie"); + Log.w(TAG, "it seems that there is no auth cookie"); } - // 解析响应获取客户端版本号 + // get the client version String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -278,293 +266,261 @@ public class GTaskClient { jsString = resString.substring(begin + jsBegin.length(), end); } JSONObject js = new JSONObject(jsString); - mClientVersion = js.getLong("v"); // 保存客户端版本号 - return true; - + mClientVersion = js.getLong("v"); } catch (JSONException e) { - Log.e(TAG, "解析登录响应失败: " + e.getMessage()); + Log.e(TAG, e.toString()); + e.printStackTrace(); return false; } catch (Exception e) { - Log.e(TAG, "登录请求失败: " + e.getMessage()); + // simply catch all exceptions + Log.e(TAG, "httpget gtask_url failed"); return false; } + + return true; } - /** - * 生成唯一操作ID(自增计数器) - * @return 操作ID - */ private int getActionId() { return mActionId++; } - /** - * 创建POST请求对象(公共请求头) - * @return HttpPost对象 - */ private HttpPost createHttpPost() { HttpPost httpPost = new HttpPost(mPostUrl); httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); - httpPost.setHeader("AT", "1"); // Google Tasks特定请求头 + httpPost.setHeader("AT", "1"); return httpPost; } - /** - * 解析HTTP响应内容(处理压缩格式) - * @param entity HTTP实体 - * @return 响应内容字符串 - * @throws IOException IO异常 - */ private String getResponseContent(HttpEntity entity) throws IOException { String contentEncoding = null; if (entity.getContentEncoding() != null) { contentEncoding = entity.getContentEncoding().getValue(); - Log.d(TAG, "响应编码: " + contentEncoding); + Log.d(TAG, "encoding: " + contentEncoding); } InputStream input = entity.getContent(); - // 处理GZip压缩 - if ("gzip".equalsIgnoreCase(contentEncoding)) { - input = new GZIPInputStream(input); - // 处理Deflate压缩 - } else if ("deflate".equalsIgnoreCase(contentEncoding)) { + if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { + input = new GZIPInputStream(entity.getContent()); + } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { Inflater inflater = new Inflater(true); - input = new InflaterInputStream(input, inflater); + input = new InflaterInputStream(entity.getContent(), inflater); } try { InputStreamReader isr = new InputStreamReader(input); BufferedReader br = new BufferedReader(isr); StringBuilder sb = new StringBuilder(); - String line; - while ((line = br.readLine()) != null) { - sb.append(line); + + while (true) { + String buff = br.readLine(); + if (buff == null) { + return sb.toString(); + } + sb = sb.append(buff); } - return sb.toString(); } finally { input.close(); } } - /** - * 发送POST请求并解析响应 - * @param js 请求体JSON - * @return 响应JSON对象 - * @throws NetworkFailureException 网络异常 - */ private JSONObject postRequest(JSONObject js) throws NetworkFailureException { if (!mLoggedin) { - Log.e(TAG, "未登录,无法发送请求"); - throw new ActionFailureException("未登录"); + Log.e(TAG, "please login first"); + throw new ActionFailureException("not logged in"); } HttpPost httpPost = createHttpPost(); try { - // 构造请求参数(将JSON包裹在"r"参数中) - LinkedList params = new LinkedList<>(); - params.add(new BasicNameValuePair("r", js.toString())); - UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "UTF-8"); + LinkedList list = new LinkedList(); + list.add(new BasicNameValuePair("r", js.toString())); + 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); } catch (ClientProtocolException e) { - Log.e(TAG, "协议异常: " + e.getMessage()); - throw new NetworkFailureException("POST请求失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new NetworkFailureException("postRequest failed"); } catch (IOException e) { - Log.e(TAG, "IO异常: " + e.getMessage()); - throw new NetworkFailureException("POST请求失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new NetworkFailureException("postRequest failed"); } catch (JSONException e) { - Log.e(TAG, "JSON解析异常: " + e.getMessage()); - throw new ActionFailureException("响应解析失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("unable to convert response content to jsonobject"); } catch (Exception e) { - Log.e(TAG, "未知异常: " + e.getMessage()); - throw new ActionFailureException("请求处理失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("error occurs when posting request"); } } - // ==================== 任务操作 ==================== - /** - * 创建任务 - * @param task 任务对象 - * @throws NetworkFailureException 网络异常 - */ public void createTask(Task task) throws NetworkFailureException { - commitUpdate(); // 提交之前的批量操作 + commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // 添加创建任务操作到请求 + // action_list actionList.put(task.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 客户端版本号 + // 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); - task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 设置新生成的任务ID + task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); } catch (JSONException e) { - Log.e(TAG, "创建任务JSON处理失败: " + e.getMessage()); - throw new ActionFailureException("创建任务失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("create task: handing jsonobject failed"); } } - /** - * 创建任务列表 - * @param tasklist 任务列表对象 - * @throws NetworkFailureException 网络异常 - */ public void createTaskList(TaskList tasklist) throws NetworkFailureException { commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); + // action_list actionList.put(tasklist.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + + // client version jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + // post JSONObject jsResponse = postRequest(jsPost); JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( GTaskStringUtils.GTASK_JSON_RESULTS).get(0); - tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 设置列表ID + tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); } catch (JSONException e) { - Log.e(TAG, "创建任务列表JSON处理失败: " + e.getMessage()); - throw new ActionFailureException("创建任务列表失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("create tasklist: handing jsonobject failed"); } } - /** - * 提交批量更新操作 - * @throws NetworkFailureException 网络异常 - */ public void commitUpdate() throws NetworkFailureException { if (mUpdateArray != null) { try { JSONObject jsPost = new JSONObject(); + + // action_list jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); + + // client_version jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - postRequest(jsPost); // 发送批量更新请求 - mUpdateArray = null; // 清空操作队列 + + postRequest(jsPost); + mUpdateArray = null; } catch (JSONException e) { - Log.e(TAG, "提交批量更新失败: " + e.getMessage()); - throw new ActionFailureException("批量更新失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("commit update: handing jsonobject failed"); } } } - /** - * 添加更新操作到批量队列 - * @param node 节点对象(任务或列表) - * @throws NetworkFailureException 网络异常 - */ public void addUpdateNode(Node node) throws NetworkFailureException { if (node != null) { - // 限制批量操作数量(超过10个则先提交当前队列) + // too many update items may result in an error + // set max to 10 items if (mUpdateArray != null && mUpdateArray.length() > 10) { commitUpdate(); } - if (mUpdateArray == null) { + + if (mUpdateArray == null) mUpdateArray = new JSONArray(); - } - mUpdateArray.put(node.getUpdateAction(getActionId())); // 添加更新操作到队列 + mUpdateArray.put(node.getUpdateAction(getActionId())); } } - /** - * 移动任务(支持跨列表移动) - * @param task 要移动的任务 - * @param preParent 原父列表 - * @param curParent 新父列表 - * @throws NetworkFailureException 网络异常 - */ - public void moveTask(Task task, TaskList preParent, TaskList curParent) + public void moveTask(Task task, TaskList preParent, TaskList curParent) throws NetworkFailureException { - commitUpdate(); // 提交之前的批量操作 + commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); - // 构造移动操作请求 - action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + // 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()); // 任务ID - - // 同一列表内移动且非首个任务时,设置前置兄弟任务ID + 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()); // 源列表ID - action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); // 目标父列表ID - - // 跨列表移动时设置目标列表ID + 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); // 发送移动请求 + postRequest(jsPost); } catch (JSONException e) { - Log.e(TAG, "移动任务JSON处理失败: " + e.getMessage()); - throw new ActionFailureException("移动任务失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("move task: handing jsonobject failed"); } } - /** - * 删除节点(任务或列表) - * @param node 要删除的节点 - * @throws NetworkFailureException 网络异常 - */ public void deleteNode(Node node) throws NetworkFailureException { - commitUpdate(); // 提交之前的批量操作 + commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // 标记节点为已删除并添加更新操作 + // action_list node.setDeleted(true); actionList.put(node.getUpdateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - postRequest(jsPost); // 发送删除请求 - mUpdateArray = null; // 清空操作队列 + // client_version + jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + postRequest(jsPost); + mUpdateArray = null; } catch (JSONException e) { - Log.e(TAG, "删除节点JSON处理失败: " + e.getMessage()); - throw new ActionFailureException("删除节点失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("delete node: handing jsonobject failed"); } } - // ==================== 查询操作 ==================== - /** - * 获取所有任务列表 - * @return 任务列表JSON数组 - * @throws NetworkFailureException 网络异常 - */ public JSONArray getTaskLists() throws NetworkFailureException { if (!mLoggedin) { - Log.e(TAG, "未登录,无法获取任务列表"); - throw new ActionFailureException("未登录"); + Log.e(TAG, "please login first"); + throw new ActionFailureException("not logged in"); } try { HttpGet httpGet = new HttpGet(mGetUrl); - HttpResponse response = mHttpClient.execute(httpGet); // 发送GET请求 + HttpResponse response = null; + response = mHttpClient.execute(httpGet); - // 解析响应获取任务列表数据 + // get the task list String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -576,65 +532,54 @@ public class GTaskClient { } JSONObject js = new JSONObject(jsString); return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); - } catch (ClientProtocolException e) { - Log.e(TAG, "获取任务列表协议异常: " + e.getMessage()); - throw new NetworkFailureException("获取任务列表失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new NetworkFailureException("gettasklists: httpget failed"); } catch (IOException e) { - Log.e(TAG, "获取任务列表IO异常: " + e.getMessage()); - throw new NetworkFailureException("获取任务列表失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new NetworkFailureException("gettasklists: httpget failed"); } catch (JSONException e) { - Log.e(TAG, "解析任务列表JSON异常: " + e.getMessage()); - throw new ActionFailureException("解析任务列表失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("get task lists: handing jasonobject failed"); } } - /** - * 获取指定任务列表中的所有任务 - * @param listGid 任务列表ID - * @return 任务JSON数组 - * @throws NetworkFailureException 网络异常 - */ public JSONArray getTaskList(String listGid) throws NetworkFailureException { - commitUpdate(); // 提交之前的批量操作 + commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); - // 构造获取任务列表操作 - action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + // action_list + action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); - action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); // 列表ID - action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); // 不获取已删除任务 + action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); + action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); actionList.put(action); - jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + + // client_version jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); JSONObject jsResponse = postRequest(jsPost); return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); - } catch (JSONException e) { - Log.e(TAG, "获取任务JSON处理失败: " + e.getMessage()); - throw new ActionFailureException("获取任务列表失败"); + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("get task list: handing jsonobject failed"); } } - // ==================== 辅助方法 ==================== - /** - * 获取当前同步的Google账号 - * @return 账号对象 - */ public Account getSyncAccount() { return mAccount; } - /** - * 重置批量更新队列 - */ public void resetUpdateArray() { mUpdateArray = null; } -} \ No newline at end of file +} diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java index 2a9f69a..d2b4082 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java @@ -22,7 +22,6 @@ import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; -import android.net.Uri; import android.util.Log; import net.micode.notes.R; @@ -48,40 +47,46 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; -/** - * Google任务管理器类 - * 负责处理与Google Tasks的同步操作 - */ + public class GTaskManager { private static final String TAG = GTaskManager.class.getSimpleName(); - // 同步状态常量 - public static final int STATE_SUCCESS = 0; // 同步成功 - public static final int STATE_NETWORK_ERROR = 1; // 网络错误 - public static final int STATE_INTERNAL_ERROR = 2; // 内部错误 - public static final int STATE_SYNC_IN_PROGRESS = 3; // 同步进行中 - public static final int STATE_SYNC_CANCELLED = 4; // 同步已取消 - - private static GTaskManager mInstance = null; // 单例实例 - - private Activity mActivity; // 活动上下文 - private Context mContext; // 应用上下文 - private ContentResolver mContentResolver; // 内容解析器 - private boolean mSyncing; // 同步状态标志 - private boolean mCancelled; // 取消状态标志 - - // 数据存储映射 - private HashMap mGTaskListHashMap; // Google任务列表映射 - private HashMap mGTaskHashMap; // Google任务映射 - private HashMap mMetaHashMap; // 元数据映射 - private TaskList mMetaList; // 元数据列表 - private HashSet mLocalDeleteIdMap; // 本地删除ID映射 - private HashMap mGidToNid; // Google ID到本地ID的映射 - private HashMap mNidToGid; // 本地ID到Google ID的映射 - - /** - * 私有构造函数,初始化所有数据结构 - */ + public static final int STATE_SUCCESS = 0; + + public static final int STATE_NETWORK_ERROR = 1; + + public static final int STATE_INTERNAL_ERROR = 2; + + public static final int STATE_SYNC_IN_PROGRESS = 3; + + public static final int STATE_SYNC_CANCELLED = 4; + + private static GTaskManager mInstance = null; + + private Activity mActivity; + + private Context mContext; + + private ContentResolver mContentResolver; + + private boolean mSyncing; + + private boolean mCancelled; + + private HashMap mGTaskListHashMap; + + private HashMap mGTaskHashMap; + + private HashMap mMetaHashMap; + + private TaskList mMetaList; + + private HashSet mLocalDeleteIdMap; + + private HashMap mGidToNid; + + private HashMap mNidToGid; + private GTaskManager() { mSyncing = false; mCancelled = false; @@ -94,9 +99,6 @@ public class GTaskManager { mNidToGid = new HashMap(); } - /** - * 获取GTaskManager的单例实例 - */ public static synchronized GTaskManager getInstance() { if (mInstance == null) { mInstance = new GTaskManager(); @@ -104,19 +106,11 @@ public class GTaskManager { return mInstance; } - /** - * 设置活动上下文,用于获取认证令牌 - */ public synchronized void setActivityContext(Activity activity) { + // used for getting authtoken mActivity = activity; } - /** - * 执行同步操作 - * @param context 上下文 - * @param asyncTask 异步任务 - * @return 同步状态 - */ public int sync(Context context, GTaskASyncTask asyncTask) { if (mSyncing) { Log.d(TAG, "Sync is in progress"); @@ -137,18 +131,18 @@ public class GTaskManager { GTaskClient client = GTaskClient.getInstance(); client.resetUpdateArray(); - // 登录Google任务 + // login google task if (!mCancelled) { if (!client.login(mActivity)) { throw new NetworkFailureException("login google task failed"); } } - // 从Google获取任务列表 + // 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) { @@ -174,10 +168,6 @@ public class GTaskManager { return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; } - /** - * 初始化Google任务列表 - * 从Google服务器获取任务列表并解析 - */ private void initGTaskList() throws NetworkFailureException { if (mCancelled) return; @@ -185,18 +175,19 @@ public class GTaskManager { try { JSONArray jsTaskLists = client.getTaskLists(); - // 首先初始化元数据列表 + // init meta list first mMetaList = null; for (int i = 0; i < jsTaskLists.length(); i++) { JSONObject object = jsTaskLists.getJSONObject(i); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); - if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + if (name + .equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { mMetaList = new TaskList(); mMetaList.setContentByRemoteJSON(object); - // 加载元数据 + // load meta data JSONArray jsMetas = client.getTaskList(gid); for (int j = 0; j < jsMetas.length(); j++) { object = (JSONObject) jsMetas.getJSONObject(j); @@ -212,27 +203,29 @@ public class GTaskManager { } } - // 如果元数据列表不存在则创建 + // create meta list if not existed if (mMetaList == null) { mMetaList = new TaskList(); - mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META); + mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_META); GTaskClient.getInstance().createTaskList(mMetaList); } - // 初始化任务列表 + // init task list for (int i = 0; i < jsTaskLists.length(); i++) { JSONObject object = jsTaskLists.getJSONObject(i); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) - && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_META)) { TaskList tasklist = new TaskList(); tasklist.setContentByRemoteJSON(object); mGTaskListHashMap.put(gid, tasklist); mGTaskHashMap.put(gid, tasklist); - // 加载任务 + // load tasks JSONArray jsTasks = client.getTaskList(gid); for (int j = 0; j < jsTasks.length(); j++) { object = (JSONObject) jsTasks.getJSONObject(j); @@ -254,10 +247,6 @@ public class GTaskManager { } } - /** - * 同步内容 - * 处理本地和远程数据的同步 - */ private void syncContent() throws NetworkFailureException { int syncType; Cursor c = null; @@ -270,7 +259,7 @@ public class GTaskManager { return; } - // 处理本地删除的笔记 + // for local deleted note try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type<>? AND parent_id=?)", new String[] { @@ -297,10 +286,10 @@ public class GTaskManager { } } - // 首先同步文件夹 + // sync folder first syncFolder(); - // 处理数据库中已存在的笔记 + // for note existing in database try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type=? AND parent_id<>?)", new String[] { @@ -317,10 +306,10 @@ public class GTaskManager { syncType = node.getSyncAction(c); } else { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // 本地添加 + // local add syncType = Node.SYNC_ACTION_ADD_REMOTE; } else { - // 远程删除 + // remote delete syncType = Node.SYNC_ACTION_DEL_LOCAL; } } @@ -337,7 +326,7 @@ public class GTaskManager { } } - // 处理剩余的项目 + // go through remaining items Iterator> iter = mGTaskHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -345,24 +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"); } } - // 刷新本地同步ID + // refresh local sync id if (!mCancelled) { GTaskClient.getInstance().commitUpdate(); refreshLocalSyncId(); } + } - /** - * 同步文件夹 - * 处理文件夹的创建、更新和删除操作 - */ private void syncFolder() throws NetworkFailureException { Cursor c = null; String gid; @@ -373,7 +361,7 @@ public class GTaskManager { return; } - // 处理根文件夹 + // for root folder try { c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); @@ -385,7 +373,7 @@ public class GTaskManager { mGTaskHashMap.remove(gid); mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); - // 对于系统文件夹,仅在必要时更新远程名称 + // for system folder, only update remote name if necessary if (!node.getName().equals( GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); @@ -402,11 +390,11 @@ public class GTaskManager { } } - // 处理通话记录文件夹 + // for call-note folder try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", new String[] { - String.valueOf(Notes.ID_CALL_RECORD_FOLDER) + String.valueOf(Notes.ID_CALL_RECORD_FOLDER) }, null); if (c != null) { if (c.moveToNext()) { @@ -416,7 +404,8 @@ 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)) @@ -435,7 +424,7 @@ public class GTaskManager { } } - // 处理本地已存在的文件夹 + // for local existing folders try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type=? AND parent_id<>?)", new String[] { @@ -452,10 +441,10 @@ public class GTaskManager { syncType = node.getSyncAction(c); } else { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // 本地添加 + // local add syncType = Node.SYNC_ACTION_ADD_REMOTE; } else { - // 远程删除 + // remote delete syncType = Node.SYNC_ACTION_DEL_LOCAL; } } @@ -471,7 +460,7 @@ public class GTaskManager { } } - // 处理远程添加的文件夹 + // for remote add folders Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -487,12 +476,6 @@ public class GTaskManager { GTaskClient.getInstance().commitUpdate(); } - /** - * 执行内容同步 - * @param syncType 同步类型 - * @param node 节点 - * @param c 游标 - */ private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -527,8 +510,8 @@ public class GTaskManager { updateRemoteNode(node, c); break; case Node.SYNC_ACTION_UPDATE_CONFLICT: - // 合并两个修改可能是个好主意 - // 现在只是简单地使用本地更新 + // merging both modifications maybe a good idea + // right now just use local update simply updateRemoteNode(node, c); break; case Node.SYNC_ACTION_NONE: @@ -539,10 +522,6 @@ public class GTaskManager { } } - /** - * 添加本地节点 - * 将远程节点添加到本地数据库 - */ private void addLocalNode(Node node) throws NetworkFailureException { if (mCancelled) { return; @@ -570,7 +549,7 @@ public class GTaskManager { if (note.has(NoteColumns.ID)) { long id = note.getLong(NoteColumns.ID); if (DataUtils.existInNoteDatabase(mContentResolver, id)) { - // ID不可用,必须创建新的 + // the id is not available, have to create a new one note.remove(NoteColumns.ID); } } @@ -583,7 +562,8 @@ public class GTaskManager { if (data.has(DataColumns.ID)) { long dataId = data.getLong(DataColumns.ID); if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { - // 数据ID不可用,必须创建新的 + // the data id is not available, have to create + // a new one data.remove(DataColumns.ID); } } @@ -604,29 +584,25 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); } - // 创建本地节点 + // create the local node sqlNote.setGtaskId(node.getGid()); sqlNote.commit(false); - // 更新gid-nid映射 + // update gid-nid mapping mGidToNid.put(node.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), node.getGid()); - // 更新元数据 + // update meta updateRemoteMeta(node.getGid(), sqlNote); } - /** - * 更新本地节点 - * 根据远程数据更新本地节点 - */ private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; } SqlNote sqlNote; - // 本地更新笔记 + // update the note locally sqlNote = new SqlNote(mContext, c); sqlNote.setContent(node.getLocalJSONFromContent()); @@ -639,14 +615,10 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); sqlNote.commit(true); - // 更新元信息 + // update meta info updateRemoteMeta(node.getGid(), sqlNote); } - /** - * 添加远程节点 - * 将本地节点添加到远程服务器 - */ private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -655,7 +627,7 @@ public class GTaskManager { SqlNote sqlNote = new SqlNote(mContext, c); Node n; - // 远程更新 + // update remotely if (sqlNote.isNoteType()) { Task task = new Task(); task.setContentByLocalJSON(sqlNote.getContent()); @@ -670,12 +642,12 @@ public class GTaskManager { GTaskClient.getInstance().createTask(task); n = (Node) task; - // 添加元数据 + // add meta updateRemoteMeta(task.getGid(), sqlNote); } else { TaskList tasklist = null; - // 如果文件夹已存在,我们需要跳过 + // we need to skip folder if it has already existed String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) folderName += GTaskStringUtils.FOLDER_DEFAULT; @@ -699,7 +671,7 @@ public class GTaskManager { } } - // 没有匹配项,现在可以添加 + // no match we can add now if (tasklist == null) { tasklist = new TaskList(); tasklist.setContentByLocalJSON(sqlNote.getContent()); @@ -709,21 +681,17 @@ public class GTaskManager { n = (Node) tasklist; } - // 更新本地笔记 + // update local note sqlNote.setGtaskId(n.getGid()); sqlNote.commit(false); sqlNote.resetLocalModified(); sqlNote.commit(true); - // gid-id映射 + // gid-id mapping mGidToNid.put(n.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), n.getGid()); } - /** - * 更新远程节点 - * 根据本地数据更新远程节点 - */ private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -731,14 +699,14 @@ public class GTaskManager { SqlNote sqlNote = new SqlNote(mContext, c); - // 远程更新 + // update remotely node.setContentByLocalJSON(sqlNote.getContent()); GTaskClient.getInstance().addUpdateNode(node); - // 更新元数据 + // update meta updateRemoteMeta(node.getGid(), sqlNote); - // 必要时移动任务 + // move task if necessary if (sqlNote.isNoteType()) { Task task = (Task) node; TaskList preParentList = task.getParent(); @@ -757,16 +725,11 @@ public class GTaskManager { } } - // 清除本地修改标志 + // clear local modified flag sqlNote.resetLocalModified(); sqlNote.commit(true); } - /** - * 更新远程元数据 - * @param gid Google ID - * @param sqlNote SQL笔记对象 - */ private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { if (sqlNote != null && sqlNote.isNoteType()) { MetaData metaData = mMetaHashMap.get(gid); @@ -783,16 +746,12 @@ public class GTaskManager { } } - /** - * 刷新本地同步ID - * 更新本地数据库中的同步ID - */ private void refreshLocalSyncId() throws NetworkFailureException { if (mCancelled) { return; } - // 获取最新的gtask列表 + // get the latest gtask list mGTaskHashMap.clear(); mGTaskListHashMap.clear(); mMetaHashMap.clear(); @@ -831,17 +790,10 @@ public class GTaskManager { } } - /** - * 获取同步账号 - * @return 同步账号名称 - */ public String getSyncAccount() { return GTaskClient.getInstance().getSyncAccount().name; } - /** - * 取消同步操作 - */ public void cancelSync() { mCancelled = true; } diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java index 375c779..cca36f7 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java @@ -23,72 +23,54 @@ import android.content.Intent; import android.os.Bundle; import android.os.IBinder; -/** - * Google Tasks同步服务 - * 负责在后台执行笔记与Google Tasks的同步操作,支持启动、取消同步,并通过广播通知UI同步状态 - */ public class GTaskSyncService extends Service { - // 服务操作类型常量 - public final static String ACTION_STRING_NAME = "sync_action_type"; // 操作类型键名 - public final static int ACTION_START_SYNC = 0; // 启动同步 - public final static int ACTION_CANCEL_SYNC = 1; // 取消同步 - public final static int ACTION_INVALID = 2; // 无效操作 - - // 广播相关常量 - public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; // 广播名称 - public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; // 是否正在同步 - public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; // 同步进度消息 - - private static GTaskASyncTask mSyncTask = null; // 同步任务实例 - private static String mSyncProgress = ""; // 同步进度消息 - - /** - * 启动同步操作 - */ + public final static String ACTION_STRING_NAME = "sync_action_type"; + + public final static int ACTION_START_SYNC = 0; + + public final static int ACTION_CANCEL_SYNC = 1; + + public final static int ACTION_INVALID = 2; + + public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; + + public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; + + public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; + + private static GTaskASyncTask mSyncTask = null; + + private static String mSyncProgress = ""; + private void startSync() { - if (mSyncTask == null) { // 避免重复启动 - // 创建并执行异步同步任务 + if (mSyncTask == null) { mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { - @Override public void onComplete() { - // 同步完成后清理资源 mSyncTask = null; - sendBroadcast(""); // 发送同步完成广播 - stopSelf(); // 停止服务 + sendBroadcast(""); + stopSelf(); } }); - sendBroadcast(""); // 发送同步开始广播 - mSyncTask.execute(); // 执行同步任务 + sendBroadcast(""); + mSyncTask.execute(); } } - /** - * 取消同步操作 - */ private void cancelSync() { if (mSyncTask != null) { - mSyncTask.cancelSync(); // 调用异步任务的取消方法 + mSyncTask.cancelSync(); } } @Override public void onCreate() { - super.onCreate(); - mSyncTask = null; // 服务创建时初始化同步任务 + mSyncTask = null; } - /** - * 处理服务启动命令 - * @param intent 启动意图,包含操作类型 - * @param flags 启动标志 - * @param startId 启动ID - * @return 服务启动模式 - */ @Override public int onStartCommand(Intent intent, int flags, int startId) { Bundle bundle = intent.getExtras(); if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { - // 根据操作类型执行相应操作 switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { case ACTION_START_SYNC: startSync(); @@ -99,75 +81,48 @@ public class GTaskSyncService extends Service { default: break; } - return START_STICKY; // 服务被杀死后尝试重新启动 + return START_STICKY; } return super.onStartCommand(intent, flags, startId); } - /** - * 系统内存不足时的回调 - * 取消正在进行的同步任务以释放资源 - */ @Override public void onLowMemory() { - super.onLowMemory(); if (mSyncTask != null) { - mSyncTask.cancelSync(); // 内存不足时取消同步 + mSyncTask.cancelSync(); } } - @Override public IBinder onBind(Intent intent) { - return null; // 不支持绑定 + return null; } - /** - * 发送同步状态广播 - * @param msg 同步进度消息 - */ public void sendBroadcast(String msg) { mSyncProgress = msg; Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); - intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); // 是否正在同步 - intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); // 同步消息 - sendBroadcast(intent); // 发送广播 + intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); + intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); + sendBroadcast(intent); } - // ==================== 静态调用方法 ==================== - /** - * 静态方法:启动同步服务 - * @param activity 调用此方法的Activity - */ public static void startSync(Activity activity) { - GTaskManager.getInstance().setActivityContext(activity); // 设置Activity上下文 + GTaskManager.getInstance().setActivityContext(activity); Intent intent = new Intent(activity, GTaskSyncService.class); intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); - activity.startService(intent); // 启动服务并传递启动同步的意图 + activity.startService(intent); } - /** - * 静态方法:取消同步服务 - * @param context 上下文 - */ public static void cancelSync(Context context) { Intent intent = new Intent(context, GTaskSyncService.class); intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); - context.startService(intent); // 启动服务并传递取消同步的意图 + context.startService(intent); } - /** - * 静态方法:检查是否正在同步 - * @return 是否正在同步 - */ public static boolean isSyncing() { return mSyncTask != null; } - /** - * 静态方法:获取当前同步进度消息 - * @return 同步进度消息 - */ public static String getProgressString() { return mSyncProgress; } -} \ No newline at end of file +} diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/model/Note.java b/MiNotes-master/app/src/main/java/net/micode/notes/model/Note.java index 2f9e49b..6706cf6 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/model/Note.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/model/Note.java @@ -15,7 +15,6 @@ */ package net.micode.notes.model; - import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentUris; @@ -34,167 +33,114 @@ import net.micode.notes.data.Notes.TextNote; import java.util.ArrayList; -/** - * 笔记模型类 - * 负责管理笔记数据的创建、修改和同步,支持文本笔记和通话记录笔记两种类型 - */ + public class Note { - private ContentValues mNoteDiffValues; // 笔记差异值(待更新的笔记属性) - private NoteData mNoteData; // 笔记关联数据 + private ContentValues mNoteDiffValues; + private NoteData mNoteData; private static final String TAG = "Note"; - /** - * 创建新笔记并返回笔记ID - * @param context 上下文 - * @param folderId 父文件夹ID - * @return 新笔记ID + * Create a new note id for adding a new note to databases */ 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); // 创建时间 - values.put(NoteColumns.MODIFIED_DATE, createdTime); // 修改时间 - values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 笔记类型 - values.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记为本地已修改 - values.put(NoteColumns.PARENT_ID, folderId); // 父文件夹ID - - // 插入数据库并获取URI + values.put(NoteColumns.CREATED_DATE, createdTime); + values.put(NoteColumns.MODIFIED_DATE, createdTime); + values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + values.put(NoteColumns.LOCAL_MODIFIED, 1); + values.put(NoteColumns.PARENT_ID, folderId); Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); long noteId = 0; try { - noteId = Long.valueOf(uri.getPathSegments().get(1)); // 从URI中解析笔记ID + noteId = Long.valueOf(uri.getPathSegments().get(1)); } catch (NumberFormatException e) { - Log.e(TAG, "获取笔记ID错误: " + e.toString()); + Log.e(TAG, "Get note id error :" + e.toString()); noteId = 0; } if (noteId == -1) { - throw new IllegalStateException("错误的笔记ID: " + noteId); + throw new IllegalStateException("Wrong note id:" + noteId); } return noteId; } - /** - * 构造函数 - */ public Note() { mNoteDiffValues = new ContentValues(); mNoteData = new NoteData(); } - /** - * 设置笔记属性值 - * @param key 属性名 - * @param value 属性值 - */ public void setNoteValue(String key, String value) { mNoteDiffValues.put(key, value); - // 标记笔记已修改,并更新修改时间 mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } - /** - * 设置文本笔记数据 - * @param key 数据键名 - * @param value 数据值 - */ public void setTextData(String key, String value) { mNoteData.setTextData(key, value); } - /** - * 设置文本数据ID - * @param id 数据ID - */ public void setTextDataId(long id) { mNoteData.setTextDataId(id); } - /** - * 获取文本数据ID - * @return 文本数据ID - */ public long getTextDataId() { return mNoteData.mTextDataId; } - /** - * 设置通话记录数据ID - * @param id 数据ID - */ public void setCallDataId(long id) { mNoteData.setCallDataId(id); } - /** - * 设置通话记录数据 - * @param key 数据键名 - * @param value 数据值 - */ public void setCallData(String key, String value) { mNoteData.setCallData(key, value); } - /** - * 检查笔记是否有本地修改 - * @return 是否有修改 - */ public boolean isLocalModified() { return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); } - /** - * 将笔记同步到数据库 - * @param context 上下文 - * @param noteId 笔记ID - * @return 同步是否成功 - */ public boolean syncNote(Context context, long noteId) { if (noteId <= 0) { - throw new IllegalArgumentException("错误的笔记ID: " + noteId); + throw new IllegalArgumentException("Wrong note id:" + noteId); } if (!isLocalModified()) { - return true; // 没有修改,无需同步 + return true; } /** - * 理论上,一旦数据发生变化,笔记应该更新LOCAL_MODIFIED和MODIFIED_DATE字段 - * 为了数据安全,即使更新笔记失败,我们仍然更新笔记数据信息 + * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and + * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the + * note data info */ if (context.getContentResolver().update( - ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), - mNoteDiffValues, null, null) == 0) { - Log.e(TAG, "更新笔记错误,这种情况不应该发生"); - // 不返回,继续处理数据更新 + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, + null) == 0) { + Log.e(TAG, "Update note error, should not happen"); + // Do not return, fall through } - mNoteDiffValues.clear(); // 清除已同步的差异值 + mNoteDiffValues.clear(); - // 同步笔记关联数据 - if (mNoteData.isLocalModified() && - (mNoteData.pushIntoContentResolver(context, noteId) == null)) { + if (mNoteData.isLocalModified() + && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { return false; } return true; } - /** - * 笔记关联数据内部类 - * 管理笔记的文本内容和通话记录内容 - */ private class NoteData { - private long mTextDataId; // 文本数据ID - private ContentValues mTextDataValues; // 文本数据值 - private long mCallDataId; // 通话记录数据ID - private ContentValues mCallDataValues; // 通话记录数据值 + private long mTextDataId; + + private ContentValues mTextDataValues; + + private long mCallDataId; + + private ContentValues mCallDataValues; + private static final String TAG = "NoteData"; - /** - * 构造函数 - */ public NoteData() { mTextDataValues = new ContentValues(); mCallDataValues = new ContentValues(); @@ -202,140 +148,106 @@ public class Note { mCallDataId = 0; } - /** - * 检查数据是否有本地修改 - * @return 是否有修改 - */ boolean isLocalModified() { return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; } - /** - * 设置文本数据ID - * @param id 数据ID - */ void setTextDataId(long id) { - if (id <= 0) { - throw new IllegalArgumentException("文本数据ID应该大于0"); + if(id <= 0) { + throw new IllegalArgumentException("Text data id should larger than 0"); } mTextDataId = id; } - /** - * 设置通话记录数据ID - * @param id 数据ID - */ void setCallDataId(long id) { if (id <= 0) { - throw new IllegalArgumentException("通话记录数据ID应该大于0"); + throw new IllegalArgumentException("Call data id should larger than 0"); } mCallDataId = id; } - /** - * 设置通话记录数据 - * @param key 数据键名 - * @param value 数据值 - */ void setCallData(String key, String value) { mCallDataValues.put(key, value); - // 标记笔记已修改,并更新修改时间 mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } - /** - * 设置文本数据 - * @param key 数据键名 - * @param value 数据值 - */ void setTextData(String key, String value) { mTextDataValues.put(key, value); - // 标记笔记已修改,并更新修改时间 mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } - /** - * 将数据推送到内容提供者(数据库) - * @param context 上下文 - * @param noteId 笔记ID - * @return 操作结果URI - */ Uri pushIntoContentResolver(Context context, long noteId) { /** - * 安全检查 + * Check for safety */ if (noteId <= 0) { - throw new IllegalArgumentException("错误的笔记ID: " + noteId); + throw new IllegalArgumentException("Wrong note id:" + noteId); } - ArrayList operationList = new ArrayList<>(); + ArrayList operationList = new ArrayList(); ContentProviderOperation.Builder builder = null; - // 处理文本数据 - if (mTextDataValues.size() > 0) { - mTextDataValues.put(DataColumns.NOTE_ID, noteId); // 设置关联的笔记ID - + if(mTextDataValues.size() > 0) { + mTextDataValues.put(DataColumns.NOTE_ID, noteId); if (mTextDataId == 0) { - // 新建文本数据 mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); - Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mTextDataValues); + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, + mTextDataValues); try { - setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); // 获取新ID + setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); } catch (NumberFormatException e) { - Log.e(TAG, "插入新文本数据失败,笔记ID: " + noteId); + Log.e(TAG, "Insert new text data fail with noteId" + noteId); mTextDataValues.clear(); return null; } } else { - // 更新现有文本数据 - builder = ContentProviderOperation.newUpdate( - ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, mTextDataId)); + builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mTextDataId)); builder.withValues(mTextDataValues); operationList.add(builder.build()); } - mTextDataValues.clear(); // 清除已处理的数据 + mTextDataValues.clear(); } - // 处理通话记录数据 - if (mCallDataValues.size() > 0) { - mCallDataValues.put(DataColumns.NOTE_ID, noteId); // 设置关联的笔记ID - + if(mCallDataValues.size() > 0) { + mCallDataValues.put(DataColumns.NOTE_ID, noteId); if (mCallDataId == 0) { - // 新建通话记录数据 mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); - Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mCallDataValues); + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, + mCallDataValues); try { - setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); // 获取新ID + setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); } catch (NumberFormatException e) { - Log.e(TAG, "插入新通话记录数据失败,笔记ID: " + noteId); + Log.e(TAG, "Insert new call data fail with noteId" + noteId); mCallDataValues.clear(); return null; } } else { - // 更新现有通话记录数据 - builder = ContentProviderOperation.newUpdate( - ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, mCallDataId)); + builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mCallDataId)); builder.withValues(mCallDataValues); operationList.add(builder.build()); } - mCallDataValues.clear(); // 清除已处理的数据 + mCallDataValues.clear(); } - // 批量执行操作 if (operationList.size() > 0) { try { ContentProviderResult[] results = context.getContentResolver().applyBatch( Notes.AUTHORITY, operationList); return (results == null || results.length == 0 || results[0] == null) ? null : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); - } catch (RemoteException | OperationApplicationException e) { - Log.e(TAG, "批量操作异常: " + e.getMessage()); + } catch (RemoteException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + return null; + } catch (OperationApplicationException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); return null; } } return null; } } -} \ No newline at end of file +} diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/model/WorkingNote.java b/MiNotes-master/app/src/main/java/net/micode/notes/model/WorkingNote.java index 49bdaae..be081e4 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/model/WorkingNote.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/model/WorkingNote.java @@ -31,43 +31,37 @@ 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; - // 笔记ID + // Note Id private long mNoteId; - // 笔记内容 + // Note content private String mContent; - // 笔记模式(普通文本或待办列表) + // Note mode private int mMode; - // 提醒日期 + private long mAlertDate; - // 修改日期 + private long mModifiedDate; - // 背景颜色ID + private int mBgColorId; - // 小部件ID + private int mWidgetId; - // 小部件类型 + private int mWidgetType; - // 文件夹ID + private long mFolderId; - // 字体加粗状态 - private boolean mBoldState; - // 上下文 + 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, @@ -76,10 +70,8 @@ public class WorkingNote { DataColumns.DATA2, DataColumns.DATA3, DataColumns.DATA4, - TextNote.BOLD, }; - // 笔记查询投影 public static final String[] NOTE_PROJECTION = new String[] { NoteColumns.PARENT_ID, NoteColumns.ALERTED_DATE, @@ -89,26 +81,27 @@ 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 DATA_BOLD_COLUMN = 7; - // 笔记列索引 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; - /** - * 创建新笔记的构造函数 - * @param context 上下文 - * @param folderId 父文件夹ID - */ + // New note construct private WorkingNote(Context context, long folderId) { mContext = context; mAlertDate = 0; @@ -121,32 +114,23 @@ public class WorkingNote { mWidgetType = Notes.TYPE_WIDGET_INVALIDE; } - /** - * 加载现有笔记的构造函数 - * @param context 上下文 - * @param noteId 笔记ID - * @param folderId 父文件夹ID - */ + // 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, null, null); + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, + null, null); if (cursor != null) { if (cursor.moveToFirst()) { - // 读取笔记基本信息 mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); @@ -156,54 +140,40 @@ public class WorkingNote { } cursor.close(); } else { - Log.e(TAG, "找不到ID为" + mNoteId + "的笔记"); - throw new IllegalArgumentException("无法找到ID为" + mNoteId + "的笔记"); + 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) }, null); + Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, + DataColumns.NOTE_ID + "=?", new String[] { + String.valueOf(mNoteId) + }, null); if (cursor != null) { if (cursor.moveToFirst()) { do { String type = cursor.getString(DATA_MIME_TYPE_COLUMN); if (DataConstants.NOTE.equals(type)) { - // 普通文本笔记数据 mContent = cursor.getString(DATA_CONTENT_COLUMN); mMode = cursor.getInt(DATA_MODE_COLUMN); mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); - mBoldState = (cursor.getInt(DATA_BOLD_COLUMN) == 1 ? true : false); } else if (DataConstants.CALL_NOTE.equals(type)) { - // 通话记录笔记数据 mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); } else { - Log.d(TAG, "错误的笔记类型: " + type); + Log.d(TAG, "Wrong note type with type:" + type); } } while (cursor.moveToNext()); } cursor.close(); } else { - Log.e(TAG, "找不到ID为" + mNoteId + "的笔记数据"); - throw new IllegalArgumentException("无法找到ID为" + mNoteId + "的笔记数据"); + Log.e(TAG, "No data with id:" + mNoteId); + throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId); } } - /** - * 创建空笔记的静态工厂方法 - * @param context 上下文 - * @param folderId 父文件夹ID - * @param widgetId 小部件ID - * @param widgetType 小部件类型 - * @param defaultBgColorId 默认背景颜色ID - * @return 工作笔记实例 - */ public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, int widgetType, int defaultBgColorId) { WorkingNote note = new WorkingNote(context, folderId); @@ -213,33 +183,23 @@ public class WorkingNote { return note; } - /** - * 从数据库加载笔记的静态工厂方法 - * @param context 上下文 - * @param id 笔记ID - * @return 工作笔记实例 - */ public static WorkingNote load(Context context, long id) { return new WorkingNote(context, id, 0); } - /** - * 保存笔记到数据库 - * @return 保存是否成功 - */ public synchronized boolean saveNote() { - if (isWorthSaving()) { // 检查是否值得保存 - if (!existInDatabase()) { // 如果笔记还不存在于数据库中 + if (isWorthSaving()) { + if (!existInDatabase()) { if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { - Log.e(TAG, "创建新笔记失败,ID: " + mNoteId); + Log.e(TAG, "Create new note fail with id:" + mNoteId); return false; } } - 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 @@ -252,20 +212,11 @@ public class WorkingNote { } } - /** - * 检查笔记是否存在于数据库中 - * @return 是否存在 - */ public boolean existInDatabase() { return mNoteId > 0; } - /** - * 判断笔记是否值得保存 - * @return 是否值得保存 - */ private boolean isWorthSaving() { - // 如果笔记已删除,或新建笔记但内容为空,或已存在但无修改,则不值得保存 if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) || (existInDatabase() && !mNote.isLocalModified())) { return false; @@ -274,19 +225,10 @@ public class WorkingNote { } } - /** - * 设置笔记设置变更监听器 - * @param l 监听器 - */ public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { mNoteSettingStatusListener = l; } - /** - * 设置提醒日期 - * @param date 提醒日期 - * @param set 是否设置提醒 - */ public void setAlertDate(long date, boolean set) { if (date != mAlertDate) { mAlertDate = date; @@ -297,22 +239,14 @@ public class WorkingNote { } } - /** - * 标记笔记为已删除 - * @param mark 是否标记为删除 - */ public void markDeleted(boolean mark) { mIsDeleted = mark; if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onWidgetChanged(); + mNoteSettingStatusListener.onWidgetChanged(); } } - /** - * 设置笔记背景颜色 - * @param id 背景颜色ID - */ public void setBgColorId(int id) { if (id != mBgColorId) { mBgColorId = id; @@ -323,10 +257,6 @@ public class WorkingNote { } } - /** - * 设置待办列表模式 - * @param mode 模式 - */ public void setCheckListMode(int mode) { if (mMode != mode) { if (mNoteSettingStatusListener != null) { @@ -337,10 +267,6 @@ public class WorkingNote { } } - /** - * 设置小部件类型 - * @param type 小部件类型 - */ public void setWidgetType(int type) { if (type != mWidgetType) { mWidgetType = type; @@ -348,10 +274,6 @@ public class WorkingNote { } } - /** - * 设置小部件ID - * @param id 小部件ID - */ public void setWidgetId(int id) { if (id != mWidgetId) { mWidgetId = id; @@ -359,10 +281,6 @@ public class WorkingNote { } } - /** - * 设置笔记文本内容 - * @param text 文本内容 - */ public void setWorkingText(String text) { if (!TextUtils.equals(mContent, text)) { mContent = text; @@ -370,161 +288,81 @@ public class WorkingNote { } } - /** - * 将笔记转换为通话记录笔记 - * @param phoneNumber 电话号码 - * @param callDate 通话日期 - */ public void convertToCallNote(String phoneNumber, long callDate) { mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); } - /** - * 检查笔记是否有提醒 - * @return 是否有提醒 - */ public boolean hasClockAlert() { return (mAlertDate > 0 ? true : false); } - /** - * 获取笔记内容 - * @return 笔记内容 - */ public String getContent() { return mContent; } - /** - * 获取提醒日期 - * @return 提醒日期 - */ public long getAlertDate() { return mAlertDate; } - /** - * 获取修改日期 - * @return 修改日期 - */ public long getModifiedDate() { return mModifiedDate; } - /** - * 获取背景颜色资源ID - * @return 背景颜色资源ID - */ public int getBgColorResId() { return NoteBgResources.getNoteBgResource(mBgColorId); } - /** - * 获取背景颜色ID - * @return 背景颜色ID - */ public int getBgColorId() { return mBgColorId; } - /** - * 获取标题栏背景资源ID - * @return 标题栏背景资源ID - */ public int getTitleBgResId() { return NoteBgResources.getNoteTitleBgResource(mBgColorId); } - /** - * 获取待办列表模式 - * @return 待办列表模式 - */ public int getCheckListMode() { return mMode; } - /** - * 获取笔记ID - * @return 笔记ID - */ public long getNoteId() { return mNoteId; } - /** - * 获取文件夹ID - * @return 文件夹ID - */ public long getFolderId() { return mFolderId; } - /** - * 获取小部件ID - * @return 小部件ID - */ public int getWidgetId() { return mWidgetId; } - /** - * 获取小部件类型 - * @return 小部件类型 - */ public int getWidgetType() { return mWidgetType; } - /** - * 设置字体加粗状态 - * @param bold 是否加粗 - */ - public void setBoldState(boolean bold) { - if (mBoldState != bold) { - mBoldState = bold; - mNote.setTextData(TextNote.BOLD, (mBoldState ? "1" : "0")); - mNote.setNoteValue(NoteColumns.SNIPPET, getContent()); - if (mNoteSettingStatusListener != null) { - // 可选:添加一个回调接口方法来通知UI加粗状态已更改 - // mNoteSettingStatusListener.onBoldStateChanged(bold); - } - } - } - - /** - * 获取字体加粗状态 - * @return 字体加粗状态 - */ - public boolean getBoldState() { - return mBoldState; - } - - /** - * 笔记设置变更监听器接口 - */ 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(); /** - * 当在待办列表模式和普通模式之间切换时调用 - * @param oldMode 切换前的模式 - * @param newMode 切换后的模式 + * 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/MiNotes-master/app/src/main/java/net/micode/notes/tool/BackupUtils.java b/MiNotes-master/app/src/main/java/net/micode/notes/tool/BackupUtils.java index 39f6ec4..f214f2a 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/tool/BackupUtils.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/tool/BackupUtils.java @@ -35,12 +35,20 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; - +/** + * 该类提供了将笔记数据备份为文本文件的功能。 + */ public class BackupUtils { private static final String TAG = "BackupUtils"; - // Singleton stuff + // 单例模式实例 private static BackupUtils sInstance; + /** + * 获取BackupUtils的单例实例。 + * + * @param context 应用上下文 + * @return BackupUtils的单例实例 + */ public static synchronized BackupUtils getInstance(Context context) { if (sInstance == null) { sInstance = new BackupUtils(context); @@ -49,43 +57,71 @@ public class BackupUtils { } /** - * Following states are signs to represents backup or restore - * status + * 以下状态常量用于表示备份或恢复操作的状态。 */ - // Currently, the sdcard is not mounted - public static final int STATE_SD_CARD_UNMOUONTED = 0; - // The backup file not exist - public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; - // The data is not well formated, may be changed by other programs - public static final int STATE_DATA_DESTROIED = 2; - // Some run-time exception which causes restore or backup fails - public static final int STATE_SYSTEM_ERROR = 3; - // Backup or restore success - public static final int STATE_SUCCESS = 4; + // 当前SD卡未挂载 + public static final int STATE_SD_CARD_UNMOUONTED = 0; + // 备份文件不存在 + public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; + // 数据格式不正确,可能被其他程序修改 + public static final int STATE_DATA_DESTROIED = 2; + // 运行时异常导致备份或恢复失败 + public static final int STATE_SYSTEM_ERROR = 3; + // 备份或恢复成功 + public static final int STATE_SUCCESS = 4; private TextExport mTextExport; + /** + * 构造函数,初始化TextExport对象。 + * + * @param context 应用上下文 + */ private BackupUtils(Context context) { mTextExport = new TextExport(context); } + /** + * 检查外部存储是否可用。 + * + * @return 如果外部存储已挂载返回true,否则返回false + */ private static boolean externalStorageAvailable() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } + /** + * 将笔记数据导出为文本文件。 + * + * @return 导出操作的状态码 + */ public int exportToText() { return mTextExport.exportToText(); } + /** + * 获取导出的文本文件的名称。 + * + * @return 导出的文本文件的名称 + */ public String getExportedTextFileName() { return mTextExport.mFileName; } + /** + * 获取导出的文本文件的目录。 + * + * @return 导出的文本文件的目录 + */ public String getExportedTextFileDir() { return mTextExport.mFileDirectory; } + /** + * 内部类,负责将笔记数据导出为文本文件。 + */ private static class TextExport { + // 用于查询笔记的投影列 private static final String[] NOTE_PROJECTION = { NoteColumns.ID, NoteColumns.MODIFIED_DATE, @@ -93,12 +129,16 @@ public class BackupUtils { NoteColumns.TYPE }; + // 笔记ID列的索引 private static final int NOTE_COLUMN_ID = 0; + // 笔记修改日期列的索引 private static final int NOTE_COLUMN_MODIFIED_DATE = 1; + // 笔记摘要列的索引 private static final int NOTE_COLUMN_SNIPPET = 2; + // 用于查询笔记数据的投影列 private static final String[] DATA_PROJECTION = { DataColumns.CONTENT, DataColumns.MIME_TYPE, @@ -108,23 +148,36 @@ public class BackupUtils { DataColumns.DATA4, }; + // 笔记数据内容列的索引 private static final int DATA_COLUMN_CONTENT = 0; + // 笔记数据MIME类型列的索引 private static final int DATA_COLUMN_MIME_TYPE = 1; + // 通话记录日期列的索引 private static final int DATA_COLUMN_CALL_DATE = 2; + // 电话号码列的索引 private static final int DATA_COLUMN_PHONE_NUMBER = 4; - private final String [] TEXT_FORMAT; - private static final int FORMAT_FOLDER_NAME = 0; - private static final int FORMAT_NOTE_DATE = 1; - private static final int FORMAT_NOTE_CONTENT = 2; + // 文本导出格式数组 + private final String[] TEXT_FORMAT; + // 文件夹名称格式索引 + private static final int FORMAT_FOLDER_NAME = 0; + // 笔记日期格式索引 + private static final int FORMAT_NOTE_DATE = 1; + // 笔记内容格式索引 + private static final int FORMAT_NOTE_CONTENT = 2; private Context mContext; private String mFileName; private String mFileDirectory; + /** + * 构造函数,初始化文本导出所需的资源。 + * + * @param context 应用上下文 + */ public TextExport(Context context) { TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); mContext = context; @@ -132,28 +185,37 @@ public class BackupUtils { mFileDirectory = ""; } + /** + * 获取指定索引的文本格式。 + * + * @param id 格式索引 + * @return 对应的文本格式 + */ private String getFormat(int id) { return TEXT_FORMAT[id]; } /** - * Export the folder identified by folder id to text + * 将指定文件夹下的笔记导出为文本。 + * + * @param folderId 文件夹ID + * @param ps 打印流 */ private void exportFolderToText(String folderId, PrintStream ps) { - // Query notes belong to this folder + // 查询属于该文件夹的笔记 Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, - NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { - folderId + NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[]{ + folderId }, null); if (notesCursor != null) { if (notesCursor.moveToFirst()) { do { - // Print note's last modified date + // 打印笔记的最后修改日期 ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( mContext.getString(R.string.format_datetime_mdhm), notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // Query data belong to this note + // 查询属于该笔记的数据 String noteId = notesCursor.getString(NOTE_COLUMN_ID); exportNoteToText(noteId, ps); } while (notesCursor.moveToNext()); @@ -163,12 +225,15 @@ public class BackupUtils { } /** - * Export note identified by id to a print stream + * 将指定笔记导出为文本。 + * + * @param noteId 笔记ID + * @param ps 打印流 */ private void exportNoteToText(String noteId, PrintStream ps) { Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, - DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { - noteId + DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[]{ + noteId }, null); if (dataCursor != null) { @@ -176,7 +241,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,11 +250,11 @@ 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)); @@ -205,9 +270,9 @@ public class BackupUtils { } dataCursor.close(); } - // print a line separator between note + // 在笔记之间打印换行符 try { - ps.write(new byte[] { + ps.write(new byte[]{ Character.LINE_SEPARATOR, Character.LETTER_NUMBER }); } catch (IOException e) { @@ -216,7 +281,9 @@ public class BackupUtils { } /** - * Note will be exported as text which is user readable + * 将笔记数据导出为用户可读的文本文件。 + * + * @return 导出操作的状态码 */ public int exportToText() { if (!externalStorageAvailable()) { @@ -229,7 +296,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,9 +307,9 @@ 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) { + if (folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { folderName = mContext.getString(R.string.call_record_folder_name); } else { folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); @@ -257,11 +324,11 @@ public class BackupUtils { folderCursor.close(); } - // Export notes in root's folder + // 导出根文件夹下的笔记 Cursor noteCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, - NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + "=0", null, null); if (noteCursor != null) { @@ -270,7 +337,7 @@ public class BackupUtils { ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( mContext.getString(R.string.format_datetime_mdhm), noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // Query data belong to this note + // 查询属于该笔记的数据 String noteId = noteCursor.getString(NOTE_COLUMN_ID); exportNoteToText(noteId, ps); } while (noteCursor.moveToNext()); @@ -283,7 +350,9 @@ public class BackupUtils { } /** - * Get a print stream pointed to the file {@generateExportedTextFile} + * 获取指向导出文本文件的打印流。 + * + * @return 打印流,如果获取失败返回null */ private PrintStream getExportToTextPrintStream() { File file = generateFileMountedOnSDcard(mContext, R.string.file_path, @@ -310,7 +379,12 @@ public class BackupUtils { } /** - * Generate the text file to store imported data + * 生成用于存储导入数据的文本文件。 + * + * @param context 应用上下文 + * @param filePathResId 文件路径资源ID + * @param fileNameFormatResId 文件名格式资源ID + * @return 生成的文件,如果生成失败返回null */ private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { StringBuilder sb = new StringBuilder(); @@ -339,6 +413,4 @@ public class BackupUtils { return null; } -} - - +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/tool/DataUtils.java b/MiNotes-master/app/src/main/java/net/micode/notes/tool/DataUtils.java index 2a14982..6d9e8c1 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/tool/DataUtils.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/tool/DataUtils.java @@ -34,9 +34,19 @@ import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; import java.util.ArrayList; import java.util.HashSet; - +/** + * 该类提供了与笔记数据操作相关的工具方法,包括批量删除、移动笔记等。 + */ public class DataUtils { public static final String TAG = "DataUtils"; + + /** + * 批量删除笔记。 + * + * @param resolver 内容解析器 + * @param ids 要删除的笔记ID集合 + * @return 如果删除成功返回true,否则返回false + */ public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { if (ids == null) { Log.d(TAG, "the ids is null"); @@ -49,12 +59,12 @@ public class DataUtils { ArrayList operationList = new ArrayList(); for (long id : ids) { - if(id == Notes.ID_ROOT_FOLDER) { + 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)); + .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); operationList.add(builder.build()); } try { @@ -72,6 +82,14 @@ public class DataUtils { return false; } + /** + * 将笔记移动到指定文件夹。 + * + * @param resolver 内容解析器 + * @param id 笔记ID + * @param srcFolderId 源文件夹ID + * @param desFolderId 目标文件夹ID + */ public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { ContentValues values = new ContentValues(); values.put(NoteColumns.PARENT_ID, desFolderId); @@ -80,8 +98,16 @@ public class DataUtils { resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); } + /** + * 批量将笔记移动到指定文件夹。 + * + * @param resolver 内容解析器 + * @param ids 要移动的笔记ID集合 + * @param folderId 目标文件夹ID + * @return 如果移动成功返回true,否则返回false + */ public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids, - long folderId) { + long folderId) { if (ids == null) { Log.d(TAG, "the ids is null"); return true; @@ -90,7 +116,7 @@ public class DataUtils { ArrayList operationList = new ArrayList(); for (long id : ids) { ContentProviderOperation.Builder builder = ContentProviderOperation - .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); + .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); builder.withValue(NoteColumns.PARENT_ID, folderId); builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); operationList.add(builder.build()); @@ -112,18 +138,21 @@ public class DataUtils { } /** - * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} + * 获取用户文件夹的数量(不包括系统文件夹)。 + * + * @param resolver 内容解析器 + * @return 用户文件夹的数量 */ public static int getUserFolderCount(ContentResolver resolver) { - Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, - new String[] { "COUNT(*)" }, + Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, + new String[]{"COUNT(*)"}, NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", - new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, + new String[]{String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, null); int count = 0; - if(cursor != null) { - if(cursor.moveToFirst()) { + if (cursor != null) { + if (cursor.moveToFirst()) { try { count = cursor.getInt(0); } catch (IndexOutOfBoundsException e) { @@ -136,11 +165,19 @@ public class DataUtils { return count; } + /** + * 检查笔记是否在数据库中可见。 + * + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @param type 笔记类型 + * @return 如果笔记可见返回true,否则返回false + */ public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, - new String [] {String.valueOf(type)}, + new String[]{String.valueOf(type)}, null); boolean exist = false; @@ -153,6 +190,13 @@ public class DataUtils { return exist; } + /** + * 检查笔记是否存在于数据库中。 + * + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @return 如果笔记存在返回true,否则返回false + */ public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, null, null, null); @@ -167,6 +211,13 @@ public class DataUtils { return exist; } + /** + * 检查数据是否存在于数据库中。 + * + * @param resolver 内容解析器 + * @param dataId 数据ID + * @return 如果数据存在返回true,否则返回false + */ public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null, null, null, null); @@ -181,15 +232,22 @@ public class DataUtils { return exist; } + /** + * 检查可见文件夹名称是否已存在。 + * + * @param resolver 内容解析器 + * @param name 文件夹名称 + * @return 如果文件夹名称已存在返回true,否则返回false + */ public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + - " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + - " AND " + NoteColumns.SNIPPET + "=?", - new String[] { name }, null); + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.SNIPPET + "=?", + new String[]{name}, null); boolean exist = false; - if(cursor != null) { - if(cursor.getCount() > 0) { + if (cursor != null) { + if (cursor.getCount() > 0) { exist = true; } cursor.close(); @@ -197,11 +255,18 @@ public class DataUtils { return exist; } + /** + * 获取指定文件夹下的笔记小部件属性集合。 + * + * @param resolver 内容解析器 + * @param folderId 文件夹ID + * @return 笔记小部件属性集合 + */ public static HashSet getFolderNoteWidget(ContentResolver resolver, long folderId) { Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, - new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, + new String[]{NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE}, NoteColumns.PARENT_ID + "=?", - new String[] { String.valueOf(folderId) }, + new String[]{String.valueOf(folderId)}, null); HashSet set = null; @@ -224,11 +289,18 @@ public class DataUtils { return set; } + /** + * 根据笔记ID获取通话号码。 + * + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @return 通话号码,如果未找到返回空字符串 + */ public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, - new String [] { CallNote.PHONE_NUMBER }, + new String[]{CallNote.PHONE_NUMBER}, CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?", - new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE }, + new String[]{String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE}, null); if (cursor != null && cursor.moveToFirst()) { @@ -243,12 +315,20 @@ public class DataUtils { return ""; } + /** + * 根据电话号码和通话日期获取笔记ID。 + * + * @param resolver 内容解析器 + * @param phoneNumber 电话号码 + * @param callDate 通话日期 + * @return 笔记ID,如果未找到返回0 + */ public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, - new String [] { CallNote.NOTE_ID }, + new String[]{CallNote.NOTE_ID}, CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL(" - + CallNote.PHONE_NUMBER + ",?)", - new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber }, + + CallNote.PHONE_NUMBER + ",?)", + new String[]{String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber}, null); if (cursor != null) { @@ -264,11 +344,19 @@ public class DataUtils { return 0; } + /** + * 根据笔记ID获取笔记摘要。 + * + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @return 笔记摘要 + * @throws IllegalArgumentException 如果未找到笔记 + */ public static String getSnippetById(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, - new String [] { NoteColumns.SNIPPET }, + new String[]{NoteColumns.SNIPPET}, NoteColumns.ID + "=?", - new String [] { String.valueOf(noteId)}, + new String[]{String.valueOf(noteId)}, null); if (cursor != null) { @@ -282,6 +370,12 @@ public class DataUtils { throw new IllegalArgumentException("Note is not found with id: " + noteId); } + /** + * 格式化笔记摘要,去除首尾空格并截取第一行。 + * + * @param snippet 笔记摘要 + * @return 格式化后的笔记摘要 + */ public static String getFormattedSnippet(String snippet) { if (snippet != null) { snippet = snippet.trim(); @@ -292,4 +386,4 @@ public class DataUtils { } return snippet; } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/tool/GTaskStringUtils.java b/MiNotes-master/app/src/main/java/net/micode/notes/tool/GTaskStringUtils.java index 666b729..3092c81 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/tool/GTaskStringUtils.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/tool/GTaskStringUtils.java @@ -16,98 +16,146 @@ package net.micode.notes.tool; +/** + * 该类包含与Google任务(GTask)相关的JSON字符串常量,用于在应用中处理GTask的JSON数据。 + */ public class GTaskStringUtils { + // 定义GTask JSON数据中的动作ID字段 public final static String GTASK_JSON_ACTION_ID = "action_id"; + // 定义GTask JSON数据中的动作列表字段 public final static String GTASK_JSON_ACTION_LIST = "action_list"; + // 定义GTask JSON数据中的动作类型字段 public final static String GTASK_JSON_ACTION_TYPE = "action_type"; + // 定义GTask JSON数据中的创建动作类型 public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; + // 定义GTask JSON数据中的获取所有动作类型 public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; + // 定义GTask JSON数据中的移动动作类型 public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; + // 定义GTask JSON数据中的更新动作类型 public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; + // 定义GTask JSON数据中的创建者ID字段 public final static String GTASK_JSON_CREATOR_ID = "creator_id"; + // 定义GTask JSON数据中的子实体字段 public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; + // 定义GTask JSON数据中的客户端版本字段 public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; + // 定义GTask JSON数据中的完成状态字段 public final static String GTASK_JSON_COMPLETED = "completed"; + // 定义GTask JSON数据中的当前列表ID字段 public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; + // 定义GTask JSON数据中的默认列表ID字段 public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; + // 定义GTask JSON数据中的删除状态字段 public final static String GTASK_JSON_DELETED = "deleted"; + // 定义GTask JSON数据中的目标列表字段 public final static String GTASK_JSON_DEST_LIST = "dest_list"; + // 定义GTask JSON数据中的目标父级字段 public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; + // 定义GTask JSON数据中的目标父级类型字段 public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; + // 定义GTask JSON数据中的实体增量字段 public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; + // 定义GTask JSON数据中的实体类型字段 public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; + // 定义GTask JSON数据中的获取已删除项字段 public final static String GTASK_JSON_GET_DELETED = "get_deleted"; + // 定义GTask JSON数据中的ID字段 public final static String GTASK_JSON_ID = "id"; + // 定义GTask JSON数据中的索引字段 public final static String GTASK_JSON_INDEX = "index"; + // 定义GTask JSON数据中的最后修改时间字段 public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; + // 定义GTask JSON数据中的最新同步点字段 public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; + // 定义GTask JSON数据中的列表ID字段 public final static String GTASK_JSON_LIST_ID = "list_id"; + // 定义GTask JSON数据中的列表集合字段 public final static String GTASK_JSON_LISTS = "lists"; + // 定义GTask JSON数据中的名称字段 public final static String GTASK_JSON_NAME = "name"; + // 定义GTask JSON数据中的新ID字段 public final static String GTASK_JSON_NEW_ID = "new_id"; + // 定义GTask JSON数据中的备注字段 public final static String GTASK_JSON_NOTES = "notes"; + // 定义GTask JSON数据中的父级ID字段 public final static String GTASK_JSON_PARENT_ID = "parent_id"; + // 定义GTask JSON数据中的前一个兄弟ID字段 public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; + // 定义GTask JSON数据中的结果字段 public final static String GTASK_JSON_RESULTS = "results"; + // 定义GTask JSON数据中的源列表字段 public final static String GTASK_JSON_SOURCE_LIST = "source_list"; + // 定义GTask JSON数据中的任务集合字段 public final static String GTASK_JSON_TASKS = "tasks"; + // 定义GTask JSON数据中的类型字段 public final static String GTASK_JSON_TYPE = "type"; + // 定义GTask JSON数据中的组类型 public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; + // 定义GTask JSON数据中的任务类型 public final static String GTASK_JSON_TYPE_TASK = "TASK"; + // 定义GTask JSON数据中的用户字段 public final static String GTASK_JSON_USER = "user"; + // 定义MIUI文件夹前缀 public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; + // 定义默认文件夹名称 public final static String FOLDER_DEFAULT = "Default"; + // 定义通话记录文件夹名称 public final static String FOLDER_CALL_NOTE = "Call_Note"; + // 定义元数据文件夹名称 public final static String FOLDER_META = "METADATA"; + // 定义元数据头中的GTask ID字段 public final static String META_HEAD_GTASK_ID = "meta_gid"; + // 定义元数据头中的备注字段 public final static String META_HEAD_NOTE = "meta_note"; + // 定义元数据头中的数据字段 public final static String META_HEAD_DATA = "meta_data"; + // 定义元数据备注名称 public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; - -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/tool/ResourceParser.java b/MiNotes-master/app/src/main/java/net/micode/notes/tool/ResourceParser.java index 1ad3ad6..12d56c8 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/tool/ResourceParser.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/tool/ResourceParser.java @@ -22,49 +22,86 @@ import android.preference.PreferenceManager; 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; - public static final int GREEN = 3; - public static final int RED = 4; + // 定义黄色背景颜色的常量 + public static final int YELLOW = 0; + // 定义蓝色背景颜色的常量 + public static final int BLUE = 1; + // 定义白色背景颜色的常量 + public static final int WHITE = 2; + // 定义绿色背景颜色的常量 + public static final int GREEN = 3; + // 定义红色背景颜色的常量 + public static final int RED = 4; + // 定义默认背景颜色为黄色 public static final int BG_DEFAULT_COLOR = YELLOW; - public static final int TEXT_SMALL = 0; - public static final int TEXT_MEDIUM = 1; - public static final int TEXT_LARGE = 2; - public static final int TEXT_SUPER = 3; + // 定义小字体大小的常量 + public static final int TEXT_SMALL = 0; + // 定义中等字体大小的常量 + public static final int TEXT_MEDIUM = 1; + // 定义大字体大小的常量 + public static final int TEXT_LARGE = 2; + // 定义超大字体大小的常量 + public static final int TEXT_SUPER = 3; + // 定义默认字体大小为中等 public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; + /** + * 内部类,用于管理笔记编辑界面的背景资源。 + */ public static class NoteBgResources { - private final static int [] BG_EDIT_RESOURCES = new int [] { - R.drawable.edit_yellow, - R.drawable.edit_blue, - R.drawable.edit_white, - R.drawable.edit_green, - R.drawable.edit_red + // 笔记编辑背景资源数组 + private final static int[] BG_EDIT_RESOURCES = new int[]{ + R.drawable.edit_yellow, + R.drawable.edit_blue, + R.drawable.edit_white, + R.drawable.edit_green, + R.drawable.edit_red }; - private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { - R.drawable.edit_title_yellow, - R.drawable.edit_title_blue, - R.drawable.edit_title_white, - R.drawable.edit_title_green, - R.drawable.edit_title_red + // 笔记编辑标题背景资源数组 + private final static int[] BG_EDIT_TITLE_RESOURCES = new int[]{ + R.drawable.edit_title_yellow, + R.drawable.edit_title_blue, + R.drawable.edit_title_white, + R.drawable.edit_title_green, + R.drawable.edit_title_red }; + /** + * 根据ID获取笔记编辑背景资源。 + * + * @param id 背景资源ID + * @return 对应的背景资源ID + */ public static int getNoteBgResource(int id) { return BG_EDIT_RESOURCES[id]; } + /** + * 根据ID获取笔记编辑标题背景资源。 + * + * @param id 标题背景资源ID + * @return 对应的标题背景资源ID + */ public static int getNoteTitleBgResource(int id) { return BG_EDIT_TITLE_RESOURCES[id]; } } + /** + * 获取默认的背景ID。 + * + * @param context 应用上下文 + * @return 默认的背景ID + */ public static int getDefaultBgId(Context context) { if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { @@ -74,99 +111,162 @@ public class ResourceParser { } } + /** + * 内部类,用于管理笔记列表项的背景资源。 + */ public static class NoteItemBgResources { - private final static int [] BG_FIRST_RESOURCES = new int [] { - R.drawable.list_yellow_up, - R.drawable.list_blue_up, - R.drawable.list_white_up, - R.drawable.list_green_up, - R.drawable.list_red_up + // 笔记列表第一项背景资源数组 + private final static int[] BG_FIRST_RESOURCES = new int[]{ + R.drawable.list_yellow_up, + R.drawable.list_blue_up, + R.drawable.list_white_up, + R.drawable.list_green_up, + R.drawable.list_red_up }; - private final static int [] BG_NORMAL_RESOURCES = new int [] { - R.drawable.list_yellow_middle, - R.drawable.list_blue_middle, - R.drawable.list_white_middle, - R.drawable.list_green_middle, - R.drawable.list_red_middle + // 笔记列表中间项背景资源数组 + private final static int[] BG_NORMAL_RESOURCES = new int[]{ + R.drawable.list_yellow_middle, + R.drawable.list_blue_middle, + R.drawable.list_white_middle, + R.drawable.list_green_middle, + R.drawable.list_red_middle }; - private final static int [] BG_LAST_RESOURCES = new int [] { - R.drawable.list_yellow_down, - R.drawable.list_blue_down, - R.drawable.list_white_down, - R.drawable.list_green_down, - R.drawable.list_red_down, + // 笔记列表最后一项背景资源数组 + private final static int[] BG_LAST_RESOURCES = new int[]{ + R.drawable.list_yellow_down, + R.drawable.list_blue_down, + R.drawable.list_white_down, + R.drawable.list_green_down, + R.drawable.list_red_down, }; - private final static int [] BG_SINGLE_RESOURCES = new int [] { - R.drawable.list_yellow_single, - R.drawable.list_blue_single, - R.drawable.list_white_single, - R.drawable.list_green_single, - R.drawable.list_red_single + // 笔记列表单项背景资源数组 + private final static int[] BG_SINGLE_RESOURCES = new int[]{ + R.drawable.list_yellow_single, + R.drawable.list_blue_single, + R.drawable.list_white_single, + R.drawable.list_green_single, + R.drawable.list_red_single }; + /** + * 根据ID获取笔记列表第一项背景资源。 + * + * @param id 背景资源ID + * @return 对应的背景资源ID + */ public static int getNoteBgFirstRes(int id) { return BG_FIRST_RESOURCES[id]; } + /** + * 根据ID获取笔记列表最后一项背景资源。 + * + * @param id 背景资源ID + * @return 对应的背景资源ID + */ public static int getNoteBgLastRes(int id) { return BG_LAST_RESOURCES[id]; } + /** + * 根据ID获取笔记列表单项背景资源。 + * + * @param id 背景资源ID + * @return 对应的背景资源ID + */ public static int getNoteBgSingleRes(int id) { return BG_SINGLE_RESOURCES[id]; } + /** + * 根据ID获取笔记列表中间项背景资源。 + * + * @param id 背景资源ID + * @return 对应的背景资源ID + */ public static int getNoteBgNormalRes(int id) { return BG_NORMAL_RESOURCES[id]; } + /** + * 获取文件夹背景资源。 + * + * @return 文件夹背景资源ID + */ public static int getFolderBgRes() { return R.drawable.list_folder; } } + /** + * 内部类,用于管理小部件的背景资源。 + */ public static class WidgetBgResources { - private final static int [] BG_2X_RESOURCES = new int [] { - R.drawable.widget_2x_yellow, - R.drawable.widget_2x_blue, - R.drawable.widget_2x_white, - R.drawable.widget_2x_green, - R.drawable.widget_2x_red, + // 2x小部件背景资源数组 + private final static int[] BG_2X_RESOURCES = new int[]{ + R.drawable.widget_2x_yellow, + R.drawable.widget_2x_blue, + R.drawable.widget_2x_white, + R.drawable.widget_2x_green, + R.drawable.widget_2x_red, }; + /** + * 根据ID获取2x小部件背景资源。 + * + * @param id 背景资源ID + * @return 对应的背景资源ID + */ public static int getWidget2xBgResource(int id) { return BG_2X_RESOURCES[id]; } - private final static int [] BG_4X_RESOURCES = new int [] { - R.drawable.widget_4x_yellow, - R.drawable.widget_4x_blue, - R.drawable.widget_4x_white, - R.drawable.widget_4x_green, - R.drawable.widget_4x_red + // 4x小部件背景资源数组 + private final static int[] BG_4X_RESOURCES = new int[]{ + R.drawable.widget_4x_yellow, + R.drawable.widget_4x_blue, + R.drawable.widget_4x_white, + R.drawable.widget_4x_green, + R.drawable.widget_4x_red }; + /** + * 根据ID获取4x小部件背景资源。 + * + * @param id 背景资源ID + * @return 对应的背景资源ID + */ public static int getWidget4xBgResource(int id) { return BG_4X_RESOURCES[id]; } } + /** + * 内部类,用于管理文本外观资源。 + */ public static class TextAppearanceResources { - private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { - R.style.TextAppearanceNormal, - R.style.TextAppearanceMedium, - R.style.TextAppearanceLarge, - R.style.TextAppearanceSuper + // 文本外观资源数组 + private final static int[] TEXTAPPEARANCE_RESOURCES = new int[]{ + R.style.TextAppearanceNormal, + R.style.TextAppearanceMedium, + R.style.TextAppearanceLarge, + R.style.TextAppearanceSuper }; + /** + * 根据ID获取文本外观资源。 + * 如果ID超出资源数组长度,返回默认字体大小对应的资源。 + * + * @param id 文本外观资源ID + * @return 对应的文本外观资源ID + */ public static int getTexAppearanceResource(int id) { /** - * HACKME: Fix bug of store the resource id in shared preference. - * The id may larger than the length of resources, in this case, - * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} + * HACKME: 修复将资源ID存储在共享偏好中的错误。 + * 如果ID大于资源数组的长度,返回默认字体大小对应的资源。 */ if (id >= TEXTAPPEARANCE_RESOURCES.length) { return BG_DEFAULT_FONT_SIZE; @@ -174,8 +274,13 @@ public class ResourceParser { return TEXTAPPEARANCE_RESOURCES[id]; } + /** + * 获取文本外观资源数组的长度。 + * + * @return 文本外观资源数组的长度 + */ public static int getResourcesSize() { return TEXTAPPEARANCE_RESOURCES.length; } } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java index 85723be..690c87a 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java @@ -40,20 +40,25 @@ 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) { super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); - + requestWindowFeature(Window.FEATURE_NO_TITLE); // 无标题栏 + + // 设置窗口属性,在锁屏时也能显示 final Window win = getWindow(); win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + // 如果屏幕关闭,则唤醒屏幕 if (!isScreenOn()) { win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON @@ -61,11 +66,12 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); } + // 从Intent获取笔记数据 Intent intent = getIntent(); - try { mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); + // 截取过长文本 mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) : mSnippet; @@ -75,50 +81,49 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } mPlayer = new MediaPlayer(); + // 检查笔记是否仍然存在 if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { - showActionDialog(); - playAlarmSound(); + showActionDialog(); // 显示操作对话框 + playAlarmSound(); // 播放闹铃声音 } else { - finish(); + finish(); // 笔记不存在则结束活动 } } + // 检查屏幕是否亮着 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 { mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); } + + // 准备并播放声音 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 + } catch (Exception e) { e.printStackTrace(); } } + // 显示操作对话框 private void showActionDialog() { AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setTitle(R.string.app_name); @@ -130,9 +135,10 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD dialog.show().setOnDismissListener(this); } + // 对话框按钮点击事件 public void onClick(DialogInterface dialog, int which) { switch (which) { - case DialogInterface.BUTTON_NEGATIVE: + case DialogInterface.BUTTON_NEGATIVE: // 进入笔记 Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, mNoteId); @@ -143,11 +149,13 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + // 对话框关闭事件 public void onDismiss(DialogInterface dialog) { - stopAlarmSound(); - finish(); + stopAlarmSound(); // 停止闹铃 + finish(); // 结束活动 } + // 停止闹铃声音 private void stopAlarmSound() { if (mPlayer != null) { mPlayer.stop(); @@ -155,4 +163,4 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD mPlayer = null; } } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java index f221202..43c108f 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java @@ -28,19 +28,23 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; +/** + * 初始化闹钟的广播接收器,通常在设备启动后运行 + */ public class AlarmInitReceiver extends BroadcastReceiver { - + // 查询笔记的投影(需要哪些列) private static final String [] PROJECTION = new String [] { NoteColumns.ID, NoteColumns.ALERTED_DATE }; - private static final int COLUMN_ID = 0; - private static final int COLUMN_ALERTED_DATE = 1; + private static final int COLUMN_ID = 0; // ID列索引 + private static final int COLUMN_ALERTED_DATE = 1; // 提醒日期列索引 @Override public void onReceive(Context context, Intent intent) { long currentDate = System.currentTimeMillis(); + // 查询所有设置了未来提醒的笔记 Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, @@ -51,8 +55,10 @@ public class AlarmInitReceiver extends BroadcastReceiver { if (c.moveToFirst()) { do { long alertDate = c.getLong(COLUMN_ALERTED_DATE); + // 为每个笔记创建闹钟 Intent sender = new Intent(context, AlarmReceiver.class); - sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); + sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, + c.getLong(COLUMN_ID))); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); AlarmManager alermManager = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); @@ -62,4 +68,4 @@ public class AlarmInitReceiver extends BroadcastReceiver { c.close(); } } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java index 54e503b..4851eb1 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java @@ -20,9 +20,13 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +/** + * 闹钟触发时接收广播并启动闹钟提醒活动 + */ public class AlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + // 启动闹钟提醒活动 intent.setClass(context, AlarmAlertActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/DateTimePicker.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/DateTimePicker.java index bd05aae..a38b1b4 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/DateTimePicker.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/DateTimePicker.java @@ -21,93 +21,136 @@ 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; +/** + * DateTimePicker类继承自FrameLayout,用于创建一个日期和时间选择器, + * 允许用户通过滚动选择日期和时间,并提供了日期和时间变化的回调接口。 + */ 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; + // 日期选择器的最小取值 private static final int DATE_SPINNER_MIN_VAL = 0; + // 日期选择器的最大取值 private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; + // 24小时制小时选择器的最小取值 private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + // 24小时制小时选择器的最大取值 private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + // 12小时制小时选择器的最小取值 private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; + // 12小时制小时选择器的最大取值 private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; + // 分钟选择器的最小取值 private static final int MINUT_SPINNER_MIN_VAL = 0; + // 分钟选择器的最大取值 private static final int MINUT_SPINNER_MAX_VAL = 59; + // 上午/下午选择器的最小取值 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; - + // 是否为24小时制 private boolean mIs24HourView; - + // 是否启用 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) { + // 根据新值和旧值的差值更新日期 mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); + // 更新日期选择器的显示 updateDateControl(); + // 调用日期和时间变化的回调方法 onDateTimeChanged(); } }; + /** + * 小时选择器值变化监听器,当小时选择器的值发生变化时调用 + */ private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { boolean isDateChanged = false; + // 创建一个新的日历对象 Calendar cal = Calendar.getInstance(); if (!mIs24HourView) { if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { + // 从下午11点到凌晨12点,日期加1 cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, 1); isDateChanged = true; } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + // 从凌晨12点到上午11点,日期减1 cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, -1); isDateChanged = true; } if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + // 切换上午/下午状态 mIsAm = !mIsAm; + // 更新上午/下午选择器的显示 updateAmPmControl(); } } else { if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { + // 从23点到0点,日期加1 cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, 1); isDateChanged = true; } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { + // 从0点到23点,日期减1 cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, -1); isDateChanged = true; } } + // 计算新的小时数 int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); + // 设置日历对象的小时数 mDate.set(Calendar.HOUR_OF_DAY, newHour); + // 调用日期和时间变化的回调方法 onDateTimeChanged(); if (isDateChanged) { + // 如果日期发生变化,更新日历对象的年、月、日 setCurrentYear(cal.get(Calendar.YEAR)); setCurrentMonth(cal.get(Calendar.MONTH)); setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); @@ -115,376 +158,347 @@ public class DateTimePicker extends FrameLayout { } }; + /** + * 分钟选择器值变化监听器,当分钟选择器的值发生变化时调用 + */ private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 获取分钟选择器的最小和最大取值 int minValue = mMinuteSpinner.getMinValue(); int maxValue = mMinuteSpinner.getMaxValue(); int offset = 0; if (oldVal == maxValue && newVal == minValue) { + // 从59分钟到0分钟,小时数加1 offset += 1; } else if (oldVal == minValue && newVal == maxValue) { + // 从0分钟到59分钟,小时数减1 offset -= 1; } if (offset != 0) { + // 根据偏移量更新日历对象的小时数 mDate.add(Calendar.HOUR_OF_DAY, offset); + // 更新小时选择器的显示 mHourSpinner.setValue(getCurrentHour()); + // 更新日期选择器的显示 updateDateControl(); int newHour = getCurrentHourOfDay(); if (newHour >= HOURS_IN_HALF_DAY) { + // 如果小时数大于等于12,设置为下午 mIsAm = false; + // 更新上午/下午选择器的显示 updateAmPmControl(); } else { + // 如果小时数小于12,设置为上午 mIsAm = true; + // 更新上午/下午选择器的显示 updateAmPmControl(); } } + // 设置日历对象的分钟数 mDate.set(Calendar.MINUTE, newVal); + // 调用日期和时间变化的回调方法 onDateTimeChanged(); } }; + /** + * 上午/下午选择器值变化监听器,当上午/下午选择器的值发生变化时调用 + */ private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 切换上午/下午状态 mIsAm = !mIsAm; if (mIsAm) { + // 从下午切换到上午,小时数减12 mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); } else { + // 从上午切换到下午,小时数加12 mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); } + // 更新上午/下午选择器的显示 + updateAmPmControl显示 updateAmPmControl(); + // 调用日期和时间变化的回调方法 onDateTimeChanged(); } }; + /** + * 日期和时间变化监听器接口,用于监听日期和时间的变化 + */ public interface OnDateTimeChangedListener { + /** + * 当日期和时间发生变化时调用 + * @param view 日期和时间选择器视图 + * @param year 年份 + * @param month 月份 + * @param dayOfMonth 日期 + * @param hourOfDay 小时数 + * @param minute 分钟数 + */ void onDateTimeChanged(DateTimePicker view, int year, int month, int dayOfMonth, int hourOfDay, int minute); } + /** + * 构造函数,使用当前时间初始化日期和时间选择器 + * @param context 上下文对象 + */ public DateTimePicker(Context context) { this(context, System.currentTimeMillis()); } + /** + * 构造函数,使用指定的时间初始化日期和时间选择器 + * @param context 上下文对象 + * @param date 指定的时间(毫秒) + */ public DateTimePicker(Context context, long date) { this(context, date, DateFormat.is24HourFormat(context)); } + /** + * 构造函数,使用指定的时间和是否为24小时制初始化日期和时间选择器 + * @param context 上下文对象 + * @param date 指定的时间(毫秒) + * @param is24HourView 是否为24小时制 + */ public DateTimePicker(Context context, long date, boolean is24HourView) { super(context); + // 初始化日历对象 mDate = Calendar.getInstance(); mInitialising = true; + // 判断当前小时数是否大于等于12,确定是否为下午 mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + // 加载布局 inflate(context, R.layout.datetime_picker, this); + // 获取日期选择器 mDateSpinner = (NumberPicker) findViewById(R.id.date); + // 设置日期选择器的最小和最大取值 mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); + // 设置日期选择器的值变化监听器 mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + // 获取小时选择器 mHourSpinner = (NumberPicker) findViewById(R.id.hour); + // 设置小时选择器的值变化监听器 mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + // 获取分钟选择器 mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); + // 设置分钟选择器的最小和最大取值 mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); + // 设置分钟选择器的长按更新间隔 mMinuteSpinner.setOnLongPressUpdateInterval(100); + // 设置分钟选择器的值变化监听器 mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); + // 获取上午/下午显示字符串数组 String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); + // 获取上午/下午选择器 mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); + // 设置上午/下午选择器的最小和最大取值 mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); + // 设置上午/下午选择器的显示值 mAmPmSpinner.setDisplayedValues(stringsForAmPm); + // 设置上午/下午选择器的值变化监听器 mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); - // update controls to initial state + // 更新日期选择器的显示 updateDateControl(); + // 更新小时选择器的显示 updateHourControl(); + // 更新上午/下午选择器的显示 updateAmPmControl(); + // 设置是否为24小时制 set24HourView(is24HourView); - // set to current time + // 设置当前日期和时间 setCurrentDate(date); + // 设置启用状态 setEnabled(isEnabled()); - // set the content descriptions + // 设置内容描述 mInitialising = false; } + /** + * 设置启用状态 + * @param enabled 是否启用 + */ @Override public void setEnabled(boolean enabled) { if (mIsEnabled == enabled) { return; } super.setEnabled(enabled); + // 设置日期选择器的启用状态 mDateSpinner.setEnabled(enabled); + // 设置分钟选择器的启用状态 mMinuteSpinner.setEnabled(enabled); + // 设置小时选择器的启用状态 mHourSpinner.setEnabled(enabled); + // 设置上午/下午选择器的启用状态 mAmPmSpinner.setEnabled(enabled); mIsEnabled = enabled; } + /** + * 获取启用状态 + * @return 是否启用 + */ @Override public boolean isEnabled() { return mIsEnabled; } /** - * 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(); cal.setTimeInMillis(date); + // 设置当前日期和时间的年、月、日、小时、分钟 setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); } /** - * 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 + * 设置当前日期和时间 + * @param year 年份 + * @param month 月份 */ - public void setCurrentDate(int year, int month, - int dayOfMonth, int hourOfDay, int minute) { - setCurrentYear(year); - setCurrentMonth(month); - setCurrentDay(dayOfMonth); - updateHourControl(); - setCurrentHour(hourOfDay); - setCurrentMinute(minute); - } - - /** - * 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()) { - return; - } + public void setCurrentDate(int year, int month, int dayOfMonth, int hourOfDay, int minute) { mDate.set(Calendar.YEAR, year); - updateDateControl(); - onDateTimeChanged(); - } + mDate.set(Calendar.MONTH, month); + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + mDate.set(Calendar.MINUTE, minute); + mDate.set(Calendar.SECOND, 0); + mDate.set(Calendar.MILLISECOND, 0); - /** - * Get current month in the year - * - * @return The current month in the year - */ - public int getCurrentMonth() { - return mDate.get(Calendar.MONTH); + updateDateControl(); + updateHourControl(); + updateAmPmControl(); } /** - * Set current month in the year - * - * @param month The month in the year + * 更新日期选择器的显示 */ - public void setCurrentMonth(int month) { - if (!mInitialising && month == getCurrentMonth()) { - return; - } - mDate.set(Calendar.MONTH, month); - updateDateControl(); - onDateTimeChanged(); + private void updateDateControl() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(mDate.getTimeInMillis()); + int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); + mDateSpinner.setValue(dayOfWeek - 1); } /** - * Get current day of the month - * - * @return The day of the month + * 更新小时选择器的显示 */ - public int getCurrentDay() { - return mDate.get(Calendar.DAY_OF_MONTH); + private void updateHourControl() { + int hour = getCurrentHour(); + mHourSpinner.setValue(hour); } /** - * Set current day of the month - * - * @param dayOfMonth The day of the month + * 更新上午/下午选择器的显示 */ - public void setCurrentDay(int dayOfMonth) { - if (!mInitialising && dayOfMonth == getCurrentDay()) { - return; - } - mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); - updateDateControl(); - onDateTimeChanged(); + private void updateAmPmControl() { + mAmPmSpinner.setValue(mIsAm ? 0 : 1); } /** - * Get current hour in 24 hour mode, in the range (0~23) - * @return The current hour in 24 hour mode + * 获取当前小时数 + * @return 当前小时数 */ - public int getCurrentHourOfDay() { - return mDate.get(Calendar.HOUR_OF_DAY); - } - private int getCurrentHour() { - if (mIs24HourView){ - return getCurrentHourOfDay(); - } else { - int hour = getCurrentHourOfDay(); - if (hour > HOURS_IN_HALF_DAY) { - return hour - HOURS_IN_HALF_DAY; - } else { - return hour == 0 ? HOURS_IN_HALF_DAY : hour; - } + int hour = mDate.get(Calendar.HOUR); + if (hour == 0) { + hour = 12; } + return hour; } /** - * Set current hour in 24 hour mode, in the range (0~23) - * - * @param hourOfDay + * 获取当前小时数(24小时制) + * @return 当前小时数(24小时制) */ - public void setCurrentHour(int hourOfDay) { - if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { - return; - } - mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); - if (!mIs24HourView) { - if (hourOfDay >= HOURS_IN_HALF_DAY) { - mIsAm = false; - if (hourOfDay > HOURS_IN_HALF_DAY) { - hourOfDay -= HOURS_IN_HALF_DAY; - } else if (hourOfDay == HOURS_IN_HALF_DAY) { - hourOfDay = HOURS_IN_HALF_DAY; - } - } else { - mIsAm = true; - if (hourOfDay == 0) { - hourOfDay = HOURS_IN_HALF_DAY; - } - } - updateAmPmControl(); - mHourSpinner.setValue(hourOfDay); - } else { - mHourSpinner.setValue(hourOfDay); - } - onDateTimeChanged(); + private int getCurrentHourOfDay() { + return mDate.get(Calendar.HOUR_OF_DAY); } /** - * Get currentMinute - * - * @return The Current Minute + * 设置当前年份 + * @param year 年份 */ - public int getCurrentMinute() { - return mDate.get(Calendar.MINUTE); + private void setCurrentYear(int year) { + mDate.set(Calendar.YEAR, year); } /** - * Set current minute + * 设置当前月份 + * @param month 月份 */ - public void setCurrentMinute(int minute) { - if (!mInitialising && minute == getCurrentMinute()) { - return; - } - mMinuteSpinner.setValue(minute); - mDate.set(Calendar.MINUTE, minute); - onDateTimeChanged(); + private void setCurrentMonth(int month) { + mDate.set(Calendar.MONTH, month); } /** - * @return true if this is in 24 hour view else false. + * 设置当前日期 + * @param dayOfMonth 日期 */ - public boolean is24HourView () { - return mIs24HourView; + private void setCurrentDay(int dayOfMonth) { + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); } /** - * Set whether in 24 hour or AM/PM mode. - * - * @param is24HourView True for 24 hour mode. False for AM/PM mode. + * 设置是否为24小时制 + * @param is24HourView 是否为24小时制 */ public void set24HourView(boolean is24HourView) { - if (mIs24HourView == is24HourView) { - return; - } mIs24HourView = is24HourView; - mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); - int hour = getCurrentHourOfDay(); - updateHourControl(); - setCurrentHour(hour); - updateAmPmControl(); - } - - private void updateDateControl() { - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(mDate.getTimeInMillis()); - cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); - mDateSpinner.setDisplayedValues(null); - for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { - cal.add(Calendar.DAY_OF_YEAR, 1); - mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); - } - mDateSpinner.setDisplayedValues(mDateDisplayValues); - mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); - mDateSpinner.invalidate(); - } - - private void updateAmPmControl() { - if (mIs24HourView) { - mAmPmSpinner.setVisibility(View.GONE); - } else { - int index = mIsAm ? Calendar.AM : Calendar.PM; - mAmPmSpinner.setValue(index); - mAmPmSpinner.setVisibility(View.VISIBLE); - } - } - - private void updateHourControl() { if (mIs24HourView) { mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); + mAmPmSpinner.setVisibility(View.GONE); } else { mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); + mAmPmSpinner.setVisibility(View.VISIBLE); } + updateHourControl(); } /** - * 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()); + if (!mInitialising && mOnDateTimeChangedListener != null) { + mOnDateTimeChangedListener.onDateTimeChanged(this, mDate.get(Calendar.YEAR), + mDate.get(Calendar.MONTH), mDate.get(Calendar.DAY_OF_MONTH), + mDate.get(Calendar.HOUR_OF_DAY), mDate.get(Calendar.MINUTE)); } } -} + + /** + * 设置日期和时间变化监听器 + * @param listener 监听器对象 + */ + public void setOnDateTimeChangedListener(OnDateTimeChangedListener listener) { + mOnDateTimeChangedListener {insert\_element\_6\_PSBsaXN0ZW5lcjsKICAgIH0KfQpgYGAKCg==}### \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java index 2c47ba4..3fe2a94 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java @@ -29,13 +29,16 @@ import android.content.DialogInterface.OnClickListener; import android.text.format.DateFormat; import android.text.format.DateUtils; +/** + * 日期时间选择对话框 + */ public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + private Calendar mDate = Calendar.getInstance(); // 当前日期 + private boolean mIs24HourView; // 是否24小时制 + private OnDateTimeSetListener mOnDateTimeSetListener; // 日期时间设置回调 + private DateTimePicker mDateTimePicker; // 日期时间选择器 - private Calendar mDate = Calendar.getInstance(); - private boolean mIs24HourView; - private OnDateTimeSetListener mOnDateTimeSetListener; - private DateTimePicker mDateTimePicker; - + // 日期时间设置回调接口 public interface OnDateTimeSetListener { void OnDateTimeSet(AlertDialog dialog, long date); } @@ -43,48 +46,56 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener 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) { + // 更新日期对象 mDate.set(Calendar.YEAR, year); mDate.set(Calendar.MONTH, month); mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); mDate.set(Calendar.MINUTE, minute); - updateTitle(mDate.getTimeInMillis()); + 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; + int flag = 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()); } } - } \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/DropdownMenu.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/DropdownMenu.java index 613dc74..201825c 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/DropdownMenu.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/DropdownMenu.java @@ -27,17 +27,22 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import net.micode.notes.R; +/** + * 下拉菜单组件 + */ public class DropdownMenu { - private Button mButton; - private PopupMenu mPopupMenu; - private Menu mMenu; + private Button mButton; // 触发按钮 + private PopupMenu mPopupMenu; // 弹出菜单 + private Menu mMenu; // 菜单对象 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 +50,20 @@ public class DropdownMenu { }); } + // 设置菜单项点击监听器 public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { if (mPopupMenu != null) { mPopupMenu.setOnMenuItemClickListener(listener); } } + // 查找菜单项 public MenuItem findItem(int id) { return mMenu.findItem(id); } + // 设置按钮文本 public void setTitle(CharSequence title) { mButton.setText(title); } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java index 96b77da..865eb81 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java @@ -28,53 +28,97 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; - +/** + * FoldersListAdapter类继承自CursorAdapter,用于将数据库中的文件夹数据绑定到ListView的每个项上, + * 显示文件夹的名称。 + */ public class FoldersListAdapter extends CursorAdapter { + // 查询数据库时使用的投影列数组,指定要查询的列 public static final String [] PROJECTION = { NoteColumns.ID, NoteColumns.SNIPPET }; + // 定义各列在投影数组中的索引,方便从游标中获取数据 public static final int ID_COLUMN = 0; public static final int NAME_COLUMN = 1; + /** + * 构造函数,初始化适配器 + * @param context 上下文对象 + * @param c 数据库游标 + */ public FoldersListAdapter(Context context, Cursor c) { super(context, c); // TODO Auto-generated constructor stub } + /** + * 创建新的列表项视图 + * @param context 上下文对象 + * @param cursor 数据库游标 + * @param parent 父视图 + * @return 新的列表项视图 + */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new FolderListItem(context); } + /** + * 将数据绑定到列表项视图上 + * @param view 列表项视图 + * @param context 上下文对象 + * @param cursor 数据库游标 + */ @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 { + // 显示文件夹名称的TextView private TextView mName; + /** + * 构造函数,初始化文件夹列表项视图 + * @param context 上下文对象 + */ public FolderListItem(Context context) { super(context); + // 加载文件夹列表项的布局 inflate(context, R.layout.folder_list_item, this); + // 获取显示文件夹名称的TextView mName = (TextView) findViewById(R.id.tv_folder_name); } + /** + * 将文件夹名称绑定到TextView上 + * @param name 文件夹名称 + */ public void bind(String name) { - mName.setText(name); - } - } + mName.setText(name){insert\_element\_7\_OwogICAgICAgIH0KICAgIH0KfQo=}``` -} +### \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java index 2f34290..ee95902 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -52,7 +52,6 @@ import android.widget.LinearLayout; import android.widget.PopupMenu; import android.widget.TextView; import android.widget.Toast; -import android.widget.ImageButton; import net.micode.notes.R; import net.micode.notes.data.Notes; @@ -73,19 +72,27 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; - +/** + * NoteEditActivity类用于处理笔记的编辑操作,包括初始化界面、加载笔记数据、处理用户输入等。 + */ public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { + + /** + * 头部视图的ViewHolder类,用于存储头部视图的控件引用 + */ private class HeadViewHolder { + // 显示修改日期的TextView public TextView tvModified; - + // 显示提醒图标的ImageView public ImageView ivAlertIcon; - + // 显示提醒日期的TextView public TextView tvAlertDate; - + // 设置背景颜色的ImageView public ImageView ibSetBgColor; } + // 背景颜色选择按钮与颜色ID的映射 private static final Map sBgSelectorBtnsMap = new HashMap(); static { sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); @@ -95,6 +102,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); } + // 背景颜色ID与选择状态图标ID的映射 private static final Map sBgSelectorSelectionMap = new HashMap(); static { sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); @@ -104,6 +112,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } + // 字体大小选择按钮与字体大小ID的映射 private static final Map sFontSizeBtnsMap = new HashMap(); static { sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); @@ -112,6 +121,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); } + // 字体大小ID与选择状态图标ID的映射 private static final Map sFontSelectorSelectionMap = new HashMap(); static { sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); @@ -122,57 +132,72 @@ public class NoteEditActivity extends Activity implements OnClickListener, private static final String TAG = "NoteEditActivity"; + // 头部视图的ViewHolder private HeadViewHolder mNoteHeaderHolder; - + // 头部视图面板 private View mHeadViewPanel; - + // 背景颜色选择器视图 private View mNoteBgColorSelector; - + // 字体大小选择器视图 private View mFontSizeSelector; - + // 笔记编辑器 private EditText mNoteEditor; - + // 笔记编辑器面板 private View mNoteEditorPanel; - + // 工作笔记对象 private WorkingNote mWorkingNote; - + // 共享偏好设置 private SharedPreferences mSharedPrefs; + // 字体大小ID 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; + /** + * 活动创建时调用,初始化界面和资源 + * @param savedInstanceState 保存的实例状态 + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // 设置布局 this.setContentView(R.layout.note_edit); + // 如果保存的实例状态为空且初始化活动状态失败,则关闭活动 if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } + // 初始化资源 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 + * 恢复活动状态时调用,恢复之前保存的状态 + * @param savedInstanceState 保存的实例状态 */ @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { + // 创建一个查看笔记的意图 Intent intent = new Intent(Intent.ACTION_VIEW); + // 从保存的状态中获取笔记ID intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); + // 初始化活动状态 if (!initActivityState(intent)) { finish(); return; @@ -181,31 +206,36 @@ 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())) { + // 获取笔记ID 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); + // 显示笔记不存在的提示信息 showToast(R.string.error_note_not_exist); finish(); return false; } else { + // 加载笔记数据 mWorkingNote = WorkingNote.load(this, noteId); if (mWorkingNote == null) { Log.e(TAG, "load note failed with note id" + noteId); @@ -213,20 +243,25 @@ 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 + // 新建笔记 + // 获取文件夹ID long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); + // 获取小部件ID int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + // 获取小部件类型 int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, Notes.TYPE_WIDGET_INVALIDE); + // 获取背景资源ID 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) { @@ -234,725 +269,19 @@ public class NoteEditActivity extends Activity implements OnClickListener, Log.w(TAG, "The call record number is null"); } long noteId = 0; + // 根据电话号码和通话日期获取笔记ID if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), phoneNumber, callDate)) > 0) { + // 加载通话记录笔记 mWorkingNote = WorkingNote.load(this, noteId); if (mWorkingNote == null) { Log.e(TAG, "load call note failed with note id" + noteId); finish(); return false; } - } else { - mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, - widgetType, bgResId); - mWorkingNote.convertToCallNote(phoneNumber, callDate); } - } else { - mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, - bgResId); } - - getWindow().setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE - | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } else { - Log.e(TAG, "Intent not specified action, should not support"); - finish(); - return false; } - mWorkingNote.setOnSettingStatusChangedListener(this); return true; } - - @Override - protected void onResume() { - super.onResume(); - initNoteScreen(); - } - - private void initNoteScreen() { - mNoteEditor.setTextAppearance(this, TextAppearanceResources - .getTexAppearanceResource(mFontSizeId)); - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - switchToListMode(mWorkingNote.getContent()); - } else { - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); - mNoteEditor.setSelection(mNoteEditor.getText().length()); - } - applyTextStyle(); - 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 { - mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( - mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); - } - mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); - mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); - } else { - mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); - mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); - }; - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - initActivityState(intent); - } - - @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 - */ - if (!mWorkingNote.existInDatabase()) { - saveNote(); - } - outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); - Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); - } - - @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); - return true; - } - return super.dispatchTouchEvent(ev); - } - - private boolean inRangeOfView(View view, MotionEvent ev) { - int []location = new int[2]; - view.getLocationOnScreen(location); - int x = location[0]; - int y = location[1]; - if (ev.getX() < x - || ev.getX() > (x + view.getWidth()) - || ev.getY() < y - || ev.getY() > (y + view.getHeight())) { - return false; - } - return true; - } - - private void initResources() { - mHeadViewPanel = findViewById(R.id.note_title); - mNoteHeaderHolder = new HeadViewHolder(); - mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); - mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); - mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); - mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); - mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); - - // 初始化菜单按钮 - ImageButton menuMore = (ImageButton) findViewById(R.id.menu_more); - menuMore.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - OnOpenMenu(v); - } - }); - - 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} - */ - if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { - mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; - } - mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); - } - - @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); - if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { - intent.setClass(this, NoteWidgetProvider_2x.class); - } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { - intent.setClass(this, NoteWidgetProvider_4x.class); - } else { - Log.e(TAG, "Unspported widget type"); - return; - } - - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { - mWorkingNote.getWidgetId() - }); - - sendBroadcast(intent); - setResult(RESULT_OK, intent); - } - - 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)) { - findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - View.GONE); - mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); - mNoteBgColorSelector.setVisibility(View.GONE); - } 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 { - mNoteEditor.setTextAppearance(this, - TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); - } - mFontSizeSelector.setVisibility(View.GONE); - } - } - - @Override - public void onBackPressed() { - if(clearSettingState()) { - return; - } - - saveNote(); - super.onBackPressed(); - } - - private boolean clearSettingState() { - if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { - mNoteBgColorSelector.setVisibility(View.GONE); - return true; - } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { - mFontSizeSelector.setVisibility(View.GONE); - return true; - } - return false; - } - - public void onBackgroundColorChanged() { - findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - View.VISIBLE); - mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); - mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - if (isFinishing()) { - return true; - } - 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 { - menu.findItem(R.id.menu_delete_remind).setVisible(false); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_new_note: - 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); - builder.setMessage(getString(R.string.alert_message_delete_note)); - builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - deleteCurrentNote(); - finish(); - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - break; - case R.id.menu_font_size: - mFontSizeSelector.setVisibility(View.VISIBLE); - 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; - case R.id.menu_share: - getWorkingText(); - sendTo(this, mWorkingNote.getContent()); - break; - case R.id.menu_send_to_desktop: - sendToDesktop(); - break; - case R.id.menu_alert: - setReminder(); - break; - case R.id.menu_delete_remind: - mWorkingNote.setAlertDate(0, false); - break; - default: - break; - } - return true; - } - - private void setReminder() { - DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); - d.setOnDateTimeSetListener(new OnDateTimeSetListener() { - public void OnDateTimeSet(AlertDialog dialog, long date) { - mWorkingNote.setAlertDate(date , true); - } - }); - d.show(); - } - - /** - * Share note to apps that support {@link Intent#ACTION_SEND} action - * and {@text/plain} type - */ - private void sendTo(Context context, String info) { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.putExtra(Intent.EXTRA_TEXT, info); - intent.setType("text/plain"); - 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); - intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); - startActivity(intent); - } - - private void deleteCurrentNote() { - if (mWorkingNote.existInDatabase()) { - HashSet ids = new HashSet(); - long id = mWorkingNote.getNoteId(); - if (id != Notes.ID_ROOT_FOLDER) { - ids.add(id); - } else { - Log.d(TAG, "Wrong note id, should not happen"); - } - if (!isSyncMode()) { - if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { - Log.e(TAG, "Delete Note error"); - } - } else { - if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { - Log.e(TAG, "Move notes to trash folder error, should not happens"); - } - } - } - mWorkingNote.markDeleted(true); - } - - private boolean isSyncMode() { - return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; - } - - 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 - */ - if (!mWorkingNote.existInDatabase()) { - saveNote(); - } - if (mWorkingNote.getNoteId() > 0) { - Intent intent = new Intent(this, AlarmReceiver.class); - intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); - int requestCode = (int) mWorkingNote.getNoteId(); - PendingIntent pendingIntent = PendingIntent.getBroadcast(this, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); - AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); - showAlertHeader(); - if(!set) { - alarmManager.cancel(pendingIntent); - } else { - // 兼容Android 6.0+,使用setExactAndAllowWhileIdle - alarmManager.setExactAndAllowWhileIdle(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); - } - } - - public void onWidgetChanged() { - updateWidget(); - } - - 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); - } - - mEditTextList.removeViewAt(index); - NoteEditText edit = null; - if(index == 0) { - edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( - R.id.et_edit_text); - } else { - edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById( - R.id.et_edit_text); - } - int length = edit.length(); - edit.append(text); - edit.requestFocus(); - edit.setSelection(length); - } - - 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"); - } - - View view = getListItem(text, index); - mEditTextList.addView(view, index); - 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); - } - } - - private void switchToListMode(String text) { - mEditTextList.removeAllViews(); - String[] items = text.split("\n"); - int index = 0; - for (String item : items) { - if(!TextUtils.isEmpty(item)) { - mEditTextList.addView(getListItem(item, index)); - index++; - } - } - mEditTextList.addView(getListItem("", index)); - mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); - - mNoteEditor.setVisibility(View.GONE); - mEditTextList.setVisibility(View.VISIBLE); - } - - private Spannable getHighlightQueryResult(String fullText, String userQuery) { - SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); - if (!TextUtils.isEmpty(userQuery)) { - mPattern = Pattern.compile(userQuery); - Matcher m = mPattern.matcher(fullText); - int start = 0; - while (m.find(start)) { - spannable.setSpan( - new BackgroundColorSpan(this.getResources().getColor( - R.color.user_query_highlight)), m.start(), m.end(), - Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - start = m.end(); - } - } - return spannable; - } - - 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)); - - if (mWorkingNote.getBoldState()) { - edit.setTypeface(null, android.graphics.Typeface.BOLD); - } else { - edit.setTypeface(null, android.graphics.Typeface.NORMAL); - } - - CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); - cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - } else { - edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); - } - } - }); - - if (item.startsWith(TAG_CHECKED)) { - cb.setChecked(true); - edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - item = item.substring(TAG_CHECKED.length(), item.length()).trim(); - } else if (item.startsWith(TAG_UNCHECKED)) { - cb.setChecked(false); - edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); - item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); - } - - edit.setOnTextViewChangeListener(this); - edit.setIndex(index); - edit.setText(getHighlightQueryResult(item, mUserQuery)); - return view; - } - - 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 { - mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); - } - } - - public void onCheckListModeChanged(int oldMode, int newMode) { - if (newMode == TextNote.MODE_CHECK_LIST) { - switchToListMode(mNoteEditor.getText().toString()); - } else { - if (!getWorkingText()) { - mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", - "")); - } - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); - mEditTextList.setVisibility(View.GONE); - mNoteEditor.setVisibility(View.VISIBLE); - } - } - - 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++) { - View view = mEditTextList.getChildAt(i); - NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - if (!TextUtils.isEmpty(edit.getText())) { - if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { - sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); - hasChecked = true; - } else { - sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); - } - } - } - mWorkingNote.setWorkingText(sb.toString()); - } else { - mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); - } - return hasChecked; - } - - 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(); - } - - if (mWorkingNote.getNoteId() > 0) { - Intent sender = new Intent(); - Intent shortcutIntent = new Intent(this, NoteEditActivity.class); - shortcutIntent.setAction(Intent.ACTION_VIEW); - shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); - sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, - makeShortcutIconTitle(mWorkingNote.getContent())); - sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, - Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); - sender.putExtra("duplicate", true); - sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); - 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); - } - } - - private String makeShortcutIconTitle(String content) { - content = content.replace(TAG_CHECKED, ""); - content = content.replace(TAG_UNCHECKED, ""); - return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, - SHORTCUT_ICON_TITLE_MAX_LEN) : content; - } - - private void showToast(int resId) { - showToast(resId, Toast.LENGTH_SHORT); - } - - private void showToast(int resId, int duration) { - Toast.makeText(this, resId, duration).show(); - } - - - public void OnOpenMenu(View view) { - clearSettingState(); - showPopupMenu(view); - } - - private void showPopupMenu(View anchor) { - PopupMenu popupMenu = new PopupMenu(this, anchor); - // 根据便签类型加载不同菜单 - if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { - popupMenu.getMenuInflater().inflate(R.menu.call_note_edit, popupMenu.getMenu()); - } else { - popupMenu.getMenuInflater().inflate(R.menu.note_edit, popupMenu.getMenu()); - } - - // 根据字体加粗状态设置菜单文本 - MenuItem boldMenuItem = popupMenu.getMenu().findItem(R.id.menu_bold); - if (boldMenuItem != null) { - if (mWorkingNote.getBoldState()) { - boldMenuItem.setTitle(R.string.menu_unbold); - } else { - boldMenuItem.setTitle(R.string.menu_bold); - } - } - - // 设置菜单项点击事件 - popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { - @Override - public boolean onMenuItemClick(MenuItem item) { - if (item.getItemId() == R.id.menu_bold) { - mWorkingNote.setBoldState(!mWorkingNote.getBoldState()); - applyTextStyle(); - return true; - } - return onOptionsItemSelected(item); // 复用原有逻辑 - } - }); - popupMenu.show(); - } - - private void applyTextStyle() { - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - // 遍历所有NoteEditText并应用样式 - for (int i = 0; i < mEditTextList.getChildCount(); i++) { - View view = mEditTextList.getChildAt(i); - NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); - if (mWorkingNote.getBoldState()) { - edit.setTypeface(null, android.graphics.Typeface.BOLD); - } else { - edit.setTypeface(null, android.graphics.Typeface.NORMAL); - } - } - } else { - mNoteEditor.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); - if (mWorkingNote.getBoldState()) { - mNoteEditor.setTypeface(null, android.graphics.Typeface.BOLD); - } else { - mNoteEditor.setTypeface(null, android.graphics.Typeface.NORMAL); - } - } - } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NoteEditText.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NoteEditText.java index 2afe2a8..4479a2b 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NoteEditText.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NoteEditText.java @@ -37,15 +37,23 @@ import net.micode.notes.R; import java.util.HashMap; import java.util.Map; +/** + * NoteEditText类继承自EditText,用于处理笔记编辑时的文本输入, + * 扩展了一些功能,如处理按键事件、触摸事件、上下文菜单等,并提供了文本变化的回调接口。 + */ public class NoteEditText extends EditText { private static final String TAG = "NoteEditText"; + // 编辑文本在列表中的索引 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:" ; + // 协议与对应的操作资源ID映射 private static final Map sSchemaActionResMap = new HashMap(); static { sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); @@ -54,66 +62,103 @@ 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; + /** + * 构造函数,使用默认属性 + * @param context 上下文对象 + */ 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; } + /** + * 构造函数,使用属性集 + * @param context 上下文对象 + * @param attrs 属性集 + */ public NoteEditText(Context context, AttributeSet attrs) { super(context, attrs, android.R.attr.editTextStyle); } + /** + * 构造函数,使用属性集和默认样式 + * @param context 上下文对象 + * @param attrs 属性集 + * @param defStyle 默认样式 + */ public NoteEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub } + /** + * 处理触摸事件,根据触摸位置设置文本选择 + * @param event 触摸事件 + * @return 是否处理该事件 + */ @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(); y -= getTotalPaddingTop(); x += getScrollX(); y += getScrollY(); + // 获取文本布局 Layout layout = getLayout(); + // 根据坐标获取行号和偏移量 int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); + // 设置文本选择位置 Selection.setSelection(getText(), off); break; } @@ -121,6 +166,12 @@ public class NoteEditText extends EditText { return super.onTouchEvent(event); } + /** + * 处理按键按下事件,记录删除操作前的选择起始位置 + * @param keyCode 按键码 + * @param event 按键事件 + * @return 是否处理该事件 + */ @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { @@ -138,11 +189,18 @@ public class NoteEditText extends EditText { return super.onKeyDown(keyCode, event); } + /** + * 处理按键抬起事件,根据按键码调用相应的监听器方法 + * @param keyCode 按键码 + * @param event 按键事件 + * @return 是否处理该事件 + */ @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch(keyCode) { case KeyEvent.KEYCODE_DEL: if (mOnTextViewChangeListener != null) { + // 如果选择起始位置为0且不是第一个编辑文本,则删除当前编辑文本 if (0 == mSelectionStartBeforeDelete && mIndex != 0) { mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); return true; @@ -153,9 +211,13 @@ public class NoteEditText extends EditText { break; case KeyEvent.KEYCODE_ENTER: if (mOnTextViewChangeListener != null) { + // 获取选择起始位置 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,10 +229,17 @@ public class NoteEditText extends EditText { return super.onKeyUp(keyCode, event); } + /** + * 处理焦点变化事件,根据焦点状态和文本内容调用监听器方法 + * @param focused 是否获得焦点 + * @param direction 焦点变化方向 + * @param previouslyFocusedRect 之前聚焦的矩形区域 + */ @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { if (mOnTextViewChangeListener != null) { if (!focused && TextUtils.isEmpty(getText())) { + // 失去焦点且文本为空时,调用文本变化监听器 mOnTextViewChangeListener.onTextChange(mIndex, false); } else { mOnTextViewChangeListener.onTextChange(mIndex, true); @@ -179,18 +248,25 @@ public class NoteEditText extends EditText { super.onFocusChanged(focused, direction, previouslyFocusedRect); } + /** + * 创建上下文菜单,处理链接相关的菜单项 + * @param menu 上下文菜单 + */ @Override protected void onCreateContextMenu(ContextMenu menu) { if (getText() instanceof Spanned) { + // 获取选择的起始和结束位置 int selStart = getSelectionStart(); int selEnd = getSelectionEnd(); int min = Math.min(selStart, selEnd); int max = Math.max(selStart, selEnd); + // 获取选择范围内的URLSpan final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); if (urls.length == 1) { int defaultResId = 0; + // 根据链接协议获取对应的操作资源ID for(String schema: sSchemaActionResMap.keySet()) { if(urls[0].getURL().indexOf(schema) >= 0) { defaultResId = sSchemaActionResMap.get(schema); @@ -202,10 +278,11 @@ public class NoteEditText extends EditText { 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 +291,4 @@ public class NoteEditText extends EditText { } super.onCreateContextMenu(menu); } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NoteItemData.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NoteItemData.java index 0f5a878..f6bfe21 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NoteItemData.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NoteItemData.java @@ -25,8 +25,12 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.tool.DataUtils; - +/** + * NoteItemData类用于封装笔记项的数据,从数据库游标中提取并存储笔记的各种信息, + * 如笔记ID、提醒日期、背景颜色、创建日期等,同时提供了获取这些信息的方法。 + */ public class NoteItemData { + // 查询数据库时使用的投影列数组,指定要查询的列 static final String [] PROJECTION = new String [] { NoteColumns.ID, NoteColumns.ALERTED_DATE, @@ -42,6 +46,7 @@ public class NoteItemData { 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; @@ -55,6 +60,7 @@ public class NoteItemData { 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; @@ -70,12 +76,18 @@ public class NoteItemData { private String mName; private String mPhoneNumber; + // 标记笔记项在列表中的位置信息 private boolean mIsLastItem; private boolean mIsFirstItem; private boolean mIsOnlyOneItem; private boolean mIsOneNoteFollowingFolder; private boolean mIsMultiNotesFollowingFolder; + /** + * 构造函数,从游标中提取笔记数据并初始化对象的属性 + * @param context 上下文对象 + * @param cursor 数据库游标,包含笔记数据 + */ public NoteItemData(Context context, Cursor cursor) { mId = cursor.getLong(ID_COLUMN); mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); @@ -86,6 +98,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,9 +106,12 @@ public class NoteItemData { mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); mPhoneNumber = ""; + // 如果笔记的父ID是通话记录文件夹的ID if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { + // 获取通话记录的电话号码 mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); if (!TextUtils.isEmpty(mPhoneNumber)) { + // 根据电话号码获取联系人姓名 mName = Contact.getContact(context, mPhoneNumber); if (mName == null) { mName = mPhoneNumber; @@ -106,9 +122,14 @@ public class NoteItemData { if (mName == null) { mName = ""; } + // 检查笔记项在列表中的位置 checkPostion(cursor); } + /** + * 检查笔记项在列表中的位置,设置相应的标记 + * @param cursor 数据库游标 + */ private void checkPostion(Cursor cursor) { mIsLastItem = cursor.isLast() ? true : false; mIsFirstItem = cursor.isFirst() ? true : false; @@ -116,14 +137,18 @@ public class NoteItemData { mIsMultiNotesFollowingFolder = false; mIsOneNoteFollowingFolder = false; + // 如果笔记类型是普通笔记且不是第一项 if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { int position = cursor.getPosition(); if (cursor.moveToPrevious()) { + // 如果前一项是文件夹或系统项 if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { if (cursor.getCount() > (position + 1)) { + // 后续有多条笔记跟随文件夹 mIsMultiNotesFollowingFolder = true; } else { + // 后续只有一条笔记跟随文件夹 mIsOneNoteFollowingFolder = true; } } @@ -134,91 +159,180 @@ public class NoteItemData { } } + /** + * 判断是否是文件夹后跟随的第一条笔记 + * @return 如果是返回true,否则返回false + */ public boolean isOneFollowingFolder() { return mIsOneNoteFollowingFolder; } + /** + * 判断是否是文件夹后跟随的多条笔记之一 + * @return 如果是返回true,否则返回false + */ public boolean isMultiFollowingFolder() { return mIsMultiNotesFollowingFolder; } + /** + * 判断是否是列表中的最后一项 + * @return 如果是返回true,否则返回false + */ public boolean isLast() { return mIsLastItem; } + /** + * 获取通话记录的联系人姓名 + * @return 联系人姓名 + */ public String getCallName() { return mName; } + /** + * 判断是否是列表中的第一项 + * @return 如果是返回true,否则返回false + */ public boolean isFirst() { return mIsFirstItem; } + /** + * 判断列表中是否只有一项 + * @return 如果是返回true,否则返回false + */ public boolean isSingle() { return mIsOnlyOneItem; } + /** + * 获取笔记的ID + * @return 笔记ID + */ public long getId() { return mId; } + /** + * 获取笔记的提醒日期 + * @return 提醒日期 + */ public long getAlertDate() { return mAlertDate; } + /** + * 获取笔记的创建日期 + * @return 创建日期 + */ public long getCreatedDate() { return mCreatedDate; } + /** + * 判断笔记是否有附件 + * @return 如果有附件返回true,否则返回false + */ public boolean hasAttachment() { return mHasAttachment; } + /** + * 获取笔记的修改日期 + * @return 修改日期 + */ public long getModifiedDate() { return mModifiedDate; } + /** + * 获取笔记的背景颜色ID + * @return 背景颜色ID + */ public int getBgColorId() { return mBgColorId; } + /** + * 获取笔记的父ID + * @return 父ID + */ public long getParentId() { return mParentId; } + /** + * 获取笔记所在文件夹中的笔记数量 + * @return 笔记数量 + */ public int getNotesCount() { return mNotesCount; } + /** + * 获取笔记所在文件夹的ID + * @return 文件夹ID + */ public long getFolderId () { return mParentId; } + /** + * 获取笔记的类型 + * @return 笔记类型 + */ public int getType() { return mType; } + /** + * 获取笔记小部件的类型 + * @return 小部件类型 + */ public int getWidgetType() { return mWidgetType; } + /** + * 获取笔记小部件的ID + * @return 小部件ID + */ public int getWidgetId() { return mWidgetId; } + /** + * 获取笔记的摘要 + * @return 笔记摘要 + */ public String getSnippet() { return mSnippet; } + /** + * 判断笔记是否有提醒 + * @return 如果有提醒返回true,否则返回false + */ public boolean hasAlert() { return (mAlertDate > 0); } + /** + * 判断笔记是否是通话记录笔记 + * @return 如果是通话记录笔记返回true,否则返回false + */ public boolean isCallRecord() { return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); } + /** + * 从游标中获取笔记的类型 + * @param cursor 数据库游标 + * @return 笔记类型 + */ public static int getNoteType(Cursor cursor) { return cursor.getInt(TYPE_COLUMN); } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesListActivity.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesListActivity.java index 2fa42ce..e01ffe6 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesListActivity.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesListActivity.java @@ -78,98 +78,131 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashSet; +/** + * NotesListActivity类用于显示笔记列表,处理笔记列表的初始化、查询、点击事件等操作。 + */ 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; - + // 文件夹删除菜单的ID private static final int MENU_FOLDER_DELETE = 0; - + // 文件夹查看菜单的ID private static final int MENU_FOLDER_VIEW = 1; - + // 文件夹重命名菜单的ID 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 Button mMenuSet; - + // 是否分发事件 private boolean mDispatch; - + // 初始Y坐标 private int mOriginY; - + // 分发Y坐标 private int mDispatchY; - + // 标题栏TextView private TextView mTitleBar; - + // 当前文件夹ID private long mCurrentFolderId; - + // 内容解析器 private ContentResolver mContentResolver; - + // 模式回调对象 private ModeCallback mModeCallBack; 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; + /** + * 活动创建时调用,初始化界面和资源 + * @param savedInstanceState 保存的实例状态 + */ @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(); } + /** + * 处理活动返回结果 + * @param requestCode 请求码 + * @param resultCode 结果码 + * @param data 返回的数据 + */ @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); } else { super.onActivityResult(requestCode, resultCode, data); } } + /** + * 从原始资源中设置应用信息,首次使用应用时插入介绍笔记 + */ 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); char [] buf = new char[1024]; int len = 0; + // 读取文件内容 while ((len = br.read(buf)) > 0) { sb.append(buf, 0, len); } @@ -183,6 +216,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } finally { if(in != null) { try { + // 关闭输入流 in.close(); } catch (IOException e) { // TODO Auto-generated catch block @@ -191,11 +225,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + // 创建一个空的工作笔记 WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, ResourceParser.RED); + // 设置笔记内容 note.setWorkingText(sb.toString()); if (note.saveNote()) { + // 保存介绍笔记成功,更新偏好设置 sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); } else { Log.e(TAG, "Save introduction note error"); @@ -204,789 +241,108 @@ 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()); + // 设置当前文件夹ID为根文件夹ID 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()); + // 设置列表项长按监听器 mNotesListView.setOnItemLongClickListener(this); + // 创建笔记列表适配器 mNotesListAdapter = new NotesListAdapter(this); + // 设置列表适配器 mNotesListView.setAdapter(mNotesListAdapter); + // 获取新增笔记按钮 mAddNewNote = (Button) findViewById(R.id.btn_new_note); + // 设置新增笔记按钮的点击监听器 mAddNewNote.setOnClickListener(this); + // 设置新增笔记按钮的触摸监听器 mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); + // 获取菜单设置按钮 mMenuSet = (Button) findViewById(R.id.btn_set); mDispatch = false; mDispatchY = 0; mOriginY = 0; + // 获取标题栏TextView mTitleBar = (TextView) findViewById(R.id.tv_title_bar); + // 设置初始列表编辑状态 mState = ListEditState.NOTE_LIST; + // 创建模式回调对象 mModeCallBack = new ModeCallback(); } + /** + * 模式回调类,处理多选模式和菜单项点击事件 + */ private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { + // 下拉菜单 private DropdownMenu mDropDownMenu; + // 动作模式 private ActionMode mActionMode; + // 菜单 private Menu menu; + // 移动菜单菜单项 private MenuItem mMoveMenu; + /** + * 创建动作模式时调用,初始化菜单和相关设置 + * @param mode 动作模式 + * @param menu 菜单 + * @return 是否创建成功 + */ public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // 加载菜单布局 getMenuInflater().inflate(R.menu.note_list_options, menu); this.menu = menu; + // 设置删除菜单项的点击监听器 menu.findItem(R.id.delete).setOnMenuItemClickListener(this); mMoveMenu = menu.findItem(R.id.move); + // 根据当前笔记的父文件夹ID和用户文件夹数量决定移动菜单是否可见 if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER || DataUtils.getUserFolderCount(mContentResolver) == 0) { mMoveMenu.setVisible(false); } else { mMoveMenu.setVisible(true); + // 设置移动菜单的点击监听器 mMoveMenu.setOnMenuItemClickListener(this); } mActionMode = mode; + // 设置适配器为选择模式 mNotesListAdapter.setChoiceMode(true); + // 禁用列表项的长按事件 mNotesListView.setLongClickable(false); + // 隐藏新增笔记按钮 mAddNewNote.setVisibility(View.GONE); + // 隐藏菜单设置按钮 mMenuSet.setVisibility(View.GONE); + // 加载自定义视图 View customView = LayoutInflater.from(NotesListActivity.this).inflate( R.layout.note_list_dropdown_menu, null); + // 设置动作模式的自定义视图 mode.setCustomView(customView); - mDropDownMenu = new DropdownMenu(NotesListActivity.this, - (Button) customView.findViewById(R.id.selection_menu), - R.menu.note_list_dropdown); - mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ - public boolean onMenuItemClick(MenuItem item) { - mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); - 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); - if (item != null) { - if (mNotesListAdapter.isAllSelected()) { - item.setChecked(true); - item.setTitle(R.string.menu_deselect_all); - } else { - item.setChecked(false); - item.setTitle(R.string.menu_select_all); - } - } - } - - 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.clearChoices(); - mNotesListView.setItemChecked(0,true); - mNotesListView.setItemChecked(0,false); - - mNotesListView.setLongClickable(true); - System.out.println("-----------------onDestroyActionMode------------------"); - mNotesListAdapter.notifyDataSetChanged(); -// closeOptionsMenu(); -// mNotesListView.invalidate(); - mAddNewNote.setVisibility(View.VISIBLE); - mMenuSet.setVisibility(View.VISIBLE); - - - -// mNotesListView.setChoiceMode(ListView.CHOICE_MODE_NONE); - -// menu.clear(); -// menu.close(); - } - - - - public void finishActionMode() { - mActionMode.finish(); - mActionMode = null; - mNotesListAdapter.setChoiceMode(false); - mNotesListView.setLongClickable(true); - } - - - - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { - mNotesListAdapter.setCheckedItem(position, checked); - updateMenu(); - } - - public boolean onMenuItemClick(MenuItem item) { - if (mNotesListAdapter.getSelectedCount() == 0) { - Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), - Toast.LENGTH_SHORT).show(); - return true; - } - - 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())); - builder.setPositiveButton(android.R.string.ok, - 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: - return false; - } - return true; - } - } - - - private class NewNoteOnTouchListener implements OnTouchListener { - - public boolean onTouch(View v, MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: { - Display display = getWindowManager().getDefaultDisplay(); - int screenHeight = display.getHeight(); - 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()); - if (view != null && view.getBottom() > start - && (view.getTop() < (start + 94))) { - mOriginY = (int) event.getY(); - mDispatchY = eventY; - event.setLocation(event.getX(), mDispatchY); - mDispatch = true; - return mNotesListView.dispatchTouchEvent(event); - } - } - break; - } - case MotionEvent.ACTION_MOVE: { - if (mDispatch) { - mDispatchY += (int) event.getY() - mOriginY; - event.setLocation(event.getX(), mDispatchY); - return mNotesListView.dispatchTouchEvent(event); - } - break; - } - default: { - if (mDispatch) { - event.setLocation(event.getX(), mDispatchY); - mDispatch = false; - return mNotesListView.dispatchTouchEvent(event); - } - break; - } - } - return false; - } - - }; - - 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) - }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); - } - - private final class BackgroundQueryHandler extends AsyncQueryHandler { - public BackgroundQueryHandler(ContentResolver contentResolver) { - super(contentResolver); - } - - @Override - 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"); - } - break; - default: - return; - } - } - } - - private void showFolderListMenu(Cursor cursor) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(R.string.menu_title_select_folder); - final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor); - builder.setAdapter(adapter, new DialogInterface.OnClickListener() { - - public void onClick(DialogInterface dialog, int which) { - DataUtils.batchMoveToFolder(mContentResolver, - mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which)); - Toast.makeText( - NotesListActivity.this, - getString(R.string.format_move_notes_to_folder, - mNotesListAdapter.getSelectedCount(), - adapter.getFolderName(NotesListActivity.this, which)), - Toast.LENGTH_SHORT).show(); - mModeCallBack.finishActionMode(); - } - }); - builder.show(); - } - - private void createNewNote() { - Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); - intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); - 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 - .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"); - } - } - return widgets; - } - - @Override - protected void onPostExecute(HashSet widgets) { - if (widgets != null) { - for (AppWidgetAttribute widget : widgets) { - if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID - && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { - updateWidget(widget.widgetId, widget.widgetType); - } - } - } - mModeCallBack.finishActionMode(); - } - }.execute(); - } - - private void deleteFolder(long folderId) { - if (folderId == Notes.ID_ROOT_FOLDER) { - Log.e(TAG, "Wrong folder id, should not happen " + folderId); - return; - } - - HashSet ids = new HashSet(); - ids.add(folderId); - 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 - && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { - updateWidget(widget.widgetId, widget.widgetType); - } - } - } - } - - private void openNode(NoteItemData data) { - Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_UID, data.getId()); - this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); - } - - 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); - mMenuSet.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 { - mTitleBar.setText(data.getSnippet()); - } - mTitleBar.setVisibility(View.VISIBLE); - } - - public void onClick(View v) { - switch (v.getId()) { - case R.id.btn_new_note: - createNewNote(); - break; - default: - break; - } - } - - private void showSoftInput() { - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - if (inputMethodManager != null) { - inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); - } - } - - private void hideSoftInput(View view) { - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - - 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()); - builder.setTitle(getString(R.string.menu_folder_change_name)); - } else { - Log.e(TAG, "The long click data item is null"); - return; - } - } else { - etName.setText(""); - builder.setTitle(this.getString(R.string.menu_create_folder)); - } - - builder.setPositiveButton(android.R.string.ok, null); - builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - hideSoftInput(etName); - } - }); - - final Dialog dialog = builder.setView(view).show(); - final Button positive = (Button)dialog.findViewById(android.R.id.button1); - positive.setOnClickListener(new OnClickListener() { - 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(); - values.put(NoteColumns.SNIPPET, name); - values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - values.put(NoteColumns.LOCAL_MODIFIED, 1); - mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID - + "=?", new String[] { - String.valueOf(mFocusNoteDataItem.getId()) - }); - } - } else if (!TextUtils.isEmpty(name)) { - ContentValues values = new ContentValues(); - values.put(NoteColumns.SNIPPET, name); - values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); - } - dialog.dismiss(); - } - }); - - 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) { - if (TextUtils.isEmpty(etName.getText())) { - positive.setEnabled(false); - } else { - positive.setEnabled(true); - } - } - - public void afterTextChanged(Editable s) { - // TODO Auto-generated method stub - - } - }); - } - - @Override - public void onBackPressed() { - - System.out.println("-------onBackPressed---00000"); - switch (mState) { - case SUB_FOLDER: - mCurrentFolderId = Notes.ID_ROOT_FOLDER; - mState = ListEditState.NOTE_LIST; - startAsyncNotesListQuery(); - mTitleBar.setVisibility(View.GONE); - break; - case CALL_RECORD_FOLDER: - mCurrentFolderId = Notes.ID_ROOT_FOLDER; - mState = ListEditState.NOTE_LIST; - mAddNewNote.setVisibility(View.VISIBLE); - mMenuSet.setVisibility(View.VISIBLE); - mTitleBar.setVisibility(View.GONE); - startAsyncNotesListQuery(); - break; - case NOTE_LIST: - System.out.println("-------onBackPressed---"); - super.onBackPressed(); - break; - default: - break; - } - } - - private void updateWidget(int appWidgetId, int appWidgetType) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - if (appWidgetType == Notes.TYPE_WIDGET_2X) { - intent.setClass(this, NoteWidgetProvider_2x.class); - } else if (appWidgetType == Notes.TYPE_WIDGET_4X) { - intent.setClass(this, NoteWidgetProvider_4x.class); - } else { - Log.e(TAG, "Unspported widget type"); - return; - } - - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { - 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) { - menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); - menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); - menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); - menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); - } - } - }; - - @Override - public void onContextMenuClosed(Menu menu) { - if (mNotesListView != null) { - mNotesListView.setOnCreateContextMenuListener(null); - } - super.onContextMenuClosed(menu); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - if (mFocusNoteDataItem == null) { - Log.e(TAG, "The long click data item is null"); - return false; - } - switch (item.getItemId()) { - case MENU_FOLDER_VIEW: - 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); - builder.setMessage(getString(R.string.alert_message_delete_folder)); - builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - deleteFolder(mFocusNoteDataItem.getId()); - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - break; - case MENU_FOLDER_CHANGE_NAME: - showCreateOrModifyFolderDialog(false); - break; - default: - break; - } - - return true; - } - - @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) { - getMenuInflater().inflate(R.menu.sub_folder, menu); - } else if (mState == ListEditState.CALL_RECORD_FOLDER) { - getMenuInflater().inflate(R.menu.call_record_folder, menu); - } else { - Log.e(TAG, "Wrong state:" + mState); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_new_folder: { - showCreateOrModifyFolderDialog(true); - break; - } - case R.id.menu_export_text: { - exportNoteToText(); - break; - } - case R.id.menu_sync: { - if (isSyncMode()) { - if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) { - GTaskSyncService.startSync(this); - } else { - GTaskSyncService.cancelSync(this); - } - } else { - startPreferenceActivity(); - } - break; - } - case R.id.menu_setting: { - startPreferenceActivity(); - break; - } - case R.id.menu_new_note: { - createNewNote(); - break; - } - case R.id.menu_search: - onSearchRequested(); - break; - default: - break; - } - return true; - } - - @Override - public boolean onSearchRequested() { - startSearch(null, false, null /* appData */, false); - return true; - } - - private void exportNoteToText() { - final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); - new AsyncTask() { - - @Override - protected Integer doInBackground(Void... unused) { - return backup.exportToText(); - } - - @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 - .getString(R.string.failed_sdcard_export)); - builder.setMessage(NotesListActivity.this - .getString(R.string.error_sdcard_unmounted)); - builder.setPositiveButton(android.R.string.ok, null); - builder.show(); - } else if (result == BackupUtils.STATE_SUCCESS) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(NotesListActivity.this - .getString(R.string.success_sdcard_export)); - builder.setMessage(NotesListActivity.this.getString( - R.string.format_exported_file_location, backup - .getExportedTextFileName(), backup.getExportedTextFileDir())); - builder.setPositiveButton(android.R.string.ok, null); - builder.show(); - } else if (result == BackupUtils.STATE_SYSTEM_ERROR) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(NotesListActivity.this - .getString(R.string.failed_sdcard_export)); - builder.setMessage(NotesListActivity.this - .getString(R.string.error_sdcard_export)); - builder.setPositiveButton(android.R.string.ok, null); - builder.show(); - } - } - - }.execute(); - } - - 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(); - mModeCallBack.onItemCheckedStateChanged(null, position, id, - !mNotesListAdapter.isSelectedItem(position)); - } - return; - } - - switch (mState) { - case NOTE_LIST: - if (item.getType() == Notes.TYPE_FOLDER - || item.getType() == Notes.TYPE_SYSTEM) { - openFolder(item); - } else if (item.getType() == Notes.TYPE_NOTE) { - openNode(item); - } else { - Log.e(TAG, "Wrong note type in NOTE_LIST"); - } - break; - case SUB_FOLDER: - case CALL_RECORD_FOLDER: - if (item.getType() == Notes.TYPE_NOTE) { - openNode(item); - } else { - Log.e(TAG, "Wrong note type in SUB_FOLDER"); - } - break; - default: - break; - } - } - } - - } - - 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 + ")"; - - mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, - null, - Notes.CONTENT_NOTE_URI, - FoldersListAdapter.PROJECTION, - selection, - new String[] { - String.valueOf(Notes.TYPE_FOLDER), - String.valueOf(Notes.ID_TRASH_FOLER), - String.valueOf(mCurrentFolderId) - }, - NoteColumns.MODIFIED_DATE + " DESC"); - } - - 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); - mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - } else { - Log.e(TAG, "startActionMode fails"); - } - } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { - mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); - } - } - return false; } - - public void OnOpenMenu(View view) { - openOptionsMenu(); - } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java index 51c9cb9..7f66770 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java @@ -30,19 +30,35 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; - +/** + * NotesListAdapter类继承自CursorAdapter,用于将数据库中的笔记数据绑定到ListView的每个项上, + * 同时处理列表项的选择状态、全选、获取选中项等操作。 + */ public class NotesListAdapter extends CursorAdapter { private static final String TAG = "NotesListAdapter"; + // 上下文对象 private Context mContext; + // 存储列表项的选择状态,键为列表项的位置,值为是否选中 private HashMap mSelectedIndex; + // 笔记的数量 private int mNotesCount; + // 是否处于选择模式 private boolean mChoiceMode; + /** + * 存储小部件属性的内部类 + */ public static class AppWidgetAttribute { + // 小部件ID public int widgetId; + // 小部件类型 public int widgetType; }; + /** + * 构造函数,初始化适配器 + * @param context 上下文对象 + */ public NotesListAdapter(Context context) { super(context, null); mSelectedIndex = new HashMap(); @@ -50,38 +66,74 @@ public class NotesListAdapter extends CursorAdapter { mNotesCount = 0; } + /** + * 创建新的列表项视图 + * @param context 上下文对象 + * @param cursor 数据库游标 + * @param parent 父视图 + * @return 新的列表项视图 + */ @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) { + // 创建笔记项数据对象 NoteItemData itemData = new NoteItemData(context, cursor); + // 将数据绑定到列表项视图上 ((NotesListItem) view).bind(context, itemData, mChoiceMode, isSelectedItem(cursor.getPosition())); } } + /** + * 设置列表项的选择状态 + * @param position 列表项的位置 + * @param checked 是否选中 + */ public void setCheckedItem(final int position, final boolean checked) { mSelectedIndex.put(position, checked); + // 通知适配器数据发生变化 notifyDataSetChanged(); } + /** + * 判断是否处于选择模式 + * @return 如果处于选择模式返回true,否则返回false + */ public boolean isInChoiceMode() { return mChoiceMode; } + /** + * 设置选择模式 + * @param mode 是否开启选择模式 + */ public void setChoiceMode(boolean mode) { + // 清空选择状态 mSelectedIndex.clear(); mChoiceMode = mode; } + /** + * 全选或全不选列表项 + * @param checked 是否全选 + */ 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,10 +141,15 @@ public class NotesListAdapter extends CursorAdapter { } } + /** + * 获取选中项的ID集合 + * @return 选中项的ID集合 + */ public HashSet getSelectedItemIds() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { if (mSelectedIndex.get(position) == true) { + // 获取列表项的ID Long id = getItemId(position); if (id == Notes.ID_ROOT_FOLDER) { Log.d(TAG, "Wrong item id, should not happen"); @@ -105,15 +162,24 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + /** + * 获取选中项的小部件属性集合 + * @return 选中项的小部件属性集合 + */ public HashSet getSelectedWidget() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { if (mSelectedIndex.get(position) == true) { + // 获取列表项对应的游标 Cursor c = (Cursor) getItem(position); if (c != null) { + // 创建小部件属性对象 AppWidgetAttribute widget = new AppWidgetAttribute(); + // 创建笔记项数据对象 NoteItemData item = new NoteItemData(mContext, c); + // 设置小部件ID widget.widgetId = item.getWidgetId(); + // 设置小部件类型 widget.widgetType = item.getWidgetType(); itemSet.add(widget); /** @@ -128,11 +194,17 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + /** + * 获取选中项的数量 + * @return 选中项的数量 + */ public int getSelectedCount() { + // 获取选择状态的集合 Collection values = mSelectedIndex.values(); if (null == values) { return 0; } + // 遍历集合,统计选中项的数量 Iterator iter = values.iterator(); int count = 0; while (iter.hasNext()) { @@ -143,11 +215,21 @@ public class NotesListAdapter extends CursorAdapter { return count; } + /** + * 判断是否全选 + * @return 如果全选返回true,否则返回false + */ public boolean isAllSelected() { + // 获取选中项的数量 int checkedCount = getSelectedCount(); return (checkedCount != 0 && checkedCount == mNotesCount); } + /** + * 判断指定位置的列表项是否选中 + * @param position 列表项的位置 + * @return 如果选中返回true,否则返回false + */ public boolean isSelectedItem(final int position) { if (null == mSelectedIndex.get(position)) { return false; @@ -155,23 +237,35 @@ 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 +275,4 @@ public class NotesListAdapter extends CursorAdapter { } } } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesListItem.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesListItem.java index 1221e80..047d562 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesListItem.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesListItem.java @@ -30,17 +30,22 @@ import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser.NoteItemBgResources; +/** + * 笔记列表项视图 + */ 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; // 复选框 public NotesListItem(Context context) { super(context); + // 加载布局 inflate(context, R.layout.note_item, this); + // 初始化视图 mAlert = (ImageView) findViewById(R.id.iv_alert_icon); mTitle = (TextView) findViewById(R.id.tv_title); mTime = (TextView) findViewById(R.id.tv_time); @@ -48,7 +53,9 @@ public class NotesListItem extends LinearLayout { mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); } + // 绑定数据到视图 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,7 +64,9 @@ public class NotesListItem extends LinearLayout { } mItemData = data; + // 根据笔记类型设置不同显示 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + // 通话记录文件夹 mCallName.setVisibility(View.GONE); mAlert.setVisibility(View.VISIBLE); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); @@ -65,10 +74,12 @@ public class NotesListItem extends LinearLayout { + 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) { + // 通话记录 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); @@ -76,16 +87,20 @@ public class NotesListItem extends LinearLayout { mAlert.setVisibility(View.GONE); } } 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())); mAlert.setVisibility(View.GONE); } else { + // 普通笔记 mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + // 设置提醒图标 if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); mAlert.setVisibility(View.VISIBLE); @@ -94,11 +109,14 @@ public class NotesListItem extends LinearLayout { } } } + // 设置相对时间显示 mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + // 设置背景 setBackground(data); } + // 根据笔记位置设置不同背景 private void setBackground(NoteItemData data) { int id = data.getBgColorId(); if (data.getType() == Notes.TYPE_NOTE) { @@ -116,7 +134,8 @@ public class NotesListItem extends LinearLayout { } } + // 获取笔记数据 public NoteItemData getItemData() { return mItemData; } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java index 07c5f7e..a62ba09 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java @@ -48,42 +48,45 @@ 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_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 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 */ + // 启用返回按钮 getActionBar().setDisplayHomeAsUpEnabled(true); + // 从XML加载偏好设置 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); } @@ -91,9 +94,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { @Override 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) { @@ -112,20 +113,20 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } } - - refreshUI(); + refreshUI(); // 刷新UI } @Override protected void onDestroy() { if (mReceiver != null) { - unregisterReceiver(mReceiver); + unregisterReceiver(mReceiver); // 注销广播接收器 } super.onDestroy(); } + // 加载账户偏好设置 private void loadAccountPreference() { - mAccountCategory.removeAll(); + mAccountCategory.removeAll(); // 清除现有项 Preference accountPref = new Preference(this); final String defaultAccount = getSyncAccountName(this); @@ -135,11 +136,10 @@ 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 { @@ -150,15 +150,15 @@ public class NotesPreferenceActivity extends PreferenceActivity { return true; } }); - 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() { @@ -176,7 +176,7 @@ 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 +193,17 @@ 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)); @@ -217,6 +220,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { mHasAddedAccount = false; if (accounts.length > 0) { + // 创建账户选择列表 CharSequence[] items = new CharSequence[accounts.length]; final CharSequence[] itemMapping = items; int checkedItem = -1; @@ -237,6 +241,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + // 添加"添加账户"视图 View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); dialogBuilder.setView(addAccountView); @@ -244,19 +249,20 @@ public class NotesPreferenceActivity extends PreferenceActivity { addAccountView.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { mHasAddedAccount = true; + // 启动添加账户设置 Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); - intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { - "gmail-ls" - }); + intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { "gmail-ls" }); startActivityForResult(intent, -1); dialog.dismiss(); } }); } + // 显示更改账户确认对话框 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 +271,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), @@ -273,9 +280,9 @@ public class NotesPreferenceActivity extends PreferenceActivity { dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { if (which == 0) { - showSelectAccountAlertDialog(); + showSelectAccountAlertDialog(); // 更改账户 } else if (which == 1) { - removeSyncAccount(); + removeSyncAccount(); // 移除账户 refreshUI(); } } @@ -283,11 +290,13 @@ public class NotesPreferenceActivity extends PreferenceActivity { dialogBuilder.show(); } + // 获取Google账户列表 private Account[] getGoogleAccounts() { AccountManager accountManager = AccountManager.get(this); return accountManager.getAccountsByType("com.google"); } + // 设置同步账户 private void setSyncAccount(String account) { if (!getSyncAccountName(this).equals(account)) { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -299,10 +308,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { } editor.commit(); - // clean up last sync time + // 清除最后同步时间 setLastSyncTime(this, 0); - // clean up local gtask related info + // 清除本地GTask相关信息 new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); @@ -318,6 +327,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + // 移除同步账户 private void removeSyncAccount() { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); @@ -329,7 +339,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { } editor.commit(); - // clean up local gtask related info + // 清除本地GTask相关信息 new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); @@ -340,12 +350,14 @@ public class NotesPreferenceActivity extends PreferenceActivity { }).start(); } + // 获取同步账户名称 public static String getSyncAccountName(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); } + // 设置最后同步时间 public static void setLastSyncTime(Context context, long time) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -354,29 +366,31 @@ public class NotesPreferenceActivity extends PreferenceActivity { editor.commit(); } + // 获取最后同步时间 public static long getLastSyncTime(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); } + // 同步任务广播接收器 private class GTaskReceiver extends BroadcastReceiver { - @Override public void onReceive(Context context, Intent intent) { - refreshUI(); + refreshUI(); // 刷新UI + // 更新同步状态显示 if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); syncStatus.setText(intent .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); } - } } + // 选项菜单选择处理 public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case android.R.id.home: + case android.R.id.home: // 返回按钮 Intent intent = new Intent(this, NotesListActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); @@ -385,4 +399,4 @@ public class NotesPreferenceActivity extends PreferenceActivity { return false; } } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider.java b/MiNotes-master/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider.java index ec6f819..8a1d927 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider.java @@ -32,24 +32,42 @@ import net.micode.notes.tool.ResourceParser; import net.micode.notes.ui.NoteEditActivity; import net.micode.notes.ui.NotesListActivity; +/** + * NoteWidgetProvider 是一个抽象类,继承自 AppWidgetProvider,用于处理便签小部件的相关操作。 + * 该类提供了小部件删除、更新等功能的实现,同时定义了一些抽象方法,由具体的子类实现。 + */ public abstract class NoteWidgetProvider extends AppWidgetProvider { + // 定义查询便签信息时使用的投影,指定要查询的列 public static final String [] PROJECTION = new String [] { NoteColumns.ID, NoteColumns.BG_COLOR_ID, NoteColumns.SNIPPET }; + // 定义投影中各列的索引,方便后续使用 public static final int COLUMN_ID = 0; public static final int COLUMN_BG_COLOR_ID = 1; public static final int COLUMN_SNIPPET = 2; + // 日志标签,用于在日志中标识该类的输出信息 private static final String TAG = "NoteWidgetProvider"; + /** + * 当小部件被删除时调用此方法。 + * 该方法会将被删除小部件对应的便签的 widget_id 字段设置为无效值。 + * + * @param context 上下文对象,用于获取 ContentResolver 进行数据库操作 + * @param appWidgetIds 被删除的小部件的 ID 数组 + */ @Override public void onDeleted(Context context, int[] appWidgetIds) { + // 创建 ContentValues 对象,用于存储要更新的数据 ContentValues values = new ContentValues(); + // 将便签的 widget_id 字段设置为无效值 values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + // 遍历被删除的小部件 ID 数组 for (int i = 0; i < appWidgetIds.length; i++) { + // 更新数据库中对应 widget_id 的便签信息 context.getContentResolver().update(Notes.CONTENT_NOTE_URI, values, NoteColumns.WIDGET_ID + "=?", @@ -57,6 +75,14 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { } } + /** + * 根据小部件 ID 获取便签小部件的信息。 + * 该方法会查询数据库,获取指定小部件 ID 对应的便签信息。 + * + * @param context 上下文对象,用于获取 ContentResolver 进行数据库查询 + * @param widgetId 小部件的 ID + * @return 返回一个 Cursor 对象,包含查询结果 + */ private Cursor getNoteWidgetInfo(Context context, int widgetId) { return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, @@ -65,68 +91,128 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { null); } + /** + * 更新小部件的信息,不开启隐私模式。 + * 该方法会调用重载的 update 方法,传入 false 表示不开启隐私模式。 + * + * @param context 上下文对象,用于获取资源和进行数据库操作 + * @param appWidgetManager 小部件管理器,用于更新小部件的视图 + * @param appWidgetIds 要更新的小部件的 ID 数组 + */ protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { update(context, appWidgetManager, appWidgetIds, false); } + /** + * 更新小部件的信息,可选择是否开启隐私模式。 + * 该方法会遍历小部件 ID 数组,根据小部件 ID 查询便签信息,更新小部件的视图。 + * + * @param context 上下文对象,用于获取资源和进行数据库操作 + * @param appWidgetManager 小部件管理器,用于更新小部件的视图 + * @param appWidgetIds 要更新的小部件的 ID 数组 + * @param privacyMode 是否开启隐私模式 + */ private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, boolean privacyMode) { + // 遍历要更新的小部件 ID 数组 for (int i = 0; i < appWidgetIds.length; i++) { + // 检查小部件 ID 是否有效 if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { + // 获取默认的背景颜色 ID int bgId = ResourceParser.getDefaultBgId(context); + // 初始化便签的摘要信息 String snippet = ""; + // 创建一个 Intent 对象,用于启动便签编辑活动 Intent intent = new Intent(context, NoteEditActivity.class); + // 设置 Intent 的标志,确保活动以单顶模式启动 intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + // 将小部件 ID 作为额外信息添加到 Intent 中 intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); + // 将小部件类型作为额外信息添加到 Intent 中 intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); + // 根据小部件 ID 查询便签信息 Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); if (c != null && c.moveToFirst()) { + // 检查查询结果是否包含多个便签,如果是则记录错误信息并关闭 Cursor if (c.getCount() > 1) { Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); c.close(); return; } + // 获取便签的摘要信息 snippet = c.getString(COLUMN_SNIPPET); + // 获取便签的背景颜色 ID bgId = c.getInt(COLUMN_BG_COLOR_ID); + // 将便签的 ID 作为额外信息添加到 Intent 中 intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); + // 设置 Intent 的动作,用于查看便签 intent.setAction(Intent.ACTION_VIEW); } else { + // 如果没有查询到便签信息,设置默认的摘要信息 snippet = context.getResources().getString(R.string.widget_havenot_content); + // 设置 Intent 的动作,用于插入或编辑便签 intent.setAction(Intent.ACTION_INSERT_OR_EDIT); } + // 关闭 Cursor,释放资源 if (c != null) { c.close(); } + // 创建 RemoteViews 对象,用于更新小部件的视图 RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); + // 设置小部件的背景图片资源 rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); + // 将背景颜色 ID 作为额外信息添加到 Intent 中 intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); + /** * Generate the pending intent to start host for the widget */ PendingIntent pendingIntent = null; if (privacyMode) { + // 如果开启隐私模式,设置小部件的文本为隐私模式提示信息 rv.setTextViewText(R.id.widget_text, context.getString(R.string.widget_under_visit_mode)); + // 创建一个 PendingIntent 对象,用于启动便签列表活动 pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); } else { + // 如果不开启隐私模式,设置小部件的文本为便签的摘要信息 rv.setTextViewText(R.id.widget_text, snippet); + // 创建一个 PendingIntent 对象,用于启动便签编辑活动 pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, PendingIntent.FLAG_UPDATE_CURRENT); } + // 设置小部件文本的点击事件,关联 PendingIntent rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); + // 更新指定 ID 的小部件的视图 appWidgetManager.updateAppWidget(appWidgetIds[i], rv); } } } + /** + * 获取背景资源的 ID,由具体的子类实现。 + * + * @param bgId 背景颜色的 ID + * @return 返回背景资源的 ID + */ protected abstract int getBgResourceId(int bgId); + /** + * 获取小部件的布局 ID,由具体的子类实现。 + * + * @return 返回小部件的布局 ID + */ protected abstract int getLayoutId(); + /** + * 获取小部件的类型,由具体的子类实现。 + * + * @return 返回小部件的类型 + */ protected abstract int getWidgetType(); -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider_2x.java b/MiNotes-master/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider_2x.java index adcb2f7..0e6b860 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider_2x.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider_2x.java @@ -23,25 +23,53 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; - +/** + * NoteWidgetProvider_2x 是 NoteWidgetProvider 的子类,用于处理 2x 大小的便签小部件。 + * 该类实现了 NoteWidgetProvider 中的抽象方法,提供了 2x 小部件的布局、背景资源和类型信息。 + */ public class NoteWidgetProvider_2x extends NoteWidgetProvider { + /** + * 当小部件需要更新时调用此方法。 + * 该方法会调用父类的 update 方法,更新 2x 小部件的信息。 + * + * @param context 上下文对象,用于获取资源和进行数据库操作 + * @param appWidgetManager 小部件管理器,用于更新小部件的视图 + * @param appWidgetIds 要更新的小部件的 ID 数组 + */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } + /** + * 获取 2x 小部件的布局 ID。 + * + * @return 返回 2x 小部件的布局 ID + */ @Override protected int getLayoutId() { return R.layout.widget_2x; } + /** + * 获取 2x 小部件的背景资源 ID。 + * 该方法会调用 ResourceParser 类的方法,根据背景颜色 ID 获取对应的背景资源 ID。 + * + * @param bgId 背景颜色的 ID + * @return 返回 2x 小部件的背景资源 ID + */ @Override protected int getBgResourceId(int bgId) { return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); } + /** + * 获取 2x 小部件的类型。 + * + * @return 返回 2x 小部件的类型 + */ @Override protected int getWidgetType() { return Notes.TYPE_WIDGET_2X; } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider_4x.java b/MiNotes-master/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider_4x.java index c12a02e..a3e7256 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider_4x.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider_4x.java @@ -23,24 +23,52 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; - +/** + * NoteWidgetProvider_4x 是 NoteWidgetProvider 的子类,用于处理 4x 大小的便签小部件。 + * 该类实现了 NoteWidgetProvider 中的抽象方法,提供了 4x 小部件的布局、背景资源和类型信息。 + */ public class NoteWidgetProvider_4x extends NoteWidgetProvider { + /** + * 当小部件需要更新时调用此方法。 + * 该方法会调用父类的 update 方法,更新 4x 小部件的信息。 + * + * @param context 上下文对象,用于获取资源和进行数据库操作 + * @param appWidgetManager 小部件管理器,用于更新小部件的视图 + * @param appWidgetIds 要更新的小部件的 ID 数组 + */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } + /** + * 获取 4x 小部件的布局 ID。 + * + * @return 返回 4x 小部件的布局 ID + */ protected int getLayoutId() { return R.layout.widget_4x; } + /** + * 获取 4x 小部件的背景资源 ID。 + * 该方法会调用 ResourceParser 类的方法,根据背景颜色 ID 获取对应的背景资源 ID。 + * + * @param bgId 背景颜色的 ID + * @return 返回 4x 小部件的背景资源 ID + */ @Override protected int getBgResourceId(int bgId) { return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); } + /** + * 获取 4x 小部件的类型。 + * + * @return 返回 4x 小部件的类型 + */ @Override protected int getWidgetType() { return Notes.TYPE_WIDGET_4X; } -} +} \ No newline at end of file diff --git a/MiNotes-master/app/src/main/res/drawable/edit_red.xml b/MiNotes-master/app/src/main/res/drawable/edit_red.xml deleted file mode 100644 index 1370ed2..0000000 --- a/MiNotes-master/app/src/main/res/drawable/edit_red.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/MiNotes-master/app/src/main/res/drawable/list_background.xml b/MiNotes-master/app/src/main/res/drawable/list_background.xml deleted file mode 100644 index a37e0a9..0000000 --- a/MiNotes-master/app/src/main/res/drawable/list_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/MiNotes-master/app/src/main/res/menu/note_edit.xml b/MiNotes-master/app/src/main/res/menu/note_edit.xml index ede9f54..35cacd1 100644 --- a/MiNotes-master/app/src/main/res/menu/note_edit.xml +++ b/MiNotes-master/app/src/main/res/menu/note_edit.xml @@ -30,10 +30,6 @@ android:id="@+id/menu_font_size" android:title="@string/menu_font_size"/> - - diff --git a/MiNotes-master/app/src/main/res/values/strings.xml b/MiNotes-master/app/src/main/res/values/strings.xml index f307720..55df868 100644 --- a/MiNotes-master/app/src/main/res/values/strings.xml +++ b/MiNotes-master/app/src/main/res/values/strings.xml @@ -57,8 +57,6 @@ Medium Large Super - Bold - Unbold Enter check list Leave check list View folder @@ -134,9 +132,4 @@ %1$s results for \"%2$s\" - - %1$s note - %1$s notes - - diff --git a/小米便签开源软件维护方案及成果.docx b/小米便签开源软件维护方案及成果.docx deleted file mode 100644 index f4de30e..0000000 Binary files a/小米便签开源软件维护方案及成果.docx and /dev/null differ diff --git a/文档模板-开源代码质量分析报告模板.docx b/文档模板-开源代码质量分析报告模板.docx new file mode 100644 index 0000000..44d5166 Binary files /dev/null and b/文档模板-开源代码质量分析报告模板.docx differ diff --git a/文档模板-软件需求构思及描述模板.docx b/文档模板-软件需求构思及描述模板.docx new file mode 100644 index 0000000..5e29a87 Binary files /dev/null and b/文档模板-软件需求构思及描述模板.docx differ diff --git a/文档模板-软件需求规格说明书模板.docx b/文档模板-软件需求规格说明书模板.docx new file mode 100644 index 0000000..7bf4df1 Binary files /dev/null and b/文档模板-软件需求规格说明书模板.docx differ