diff --git a/NUNG95)KSCVJKJJAANMGS(8.png b/NUNG95)KSCVJKJJAANMGS(8.png new file mode 100644 index 0000000..78adfc7 Binary files /dev/null and b/NUNG95)KSCVJKJJAANMGS(8.png differ diff --git a/W3~]OT3)~ZILZ1W`2_1M`R4.png b/W3~]OT3)~ZILZ1W`2_1M`R4.png new file mode 100644 index 0000000..5d75240 Binary files /dev/null and b/W3~]OT3)~ZILZ1W`2_1M`R4.png differ diff --git a/doc/字符统计/字符.png b/doc/字符统计/字符.png new file mode 100644 index 0000000..3afcb66 Binary files /dev/null and b/doc/字符统计/字符.png differ diff --git a/doc/字符统计/物理体系结构图.png b/doc/字符统计/物理体系结构图.png new file mode 100644 index 0000000..2cf1bc7 Binary files /dev/null and b/doc/字符统计/物理体系结构图.png differ diff --git a/doc/字符统计/界面跳转顺序图.docx b/doc/字符统计/界面跳转顺序图.docx new file mode 100644 index 0000000..2319f6a Binary files /dev/null and b/doc/字符统计/界面跳转顺序图.docx differ diff --git a/doc/显示实时天气/天气显示.png b/doc/显示实时天气/天气显示.png new file mode 100644 index 0000000..86308a9 Binary files /dev/null and b/doc/显示实时天气/天气显示.png differ diff --git a/doc/显示实时天气/物理体系结构体.png b/doc/显示实时天气/物理体系结构体.png new file mode 100644 index 0000000..8d353ab Binary files /dev/null and b/doc/显示实时天气/物理体系结构体.png differ diff --git a/doc/显示实时天气/界面跳转顺序图.docx b/doc/显示实时天气/界面跳转顺序图.docx new file mode 100644 index 0000000..2319f6a Binary files /dev/null and b/doc/显示实时天气/界面跳转顺序图.docx differ diff --git a/doc/显示实时天气/菜单栏.png b/doc/显示实时天气/菜单栏.png new file mode 100644 index 0000000..1ed2bce Binary files /dev/null and b/doc/显示实时天气/菜单栏.png differ diff --git a/doc/精读报告/data/Contact.doc b/doc/精读报告/data/Contact.doc deleted file mode 100644 index fc86dec..0000000 Binary files a/doc/精读报告/data/Contact.doc and /dev/null differ diff --git a/doc/精读报告/data/Notes.doc b/doc/精读报告/data/Notes.doc deleted file mode 100644 index b711803..0000000 Binary files a/doc/精读报告/data/Notes.doc and /dev/null differ diff --git a/doc/精读报告/data/NotesDatabaseHelper.doc b/doc/精读报告/data/NotesDatabaseHelper.doc deleted file mode 100644 index c3abc6b..0000000 Binary files a/doc/精读报告/data/NotesDatabaseHelper.doc and /dev/null differ diff --git a/doc/精读报告/data/NotesProvider.doc b/doc/精读报告/data/NotesProvider.doc deleted file mode 100644 index 921721e..0000000 Binary files a/doc/精读报告/data/NotesProvider.doc and /dev/null differ diff --git a/doc/精读报告/gtask/data/MetaData.doc b/doc/精读报告/gtask/data/MetaData.doc deleted file mode 100644 index f876671..0000000 --- a/doc/精读报告/gtask/data/MetaData.doc +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.data; - -import android.database.Cursor; -import android.util.Log; - -import net.micode.notes.tool.GTaskStringUtils; - -import org.json.JSONException; -import org.json.JSONObject; - - -public class MetaData extends Task { - /* - * 功能描述:得到类的简写名称存入字符串TAG中 - * 实现过程:调用getSimpleName ()函数 - */ - private final static String TAG = MetaData.class.getSimpleName(); - private String mRelatedGid = null; - /* - * 功能描述:设置数据,即生成元数据库 - * 实现过程:调用JSONObject库函数put (),Task类中的setNotes ()和setName ()函数 - */ - public void setMeta(String gid, JSONObject metaInfo) { - //对函数块进行注释 - try { - metaIwnfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); - /* - * 将这对键值放入metaInfo这个jsonobject对象中 - */ - } catch (JSONException e) { - 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数据对象设置元数据内容 - * 实现过程:调用父类Task中的setContentByRemoteJSON ()函数 - */ - @Override - public void setContentByRemoteJSON(JSONObject js) { - super.setContentByRemoteJSON(js); - if (getNotes() != null) { - try { - JSONObject metaInfo = new JSONObject(getNotes().trim()); - mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); - } catch (JSONException e) { - Log.w(TAG, "failed to get related gid"); - /* - * 输出警告信息 - */ - mRelatedGid = null; - } - } - } - /* - * 功能描述:使用本地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/doc/精读报告/gtask/data/Node.doc b/doc/精读报告/gtask/data/Node.doc deleted file mode 100644 index fabe296..0000000 --- a/doc/精读报告/gtask/data/Node.doc +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.data; - -import android.database.Cursor; - -import org.json.JSONObject; -/* - * 应该是同步操作的基础数据类型,定义了相关指示同步操作的常量 -*/ -public abstract class Node { - //定义了各种用于表征同步状态的常量 - public static final int SYNC_ACTION_NONE = 0;// 本地和云端都无可更新内容(即本地和云端内容一致) - - public static final int SYNC_ACTION_ADD_REMOTE = 1;// 需要在远程云端增加内容 - - public static final int SYNC_ACTION_ADD_LOCAL = 2;// 需要在本地增加内容 - - public static final int SYNC_ACTION_DEL_REMOTE = 3;// 需要在远程云端删除内容 - - public static final int SYNC_ACTION_DEL_LOCAL = 4;// 需要在本地删除内容 - - public static final int SYNC_ACTION_UPDATE_REMOTE = 5;// 需要将本地内容更新到远程云端 - - public static final int SYNC_ACTION_UPDATE_LOCAL = 6;// 需要将远程云端内容更新到本地 - - public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;// 同步出现冲突 - - public static final int SYNC_ACTION_ERROR = 8;// 同步出现错误 - - private String mGid; - - private String mName; - - private long mLastModified; - //记录最后一次修改时间 - private boolean mDeleted; - //表征是否被删除 - public Node() { - mGid = null; - mName = ""; - mLastModified = 0; - mDeleted = false; - } - - public abstract JSONObject getCreateAction(int actionId); - - public abstract JSONObject getUpdateAction(int actionId); - - public abstract void setContentByRemoteJSON(JSONObject js); - - public abstract void setContentByLocalJSON(JSONObject js); - - public abstract JSONObject getLocalJSONFromContent(); - - public abstract int getSyncAction(Cursor c); - - public void setGid(String gid) { - this.mGid = gid; - } - - public void setName(String name) { - this.mName = name; - } - - public void setLastModified(long lastModified) { - this.mLastModified = lastModified; - } - - public void setDeleted(boolean deleted) { - this.mDeleted = deleted; - } - - public String getGid() { - return this.mGid; - } - - public String getName() { - return this.mName; - } - - public long getLastModified() { - return this.mLastModified; - } - - public boolean getDeleted() { - return this.mDeleted; - } - -} diff --git a/doc/精读报告/gtask/data/SqlData.doc b/doc/精读报告/gtask/data/SqlData.doc deleted file mode 100644 index ff9ab81..0000000 --- a/doc/精读报告/gtask/data/SqlData.doc +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/* - * Description:用于支持小米便签最底层的数据库相关操作,和sqlnote的关系上是子集关系,即data是note的子集(节点)。 - * SqlData其实就是也就是所谓数据中的数据 - */ -package net.micode.notes.gtask.data; - -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.util.Log; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.DataConstants; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.data.NotesDatabaseHelper.TABLE; -import net.micode.notes.gtask.exception.ActionFailureException; - -import org.json.JSONException; -import org.json.JSONObject; - - -public class SqlData { - /* - * 功能描述:得到类的简写名称存入字符串TAG中 - * 实现过程:调用getSimpleName ()函数 - */ - private static final String TAG = SqlData.class.getSimpleName(); - - private static final int INVALID_ID = -99999; - /* - * 来自Notes类中定义的DataColumn中的一些常量 - */ - // 集合了interface DataColumns中所有SF常量 - public static final String[] PROJECTION_DATA = new String[] { - DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, - DataColumns.DATA3 - }; -/* - * 以下五个变量作为sql表中5列的编号 - */ - public static final int DATA_ID_COLUMN = 0; - - public static final int DATA_MIME_TYPE_COLUMN = 1; - - public static final int DATA_CONTENT_COLUMN = 2; - - public static final int DATA_CONTENT_DATA_1_COLUMN = 3; - - public static final int DATA_CONTENT_DATA_3_COLUMN = 4; - - private ContentResolver mContentResolver; - //判断是否直接用Content生成,是为true,否则为false - private boolean mIsCreate; - - private long mDataId; - - private String mDataMimeType; - - private String mDataContent; - - private long mDataContentData1; - - private String mDataContentData3; - - private ContentValues mDiffDataValues; - /* - * 功能描述:构造函数,用于初始化数据 - * 参数注解:mContentResolver用于获取ContentProvider提供的数据 - * 参数注解: mIsCreate表征当前数据是用哪种方式创建(两种构造函数的参数不同) - */ - public SqlData(Context context) { - mContentResolver = context.getContentResolver(); - mIsCreate = true; - mDataId = INVALID_ID; - mDataMimeType = DataConstants.NOTE; - mDataContent = ""; - mDataContentData1 = 0; - 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); - mDataContent = c.getString(DATA_CONTENT_COLUMN); - mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); - mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); - } - /* - * 功能描述:设置用于共享的数据,并提供异常抛出与处理机制 - */ - public void setContent(JSONObject js) throws JSONException { - //如果传入的JSONObject对象中有DataColumns.ID这一项,则设置,否则设为INVALID_ID - long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; - if (mIsCreate || mDataId != dataId) { - mDiffDataValues.put(DataColumns.ID, dataId); - } - mDataId = dataId; - - String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE) - : DataConstants.NOTE; - if (mIsCreate || !mDataMimeType.equals(dataMimeType)) { - mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); - } - mDataMimeType = dataMimeType; - - String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : ""; - if (mIsCreate || !mDataContent.equals(dataContent)) { - mDiffDataValues.put(DataColumns.CONTENT, dataContent); - } - mDataContent = dataContent; - - long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0; - if (mIsCreate || mDataContentData1 != dataContentData1) { - mDiffDataValues.put(DataColumns.DATA1, dataContentData1); - } - mDataContentData1 = dataContentData1; - - String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : ""; - if (mIsCreate || !mDataContentData3.equals(dataContentData3)) { - mDiffDataValues.put(DataColumns.DATA3, dataContentData3); - } - mDataContentData3 = dataContentData3; - } - /* - * 功能描述:获取共享的数据内容,并提供异常抛出与处理机制 - */ - public JSONObject getContent() throws JSONException { - if (mIsCreate) { - Log.e(TAG, "it seems that we haven't created this in database yet"); - return null; - //创建JSONObject对象。并将相关数据放入其中,并返回。 - } - JSONObject js = new JSONObject(); - js.put(DataColumns.ID, mDataId); - js.put(DataColumns.MIME_TYPE, mDataMimeType); - js.put(DataColumns.CONTENT, mDataContent); - js.put(DataColumns.DATA1, mDataContentData1); - js.put(DataColumns.DATA3, mDataContentData3); - return js; - } - /* - * 功能描述:commit函数用于把当前造作所做的修改保存到数据库 - */ - public void commit(long noteId, boolean validateVersion, long version) { - - if (mIsCreate) { - if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { - mDiffDataValues.remove(DataColumns.ID); - } - - mDiffDataValues.put(DataColumns.NOTE_ID, noteId); - Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); - try { - mDataId = Long.valueOf(uri.getPathSegments().get(1)); - } catch (NumberFormatException e) { - Log.e(TAG, "Get note id error :" + e.toString()); - throw new ActionFailureException("create note failed"); - } - } else { - if (mDiffDataValues.size() > 0) { - int result = 0; - if (!validateVersion) { - result = mContentResolver.update(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); - } else { - result = mContentResolver.update(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, - " ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE - + " WHERE " + NoteColumns.VERSION + "=?)", new String[] { - String.valueOf(noteId), String.valueOf(version) - }); - } - if (result == 0) { - Log.w(TAG, "there is no update. maybe user updates note when syncing"); - } - } - } - - mDiffDataValues.clear(); - mIsCreate = false; - } - /* - * 功能描述:获取当前id - */ - public long getId() { - return mDataId; - } -} diff --git a/doc/精读报告/gtask/data/SqlNote.doc b/doc/精读报告/gtask/data/SqlNote.doc deleted file mode 100644 index 6e3e842..0000000 --- a/doc/精读报告/gtask/data/SqlNote.doc +++ /dev/null @@ -1,566 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.data; - -import android.appwidget.AppWidgetManager; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.util.Log; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.gtask.exception.ActionFailureException; -import net.micode.notes.tool.GTaskStringUtils; -import net.micode.notes.tool.ResourceParser; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; - - -public class SqlNote { - /* - * 功能描述:得到类的简写名称存入字符串TAG中 - * 实现过程:调用getSimpleName ()函数 - */ - private static final String TAG = SqlNote.class.getSimpleName(); - - private static final int INVALID_ID = -99999; - // 集合了interface NoteColumns中所有SF常量(17个) - public static final String[] PROJECTION_NOTE = new String[] { - NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID, - NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE, - NoteColumns.NOTES_COUNT, NoteColumns.PARENT_ID, NoteColumns.SNIPPET, NoteColumns.TYPE, - NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, NoteColumns.SYNC_ID, - NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID, - NoteColumns.VERSION - }; - - public static final int ID_COLUMN = 0; - - public static final int ALERTED_DATE_COLUMN = 1; - - public static final int BG_COLOR_ID_COLUMN = 2; - - public static final int CREATED_DATE_COLUMN = 3; - - public static final int HAS_ATTACHMENT_COLUMN = 4; - - public static final int MODIFIED_DATE_COLUMN = 5; - - public static final int NOTES_COUNT_COLUMN = 6; - - public static final int PARENT_ID_COLUMN = 7; - - public static final int SNIPPET_COLUMN = 8; - - public static final int TYPE_COLUMN = 9; - - public static final int WIDGET_ID_COLUMN = 10; - - public static final int WIDGET_TYPE_COLUMN = 11; - - public static final int SYNC_ID_COLUMN = 12; - - public static final int LOCAL_MODIFIED_COLUMN = 13; - - public static final int ORIGIN_PARENT_ID_COLUMN = 14; - - public static final int GTASK_ID_COLUMN = 15; - - public static final int VERSION_COLUMN = 16; - //一下定义了17个内部的变量,其中12个可以由content中获得,5个需要初始化为0或者new - private Context mContext; - - private ContentResolver mContentResolver; - - private boolean mIsCreate; - - private long mId; - - private long mAlertDate; - - private int mBgColorId; - - private long mCreatedDate; - - private int mHasAttachment; - - private long mModifiedDate; - - private long mParentId; - - private String mSnippet; - - private int mType; - - private int mWidgetId; - - private int mWidgetType; - - private long mOriginParent; - - private long mVersion; - - private ContentValues mDiffNoteValues; - - private ArrayList mDataList; - - - /* - * 功能描述:构造函数 - * 参数注解: mIsCreate用于标示构造方式 - */ - //构造函数只有context,对所有的变量进行初始化 - public SqlNote(Context context) { - mContext = context; - mContentResolver = context.getContentResolver(); - mIsCreate = true; - mId = INVALID_ID; - mAlertDate = 0; - mBgColorId = ResourceParser.getDefaultBgId(context); - mCreatedDate = System.currentTimeMillis();//调用系统函数获得创建时间 - mHasAttachment = 0; - mModifiedDate = System.currentTimeMillis();//最后一次修改时间初始化为创建时间 - mParentId = 0; - mSnippet = ""; - mType = Notes.TYPE_NOTE; - mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; - mWidgetType = Notes.TYPE_WIDGET_INVALIDE; - mOriginParent = 0; - mVersion = 0; - mDiffNoteValues = new ContentValues(); - mDataList = new ArrayList(); - } - /* - * 功能描述:构造函数 - * 参数注解: mIsCreate用于标示构造方式 - */ - //构造函数有context和一个数据库的cursor,多数变量通过cursor指向的一条记录直接进行初始化 - public SqlNote(Context context, Cursor c) { - mContext = context; - mContentResolver = context.getContentResolver(); - mIsCreate = false; - loadFromCursor(c); - mDataList = new ArrayList(); - if (mType == Notes.TYPE_NOTE) - loadDataContent(); - mDiffNoteValues = new ContentValues(); - } - /* - * 功能描述:构造函数 - * 参数注解: mIsCreate用于标示构造方式 - */ - public SqlNote(Context context, long id) { - mContext = context; - mContentResolver = context.getContentResolver(); - mIsCreate = false; - loadFromCursor(id); - mDataList = new ArrayList(); - if (mType == Notes.TYPE_NOTE) - loadDataContent(); - mDiffNoteValues = new ContentValues(); - - } -/* - * 功能描述:通过id从光标处加载数据 -*/ - private void loadFromCursor(long id) { - Cursor c = null; - try { - c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)", - new String[] { - String.valueOf(id) - }, null);//通过id获得对应的ContentResolver中的cursor - if (c != null) { - c.moveToNext(); - loadFromCursor(c);//然后加载数据进行初始化,这样函数 - - //SqlNote(Context context, long id)与SqlNote(Context context, long id)的实现方式基本相同 - } else { - Log.w(TAG, "loadFromCursor: cursor = null"); - } - } finally { - if (c != null) - c.close(); - } - } - /* - * 功能描述:通过游标从光标处加载数据 - */ - - private void loadFromCursor(Cursor c) { - //直接从一条记录中的获得以下变量的初始值 - mId = c.getLong(ID_COLUMN); - mAlertDate = c.getLong(ALERTED_DATE_COLUMN); - mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); - mCreatedDate = c.getLong(CREATED_DATE_COLUMN); - mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN); - mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN); - mParentId = c.getLong(PARENT_ID_COLUMN); - mSnippet = c.getString(SNIPPET_COLUMN); - mType = c.getInt(TYPE_COLUMN); - mWidgetId = c.getInt(WIDGET_ID_COLUMN); - mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); - mVersion = c.getLong(VERSION_COLUMN); - } - /* - * 功能描述:通过content机制获取共享数据并加载到数据库当前游标处 - */ - private void loadDataContent() { - Cursor c = null; - mDataList.clear(); - try { - c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA, - "(note_id=?)", new String[] { - String.valueOf(mId) - }, null); - if (c != null) { - if (c.getCount() == 0) { - Log.w(TAG, "it seems that the note has not data"); - return; - } - while (c.moveToNext()) { - SqlData data = new SqlData(mContext, c); - mDataList.add(data); - } - } else { - Log.w(TAG, "loadDataContent: cursor = null"); - } - } finally { - if (c != null) - c.close(); - } - } - /* - * 功能描述:设置通过content机制用于共享的数据信息 - - */ - public boolean setContent(JSONObject js) { - try { - JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { - Log.w(TAG, "cannot set system folder"); - } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { - // for folder we can only update the snnipet and type - String snippet = note.has(NoteColumns.SNIPPET) ? note - .getString(NoteColumns.SNIPPET) : ""; - if (mIsCreate || !mSnippet.equals(snippet)) { - mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); - } - mSnippet = snippet; - - int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) - : Notes.TYPE_NOTE; - if (mIsCreate || mType != type) { - mDiffNoteValues.put(NoteColumns.TYPE, type); - } - mType = type; - } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { - JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); - long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID; - if (mIsCreate || mId != id) { - mDiffNoteValues.put(NoteColumns.ID, id); - } - mId = id; - - long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note - .getLong(NoteColumns.ALERTED_DATE) : 0; - if (mIsCreate || mAlertDate != alertDate) { - mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate); - } - mAlertDate = alertDate; - - int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note - .getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext); - if (mIsCreate || mBgColorId != bgColorId) { - mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId); - } - mBgColorId = bgColorId; - - long createDate = note.has(NoteColumns.CREATED_DATE) ? note - .getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis(); - if (mIsCreate || mCreatedDate != createDate) { - mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate); - } - mCreatedDate = createDate; - - int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note - .getInt(NoteColumns.HAS_ATTACHMENT) : 0; - if (mIsCreate || mHasAttachment != hasAttachment) { - mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment); - } - mHasAttachment = hasAttachment; - - long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note - .getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis(); - if (mIsCreate || mModifiedDate != modifiedDate) { - mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate); - } - mModifiedDate = modifiedDate; - - long parentId = note.has(NoteColumns.PARENT_ID) ? note - .getLong(NoteColumns.PARENT_ID) : 0; - if (mIsCreate || mParentId != parentId) { - mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId); - } - mParentId = parentId; - - String snippet = note.has(NoteColumns.SNIPPET) ? note - .getString(NoteColumns.SNIPPET) : ""; - if (mIsCreate || !mSnippet.equals(snippet)) { - mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); - } - mSnippet = snippet; - - int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) - : Notes.TYPE_NOTE; - if (mIsCreate || mType != type) { - mDiffNoteValues.put(NoteColumns.TYPE, type); - } - mType = type; - - int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID) - : AppWidgetManager.INVALID_APPWIDGET_ID; - if (mIsCreate || mWidgetId != widgetId) { - mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId); - } - mWidgetId = widgetId; - - int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note - .getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE; - if (mIsCreate || mWidgetType != widgetType) { - mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType); - } - mWidgetType = widgetType; - - long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note - .getLong(NoteColumns.ORIGIN_PARENT_ID) : 0; - if (mIsCreate || mOriginParent != originParent) { - mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent); - } - mOriginParent = originParent; - - for (int i = 0; i < dataArray.length(); i++) { - JSONObject data = dataArray.getJSONObject(i); - SqlData sqlData = null; - if (data.has(DataColumns.ID)) { - long dataId = data.getLong(DataColumns.ID); - for (SqlData temp : mDataList) { - if (dataId == temp.getId()) { - sqlData = temp; - } - } - } - - if (sqlData == null) { - sqlData = new SqlData(mContext); - mDataList.add(sqlData); - } - - sqlData.setContent(data); - } - } - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - return false; - } - return true; - } - /* - * 功能描述:获取content机制提供的数据并加载到note中 - */ - public JSONObject getContent() { - try { - JSONObject js = new JSONObject(); - - if (mIsCreate) { - Log.e(TAG, "it seems that we haven't created this in database yet"); - return null; - } - - JSONObject note = new JSONObject(); - if (mType == Notes.TYPE_NOTE) { - //类型为note时 - note.put(NoteColumns.ID, mId); - note.put(NoteColumns.ALERTED_DATE, mAlertDate); - note.put(NoteColumns.BG_COLOR_ID, mBgColorId); - note.put(NoteColumns.CREATED_DATE, mCreatedDate); - note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment); - note.put(NoteColumns.MODIFIED_DATE, mModifiedDate); - note.put(NoteColumns.PARENT_ID, mParentId); - note.put(NoteColumns.SNIPPET, mSnippet); - note.put(NoteColumns.TYPE, mType); - note.put(NoteColumns.WIDGET_ID, mWidgetId); - note.put(NoteColumns.WIDGET_TYPE, mWidgetType); - note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent); - js.put(GTaskStringUtils.META_HEAD_NOTE, note); - - JSONArray dataArray = new JSONArray(); - for (SqlData sqlData : mDataList) { - JSONObject data = sqlData.getContent(); - if (data != null) { - dataArray.put(data); - } - } - js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); - } else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { - note.put(NoteColumns.ID, mId); - note.put(NoteColumns.TYPE, mType); - note.put(NoteColumns.SNIPPET, mSnippet); - js.put(GTaskStringUtils.META_HEAD_NOTE, note); - } - - return js; - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - } - return null; - } -/* - * 功能描述:给当前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; - } - - /* - * 功能描述:commit函数用于把当前造作所做的修改保存到数据库 - */ - public void commit(boolean validateVersion) { - if (mIsCreate) { - if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) { - mDiffNoteValues.remove(NoteColumns.ID); - } - - Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); - try { - mId = Long.valueOf(uri.getPathSegments().get(1)); - } catch (NumberFormatException e) { - Log.e(TAG, "Get note id error :" + e.toString()); - throw new ActionFailureException("create note failed"); - } - if (mId == 0) { - throw new IllegalStateException("Create thread id failed"); - } - - if (mType == Notes.TYPE_NOTE) { - for (SqlData sqlData : mDataList) { - //直接使用sqldata中的实现 - sqlData.commit(mId, false, -1); - } - } - } else { - if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) { - Log.e(TAG, "No such note"); - throw new IllegalStateException("Try to update note with invalid id"); - } - if (mDiffNoteValues.size() > 0) { - mVersion ++; - int result = 0; - if (!validateVersion) { - //构造字符串 - result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?)", new String[] { - String.valueOf(mId) - }); - } else { - result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", - new String[] { - String.valueOf(mId), String.valueOf(mVersion) - }); - } - if (result == 0) { - Log.w(TAG, "there is no update. maybe user updates note when syncing"); - } - } - - if (mType == Notes.TYPE_NOTE) { - for (SqlData sqlData : mDataList) { - sqlData.commit(mId, validateVersion, mVersion); - } - } - } - - // refresh local info - loadFromCursor(mId); - if (mType == Notes.TYPE_NOTE) - loadDataContent(); - - mDiffNoteValues.clear(); - mIsCreate = false; - } -} diff --git a/doc/精读报告/gtask/data/Task.doc b/doc/精读报告/gtask/data/Task.doc deleted file mode 100644 index 72068a0..0000000 --- a/doc/精读报告/gtask/data/Task.doc +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.data; - -import android.database.Cursor; -import android.text.TextUtils; -import android.util.Log; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.DataConstants; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.gtask.exception.ActionFailureException; -import net.micode.notes.tool.GTaskStringUtils; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - - -public class Task extends Node { - private static final String TAG = Task.class.getSimpleName(); - - private boolean mCompleted;//是否完成 - - private String mNotes; - - private JSONObject mMetaInfo;//将在实例中存储数据的类型 - - private Task mPriorSibling;//对应的优先兄弟Task的指针(待完善) - - private TaskList mParent;//所在的任务列表的指针 - - public Task() { - super(); - mCompleted = false; - mNotes = null; - mPriorSibling = null;//TaskList中当前Task前面的Task的指针 - mParent = null;//当前Task所在的TaskList - mMetaInfo = null; - } - - public JSONObject getCreateAction(int actionId) { - JSONObject js = new JSONObject(); - - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - - // action_id - js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // index - js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); - - // entity_delta - JSONObject entity = new JSONObject(); - entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); - entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); - entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, - GTaskStringUtils.GTASK_JSON_TYPE_TASK); - if (getNotes() != null) { - entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); - } - js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - - // parent_id - js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); - - // dest_parent_type - js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, - GTaskStringUtils.GTASK_JSON_TYPE_GROUP); - - // list_id - js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); - - // prior_sibling_id - if (mPriorSibling != null) { - js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); - } - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to generate task-create jsonobject"); - } - - return js; - } - - public JSONObject getUpdateAction(int actionId) { - JSONObject js = new JSONObject(); - - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - - // action_id - js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // id - js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - - // entity_delta - JSONObject entity = new JSONObject(); - entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); - if (getNotes() != null) { - entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); - } - entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); - js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to generate task-update jsonobject"); - } - - return js; - } - - public void setContentByRemoteJSON(JSONObject js) { - if (js != null) { - try { - // id - if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { - setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); - } - - // last_modified - if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { - setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); - } - - // name - if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { - setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); - } - - // notes - if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { - setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); - } - - // deleted - if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { - setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); - } - - // completed - if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { - setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); - } - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to get task content from jsonobject"); - } - } - } - - public void setContentByLocalJSON(JSONObject js) { - if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) - || !js.has(GTaskStringUtils.META_HEAD_DATA)) { - Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); - } - - try { - JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); - - if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { - Log.e(TAG, "invalid type"); - return; - } - - for (int i = 0; i < dataArray.length(); i++) { - JSONObject data = dataArray.getJSONObject(i); - if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { - setName(data.getString(DataColumns.CONTENT)); - break; - } - } - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - } - } - - public JSONObject getLocalJSONFromContent() { - String name = getName(); - try { - if (mMetaInfo == null) { - // new task created from web - if (name == null) { - Log.w(TAG, "the note seems to be an empty one"); - return null; - } - - JSONObject js = new JSONObject(); - JSONObject note = new JSONObject(); - JSONArray dataArray = new JSONArray(); - JSONObject data = new JSONObject(); - data.put(DataColumns.CONTENT, name); - dataArray.put(data); - js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); - note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); - js.put(GTaskStringUtils.META_HEAD_NOTE, note); - return js; - } else { - // synced task - JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); - - for (int i = 0; i < dataArray.length(); i++) { - JSONObject data = dataArray.getJSONObject(i); - if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { - data.put(DataColumns.CONTENT, getName()); - break; - } - } - - note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); - return mMetaInfo; - } - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - return null; - } - } - - public void setMetaInfo(MetaData metaData) { - if (metaData != null && metaData.getNotes() != null) { - try { - mMetaInfo = new JSONObject(metaData.getNotes()); - } catch (JSONException e) { - Log.w(TAG, e.toString()); - mMetaInfo = null; - } - } - } - - public int getSyncAction(Cursor c) { - try { - JSONObject noteInfo = null; - if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) { - noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - } - - if (noteInfo == null) { - Log.w(TAG, "it seems that note meta has been deleted"); - return SYNC_ACTION_UPDATE_REMOTE; - } - - if (!noteInfo.has(NoteColumns.ID)) { - Log.w(TAG, "remote note id seems to be deleted"); - return SYNC_ACTION_UPDATE_LOCAL; - } - - // validate the note id now - if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { - Log.w(TAG, "note id doesn't match"); - return SYNC_ACTION_UPDATE_LOCAL; - } - - if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update - if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side - return SYNC_ACTION_NONE; - } else { - // apply remote to local - return SYNC_ACTION_UPDATE_LOCAL; - } - } else { - // validate gtask id - if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { - Log.e(TAG, "gtask id doesn't match"); - return SYNC_ACTION_ERROR; - } - if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only - return SYNC_ACTION_UPDATE_REMOTE; - } else { - return SYNC_ACTION_UPDATE_CONFLICT; - } - } - } catch (Exception e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - } - - return SYNC_ACTION_ERROR; - } - - public boolean isWorthSaving() { - return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) - || (getNotes() != null && getNotes().trim().length() > 0); - } - - public void setCompleted(boolean completed) { - this.mCompleted = completed; - } - - public void setNotes(String notes) { - this.mNotes = notes; - } - - public void setPriorSibling(Task priorSibling) { - this.mPriorSibling = priorSibling; - } - - public void setParent(TaskList parent) { - this.mParent = parent; - } - - public boolean getCompleted() { - return this.mCompleted; - } - - public String getNotes() { - return this.mNotes; - } - - public Task getPriorSibling() { - return this.mPriorSibling; - } - - public TaskList getParent() { - return this.mParent; - } - -} diff --git a/doc/精读报告/gtask/data/TaskList.doc b/doc/精读报告/gtask/data/TaskList.doc deleted file mode 100644 index 37dad1c..0000000 --- a/doc/精读报告/gtask/data/TaskList.doc +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.data; - -import android.database.Cursor; -import android.util.Log; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.gtask.exception.ActionFailureException; -import net.micode.notes.tool.GTaskStringUtils; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; - - -public class TaskList extends Node { - private static final String TAG = TaskList.class.getSimpleName();//tag标记 - - private int mIndex;//当前TaskList的指针 - - private ArrayList mChildren;//类中主要的保存数据的单元,用来实现一个以Task为元素的ArrayList - - public TaskList() { - super(); - mChildren = new ArrayList(); - mIndex = 1; - } - /* (non-Javadoc) - * @see net.micode.notes.gtask.data.Node#getCreateAction(int) - * 生成并返回一个包含了一定数据的JSONObject实体 - */ - public JSONObject getCreateAction(int actionId) { - JSONObject js = new JSONObject(); - - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - - // action_id - js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // index - js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); - - // entity_delta - JSONObject entity = new JSONObject(); - entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); - entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); - entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, - GTaskStringUtils.GTASK_JSON_TYPE_GROUP); - js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to generate tasklist-create jsonobject"); - } - - return js; - } - - - /* (non-Javadoc) - * @see net.micode.notes.gtask.data.Node#getUpdateAction(int) - * 生成并返回一个包含了一定数据的JSONObject实体 - */ - public JSONObject getUpdateAction(int actionId) { - JSONObject js = new JSONObject(); - - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - - // action_id - js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // id - js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - - // entity_delta - JSONObject entity = new JSONObject(); - entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); - entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); - js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to generate tasklist-update jsonobject"); - } - - return js; - } - - public void setContentByRemoteJSON(JSONObject js) { - if (js != null) { - try { - // id - if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { - setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); - } - - // last_modified - if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { - setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); - } - - // name - if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { - setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); - } - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to get tasklist content from jsonobject"); - } - } - } - - public void setContentByLocalJSON(JSONObject js) { - if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { - Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); - } - - try { - JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - - if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { - String name = folder.getString(NoteColumns.SNIPPET); - setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); - } else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { - if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) - setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); - else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER) - setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_CALL_NOTE); - else - Log.e(TAG, "invalid system folder"); - } else { - Log.e(TAG, "error type"); - } - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - } - } - - public JSONObject getLocalJSONFromContent() { - try { - JSONObject js = new JSONObject(); - JSONObject folder = new JSONObject(); - - String folderName = getName(); - if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) - folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(), - folderName.length()); - folder.put(NoteColumns.SNIPPET, folderName); - if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) - || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) - folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - else - folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - - js.put(GTaskStringUtils.META_HEAD_NOTE, folder); - - return js; - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - return null; - } - } - - public int getSyncAction(Cursor c) { - try { - if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update - if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side - return SYNC_ACTION_NONE; - } else { - // apply remote to local - return SYNC_ACTION_UPDATE_LOCAL; - } - } else { - // validate gtask id - if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { - Log.e(TAG, "gtask id doesn't match"); - return SYNC_ACTION_ERROR; - } - if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only - return SYNC_ACTION_UPDATE_REMOTE; - } else { - // for folder conflicts, just apply local modification - return SYNC_ACTION_UPDATE_REMOTE; - } - } - } catch (Exception e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - } - - return SYNC_ACTION_ERROR; - } - /* - * 功能:获得TaskList的大小,即mChildren的大小 - */ - public int getChildTaskCount() { - return mChildren.size(); - } - /** - * @param task - * @return 返回值为是否成功添加任务。 - * 功能:在当前任务表末尾添加新的任务。 - */ - public boolean addChildTask(Task task) { - boolean ret = false; - if (task != null && !mChildren.contains(task)) { - ret = mChildren.add(task); - if (ret) { - // need to set prior sibling and parent - task.setPriorSibling(mChildren.isEmpty() ? null : mChildren - .get(mChildren.size() - 1)); - task.setParent(this); - //注意:每一次ArrayList的变化都要紧跟相关Task中PriorSibling的更改 - //,接下来几个函数都有相关操作 - - } - } - return ret; - } - /** - * @param task - * @param index - * @return - * 功能:在当前任务表的指定位置添加新的任务。 - */ - public boolean addChildTask(Task task, int index) { - if (index < 0 || index > mChildren.size()) { - Log.e(TAG, "add child task: invalid index"); - return false; - } - - int pos = mChildren.indexOf(task); - if (task != null && pos == -1) { - mChildren.add(index, task); - - // update the task list - Task preTask = null; - Task afterTask = null; - if (index != 0) - preTask = mChildren.get(index - 1); - if (index != mChildren.size() - 1) - afterTask = mChildren.get(index + 1); - - task.setPriorSibling(preTask); - if (afterTask != null) - afterTask.setPriorSibling(task); - } - - return true; - } - /** - * @param task - * @return 返回删除是否成功 - * 功能:删除TaskList中的一个Task - */ - /** - * @param task - * @param index - * @return - * 功能:将当前TaskList中含有的某个Task移到index位置 - */ - public boolean removeChildTask(Task task) { - boolean ret = false; - int index = mChildren.indexOf(task); - if (index != -1) { - ret = mChildren.remove(task); - - if (ret) { - // reset prior sibling and parent - task.setPriorSibling(null); - task.setParent(null); - - // update the task list - if (index != mChildren.size()) { - mChildren.get(index).setPriorSibling( - index == 0 ? null : mChildren.get(index - 1)); - } - } - } - return ret; - } - /** - * @param gid - * @return返回寻找结果 - * 功能:按gid寻找Task - */ - public boolean moveChildTask(Task task, int index) { - - if (index < 0 || index >= mChildren.size()) { - Log.e(TAG, "move child task: invalid index"); - return false; - } - - int pos = mChildren.indexOf(task); - if (pos == -1) { - Log.e(TAG, "move child task: the task should in the list"); - return false; - } - - if (pos == index) - return true; - return (removeChildTask(task) && addChildTask(task, index)); - //利用已实现好的功能完成当下功能; - } - - - /** - * @param task - * @return - * 功能:返回指定Task的index - */ - public Task findChildTaskByGid(String gid) { - for (int i = 0; i < mChildren.size(); i++) { - Task t = mChildren.get(i); - if (t.getGid().equals(gid)) { - return t; - } - } - return null; - } - - - /** - * @param index - * @return - * 功能:返回指定index的Task - */ - public int getChildTaskIndex(Task task) { - return mChildren.indexOf(task); - } - - public Task getChildTaskByIndex(int index) { - if (index < 0 || index >= mChildren.size()) { - Log.e(TAG, "getTaskByIndex: invalid index"); - return null; - } - return mChildren.get(index); - } - - - /** - * @param gid - * @return - * 功能:返回指定gid的Task - */ - public Task getChilTaskByGid(String gid) { - for (Task task : mChildren) {//一种常见的ArrayList的遍历方法(四种,见精读笔记) - - if (task.getGid().equals(gid)) - return task; - } - return null; - } - - - - public ArrayList getChildTaskList() { - return this.mChildren; - } - - public void setIndex(int index) { - this.mIndex = index; - } - - public int getIndex() { - return this.mIndex; - } -} diff --git a/doc/精读报告/tool/BackupUtils.doc b/doc/精读报告/tool/BackupUtils.doc new file mode 100644 index 0000000..5abf325 --- /dev/null +++ b/doc/精读报告/tool/BackupUtils.doc @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +import android.content.Context; +import android.database.Cursor; +import android.os.Environment; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.util.Log; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; + + +public class BackupUtils { + private static final String TAG = "BackupUtils";//定义一个常量,用于日志输出的标签 + // Singleton stuff + private static BackupUtils sInstance; // 定义一个静态变量,用于保存单例对象的引用 + + public static synchronized BackupUtils getInstance(Context context) {// 定义一个静态同步方法,用于获取单例对象的实例 + if (sInstance == null) {// 如果单例对象还没有创建 + sInstance = new BackupUtils(context);// 就用传入的上下文参数创建一个新的单例对象 + } + return sInstance;// 返回单例对象的引用 + } + + /** + * Following states are signs to represents backup or restore + * status + */ + // Currently, the sdcard is not mounted + public static final int STATE_SD_CARD_UNMOUONTED = 0;// 定义一个常量,表示当前的状态是 SD 卡没有挂载 + // The backup file not exist + public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;// 定义一个常量,表示当前的状态是备份文件不存在 + // The data is not well formated, may be changed by other programs + public static final int STATE_DATA_DESTROIED = 2;// 定义一个常量,表示当前的状态是数据格式不正确,可能被其他程序修改 + // Some run-time exception which causes restore or backup fails + public static final int STATE_SYSTEM_ERROR = 3;// 定义一个常量,表示当前的状态是系统错误,导致恢复或备份失败 + // Backup or restore success + public static final int STATE_SUCCESS = 4;// 定义一个常量,表示当前的状态是恢复或备份成功 + + private TextExport mTextExport;// 定义一个私有变量,用于保存一个 TextExport 对象的引用 + + private BackupUtils(Context context) { + // 定义一个私有构造器,用于创建 BackupUtils 对象mTextExport = new TextExport(context);// 用传入的上下文参数创建一个 TextExport 对象,并赋值给 mTextExport 变量 + } + + private static boolean externalStorageAvailable() {// 定义一个私有静态方法,用于判断外部存储是否可用 + return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); + } + + public int exportToText() { + // 定义一个公有方法,用于导出文本文件 + return mTextExport.exportToText();//调用 mTextExport 对象的 exportToText 方法,并返回其结果 + } + + public String getExportedTextFileName() {// 定义一个公有方法,用于获取导出的文本文件名 + return mTextExport.mFileName;// 返回 mTextExport 对象的 mFileName 变量的值 + } + + public String getExportedTextFileDir() {// 定义一个公有方法,用于获取导出的文本文件目录 + return mTextExport.mFileDirectory;// 返回 mTextExport 对象的 mFileDirectory 变量的值 + } + + private static class TextExport {// 定义一个私有静态内部类,用于实现文本文件的导出功能 + private static final String[] NOTE_PROJECTION = { // 定义一个私有静态常量数组,用于指定查询笔记表时需要返回的列名 + NoteColumns.ID, + NoteColumns.MODIFIED_DATE, + NoteColumns.SNIPPET, + NoteColumns.TYPE + }; + + private static final int NOTE_COLUMN_ID = 0;// 定义一个私有静态常量,表示笔记 ID 列在 NOTE_PROJECTION 数组中的索引 + + private static final int NOTE_COLUMN_MODIFIED_DATE = 1;// 定义一个私有静态常量,表示笔记修改日期列在 NOTE_PROJECTION 数组中的索引 + + private static final int NOTE_COLUMN_SNIPPET = 2; // 定义一个私有静态常量,表示笔记摘要列在 NOTE_PROJECTION 数组中的索引 + + private static final String[] DATA_PROJECTION = {// 定义一个私有静态常量数组,用于指定查询数据表时需要返回的列名 + DataColumns.CONTENT, + DataColumns.MIME_TYPE, + DataColumns.DATA1, + DataColumns.DATA2, + DataColumns.DATA3, + DataColumns.DATA4, + }; + + private static final int DATA_COLUMN_CONTENT = 0;// 定义一个私有静态常量,表示数据内容列在 DATA_PROJECTION 数组中的索引 + + private static final int DATA_COLUMN_MIME_TYPE = 1;// 定义一个私有静态常量,表示数据 MIME 类型列在 DATA_PROJECTION 数组中的索引 + + private static final int DATA_COLUMN_CALL_DATE = 2;// 定义一个私有静态常量,表示数据 MIME 类型列在 DATA_PROJECTION 数组中的索引 + + private static final int DATA_COLUMN_PHONE_NUMBER = 4;// 定义一个私有静态常量,表示数据 3 列在 DATA_PROJECTION 数组中的索引,用于存储电话号码 + + private final String [] TEXT_FORMAT;// 定义一个私有不可变数组,用于存储文本文件的格式字符串 + private static final int FORMAT_FOLDER_NAME = 0;// 定义一个私有静态常量,表示文件夹名称格式在 TEXT_FORMAT 数组中的索引 + private static final int FORMAT_NOTE_DATE = 1;// 定义一个私有静态常量,表示笔记日期格式在 TEXT_FORMAT 数组中的索引 + private static final int FORMAT_NOTE_CONTENT = 2;// 定义一个私有静态常量,表示笔记内容格式在 TEXT_FORMAT 数组中的索引 + + private Context mContext;// 定义一个私有变量,用于保存上下文对象的引用 + private String mFileName;// 定义一个私有变量,用于保存导出的文本文件名 + private String mFileDirectory;// 定义一个私有变量,用于保存导出的文本文件目录 + + public TextExport(Context context) {// 定义一个公有构造器,用于创建 TextExport 对象 + TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);// 用传入的上下文参数获取资源数组,并赋值给 TEXT_FORMAT 数组 + mContext = context; // 用传入的上下文参数赋值给 mContext 变量 + mFileName = "";// 初始化 mFileName 变量为空字符串 + mFileDirectory = "";// 初始化 mFileDirectory 变量为空字符串 + } + + private String getFormat(int id) { + return TEXT_FORMAT[id]; + }// 定义一个私有方法,用于根据索引获取格式字符串 + + /** + * Export the folder identified by folder id to text + */ + private void exportFolderToText(String folderId, PrintStream ps) {// 定义一个私有方法,用于导出指定文件夹 ID 的文本文件,需要传入文件夹 ID 和打印流对象作为参数 + // Query notes belong to this folder + Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,// 用 mContext 变量获取内容解析器,并查询笔记表的 URI + NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {// 指定需要返回的列名数组为 NOTE_PROJECTION + folderId + }, null); + + if (notesCursor != null) { + if (notesCursor.moveToFirst()) { + do { + // Print note's last modified date + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(// 用打印流对象打印一行字符串,格式化为笔记日期格式,内容为游标当前行的修改日期列的值,转换为指定的日期时间格式 + mContext.getString(R.string.format_datetime_mdhm), + notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));// 用游标获取当前行的修改日期列的值,转换为长整型 + // Query data belong to this note + String noteId = notesCursor.getString(NOTE_COLUMN_ID);// 用游标获取当前行的笔记 ID 列的值,转换为字符串,并赋值给 noteId 变量 + exportNoteToText(noteId, ps);// 调用 exportNoteToText 方法,传入笔记 ID 和打印流对象作为参数,导出该笔记的文本文件 + } while (notesCursor.moveToNext());// 循环条件为游标移动到下一行,直到没有更多行为止 + } + notesCursor.close();//关闭游标对象,释放资源 + } + } + + /** + * Export note identified by id to a print stream + */ + private void exportNoteToText(String noteId, PrintStream ps) { // 定义一个私有方法,用于导出指定笔记 ID 的文本文件,需要传入笔记 ID 和打印流对象作为参数 + Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,// 用 mContext 变量获取内容解析器,并查询数据表的 URI + DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {// 指定查询条件为笔记 ID 等于传入的笔记 ID noteId }, null); // 指定排序方式为 null + noteId + }, null); + + if (dataCursor != null) { // 如果数据游标不为空 + if (dataCursor.moveToFirst()) { // 如果数据游标移动到第一行 + do { + String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); // 获取数据的类型 + if (DataConstants.CALL_NOTE.equals(mimeType)) { // 如果数据是通话记录 + // 打印电话号码 + String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); // 获取电话号码 + long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); // 获取通话日期 + String location = dataCursor.getString(DATA_COLUMN_CONTENT); // 获取通话附件位置 + + if (!TextUtils.isEmpty(phoneNumber)) { // 如果电话号码不为空 + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + phoneNumber)); // 格式化并打印电话号码 + } + // 打印通话日期 + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat + .format(mContext.getString(R.string.format_datetime_mdhm), + callDate))); // 格式化并打印通话日期 + // 打印通话附件位置 + if (!TextUtils.isEmpty(location)) { // 如果通话附件位置不为空 + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + location)); // 格式化并打印通话附件位置 + } + } else if (DataConstants.NOTE.equals(mimeType)) { // 如果数据是便签 + String content = dataCursor.getString(DATA_COLUMN_CONTENT); // 获取便签内容 + if (!TextUtils.isEmpty(content)) { // 如果便签内容不为空 + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + content)); // 格式化并打印便签内容 + } + } + } while (dataCursor.moveToNext()); // 循环直到数据游标移动到最后一行 + } + dataCursor.close(); // 关闭数据游标 + } + // print a line separator between note + try { + ps.write(new byte[] { + Character.LINE_SEPARATOR, Character.LETTER_NUMBER + }); // 尝试写入一个字节数组,包含换行符和字母数字 + } catch (IOException e) { // 如果发生输入输出异常 + Log.e(TAG, e.toString()); // 打印错误日志 + } + } + + /** + * Note will be exported as text which is user readable + */ + public int exportToText() { // 定义一个导出文本的方法 + if (!externalStorageAvailable()) { // 如果外部存储不可用 + Log.d(TAG, "Media was not mounted"); // 打印调试日志 + return STATE_SD_CARD_UNMOUONTED; // 返回SD卡未挂载的状态 + } + + PrintStream ps = getExportToTextPrintStream(); // 获取导出文本的打印流 + if (ps == null) { // 如果打印流为空 + Log.e(TAG, "get print stream error"); // 打印错误日志 + return STATE_SYSTEM_ERROR; // 返回系统错误的状态 + } + // 首先导出文件夹和它们的便签 + Cursor folderCursor = mContext.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, + "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " + + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " + + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); // 查询文件夹类型的便签,排除回收站和通话记录文件夹 + + if (folderCursor != null) { // 如果文件夹游标不为空 + if (folderCursor.moveToFirst()) { // 如果文件夹游标移动到第一行 + do { + // 打印文件夹的名字 + String folderName = ""; + if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { // 如果文件夹是通话记录文件夹 + folderName = mContext.getString(R.string.call_record_folder_name); // 获取通话记录文件夹的名字 + } else { // 否则 + folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); // 获取文件夹的摘要作为名字 + } + if (!TextUtils.isEmpty(folderName)) { // 如果文件夹名字不为空 + ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); // 格式化并打印文件夹名字 + } + String folderId = folderCursor.getString(NOTE_COLUMN_ID); // 获取文件夹的ID + exportFolderToText(folderId, ps); // 调用导出文件夹到文本的方法,传入文件夹ID和打印流 + } while (folderCursor.moveToNext()); // 循环直到文件夹游标移动到最后一行 + } + folderCursor.close(); // 关闭文件夹游标 + } + + // Export notes in root's folder + Cursor noteCursor = mContext.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, + NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + + "=0", null, null); // 查询便签类型的便签,且没有父文件夹 + + if (noteCursor != null) { // 如果便签游标不为空 + if (noteCursor.moveToFirst()) { // 如果便签游标移动到第一行 + do { + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( + mContext.getString(R.string.format_datetime_mdhm), + noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); // 格式化并打印便签的修改日期 + // 查询属于这个便签的数据 + String noteId = noteCursor.getString(NOTE_COLUMN_ID); // 获取便签的ID + exportNoteToText(noteId, ps); // 调用导出便签到文本的方法,传入便签ID和打印流 + } while (noteCursor.moveToNext()); // 循环直到便签游标移动到最后一行 + } + noteCursor.close(); // 关闭便签游标 + } + ps.close(); // 关闭打印流 + + return STATE_SUCCESS; // 返回成功的状态 + } + + /** + * Get a print stream pointed to the file {@generateExportedTextFile} + */ + private PrintStream getExportToTextPrintStream() { // 定义一个获取导出文本的打印流的方法 + File file = generateFileMountedOnSDcard(mContext, R.string.file_path, + R.string.file_name_txt_format); // 调用生成挂载在SD卡上的文件的方法,传入上下文,文件路径和文件名格式 + if (file == null) { // 如果文件为空 + Log.e(TAG, "create file to exported failed"); // 打印错误日志 + return null; // 返回空值 + } + mFileName = file.getName(); // 获取文件的名字 + mFileDirectory = mContext.getString(R.string.file_path); // 获取文件的目录 + PrintStream ps = null; // 声明一个打印流变量 + try { + FileOutputStream fos = new FileOutputStream(file); // 创建一个文件输出流,传入文件 + ps = new PrintStream(fos); // 创建一个打印流,传入文件输出流 + } catch (FileNotFoundException e) { // 如果发生文件未找到异常 + e.printStackTrace(); // 打印异常堆栈 + return null; // 返回空值 + } catch (NullPointerException e) { // 如果发生空指针异常 + e.printStackTrace(); // 打印异常堆栈 + return null; // 返回空值 + } + return ps; // 返回打印流 + } + } + + /** + * Generate the text file to store imported data + */ + private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { // 定义一个生成挂载在SD卡上的文件的方法,传入上下文,文件路径资源ID和文件名格式资源ID + StringBuilder sb = new StringBuilder(); // 创建一个字符串构建器 + sb.append(Environment.getExternalStorageDirectory()); // 追加外部存储目录 + sb.append(context.getString(filePathResId)); // 追加文件路径字符串 + File filedir = new File(sb.toString()); // 创建一个文件目录对象,传入字符串构建器的内容 + sb.append(context.getString( + fileNameFormatResId, + DateFormat.format(context.getString(R.string.format_date_ymd), + System.currentTimeMillis()))); // 追加文件名字符串,根据日期格式化 + File file = new File(sb.toString()); // 创建一个文件对象,传入字符串构建器的内容 + + try { + if (!filedir.exists()) { // 如果文件目录不存在 + filedir.mkdir(); // 创建文件目录 + } + if (!file.exists()) { // 如果文件不存在 + file.createNewFile(); // 创建新文件 + } + return file; // 返回文件对象 + } catch (SecurityException e) { // 如果发生安全异常 + e.printStackTrace(); // 打印异常堆栈 + } catch (IOException e) { // 如果发生输入输出异常 + e.printStackTrace(); // 打印异常堆栈 + } + + return null; // 返回空值 + } +} + + diff --git a/doc/精读报告/tool/DataUtils.doc b/doc/精读报告/tool/DataUtils.doc new file mode 100644 index 0000000..472fd39 --- /dev/null +++ b/doc/精读报告/tool/DataUtils.doc @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.os.RemoteException; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.CallNote; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; + +import java.util.ArrayList; +import java.util.HashSet; + + +public class DataUtils { // 定义一个数据工具类 + public static final String TAG = "DataUtils"; // 定义一个静态常量字符串,表示日志标签 + public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { // 定义一个静态方法,批量删除便签,传入内容解析器和便签ID的集合 + if (ids == null) { // 如果ID集合为空 + Log.d(TAG, "the ids is null"); // 打印调试日志 + return true; // 返回真值 + } + if (ids.size() == 0) { // 如果ID集合的大小为零 + Log.d(TAG, "no id is in the hashset"); // 打印调试日志 + return true; // 返回真值 + } + + ArrayList operationList = new ArrayList(); // 创建一个内容提供者操作的列表 + for (long id : ids) { // 遍历ID集合中的每个ID + if(id == Notes.ID_ROOT_FOLDER) { // 如果ID是根文件夹的ID + Log.e(TAG, "Don't delete system folder root"); // 打印错误日志 + continue; // 跳过本次循环 + } + ContentProviderOperation.Builder builder = ContentProviderOperation + .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); // 创建一个内容提供者操作的构建器,指定删除便签的URI和ID + operationList.add(builder.build()); // 把构建器构建出来的操作添加到操作列表中 + } + try { + ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); // 调用内容解析器的批量应用方法,传入便签的授权和操作列表,获取结果数组 + if (results == null || results.length == 0 || results[0] == null) { // 如果结果数组为空或长度为零或第一个元素为空 + Log.d(TAG, "delete notes failed, ids:" + ids.toString()); // 打印调试日志,显示失败的ID集合 + return false; // 返回假值 + } + return true; // 返回真值 + } catch (RemoteException e) { // 如果发生远程异常 + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); // 打印错误日志,显示异常信息 + } catch (OperationApplicationException e) { // 如果发生操作应用异常 + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); // 打印错误日志,显示异常信息 + } + return false; // 返回假值 + } + + public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { // 定义一个静态方法,移动便签到文件夹,传入内容解析器,便签ID,源文件夹ID和目标文件夹ID + ContentValues values = new ContentValues(); // 创建一个内容值对象 + values.put(NoteColumns.PARENT_ID, desFolderId); // 把目标文件夹ID作为父ID放入内容值中 + values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); // 把源文件夹ID作为原始父ID放入内容值中 + values.put(NoteColumns.LOCAL_MODIFIED, 1); // 把本地修改标志设为1放入内容值中 + resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); // 调用内容解析器的更新方法,传入便签的URI和ID,内容值,空的选择和选择参数 + } + + public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids, + long folderId) { // 定义一个静态方法,批量移动到文件夹,传入内容解析器,便签ID的集合和文件夹ID + if (ids == null) { // 如果ID集合为空 + Log.d(TAG, "the ids is null"); // 打印调试日志 + return true; // 返回真值 + } + + ArrayList operationList = new ArrayList(); // 创建一个内容提供者操作的列表 + for (long id : ids) { // 遍历ID集合中的每个ID + ContentProviderOperation.Builder builder = ContentProviderOperation + .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); // 创建一个内容提供者操作的构建器,指定更新便签的URI和ID + builder.withValue(NoteColumns.PARENT_ID, folderId); // 把文件夹ID作为父ID放入构建器中 + builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); // 把本地修改标志设为1放入构建器中 + operationList.add(builder.build()); // 把构建器构建出来的操作添加到操作列表中 + } + + try { + ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); // 调用内容解析器的批量应用方法,传入便签的授权和操作列表,获取结果数组 + if (results == null || results.length == 0 || results[0] == null) { // 如果结果数组为空或长度为零或第一个元素为空 + Log.d(TAG, "delete notes failed, ids:" + ids.toString()); // 打印调试日志,显示失败的ID集合 + return false; // 返回假值 + } + return true; // 返回真值 + } catch (RemoteException e) { // 如果发生远程异常 + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); // 打印错误日志,显示异常信息 + } catch (OperationApplicationException e) { // 如果发生操作应用异常 + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); // 打印错误日志,显示异常信息 + } + return false; // 返回假值 + } + + /** + * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} + */ + public static int getUserFolderCount(ContentResolver resolver) { // 定义一个静态方法,获取用户文件夹的数量,传入内容解析器 + Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, + new String[] { "COUNT(*)" }, + NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", + new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, + null); // 查询便签的URI,返回文件夹类型且不在回收站的便签的数量 + + int count = 0; // 声明一个整型变量,表示数量 + if(cursor != null) { // 如果游标不为空 + if(cursor.moveToFirst()) { // 如果游标移动到第一行 + try { + count = cursor.getInt(0); // 获取游标的第一列的值,即数量 + } catch (IndexOutOfBoundsException e) { // 如果发生索引越界异常 + Log.e(TAG, "get folder count failed:" + e.toString()); // 打印错误日志,显示异常信息 + } finally { + cursor.close(); // 最终关闭游标 + } + } + } + return count; // 返回数量 + } + + public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { // 定义一个静态方法,判断便签是否在数据库中可见,传入内容解析器,便签ID和类型 + Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), + null, + NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, + new String [] {String.valueOf(type)}, + null); // 查询便签的URI和ID,返回指定类型且不在回收站的便签 + + boolean exist = false; // 声明一个布尔型变量,表示是否存在 + if (cursor != null) { // 如果游标不为空 + if (cursor.getCount() > 0) { // 如果游标的数量大于零 + exist = true; // 把存在设为真值 + } + cursor.close(); // 关闭游标 + } + return exist; // 返回是否存在 + } + + public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { + // 查询便签的URI和ID,返回便签的所有列 + Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), + null, null, null, null); + + boolean exist = false; // 声明一个布尔型变量,表示是否存在 + if (cursor != null) { // 如果游标不为空 + if (cursor.getCount() > 0) { // 如果游标的数量大于零 + exist = true; // 把存在设为真值 + } + cursor.close(); // 关闭游标 + } + return exist; // 返回是否存在 + } + + + public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { + // 查询数据的URI和ID,返回数据的所有列 + Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), + null, null, null, null); + + boolean exist = false; // 声明一个布尔型变量,表示是否存在 + if (cursor != null) { // 如果游标不为空 + if (cursor.getCount() > 0) { // 如果游标的数量大于零 + exist = true; // 把存在设为真值 + } + cursor.close(); // 关闭游标 + } + return exist; // 返回是否存在 + } + + public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { + // 查询便签的URI,返回文件夹类型且不在回收站且名称等于指定值的便签 + Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.SNIPPET + "=?", + new String[] { name }, null); + + boolean exist = false; // 声明一个布尔型变量,表示是否存在 + if(cursor != null) { // 如果游标不为空 + if(cursor.getCount() > 0) { // 如果游标的数量大于零 + exist = true; // 把存在设为真值 + } + cursor.close(); // 关闭游标 + } + return exist; // 返回是否存在 + } + public static HashSet getFolderNoteWidget(ContentResolver resolver, long folderId) { + // 查询便签的URI,返回指定父ID的便签的小部件ID和类型 + Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, + new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, + NoteColumns.PARENT_ID + "=?", + new String[] { String.valueOf(folderId) }, + null); + + HashSet set = null; // 声明一个哈希集合,用于存储便签小部件的属性 + if (c != null) { // 如果游标不为空 + if (c.moveToFirst()) { // 如果游标移动到第一行 + set = new HashSet(); // 创建一个新的哈希集合 + do { + try { + AppWidgetAttribute widget = new AppWidgetAttribute(); // 创建一个新的便签小部件属性对象 + widget.widgetId = c.getInt(0); // 获取游标的第一列的值,即小部件ID + widget.widgetType = c.getInt(1); // 获取游标的第二列的值,即小部件类型 + set.add(widget); // 把便签小部件属性对象添加到哈希集合中 + } catch (IndexOutOfBoundsException e) { // 如果发生索引越界异常 + Log.e(TAG, e.toString()); // 打印错误日志,显示异常信息 + } + } while (c.moveToNext()); // 当游标移动到下一行时,重复上述操作 + } + c.close(); // 关闭游标 + } + return set; // 返回哈希集合 + } + + + public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { + // 查询数据的URI,返回指定便签ID和MIME类型的数据的电话号码 + Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, + new String [] { CallNote.PHONE_NUMBER }, + CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?", + new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE }, + null); + + if (cursor != null && cursor.moveToFirst()) { // 如果游标不为空且移动到第一行 + try { + return cursor.getString(0); // 返回游标的第一列的值,即电话号码 + } catch (IndexOutOfBoundsException e) { // 如果发生索引越界异常 + Log.e(TAG, "Get call number fails " + e.toString()); // 打印错误日志,显示异常信息 + } finally { + cursor.close(); // 最终关闭游标 + } + } + return ""; // 返回空字符串 + } + + public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { + // 查询数据的URI,返回指定通话日期、MIME类型和电话号码的数据的便签ID + Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, + new String [] { CallNote.NOTE_ID }, + CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL(" + + CallNote.PHONE_NUMBER + ",?)", + new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber }, + null); + + if (cursor != null) { // 如果游标不为空 + if (cursor.moveToFirst()) { // 如果游标移动到第一行 + try { + return cursor.getLong(0); // 返回游标的第一列的值,即便签ID + } catch (IndexOutOfBoundsException e) { // 如果发生索引越界异常 + Log.e(TAG, "Get call note id fails " + e.toString()); // 打印错误日志,显示异常信息 + } + } + cursor.close(); // 关闭游标 + } + return 0; // 返回0 + } + + + public static String getSnippetById(ContentResolver resolver, long noteId) { + // 查询便签的URI,返回指定ID的便签的摘要 + Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, + new String [] { NoteColumns.SNIPPET }, + NoteColumns.ID + "=?", + new String [] { String.valueOf(noteId)}, + null); + + if (cursor != null) { // 如果游标不为空 + String snippet = ""; // 声明一个字符串变量,表示摘要 + if (cursor.moveToFirst()) { // 如果游标移动到第一行 + snippet = cursor.getString(0); // 获取游标的第一列的值,即摘要 + } + cursor.close(); // 关闭游标 + return snippet; // 返回摘要 + } + throw new IllegalArgumentException("Note is not found with id: " + noteId); // 抛出非法参数异常,显示错误信息 + } + + + public static String getFormattedSnippet(String snippet) { + if (snippet != null) { // 如果摘要不为空 + snippet = snippet.trim(); // 去掉摘要两端的空格 + int index = snippet.indexOf('\n'); // 查找摘要中第一个换行符的位置 + if (index != -1) { // 如果找到了换行符 + snippet = snippet.substring(0, index); // 截取换行符之前的部分作为新的摘要 + } + } + return snippet; // 返回格式化后的摘要字符串 + } +} diff --git a/doc/精读报告/tool/GTaskStringUtils.doc b/doc/精读报告/tool/GTaskStringUtils.doc new file mode 100644 index 0000000..a147377 --- /dev/null +++ b/doc/精读报告/tool/GTaskStringUtils.doc @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +public class GTaskStringUtils { + + // 定义一些常量字符串,表示Google任务的JSON格式的属性名 + + public final static String GTASK_JSON_ACTION_ID = "action_id"; // 动作ID + + public final static String GTASK_JSON_ACTION_LIST = "action_list"; // 动作列表 + + public final static String GTASK_JSON_ACTION_TYPE = "action_type"; // 动作类型 + + public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; // 创建动作 + + public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; // 获取所有动作 + + public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; // 移动动作 + + public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; // 更新动作 + + public final static String GTASK_JSON_CREATOR_ID = "creator_id"; // 创建者ID + + public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; // 子实体 + + public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; // 客户端版本 + + public final static String GTASK_JSON_COMPLETED = "completed"; // 完成状态 + + public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; // 当前列表ID + + public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; // 默认列表ID + + public final static String GTASK_JSON_DELETED = "deleted"; // 删除状态 + + public final static String GTASK_JSON_DEST_LIST = "dest_list"; // 目标列表 + + public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; // 目标父实体 + + public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; // 目标父实体类型 + + public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; // 实体变化 + + public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; // 实体类型 + + public final static String GTASK_JSON_GET_DELETED = "get_deleted"; // 获取删除状态 + + public final static String GTASK_JSON_ID = "id"; // ID + + public final static String GTASK_JSON_INDEX = "index"; // 索引 + + public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; // 最后修改时间 + + public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; // 最新同步点 + + public final static String GTASK_JSON_LIST_ID = "list_id"; // 列表ID + + public final static String GTASK_JSON_LISTS = "lists"; // 列表 + + public final static String GTASK_JSON_NAME = "name"; // 名称 + + public final static String GTASK_JSON_NEW_ID = "new_id"; // 新ID + + public final static String GTASK_JSON_NOTES = "notes"; // 便签 + + public final static String GTASK_JSON_PARENT_ID = "parent_id"; // 父ID + + public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; // 前一个兄弟ID + + public final static String GTASK_JSON_RESULTS = "results"; // 结果 + + public final static String GTASK_JSON_SOURCE_LIST = "source_list"; // 源列表 + + public final static String GTASK_JSON_TASKS = "tasks"; // 任务 + + public final static String GTASK_JSON_TYPE = "type"; // 类型 + + public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; // 组类型 + + public final static String GTASK_JSON_TYPE_TASK = "TASK"; // 任务类型 + + public final static String GTASK_JSON_USER = "user"; // 用户 + + public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; // MIUI文件夹的前缀 + + public final static String FOLDER_DEFAULT = "Default"; // 默认文件夹的名称 + + public final static String FOLDER_CALL_NOTE = "Call_Note"; // 通话便签文件夹的名称 + + public final static String FOLDER_META = "METADATA"; // 元数据文件夹的名称 + + public final static String META_HEAD_GTASK_ID = "meta_gid"; // 元数据中的Google任务ID的头部 + + public final static String META_HEAD_NOTE = "meta_note"; // 元数据中的便签的头部 + + public final static String META_HEAD_DATA = "meta_data"; // 元数据中的数据的头部 + + public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; // 元数据便签的名称,提示不要更新和删除 + +} diff --git a/doc/精读报告/tool/ResourceParser.doc b/doc/精读报告/tool/ResourceParser.doc new file mode 100644 index 0000000..2708d61 --- /dev/null +++ b/doc/精读报告/tool/ResourceParser.doc @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +import android.content.Context; +import android.preference.PreferenceManager; + +import net.micode.notes.R; +import net.micode.notes.ui.NotesPreferenceActivity; + +public class ResourceParser { + + // 定义一些常量整型,表示便签的背景颜色 + public static final int YELLOW = 0; + public static final int BLUE = 1; + public static final int WHITE = 2; + public static final int GREEN = 3; + public static final int RED = 4; + + // 定义一个常量整型,表示默认的背景颜色 + public static final int BG_DEFAULT_COLOR = YELLOW; + + // 定义一些常量整型,表示便签的文字大小 + public static final int TEXT_SMALL = 0; + public static final int TEXT_MEDIUM = 1; + public static final int TEXT_LARGE = 2; + public static final int TEXT_SUPER = 3; + + // 定义一个常量整型,表示默认的文字大小 + public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; + + // 定义一个静态内部类,用于存储便签的背景资源 + public static class NoteBgResources { + // 定义一个静态整型数组,表示编辑模式下的便签背景资源 + private final static int [] BG_EDIT_RESOURCES = new int [] { + R.drawable.edit_yellow, + R.drawable.edit_blue, + R.drawable.edit_white, + R.drawable.edit_green, + R.drawable.edit_red + }; + + // 定义一个静态整型数组,表示编辑模式下的便签标题背景资源 + private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { + R.drawable.edit_title_yellow, + R.drawable.edit_title_blue, + R.drawable.edit_title_white, + R.drawable.edit_title_green, + R.drawable.edit_title_red + }; + + // 定义一个静态方法,根据ID获取便签背景资源 + public static int getNoteBgResource(int id) { + return BG_EDIT_RESOURCES[id]; + } + + // 定义一个静态方法,根据ID获取便签标题背景资源 + public static int getNoteTitleBgResource(int id) { + return BG_EDIT_TITLE_RESOURCES[id]; + } + } + + // 定义一个静态方法,获取默认的背景ID + public static int getDefaultBgId(Context context) { + // 如果用户设置了随机背景颜色 + if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( + NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { + // 返回一个随机的背景ID + return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length); + } else { + // 否则返回默认的背景ID + return BG_DEFAULT_COLOR; + } + } + + // 这是一个公共静态类,用于存储笔记项的背景资源 + public static class NoteItemBgResources { + // 这是一个整型数组,用于存储第一个笔记项的背景资源 + private final static int [] BG_FIRST_RESOURCES = new int [] { + R.drawable.list_yellow_up, + R.drawable.list_blue_up, + R.drawable.list_white_up, + R.drawable.list_green_up, + R.drawable.list_red_up + }; + + // 这是一个整型数组,用于存储中间笔记项的背景资源 + private final static int [] BG_NORMAL_RESOURCES = new int [] { + R.drawable.list_yellow_middle, + R.drawable.list_blue_middle, + R.drawable.list_white_middle, + R.drawable.list_green_middle, + R.drawable.list_red_middle + }; + + // 这是一个整型数组,用于存储最后一个笔记项的背景资源 + private final static int [] BG_LAST_RESOURCES = new int [] { + R.drawable.list_yellow_down, + R.drawable.list_blue_down, + R.drawable.list_white_down, + R.drawable.list_green_down, + R.drawable.list_red_down, + }; + + // 这是一个整型数组,用于存储单个笔记项的背景资源 + private final static int [] BG_SINGLE_RESOURCES = new int [] { + R.drawable.list_yellow_single, + R.drawable.list_blue_single, + R.drawable.list_white_single, + R.drawable.list_green_single, + R.drawable.list_red_single + }; + + // 这是一个公共静态方法,用于根据id返回第一个笔记项的背景资源 + public static int getNoteBgFirstRes(int id) { + return BG_FIRST_RESOURCES[id]; + } + + // 这是一个公共静态方法,用于根据id返回最后一个笔记项的背景资源 + public static int getNoteBgLastRes(int id) { + return BG_LAST_RESOURCES[id]; + } + + // 这是一个公共静态方法,用于根据id返回单个笔记项的背景资源 + public static int getNoteBgSingleRes(int id) { + return BG_SINGLE_RESOURCES[id]; + } + + // 这是一个公共静态方法,用于根据id返回中间笔记项的背景资源 + public static int getNoteBgNormalRes(int id) { + return BG_NORMAL_RESOURCES[id]; + } + + // 这是一个公共静态方法,用于返回文件夹的背景资源 + // 这是一个公共静态方法,用于返回文件夹的背景资源 + public static int getFolderBgRes() { + return R.drawable.list_folder; + } + } + + // 这是一个公共静态类,用于存储小部件的背景资源 + public static class WidgetBgResources { + // 这是一个整型数组,用于存储2x2小部件的背景资源 + private final static int [] BG_2X_RESOURCES = new int [] { + R.drawable.widget_2x_yellow, + R.drawable.widget_2x_blue, + R.drawable.widget_2x_white, + R.drawable.widget_2x_green, + R.drawable.widget_2x_red, + }; + + // 这是一个公共静态方法,用于根据id返回2x2小部件的背景资源 + public static int getWidget2xBgResource(int id) { + return BG_2X_RESOURCES[id]; + } + + // 这是一个整型数组,用于存储4x4小部件的背景资源 + private final static int [] BG_4X_RESOURCES = new int [] { + R.drawable.widget_4x_yellow, + R.drawable.widget_4x_blue, + R.drawable.widget_4x_white, + R.drawable.widget_4x_green, + R.drawable.widget_4x_red + }; + + // 这是一个公共静态方法,用于根据id返回4x4小部件的背景资源 + public static int getWidget4xBgResource(int id) { + return BG_4X_RESOURCES[id]; + } + } + + // 这是一个公共静态类,用于存储文本外观的资源 + public static class TextAppearanceResources { + // 这是一个整型数组,用于存储不同大小的文本外观资源 + private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { + R.style.TextAppearanceNormal, + R.style.TextAppearanceMedium, + R.style.TextAppearanceLarge, + R.style.TextAppearanceSuper + }; + + // 这是一个公共静态方法,用于根据id返回文本外观资源 + public static int getTexAppearanceResource(int id) { + /** + * HACKME: Fix bug of store the resource id in shared preference. + * The id may larger than the length of resources, in this case, + * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} + */ + if (id >= TEXTAPPEARANCE_RESOURCES.length) { + return BG_DEFAULT_FONT_SIZE; + } + return TEXTAPPEARANCE_RESOURCES[id]; + } + + // 这是一个公共静态方法,用于返回文本外观资源的大小 + public static int getResourcesSize() { + return TEXTAPPEARANCE_RESOURCES.length; + } + } +} diff --git a/doc/精读报告/ui/AlarmAlertActivity.doc b/doc/精读报告/ui/AlarmAlertActivity.doc new file mode 100644 index 0000000..d3f8954 --- /dev/null +++ b/doc/精读报告/ui/AlarmAlertActivity.doc @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.Intent; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.PowerManager; +import android.provider.Settings; +import android.view.Window; +import android.view.WindowManager; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.tool.DataUtils; + +import java.io.IOException; + + +public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + //. 继承了Activity类,改类实现了OnClickListener和 OnDismissListener两个接口,OnClickListener接口用来监听点击事件,OnDismissListener接口用来监听关闭对话框事件 + private long mNoteId;//文本在数据库中的ID号 + private String mSnippet;//闹钟提示时显示的文本 + private static final int SNIPPET_PREW_MAX_LEN = 60;//文本最大长度 + MediaPlayer mPlayer; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState);//Bundle类型的数据与Map类型的数据相似,都是以key-value的形式存储数据的 + //onsaveInstanceState方法是用来保存Activity的状态的 + //能从onCreate的参数savedInsanceState中获得状态数据 + requestWindowFeature(Window.FEATURE_NO_TITLE);//界面显示无标题 + + final Window win = getWindow(); + win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + + if (!isScreenOn()) { + win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + //保持屏幕亮屏 + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + //唤醒屏幕 + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + //允许亮屏时锁屏 + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); + }//手机息屏后若闹钟铃响则亮屏 + + Intent intent = getIntent(); + + try { + mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); + //根据ID从数据库中获取标签的内容 + //getContentResolver()是实现数据共享,实例存储 + mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, + SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) + : mSnippet;//判断标签片段是否达到符合长度 + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return; + } + + mPlayer = new MediaPlayer(); + if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { + showActionDialog();//弹出对话框 + playAlarmSound();//闹钟发出提示音 + } else { + finish();//完成闹钟动作 + } + } + + private boolean isScreenOn() { + //判断屏幕是否锁屏,调用系统函数判断,最后返回值是布尔类型 + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + return pm.isScreenOn(); + } + + private void playAlarmSound() { + //闹钟发出提示音 + Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + //调用系统的铃声管理URI,得到闹钟提示音 + 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); + //根据Uri设置多媒体数据来源 + 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 + e.printStackTrace(); + } catch (IllegalStateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + private void showActionDialog() { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + //AlertDialog的构造方法全部是Protected的 + //所以不能直接通过new一个AlertDialog来创建出一个AlertDialog。 + //要创建一个AlertDialog,就要用到AlertDialog.Builder中的create()方法 + //如这里的dialog就是新建了一个AlertDialog + dialog.setTitle(R.string.app_name);//为对话框设置标题 + dialog.setMessage(mSnippet);//为对话框设置内容 + dialog.setPositiveButton(R.string.notealert_ok, this);//给对话框添加"Yes"按钮 + if (isScreenOn()) { + dialog.setNegativeButton(R.string.notealert_enter, this); + }//对话框添加"No"按钮 + dialog.show().setOnDismissListener(this); + } + + public void onClick(DialogInterface dialog, int which) { + switch (which) { + //用which来选择click后下一步的操作 + case DialogInterface.BUTTON_NEGATIVE://这是取消操作 + Intent intent = new Intent(this, NoteEditActivity.class);//实现两个类间的数据传输 + intent.setAction(Intent.ACTION_VIEW);//设置动作属性 + intent.putExtra(Intent.EXTRA_UID, mNoteId);//实现key-value对 + //EXTRA_UID为key;mNoteId为键 + startActivity(intent);//开始动作 + break; + default://这是确定操作 + break; + } + } + + public void onDismiss(DialogInterface dialog) {//忽略 + stopAlarmSound();//停止闹钟声音 + finish();//完成该动作 + } + + private void stopAlarmSound() { + if (mPlayer != null) { + mPlayer.stop();//停止播放 + mPlayer.release();//释放MediaPlayer对象 + mPlayer = null; + } + } +} diff --git a/doc/精读报告/ui/AlarmInitReceiver.doc b/doc/精读报告/ui/AlarmInitReceiver.doc new file mode 100644 index 0000000..5c70518 --- /dev/null +++ b/doc/精读报告/ui/AlarmInitReceiver.doc @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + + +public class AlarmInitReceiver extends BroadcastReceiver { + + private static final String [] PROJECTION = new String [] { + NoteColumns.ID, + NoteColumns.ALERTED_DATE + }; + //对数据库的操作,调用标签ID和闹钟时间 + private static final int COLUMN_ID = 0; + private static final int COLUMN_ALERTED_DATE = 1; + + @Override + public void onReceive(Context context, Intent intent) { + long currentDate = System.currentTimeMillis();//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) },//将long变量currentDate转化为字符串 + null); +//Cursor在这里的作用是通过查找数据库中的标签内容,找到和当前系统时间相等的标签 + if (c != null) {//当c != null的时候,将cursor移动到开始处,然后执行相关对信息的读取工作,直至读取结束,然后关闭该cursor + if (c.moveToFirst()) {//游标移动到开始位置 + do { + long alertDate = c.getLong(COLUMN_ALERTED_DATE);//获取便签的提醒时间 + Intent sender = new Intent(context, AlarmReceiver.class);//新建一个intent类 来指向alarmreceiver 来传输数据 + sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));//设置数据为便签的uri和id + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + //使用了PendingIntent方法,得到可以延时向之前定义的Intent sender发送广播的PendingIntent实例Pendingintent + AlarmManager alermManager = (AlarmManager) context//新建系统的闹钟服务 + .getSystemService(Context.ALARM_SERVICE); + alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); + //调用android系统方法AlarmManager,设置一个系统提醒事项,设置提醒时间为当前便签中所存alertdate,并设置激活提醒时,广播类Pendingintent + } while (c.moveToNext());//游标移动到下一位置 + } + c.close();//关闭游标 + } + } +} diff --git a/doc/精读报告/ui/AlarmReceiver.doc b/doc/精读报告/ui/AlarmReceiver.doc new file mode 100644 index 0000000..37f264c --- /dev/null +++ b/doc/精读报告/ui/AlarmReceiver.doc @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class AlarmReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + intent.setClass(context, AlarmAlertActivity.class);//启动AlarmAlertActivity + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + //属性设置启动Activity模式:在任务栈中会判断是否存在相同的activity,如果存在,那么会清除该activity之上的其他activity对象显示,如果不存在,则会创建一个新的activity放入栈顶 + context.startActivity(intent);//使用intent启动activity + } +} diff --git a/doc/精读报告/ui/DateTimePicker.doc b/doc/精读报告/ui/DateTimePicker.doc new file mode 100644 index 0000000..9e6c5ee --- /dev/null +++ b/doc/精读报告/ui/DateTimePicker.doc @@ -0,0 +1,496 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import java.text.DateFormatSymbols; +import java.util.Calendar; + +import net.micode.notes.R; + + +import android.content.Context; +import android.text.format.DateFormat; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.NumberPicker; + +public class DateTimePicker extends FrameLayout { + //FrameLayout是布局模板之一 + //所有的子元素全部在屏幕的右上方 + private static final boolean DEFAULT_ENABLE_STATE = true; + + private static final int HOURS_IN_HALF_DAY = 12;//定义半天为12小时 + private static final int HOURS_IN_ALL_DAY = 24;//定义一天为24小时 + private static final int DAYS_IN_ALL_WEEK = 7;//定义一周为七天 + private static final int DATE_SPINNER_MIN_VAL = 0;//定义日期循环最小值为0 + private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;//一周循环最大天数为一周天数减1天 + private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;//24小时制小时最小值为0 + private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;//定义24小时循环的最大值为23 + private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;// 12小时制下小时转轮最小值 + private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;// 12小时制下小时转轮最大值 + private static final int MINUT_SPINNER_MIN_VAL = 0;//分钟轮转最小值为0 + private static final int MINUT_SPINNER_MAX_VAL = 59;//分钟轮转最大值为59 + private static final int AMPM_SPINNER_MIN_VAL = 0;//标志上下午的轮转值,没有具体意义 + private static final int AMPM_SPINNER_MAX_VAL = 1;//标志上下午的轮转值,没有具体意义 + //初始化控件 + private final NumberPicker mDateSpinner; + private final NumberPicker mHourSpinner; + private final NumberPicker mMinuteSpinner; + private final NumberPicker mAmPmSpinner; + //NumberPicker是数字选择器 + //这里定义的四个变量全部是在设置闹钟时需要选择的变量(如日期、时、分、上午或者下午) + private Calendar mDate; + //定义了Calendar类型的变量mDate,用于操作时间 + + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + + private boolean mIsAm; + + private boolean mIs24HourView; + + private boolean mIsEnabled = DEFAULT_ENABLE_STATE; + + private boolean mInitialising; + + private OnDateTimeChangedListener mOnDateTimeChangedListener; + + private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); + updateDateControl(); + onDateTimeChanged(); + } + };//OnValueChangeListener,这是时间改变监听器,这里主要是对日期的监听 + //将现在日期的值传递给mDate;updateDateControl是同步操作 + + private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {//这里是对 小时(Hour) 的监听 + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + boolean isDateChanged = false; + Calendar cal = Calendar.getInstance();//声明一个Calendar的变量cal,便于后续的操作 + if (!mIs24HourView) { + 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;//这里是对于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点交替时对日期的更改 + } + 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();//这里是对于12小时制时,中午11点和12点交替时对AM和PM的更改 + } + } else { + if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true;//这里是对于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;//这里是对于12小时制时,凌晨11点和12点交替时对日期的更改 + } + } + int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);//通过数字选择器对newHour的赋值 + mDate.set(Calendar.HOUR_OF_DAY, newHour);//通过set函数将新的Hour值传给mDate + + onDateTimeChanged(); + if (isDateChanged) { + setCurrentYear(cal.get(Calendar.YEAR)); + setCurrentMonth(cal.get(Calendar.MONTH)); + setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); + } + } + }; + + private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { + @Override//这里是对 分钟(Minute)改变的监听 + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + int minValue = mMinuteSpinner.getMinValue(); + int maxValue = mMinuteSpinner.getMaxValue(); + int offset = 0;//设置offset,作为小时改变的一个记录数据 + if (oldVal == maxValue && newVal == minValue) { + offset += 1; + } else if (oldVal == minValue && newVal == maxValue) { + offset -= 1; + }//如果原值为59,新值为0,则offset加1 + //如果原值为0,新值为59,则offset减1 + if (offset != 0) { + mDate.add(Calendar.HOUR_OF_DAY, offset); + mHourSpinner.setValue(getCurrentHour()); + updateDateControl(); + int newHour = getCurrentHourOfDay(); + if (newHour >= HOURS_IN_HALF_DAY) { + mIsAm = false; + updateAmPmControl(); + } else { + mIsAm = true; + updateAmPmControl(); + } + } + mDate.set(Calendar.MINUTE, newVal); + onDateTimeChanged(); + } + }; + + private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { //对AM和PM的监听 + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mIsAm = !mIsAm; + if (mIsAm) { + mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); + } else { + mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); + } + updateAmPmControl(); + onDateTimeChanged(); + } + }; + + public interface OnDateTimeChangedListener { + void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute); + } + + public DateTimePicker(Context context) { + this(context, System.currentTimeMillis()); + }//通过对数据库的访问,获取当前的系统时间 + + + public DateTimePicker(Context context, long date) {//实例化时间日期选择器 + this(context, date, DateFormat.is24HourFormat(context)); + } + + public DateTimePicker(Context context, long date, boolean is24HourView) { + super(context);//获取系统时间 + mDate = Calendar.getInstance(); + mInitialising = true; + mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + inflate(context, R.layout.datetime_picker, this); + //如果当前Activity里用到别的layout,比如对话框layout + //还要设置这个layout上的其他组件的内容,就必须用inflate()方法先将对话框的layout找出来 + //然后再用findViewById()找到它上面的其它组件 + mDateSpinner = (NumberPicker) findViewById(R.id.date); + mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);//设置组件最小值属性日期最小值 + mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);//设置组件最大值属性为日期最大值 + mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + + mHourSpinner = (NumberPicker) findViewById(R.id.hour);//显示设置小时的视图 + mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);//显示设置分钟的视图 + mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);//设置组件最小值属性为分钟最小值 + mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);//设置组件最大值属性为分钟最大值 + mMinuteSpinner.setOnLongPressUpdateInterval(100);//置监听长按时间间隔为100ms + mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); + + String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();//对24小时下的 am 与pm各属性值进行初始化 + mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);//显示设置上下午的视图 + mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);//设置组件最小值属性为上下午编码最小值 + mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);//设置组件最大值属性为上下午编码最大值 + mAmPmSpinner.setDisplayedValues(stringsForAmPm);//设置显示上下午字符串的值 + mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); + + // update controls to initial state将日期的各参数值更新为初始化状态 + updateDateControl(); + updateHourControl(); + updateAmPmControl(); + + set24HourView(is24HourView); + + // set to current time设置当前时间 + setCurrentDate(date); + + setEnabled(isEnabled()); + + // set the content descriptions + mInitialising = false;//表示初始化已经结束 + } + + @Override + public void setEnabled(boolean enabled) {//将当前监听器的状态设置为开启状态 + if (mIsEnabled == enabled) {//如果已经在开启状态,则返回 + return; + } + super.setEnabled(enabled);//将各部分设置为开启状态 + mDateSpinner.setEnabled(enabled); + mMinuteSpinner.setEnabled(enabled); + mHourSpinner.setEnabled(enabled); + mAmPmSpinner.setEnabled(enabled); + mIsEnabled = enabled; + } + + @Override + public boolean isEnabled() { + return mIsEnabled; + } + + /** + * Get the current date in millis + * + * @return the current date in millis + */ + public long getCurrentDateInTimeMillis() { + return mDate.getTimeInMillis(); + }//实现函数——得到当前的秒数 + + /** + * Set the current date + * + * @param date The current date in millis + */ + public void setCurrentDate(long date) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(date); + setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), + cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));//实现函数功能——设置当前的时间,参数是date + } + + /** + * Set the current date + * + * @param year The current year + * @param month The current month + * @param dayOfMonth The current dayOfMonth + * @param hourOfDay The current hourOfDay + * @param minute The current minute + */ + public void setCurrentDate(int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + setCurrentYear(year); + setCurrentMonth(month); + setCurrentDay(dayOfMonth); + setCurrentHour(hourOfDay); + setCurrentMinute(minute); + }//实现函数功能——设置当前的时间,参数是各详细的变量 + + /** + * Get current year + * + * @return The current year + */ + //下面是得到year、month、day等值 + public int getCurrentYear() { + return mDate.get(Calendar.YEAR); + } + + /** + * Set current year + * + * @param year The current year + */ + public void setCurrentYear(int year) { + if (!mInitialising && year == getCurrentYear()) { + return; + } + mDate.set(Calendar.YEAR, year); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current month in the year + * + * @return The current month in the year + */ + public int getCurrentMonth() { + return mDate.get(Calendar.MONTH); + } + + /** + * Set current month in the year + * + * @param month The month in the year + */ + public void setCurrentMonth(int month) { + if (!mInitialising && month == getCurrentMonth()) { + return; + } + mDate.set(Calendar.MONTH, month); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current day of the month + * + * @return The day of the month + */ + public int getCurrentDay() { + return mDate.get(Calendar.DAY_OF_MONTH); + } + + /** + * Set current day of the month + * + * @param dayOfMonth The day of the month + */ + public void setCurrentDay(int dayOfMonth) { + if (!mInitialising && dayOfMonth == getCurrentDay()) { + return; + } + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current hour in 24 hour mode, in the range (0~23) + * @return The current hour in 24 hour mode + */ + public int getCurrentHourOfDay() { + return mDate.get(Calendar.HOUR_OF_DAY); + } + + private int getCurrentHour() { + if (mIs24HourView){ + return getCurrentHourOfDay(); + } else { + int hour = getCurrentHourOfDay(); + if (hour > HOURS_IN_HALF_DAY) { + return hour - HOURS_IN_HALF_DAY; + } else { + return hour == 0 ? HOURS_IN_HALF_DAY : hour; + } + } + } + + /** + * Set current hour in 24 hour mode, in the range (0~23) + * + * @param hourOfDay + */ + public void setCurrentHour(int hourOfDay) { + if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { + return; + } + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + if (!mIs24HourView) { + if (hourOfDay >= HOURS_IN_HALF_DAY) { + mIsAm = false; + if (hourOfDay > HOURS_IN_HALF_DAY) { + hourOfDay -= HOURS_IN_HALF_DAY; + } + } else { + mIsAm = true; + if (hourOfDay == 0) { + hourOfDay = HOURS_IN_HALF_DAY; + } + } + updateAmPmControl(); + } + mHourSpinner.setValue(hourOfDay); + onDateTimeChanged(); + } + + /** + * Get currentMinute + * + * @return The Current Minute + */ + public int getCurrentMinute() { + return mDate.get(Calendar.MINUTE); + } + + /** + * Set current minute + */ + public void setCurrentMinute(int minute) { + if (!mInitialising && minute == getCurrentMinute()) { + return; + } + mMinuteSpinner.setValue(minute); + mDate.set(Calendar.MINUTE, minute); + onDateTimeChanged(); + } + + /** + * @return true if this is in 24 hour view else false. + */ + public boolean is24HourView () { + return mIs24HourView; + } + + /** + * Set whether in 24 hour or AM/PM mode. + * + * @param is24HourView True for 24 hour mode. False for AM/PM mode. + */ + public void set24HourView(boolean is24HourView) { + if (mIs24HourView == is24HourView) { + return; + } + mIs24HourView = is24HourView; + mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); + int hour = getCurrentHourOfDay(); + updateHourControl(); + setCurrentHour(hour); + updateAmPmControl(); + } + + private void updateDateControl() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); + mDateSpinner.setDisplayedValues(null); + for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { + cal.add(Calendar.DAY_OF_YEAR, 1); + mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); + } + mDateSpinner.setDisplayedValues(mDateDisplayValues); + mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); + mDateSpinner.invalidate();// 对于星期几的算法 + } + + private void updateAmPmControl() { + if (mIs24HourView) { + mAmPmSpinner.setVisibility(View.GONE); + } else { + int index = mIsAm ? Calendar.AM : Calendar.PM; + mAmPmSpinner.setValue(index); + mAmPmSpinner.setVisibility(View.VISIBLE); + }// 对于上下午操作的算法 + } + + private void updateHourControl() { + if (mIs24HourView) { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); + } else { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); + } //对于小时的算法 + } + + /** + * Set the callback that indicates the 'Set' button has been pressed. + * @param callback the callback, if null will do nothing + */ + public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { + mOnDateTimeChangedListener = callback; + } + + private void onDateTimeChanged() { + if (mOnDateTimeChangedListener != null) { + mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), + getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); + } + } +} diff --git a/doc/精读报告/ui/DateTimePickerDialog.doc b/doc/精读报告/ui/DateTimePickerDialog.doc new file mode 100644 index 0000000..3c5bc83 --- /dev/null +++ b/doc/精读报告/ui/DateTimePickerDialog.doc @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import java.util.Calendar; + +import net.micode.notes.R; +import net.micode.notes.ui.DateTimePicker; +import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.text.format.DateFormat; +import android.text.format.DateUtils; + +public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + + private Calendar mDate = Calendar.getInstance();//创建一个Calendar类型的变量 mDate,方便时间的操作 + private boolean mIs24HourView; + private OnDateTimeSetListener mOnDateTimeSetListener;//声明一个时间日期滚动选择控件 mOnDateTimeSetListener + private DateTimePicker mDateTimePicker;//DateTimePicker控件,控件一般用于让用户可以从日期列表中选择单个值。 + //运行时,单击控件边上的下拉箭头,会显示为两个部分:一个下拉列表,一个用于选择日期 + + public interface OnDateTimeSetListener { + void OnDateTimeSet(AlertDialog dialog, long date); + } + + public DateTimePickerDialog(Context context, long date) {//对该界面对话框的实例化 + super(context);//对数据库的操作 + mDateTimePicker = new DateTimePicker(context); + 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);//将秒数设置为0 + mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); + setButton(context.getString(R.string.datetime_dialog_ok), this); + setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);//设置按钮 + set24HourView(DateFormat.is24HourFormat(this.getContext()));//时间标准化打印 + updateTitle(mDate.getTimeInMillis()); + } + + public void set24HourView(boolean is24HourView) { + mIs24HourView = is24HourView; + } + + public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { + mOnDateTimeSetListener = callBack; + }//将时间日期滚动选择控件实例化 + + private void updateTitle(long date) { + int flag = + DateUtils.FORMAT_SHOW_YEAR | + DateUtils.FORMAT_SHOW_DATE | + DateUtils.FORMAT_SHOW_TIME; + flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; + setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); + }//android开发中常见日期管理工具类(API)——DateUtils:按照上下午显示时间 + + public void onClick(DialogInterface arg0, int arg1) { + if (mOnDateTimeSetListener != null) { + mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); + } + }//第一个参数arg0是接收到点击事件的对话框 + //第二个参数arg1是该对话框上的按钮 + +} \ No newline at end of file diff --git a/doc/精读报告/ui/DropdownMenu.doc b/doc/精读报告/ui/DropdownMenu.doc new file mode 100644 index 0000000..1d7851c --- /dev/null +++ b/doc/精读报告/ui/DropdownMenu.doc @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.PopupMenu; +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);//设置这个view的背景 + mPopupMenu = new PopupMenu(context, mButton); + mMenu = mPopupMenu.getMenu(); + mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + //MenuInflater是用来实例化Menu目录下的Menu布局文件 + //根据ID来确认menu的内容选项 + mButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mPopupMenu.show(); + } + }); + } + + public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { + if (mPopupMenu != null) { + mPopupMenu.setOnMenuItemClickListener(listener); + }//设置菜单的监听 + } + + public MenuItem findItem(int id) { + return mMenu.findItem(id); + }//对于菜单选项的初始化,根据索引搜索菜单需要的选项 + + public void setTitle(CharSequence title) { + mButton.setText(title); + }//布局文件,设置标题 +} diff --git a/doc/精读报告/ui/FoldersListAdapter.doc b/doc/精读报告/ui/FoldersListAdapter.doc new file mode 100644 index 0000000..0088aa0 --- /dev/null +++ b/doc/精读报告/ui/FoldersListAdapter.doc @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context;//引用一些类,大多为与数据库的交互,对文件夹的列表名称等做调整 +import android.database.Cursor; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + + +public class FoldersListAdapter extends CursorAdapter { + //CursorAdapter是Cursor和ListView的接口 + //FoldersListAdapter继承了CursorAdapter的类 + //主要作用是便签数据库和用户的交互 + //这里就是用folder(文件夹)的形式展现给用户 + public static final String [] PROJECTION = { + NoteColumns.ID, + NoteColumns.SNIPPET + };//调用数据库中便签的ID和片段 + + public static final int ID_COLUMN = 0; + public static final int NAME_COLUMN = 1;//初始化Column的id和name,id唯一标识了Colum + + public FoldersListAdapter(Context context, Cursor c) { + super(context, c); + // TODO Auto-generated constructor stub + }//数据库操作 + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return new FolderListItem(context); + }//创建一个文件夹,对于各文件夹中子标签的初始化 + + @Override + public void bindView(View view, Context context, Cursor cursor) { + if (view instanceof FolderListItem) { + String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context + .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); + ((FolderListItem) view).bind(folderName); + } + }//将各个布局文件绑定起来 + + public String getFolderName(Context context, int position) { + Cursor cursor = (Cursor) getItem(position); + return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context + .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); + }//根据数据库中标签的ID得到标签的各项内容 + + private class FolderListItem extends LinearLayout { + private TextView mName; + + public FolderListItem(Context context) { + super(context);//操作数据库 + inflate(context, R.layout.folder_list_item, this);//根据布局文件的名字等信息将其找出来 + mName = (TextView) findViewById(R.id.tv_folder_name); + } + + public void bind(String name) { + mName.setText(name); + } + } + +} diff --git a/doc/精读报告/ui/NoteEditActivity.doc b/doc/精读报告/ui/NoteEditActivity.doc new file mode 100644 index 0000000..1b32368 --- /dev/null +++ b/doc/精读报告/ui/NoteEditActivity.doc @@ -0,0 +1,877 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlarmManager; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.app.SearchManager; +import android.appwidget.AppWidgetManager; +import android.content.ContentUris; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Paint; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.text.style.BackgroundColorSpan; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.WindowManager; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.TextNote; +import net.micode.notes.model.WorkingNote; +import net.micode.notes.model.WorkingNote.NoteSettingChangedListener; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.tool.ResourceParser.TextAppearanceResources; +import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; +import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; +import net.micode.notes.widget.NoteWidgetProvider_2x; +import net.micode.notes.widget.NoteWidgetProvider_4x; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class NoteEditActivity extends Activity implements OnClickListener, + NoteSettingChangedListener, OnTextViewChangeListener { + //该类主要是针对标签的编辑 + //继承了系统内部许多和监听有关的类 + private class HeadViewHolder { + public TextView tvModified; + + public ImageView ivAlertIcon; + + public TextView tvAlertDate; + + public ImageView ibSetBgColor; + } //使用Map实现数据存储 + + private static final Map sBgSelectorBtnsMap = new HashMap(); + static { + sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);//设置黄色的背景颜色资源选择器 + sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);//设置红色的背景颜色资源选择器 + sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);//设置蓝色的背景颜色资源选择器 + sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);//设置绿色的背景颜色资源选择器 + sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);//设置白色的背景颜色资源选择器 + //put函数是将指定值和指定键相连 + } + + private static final Map sBgSelectorSelectionMap = new HashMap(); + static { + sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); + sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select); + sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); + sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); + sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); + } + + private static final Map sFontSizeBtnsMap = new HashMap(); + static { + sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); + sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL); + sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); + sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); + }//用户可以选择笔记本界面的大小,提供四个规格 + + private static final Map sFontSelectorSelectionMap = new HashMap(); + static { + sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); + } + + private static final String TAG = "NoteEditActivity"; + + private HeadViewHolder mNoteHeaderHolder; + + private View mHeadViewPanel; + //私有化一个界面操作mHeadViewPanel,对表头的操作 + private View mNoteBgColorSelector; + //私有化一个界面操作mNoteBgColorSelector,对背景颜色的操作 + private View mFontSizeSelector; + //私有化一个界面操作mFontSizeSelector,对标签字体的操作 + private EditText mNoteEditor; + //声明编辑控件,对文本操作 + private View mNoteEditorPanel; + //私有化一个界面操作mNoteEditorPanel,文本编辑的控制板 + private WorkingNote mWorkingNote; + //对模板WorkingNote的初始化 + private SharedPreferences mSharedPrefs;//私有化SharedPreferences的数据存储方式 + private int mFontSizeId;//用于操作字体的大小 + + private static final String PREFERENCE_FONT_SIZE = "pref_font_size";//定义常量 字体大小设置 + + private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;//定义final常量,设置了桌面快捷方式标题的最大长度 + + public static final String TAG_CHECKED = String.valueOf('\u221A');//定义final字符串,表明已标记 + public static final String TAG_UNCHECKED = String.valueOf('\u25A1');//定义final字符串,表明未标记 + + private LinearLayout mEditTextList;//线性布局 + + private String mUserQuery;//用户请求 + private Pattern mPattern;//一个Pattern是一个正则表达式经编译后的表现模式 + + @Override + protected void onCreate(Bundle savedInstanceState) {//初始化系统的状态 + super.onCreate(savedInstanceState); + this.setContentView(R.layout.note_edit); + + if (savedInstanceState == null && !initActivityState(getIntent())) {//判断传入参数是否为空,且对activity进行初始化是否成功,满足则结束create函数 如果传入的保存的状态不为NULL时,则不会调用之后的initActivityState函数进行初始化操作 + finish(); + return; + } + initResources(); + } + + /** + * Current activity may be killed when the memory is low. Once it is killed, for another time + * user load this activity, we should restore the former state + */ + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) {//保存当前界面状态。当当前的活动界面因为释放内存而结束,再次加载活动时,可以根据保存的信息恢复到原来的状态 + super.onRestoreInstanceState(savedInstanceState); + if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); + if (!initActivityState(intent)) { + finish(); + return; + } + Log.d(TAG, "Restoring from killed activity"); + } + } + + private boolean initActivityState(Intent intent) {//用户没有给出ID,就跳转到NoteListActivity + /** + * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, + * then jump to the NotesListActivity + */ + mWorkingNote = null; + if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {//判断intent中的action是ACTION_VIEW还是ACTION_INSERT_OR_EDIT,分别进行不同的操作 + long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); + mUserQuery = ""; + + /** + * Starting from the searched result + */ + if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) {//查找intent中的数据。将数据标识符存在noteIId中,用户请求字符串存在mUserQuery中 + noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); + mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); + } + + if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) {//如果没有找到便签标识符,就跳转到主界面 + Intent jump = new Intent(this, NotesListActivity.class); + startActivity(jump); + showToast(R.string.error_note_not_exist); + finish(); + return false; + } else { + mWorkingNote = WorkingNote.load(this, noteId);//根据noteId导入WorkingNote + if (mWorkingNote == null) {//noteId对应的活动标签为空,报错并结束活动,初始化活动返回false,即初始化活动状态位成功 + 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);//调节Edit界面,使之适应软键盘弹出 + } else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) {//判断intent的活动是INSERT OR EDIT,即新建便签 + // New note + long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); + int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID); + int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, + Notes.TYPE_WIDGET_INVALIDE); + int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, + ResourceParser.getDefaultBgId(this)); + + // Parse call-record note + String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);//下面的程序实现的功能是解析通话记录便签,首先获取intent里的电话号码 + long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0);//获得通话日期 + if (callDate != 0 && phoneNumber != null) {//如果便签的通话日期和通话号码均存在,则把其当做通话便签进行处理,否则当做普通便签进行初始化 + if (TextUtils.isEmpty(phoneNumber)) {//如果记录的通话号码为空,则显示警告 + Log.w(TAG, "The call record number is null"); + } + long noteId = 0; + if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(),//根据电话号码和日期找到对应标识符,赋给noteId. + phoneNumber, callDate)) > 0) { + mWorkingNote = WorkingNote.load(this, noteId); + if (mWorkingNote == null) { + Log.e(TAG, "load call note failed with note id" + noteId); + finish(); + return false; + } + } else {//将电话号码与手机的号码簿相关联 + mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, + widgetType, bgResId); + mWorkingNote.convertToCallNote(phoneNumber, callDate); + } + } else { + mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, + bgResId); + } + + getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE + | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + } else { + Log.e(TAG, "Intent not specified action, should not support");//显示错误信息,Intent没有指定动作,不应支持 + finish(); + return false; + } + mWorkingNote.setOnSettingStatusChangedListener(this); + return true; + } + + @Override + protected void onResume() {//重写Activity的onResume方法,将标签编辑界面作为界面的最上层呈现 + super.onResume(); + initNoteScreen(); + } + + private void initNoteScreen() {//初始化便签界面的窗口 + mNoteEditor.setTextAppearance(this, TextAppearanceResources + .getTexAppearanceResource(mFontSizeId)); + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//如果载入的workingnote对象模式为Check_List则调用switchToListMode函数 + switchToListMode(mWorkingNote.getContent());//切换到清单模式 + } else { + mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));//设置文本选中的文字和长度 + mNoteEditor.setSelection(mNoteEditor.getText().length()); + } + for (Integer id : sBgSelectorSelectionMap.keySet()) {//对所有图片文件做遍历循环,将其可视性设为不可视 + findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); + } + mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); + mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + + mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, + mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_YEAR)); + + /** + * TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker + * is not ready + */ + showAlertHeader(); + } + + private void showAlertHeader() {//显示便签提醒的标头 + if (mWorkingNote.hasClockAlert()) { + long time = System.currentTimeMillis();//获取当前时间,定义为long类型 + if (time > mWorkingNote.getAlertDate()) {//.如果系统时间大于了闹钟设置的时间,那么闹钟失效 + mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); + } else { + mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( + mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS));//如果提醒时间未到,则设置标题中显示提醒时间 + } + mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE);//显示闹钟提醒的开始图标 + mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE);//提醒时间图标显示可见 + } else { + mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); + mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE);//如果便签不包含提醒,则不显示 + }; + } + + @Override + protected void onNewIntent(Intent intent) {//当活动从后台切换但前台时会调用该函数,再次启动Activity的状态 + super.onNewIntent(intent); + initActivityState(intent); + } + + @Override + protected void onSaveInstanceState(Bundle outState) {//该函数用于分派触摸事件,其中参数MotionEvent是对屏幕触控的传递机制 + super.onSaveInstanceState(outState); + /** + * For new note without note id, we should firstly save it to + * generate a id. If the editing note is not worth saving, there + * is no id which is equivalent to create new note + */ + if (!mWorkingNote.existInDatabase()) {//当文字大小选择器可见,并且触控事件不在它的范围内时,执行下面代码 + saveNote(); + } + outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); + Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) {//判断屏幕上的触控动作是否在视图范围内 + if (mNoteBgColorSelector.getVisibility() == View.VISIBLE + && !inRangeOfView(mNoteBgColorSelector, ev)) { + mNoteBgColorSelector.setVisibility(View.GONE); + return true; + } + + if (mFontSizeSelector.getVisibility() == View.VISIBLE + && !inRangeOfView(mFontSizeSelector, ev)) { + mFontSizeSelector.setVisibility(View.GONE); + return true; + } + return super.dispatchTouchEvent(ev); + } + + private boolean inRangeOfView(View view, MotionEvent ev) { + int []location = new int[2]; + view.getLocationOnScreen(location);//获取屏幕上的起点位置 + int x = location[0]; + int y = location[1]; + if (ev.getX() < x//当触摸点的位置超出该控件的视图区域范围时,返回false + || ev.getX() > (x + view.getWidth()) + || ev.getY() < y + || ev.getY() > (y + view.getHeight())) { + return false; + } + return true; + } + + private void initResources() {//对标签各项属性内容的初始化 + mHeadViewPanel = findViewById(R.id.note_title); + mNoteHeaderHolder = new HeadViewHolder(); + mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); + mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); + mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); + mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); + mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); + mNoteEditor = (EditText) findViewById(R.id.note_edit_view); + mNoteEditorPanel = findViewById(R.id.sv_note_edit); + mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); + for (int id : sBgSelectorBtnsMap.keySet()) { + ImageView iv = (ImageView) findViewById(id); + iv.setOnClickListener(this); + } + + mFontSizeSelector = findViewById(R.id.font_size_selector);//为背景颜色选择界面的每一个按钮设置相对应的资源文件,并且设置了点击事件监听器 + for (int id : sFontSizeBtnsMap.keySet()) { + View view = findViewById(id); + view.setOnClickListener(this); + }; + mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);//得到用户的偏好信息,设置字体大小,如果大于资源的最大长度,设为默认大小 + mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); + /** + * HACKME: Fix bug of store the resource id in shared preference. + * The id may larger than the length of resources, in this case, + * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} + */ + if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {//如果Size大于TextAppearance的大小,则将之设置为默认大小 + mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; + } + mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);//得到布局列表 + } + + @Override + protected void onPause() {//重写onPause方法,将便签编辑界面暂停 + super.onPause(); + if(saveNote()) { + Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());//暂停时即进行便签的存储,记录log文件 + } + clearSettingState(); + } + + private void updateWidget() {//更新窗口与桌面小窗口同步 + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { + intent.setClass(this, NoteWidgetProvider_2x.class);//判断当前工作中的便签的桌面挂件的类型,并根据不同类型的桌面挂件指定Intent不同的目的组件 + } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { + intent.setClass(this, NoteWidgetProvider_4x.class); + } else { + Log.e(TAG, "Unspported widget type");//Log输出error信息,不支持的widget类型 + return; + } + + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { + mWorkingNote.getWidgetId() + }); + + sendBroadcast(intent); + setResult(RESULT_OK, intent); + } + + public void onClick(View v) {//事件点击时执行该方法 + int id = v.getId();//获取点击目标的id + if (id == R.id.btn_set_bg_color) {//如果点击的是设置背景颜色,执行下面的函数 + mNoteBgColorSelector.setVisibility(View.VISIBLE); + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + - View.VISIBLE); + } else if (sBgSelectorBtnsMap.containsKey(id)) {//将备选颜色界面设为不可见 + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + View.GONE); + mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); + mNoteBgColorSelector.setVisibility(View.GONE); + } else if (sFontSizeBtnsMap.containsKey(id)) {//判断是否是文字大小设置器中的文字大小选项按钮 + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); + mFontSizeId = sFontSizeBtnsMap.get(id); + mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//如果workingNote模式为核对列表模式,则执行下面函数 + getWorkingText(); + switchToListMode(mWorkingNote.getContent()); + } else { + mNoteEditor.setTextAppearance(this,//如果不是check模式,则设置文字编辑器中文字为相应字体大小 + TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); + } + mFontSizeSelector.setVisibility(View.GONE); + } + } + + @Override + public void onBackPressed() {//点击返回键时的操作 + if(clearSettingState()) {//如果字体选择器和背景选择器均不可见,即为便签编辑界面,点击返回键退出,保存便签如果字体选择器和背景选择器可见,则点击返回键时关闭选择界面 + + return; + } + + saveNote(); + super.onBackPressed(); + } + + private boolean clearSettingState() {//清除设置状态 + if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {//将字体选择器和背景颜色选择器设为不可见 + mNoteBgColorSelector.setVisibility(View.GONE); + return true; + } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {//如果文字大小选择器可见,则将其设置为不可见 + mFontSizeSelector.setVisibility(View.GONE); + return true; + } + return false; + } + + public void onBackgroundColorChanged() {//设置背景颜色改变的监听 + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + View.VISIBLE);//将便签背景色设置为可见,并显示该视图 + mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());//将文件编辑区域的背景资源设置为便签背景色 + mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());//将便签头的背景资源设置为标题的背景色 + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + if (isFinishing()) {//如果窗口正在关闭,则不做处理 + return true; + } + clearSettingState();//清除设置状态 + menu.clear();//清除菜单项 + if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {//如果便签所在的文件夹为便签的通话记录文件夹,执行下面函数 + getMenuInflater().inflate(R.menu.call_note_edit, menu); + } else { + getMenuInflater().inflate(R.menu.note_edit, menu); + } + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//判断语句判断条件为是否为清单模式,根据是否为清单模式填充不同的资源 + menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); + } else { + menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); + } + if (mWorkingNote.hasClockAlert()) {//如果便签中有闹钟的提醒。则菜单中的提醒按钮不可见。如果没有提醒,删除提醒按钮不可见 + menu.findItem(R.id.menu_alert).setVisible(false); + } else { + menu.findItem(R.id.menu_delete_remind).setVisible(false); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) {//监听菜单中的项是否被选择 + switch (item.getItemId()) {//获得项的id,即根据菜单中不同的按钮设置不同的处理 + case R.id.menu_new_note: + createNewNote();//创建新的便签 + break; + case R.id.menu_delete: + AlertDialog.Builder builder = new AlertDialog.Builder(this);//新建一个提醒对话框实例,用于确认删除操作 + builder.setTitle(getString(R.string.alert_title_delete));//创建关于删除操作的对话框 + builder.setIcon(android.R.drawable.ic_dialog_alert);//设置对话框图标 + builder.setMessage(getString(R.string.alert_message_delete_note));//设置对话框消息内容 + builder.setPositiveButton(android.R.string.ok,//设置确认按钮,并监听它是否被点击 + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) {//设置对话框的点击函数 + deleteCurrentNote();//删除当前的便签 + finish();//结束这个activity + } + }); + builder.setNegativeButton(android.R.string.cancel, null);//设置取消按钮 + builder.show();//显示提醒对话框 + break; + case R.id.menu_font_size://菜单字体大小的设置 + mFontSizeSelector.setVisibility(View.VISIBLE);//将字体大小选择器设置为可见 + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);//通过id找到相应字体的大小 + break; + case R.id.menu_list_mode://选择清单模式 + mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?//设置清单模式,若当前为清单模式,则取消;否之则设为清单模式 + TextNote.MODE_CHECK_LIST : 0); + break; + case R.id.menu_share://选择分享 + getWorkingText();//获取当前便签的文本 + sendTo(this, mWorkingNote.getContent());//发送文本 + break; + case R.id.menu_send_to_desktop://选择发送到桌面 + sendToDesktop(); + break; + case R.id.menu_alert://选择设置提醒 + setReminder(); + break; + case R.id.menu_delete_remind://选择删除提醒 + mWorkingNote.setAlertDate(0, false); + break; + default: + break; + } + return true; + } + + private void setReminder() {//设置提醒器 + DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());//实例化一个时间日期选择器的对话框,进行提醒日期的设置 + d.setOnDateTimeSetListener(new OnDateTimeSetListener() { + public void OnDateTimeSet(AlertDialog dialog, long date) { + mWorkingNote.setAlertDate(date , true); + } + }); + d.show(); + } + + /** + * Share note to apps that support {@link Intent#ACTION_SEND} action + * and {@text/plain} type + */ + private void sendTo(Context context, String info) {//将便签的文本发送到其他支持plain类型文字显示的app中 + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_TEXT, info); + intent.setType("text/plain"); + context.startActivity(intent); + } + + private void createNewNote() {//创建新的便签 + // Firstly, save current editing notes + saveNote(); + + // For safety, start a new NoteEditActivity + finish(); + Intent intent = new Intent(this, NoteEditActivity.class);//设置一个新的便签活动编辑器 + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); + startActivity(intent); + } + + private void deleteCurrentNote() {//删除当前的便签 + if (mWorkingNote.existInDatabase()) { + HashSet ids = new HashSet(); + long id = mWorkingNote.getNoteId(); + if (id != Notes.ID_ROOT_FOLDER) { + ids.add(id); + } else { + Log.d(TAG, "Wrong note id, should not happen"); + } + if (!isSyncMode()) { + if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { + Log.e(TAG, "Delete Note error"); + } + } else { + if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { + Log.e(TAG, "Move notes to trash folder error, should not happens"); + } + } + } + mWorkingNote.markDeleted(true); + } + + private boolean isSyncMode() {//判断是否为同步模式 + return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + }//返回同步的用户名长度,如果大于0则有同步状态,否则非同步 + + public void onClockAlertChanged(long date, boolean set) {//设置闹钟提醒改变的监听 + /** + * User could set clock to an unsaved note, so before setting the + * alert clock, we should save the note first + */ + if (!mWorkingNote.existInDatabase()) {//如果设置提醒的便签未保存,则先保存 + saveNote(); + } + if (mWorkingNote.getNoteId() > 0) {//数据库中有该提醒的id,执行下面操作。否则,报错 + 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 { + /** + * There is the condition that user has input nothing (the note is + * not worthy saving), we have no note id, remind the user that he + * should input something + */ + Log.e(TAG, "Clock alert setting error"); + showToast(R.string.error_note_empty_for_clock); + } + } + + public void onWidgetChanged() { + updateWidget(); + }//当widget变化时,执行更新widget的函数 + + public void onEditTextDelete(int index, String text) {//当删除编辑文本时,执行这个函数 + int childCount = mEditTextList.getChildCount(); + if (childCount == 1) { + return; + } + + for (int i = index + 1; i < childCount; i++) { + ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) + .setIndex(i - 1); + } + + mEditTextList.removeViewAt(index); + NoteEditText edit = null; + if(index == 0) { + edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( + R.id.et_edit_text); + } else { + edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById( + R.id.et_edit_text); + } + int length = edit.length(); + edit.append(text); + edit.requestFocus(); + edit.setSelection(length); + } + + public void onEditTextEnter(int index, String text) {//进入编辑文本框所触发的事件 + /** + * Should not happen, check for debug + */ + if(index > mEditTextList.getChildCount()) {//如果便签长度超过了限制,则提示错误 + Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); + } + + View view = getListItem(text, index); + mEditTextList.addView(view, index); + NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + edit.requestFocus(); + edit.setSelection(0); + for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {//遍历子文本框并设置对应对下标 + ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) + .setIndex(i); + } + } + + private void switchToListMode(String text) {//切换至列表模式 + mEditTextList.removeAllViews(); + String[] items = text.split("\n"); + int index = 0; + for (String item : items) { + if(!TextUtils.isEmpty(item)) { + mEditTextList.addView(getListItem(item, index)); + index++; + } + } + mEditTextList.addView(getListItem("", index)); + mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); + + mNoteEditor.setVisibility(View.GONE); + mEditTextList.setVisibility(View.VISIBLE); + }//清空所有视图 初始化下标 遍历所有文本单元并添加到文本框中 优先请求此操作 便签编辑器不可见 将文本编辑框置为可见 + + private Spannable getHighlightQueryResult(String fullText, String userQuery) {//获取高亮效果的反馈结果 + SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); + if (!TextUtils.isEmpty(userQuery)) { + mPattern = Pattern.compile(userQuery); + Matcher m = mPattern.matcher(fullText); + int start = 0; + while (m.find(start)) { + spannable.setSpan( + new BackgroundColorSpan(this.getResources().getColor( + R.color.user_query_highlight)), m.start(), m.end(), + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + start = m.end(); + } + } + return spannable; + } + + private View getListItem(String item, int index) {//获取列表项 + View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); + final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); + CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); + cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {//如果CheckBox已勾选,则执行下面函数,设置显示方式 + if (isChecked) {//如果已经被标记 + edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } else { + edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); + } + } + }); + + if (item.startsWith(TAG_CHECKED)) {//判断是否勾选 + cb.setChecked(true); + edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + item = item.substring(TAG_CHECKED.length(), item.length()).trim(); + } else if (item.startsWith(TAG_UNCHECKED)) { + cb.setChecked(false); + edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); + item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); + } + + edit.setOnTextViewChangeListener(this); + edit.setIndex(index); + edit.setText(getHighlightQueryResult(item, mUserQuery)); + return view; + } + + public void onTextChange(int index, boolean hasText) {//便签内容发生改变所触发的事件 越界报错 如果内容不为空则将其子编辑框可见性置为可见,否则不可见 + if (index >= mEditTextList.getChildCount()) { + Log.e(TAG, "Wrong index, should not happen"); + return; + } + if(hasText) { + mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); + } else { + mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); + } + } + + public void onCheckListModeChanged(int oldMode, int newMode) {//监听清单模式的变化 + if (newMode == TextNote.MODE_CHECK_LIST) { + switchToListMode(mNoteEditor.getText().toString()); + } else { + if (!getWorkingText()) { + mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", + "")); + } + mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + mEditTextList.setVisibility(View.GONE); + mNoteEditor.setVisibility(View.VISIBLE); + } + }//如果新模式为列表模式,则调用switchToListMode()方法将便签编辑器的文本切换到列表模式,否则若是获取到文本就改变其检查标记,修改文本编辑器的内容和可见性 + + private boolean getWorkingText() {//获取当前活动便签的文本并保存到数据库中 + boolean hasChecked = false;//初始化check标记 + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mEditTextList.getChildCount(); i++) { + View view = mEditTextList.getChildAt(i); + NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + if (!TextUtils.isEmpty(edit.getText())) { + if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { + sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); + hasChecked = true; + } else { + sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); + } + } + } + mWorkingNote.setWorkingText(sb.toString()); + } else { + mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); + } + return hasChecked; + }//如果当前工作便签为清单模式,转化为文字形式保存其数据否则直接保存文字数据到数据库中 + + private boolean saveNote() {//保存便签 + getWorkingText(); + boolean saved = mWorkingNote.saveNote(); + if (saved) { + /** + * There are two modes from List view to edit view, open one note, + * create/edit a node. Opening node requires to the original + * position in the list when back from edit view, while creating a + * new node requires to the top of the list. This code + * {@link #RESULT_OK} is used to identify the create/edit state + */ + setResult(RESULT_OK); + } + return saved; + } + + private void sendToDesktop() {//将便签发送至桌面 + /** + * Before send message to home, we should make sure that current + * editing note is exists in databases. So, for new note, firstly + * save it + */ + if (!mWorkingNote.existInDatabase()) { + saveNote(); + }//核对正编辑的便签是否存在于数据库中,若不存在,则先保存便签 + + if (mWorkingNote.getNoteId() > 0) {//若便签的id大于0,则执行以下程序 + Intent sender = new Intent(); + Intent shortcutIntent = new Intent(this, NoteEditActivity.class); + shortcutIntent.setAction(Intent.ACTION_VIEW); + shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); + sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, + makeShortcutIconTitle(mWorkingNote.getContent())); + sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, + Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); + sender.putExtra("duplicate", true); + sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + showToast(R.string.info_note_enter_desktop); + sendBroadcast(sender);//建立发送到桌面的连接器 链接为一个视图 将便签的相关信息都添加到要发送的文件里 设置sneder的行为是发送 + } else {//如果便签的id错误,则保存提醒用户 + /** + * There is the condition that user has input nothing (the note is + * not worthy saving), we have no note id, remind the user that he + * should input something + */ + Log.e(TAG, "Send to desktop error"); + showToast(R.string.error_note_empty_for_send_to_desktop); + } + } + + private String makeShortcutIconTitle(String content) {//编辑小图标的标题 + content = content.replace(TAG_CHECKED, ""); + content = content.replace(TAG_UNCHECKED, ""); + return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, + SHORTCUT_ICON_TITLE_MAX_LEN) : content; + }//直接设置为content中的内容并返回,有勾选和未勾选2种 + + 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/doc/精读报告/ui/NoteEditText.doc b/doc/精读报告/ui/NoteEditText.doc new file mode 100644 index 0000000..9318ea6 --- /dev/null +++ b/doc/精读报告/ui/NoteEditText.doc @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.graphics.Rect; +import android.text.Layout; +import android.text.Selection; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.URLSpan; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ContextMenu; +import android.view.KeyEvent; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.MotionEvent; +import android.widget.EditText; + +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; + private int mSelectionStartBeforeDelete; + + private static final String SCHEME_TEL = "tel:" ; + 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); + sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); + sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); + } + + /** + * Call by the {@link NoteEditActivity} to delete or add edit text + */ + public interface OnTextViewChangeListener {//该接口用于实现对TextView组件中的文字信息进行修改 + /** + * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens + * and the text is null + */ + void onEditTextDelete(int index, String text);//当delete键按下时删除当前编辑的文字块 + + /** + * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} + * happen + */ + void onEditTextEnter(int index, String text);//当enter键按下时添加一个文字编辑块 + + /** + * Hide or show item option when text change + */ + void onTextChange(int index, boolean hasText);//当文字发生变化时隐藏或者显示设置 + } + + private OnTextViewChangeListener mOnTextViewChangeListener;//声明文本视图变化监听器 + + 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) {//下面两个函数都是NoteEditText的构造函数,它们同样继承了父类的构造函数,不同的是它们调用的参数不一样 + super(context, attrs, android.R.attr.editTextStyle); + } + + public NoteEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + // TODO Auto-generated constructor stub + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + //触摸屏幕编辑便签时触发,参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息,例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创 + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN://重写屏幕触发事件 + + int x = (int) event.getX(); + int y = (int) event.getY(); + x -= getTotalPaddingLeft();//减去左边控件的距离 + y -= getTotalPaddingTop(); + x += getScrollX();//加上滚轮滚过的距离 + y += getScrollY(); + + Layout layout = getLayout();//用布局控件layout根据x,y的新值设置新的位置 + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + Selection.setSelection(getText(), off); + break; + } + + return super.onTouchEvent(event);//这是调用父类的方法,当屏幕有Touch事件时,此方法就会被调用 + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + if (mOnTextViewChangeListener != null) { + //mOnTextViewChangeListener是上面接口OnTextViewChangeListener的引用,指向初始化时的listener对象,该对象是OnTextViewChangeListener的实现类的对象,接口无法直接实例化。mOnTextViewChangeListener标志着文本是否被改变。 + return false; + } + break; + case KeyEvent.KEYCODE_DEL: + mSelectionStartBeforeDelete = getSelectionStart();//获取删除文本开始位置 + break; + default: + break; + } + 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; + } + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted");////其他情况报错,文档的改动监听器并没有建立 + } + break; + case KeyEvent.KEYCODE_ENTER: + if (mOnTextViewChangeListener != null) { + int selectionStart = getSelectionStart();////获取当前位置 + String text = getText().subSequence(selectionStart, length()).toString();//获取选择区域后面的文本信息 + setText(getText().subSequence(0, selectionStart));//根据获取的文本设置当前文本 + mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);//将选择区域内的文字移到下一行 + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted");//其他情况报错,监听器OnTextViewChangeListener并没有建立 + } + break; + default: + break; + } + 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);//mOnTextViewChangeListener子函数,置false隐藏事件选项 + } else { + mOnTextViewChangeListener.onTextChange(mIndex, true);//mOnTextViewChangeListener子函数,置true显示事件选项 + + } + } + super.onFocusChanged(focused, direction, previouslyFocusedRect);//继续执行父类的其他焦点变化的事件 + } + + @Override + protected void onCreateContextMenu(ContextMenu menu) {//生成上下文菜单 + if (getText() instanceof Spanned) {//如果有文本存在 + int selStart = getSelectionStart(); + int selEnd = getSelectionEnd();//获取文本开始和结尾位置 + + int min = Math.min(selStart, selEnd); + int max = Math.max(selStart, selEnd);//获取开始到结尾的最大、最小值 + + final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);//获取一段text内容,并且把这段内容强制转换为Spanned类型,并存入URLSpan数组urls中 + if (urls.length == 1) { + int defaultResId = 0; + for(String schema: sSchemaActionResMap.keySet()) { + if(urls[0].getURL().indexOf(schema) >= 0) {//若url可以添加则在添加后将defaultResId置为key所映射的值 + defaultResId = sSchemaActionResMap.get(schema); + break; + } + } + + if (defaultResId == 0) {//defaultResId == 0则说明url并没有添加任何东西,所以置为连接其他SchemaActionResMap的值 + defaultResId = R.string.note_link_other; + } + + menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( + new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) {//如果点击菜单执行操作 + // goto a new intent + urls[0].onClick(NoteEditText.this);//根据相应的文本设置菜单的按键 + return true; + } + }); + } + } + super.onCreateContextMenu(menu);//继续执行父类的其他菜单创建的事件 + } +} diff --git a/doc/精读报告/ui/NotesListActivity.doc b/doc/精读报告/ui/NotesListActivity.doc new file mode 100644 index 0000000..784995a --- /dev/null +++ b/doc/精读报告/ui/NotesListActivity.doc @@ -0,0 +1,1220 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.appwidget.AppWidgetManager; +import android.content.AsyncQueryHandler; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.ActionMode; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Display; +import android.view.HapticFeedbackConstants; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnCreateContextMenuListener; +import android.view.View.OnTouchListener; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.PopupMenu; +import android.widget.TextView; +import android.widget.Toast; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.remote.GTaskSyncService; +import net.micode.notes.model.WorkingNote; +import net.micode.notes.tool.BackupUtils; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; +import net.micode.notes.widget.NoteWidgetProvider_2x; +import net.micode.notes.widget.NoteWidgetProvider_4x; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashSet; + +// 定义一个类,继承自Activity类,实现OnClickListener和OnItemLongClickListener接口 +public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { + // 定义一个常量,表示文件夹笔记列表查询的标识符 + private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; + + // 定义一个常量,表示文件夹列表查询的标识符 + private static final int FOLDER_LIST_QUERY_TOKEN = 1; + + // 定义一个常量,表示菜单中删除文件夹的选项 + private static final int MENU_FOLDER_DELETE = 0; + + // 定义一个常量,表示菜单中查看文件夹的选项 + private static final int MENU_FOLDER_VIEW = 1; + + // 定义一个常量,表示菜单中修改文件夹名称的选项 + private static final int MENU_FOLDER_CHANGE_NAME = 2; + + // 定义一个常量,表示偏好设置中添加介绍的选项 + private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; + + // 定义一个枚举类型,表示列表编辑的状态,有三种可能:笔记列表、子文件夹、通话记录文件夹 + private enum ListEditState { + NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER + }; + + // 定义一个变量,表示当前的列表编辑状态 + private ListEditState mState; + + // 定义一个变量,表示后台查询处理器 + private BackgroundQueryHandler mBackgroundQueryHandler; + + // 定义一个变量,表示笔记列表适配器 + private NotesListAdapter mNotesListAdapter; + + // 定义一个变量,表示笔记列表视图 + private ListView mNotesListView; + + private Button mAddNewNote; // 添加新便签的按钮 + private boolean mDispatch; // 用于判断是否需要分发触摸事件 + private int mOriginY; // 用于记录触摸事件的原始 Y 坐标 + private int mDispatchY; // 用于记录分发触摸事件的 Y 坐标 + private TextView mTitleBar; // 用于显示标题栏的文本视图 + private long mCurrentFolderId; // 用于记录当前文件夹的 ID + private ContentResolver mContentResolver; // 用于访问内容提供器的对象 + private ModeCallback mModeCallBack; // 用于处理上下文菜单的回调对象 + private static final String TAG = "NotesListActivity"; // 定义一个常量字符串,用于日志输出 + + public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; // 定义一个常量整数,用于设置便签列表视图的滚动速率 + + private NoteItemData mFocusNoteDataItem; // 用于记录当前焦点便签的数据对象 + + // 定义一些常量字符串,用于查询便签数据库的条件 + private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; + private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" + + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + + " OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + + " AND " + NoteColumns.NOTES_COUNT + ">0)"; + + // 定义一些常量整数,用于标识不同的请求码 + private final static int REQUEST_CODE_OPEN_NODE = 102; + private final static int REQUEST_CODE_NEW_NODE = 103; + + @Override + protected void onCreate(Bundle savedInstanceState) {// 调用父类的方法 + super.onCreate(savedInstanceState);// 设置布局文件为note_list.xml + + setContentView(R.layout.note_list);// 初始化资源 + initResources(); + + /** + * Insert an introduction when user firstly use this application + */ + setAppInfoFromRawRes(); // 从原始资源文件中设置应用程序的介绍信息 + } + + @Override + // 这个方法是在一个Activity从另一个Activity返回结果时调用的 + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // 如果结果是成功的,并且请求码是打开或新建一个节点,那么就更新列表适配器的数据源 + if (resultCode == RESULT_OK + && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { + mNotesListAdapter.changeCursor(null); + } else { + // 否则,调用父类的方法处理结果 + super.onActivityResult(requestCode, resultCode, data); + } + } + + // 这个方法是从raw资源文件中读取应用信息,并保存到SharedPreferences中 + private void setAppInfoFromRawRes() { + // 获取SharedPreferences对象 + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + // 如果没有添加过介绍信息,就执行以下操作 + if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { + // 创建一个StringBuilder对象,用来存储读取的内容 + StringBuilder sb = new StringBuilder(); + // 声明一个输入流对象 + InputStream in = null; + try { + // 从raw资源文件中打开输入流 + in = getResources().openRawResource(R.raw.introduction); + // 如果输入流不为空,就创建一个字符输入流和缓冲输入流,按照字符数组读取内容,并追加到StringBuilder中 + if (in != null) { + InputStreamReader isr = new InputStreamReader(in); + BufferedReader br = new BufferedReader(isr); + char [] buf = new char[1024]; + int len = 0; + while ((len = br.read(buf)) > 0) { + sb.append(buf, 0, len); + } + } else { + // 如果输入流为空,就打印错误日志并返回 + Log.e(TAG, "Read introduction file error"); + return; + } + } catch (IOException e) { + // 如果发生异常,就打印堆栈信息并返回 + e.printStackTrace(); + return; + } finally { + // 最后,如果输入流不为空,就关闭输入流 + if(in != null) { + try { + in.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + // 这个代码段是在读取完介绍信息后,创建一个空的WorkingNote对象,并设置其属性和内容 + WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, + AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, + ResourceParser.RED); + // 设置WorkingNote对象的文本内容为StringBuilder中的内容 + note.setWorkingText(sb.toString()); + // 如果保存WorkingNote对象成功,就把SharedPreferences中的添加介绍信息的标志设为true + if (note.saveNote()) { + sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); + } else { + // 如果保存失败,就打印错误日志并返回 + Log.e(TAG, "Save introduction note error"); + return; + } + } + } + + @Override + // 这个方法是在Activity启动时调用的 + protected void onStart() { + // 调用父类的方法 + super.onStart(); + // 开始异步查询笔记列表 + startAsyncNotesListQuery(); + } + + // 这个方法是初始化资源的 + private void initResources() { + // 获取内容解析器对象 + mContentResolver = this.getContentResolver(); + // 创建一个后台查询处理器对象 + mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); + // 设置当前文件夹的ID为根文件夹的ID + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + // 获取笔记列表视图对象,并设置其底部视图和点击监听器 + mNotesListView = (ListView) findViewById(R.id.notes_list); + mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), + null, false); + mNotesListView.setOnItemClickListener(new OnListItemClickListener()); + mNotesListView.setOnItemLongClickListener(this); + // 创建一个笔记列表适配器对象,并设置给笔记列表视图 + mNotesListAdapter = new NotesListAdapter(this); + mNotesListView.setAdapter(mNotesListAdapter); + // 获取添加新笔记按钮对象,并设置其点击监听器和触摸监听器 + mAddNewNote = (Button) findViewById(R.id.btn_new_note); + mAddNewNote.setOnClickListener(this); + mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); + // 初始化一些变量,用来控制事件分发和标题栏显示 + mDispatch = false; + mDispatchY = 0; + mOriginY = 0; + mTitleBar = (TextView) findViewById(R.id.tv_title_bar); + // 设置当前的列表编辑状态为笔记列表状态 + mState = ListEditState.NOTE_LIST; + // 创建一个模式回调对象,用来处理上下文菜单的操作 + mModeCallBack = new ModeCallback(); + } + + // 这个内部类实现了多选模式监听器和菜单项点击监听器的接口,用来处理列表视图的多选模式和菜单操作 + private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { + // 声明一个下拉菜单对象,一个操作模式对象和一个移动菜单项对象 + private DropdownMenu mDropDownMenu; + private ActionMode mActionMode; + private MenuItem mMoveMenu; + + // 这个方法是在创建操作模式时调用的 + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // 从资源文件中加载菜单项,并设置删除和移动菜单项的点击监听器 + getMenuInflater().inflate(R.menu.note_list_options, menu); + menu.findItem(R.id.delete).setOnMenuItemClickListener(this); + mMoveMenu = menu.findItem(R.id.move); + // 如果当前的文件夹是通话记录文件夹或者用户没有自定义文件夹,就隐藏移动菜单项 + if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER + || DataUtils.getUserFolderCount(mContentResolver) == 0) { + mMoveMenu.setVisible(false); + } else { + // 否则,显示移动菜单项,并设置其点击监听器 + mMoveMenu.setVisible(true); + mMoveMenu.setOnMenuItemClickListener(this); + } + // 保存操作模式对象的引用,并设置列表适配器的选择模式为true + mActionMode = mode; + mNotesListAdapter.setChoiceMode(true); + // 设置列表视图不可长按,并隐藏添加新笔记按钮 + mNotesListView.setLongClickable(false); + mAddNewNote.setVisibility(View.GONE); + + // 从布局文件中加载自定义视图,并设置给操作模式对象 + View customView = LayoutInflater.from(NotesListActivity.this).inflate( + R.layout.note_list_dropdown_menu, null); + mode.setCustomView(customView); + // 创建一个下拉菜单对象,并设置其按钮和菜单资源 + mDropDownMenu = new DropdownMenu(NotesListActivity.this, + (Button) customView.findViewById(R.id.selection_menu), + R.menu.note_list_dropdown); + // 设置下拉菜单的菜单项点击监听器,用来实现全选或取消全选的功能,并更新菜单状态 + mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ + public boolean onMenuItemClick(MenuItem item) { + mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); + updateMenu(); + return true; + } + + }); + return true; + } + // 这个方法是更新菜单状态的 + private void updateMenu() { + // 获取已选择的笔记数量 + int selectedCount = mNotesListAdapter.getSelectedCount(); + // 更新下拉菜单的标题,显示已选择的数量 + String format = getResources().getString(R.string.menu_select_title, selectedCount); + mDropDownMenu.setTitle(format); + // 获取下拉菜单中的全选菜单项,并根据是否全选设置其标题和状态 + MenuItem item = mDropDownMenu.findItem(R.id.action_select_all); + if (item != null) { + if (mNotesListAdapter.isAllSelected()) { + item.setChecked(true); + item.setTitle(R.string.menu_deselect_all); + } else { + item.setChecked(false); + item.setTitle(R.string.menu_select_all); + } + } + } + + // 这个方法是在准备操作模式时调用的,这里没有实现任何功能 + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // TODO Auto-generated method stub + return false; + } + + // 这个方法是在点击操作模式中的菜单项时调用的,这里没有实现任何功能 + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + // TODO Auto-generated method stub + return false; + } + + // 这个方法是在销毁操作模式时调用的 + public void onDestroyActionMode(ActionMode mode) { + // 设置列表适配器的选择模式为false,并恢复列表视图的长按功能和添加新笔记按钮的可见性 + mNotesListAdapter.setChoiceMode(false); + mNotesListView.setLongClickable(true); + mAddNewNote.setVisibility(View.VISIBLE); + } + + // 这个方法是结束操作模式的 + public void finishActionMode() { + mActionMode.finish(); + } + + // 这个方法是在列表视图中的某一项被选中或取消选中时调用的 + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, + boolean checked) { + // 设置列表适配器中对应位置的笔记为选中或未选中,并更新菜单状态 + mNotesListAdapter.setCheckedItem(position, checked); + updateMenu(); + } + + // 这个方法是实现菜单项点击监听器的接口,用来处理删除和移动菜单项的点击事件 + public boolean onMenuItemClick(MenuItem item) { + // 如果没有选择任何笔记,就弹出一个提示信息,并返回true + if (mNotesListAdapter.getSelectedCount() == 0) { + Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), + Toast.LENGTH_SHORT).show(); + return true; + } + + // 根据菜单项的ID,执行相应的操作 + switch (item.getItemId()) { + // 如果是删除菜单项,就弹出一个确认对话框,询问用户是否要删除所选的笔记 + case R.id.delete: + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(getString(R.string.alert_title_delete)); + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setMessage(getString(R.string.alert_message_delete_notes, + mNotesListAdapter.getSelectedCount())); + // 如果用户点击确定,就调用批量删除的方法 + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + batchDelete(); + } + }); + // 如果用户点击取消,就什么都不做 + builder.setNegativeButton(android.R.string.cancel, null); + // 显示对话框 + builder.show(); + break; + // 如果是移动菜单项,就开始查询目标文件夹的数据 + case R.id.move: + startQueryDestinationFolders(); + break; + // 如果是其他菜单项,就返回false + default: + return false; + } + return true; + } + } + + // 这个内部类实现了触摸监听器的接口,用来处理添加新笔记按钮的触摸事件 + private class NewNoteOnTouchListener implements OnTouchListener { + + // 这个方法是在触摸按钮时调用的 + public boolean onTouch(View v, MotionEvent event) { + // 根据触摸事件的类型,执行相应的操作 + switch (event.getAction()) { + // 如果是按下事件,就执行以下操作 + case MotionEvent.ACTION_DOWN: { + // 获取屏幕的显示对象,并获取屏幕的高度 + Display display = getWindowManager().getDefaultDisplay(); + int screenHeight = display.getHeight(); + // 获取添加新笔记按钮的高度,并计算出按钮的起始位置 + int newNoteViewHeight = mAddNewNote.getHeight(); + int start = screenHeight - newNoteViewHeight; + // 获取触摸事件在屏幕上的Y坐标,并加上按钮的起始位置 + int eventY = start + (int) event.getY(); + /** + * Minus TitleBar's height + */ + // 如果当前的列表编辑状态是子文件夹状态,就减去标题栏的高度 + if (mState == ListEditState.SUB_FOLDER) { + eventY -= mTitleBar.getHeight(); + start -= mTitleBar.getHeight(); + } + + // 如果触摸事件在一个特定的区域内,就执行以下操作 + if (event.getY() < (event.getX() * (-0.12) + 94)) { + // 获取列表视图中最后一个可见的视图对象(除了底部视图) + View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1 + - mNotesListView.getFooterViewsCount()); + // 如果视图对象不为空,并且它的底部位置大于按钮的起始位置,并且它的顶部位置小于按钮的结束位置,就执行以下操作 + if (view != null && view.getBottom() > start + && (view.getTop() < (start + 94))) { + // 保存触摸事件在按钮上的Y坐标和在屏幕上的Y坐标 + mOriginY = (int) event.getY(); + mDispatchY = eventY; + // 设置触摸事件在屏幕上的Y坐标为之前保存的值 + event.setLocation(event.getX(), mDispatchY); + // 设置分发标志为true,并把触摸事件分发给列表视图处理 + mDispatch = true; + return mNotesListView.dispatchTouchEvent(event); + } + } + break; + } + // 如果是移动事件,就执行以下操作 + case MotionEvent.ACTION_MOVE: { + // 如果分发标志为true,就执行以下操作 + if (mDispatch) { + // 更新触摸事件在屏幕上的Y坐标,并设置给触摸事件对象 + mDispatchY += (int) event.getY() - mOriginY; + event.setLocation(event.getX(), mDispatchY); + // 把触摸事件分发给列表视图处理,并返回true + return mNotesListView.dispatchTouchEvent(event); + } + break; + } + // 如果是其他类型的事件,就执行以下操作 + default: { + // 如果分发标志为true,就执行以下操作 + if (mDispatch) { + // 设置触摸事件在屏幕上的Y坐标为之前保存的值,并把分发标志设为false + event.setLocation(event.getX(), mDispatchY); + mDispatch = false; + // 把触摸事件分发给列表视图处理,并返回true + return mNotesListView.dispatchTouchEvent(event); + } + break; + } + } + // 如果没有分发触摸事件,就返回false + return false; + } + + }; + + // 这个方法是开始异步查询笔记列表的 + private void startAsyncNotesListQuery() { + // 根据当前文件夹的ID,设置查询条件 + String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION + : NORMAL_SELECTION; + // 调用后台查询处理器对象,开始查询笔记的数据,并指定查询标识符、投影、条件和排序方式 + mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, + Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] { + String.valueOf(mCurrentFolderId) + }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); + } + + // 这个内部类继承了异步查询处理器类,用来处理异步查询的结果 + private final class BackgroundQueryHandler extends AsyncQueryHandler { + // 构造方法,调用父类的构造方法,传入内容解析器对象 + public BackgroundQueryHandler(ContentResolver contentResolver) { + super(contentResolver); + } + + @Override + // 这个方法是在查询完成时调用的,传入查询标识符、对象和游标 + protected void onQueryComplete(int token, Object cookie, Cursor cursor) { + // 根据查询标识符,执行相应的操作 + switch (token) { + // 如果是查询笔记列表的标识符,就把游标传给列表适配器 + case FOLDER_NOTE_LIST_QUERY_TOKEN: + mNotesListAdapter.changeCursor(cursor); + break; + // 如果是查询文件夹列表的标识符,就判断游标是否有效,如果有效,就显示文件夹列表菜单 + case FOLDER_LIST_QUERY_TOKEN: + if (cursor != null && cursor.getCount() > 0) { + showFolderListMenu(cursor); + } else { + // 如果无效,就打印错误日志 + Log.e(TAG, "Query folder failed"); + } + break; + // 如果是其他标识符,就什么都不做 + default: + return; + } + } + } + + // 这个方法是显示文件夹列表菜单的,传入一个游标对象 + private void showFolderListMenu(Cursor cursor) { + // 创建一个对话框构造器对象,并设置其标题 + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(R.string.menu_title_select_folder); + // 创建一个文件夹列表适配器对象,并设置给对话框构造器 + final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor); + builder.setAdapter(adapter, new DialogInterface.OnClickListener() { + + // 设置对话框的点击监听器,用来处理用户选择某个文件夹的事件 + public void onClick(DialogInterface dialog, int which) { + // 调用数据工具类的方法,把已选择的笔记批量移动到用户选择的文件夹中 + DataUtils.batchMoveToFolder(mContentResolver, + mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which)); + // 弹出一个提示信息,显示移动了多少笔记到哪个文件夹中 + Toast.makeText( + NotesListActivity.this, + getString(R.string.format_move_notes_to_folder, + mNotesListAdapter.getSelectedCount(), + adapter.getFolderName(NotesListActivity.this, which)), + Toast.LENGTH_SHORT).show(); + // 结束操作模式 + mModeCallBack.finishActionMode(); + } + }); + // 显示对话框 + builder.show(); + } + + private void createNewNote() { + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); + this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); + } + + // 定义一个私有方法,用于批量删除笔记 + private void batchDelete() { + // 创建一个异步任务,传入空参数,返回一个AppWidgetAttribute的集合 + new AsyncTask>() { + // 在后台线程执行的方法,返回要更新的小部件的集合 + protected HashSet doInBackground(Void... unused) { + // 获取选中的小部件的集合 + HashSet widgets = mNotesListAdapter.getSelectedWidget(); + // 判断是否是同步模式 + if (!isSyncMode()) { + // 如果不是同步模式,直接删除选中的笔记 + if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter + .getSelectedItemIds())) { + } else { + // 如果删除失败,打印错误日志 + Log.e(TAG, "Delete notes error, should not happens"); + } + } else { + // 如果是同步模式,将选中的笔记移动到回收站文件夹中 + if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter + .getSelectedItemIds(), Notes.ID_TRASH_FOLER)) { + // 如果移动失败,打印错误日志 + Log.e(TAG, "Move notes to trash folder error, should not happens"); + } + } + // 返回要更新的小部件的集合 + return widgets; + } + + @Override + // 在主线程执行的方法,传入要更新的小部件的集合 + protected void onPostExecute(HashSet widgets) { + // 判断小部件集合是否为空 + if (widgets != null) { + // 遍历小部件集合 + for (AppWidgetAttribute widget : widgets) { + // 判断小部件的id和类型是否有效 + if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { + // 更新小部件的显示内容 + updateWidget(widget.widgetId, widget.widgetType); + } + } + } + // 结束操作模式 + mModeCallBack.finishActionMode(); + } + }.execute(); // 执行异步任务 + } + + // 这个方法是删除一个文件夹的,传入一个文件夹的ID + private void deleteFolder(long folderId) { + // 如果文件夹的ID是根文件夹的ID,就打印错误日志,并返回 + if (folderId == Notes.ID_ROOT_FOLDER) { + Log.e(TAG, "Wrong folder id, should not happen " + folderId); + return; + } + + // 创建一个哈希集合对象,用来存储要删除的文件夹的ID + HashSet ids = new HashSet(); + ids.add(folderId); + // 调用数据工具类的方法,获取该文件夹关联的小部件属性集合 + HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver, + folderId); + // 判断是否是同步模式 + if (!isSyncMode()) { + // 如果不是同步模式,就直接批量删除该文件夹 + DataUtils.batchDeleteNotes(mContentResolver, ids); + } else { + // 如果是同步模式,就把该文件夹批量移动到回收站文件夹中 + DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER); + } + // 如果小部件属性集合不为空,就遍历每个小部件属性对象,并更新对应的小部件 + if (widgets != null) { + for (AppWidgetAttribute widget : widgets) { + if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { + updateWidget(widget.widgetId, widget.widgetType); + } + } + } + } + + // 这个方法是打开一个笔记节点的,传入一个笔记项数据对象 + private void openNode(NoteItemData data) { + // 创建一个意图对象,并设置其动作为查看 + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + // 把笔记项数据对象的ID作为额外数据传给意图对象 + intent.putExtra(Intent.EXTRA_UID, data.getId()); + // 用该意图启动一个新的Activity,并指定请求码 + this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); + } + + // 这个方法是打开一个文件夹的,传入一个笔记项数据对象 + private void openFolder(NoteItemData data) { + // 设置当前文件夹的ID为笔记项数据对象的ID,并开始异步查询笔记列表 + mCurrentFolderId = data.getId(); + startAsyncNotesListQuery(); + // 判断笔记项数据对象的ID是否是通话记录文件夹的ID,如果是,就设置当前的列表编辑状态为通话记录文件夹状态,并隐藏添加新笔记按钮 + if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + mState = ListEditState.CALL_RECORD_FOLDER; + mAddNewNote.setVisibility(View.GONE); + } else { + // 否则,设置当前的列表编辑状态为子文件夹状态 + mState = ListEditState.SUB_FOLDER; + } + // 判断笔记项数据对象的ID是否是通话记录文件夹的ID,如果是,就设置标题栏的文本为通话记录文件夹的名称 + if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + mTitleBar.setText(R.string.call_record_folder_name); + } else { + // 否则,设置标题栏的文本为笔记项数据对象的摘要 + mTitleBar.setText(data.getSnippet()); + } + // 设置标题栏为可见 + mTitleBar.setVisibility(View.VISIBLE); + } + + // 这个方法是实现点击监听器的接口,用来处理点击事件 + public void onClick(View v) { + // 根据被点击的视图的ID,执行相应的操作 + switch (v.getId()) { + // 如果是添加新笔记按钮,就调用创建新笔记的方法 + case R.id.btn_new_note: + createNewNote(); + break; + // 如果是其他视图,就什么都不做 + default: + break; + } + } + + // 这个方法是显示软键盘的 + private void showSoftInput() { + // 获取输入法管理器对象 + InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + if (inputMethodManager != null) { + // 强制显示软键盘 + inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + } + } + + // 这个方法是隐藏软键盘的,传入一个视图对象 + private void hideSoftInput(View view) { + // 获取输入法管理器对象 + InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + // 隐藏软键盘,并传入视图对象的窗口标识符 + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + // 这个方法是显示创建或修改文件夹的对话框的,传入一个布尔值,表示是否是创建 + private void showCreateOrModifyFolderDialog(final boolean create) { + // 创建一个对话框构造器对象 + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + // 从布局文件中加载一个视图对象,并获取其中的编辑文本对象 + View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); + final EditText etName = (EditText) view.findViewById(R.id.et_foler_name); + // 显示软键盘 + showSoftInput(); + // 如果不是创建,就执行以下操作 + if (!create) { + // 如果当前的焦点笔记项数据对象不为空,就把它的摘要设置给编辑文本对象,并设置对话框的标题为修改文件夹名称 + if (mFocusNoteDataItem != null) { + etName.setText(mFocusNoteDataItem.getSnippet()); + builder.setTitle(getString(R.string.menu_folder_change_name)); + } else { + // 如果为空,就打印错误日志,并返回 + Log.e(TAG, "The long click data item is null"); + return; + } + } else { + // 如果是创建,就清空编辑文本对象,并设置对话框的标题为创建文件夹 + etName.setText(""); + builder.setTitle(this.getString(R.string.menu_create_folder)); + } + + // 设置对话框的确定按钮,但不设置点击监听器,因为要在后面自定义点击事件的逻辑 + builder.setPositiveButton(android.R.string.ok, null); + // 设置对话框的取消按钮,并设置点击监听器,用来隐藏软键盘 + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + hideSoftInput(etName); + } + }); + + // 创建并显示对话框,并获取其中的确定按钮对象 + final Dialog dialog = builder.setView(view).show(); + final Button positive = (Button)dialog.findViewById(android.R.id.button1); + // 设置确定按钮的点击监听器,用来处理用户输入的文件夹名称 + positive.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + // 隐藏软键盘 + hideSoftInput(etName); + // 获取编辑文本对象中的内容,并转换为字符串 + String name = etName.getText().toString(); + // 调用数据工具类的方法,检查该文件夹名称是否已经存在,如果存在,就弹出一个提示信息,并选中编辑文本对象中的内容,然后返回 + if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { + Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name), + Toast.LENGTH_LONG).show(); + etName.setSelection(0, etName.length()); + return; + } + // 如果不是创建,就执行以下操作 + if (!create) { + // 如果文件夹名称不为空,就创建一个内容值对象,并设置其摘要、类型和本地修改标志 + if (!TextUtils.isEmpty(name)) { + ContentValues values = new ContentValues(); + values.put(NoteColumns.SNIPPET, name); + values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); + values.put(NoteColumns.LOCAL_MODIFIED, 1); + // 调用内容解析器对象,更新对应的笔记项数据对象 + mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID + + "=?", new String[] { + String.valueOf(mFocusNoteDataItem.getId()) + }); + } + } else if (!TextUtils.isEmpty(name)) { + // 如果是创建,并且文件夹名称不为空,就创建一个内容值对象,并设置其摘要和类型 + ContentValues values = new ContentValues(); + values.put(NoteColumns.SNIPPET, name); + values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); + // 调用内容解析器对象,插入一个新的笔记项数据对象 + mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); + } + // 关闭对话框 + dialog.dismiss(); + } + }); + + // 如果编辑文本对象中的内容为空,就设置确定按钮为不可用 + if (TextUtils.isEmpty(etName.getText())) { + positive.setEnabled(false); + } + /** + * When the name edit text is null, disable the positive button + */ + // 给编辑文本对象添加一个文本变化监听器,用来实时更新确定按钮的状态 + etName.addTextChangedListener(new TextWatcher() { + // 这个方法是在文本变化之前调用的,这里没有实现任何功能 + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // TODO Auto-generated method stub + + } + + // 这个方法是在文本变化时调用的 + public void onTextChanged(CharSequence s, int start, int before, int count) { + // 如果编辑文本对象中的内容为空,就设置确定按钮为不可用,否则设置为可用 + if (TextUtils.isEmpty(etName.getText())) { + positive.setEnabled(false); + } else { + positive.setEnabled(true); + } + } + + // 这个方法是在文本变化之后调用的,这里没有实现任何功能 + public void afterTextChanged(Editable s) { + // TODO Auto-generated method stub + + } + }); + } + + @Override + // 这个方法是在用户按下返回键时调用的 + public void onBackPressed() { + // 根据当前的列表编辑状态,执行相应的操作 + switch (mState) { + // 如果是子文件夹状态,就把当前文件夹的ID设为根文件夹的ID,并把列表编辑状态设为笔记列表状态,然后开始异步查询笔记列表,并隐藏标题栏 + case SUB_FOLDER: + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + mState = ListEditState.NOTE_LIST; + startAsyncNotesListQuery(); + mTitleBar.setVisibility(View.GONE); + break; + // 如果是通话记录文件夹状态,就把当前文件夹的ID设为根文件夹的ID,并把列表编辑状态设为笔记列表状态,然后开始异步查询笔记列表,并显示添加新笔记按钮,并隐藏标题栏 + case CALL_RECORD_FOLDER: + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + mState = ListEditState.NOTE_LIST; + mAddNewNote.setVisibility(View.VISIBLE); + mTitleBar.setVisibility(View.GONE); + startAsyncNotesListQuery(); + break; + // 如果是笔记列表状态,就调用父类的方法 + case NOTE_LIST: + super.onBackPressed(); + break; + // 如果是其他状态,就什么都不做 + default: + break; + } + } + + // 这个方法是更新小部件的,传入一个小部件的ID和类型 + private void updateWidget(int appWidgetId, int appWidgetType) { + // 创建一个意图对象,并设置其动作为小部件更新 + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + // 根据小部件的类型,设置意图对象的类为对应的小部件提供器类 + if (appWidgetType == Notes.TYPE_WIDGET_2X) { + intent.setClass(this, NoteWidgetProvider_2x.class); + } else if (appWidgetType == Notes.TYPE_WIDGET_4X) { + intent.setClass(this, NoteWidgetProvider_4x.class); + } else { + // 如果不支持该类型的小部件,就打印错误日志,并返回 + Log.e(TAG, "Unspported widget type"); + return; + } + + // 把小部件的ID作为额外数据传给意图对象 + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { + appWidgetId + }); + + // 发送广播,通知小部件更新,并设置结果为成功 + sendBroadcast(intent); + setResult(RESULT_OK, intent); + } + + // 这个变量是一个创建上下文菜单的监听器对象,用来处理文件夹的上下文菜单 + private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { + // 这个方法是在创建上下文菜单时调用的,传入一个菜单对象、一个视图对象和一个菜单信息对象 + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + // 如果当前的焦点笔记项数据对象不为空,就执行以下操作 + if (mFocusNoteDataItem != null) { + // 设置菜单的标题为焦点笔记项数据对象的摘要 + menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); + // 添加三个菜单项,分别是查看文件夹、删除文件夹和修改文件夹名称,并设置其ID和标题 + menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); + menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); + menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); + } + } + }; + + @Override + // 这个方法是在上下文菜单关闭时调用的,传入一个菜单对象 + public void onContextMenuClosed(Menu menu) { + // 如果列表视图对象不为空,就设置其创建上下文菜单的监听器为null + if (mNotesListView != null) { + mNotesListView.setOnCreateContextMenuListener(null); + } + // 调用父类的方法 + super.onContextMenuClosed(menu); + } + @Override + + // 这个方法是在选择上下文菜单中的某一项时调用的,传入一个菜单项对象 + public boolean onContextItemSelected(MenuItem item) { + // 如果当前的焦点笔记项数据对象为空,就打印错误日志,并返回false + if (mFocusNoteDataItem == null) { + Log.e(TAG, "The long click data item is null"); + return false; + } + // 根据菜单项的ID,执行相应的操作 + switch (item.getItemId()) { + // 如果是查看文件夹菜单项,就调用打开文件夹的方法,传入焦点笔记项数据对象 + case MENU_FOLDER_VIEW: + openFolder(mFocusNoteDataItem); + break; + // 如果是删除文件夹菜单项,就弹出一个确认对话框,询问用户是否要删除该文件夹 + case MENU_FOLDER_DELETE: + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.alert_title_delete)); + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setMessage(getString(R.string.alert_message_delete_folder)); + // 如果用户点击确定,就调用删除文件夹的方法,传入焦点笔记项数据对象的ID + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + deleteFolder(mFocusNoteDataItem.getId()); + } + }); + // 如果用户点击取消,就什么都不做 + builder.setNegativeButton(android.R.string.cancel, null); + // 显示对话框 + builder.show(); + break; + // 如果是修改文件夹名称菜单项,就调用显示创建或修改文件夹对话框的方法,传入false表示不是创建 + case MENU_FOLDER_CHANGE_NAME: + showCreateOrModifyFolderDialog(false); + break; + // 如果是其他菜单项,就什么都不做 + default: + break; + } + + // 返回true表示处理了该事件 + return true; + } + + @Override + // 这个方法是在准备选项菜单时调用的,传入一个菜单对象 + public boolean onPrepareOptionsMenu(Menu menu) { + // 清空菜单对象中的所有菜单项 + menu.clear(); + // 根据当前的列表编辑状态,执行相应的操作 + if (mState == ListEditState.NOTE_LIST) { + // 如果是笔记列表状态,就从资源文件中加载笔记列表菜单,并设置同步或取消同步菜单项的标题 + getMenuInflater().inflate(R.menu.note_list, menu); + // set sync or sync_cancel + menu.findItem(R.id.menu_sync).setTitle( + GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync); + } else if (mState == ListEditState.SUB_FOLDER) { + // 如果是子文件夹状态,就从资源文件中加载子文件夹菜单 + getMenuInflater().inflate(R.menu.sub_folder, menu); + } else if (mState == ListEditState.CALL_RECORD_FOLDER) { + // 如果是通话记录文件夹状态,就从资源文件中加载通话记录文件夹菜单 + getMenuInflater().inflate(R.menu.call_record_folder, menu); + } else { + // 如果是其他状态,就打印错误日志 + Log.e(TAG, "Wrong state:" + mState); + } + // 返回true表示准备好了选项菜单 + return true; + } + + @Override + // 这个方法是在选择选项菜单中的某一项时调用的,传入一个菜单项对象 + public boolean onOptionsItemSelected(MenuItem item) { + // 根据菜单项的ID,执行相应的操作 + switch (item.getItemId()) { + // 如果是新建文件夹菜单项,就调用显示创建或修改文件夹对话框的方法,传入true表示是创建 + case R.id.menu_new_folder: { + showCreateOrModifyFolderDialog(true); + break; + } + // 如果是导出文本菜单项,就调用导出笔记到文本的方法 + case R.id.menu_export_text: { + exportNoteToText(); + break; + } + // 如果是同步或取消同步菜单项,就判断是否是同步模式 + case R.id.menu_sync: { + if (isSyncMode()) { + // 如果是同步模式,就判断菜单项的标题是否是同步,如果是,就调用同步服务类的方法,开始同步 + if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) { + GTaskSyncService.startSync(this); + } else { + // 否则,就调用同步服务类的方法,取消同步 + GTaskSyncService.cancelSync(this); + } + } else { + // 如果不是同步模式,就调用启动偏好设置Activity的方法 + startPreferenceActivity(); + } + break; + } + // 如果是设置菜单项,就调用启动偏好设置Activity的方法 + case R.id.menu_setting: { + startPreferenceActivity(); + break; + } + // 如果是新建笔记菜单项,就调用创建新笔记的方法 + case R.id.menu_new_note: { + createNewNote(); + break; + } + // 如果是搜索菜单项,就调用启动搜索请求的方法 + case R.id.menu_search: + onSearchRequested(); + break; + // 如果是其他菜单项,就什么都不做 + default: + break; + } + // 返回true表示处理了该事件 + return true; + } + + @Override + public boolean onSearchRequested() { + startSearch(null, false, null /* appData */, false); + return true; + } + + private void exportNoteToText() { + final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); + new AsyncTask() { + + @Override + protected Integer doInBackground(Void... unused) { + return backup.exportToText(); + } + + @Override + // 这个方法是在异步任务执行完毕后调用的 + protected void onPostExecute(Integer result) { + // 根据结果的不同,显示不同的对话框 + if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { + // 如果结果是SD卡未挂载,就创建一个警告对话框 + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + // 设置对话框的标题为导出失败 + builder.setTitle(NotesListActivity.this + .getString(R.string.failed_sdcard_export)); + // 设置对话框的内容为SD卡未挂载的错误信息 + builder.setMessage(NotesListActivity.this + .getString(R.string.error_sdcard_unmounted)); + // 设置对话框的确定按钮,点击后关闭对话框 + builder.setPositiveButton(android.R.string.ok, null); + // 显示对话框 + builder.show(); + } else if (result == BackupUtils.STATE_SUCCESS) { + // 如果结果是成功,就创建一个提示对话框 + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + // 设置对话框的标题为导出成功 + builder.setTitle(NotesListActivity.this + .getString(R.string.success_sdcard_export)); + // 设置对话框的内容为导出文件的位置和名称 + builder.setMessage(NotesListActivity.this.getString( + R.string.format_exported_file_location, backup + .getExportedTextFileName(), backup.getExportedTextFileDir())); + // 设置对话框的确定按钮,点击后关闭对话框 + builder.setPositiveButton(android.R.string.ok, null); + // 显示对话框 + builder.show(); + } else if (result == BackupUtils.STATE_SYSTEM_ERROR) { + // 如果结果是系统错误,就创建一个警告对话框 + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + // 设置对话框的标题为导出失败 + builder.setTitle(NotesListActivity.this + .getString(R.string.failed_sdcard_export)); + // 设置对话框的内容为系统错误的信息 + builder.setMessage(NotesListActivity.this + .getString(R.string.error_sdcard_export)); + // 设置对话框的确定按钮,点击后关闭对话框 + builder.setPositiveButton(android.R.string.ok, null); + // 显示对话框 + builder.show(); + } + } + +// 执行异步任务 + }.execute(); + } + + private boolean isSyncMode() { + return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + } + + private void startPreferenceActivity() { + Activity from = getParent() != null ? getParent() : this; + Intent intent = new Intent(from, NotesPreferenceActivity.class); + from.startActivityIfNeeded(intent, -1); + } + + // 这个内部类实现了列表项点击监听器的接口,用来处理列表视图中的点击事件 + private class OnListItemClickListener implements OnItemClickListener { + + // 这个方法是在点击列表视图中的某一项时调用的,传入一个父视图对象、一个被点击的视图对象、一个位置和一个ID + public void onItemClick(AdapterView parent, View view, int position, long id) { + // 如果被点击的视图对象是一个笔记列表项对象,就执行以下操作 + if (view instanceof NotesListItem) { + // 获取笔记列表项对象中的笔记项数据对象 + NoteItemData item = ((NotesListItem) view).getItemData(); + // 判断列表适配器是否处于选择模式 + if (mNotesListAdapter.isInChoiceMode()) { + // 如果是选择模式,并且笔记项数据对象的类型是笔记类型,就执行以下操作 + if (item.getType() == Notes.TYPE_NOTE) { + // 计算出被点击的位置(减去列表视图的头部视图数量),并调用操作模式回调对象的方法,改变该位置的选中状态 + position = position - mNotesListView.getHeaderViewsCount(); + mModeCallBack.onItemCheckedStateChanged(null, position, id, + !mNotesListAdapter.isSelectedItem(position)); + } + // 返回,不执行后面的操作 + return; + } + + // 根据当前的列表编辑状态,执行相应的操作 + switch (mState) { + // 如果是笔记列表状态,就判断笔记项数据对象的类型 + case NOTE_LIST: + // 如果是文件夹类型或系统类型,就调用打开文件夹的方法,传入笔记项数据对象 + if (item.getType() == Notes.TYPE_FOLDER + || item.getType() == Notes.TYPE_SYSTEM) { + openFolder(item); + } else if (item.getType() == Notes.TYPE_NOTE) { + // 如果是笔记类型,就调用打开笔记节点的方法,传入笔记项数据对象 + openNode(item); + } else { + // 如果是其他类型,就打印错误日志 + Log.e(TAG, "Wrong note type in NOTE_LIST"); + } + break; + // 如果是子文件夹状态或通话记录文件夹状态,就判断笔记项数据对象的类型 + case SUB_FOLDER: + case CALL_RECORD_FOLDER: + // 如果是笔记类型,就调用打开笔记节点的方法,传入笔记项数据对象 + if (item.getType() == Notes.TYPE_NOTE) { + openNode(item); + } else { + // 如果是其他类型,就打印错误日志 + Log.e(TAG, "Wrong note type in SUB_FOLDER"); + } + break; + // 如果是其他状态,就什么都不做 + default: + break; + } + } + } + + } + + // 这个方法是开始查询目标文件夹的 + private void startQueryDestinationFolders() { + // 设置查询条件,筛选出类型为文件夹,并且父ID不是回收站文件夹,并且ID不是当前文件夹的笔记项 + String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; + // 如果当前的列表编辑状态是笔记列表状态,就保持原来的查询条件,否则就加上根文件夹的ID + selection = (mState == ListEditState.NOTE_LIST) ? selection: + "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; + + // 调用后台查询处理器对象,开始查询笔记的数据,并指定查询标识符、投影、条件和排序方式 + mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, + null, + Notes.CONTENT_NOTE_URI, + FoldersListAdapter.PROJECTION, + selection, + new String[] { + String.valueOf(Notes.TYPE_FOLDER), + String.valueOf(Notes.ID_TRASH_FOLER), + String.valueOf(mCurrentFolderId) + }, + NoteColumns.MODIFIED_DATE + " DESC"); + } + + // 这个方法是实现列表项长按监听器的接口,用来处理列表视图中的长按事件 + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + // 如果被长按的视图对象是一个笔记列表项对象,就执行以下操作 + if (view instanceof NotesListItem) { + // 获取笔记列表项对象中的笔记项数据对象,并赋值给当前的焦点笔记项数据对象 + mFocusNoteDataItem = ((NotesListItem) view).getItemData(); + // 判断焦点笔记项数据对象的类型是否是笔记类型,并且列表适配器是否不处于选择模式 + if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { + // 如果是,就调用列表视图对象的方法,启动操作模式,并传入操作模式回调对象 + if (mNotesListView.startActionMode(mModeCallBack) != null) { + // 如果启动成功,就调用操作模式回调对象的方法,改变被长按位置的选中状态,并执行触觉反馈 + mModeCallBack.onItemCheckedStateChanged(null, position, id, true); + mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } else { + // 如果启动失败,就打印错误日志 + Log.e(TAG, "startActionMode fails"); + } + } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { + // 如果焦点笔记项数据对象的类型是文件夹类型,就设置列表视图对象的创建上下文菜单的监听器为文件夹创建上下文菜单的监听器 + mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); + } + } + // 返回false表示没有处理该事件 + return false; + } +} diff --git a/doc/精读报告/widget/NoteWidgetProvider.doc b/doc/精读报告/widget/NoteWidgetProvider.doc new file mode 100644 index 0000000..364842f --- /dev/null +++ b/doc/精读报告/widget/NoteWidgetProvider.doc @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.widget; +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.util.Log; +import android.widget.RemoteViews; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.ui.NoteEditActivity; +import net.micode.notes.ui.NotesListActivity; + +public abstract class NoteWidgetProvider extends AppWidgetProvider { + //查询便签数据库时使用的列名 + public static final String [] PROJECTION = new String [] { + NoteColumns.ID, + NoteColumns.BG_COLOR_ID, + NoteColumns.SNIPPET + }; + + //PROJECTION中对应列的下标 + public static final int COLUMN_ID = 0; + public static final int COLUMN_BG_COLOR_ID = 1; + public static final int COLUMN_SNIPPET = 2; + + //Log输出的标签 + private static final String TAG = "NoteWidgetProvider"; + + /* + * 重写onDeleted()方法,实现删除 Widget 时清除相应 Widget 对应的便签数据库中的记录 + * value:用于更新的ContentValues,将 Widget ID 设为无效值 + * appWidgetIds:被删除的所有 Widget 的 ID 数组 + */ + @Override +// 定义一个方法,用于在widget被删除时更新数据库中的widget_id字段 + public void onDeleted(Context context, int[] appWidgetIds) { + // 创建一个ContentValues对象,用于存放要更新的字段和值 + ContentValues values = new ContentValues(); + // 把widget_id设置为无效的值 + values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + //遍历所有被删除的widget的id + for (int i = 0; i < appWidgetIds.length; i++) { + // 根据widget_id更新数据库中对应的笔记记录 + context.getContentResolver().update(Notes.CONTENT_NOTE_URI, + values, + NoteColumns.WIDGET_ID + "=?", + new String[] { String.valueOf(appWidgetIds[i])}); + } + } + +// 定义一个方法,用于根据widget_id查询数据库中的笔记信息 + private Cursor getNoteWidgetInfo(Context context, int widgetId) { + // 使用ContentResolver查询笔记表,返回一个Cursor对象 + return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, + PROJECTION, + NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", + new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, + null); + } + +// 定义一个方法,用于更新widget的视图 + protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + // 调用另一个重载的update方法,传入false表示不是访客模式 + update(context, appWidgetManager, appWidgetIds, false); + } + private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, + boolean privacyMode) { + for (int i = 0; i < appWidgetIds.length; i++) { + if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { + // 获取 Widget 的默认背景 ID + int bgId = ResourceParser.getDefaultBgId(context); + // 默认便签摘要为空字符串 + String snippet = ""; + // 创建用于启动编辑页面的 Intent,并添加必要的参数 + Intent intent = new Intent(context, NoteEditActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); + + // 从数据库中查询指定 Widget ID 对应的便签信息 + Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); + if (c != null && c.moveToFirst()) { + // 检查查询结果是否出现异常,如出现异常则打印错误日志和异常信息 + if (c.getCount() > 1) { + Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); + c.close(); + return; + } + // 设置 Widget 中需要显示的便签摘要、背景 ID、绑定的便签 ID 和点击跳转功能 + snippet = c.getString(COLUMN_SNIPPET); + bgId = c.getInt(COLUMN_BG_COLOR_ID); + intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); + intent.setAction(Intent.ACTION_VIEW); + } else { + // 若查询结果为空,则打开编辑页面并设置其模式为插入模式 + snippet = context.getResources().getString(R.string.widget_havenot_content); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + } + + // 关闭查询结果的 Cursor 对象 + if (c != null) { + c.close(); + } + + // 创建 RemoteViews 对象,并为其设置相应布局及需要显示的参数 + RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); + rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); + intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); + + /** + * Generate the pending intent to start host for the widget + */ + // 定义一个变量pendingIntent,初始化为null + PendingIntent pendingIntent = null; +// 如果privacyMode为真,表示用户处于访客模式 + if (privacyMode) { + // 设置widget_text的文本为“访客模式” + rv.setTextViewText(R.id.widget_text, + context.getString(R.string.widget_under_visit_mode)); + // 创建一个PendingIntent,用于启动NotesListActivity + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( + context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); + } else { + // 否则,设置widget_text的文本为snippet,即笔记的摘要 + rv.setTextViewText(R.id.widget_text, snippet); + // 创建一个PendingIntent,用于启动intent指定的Activity + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } + +// 设置widget_text的点击事件为pendingIntent + rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); +// 更新widget的视图 + appWidgetManager.updateAppWidget(appWidgetIds[i], rv); + } + } + } + +// 定义一个抽象方法,用于根据bgId返回背景资源的id + protected abstract int getBgResourceId(int bgId); + +// 定义一个抽象方法,用于返回布局资源的id + protected abstract int getLayoutId(); + +// 定义一个抽象方法,用于返回widget的类型 + protected abstract int getWidgetType(); +} diff --git a/doc/精读报告/widget/NoteWidgetProvider_2x.doc b/doc/精读报告/widget/NoteWidgetProvider_2x.doc new file mode 100644 index 0000000..9933e13 --- /dev/null +++ b/doc/精读报告/widget/NoteWidgetProvider_2x.doc @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.widget; //声明package + +import android.appwidget.AppWidgetManager; //引用类 +import android.content.Context; //引用类 + +import net.micode.notes.R; //引用类 +import net.micode.notes.data.Notes; //引用类 +import net.micode.notes.tool.ResourceParser; //引用类 + +public class NoteWidgetProvider_2x extends NoteWidgetProvider {//继承NoteWidgetProvider类 + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + super.update(context, appWidgetManager, appWidgetIds); //调用NoteWidgetProvider的update方法 + } + + @Override + protected int getLayoutId() { //重写NoteWidgetProvider中的getLayoutId方法 + return R.layout.widget_2x; //返回 widget_2x 布局文件的ID + } + + @Override + protected int getBgResourceId(int bgId) { //重写NoteWidgetProvider中的getBgResourceId方法,接受传入的bgId参数 + return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); //调用ResourceParser类中的getWidget2xBgResource(bgId)方法,返回该bgId对应的Widget2xBg资源ID + } + + @Override + protected int getWidgetType() { //重写NoteWidgetProvider中的getWidgetType方法 + return Notes.TYPE_WIDGET_2X; //返回Note中的TYPE_WIDGET_2X常量值 + } +} + diff --git a/doc/精读报告/widget/NoteWidgetProvider_4x.doc b/doc/精读报告/widget/NoteWidgetProvider_4x.doc new file mode 100644 index 0000000..17222a0 --- /dev/null +++ b/doc/精读报告/widget/NoteWidgetProvider_4x.doc @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.widget; + +import android.appwidget.AppWidgetManager; +import android.content.Context; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.tool.ResourceParser; + + +public class NoteWidgetProvider_4x extends NoteWidgetProvider { + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + // 调用父类中的 update 方法,进行具体的 Widget 内容更新操作 + super.update(context, appWidgetManager, appWidgetIds); + } + + + protected int getLayoutId() { + return R.layout.widget_4x; + } + + /** + * 根据传入的背景颜色 ID 获取对应的背景资源 ID + * @param bgId 背景颜色 ID + * @return 对应的背景资源 ID + */ + @Override + protected int getBgResourceId(int bgId) { + return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); + } + + /** + * 获取该 Widget 对应的类型常量,用于在程序中进行判断和处理 + * @return 4x4 Widget 对应的类型常量 + */ + @Override + protected int getWidgetType() { + return Notes.TYPE_WIDGET_4X; + } +}