diff --git a/README.md b/README.md deleted file mode 100644 index 9091b14..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# MINOTES - diff --git a/src/data/Notes.java b/src/data/Notes.java index e2df597..2a5b37a 100644 --- a/src/data/Notes.java +++ b/src/data/Notes.java @@ -19,324 +19,324 @@ package net.micode.notes.data; import android.net.Uri; public class Notes { // 定义类,给模块的其他类提供变量定义 - public static final String AUTHORITY = "micode_notes"; // 设置AUTHORITY - public static final String TAG = "Notes"; // 设置TAG - public static final int TYPE_NOTE = 0; - public static final int TYPE_FOLDER = 1; - public static final int TYPE_SYSTEM = 2; - - /** - * Following IDs are system folders' identifiers - * {@link Notes#ID_ROOT_FOLDER } is default folder - * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder - * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records - */ - public static final int ID_ROOT_FOLDER = 0; - public static final int ID_TEMPARAY_FOLDER = -1; - public static final int ID_CALL_RECORD_FOLDER = -2; - public static final int ID_TRASH_FOLER = -3; - - public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; - public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; - public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; - public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; - public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; - public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; - - public static final int TYPE_WIDGET_INVALIDE = -1; - public static final int TYPE_WIDGET_2X = 0; - public static final int TYPE_WIDGET_4X = 1; - - public static class DataConstants { // 获取记录 - public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; - public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; - } - - /** - * Uri to query all notes and folders - */ - public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");// 查询所有笔记与文件的uri - - /** - * Uri to query data - */ - public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");// 查询数据的uri - - public interface NoteColumns { // 定义便签变量接口 - /** - * The unique ID for a row - *

- * Type: INTEGER (long) - *

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

- * Type: INTEGER (long) - *

- */ - public static final String PARENT_ID = "parent_id"; // 父文件夹的id - - /** - * Created data for note or folder - *

- * Type: INTEGER (long) - *

- */ - public static final String CREATED_DATE = "created_date"; // 表示文件夹或便签创建日期 - - /** - * Latest modified date - *

- * Type: INTEGER (long) - *

- */ - public static final String MODIFIED_DATE = "modified_date"; // 便签的最新的修改日期 - - /** - * Alert date - *

- * Type: INTEGER (long) - *

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

- * Type: TEXT - *

- */ - public static final String SNIPPET = "snippet"; // 文件夹名称或便签内容 - - /** - * Note's widget id - *

- * Type: INTEGER (long) - *

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

- * Type: INTEGER (long) - *

- */ - public static final String WIDGET_TYPE = "widget_type"; // 便签widget的类型 - - /** - * Note's background color's id - *

- * Type: INTEGER (long) - *

- */ - public static final String BG_COLOR_ID = "bg_color_id"; // 背景颜色代号 - - /** - * For text note, it doesn't has attachment, for multi-media - * note, it has at least one attachment - *

- * Type: INTEGER - *

- */ - public static final String HAS_ATTACHMENT = "has_attachment"; - - /** - * Folder's count of notes - *

- * Type: INTEGER (long) - *

- */ - public static final String NOTES_COUNT = "notes_count"; // 文件夹内的便签数 - - /** - * The file type: folder or note - *

- * Type: INTEGER - *

- */ - public static final String TYPE = "type"; // 文件类型,是文件夹或便签 - - /** - * The last sync id - *

- * Type: INTEGER (long) - *

- */ - public static final String SYNC_ID = "sync_id"; // 最后一次同步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 - *

- * Type : INTEGER - *

- */ - public static final String ORIGIN_PARENT_ID = "origin_parent_id"; // 移动文件时文件的前父类文件夹 - - /** - * The gtask id - *

- * Type : TEXT - *

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

- * Type : INTEGER (long) - *

- */ - public static final String VERSION = "version"; // 版本名称 - } - - public interface DataColumns { // 定义数据变量接口 - /** - * 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. - *

- * Type: Text - *

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

- * Type: INTEGER (long) - *

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

- * Type: INTEGER (long) - *

- */ - public static final String CREATED_DATE = "created_date"; // 创建文件夹或者便签的名称 - - /** - * Latest modified date - *

- * Type: INTEGER (long) - *

- */ - public static final String MODIFIED_DATE = "modified_date"; // 最后修改日期 - - /** - * Data's content - *

- * Type: TEXT - *

- */ - public static final String CONTENT = "content"; // 便签内容数据 + public static final String AUTHORITY = "micode_notes"; // 设置AUTHORITY + public static final String TAG = "Notes"; // 设置TAG + public static final int TYPE_NOTE = 0; + public static final int TYPE_FOLDER = 1; + public static final int TYPE_SYSTEM = 2; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *

- * Type: INTEGER - *

+ * Following IDs are system folders' identifiers + * {@link Notes#ID_ROOT_FOLDER } is default folder + * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder + * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records */ - public static final String DATA1 = "data1"; // 不同类型的通用数据列,用于可能的存储与查找 + public static final int ID_ROOT_FOLDER = 0; + public static final int ID_TEMPARAY_FOLDER = -1; + public static final int ID_CALL_RECORD_FOLDER = -2; + public static final int ID_TRASH_FOLER = -3; + + public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; + public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; + public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; + public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; + public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; + public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; + + public static final int TYPE_WIDGET_INVALIDE = -1; + public static final int TYPE_WIDGET_2X = 0; + public static final int TYPE_WIDGET_4X = 1; + + public static class DataConstants { // 获取记录 + public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; + public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; + } /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *

- * Type: INTEGER - *

+ * Uri to query all notes and folders */ - public static final String DATA2 = "data2"; + public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");// 查询所有笔记与文件的uri /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

- * Type: TEXT - *

+ * Uri to query data */ - 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"; - } - - 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 - *

- * //1为列表检查模式,0为普通模式 - */ - public static final String MODE = DATA1; // 模式 - - public static final int MODE_CHECK_LIST = 1; // 列表检查模式 - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; - - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");// 访问数据表uri - } - - public static final class CallNote implements DataColumns { - /** - * Call date for this record - *

- * Type: INTEGER (long) - *

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

- * Type: TEXT - *

- */ - public static final String PHONE_NUMBER = DATA3; // 呼叫号码 - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; - - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");// 访问电话记录uri - } + public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");// 查询数据的uri + + public interface NoteColumns { // 定义便签变量接口 + /** + * The unique ID for a row + *

+ * Type: INTEGER (long) + *

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

+ * Type: INTEGER (long) + *

+ */ + public static final String PARENT_ID = "parent_id"; // 父文件夹的id + + /** + * Created data for note or folder + *

+ * Type: INTEGER (long) + *

+ */ + public static final String CREATED_DATE = "created_date"; // 表示文件夹或便签创建日期 + + /** + * Latest modified date + *

+ * Type: INTEGER (long) + *

+ */ + public static final String MODIFIED_DATE = "modified_date"; // 便签的最新的修改日期 + + /** + * Alert date + *

+ * Type: INTEGER (long) + *

+ */ + public static final String ALERTED_DATE = "alert_date"; // 提醒日期 + + /** + * Folder's name or text content of note + *

+ * Type: TEXT + *

+ */ + public static final String SNIPPET = "snippet"; // 文件夹名称或便签内容 + + /** + * Note's widget id + *

+ * Type: INTEGER (long) + *

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

+ * Type: INTEGER (long) + *

+ */ + public static final String WIDGET_TYPE = "widget_type"; // 便签widget的类型 + + /** + * Note's background color's id + *

+ * Type: INTEGER (long) + *

+ */ + public static final String BG_COLOR_ID = "bg_color_id"; // 背景颜色代号 + + /** + * For text note, it doesn't has attachment, for multi-media + * note, it has at least one attachment + *

+ * Type: INTEGER + *

+ */ + public static final String HAS_ATTACHMENT = "has_attachment"; + + /** + * Folder's count of notes + *

+ * Type: INTEGER (long) + *

+ */ + public static final String NOTES_COUNT = "notes_count"; // 文件夹内的便签数 + + /** + * The file type: folder or note + *

+ * Type: INTEGER + *

+ */ + public static final String TYPE = "type"; // 文件类型,是文件夹或便签 + + /** + * The last sync id + *

+ * Type: INTEGER (long) + *

+ */ + public static final String SYNC_ID = "sync_id"; // 最后一次同步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 + *

+ * Type : INTEGER + *

+ */ + public static final String ORIGIN_PARENT_ID = "origin_parent_id"; // 移动文件时文件的前父类文件夹 + + /** + * The gtask id + *

+ * Type : TEXT + *

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

+ * Type : INTEGER (long) + *

+ */ + public static final String VERSION = "version"; // 版本名称 + } + + public interface DataColumns { // 定义数据变量接口 + /** + * 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. + *

+ * Type: Text + *

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

+ * Type: INTEGER (long) + *

+ */ + public static final String NOTE_ID = "note_id"; // 便签名称 + + /** + * Created data for note or folder + *

+ * Type: INTEGER (long) + *

+ */ + public static final String CREATED_DATE = "created_date"; // 创建文件夹或者便签的名称 + + /** + * Latest modified date + *

+ * Type: INTEGER (long) + *

+ */ + public static final String MODIFIED_DATE = "modified_date"; // 最后修改日期 + + /** + * Data's content + *

+ * Type: TEXT + *

+ */ + public static final String CONTENT = "content"; // 便签内容数据 + + /** + * 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"; + } + + 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 + *

+ * //1为列表检查模式,0为普通模式 + */ + public static final String MODE = DATA1; // 模式 + + public static final int MODE_CHECK_LIST = 1; // 列表检查模式 + + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; + + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; + + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");// 访问数据表uri + } + + public static final class CallNote implements DataColumns { + /** + * Call date for this record + *

+ * Type: INTEGER (long) + *

+ */ + public static final String CALL_DATE = DATA1; // 呼叫数据 + + /** + * Phone number for this record + *

+ * Type: TEXT + *

+ */ + public static final String PHONE_NUMBER = DATA3; // 呼叫号码 + + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; + + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; + + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");// 访问电话记录uri + } } diff --git a/src/gtask/data/MetaData.java b/src/gtask/data/MetaData.java index 3a2050b..c4683e4 100644 --- a/src/gtask/data/MetaData.java +++ b/src/gtask/data/MetaData.java @@ -24,31 +24,33 @@ 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(); + // 通过getSimpleName()获得类的gid并存入字符串中 + 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) { + // 生成无法创建关联gid的日志 Log.e(TAG, "failed to put related gid"); } setNotes(metaInfo.toString()); setName(GTaskStringUtils.META_NOTE_NAME); } - + // 获取关联的gid public String getRelatedGid() { return mRelatedGid; } - + // 判断数据是否为空是否值得保存 @Override public boolean isWorthSaving() { return getNotes() != null; } - + // 使用远程json设置元数据内容 @Override public void setContentByRemoteJSON(JSONObject js) { super.setContentByRemoteJSON(js); @@ -62,18 +64,18 @@ public class MetaData extends Task { } } } - + // 使用本地json对象设置数据内容,该方法不应该被调用,若调用则抛出异常 @Override public void setContentByLocalJSON(JSONObject js) { // this function should not be called throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); } - + // 从元数据中获取本地json对象,该方法不应该被调用,若调用则抛出异常 @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"); diff --git a/src/gtask/data/Node.java b/src/gtask/data/Node.java index 63950e0..1484c14 100644 --- a/src/gtask/data/Node.java +++ b/src/gtask/data/Node.java @@ -19,32 +19,33 @@ 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; public Node() { diff --git a/src/gtask/data/SqlData.java b/src/gtask/data/SqlData.java index d3ec3be..72ba013 100644 --- a/src/gtask/data/SqlData.java +++ b/src/gtask/data/SqlData.java @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +// Description:用于支持小米便签最底层的数据库相关操作,和sqlnote的关系上是子集关系,即data是note的子集(节点)。 +// SqlData其实就是也就是所谓数据中的数据 package net.micode.notes.gtask.data; import android.content.ContentResolver; @@ -34,17 +35,17 @@ import net.micode.notes.gtask.exception.ActionFailureException; import org.json.JSONException; import org.json.JSONObject; - public class SqlData { + // 将得到的内容写入字符串tag中 private static final String TAG = SqlData.class.getSimpleName(); - + // 设置无效id为-99999 private static final int INVALID_ID = -99999; - + // 集合interface DataColumns中所有sf常量 public static final String[] PROJECTION_DATA = new String[] { DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, DataColumns.DATA3 }; - + // 为sql表编号 public static final int DATA_ID_COLUMN = 0; public static final int DATA_MIME_TYPE_COLUMN = 1; @@ -56,7 +57,7 @@ public class SqlData { public static final int DATA_CONTENT_DATA_3_COLUMN = 4; private ContentResolver mContentResolver; - + // 判断是否用content生成,真为true,假为false private boolean mIsCreate; private long mDataId; @@ -70,7 +71,7 @@ public class SqlData { private String mDataContentData3; private ContentValues mDiffDataValues; - + // 初始化数据库的构造函数 public SqlData(Context context) { mContentResolver = context.getContentResolver(); mIsCreate = true; @@ -81,14 +82,14 @@ public class SqlData { mDataContentData3 = ""; mDiffDataValues = new ContentValues(); } - + // 初始化数据库的构造函数 public SqlData(Context context, Cursor c) { mContentResolver = context.getContentResolver(); mIsCreate = false; loadFromCursor(c); mDiffDataValues = new ContentValues(); } - + // 从游标处加载数据 private void loadFromCursor(Cursor c) { mDataId = c.getLong(DATA_ID_COLUMN); mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); @@ -96,7 +97,7 @@ public class SqlData { mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); } - + // 设置用于共享的数据 public void setContent(JSONObject js) throws JSONException { long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; if (mIsCreate || mDataId != dataId) { @@ -129,7 +130,7 @@ public class SqlData { } mDataContentData3 = dataContentData3; } - + // 获取共享数据的内容 public JSONObject getContent() throws JSONException { if (mIsCreate) { Log.e(TAG, "it seems that we haven't created this in database yet"); @@ -143,7 +144,7 @@ public class SqlData { js.put(DataColumns.DATA3, mDataContentData3); return js; } - + // 提交更改至数据库 public void commit(long noteId, boolean validateVersion, long version) { if (mIsCreate) { @@ -182,7 +183,7 @@ public class SqlData { mDiffDataValues.clear(); mIsCreate = false; } - + // 获取当前id public long getId() { return mDataId; } diff --git a/src/gtask/data/SqlNote.java b/src/gtask/data/SqlNote.java index 79a4095..be42a61 100644 --- a/src/gtask/data/SqlNote.java +++ b/src/gtask/data/SqlNote.java @@ -37,12 +37,15 @@ import org.json.JSONObject; import java.util.ArrayList; +// 用于支持小米便签最底层的数据库相关操作,和sqldata的关系上是父集关系,即note是data的子父集 +// 和SqlData相比SqlNote算是真正意义上的数据 public class SqlNote { + // 得到的类名写入tag中 private static final String TAG = SqlNote.class.getSimpleName(); private static final int INVALID_ID = -99999; - + // 集合了interface NoteColumns中所有SF常量 public static final String[] PROJECTION_NOTE = new String[] { NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID, NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE, @@ -121,7 +124,7 @@ public class SqlNote { private ContentValues mDiffNoteValues; private ArrayList mDataList; - + // 构造函数,初始化context参数 public SqlNote(Context context) { mContext = context; mContentResolver = context.getContentResolver(); @@ -142,7 +145,7 @@ public class SqlNote { mDiffNoteValues = new ContentValues(); mDataList = new ArrayList(); } - + // 通过游标初始化context public SqlNote(Context context, Cursor c) { mContext = context; mContentResolver = context.getContentResolver(); @@ -165,7 +168,7 @@ public class SqlNote { mDiffNoteValues = new ContentValues(); } - + // 从游标加载数据 private void loadFromCursor(long id) { Cursor c = null; try { @@ -184,7 +187,7 @@ public class SqlNote { c.close(); } } - + // 从游标加载数据 private void loadFromCursor(Cursor c) { mId = c.getLong(ID_COLUMN); mAlertDate = c.getLong(ALERTED_DATE_COLUMN); @@ -199,7 +202,7 @@ public class SqlNote { mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); mVersion = c.getLong(VERSION_COLUMN); } - + // 获取共享数据并加载到数据库当前游标处 private void loadDataContent() { Cursor c = null; mDataList.clear(); @@ -225,7 +228,7 @@ public class SqlNote { c.close(); } } - + // 设置通过content机制用于共享的数据信息 public boolean setContent(JSONObject js) { try { JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); @@ -358,7 +361,7 @@ public class SqlNote { } return true; } - + // 获取content机制提供的数据并加载到note中 public JSONObject getContent() { try { JSONObject js = new JSONObject(); @@ -406,40 +409,40 @@ public class SqlNote { } return null; } - + //给当前id设置父id public void setParentId(long id) { mParentId = id; mDiffNoteValues.put(NoteColumns.PARENT_ID, id); } - + // 给当前id设置Gtaskid public void setGtaskId(String gid) { mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); } - + // 给当前id设置同步id public void setSyncId(long syncId) { mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); } - + // 初始化本地修改,即撤销 public void resetLocalModified() { mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); } - + // 获得当前id public long getId() { return mId; } - + // 获得id的父id 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)) { diff --git a/src/gtask/data/Task.java b/src/gtask/data/Task.java index 6a19454..58097db 100644 --- a/src/gtask/data/Task.java +++ b/src/gtask/data/Task.java @@ -34,17 +34,17 @@ import org.json.JSONObject; public class Task extends Node { private static final String TAG = Task.class.getSimpleName(); - + // 完成判断 private boolean mCompleted; private String mNotes; - + // 实例化存储的数据类型 private JSONObject mMetaInfo; - + // 兄弟任务的指针 private Task mPriorSibling; - + // 父类指针 private TaskList mParent; - + // 初始化构造方法 public Task() { super(); mCompleted = false; @@ -53,7 +53,7 @@ public class Task extends Node { mParent = null; mMetaInfo = null; } - + // 用于创建jsonobject对象,获取创建新行为的操作 public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); @@ -102,7 +102,7 @@ public class Task extends Node { return js; } - + // 用于创建jsonobject对象,实现创建更新行为的操作 public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); @@ -134,7 +134,7 @@ public class Task extends Node { return js; } - + // 用于从远端目录创建 public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { @@ -174,7 +174,7 @@ public class Task extends Node { } } } - + // 本地创建 public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) || !js.has(GTaskStringUtils.META_HEAD_DATA)) { @@ -203,7 +203,7 @@ public class Task extends Node { e.printStackTrace(); } } - + // 从目录获取本地json public JSONObject getLocalJSONFromContent() { String name = getName(); try { @@ -246,7 +246,7 @@ public class Task extends Node { return null; } } - + // 设置meta信息 public void setMetaInfo(MetaData metaData) { if (metaData != null && metaData.getNotes() != null) { try { @@ -257,7 +257,7 @@ public class Task extends Node { } } } - + // 获取同步行为 public int getSyncAction(Cursor c) { try { JSONObject noteInfo = null; diff --git a/src/gtask/data/TaskList.java b/src/gtask/data/TaskList.java index 4ea21c5..5f48d1d 100644 --- a/src/gtask/data/TaskList.java +++ b/src/gtask/data/TaskList.java @@ -32,9 +32,9 @@ import java.util.ArrayList; public class TaskList extends Node { private static final String TAG = TaskList.class.getSimpleName(); - + // 当前TaskList的指针 private int mIndex; - + // 类中主要的保存数据的单元,用来实现一个以Task为元素的ArrayList private ArrayList mChildren; public TaskList() { @@ -42,7 +42,7 @@ public class TaskList extends Node { mChildren = new ArrayList(); mIndex = 1; } - + // 生成并返回一个包含了一定数据的JSONObject实体 public JSONObject getCreateAction(int actionId) { JSONObject js = new JSONObject(); @@ -73,7 +73,7 @@ public class TaskList extends Node { return js; } - + // 更新一个包含了一定数据的JSONObject实体 public JSONObject getUpdateAction(int actionId) { JSONObject js = new JSONObject(); @@ -215,11 +215,11 @@ public class TaskList extends Node { return SYNC_ACTION_ERROR; } - + // 获得TaskList的大小 public int getChildTaskCount() { return mChildren.size(); } - + // 在当前任务表末尾添加新的任务 public boolean addChildTask(Task task) { boolean ret = false; if (task != null && !mChildren.contains(task)) { @@ -233,7 +233,7 @@ public class TaskList extends Node { } return ret; } - + // 在当前任务表的指定位置添加新的任务 public boolean addChildTask(Task task, int index) { if (index < 0 || index > mChildren.size()) { Log.e(TAG, "add child task: invalid index"); @@ -259,7 +259,7 @@ public class TaskList extends Node { return true; } - + // 删除TaskList中的一个Task public boolean removeChildTask(Task task) { boolean ret = false; int index = mChildren.indexOf(task); @@ -280,7 +280,7 @@ public class TaskList extends Node { } return ret; } - + // 移动TaskList中的一个Task public boolean moveChildTask(Task task, int index) { if (index < 0 || index >= mChildren.size()) { @@ -298,7 +298,7 @@ public class TaskList extends Node { return true; return (removeChildTask(task) && addChildTask(task, index)); } - + // 按gid寻找Task public Task findChildTaskByGid(String gid) { for (int i = 0; i < mChildren.size(); i++) { Task t = mChildren.get(i); @@ -308,11 +308,11 @@ public class TaskList extends Node { } return null; } - + // 返回指定Task的index public int getChildTaskIndex(Task task) { return mChildren.indexOf(task); } - + // 返回指定index的Task public Task getChildTaskByIndex(int index) { if (index < 0 || index >= mChildren.size()) { Log.e(TAG, "getTaskByIndex: invalid index"); @@ -320,7 +320,7 @@ public class TaskList extends Node { } return mChildren.get(index); } - + // 返回指定gid的task public Task getChilTaskByGid(String gid) { for (Task task : mChildren) { if (task.getGid().equals(gid)) diff --git a/src/gtask/exception/ActionFailureException.java b/src/gtask/exception/ActionFailureException.java index 15504be..7787d25 100644 --- a/src/gtask/exception/ActionFailureException.java +++ b/src/gtask/exception/ActionFailureException.java @@ -14,15 +14,17 @@ * limitations under the License. */ +// 小米便签运行过程中的运行异常处理 package net.micode.notes.gtask.exception; public class ActionFailureException extends RuntimeException { + // 版本控制用,反序列化版本升级时仍保持对象唯一性 private static final long serialVersionUID = 4425249765923293627L; + // 通过super()来引用父类成分 public ActionFailureException() { super(); } - public ActionFailureException(String paramString) { super(paramString); } diff --git a/src/gtask/exception/NetworkFailureException.java b/src/gtask/exception/NetworkFailureException.java index b08cfb1..6f67afe 100644 --- a/src/gtask/exception/NetworkFailureException.java +++ b/src/gtask/exception/NetworkFailureException.java @@ -14,11 +14,14 @@ * limitations under the License. */ +// 运行过程中的网络异常处理 package net.micode.notes.gtask.exception; public class NetworkFailureException extends Exception { + // 版本控制用,反序列化版本升级时仍保持对象唯一性 private static final long serialVersionUID = 2107610287180234136L; + // 通过super()来引用父类成分 public NetworkFailureException() { super(); } diff --git a/src/gtask/remote/GTaskASyncTask.java b/src/gtask/remote/GTaskASyncTask.java index b3b61e7..7981605 100644 --- a/src/gtask/remote/GTaskASyncTask.java +++ b/src/gtask/remote/GTaskASyncTask.java @@ -15,6 +15,14 @@ * limitations under the License. */ +/* + *异步操作类,实现Gtask异步操作过程 + *主要方法: + *private void showNotification(int tickerId, String content) 用于为用户同步当前事件状态 + *protected Integer doInBackground(Void... unused) 后台线程执行,完成任务主要工作 + *protected void onProgressUpdate(String... progress) 主线程运行,以进度条显示用户工作完成状态 + *protected void onPostExecute(Integer result) 更新UI + */ package net.micode.notes.gtask.remote; import android.app.Notification; @@ -44,7 +52,7 @@ public class GTaskASyncTask extends AsyncTask { private GTaskManager mTaskManager; private OnCompleteListener mOnCompleteListener; - + // GTask的一个同步进程类,包含上下文信息,事件完成进度监听方法,通知管理方法,和进程管理方法 public GTaskASyncTask(Context context, OnCompleteListener listener) { mContext = context; mOnCompleteListener = listener; @@ -52,56 +60,70 @@ public class GTaskASyncTask extends AsyncTask { .getSystemService(Context.NOTIFICATION_SERVICE); mTaskManager = GTaskManager.getInstance(); } - + //取消同步 public void cancelSync() { mTaskManager.cancelSync(); } - + // 发布并更新事件处理进程 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); - + // 同步成功显示活动信息列表 } else { + pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesListActivity.class), 0); } - notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, - pendingIntent); + // 该方法疑似为设置显示最新事件信息,但运行时编译器报错,暂时注释以规避报错选项 + /*notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, + pendingIntent);*/ + // mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); } @Override protected Integer doInBackground(Void... unused) { + //利用getString将NotesPreferenceActivity.getSyncAccountName(mContext)的字符串内容 + //传进sync_progress_login中 publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity .getSyncAccountName(mContext))); + //返回后台同步的具体操作 return mTaskManager.sync(mContext, this); } @Override protected void onProgressUpdate(String... progress) { showNotification(R.string.ticker_syncing, progress[0]); + //判断mContext是否为GTaskSyncService的实例 if (mContext instanceof GTaskSyncService) { ((GTaskSyncService) mContext).sendBroadcast(progress[0]); } } @Override + //后台运行完后更新ui,显示更新后结果 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)); @@ -111,9 +133,11 @@ public class GTaskASyncTask extends AsyncTask { showNotification(R.string.ticker_cancel, mContext .getString(R.string.error_sync_cancelled)); } + //不同状态下的结果显示 if (mOnCompleteListener != null) { + //新增可运行线程 new Thread(new Runnable() { - + //线程运行,初始化操作 public void run() { mOnCompleteListener.onComplete(); } diff --git a/src/gtask/remote/GTaskClient.java b/src/gtask/remote/GTaskClient.java index c67dfdf..fee4803 100644 --- a/src/gtask/remote/GTaskClient.java +++ b/src/gtask/remote/GTaskClient.java @@ -60,10 +60,13 @@ import java.util.zip.GZIPInputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; - +/* + *实现GTask登录操作,创建GTask任务,与谷歌服务联网获取任务和任务列表 + *主要用类:accountManager JSONObject HttpParams authToken Gid + */ public class GTaskClient { private static final String TAG = GTaskClient.class.getSimpleName(); - + //指定登录URL地址 private static final String GTASK_URL = "https://mail.google.com/tasks/"; private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; @@ -89,7 +92,7 @@ public class GTaskClient { private Account mAccount; private JSONArray mUpdateArray; - + // 创建GTask构造方法 private GTaskClient() { mHttpClient = null; mGetUrl = GTASK_GET_URL; @@ -101,35 +104,38 @@ public class GTaskClient { mAccount = null; mUpdateArray = null; } - + // 实例化并锁定GTaskClient,使用getInstance()返回mInstance public static synchronized GTaskClient getInstance() { if (mInstance == null) { mInstance = new GTaskClient(); } return mInstance; } - + // 用于实现登录操作的布尔方法,提供了账号URL密码登录和谷歌官方邮箱URL登录两种登陆方法 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(); + // 返回的Token为空则登陆失败 String authToken = loginGoogleAccount(activity, false); if (authToken == null) { Log.e(TAG, "login google account failed"); @@ -137,6 +143,7 @@ public class GTaskClient { } // login with custom domain if necessary + // 必要时使用本地输入谷歌邮箱登录 if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() .endsWith("googlemail.com"))) { StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); @@ -152,6 +159,7 @@ public class GTaskClient { } // try to login with google official url + // 调取谷歌邮箱官方URL登录 if (!mLoggedin) { mGetUrl = GTASK_GET_URL; mPostUrl = GTASK_POST_URL; @@ -163,25 +171,30 @@ public class GTaskClient { mLoggedin = true; return true; } - + // 实现谷歌登录的方法,以AccountManager管理账号,Token登录 private String loginGoogleAccount(Activity activity, boolean invalidateToken) { + // 登录Token String authToken; + // 账号管理,提供账号注册接口 AccountManager accountManager = AccountManager.get(activity); + // 获取以com.google结尾的账号列表 Account[] accounts = accountManager.getAccountsByType("com.google"); - + // 如果账号长度为空则返回无可用账号 if (accounts.length == 0) { Log.e(TAG, "there is no available google account"); return null; } - + // 获取账户名称 String accountName = NotesPreferenceActivity.getSyncAccountName(activity); Account account = null; + // 遍历返回的账户信息,寻找记录的账户信息 for (Account a : accounts) { if (a.name.equals(accountName)) { account = a; break; } } + if (account != null) { mAccount = account; } else { @@ -190,11 +203,13 @@ public class GTaskClient { } // get the token now + // 取得登录Token AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); try { Bundle authTokenBundle = accountManagerFuture.getResult(); authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); + // 若Token无效,则通过invalidateAuthToken方法废除该Token if (invalidateToken) { accountManager.invalidateAuthToken("com.google", authToken); loginGoogleAccount(activity, false); @@ -206,11 +221,12 @@ public class GTaskClient { return authToken; } - + // 尝试登录的布尔方法需要预先判断Token是否有效 private boolean tryToLoginGtask(Activity activity, String authToken) { if (!loginGtask(authToken)) { // maybe the auth token is out of date, now let's invalidate the // token and try again + // 若Token则废弃Token且重试 authToken = loginGoogleAccount(activity, true); if (authToken == null) { Log.e(TAG, "login google account failed"); @@ -224,26 +240,35 @@ public class GTaskClient { } return true; } - + // 登录GTask具体操作 private boolean loginGtask(String authToken) { int timeoutConnection = 10000; + // socket为一种通信数据交换的端口 int timeoutSocket = 15000; + // 实现一个新的HTTP参数类 HttpParams httpParameters = new BasicHttpParams(); + // 设置链接超时时间 HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); + // 设置设置端口超时时间 HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); mHttpClient = new DefaultHttpClient(httpParameters); + // 存储新的本地cookie BasicCookieStore localBasicCookieStore = new BasicCookieStore(); mHttpClient.setCookieStore(localBasicCookieStore); HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); // login gtask + // 登录GTask具体操作 try { + // 设置登录URL String loginUrl = mGetUrl + "?auth=" + authToken; + // 通过已实例化URL网页中的资源查找 HttpGet httpGet = new HttpGet(loginUrl); HttpResponse response = null; response = mHttpClient.execute(httpGet); // get the cookie now + // 遍历已存储的cookie以验证是否与登录cookie相符,若相符则匹配 List cookies = mHttpClient.getCookieStore().getCookies(); boolean hasAuthCookie = false; for (Cookie cookie : cookies) { @@ -256,6 +281,7 @@ public class GTaskClient { } // get the client version + // 以脚本获取返回的Content中GTask_Url的内容 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -273,40 +299,47 @@ public class GTaskClient { return false; } catch (Exception e) { // simply catch all exceptions + // 仅捕获到异常则返回捕获失败 Log.e(TAG, "httpget gtask_url failed"); return false; } return true; } - + // 获取行为ID private int getActionId() { return mActionId++; } + // 实例化创建用于网络传输的对向,使用HTTPpost类来创建并返回一个空的的对向 private HttpPost createHttpPost() { HttpPost httpPost = new HttpPost(mPostUrl); httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); httpPost.setHeader("AT", "1"); return httpPost; } - + // 获取响应的资源目录 private String getResponseContent(HttpEntity entity) throws IOException { String contentEncoding = null; + // 荣国URL获得HttpEntity对象,若返回值不为空,则创建数据流获取返回对象 if (entity.getContentEncoding() != null) { contentEncoding = entity.getContentEncoding().getValue(); Log.d(TAG, "encoding: " + contentEncoding); } InputStream input = entity.getContent(); + // Gzip为使用DEFLATE压缩数据的另一个压缩库 if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { input = new GZIPInputStream(entity.getContent()); - } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { + } + // Deflate为一个无专利的无损数据压缩算法 + else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { Inflater inflater = new Inflater(true); input = new InflaterInputStream(entity.getContent(), inflater); } try { + // 包装类,用于提高读取的运行效率 InputStreamReader isr = new InputStreamReader(input); BufferedReader br = new BufferedReader(isr); StringBuilder sb = new StringBuilder(); @@ -322,7 +355,10 @@ public class GTaskClient { input.close(); } } - + /* + * 以json发送请求,请求的内容在json中的实例化对象传入,利用json获取task中内容 + * 并创捷对应jspost,利用postrequest得到返回的任务信息并用task_setGid设置task的新id + */ private JSONObject postRequest(JSONObject js) throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); @@ -360,6 +396,8 @@ public class GTaskClient { } } + // 创建单个任务,传入task类的对象、使用json获取task中的内容,并创建相应的jspost,利用postRequest获得任务返回信 + // 息并且使用task.setGid设置task的new_id public void createTask(Task task) throws NetworkFailureException { commitUpdate(); try { @@ -385,7 +423,7 @@ public class GTaskClient { throw new ActionFailureException("create task: handing jsonobject failed"); } } - + // 创建任务列表,设置tasklist_Gid public void createTaskList(TaskList tasklist) throws NetworkFailureException { commitUpdate(); try { @@ -411,7 +449,8 @@ public class GTaskClient { throw new ActionFailureException("create tasklist: handing jsonobject failed"); } } - + // 提交更新,使用JSONobject进行存储,使用jsPost.put,Put的信息包括UpdateArray和ClientVersion,使用 + // postRequest发送这个jspost,进行处理 public void commitUpdate() throws NetworkFailureException { if (mUpdateArray != null) { try { @@ -432,7 +471,7 @@ public class GTaskClient { } } } - + // 添加更新事项,通过调用 commitUpdate()实现 public void addUpdateNode(Node node) throws NetworkFailureException { if (node != null) { // too many update items may result in an error @@ -446,7 +485,9 @@ public class GTaskClient { mUpdateArray.put(node.getUpdateAction(getActionId())); } } - + // 移动task至不同的任务列表中去,通过getgid获取所属不同任务列表的gid,通过 + // JSONObject.put(String name, Object value)函数设置移动后的task的相关属性值,从而达到移动的目的 + // 最后通过postRequest进行更新后的任务列表的发送 public void moveTask(Task task, TaskList preParent, TaskList curParent) throws NetworkFailureException { commitUpdate(); @@ -463,6 +504,7 @@ public class GTaskClient { if (preParent == curParent && task.getPriorSibling() != null) { // put prioring_sibing_id only if moving within the tasklist and // it is not the first one + //设置优先级ID,只有当移动是发生在文件中 action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); } action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); @@ -472,6 +514,7 @@ public class GTaskClient { action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); } actionList.put(action); + //最后将ACTION_LIST加入到jsPost中 jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // client_version @@ -486,6 +529,7 @@ public class GTaskClient { } } + //删除操作节点,利用JSON,删除后使用postRequest发送删除后的结果 public void deleteNode(Node node) throws NetworkFailureException { commitUpdate(); try { @@ -494,6 +538,7 @@ public class GTaskClient { // action_list node.setDeleted(true); + // 这里会获取到删除操作的ID,加入到actionLiast中 actionList.put(node.getUpdateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); @@ -508,7 +553,8 @@ public class GTaskClient { throw new ActionFailureException("delete node: handing jsonobject failed"); } } - + //获取任务列表,首先通过GetURL使用getResponseContent联网获取数据,然后筛选出"_setup("到)}的部分, + // 并且从中获取GTASK_JSON_LISTS的内容返回 public JSONArray getTaskLists() throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); @@ -521,6 +567,7 @@ public class GTaskClient { response = mHttpClient.execute(httpGet); // get the task list + // 获取任务列表并存储进jsString中 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -547,6 +594,7 @@ public class GTaskClient { } } + // 通过传入的TASKList的gid,从网络上获取相应属于这个任务列表的任务 public JSONArray getTaskList(String listGid) throws NetworkFailureException { commitUpdate(); try { @@ -558,6 +606,7 @@ public class GTaskClient { action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); + // 设置为传入的listGid action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); actionList.put(action); @@ -578,7 +627,7 @@ public class GTaskClient { public Account getSyncAccount() { return mAccount; } - + // 重装更新后的内容 public void resetUpdateArray() { mUpdateArray = null; } diff --git a/src/gtask/remote/GTaskManager.java b/src/gtask/remote/GTaskManager.java index d2b4082..d514ada 100644 --- a/src/gtask/remote/GTaskManager.java +++ b/src/gtask/remote/GTaskManager.java @@ -86,33 +86,44 @@ public class GTaskManager { private HashMap mGidToNid; private HashMap mNidToGid; - + // 初始化Google任务管理器的函数 private GTaskManager() { + // 初始化同步状态为未执行 mSyncing = false; + // 全局状态标识为可执行 mCancelled = false; + // java泛型类,用于创建一个用类作为参数的类 mGTaskListHashMap = new HashMap(); mGTaskHashMap = new HashMap(); mMetaHashMap = new HashMap(); mMetaList = null; mLocalDeleteIdMap = new HashSet(); + // 将Gid转换为Noteid通过哈希表建立映射 mGidToNid = new HashMap(); + // 将Noteid转换为Gid通过哈希表建立映射 mNidToGid = new HashMap(); } - + // synchronizedy语言级同步,用于表述该方法可能运行于多线程环境之下 + // 该方法用于初始化mInstance + // @return GTaskManager public static synchronized GTaskManager getInstance() { if (mInstance == null) { mInstance = new GTaskManager(); } return mInstance; } - + // synchronizedy语言级同步,用于表述该方法可能运行于多线程环境之下 public synchronized void setActivityContext(Activity activity) { // used for getting authtoken mActivity = activity; } - + // 用于本地化和远端同步的方法 + // @param context-----获取上下文 + // @param assyncTask-----用于同步的异步操作类 + // @return int public int sync(Context context, GTaskASyncTask asyncTask) { if (mSyncing) { + // 创建同步的日志文件 Log.d(TAG, "Sync is in progress"); return STATE_SYNC_IN_PROGRESS; } @@ -128,11 +139,14 @@ public class GTaskManager { mNidToGid.clear(); try { + // 创建用户机实例 GTaskClient client = GTaskClient.getInstance(); + // JSON类型,用于置空NULL client.resetUpdateArray(); // login google task if (!mCancelled) { + // 谷歌登录操作,若非则抛出登陆失败异常 if (!client.login(mActivity)) { throw new NetworkFailureException("login google task failed"); } @@ -140,14 +154,17 @@ public class GTaskManager { // get the task list from google asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); + // 将谷歌上获取的JSONTasklist转换为本地Tasklist initGTaskList(); // do content sync work asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); syncContent(); + // 抓取网络异常并创建调试日志抛出error } catch (NetworkFailureException e) { Log.e(TAG, e.toString()); return STATE_NETWORK_ERROR; + // 抓取操作异常并创建调试日志抛出error } catch (ActionFailureException e) { Log.e(TAG, e.toString()); return STATE_INTERNAL_ERROR; @@ -167,32 +184,44 @@ public class GTaskManager { return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; } - + // 初始化GTaskList,获取Google上JSONTaskList并转换为本地TaskList + // 将获取的数据储存在mMetaList,mGTaskListHashMap,MGTaskHashmap中 + // @exception NetworkFailureException + // @return void private void initGTaskList() throws NetworkFailureException { if (mCancelled) return; GTaskClient client = GTaskClient.getInstance(); try { + // Json对象是子元素的无序集合,相当于创建一个Map对象, + // bantouyan-json库对Json对象的抽象概念,提供操纵json对象的各种方法, + // 其格式为("key1": value1,"key2": value2...)key为字符串 + // ajax请求不刷新页面,因此配合js可实现局部刷新,所以json常用作异步请求返回对象使用 JSONArray jsTaskLists = client.getTaskLists(); // init meta list first + // TaskList类型 mMetaList = null; for (int i = 0; i < jsTaskLists.length(); i++) { + // JSONObject与JSONArray一个为对象,一个为数组。此处取出单个JSONObject JSONObject object = jsTaskLists.getJSONObject(i); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); - if (name - .equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + // MetaList为元表,此处为初始化TaskList mMetaList = new TaskList(); + // 由远端JSON获取对象信息 mMetaList.setContentByRemoteJSON(object); // load meta data + // 获取用户端的TaskList的gid JSONArray jsMetas = client.getTaskList(gid); for (int j = 0; j < jsMetas.length(); j++) { object = (JSONObject) jsMetas.getJSONObject(j); MetaData metaData = new MetaData(); metaData.setContentByRemoteJSON(object); + // 判断值是否值得存储,为否则不加入mMetaList if (metaData.isWorthSaving()) { mMetaList.addChildTask(metaData); if (metaData.getGid() != null) { @@ -214,18 +243,21 @@ public class GTaskManager { // init task list for (int i = 0; i < jsTaskLists.length(); i++) { JSONObject object = jsTaskLists.getJSONObject(i); + // 通过GetString传入本地某标志数据的名称,获取其在远端的名称 String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + // 由本地tasklist获取内容用于和远端同步 TaskList tasklist = new TaskList(); tasklist.setContentByRemoteJSON(object); mGTaskListHashMap.put(gid, tasklist); mGTaskHashMap.put(gid, tasklist); // load tasks + // 加载tasks JSONArray jsTasks = client.getTaskList(gid); for (int j = 0; j < jsTasks.length(); j++) { object = (JSONObject) jsTasks.getJSONObject(j); @@ -246,7 +278,8 @@ public class GTaskManager { throw new ActionFailureException("initGTaskList: handing JSONObject failed"); } } - + // 本地内容同步操作 + // @throw NetWorkFailureException private void syncContent() throws NetworkFailureException { int syncType; Cursor c = null; @@ -300,6 +333,7 @@ public class GTaskManager { gid = c.getString(SqlNote.GTASK_ID_COLUMN); node = mGTaskHashMap.get(gid); if (node != null) { + // 通过哈希表获取gid mGTaskHashMap.remove(gid); mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); @@ -350,7 +384,7 @@ public class GTaskManager { } } - + // 同步文件夹 private void syncFolder() throws NetworkFailureException { Cursor c = null; String gid; @@ -475,7 +509,7 @@ public class GTaskManager { if (!mCancelled) GTaskClient.getInstance().commitUpdate(); } - + // 为同步类型分类 private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -521,7 +555,7 @@ public class GTaskManager { throw new ActionFailureException("unkown sync action type"); } } - + // 本地增加新的Node private void addLocalNode(Node node) throws NetworkFailureException { if (mCancelled) { return; @@ -595,7 +629,7 @@ public class GTaskManager { // update meta updateRemoteMeta(node.getGid(), sqlNote); } - + // 更新本地Node private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -618,12 +652,12 @@ public class GTaskManager { // update meta info updateRemoteMeta(node.getGid(), sqlNote); } - + // 添加远端node private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; } - + // 从本地数据库中获取内容 SqlNote sqlNote = new SqlNote(mContext, c); Node n; @@ -637,8 +671,9 @@ public class GTaskManager { Log.e(TAG, "cannot find task's parent tasklist"); throw new ActionFailureException("cannot add remote task"); } + // 在本地生成的GTaskList中添加子节点 mGTaskListHashMap.get(parentGid).addChildTask(task); - + // 登录远端服务器创建task GTaskClient.getInstance().createTask(task); n = (Node) task; @@ -655,7 +690,7 @@ public class GTaskManager { folderName += GTaskStringUtils.FOLDER_CALL_NOTE; else folderName += sqlNote.getSnippet(); - + // 迭代器,通过统一接口迭代所有map元素 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -691,7 +726,7 @@ public class GTaskManager { mGidToNid.put(n.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), n.getGid()); } - + // 更新远端node private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -709,15 +744,17 @@ public class GTaskManager { // move task if necessary if (sqlNote.isNoteType()) { Task task = (Task) node; + // preParentList通过node获取的父节点列表 TaskList preParentList = task.getParent(); - + // curParentGid为通过光标在数据库中找到sqlNote的mParentId,再通过mNidToGid由long类型转为String类型的Gid String curParentGid = mNidToGid.get(sqlNote.getParentId()); if (curParentGid == null) { Log.e(TAG, "cannot find task's parent tasklist"); throw new ActionFailureException("cannot update remote task"); } + // 通过HashMap找到对应Gid的TaskList TaskList curParentList = mGTaskListHashMap.get(curParentGid); - + // 判断父上一个节点是否与本节点相符,用于更新操作 if (preParentList != curParentList) { preParentList.removeChildTask(task); curParentList.addChildTask(task); @@ -729,7 +766,7 @@ public class GTaskManager { sqlNote.resetLocalModified(); sqlNote.commit(true); } - + // 升级远程meta private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { if (sqlNote != null && sqlNote.isNoteType()) { MetaData metaData = mMetaHashMap.get(gid); @@ -745,7 +782,7 @@ public class GTaskManager { } } } - + // 刷新本地syncid以对应更改后的对象 private void refreshLocalSyncId() throws NetworkFailureException { if (mCancelled) { return; @@ -769,8 +806,10 @@ public class GTaskManager { Node node = mGTaskHashMap.get(gid); if (node != null) { mGTaskHashMap.remove(gid); + // 在ContentValues中创建键值对。准备通过contentResolver写入数据 ContentValues values = new ContentValues(); values.put(NoteColumns.SYNC_ID, node.getLastModified()); + // 进行批量更改,选择参数为NULL,应该可以用insert替换,参数分别为表名和需要更新的value对象。 mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(SqlNote.ID_COLUMN)), values, null, null); } else { diff --git a/src/gtask/remote/GTaskSyncService.java b/src/gtask/remote/GTaskSyncService.java index cca36f7..a23452b 100644 --- a/src/gtask/remote/GTaskSyncService.java +++ b/src/gtask/remote/GTaskSyncService.java @@ -16,6 +16,23 @@ package net.micode.notes.gtask.remote; +/* + * Service是在一段不定的时间运行在后台,不和用户交互的应用组件 + * 主要方法: + * private void startSync() 启动一个同步工作 + * private void cancelSync() 取消同步 + * public void onCreate() + * public int onStartCommand(Intent intent, int flags, int startId) service生命周期的组成部分,相当于重启service(比如在被暂停之后),而不是创建一个新的service + * public void onLowMemory() 在没有内存的情况下如果存在service则结束掉这的service + * public IBinder onBind() + * public void sendBroadcast(String msg) 发送同步的相关通知 + * public static void startSync(Activity activity) + * public static void cancelSync(Context context) + * public static boolean isSyncing() 判读是否在进行同步 + * public static String getProgressString() 获取当前进度的信息 + */ + + import android.app.Activity; import android.app.Service; import android.content.Context; @@ -41,7 +58,7 @@ public class GTaskSyncService extends Service { private static GTaskASyncTask mSyncTask = null; private static String mSyncProgress = ""; - + // 开始进行一个同的步 private void startSync() { if (mSyncTask == null) { mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { @@ -52,6 +69,7 @@ public class GTaskSyncService extends Service { } }); sendBroadcast(""); + // 该函数主打的是一个让任务以单线程队列或者线程池队列方式运行 mSyncTask.execute(); } } @@ -63,6 +81,7 @@ public class GTaskSyncService extends Service { } @Override + // 对Service进行一个初始化 public void onCreate() { mSyncTask = null; } @@ -71,6 +90,7 @@ public class GTaskSyncService extends Service { public int onStartCommand(Intent intent, int flags, int startId) { Bundle bundle = intent.getExtras(); if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { + // 对于开始同步和取消同步分情况表示 switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { case ACTION_START_SYNC: startSync(); @@ -92,26 +112,28 @@ public class GTaskSyncService extends Service { mSyncTask.cancelSync(); } } - + // 重制客户端的服务绑定 public IBinder onBind(Intent intent) { return null; } public void sendBroadcast(String msg) { mSyncProgress = msg; + // 创建新的新意图 Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); sendBroadcast(intent); } - + // 对同步进行一个开始 public static void startSync(Activity activity) { GTaskManager.getInstance().setActivityContext(activity); Intent intent = new Intent(activity, GTaskSyncService.class); intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); + // 发送通知 activity.startService(intent); } - + // 对同步进行一个取消 public static void cancelSync(Context context) { Intent intent = new Intent(context, GTaskSyncService.class); intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); @@ -125,4 +147,4 @@ public class GTaskSyncService extends Service { public static String getProgressString() { return mSyncProgress; } -} +} \ No newline at end of file diff --git a/src/ui/AlarmAlertActivity.java b/src/ui/AlarmAlertActivity.java index 85723be..75de484 100644 --- a/src/ui/AlarmAlertActivity.java +++ b/src/ui/AlarmAlertActivity.java @@ -41,30 +41,42 @@ import java.io.IOException; public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { - private long mNoteId; - private String mSnippet; - private static final int SNIPPET_PREW_MAX_LEN = 60; + private long mNoteId; //用于存储笔记Id + private String mSnippet; //用于存储笔记内容摘要 + private static final int SNIPPET_PREW_MAX_LEN = 60;//限制笔记长度 MediaPlayer mPlayer; + + //onCreate() 函数在 Activity 初始化时调用,用于初始化 + //savedInstanceState 该参数用于恢复 Activity 状态。 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + //界面显示:无标题 requestWindowFeature(Window.FEATURE_NO_TITLE); + //获取 Window 对象,添加 FLAG_SHOW_WHEN_LOCKED 标记 final Window win = getWindow(); win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + //如果屏幕没亮,添加一系列 屏幕唤醒标记 if (!isScreenOn()) { - win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON - | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON //保持窗口常亮 + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON //点亮窗口 + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON //允许在窗口点亮时锁屏 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); } + //获取Intent对象 Intent intent = getIntent(); + //从Intent对象中获取笔记的 Id 和内容 try { mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + + //根据便签Id从数据库中获取便签内容 + //getContentResolver() 实现数据共享,实例存储 mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) @@ -74,6 +86,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD return; } + //创建 MediaPlayer 对象,根据笔记是否存在数据库中来展示操作对话框或者直接退出 mPlayer = new MediaPlayer(); if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { showActionDialog(); @@ -83,29 +96,38 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + //用于判断屏幕是否被唤醒(亮着) private boolean isScreenOn() { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); return pm.isScreenOn(); } + //用于实现报警铃声的播放 private void playAlarmSound() { + //获取系统默认的铃声 Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + //获取静音模式下允许响铃的类型 int silentModeStreams = Settings.System.getInt(getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + //判断闹钟铃声是否被静音模式影响 + //若被影响则设置播放音频类型为允许响铃的类型 + //若违背影响则设置音频为默认类型 if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { mPlayer.setAudioStreamType(silentModeStreams); } else { mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); } try { - mPlayer.setDataSource(this, url); - mPlayer.prepare(); - mPlayer.setLooping(true); - mPlayer.start(); + mPlayer.setDataSource(this, url); //设置报警铃声 + mPlayer.prepare(); //准备同步 + mPlayer.setLooping(true); //设置循环播放 + mPlayer.start(); //开始播放 } catch (IllegalArgumentException e) { // TODO Auto-generated catch block + + //以下的 e.printStackTrace() 函数功能是抛出异常,还将显示出更深的调用信息 e.printStackTrace(); } catch (SecurityException e) { // TODO Auto-generated catch block @@ -119,32 +141,52 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + + //创建一个对话框,标题为app的名字,内容为mSnippet,确定按钮为notealert_ok, + // 当屏幕开启时,取消按钮为notealert_enter,点击弹出框外部可以隐藏弹出框 private void showActionDialog() { AlertDialog.Builder dialog = new AlertDialog.Builder(this); + //为对话框设置标题 dialog.setTitle(R.string.app_name); + //为对话框设置内容 dialog.setMessage(mSnippet); + //给对话框添加“OK”按钮 dialog.setPositiveButton(R.string.notealert_ok, this); + if (isScreenOn()) { + //给对话框添加“CANCEL”按钮 dialog.setNegativeButton(R.string.notealert_enter, this); } + //给对话框设置监听器 dialog.show().setOnDismissListener(this); } + //当对话框界面内某个按钮被点击时调用以下方法 + // 接收 对话框界面 和 点击的按钮位置which 为参数 public void onClick(DialogInterface dialog, int which) { + //switch语句,根据按钮位置的不同,执行不同的操作 switch (which) { + //当点击点击的是返回按键 case DialogInterface.BUTTON_NEGATIVE: + //将编辑的便签内容传输到特定类中 Intent intent = new Intent(this, NoteEditActivity.class); + //设置操作属性 intent.setAction(Intent.ACTION_VIEW); + //创建一个特定的Note Id传输给便签 intent.putExtra(Intent.EXTRA_UID, mNoteId); + //开始操作 startActivity(intent); break; + //如果是其他按钮被点击则不执行任何操作 default: break; } } public void onDismiss(DialogInterface dialog) { + //终止报警声音 stopAlarmSound(); + //完成动作 finish(); } @@ -152,6 +194,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD if (mPlayer != null) { mPlayer.stop(); mPlayer.release(); + //释放 MediaPlayer(媒体播放器)对象 mPlayer = null; } } diff --git a/src/ui/AlarmInitReceiver.java b/src/ui/AlarmInitReceiver.java index f221202..edd2b85 100644 --- a/src/ui/AlarmInitReceiver.java +++ b/src/ui/AlarmInitReceiver.java @@ -28,8 +28,10 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; +//定时向用户推送闹钟提醒 public class AlarmInitReceiver extends BroadcastReceiver { + //对数据库的操作,调用笔记Id和时钟时间 private static final String [] PROJECTION = new String [] { NoteColumns.ID, NoteColumns.ALERTED_DATE @@ -38,16 +40,23 @@ public class AlarmInitReceiver extends BroadcastReceiver { private static final int COLUMN_ID = 0; private static final int COLUMN_ALERTED_DATE = 1; + //重写 BroadcastReceiver 的 onReceive 方法 @Override public void onReceive(Context context, Intent intent) { + + //获取当前系统时间 long currentDate = System.currentTimeMillis(); + + //查询数据库,找出大于当前系统时间、类型相等的笔记的记录 Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, new String[] { String.valueOf(currentDate) }, null); + //当查询结果不为空时 if (c != null) { + // 如果有符合要求的记录,就遍历每一条记录 if (c.moveToFirst()) { do { long alertDate = c.getLong(COLUMN_ALERTED_DATE); diff --git a/src/ui/AlarmReceiver.java b/src/ui/AlarmReceiver.java index 54e503b..1b4d096 100644 --- a/src/ui/AlarmReceiver.java +++ b/src/ui/AlarmReceiver.java @@ -24,6 +24,8 @@ public class AlarmReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { intent.setClass(context, AlarmAlertActivity.class); + + //为intent对象添加一个标志,表示它将启动一个新的任务栈 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } diff --git a/src/ui/DateTimePicker.java b/src/ui/DateTimePicker.java index 496b0cd..ab88e69 100644 --- a/src/ui/DateTimePicker.java +++ b/src/ui/DateTimePicker.java @@ -30,6 +30,7 @@ import android.widget.NumberPicker; public class DateTimePicker extends FrameLayout { + //初始化控件 private static final boolean DEFAULT_ENABLE_STATE = true; private static final int HOURS_IN_HALF_DAY = 12; @@ -46,11 +47,12 @@ public class DateTimePicker extends FrameLayout { private static final int AMPM_SPINNER_MIN_VAL = 0; private static final int AMPM_SPINNER_MAX_VAL = 1; + //设置闹钟的变量 private final NumberPicker mDateSpinner; private final NumberPicker mHourSpinner; private final NumberPicker mMinuteSpinner; private final NumberPicker mAmPmSpinner; - private Calendar mDate; + private Calendar mDate; //用于操作时间 private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; @@ -64,7 +66,20 @@ public class DateTimePicker extends FrameLayout { private OnDateTimeChangedListener mOnDateTimeChangedListener; + /* + 设置时间选择器的监听器, + 包括了日期选择器的监听器 mOnDateChangedListener, + 小时选择器的监听器 mOnHourChangedListener, + 分钟选择器的监听器mOnMinuteChangedListener, + 上午和下午选择器的监听器 mOnAmPmChangedListener + + */ + + + //日期选择器的监听器 private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { + + //将目前的日期传给 mDate,updateDateControl是同步操作 @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); @@ -73,39 +88,58 @@ public class DateTimePicker extends FrameLayout { } }; + //选择器的监听器 private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { + @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { boolean isDateChanged = false; Calendar cal = Calendar.getInstance(); if (!mIs24HourView) { + + //这里是对于12小时制时,晚上11点和12点交替时对日期的更改 if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, 1); isDateChanged = true; - } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + } + + //这里是对于12小时制时,凌晨11点和12点交替时对日期的更改 + else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, -1); isDateChanged = true; } + + //这里是对于12小时制时,中午11点和12点交替时对AM和PM的更改 if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { mIsAm = !mIsAm; updateAmPmControl(); } } else { + + //这里是对于24小时制时,晚上11点和12点交替时对日期的更改 if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, 1); isDateChanged = true; - } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { + } + + //这里是对于24小时制时,凌晨11点和12点交替时对日期的更改 + else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, -1); isDateChanged = true; } } + + //用数字选择器给 newHour 赋值 int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); + + //将新的Hour值传给mDate mDate.set(Calendar.HOUR_OF_DAY, newHour); + onDateTimeChanged(); if (isDateChanged) { setCurrentYear(cal.get(Calendar.YEAR)); @@ -115,12 +149,13 @@ public class DateTimePicker extends FrameLayout { } }; + //分钟选择器的监听器 private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { int minValue = mMinuteSpinner.getMinValue(); int maxValue = mMinuteSpinner.getMaxValue(); - int offset = 0; + int offset = 0; //设置一个小时改变的记录数据 if (oldVal == maxValue && newVal == minValue) { offset += 1; } else if (oldVal == minValue && newVal == maxValue) { @@ -144,6 +179,7 @@ public class DateTimePicker extends FrameLayout { } }; + // 上午和下午选择器的监听器 private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { @@ -163,34 +199,43 @@ public class DateTimePicker extends FrameLayout { int dayOfMonth, int hourOfDay, int minute); } + // 通过对数据库的访问,获取当前系统时间 public DateTimePicker(Context context) { this(context, System.currentTimeMillis()); } + // 获得一个天文数字(1970年至今的秒数),需要DateFormat将其变得有意义 public DateTimePicker(Context context, long date) { this(context, date, DateFormat.is24HourFormat(context)); } public DateTimePicker(Context context, long date, boolean is24HourView) { - super(context); + super(context);//获取系统时间 mDate = Calendar.getInstance(); mInitialising = true; mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + + //在控件中填充R.layout.datetime_picker布局 inflate(context, R.layout.datetime_picker, this); + //获取年月日Spinner,设置最小值和最大值,设置值改变监听 mDateSpinner = (NumberPicker) findViewById(R.id.date); mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + // 获取时Spinner,设置值改变监听 mHourSpinner = (NumberPicker) findViewById(R.id.hour); mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + + // 获取分Spinner,设置最小值、最大值、长按更新间隔和值改变监听 mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); mMinuteSpinner.setOnLongPressUpdateInterval(100); mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); + // 获取上午/下午Spinner,设置最小值、最大值、显示值和值改变监听 String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); @@ -214,8 +259,12 @@ public class DateTimePicker extends FrameLayout { mInitialising = false; } + // 设置控件及其子控件的可用状态 @Override public void setEnabled(boolean enabled) { + + // 如果mIsEnabled变量与传入的enabled相同,则不进行操作, + // 否则将控件及其子控件的可用状态更新为enabled并更新mIsEnabled变量。 if (mIsEnabled == enabled) { return; } @@ -434,6 +483,7 @@ public class DateTimePicker extends FrameLayout { updateAmPmControl(); } + // 更新日期控件,根据当前日期更新展示的日期列表 private void updateDateControl() { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(mDate.getTimeInMillis()); @@ -448,6 +498,8 @@ public class DateTimePicker extends FrameLayout { mDateSpinner.invalidate(); } + // 更新上午/下午控件,如果是24小时制,则隐藏此控件; + // 否则根据当前选择的时间更新展示的上午/下午选项 private void updateAmPmControl() { if (mIs24HourView) { mAmPmSpinner.setVisibility(View.GONE); @@ -458,6 +510,7 @@ public class DateTimePicker extends FrameLayout { } } + // 更新小时控件,根据当前选择的时间更新展示的小时选项 private void updateHourControl() { if (mIs24HourView) { mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); diff --git a/src/ui/DateTimePickerDialog.java b/src/ui/DateTimePickerDialog.java index 2c47ba4..be8d4ae 100644 --- a/src/ui/DateTimePickerDialog.java +++ b/src/ui/DateTimePickerDialog.java @@ -31,37 +31,52 @@ import android.text.format.DateUtils; public class DateTimePickerDialog extends AlertDialog implements OnClickListener { - private Calendar mDate = Calendar.getInstance(); + private Calendar mDate = Calendar.getInstance();//便于时间操作 private boolean mIs24HourView; - private OnDateTimeSetListener mOnDateTimeSetListener; - private DateTimePicker mDateTimePicker; + private OnDateTimeSetListener mOnDateTimeSetListener;//时间日期滚动控件 + private DateTimePicker mDateTimePicker;//用于设置或获取日期和时间 public interface OnDateTimeSetListener { + + //在选择日期后返回结果 void OnDateTimeSet(AlertDialog dialog, long date); } public DateTimePickerDialog(Context context, long date) { super(context); mDateTimePicker = new DateTimePicker(context); - setView(mDateTimePicker); + setView(mDateTimePicker); //设置一个子视图 + + //设置一个监听器 mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { public void onDateTimeChanged(DateTimePicker view, int year, int month, int dayOfMonth, int hourOfDay, int minute) { + + //设置日期时间为用户选择的日期和时间 mDate.set(Calendar.YEAR, year); mDate.set(Calendar.MONTH, month); mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); mDate.set(Calendar.MINUTE, minute); + + // 更新标题为选择的日期和时间 updateTitle(mDate.getTimeInMillis()); } }); - mDate.setTimeInMillis(date); - mDate.set(Calendar.SECOND, 0); + + mDate.setTimeInMillis(date);// 初始化mDate对象 + mDate.set(Calendar.SECOND, 0);// 将秒数设为0 mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); + + // 为对话框设置一个确定按钮 setButton(context.getString(R.string.datetime_dialog_ok), this); + + // 为对话框设置一个取消按钮,传入null为点击事件处理器 setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); + + //根据当前的时间的时间格式设置是否使用24小时制显示时间 set24HourView(DateFormat.is24HourFormat(this.getContext())); - updateTitle(mDate.getTimeInMillis()); + updateTitle(mDate.getTimeInMillis());// 更新对话框的标题为初始日期和时间 } public void set24HourView(boolean is24HourView) { @@ -73,15 +88,24 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener } private void updateTitle(long date) { + + // 定义一个标识变量,用于根据给定的日期时间更新标题栏的显示 int flag = DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME; + + // 根据用户选择的,决定是否使用24小时制 flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; + + // 将日期转化为字符串,并设置标题 setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); } + // 用于处理对话框的点击事件 public void onClick(DialogInterface arg0, int arg1) { + + // 如果有日期时间设置监听器,则传递当前日期时间 if (mOnDateTimeSetListener != null) { mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); } diff --git a/src/ui/DropdownMenu.java b/src/ui/DropdownMenu.java index 613dc74..0cdbfbc 100644 --- a/src/ui/DropdownMenu.java +++ b/src/ui/DropdownMenu.java @@ -28,16 +28,23 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import net.micode.notes.R; public class DropdownMenu { + // 定义一个按钮,用于显示和触发下拉菜单 private Button mButton; + // 定义一个弹出菜单,用于显示菜单项 private PopupMenu mPopupMenu; + // 定义一个菜单类,用于存储菜单项 private Menu mMenu; public DropdownMenu(Context context, Button button, int menuId) { mButton = button; + // 设置背景为下拉图标 mButton.setBackgroundResource(R.drawable.dropdown_icon); + // 初始化弹出菜单,并绑定按钮对象 mPopupMenu = new PopupMenu(context, mButton); + // 获取菜单对象,加载菜单资源 mMenu = mPopupMenu.getMenu(); mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + // 设置点击监听器,点击时弹出菜单 mButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { mPopupMenu.show(); @@ -45,16 +52,20 @@ public class DropdownMenu { }); } + // 用于设置下拉菜单点击监听器 public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { + // 如果弹出菜单不为空则为其设置监听器 if (mPopupMenu != null) { mPopupMenu.setOnMenuItemClickListener(listener); } } + // 可根据id查找菜单项 public MenuItem findItem(int id) { return mMenu.findItem(id); } + // 用于设置按钮标题 public void setTitle(CharSequence title) { mButton.setText(title); } diff --git a/src/ui/FoldersListAdapter.java b/src/ui/FoldersListAdapter.java index 96b77da..930f65d 100644 --- a/src/ui/FoldersListAdapter.java +++ b/src/ui/FoldersListAdapter.java @@ -29,27 +29,35 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; +// 指定每个列表项的布局模板,指定Cursor中的每个字段应该绑定到那些视图上 +// 以文件夹的形式展现给用户 public class FoldersListAdapter extends CursorAdapter { + // 指定查询时返回的列,NoteColumns是一个接口 public static final String [] PROJECTION = { NoteColumns.ID, NoteColumns.SNIPPET }; + // 定义查询文件夹表是返回的列的索引 public static final int ID_COLUMN = 0; public static final int NAME_COLUMN = 1; + // 在列表视图中显示文件夹数据 public FoldersListAdapter(Context context, Cursor c) { super(context, c); // TODO Auto-generated constructor stub } + // 创建一个新视图,并初始化子标签 @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new FolderListItem(context); } + // 用于绑定视图和数据 @Override public void bindView(View view, Context context, Cursor cursor) { + // 判断视图对象是否为 FolderListItem 的实例 if (view instanceof FolderListItem) { String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); @@ -57,21 +65,27 @@ public class FoldersListAdapter extends CursorAdapter { } } + // 获取对应文件夹名称 public String getFolderName(Context context, int position) { Cursor cursor = (Cursor) getItem(position); + // 根据文件夹id获取文件夹的名称 return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); } + // 用于显示文件夹名称 private class FolderListItem extends LinearLayout { private TextView mName; public FolderListItem(Context context) { super(context); + // 将布局文件加载到当前视图 inflate(context, R.layout.folder_list_item, this); + // 将获取到的TextView对象赋值给mName mName = (TextView) findViewById(R.id.tv_folder_name); } + // 将给定的名称设置到mName中 public void bind(String name) { mName.setText(name); } diff --git a/src/ui/NoteEditActivity.java b/src/ui/NoteEditActivity.java index 96a9ff8..689fb1f 100644 --- a/src/ui/NoteEditActivity.java +++ b/src/ui/NoteEditActivity.java @@ -74,16 +74,19 @@ import java.util.regex.Pattern; public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { + // 头部视图空间 private class HeadViewHolder { + // 文本视图,显示修改的日期 public TextView tvModified; - + // 图像视图,显示警告图标 public ImageView ivAlertIcon; - + // 文本视图,显示警告日期 public TextView tvAlertDate; - + // 图像按钮,用于设置背景颜色 public ImageView ibSetBgColor; } + // 用于存储不同背景选择按钮的id和对应颜色 private static final Map sBgSelectorBtnsMap = new HashMap(); static { sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); @@ -93,6 +96,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); } + // 用于存储不同颜色值和对应的背景选择器的id private static final Map sBgSelectorSelectionMap = new HashMap(); static { sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); @@ -102,6 +106,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } + // 用于存储不同字体大小按钮id和对应的字体大小 private static final Map sFontSizeBtnsMap = new HashMap(); static { sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); @@ -110,6 +115,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); } + // 用于存储不同字体大小和对应的字体选择器的id private static final Map sFontSelectorSelectionMap = new HashMap(); static { sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); @@ -119,45 +125,53 @@ public class NoteEditActivity extends Activity implements OnClickListener, } private static final String TAG = "NoteEditActivity"; - + // 存储笔记的头部视图 private HeadViewHolder mNoteHeaderHolder; - + // 存储头部视图图面板 private View mHeadViewPanel; private View mNoteBgColorSelector; private View mFontSizeSelector; - + // 用于存储笔记编辑器 private EditText mNoteEditor; - + // 用于存储笔记编辑面板 private View mNoteEditorPanel; - + // 用于存储正在编辑的笔记 private WorkingNote mWorkingNote; - + // 用于存储用户的偏好设置 private SharedPreferences mSharedPrefs; - private int mFontSizeId; + private int mFontSizeId; + // 用于表示字体大小偏好设置键 private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; - + // 用于限制快捷方式图标标题的最大长度 private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; - + // 表示已被选中的标签符号 public static final String TAG_CHECKED = String.valueOf('\u221A'); + // 表示未被选中的标签符号 public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); - + // 用于存储文本编辑列表 private LinearLayout mEditTextList; + // 用于存储用户的输入内容 private String mUserQuery; + private Pattern mPattern; + // 在活动创建时进行一些初始化操作 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.note_edit); + // 如果传入的参数为空,且调用initActivityState方法传入getIntent方法返回的Intent对象返回false, + // 表示活动状态初始化失败,结束活动 if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } + // 初始化一些资源 initResources(); } @@ -165,20 +179,27 @@ public class NoteEditActivity extends Activity implements OnClickListener, * Current activity may be killed when the memory is low. Once it is killed, for another time * user load this activity, we should restore the former state */ + // 为防止内存不足时程序的终止,在这里有一个保存现场的函数 + // 用于在活动恢复时执行一些操作 @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { + // 创建一个新的Intent对象,设置其动作为Intent.ACTION_VIEW Intent intent = new Intent(Intent.ACTION_VIEW); + // 将Intent.EXTRA_UID作为额外数据放入intent对象中 intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); + // 如果初始化活动失败则结束活动 if (!initActivityState(intent)) { finish(); return; } + // 打印一条调试信息,表示从被杀死的活动中恢复 Log.d(TAG, "Restoring from killed activity"); } } + // 用于返回活动初始化状态 private boolean initActivityState(Intent intent) { /** * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, @@ -192,25 +213,30 @@ public class NoteEditActivity extends Activity implements OnClickListener, /** * Starting from the searched result */ + // 根据键值查找ID if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); } - + // 如果在内容提供者中,noteId对应的笔记不可见或者不是笔记类型 if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { Intent jump = new Intent(this, NotesListActivity.class); + //程序将跳转到上面声明的intent——jump startActivity(jump); showToast(R.string.error_note_not_exist); finish(); return false; } else { + // 否则向 load函数 传入当前活动和noteId作为参数,返回一个WorkingNote对象 mWorkingNote = WorkingNote.load(this, noteId); + // 如果正在编辑的笔记为空 if (mWorkingNote == null) { Log.e(TAG, "load note failed with note id" + noteId); finish(); return false; } } + // 获取当前活动的窗口对象,设置软输入状态为隐藏状态并调整大小 getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); @@ -243,6 +269,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, } else { mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId); + // 将空笔记转换为通话笔记 mWorkingNote.convertToCallNote(phoneNumber, callDate); } } else { @@ -258,6 +285,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, finish(); return false; } + // 设置其设置状态为改变监听器 mWorkingNote.setOnSettingStatusChangedListener(this); return true; } @@ -268,9 +296,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, initNoteScreen(); } + // 用于初始化笔记的界面和功能 private void initNoteScreen() { + // 设置笔记编辑器的文字外观,根据字体大小的id选择合适的资源 mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); + // 设置外观 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { switchToListMode(mWorkingNote.getContent()); } else { @@ -295,18 +326,23 @@ public class NoteEditActivity extends Activity implements OnClickListener, showAlertHeader(); } + // 设置闹钟显示 private void showAlertHeader() { + // 判断当前笔记是否有闹钟提醒 if (mWorkingNote.hasClockAlert()) { long time = System.currentTimeMillis(); + // 如果系统时间大于闹钟设置的时间,则闹钟时间设置为在系统时间的基础上加一个时间跨度 if (time > mWorkingNote.getAlertDate()) { mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); } else { mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); } + // 显示闹钟的开始图标 mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); } else { + // 如果当前的笔记没有闹钟提醒,就将警告日期文本和警告图标设置为不可见 mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); }; @@ -326,6 +362,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, * generate a id. If the editing note is not worth saving, there * is no id which is equivalent to create new note */ + //在创建一个新的标签时,先在数据库中匹配 + //如果不存在,那么先在数据库中存储 if (!mWorkingNote.existInDatabase()) { saveNote(); } @@ -333,27 +371,32 @@ public class NoteEditActivity extends Activity implements OnClickListener, Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); } + //用于分发触摸事件到合适的视图中 @Override public boolean dispatchTouchEvent(MotionEvent ev) { + // 判断当前笔记的颜色选择器在屏幕上是否可见,且在可触控范围内 if (mNoteBgColorSelector.getVisibility() == View.VISIBLE && !inRangeOfView(mNoteBgColorSelector, ev)) { mNoteBgColorSelector.setVisibility(View.GONE); return true; } - + // 判断当前笔记的字体大小选择器是否可见,且在触控范围内 if (mFontSizeSelector.getVisibility() == View.VISIBLE && !inRangeOfView(mFontSizeSelector, ev)) { mFontSizeSelector.setVisibility(View.GONE); return true; } + // 如果两个选择器在屏幕都不可见,则重新分配触发事件 return super.dispatchTouchEvent(ev); } + // 对屏幕触控的坐标进行操作 private boolean inRangeOfView(View view, MotionEvent ev) { int []location = new int[2]; view.getLocationOnScreen(location); int x = location[0]; int y = location[1]; + // 如果触控的位置超出了给定的范围 if (ev.getX() < x || ev.getX() > (x + view.getWidth()) || ev.getY() < y @@ -363,6 +406,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + //对标签各项属性内容的初始化 private void initResources() { mHeadViewPanel = findViewById(R.id.note_title); mNoteHeaderHolder = new HeadViewHolder(); @@ -379,6 +423,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, iv.setOnClickListener(this); } + //对字体大小的选择 mFontSizeSelector = findViewById(R.id.font_size_selector); for (int id : sFontSizeBtnsMap.keySet()) { View view = findViewById(id); @@ -400,12 +445,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, @Override protected void onPause() { super.onPause(); + // 尝试保存当前笔记数据,并判断是否保持成功 if(saveNote()) { Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); } + // 清除一些设置状态 clearSettingState(); } + // 与桌面小部件的内容和外观同步 private void updateWidget() { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { @@ -425,8 +473,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, setResult(RESULT_OK, intent); } + // 在一个视图被点击时调用 public void onClick(View v) { + // 获取被点击的视图的id int id = v.getId(); + // 如果是背景颜色的按钮,则将背景颜色选择器和当前笔记背景颜色对应的视图设为可见 if (id == R.id.btn_set_bg_color) { mNoteBgColorSelector.setVisibility(View.VISIBLE); findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( @@ -452,6 +503,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + // 当按下返回键时执行操作 @Override public void onBackPressed() { if(clearSettingState()) { @@ -462,6 +514,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, super.onBackPressed(); } + // 用于清除一些设置状态 private boolean clearSettingState() { if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { mNoteBgColorSelector.setVisibility(View.GONE); @@ -473,6 +526,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } + // 在改变背景颜色时执行的操作 public void onBackgroundColorChanged() { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.VISIBLE); @@ -480,38 +534,53 @@ public class NoteEditActivity extends Activity implements OnClickListener, mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } + // 在准备选择菜单执行的操作 @Override public boolean onPrepareOptionsMenu(Menu menu) { + // 判断Activity是否正在结束 if (isFinishing()) { return true; } clearSettingState(); + // 清空菜单所有项目 menu.clear(); + // 判断当前笔记的文件夹id是否是通话记录文件夹的id if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { + // 加载通话记录编辑菜单的资源 getMenuInflater().inflate(R.menu.call_note_edit, menu); } else { + // 加载普通笔记编辑菜单的资源 getMenuInflater().inflate(R.menu.note_edit, menu); } + // 笔记是否处于清单模式 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + // 将菜单中的列表模式项目的标题设置为普通模式 menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); } else { + // 将菜单中的列表模式项目的标题设置为列表模式 menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); } + // 判断当前笔记是否有闹钟提醒 if (mWorkingNote.hasClockAlert()) { + // 将菜单中的提醒项目设置为不可见 menu.findItem(R.id.menu_alert).setVisible(false); } else { + // 将菜单中的删除提醒项目设置为不可见 menu.findItem(R.id.menu_delete_remind).setVisible(false); } return true; } + // 当选择菜单中的某个项目被点击调用时的操作 @Override public boolean onOptionsItemSelected(MenuItem item) { + // 根据被点击项目的id,进行不同的处理 switch (item.getItemId()) { case R.id.menu_new_note: createNewNote(); break; case R.id.menu_delete: + // 创建对话框,询问用户是否删除 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); @@ -527,24 +596,30 @@ public class NoteEditActivity extends Activity implements OnClickListener, builder.show(); break; case R.id.menu_font_size: + // 将字体大小选择器和当前字体大小对应的视图设置为可见 mFontSizeSelector.setVisibility(View.VISIBLE); findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); break; case R.id.menu_list_mode: + // 根据当前笔记是否是清单模式,切换到相反的模式,并更新笔记的清单模式属性 mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? TextNote.MODE_CHECK_LIST : 0); break; case R.id.menu_share: getWorkingText(); + // 将文本发送给其他应用 sendTo(this, mWorkingNote.getContent()); break; case R.id.menu_send_to_desktop: + // 将当前笔记作为一个快捷方式发送到桌面 sendToDesktop(); break; case R.id.menu_alert: + // 设置一个闹钟提醒 setReminder(); break; case R.id.menu_delete_remind: + // 将当前笔记的提醒日期设置为0,并更新数据库 mWorkingNote.setAlertDate(0, false); break; default: @@ -553,13 +628,17 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + // 设置闹钟提醒 private void setReminder() { + // 用于显示日期时间选择器对话框 DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); d.setOnDateTimeSetListener(new OnDateTimeSetListener() { + // 根据用户选择的日期时间,设置当前笔记的提醒日期,并更新数据库 public void OnDateTimeSet(AlertDialog dialog, long date) { mWorkingNote.setAlertDate(date , true); } }); + // 显示对话框,让用户选择一个日期时间 d.show(); } @@ -567,13 +646,17 @@ public class NoteEditActivity extends Activity implements OnClickListener, * Share note to apps that support {@link Intent#ACTION_SEND} action * and {@text/plain} type */ + // 与其他应用共享文本信息 private void sendTo(Context context, String info) { Intent intent = new Intent(Intent.ACTION_SEND); + // 将要共享的文本信息作为额外数据放入意图中 intent.putExtra(Intent.EXTRA_TEXT, info); intent.setType("text/plain"); + // 启动一个活动,让用户选择一个可以接收这个意图的应用 context.startActivity(intent); } + // 创建一个新便签 private void createNewNote() { // Firstly, save current editing notes saveNote(); @@ -586,15 +669,19 @@ public class NoteEditActivity extends Activity implements OnClickListener, startActivity(intent); } + // 删除当前便签 private void deleteCurrentNote() { + // 如果正在编辑的笔记存有数据 if (mWorkingNote.existInDatabase()) { HashSet ids = new HashSet(); long id = mWorkingNote.getNoteId(); + // 判断是否为根文件夹id if (id != Notes.ID_ROOT_FOLDER) { ids.add(id); } else { Log.d(TAG, "Wrong note id, should not happen"); } + // 判断当前是否是同步模式 if (!isSyncMode()) { if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { Log.e(TAG, "Delete Note error"); @@ -608,10 +695,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, mWorkingNote.markDeleted(true); } + // 判断是否是同步模式 private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + // 笔记闹钟提醒发生变化时调用, set 是是否设置提醒的标志 public void onClockAlertChanged(long date, boolean set) { /** * User could set clock to an unsaved note, so before setting the @@ -620,15 +709,22 @@ public class NoteEditActivity extends Activity implements OnClickListener, if (!mWorkingNote.existInDatabase()) { saveNote(); } + // 大于0,即保存到了数据库中,否则就是未保存到数据库 if (mWorkingNote.getNoteId() > 0) { + // 用于发送一个闹钟接收的广播,将当前笔记的uri作为数据放入意图中 Intent intent = new Intent(this, AlarmReceiver.class); intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); + // 用于包装上面的意图对象,以便在闹钟触发时发送广播 PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); + // 获取一个闹钟管理器对象,用于设置或取消闹钟 AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); showAlertHeader(); + // 判断是否设置提醒 if(!set) { + // 取消之前设置的闹钟 alarmManager.cancel(pendingIntent); } else { + // 设置一个在指定日期时间触发的闹钟,并发送待定意图 alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); } } else { @@ -646,6 +742,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, updateWidget(); } + // 在用户删除某个文本编辑器时,将被删除的文本内容追加到前面或者后面的文本编辑器中,并更新列表的索引值 public void onEditTextDelete(int index, String text) { int childCount = mEditTextList.getChildCount(); if (childCount == 1) { @@ -672,6 +769,10 @@ public class NoteEditActivity extends Activity implements OnClickListener, edit.setSelection(length); } + /* 在用户在某个文本编辑器中按下回车键时,创建一个新的文本编辑器, + * 并将当前的文本内容分割到两个文本编辑器中。 + * 实现在笔记应用中输入多行文本。 + */ public void onEditTextEnter(int index, String text) { /** * Should not happen, check for debug @@ -691,6 +792,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + // 将用户输入的文本转换成多个文本编辑器,以便用户在列表模式下编辑多行文本。 private void switchToListMode(String text) { mEditTextList.removeAllViews(); String[] items = text.split("\n"); @@ -708,6 +810,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, mEditTextList.setVisibility(View.VISIBLE); } + // 将用户输入的搜索关键词在完整文本中高亮显示,以便用户快速定位到相关内容 private Spannable getHighlightQueryResult(String fullText, String userQuery) { SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); if (!TextUtils.isEmpty(userQuery)) { @@ -725,6 +828,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, return spannable; } + // 根据用户输入的文本内容和搜索关键词创建一个带有复选框和高亮效果的文本编辑器视图 private View getListItem(String item, int index) { View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); @@ -756,6 +860,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, return view; } + // 根据用户在文本编辑器中输入或删除文本时,动态显示或隐藏复选框。 public void onTextChange(int index, boolean hasText) { if (index >= mEditTextList.getChildCount()) { Log.e(TAG, "Wrong index, should not happen"); @@ -768,6 +873,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + // 根据用户选择的模式,将文本编辑器从普通模式切换到列表模式,或者从列表模式切换到普通模式。 public void onCheckListModeChanged(int oldMode, int newMode) { if (newMode == TextNote.MODE_CHECK_LIST) { switchToListMode(mNoteEditor.getText().toString()); @@ -782,6 +888,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + // 根据笔记的模式,从文本编辑器或者文本编辑器列表中获取文本内容 private boolean getWorkingText() { boolean hasChecked = false; if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { @@ -805,6 +912,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, return hasChecked; } + // 用于保存一个笔记应用的文本内容 private boolean saveNote() { getWorkingText(); boolean saved = mWorkingNote.saveNote(); @@ -821,6 +929,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, return saved; } + // 将笔记内容发送到桌面 private void sendToDesktop() { /** * Before send message to home, we should make sure that current @@ -856,6 +965,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + // 用于编辑小图标的标题 private String makeShortcutIconTitle(String content) { content = content.replace(TAG_CHECKED, ""); content = content.replace(TAG_UNCHECKED, ""); @@ -863,10 +973,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, SHORTCUT_ICON_TITLE_MAX_LEN) : content; } + // 显示提示的视图 private void showToast(int resId) { showToast(resId, Toast.LENGTH_SHORT); } + // 持续显示提示视图 private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).show(); } diff --git a/src/ui/NoteEditText.java b/src/ui/NoteEditText.java index 2afe2a8..1f7235e 100644 --- a/src/ui/NoteEditText.java +++ b/src/ui/NoteEditText.java @@ -37,6 +37,7 @@ import net.micode.notes.R; import java.util.HashMap; import java.util.Map; +// 继承EditText,设置标签设置文本框 public class NoteEditText extends EditText { private static final String TAG = "NoteEditText"; private int mIndex; @@ -46,6 +47,7 @@ public class NoteEditText extends EditText { private static final String SCHEME_HTTP = "http:" ; private static final String SCHEME_EMAIL = "mailto:" ; + // 建立一个字符和整数的hash表,用于链接电话,网站,还有邮箱 private static final Map sSchemaActionResMap = new HashMap(); static { sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); @@ -56,6 +58,7 @@ public class NoteEditText extends EditText { /** * Call by the {@link NoteEditActivity} to delete or add edit text */ + //在NoteEditActivity中删除或添加文本的操作,可以看做是一个文本是否被变的标记 public interface OnTextViewChangeListener { /** * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens @@ -77,19 +80,22 @@ public class NoteEditText extends EditText { private OnTextViewChangeListener mOnTextViewChangeListener; + // 根据context设置文本 public NoteEditText(Context context) { super(context, null); mIndex = 0; } - + // 设置当前光标 public void setIndex(int index) { mIndex = index; } + // 初始化文本修改标记 public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { mOnTextViewChangeListener = listener; } + // 初始化便签 public NoteEditText(Context context, AttributeSet attrs) { super(context, attrs, android.R.attr.editTextStyle); } @@ -99,6 +105,7 @@ public class NoteEditText extends EditText { // TODO Auto-generated constructor stub } + // 根据用户触摸的位置,获取对应的文本偏移量,并设置为选择状态。 @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { @@ -121,6 +128,7 @@ public class NoteEditText extends EditText { return super.onTouchEvent(event); } + // 处理用户按下一个键盘按键时会触发 的事件 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { @@ -138,11 +146,14 @@ public class NoteEditText extends EditText { return super.onKeyDown(keyCode, event); } + // 处理用户松开一个键盘按键时会触发 的事件 @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch(keyCode) { case KeyEvent.KEYCODE_DEL: + // 如果被修改过 if (mOnTextViewChangeListener != null) { + // 如果被修改过且文档不为空 if (0 == mSelectionStartBeforeDelete && mIndex != 0) { mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); return true; @@ -167,9 +178,11 @@ public class NoteEditText extends EditText { return super.onKeyUp(keyCode, event); } + // 当焦点发生变化时,会自动调用该方法来处理焦点改变的事件 @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { if (mOnTextViewChangeListener != null) { + // 获取到焦点并且文本不为空 if (!focused && TextUtils.isEmpty(getText())) { mOnTextViewChangeListener.onTextChange(mIndex, false); } else { @@ -179,8 +192,10 @@ public class NoteEditText extends EditText { super.onFocusChanged(focused, direction, previouslyFocusedRect); } + // 生成上下文菜单 @Override protected void onCreateContextMenu(ContextMenu menu) { + // 如果有文本存在 if (getText() instanceof Spanned) { int selStart = getSelectionStart(); int selEnd = getSelectionEnd(); @@ -201,7 +216,7 @@ public class NoteEditText extends EditText { if (defaultResId == 0) { defaultResId = R.string.note_link_other; } - + // 建立菜单 menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { diff --git a/src/ui/NoteItemData.java b/src/ui/NoteItemData.java index 0f5a878..fe94192 100644 --- a/src/ui/NoteItemData.java +++ b/src/ui/NoteItemData.java @@ -26,6 +26,9 @@ import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.tool.DataUtils; +/*用于存储和操作一个笔记的id、标题、内容、创建时间、 +修改时间、背景颜色、字体大小、模式、文件夹id等信息。 + */ public class NoteItemData { static final String [] PROJECTION = new String [] { NoteColumns.ID, @@ -76,6 +79,7 @@ public class NoteItemData { private boolean mIsOneNoteFollowingFolder; private boolean mIsMultiNotesFollowingFolder; + // 初始化NoteItemData,主要利用光标cursor获取的东西 public NoteItemData(Context context, Cursor cursor) { mId = cursor.getLong(ID_COLUMN); mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); @@ -109,6 +113,7 @@ public class NoteItemData { checkPostion(cursor); } + // 根据鼠标的位置设置标记和位置 private void checkPostion(Cursor cursor) { mIsLastItem = cursor.isLast() ? true : false; mIsFirstItem = cursor.isFirst() ? true : false; @@ -214,6 +219,7 @@ public class NoteItemData { return (mAlertDate > 0); } + // 判断一个笔记是否是一个通话记录 public boolean isCallRecord() { return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); } diff --git a/src/ui/NotesListAdapter.java b/src/ui/NotesListAdapter.java index 51c9cb9..af6f2fd 100644 --- a/src/ui/NotesListAdapter.java +++ b/src/ui/NotesListAdapter.java @@ -31,6 +31,7 @@ import java.util.HashSet; import java.util.Iterator; +// 用于创建笔记应用的列表适配器 public class NotesListAdapter extends CursorAdapter { private static final String TAG = "NotesListAdapter"; private Context mContext; @@ -55,6 +56,7 @@ public class NotesListAdapter extends CursorAdapter { return new NotesListItem(context); } + // 用于绑定视图和数据 @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof NotesListItem) { @@ -64,20 +66,24 @@ public class NotesListAdapter extends CursorAdapter { } } + // 设置勾选框,并更新视图 public void setCheckedItem(final int position, final boolean checked) { mSelectedIndex.put(position, checked); notifyDataSetChanged(); } + // 判断单选按钮是否勾选 public boolean isInChoiceMode() { return mChoiceMode; } + // 设置单项选项框 public void setChoiceMode(boolean mode) { mSelectedIndex.clear(); mChoiceMode = mode; } + // 根据参数checked,选择或取消选择所有的便签项 public void selectAll(boolean checked) { Cursor cursor = getCursor(); for (int i = 0; i < getCount(); i++) { @@ -89,6 +95,7 @@ public class NotesListAdapter extends CursorAdapter { } } + // 获取所有选中的项的id public HashSet getSelectedItemIds() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -105,6 +112,7 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + // 获取所有选中的项的小部件属性 public HashSet getSelectedWidget() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -128,6 +136,7 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + // 获取选中的项的数量,并返回一个整数。 public int getSelectedCount() { Collection values = mSelectedIndex.values(); if (null == values) { @@ -143,11 +152,13 @@ public class NotesListAdapter extends CursorAdapter { return count; } + // 判断是否所有项都被选中 public boolean isAllSelected() { int checkedCount = getSelectedCount(); return (checkedCount != 0 && checkedCount == mNotesCount); } + // 判断是否被选中 public boolean isSelectedItem(final int position) { if (null == mSelectedIndex.get(position)) { return false; @@ -155,18 +166,21 @@ public class NotesListAdapter extends CursorAdapter { return mSelectedIndex.get(position); } + // 内容发生变化时,重新计算便签数量 @Override protected void onContentChanged() { super.onContentChanged(); calcNotesCount(); } + // 光标发生变动时,回调该函数计算便签数量 @Override public void changeCursor(Cursor cursor) { super.changeCursor(cursor); calcNotesCount(); } + // 计算便签数量 private void calcNotesCount() { mNotesCount = 0; for (int i = 0; i < getCount(); i++) { diff --git a/src/ui/NotesListItem.java b/src/ui/NotesListItem.java index 1221e80..dad78e7 100644 --- a/src/ui/NotesListItem.java +++ b/src/ui/NotesListItem.java @@ -30,6 +30,7 @@ import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser.NoteItemBgResources; +//创建便签列表项目选项 public class NotesListItem extends LinearLayout { private ImageView mAlert; private TextView mTitle; @@ -38,6 +39,7 @@ public class NotesListItem extends LinearLayout { private NoteItemData mItemData; private CheckBox mCheckBox; + public NotesListItem(Context context) { super(context); inflate(context, R.layout.note_item, this); @@ -48,15 +50,20 @@ public class NotesListItem extends LinearLayout { mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); } + // 绑定数据和视图 public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { + // 如果是多选模式并且数据是便签类型,就将复选框设置为可见 + // 让用户可以在多选模式下选择或取消选择便签项 if (choiceMode && data.getType() == Notes.TYPE_NOTE) { mCheckBox.setVisibility(View.VISIBLE); + // 设置是否选中 mCheckBox.setChecked(checked); } else { mCheckBox.setVisibility(View.GONE); } mItemData = data; + // 根据数据的id和父id,设置不同的标题,时间,图标和呼叫名 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mCallName.setVisibility(View.GONE); mAlert.setVisibility(View.VISIBLE); @@ -99,6 +106,7 @@ public class NotesListItem extends LinearLayout { setBackground(data); } + // 根据传入的数据设置背景颜色 private void setBackground(NoteItemData data) { int id = data.getBgColorId(); if (data.getType() == Notes.TYPE_NOTE) { @@ -116,6 +124,7 @@ public class NotesListItem extends LinearLayout { } } + // 获取视图板顶的数据对象 public NoteItemData getItemData() { return mItemData; } diff --git a/src/ui/NotesPreferenceActivity.java b/src/ui/NotesPreferenceActivity.java index 07c5f7e..5134c10 100644 --- a/src/ui/NotesPreferenceActivity.java +++ b/src/ui/NotesPreferenceActivity.java @@ -69,6 +69,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { private boolean mHasAddedAccount; + // 在活动创建时执行一些初始化操作 @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -76,6 +77,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { /* using the app icon for navigation */ getActionBar().setDisplayHomeAsUpEnabled(true); + // 从xml文件中加载活动的偏好设置 addPreferencesFromResource(R.xml.preferences); mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); mReceiver = new GTaskReceiver(); @@ -84,7 +86,9 @@ public class NotesPreferenceActivity extends PreferenceActivity { registerReceiver(mReceiver, filter); mOriAccounts = null; + // 从xml文件中加载了一个视图,并赋值给header这个局部变量 View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); + // 将这个视图添加到列表的头部 getListView().addHeaderView(header, null, true); } @@ -112,10 +116,11 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } } - + // 刷新用户界面 refreshUI(); } + // 在活动销毁时执行一些清理操作 @Override protected void onDestroy() { if (mReceiver != null) { @@ -124,6 +129,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { super.onDestroy(); } + // 加载账户相关的偏好设置 private void loadAccountPreference() { mAccountCategory.removeAll(); @@ -154,6 +160,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { mAccountCategory.addPreference(accountPref); } + // 加载同步按钮和同步状态的显示 private void loadSyncButton() { Button syncButton = (Button) findViewById(R.id.preference_sync_button); TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); @@ -193,29 +200,35 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + // 刷新界面 private void refreshUI() { loadAccountPreference(); loadSyncButton(); } + // 显示账户选择的对话框并进行账户的设置 private void showSelectAccountAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + // 设置标题以及子标题内容 View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); + // 设置对话框的自定义标题 dialogBuilder.setCustomTitle(titleView); dialogBuilder.setPositiveButton(null, null); + // 获取同步账户信息 Account[] accounts = getGoogleAccounts(); String defAccount = getSyncAccountName(this); mOriAccounts = accounts; mHasAddedAccount = false; + // 如果账户不为空 if (accounts.length > 0) { CharSequence[] items = new CharSequence[accounts.length]; final CharSequence[] itemMapping = items; @@ -227,8 +240,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { } items[index++] = account.name; } + // 在对话框建立一个单选的复选框 dialogBuilder.setSingleChoiceItems(items, checkedItem, new DialogInterface.OnClickListener() { + //设置点击后执行的事件,包括检录新同步账户和刷新标签界面 public void onClick(DialogInterface dialog, int which) { setSyncAccount(itemMapping[which].toString()); dialog.dismiss(); @@ -254,15 +269,18 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + // 显示一个更改账户的确认对话框 private void showChangeAccountConfirmAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + // 根据同步修改的账户信息设置标题以及子标题的内容 View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, getSyncAccountName(this))); TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); + // 设置对话框的自定义标题 dialogBuilder.setCustomTitle(titleView); CharSequence[] menuItemArray = new CharSequence[] { @@ -283,11 +301,13 @@ public class NotesPreferenceActivity extends PreferenceActivity { dialogBuilder.show(); } + // 获取谷歌账户 private Account[] getGoogleAccounts() { AccountManager accountManager = AccountManager.get(this); return accountManager.getAccountsByType("com.google"); } + // 设置同步账户 private void setSyncAccount(String account) { if (!getSyncAccountName(this).equals(account)) { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -318,6 +338,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + // 删除同步账户 private void removeSyncAccount() { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); @@ -340,12 +361,14 @@ public class NotesPreferenceActivity extends PreferenceActivity { }).start(); } + // 获取同步账户名称 public static String getSyncAccountName(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); } + // 设置最终同步时间 public static void setLastSyncTime(Context context, long time) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -354,12 +377,14 @@ public class NotesPreferenceActivity extends PreferenceActivity { editor.commit(); } + // 获取最终同步时间 public static long getLastSyncTime(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); } + // 接收同步状态和信息 private class GTaskReceiver extends BroadcastReceiver { @Override @@ -374,6 +399,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + // 处理菜单选项 public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: