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 79a4095..5ad3672 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 @@ -37,13 +37,16 @@ import org.json.JSONObject; import java.util.ArrayList; - +/** + * 数据库笔记操作类 + * 处理笔记(Note)的增删改查及同步逻辑,支持与Google任务(GTask)的数据交互 + */ public class SqlNote { private static final String TAG = SqlNote.class.getSimpleName(); + private static final int INVALID_ID = -99999; // 无效ID标识 - private static final int INVALID_ID = -99999; - - public static final String[] PROJECTION_NOTE = new String[] { + // 笔记查询投影字段(对应数据库表中的列) + public static final String[] PROJECTION_NOTE = new String[]{ NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID, NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE, NoteColumns.NOTES_COUNT, NoteColumns.PARENT_ID, NoteColumns.SNIPPET, NoteColumns.TYPE, @@ -52,139 +55,133 @@ public class SqlNote { NoteColumns.VERSION }; + // 各字段在投影中的索引位置(便于通过游标获取数据) public static final int ID_COLUMN = 0; - public static final int ALERTED_DATE_COLUMN = 1; - public static final int BG_COLOR_ID_COLUMN = 2; - public static final int CREATED_DATE_COLUMN = 3; - public static final int HAS_ATTACHMENT_COLUMN = 4; - public static final int MODIFIED_DATE_COLUMN = 5; - public static final int NOTES_COUNT_COLUMN = 6; - public static final int PARENT_ID_COLUMN = 7; - public static final int SNIPPET_COLUMN = 8; - public static final int TYPE_COLUMN = 9; - public static final int WIDGET_ID_COLUMN = 10; - public static final int WIDGET_TYPE_COLUMN = 11; - public static final int SYNC_ID_COLUMN = 12; - public static final int LOCAL_MODIFIED_COLUMN = 13; - public static final int ORIGIN_PARENT_ID_COLUMN = 14; - public static final int GTASK_ID_COLUMN = 15; - public static final int VERSION_COLUMN = 16; - private Context mContext; - - private ContentResolver mContentResolver; - - private boolean mIsCreate; - - private long mId; - - private long mAlertDate; - - private int mBgColorId; - - private long mCreatedDate; - - private int mHasAttachment; - - private long mModifiedDate; - - private long mParentId; - - private String mSnippet; - - private int mType; - - private int mWidgetId; - - private int mWidgetType; - - private long mOriginParent; - - private long mVersion; - - private ContentValues mDiffNoteValues; - - private ArrayList mDataList; - + private Context mContext; // 上下文 + private ContentResolver mContentResolver; // 内容解析器(用于操作ContentProvider) + private boolean mIsCreate; // 是否为新建笔记(未插入数据库) + private long mId; // 笔记ID + private long mAlertDate; // 提醒时间(时间戳) + private int mBgColorId; // 背景颜色ID(对应资源文件中的颜色) + private long mCreatedDate; // 创建时间(时间戳) + private int mHasAttachment; // 是否有附件(0-无,1-有) + private long mModifiedDate; // 修改时间(时间戳) + private long mParentId; // 父文件夹ID(0表示根目录) + private String mSnippet; // 笔记摘要(内容预览) + private int mType; // 类型(笔记、文件夹、系统文件夹等) + private int mWidgetId; // 小部件ID(关联桌面小部件) + private int mWidgetType; // 小部件类型(无效/文本/列表等) + private long mOriginParent; // 原始父文件夹ID(用于移动操作跟踪) + private long mVersion; // 版本号(用于数据同步冲突检测) + private ContentValues mDiffNoteValues; // 差异值(记录变更字段) + private ArrayList mDataList; // 关联的数据项列表(如文本内容、附件等) + + /** + * 构造函数:创建新笔记(默认值初始化) + * @param context 上下文 + */ public SqlNote(Context context) { mContext = context; mContentResolver = context.getContentResolver(); mIsCreate = true; mId = INVALID_ID; mAlertDate = 0; + // 默认背景颜色(从资源解析器获取) mBgColorId = ResourceParser.getDefaultBgId(context); - mCreatedDate = System.currentTimeMillis(); + mCreatedDate = System.currentTimeMillis(); // 当前时间戳 mHasAttachment = 0; - mModifiedDate = System.currentTimeMillis(); - mParentId = 0; + mModifiedDate = System.currentTimeMillis(); // 当前时间戳 + mParentId = 0; // 根目录 mSnippet = ""; - mType = Notes.TYPE_NOTE; - mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; - mWidgetType = Notes.TYPE_WIDGET_INVALIDE; + mType = Notes.TYPE_NOTE; // 默认类型为普通笔记 + mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; // 无效小部件ID + mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 无效小部件类型 mOriginParent = 0; mVersion = 0; mDiffNoteValues = new ContentValues(); mDataList = new ArrayList(); } + /** + * 构造函数:从游标加载现有笔记数据 + * @param context 上下文 + * @param c 数据库游标(已查询到的笔记记录) + */ public SqlNote(Context context, Cursor c) { mContext = context; mContentResolver = context.getContentResolver(); mIsCreate = false; - loadFromCursor(c); + loadFromCursor(c); // 从游标加载基础字段 mDataList = new ArrayList(); - if (mType == Notes.TYPE_NOTE) - loadDataContent(); + if (mType == Notes.TYPE_NOTE) { + loadDataContent(); // 加载笔记关联的数据项(如文本内容) + } mDiffNoteValues = new ContentValues(); } + /** + * 构造函数:通过ID加载现有笔记数据 + * @param context 上下文 + * @param id 笔记ID + */ public SqlNote(Context context, long id) { mContext = context; mContentResolver = context.getContentResolver(); mIsCreate = false; - loadFromCursor(id); + loadFromCursor(id); // 通过ID查询并加载数据 mDataList = new ArrayList(); - if (mType == Notes.TYPE_NOTE) - loadDataContent(); + if (mType == Notes.TYPE_NOTE) { + loadDataContent(); // 加载数据项 + } mDiffNoteValues = new ContentValues(); - } + /** + * 从游标加载笔记基础字段(通过ID查询) + * @param id 笔记ID + */ private void loadFromCursor(long id) { Cursor c = null; try { - c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)", - new String[] { - String.valueOf(id) - }, null); - if (c != null) { - c.moveToNext(); - loadFromCursor(c); + // 查询笔记表,条件为ID匹配 + c = mContentResolver.query( + Notes.CONTENT_NOTE_URI, + PROJECTION_NOTE, + "(_id=?)", + new String[]{String.valueOf(id)}, + null + ); + if (c != null && c.moveToNext()) { + loadFromCursor(c); // 调用游标加载方法 } else { - Log.w(TAG, "loadFromCursor: cursor = null"); + Log.w(TAG, "loadFromCursor: 游标为空或无数据"); } } finally { - if (c != null) - c.close(); + if (c != null) c.close(); // 关闭游标释放资源 } } + /** + * 从游标加载笔记基础字段(直接通过游标数据) + * @param c 已定位到目标记录的游标 + */ private void loadFromCursor(Cursor c) { mId = c.getLong(ID_COLUMN); mAlertDate = c.getLong(ALERTED_DATE_COLUMN); @@ -197,179 +194,138 @@ public class SqlNote { mType = c.getInt(TYPE_COLUMN); mWidgetId = c.getInt(WIDGET_ID_COLUMN); mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); - mVersion = c.getLong(VERSION_COLUMN); + mVersion = c.getLong(VERSION_COLUMN); // 版本号用于同步冲突检测 } + /** + * 加载笔记关联的数据项(如文本内容、附件等) + */ private void loadDataContent() { Cursor c = null; - mDataList.clear(); + mDataList.clear(); // 清空现有数据项列表 try { - c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA, - "(note_id=?)", new String[] { - String.valueOf(mId) - }, null); + // 查询数据项表,条件为所属笔记ID + c = mContentResolver.query( + Notes.CONTENT_DATA_URI, + SqlData.PROJECTION_DATA, + "(note_id=?)", + new String[]{String.valueOf(mId)}, + null + ); if (c != null) { if (c.getCount() == 0) { - Log.w(TAG, "it seems that the note has not data"); + Log.w(TAG, "该笔记无数据项"); return; } while (c.moveToNext()) { + // 每个数据项创建SqlData实例并添加到列表 SqlData data = new SqlData(mContext, c); mDataList.add(data); } } else { - Log.w(TAG, "loadDataContent: cursor = null"); + Log.w(TAG, "loadDataContent: 游标为空"); } } finally { - if (c != null) - c.close(); + if (c != null) c.close(); } } + /** + * 从JSON对象设置笔记内容(支持普通笔记、文件夹、系统文件夹) + * @param js 包含笔记数据的JSON对象 + * @return 是否设置成功 + */ public boolean setContent(JSONObject js) { try { - JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { - Log.w(TAG, "cannot set system folder"); - } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { - // for folder we can only update the snnipet and type - String snippet = note.has(NoteColumns.SNIPPET) ? note - .getString(NoteColumns.SNIPPET) : ""; - if (mIsCreate || !mSnippet.equals(snippet)) { - mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); - } - mSnippet = snippet; - - int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) - : Notes.TYPE_NOTE; - if (mIsCreate || mType != type) { - mDiffNoteValues.put(NoteColumns.TYPE, type); - } - mType = type; - } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { - JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); - long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID; - if (mIsCreate || mId != id) { - mDiffNoteValues.put(NoteColumns.ID, id); - } - mId = id; - - long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note - .getLong(NoteColumns.ALERTED_DATE) : 0; - if (mIsCreate || mAlertDate != alertDate) { - mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate); - } - mAlertDate = alertDate; - - int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note - .getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext); - if (mIsCreate || mBgColorId != bgColorId) { - mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId); + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 提取笔记元数据 + int type = note.getInt(NoteColumns.TYPE); // 获取笔记类型 + + // 系统文件夹禁止修改(直接返回) + if (type == Notes.TYPE_SYSTEM) { + Log.w(TAG, "无法修改系统文件夹"); + return false; + } else if (type == Notes.TYPE_FOLDER) { + // 文件夹仅允许更新摘要和类型 + String snippet = note.optString(NoteColumns.SNIPPET, ""); + if (!mSnippet.equals(snippet)) { // 对比现有摘要 + mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); // 记录差异 + mSnippet = snippet; } - mBgColorId = bgColorId; - - long createDate = note.has(NoteColumns.CREATED_DATE) ? note - .getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis(); - if (mIsCreate || mCreatedDate != createDate) { - mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate); - } - mCreatedDate = createDate; - - int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note - .getInt(NoteColumns.HAS_ATTACHMENT) : 0; - if (mIsCreate || mHasAttachment != hasAttachment) { - mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment); - } - mHasAttachment = hasAttachment; - - long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note - .getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis(); - if (mIsCreate || mModifiedDate != modifiedDate) { - mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate); - } - mModifiedDate = modifiedDate; - - long parentId = note.has(NoteColumns.PARENT_ID) ? note - .getLong(NoteColumns.PARENT_ID) : 0; - if (mIsCreate || mParentId != parentId) { - mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId); - } - mParentId = parentId; - - String snippet = note.has(NoteColumns.SNIPPET) ? note - .getString(NoteColumns.SNIPPET) : ""; - if (mIsCreate || !mSnippet.equals(snippet)) { - mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); - } - mSnippet = snippet; - - int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) - : Notes.TYPE_NOTE; - if (mIsCreate || mType != type) { - mDiffNoteValues.put(NoteColumns.TYPE, type); - } - mType = type; - - int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID) - : AppWidgetManager.INVALID_APPWIDGET_ID; - if (mIsCreate || mWidgetId != widgetId) { - mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId); + int newType = note.optInt(NoteColumns.TYPE, Notes.TYPE_NOTE); + if (mType != newType) { // 对比类型 + mDiffNoteValues.put(NoteColumns.TYPE, newType); // 记录差异 + mType = newType; } - mWidgetId = widgetId; + } else if (type == Notes.TYPE_NOTE) { + // 普通笔记处理(包含数据项) + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 提取数据项数组 - int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note - .getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE; - if (mIsCreate || mWidgetType != widgetType) { - mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType); - } - mWidgetType = widgetType; - - long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note - .getLong(NoteColumns.ORIGIN_PARENT_ID) : 0; - if (mIsCreate || mOriginParent != originParent) { - mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent); + // 基础字段更新 + long id = note.optLong(NoteColumns.ID, INVALID_ID); + if (mId != id) { + mDiffNoteValues.put(NoteColumns.ID, id); + mId = id; } - mOriginParent = originParent; + mAlertDate = note.optLong(NoteColumns.ALERTED_DATE, 0); + mBgColorId = note.optInt(NoteColumns.BG_COLOR_ID, ResourceParser.getDefaultBgId(mContext)); + mCreatedDate = note.optLong(NoteColumns.CREATED_DATE, System.currentTimeMillis()); + mHasAttachment = note.optInt(NoteColumns.HAS_ATTACHMENT, 0); + mModifiedDate = note.optLong(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + mParentId = note.optLong(NoteColumns.PARENT_ID, 0); + mSnippet = note.optString(NoteColumns.SNIPPET, ""); + mType = note.optInt(NoteColumns.TYPE, Notes.TYPE_NOTE); + mWidgetId = note.optInt(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + mWidgetType = note.optInt(NoteColumns.WIDGET_TYPE, Notes.TYPE_WIDGET_INVALIDE); + mOriginParent = note.optLong(NoteColumns.ORIGIN_PARENT_ID, 0); + + // 数据项处理(新增/更新) for (int i = 0; i < dataArray.length(); i++) { - JSONObject data = dataArray.getJSONObject(i); + JSONObject dataJson = dataArray.getJSONObject(i); + long dataId = dataJson.optLong(DataColumns.ID, INVALID_ID); SqlData sqlData = null; - if (data.has(DataColumns.ID)) { - long dataId = data.getLong(DataColumns.ID); - for (SqlData temp : mDataList) { - if (dataId == temp.getId()) { - sqlData = temp; - } + + // 查找已存在的数据项(通过ID匹配) + for (SqlData temp : mDataList) { + if (temp.getId() == dataId) { + sqlData = temp; + break; } } + // 不存在则创建新数据项 if (sqlData == null) { sqlData = new SqlData(mContext); mDataList.add(sqlData); } - sqlData.setContent(data); + // 设置数据项内容(自动处理差异) + sqlData.setContent(dataJson); } } } catch (JSONException e) { - Log.e(TAG, e.toString()); + Log.e(TAG, "JSON解析异常: " + e.toString()); e.printStackTrace(); return false; } return true; } + /** + * 将笔记内容转换为JSON对象(用于同步传输) + * @return JSON对象,包含笔记元数据和数据项 + */ public JSONObject getContent() { try { JSONObject js = new JSONObject(); - if (mIsCreate) { - Log.e(TAG, "it seems that we haven't created this in database yet"); + Log.e(TAG, "笔记尚未创建,无法生成JSON"); return null; } JSONObject note = new JSONObject(); if (mType == Notes.TYPE_NOTE) { + // 普通笔记:包含完整元数据和数据项 note.put(NoteColumns.ID, mId); note.put(NoteColumns.ALERTED_DATE, mAlertDate); note.put(NoteColumns.BG_COLOR_ID, mBgColorId); @@ -384,6 +340,7 @@ public class SqlNote { note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent); js.put(GTaskStringUtils.META_HEAD_NOTE, note); + // 数据项数组 JSONArray dataArray = new JSONArray(); for (SqlData sqlData : mDataList) { JSONObject data = sqlData.getContent(); @@ -393,113 +350,49 @@ public class SqlNote { } js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); } else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { + // 文件夹/系统文件夹:仅包含基础元数据 note.put(NoteColumns.ID, mId); note.put(NoteColumns.TYPE, mType); note.put(NoteColumns.SNIPPET, mSnippet); js.put(GTaskStringUtils.META_HEAD_NOTE, note); } - return js; } catch (JSONException e) { - Log.e(TAG, e.toString()); + Log.e(TAG, "JSON生成异常: " + e.toString()); e.printStackTrace(); } return null; } + // ==================== 辅助设置方法(更新差异值) ==================== public void setParentId(long id) { mParentId = id; - mDiffNoteValues.put(NoteColumns.PARENT_ID, id); + mDiffNoteValues.put(NoteColumns.PARENT_ID, id); // 记录父ID变更 } public void setGtaskId(String gid) { - mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); + mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); // 记录GTask ID变更 } public void setSyncId(long syncId) { - mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); + mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); // 记录同步ID变更 } public void resetLocalModified() { - mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); + mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); // 重置本地修改标记 } - public long getId() { - return mId; - } - - public long getParentId() { - return mParentId; - } - - public String getSnippet() { - return mSnippet; - } - - public boolean isNoteType() { - return mType == Notes.TYPE_NOTE; - } + // ==================== 基础属性访问器 ==================== + public long getId() { return mId; } + public long getParentId() { return mParentId; } + public String getSnippet() { return mSnippet; } + public boolean isNoteType() { return mType == Notes.TYPE_NOTE; } + /** + * 提交变更到数据库(执行插入或更新操作) + * @param validateVersion 是否验证版本号(防止并发冲突) + */ public void commit(boolean validateVersion) { if (mIsCreate) { - if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) { - mDiffNoteValues.remove(NoteColumns.ID); - } - - Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); - try { - mId = Long.valueOf(uri.getPathSegments().get(1)); - } catch (NumberFormatException e) { - Log.e(TAG, "Get note id error :" + e.toString()); - throw new ActionFailureException("create note failed"); - } - if (mId == 0) { - throw new IllegalStateException("Create thread id failed"); - } - - if (mType == Notes.TYPE_NOTE) { - for (SqlData sqlData : mDataList) { - sqlData.commit(mId, false, -1); - } - } - } else { - if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) { - Log.e(TAG, "No such note"); - throw new IllegalStateException("Try to update note with invalid id"); - } - if (mDiffNoteValues.size() > 0) { - mVersion ++; - int result = 0; - if (!validateVersion) { - result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?)", new String[] { - String.valueOf(mId) - }); - } else { - result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", - new String[] { - String.valueOf(mId), String.valueOf(mVersion) - }); - } - if (result == 0) { - Log.w(TAG, "there is no update. maybe user updates note when syncing"); - } - } - - if (mType == Notes.TYPE_NOTE) { - for (SqlData sqlData : mDataList) { - sqlData.commit(mId, validateVersion, mVersion); - } - } - } - - // refresh local info - loadFromCursor(mId); - if (mType == Notes.TYPE_NOTE) - loadDataContent(); - - mDiffNoteValues.clear(); - mIsCreate = false; - } -} + // 新建笔记:插入到笔记表 + if (mId == IN \ No newline at end of file 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 6a19454..bd39b66 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,19 +31,18 @@ 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(); @@ -54,21 +53,26 @@ 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); - // action_id + // 设置操作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // index + // 设置任务在列表中的索引位置 js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); - // entity_delta + // 设置任务实体信息 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); @@ -79,17 +83,17 @@ public class Task extends Node { } js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - // parent_id + // 设置父任务列表ID js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); - // dest_parent_type + // 设置目标父类型为任务组 js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_GROUP); - // list_id + // 设置任务列表ID js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); - // prior_sibling_id + // 设置前一个兄弟任务ID(如果有) if (mPriorSibling != null) { js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); } @@ -97,27 +101,32 @@ public class Task extends Node { } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); - throw new ActionFailureException("fail to generate task-create jsonobject"); + throw new ActionFailureException("生成创建任务的JSON对象失败"); } 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); - // action_id + // 设置操作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // id + // 设置任务ID js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - // entity_delta + // 设置要更新的任务实体信息 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); if (getNotes() != null) { @@ -129,67 +138,78 @@ public class Task extends Node { } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); - throw new ActionFailureException("fail to generate task-update jsonobject"); + throw new ActionFailureException("生成更新任务的JSON对象失败"); } return js; } + /** + * 从远程JSON数据设置任务内容 + * @param js 包含任务信息的JSON对象 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { - // id + // 从JSON中提取并设置任务ID if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); } - // last_modified + // 提取并设置最后修改时间 if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); } - // name + // 提取并设置任务名称 if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); } - // notes + // 提取并设置任务备注 if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); } - // deleted + // 提取并设置任务是否已删除 if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); } - // completed + // 提取并设置任务是否已完成 if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); } } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); - throw new ActionFailureException("fail to get task content from jsonobject"); + throw new ActionFailureException("从JSON对象获取任务内容失败"); } } } + /** + * 从本地JSON数据设置任务内容 + * @param js 包含本地笔记信息的JSON对象 + */ public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) || !js.has(GTaskStringUtils.META_HEAD_DATA)) { - Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); + Log.w(TAG, "setContentByLocalJSON: 没有可用数据"); } try { + // 提取笔记和数据信息 JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + // 确保笔记类型正确 if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { - Log.e(TAG, "invalid type"); + Log.e(TAG, "无效的笔记类型"); return; } + // 从数据中提取任务名称 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { @@ -204,16 +224,21 @@ public class Task extends Node { } } + /** + * 将任务内容转换为本地JSON格式 + * @return 包含任务信息的JSON对象 + */ public JSONObject getLocalJSONFromContent() { String name = getName(); try { if (mMetaInfo == null) { - // new task created from web + // 从网页创建的新任务 if (name == null) { - Log.w(TAG, "the note seems to be an empty one"); + Log.w(TAG, "该笔记似乎为空"); return null; } + // 创建新的JSON对象表示任务 JSONObject js = new JSONObject(); JSONObject note = new JSONObject(); JSONArray dataArray = new JSONArray(); @@ -225,10 +250,11 @@ public class Task extends Node { js.put(GTaskStringUtils.META_HEAD_NOTE, note); return js; } else { - // synced task + // 已同步的任务,更新现有元数据 JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + // 更新任务名称 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { @@ -247,6 +273,10 @@ public class Task extends Node { } } + /** + * 设置任务的元数据信息 + * @param metaData 元数据对象 + */ public void setMetaInfo(MetaData metaData) { if (metaData != null && metaData.getNotes() != null) { try { @@ -258,6 +288,11 @@ public class Task extends Node { } } + /** + * 根据本地数据库状态确定同步操作类型 + * @param c 包含本地笔记信息的数据库游标 + * @return 同步操作类型(如更新远程、更新本地、冲突等) + */ public int getSyncAction(Cursor c) { try { JSONObject noteInfo = null; @@ -266,40 +301,42 @@ public class Task extends Node { } if (noteInfo == null) { - Log.w(TAG, "it seems that note meta has been deleted"); + Log.w(TAG, "笔记元数据似乎已被删除"); return SYNC_ACTION_UPDATE_REMOTE; } if (!noteInfo.has(NoteColumns.ID)) { - Log.w(TAG, "remote note id seems to be deleted"); + Log.w(TAG, "远程笔记ID似乎已被删除"); return SYNC_ACTION_UPDATE_LOCAL; } - // validate the note id now + // 验证笔记ID if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { - Log.w(TAG, "note id doesn't match"); + Log.w(TAG, "笔记ID不匹配"); return SYNC_ACTION_UPDATE_LOCAL; } + // 判断是否有本地修改 if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update + // 本地没有更新 if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side + // 两边都没有更新 return SYNC_ACTION_NONE; } else { - // apply remote to local + // 应用远程更新到本地 return SYNC_ACTION_UPDATE_LOCAL; } } else { - // validate gtask id + // 有本地修改,验证GTask ID if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { - Log.e(TAG, "gtask id doesn't match"); + Log.e(TAG, "GTask ID不匹配"); return SYNC_ACTION_ERROR; } if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only + // 只有本地修改 return SYNC_ACTION_UPDATE_REMOTE; } else { + // 本地和远程都有修改,发生冲突 return SYNC_ACTION_UPDATE_CONFLICT; } } @@ -311,11 +348,17 @@ 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; } @@ -347,5 +390,4 @@ 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 4ea21c5..f634932 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,315 +29,292 @@ 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; + mChildren = new ArrayList<>(); // 初始化子任务集合 + mIndex = 1; // 默认索引为1(可能用于排序) } + /** + * 生成创建任务列表的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); - - // action_id + // 设置操作类型为"创建" + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); + // 设置操作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"); - 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"); // 创建者ID(固定值) + 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, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to generate tasklist-create jsonobject"); + Log.e(TAG, "生成创建任务列表JSON失败: " + e.getMessage()); + throw new ActionFailureException("创建任务列表JSON生成失败"); } - 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); - - // action_id + // 设置操作类型为"更新" + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); + // 设置操作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // id + // 设置任务列表ID(GID) 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, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to generate tasklist-update jsonobject"); + Log.e(TAG, "生成更新任务列表JSON失败: " + e.getMessage()); + throw new ActionFailureException("更新任务列表JSON生成失败"); } - return js; } + /** + * 从远程JSON数据设置任务列表内容 + * @param js 包含任务列表信息的JSON对象 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { - // id + // 提取并设置任务列表ID(GID) 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, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to get tasklist content from jsonobject"); + Log.e(TAG, "解析远程任务列表JSON失败: " + e.getMessage()); + throw new ActionFailureException("远程任务列表内容解析失败"); } } } + /** + * 从本地JSON数据设置任务列表内容(映射到本地笔记文件夹) + * @param js 包含本地笔记信息的JSON对象 + */ public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { - Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); + Log.w(TAG, "本地JSON数据为空或缺少笔记元数据"); + return; } - try { - JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 提取笔记元数据 + int type = folder.getInt(NoteColumns.TYPE); // 获取笔记类型 - if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { + if (type == Notes.TYPE_FOLDER) { + // 普通文件夹:设置名称(添加MIUI前缀) String name = folder.getString(NoteColumns.SNIPPET); setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); - } else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { - if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) + } else if (type == Notes.TYPE_SYSTEM) { + // 系统文件夹:根据ID设置固定名称 + long folderId = folder.getLong(NoteColumns.ID); + if (folderId == Notes.ID_ROOT_FOLDER) { setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); - 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 if (folderId == Notes.ID_CALL_RECORD_FOLDER) { + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE); + } else { + Log.e(TAG, "无效的系统文件夹ID: " + folderId); + } } else { - Log.e(TAG, "error type"); + Log.e(TAG, "无效的笔记类型: " + type); } } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); + Log.e(TAG, "解析本地任务列表JSON失败: " + e.getMessage()); } } + /** + * 将任务列表内容转换为本地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); - js.put(GTaskStringUtils.META_HEAD_NOTE, folder); + // 移除MIUI前缀(还原本地文件夹名称) + if (folderName.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) { + folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length()); + } + // 设置文件夹类型 + 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, e.toString()); - e.printStackTrace(); + Log.e(TAG, "生成本地任务列表JSON失败: " + e.getMessage()); 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()) { - // no update both side - return SYNC_ACTION_NONE; + return SYNC_ACTION_NONE; // 本地与远程一致,无操作 } else { - // apply remote to local - return SYNC_ACTION_UPDATE_LOCAL; + return SYNC_ACTION_UPDATE_LOCAL; // 应用远程更新到本地 } } else { - // validate gtask id + // 本地有修改 if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { - Log.e(TAG, "gtask id doesn't match"); - return SYNC_ACTION_ERROR; + Log.e(TAG, "GTask ID不匹配,同步错误"); + return SYNC_ACTION_ERROR; // GID不一致,同步错误 } if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only - return SYNC_ACTION_UPDATE_REMOTE; + return SYNC_ACTION_UPDATE_REMOTE; // 仅本地修改,更新远程 } else { - // for folder conflicts, just apply local modification + // 文件夹冲突处理策略:强制应用本地修改(可能丢失远程数据) + Log.w(TAG, "文件夹同步冲突,应用本地修改"); return SYNC_ACTION_UPDATE_REMOTE; } } } catch (Exception e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); + Log.e(TAG, "同步操作判断失败: " + e.getMessage()); + return SYNC_ACTION_ERROR; } - - return SYNC_ACTION_ERROR; } + // ==================== 子任务管理方法 ==================== public int getChildTaskCount() { - return mChildren.size(); + return mChildren.size(); // 获取子任务数量 } public boolean addChildTask(Task task) { - boolean ret = false; - if (task != null && !mChildren.contains(task)) { - ret = mChildren.add(task); - if (ret) { - // need to set prior sibling and parent - task.setPriorSibling(mChildren.isEmpty() ? null : mChildren - .get(mChildren.size() - 1)); - task.setParent(this); - } + 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); } return ret; } public boolean addChildTask(Task task, int index) { if (index < 0 || index > mChildren.size()) { - Log.e(TAG, "add child task: invalid index"); + Log.e(TAG, "添加子任务:无效索引"); return false; } - - 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); - } - + 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); return true; } public boolean removeChildTask(Task task) { - boolean ret = false; int index = mChildren.indexOf(task); - 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)); - } + 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); } } return ret; } - 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"); + public boolean moveChildTask(Task task, int newIndex) { + int oldIndex = mChildren.indexOf(task); + if (oldIndex == -1 || newIndex < 0 || newIndex >= mChildren.size()) { + Log.e(TAG, "移动子任务:无效索引或任务不存在"); return false; } - - if (pos == index) - return true; - return (removeChildTask(task) && addChildTask(task, index)); + if (oldIndex == newIndex) return true; + // 先移除再插入实现移动 + return removeChildTask(task) && addChildTask(task, newIndex); } public Task findChildTaskByGid(String gid) { - for (int i = 0; i < mChildren.size(); i++) { - Task t = mChildren.get(i); - if (t.getGid().equals(gid)) { - return t; + for (Task task : mChildren) { + if (task.getGid().equals(gid)) { + return task; // 根据GID查找子任务 } } 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, "getTaskByIndex: invalid index"); + Log.e(TAG, "获取子任务:无效索引"); return null; } - return mChildren.get(index); - } - - public Task getChilTaskByGid(String gid) { - for (Task task : mChildren) { - if (task.getGid().equals(gid)) - return task; - } - return null; + return mChildren.get(index); // 根据索引获取子任务 } public ArrayList getChildTaskList() { - return this.mChildren; + return mChildren; // 获取所有子任务列表 } + // ==================== 索引管理 ==================== public void setIndex(int index) { - this.mIndex = index; + this.mIndex = index; // 设置任务列表的排序索引 } public int getIndex() { - return this.mIndex; + return 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 15504be..016f0ea 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,18 +16,34 @@ 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 b08cfb1..0d2996c 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,18 +16,34 @@ 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 56c8e5e..bca1cb6 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,97 +28,136 @@ 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 int GTASK_SYNC_NOTIFICATION_ID = 5234235; + private static final int GTASK_SYNC_NOTIFICATION_ID = 5234235; // 同步通知的唯一标识 + // 完成监听器接口,用于同步完成后回调 public interface OnCompleteListener { - void onComplete(); + void onComplete(); // 同步完成回调方法 } - private Context mContext; - - private NotificationManager mNotifiManager; - - private GTaskManager mTaskManager; - - private OnCompleteListener mOnCompleteListener; + private Context mContext; // 上下文 + private NotificationManager mNotifiManager; // 通知管理器 + private GTaskManager mTaskManager; // Google Tasks管理类实例 + private OnCompleteListener mOnCompleteListener; // 完成监听器 + /** + * 构造方法 + * @param context 上下文 + * @param listener 同步完成监听器 + */ public GTaskASyncTask(Context context, OnCompleteListener listener) { mContext = context; mOnCompleteListener = listener; - mNotifiManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); + // 获取通知管理器服务 + mNotifiManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + // 获取Google Tasks管理类的单例实例 mTaskManager = GTaskManager.getInstance(); } + /** + * 取消同步操作 + */ public void cancelSync() { - mTaskManager.cancelSync(); + mTaskManager.cancelSync(); // 调用GTaskManager的取消同步方法 } + /** + * 发布进度消息(包装为数组以适配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; + // 创建通知对象 + 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 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); } -// notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, -// pendingIntent); + + // 设置通知内容(已弃用的setLatestEventInfo,改用contentIntent) 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]); + showNotification(R.string.ticker_syncing, progress[0]); // 显示同步中的通知 + // 如果上下文是GTaskSyncService,发送广播通知进度 if (mContext instanceof GTaskSyncService) { ((GTaskSyncService) mContext).sendBroadcast(progress[0]); } } + /** + * 处理后台任务完成(在主线程执行,用于处理结果和通知用户) + * @param result 同步结果状态码 + */ @Override protected void onPostExecute(Integer result) { + // 根据不同的结果状态显示对应的通知 if (result == GTaskManager.STATE_SUCCESS) { - showNotification(R.string.ticker_success, mContext.getString( - R.string.success_sync_account, mTaskManager.getSyncAccount())); + 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 c67dfdf..f7c6489 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,36 +60,31 @@ 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/"; - - 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 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 GTaskClient() { mHttpClient = null; mGetUrl = GTASK_GET_URL; @@ -102,6 +97,10 @@ public class GTaskClient { mUpdateArray = null; } + /** + * 获取单例实例 + * @return GTaskClient实例 + */ public static synchronized GTaskClient getInstance() { if (mInstance == null) { mInstance = new GTaskClient(); @@ -109,49 +108,49 @@ public class GTaskClient { return mInstance; } + /** + * 登录Google Tasks + * @param activity 活动上下文(用于获取账号令牌) + * @return 登录是否成功 + */ public boolean login(Activity activity) { - // we suppose that the cookie would expire after 5 minutes - // then we need to re-login + // 登录状态超时控制(5分钟) final long interval = 1000 * 60 * 5; if (mLastLoginTime + interval < System.currentTimeMillis()) { mLoggedin = false; } - - // need to re-login after account switch - if (mLoggedin - && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity - .getSyncAccountName(activity))) { + // 账号切换后强制重新登录 + if (mLoggedin && !TextUtils.equals(getSyncAccount().name, + NotesPreferenceActivity.getSyncAccountName(activity))) { mLoggedin = false; } - if (mLoggedin) { - Log.d(TAG, "already logged in"); + Log.d(TAG, "已登录"); return true; } mLastLoginTime = System.currentTimeMillis(); - String authToken = loginGoogleAccount(activity, false); + String authToken = loginGoogleAccount(activity, false); // 获取授权令牌 if (authToken == null) { - Log.e(TAG, "login google account failed"); + Log.e(TAG, "Google账号登录失败"); return false; } - // login with custom domain if necessary - if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() - .endsWith("googlemail.com"))) { + // 处理自定义域名账号(非gmail/googlemail后缀) + 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; @@ -159,103 +158,116 @@ 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"); + Account[] accounts = accountManager.getAccountsByType("com.google"); // 获取所有Google账号 if (accounts.length == 0) { - Log.e(TAG, "there is no available google account"); + Log.e(TAG, "无可用Google账号"); 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) { - mAccount = account; - } else { - Log.e(TAG, "unable to get an account with the same name in the settings"); + if (account == null) { + Log.e(TAG, "无法找到配置的同步账号"); return null; } + mAccount = account; - // get the token now - AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, + // 获取授权令牌 + AccountManagerFuture future = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); try { - Bundle authTokenBundle = accountManagerFuture.getResult(); - authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); + Bundle bundle = future.getResult(); + authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN); if (invalidateToken) { + // 失效旧令牌并重新获取(用于处理令牌过期) accountManager.invalidateAuthToken("com.google", authToken); - loginGoogleAccount(activity, false); + return loginGoogleAccount(activity, false); } + return authToken; } catch (Exception e) { - Log.e(TAG, "get auth token failed"); - authToken = null; + Log.e(TAG, "获取授权令牌失败: " + e.getMessage()); + return null; } - - return authToken; } + /** + * 尝试登录Google Tasks(携带授权令牌) + * @param activity 活动上下文 + * @param authToken 授权令牌 + * @return 登录是否成功 + */ private boolean tryToLoginGtask(Activity activity, String authToken) { - if (!loginGtask(authToken)) { - // maybe the auth token is out of date, now let's invalidate the - // token and try again + if (!loginGtask(authToken)) { // 首次登录尝试 + // 令牌可能过期,失效后重试 authToken = loginGoogleAccount(activity, true); if (authToken == null) { - Log.e(TAG, "login google account failed"); + Log.e(TAG, "重新获取令牌失败"); return false; } - - if (!loginGtask(authToken)) { - Log.e(TAG, "login gtask failed"); + if (!loginGtask(authToken)) { // 二次登录尝试 + Log.e(TAG, "二次登录失败"); return false; } } return true; } + /** + * 执行Google Tasks登录请求 + * @param authToken 授权令牌 + * @return 登录是否成功 + */ private boolean loginGtask(String authToken) { - int timeoutConnection = 10000; - int timeoutSocket = 15000; + int timeoutConnection = 10000; // 连接超时(10秒) + int timeoutSocket = 15000; // 套接字超时(15秒) HttpParams httpParameters = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); mHttpClient = new DefaultHttpClient(httpParameters); - BasicCookieStore localBasicCookieStore = new BasicCookieStore(); - mHttpClient.setCookieStore(localBasicCookieStore); + BasicCookieStore cookieStore = new BasicCookieStore(); + mHttpClient.setCookieStore(cookieStore); HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); - // login gtask try { - String loginUrl = mGetUrl + "?auth=" + authToken; + String loginUrl = mGetUrl + "?auth=" + authToken; // 构造登录URL HttpGet httpGet = new HttpGet(loginUrl); - HttpResponse response = null; - response = mHttpClient.execute(httpGet); + HttpResponse response = mHttpClient.execute(httpGet); // 发送GET请求 - // get the cookie now - List cookies = mHttpClient.getCookieStore().getCookies(); + // 检查Cookie是否包含GTL认证信息 + List cookies = cookieStore.getCookies(); boolean hasAuthCookie = false; for (Cookie cookie : cookies) { if (cookie.getName().contains("GTL")) { hasAuthCookie = true; + break; } } if (!hasAuthCookie) { - Log.w(TAG, "it seems that there is no auth cookie"); + Log.w(TAG, "未获取到认证Cookie"); } - // get the client version + // 解析响应获取客户端版本号 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -266,261 +278,293 @@ public class GTaskClient { jsString = resString.substring(begin + jsBegin.length(), end); } JSONObject js = new JSONObject(jsString); - mClientVersion = js.getLong("v"); + mClientVersion = js.getLong("v"); // 保存客户端版本号 + return true; + } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); + Log.e(TAG, "解析登录响应失败: " + e.getMessage()); return false; } catch (Exception e) { - // simply catch all exceptions - Log.e(TAG, "httpget gtask_url failed"); + Log.e(TAG, "登录请求失败: " + e.getMessage()); 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"); + httpPost.setHeader("AT", "1"); // Google Tasks特定请求头 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, "encoding: " + contentEncoding); + Log.d(TAG, "响应编码: " + contentEncoding); } InputStream input = entity.getContent(); - if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { - input = new GZIPInputStream(entity.getContent()); - } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { + // 处理GZip压缩 + if ("gzip".equalsIgnoreCase(contentEncoding)) { + input = new GZIPInputStream(input); + // 处理Deflate压缩 + } else if ("deflate".equalsIgnoreCase(contentEncoding)) { Inflater inflater = new Inflater(true); - input = new InflaterInputStream(entity.getContent(), inflater); + input = new InflaterInputStream(input, inflater); } try { InputStreamReader isr = new InputStreamReader(input); BufferedReader br = new BufferedReader(isr); StringBuilder sb = new StringBuilder(); - - while (true) { - String buff = br.readLine(); - if (buff == null) { - return sb.toString(); - } - sb = sb.append(buff); + String line; + while ((line = br.readLine()) != null) { + sb.append(line); } + return sb.toString(); } finally { input.close(); } } + /** + * 发送POST请求并解析响应 + * @param js 请求体JSON + * @return 响应JSON对象 + * @throws NetworkFailureException 网络异常 + */ private JSONObject postRequest(JSONObject js) throws NetworkFailureException { if (!mLoggedin) { - Log.e(TAG, "please login first"); - throw new ActionFailureException("not logged in"); + Log.e(TAG, "未登录,无法发送请求"); + throw new ActionFailureException("未登录"); } HttpPost httpPost = createHttpPost(); try { - LinkedList list = new LinkedList(); - list.add(new BasicNameValuePair("r", js.toString())); - UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); + // 构造请求参数(将JSON包裹在"r"参数中) + LinkedList params = new LinkedList<>(); + params.add(new BasicNameValuePair("r", js.toString())); + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params, "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.toString()); - e.printStackTrace(); - throw new NetworkFailureException("postRequest failed"); + Log.e(TAG, "协议异常: " + e.getMessage()); + throw new NetworkFailureException("POST请求失败"); } catch (IOException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new NetworkFailureException("postRequest failed"); + Log.e(TAG, "IO异常: " + e.getMessage()); + throw new NetworkFailureException("POST请求失败"); } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("unable to convert response content to jsonobject"); + Log.e(TAG, "JSON解析异常: " + e.getMessage()); + throw new ActionFailureException("响应解析失败"); } catch (Exception e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("error occurs when posting request"); + Log.e(TAG, "未知异常: " + e.getMessage()); + throw new ActionFailureException("请求处理失败"); } } + // ==================== 任务操作 ==================== + /** + * 创建任务 + * @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)); + task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 设置新生成的任务ID } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("create task: handing jsonobject failed"); + Log.e(TAG, "创建任务JSON处理失败: " + e.getMessage()); + throw new ActionFailureException("创建任务失败"); } } + /** + * 创建任务列表 + * @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)); + tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 设置列表ID } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("create tasklist: handing jsonobject failed"); + Log.e(TAG, "创建任务列表JSON处理失败: " + e.getMessage()); + throw new ActionFailureException("创建任务列表失败"); } } + /** + * 提交批量更新操作 + * @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.toString()); - e.printStackTrace(); - throw new ActionFailureException("commit update: handing jsonobject failed"); + Log.e(TAG, "提交批量更新失败: " + e.getMessage()); + throw new ActionFailureException("批量更新失败"); } } } + /** + * 添加更新操作到批量队列 + * @param node 节点对象(任务或列表) + * @throws NetworkFailureException 网络异常 + */ public void addUpdateNode(Node node) throws NetworkFailureException { if (node != null) { - // too many update items may result in an error - // set max to 10 items + // 限制批量操作数量(超过10个则先提交当前队列) if (mUpdateArray != null && mUpdateArray.length() > 10) { commitUpdate(); } - - if (mUpdateArray == null) + if (mUpdateArray == null) { mUpdateArray = new JSONArray(); - mUpdateArray.put(node.getUpdateAction(getActionId())); + } + mUpdateArray.put(node.getUpdateAction(getActionId())); // 添加更新操作到队列 } } - public void moveTask(Task task, TaskList preParent, TaskList curParent) + /** + * 移动任务(支持跨列表移动) + * @param task 要移动的任务 + * @param preParent 原父列表 + * @param curParent 新父列表 + * @throws NetworkFailureException 网络异常 + */ + 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_list - action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + // 构造移动操作请求 + 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()); + action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); // 任务ID + + // 同一列表内移动且非首个任务时,设置前置兄弟任务ID if (preParent == curParent && task.getPriorSibling() != null) { - // put prioring_sibing_id only if moving within the tasklist and - // it is not the first one action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); } - action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); - action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); + + action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); // 源列表ID + action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); // 目标父列表ID + + // 跨列表移动时设置目标列表ID if (preParent != curParent) { - // put the dest_list only if moving between tasklists action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); } + actionList.put(action); 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, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("move task: handing jsonobject failed"); + Log.e(TAG, "移动任务JSON处理失败: " + e.getMessage()); + throw new ActionFailureException("移动任务失败"); } } + /** + * 删除节点(任务或列表) + * @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); - - // 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.toString()); - e.printStackTrace(); - throw new ActionFailureException("delete node: handing jsonobject failed"); + Log.e(TAG, "删除节点JSON处理失败: " + e.getMessage()); + throw new ActionFailureException("删除节点失败"); } } + // ==================== 查询操作 ==================== + /** + * 获取所有任务列表 + * @return 任务列表JSON数组 + * @throws NetworkFailureException 网络异常 + */ public JSONArray getTaskLists() throws NetworkFailureException { if (!mLoggedin) { - Log.e(TAG, "please login first"); - throw new ActionFailureException("not logged in"); + Log.e(TAG, "未登录,无法获取任务列表"); + throw new ActionFailureException("未登录"); } try { HttpGet httpGet = new HttpGet(mGetUrl); - HttpResponse response = null; - response = mHttpClient.execute(httpGet); + HttpResponse response = mHttpClient.execute(httpGet); // 发送GET请求 - // get the task list + // 解析响应获取任务列表数据 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -532,54 +576,65 @@ public class GTaskClient { } JSONObject js = new JSONObject(jsString); return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); + } catch (ClientProtocolException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new NetworkFailureException("gettasklists: httpget failed"); + Log.e(TAG, "获取任务列表协议异常: " + e.getMessage()); + throw new NetworkFailureException("获取任务列表失败"); } catch (IOException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new NetworkFailureException("gettasklists: httpget failed"); + Log.e(TAG, "获取任务列表IO异常: " + e.getMessage()); + throw new NetworkFailureException("获取任务列表失败"); } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("get task lists: handing jasonobject failed"); + Log.e(TAG, "解析任务列表JSON异常: " + e.getMessage()); + throw new ActionFailureException("解析任务列表失败"); } } + /** + * 获取指定任务列表中的所有任务 + * @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_list - action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + // 构造获取任务列表操作 + action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); - action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); - action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); + action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); // 列表ID + 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, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("get task list: handing jsonobject failed"); + Log.e(TAG, "获取任务JSON处理失败: " + e.getMessage()); + throw new ActionFailureException("获取任务列表失败"); } } + // ==================== 辅助方法 ==================== + /** + * 获取当前同步的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 d2b4082..d50cf72 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 @@ -47,58 +47,55 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; - +/** + * Google Tasks同步管理类 + * 负责协调本地数据库与Google Tasks服务的数据同步逻辑,包括任务列表初始化、内容同步、冲突处理等 + */ public class GTaskManager { private static final String TAG = GTaskManager.class.getSimpleName(); - public static final int STATE_SUCCESS = 0; - - public static final int STATE_NETWORK_ERROR = 1; - - public static final int STATE_INTERNAL_ERROR = 2; - - public static final int STATE_SYNC_IN_PROGRESS = 3; - - public static final int STATE_SYNC_CANCELLED = 4; - - private static GTaskManager mInstance = null; - - private Activity mActivity; - - private Context mContext; - - private ContentResolver mContentResolver; - - private boolean mSyncing; - - private boolean mCancelled; - - private HashMap mGTaskListHashMap; - - private HashMap mGTaskHashMap; - - private HashMap mMetaHashMap; - - private TaskList mMetaList; - - private HashSet mLocalDeleteIdMap; - - private HashMap mGidToNid; - - private HashMap mNidToGid; - + // 同步状态码 + public static final int STATE_SUCCESS = 0; // 同步成功 + public static final int STATE_NETWORK_ERROR = 1; // 网络错误 + public static final int STATE_INTERNAL_ERROR = 2; // 内部错误 + public static final int STATE_SYNC_IN_PROGRESS = 3; // 同步进行中 + public static final int STATE_SYNC_CANCELLED = 4; // 同步取消 + + private static GTaskManager mInstance = null; // 单例实例 + + private Activity mActivity; // 活动上下文(用于获取账号令牌) + private Context mContext; // 应用上下文 + private ContentResolver mContentResolver; // 内容解析器(操作本地数据库) + private boolean mSyncing; // 同步进行状态 + private boolean mCancelled; // 同步取消标记 + private HashMap mGTaskListHashMap; // 远程任务列表缓存(GID为键) + private HashMap mGTaskHashMap; // 远程节点缓存(任务/列表,GID为键) + private HashMap mMetaHashMap; // 元数据缓存(关联任务与本地笔记) + private TaskList mMetaList; // 元数据存储的特殊任务列表 + private HashSet mLocalDeleteIdMap; // 本地已删除笔记ID集合 + private HashMap mGidToNid; // GID到本地笔记ID的映射 + private HashMap mNidToGid; // 本地笔记ID到GID的映射 + + /** + * 构造方法(私有化,保证单例) + */ private GTaskManager() { mSyncing = false; mCancelled = false; - mGTaskListHashMap = new HashMap(); - mGTaskHashMap = new HashMap(); - mMetaHashMap = new HashMap(); + // 初始化缓存集合 + mGTaskListHashMap = new HashMap<>(); + mGTaskHashMap = new HashMap<>(); + mMetaHashMap = new HashMap<>(); mMetaList = null; - mLocalDeleteIdMap = new HashSet(); - mGidToNid = new HashMap(); - mNidToGid = new HashMap(); + mLocalDeleteIdMap = new HashSet<>(); + mGidToNid = new HashMap<>(); + mNidToGid = new HashMap<>(); } + /** + * 获取单例实例 + * @return GTaskManager实例 + */ public static synchronized GTaskManager getInstance() { if (mInstance == null) { mInstance = new GTaskManager(); @@ -106,20 +103,31 @@ public class GTaskManager { return mInstance; } + /** + * 设置活动上下文(用于获取账号令牌) + * @param activity 活动对象 + */ 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"); + if (mSyncing) { // 防止并发同步 + Log.d(TAG, "同步正在进行中"); return STATE_SYNC_IN_PROGRESS; } + // 初始化上下文和解析器 mContext = context; mContentResolver = mContext.getContentResolver(); mSyncing = true; mCancelled = false; + // 清空缓存 mGTaskListHashMap.clear(); mGTaskHashMap.clear(); mMetaHashMap.clear(); @@ -129,33 +137,33 @@ public class GTaskManager { try { GTaskClient client = GTaskClient.getInstance(); - client.resetUpdateArray(); + client.resetUpdateArray(); // 重置批量更新队列 - // login google task - if (!mCancelled) { - if (!client.login(mActivity)) { - throw new NetworkFailureException("login google task failed"); - } + // 登录Google Tasks + if (!mCancelled && !client.login(mActivity)) { + throw new NetworkFailureException("Google Tasks登录失败"); } - // 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) { - Log.e(TAG, e.toString()); + + } catch (NetworkFailureException e) { // 网络异常 + Log.e(TAG, "网络错误: " + e.getMessage()); return STATE_NETWORK_ERROR; - } catch (ActionFailureException e) { - Log.e(TAG, e.toString()); + } catch (ActionFailureException e) { // 操作失败 + Log.e(TAG, "操作失败: " + e.getMessage()); return STATE_INTERNAL_ERROR; - } catch (Exception e) { - Log.e(TAG, e.toString()); + } catch (Exception e) { // 其他异常 + Log.e(TAG, "未知异常: " + e.getMessage()); e.printStackTrace(); return STATE_INTERNAL_ERROR; } finally { + // 清理资源 mGTaskListHashMap.clear(); mGTaskHashMap.clear(); mMetaHashMap.clear(); @@ -165,119 +173,124 @@ public class GTaskManager { mSyncing = false; } - return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; + return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; // 返回同步结果 } + /** + * 初始化远程任务列表(从Google Tasks获取) + * @throws NetworkFailureException 网络异常 + */ private void initGTaskList() throws NetworkFailureException { - if (mCancelled) - return; + if (mCancelled) return; // 取消同步时跳过 GTaskClient client = GTaskClient.getInstance(); try { - JSONArray jsTaskLists = client.getTaskLists(); + 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); + mMetaList.setContentByRemoteJSON(object); // 从JSON初始化列表信息 - // load meta data + // 加载元数据任务(存储在元数据列表中的任务) JSONArray jsMetas = client.getTaskList(gid); for (int j = 0; j < jsMetas.length(); j++) { - object = (JSONObject) jsMetas.getJSONObject(j); + JSONObject metaObject = jsMetas.getJSONObject(j); MetaData metaData = new MetaData(); - metaData.setContentByRemoteJSON(object); - if (metaData.isWorthSaving()) { - mMetaList.addChildTask(metaData); - if (metaData.getGid() != null) { - mMetaHashMap.put(metaData.getRelatedGid(), metaData); + metaData.setContentByRemoteJSON(metaObject); + if (metaData.isWorthSaving()) { // 过滤无效元数据 + mMetaList.addChildTask(metaData); // 添加到元数据列表 + if (metaData.getRelatedGid() != null) { + mMetaHashMap.put(metaData.getRelatedGid(), metaData); // 缓存元数据 } } } } } - // create meta list if not existed + // 如果元数据列表不存在,则创建新的 if (mMetaList == null) { mMetaList = new TaskList(); - mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_META); - GTaskClient.getInstance().createTaskList(mMetaList); + mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META); + client.createTaskList(mMetaList); // 调用远程API创建列表 } - // 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)) { + // 过滤系统生成的文件夹(以特定前缀开头,排除元数据列表) + if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) && + !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { TaskList tasklist = new TaskList(); - tasklist.setContentByRemoteJSON(object); - mGTaskListHashMap.put(gid, tasklist); - mGTaskHashMap.put(gid, tasklist); + tasklist.setContentByRemoteJSON(object); // 从JSON初始化列表信息 + 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); - gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); + JSONObject taskObject = jsTasks.getJSONObject(j); + String taskGid = taskObject.getString(GTaskStringUtils.GTASK_JSON_ID); Task task = new Task(); - task.setContentByRemoteJSON(object); - if (task.isWorthSaving()) { - task.setMetaInfo(mMetaHashMap.get(gid)); - tasklist.addChildTask(task); - mGTaskHashMap.put(gid, task); + task.setContentByRemoteJSON(taskObject); // 从JSON初始化任务信息 + if (task.isWorthSaving()) { // 过滤无效任务 + task.setMetaInfo(mMetaHashMap.get(taskGid)); // 关联元数据 + tasklist.addChildTask(task); // 添加到任务列表 + mGTaskHashMap.put(taskGid, task); // 缓存任务节点 } } } } } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("initGTaskList: handing JSONObject failed"); + Log.e(TAG, "解析任务列表JSON失败: " + e.getMessage()); + throw new ActionFailureException("初始化任务列表失败"); } } + /** + * 执行内容同步(本地与远程数据双向同步) + * @throws NetworkFailureException 网络异常 + */ private void syncContent() throws NetworkFailureException { - int syncType; - Cursor c = null; - String gid; - Node node; + int syncType; // 同步操作类型(增/删/改) + Cursor c = null; // 数据库查询游标 + String gid; // 远程GID + Node node; // 当前处理的节点 - mLocalDeleteIdMap.clear(); + mLocalDeleteIdMap.clear(); // 清空本地删除记录 if (mCancelled) { return; } - // for local deleted note + // 处理本地已删除的笔记(位于回收站) try { + // 查询回收站中的非系统笔记 c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, - "(type<>? AND parent_id=?)", new String[] { - String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) - }, null); + "(type<>? AND parent_id=?)", + new String[]{String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)}, + null); if (c != null) { while (c.moveToNext()) { - gid = c.getString(SqlNote.GTASK_ID_COLUMN); - node = mGTaskHashMap.get(gid); + gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取远程GID + node = mGTaskHashMap.get(gid); // 查找对应的远程节点 if (node != null) { - mGTaskHashMap.remove(gid); - doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); + mGTaskHashMap.remove(gid); // 从缓存中移除 + doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); // 删除远程节点 } - - mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 记录本地删除ID } } else { - Log.w(TAG, "failed to query trash folder"); + Log.w(TAG, "查询回收站失败"); } } finally { if (c != null) { @@ -286,39 +299,37 @@ 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[] { - String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER) - }, NoteColumns.TYPE + " DESC"); + "(type=? AND parent_id<>?)", + new String[]{String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER)}, + NoteColumns.TYPE + " DESC"); if (c != null) { while (c.moveToNext()) { - gid = c.getString(SqlNote.GTASK_ID_COLUMN); - node = mGTaskHashMap.get(gid); + gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取远程GID + node = mGTaskHashMap.get(gid); // 查找对应的远程节点 if (node != null) { - mGTaskHashMap.remove(gid); - mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); + mGTaskHashMap.remove(gid); // 从缓存中移除 + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // 记录GID与本地ID映射 mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); - syncType = node.getSyncAction(c); + syncType = node.getSyncAction(c); // 确定同步操作类型 } else { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add - syncType = Node.SYNC_ACTION_ADD_REMOTE; + syncType = Node.SYNC_ACTION_ADD_REMOTE; // 本地新增,需同步到远程 } else { - // remote delete - syncType = Node.SYNC_ACTION_DEL_LOCAL; + syncType = Node.SYNC_ACTION_DEL_LOCAL; // 远程已删除,需本地删除 } } - doContentSync(syncType, node, c); + doContentSync(syncType, node, c); // 执行同步操作 } } else { - Log.w(TAG, "failed to query existing note in database"); + Log.w(TAG, "查询本地笔记失败"); } - } finally { if (c != null) { c.close(); @@ -326,31 +337,33 @@ public class GTaskManager { } } - // go through remaining items + // 处理远程新增但本地没有的节点(遍历剩余缓存节点) Iterator> iter = mGTaskHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); node = entry.getValue(); - doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + 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"); + throw new ActionFailureException("批量删除本地已删除笔记失败"); } } - // refresh local sync id + // 提交批量更新并刷新本地同步ID if (!mCancelled) { - GTaskClient.getInstance().commitUpdate(); - refreshLocalSyncId(); + GTaskClient.getInstance().commitUpdate(); // 提交所有批量操作 + refreshLocalSyncId(); // 更新本地笔记的同步时间 } - } + // ==================== 文件夹同步 ==================== + /** + * 同步文件夹(系统文件夹和用户创建的文件夹) + * @throws NetworkFailureException 网络异常 + */ private void syncFolder() throws NetworkFailureException { Cursor c = null; String gid; @@ -361,27 +374,26 @@ 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); - if (c != null) { - c.moveToNext(); + if (c != null && c.moveToNext()) { gid = c.getString(SqlNote.GTASK_ID_COLUMN); node = mGTaskHashMap.get(gid); if (node != null) { 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)) + // 仅当远程名称不一致时更新(系统文件夹名称固定) + if (!node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); + } } else { - doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); // 新增到远程 } } else { - Log.w(TAG, "failed to query root folder"); + Log.w(TAG, "查询根文件夹失败"); } } finally { if (c != null) { @@ -390,32 +402,27 @@ 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) - }, null); - if (c != null) { - if (c.moveToNext()) { - gid = c.getString(SqlNote.GTASK_ID_COLUMN); - node = mGTaskHashMap.get(gid); - if (node != null) { - 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)) - doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); - } else { - doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); + new String[]{String.valueOf(Notes.ID_CALL_RECORD_FOLDER)}, null); + if (c != null && c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); + mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, + mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); + // 仅当远程名称不一致时更新(系统文件夹名称固定) + if (!node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { + doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); } + } else { + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); // 新增到远程 } } else { - Log.w(TAG, "failed to query call note folder"); + Log.w(TAG, "查询通话记录文件夹失败"); } } finally { if (c != null) { @@ -424,12 +431,12 @@ public class GTaskManager { } } - // for local existing folders + // 同步本地已存在的用户文件夹 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, - "(type=? AND parent_id<>?)", new String[] { - String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) - }, NoteColumns.TYPE + " DESC"); + "(type=? AND parent_id<>?)", + new String[]{String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, + NoteColumns.TYPE + " DESC"); if (c != null) { while (c.moveToNext()) { gid = c.getString(SqlNote.GTASK_ID_COLUMN); @@ -438,20 +445,18 @@ public class GTaskManager { mGTaskHashMap.remove(gid); mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); - syncType = node.getSyncAction(c); + syncType = node.getSyncAction(c); // 确定同步操作类型 } else { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add - syncType = Node.SYNC_ACTION_ADD_REMOTE; + syncType = Node.SYNC_ACTION_ADD_REMOTE; // 本地新增,需同步到远程 } else { - // remote delete - syncType = Node.SYNC_ACTION_DEL_LOCAL; + syncType = Node.SYNC_ACTION_DEL_LOCAL; // 远程已删除,需本地删除 } } - doContentSync(syncType, node, c); + doContentSync(syncType, node, c); // 执行同步操作 } } else { - Log.w(TAG, "failed to query existing folder"); + Log.w(TAG, "查询本地文件夹失败"); } } finally { if (c != null) { @@ -460,7 +465,7 @@ public class GTaskManager { } } - // for remote add folders + // 处理远程新增但本地没有的文件夹 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -468,14 +473,23 @@ public class GTaskManager { node = entry.getValue(); if (mGTaskHashMap.containsKey(gid)) { mGTaskHashMap.remove(gid); - doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 新增到本地 } } + // 提交批量更新(如果有) if (!mCancelled) GTaskClient.getInstance().commitUpdate(); } + // ==================== 内容同步核心方法 ==================== + /** + * 执行具体的同步操作(根据同步类型处理节点) + * @param syncType 同步操作类型(增/删/改等) + * @param node 要同步的节点 + * @param c 本地数据库游标(可能为null) + * @throws NetworkFailureException 网络异常 + */ private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -484,44 +498,52 @@ public class GTaskManager { MetaData meta; switch (syncType) { case Node.SYNC_ACTION_ADD_LOCAL: - addLocalNode(node); + addLocalNode(node); // 远程新增,添加到本地 break; case Node.SYNC_ACTION_ADD_REMOTE: - addRemoteNode(node, c); + addRemoteNode(node, c); // 本地新增,添加到远程 break; case Node.SYNC_ACTION_DEL_LOCAL: + // 远程已删除,本地也删除(并删除关联的元数据) meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); if (meta != null) { GTaskClient.getInstance().deleteNode(meta); } - mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 标记为本地待删除 break; case Node.SYNC_ACTION_DEL_REMOTE: + // 本地已删除,远程也删除(并删除关联的元数据) meta = mMetaHashMap.get(node.getGid()); if (meta != null) { GTaskClient.getInstance().deleteNode(meta); } - GTaskClient.getInstance().deleteNode(node); + GTaskClient.getInstance().deleteNode(node); // 删除远程节点 break; case Node.SYNC_ACTION_UPDATE_LOCAL: - updateLocalNode(node, c); + updateLocalNode(node, c); // 远程更新,更新本地 break; case Node.SYNC_ACTION_UPDATE_REMOTE: - updateRemoteNode(node, c); + updateRemoteNode(node, c); // 本地更新,更新远程 break; case Node.SYNC_ACTION_UPDATE_CONFLICT: - // merging both modifications maybe a good idea - // right now just use local update simply + // 冲突处理:本地和远程都有更新(当前策略:优先使用本地更新) + // 可优化为更智能的合并策略 updateRemoteNode(node, c); break; case Node.SYNC_ACTION_NONE: + // 无需同步(数据相同) break; case Node.SYNC_ACTION_ERROR: default: - throw new ActionFailureException("unkown sync action type"); + throw new ActionFailureException("未知同步操作类型"); } } + /** + * 将远程节点添加到本地数据库 + * @param node 远程节点(任务或列表) + * @throws NetworkFailureException 网络异常 + */ private void addLocalNode(Node node) throws NetworkFailureException { if (mCancelled) { return; @@ -529,32 +551,35 @@ public class GTaskManager { SqlNote sqlNote; if (node instanceof TaskList) { - if (node.getName().equals( - GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { - sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); - } else if (node.getName().equals( - GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { - sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); + // 处理文件夹 + if (node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { + sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); // 根文件夹 + } else if (node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { + sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); // 通话记录文件夹 } else { + // 普通文件夹 sqlNote = new SqlNote(mContext); - sqlNote.setContent(node.getLocalJSONFromContent()); - sqlNote.setParentId(Notes.ID_ROOT_FOLDER); + sqlNote.setContent(node.getLocalJSONFromContent()); // 设置内容 + sqlNote.setParentId(Notes.ID_ROOT_FOLDER); // 设置父文件夹为根文件夹 } } else { + // 处理任务(笔记) sqlNote = new SqlNote(mContext); - JSONObject js = node.getLocalJSONFromContent(); + JSONObject js = node.getLocalJSONFromContent(); // 获取远程内容并转换为本地格式 try { + // 处理笔记内容 if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); if (note.has(NoteColumns.ID)) { long id = note.getLong(NoteColumns.ID); if (DataUtils.existInNoteDatabase(mContentResolver, id)) { - // the id is not available, have to create a new one + // ID已存在,移除ID以创建新笔记 note.remove(NoteColumns.ID); } } } + // 处理笔记数据(如附件等) if (js.has(GTaskStringUtils.META_HEAD_DATA)) { JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); for (int i = 0; i < dataArray.length(); i++) { @@ -562,92 +587,106 @@ public class GTaskManager { if (data.has(DataColumns.ID)) { long dataId = data.getLong(DataColumns.ID); if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { - // the data id is not available, have to create - // a new one + // ID已存在,移除ID以创建新数据项 data.remove(DataColumns.ID); } } } - } } catch (JSONException e) { - Log.w(TAG, e.toString()); + Log.w(TAG, "处理JSON数据失败: " + e.getMessage()); e.printStackTrace(); } - sqlNote.setContent(js); + sqlNote.setContent(js); // 设置处理后的内容 + // 设置父文件夹(根据远程任务的父列表GID查找本地对应ID) Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); if (parentId == null) { - Log.e(TAG, "cannot find task's parent id locally"); - throw new ActionFailureException("cannot add local node"); + Log.e(TAG, "无法找到任务的父文件夹ID"); + throw new ActionFailureException("无法添加本地节点"); } sqlNote.setParentId(parentId.longValue()); } - // create the local node - sqlNote.setGtaskId(node.getGid()); - sqlNote.commit(false); + // 创建本地节点 + sqlNote.setGtaskId(node.getGid()); // 设置远程GID + sqlNote.commit(false); // 提交到数据库(不更新同步状态) - // update gid-nid mapping + // 更新GID与本地ID映射 mGidToNid.put(node.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), node.getGid()); - // update meta + // 更新元数据(关联远程任务与本地笔记) updateRemoteMeta(node.getGid(), sqlNote); } + /** + * 更新本地节点(根据远程变更) + * @param node 远程节点 + * @param c 本地数据库游标 + * @throws NetworkFailureException 网络异常 + */ private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; } - SqlNote sqlNote; - // update the note locally - sqlNote = new SqlNote(mContext, c); - sqlNote.setContent(node.getLocalJSONFromContent()); + SqlNote sqlNote = new SqlNote(mContext, c); // 从游标创建SqlNote对象 + sqlNote.setContent(node.getLocalJSONFromContent()); // 设置远程内容 - Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) - : new Long(Notes.ID_ROOT_FOLDER); + // 设置父文件夹(根据远程任务的父列表GID查找本地对应ID) + Long parentId = (node instanceof Task) ? + mGidToNid.get(((Task) node).getParent().getGid()) : + new Long(Notes.ID_ROOT_FOLDER); if (parentId == null) { - Log.e(TAG, "cannot find task's parent id locally"); - throw new ActionFailureException("cannot update local node"); + Log.e(TAG, "无法找到任务的父文件夹ID"); + throw new ActionFailureException("无法更新本地节点"); } sqlNote.setParentId(parentId.longValue()); - sqlNote.commit(true); + sqlNote.commit(true); // 提交更新(更新同步状态) - // update meta info + // 更新元数据 updateRemoteMeta(node.getGid(), sqlNote); } + /** + * 将本地节点添加到远程Google Tasks + * @param node 本地节点(可能为null,表示需要从游标创建) + * @param c 本地数据库游标 + * @throws NetworkFailureException 网络异常 + */ private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; } - SqlNote sqlNote = new SqlNote(mContext, c); + SqlNote sqlNote = new SqlNote(mContext, c); // 从游标创建SqlNote对象 Node n; - // update remotely + // 更新到远程 if (sqlNote.isNoteType()) { + // 处理笔记 Task task = new Task(); - task.setContentByLocalJSON(sqlNote.getContent()); + task.setContentByLocalJSON(sqlNote.getContent()); // 从本地内容初始化任务 + // 获取父文件夹的GID String parentGid = mNidToGid.get(sqlNote.getParentId()); if (parentGid == null) { - Log.e(TAG, "cannot find task's parent tasklist"); - throw new ActionFailureException("cannot add remote task"); + Log.e(TAG, "无法找到任务的父文件夹"); + throw new ActionFailureException("无法添加远程任务"); } - mGTaskListHashMap.get(parentGid).addChildTask(task); + mGTaskListHashMap.get(parentGid).addChildTask(task); // 添加到父列表 - GTaskClient.getInstance().createTask(task); + GTaskClient.getInstance().createTask(task); // 创建远程任务 n = (Node) task; - // add meta + // 添加元数据 updateRemoteMeta(task.getGid(), sqlNote); } else { + // 处理文件夹 TaskList tasklist = null; - // we need to skip folder if it has already existed + // 构建文件夹名称(添加特定前缀) String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) folderName += GTaskStringUtils.FOLDER_DEFAULT; @@ -656,6 +695,7 @@ public class GTaskManager { else folderName += sqlNote.getSnippet(); + // 检查文件夹是否已存在 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -671,87 +711,106 @@ public class GTaskManager { } } - // no match we can add now + // 如果不存在则创建新文件夹 if (tasklist == null) { tasklist = new TaskList(); - tasklist.setContentByLocalJSON(sqlNote.getContent()); - GTaskClient.getInstance().createTaskList(tasklist); - mGTaskListHashMap.put(tasklist.getGid(), tasklist); + tasklist.setContentByLocalJSON(sqlNote.getContent()); // 从本地内容初始化列表 + GTaskClient.getInstance().createTaskList(tasklist); // 创建远程列表 + mGTaskListHashMap.put(tasklist.getGid(), tasklist); // 缓存列表 } n = (Node) tasklist; } - // update local note + // 更新本地笔记(设置远程GID) sqlNote.setGtaskId(n.getGid()); - sqlNote.commit(false); - sqlNote.resetLocalModified(); - sqlNote.commit(true); + sqlNote.commit(false); // 提交(不更新同步状态) + sqlNote.resetLocalModified(); // 重置本地修改标记 + sqlNote.commit(true); // 再次提交(更新同步状态) - // gid-id mapping + // 更新GID与本地ID映射 mGidToNid.put(n.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), n.getGid()); } + /** + * 更新远程节点(根据本地变更) + * @param node 远程节点 + * @param c 本地数据库游标 + * @throws NetworkFailureException 网络异常 + */ private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; } - SqlNote sqlNote = new SqlNote(mContext, c); + SqlNote sqlNote = new SqlNote(mContext, c); // 从游标创建SqlNote对象 - // update remotely - node.setContentByLocalJSON(sqlNote.getContent()); - GTaskClient.getInstance().addUpdateNode(node); + // 更新远程节点 + 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(); + TaskList preParentList = task.getParent(); // 原父列表 - String curParentGid = mNidToGid.get(sqlNote.getParentId()); + String curParentGid = mNidToGid.get(sqlNote.getParentId()); // 当前父列表GID if (curParentGid == null) { - Log.e(TAG, "cannot find task's parent tasklist"); - throw new ActionFailureException("cannot update remote task"); + Log.e(TAG, "无法找到任务的父文件夹"); + throw new ActionFailureException("无法更新远程任务"); } - TaskList curParentList = mGTaskListHashMap.get(curParentGid); + TaskList curParentList = mGTaskListHashMap.get(curParentGid); // 当前父列表 + // 如果父列表变更,执行移动操作 if (preParentList != curParentList) { - preParentList.removeChildTask(task); - curParentList.addChildTask(task); - GTaskClient.getInstance().moveTask(task, preParentList, curParentList); + preParentList.removeChildTask(task); // 从原列表移除 + curParentList.addChildTask(task); // 添加到新列表 + GTaskClient.getInstance().moveTask(task, preParentList, curParentList); // 执行移动 } } - // clear local modified flag + // 清除本地修改标记 sqlNote.resetLocalModified(); sqlNote.commit(true); } + /** + * 更新远程元数据(关联任务与本地笔记) + * @param gid 远程任务GID + * @param sqlNote 本地笔记对象 + * @throws NetworkFailureException 网络异常 + */ private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { if (sqlNote != null && sqlNote.isNoteType()) { - MetaData metaData = mMetaHashMap.get(gid); + MetaData metaData = mMetaHashMap.get(gid); // 获取元数据 if (metaData != null) { + // 元数据已存在,更新内容 metaData.setMeta(gid, sqlNote.getContent()); - GTaskClient.getInstance().addUpdateNode(metaData); + GTaskClient.getInstance().addUpdateNode(metaData); // 添加到批量更新队列 } else { + // 元数据不存在,创建新的 metaData = new MetaData(); metaData.setMeta(gid, sqlNote.getContent()); - mMetaList.addChildTask(metaData); - mMetaHashMap.put(gid, metaData); - GTaskClient.getInstance().createTask(metaData); + mMetaList.addChildTask(metaData); // 添加到元数据列表 + mMetaHashMap.put(gid, metaData); // 缓存元数据 + GTaskClient.getInstance().createTask(metaData); // 创建远程任务(元数据以任务形式存储) } } } + /** + * 刷新本地同步ID(更新本地笔记的最后同步时间) + * @throws NetworkFailureException 网络异常 + */ private void refreshLocalSyncId() throws NetworkFailureException { if (mCancelled) { return; } - // get the latest gtask list + // 重新获取最新的远程任务列表 mGTaskHashMap.clear(); mGTaskListHashMap.clear(); mMetaHashMap.clear(); @@ -759,28 +818,29 @@ public class GTaskManager { Cursor c = null; try { + // 查询所有非系统、非回收站的笔记 c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, - "(type<>? AND parent_id<>?)", new String[] { - String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) - }, NoteColumns.TYPE + " DESC"); + "(type<>? AND parent_id<>?)", + new String[]{String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)}, + NoteColumns.TYPE + " DESC"); if (c != null) { while (c.moveToNext()) { String gid = c.getString(SqlNote.GTASK_ID_COLUMN); Node node = mGTaskHashMap.get(gid); if (node != null) { mGTaskHashMap.remove(gid); + // 更新本地笔记的同步ID(记录最后修改时间) ContentValues values = new ContentValues(); values.put(NoteColumns.SYNC_ID, node.getLastModified()); mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(SqlNote.ID_COLUMN)), values, null, null); } else { - Log.e(TAG, "something is missed"); - throw new ActionFailureException( - "some local items don't have gid after sync"); + Log.e(TAG, "找不到对应的远程节点"); + throw new ActionFailureException("同步后部分本地项没有GID"); } } } else { - Log.w(TAG, "failed to query local note to refresh sync id"); + Log.w(TAG, "查询本地笔记以刷新同步ID失败"); } } finally { if (c != null) { @@ -790,11 +850,18 @@ public class GTaskManager { } } + /** + * 获取当前同步的Google账号 + * @return 账号名称 + */ public String getSyncAccount() { return GTaskClient.getInstance().getSyncAccount().name; } + /** + * 取消同步操作 + */ public void cancelSync() { mCancelled = true; } -} +} \ No newline at end of file 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 cca36f7..375c779 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,54 +23,72 @@ 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() { - mSyncTask = null; + super.onCreate(); + 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(); @@ -81,48 +99,75 @@ 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); + GTaskManager.getInstance().setActivityContext(activity); // 设置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 6706cf6..2f9e49b 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,6 +15,7 @@ */ package net.micode.notes.model; + import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentUris; @@ -33,114 +34,167 @@ 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"; + /** - * Create a new note id for adding a new note to databases + * 创建新笔记并返回笔记ID + * @param context 上下文 + * @param folderId 父文件夹ID + * @return 新笔记ID */ public static synchronized long getNewNoteId(Context context, long folderId) { - // Create a new note in the database + // 创建新笔记的基本信息 ContentValues values = new ContentValues(); long createdTime = System.currentTimeMillis(); - values.put(NoteColumns.CREATED_DATE, createdTime); - 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); + 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 Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); long noteId = 0; try { - noteId = Long.valueOf(uri.getPathSegments().get(1)); + noteId = Long.valueOf(uri.getPathSegments().get(1)); // 从URI中解析笔记ID } catch (NumberFormatException e) { - Log.e(TAG, "Get note id error :" + e.toString()); + Log.e(TAG, "获取笔记ID错误: " + e.toString()); noteId = 0; } if (noteId == -1) { - throw new IllegalStateException("Wrong note id:" + noteId); + throw new IllegalStateException("错误的笔记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("Wrong note id:" + noteId); + throw new IllegalArgumentException("错误的笔记ID: " + noteId); } if (!isLocalModified()) { - return true; + return true; // 没有修改,无需同步 } /** - * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and - * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the - * note data info + * 理论上,一旦数据发生变化,笔记应该更新LOCAL_MODIFIED和MODIFIED_DATE字段 + * 为了数据安全,即使更新笔记失败,我们仍然更新笔记数据信息 */ if (context.getContentResolver().update( - ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, - null) == 0) { - Log.e(TAG, "Update note error, should not happen"); - // Do not return, fall through + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), + mNoteDiffValues, null, null) == 0) { + Log.e(TAG, "更新笔记错误,这种情况不应该发生"); + // 不返回,继续处理数据更新 } - 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; - - private ContentValues mTextDataValues; - - private long mCallDataId; - - private ContentValues mCallDataValues; - + private long mTextDataId; // 文本数据ID + private ContentValues mTextDataValues; // 文本数据值 + private long mCallDataId; // 通话记录数据ID + private ContentValues mCallDataValues; // 通话记录数据值 private static final String TAG = "NoteData"; + /** + * 构造函数 + */ public NoteData() { mTextDataValues = new ContentValues(); mCallDataValues = new ContentValues(); @@ -148,106 +202,140 @@ 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("Text data id should larger than 0"); + if (id <= 0) { + throw new IllegalArgumentException("文本数据ID应该大于0"); } mTextDataId = id; } + /** + * 设置通话记录数据ID + * @param id 数据ID + */ void setCallDataId(long id) { if (id <= 0) { - throw new IllegalArgumentException("Call data id should larger than 0"); + throw new IllegalArgumentException("通话记录数据ID应该大于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("Wrong note id:" + noteId); + throw new IllegalArgumentException("错误的笔记ID: " + noteId); } - ArrayList operationList = new ArrayList(); + ArrayList operationList = new ArrayList<>(); ContentProviderOperation.Builder builder = null; - if(mTextDataValues.size() > 0) { - mTextDataValues.put(DataColumns.NOTE_ID, noteId); + // 处理文本数据 + if (mTextDataValues.size() > 0) { + mTextDataValues.put(DataColumns.NOTE_ID, noteId); // 设置关联的笔记ID + if (mTextDataId == 0) { + // 新建文本数据 mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); - Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, - mTextDataValues); + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mTextDataValues); try { - setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); + setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); // 获取新ID } catch (NumberFormatException e) { - Log.e(TAG, "Insert new text data fail with noteId" + noteId); + Log.e(TAG, "插入新文本数据失败,笔记ID: " + 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); + // 处理通话记录数据 + if (mCallDataValues.size() > 0) { + mCallDataValues.put(DataColumns.NOTE_ID, noteId); // 设置关联的笔记ID + if (mCallDataId == 0) { + // 新建通话记录数据 mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); - Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, - mCallDataValues); + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mCallDataValues); try { - setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); + setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); // 获取新ID } catch (NumberFormatException e) { - Log.e(TAG, "Insert new call data fail with noteId" + noteId); + Log.e(TAG, "插入新通话记录数据失败,笔记ID: " + 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 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())); + } catch (RemoteException | OperationApplicationException e) { + Log.e(TAG, "批量操作异常: " + 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 be081e4..d01b700 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,37 +31,41 @@ import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.TextNote; import net.micode.notes.tool.ResourceParser.NoteBgResources; - +/** + * 工作笔记类 + * 管理笔记的编辑状态、属性和数据同步,支持普通笔记和通话记录笔记 + */ public class WorkingNote { - // Note for the working note + // 笔记对象 private Note mNote; - // Note Id + // 笔记ID private long mNoteId; - // Note content + // 笔记内容 private String mContent; - // Note mode + // 笔记模式(普通文本或待办列表) private int mMode; - + // 提醒日期 private long mAlertDate; - + // 修改日期 private long mModifiedDate; - + // 背景颜色ID private int mBgColorId; - + // 小部件ID private int mWidgetId; - + // 小部件类型 private int mWidgetType; - + // 文件夹ID private long mFolderId; - + // 上下文 private Context mContext; - + // 日志标签 private static final String TAG = "WorkingNote"; - + // 删除标记 private boolean mIsDeleted; - + // 设置变更监听器 private NoteSettingChangedListener mNoteSettingStatusListener; + // 数据查询投影 public static final String[] DATA_PROJECTION = new String[] { DataColumns.ID, DataColumns.CONTENT, @@ -72,6 +76,7 @@ public class WorkingNote { DataColumns.DATA4, }; + // 笔记查询投影 public static final String[] NOTE_PROJECTION = new String[] { NoteColumns.PARENT_ID, NoteColumns.ALERTED_DATE, @@ -81,27 +86,25 @@ public class WorkingNote { NoteColumns.MODIFIED_DATE }; + // 数据列索引 private static final int DATA_ID_COLUMN = 0; - private static final int DATA_CONTENT_COLUMN = 1; - private static final int DATA_MIME_TYPE_COLUMN = 2; - private static final int DATA_MODE_COLUMN = 3; + // 笔记列索引 private static final int NOTE_PARENT_ID_COLUMN = 0; - private static final int NOTE_ALERTED_DATE_COLUMN = 1; - private static final int NOTE_BG_COLOR_ID_COLUMN = 2; - private static final int NOTE_WIDGET_ID_COLUMN = 3; - private static final int NOTE_WIDGET_TYPE_COLUMN = 4; - private static final int NOTE_MODIFIED_DATE_COLUMN = 5; - // New note construct + /** + * 创建新笔记的构造函数 + * @param context 上下文 + * @param folderId 父文件夹ID + */ private WorkingNote(Context context, long folderId) { mContext = context; mAlertDate = 0; @@ -114,23 +117,32 @@ public class WorkingNote { mWidgetType = Notes.TYPE_WIDGET_INVALIDE; } - // Existing note construct + /** + * 加载现有笔记的构造函数 + * @param context 上下文 + * @param noteId 笔记ID + * @param folderId 父文件夹ID + */ private WorkingNote(Context context, long noteId, long folderId) { mContext = context; mNoteId = noteId; 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); @@ -140,40 +152,53 @@ public class WorkingNote { } cursor.close(); } else { - Log.e(TAG, "No note with id:" + mNoteId); - throw new IllegalArgumentException("Unable to find note with id " + mNoteId); + Log.e(TAG, "找不到ID为" + mNoteId + "的笔记"); + throw new IllegalArgumentException("无法找到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)); } else if (DataConstants.CALL_NOTE.equals(type)) { + // 通话记录笔记数据 mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); } else { - Log.d(TAG, "Wrong note type with type:" + type); + Log.d(TAG, "错误的笔记类型: " + type); } } while (cursor.moveToNext()); } cursor.close(); } else { - Log.e(TAG, "No data with id:" + mNoteId); - throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId); + Log.e(TAG, "找不到ID为" + mNoteId + "的笔记数据"); + throw new IllegalArgumentException("无法找到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); @@ -183,23 +208,33 @@ 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, "Create new note fail with id:" + mNoteId); + Log.e(TAG, "创建新笔记失败,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 @@ -212,11 +247,20 @@ 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; @@ -225,10 +269,19 @@ public class WorkingNote { } } + /** + * 设置笔记设置变更监听器 + * @param l 监听器 + */ public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { mNoteSettingStatusListener = l; } + /** + * 设置提醒日期 + * @param date 提醒日期 + * @param set 是否设置提醒 + */ public void setAlertDate(long date, boolean set) { if (date != mAlertDate) { mAlertDate = date; @@ -239,14 +292,22 @@ 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; @@ -257,6 +318,10 @@ public class WorkingNote { } } + /** + * 设置待办列表模式 + * @param mode 模式 + */ public void setCheckListMode(int mode) { if (mMode != mode) { if (mNoteSettingStatusListener != null) { @@ -267,6 +332,10 @@ public class WorkingNote { } } + /** + * 设置小部件类型 + * @param type 小部件类型 + */ public void setWidgetType(int type) { if (type != mWidgetType) { mWidgetType = type; @@ -274,6 +343,10 @@ public class WorkingNote { } } + /** + * 设置小部件ID + * @param id 小部件ID + */ public void setWidgetId(int id) { if (id != mWidgetId) { mWidgetId = id; @@ -281,6 +354,10 @@ public class WorkingNote { } } + /** + * 设置笔记文本内容 + * @param text 文本内容 + */ public void setWorkingText(String text) { if (!TextUtils.equals(mContent, text)) { mContent = text; @@ -288,81 +365,137 @@ 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; } + /** + * 笔记设置变更监听器接口 + */ public interface NoteSettingChangedListener { /** - * Called when the background color of current note has just changed + * 当笔记背景颜色改变时调用 */ void onBackgroundColorChanged(); /** - * Called when user set clock + * 当用户设置提醒时钟时调用 */ void onClockAlertChanged(long date, boolean set); /** - * Call when user create note from widget + * 当用户从小部件创建笔记时调用 */ void onWidgetChanged(); /** - * Call when switch between check list mode and normal mode - * @param oldMode is previous mode before change - * @param newMode is new mode + * 当在待办列表模式和普通模式之间切换时调用 + * @param oldMode 切换前的模式 + * @param newMode 切换后的模式 */ void onCheckListModeChanged(int oldMode, int newMode); } -} +} \ No newline at end of file