+ */
+ public static final String BG_COLOR_ID = "bg_color_id";
+
+ /**
+ * For text note, it doesn't has attachment, for multi-media
+ * note, it has at least one attachment(笔记是否包含附件)
+ *
Type: INTEGER
+ */
+ public static final String HAS_ATTACHMENT = "has_attachment";
+
+ /**
+ * Folder's count of notes(文件夹中笔记的数量)
+ *
Type: INTEGER (long)
+ */
+ public static final String NOTES_COUNT = "notes_count";
+
+ /**
+ * The file type: folder or note(笔记类型,可以是文件夹或笔记)
+ *
Type: INTEGER
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * The last sync id(最后同步 ID)
+ *
Type: INTEGER (long)
+ */
+ public static final String SYNC_ID = "sync_id";
+
+ /**
+ * Sign to indicate local modified or not(本地是否修改过)
+ *
Type: INTEGER
+ */
+ public static final String LOCAL_MODIFIED = "local_modified";
+
+ /**
+ * Original parent id before moving into temporary folder(移动到临时文件夹之前的原始父级 ID)
+ *
Type : INTEGER
+ */
+ public static final String ORIGIN_PARENT_ID = "origin_parent_id";
+
+ /**
+ * The gtask id(Google 任务 ID)
+ *
Type : TEXT
+ */
+ public static final String GTASK_ID = "gtask_id";
+
+ /**
+ * The version code(版本号)
+ *
Type : INTEGER (long)
+ */
+ public static final String VERSION = "version";
+ }
+
+ /**
+ * 这个接口DataColumns定义了笔记应用程序中的笔记数据表格中所有列的常量,
+ * 而NotesColumns定义了笔记应用程序中的笔记数据表格中的特定列的常量。
+ * 因此,DataColumns更通用,而NotesColumns更具体。
+ * NotesColumns接口包含笔记的标题、正文和颜色等属性,
+ * 而DataColumns接口包含笔记数据表格中的通用列。
+ * 在许多情况下,这两个接口将一起使用。所以重复的常量就不在做解释了。
+ */
+ public interface DataColumns {
+ /**
+ * The unique ID for a row
+ *
Type: INTEGER (long)
+ */
+ public static final String ID = "_id";
+
+ /**
+ * The MIME type of the item represented by this row.(表示该行数据的MIME类型)
+ *
Type: Text
+ */
+ public static final String MIME_TYPE = "mime_type";
+
+ /**
+ * The reference id to note that this data belongs to(表示该行数据所属的笔记的ID)
+ *
Type: INTEGER (long)
+ */
+ public static final String NOTE_ID = "note_id";
+
+ /**
+ * Created data for note or folder
+ *
Type: INTEGER (long)
+ */
+ public static final String CREATED_DATE = "created_date";
+
+ /**
+ * Latest modified date
+ *
Type: INTEGER (long)
+ */
+ public static final String MODIFIED_DATE = "modified_date";
+
+ /**
+ * Data's content
+ *
Type: TEXT
+ */
+ public static final String CONTENT = "content";
+
+
+ /**
+ * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
+ * integer data type
+ *
Type: INTEGER
+ */
+ public static final String DATA1 = "data1";
+
+ /**
+ * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
+ * integer data type
+ *
Type: INTEGER
+ */
+ public static final String DATA2 = "data2";
+
+ /**
+ * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
+ * TEXT data type
+ *
Type: TEXT
+ */
+ public static final String DATA3 = "data3";
+
+ /**
+ * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
+ * TEXT data type
+ *
Type: TEXT
+ */
+ public static final String DATA4 = "data4";
+
+ /**
+ * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
+ * TEXT data type
+ *
Type: TEXT
+ */
+ public static final String DATA5 = "data5";
+ }
+
+ /**
+ * 这是一个嵌套在NotePad类中的静态内部类TextNote。
+ * 它实现了DataColumns接口,并定义了特定于文本笔记的常量和内容URI。
+ * 与NotesColumns类似,TextNote还具有特定于文本笔记的常量,
+ * 例如MODE和MODE_CHECK_LIST,以及特定于文本笔记的内容类型和内容项类型常量。
+ * 此外,TextNote还定义了用于访问文本笔记数据的内容URI,该URI指向ContentProvider的text_note表。
+ */
+ public static final class TextNote implements DataColumns {
+ /**
+ * Mode to indicate the text in check list mode or not
+ *
Type: Integer 1:check list mode 0: normal mode
+ */
+ public static final String MODE = DATA1;//MODE字段表示文本笔记的模式,可以是正常模式或者是带有任务清单的模式,类型为整数。它使用了接口中的DATA1字段来表示其存储值的列名。
+
+ public static final int MODE_CHECK_LIST = 1;//MODE_CHECK_LIST字段表示任务清单模式的值,为1。
+
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
+ //CONTENT_TYPE字段表示此数据的MIME类型,指示ContentProvider返回的数据类型。此处表示返回的是文本笔记列表。
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";
+ //CONTENT_ITEM_TYPE字段表示此数据的单个项目的MIME类型。
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
+ //CONTENT_URI字段表示此数据的URI地址。
+ }
+
+ /**
+ * 这是一个嵌套在Notes类中的静态内部类CallNote,实现了DataColumns接口。
+ * CallNote定义了一些特定于电话记录的字段,如CALL_DATE(通话日期)和PHONE_NUMBER(电话号码),
+ * 并定义了ContentProvider使用的MIME类型和URI。
+ */
+ public static final class CallNote implements DataColumns {
+ /**
+ * Call date for this record(记录通话日期)
+ *
Type: INTEGER (long)
+ */
+ public static final String CALL_DATE = DATA1;
+ //表示通话日期的列名,数据类型为long,存储在DATA1列中。
+ /**
+ * Phone number for this record(记录通话号码)
+ *
Type: TEXT
+ */
+ public static final String PHONE_NUMBER = DATA3;
+ //表示电话号码的列名,数据类型为String,存储在DATA3列中。
+ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
+ //单条通话记录,用于指定MIME类型的常量
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
+ //同上,多条通话记录
+
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
+ //该类的内容URI,用于访问通话记录。
+ }
+}
diff --git a/other/06_190243114庞浩_代码标注/NotesDatabaseHelper.java b/other/06_190243114庞浩_代码标注/NotesDatabaseHelper.java
new file mode 100644
index 0000000..d68f611
--- /dev/null
+++ b/other/06_190243114庞浩_代码标注/NotesDatabaseHelper.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.data;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+import net.micode.notes.data.Notes.DataColumns;
+import net.micode.notes.data.Notes.DataConstants;
+import net.micode.notes.data.Notes.NoteColumns;
+
+/**
+ * 这段代码定义了一个名为NotesDatabaseHelper的类,继承自SQLiteOpenHelper。
+ * 在该类中定义了数据库名(DB_NAME)和数据库版本号(DB_VERSION),
+ * 并定义了一个内部接口TABLE,该接口中定义了NOTE和DATA两个常量,分别表示数据库中的两个表名。
+ * 其中SQLiteOpenHelper是Android提供的用于管理SQLite数据库的类,
+ * 它提供了创建、更新、打开和关闭数据库的方法,同时它也能够在不同的版本之间管理数据库的迁移。
+ */
+public class NotesDatabaseHelper extends SQLiteOpenHelper {
+ private static final String DB_NAME = "note.db";
+
+ private static final int DB_VERSION = 4;
+
+ public interface TABLE {
+ public static final String NOTE = "note";
+
+ public static final String DATA = "data";
+ }
+
+ private static final String TAG = "NotesDatabaseHelper";//这一行代码定义了一个名为TAG的字符串常量,用于在调试时作为日志标签。
+ /**
+ * 定义了一个静态变量mInstance,类型是NotesDatabaseHelper,并且标记为static,
+ * 表示这个变量是属于类的而不是实例的。
+ * 在类被加载的时候,这个变量会被创建。这个变量的作用是在整个应用中只创建一个NotesDatabaseHelper实例,
+ * 这样可以避免在多个地方重复创建数据库连接和实例,提高应用的性能和效率。
+ */
+ private static NotesDatabaseHelper mInstance;
+ /**
+ * 这段代码定义了两个字符串常量 CREATE_NOTE_TABLE_SQL 和 CREATE_DATA_TABLE_SQL,
+ * 分别用于创建两个数据库表格:note 和 data。
+ */
+ private static final String CREATE_NOTE_TABLE_SQL =
+ "CREATE TABLE " + TABLE.NOTE + "(" +
+ NoteColumns.ID + " INTEGER PRIMARY KEY," +//笔记 ID,整数类型,作为主键
+ NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +//父笔记 ID,整数类型,不为空,默认为 0
+ NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," +//提醒时间,整数类型,不为空,默认为 0
+ NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," +//背景颜色 ID,整数类型,不为空,默认为 0
+ NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +//创建时间,整数类型,不为空,默认为当前时间
+ NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," +//是否有附件,整数类型,不为空,默认为 0
+ NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +//修改时间,整数类型,不为空,默认为当前时间
+ NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," +//笔记数量,整数类型,不为空,默认为 0
+ NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," +//笔记概要,文本类型,不为空,默认为空字符串
+ NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," +//笔记类型,整数类型,不为空,默认为 0
+ NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," +//小部件 ID,整数类型,不为空,默认为 0
+ NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," +//小部件类型,整数类型,不为空,默认为 -1
+ NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," +//同步 ID,整数类型,不为空,默认为 0
+ NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +//本地修改标识,整数类型,不为空,默认为 0
+ NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +//原始父笔记 ID,整数类型,不为空,默认为 0
+ NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +//Google 任务 ID,文本类型,不为空,默认为空字符串
+ NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +//版本号,整数类型,不为空,默认为 0
+ ")";
+
+ private static final String CREATE_DATA_TABLE_SQL =
+ "CREATE TABLE " + TABLE.DATA + "(" +
+ DataColumns.ID + " INTEGER PRIMARY KEY," +//数据 ID,整数类型,作为主键
+ DataColumns.MIME_TYPE + " TEXT NOT NULL," +//MIME 类型,文本类型,不为空
+ DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," +//笔记 ID,整数类型,不为空,默认为 0
+ NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +//创建时间,整数类型,不为空,默认为当前时间
+ NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +//修改时间,整数类型,不为空,默认为当前时间
+ DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," +//数据内容,文本类型,不为空,默认为空字符串
+ DataColumns.DATA1 + " INTEGER," +
+ DataColumns.DATA2 + " INTEGER," +
+ DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
+ DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
+ DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
+ ")";
+ /**
+ * 这是一个用于创建数据库索引的SQL语句,它将创建一个名为"note_id_index"的索引,
+ * 该索引将位于"TABLE.DATA"表上,并将使用"DataColumns.NOTE_ID"列作为索引键。
+ * 这个索引的目的是在查询该表时提高性能,尤其是在根据笔记ID进行过滤时。如果索引已经存在,则不会重复创建。
+ */
+ private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
+ "CREATE INDEX IF NOT EXISTS note_id_index ON " +
+ TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
+//以下是各种 SQLite 数据库的触发器,用于更新数据库
+ /**
+ * Increase folder's note count when move note to the folder(移动笔记到文件夹时增加文件夹的笔记数)
+ */
+ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
+ "CREATE TRIGGER increase_folder_count_on_update "+
+ " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
+ " BEGIN " +
+ " UPDATE " + TABLE.NOTE +
+ " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
+ " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
+ " END";
+
+ /**
+ * Decrease folder's note count when move note from folder(从文件夹中移动笔记时减少文件夹笔记数)
+ */
+ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
+ "CREATE TRIGGER decrease_folder_count_on_update " +
+ " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
+ " BEGIN " +
+ " UPDATE " + TABLE.NOTE +
+ " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
+ " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
+ " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
+ " END";
+
+ /**
+ * Increase folder's note count when insert new note to the folder(向文件夹插入新笔记时增加文件夹笔记计数)
+ */
+ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
+ "CREATE TRIGGER increase_folder_count_on_insert " +
+ " AFTER INSERT ON " + TABLE.NOTE +
+ " BEGIN " +
+ " UPDATE " + TABLE.NOTE +
+ " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
+ " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
+ " END";
+
+ /**
+ * Decrease folder's note count when delete note from the folder(从文件夹中删除笔记时减少文件夹的笔记数)
+ */
+ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
+ "CREATE TRIGGER decrease_folder_count_on_delete " +
+ " AFTER DELETE ON " + TABLE.NOTE +
+ " BEGIN " +
+ " UPDATE " + TABLE.NOTE +
+ " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
+ " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
+ " AND " + NoteColumns.NOTES_COUNT + ">0;" +
+ " END";
+
+ /**
+ * Update note's content when insert data with type {@link DataConstants#NOTE}(插入DataConstants类型的数据时更新笔记的内容)
+ */
+ private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
+ "CREATE TRIGGER update_note_content_on_insert " +
+ " AFTER INSERT ON " + TABLE.DATA +
+ " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
+ " BEGIN" +
+ " UPDATE " + TABLE.NOTE +
+ " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
+ " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
+ " END";
+
+ /**
+ * Update note's content when data with {@link DataConstants#NOTE} type has changed(当DataConstants类型的数据发生更改时更新笔记的内容)
+ */
+ private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
+ "CREATE TRIGGER update_note_content_on_update " +
+ " AFTER UPDATE ON " + TABLE.DATA +
+ " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
+ " BEGIN" +
+ " UPDATE " + TABLE.NOTE +
+ " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
+ " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
+ " END";
+
+ /**
+ * Update note's content when data with {@link DataConstants#NOTE} type has deleted(删除DataConstants类型的数据时更新笔记的内容)
+ */
+ private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
+ "CREATE TRIGGER update_note_content_on_delete " +
+ " AFTER delete ON " + TABLE.DATA +
+ " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
+ " BEGIN" +
+ " UPDATE " + TABLE.NOTE +
+ " SET " + NoteColumns.SNIPPET + "=''" +
+ " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
+ " END";
+
+ /**
+ * Delete datas belong to note which has been deleted(删除属于已删除笔记的数据)
+ */
+ private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
+ "CREATE TRIGGER delete_data_on_delete " +
+ " AFTER DELETE ON " + TABLE.NOTE +
+ " BEGIN" +
+ " DELETE FROM " + TABLE.DATA +
+ " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +
+ " END";
+
+ /**
+ * Delete notes belong to folder which has been deleted(删除属于已删除文件夹的笔记)
+ */
+ private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
+ "CREATE TRIGGER folder_delete_notes_on_delete " +
+ " AFTER DELETE ON " + TABLE.NOTE +
+ " BEGIN" +
+ " DELETE FROM " + TABLE.NOTE +
+ " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
+ " END";
+
+ /**
+ * Move notes belong to folder which has been moved to trash folder(当一个文件夹被更新到废纸篓时,将其下所有笔记移动到废纸篓文件夹)
+ */
+ private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
+ "CREATE TRIGGER folder_move_notes_on_trash " +
+ " AFTER UPDATE ON " + TABLE.NOTE +
+ " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
+ " BEGIN" +
+ " UPDATE " + TABLE.NOTE +
+ " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
+ " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
+ " END";
+
+ /**
+ * 这段代码是一个构造函数,用于创建一个NotesDatabaseHelper对象。这个对象是用于管理SQLite数据库的帮助类,可以用来创建、升级和管理数据库表格。
+ * 构造函数有四个参数,分别是:
+ * Context context:上下文对象,用于访问应用程序的资源和环境。
+ * String DB_NAME:数据库的名称。
+ * CursorFactory factory:用于创建游标对象的工厂类,如果为null,则使用默认工厂。
+ * int DB_VERSION:数据库的版本号,用于管理数据库的升级。
+ */
+ public NotesDatabaseHelper(Context context) {
+ super(context, DB_NAME, null, DB_VERSION);
+ }
+
+ /**
+ * 该方法负责在数据库中创建笔记表。
+ * 它执行SQL语句CREATE_NOTE_TABLE_SQL来创建表,然后调用reCreateNoteTableTriggers方法来重新创建与笔记表关联的触发器。
+ * 然后,它通过调用createSystemFolder方法创建一个系统文件夹,并使用Log.d方法记录一条消息,以指示笔记表已经创建。
+ */
+ public void createNoteTable(SQLiteDatabase db) {
+ db.execSQL(CREATE_NOTE_TABLE_SQL);
+ reCreateNoteTableTriggers(db);
+ createSystemFolder(db);
+ Log.d(TAG, "note table has been created");
+ }
+
+ /**
+ * 该方法用于重新创建笔记表相关的触发器。
+ * 它首先使用DROP TRIGGER语句删除所有已有的触发器,然后使用CREATE TRIGGER语句重新创建它们。
+ * 这个方法是为了在升级数据库时使用,以便更新旧版本的触发器。
+ * @param db
+ */
+ private void reCreateNoteTableTriggers(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
+ db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete");
+ db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
+ db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
+
+ db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
+ db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
+ db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
+ db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER);
+ db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER);
+ db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
+ db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
+ }
+
+ /**
+ * 此方法在数据库中创建四个系统文件夹:通话记录文件夹、根文件夹、临时文件夹和废纸篓文件夹。
+ * 该方法创建一个ContentValues对象,并为每个文件夹的ID和TYPE列添加值。
+ * 然后,该方法使用SQLiteDatabase类的insert()方法将值插入笔记表中。
+ * 系统文件夹的ID定义为Notes类中的常量。
+ * 通话记录文件夹用于通话笔记,根文件夹是默认文件夹,临时文件夹用于移动笔记,废纸篓文件夹是移动已删除笔记的地方。
+ */
+ private void createSystemFolder(SQLiteDatabase db) {
+ ContentValues values = new ContentValues();
+
+ /**
+ * call record folder for call notes
+ */
+ values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
+ values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
+ db.insert(TABLE.NOTE, null, values);
+
+ /**
+ * root folder which is default folder(mo
+ */
+ values.clear();
+ values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
+ values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
+ db.insert(TABLE.NOTE, null, values);
+
+ /**
+ * temporary folder which is used for moving note
+ */
+ values.clear();
+ values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
+ values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
+ db.insert(TABLE.NOTE, null, values);
+
+ /**
+ * create trash folder
+ */
+ values.clear();
+ values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
+ values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
+ db.insert(TABLE.NOTE, null, values);
+ }
+
+ /**
+ * 此方法在数据库中创建数据表,并设置必要的触发器和索引。
+ * 它将SQLiteDatabase类的实例作为参数,用于执行SQL语句以创建数据表、重新创建触发器和创建索引。
+ * CREATE_DATA_TABLE_SQL常量保存用于创建数据表的SQL语句,该语句定义列及其数据类型。
+ * 调用recreatedatabletriggers()方法来删除和重新创建与数据表关联的触发器,这将确保触发器是最新的模式更改。
+ * CREATE_DATA_NOTE_ID_INDEX_SQL常量保存SQL语句,用于在数据表的NOTE ID列上创建索引,
+ * 这提高了在NOTE ID列上联接数据和NOTE表的查询的性能。最后,打印一条日志消息,指示数据表已经创建。
+ */
+ public void createDataTable(SQLiteDatabase db) {
+ db.execSQL(CREATE_DATA_TABLE_SQL);
+ reCreateDataTableTriggers(db);
+ db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
+ Log.d(TAG, "data table has been created");
+ }
+
+ /**
+ * Recreatedatabletriggers方法用于删除用于在insert、update和delete上更新注释内容的现有触发器,
+ * 然后使用最新定义重新创建它们。
+ * 以下是被删除的触发器:update_note_content_on_insert_content_on_update_note_content_on_delete,
+ * 这些是重新创建的触发器:data_update_note_content_on_insert_trigger data_update_trigger data_update_note_content_on_delete_trigger
+ * 这些触发器确保每当在数据表中创建、更新或删除笔记时,笔记表中的snippet字段都会被更新。
+ */
+ private void reCreateDataTableTriggers(SQLiteDatabase db) {
+ db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
+ db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete");
+
+ db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER);
+ db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
+ db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
+ }
+
+ /**
+ * 这是一个静态方法,它返回NotesDatabaseHelper类的单个实例。
+ * 它将Context对象作为参数,并检查NotesDatabaseHelper的实例是否为null。
+ * 如果实例为null,则创建NotesDatabaseHelper类的新实例并返回它。如果实例已经存在,则返回现有实例。
+ * 该方法是同步的,以确保一次只有一个线程可以访问它,避免任何并发问题。
+ */
+ static synchronized NotesDatabaseHelper getInstance(Context context) {
+ if (mInstance == null) {
+ mInstance = new NotesDatabaseHelper(context);
+ }
+ return mInstance;
+ }
+
+ /**
+ * 这是NotesDatabaseHelper类中onCreate()和onUpgrade()方法的实现。
+ * 第一次创建数据库时调用onCreate()。
+ * 它分别使用createNoteTable()和createDataTable()方法在数据库中创建Note和Data两个表。
+ * 当数据库需要升级到较新版本时,将调用onUpgrade()。
+ * 它首先检查数据库的旧版本,并执行必要的升级将其带到新版本。
+ * 在这个实现中,有三个升级步骤:从版本1升级到版本2:这将删除现有的Note和数据表,并使用createNoteTable()和createDataTable()方法重新创建它们。
+ * 从版本2升级到版本3:这将删除一些未使用的触发器,并向Note表添加一个新列gtask_id。它还为回收筒创建一个新的系统文件夹。
+ * 从版本3升级到版本4:这将向注释表添加一个新的列版本。
+ * 如果在升级步骤中删除了任何触发器,则调用reCreateNoteTableTriggers()和recreateDatabletriggers()方法来重新创建它们。
+ * 如果升级因某种原因失败,将引发IllegalStateException。
+ */
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ createNoteTable(db);
+ createDataTable(db);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ boolean reCreateTriggers = false;
+ boolean skipV2 = false;
+
+ if (oldVersion == 1) {
+ upgradeToV2(db);
+ skipV2 = true; // this upgrade including the upgrade from v2 to v3
+ oldVersion++;
+ }
+
+ if (oldVersion == 2 && !skipV2) {
+ upgradeToV3(db);
+ reCreateTriggers = true;
+ oldVersion++;
+ }
+
+ if (oldVersion == 3) {
+ upgradeToV4(db);
+ oldVersion++;
+ }
+
+ if (reCreateTriggers) {
+ reCreateNoteTableTriggers(db);
+ reCreateDataTableTriggers(db);
+ }
+
+ if (oldVersion != newVersion) {
+ throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ + "fails");
+ }
+ }
+
+ private void upgradeToV2(SQLiteDatabase db) {
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
+ createNoteTable(db);
+ createDataTable(db);
+ }
+
+ private void upgradeToV3(SQLiteDatabase db) {
+ // drop unused triggers
+ db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
+ db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
+ db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
+ // add a column for gtask id
+ db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ + " TEXT NOT NULL DEFAULT ''");
+ // add a trash system folder
+ ContentValues values = new ContentValues();
+ values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
+ values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
+ db.insert(TABLE.NOTE, null, values);
+ }
+
+ private void upgradeToV4(SQLiteDatabase db) {
+ db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ + " INTEGER NOT NULL DEFAULT 0");
+ }
+}
diff --git a/other/06_190243114庞浩_代码标注/NotesProvider.java b/other/06_190243114庞浩_代码标注/NotesProvider.java
new file mode 100644
index 0000000..e1e5eb2
--- /dev/null
+++ b/other/06_190243114庞浩_代码标注/NotesProvider.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.data;
+
+
+import android.app.SearchManager;
+import android.content.ContentProvider;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+import net.micode.notes.R;
+import net.micode.notes.data.Notes.DataColumns;
+import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.data.NotesDatabaseHelper.TABLE;
+
+
+public class NotesProvider extends ContentProvider {//定义NotesProvider类,它继承自ContentProvider类
+ private static final UriMatcher mMatcher;//声明UriMatcher实例
+
+ private NotesDatabaseHelper mHelper;//声明NotesDatabaseHelper实例用于匹配Uri,以确定请求的类型,例如是否是请求特定的数据项,或者是否是搜索建议请求。
+
+ private static final String TAG = "NotesProvider";
+
+ private static final int URI_NOTE = 1;//代表笔记数据的URI
+ private static final int URI_NOTE_ITEM = 2;//代表单个笔记数据的URI
+ private static final int URI_DATA = 3;//代表附加数据的URI
+ private static final int URI_DATA_ITEM = 4;//代表单个附加数据的URI
+
+ private static final int URI_SEARCH = 5;//代表搜索请求的URI
+ private static final int URI_SEARCH_SUGGEST = 6;//代表搜索建议请求的URI
+
+ static {
+ mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
+ mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
+ mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
+ mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
+ mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
+ mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
+ mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
+ }
+
+ /**
+ * x'0A' represents the '\n' character in sqlite. For title and content in the search result,
+ * we will trim '\n' and white space in order to show more information.
+ */
+ // 定义将在搜索结果中返回的列
+ private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
+ + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + ","
+ + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + ","
+ + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + ","
+ + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
+ + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;
+ // 定义将执行以检索搜索结果的搜索查询
+ private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION
+ + " FROM " + TABLE.NOTE// 指定要搜索的表
+ + " WHERE " + NoteColumns.SNIPPET + " LIKE ?"// 指定搜索查询
+ + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER// 排除在垃圾桶文件夹中的笔记
+ + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;// 排除非笔记条目
+
+ @Override
+ public boolean onCreate() {// 获取数据库帮助类的实例
+ mHelper = NotesDatabaseHelper.getInstance(getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ Cursor c = null;// 获取可读的数据库实例
+ SQLiteDatabase db = mHelper.getReadableDatabase();
+ String id = null;
+ switch (mMatcher.match(uri)) {
+ case URI_NOTE:// 查询所有笔记
+ c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
+ sortOrder);
+ break;
+ case URI_NOTE_ITEM:// 查询单个笔记
+ id = uri.getPathSegments().get(1);
+ c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ + parseSelection(selection), selectionArgs, null, null, sortOrder);
+ break;
+ case URI_DATA:// 查询所有数据
+ c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
+ sortOrder);
+ break;
+ case URI_DATA_ITEM: // 查询单个数据
+ id = uri.getPathSegments().get(1);
+ c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ + parseSelection(selection), selectionArgs, null, null, sortOrder);
+ break;
+ case URI_SEARCH:
+ case URI_SEARCH_SUGGEST:// 搜索笔记
+ if (sortOrder != null || projection != null) {
+ throw new IllegalArgumentException(
+ "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
+ }
+
+ String searchString = null;
+ if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
+ if (uri.getPathSegments().size() > 1) {
+ searchString = uri.getPathSegments().get(1);
+ }
+ } else {
+ searchString = uri.getQueryParameter("pattern");
+ }
+
+ if (TextUtils.isEmpty(searchString)) {
+ return null;
+ }
+
+ try {// 搜索所有匹配的笔记
+ searchString = String.format("%%%s%%", searchString);
+ c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
+ new String[] { searchString });
+ } catch (IllegalStateException ex) {
+ Log.e(TAG, "got exception: " + ex.toString());
+ }
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ if (c != null) {// 设置通知URI,以便在数据更改时通知观察者
+ c.setNotificationUri(getContext().getContentResolver(), uri);
+ }
+ return c;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {// 获取可写的 SQLiteDatabase 对象
+ SQLiteDatabase db = mHelper.getWritableDatabase();// 初始化三个变量,用于存储 note、data 和插入的记录的 ID
+ long dataId = 0, noteId = 0, insertedId = 0;// 根据传入的 Uri 进行匹配
+ switch (mMatcher.match(uri)) {
+ case URI_NOTE:// 向 TABLE.NOTE 表中插入记录,并获取插入的记录的 ID
+ insertedId = noteId = db.insert(TABLE.NOTE, null, values);
+ break;
+ case URI_DATA:
+ if (values.containsKey(DataColumns.NOTE_ID)) {// 如果 ContentValues 包含 DataColumns.NOTE_ID 列
+ noteId = values.getAsLong(DataColumns.NOTE_ID);// 获取 DataColumns.NOTE_ID 列的值
+ } else {
+ Log.d(TAG, "Wrong data format without note id:" + values.toString());
+ }//打印日志
+ insertedId = dataId = db.insert(TABLE.DATA, null, values);// 向 TABLE.DATA 表中插入记录,并获取插入的记录的 ID
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);// 抛出 IllegalArgumentException 异常
+ }
+ // Notify the note uri
+ if (noteId > 0) { // 如果 noteId 大于 0,则通知 content resolver 对应的 Uri 对应的数据已经发生了变化
+ getContext().getContentResolver().notifyChange(
+ ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
+ }
+
+ // Notify the data uri
+ if (dataId > 0) {// 如果 dataId 大于 0,则通知 content resolver 对应的 Uri 对应的数据已经发生了变化
+ getContext().getContentResolver().notifyChange(
+ ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
+ }
+
+ return ContentUris.withAppendedId(uri, insertedId); // 返回插入的记录的 Uri
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ int count = 0;
+ String id = null;
+ SQLiteDatabase db = mHelper.getWritableDatabase();
+ boolean deleteData = false;
+ switch (mMatcher.match(uri)) {// 根据传入的 Uri 进行匹配
+ case URI_NOTE:// 如果匹配到 URI_NOTE
+ selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";// 组合 selection 条件,以确保删除的是用户笔记,而不是系统文件夹
+ count = db.delete(TABLE.NOTE, selection, selectionArgs);// 删除 TABLE.NOTE 表中符合条件的记录,并返回删除的数量
+ break;
+ case URI_NOTE_ITEM: // 如果匹配到 URI_NOTE_ITEM
+ id = uri.getPathSegments().get(1);// 获取 Uri 中的 ID
+ /**
+ * ID that smaller than 0 is system folder which is not allowed to
+ * trash
+ */
+ long noteId = Long.valueOf(id);
+ if (noteId <= 0) { // 如果 ID 小于等于 0,则直接跳出 switch 语句
+ break;
+ }
+ count = db.delete(TABLE.NOTE,
+ NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);// 删除 TABLE.NOTE 表中符合条件的记录,并返回删除的数量
+ break;
+ case URI_DATA:// 如果匹配到 URI_DATA
+ count = db.delete(TABLE.DATA, selection, selectionArgs);// 删除 TABLE.DATA 表中符合条件的记录,并返回删除的数量
+ deleteData = true;
+ break;
+ case URI_DATA_ITEM:// 如果匹配到 URI_DATA_ITEM
+ id = uri.getPathSegments().get(1); // 获取 Uri 中的 ID
+ count = db.delete(TABLE.DATA,
+ DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);// 删除 TABLE.DATA 表中符合条件的记录,并返回删除的数量
+ deleteData = true;
+ break;
+ default:// 如果无法匹配到 Uri
+ throw new IllegalArgumentException("Unknown URI " + uri);// 抛出 IllegalArgumentException 异常
+ }
+ if (count > 0) {// 如果删除的数量大于 0
+ if (deleteData) {
+ getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
+ } // 如果删除的是 TABLE.DATA 表中的记录,则通知 content resolver 对应的 Uri 对应的笔记数据已经发生了变化
+ getContext().getContentResolver().notifyChange(uri, null);
+ }
+ return count;// 返回删除的数量
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ int count = 0;// 受影响的行数
+ String id = null;// 被更新的笔记或数据项的ID
+ SQLiteDatabase db = mHelper.getWritableDatabase();// 获取可写数据库
+ boolean updateData = false;// 是否更新数据项
+
+ switch (mMatcher.match(uri)) {// 使用 switch 语句处理不同类型的 URI
+ case URI_NOTE:
+ increaseNoteVersion(-1, selection, selectionArgs);// 在更新前增加所有受影响的笔记的版本号
+ count = db.update(TABLE.NOTE, values, selection, selectionArgs);// 更新数据库中的行并获取受影响的行数
+
+ break;
+ case URI_NOTE_ITEM:
+ id = uri.getPathSegments().get(1);// 在更新前增加笔记的版本号
+ increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); // 更新数据库中的行并获取受影响的行数
+ count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ + parseSelection(selection), selectionArgs);
+ break;
+ case URI_DATA:
+ count = db.update(TABLE.DATA, values, selection, selectionArgs);
+ updateData = true;
+ break;
+ case URI_DATA_ITEM:
+ id = uri.getPathSegments().get(1);
+ count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ + parseSelection(selection), selectionArgs);
+ updateData = true;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ // 如果有行被更新,通知已注册的 ContentObserver
+ if (count > 0) {
+ if (updateData) {
+ getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
+ }
+ getContext().getContentResolver().notifyChange(uri, null);
+ }
+ return count; // 返回受影响的行数
+ }
+ // 辅助方法,如果传递了 selection 参数,则将其添加到 SQL 查询中
+ private String parseSelection(String selection) {
+ return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
+ }
+ // 辅助方法,在更新笔记之前增加其版本号
+ private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
+ StringBuilder sql = new StringBuilder(120);
+ sql.append("UPDATE ");
+ sql.append(TABLE.NOTE);
+ sql.append(" SET ");
+ sql.append(NoteColumns.VERSION);
+ sql.append("=" + NoteColumns.VERSION + "+1 ");
+ // 如果传递了 ID 或 selection 参数,则将其添加到 SQL 查询中
+ if (id > 0 || !TextUtils.isEmpty(selection)) {
+ sql.append(" WHERE ");
+ }
+ if (id > 0) {
+ sql.append(NoteColumns.ID + "=" + String.valueOf(id));
+ }
+ if (!TextUtils.isEmpty(selection)) {
+ String selectString = id > 0 ? parseSelection(selection) : selection;
+ for (String args : selectionArgs) {
+ selectString = selectString.replaceFirst("\\?", args);
+ }
+ sql.append(selectString);
+ }
+
+ mHelper.getWritableDatabase().execSQL(sql.toString());// 执行 SQL 查询以更新笔记的版本号
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ // TODO Auto-generated method stub这是一个待办事项,需要根据 ContentProvider需求添加适当的 MIME类型。
+ return null;
+ }
+
+}
diff --git a/other/06_190243114庞浩_代码标注/WorkingNote.java b/other/06_190243114庞浩_代码标注/WorkingNote.java
new file mode 100644
index 0000000..8ae0c83
--- /dev/null
+++ b/other/06_190243114庞浩_代码标注/WorkingNote.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.model;
+
+import android.appwidget.AppWidgetManager;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.text.TextUtils;
+import android.util.Log;
+
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.CallNote;
+import net.micode.notes.data.Notes.DataColumns;
+import net.micode.notes.data.Notes.DataConstants;
+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;// 修改日期
+
+ private int mBgColorId;// 背景颜色ID
+
+ private int mWidgetId;// 小部件ID
+
+ private int mWidgetType;// 小部件类型
+
+ private long mFolderId;// 文件夹ID
+
+ private Context mContext;// 上下文
+
+ private static final String TAG = "WorkingNote";// 标签
+
+ private boolean mIsDeleted;// 是否已删除
+
+ private NoteSettingChangedListener mNoteSettingStatusListener;// 笔记设置改变监听器
+
+ public static final String[] DATA_PROJECTION = new String[] {// 查询数据时需要返回的列名数组
+ DataColumns.ID,// ID列
+ DataColumns.CONTENT, // 内容列
+ DataColumns.MIME_TYPE, // MIME类型列
+ DataColumns.DATA1, // 数据列1
+ DataColumns.DATA2,// 数据列2
+ DataColumns.DATA3, // 数据列3
+ DataColumns.DATA4,// 数据列4
+ };
+
+ public static final String[] NOTE_PROJECTION = new String[] {
+ NoteColumns.PARENT_ID,// 父ID列
+ NoteColumns.ALERTED_DATE,// 提醒日期列
+ NoteColumns.BG_COLOR_ID,// 背景颜色ID列
+ NoteColumns.WIDGET_ID,// 小部件ID列
+ NoteColumns.WIDGET_TYPE,// 小部件类型列
+ NoteColumns.MODIFIED_DATE // 修改日期列
+ };
+
+ private static final int DATA_ID_COLUMN = 0;// 数据表中数据ID列的索引,常量整数值为0
+
+
+ private static final int DATA_CONTENT_COLUMN = 1; // 数据表中数据内容列的索引,常量整数值为1
+
+
+ private static final int DATA_MIME_TYPE_COLUMN = 2;// 数据表中数据MIME类型列的索引,常量整数值为2
+
+
+ private static final int DATA_MODE_COLUMN = 3; // 数据表中数据模式列的索引,常量整数值为3
+
+
+ private static final int NOTE_PARENT_ID_COLUMN = 0; // 笔记表中笔记父ID列的索引,常量整数值为0
+
+ private static final int NOTE_ALERTED_DATE_COLUMN = 1;// 笔记表中笔记提醒日期列的索引,常量整数值为1
+
+ private static final int NOTE_BG_COLOR_ID_COLUMN = 2;// 笔记表中笔记背景色ID列的索引,常量整数值为2
+
+ private static final int NOTE_WIDGET_ID_COLUMN = 3; // 笔记表中笔记小部件ID列的索引,常量整数值为3
+
+ private static final int NOTE_WIDGET_TYPE_COLUMN = 4;// 笔记表中笔记小部件类型列的索引,常量整数值为4
+
+ private static final int NOTE_MODIFIED_DATE_COLUMN = 5;// 笔记表中笔记修改日期列的索引,常量整数值为5
+
+ // New note construct创建新的笔记构造函数,传入上下文和文件夹ID
+ private WorkingNote(Context context, long folderId) {
+ mContext = context;// 初始化上下文
+ mAlertDate = 0;// 提醒日期设置为0
+ mModifiedDate = System.currentTimeMillis(); // 修改日期设置为当前时间
+ mFolderId = folderId;// 设置文件夹ID
+ mNote = new Note(); // 创建一个新的Note对象
+ mNoteId = 0;// 设置笔记ID为0
+ mIsDeleted = false;// 设置是否已删除为false
+ mMode = 0;// 设置工作模式为0
+ mWidgetType = Notes.TYPE_WIDGET_INVALIDE;// 设置小部件类型为无效类型
+ }
+
+ // Existing note construct加载现有笔记的构造函数,传入上下文、笔记ID和文件夹ID
+ private WorkingNote(Context context, long noteId, long folderId) {
+ mContext = context; // 初始化上下文
+ mNoteId = noteId;// 设置笔记ID
+ mFolderId = folderId;---------------------------// 设置文件夹ID
+ mIsDeleted = false;// 设置是否已删除为false
+ mNote = new Note(); // 创建一个新的Note对象
+ loadNote();// 加载现有笔记
+ }
+
+ private void loadNote() {// 通过笔记ID查询笔记的游标
+ Cursor cursor = mContext.getContentResolver().query(// 通过笔记ID查询笔记的游标
+ 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);
+ mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
+ mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
+ mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
+ }
+ cursor.close();
+ } else {// 如果找不到笔记,则记录错误日志并抛出异常
+ Log.e(TAG, "No note with id:" + mNoteId);
+ throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
+ }// 加载笔记的数据
+ loadNoteData();
+ }
+
+ private void loadNoteData() { // 通过笔记ID查询笔记数据的游标
+ 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)) {// 如果是笔记文本数据,则获取内容和模式,并设置笔记的文本数据ID
+ 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));// 如果是电话笔记数据,则设置笔记的电话数据ID
+ } else {
+ Log.d(TAG, "Wrong note type with type:" + 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);
+ }
+ }
+ // 创建一个空笔记
+ public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
+ int widgetType, int defaultBgColorId) {
+ WorkingNote note = new WorkingNote(context, folderId);
+ note.setBgColorId(defaultBgColorId);
+ note.setWidgetId(widgetId);
+ note.setWidgetType(widgetType);
+ return note;
+ }
+ // 加载笔记
+ public static WorkingNote load(Context context, long id) {
+ return new WorkingNote(context, id, 0);
+ }
+ // 保存笔记
+ public synchronized boolean saveNote() {
+ if (isWorthSaving()) {
+ if (!existInDatabase()) {// 如果笔记还不存在于数据库中,则为其生成一个新的ID
+ if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
+ Log.e(TAG, "Create new note fail with id:" + mNoteId);
+ return false;
+ }
+ }
+ // 将笔记同步到数据库中
+ mNote.syncNote(mContext, mNoteId);
+
+ /**
+ * Update widget content if there exist any widget of this note如果笔记存在小部件,则更新小部件的内容
+ */
+ if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
+ && mWidgetType != Notes.TYPE_WIDGET_INVALIDE
+ && mNoteSettingStatusListener != null) {
+ mNoteSettingStatusListener.onWidgetChanged();
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+ // 判断笔记是否已经存在于数据库中
+ public boolean existInDatabase() {
+ return mNoteId > 0;
+ }
+ // 判断笔记是否值得保存
+ private boolean isWorthSaving() {
+ if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
+ || (existInDatabase() && !mNote.isLocalModified())) {
+ return false;
+ } else {
+ return true;
+ }
+ }
+ // 设置笔记设置状态改变的监听器
+ public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
+ mNoteSettingStatusListener = l;
+ }
+ // 设置提醒日期
+ public void setAlertDate(long date, boolean set) {
+ if (date != mAlertDate) {
+ mAlertDate = date;
+ mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
+ }
+ if (mNoteSettingStatusListener != null) {
+ mNoteSettingStatusListener.onClockAlertChanged(date, set);
+ }
+ }
+ // 标记笔记是否被删除
+ public void markDeleted(boolean mark) {
+ mIsDeleted = mark;
+ if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
+ && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) {
+ mNoteSettingStatusListener.onWidgetChanged();
+ }
+ }
+ // 设置笔记的背景颜色
+ public void setBgColorId(int id) {
+ if (id != mBgColorId) {
+ mBgColorId = id;
+ if (mNoteSettingStatusListener != null) {
+ mNoteSettingStatusListener.onBackgroundColorChanged();
+ }
+ mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
+ }
+ }
+ // 设置笔记的检查列表模式
+ public void setCheckListMode(int mode) {
+ if (mMode != mode) {
+ if (mNoteSettingStatusListener != null) {
+ mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
+ }
+ mMode = mode;
+ mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
+ }
+ }
+ // 设置笔记的小部件类型
+ public void setWidgetType(int type) {
+ if (type != mWidgetType) {
+ mWidgetType = type;
+ mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
+ }
+ }
+ // 设置笔记的小部件ID
+ public void setWidgetId(int id) {
+ if (id != mWidgetId) {
+ mWidgetId = id;
+ mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
+ }
+ }
+ //设置文本内容
+ public void setWorkingText(String text) {
+ if (!TextUtils.equals(mContent, text)) {
+ mContent = text;
+ mNote.setTextData(DataColumns.CONTENT, mContent);
+ }
+ }
+ //这是一个将笔记转换为电话记录的方法。它包含电话号码和通话日期,并更新笔记对象的值
+ public void convertToCallNote(String phoneNumber, long callDate) {
+ mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
+ mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
+ mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
+ }
+ //指示笔记是否有闹钟提醒
+ public boolean hasClockAlert() {
+ return (mAlertDate > 0 ? true : false);
+ }
+ // 返回笔记的文本内容
+ public String getContent() {
+ return mContent;
+ }
+ // 返回笔记的警报日期
+ public long getAlertDate() {
+ return mAlertDate;
+ }
+ // 返回笔记的修改日期
+ public long getModifiedDate() {
+ return mModifiedDate;
+ }
+ // 返回笔记的背景颜色资源ID
+ public int getBgColorResId() {
+ return NoteBgResources.getNoteBgResource(mBgColorId);
+ }
+ // 返回笔记的背景颜色ID
+ public int getBgColorId() {
+ return mBgColorId;
+ }
+ // 返回笔记标题的背景颜色资源ID
+ public int getTitleBgResId() {
+ return NoteBgResources.getNoteTitleBgResource(mBgColorId);
+ }
+ // 返回笔记的检查清单模式
+ public int getCheckListMode() {
+ return mMode;
+ }
+ // 返回笔记的ID
+ public long getNoteId() {
+ return mNoteId;
+ }
+ // 返回笔记所在文件夹的ID
+ public long getFolderId() {
+ return mFolderId;
+ }
+ // 返回笔记所属的小部件ID
+ public int getWidgetId() {
+ return mWidgetId;
+ }
+ // 返回笔记所属的小部件类型
+ public int getWidgetType() {
+ return mWidgetType;
+ }
+
+ public interface NoteSettingChangedListener {
+ /**
+ * Called when the background color of current note has just changed
+ */
+ void onBackgroundColorChanged();
+
+ /**
+ * Called when user set clock
+ */
+ void onClockAlertChanged(long date, boolean set);
+
+ /**
+ * Call when user create note from widget
+ */
+ void onWidgetChanged();
+
+ /**
+ * Call when switch between check list mode and normal mode
+ * @param oldMode is previous mode before change
+ * @param newMode is new mode
+ */
+ void onCheckListModeChanged(int oldMode, int newMode);
+ }
+}
diff --git a/other/06_190341301蔡玉祥_代码标注/ActionFailureException.java b/other/06_190341301蔡玉祥_代码标注/ActionFailureException.java
new file mode 100644
index 0000000..3579e44
--- /dev/null
+++ b/other/06_190341301蔡玉祥_代码标注/ActionFailureException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.gtask.exception;
+
+public class ActionFailureException extends RuntimeException {
+
+ private static final long serialVersionUID = 4425249765923293627L;
+
+ public ActionFailureException() {
+ super(); // 调用父类的构造函数
+ }
+
+ public ActionFailureException(String paramString) {
+ super(paramString); // 调用父类的构造函数,并传入异常信息
+ }
+
+ public ActionFailureException(String paramString, Throwable paramThrowable) {
+ super(paramString, paramThrowable); // 调用父类的构造函数,并传入异常信息和原始异常
+ }
+}
diff --git a/other/06_190341301蔡玉祥_代码标注/GTaskASyncTask.java b/other/06_190341301蔡玉祥_代码标注/GTaskASyncTask.java
new file mode 100644
index 0000000..c88aef0
--- /dev/null
+++ b/other/06_190341301蔡玉祥_代码标注/GTaskASyncTask.java
@@ -0,0 +1,123 @@
+
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.gtask.remote;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+
+import net.micode.notes.R;
+import net.micode.notes.ui.NotesListActivity;
+import net.micode.notes.ui.NotesPreferenceActivity;
+
+
+public class GTaskASyncTask extends AsyncTask {
+
+ private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
+
+ public interface OnCompleteListener {
+ void onComplete();
+ }
+
+ private Context mContext; // 上下文对象
+
+ private NotificationManager mNotifiManager; // 通知管理器对象
+
+ private GTaskManager mTaskManager; // GTask 管理器对象
+
+ private OnCompleteListener mOnCompleteListener; // 异步任务完成后的回调接口
+
+ public GTaskASyncTask(Context context, OnCompleteListener listener) {
+ mContext = context; // 初始化上下文对象
+ mOnCompleteListener = listener; // 初始化回调接口
+ mNotifiManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE); // 初始化通知管理器对象
+ mTaskManager = GTaskManager.getInstance(); // 获取 GTask 管理器对象的单例
+ }
+
+ public void cancelSync() {
+ mTaskManager.cancelSync(); // 取消 GTask 同步
+ }
+
+ public void publishProgess(String message) {
+ publishProgress(new String[] {
+ message
+ }); // 向主线程发布进度更新
+ }
+
+ private void showNotification(int tickerId, String content) {
+ Notification notification = new Notification(R.drawable.notification, mContext
+ .getString(tickerId), System.currentTimeMillis()); // 创建通知
+ notification.defaults = Notification.DEFAULT_LIGHTS; // 设置默认的通知灯光
+ notification.flags = Notification.FLAG_AUTO_CANCEL; // 设置通知被点击后自动取消
+ PendingIntent pendingIntent;
+ if (tickerId != R.string.ticker_success) { // 如果是同步失败的通知
+ pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
+ NotesPreferenceActivity.class), 0); // 点击通知后打开 NotesPreferenceActivity
+
+ } else { // 如果是同步成功的通知
+ pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
+ NotesListActivity.class), 0); // 点击通知后打开 NotesListActivity
+ }
+ //notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
+ // pendingIntent); // 设置通知的标题、内容和点击后的操作
+ mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); // 发送通知
+ }
+
+ @Override
+ protected Integer doInBackground(Void... unused) {
+ publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
+ .getSyncAccountName(mContext))); // 向主线程发布进度更新,显示正在登录的提示信息
+ return mTaskManager.sync(mContext, this); // 开始 GTask 同步,并返回同步结果
+ }
+
+ @Override
+ protected void onProgressUpdate(String... progress) {
+ showNotification(R.string.ticker_syncing, progress[0]); // 显示同步进度的通知
+ if (mContext instanceof GTaskSyncService) {
+ ((GTaskSyncService) mContext).sendBroadcast(progress[0]); // 向 GTaskSyncService 发送广播,以便更新 UI
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ if (result == GTaskManager.STATE_SUCCESS) { // 如果同步成功
+ showNotification(R.string.ticker_success, mContext.getString(
+ R.string.success_sync_account, mTaskManager.getSyncAccount())); // 显示同步成功的通知
+ NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); // 更新最后同步时间
+ } else if (result == GTaskManager.STATE_NETWORK_ERROR) { // 如果网络错误
+ showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); // 显示同步失败的通知,提示网络错误
+ } else if (result == GTaskManager.STATE_INTERNAL_ERROR) { // 如果内部错误
+ showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); // 显示同步失败的通知,提示内部错误
+ } else if (result == GTaskManager.STATE_SYNC_CANCELLED) { // 如果同步被取消
+ showNotification(R.string.ticker_cancel, mContext
+ .getString(R.string.error_sync_cancelled)); // 显示同步被取消的通知
+ }
+ if (mOnCompleteListener != null) { // 如果设置了 OnCompleteListener
+ new Thread(new Runnable() { // 在新线程中执行 OnCompleteListener
+
+ public void run() {
+ mOnCompleteListener.onComplete();
+ }
+ }).start();
+ }
+ }
+}
\ No newline at end of file
diff --git a/other/06_190341301蔡玉祥_代码标注/GTaskClient.java b/other/06_190341301蔡玉祥_代码标注/GTaskClient.java
new file mode 100644
index 0000000..91314f8
--- /dev/null
+++ b/other/06_190341301蔡玉祥_代码标注/GTaskClient.java
@@ -0,0 +1,668 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.gtask.remote;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.AccountManagerFuture;
+import android.app.Activity;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import net.micode.notes.gtask.data.Node;
+import net.micode.notes.gtask.data.Task;
+import net.micode.notes.gtask.data.TaskList;
+import net.micode.notes.gtask.exception.ActionFailureException;
+import net.micode.notes.gtask.exception.NetworkFailureException;
+import net.micode.notes.tool.GTaskStringUtils;
+import net.micode.notes.ui.NotesPreferenceActivity;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.cookie.Cookie;
+import org.apache.http.impl.client.BasicCookieStore;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+
+
+public class GTaskClient {
+ private static final String TAG = GTaskClient.class.getSimpleName();
+
+ private static final String GTASK_URL = "https://mail.google.com/tasks/"; // GTask 的基础 URL
+
+ private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; // 获取 GTask 数据的 URL
+
+ private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; // 提交 GTask 数据的 URL
+
+ private static GTaskClient mInstance = null; // 单例模式,保存 GTaskClient 的唯一实例
+
+ private DefaultHttpClient mHttpClient; // HTTP 客户端
+
+ private String mGetUrl; // 获取 GTask 数据的完整 URL
+
+ private String mPostUrl; // 提交 GTask 数据的完整 URL
+
+ private long mClientVersion; // 客户端版本号
+
+ private boolean mLoggedin; // 是否已登录
+
+ private long mLastLoginTime; // 上次登录时间
+
+ private int mActionId; // 操作 ID
+
+ private Account mAccount; // GTask 帐户
+
+ private JSONArray mUpdateArray; // 待更新的 GTask 数据
+ private GTaskClient() {
+ mHttpClient = null; // 初始化 HTTP 客户端为 null
+ mGetUrl = GTASK_GET_URL; // 初始化获取 GTask 数据的 URL
+ mPostUrl = GTASK_POST_URL; // 初始化提交 GTask 数据的 URL
+ mClientVersion = -1; // 初始化客户端版本号为 -1
+ mLoggedin = false; // 初始化登录状态为 false
+ mLastLoginTime = 0; // 初始化上次登录时间为 0
+ mActionId = 1; // 初始化操作 ID 为 1
+ mAccount = null; // 初始化 GTask 帐户为 null
+ mUpdateArray = null; // 初始化待更新的 GTask 数据为 null
+ }/*该构造方法用于创建GTaskClient的实例,其中将mHttpClient、mAccount、mUpdateArray等成员变量初始化为 null 或默认值,将mGetUrl和mPostUrl初始化为 GTask 的默认 URL,将mClientVersion初始化为 -1,将mLoggedin初始化为 false,将mLastLoginTime初始化为 0,将mActionId初始化为 1。
+ 这里使用了默认访问控制符private,意味着该构造方法只能在GTaskClient类内部使用,不能在其他类中创建GTaskClient的实例。*/
+
+ public static synchronized GTaskClient getInstance() {
+ if (mInstance == null) { // 如果唯一实例不存在
+ mInstance = new GTaskClient(); // 则创建一个新实例
+ }
+ return mInstance; // 返回唯一实例
+ }/*该方法是单例模式的实现,用于获取GTaskClient的唯一实例。在该方法内部,首先判断唯一实例是否已经存在,如果不存在则创建一个新实例,并将其赋值给mInstance。最后返回唯一实例。
+ 由于该方法可能被多个线程同时调用,所以使用了synchronized关键字来保证在同一时刻只有一个线程能够访问该方法。
+ 同时,该方法返回的是静态成员变量mInstance,因此可以通过GTaskClient.getInstance()的方式在任意位置获取GTaskClient的唯一实例。*/
+
+ public boolean login(Activity activity) {
+ // we suppose that the cookie would expire after 5 minutes
+ // then we need to re-login
+ final long interval = 1000 * 60 * 5;
+ if (mLastLoginTime + interval < System.currentTimeMillis()) {
+ mLoggedin = false;
+ }
+
+ // need to re-login after account switch
+ if (mLoggedin
+ && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
+ .getSyncAccountName(activity))) {
+ mLoggedin = false;
+ }
+
+ if (mLoggedin) { // 如果已登录,则直接返回
+ Log.d(TAG, "already logged in");
+ return true;
+ }
+
+ mLastLoginTime = System.currentTimeMillis(); // 记录登录时间
+ String authToken = loginGoogleAccount(activity, false); // 登录 Google 帐户,获取授权令牌
+ if (authToken == null) { // 如果登录失败,则返回 false
+ Log.e(TAG, "login google account failed");
+ return false;
+ }
+
+ // login with custom domain if necessary
+ if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
+ .endsWith("googlemail.com"))) { // 如果不是 Gmail 或 Googlemail 帐户,则使用自定义域名登录
+ StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); // 构造自定义域名的 URL
+ int index = mAccount.name.indexOf('@') + 1;
+ String suffix = mAccount.name.substring(index);
+ url.append(suffix + "/");
+ mGetUrl = url.toString() + "ig"; // 更新获取 GTask 数据的 URL
+ mPostUrl = url.toString() + "r/ig"; // 更新提交 GTask 数据的 URL
+
+ if (tryToLoginGtask(activity, authToken)) { // 尝试登录 GTask
+ mLoggedin = true; // 登录成功
+ }
+ }
+
+ // 如果自定义域名登录失败,则使用 Google 官方 URL 登录
+ if (!mLoggedin) {
+ mGetUrl = GTASK_GET_URL;
+ mPostUrl = GTASK_POST_URL;
+ if (!tryToLoginGtask(activity, authToken)) {
+ return false;
+ }
+ }
+
+ mLoggedin = true; // 登录成功
+ return true;
+ }/*该方法用于登录 GTask,首先检查上次登录时间是否超过 5 分钟,如果超过则需要重新登录,将mLoggedin设置为 false。
+ 然后判断当前帐户是否发生切换,如果发生切换也需要重新登录,同样将mLoggedin设置为 false。
+ 如果已经登录,则直接返回 true。否则,记录本次登录时间,然后使用loginGoogleAccount()方法登录 Google 帐户,获取授权令牌。
+ 如果登录失败,则返回 false。接下来,如果当前帐户不是 Gmail 或 Googlemail 帐户,则使用自定义域名登录,更新获取 GTask 数据和提交 GTask 数据的 URL,然后尝试登录 GTask。
+ 如果自定义域名登录失败,则使用 Google 官方 URL 登录。
+ 无论使用哪种方式登录成功,最后将mLoggedin设置为 true,表示已经登录成功。*/
+
+ private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
+ String authToken;
+ AccountManager accountManager = AccountManager.get(activity); // 获取 AccountManager 实例
+ Account[] accounts = accountManager.getAccountsByType("com.google"); // 获取所有 Google 帐户
+
+ if (accounts.length == 0) { // 如果没有可用的 Google 帐户,则返回 null
+ Log.e(TAG, "there is no available google account");
+ return null;
+ }
+
+ String accountName = NotesPreferenceActivity.getSyncAccountName(activity); // 获取设置中的同步帐户名称
+ Account account = null;
+ for (Account a : accounts) {
+ if (a.name.equals(accountName)) { // 如果找到同名帐户,则使用该帐户
+ account = a;
+ break;
+ }
+ }
+ if (account != null) {
+ mAccount = account; // 更新当前使用的帐户
+ } else {
+ Log.e(TAG, "unable to get an account with the same name in the settings");
+ return null;
+ }
+
+ // get the token now
+ AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account,
+ "goanna_mobile", null, activity, null, null); // 获取 token
+ try {
+ Bundle authTokenBundle = accountManagerFuture.getResult();
+ authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); // 从 Bundle 中获取 token
+ if (invalidateToken) { // 如果需要使 token 失效
+ accountManager.invalidateAuthToken("com.google", authToken); // 使 token 失效
+ loginGoogleAccount(activity, false); // 重新登录
+ }
+ } catch (Exception e) { // 获取 token 失败
+ Log.e(TAG, "get auth token failed");
+ authToken = null;
+ }
+
+ return authToken; // 返回 token
+ }
+ /*该方法的作用是获取 Google 帐户的 token,以用于访问 Google 服务。
+ 它首先获取所有的 Google 帐户,然后根据设置中的同步帐户名称选择使用哪个帐户。
+ 接着,它使用AccountManager获取该帐户的 token,并返回该 token。
+ 如果invalidateToken参数为true,则该方法会使 token 失效,并重新登录,以获取新的 token。
+ */
+
+ private boolean tryToLoginGtask(Activity activity, String authToken) {
+ if (!loginGtask(authToken)) { // 如果登录 GTask 失败
+ // maybe the auth token is out of date, now let's invalidate the
+ // token and try again
+ authToken = loginGoogleAccount(activity, true); // 使 token 失效并重新登录
+ if (authToken == null) { // 如果重新登录失败,则返回 false
+ Log.e(TAG, "login google account failed");
+ return false;
+ }
+
+ if (!loginGtask(authToken)) { // 如果重新登录 GTask 仍然失败,则返回 false
+ Log.e(TAG, "login gtask failed");
+ return false;
+ }
+ }
+ return true; // 登录 GTask 成功,返回 true
+ }
+ /*该方法的作用是尝试登录 GTask,它接收一个authToken参数,该参数是通过loginGoogleAccount()方法获取的 Google 帐户的 token。
+ 如果登录 GTask 失败,则会使 token 失效并重新登录,再次尝试登录 GTask。
+ 如果重新登录失败,则返回false,否则返回true。
+ */
+
+ private boolean loginGtask(String authToken) {
+ int timeoutConnection = 10000;
+ int timeoutSocket = 15000;
+ HttpParams httpParameters = new BasicHttpParams();
+ HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
+ HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
+
+ // 设置 HttpClient 的参数
+ mHttpClient = new DefaultHttpClient(httpParameters);
+ BasicCookieStore localBasicCookieStore = new BasicCookieStore();
+ mHttpClient.setCookieStore(localBasicCookieStore);
+ HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
+
+ // login gtask
+ try {
+ String loginUrl = mGetUrl + "?auth=" + authToken;
+ HttpGet httpGet = new HttpGet(loginUrl); // 创建 HTTP GET 请求
+ HttpResponse response = mHttpClient.execute(httpGet); // 执行请求
+
+ // 获取 cookie
+ List cookies = mHttpClient.getCookieStore().getCookies();
+ boolean hasAuthCookie = false;
+ for (Cookie cookie : cookies) {
+ if (cookie.getName().contains("GTL")) {
+ hasAuthCookie = true;
+ }
+ }
+ if (!hasAuthCookie) {
+ Log.w(TAG, "it seems that there is no auth cookie");
+ }
+
+ // 获取客户端版本
+ String resString = getResponseContent(response.getEntity());
+ String jsBegin = "_setup(";
+ String jsEnd = ")}";
+ int begin = resString.indexOf(jsBegin);
+ int end = resString.lastIndexOf(jsEnd);
+ String jsString = null;
+ if (begin != -1 && end != -1 && begin < end) {
+ jsString = resString.substring(begin + jsBegin.length(), end);
+ }
+ JSONObject js = new JSONObject(jsString);
+ mClientVersion = js.getLong("v");
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ return false;
+ } catch (Exception e) {
+ // simply catch all exceptions
+ Log.e(TAG, "httpget gtask_url failed");
+ return false;
+ }
+
+ return true;
+ }
+ /*该方法的作用是使用给定的authToken登录 GTask。
+ 它创建了一个 HTTP GET 请求,并将authToken添加到 URL 末尾,然后执行该请求并获取响应。
+ 它还获取了响应中包含的 cookie,并将客户端版本存储在mClientVersion中。
+ 如果登录成功,则返回true,否则返回false。如果发生异常,则返回false。
+ */
+
+ private int getActionId() {
+ return mActionId++; // 返回下一个 action ID,并将 mActionId 加 1
+ }
+
+ private HttpPost createHttpPost() {
+ HttpPost httpPost = new HttpPost(mPostUrl); // 创建一个 HTTP POST 请求
+ httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); // 设置请求头的 Content-Type
+ httpPost.setHeader("AT", "1"); // 设置请求头的 AT 字段为 1
+ return httpPost; // 返回创建的 HTTP POST 请求
+ }/*getActionId()的作用是获取下一个 action ID,每次调用它都会将mActionId加 1,并返回加 1 后的值。
+ createHttpPost()的作用是创建一个 HTTP POST 请求,并设置请求头的 Content-Type 为application/x-www-form-urlencoded;charset=utf-8,设置请求头的 AT 字段为 1。
+ 它返回创建的 HTTP POST 请求对象。
+ */
+
+ private String getResponseContent(HttpEntity entity) throws IOException {
+ String contentEncoding = null;
+ if (entity.getContentEncoding() != null) {
+ contentEncoding = entity.getContentEncoding().getValue(); // 获取响应内容的编码方式
+ Log.d(TAG, "encoding: " + contentEncoding);
+ }
+
+ InputStream input = entity.getContent(); // 获取响应内容的输入流
+ if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
+ input = new GZIPInputStream(entity.getContent()); // 如果响应内容被 gzip 压缩了,则创建一个 GZIPInputStream 解压缩
+ } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
+ Inflater inflater = new Inflater(true);
+ input = new InflaterInputStream(entity.getContent(), inflater); // 如果响应内容被 deflate 压缩了,则创建一个 InflaterInputStream 解压缩
+ }
+
+ try {
+ InputStreamReader isr = new InputStreamReader(input); // 创建一个 InputStreamReader 对象
+ BufferedReader br = new BufferedReader(isr); // 创建一个 BufferedReader 对象
+ StringBuilder sb = new StringBuilder(); // 创建一个 StringBuilder 对象
+
+ while (true) {
+ String buff = br.readLine(); // 逐行读取响应内容
+ if (buff == null) {
+ return sb.toString(); // 如果读到了末尾,则返回读取到的响应内容
+ }
+ sb = sb.append(buff); // 将读取到的响应内容追加到 StringBuilder 对象中
+ }
+ } finally {
+ input.close(); // 关闭输入流
+ }
+ }
+ /*getResponseContent(HttpEntity entity)的作用是从HttpEntity对象中获取响应内容,并将其解压(如果响应内容被压缩了)。它返回解压后的响应内容。
+ 首先获取响应内容的编码方式,然后根据编码方式创建对应的输入流。
+ 如果响应内容被 gzip 压缩了,则创建一个 GZIPInputStream 对象解压缩;如果响应内容被 deflate 压缩了,则创建一个 InflaterInputStream 对象解压缩。
+ 接着使用 InputStreamReader 和 BufferedReader 逐行读取响应内容,并将其追加到 StringBuilder 对象中。
+ 最后返回读取到的响应内容,并关闭输入流。
+ */
+
+ private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
+ if (!mLoggedin) {
+ Log.e(TAG, "please login first");
+ throw new ActionFailureException("not logged in");
+ }
+
+ HttpPost httpPost = createHttpPost(); // 创建 HTTP POST 请求
+ try {
+ LinkedList list = new LinkedList();
+ list.add(new BasicNameValuePair("r", js.toString())); // 将传入的 JSONObject 对象转为字符串,并添加到请求参数中
+ UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); // 创建一个 UrlEncodedFormEntity 对象,用于封装请求参数
+ httpPost.setEntity(entity); // 将 UrlEncodedFormEntity 对象设置为 HTTP POST 请求的实体
+
+ // execute the post
+ HttpResponse response = mHttpClient.execute(httpPost); // 执行 HTTP POST 请求
+ String jsString = getResponseContent(response.getEntity()); // 获取响应内容,并将其解压(如果响应内容被压缩了),然后转为字符串
+ return new JSONObject(jsString); // 将响应内容转为 JSONObject 对象
+
+ } catch (ClientProtocolException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new NetworkFailureException("postRequest failed");
+ } catch (IOException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new NetworkFailureException("postRequest failed");
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new ActionFailureException("unable to convert response content to jsonobject");
+ } catch (Exception e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new ActionFailureException("error occurs when posting request");
+ }
+ }
+ /*postRequest(JSONObject js)的作用是向服务器发送 HTTP POST 请求,并将响应内容转为JSONObject对象返回。
+ 首先检查用户是否已经登录,如果没有登录则抛出异常。然后创建一个 HTTP POST 请求,并将传入的 JSONObject 对象转为字符串,并添加到请求参数中。接着,创建一个 UrlEncodedFormEntity 对象,用于封装请求参数,并将其设置为 HTTP POST 请求的实体。
+ 执行 HTTP POST 请求,并获取响应内容。将响应内容解压(如果响应内容被压缩了),然后将其转为字符串,并通过 JSONObject 构造方法将其转为 JSONObject 对象。如果转换失败,则抛出异常。
+ 如果在执行 HTTP POST 请求或转换响应内容为 JSONObject 对象时出现异常,则抛出相应的异常。
+ */
+ public void createTask(Task task) throws NetworkFailureException {
+ commitUpdate(); // 提交所有未提交的更新操作
+
+ try {
+ JSONObject jsPost = new JSONObject(); // 创建一个新的 JSONObject 对象
+ JSONArray actionList = new JSONArray(); // 创建一个新的 JSONArray 对象,用于存储操作列表
+
+ // action_list
+ actionList.put(task.getCreateAction(getActionId())); // 将新增任务的操作添加到操作列表中
+ jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 将操作列表添加到 JSONObject 对象中
+
+ // client_version
+ jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 添加客户端版本号到 JSONObject 对象中
+
+ // post
+ JSONObject jsResponse = postRequest(jsPost); // 向服务器发送 HTTP POST 请求,并获取响应内容
+ JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
+ GTaskStringUtils.GTASK_JSON_RESULTS).get(0); // 从响应内容中获取结果列表,并获取第一个结果
+ task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 获取新任务的 ID,并将其设置为 Task 对象的 gid 属性
+
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new ActionFailureException("create task: handing jsonobject failed");
+ }
+ }
+ /*createTask(Task task)的作用是创建新任务。首先调用commitUpdate()方法提交所有未提交的更新操作。
+ 然后创建一个新的 JSONObject 对象,并创建一个新的 JSONArray 对象,用于存储操作列表。将新增任务的操作添加到操作列表中,并将操作列表添加到 JSONObject 对象中。将客户端版本号也添加到 JSONObject 对象中。
+ 向服务器发送 HTTP POST 请求,并获取响应内容。从响应内容中获取结果列表,并获取第一个结果。从结果中获取新任务的 ID,并将其设置为 Task 对象的 gid 属性。
+ 如果在处理 JSONObject 对象时出现异常,则抛出相应的异常。
+ */
+ public void createTaskList(TaskList tasklist) throws NetworkFailureException {
+ commitUpdate(); // 提交所有未提交的更新操作
+
+ try {
+ JSONObject jsPost = new JSONObject(); // 创建一个新的 JSONObject 对象
+ JSONArray actionList = new JSONArray(); // 创建一个新的 JSONArray 对象,用于存储操作列表
+
+ // action_list
+ actionList.put(tasklist.getCreateAction(getActionId())); // 将新增任务列表的操作添加到操作列表中
+ jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 将操作列表添加到 JSONObject 对象中
+
+ // client_version
+ jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 添加客户端版本号到 JSONObject 对象中
+
+ // post
+ JSONObject jsResponse = postRequest(jsPost); // 向服务器发送 HTTP POST 请求,并获取响应内容
+ JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
+ GTaskStringUtils.GTASK_JSON_RESULTS).get(0); // 从响应内容中获取结果列表,并获取第一个结果
+ tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 获取新任务列表的 ID,并将其设置为 TaskList 对象的 gid 属性
+
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new ActionFailureException("create tasklist: handing jsonobject failed");
+ }
+ }
+ /*createTaskList(TaskList tasklist)的作用是创建新任务列表。首先调用commitUpdate()方法提交所有未提交的更新操作。
+ 然后创建一个新的 JSONObject 对象,并创建一个新的 JSONArray 对象,用于存储操作列表。将新增任务列表的操作添加到操作列表中,并将操作列表添加到 JSONObject 对象中。将客户端版本号也添加到 JSONObject 对象中。
+ 向服务器发送 HTTP POST 请求,并获取响应内容。从响应内容中获取结果列表,并获取第一个结果。从结果中获取新任务列表的 ID,并将其设置为 TaskList 对象的 gid 属性。
+ 如果在处理 JSONObject 对象时出现异常,则抛出相应的异常。
+ */
+ public void commitUpdate() throws NetworkFailureException {
+ if (mUpdateArray != null) { // 判断更新操作列表是否为空
+ try {
+ JSONObject jsPost = new JSONObject(); // 创建一个新的 JSONObject 对象
+
+ // action_list
+ jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); // 将更新操作列表添加到 JSONObject 对象中
+
+ // client_version
+ jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 添加客户端版本号到 JSONObject 对象中
+
+ postRequest(jsPost); // 向服务器发送 HTTP POST 请求,提交更新操作
+ mUpdateArray = null; // 更新操作提交成功后,清空更新操作列表
+
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new ActionFailureException("commit update: handing jsonobject failed");
+ }
+ }
+ }
+ /*commitUpdate()的作用是提交所有未提交的更新操作。如果更新操作列表不为空,则创建一个新的 JSONObject 对象,将更新操作列表添加到 JSONObject 对象中,并将客户端版本号也添加到 JSONObject 对象中。
+ 向服务器发送 HTTP POST 请求,提交更新操作。提交成功后,清空更新操作列表。
+ 如果在处理 JSONObject 对象时出现异常,则抛出相应的异常。
+ */
+ public void addUpdateNode(Node node) throws NetworkFailureException {
+ if (node != null) { // 判断 Node 对象是否为空
+ // too many update items may result in an error
+ // set max to 10 items
+ if (mUpdateArray != null && mUpdateArray.length() > 10) { // 判断更新操作列表是否已满
+ commitUpdate(); // 如果已满,则提交所有未提交的更新操作
+ }
+
+ if (mUpdateArray == null)
+ mUpdateArray = new JSONArray(); // 如果更新操作列表还未创建,则创建一个新的 JSONArray 对象
+ mUpdateArray.put(node.getUpdateAction(getActionId())); // 将 Node 对象的更新操作添加到更新操作列表中
+ }
+ }
+ /*addUpdateNode(Node node)的作用是向更新操作列表中添加一个新的更新操作。首先判断 Node 对象是否为空。
+ 如果更新操作列表已满(长度大于 10),则调用commitUpdate()方法提交所有未提交的更新操作。
+ 如果更新操作列表还未创建,则创建一个新的 JSONArray 对象。将 节点
+ 如果 Node 对象为空,则不执行任何操作。
+ */
+ public void moveTask(Task task, TaskList preParent, TaskList curParent)
+ throws NetworkFailureException {
+ commitUpdate(); // 先提交所有未提交的更新操作
+ try {
+ JSONObject jsPost = new JSONObject(); // 创建一个新的 JSONObject 对象
+ JSONArray actionList = new JSONArray(); // 创建一个新的 JSONArray 对象,用于存储更新操作
+ JSONObject action = new JSONObject(); // 创建一个新的 JSONObject 对象,用于存储移动任务的操作
+
+ // action_list
+ action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
+ GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); // 设置操作类型为移动任务
+ action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); // 设置操作 ID
+ action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); // 设置任务的 GID
+
+ 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()); // 如果是在同一任务列表中移动任务,则设置前一个任务的 GID
+ }
+
+ action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); // 设置任务的原始任务列表的 GID
+ action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); // 设置任务的目标任务列表的 GID
+
+ if (preParent != curParent) {
+ // put the dest_list only if moving between tasklists
+ action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); // 如果是在不同的任务列表之间移动任务,则设置任务的目标任务列表的 GID
+ }
+
+ actionList.put(action); // 将移动任务的操作添加到更新操作列表中
+ jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 将更新操作列表添加到 JSONObject 对象中
+
+ // client_version
+ jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 添加客户端版本号到 JSONObject 对象中
+
+ postRequest(jsPost); // 向服务器发送 HTTP POST 请求,提交更新操作
+
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new ActionFailureException("move task: handing jsonobject failed");
+ }
+ }
+ /*moveTask(Task task, TaskList preParent, TaskList curParent)的作用是移动一个任务到另一个任务列表中。
+ 首先调用commitUpdate()方法提交所有未提交的更新操作。然后,创建一个新的 JSONObject 对象,用于存储移动任务的操作。
+ 在移动任务的操作中,设置操作类型为移动任务。设置操作 ID 和任务的 GID。如果是在同一任务列表中移动任务,则设置前一个任务的 GID。设置任务的原始任务列表的 GID 和任务的目标任务列表的 GID。如果是在不同的任务列表之间移动任务,则设置任务的目标任务列表的 GID。
+ 将移动任务的操作添加到更新操作列表中,然后将更新操作列表添加到 JSONObject 对象中。最后,添加客户端版本号到 JSONObject 对象中,向服务器发送 HTTP POST 请求,提交更新操作。
+ 如果处理 JSONObject 对象时出现异常,则抛出 ActionFailureException 异常。
+ */
+ public void deleteNode(Node node) throws NetworkFailureException {
+ commitUpdate(); // 先提交所有未提交的更新操作
+ try {
+ JSONObject jsPost = new JSONObject(); // 创建一个新的 JSONObject 对象
+ JSONArray actionList = new JSONArray(); // 创建一个新的 JSONArray 对象,用于存储更新操作
+
+ // action_list
+ node.setDeleted(true); // 将节点标记为已删除
+ actionList.put(node.getUpdateAction(getActionId())); // 将节点的更新操作添加到更新操作列表中
+ jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 将更新操作列表添加到 JSONObject 对象中
+
+ // client_version
+ jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 添加客户端版本号到 JSONObject 对象中
+
+ postRequest(jsPost); // 向服务器发送 HTTP POST 请求,提交更新操作
+ mUpdateArray = null; // 将更新操作数组置为空
+
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new ActionFailureException("delete node: handing jsonobject failed"); // 处理 JSONObject 对象时出现异常,则抛出 ActionFailureException 异常
+ }
+ }
+ /*此方法的主要目的是删除一个节点,并将删除操作提交到服务器。
+ 方法首先提交所有未提交的更新操作,然后创建一个新的 JSONObject 对象,将节点的删除操作添加到该对象中,同时添加客户端版本号。
+ 最后,方法通过向服务器发送 HTTP POST 请求来提交更新操作,如果在处理 JSONObject 对象时出现异常,则会抛出 ActionFailureException 异常。
+ */
+
+ public JSONArray getTaskLists() throws NetworkFailureException {
+ if (!mLoggedin) { // 如果用户没有登录,则抛出 ActionFailureException 异常
+ Log.e(TAG, "please login first");
+ throw new ActionFailureException("not logged in");
+ }
+
+ try {
+ HttpGet httpGet = new HttpGet(mGetUrl); // 创建一个新的 HttpGet 请求对象
+ HttpResponse response = null;
+ response = mHttpClient.execute(httpGet); // 执行 HttpGet 请求
+
+ // get the task list
+ String resString = getResponseContent(response.getEntity()); // 获取响应内容
+ String jsBegin = "_setup(";
+ String jsEnd = ")}";
+ int begin = resString.indexOf(jsBegin);
+ int end = resString.lastIndexOf(jsEnd);
+ String jsString = null;
+ if (begin != -1 && end != -1 && begin < end) { // 如果响应内容中包含任务列表,那么提取出来
+ jsString = resString.substring(begin + jsBegin.length(), end);
+ }
+ JSONObject js = new JSONObject(jsString); // 将任务列表转换成 JSONObject 对象
+ return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); // 返回任务列表数组
+
+ } catch (ClientProtocolException e) { // 如果发生协议错误,则抛出 NetworkFailureException 异常
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new NetworkFailureException("gettasklists: httpget failed");
+ } catch (IOException e) { // 如果发生 I/O 错误,则抛出 NetworkFailureException 异常
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new NetworkFailureException("gettasklists: httpget failed");
+ } catch (JSONException e) { // 如果处理 JSONObject 对象时出现异常,则抛出 ActionFailureException 异常
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new ActionFailureException("get task lists: handing jasonobject failed");
+ }
+ }
+ /*此方法的主要目的是从服务器获取用户的任务列表,并将其作为 JSONArray 对象返回。
+ 方法首先检查用户是否已登录,然后创建一个新的 HttpGet 请求对象,并通过执行该请求来获取响应内容。
+ 接下来,方法从响应内容中提取出任务列表,并将其转换为 JSONObject 对象,最后返回任务列表数组。
+ 如果在执行 HttpGet 请求或处理 JSONObject 对象时发生错误,则会抛出 NetworkFailureException 或 ActionFailureException 异常。
+ */
+
+ public JSONArray getTaskList(String listGid) throws NetworkFailureException {
+ commitUpdate(); // 提交所有未提交的更改
+ try {
+ JSONObject jsPost = new JSONObject(); // 创建一个新的 JSONObject 对象
+ JSONArray actionList = new JSONArray(); // 创建一个新的 JSONArray 对象
+ JSONObject action = new JSONObject(); // 创建一个新的 JSONObject 对象
+
+ // action_list
+ action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
+ GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); // 设置 action 的类型为 "getall"
+ action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); // 设置 action 的 ID
+ action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); // 设置 action 操作的任务列表 ID
+ action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); // 设置是否获取已删除的任务
+ actionList.put(action); // 将 action 添加到 action_list 中
+ jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 将 action_list 添加到 jsPost 中
+
+ // client_version
+ jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 设置客户端版本号
+
+ JSONObject jsResponse = postRequest(jsPost); // 发送请求并获取响应
+ return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); // 从响应中获取任务列表并返回
+
+ } catch (JSONException e) { // 如果处理 JSONObject 对象时出现异常,则抛出 ActionFailureException 异常
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new ActionFailureException("get task list: handing jsonobject failed");
+ }
+ }
+ /*此方法的主要目的是从服务器获取特定任务列表的任务,并将其作为 JSONArray 对象返回。
+ 方法首先提交所有未提交的更改,然后构造一个包含获取任务列表的请求并发送它。
+ 接下来,方法从响应中提取出任务列表,并将其作为 JSONArray 对象返回。
+ 如果在处理 JSONObject 对象时发生错误,则会抛出 ActionFailureException 异常。
+ */
+
+ public Account getSyncAccount() {
+ return mAccount;
+ }
+ //这个方法返回GTaskClient的同步账户(即当前使用的 Google 帐户)。
+
+ public void resetUpdateArray() {
+ mUpdateArray = null;
+ }
+}
+ /*这个方法将GTaskClient的更新数组mUpdateArray设置为null,以清除未提交的更改。
+ 在更新任务列表之前,需要调用commitUpdate()方法提交所有未提交的更改,如果您想丢弃这些更改,可以调用resetUpdateArray()方法以清除它们。
+ */
\ No newline at end of file
diff --git a/other/06_190341301蔡玉祥_代码标注/MetaData.java b/other/06_190341301蔡玉祥_代码标注/MetaData.java
new file mode 100644
index 0000000..b08b2f8
--- /dev/null
+++ b/other/06_190341301蔡玉祥_代码标注/MetaData.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.gtask.data;
+
+import android.database.Cursor;
+import android.util.Log;
+
+import net.micode.notes.tool.GTaskStringUtils;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+
+public class MetaData extends Task {
+ private final static String TAG = MetaData.class.getSimpleName();
+
+ private String mRelatedGid = null;
+
+ public void setMeta(String gid, JSONObject metaInfo) {
+ try {
+ metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
+ } catch (JSONException e) {
+ Log.e(TAG, "failed to put related gid");
+ }
+ setNotes(metaInfo.toString());
+ setName(GTaskStringUtils.META_NOTE_NAME);
+ }
+ /*
+ 这段代码定义了一个名为 MetaData 的类,它继承了 Task 类。
+ Meta Data 类有一个私有的属性 mRelatedGid,它的值为 null。
+ 类中有两个方法,分别为 setMeta 和 getRelatedGid。
+ setMeta 方法接受两个参数,一个字符串 gid 和一个 JSON 对象 metaInfo。
+ 此方法会尝试在 metaInfo 对象中添加一个以 GTaskStringUtils.META_HEAD_GTASK_ID 为键、gid 为值的键值对,如果添加失败则输出一个错误日志。
+ 随后该方法使用 setNotes 方法将 metaInfo 对象的字符串形式设置为任务的说明,并设置任务的名称为 GTaskStringUtils.META_NOTE_NAME。
+ getRelatedGid 方法返回 mRelatedGid 属性的值。
+ */
+
+ public String getRelatedGid() {
+ return mRelatedGid;
+ }
+ /*getRelatedGid() 方法:
+ 该方法返回 MetaData 对象的 mRelatedGid 字段,即任务的全局唯一标识符 gid。
+ */
+
+ @Override
+ public boolean isWorthSaving() {
+ return getNotes() != null;
+ }
+ /*isWorthSaving() 方法:该方法覆盖了 Task 类中的同名方法。
+ 该方法返回一个布尔值,指示任务是否值得保存。
+ 在这个实现中,如果任务的备注信息不为 null,则任务值得保存,返回 true;
+ 否则返回 false。
+ */
+
+
+ @Override
+ public void setContentByRemoteJSON(JSONObject js) {// 调用父类的方法来设置 JSON 内容
+ super.setContentByRemoteJSON(js);// 检查是否存在与对象相关联的注释(notes)
+ if (getNotes() != null) {
+ try {
+ // 将注释内容解析为 JSON 对象
+ JSONObject metaInfo = new JSONObject(getNotes().trim());
+ // 从 JSON 对象中提取 GTASK_ID 字段的值,并将其存储在 mRelatedGid 变量中
+ mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
+ } catch (JSONException e) {
+ ///如果在解析注释时出现异常,则将 mRelatedGid 变量设置为 null,并记录一个警告日志
+ Log.w(TAG, "failed to get related gid");
+ mRelatedGid = null;
+ }
+ }
+ }
+ /*这段代码是 MetaData 类的另外两个方法,isWorthSaving 和 setContentByRemoteJSON。
+
+ isWorthSaving 方法覆盖了 Task 类中的同名方法。
+ 它返回一个布尔值,如果任务的说明不为 null,则返回 true,否则返回 false。
+
+ setContentByRemoteJSON 方法覆盖了 Task 类中的同名方法。
+ 它接受一个 JSONObject 对象 js。首先,它调用了 Task 类中的 setContentByRemoteJSON 方法。
+ 然后,它检查任务的说明是否为 null。
+ 如果不是 null,则将它转换为一个 JSONObject 对象 metaInfo,然后从 metaInfo 对象中获取以 GTaskStringUtils.META_HEAD_GTASK_ID 为键的字符串值,
+ 将它赋值给 mRelatedGid 属性。如果无法从 metaInfo 对象中获取相关键值对,则将 mRelatedGid 属性赋值为 null,并输出一个警告日志。*/
+
+ @Override
+ public void setContentByLocalJSON(JSONObject js) {
+ // this function should not be called
+ throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
+ }
+
+ @Override
+ public JSONObject getLocalJSONFromContent() {
+ throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
+ }
+
+ @Override
+ public int getSyncAction(Cursor c) {
+ throw new IllegalAccessError("MetaData:getSyncAction should not be called");
+ }
+
+}
+/*这段代码定义了三个方法,均为覆盖(override)自父类 Task 的方法。这些方法被 MetaData 类重写,主要是为了禁止使用它们来保证程序的正确性。
+
+setContentByLocalJSON 方法不应该被调用,因此该方法的实现中直接抛出了一个 IllegalAccessError 异常,提示该方法不应该被调用。
+
+getLocalJSONFromContent 方法也不应该被调用,因此其实现方法与上述方法相同,也会抛出IllegalAccessError 异常。
+
+getSyncAction 方法同样不应该被调用,并且其实现方法与前两个方法类似,会抛出一个IllegalAccessError 异常。
+
+这些方法的目的是为了确保程序在运行时不会意外地调用这些方法,从而导致错误发生。如果有人在代码中尝试调用这些方法,将会得到明确的错误提示。*/
\ No newline at end of file
diff --git a/other/06_190341301蔡玉祥_代码标注/NetworkFailureException.java b/other/06_190341301蔡玉祥_代码标注/NetworkFailureException.java
new file mode 100644
index 0000000..c6a2691
--- /dev/null
+++ b/other/06_190341301蔡玉祥_代码标注/NetworkFailureException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.gtask.exception;
+
+public class NetworkFailureException extends Exception {
+
+ private static final long serialVersionUID = 2107610287180234136L;
+
+ public NetworkFailureException() {
+ super(); // 调用父类的构造函数
+ }
+
+ public NetworkFailureException(String paramString) {
+ super(paramString); // 调用父类的构造函数,并传入异常信息
+ }
+
+ public NetworkFailureException(String paramString, Throwable paramThrowable) {
+ super(paramString, paramThrowable); // 调用父类的构造函数,并传入异常信息和原始异常
+ }
+}
diff --git a/other/06_190341301蔡玉祥_代码标注/Node.java b/other/06_190341301蔡玉祥_代码标注/Node.java
new file mode 100644
index 0000000..44ab2bc
--- /dev/null
+++ b/other/06_190341301蔡玉祥_代码标注/Node.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.gtask.data;
+
+import android.database.Cursor;
+
+import org.json.JSONObject;
+
+public abstract class Node {
+ public static final int SYNC_ACTION_NONE = 0;
+
+ public static final int SYNC_ACTION_ADD_REMOTE = 1;
+
+ public static final int SYNC_ACTION_ADD_LOCAL = 2;
+
+ public static final int SYNC_ACTION_DEL_REMOTE = 3;
+
+ public static final int SYNC_ACTION_DEL_LOCAL = 4;
+
+ public static final int SYNC_ACTION_UPDATE_REMOTE = 5;
+
+ public static final int SYNC_ACTION_UPDATE_LOCAL = 6;
+
+ public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;
+
+ public static final int SYNC_ACTION_ERROR = 8;
+
+ private String mGid;
+
+ private String mName;
+
+ private long mLastModified;
+
+ private boolean mDeleted;
+/*这是一个抽象类 Node 的定义,其中包含以下几个字段和常量:
+
+字段 mGid:表示节点的全局唯一标识符 gid,每个节点都有一个唯一的 gid。
+
+字段 mName:表示节点的名称,该字段为字符串类型。
+
+字段 mLastModified:表示节点最后修改的时间戳,以毫秒为单位。
+
+字段 mDeleted:表示节点是否被删除(true 表示已删除,false 表示未删除)。
+
+常量 SYNC_ACTION_*:这些常量表示同步操作的各种情况。可以使用这些常量来指定要执行的同步操作类型。具体而言,这些常量的含义分别如下:
+
+SYNC_ACTION_NONE:不进行同步操作;
+
+SYNC_ACTION_ADD_REMOTE:在远程服务器上添加一个新节点;
+
+SYNC_ACTION_ADD_LOCAL:在本地数据库中添加一个新节点;
+
+SYNC_ACTION_DEL_REMOTE:在远程服务器上删除一个节点;
+
+SYNC_ACTION_DEL_LOCAL:在本地数据库中删除一个节点;
+
+SYNC_ACTION_UPDATE_REMOTE:在远程服务器上更新一个节点;
+
+SYNC_ACTION_UPDATE_LOCAL:在本地数据库中更新一个节点;
+
+SYNC_ACTION_UPDATE_CONFLICT:发生同步冲突,需要解决冲突;
+
+SYNC_ACTION_ERROR:同步错误,同步失败。
+
+此外,Node 类还是一个抽象类,无法直接创建它的实例。所有的节点都是 Node 类的子类,以具体类型的形式继承 Node 类,并提供它们自己的实现。*/
+ public Node() {
+ mGid = null;
+ mName = "";
+ mLastModified = 0;
+ mDeleted = false;
+ }
+/*这是 Node 类的默认构造方法。该方法会初始化节点的各个字段,包括 mGid、mName、mLastModified 和 mDeleted。
+
+具体而言,mGid 字段被初始化为 null,表示此时节点没有被分配一个全局唯一标识符;
+mName 字段被初始化为空字符串,表示节点名称为空;
+mLastModified 字段被初始化为 0,表示节点最后修改时间的时间戳为 0(即从未修改过);
+mDeleted 字段被初始化为 false,表示节点当前未被删除。
+ */
+ public abstract JSONObject getCreateAction(int actionId);
+
+ public abstract JSONObject getUpdateAction(int actionId);
+
+ public abstract void setContentByRemoteJSON(JSONObject js);
+
+ public abstract void setContentByLocalJSON(JSONObject js);
+
+ public abstract JSONObject getLocalJSONFromContent();
+
+ public abstract int getSyncAction(Cursor c);
+/*这是 Node 抽象类中的一些抽象方法,需要在其子类中进行实现。这些方法分别如下:
+
+getCreateAction(int actionId):该方法用于生成在远程服务器上创建一个新节点的 JSON 数据。actionId 表示同步操作类型,可以根据不同的同步操作类型来生成对应的 JSON 数据。
+
+getUpdateAction(int actionId):该方法用于生成在远程服务器上更新一个节点的 JSON 数据,与 getCreateAction 方法类似,actionId 表示同步操作类型。
+
+setContentByRemoteJSON(JSONObject js):该方法用于从远程服务器返回的 JSON 数据中设置节点对象的属性值。
+
+setContentByLocalJSON(JSONObject js):该方法用于从本地数据库中读取的 JSON 数据中设置节点对象的属性值。
+
+getLocalJSONFromContent():该方法将节点对象转换为本地数据库中的 JSON 形式。
+
+getSyncAction(Cursor c):该方法返回表示节点同步操作的常量。
+
+这些抽象方法是用于描述哪些数据需要被从远程服务器或本地数据库中读取或写入,并且确定在特定同步操作期间执行的操作类型。这使得 Node 的子类可以根据具体情况对这些方法进行自定义实现,以适应不同场景的需求。*/
+ public void setGid(String gid) {
+ this.mGid = gid;
+ }
+/*这是 Node 类的 setGid 方法,用于设置节点的唯一标识符 gid。
+该方法接收一个 String 类型的参数 gid,表示要为节点设置的全局唯一标识符。
+在执行该方法时,将会将传入的 gid 参数赋值给 mGid 字段,以便在后续的操作中使用该值。
+这个方法可以被其子类调用来设置自己的 gid 值。
+ */
+ public void setName(String name) {
+ this.mName = name;
+ }
+/*这是 Node 类的一个方法,用于设置节点的名称 mName 的值。
+它接受一个字符串类型的参数 name,并将它赋值给 mName 字段。
+this 表示当前对象实例。
+ */
+ public void setLastModified(long lastModified) {
+ this.mLastModified = lastModified;
+ }
+/*setLastModified具有一个long类型的参数lastModified,表示将该对象的最后修改时间设置为给定值。
+在方法体内,关键字this表示当前对象的引用,即在调用该方法的对象。
+将参数lastModified的值分配给该对象的私有成员变量mLastModified,以便以后可以通过getter方法检索该值。
+ */
+ public void setDeleted(boolean deleted) {
+ this.mDeleted = deleted;
+ }
+/*这是一个Java类中的公共方法,名称为setDeleted,它具有一个boolean类型的参数deleted,表示将该对象的删除状态设置为给定的值。
+
+在方法体内,关键字this表示当前对象的引用,即在调用该方法的对象。
+将参数deleted的值分配给该对象的私有成员变量mDeleted,以便以后可以通过getter方法检索该值。
+如果参数deleted的值为true,则表示该对象被删除;如果参数deleted的值为false,则表示该对象未被删除。
+ */
+ public String getGid() {
+ return this.mGid;
+ }
+
+ public String getName() {
+ return this.mName;
+ }
+/*这是一个Java类中的公共方法,名称分别为getGid和getName,它们都不具有任何参数,并返回该对象的私有成员变量mGid和mName的值,分别表示该对象的ID和名称。
+
+在方法体内,关键字this表示当前对象的引用,即在调用该方法的对象。
+这些方法通常用于获取对象的状态,而不是修改状态。
+例如,在代码中调用getGid方法将返回该对象的ID,以便在其他操作中使用该ID。
+同样,调用getName方法将返回该对象的名称。
+ */
+ public long getLastModified() {
+ return this.mLastModified;
+ }
+/*这是一个Java类中的公共方法,名称为getLastModified,它不具有任何参数,并返回该对象的私有成员变量mLastModified的值,表示该对象的最后修改时间。
+
+在方法体内,关键字this表示当前对象的引用,即在调用该方法的对象。
+这个方法通常用于获取对象的状态,以便在其他操作中使用该状态。
+例如,在代码中调用getLastModified方法将返回该对象的最后修改时间,以便在其他操作中使用该时间戳。
+ */
+ public boolean getDeleted() {
+ return this.mDeleted;
+ }
+/*这是一个Java类中的公共方法,名称为getDeleted,它不具有任何参数,并返回该对象的私有成员变量mDeleted的值,表示该对象是否被删除。
+
+在方法体内,关键字this表示当前对象的引用,即在调用该方法的对象。
+这个方法通常用于获取对象的状态,以便在其他操作中使用该状态。
+例如,在代码中调用getDeleted方法将返回一个boolean类型的值,表示该对象是否被删除。
+如果该值为true,则表示该对象已被删除;如果该值为false,则表示该对象未被删除。
+ */
+}
diff --git a/other/06_190341301蔡玉祥_代码标注/SqlData.java b/other/06_190341301蔡玉祥_代码标注/SqlData.java
new file mode 100644
index 0000000..e6a6be8
--- /dev/null
+++ b/other/06_190341301蔡玉祥_代码标注/SqlData.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.gtask.data;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.util.Log;
+
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.DataColumns;
+import net.micode.notes.data.Notes.DataConstants;
+import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.data.NotesDatabaseHelper.TABLE;
+import net.micode.notes.gtask.exception.ActionFailureException;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+
+public class SqlData {
+ private static final String TAG = SqlData.class.getSimpleName();
+
+ private static final int INVALID_ID = -99999;
+
+ public static final String[] PROJECTION_DATA = new String[] {
+ DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
+ DataColumns.DATA3
+ };
+/*这是一个Java类,名称为SqlData,其中定义了一些静态变量和常量,用于在类内和类外引用。
+
+第一行定义了一个名为TAG的静态常量字符串,用于在日志中标识该类。这个字符串的值是SqlData类的简单名称。
+
+第二行定义了一个名为INVALID_ID的私有静态常量整数,用于表示无效的ID。该值被设置为-99999。
+
+第三行定义了一个名为PROJECTION_DATA的公共静态常量字符串数组,用于定义一个查询所需返回的列。这些列包括DataColumns类中定义的ID、MIME_TYPE、CONTENT、DATA1和DATA3。
+
+这些变量和常量都被声明为静态的,这意味着它们属于类本身,而不是类的实例。因此,它们可以在类的所有实例之间共享和访问。这些变量和常量的使用可以提高代码的可读性和可维护性,并避免在代码中多次重复相同的值。*/
+ public static final int DATA_ID_COLUMN = 0;
+
+ public static final int DATA_MIME_TYPE_COLUMN = 1;
+
+ public static final int DATA_CONTENT_COLUMN = 2;
+
+ public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
+
+ public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
+
+ private ContentResolver mContentResolver;
+
+ private boolean mIsCreate;
+
+ private long mDataId;
+
+ private String mDataMimeType;
+
+ private String mDataContent;
+
+ private long mDataContentData1;
+
+ private String mDataContentData3;
+
+ private ContentValues mDiffDataValues;
+/*这是一个Java类,名称为SqlData,其中定义了一些成员变量和常量,用于在类内存储和操作数据。
+
+第一行定义了一个名为DATA_ID_COLUMN的公共静态常量整数,用于表示列索引中的数据ID列。
+
+第二行定义了一个名为DATA_MIME_TYPE_COLUMN的公共静态常量整数,用于表示列索引中的MIME类型列。
+
+第三行定义了一个名为DATA_CONTENT_COLUMN的公共静态常量整数,用于表示列索引中的内容列。
+
+第四行定义了一个名为DATA_CONTENT_DATA_1_COLUMN的公共静态常量整数,用于表示列索引中的DATA1列。
+
+第五行定义了一个名为DATA_CONTENT_DATA_3_COLUMN的公共静态常量整数,用于表示列索引中的DATA3列。
+
+接下来的几行定义了该类的一些成员变量,包括ContentResolver类型的mContentResolver、boolean类型的mIsCreate、long类型的mDataId、String类型的mDataMimeType、String类型的mDataContent、long类型的mDataContentData1、String类型的mDataContentData3和ContentValues类型的mDiffDataValues。这些变量表示了该类的不同数据属性。
+
+其中,ContentValues是一个键值对的集合,用于在Android应用中存储和操作数据。在该类中,mDiffDataValues被用于存储该对象的数据属性的差异,即该对象已更改但尚未提交到数据库中的值。*/
+ // 构造函数用于初始化一个SqlData对象的实例
+ public SqlData(Context context) {
+ mContentResolver = context.getContentResolver();// 获取ContentResolver对象,用于访问ContentProvider中的数据
+ mIsCreate = true;// 初始化数据的创建状态
+ mDataId = INVALID_ID; // 初始化数据的ID值
+ mDataMimeType = DataConstants.NOTE;// 初始化数据的MIME类型为NOTE
+ mDataContent = ""; // 初始化数据的内容为空字符串
+ mDataContentData1 = 0; // 初始化数据的数据内容Data1为0
+ mDataContentData3 = "";// 初始化数据的数据内容Data3为空字符串
+ mDiffDataValues = new ContentValues();// 初始化差异数据的ContentValues对象,用于保存数据的不同之处
+ }
+
+ // 定义SqlData类,该类用于处理数据库中的数据
+ public SqlData(Context context, Cursor c) {
+ // 获取ContentResolver对象
+ mContentResolver = context.getContentResolver();
+ // 设置初始状态为非创建状态
+ mIsCreate = false;
+ // 从Cursor对象中读取数据,并将数据加载到
+ loadFromCursor(c);
+ // 创建一个新的ContentValues对象,用于存储数据的差异
+ mDiffDataValues = new ContentValues();
+ }
+/*该构造函数主要是用于初始化SqlData对象,并且在初始化过程中从Cursor对象中读取数据,同时创建一个新的ContentValues对象,以备后续存储数据的差异。*/
+/*定义了一个私有方法loadFromCursor,该方法接收一个Cursor对象作为参数,并且在该方法中从Cursor对象中读取数据,将这些数据加载到SqlData对象中。*/
+// 从Cursor对象中读取数据,并将数据加载到SqlData对象中
+ private void loadFromCursor(Cursor c) {
+ // 从Cursor对象中读取数据id
+ mDataId = c.getLong(DATA_ID_COLUMN);
+ // 从Cursor对象中读取数据类型
+ mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
+ // 从Cursor对象中读取数据内容
+ mDataContent = c.getString(DATA_CONTENT_COLUMN);
+ // 从Cursor对象中读取数据内容的第一个数据项
+ mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
+ // 从Cursor对象中读取数据内容的第三个数据项
+ mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
+ }
+/*该方法主要是用于从Cursor对象中读取数据,并将这些数据加载到SqlData对象的相关成员变量中。其中,数据的读取顺序和类型需要与Cursor对象中的列名一一对应。*/
+ public void setContent(JSONObject js) throws JSONException {// 设置SqlData对象的内容
+ long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;// 从JSONObject对象中获取数据id
+ if (mIsCreate || mDataId != dataId) {
+ mDiffDataValues.put(DataColumns.ID, dataId);
+ }
+ mDataId = dataId; // 如果当前SqlData对象是创建状态,或者数据id与当前对象的数据id不同,将数据id加入到差异数据值对象中
+
+//setContent方法的主要部分,用于设置SqlData对象的各个成员变量值,并且在设置过程中检查差异数据。
+ String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
+ : DataConstants.NOTE;
+ if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
+ mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
+ }
+ mDataMimeType = dataMimeType;
+ // 从JSONObject对象中获取数据类型,并将其设置为SqlData对象的数据类型
+ String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
+ if (mIsCreate || !mDataContent.equals(dataContent)) {
+ mDiffDataValues.put(DataColumns.CONTENT, dataContent);
+ }
+ mDataContent = dataContent;
+ // 从JSONObject对象中获取数据内容的第一个数据项,并将其设置为SqlData对象的数据内容的第一个数据项
+ long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
+ if (mIsCreate || mDataContentData1 != dataContentData1) {
+ mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
+ }
+ mDataContentData1 = dataContentData1;
+ // 从JSONObject对象中获取数据内容的第三个数据项,并将其设置为SqlData对象的数据内容的第三个数据项
+ String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
+ if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
+ mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
+ }
+ mDataContentData3 = dataContentData3;
+ }
+/*该代码段主要是根据JSONObject对象中的数据,设置SqlData对象的相应成员变量,并且在设置过程中检查差异数据。
+ 其中,如果当前SqlData对象是创建状态,或者相应成员变量的值与JSONObject对象中的值不同,就将相应的数据加入到差异数据值对象中。
+ 这些差异数据值将在后续的操作中被用于更新数据库中的数据。*/
+ public JSONObject getContent() throws JSONException {
+ if (mIsCreate) {
+ Log.e(TAG, "it seems that we haven't created this in database yet");
+ return null;
+ }
+ JSONObject js = new JSONObject();
+ js.put(DataColumns.ID, mDataId);
+ js.put(DataColumns.MIME_TYPE, mDataMimeType);
+ js.put(DataColumns.CONTENT, mDataContent);
+ js.put(DataColumns.DATA1, mDataContentData1);
+ js.put(DataColumns.DATA3, mDataContentData3);
+ return js;
+ }
+/*这段代码定义了一个名为getContent()的方法,它返回一个JSONObject对象。
+该方法可能会抛出一个JSONException异常,因此在调用该方法时需要处理该异常。
+方法体内的第一条语句检查一个名为mIsCreate的 boolean 类型的成员变量是否为 true,如果是,则输出一个错误日志并返回 null。否则,方法会创建一个新的JSONObject对象,并向它添加一些键值对,这些键值对是由mDataId、mDataMimeType、mDataContent、mDataContentData1和mDataContentData3这些成员变量组成的。
+最后,该方法返回一个JSONObject对象,其中包含了这些成员变量的值。*/
+
+
+ public void commit(long noteId, boolean validateVersion, long version) {
+
+ if (mIsCreate) {
+ if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
+ mDiffDataValues.remove(DataColumns.ID);
+ }
+
+ mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
+ Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
+ try {
+ mDataId = Long.valueOf(uri.getPathSegments().get(1));
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Get note id error :" + e.toString());
+ throw new ActionFailureException("create note failed");
+ }
+ } else {
+ if (mDiffDataValues.size() > 0) {
+ int result = 0;
+ if (!validateVersion) {
+ result = mContentResolver.update(ContentUris.withAppendedId(
+ Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
+ } else {
+ result = mContentResolver.update(ContentUris.withAppendedId(
+ Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
+ " ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ + " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
+ String.valueOf(noteId), String.valueOf(version)
+ });
+ }
+ if (result == 0) {
+ Log.w(TAG, "there is no update. maybe user updates note when syncing");
+ }
+ }
+ }
+
+ mDiffDataValues.clear();
+ mIsCreate = false;
+ }
+/*这段代码定义了一个名为commit()的方法,用于将已经修改的数据保存到数据库中。
+方法接受三个参数:noteId为 long 类型,表示当前笔记的 ID;
+validateVersion为 boolean 类型,表示是否需要验证版本;
+version为 long 类型,表示当前笔记的版本。
+方法体内的第一条语句检查一个名为mIsCreate的boolean类型的成员变量是否为true,如果是,则表示当前是创建一个新的数据,需要将数据插入到数据库中。
+如果mDiffDataValues中包含 DataColumns.ID 这个键值对,表明该成员变量的值已经被赋值,需要将其移除,因为它是自增的主键,不应该由用户指定。
+接下来,将 DataColumns.NOTE_ID 对应的值设置为noteId,并将mDiffDataValues插入到Notes.CONTENT_DATA_URI中获取Uri对象,然后Uri对象中获取新的数据的 ID 并设置给成员变量mDataId。
+如果获取 ID 失败,则记录错误日志并抛出一个ActionFailureException异常。
+如果mIsCreate的值为 false,则表示需要更新数据库中的数据。
+如果mDiffDataValues中有数据,则调用mContentResolver.update()方法对数据库进行更新。
+如果validateVersion的值为 false,则直接更新数据,否则需要先验证版本。验证版本的过程是通过查询NoteColumns.VERSION等于version且NoteColumns.ID等于的noteId
+无论是插入数据还是更新数据,都需要清空mDiffDataValues的内容并将mIsCreate设为 false,表示数据已经提交保存到数据库中。*/
+ public long getId() {
+ return mDataId;
+ }//定义了一个名为getId()的方法,返回一个long类型的值,表示当前笔记的ID。
+ //直接返回成员变量mDataId,因此不会引发任何异常。
+}
+6
\ No newline at end of file
diff --git a/other/06_190341301蔡玉祥_代码标注/SqlNote.java b/other/06_190341301蔡玉祥_代码标注/SqlNote.java
new file mode 100644
index 0000000..79a4095
--- /dev/null
+++ b/other/06_190341301蔡玉祥_代码标注/SqlNote.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.gtask.data;
+
+import android.appwidget.AppWidgetManager;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.util.Log;
+
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.DataColumns;
+import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.gtask.exception.ActionFailureException;
+import net.micode.notes.tool.GTaskStringUtils;
+import net.micode.notes.tool.ResourceParser;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+
+
+public class SqlNote {
+ private static final String TAG = SqlNote.class.getSimpleName();
+
+ private static final int INVALID_ID = -99999;
+
+ 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,
+ NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, NoteColumns.SYNC_ID,
+ NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID,
+ NoteColumns.VERSION
+ };
+
+ public static final int ID_COLUMN = 0;
+
+ public static final int ALERTED_DATE_COLUMN = 1;
+
+ public static final int BG_COLOR_ID_COLUMN = 2;
+
+ public static final int CREATED_DATE_COLUMN = 3;
+
+ public static final int HAS_ATTACHMENT_COLUMN = 4;
+
+ public static final int MODIFIED_DATE_COLUMN = 5;
+
+ public static final int NOTES_COUNT_COLUMN = 6;
+
+ public static final int PARENT_ID_COLUMN = 7;
+
+ public static final int SNIPPET_COLUMN = 8;
+
+ public static final int TYPE_COLUMN = 9;
+
+ public static final int WIDGET_ID_COLUMN = 10;
+
+ public static final int WIDGET_TYPE_COLUMN = 11;
+
+ public static final int SYNC_ID_COLUMN = 12;
+
+ public static final int LOCAL_MODIFIED_COLUMN = 13;
+
+ public static final int ORIGIN_PARENT_ID_COLUMN = 14;
+
+ public static final int GTASK_ID_COLUMN = 15;
+
+ public static final int VERSION_COLUMN = 16;
+
+ private Context mContext;
+
+ private ContentResolver mContentResolver;
+
+ private boolean mIsCreate;
+
+ private long mId;
+
+ private long mAlertDate;
+
+ private int mBgColorId;
+
+ private long mCreatedDate;
+
+ private int mHasAttachment;
+
+ private long mModifiedDate;
+
+ private long mParentId;
+
+ private String mSnippet;
+
+ private int mType;
+
+ private int mWidgetId;
+
+ private int mWidgetType;
+
+ private long mOriginParent;
+
+ private long mVersion;
+
+ private ContentValues mDiffNoteValues;
+
+ private ArrayList mDataList;
+
+ public SqlNote(Context context) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mIsCreate = true;
+ mId = INVALID_ID;
+ mAlertDate = 0;
+ mBgColorId = ResourceParser.getDefaultBgId(context);
+ mCreatedDate = System.currentTimeMillis();
+ mHasAttachment = 0;
+ mModifiedDate = System.currentTimeMillis();
+ mParentId = 0;
+ mSnippet = "";
+ mType = Notes.TYPE_NOTE;
+ mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
+ mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
+ mOriginParent = 0;
+ mVersion = 0;
+ mDiffNoteValues = new ContentValues();
+ mDataList = new ArrayList();
+ }
+
+ public SqlNote(Context context, Cursor c) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mIsCreate = false;
+ loadFromCursor(c);
+ mDataList = new ArrayList();
+ if (mType == Notes.TYPE_NOTE)
+ loadDataContent();
+ mDiffNoteValues = new ContentValues();
+ }
+
+ public SqlNote(Context context, long id) {
+ mContext = context;
+ mContentResolver = context.getContentResolver();
+ mIsCreate = false;
+ loadFromCursor(id);
+ mDataList = new ArrayList();
+ if (mType == Notes.TYPE_NOTE)
+ loadDataContent();
+ mDiffNoteValues = new ContentValues();
+
+ }
+
+ 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);
+ } else {
+ Log.w(TAG, "loadFromCursor: cursor = null");
+ }
+ } finally {
+ if (c != null)
+ c.close();
+ }
+ }
+
+ private void loadFromCursor(Cursor c) {
+ mId = c.getLong(ID_COLUMN);
+ mAlertDate = c.getLong(ALERTED_DATE_COLUMN);
+ mBgColorId = c.getInt(BG_COLOR_ID_COLUMN);
+ mCreatedDate = c.getLong(CREATED_DATE_COLUMN);
+ mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN);
+ mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN);
+ mParentId = c.getLong(PARENT_ID_COLUMN);
+ mSnippet = c.getString(SNIPPET_COLUMN);
+ mType = c.getInt(TYPE_COLUMN);
+ mWidgetId = c.getInt(WIDGET_ID_COLUMN);
+ mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
+ mVersion = c.getLong(VERSION_COLUMN);
+ }
+
+ private void loadDataContent() {
+ Cursor c = null;
+ mDataList.clear();
+ try {
+ 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");
+ return;
+ }
+ while (c.moveToNext()) {
+ SqlData data = new SqlData(mContext, c);
+ mDataList.add(data);
+ }
+ } else {
+ Log.w(TAG, "loadDataContent: cursor = null");
+ }
+ } finally {
+ if (c != null)
+ c.close();
+ }
+ }
+
+ 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);
+ }
+ 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);
+ }
+ mWidgetId = widgetId;
+
+ 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);
+ }
+ mOriginParent = originParent;
+
+ for (int i = 0; i < dataArray.length(); i++) {
+ JSONObject data = dataArray.getJSONObject(i);
+ SqlData sqlData = null;
+ if (data.has(DataColumns.ID)) {
+ long dataId = data.getLong(DataColumns.ID);
+ for (SqlData temp : mDataList) {
+ if (dataId == temp.getId()) {
+ sqlData = temp;
+ }
+ }
+ }
+
+ if (sqlData == null) {
+ sqlData = new SqlData(mContext);
+ mDataList.add(sqlData);
+ }
+
+ sqlData.setContent(data);
+ }
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ return false;
+ }
+ return true;
+ }
+
+ public JSONObject getContent() {
+ try {
+ JSONObject js = new JSONObject();
+
+ if (mIsCreate) {
+ Log.e(TAG, "it seems that we haven't created this in database yet");
+ 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);
+ note.put(NoteColumns.CREATED_DATE, mCreatedDate);
+ note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment);
+ note.put(NoteColumns.MODIFIED_DATE, mModifiedDate);
+ note.put(NoteColumns.PARENT_ID, mParentId);
+ note.put(NoteColumns.SNIPPET, mSnippet);
+ note.put(NoteColumns.TYPE, mType);
+ note.put(NoteColumns.WIDGET_ID, mWidgetId);
+ note.put(NoteColumns.WIDGET_TYPE, mWidgetType);
+ 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();
+ if (data != null) {
+ dataArray.put(data);
+ }
+ }
+ 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());
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public void setParentId(long id) {
+ mParentId = id;
+ mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
+ }
+
+ public void setGtaskId(String gid) {
+ mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
+ }
+
+ public void setSyncId(long syncId) {
+ mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
+ }
+
+ public void resetLocalModified() {
+ 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 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;
+ }
+}
diff --git a/other/06_190341301蔡玉祥_代码标注/Task.java b/other/06_190341301蔡玉祥_代码标注/Task.java
new file mode 100644
index 0000000..6a19454
--- /dev/null
+++ b/other/06_190341301蔡玉祥_代码标注/Task.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.gtask.data;
+
+import android.database.Cursor;
+import android.text.TextUtils;
+import android.util.Log;
+
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.DataColumns;
+import net.micode.notes.data.Notes.DataConstants;
+import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.gtask.exception.ActionFailureException;
+import net.micode.notes.tool.GTaskStringUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+
+public class Task extends Node {
+ private static final String TAG = Task.class.getSimpleName();
+
+ private boolean mCompleted;
+
+ private String mNotes;
+
+ private JSONObject mMetaInfo;
+
+ private Task mPriorSibling;
+
+ private TaskList mParent;
+
+ public Task() {
+ super();
+ mCompleted = false;
+ mNotes = null;
+ mPriorSibling = null;
+ mParent = null;
+ mMetaInfo = null;
+ }
+
+ 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_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");
+ entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
+ GTaskStringUtils.GTASK_JSON_TYPE_TASK);
+ if (getNotes() != null) {
+ entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
+ }
+ js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
+
+ // parent_id
+ js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
+
+ // dest_parent_type
+ js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
+ GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
+
+ // list_id
+ js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
+
+ // prior_sibling_id
+ if (mPriorSibling != null) {
+ js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
+ }
+
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new ActionFailureException("fail to generate task-create jsonobject");
+ }
+
+ return js;
+ }
+
+ 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_ID, actionId);
+
+ // id
+ js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
+
+ // entity_delta
+ JSONObject entity = new JSONObject();
+ entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
+ if (getNotes() != null) {
+ entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
+ }
+ 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 task-update jsonobject");
+ }
+
+ return js;
+ }
+
+ public void setContentByRemoteJSON(JSONObject js) {
+ if (js != null) {
+ try {
+ // id
+ if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
+ setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
+ }
+
+ // last_modified
+ if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
+ setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
+ }
+
+ // name
+ if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
+ setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
+ }
+
+ // notes
+ if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
+ setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
+ }
+
+ // deleted
+ if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
+ setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
+ }
+
+ // completed
+ if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
+ setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new ActionFailureException("fail to get task content from jsonobject");
+ }
+ }
+ }
+
+ 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");
+ }
+
+ 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");
+ return;
+ }
+
+ for (int i = 0; i < dataArray.length(); i++) {
+ JSONObject data = dataArray.getJSONObject(i);
+ if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
+ setName(data.getString(DataColumns.CONTENT));
+ break;
+ }
+ }
+
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ }
+ }
+
+ 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");
+ return null;
+ }
+
+ JSONObject js = new JSONObject();
+ JSONObject note = new JSONObject();
+ JSONArray dataArray = new JSONArray();
+ JSONObject data = new JSONObject();
+ data.put(DataColumns.CONTENT, name);
+ dataArray.put(data);
+ js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
+ note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
+ 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)) {
+ data.put(DataColumns.CONTENT, getName());
+ break;
+ }
+ }
+
+ note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
+ return mMetaInfo;
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public void setMetaInfo(MetaData metaData) {
+ if (metaData != null && metaData.getNotes() != null) {
+ try {
+ mMetaInfo = new JSONObject(metaData.getNotes());
+ } catch (JSONException e) {
+ Log.w(TAG, e.toString());
+ mMetaInfo = null;
+ }
+ }
+ }
+
+ public int getSyncAction(Cursor c) {
+ try {
+ JSONObject noteInfo = null;
+ if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
+ noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
+ }
+
+ if (noteInfo == null) {
+ Log.w(TAG, "it seems that note meta has been deleted");
+ return SYNC_ACTION_UPDATE_REMOTE;
+ }
+
+ if (!noteInfo.has(NoteColumns.ID)) {
+ Log.w(TAG, "remote note id seems to be deleted");
+ return SYNC_ACTION_UPDATE_LOCAL;
+ }
+
+ // validate the note id now
+ if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
+ Log.w(TAG, "note id doesn't match");
+ return SYNC_ACTION_UPDATE_LOCAL;
+ }
+
+ if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
+ // there is no local update
+ if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
+ // no update both side
+ return SYNC_ACTION_NONE;
+ } else {
+ // apply remote to local
+ return SYNC_ACTION_UPDATE_LOCAL;
+ }
+ } else {
+ // validate gtask id
+ if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
+ Log.e(TAG, "gtask id doesn't match");
+ return SYNC_ACTION_ERROR;
+ }
+ if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
+ // local modification only
+ return SYNC_ACTION_UPDATE_REMOTE;
+ } else {
+ return SYNC_ACTION_UPDATE_CONFLICT;
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ }
+
+ return SYNC_ACTION_ERROR;
+ }
+
+ public boolean isWorthSaving() {
+ return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
+ || (getNotes() != null && getNotes().trim().length() > 0);
+ }
+
+ public void setCompleted(boolean completed) {
+ this.mCompleted = completed;
+ }
+
+ public void setNotes(String notes) {
+ this.mNotes = notes;
+ }
+
+ public void setPriorSibling(Task priorSibling) {
+ this.mPriorSibling = priorSibling;
+ }
+
+ public void setParent(TaskList parent) {
+ this.mParent = parent;
+ }
+
+ public boolean getCompleted() {
+ return this.mCompleted;
+ }
+
+ public String getNotes() {
+ return this.mNotes;
+ }
+
+ public Task getPriorSibling() {
+ return this.mPriorSibling;
+ }
+
+ public TaskList getParent() {
+ return this.mParent;
+ }
+
+}
diff --git a/other/06_190341301蔡玉祥_代码标注/TaskList.java b/other/06_190341301蔡玉祥_代码标注/TaskList.java
new file mode 100644
index 0000000..4ea21c5
--- /dev/null
+++ b/other/06_190341301蔡玉祥_代码标注/TaskList.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.micode.notes.gtask.data;
+
+import android.database.Cursor;
+import android.util.Log;
+
+import net.micode.notes.data.Notes;
+import net.micode.notes.data.Notes.NoteColumns;
+import net.micode.notes.gtask.exception.ActionFailureException;
+import net.micode.notes.tool.GTaskStringUtils;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+
+
+public class TaskList extends Node {
+ private static final String TAG = TaskList.class.getSimpleName();
+
+ private int mIndex;
+
+ private ArrayList mChildren;
+
+ public TaskList() {
+ super();
+ mChildren = new ArrayList();
+ mIndex = 1;
+ }
+
+ 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_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);
+
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new ActionFailureException("fail to generate tasklist-create jsonobject");
+ }
+
+ return js;
+ }
+
+ 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_ID, actionId);
+
+ // id
+ js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
+
+ // entity_delta
+ JSONObject entity = new JSONObject();
+ entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
+ entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
+ 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");
+ }
+
+ return js;
+ }
+
+ public void setContentByRemoteJSON(JSONObject js) {
+ if (js != null) {
+ try {
+ // id
+ if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
+ setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
+ }
+
+ // last_modified
+ if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
+ setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
+ }
+
+ // name
+ if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
+ setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
+ }
+
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ throw new ActionFailureException("fail to get tasklist content from jsonobject");
+ }
+ }
+ }
+
+ public void setContentByLocalJSON(JSONObject js) {
+ if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
+ Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
+ }
+
+ try {
+ JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
+
+ if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
+ 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)
+ 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 {
+ Log.e(TAG, "error type");
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ }
+ }
+
+ 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);
+
+ return js;
+ } catch (JSONException e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public int getSyncAction(Cursor c) {
+ try {
+ if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
+ // there is no local update
+ if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
+ // no update both side
+ return SYNC_ACTION_NONE;
+ } else {
+ // apply remote to local
+ return SYNC_ACTION_UPDATE_LOCAL;
+ }
+ } else {
+ // validate gtask id
+ if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
+ Log.e(TAG, "gtask id doesn't match");
+ return SYNC_ACTION_ERROR;
+ }
+ if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
+ // local modification only
+ return SYNC_ACTION_UPDATE_REMOTE;
+ } else {
+ // for folder conflicts, just apply local modification
+ return SYNC_ACTION_UPDATE_REMOTE;
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, e.toString());
+ e.printStackTrace();
+ }
+
+ return SYNC_ACTION_ERROR;
+ }
+
+ public int getChildTaskCount() {
+ 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);
+ }
+ }
+ return ret;
+ }
+
+ public boolean addChildTask(Task task, int index) {
+ if (index < 0 || index > mChildren.size()) {
+ Log.e(TAG, "add child task: invalid index");
+ 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);
+ }
+
+ 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));
+ }
+ }
+ }
+ 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");
+ return false;
+ }
+
+ if (pos == index)
+ return true;
+ return (removeChildTask(task) && addChildTask(task, index));
+ }
+
+ 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;
+ }
+ }
+ return null;
+ }
+
+ public int getChildTaskIndex(Task task) {
+ return mChildren.indexOf(task);
+ }
+
+ public Task getChildTaskByIndex(int index) {
+ if (index < 0 || index >= mChildren.size()) {
+ Log.e(TAG, "getTaskByIndex: invalid index");
+ return null;
+ }
+ return mChildren.get(index);
+ }
+
+ public Task getChilTaskByGid(String gid) {
+ for (Task task : mChildren) {
+ if (task.getGid().equals(gid))
+ return task;
+ }
+ return null;
+ }
+
+ public ArrayList getChildTaskList() {
+ return this.mChildren;
+ }
+
+ public void setIndex(int index) {
+ this.mIndex = index;
+ }
+
+ public int getIndex() {
+ return this.mIndex;
+ }
+}
diff --git a/other/07_190243114庞浩_实践总结报告.docx b/other/07_190243114庞浩_实践总结报告.docx
new file mode 100644
index 0000000..dbe3720
Binary files /dev/null and b/other/07_190243114庞浩_实践总结报告.docx differ
diff --git a/other/190341301蔡玉祥.docx b/other/190341301蔡玉祥.docx
new file mode 100644
index 0000000..a5fda11
Binary files /dev/null and b/other/190341301蔡玉祥.docx differ
diff --git a/other/新增统计字数功能(庞浩).docx b/other/新增统计字数功能(庞浩).docx
new file mode 100644
index 0000000..b9d7c64
Binary files /dev/null and b/other/新增统计字数功能(庞浩).docx differ
diff --git a/other/新增统计文件夹下多少便签功能(邹兴云).wps b/other/新增统计文件夹下多少便签功能(邹兴云).wps
new file mode 100644
index 0000000..88f7b47
Binary files /dev/null and b/other/新增统计文件夹下多少便签功能(邹兴云).wps differ
diff --git a/src/.idea/modules.xml b/src/.idea/modules.xml
new file mode 100644
index 0000000..fba00ca
--- /dev/null
+++ b/src/.idea/modules.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.idea/modules/app/src.app.androidTest.iml b/src/.idea/modules/app/src.app.androidTest.iml
new file mode 100644
index 0000000..70d4b1e
--- /dev/null
+++ b/src/.idea/modules/app/src.app.androidTest.iml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.idea/modules/app/src.app.iml b/src/.idea/modules/app/src.app.iml
new file mode 100644
index 0000000..8f8bb49
--- /dev/null
+++ b/src/.idea/modules/app/src.app.iml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.idea/modules/app/src.app.main.iml b/src/.idea/modules/app/src.app.main.iml
new file mode 100644
index 0000000..213d746
--- /dev/null
+++ b/src/.idea/modules/app/src.app.main.iml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.idea/modules/app/src.app.unitTest.iml b/src/.idea/modules/app/src.app.unitTest.iml
new file mode 100644
index 0000000..b653258
--- /dev/null
+++ b/src/.idea/modules/app/src.app.unitTest.iml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.idea/src.iml b/src/.idea/src.iml
new file mode 100644
index 0000000..d1374d1
--- /dev/null
+++ b/src/.idea/src.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/.scannerwork/.sonar_lock b/src/.scannerwork/.sonar_lock
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_45854fc754e3cc87bc97e1c5f9d8abffab1eba58b278e4f844735d84b0b806c2_bucket_0/graph.bin b/src/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_45854fc754e3cc87bc97e1c5f9d8abffab1eba58b278e4f844735d84b0b806c2_bucket_0/graph.bin
new file mode 100644
index 0000000..601f245
Binary files /dev/null and b/src/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_45854fc754e3cc87bc97e1c5f9d8abffab1eba58b278e4f844735d84b0b806c2_bucket_0/graph.bin differ
diff --git a/src/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_45854fc754e3cc87bc97e1c5f9d8abffab1eba58b278e4f844735d84b0b806c2_bucket_1/graph.bin b/src/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_45854fc754e3cc87bc97e1c5f9d8abffab1eba58b278e4f844735d84b0b806c2_bucket_1/graph.bin
new file mode 100644
index 0000000..601f245
Binary files /dev/null and b/src/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_45854fc754e3cc87bc97e1c5f9d8abffab1eba58b278e4f844735d84b0b806c2_bucket_1/graph.bin differ
diff --git a/src/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_45854fc754e3cc87bc97e1c5f9d8abffab1eba58b278e4f844735d84b0b806c2_bucket_2/graph.bin b/src/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_45854fc754e3cc87bc97e1c5f9d8abffab1eba58b278e4f844735d84b0b806c2_bucket_2/graph.bin
new file mode 100644
index 0000000..601f245
Binary files /dev/null and b/src/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_45854fc754e3cc87bc97e1c5f9d8abffab1eba58b278e4f844735d84b0b806c2_bucket_2/graph.bin differ
diff --git a/src/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_45854fc754e3cc87bc97e1c5f9d8abffab1eba58b278e4f844735d84b0b806c2_bucket_3/graph.bin b/src/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_45854fc754e3cc87bc97e1c5f9d8abffab1eba58b278e4f844735d84b0b806c2_bucket_3/graph.bin
new file mode 100644
index 0000000..601f245
Binary files /dev/null and b/src/app/build/intermediates/desugar_graph/debug/out/currentProject/jar_45854fc754e3cc87bc97e1c5f9d8abffab1eba58b278e4f844735d84b0b806c2_bucket_3/graph.bin differ
diff --git a/src/app/build/intermediates/javac/debug/classes/net/micode/notes/ui/NoteEditActivity$4.class b/src/app/build/intermediates/javac/debug/classes/net/micode/notes/ui/NoteEditActivity$4.class
new file mode 100644
index 0000000..f38e53b
Binary files /dev/null and b/src/app/build/intermediates/javac/debug/classes/net/micode/notes/ui/NoteEditActivity$4.class differ
diff --git a/src/app/build/intermediates/project_dex_archive/debug/out/c4ee57c560f01f0216033dc0a7e3582676967ef2d1c3da88eb07b635aef44ff9_1.jar b/src/app/build/intermediates/project_dex_archive/debug/out/c4ee57c560f01f0216033dc0a7e3582676967ef2d1c3da88eb07b635aef44ff9_1.jar
new file mode 100644
index 0000000..6b5db94
Binary files /dev/null and b/src/app/build/intermediates/project_dex_archive/debug/out/c4ee57c560f01f0216033dc0a7e3582676967ef2d1c3da88eb07b635aef44ff9_1.jar differ
diff --git a/src/app/build/intermediates/project_dex_archive/debug/out/net/micode/notes/ui/NoteEditActivity$4.dex b/src/app/build/intermediates/project_dex_archive/debug/out/net/micode/notes/ui/NoteEditActivity$4.dex
new file mode 100644
index 0000000..49b4bd3
Binary files /dev/null and b/src/app/build/intermediates/project_dex_archive/debug/out/net/micode/notes/ui/NoteEditActivity$4.dex differ
diff --git a/src/app/build/outputs/apk/debug/app-debug.apk b/src/app/build/outputs/apk/debug/app-debug.apk
new file mode 100644
index 0000000..a1a1748
Binary files /dev/null and b/src/app/build/outputs/apk/debug/app-debug.apk differ
diff --git a/src/app/build/outputs/apk/debug/output-metadata.json b/src/app/build/outputs/apk/debug/output-metadata.json
new file mode 100644
index 0000000..b2231b9
--- /dev/null
+++ b/src/app/build/outputs/apk/debug/output-metadata.json
@@ -0,0 +1,20 @@
+{
+ "version": 3,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "net.micode.notes",
+ "variantName": "debug",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "attributes": [],
+ "versionCode": 1,
+ "versionName": "0.1",
+ "outputFile": "app-debug.apk"
+ }
+ ],
+ "elementType": "File"
+}
\ No newline at end of file