Compare commits

...

39 Commits

Author SHA1 Message Date
13864042598 04c7485085 Merge branch 'huyunfei'
2 months ago
hyf e4cadc5fa1 胡云飞--ui包
2 months ago
XJS 6e7f066ceb Merge branch 'xingjiasen'
2 months ago
XYJ 7939ce2f52 Merge branch 'xinyajie'
2 months ago
hyf 6e14c3527a Merge branch 'huyunfei'
2 months ago
hyf ec0ea1cced 胡云飞
2 months ago
hyf 69ede62db3 胡云飞
2 months ago
hyf edb24d5740 质量分析报告
2 months ago
13864042598 c9a359ebd9 tool包
2 months ago
XYJ 8dd7a9b830 信亚杰 gtask包remote包
2 months ago
hyf 1249eaffd8 ..
2 months ago
hyf 5ad54ad609 泛读报告
2 months ago
XYJ 51814cff35 信亚杰 data包注释
2 months ago
13864042598 557e9f6072 22
2 months ago
13864042598 6e4861104b 11
2 months ago
HYF 7f87b931f0 文档
2 months ago
HYF c40efe34a5 12.20
2 months ago
HYF 3838db52b6 12.20
2 months ago
13864042598 8b526d7be0 10.17
4 months ago
XYJ db432618c6 10.17-xinyajie
4 months ago
HYF f2b3d1d113 10.17-huyunfei
4 months ago
HYF 3875d37f70 10.17-huyunfei
4 months ago
HYF 19ed944802 10.17-huyunfei
4 months ago
HYF 50c175747d 10.17-huyunfei
4 months ago
HYF f719fe087d 10.17-huyunfei
4 months ago
13864042598 c7649f85c5 Merge branch 'main' of https://bdgit.educoder.net/pa2tefyxw/xiaomibianqian
4 months ago
HYF 4a1d1601ff hebingceshi
4 months ago
pa2tefyxw 411868b8c3 Merge pull request '合并测试' (#5) from huyunfei into main
4 months ago
HYF 8a5f5d3c17 10.17-huyunfei
4 months ago
13864042598 7f8e96e4a0 10.17-ceshi
4 months ago
13864042598 db1d3885a1 10.17-huyunfei
4 months ago
XYJ 569b393556 10.17--huyunfei
4 months ago
pa2tefyxw 45433723b2 Update README.md
4 months ago
13864042598 6535529958 Merge branch 'xinyajie' of https://bdgit.educoder.net/pa2tefyxw/xiaomibianqian into xinyajie
4 months ago
hyf b044e40ef9 Merge branch 'main' of https://bdgit.educoder.net/pa2tefyxw/xiaomibianqian into xinyajie
4 months ago
pa2tefyxw c10b60cb0c Update README.md
4 months ago
13864042598 64bfbfadfa Merge branch 'main' of https://bdgit.educoder.net/pa2tefyxw/xiaomibianqian into xinyajie
4 months ago
hyf 5ce76cc89c 10.16
4 months ago
HYF b7ddca9ff8 xinyajie.txt
6 months ago

@ -0,0 +1 @@
main分支

@ -25,47 +25,75 @@ import android.util.Log;
import java.util.HashMap; import java.util.HashMap;
<<<<<<< HEAD
//ceshihebing2
public class Contact { public class Contact {
private static HashMap<String, String> sContactCache; private static HashMap<String, String> sContactCache;
private static final String TAG = "Contact"; private static final String TAG = "Contact";
=======
// 该类用于获取与给定电话号码对应的联系人姓名,通过缓存已查询结果来提高性能
>>>>>>> xinyajie
public class Contact {
// 该类用于获取与给定电话号码对应的联系人姓名,通过缓存已查询结果来提高性能
private static HashMap<String, String> sContactCache;
// 日志标签,用于在 Log 输出中标识该类的相关信息
private static final String TAG = "Contact";
// 查询条件字符串,用于从联系人数据库中筛选出匹配电话号码的记录
// 它通过比较电话号码和联系人数据中的电话号码,确保数据类型为电话类型,并限制在特定的 raw_contact_id 范围内进行查询
// 其中PHONE_NUMBERS_EQUAL 是一个函数,用于比较电话号码是否相等,这里使用了占位符 '?',后续会替换为实际的电话号码
// Data.MIMETYPE 用于指定数据类型Phone.CONTENT_ITEM_TYPE 表示电话类型的数据
// Data.RAW_CONTACT_ID 用于指定原始联系人 ID这里通过子查询从 phone_lookup 表中获取满足 min_match = '+' 条件的 raw_contact_id
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN " + " AND " + Data.RAW_CONTACT_ID + " IN "
+ "(SELECT raw_contact_id " + "(SELECT raw_contact_id "
+ " FROM phone_lookup" + " FROM phone_lookup"
+ " WHERE min_match = '+')"; + " WHERE min_match = '+')";
// 根据给定的上下文和电话号码获取联系人姓名的静态方法
public static String getContact(Context context, String phoneNumber) { public static String getContact(Context context, String phoneNumber) {
// 如果缓存为空,则创建一个新的 HashMap 用于缓存
if(sContactCache == null) { if(sContactCache == null) {
sContactCache = new HashMap<String, String>(); sContactCache = new HashMap<String, String>();
} }
// 首先检查缓存中是否已经存在该电话号码对应的联系人姓名,如果存在则直接返回缓存中的结果
if(sContactCache.containsKey(phoneNumber)) { if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber); return sContactCache.get(phoneNumber);
} }
// 替换查询条件中的 '+' 为电话号码的最小匹配格式,以便进行更准确的查询
String selection = CALLER_ID_SELECTION.replace("+", String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
// 使用 ContentResolver 查询联系人数据库,获取匹配电话号码的联系人姓名
// 查询的 URI 为 Data.CONTENT_URI表示联系人数据的通用 URI
// 查询的列仅包含 Phone.DISPLAY_NAME即联系人的显示名称
// 选择条件为上面生成的 selection选择参数为实际的电话号码
// 排序方式为默认,这里传入 null
Cursor cursor = context.getContentResolver().query( Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI, Data.CONTENT_URI,
new String [] { Phone.DISPLAY_NAME }, new String [] { Phone.DISPLAY_NAME },
selection, selection,
new String[] { phoneNumber }, new String[] { phoneNumber },
null); null);
// 如果查询结果不为空且游标能够移动到第一条记录,则表示找到了匹配的联系人
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
try { try {
// 从游标中获取联系人姓名,索引为 0因为查询结果只包含一列联系人姓名
String name = cursor.getString(0); String name = cursor.getString(0);
// 将查询到的电话号码和对应的联系人姓名存入缓存
sContactCache.put(phoneNumber, name); sContactCache.put(phoneNumber, name);
// 返回联系人姓名
return name; return name;
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
// 如果在获取游标数据时发生越界异常,则记录错误日志,并返回 null
Log.e(TAG, " Cursor get string error " + e.toString()); Log.e(TAG, " Cursor get string error " + e.toString());
return null; return null;
} finally { } finally {
// 无论是否发生异常,都需要关闭游标,释放资源
cursor.close(); cursor.close();
} }
} else { } else {
// 如果没有找到匹配的联系人,则记录调试日志,并返回 null
Log.d(TAG, "No contact matched with number:" + phoneNumber); Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null; return null;
} }

@ -17,12 +17,20 @@
package net.micode.notes.data; package net.micode.notes.data;
import android.net.Uri; import android.net.Uri;
// 该类用于定义笔记应用程序中的各种常量和数据结构相关信息
public class Notes { public class Notes {
// 定义笔记应用的授权Authority用于 Content Provider 的标识
public static final String AUTHORITY = "micode_notes"; public static final String AUTHORITY = "micode_notes";
// 日志标签,用于在应用中标记与笔记相关的日志信息
public static final String TAG = "Notes"; public static final String TAG = "Notes";
// 定义不同类型的笔记或文件夹
public static final int TYPE_NOTE = 0; public static final int TYPE_NOTE = 0;
public static final int TYPE_FOLDER = 1; public static final int TYPE_FOLDER = 1;
public static final int TYPE_SYSTEM = 2; public static final int TYPE_SYSTEM = 2;
// 系统文件夹的标识符
// ID_ROOT_FOLDER 是默认文件夹的 ID
/** /**
* Following IDs are system folders' identifiers * Following IDs are system folders' identifiers
@ -31,179 +39,194 @@ public class Notes {
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/ */
public static final int ID_ROOT_FOLDER = 0; public static final int ID_ROOT_FOLDER = 0;
// ID_TEMPARAY_FOLDER 用于存放不属于任何文件夹的笔记
public static final int ID_TEMPARAY_FOLDER = -1; public static final int ID_TEMPARAY_FOLDER = -1;
// ID_CALL_RECORD_FOLDER 用于存储通话记录的文件夹 ID
public static final int ID_CALL_RECORD_FOLDER = -2; public static final int ID_CALL_RECORD_FOLDER = -2;
// ID_TRASH_FOLER 垃圾桶文件夹 ID
public static final int ID_TRASH_FOLER = -3; public static final int ID_TRASH_FOLER = -3;
// 用于传递提醒日期的 Intent 额外数据键
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date";
// 用于传递背景颜色 ID 的 Intent 额外数据键
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id";
// 用于传递小部件 ID 的 Intent 额外数据键
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id";
// 用于传递小部件类型的 Intent 额外数据键
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type";
// 用于传递文件夹 ID 的 Intent 额外数据键
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id";
// 用于传递通话日期的 Intent 额外数据键
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date";
// 无效的小部件类型
public static final int TYPE_WIDGET_INVALIDE = -1; public static final int TYPE_WIDGET_INVALIDE = -1;
// 2x 小部件类型
public static final int TYPE_WIDGET_2X = 0; public static final int TYPE_WIDGET_2X = 0;
// 4x 小部件类型
public static final int TYPE_WIDGET_4X = 1; public static final int TYPE_WIDGET_4X = 1;
// 内部类,用于定义数据常量
public static class DataConstants { public static class DataConstants {
// 文本笔记的 Content Item Type
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
// 通话笔记的 Content Item Type
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
} }
// 用于查询所有笔记和文件夹的 Uri
/** /**
* Uri to query all notes and folders * Uri to query all notes and folders
*/ */
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
// 用于查询数据的 Uri
/** /**
* Uri to query data * Uri to query data
*/ */
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
// 定义笔记列的接口
public interface NoteColumns { public interface NoteColumns {
// 行的唯一 ID类型为长整型整数
/** /**
* The unique ID for a row * The unique ID for a row
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String ID = "_id"; public static final String ID = "_id";
// 笔记或文件夹的父 ID类型为长整型整数
/** /**
* The parent's id for note or folder * The parent's id for note or folder
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String PARENT_ID = "parent_id"; public static final String PARENT_ID = "parent_id";
// 笔记或文件夹的创建日期,类型为长整型整数
/** /**
* Created data for note or folder * Created data for note or folder
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String CREATED_DATE = "created_date"; public static final String CREATED_DATE = "created_date";
// 最新修改日期,类型为长整型整数
/** /**
* Latest modified date * Latest modified date
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String MODIFIED_DATE = "modified_date"; public static final String MODIFIED_DATE = "modified_date";
// 提醒日期,类型为长整型整数
/** /**
* Alert date * Alert date
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String ALERTED_DATE = "alert_date"; public static final String ALERTED_DATE = "alert_date";
// 文件夹名称或笔记的文本内容,类型为文本
/** /**
* Folder's name or text content of note * Folder's name or text content of note
* <P> Type: TEXT </P> * <P> Type: TEXT </P>
*/ */
public static final String SNIPPET = "snippet"; public static final String SNIPPET = "snippet";
// 笔记的小部件 ID类型为长整型整数
/** /**
* Note's widget id * Note's widget id
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String WIDGET_ID = "widget_id"; public static final String WIDGET_ID = "widget_id";
// 笔记的小部件类型,类型为长整型整数
/** /**
* Note's widget type * Note's widget type
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String WIDGET_TYPE = "widget_type"; public static final String WIDGET_TYPE = "widget_type";
// 笔记的背景颜色 ID类型为长整型整数
/** /**
* Note's background color's id * Note's background color's id
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String BG_COLOR_ID = "bg_color_id"; public static final String BG_COLOR_ID = "bg_color_id";
// 对于文本笔记,无附件时为 0多媒体笔记至少有一个附件时为 1类型为整数
/** /**
* For text note, it doesn't has attachment, for multi-media * For text note, it doesn't has attachment, for multi-media
* note, it has at least one attachment * note, it has at least one attachment
* <P> Type: INTEGER </P> * <P> Type: INTEGER </P>
*/ */
public static final String HAS_ATTACHMENT = "has_attachment"; public static final String HAS_ATTACHMENT = "has_attachment";
// 文件夹中的笔记数量,类型为长整型整数
/** /**
* Folder's count of notes * Folder's count of notes
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String NOTES_COUNT = "notes_count"; public static final String NOTES_COUNT = "notes_count";
// 文件类型(文件夹或笔记),类型为整数
/** /**
* The file type: folder or note * The file type: folder or note
* <P> Type: INTEGER </P> * <P> Type: INTEGER </P>
*/ */
public static final String TYPE = "type"; public static final String TYPE = "type";
// 最后同步 ID类型为长整型整数
/** /**
* The last sync id * The last sync id
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String SYNC_ID = "sync_id"; public static final String SYNC_ID = "sync_id";
// 表示本地是否修改的标志,类型为整数
/** /**
* Sign to indicate local modified or not * Sign to indicate local modified or not
* <P> Type: INTEGER </P> * <P> Type: INTEGER </P>
*/ */
public static final String LOCAL_MODIFIED = "local_modified"; public static final String LOCAL_MODIFIED = "local_modified";
// 移动到临时文件夹之前的原始父 ID类型为整数
/** /**
* Original parent id before moving into temporary folder * Original parent id before moving into temporary folder
* <P> Type : INTEGER </P> * <P> Type : INTEGER </P>
*/ */
public static final String ORIGIN_PARENT_ID = "origin_parent_id"; public static final String ORIGIN_PARENT_ID = "origin_parent_id";
// gtask ID类型为文本
/** /**
* The gtask id * The gtask id
* <P> Type : TEXT </P> * <P> Type : TEXT </P>
*/ */
public static final String GTASK_ID = "gtask_id"; public static final String GTASK_ID = "gtask_id";
// 版本代码,类型为长整型整数
/** /**
* The version code * The version code
* <P> Type : INTEGER (long) </P> * <P> Type : INTEGER (long) </P>
*/ */
public static final String VERSION = "version"; public static final String VERSION = "version";
} }
// 定义数据列的接口
public interface DataColumns { public interface DataColumns {
// 行的唯一 ID类型为长整型整数
/** /**
* The unique ID for a row * The unique ID for a row
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String ID = "_id"; public static final String ID = "_id";
// 此行表示的项目的 MIME 类型,类型为文本
/** /**
* The MIME type of the item represented by this row. * The MIME type of the item represented by this row.
* <P> Type: Text </P> * <P> Type: Text </P>
*/ */
public static final String MIME_TYPE = "mime_type"; public static final String MIME_TYPE = "mime_type";
// 此数据所属笔记的引用 ID类型为长整型整数
/** /**
* The reference id to note that this data belongs to * The reference id to note that this data belongs to
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String NOTE_ID = "note_id"; public static final String NOTE_ID = "note_id";
// 笔记或文件夹的创建日期,类型为长整型整数
/** /**
* Created data for note or folder * Created data for note or folder
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String CREATED_DATE = "created_date"; public static final String CREATED_DATE = "created_date";
// 最新修改日期,类型为长整型整数
/** /**
* Latest modified date * Latest modified date
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String MODIFIED_DATE = "modified_date"; public static final String MODIFIED_DATE = "modified_date";
// 数据内容,类型为文本
/** /**
* Data's content * Data's content
* <P> Type: TEXT </P> * <P> Type: TEXT </P>
*/ */
public static final String CONTENT = "content"; public static final String CONTENT = "content";
// 通用数据列,含义取决于 MIME_TYPE用于整数数据类型
/** /**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
@ -211,28 +234,28 @@ public class Notes {
* <P> Type: INTEGER </P> * <P> Type: INTEGER </P>
*/ */
public static final String DATA1 = "data1"; public static final String DATA1 = "data1";
// 通用数据列,含义取决于 MIME_TYPE用于整数数据类型
/** /**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type * integer data type
* <P> Type: INTEGER </P> * <P> Type: INTEGER </P>
*/ */
public static final String DATA2 = "data2"; public static final String DATA2 = "data2";
// 通用数据列,含义取决于 MIME_TYPE用于文本数据类型
/** /**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type * TEXT data type
* <P> Type: TEXT </P> * <P> Type: TEXT </P>
*/ */
public static final String DATA3 = "data3"; public static final String DATA3 = "data3";
// 通用数据列,含义取决于 MIME_TYPE用于文本数据类型
/** /**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type * TEXT data type
* <P> Type: TEXT </P> * <P> Type: TEXT </P>
*/ */
public static final String DATA4 = "data4"; public static final String DATA4 = "data4";
// 通用数据列,含义取决于 MIME_TYPE用于文本数据类型
/** /**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type * TEXT data type
@ -240,8 +263,10 @@ public class Notes {
*/ */
public static final String DATA5 = "data5"; public static final String DATA5 = "data5";
} }
// 文本笔记类,实现 DataColumns 接口
public static final class TextNote implements DataColumns { public static final class TextNote implements DataColumns {
// 表示文本是否处于清单模式的模式1 为清单模式0 为正常模式,类型为整数
/** /**
* Mode to indicate the text in check list mode or not * Mode to indicate the text in check list mode or not
* <P> Type: Integer 1:check list mode 0: normal mode </P> * <P> Type: Integer 1:check list mode 0: normal mode </P>
@ -249,31 +274,32 @@ public class Notes {
public static final String MODE = DATA1; public static final String MODE = DATA1;
public static final int MODE_CHECK_LIST = 1; public static final int MODE_CHECK_LIST = 1;
// 文本笔记的 Content Type目录
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
// 文本笔记的 Content Item Type项目
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";
// 文本笔记的 Content Uri
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
} }
// 通话笔记类,实现 DataColumns 接口
public static final class CallNote implements DataColumns { public static final class CallNote implements DataColumns {
// 通话记录的通话日期,类型为长整型整数
/** /**
* Call date for this record * Call date for this record
* <P> Type: INTEGER (long) </P> * <P> Type: INTEGER (long) </P>
*/ */
public static final String CALL_DATE = DATA1; public static final String CALL_DATE = DATA1;
// 通话记录的电话号码,类型为文本
/** /**
* Phone number for this record * Phone number for this record
* <P> Type: TEXT </P> * <P> Type: TEXT </P>
*/ */
public static final String PHONE_NUMBER = DATA3; public static final String PHONE_NUMBER = DATA3;
// 通话笔记的 Content Type目录
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
// 通话笔记的 Content Item Type项目
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
// 通话笔记的 Content Uri
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
} }
} }

@ -21,27 +21,28 @@ import android.content.Context;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log; import android.util.Log;
// 该类继承自 SQLiteOpenHelper用于管理笔记应用的数据库创建、升级和相关操作
import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
public class NotesDatabaseHelper extends SQLiteOpenHelper { public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 数据库名称
private static final String DB_NAME = "note.db"; private static final String DB_NAME = "note.db";
// 数据库版本号
private static final int DB_VERSION = 4; private static final int DB_VERSION = 4;
// 定义数据库表名的接口
public interface TABLE { public interface TABLE {
public static final String NOTE = "note"; public static final String NOTE = "note";
public static final String DATA = "data"; public static final String DATA = "data";
} }
// 日志标签,用于在数据库操作过程中记录日志信息
private static final String TAG = "NotesDatabaseHelper"; private static final String TAG = "NotesDatabaseHelper";
// 单例模式实例
private static NotesDatabaseHelper mInstance; private static NotesDatabaseHelper mInstance;
// 创建笔记表的 SQL 语句
private static final String CREATE_NOTE_TABLE_SQL = private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" + "CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," + NoteColumns.ID + " INTEGER PRIMARY KEY," +
@ -62,7 +63,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")"; ")";
// 创建数据表的 SQL 语句
private static final String CREATE_DATA_TABLE_SQL = private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" + "CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," + DataColumns.ID + " INTEGER PRIMARY KEY," +
@ -77,11 +78,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
")"; ")";
// 在数据表上创建基于 note_id 的索引 SQL 语句,如果不存在则创建
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " + "CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
// 当笔记移动到文件夹时增加文件夹笔记数量的触发器 SQL 语句
/** /**
* Increase folder's note count when move note to the folder * Increase folder's note count when move note to the folder
*/ */
@ -93,7 +94,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END"; " END";
// 当笔记从文件夹移出时减少文件夹笔记数量的触发器 SQL 语句
/** /**
* Decrease folder's note count when move note from folder * Decrease folder's note count when move note from folder
*/ */
@ -106,7 +107,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
" END"; " END";
// 当在文件夹中插入新笔记时增加文件夹笔记数量的触发器 SQL 语句
/** /**
* Increase folder's note count when insert new note to the folder * Increase folder's note count when insert new note to the folder
*/ */
@ -118,7 +119,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END"; " END";
// 当从文件夹中删除笔记时减少文件夹笔记数量的触发器 SQL 语句
/** /**
* Decrease folder's note count when delete note from the folder * Decrease folder's note count when delete note from the folder
*/ */
@ -131,7 +132,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0;" + " AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END"; " END";
// 当插入类型为笔记的数据时更新笔记内容的触发器 SQL 语句
/** /**
* Update note's content when insert data with type {@link DataConstants#NOTE} * Update note's content when insert data with type {@link DataConstants#NOTE}
*/ */
@ -144,7 +145,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END"; " END";
// 当笔记类型的数据更新时更新笔记内容的触发器 SQL 语句
/** /**
* Update note's content when data with {@link DataConstants#NOTE} type has changed * Update note's content when data with {@link DataConstants#NOTE} type has changed
*/ */
@ -157,7 +158,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END"; " END";
// 当笔记类型的数据删除时更新笔记内容的触发器 SQL 语句
/** /**
* Update note's content when data with {@link DataConstants#NOTE} type has deleted * Update note's content when data with {@link DataConstants#NOTE} type has deleted
*/ */
@ -170,7 +171,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" SET " + NoteColumns.SNIPPET + "=''" + " SET " + NoteColumns.SNIPPET + "=''" +
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
" END"; " END";
// 当笔记被删除时删除其关联数据的触发器 SQL 语句
/** /**
* Delete datas belong to note which has been deleted * Delete datas belong to note which has been deleted
*/ */
@ -181,7 +182,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" DELETE FROM " + TABLE.DATA + " DELETE FROM " + TABLE.DATA +
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +
" END"; " END";
// 当文件夹被删除时删除其关联笔记的触发器 SQL 语句
/** /**
* Delete notes belong to folder which has been deleted * Delete notes belong to folder which has been deleted
*/ */
@ -192,7 +193,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" DELETE FROM " + TABLE.NOTE + " DELETE FROM " + TABLE.NOTE +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END"; " END";
// 当文件夹被移动到垃圾桶文件夹时移动其关联笔记的触发器 SQL 语句
/** /**
* Move notes belong to folder which has been moved to trash folder * Move notes belong to folder which has been moved to trash folder
*/ */
@ -205,18 +206,18 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END"; " END";
// 构造函数,调用父类 SQLiteOpenHelper 的构造函数,传入上下文、数据库名称、游标工厂(这里为 null和版本号
public NotesDatabaseHelper(Context context) { public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION); super(context, DB_NAME, null, DB_VERSION);
} }
// 创建笔记表的方法,执行创建表的 SQL 语句,重新创建触发器,并创建系统文件夹
public void createNoteTable(SQLiteDatabase db) { public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL); db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db); reCreateNoteTableTriggers(db);
createSystemFolder(db); createSystemFolder(db);
Log.d(TAG, "note table has been created"); Log.d(TAG, "note table has been created");
} }
// 重新创建笔记表触发器的方法,先删除已存在的相关触发器,再创建新的触发器
private void reCreateNoteTableTriggers(SQLiteDatabase db) { private void reCreateNoteTableTriggers(SQLiteDatabase db) {
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
@ -234,17 +235,17 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
} }
// 创建系统文件夹的方法,向笔记表中插入通话记录文件夹、根文件夹、临时文件夹和垃圾桶文件夹的记录
private void createSystemFolder(SQLiteDatabase db) { private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
// 通话记录文件夹
/** /**
* call record foler for call notes * call record foler for call notes
*/ */
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); db.insert(TABLE.NOTE, null, values);
// 根文件夹
/** /**
* root folder which is default folder * root folder which is default folder
*/ */
@ -252,7 +253,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); db.insert(TABLE.NOTE, null, values);
// 临时文件夹
/** /**
* temporary folder which is used for moving note * temporary folder which is used for moving note
*/ */
@ -260,7 +261,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); db.insert(TABLE.NOTE, null, values);
// 垃圾桶文件夹
/** /**
* create trash folder * create trash folder
*/ */
@ -269,14 +270,14 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); db.insert(TABLE.NOTE, null, values);
} }
// 创建数据表的方法,执行创建表的 SQL 语句,重新创建触发器,并创建索引
public void createDataTable(SQLiteDatabase db) { public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL); db.execSQL(CREATE_DATA_TABLE_SQL);
reCreateDataTableTriggers(db); reCreateDataTableTriggers(db);
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
Log.d(TAG, "data table has been created"); Log.d(TAG, "data table has been created");
} }
// 重新创建数据表触发器的方法,先删除已存在的相关触发器,再创建新的触发器
private void reCreateDataTableTriggers(SQLiteDatabase db) { private void reCreateDataTableTriggers(SQLiteDatabase db) {
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
@ -286,20 +287,20 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
} }
// 单例模式获取实例的方法,确保只有一个 NotesDatabaseHelper 实例存在
static synchronized NotesDatabaseHelper getInstance(Context context) { static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) { if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context); mInstance = new NotesDatabaseHelper(context);
} }
return mInstance; return mInstance;
} }
// 重写 onCreate 方法,在数据库首次创建时调用,创建笔记表和数据表
@Override @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
createNoteTable(db); createNoteTable(db);
createDataTable(db); createDataTable(db);
} }
// 重写 onUpgrade 方法,在数据库版本升级时调用,根据不同的旧版本执行相应的升级操作
@Override @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false; boolean reCreateTriggers = false;
@ -307,7 +308,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
if (oldVersion == 1) { if (oldVersion == 1) {
upgradeToV2(db); upgradeToV2(db);
skipV2 = true; // this upgrade including the upgrade from v2 to v3 skipV2 = true; // 此次升级包含从 v2 到 v3 的升级
oldVersion++; oldVersion++;
} }
@ -332,29 +333,29 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
+ "fails"); + "fails");
} }
} }
// 从版本 1 升级到版本 2 的方法,删除旧表并重新创建新表
private void upgradeToV2(SQLiteDatabase db) { private void upgradeToV2(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
createNoteTable(db); createNoteTable(db);
createDataTable(db); createDataTable(db);
} }
// 从版本 2 升级到版本 3 的方法,删除无用触发器,添加 gtask_id 列,并添加垃圾桶系统文件夹
private void upgradeToV3(SQLiteDatabase db) { private void upgradeToV3(SQLiteDatabase db) {
// drop unused triggers // 删除无用触发器
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// add a column for gtask id // 添加 gtask_id 列
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''"); + " TEXT NOT NULL DEFAULT ''");
// add a trash system folder // 添加垃圾桶系统文件夹
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); db.insert(TABLE.NOTE, null, values);
} }
// 从版本 3 升级到版本 4 的方法,添加 version 列
private void upgradeToV4(SQLiteDatabase db) { private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0"); + " INTEGER NOT NULL DEFAULT 0");

@ -34,14 +34,15 @@ import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE; import net.micode.notes.data.NotesDatabaseHelper.TABLE;
// 该类继承自 ContentProvider用于在 Android 系统中提供笔记数据的增删改查功能,并处理搜索相关操作
public class NotesProvider extends ContentProvider { public class NotesProvider extends ContentProvider {
// 用于匹配不同的 Uri 模式的 UriMatcher 实例
private static final UriMatcher mMatcher; private static final UriMatcher mMatcher;
// 数据库帮助类实例,用于操作数据库
private NotesDatabaseHelper mHelper; private NotesDatabaseHelper mHelper;
// 日志标签,用于记录日志信息
private static final String TAG = "NotesProvider"; private static final String TAG = "NotesProvider";
// 定义不同 Uri 模式的匹配码
private static final int URI_NOTE = 1; private static final int URI_NOTE = 1;
private static final int URI_NOTE_ITEM = 2; private static final int URI_NOTE_ITEM = 2;
private static final int URI_DATA = 3; private static final int URI_DATA = 3;
@ -49,7 +50,7 @@ public class NotesProvider extends ContentProvider {
private static final int URI_SEARCH = 5; private static final int URI_SEARCH = 5;
private static final int URI_SEARCH_SUGGEST = 6; private static final int URI_SEARCH_SUGGEST = 6;
// 静态代码块,用于初始化 UriMatcher添加各种 Uri 模式的匹配规则
static { static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH); mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
@ -60,7 +61,8 @@ public class NotesProvider extends ContentProvider {
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
} }
// 搜索结果投影字符串,用于定义搜索结果中返回的列信息
// 包括笔记 ID、将笔记 ID 作为意图额外数据、修剪和替换换行符后的笔记摘要作为搜索建议的文本列、图标资源 ID、意图动作和意图数据类型
/** /**
* x'0A' represents the '\n' character in sqlite. For title and content in the search result, * x'0A' represents the '\n' character in sqlite. For title and content in the search result,
* we will trim '\n' and white space in order to show more information. * we will trim '\n' and white space in order to show more information.
@ -72,52 +74,61 @@ public class NotesProvider extends ContentProvider {
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + ","
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;
// 搜索查询语句,用于从笔记表中查询符合搜索条件的笔记信息
// 搜索条件是笔记摘要包含指定字符串,且笔记不在垃圾桶文件夹中,并且是普通笔记类型
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION
+ " FROM " + TABLE.NOTE + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?" + " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
// 重写 ContentProvider 的 onCreate 方法,在 ContentProvider 启动时调用
// 用于获取 NotesDatabaseHelper 的单例实例
@Override @Override
public boolean onCreate() { public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext()); mHelper = NotesDatabaseHelper.getInstance(getContext());
return true; return true;
} }
// 重写 ContentProvider 的 query 方法,用于处理数据查询请求
@Override @Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) { String sortOrder) {
Cursor c = null; Cursor c = null;
SQLiteDatabase db = mHelper.getReadableDatabase(); SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null; String id = null;
// 根据 Uri 匹配码执行不同的查询操作
switch (mMatcher.match(uri)) { switch (mMatcher.match(uri)) {
case URI_NOTE: case URI_NOTE:
// 查询笔记表,返回符合条件的游标
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder); sortOrder);
break; break;
case URI_NOTE_ITEM: case URI_NOTE_ITEM:
// 获取路径片段中的笔记 ID查询指定 ID 的笔记
id = uri.getPathSegments().get(1); id = uri.getPathSegments().get(1);
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder); + parseSelection(selection), selectionArgs, null, null, sortOrder);
break; break;
case URI_DATA: case URI_DATA:
// 查询数据表,返回符合条件的游标
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder); sortOrder);
break; break;
case URI_DATA_ITEM: case URI_DATA_ITEM:
// 获取路径片段中的数据 ID查询指定 ID 的数据
id = uri.getPathSegments().get(1); id = uri.getPathSegments().get(1);
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder); + parseSelection(selection), selectionArgs, null, null, sortOrder);
break; break;
case URI_SEARCH: case URI_SEARCH:
case URI_SEARCH_SUGGEST: case URI_SEARCH_SUGGEST:
// 检查是否指定了不允许的参数,如果指定则抛出异常
if (sortOrder != null || projection != null) { if (sortOrder != null || projection != null) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
} }
String searchString = null; String searchString = null;
// 根据 Uri 匹配码获取搜索字符串
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
if (uri.getPathSegments().size() > 1) { if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1); searchString = uri.getPathSegments().get(1);
@ -125,12 +136,13 @@ public class NotesProvider extends ContentProvider {
} else { } else {
searchString = uri.getQueryParameter("pattern"); searchString = uri.getQueryParameter("pattern");
} }
// 如果搜索字符串为空,则返回 null
if (TextUtils.isEmpty(searchString)) { if (TextUtils.isEmpty(searchString)) {
return null; return null;
} }
try { try {
// 格式化搜索字符串,执行原始查询并返回游标
searchString = String.format("%%%s%%", searchString); searchString = String.format("%%%s%%", searchString);
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[] { searchString }); new String[] { searchString });
@ -139,61 +151,71 @@ public class NotesProvider extends ContentProvider {
} }
break; break;
default: default:
// 如果 Uri 不匹配任何已知模式,则抛出异常
throw new IllegalArgumentException("Unknown URI " + uri); throw new IllegalArgumentException("Unknown URI " + uri);
} }
// 如果游标不为空,则设置通知 Uri以便在数据变化时接收通知
if (c != null) { if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri); c.setNotificationUri(getContext().getContentResolver(), uri);
} }
return c; return c;
} }
// 重写 ContentProvider 的 insert 方法,用于处理数据插入请求
@Override @Override
public Uri insert(Uri uri, ContentValues values) { public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase(); SQLiteDatabase db = mHelper.getWritableDatabase();
long dataId = 0, noteId = 0, insertedId = 0; long dataId = 0, noteId = 0, insertedId = 0;
// 根据 Uri 匹配码执行不同的插入操作
switch (mMatcher.match(uri)) { switch (mMatcher.match(uri)) {
case URI_NOTE: case URI_NOTE:
// 插入笔记数据,获取插入的笔记 ID
insertedId = noteId = db.insert(TABLE.NOTE, null, values); insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break; break;
case URI_DATA: case URI_DATA:
// 获取数据所属的笔记 ID如果不存在则记录错误日志
if (values.containsKey(DataColumns.NOTE_ID)) { if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID); noteId = values.getAsLong(DataColumns.NOTE_ID);
} else { } else {
Log.d(TAG, "Wrong data format without note id:" + values.toString()); Log.d(TAG, "Wrong data format without note id:" + values.toString());
} }
// 插入数据,获取插入的数据 ID
insertedId = dataId = db.insert(TABLE.DATA, null, values); insertedId = dataId = db.insert(TABLE.DATA, null, values);
break; break;
default: default:
// 如果 Uri 不匹配任何已知模式,则抛出异常
throw new IllegalArgumentException("Unknown URI " + uri); throw new IllegalArgumentException("Unknown URI " + uri);
} }
// Notify the note uri // 如果插入了笔记数据,则通知笔记 Uri 数据变化
if (noteId > 0) { if (noteId > 0) {
getContext().getContentResolver().notifyChange( getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
} }
// 如果插入了数据,则通知数据 Uri 数据变化
// Notify the data uri
if (dataId > 0) { if (dataId > 0) {
getContext().getContentResolver().notifyChange( getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
} }
// 返回插入数据后的 Uri包含插入的 ID
return ContentUris.withAppendedId(uri, insertedId); return ContentUris.withAppendedId(uri, insertedId);
} }
// 重写 ContentProvider 的 delete 方法,用于处理数据删除请求
@Override @Override
public int delete(Uri uri, String selection, String[] selectionArgs) { public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0; int count = 0;
String id = null; String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase(); SQLiteDatabase db = mHelper.getWritableDatabase();
boolean deleteData = false; boolean deleteData = false;
// 根据 Uri 匹配码执行不同的删除操作
switch (mMatcher.match(uri)) { switch (mMatcher.match(uri)) {
case URI_NOTE: case URI_NOTE:
// 添加额外的条件,确保只删除有效 ID 的笔记
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs); count = db.delete(TABLE.NOTE, selection, selectionArgs);
break; break;
case URI_NOTE_ITEM: case URI_NOTE_ITEM:
// 获取路径片段中的笔记 ID
id = uri.getPathSegments().get(1); id = uri.getPathSegments().get(1);
// 如果笔记 ID 小于等于 0则不允许删除可能是系统文件夹
/** /**
* ID that smaller than 0 is system folder which is not allowed to * ID that smaller than 0 is system folder which is not allowed to
* trash * trash
@ -206,18 +228,22 @@ public class NotesProvider extends ContentProvider {
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
break; break;
case URI_DATA: case URI_DATA:
// 删除数据,设置标志位
count = db.delete(TABLE.DATA, selection, selectionArgs); count = db.delete(TABLE.DATA, selection, selectionArgs);
deleteData = true; deleteData = true;
break; break;
case URI_DATA_ITEM: case URI_DATA_ITEM:
// 获取路径片段中的数据 ID
id = uri.getPathSegments().get(1); id = uri.getPathSegments().get(1);
count = db.delete(TABLE.DATA, count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
deleteData = true; deleteData = true;
break; break;
default: default:
// 如果 Uri 不匹配任何已知模式,则抛出异常
throw new IllegalArgumentException("Unknown URI " + uri); throw new IllegalArgumentException("Unknown URI " + uri);
} }
// 如果删除了数据且有数据被删除,则通知笔记 Uri 和删除的 Uri 数据变化
if (count > 0) { if (count > 0) {
if (deleteData) { if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
@ -226,38 +252,44 @@ public class NotesProvider extends ContentProvider {
} }
return count; return count;
} }
// 重写 ContentProvider 的 update 方法,用于处理数据更新请求
@Override @Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0; int count = 0;
String id = null; String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase(); SQLiteDatabase db = mHelper.getWritableDatabase();
boolean updateData = false; boolean updateData = false;
// 根据 Uri 匹配码执行不同的更新操作
switch (mMatcher.match(uri)) { switch (mMatcher.match(uri)) {
case URI_NOTE: case URI_NOTE:
// 增加笔记版本号,执行更新操作
increaseNoteVersion(-1, selection, selectionArgs); increaseNoteVersion(-1, selection, selectionArgs);
count = db.update(TABLE.NOTE, values, selection, selectionArgs); count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break; break;
case URI_NOTE_ITEM: case URI_NOTE_ITEM:
// 获取路径片段中的笔记 ID增加笔记版本号执行更新操作
id = uri.getPathSegments().get(1); id = uri.getPathSegments().get(1);
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs); + parseSelection(selection), selectionArgs);
break; break;
case URI_DATA: case URI_DATA:
// 更新数据,设置标志位
count = db.update(TABLE.DATA, values, selection, selectionArgs); count = db.update(TABLE.DATA, values, selection, selectionArgs);
updateData = true; updateData = true;
break; break;
case URI_DATA_ITEM: case URI_DATA_ITEM:
// 获取路径片段中的数据 ID更新数据
id = uri.getPathSegments().get(1); id = uri.getPathSegments().get(1);
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs); + parseSelection(selection), selectionArgs);
updateData = true; updateData = true;
break; break;
default: default:
// 如果 Uri 不匹配任何已知模式,则抛出异常
throw new IllegalArgumentException("Unknown URI " + uri); throw new IllegalArgumentException("Unknown URI " + uri);
} }
// 如果更新了数据且有数据被更新,则通知笔记 Uri 和更新的 Uri 数据变化
if (count > 0) { if (count > 0) {
if (updateData) { if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
@ -266,11 +298,11 @@ public class NotesProvider extends ContentProvider {
} }
return count; return count;
} }
// 辅助方法,用于解析选择条件,添加括号和 AND 连接符
private String parseSelection(String selection) { private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
} }
// 增加笔记版本号的方法,根据指定的笔记 ID 和选择条件构建更新 SQL 语句并执行
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120); StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE "); sql.append("UPDATE ");
@ -295,7 +327,7 @@ public class NotesProvider extends ContentProvider {
mHelper.getWritableDatabase().execSQL(sql.toString()); mHelper.getWritableDatabase().execSQL(sql.toString());
} }
// 重写 ContentProvider 的 getType 方法,目前未实现,返回 null
@Override @Override
public String getType(Uri uri) { public String getType(Uri uri) {
// TODO Auto-generated method stub // TODO Auto-generated method stub

@ -14,7 +14,11 @@
* limitations under the License. * limitations under the License.
*/ */
<<<<<<< HEAD
package net.micode.notes.gtask.data;//测试
=======
package net.micode.notes.gtask.data;// 该类所在的包用于组织代码结构可能与gtask数据相关 package net.micode.notes.gtask.data;// 该类所在的包用于组织代码结构可能与gtask数据相关
>>>>>>> xingjiasen
import android.database.Cursor; import android.database.Cursor;
import android.util.Log; import android.util.Log;

@ -28,47 +28,53 @@ import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity; import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity; import net.micode.notes.ui.NotesPreferenceActivity;
// 该类继承自 AsyncTask用于在后台执行与 GTask 同步相关的任务,并在任务执行过程中更新进度、处理结果并显示通知
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> { public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
// 用于标识 GTask 同步通知的 ID
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
// 定义任务完成监听器接口
public interface OnCompleteListener { public interface OnCompleteListener {
void onComplete(); void onComplete();
} }
// 上下文对象,用于获取系统服务和资源
private Context mContext; private Context mContext;
// 通知管理器,用于显示通知
private NotificationManager mNotifiManager; private NotificationManager mNotifiManager;
// GTask 管理器实例,用于执行同步操作
private GTaskManager mTaskManager; private GTaskManager mTaskManager;
// 任务完成监听器实例
private OnCompleteListener mOnCompleteListener; private OnCompleteListener mOnCompleteListener;
// 构造函数,初始化相关成员变量
public GTaskASyncTask(Context context, OnCompleteListener listener) { public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context; mContext = context;
mOnCompleteListener = listener; mOnCompleteListener = listener;
// 获取通知管理器服务
mNotifiManager = (NotificationManager) mContext mNotifiManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE); .getSystemService(Context.NOTIFICATION_SERVICE);
// 获取 GTask 管理器单例实例
mTaskManager = GTaskManager.getInstance(); mTaskManager = GTaskManager.getInstance();
} }
// 取消同步的方法,调用 GTask 管理器的取消同步方法
public void cancelSync() { public void cancelSync() {
mTaskManager.cancelSync(); mTaskManager.cancelSync();
} }
// 发布进度的方法,调用父类的 publishProgress 方法传递进度信息
public void publishProgess(String message) { public void publishProgess(String message) {
publishProgress(new String[] { publishProgress(new String[] {
message message
}); });
} }
// 显示通知的私有方法,根据给定的 tickerId 和内容创建并显示通知
private void showNotification(int tickerId, String content) { private void showNotification(int tickerId, String content) {
// 创建通知对象,设置图标、标题和时间
Notification notification = new Notification(R.drawable.notification, mContext Notification notification = new Notification(R.drawable.notification, mContext
.getString(tickerId), System.currentTimeMillis()); .getString(tickerId), System.currentTimeMillis());
// 设置通知默认的灯光效果
notification.defaults = Notification.DEFAULT_LIGHTS; notification.defaults = Notification.DEFAULT_LIGHTS;
// 设置通知自动取消
notification.flags = Notification.FLAG_AUTO_CANCEL; notification.flags = Notification.FLAG_AUTO_CANCEL;
PendingIntent pendingIntent; PendingIntent pendingIntent;
// 根据 tickerId 设置不同的点击意图
if (tickerId != R.string.ticker_success) { if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), 0); NotesPreferenceActivity.class), 0);
@ -77,31 +83,39 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), 0); NotesListActivity.class), 0);
} }
// 设置通知的详细信息和点击意图
notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
pendingIntent); pendingIntent);
// 显示通知
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
} }
// 重写 doInBackground 方法,在后台线程执行同步任务
@Override @Override
protected Integer doInBackground(Void... unused) { protected Integer doInBackground(Void... unused) {
// 发布登录进度通知
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
.getSyncAccountName(mContext))); .getSyncAccountName(mContext)));
// 调用 GTask 管理器的同步方法并返回结果
return mTaskManager.sync(mContext, this); return mTaskManager.sync(mContext, this);
} }
// 重写 onProgressUpdate 方法,在主线程更新进度通知
@Override @Override
protected void onProgressUpdate(String... progress) { protected void onProgressUpdate(String... progress) {
// 显示同步进度通知
showNotification(R.string.ticker_syncing, progress[0]); showNotification(R.string.ticker_syncing, progress[0]);
// 如果上下文是 GTaskSyncService则发送广播传递进度信息
if (mContext instanceof GTaskSyncService) { if (mContext instanceof GTaskSyncService) {
((GTaskSyncService) mContext).sendBroadcast(progress[0]); ((GTaskSyncService) mContext).sendBroadcast(progress[0]);
} }
} }
// 重写 onPostExecute 方法,在主线程处理同步任务结果
@Override @Override
protected void onPostExecute(Integer result) { protected void onPostExecute(Integer result) {
// 根据不同的结果显示相应的通知
if (result == GTaskManager.STATE_SUCCESS) { if (result == GTaskManager.STATE_SUCCESS) {
showNotification(R.string.ticker_success, mContext.getString( showNotification(R.string.ticker_success, mContext.getString(
R.string.success_sync_account, mTaskManager.getSyncAccount())); R.string.success_sync_account, mTaskManager.getSyncAccount()));
// 设置最后同步时间
NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis());
} else if (result == GTaskManager.STATE_NETWORK_ERROR) { } else if (result == GTaskManager.STATE_NETWORK_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network));
@ -111,6 +125,7 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
showNotification(R.string.ticker_cancel, mContext showNotification(R.string.ticker_cancel, mContext
.getString(R.string.error_sync_cancelled)); .getString(R.string.error_sync_cancelled));
} }
// 如果有任务完成监听器,则在新线程中触发监听器的 onComplete 方法
if (mOnCompleteListener != null) { if (mOnCompleteListener != null) {
new Thread(new Runnable() { new Thread(new Runnable() {

@ -59,37 +59,39 @@ import java.util.List;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater; import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream; import java.util.zip.InflaterInputStream;
// 该类是与 Google TasksGTask进行交互的客户端类负责处理登录、任务和任务列表的创建、更新、移动、删除以及获取等操作
public class GTaskClient { public class GTaskClient {
// 日志标签,用于记录类的相关操作信息
private static final String TAG = GTaskClient.class.getSimpleName(); private static final String TAG = GTaskClient.class.getSimpleName();
// Google Tasks 的基础 URL
private static final String GTASK_URL = "https://mail.google.com/tasks/"; private static final String GTASK_URL = "https://mail.google.com/tasks/";
// 用于获取任务数据的 URL
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
// 用于提交任务相关操作的 URL
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
// 单例模式实例
private static GTaskClient mInstance = null; private static GTaskClient mInstance = null;
// HTTP 客户端实例,用于发送 HTTP 请求
private DefaultHttpClient mHttpClient; private DefaultHttpClient mHttpClient;
// 当前使用的获取任务数据的 URL
private String mGetUrl; private String mGetUrl;
// 当前使用的提交任务操作的 URL
private String mPostUrl; private String mPostUrl;
// 客户端版本号
private long mClientVersion; private long mClientVersion;
// 登录状态
private boolean mLoggedin; private boolean mLoggedin;
// 上次登录时间
private long mLastLoginTime; private long mLastLoginTime;
// 操作 ID用于唯一标识每个操作
private int mActionId; private int mActionId;
// 同步的 Google 账户
private Account mAccount; private Account mAccount;
// 用于存储待提交的更新操作数组
private JSONArray mUpdateArray; private JSONArray mUpdateArray;
// 私有构造函数,初始化成员变量
private GTaskClient() { private GTaskClient() {
mHttpClient = null; mHttpClient = null;
mGetUrl = GTASK_GET_URL; mGetUrl = GTASK_GET_URL;
@ -101,23 +103,21 @@ public class GTaskClient {
mAccount = null; mAccount = null;
mUpdateArray = null; mUpdateArray = null;
} }
// 单例模式获取实例的方法
public static synchronized GTaskClient getInstance() { public static synchronized GTaskClient getInstance() {
if (mInstance == null) { if (mInstance == null) {
mInstance = new GTaskClient(); mInstance = new GTaskClient();
} }
return mInstance; return mInstance;
} }
// 登录方法,根据账户状态和设置进行登录操作
public boolean login(Activity activity) { public boolean login(Activity activity) {
// we suppose that the cookie would expire after 5 minutes // 假设 cookie 在 5 分钟后过期,需要重新登录
// then we need to re-login
final long interval = 1000 * 60 * 5; final long interval = 1000 * 60 * 5;
if (mLastLoginTime + interval < System.currentTimeMillis()) { if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false; mLoggedin = false;
} }
// 账户切换后需要重新登录
// need to re-login after account switch
if (mLoggedin if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) { .getSyncAccountName(activity))) {
@ -130,13 +130,13 @@ public class GTaskClient {
} }
mLastLoginTime = System.currentTimeMillis(); mLastLoginTime = System.currentTimeMillis();
// 登录 Google 账户获取授权令牌
String authToken = loginGoogleAccount(activity, false); String authToken = loginGoogleAccount(activity, false);
if (authToken == null) { if (authToken == null) {
Log.e(TAG, "login google account failed"); Log.e(TAG, "login google account failed");
return false; return false;
} }
// 如果账户不是 gmail.com 或 googlemail.com 结尾,使用自定义域名登录 GTask
// login with custom domain if necessary
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) { .endsWith("googlemail.com"))) {
StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
@ -150,8 +150,8 @@ public class GTaskClient {
mLoggedin = true; mLoggedin = true;
} }
} }
// 使用 Google 官方 URL 登录 GTask
// try to login with google official url
if (!mLoggedin) { if (!mLoggedin) {
mGetUrl = GTASK_GET_URL; mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL; mPostUrl = GTASK_POST_URL;
@ -163,17 +163,19 @@ public class GTaskClient {
mLoggedin = true; mLoggedin = true;
return true; return true;
} }
// 登录 Google 账户的私有方法
private String loginGoogleAccount(Activity activity, boolean invalidateToken) { private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken; String authToken;
// 获取账户管理器实例
AccountManager accountManager = AccountManager.get(activity); AccountManager accountManager = AccountManager.get(activity);
// 获取所有 Google 账户
Account[] accounts = accountManager.getAccountsByType("com.google"); Account[] accounts = accountManager.getAccountsByType("com.google");
if (accounts.length == 0) { if (accounts.length == 0) {
Log.e(TAG, "there is no available google account"); Log.e(TAG, "there is no available google account");
return null; return null;
} }
// 获取同步账户名称
String accountName = NotesPreferenceActivity.getSyncAccountName(activity); String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
Account account = null; Account account = null;
for (Account a : accounts) { for (Account a : accounts) {
@ -188,8 +190,7 @@ public class GTaskClient {
Log.e(TAG, "unable to get an account with the same name in the settings"); Log.e(TAG, "unable to get an account with the same name in the settings");
return null; return null;
} }
// 获取授权令牌
// get the token now
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account, AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null); "goanna_mobile", null, activity, null, null);
try { try {
@ -206,9 +207,10 @@ public class GTaskClient {
return authToken; return authToken;
} }
// 尝试登录 GTask 的私有方法
private boolean tryToLoginGtask(Activity activity, String authToken) { private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) { if (!loginGtask(authToken)) {
// 如果登录失败,可能是授权令牌过期,重新获取令牌并再次尝试登录
// maybe the auth token is out of date, now let's invalidate the // maybe the auth token is out of date, now let's invalidate the
// token and try again // token and try again
authToken = loginGoogleAccount(activity, true); authToken = loginGoogleAccount(activity, true);
@ -224,10 +226,11 @@ public class GTaskClient {
} }
return true; return true;
} }
// 登录 GTask 的私有方法,获取客户端版本号和设置 HTTP 客户端相关参数
private boolean loginGtask(String authToken) { private boolean loginGtask(String authToken) {
int timeoutConnection = 10000; int timeoutConnection = 10000;
int timeoutSocket = 15000; int timeoutSocket = 15000;
// 设置 HTTP 请求参数
HttpParams httpParameters = new BasicHttpParams(); HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
@ -236,14 +239,14 @@ public class GTaskClient {
mHttpClient.setCookieStore(localBasicCookieStore); mHttpClient.setCookieStore(localBasicCookieStore);
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// login gtask // 登录 GTask
try { try {
String loginUrl = mGetUrl + "?auth=" + authToken; String loginUrl = mGetUrl + "?auth=" + authToken;
HttpGet httpGet = new HttpGet(loginUrl); HttpGet httpGet = new HttpGet(loginUrl);
HttpResponse response = null; HttpResponse response = null;
response = mHttpClient.execute(httpGet); response = mHttpClient.execute(httpGet);
// get the cookie now // 获取登录后的 cookie
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies(); List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false; boolean hasAuthCookie = false;
for (Cookie cookie : cookies) { for (Cookie cookie : cookies) {
@ -255,7 +258,7 @@ public class GTaskClient {
Log.w(TAG, "it seems that there is no auth cookie"); Log.w(TAG, "it seems that there is no auth cookie");
} }
// get the client version // 获取客户端版本号
String resString = getResponseContent(response.getEntity()); String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup("; String jsBegin = "_setup(";
String jsEnd = ")}</script>"; String jsEnd = ")}</script>";
@ -279,18 +282,18 @@ public class GTaskClient {
return true; return true;
} }
// 获取操作 ID 的方法,每次调用后自增
private int getActionId() { private int getActionId() {
return mActionId++; return mActionId++;
} }
// 创建用于提交任务操作的 HttpPost 对象的方法
private HttpPost createHttpPost() { private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl); HttpPost httpPost = new HttpPost(mPostUrl);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
httpPost.setHeader("AT", "1"); httpPost.setHeader("AT", "1");
return httpPost; return httpPost;
} }
// 从 HTTP 响应实体中获取响应内容的方法,处理不同的编码格式
private String getResponseContent(HttpEntity entity) throws IOException { private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null; String contentEncoding = null;
if (entity.getContentEncoding() != null) { if (entity.getContentEncoding() != null) {
@ -322,7 +325,7 @@ public class GTaskClient {
input.close(); input.close();
} }
} }
// 提交 POST 请求的方法,将 JSON 数据发送到 GTask 服务器并处理响应
private JSONObject postRequest(JSONObject js) throws NetworkFailureException { private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) { if (!mLoggedin) {
Log.e(TAG, "please login first"); Log.e(TAG, "please login first");
@ -336,7 +339,7 @@ public class GTaskClient {
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httpPost.setEntity(entity); httpPost.setEntity(entity);
// execute the post // 执行 POST 请求
HttpResponse response = mHttpClient.execute(httpPost); HttpResponse response = mHttpClient.execute(httpPost);
String jsString = getResponseContent(response.getEntity()); String jsString = getResponseContent(response.getEntity());
return new JSONObject(jsString); return new JSONObject(jsString);
@ -359,21 +362,21 @@ public class GTaskClient {
throw new ActionFailureException("error occurs when posting request"); throw new ActionFailureException("error occurs when posting request");
} }
} }
// 创建任务的方法,将任务创建操作提交到 GTask 服务器
public void createTask(Task task) throws NetworkFailureException { public void createTask(Task task) throws NetworkFailureException {
commitUpdate(); commitUpdate();
try { try {
JSONObject jsPost = new JSONObject(); JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray(); JSONArray actionList = new JSONArray();
// action_list // 添加任务创建操作到操作列表
actionList.put(task.getCreateAction(getActionId())); actionList.put(task.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version // 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post // 提交请求并处理响应,设置任务的全局 ID
JSONObject jsResponse = postRequest(jsPost); JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0); GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
@ -385,21 +388,21 @@ public class GTaskClient {
throw new ActionFailureException("create task: handing jsonobject failed"); throw new ActionFailureException("create task: handing jsonobject failed");
} }
} }
// 创建任务列表的方法,将任务列表创建操作提交到 GTask 服务器
public void createTaskList(TaskList tasklist) throws NetworkFailureException { public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate(); commitUpdate();
try { try {
JSONObject jsPost = new JSONObject(); JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray(); JSONArray actionList = new JSONArray();
// action_list // 添加任务列表创建操作到操作列表
actionList.put(tasklist.getCreateAction(getActionId())); actionList.put(tasklist.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client version // 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post // 提交请求并处理响应,设置任务列表的全局 ID
JSONObject jsResponse = postRequest(jsPost); JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0); GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
@ -411,16 +414,16 @@ public class GTaskClient {
throw new ActionFailureException("create tasklist: handing jsonobject failed"); throw new ActionFailureException("create tasklist: handing jsonobject failed");
} }
} }
// 提交更新操作的方法,将存储的更新操作数组提交到 GTask 服务器
public void commitUpdate() throws NetworkFailureException { public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) { if (mUpdateArray != null) {
try { try {
JSONObject jsPost = new JSONObject(); JSONObject jsPost = new JSONObject();
// action_list // 设置操作列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);
// client_version // 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost); postRequest(jsPost);
@ -432,11 +435,10 @@ public class GTaskClient {
} }
} }
} }
// 添加更新节点操作的方法,将节点更新操作添加到更新操作数组
public void addUpdateNode(Node node) throws NetworkFailureException { public void addUpdateNode(Node node) throws NetworkFailureException {
if (node != null) { if (node != null) {
// too many update items may result in an error // 为避免更新项过多导致错误,设置最大更新项为 10
// set max to 10 items
if (mUpdateArray != null && mUpdateArray.length() > 10) { if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate(); commitUpdate();
} }
@ -446,81 +448,98 @@ public class GTaskClient {
mUpdateArray.put(node.getUpdateAction(getActionId())); mUpdateArray.put(node.getUpdateAction(getActionId()));
} }
} }
// 移动任务的方法,将任务移动操作提交到 GTask 服务器
public void moveTask(Task task, TaskList preParent, TaskList curParent) public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException { throws NetworkFailureException {
// 先提交之前可能存在的更新操作
commitUpdate(); commitUpdate();
try { try {
JSONObject jsPost = new JSONObject(); JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray(); JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject(); JSONObject action = new JSONObject();
// action_list // 设置操作类型为移动任务
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);
// 设置操作 ID
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
// 设置要移动的任务的 ID
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid());
// 如果源任务列表和目标任务列表相同且任务有前驱兄弟节点,则设置前驱兄弟节点 ID
if (preParent == curParent && task.getPriorSibling() != null) { if (preParent == curParent && task.getPriorSibling() != null) {
// put prioring_sibing_id only if moving within the tasklist and // put prioring_sibing_id only if moving within the tasklist and
// it is not the first one // it is not the first one
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling());
} }
// 设置源任务列表 ID
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid());
// 设置目标父任务列表 ID
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid());
// 如果源任务列表和目标任务列表不同,则设置目标任务列表 ID
if (preParent != curParent) { if (preParent != curParent) {
// put the dest_list only if moving between tasklists // put the dest_list only if moving between tasklists
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
} }
// 将操作添加到操作列表
actionList.put(action); actionList.put(action);
// 将操作列表添加到提交的 JSON 对象中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version // 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 提交请求
postRequest(jsPost); postRequest(jsPost);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, e.toString()); Log.e(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
// 如果 JSON 处理出现问题,抛出操作失败异常
throw new ActionFailureException("move task: handing jsonobject failed"); throw new ActionFailureException("move task: handing jsonobject failed");
} }
} }
// 删除节点的方法,接受要删除的节点作为参数,并可能抛出网络异常
public void deleteNode(Node node) throws NetworkFailureException { public void deleteNode(Node node) throws NetworkFailureException {
// 先提交之前可能存在的更新操作
commitUpdate(); commitUpdate();
try { try {
JSONObject jsPost = new JSONObject(); JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray(); JSONArray actionList = new JSONArray();
// action_list // 设置节点为已删除状态
node.setDeleted(true); node.setDeleted(true);
// 将节点的更新操作添加到操作列表
actionList.put(node.getUpdateAction(getActionId())); actionList.put(node.getUpdateAction(getActionId()));
// 将操作列表添加到提交的 JSON 对象中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version // 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 提交请求并删除后将更新数组置空
postRequest(jsPost); postRequest(jsPost);
mUpdateArray = null; mUpdateArray = null;
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, e.toString()); Log.e(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
// 如果 JSON 处理出现问题,抛出操作失败异常
throw new ActionFailureException("delete node: handing jsonobject failed"); throw new ActionFailureException("delete node: handing jsonobject failed");
} }
} }
// 获取所有任务列表的方法,可能抛出网络异常
public JSONArray getTaskLists() throws NetworkFailureException { public JSONArray getTaskLists() throws NetworkFailureException {
// 检查是否已登录,未登录则抛出异常
if (!mLoggedin) { if (!mLoggedin) {
Log.e(TAG, "please login first"); Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in"); throw new ActionFailureException("not logged in");
} }
try { try {
// 创建 HTTP GET 请求
HttpGet httpGet = new HttpGet(mGetUrl); HttpGet httpGet = new HttpGet(mGetUrl);
HttpResponse response = null; HttpResponse response = null;
// 执行请求
response = mHttpClient.execute(httpGet); response = mHttpClient.execute(httpGet);
// get the task list // 获取响应内容并解析出任务列表的 JSON 数组
String resString = getResponseContent(response.getEntity()); String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup("; String jsBegin = "_setup(";
String jsEnd = ")}</script>"; String jsEnd = ")}</script>";
@ -535,50 +554,60 @@ public class GTaskClient {
} catch (ClientProtocolException e) { } catch (ClientProtocolException e) {
Log.e(TAG, e.toString()); Log.e(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
// 如果 HTTP 协议出现问题,抛出网络异常
throw new NetworkFailureException("gettasklists: httpget failed"); throw new NetworkFailureException("gettasklists: httpget failed");
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, e.toString()); Log.e(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
// 如果 I/O 出现问题,抛出网络异常
throw new NetworkFailureException("gettasklists: httpget failed"); throw new NetworkFailureException("gettasklists: httpget failed");
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, e.toString()); Log.e(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
// 如果 JSON 解析出现问题,抛出操作失败异常
throw new ActionFailureException("get task lists: handing jasonobject failed"); throw new ActionFailureException("get task lists: handing jasonobject failed");
} }
} }
// 根据任务列表 ID 获取任务列表的方法,可能抛出网络异常
public JSONArray getTaskList(String listGid) throws NetworkFailureException { public JSONArray getTaskList(String listGid) throws NetworkFailureException {
// 先提交之前可能存在的更新操作
commitUpdate(); commitUpdate();
try { try {
JSONObject jsPost = new JSONObject(); JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray(); JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject(); JSONObject action = new JSONObject();
// action_list // 设置操作类型为获取所有任务
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL);
// 设置操作 ID
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
// 设置任务列表 ID
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid);
// 设置不获取已删除任务
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false);
// 将操作添加到操作列表
actionList.put(action); actionList.put(action);
// 将操作列表添加到提交的 JSON 对象中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version // 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 提交请求并获取响应中的任务列表 JSON 数组
JSONObject jsResponse = postRequest(jsPost); JSONObject jsResponse = postRequest(jsPost);
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, e.toString()); Log.e(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
// 如果 JSON 处理出现问题,抛出操作失败异常
throw new ActionFailureException("get task list: handing jsonobject failed"); throw new ActionFailureException("get task list: handing jsonobject failed");
} }
} }
// 获取同步账户的方法
public Account getSyncAccount() { public Account getSyncAccount() {
return mAccount; return mAccount;
} }
// 重置更新数组的方法,将更新数组置空
public void resetUpdateArray() { public void resetUpdateArray() {
mUpdateArray = null; mUpdateArray = null;
} }

@ -47,10 +47,10 @@ import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
// GTaskManager类用于管理与Google Task的同步操作以及本地数据和远程数据的交互
public class GTaskManager { public class GTaskManager {
private static final String TAG = GTaskManager.class.getSimpleName(); private static final String TAG = GTaskManager.class.getSimpleName();
// 定义同步操作的各种状态码
public static final int STATE_SUCCESS = 0; public static final int STATE_SUCCESS = 0;
public static final int STATE_NETWORK_ERROR = 1; public static final int STATE_NETWORK_ERROR = 1;
@ -60,33 +60,33 @@ public class GTaskManager {
public static final int STATE_SYNC_IN_PROGRESS = 3; public static final int STATE_SYNC_IN_PROGRESS = 3;
public static final int STATE_SYNC_CANCELLED = 4; public static final int STATE_SYNC_CANCELLED = 4;
// 单例模式确保只有一个GTaskManager实例
private static GTaskManager mInstance = null; private static GTaskManager mInstance = null;
// 用于获取认证令牌等操作的Activity
private Activity mActivity; private Activity mActivity;
// 应用上下文
private Context mContext; private Context mContext;
// 用于访问内容提供者的ContentResolver
private ContentResolver mContentResolver; private ContentResolver mContentResolver;
// 表示是否正在同步
private boolean mSyncing; private boolean mSyncing;
// 表示同步是否已取消
private boolean mCancelled; private boolean mCancelled;
// 存储Google Task列表的哈希表键为任务列表的全局唯一标识符gid值为TaskList对象
private HashMap<String, TaskList> mGTaskListHashMap; private HashMap<String, TaskList> mGTaskListHashMap;
// 存储Google Task节点任务或任务列表的哈希表键为节点的gid值为Node对象
private HashMap<String, Node> mGTaskHashMap; private HashMap<String, Node> mGTaskHashMap;
// 存储元数据的哈希表键为相关的gid值为MetaData对象
private HashMap<String, MetaData> mMetaHashMap; private HashMap<String, MetaData> mMetaHashMap;
// 元数据列表
private TaskList mMetaList; private TaskList mMetaList;
// 存储本地已删除笔记的ID集合
private HashSet<Long> mLocalDeleteIdMap; private HashSet<Long> mLocalDeleteIdMap;
// 用于将Google Task的gid映射到本地笔记的ID
private HashMap<String, Long> mGidToNid; private HashMap<String, Long> mGidToNid;
// 用于将本地笔记的ID映射到Google Task的gid
private HashMap<Long, String> mNidToGid; private HashMap<Long, String> mNidToGid;
// 私有构造函数,用于初始化各种数据结构和变量
private GTaskManager() { private GTaskManager() {
mSyncing = false; mSyncing = false;
mCancelled = false; mCancelled = false;
@ -98,21 +98,22 @@ public class GTaskManager {
mGidToNid = new HashMap<String, Long>(); mGidToNid = new HashMap<String, Long>();
mNidToGid = new HashMap<Long, String>(); mNidToGid = new HashMap<Long, String>();
} }
// 获取GTaskManager的单例实例
public static synchronized GTaskManager getInstance() { public static synchronized GTaskManager getInstance() {
if (mInstance == null) { if (mInstance == null) {
mInstance = new GTaskManager(); mInstance = new GTaskManager();
} }
return mInstance; return mInstance;
} }
// 设置Activity上下文主要用于获取认证令牌
public synchronized void setActivityContext(Activity activity) { public synchronized void setActivityContext(Activity activity) {
// used for getting authtoken // used for getting authtoken
mActivity = activity; mActivity = activity;
} }
// 执行同步操作的主要方法
public int sync(Context context, GTaskASyncTask asyncTask) { public int sync(Context context, GTaskASyncTask asyncTask) {
if (mSyncing) { if (mSyncing) {
// 如果同步已经在进行中,记录日志并返回相应状态码
Log.d(TAG, "Sync is in progress"); Log.d(TAG, "Sync is in progress");
return STATE_SYNC_IN_PROGRESS; return STATE_SYNC_IN_PROGRESS;
} }
@ -120,6 +121,7 @@ public class GTaskManager {
mContentResolver = mContext.getContentResolver(); mContentResolver = mContext.getContentResolver();
mSyncing = true; mSyncing = true;
mCancelled = false; mCancelled = false;
// 清除之前同步过程中存储的数据
mGTaskListHashMap.clear(); mGTaskListHashMap.clear();
mGTaskHashMap.clear(); mGTaskHashMap.clear();
mMetaHashMap.clear(); mMetaHashMap.clear();
@ -131,24 +133,26 @@ public class GTaskManager {
GTaskClient client = GTaskClient.getInstance(); GTaskClient client = GTaskClient.getInstance();
client.resetUpdateArray(); client.resetUpdateArray();
// login google task // 登录Google Task
if (!mCancelled) { if (!mCancelled) {
if (!client.login(mActivity)) { if (!client.login(mActivity)) {
throw new NetworkFailureException("login google task failed"); throw new NetworkFailureException("login google task failed");
} }
} }
// get the task list from google // 获取Google Task列表并进行初始化
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList(); initGTaskList();
// do content sync work // 执行内容同步操作
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent(); syncContent();
} catch (NetworkFailureException e) { } catch (NetworkFailureException e) {
// 处理网络故障异常,记录日志并返回相应状态码
Log.e(TAG, e.toString()); Log.e(TAG, e.toString());
return STATE_NETWORK_ERROR; return STATE_NETWORK_ERROR;
} catch (ActionFailureException e) { } catch (ActionFailureException e) {
// 处理操作失败异常,记录日志并返回相应状态码
Log.e(TAG, e.toString()); Log.e(TAG, e.toString());
return STATE_INTERNAL_ERROR; return STATE_INTERNAL_ERROR;
} catch (Exception e) { } catch (Exception e) {
@ -156,6 +160,7 @@ public class GTaskManager {
e.printStackTrace(); e.printStackTrace();
return STATE_INTERNAL_ERROR; return STATE_INTERNAL_ERROR;
} finally { } finally {
// 无论同步是否成功,都清除相关数据并重置同步状态
mGTaskListHashMap.clear(); mGTaskListHashMap.clear();
mGTaskHashMap.clear(); mGTaskHashMap.clear();
mMetaHashMap.clear(); mMetaHashMap.clear();
@ -164,7 +169,7 @@ public class GTaskManager {
mNidToGid.clear(); mNidToGid.clear();
mSyncing = false; mSyncing = false;
} }
// 初始化Google Task列表的方法
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
} }
@ -173,21 +178,22 @@ public class GTaskManager {
return; return;
GTaskClient client = GTaskClient.getInstance(); GTaskClient client = GTaskClient.getInstance();
try { try {
// 从Google获取任务列表的JSON数组
JSONArray jsTaskLists = client.getTaskLists(); JSONArray jsTaskLists = client.getTaskLists();
// init meta list first // 初始化元数据列表
mMetaList = null; mMetaList = null;
for (int i = 0; i < jsTaskLists.length(); i++) { for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i); JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
// 查找元数据文件夹
if (name if (name
.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { .equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
mMetaList = new TaskList(); mMetaList = new TaskList();
mMetaList.setContentByRemoteJSON(object); mMetaList.setContentByRemoteJSON(object);
// load meta data // 加载元数据
JSONArray jsMetas = client.getTaskList(gid); JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) { for (int j = 0; j < jsMetas.length(); j++) {
object = (JSONObject) jsMetas.getJSONObject(j); object = (JSONObject) jsMetas.getJSONObject(j);
@ -203,7 +209,7 @@ public class GTaskManager {
} }
} }
// create meta list if not existed // 如果元数据列表不存在,则创建一个新的元数据列表
if (mMetaList == null) { if (mMetaList == null) {
mMetaList = new TaskList(); mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
@ -211,12 +217,12 @@ public class GTaskManager {
GTaskClient.getInstance().createTaskList(mMetaList); GTaskClient.getInstance().createTaskList(mMetaList);
} }
// init task list // 初始化任务列表和任务
for (int i = 0; i < jsTaskLists.length(); i++) { for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i); JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
// 处理以特定前缀开头且不是元数据文件夹的任务列表
if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)
&& !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META)) { + GTaskStringUtils.FOLDER_META)) {
@ -225,7 +231,7 @@ public class GTaskManager {
mGTaskListHashMap.put(gid, tasklist); mGTaskListHashMap.put(gid, tasklist);
mGTaskHashMap.put(gid, tasklist); mGTaskHashMap.put(gid, tasklist);
// load tasks // 加载任务列表中的任务
JSONArray jsTasks = client.getTaskList(gid); JSONArray jsTasks = client.getTaskList(gid);
for (int j = 0; j < jsTasks.length(); j++) { for (int j = 0; j < jsTasks.length(); j++) {
object = (JSONObject) jsTasks.getJSONObject(j); object = (JSONObject) jsTasks.getJSONObject(j);
@ -241,12 +247,13 @@ public class GTaskManager {
} }
} }
} catch (JSONException e) { } catch (JSONException e) {
// 处理JSON解析异常记录日志、打印堆栈信息并抛出操作失败异常
Log.e(TAG, e.toString()); Log.e(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
throw new ActionFailureException("initGTaskList: handing JSONObject failed"); throw new ActionFailureException("initGTaskList: handing JSONObject failed");
} }
} }
// 执行内容同步的方法
private void syncContent() throws NetworkFailureException { private void syncContent() throws NetworkFailureException {
int syncType; int syncType;
Cursor c = null; Cursor c = null;
@ -259,7 +266,7 @@ public class GTaskManager {
return; return;
} }
// for local deleted note // 处理本地已删除的笔记
try { try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id=?)", new String[] { "(type<>? AND parent_id=?)", new String[] {
@ -286,10 +293,10 @@ public class GTaskManager {
} }
} }
// sync folder first // 同步文件夹
syncFolder(); syncFolder();
// for note existing in database // 处理数据库中存在的笔记
try { try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] { "(type=? AND parent_id<>?)", new String[] {
@ -306,10 +313,10 @@ public class GTaskManager {
syncType = node.getSyncAction(c); syncType = node.getSyncAction(c);
} else { } else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add // 本地新增
syncType = Node.SYNC_ACTION_ADD_REMOTE; syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else { } else {
// remote delete // 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL; syncType = Node.SYNC_ACTION_DEL_LOCAL;
} }
} }
@ -326,14 +333,14 @@ public class GTaskManager {
} }
} }
// go through remaining items // 处理剩余的未同步节点
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator(); Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
Map.Entry<String, Node> entry = iter.next(); Map.Entry<String, Node> entry = iter.next();
node = entry.getValue(); node = entry.getValue();
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
} }
// 检查同步是否取消,如果未取消则执行本地删除操作和提交更新操作
// mCancelled can be set by another thread, so we neet to check one by // mCancelled can be set by another thread, so we neet to check one by
// one // one
// clear local delete table // clear local delete table
@ -342,15 +349,14 @@ public class GTaskManager {
throw new ActionFailureException("failed to batch-delete local deleted notes"); throw new ActionFailureException("failed to batch-delete local deleted notes");
} }
} }
// 检查同步是否取消如果未取消则提交更新并刷新本地同步ID
// refresh local sync id
if (!mCancelled) { if (!mCancelled) {
GTaskClient.getInstance().commitUpdate(); GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId(); refreshLocalSyncId();
} }
} }
// 同步文件夹的方法
private void syncFolder() throws NetworkFailureException { private void syncFolder() throws NetworkFailureException {
Cursor c = null; Cursor c = null;
String gid; String gid;
@ -361,7 +367,7 @@ public class GTaskManager {
return; return;
} }
// for root folder // 处理根文件夹
try { try {
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null);
@ -373,7 +379,7 @@ public class GTaskManager {
mGTaskHashMap.remove(gid); mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// for system folder, only update remote name if necessary // 对于系统文件夹,仅在必要时更新远程名称
if (!node.getName().equals( if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
@ -390,7 +396,7 @@ public class GTaskManager {
} }
} }
// for call-note folder // 处理通话记录文件夹
try { try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
new String[] { new String[] {
@ -404,8 +410,7 @@ public class GTaskManager {
mGTaskHashMap.remove(gid); mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);
// for system folder, only update remote name if // 对于系统文件夹,仅在必要时更新远程名称
// necessary
if (!node.getName().equals( if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE)) + GTaskStringUtils.FOLDER_CALL_NOTE))
@ -424,7 +429,7 @@ public class GTaskManager {
} }
} }
// for local existing folders // 处理本地现有文件夹
try { try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] { "(type=? AND parent_id<>?)", new String[] {
@ -441,10 +446,10 @@ public class GTaskManager {
syncType = node.getSyncAction(c); syncType = node.getSyncAction(c);
} else { } else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add // 本地新增文件夹
syncType = Node.SYNC_ACTION_ADD_REMOTE; syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else { } else {
// remote delete // 远程删除文件夹
syncType = Node.SYNC_ACTION_DEL_LOCAL; syncType = Node.SYNC_ACTION_DEL_LOCAL;
} }
} }
@ -460,7 +465,7 @@ public class GTaskManager {
} }
} }
// for remote add folders // 处理远程新增文件夹
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator(); Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next(); Map.Entry<String, TaskList> entry = iter.next();
@ -475,7 +480,7 @@ public class GTaskManager {
if (!mCancelled) if (!mCancelled)
GTaskClient.getInstance().commitUpdate(); GTaskClient.getInstance().commitUpdate();
} }
// 根据同步类型执行具体同步操作的方法
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) { if (mCancelled) {
return; return;
@ -484,12 +489,15 @@ public class GTaskManager {
MetaData meta; MetaData meta;
switch (syncType) { switch (syncType) {
case Node.SYNC_ACTION_ADD_LOCAL: case Node.SYNC_ACTION_ADD_LOCAL:
// 本地新增节点操作
addLocalNode(node); addLocalNode(node);
break; break;
case Node.SYNC_ACTION_ADD_REMOTE: case Node.SYNC_ACTION_ADD_REMOTE:
// 远程新增节点操作
addRemoteNode(node, c); addRemoteNode(node, c);
break; break;
case Node.SYNC_ACTION_DEL_LOCAL: case Node.SYNC_ACTION_DEL_LOCAL:
// 本地删除节点操作
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN));
if (meta != null) { if (meta != null) {
GTaskClient.getInstance().deleteNode(meta); GTaskClient.getInstance().deleteNode(meta);
@ -497,6 +505,7 @@ public class GTaskManager {
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
break; break;
case Node.SYNC_ACTION_DEL_REMOTE: case Node.SYNC_ACTION_DEL_REMOTE:
// 远程删除节点操作
meta = mMetaHashMap.get(node.getGid()); meta = mMetaHashMap.get(node.getGid());
if (meta != null) { if (meta != null) {
GTaskClient.getInstance().deleteNode(meta); GTaskClient.getInstance().deleteNode(meta);
@ -504,24 +513,28 @@ public class GTaskManager {
GTaskClient.getInstance().deleteNode(node); GTaskClient.getInstance().deleteNode(node);
break; break;
case Node.SYNC_ACTION_UPDATE_LOCAL: case Node.SYNC_ACTION_UPDATE_LOCAL:
// 本地更新节点操作
updateLocalNode(node, c); updateLocalNode(node, c);
break; break;
case Node.SYNC_ACTION_UPDATE_REMOTE: case Node.SYNC_ACTION_UPDATE_REMOTE:
// 远程更新节点操作
updateRemoteNode(node, c); updateRemoteNode(node, c);
break; break;
case Node.SYNC_ACTION_UPDATE_CONFLICT: case Node.SYNC_ACTION_UPDATE_CONFLICT:
// merging both modifications maybe a good idea // 处理更新冲突,目前简单地使用本地更新
// right now just use local update simply // 可能合并双方修改会是更好的做法,但目前仅做简单处理
updateRemoteNode(node, c); updateRemoteNode(node, c);
break; break;
case Node.SYNC_ACTION_NONE: case Node.SYNC_ACTION_NONE:
// 无需同步操作
break; break;
case Node.SYNC_ACTION_ERROR: case Node.SYNC_ACTION_ERROR:
default: default:
// 未知同步操作类型,抛出异常
throw new ActionFailureException("unkown sync action type"); throw new ActionFailureException("unkown sync action type");
} }
} }
// 本地新增节点的具体实现方法
private void addLocalNode(Node node) throws NetworkFailureException { private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) { if (mCancelled) {
return; return;
@ -549,7 +562,7 @@ public class GTaskManager {
if (note.has(NoteColumns.ID)) { if (note.has(NoteColumns.ID)) {
long id = note.getLong(NoteColumns.ID); long id = note.getLong(NoteColumns.ID);
if (DataUtils.existInNoteDatabase(mContentResolver, id)) { if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
// the id is not available, have to create a new one // 如果本地数据库中已存在该笔记ID则移除该ID重新生成新的笔记
note.remove(NoteColumns.ID); note.remove(NoteColumns.ID);
} }
} }
@ -562,8 +575,7 @@ public class GTaskManager {
if (data.has(DataColumns.ID)) { if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID); long dataId = data.getLong(DataColumns.ID);
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
// the data id is not available, have to create // 如果本地数据库中已存在该数据ID则移除该ID重新生成新的数据
// a new one
data.remove(DataColumns.ID); data.remove(DataColumns.ID);
} }
} }
@ -584,25 +596,25 @@ public class GTaskManager {
sqlNote.setParentId(parentId.longValue()); sqlNote.setParentId(parentId.longValue());
} }
// create the local node // 创建本地节点
sqlNote.setGtaskId(node.getGid()); sqlNote.setGtaskId(node.getGid());
sqlNote.commit(false); sqlNote.commit(false);
// update gid-nid mapping // 更新gid - nid映射
mGidToNid.put(node.getGid(), sqlNote.getId()); mGidToNid.put(node.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), node.getGid()); mNidToGid.put(sqlNote.getId(), node.getGid());
// update meta // 更新远程元数据
updateRemoteMeta(node.getGid(), sqlNote); updateRemoteMeta(node.getGid(), sqlNote);
} }
// 本地更新节点的具体实现方法
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) { if (mCancelled) {
return; return;
} }
SqlNote sqlNote; SqlNote sqlNote;
// update the note locally // 更新本地笔记
sqlNote = new SqlNote(mContext, c); sqlNote = new SqlNote(mContext, c);
sqlNote.setContent(node.getLocalJSONFromContent()); sqlNote.setContent(node.getLocalJSONFromContent());
@ -615,10 +627,10 @@ public class GTaskManager {
sqlNote.setParentId(parentId.longValue()); sqlNote.setParentId(parentId.longValue());
sqlNote.commit(true); sqlNote.commit(true);
// update meta info // 更新远程元数据
updateRemoteMeta(node.getGid(), sqlNote); updateRemoteMeta(node.getGid(), sqlNote);
} }
// 远程新增节点的具体实现方法
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) { if (mCancelled) {
return; return;
@ -627,7 +639,7 @@ public class GTaskManager {
SqlNote sqlNote = new SqlNote(mContext, c); SqlNote sqlNote = new SqlNote(mContext, c);
Node n; Node n;
// update remotely // 远程更新操作
if (sqlNote.isNoteType()) { if (sqlNote.isNoteType()) {
Task task = new Task(); Task task = new Task();
task.setContentByLocalJSON(sqlNote.getContent()); task.setContentByLocalJSON(sqlNote.getContent());
@ -642,12 +654,12 @@ public class GTaskManager {
GTaskClient.getInstance().createTask(task); GTaskClient.getInstance().createTask(task);
n = (Node) task; n = (Node) task;
// add meta // 添加元数据
updateRemoteMeta(task.getGid(), sqlNote); updateRemoteMeta(task.getGid(), sqlNote);
} else { } else {
TaskList tasklist = null; TaskList tasklist = null;
// we need to skip folder if it has already existed // 检查文件夹是否已存在如果存在则获取对应的TaskList对象
String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX;
if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) if (sqlNote.getId() == Notes.ID_ROOT_FOLDER)
folderName += GTaskStringUtils.FOLDER_DEFAULT; folderName += GTaskStringUtils.FOLDER_DEFAULT;
@ -671,7 +683,7 @@ public class GTaskManager {
} }
} }
// no match we can add now // 如果未找到匹配的文件夹则创建新的TaskList对象
if (tasklist == null) { if (tasklist == null) {
tasklist = new TaskList(); tasklist = new TaskList();
tasklist.setContentByLocalJSON(sqlNote.getContent()); tasklist.setContentByLocalJSON(sqlNote.getContent());
@ -681,17 +693,17 @@ public class GTaskManager {
n = (Node) tasklist; n = (Node) tasklist;
} }
// update local note // 更新本地笔记
sqlNote.setGtaskId(n.getGid()); sqlNote.setGtaskId(n.getGid());
sqlNote.commit(false); sqlNote.commit(false);
sqlNote.resetLocalModified(); sqlNote.resetLocalModified();
sqlNote.commit(true); sqlNote.commit(true);
// gid-id mapping // 更新gid - id映射
mGidToNid.put(n.getGid(), sqlNote.getId()); mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid()); mNidToGid.put(sqlNote.getId(), n.getGid());
} }
// 远程更新节点的具体实现方法
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) { if (mCancelled) {
return; return;
@ -699,11 +711,11 @@ public class GTaskManager {
SqlNote sqlNote = new SqlNote(mContext, c); SqlNote sqlNote = new SqlNote(mContext, c);
// update remotely // 远程更新节点内容
node.setContentByLocalJSON(sqlNote.getContent()); node.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(node); GTaskClient.getInstance().addUpdateNode(node);
// update meta // 更新远程元数据
updateRemoteMeta(node.getGid(), sqlNote); updateRemoteMeta(node.getGid(), sqlNote);
// move task if necessary // move task if necessary
@ -725,11 +737,11 @@ public class GTaskManager {
} }
} }
// clear local modified flag // 清除本地修改标志
sqlNote.resetLocalModified(); sqlNote.resetLocalModified();
sqlNote.commit(true); sqlNote.commit(true);
} }
// 更新远程元数据的方法
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
if (sqlNote != null && sqlNote.isNoteType()) { if (sqlNote != null && sqlNote.isNoteType()) {
MetaData metaData = mMetaHashMap.get(gid); MetaData metaData = mMetaHashMap.get(gid);
@ -745,13 +757,13 @@ public class GTaskManager {
} }
} }
} }
// 刷新本地同步ID的方法
private void refreshLocalSyncId() throws NetworkFailureException { private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) { if (mCancelled) {
return; return;
} }
// get the latest gtask list // 重新获取最新的Google Task列表清除之前的数据缓存
mGTaskHashMap.clear(); mGTaskHashMap.clear();
mGTaskListHashMap.clear(); mGTaskListHashMap.clear();
mMetaHashMap.clear(); mMetaHashMap.clear();
@ -789,11 +801,11 @@ public class GTaskManager {
} }
} }
} }
// 获取同步账户名称的方法
public String getSyncAccount() { public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name; return GTaskClient.getInstance().getSyncAccount().name;
} }
// 取消同步操作的方法
public void cancelSync() { public void cancelSync() {
mCancelled = true; mCancelled = true;
} }

@ -22,70 +22,89 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
// GTaskSyncService类是一个Android服务用于管理Google Task的同步任务
public class GTaskSyncService extends Service { public class GTaskSyncService extends Service {
// 定义用于表示同步操作类型的字符串常量
public final static String ACTION_STRING_NAME = "sync_action_type"; public final static String ACTION_STRING_NAME = "sync_action_type";
// 表示开始同步的操作类型值
public final static int ACTION_START_SYNC = 0; public final static int ACTION_START_SYNC = 0;
// 表示取消同步的操作类型值
public final static int ACTION_CANCEL_SYNC = 1; public final static int ACTION_CANCEL_SYNC = 1;
// 表示无效操作类型的默认值
public final static int ACTION_INVALID = 2; public final static int ACTION_INVALID = 2;
// 定义用于广播同步服务相关信息的动作名称
public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service";
// 用于在广播中表示是否正在同步的键
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing";
// 用于在广播中表示同步进度消息的键
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg";
// 存储当前正在执行的同步任务实例初始化为null
private static GTaskASyncTask mSyncTask = null; private static GTaskASyncTask mSyncTask = null;
// 存储同步进度消息,初始化为空字符串
private static String mSyncProgress = ""; private static String mSyncProgress = "";
// 启动同步任务的方法
private void startSync() { private void startSync() {
// 如果当前没有正在执行的同步任务
if (mSyncTask == null) { if (mSyncTask == null) {
// 创建一个新的GTaskASyncTask实例并传入当前服务实例和一个完成监听器
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
// 当同步任务完成时调用的方法
public void onComplete() { public void onComplete() {
// 将当前同步任务实例设置为null表示任务已完成
mSyncTask = null; mSyncTask = null;
// 发送一个空消息的广播
sendBroadcast(""); sendBroadcast("");
// 停止当前服务
stopSelf(); stopSelf();
} }
}); });
// 发送一个空消息的广播,可能用于通知相关组件同步任务即将开始
sendBroadcast(""); sendBroadcast("");
// 执行同步任务
mSyncTask.execute(); mSyncTask.execute();
} }
} }
// 取消同步任务的方法
private void cancelSync() { private void cancelSync() {
// 如果当前有正在执行的同步任务
if (mSyncTask != null) { if (mSyncTask != null) {
// 调用同步任务的取消同步方法
mSyncTask.cancelSync(); mSyncTask.cancelSync();
} }
} }
// 服务创建时调用的方法将同步任务实例设置为null
@Override @Override
public void onCreate() { public void onCreate() {
mSyncTask = null; mSyncTask = null;
} }
// 服务接收到启动命令时调用的方法
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
// 获取启动意图中的额外数据
Bundle bundle = intent.getExtras(); Bundle bundle = intent.getExtras();
// 如果额外数据不为空且包含同步操作类型键
if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) {
// 根据操作类型值执行相应操作
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) {
case ACTION_START_SYNC: case ACTION_START_SYNC:
// 启动同步任务
startSync(); startSync();
break; break;
case ACTION_CANCEL_SYNC: case ACTION_CANCEL_SYNC:
// 取消同步任务
cancelSync(); cancelSync();
break; break;
default: default:
break; break;
} }
// 返回粘性服务标志,表示服务在被异常终止后会尝试重新启动
return START_STICKY; return START_STICKY;
} }
// 如果不满足上述条件则调用父类的onStartCommand方法
return super.onStartCommand(intent, flags, startId); return super.onStartCommand(intent, flags, startId);
} }
// 系统内存不足时调用的方法,如果有正在执行的同步任务则取消它
@Override @Override
public void onLowMemory() { public void onLowMemory() {
if (mSyncTask != null) { if (mSyncTask != null) {
@ -93,35 +112,47 @@ public class GTaskSyncService extends Service {
} }
} }
// 服务绑定方法返回null表示不支持绑定操作
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return null; return null;
} }
// 发送广播的方法,用于向感兴趣的组件发送同步服务的相关信息
public void sendBroadcast(String msg) { public void sendBroadcast(String msg) {
// 更新同步进度消息
mSyncProgress = msg; mSyncProgress = msg;
// 创建一个意图,指定广播动作名称
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
// 将是否正在同步的状态添加到意图的额外数据中
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null);
// 将同步进度消息添加到意图的额外数据中
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg);
// 发送广播
sendBroadcast(intent); sendBroadcast(intent);
} }
// 静态方法用于启动同步任务在Activity中调用
public static void startSync(Activity activity) { public static void startSync(Activity activity) {
// 设置同步任务的Activity上下文
GTaskManager.getInstance().setActivityContext(activity); GTaskManager.getInstance().setActivityContext(activity);
// 创建一个启动当前服务的意图,并指定操作类型为开始同步
Intent intent = new Intent(activity, GTaskSyncService.class); Intent intent = new Intent(activity, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC);
// 启动服务
activity.startService(intent); activity.startService(intent);
} }
// 静态方法用于取消同步任务在Context中调用
public static void cancelSync(Context context) { public static void cancelSync(Context context) {
// 创建一个启动当前服务的意图,并指定操作类型为取消同步
Intent intent = new Intent(context, GTaskSyncService.class); Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
// 启动服务
context.startService(intent); context.startService(intent);
} }
// 静态方法,用于检查是否正在进行同步任务
public static boolean isSyncing() { public static boolean isSyncing() {
return mSyncTask != null; return mSyncTask != null;
} }
// 静态方法,用于获取同步进度消息
public static String getProgressString() { public static String getProgressString() {
return mSyncProgress; return mSyncProgress;
} }

@ -33,73 +33,121 @@ import net.micode.notes.data.Notes.TextNote;
import java.util.ArrayList; import java.util.ArrayList;
// 定义 Note 类,用于管理笔记相关的数据和操作
public class Note { public class Note {
// 存储笔记的不同部分的变更数据
private ContentValues mNoteDiffValues; private ContentValues mNoteDiffValues;
// 存储笔记的数据
private NoteData mNoteData; private NoteData mNoteData;
// 日志标记
private static final String TAG = "Note"; private static final String TAG = "Note";
/** /**
* Create a new note id for adding a new note to databases * Create a new note id for adding a new note to databases
*/ */
/**
* ID
*
* @param context
* @param folderId ID
* @return ID
*/
public static synchronized long getNewNoteId(Context context, long folderId) { public static synchronized long getNewNoteId(Context context, long folderId) {
// Create a new note in the database // Create a new note in the database
// 创建一个新的 ContentValues 对象来存储笔记的初始信息
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
// 获取当前时间作为创建时间
long createdTime = System.currentTimeMillis(); long createdTime = System.currentTimeMillis();
values.put(NoteColumns.CREATED_DATE, createdTime); values.put(NoteColumns.CREATED_DATE, createdTime);
values.put(NoteColumns.MODIFIED_DATE, createdTime); values.put(NoteColumns.MODIFIED_DATE, createdTime);
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
values.put(NoteColumns.LOCAL_MODIFIED, 1); values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.PARENT_ID, folderId); values.put(NoteColumns.PARENT_ID, folderId);
// 将笔记信息插入到内容提供者的 URI 中
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
long noteId = 0; long noteId = 0;
try { try {
// 从 URI 中提取笔记 ID通常 URI 包含笔记的路径信息,路径中的一部分是笔记 ID
noteId = Long.valueOf(uri.getPathSegments().get(1)); noteId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
// 若出现异常,打印错误日志并将笔记 ID 设为 0
Log.e(TAG, "Get note id error :" + e.toString()); Log.e(TAG, "Get note id error :" + e.toString());
noteId = 0; noteId = 0;
} }
if (noteId == -1) { if (noteId == -1) {
// 若笔记 ID 为 -1抛出异常
throw new IllegalStateException("Wrong note id:" + noteId); throw new IllegalStateException("Wrong note id:" + noteId);
} }
return noteId; return noteId;
} }
// 构造函数,初始化笔记的数据存储和变更存储
public Note() { public Note() {
mNoteDiffValues = new ContentValues(); mNoteDiffValues = new ContentValues();
mNoteData = new NoteData(); mNoteData = new NoteData();
} }
/**
*
* @param key
* @param value
*/
public void setNoteValue(String key, String value) { public void setNoteValue(String key, String value) {
mNoteDiffValues.put(key, value); mNoteDiffValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
} }
/**
*
* @param key
* @param value
*/
public void setTextData(String key, String value) { public void setTextData(String key, String value) {
mNoteData.setTextData(key, value); mNoteData.setTextData(key, value);
} }
/**
* ID
* @param id ID
*/
public void setTextDataId(long id) { public void setTextDataId(long id) {
mNoteData.setTextDataId(id); mNoteData.setTextDataId(id);
} }
/**
* ID
* @return ID
*/
public long getTextDataId() { public long getTextDataId() {
return mNoteData.mTextDataId; return mNoteData.mTextDataId;
} }
/**
* ID
* @param id ID
*/
public void setCallDataId(long id) { public void setCallDataId(long id) {
mNoteData.setCallDataId(id); mNoteData.setCallDataId(id);
} }
/**
*
* @param key
* @param value
*/
public void setCallData(String key, String value) { public void setCallData(String key, String value) {
mNoteData.setCallData(key, value); mNoteData.setCallData(key, value);
} }
/**
*
* @return true false
*/
public boolean isLocalModified() { public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
} }
/**
*
* @param context
* @param noteId ID
* @return true false
*/
public boolean syncNote(Context context, long noteId) { public boolean syncNote(Context context, long noteId) {
if (noteId <= 0) { if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId); throw new IllegalArgumentException("Wrong note id:" + noteId);
@ -110,15 +158,14 @@ public class Note {
} }
/** /**
* In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and * LOCAL_MODIFIED MODIFIED_DATE
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the * 使
* note data info
*/ */
if (context.getContentResolver().update( if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) { null) == 0) {
Log.e(TAG, "Update note error, should not happen"); Log.e(TAG, "Update note error, should not happen");
// Do not return, fall through // 不返回,继续执行后续代码
} }
mNoteDiffValues.clear(); mNoteDiffValues.clear();
@ -129,7 +176,7 @@ public class Note {
return true; return true;
} }
// 内部类 NoteData用于存储笔记的数据
private class NoteData { private class NoteData {
private long mTextDataId; private long mTextDataId;
@ -140,44 +187,67 @@ public class Note {
private ContentValues mCallDataValues; private ContentValues mCallDataValues;
private static final String TAG = "NoteData"; private static final String TAG = "NoteData";
// 构造函数,初始化数据存储
public NoteData() { public NoteData() {
mTextDataValues = new ContentValues(); mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues(); mCallDataValues = new ContentValues();
mTextDataId = 0; mTextDataId = 0;
mCallDataId = 0; mCallDataId = 0;
} }
/**
*
* @return true false
*/
boolean isLocalModified() { boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
} }
/**
* ID
* @param id ID
*/
void setTextDataId(long id) { void setTextDataId(long id) {
if(id <= 0) { if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0"); throw new IllegalArgumentException("Text data id should larger than 0");
} }
mTextDataId = id; mTextDataId = id;
} }
/**
* ID
* @param id ID
*/
void setCallDataId(long id) { void setCallDataId(long id) {
if (id <= 0) { if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0"); throw new IllegalArgumentException("Call data id should larger than 0");
} }
mCallDataId = id; mCallDataId = id;
} }
/**
*
* @param key
* @param value
*/
void setCallData(String key, String value) { void setCallData(String key, String value) {
mCallDataValues.put(key, value); mCallDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
} }
/**
*
* @param key
* @param value
*/
void setTextData(String key, String value) { void setTextData(String key, String value) {
mTextDataValues.put(key, value); mTextDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
} }
/**
*
* @param context
* @param noteId ID
* @return URI null
*/
Uri pushIntoContentResolver(Context context, long noteId) { Uri pushIntoContentResolver(Context context, long noteId) {
/** /**
* Check for safety * Check for safety
@ -193,9 +263,11 @@ public class Note {
mTextDataValues.put(DataColumns.NOTE_ID, noteId); mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) { if (mTextDataId == 0) {
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
// 插入新的文本数据
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mTextDataValues); mTextDataValues);
try { try {
// 从 URI 中提取新插入的文本数据的 ID
setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Log.e(TAG, "Insert new text data fail with noteId" + noteId); Log.e(TAG, "Insert new text data fail with noteId" + noteId);
@ -203,6 +275,7 @@ public class Note {
return null; return null;
} }
} else { } else {
// 更新已有的文本数据
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId)); Notes.CONTENT_DATA_URI, mTextDataId));
builder.withValues(mTextDataValues); builder.withValues(mTextDataValues);
@ -215,9 +288,11 @@ public class Note {
mCallDataValues.put(DataColumns.NOTE_ID, noteId); mCallDataValues.put(DataColumns.NOTE_ID, noteId);
if (mCallDataId == 0) { if (mCallDataId == 0) {
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
// 插入新的通话数据
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mCallDataValues); mCallDataValues);
try { try {
// 从 URI 中提取新插入的通话数据的 ID
setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Log.e(TAG, "Insert new call data fail with noteId" + noteId); Log.e(TAG, "Insert new call data fail with noteId" + noteId);
@ -225,6 +300,7 @@ public class Note {
return null; return null;
} }
} else { } else {
// 更新已有的通话数据
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mCallDataId)); Notes.CONTENT_DATA_URI, mCallDataId));
builder.withValues(mCallDataValues); builder.withValues(mCallDataValues);
@ -235,6 +311,7 @@ public class Note {
if (operationList.size() > 0) { if (operationList.size() > 0) {
try { try {
// 执行批量操作
ContentProviderResult[] results = context.getContentResolver().applyBatch( ContentProviderResult[] results = context.getContentResolver().applyBatch(
Notes.AUTHORITY, operationList); Notes.AUTHORITY, operationList);
return (results == null || results.length == 0 || results[0] == null) ? null return (results == null || results.length == 0 || results[0] == null) ? null

@ -31,37 +31,37 @@ import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote; import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources; import net.micode.notes.tool.ResourceParser.NoteBgResources;
// 表示正在编辑或处理的笔记的类
public class WorkingNote { public class WorkingNote {
// Note for the working note // 笔记对象
private Note mNote; private Note mNote;
// Note Id // 笔记的 ID
private long mNoteId; private long mNoteId;
// Note content // 笔记的内容
private String mContent; private String mContent;
// Note mode // 笔记的模式
private int mMode; private int mMode;
// 提醒日期
private long mAlertDate; private long mAlertDate;
// 最后修改日期
private long mModifiedDate; private long mModifiedDate;
// 背景颜色 ID
private int mBgColorId; private int mBgColorId;
// 小部件的 ID
private int mWidgetId; private int mWidgetId;
// 小部件的类型
private int mWidgetType; private int mWidgetType;
// 笔记所在的文件夹 ID
private long mFolderId; private long mFolderId;
// 上下文对象
private Context mContext; private Context mContext;
// 日志标记
private static final String TAG = "WorkingNote"; private static final String TAG = "WorkingNote";
// 笔记是否已删除
private boolean mIsDeleted; private boolean mIsDeleted;
// 笔记设置变更监听器
private NoteSettingChangedListener mNoteSettingStatusListener; private NoteSettingChangedListener mNoteSettingStatusListener;
// 数据投影,用于查询笔记数据时指定要查询的列
public static final String[] DATA_PROJECTION = new String[] { public static final String[] DATA_PROJECTION = new String[] {
DataColumns.ID, DataColumns.ID,
DataColumns.CONTENT, DataColumns.CONTENT,
@ -71,7 +71,7 @@ public class WorkingNote {
DataColumns.DATA3, DataColumns.DATA3,
DataColumns.DATA4, DataColumns.DATA4,
}; };
// 笔记投影,用于查询笔记时指定要查询的列
public static final String[] NOTE_PROJECTION = new String[] { public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID, NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE, NoteColumns.ALERTED_DATE,
@ -80,7 +80,7 @@ public class WorkingNote {
NoteColumns.WIDGET_TYPE, NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE NoteColumns.MODIFIED_DATE
}; };
// 数据投影中各列的索引
private static final int DATA_ID_COLUMN = 0; private static final int DATA_ID_COLUMN = 0;
private static final int DATA_CONTENT_COLUMN = 1; private static final int DATA_CONTENT_COLUMN = 1;
@ -88,7 +88,7 @@ public class WorkingNote {
private static final int DATA_MIME_TYPE_COLUMN = 2; private static final int DATA_MIME_TYPE_COLUMN = 2;
private static final int DATA_MODE_COLUMN = 3; private static final int DATA_MODE_COLUMN = 3;
// 笔记投影中各列的索引
private static final int NOTE_PARENT_ID_COLUMN = 0; private static final int NOTE_PARENT_ID_COLUMN = 0;
private static final int NOTE_ALERTED_DATE_COLUMN = 1; private static final int NOTE_ALERTED_DATE_COLUMN = 1;
@ -101,20 +101,22 @@ public class WorkingNote {
private static final int NOTE_MODIFIED_DATE_COLUMN = 5; private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
// New note construct // 创建新笔记的构造函数
private WorkingNote(Context context, long folderId) { private WorkingNote(Context context, long folderId) {
mContext = context; mContext = context;
mAlertDate = 0; mAlertDate = 0;
//初始化修改日期为当前系统时间
mModifiedDate = System.currentTimeMillis(); mModifiedDate = System.currentTimeMillis();
mFolderId = folderId; mFolderId = folderId;
mNote = new Note(); mNote = new Note();
mNoteId = 0; mNoteId = 0;
// 标记笔记未删除
mIsDeleted = false; mIsDeleted = false;
mMode = 0; mMode = 0;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
} }
// Existing note construct // 加载现有笔记的构造函数
private WorkingNote(Context context, long noteId, long folderId) { private WorkingNote(Context context, long noteId, long folderId) {
mContext = context; mContext = context;
mNoteId = noteId; mNoteId = noteId;
@ -123,18 +125,25 @@ public class WorkingNote {
mNote = new Note(); mNote = new Note();
loadNote(); loadNote();
} }
// 加载笔记的方法,从数据库中查询笔记的基本信息
private void loadNote() { private void loadNote() {
// 1. 通过 ContentResolver 查询笔记信息,使用 NOTE_PROJECTION 投影指定要查询的列
Cursor cursor = mContext.getContentResolver().query( Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
null, null); null, null);
if (cursor != null) { if (cursor != null) {
// 2. 将游标移动到第一个结果
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
// 3. 从游标中读取并存储父文件夹 ID
mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN);
// 4. 从游标中读取并存储背景颜色 ID
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN);
// 5. 从游标中读取并存储小部件 ID
mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN);
// 6. 从游标中读取并存储小部件类型
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
// 7. 从游标中读取并存储提醒日期
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
} }
@ -145,24 +154,30 @@ public class WorkingNote {
} }
loadNoteData(); loadNoteData();
} }
// 加载笔记的数据,从数据库中查询笔记的数据信息
private void loadNoteData() { private void loadNoteData() {
// 1. 通过 ContentResolver 查询笔记数据,使用 DATA_PROJECTION 投影指定要查询的列,并通过 NOTE_ID 筛选
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] { DataColumns.NOTE_ID + "=?", new String[] {
String.valueOf(mNoteId) String.valueOf(mNoteId)
}, null); }, null);
if (cursor != null) { if (cursor != null) {
// 2. 将游标移动到第一个结果
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
do { do {
// 3. 读取数据的 MIME 类型
String type = cursor.getString(DATA_MIME_TYPE_COLUMN); String type = cursor.getString(DATA_MIME_TYPE_COLUMN);
if (DataConstants.NOTE.equals(type)) { if (DataConstants.NOTE.equals(type)) {
// 4. 如果是普通笔记,存储内容和模式,并设置文本数据的 ID
mContent = cursor.getString(DATA_CONTENT_COLUMN); mContent = cursor.getString(DATA_CONTENT_COLUMN);
mMode = cursor.getInt(DATA_MODE_COLUMN); mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
} else if (DataConstants.CALL_NOTE.equals(type)) { } else if (DataConstants.CALL_NOTE.equals(type)) {
// 5. 如果是通话笔记,设置通话数据的 ID
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
} else { } else {
// 6. 对于不识别的类型,打印调试日志
Log.d(TAG, "Wrong note type with type:" + type); Log.d(TAG, "Wrong note type with type:" + type);
} }
} while (cursor.moveToNext()); } while (cursor.moveToNext());
@ -173,7 +188,7 @@ public class WorkingNote {
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId); throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
} }
} }
// 创建一个空的笔记
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) { int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId); WorkingNote note = new WorkingNote(context, folderId);
@ -182,24 +197,25 @@ public class WorkingNote {
note.setWidgetType(widgetType); note.setWidgetType(widgetType);
return note; return note;
} }
// 从数据库加载一个笔记
public static WorkingNote load(Context context, long id) { public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0); return new WorkingNote(context, id, 0);
} }
// 保存笔记到数据库
public synchronized boolean saveNote() { public synchronized boolean saveNote() {
if (isWorthSaving()) { if (isWorthSaving()) {
if (!existInDatabase()) { if (!existInDatabase()) {
// 如果笔记不存在于数据库中,创建一个新的笔记 ID
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId); Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false; return false;
} }
} }
// 调用 Note 对象的 syncNote 方法同步笔记信息
mNote.syncNote(mContext, mNoteId); mNote.syncNote(mContext, mNoteId);
/** /**
* Update widget content if there exist any widget of this note *
*/ */
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mWidgetType != Notes.TYPE_WIDGET_INVALIDE
@ -212,11 +228,13 @@ public class WorkingNote {
} }
} }
// 检查笔记是否存在于数据库中
public boolean existInDatabase() { public boolean existInDatabase() {
return mNoteId > 0; return mNoteId > 0;
} }
// 判断笔记是否值得保存
private boolean isWorthSaving() { private boolean isWorthSaving() {
// 1. 判断笔记是否已删除,或者如果笔记不存在且内容为空,或者笔记存在但未被本地修改,则不值得保存
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) { || (existInDatabase() && !mNote.isLocalModified())) {
return false; return false;
@ -224,11 +242,11 @@ public class WorkingNote {
return true; return true;
} }
} }
// 设置笔记设置变更监听器
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l; mNoteSettingStatusListener = l;
} }
// 设置提醒日期
public void setAlertDate(long date, boolean set) { public void setAlertDate(long date, boolean set) {
if (date != mAlertDate) { if (date != mAlertDate) {
mAlertDate = date; mAlertDate = date;
@ -238,7 +256,7 @@ public class WorkingNote {
mNoteSettingStatusListener.onClockAlertChanged(date, set); mNoteSettingStatusListener.onClockAlertChanged(date, set);
} }
} }
// 标记笔记是否已删除
public void markDeleted(boolean mark) { public void markDeleted(boolean mark) {
mIsDeleted = mark; mIsDeleted = mark;
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
@ -246,7 +264,7 @@ public class WorkingNote {
mNoteSettingStatusListener.onWidgetChanged(); mNoteSettingStatusListener.onWidgetChanged();
} }
} }
// 设置背景颜色 ID
public void setBgColorId(int id) { public void setBgColorId(int id) {
if (id != mBgColorId) { if (id != mBgColorId) {
mBgColorId = id; mBgColorId = id;
@ -256,7 +274,7 @@ public class WorkingNote {
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
} }
} }
// 设置检查列表模式
public void setCheckListMode(int mode) { public void setCheckListMode(int mode) {
if (mMode != mode) { if (mMode != mode) {
if (mNoteSettingStatusListener != null) { if (mNoteSettingStatusListener != null) {
@ -266,102 +284,102 @@ public class WorkingNote {
mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
} }
} }
// 设置小部件类型
public void setWidgetType(int type) { public void setWidgetType(int type) {
if (type != mWidgetType) { if (type != mWidgetType) {
mWidgetType = type; mWidgetType = type;
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
} }
} }
// 设置小部件 ID
public void setWidgetId(int id) { public void setWidgetId(int id) {
if (id != mWidgetId) { if (id != mWidgetId) {
mWidgetId = id; mWidgetId = id;
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
} }
} }
// 设置笔记的工作文本
public void setWorkingText(String text) { public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) { if (!TextUtils.equals(mContent, text)) {
mContent = text; mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent); mNote.setTextData(DataColumns.CONTENT, mContent);
} }
} }
// 将笔记转换为通话笔记
public void convertToCallNote(String phoneNumber, long callDate) { public void convertToCallNote(String phoneNumber, long callDate) {
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
} }
// 检查是否有闹钟提醒
public boolean hasClockAlert() { public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false); return (mAlertDate > 0 ? true : false);
} }
// 获取笔记内容
public String getContent() { public String getContent() {
return mContent; return mContent;
} }
// 获取提醒日期
public long getAlertDate() { public long getAlertDate() {
return mAlertDate; return mAlertDate;
} }
// 获取最后修改日期
public long getModifiedDate() { public long getModifiedDate() {
return mModifiedDate; return mModifiedDate;
} }
// 获取背景颜色的资源 ID
public int getBgColorResId() { public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId); return NoteBgResources.getNoteBgResource(mBgColorId);
} }
// 获取背景颜色 ID
public int getBgColorId() { public int getBgColorId() {
return mBgColorId; return mBgColorId;
} }
// 获取标题背景的资源 ID
public int getTitleBgResId() { public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId); return NoteBgResources.getNoteTitleBgResource(mBgColorId);
} }
// 获取检查列表模式
public int getCheckListMode() { public int getCheckListMode() {
return mMode; return mMode;
} }
// 获取笔记 ID
public long getNoteId() { public long getNoteId() {
return mNoteId; return mNoteId;
} }
// 获取文件夹 ID
public long getFolderId() { public long getFolderId() {
return mFolderId; return mFolderId;
} }
// 获取小部件 ID
public int getWidgetId() { public int getWidgetId() {
return mWidgetId; return mWidgetId;
} }
// 获取小部件类型
public int getWidgetType() { public int getWidgetType() {
return mWidgetType; return mWidgetType;
} }
// 笔记设置变更监听器接口
public interface NoteSettingChangedListener { public interface NoteSettingChangedListener {
/** /**
* Called when the background color of current note has just changed *
*/ */
void onBackgroundColorChanged(); void onBackgroundColorChanged();
/** /**
* Called when user set clock *
*/ */
void onClockAlertChanged(long date, boolean set); void onClockAlertChanged(long date, boolean set);
/** /**
* Call when user create note from widget *
*/ */
void onWidgetChanged(); void onWidgetChanged();
/** /**
* Call when switch between check list mode and normal mode *
* @param oldMode is previous mode before change * @param oldMode
* @param newMode is new mode * @param newMode
*/ */
void onCheckListModeChanged(int oldMode, int newMode); void onCheckListModeChanged(int oldMode, int newMode);
} }

@ -35,12 +35,15 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
// BackupUtils 类用于实现笔记数据的备份和导出为文本文件的功能
public class BackupUtils { public class BackupUtils {
// 用于日志记录的标签
private static final String TAG = "BackupUtils"; private static final String TAG = "BackupUtils";
// Singleton stuff // Singleton stuff
// 单例模式相关
// 保存单例实例
private static BackupUtils sInstance; private static BackupUtils sInstance;
// 获取单例实例的方法,如果实例不存在则创建一个新的实例
public static synchronized BackupUtils getInstance(Context context) { public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) { if (sInstance == null) {
sInstance = new BackupUtils(context); sInstance = new BackupUtils(context);
@ -53,52 +56,60 @@ public class BackupUtils {
* status * status
*/ */
// Currently, the sdcard is not mounted // Currently, the sdcard is not mounted
// 以下是表示备份或恢复状态的常量
// 表示 SD 卡未挂载
public static final int STATE_SD_CARD_UNMOUONTED = 0; public static final int STATE_SD_CARD_UNMOUONTED = 0;
// The backup file not exist // The backup file not exist
// 表示备份文件不存在
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// The data is not well formated, may be changed by other programs // The data is not well formated, may be changed by other programs
// 表示数据格式不正确,可能被其他程序修改
public static final int STATE_DATA_DESTROIED = 2; public static final int STATE_DATA_DESTROIED = 2;
// Some run-time exception which causes restore or backup fails // Some run-time exception which causes restore or backup fails
// 表示一些运行时异常导致恢复或备份失败
public static final int STATE_SYSTEM_ERROR = 3; public static final int STATE_SYSTEM_ERROR = 3;
// Backup or restore success // Backup or restore success
// 表示备份或恢复成功
public static final int STATE_SUCCESS = 4; public static final int STATE_SUCCESS = 4;
// 用于文本导出的内部类实例
private TextExport mTextExport; private TextExport mTextExport;
// 私有构造函数,初始化时创建 TextExport 实例
private BackupUtils(Context context) { private BackupUtils(Context context) {
mTextExport = new TextExport(context); mTextExport = new TextExport(context);
} }
// 检查外部存储是否可用的方法
private static boolean externalStorageAvailable() { private static boolean externalStorageAvailable() {
// 比较外部存储状态是否为已挂载
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
} }
// 调用 TextExport 的 exportToText 方法进行数据导出为文本,并返回结果状态
public int exportToText() { public int exportToText() {
return mTextExport.exportToText(); return mTextExport.exportToText();
} }
// 获取导出的文本文件名
public String getExportedTextFileName() { public String getExportedTextFileName() {
return mTextExport.mFileName; return mTextExport.mFileName;
} }
// 获取导出的文本文件目录
public String getExportedTextFileDir() { public String getExportedTextFileDir() {
return mTextExport.mFileDirectory; return mTextExport.mFileDirectory;
} }
// 内部类 TextExport用于实现具体的文本导出功能
private static class TextExport { private static class TextExport {
// 用于查询笔记的投影,包含笔记的 ID、修改日期、摘要和类型
private static final String[] NOTE_PROJECTION = { private static final String[] NOTE_PROJECTION = {
NoteColumns.ID, NoteColumns.ID,
NoteColumns.MODIFIED_DATE, NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET, NoteColumns.SNIPPET,
NoteColumns.TYPE NoteColumns.TYPE
}; };
// 笔记投影中 ID 列的索引
private static final int NOTE_COLUMN_ID = 0; private static final int NOTE_COLUMN_ID = 0;
// 笔记投影中修改日期列的索引
private static final int NOTE_COLUMN_MODIFIED_DATE = 1; private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
// 笔记投影中摘要列的索引
private static final int NOTE_COLUMN_SNIPPET = 2; private static final int NOTE_COLUMN_SNIPPET = 2;
// 用于查询数据的投影包含数据的内容、MIME 类型等多个字段
private static final String[] DATA_PROJECTION = { private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT, DataColumns.CONTENT,
DataColumns.MIME_TYPE, DataColumns.MIME_TYPE,
@ -107,31 +118,36 @@ public class BackupUtils {
DataColumns.DATA3, DataColumns.DATA3,
DataColumns.DATA4, DataColumns.DATA4,
}; };
// 数据投影中内容列的索引
private static final int DATA_COLUMN_CONTENT = 0; private static final int DATA_COLUMN_CONTENT = 0;
// 数据投影中 MIME 类型列的索引
private static final int DATA_COLUMN_MIME_TYPE = 1; private static final int DATA_COLUMN_MIME_TYPE = 1;
// 数据投影中通话日期列的索引
private static final int DATA_COLUMN_CALL_DATE = 2; private static final int DATA_COLUMN_CALL_DATE = 2;
// 数据投影中电话号码列的索引
private static final int DATA_COLUMN_PHONE_NUMBER = 4; private static final int DATA_COLUMN_PHONE_NUMBER = 4;
// 用于格式化输出的文本格式数组
private final String [] TEXT_FORMAT; private final String [] TEXT_FORMAT;
// 格式化文件夹名称的索引
private static final int FORMAT_FOLDER_NAME = 0; private static final int FORMAT_FOLDER_NAME = 0;
// 格式化笔记日期的索引
private static final int FORMAT_NOTE_DATE = 1; private static final int FORMAT_NOTE_DATE = 1;
// 格式化笔记内容的索引
private static final int FORMAT_NOTE_CONTENT = 2; private static final int FORMAT_NOTE_CONTENT = 2;
// 上下文对象,用于获取资源等操作
private Context mContext; private Context mContext;
// 导出的文件名
private String mFileName; private String mFileName;
// 导出的文件目录
private String mFileDirectory; private String mFileDirectory;
// 构造函数,初始化文本格式数组、上下文对象,并设置文件名和目录为空字符串
public TextExport(Context context) { public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context; mContext = context;
mFileName = ""; mFileName = "";
mFileDirectory = ""; mFileDirectory = "";
} }
// 根据索引获取对应的文本格式字符串
private String getFormat(int id) { private String getFormat(int id) {
return TEXT_FORMAT[id]; return TEXT_FORMAT[id];
} }
@ -139,8 +155,10 @@ public class BackupUtils {
/** /**
* Export the folder identified by folder id to text * Export the folder identified by folder id to text
*/ */
// 将指定文件夹及其下的笔记导出到 PrintStream
private void exportFolderToText(String folderId, PrintStream ps) { private void exportFolderToText(String folderId, PrintStream ps) {
// Query notes belong to this folder // Query notes belong to this folder
// 查询属于该文件夹的笔记
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId folderId
@ -150,14 +168,18 @@ public class BackupUtils {
if (notesCursor.moveToFirst()) { if (notesCursor.moveToFirst()) {
do { do {
// Print note's last modified date // Print note's last modified date
// 打印笔记的最后修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm), mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note // Query data belong to this note
// 获取笔记 ID
String noteId = notesCursor.getString(NOTE_COLUMN_ID); String noteId = notesCursor.getString(NOTE_COLUMN_ID);
// 导出该笔记
exportNoteToText(noteId, ps); exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext()); } while (notesCursor.moveToNext());
} }
// 关闭游标
notesCursor.close(); notesCursor.close();
} }
} }
@ -165,7 +187,9 @@ public class BackupUtils {
/** /**
* Export note identified by id to a print stream * Export note identified by id to a print stream
*/ */
// 将指定笔记及其相关数据导出到 PrintStream
private void exportNoteToText(String noteId, PrintStream ps) { private void exportNoteToText(String noteId, PrintStream ps) {
// 查询属于该笔记的数据
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId noteId
@ -177,6 +201,7 @@ public class BackupUtils {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) { if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number // Print phone number
// 如果是通话笔记,打印电话号码
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT); String location = dataCursor.getString(DATA_COLUMN_CONTENT);
@ -186,15 +211,18 @@ public class BackupUtils {
phoneNumber)); phoneNumber));
} }
// Print call date // Print call date
// 打印通话日期
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm), .format(mContext.getString(R.string.format_datetime_mdhm),
callDate))); callDate)));
// Print call attachment location // Print call attachment location
// 打印通话附件位置
if (!TextUtils.isEmpty(location)) { if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location)); location));
} }
} else if (DataConstants.NOTE.equals(mimeType)) { } else if (DataConstants.NOTE.equals(mimeType)) {
// 如果是普通笔记,打印内容
String content = dataCursor.getString(DATA_COLUMN_CONTENT); String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) { if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
@ -203,9 +231,11 @@ public class BackupUtils {
} }
} while (dataCursor.moveToNext()); } while (dataCursor.moveToNext());
} }
// 关闭游标
dataCursor.close(); dataCursor.close();
} }
// print a line separator between note // print a line separator between note
// 在笔记之间打印换行符
try { try {
ps.write(new byte[] { ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER Character.LINE_SEPARATOR, Character.LETTER_NUMBER
@ -218,18 +248,22 @@ public class BackupUtils {
/** /**
* Note will be exported as text which is user readable * Note will be exported as text which is user readable
*/ */
// 将笔记数据导出为用户可读的文本文件
public int exportToText() { public int exportToText() {
// 检查外部存储是否可用
if (!externalStorageAvailable()) { if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted"); Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED; return STATE_SD_CARD_UNMOUONTED;
} }
// 获取用于导出的 PrintStream
PrintStream ps = getExportToTextPrintStream(); PrintStream ps = getExportToTextPrintStream();
if (ps == null) { if (ps == null) {
Log.e(TAG, "get print stream error"); Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR; return STATE_SYSTEM_ERROR;
} }
// First export folder and its notes // First export folder and its notes
// 首先导出文件夹及其下的笔记
Cursor folderCursor = mContext.getContentResolver().query( Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI, Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NOTE_PROJECTION,
@ -241,6 +275,7 @@ public class BackupUtils {
if (folderCursor.moveToFirst()) { if (folderCursor.moveToFirst()) {
do { do {
// Print folder's name // Print folder's name
// 获取文件夹名称
String folderName = ""; String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name); folderName = mContext.getString(R.string.call_record_folder_name);
@ -254,10 +289,12 @@ public class BackupUtils {
exportFolderToText(folderId, ps); exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext()); } while (folderCursor.moveToNext());
} }
// 关闭游标
folderCursor.close(); folderCursor.close();
} }
// Export notes in root's folder // Export notes in root's folder
// 导出根文件夹下的笔记
Cursor noteCursor = mContext.getContentResolver().query( Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI, Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NOTE_PROJECTION,
@ -271,12 +308,15 @@ public class BackupUtils {
mContext.getString(R.string.format_datetime_mdhm), mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note // Query data belong to this note
// 获取笔记 ID
String noteId = noteCursor.getString(NOTE_COLUMN_ID); String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps); exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext()); } while (noteCursor.moveToNext());
} }
// 关闭游标
noteCursor.close(); noteCursor.close();
} }
// 关闭 PrintStream
ps.close(); ps.close();
return STATE_SUCCESS; return STATE_SUCCESS;
@ -285,7 +325,9 @@ public class BackupUtils {
/** /**
* Get a print stream pointed to the file {@generateExportedTextFile} * Get a print stream pointed to the file {@generateExportedTextFile}
*/ */
// 获取指向导出文件的 PrintStream
private PrintStream getExportToTextPrintStream() { private PrintStream getExportToTextPrintStream() {
// 在 SD 卡上生成导出文件
File file = generateFileMountedOnSDcard(mContext, R.string.file_path, File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format); R.string.file_name_txt_format);
if (file == null) { if (file == null) {
@ -296,6 +338,7 @@ public class BackupUtils {
mFileDirectory = mContext.getString(R.string.file_path); mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null; PrintStream ps = null;
try { try {
// 创建文件输出流和 PrintStream
FileOutputStream fos = new FileOutputStream(file); FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos); ps = new PrintStream(fos);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
@ -312,6 +355,7 @@ public class BackupUtils {
/** /**
* Generate the text file to store imported data * Generate the text file to store imported data
*/ */
// 在 SD 卡上生成用于存储导入数据的文本文件
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory()); sb.append(Environment.getExternalStorageDirectory());
@ -324,9 +368,11 @@ public class BackupUtils {
File file = new File(sb.toString()); File file = new File(sb.toString());
try { try {
// 如果目录不存在则创建目录
if (!filedir.exists()) { if (!filedir.exists()) {
filedir.mkdir(); filedir.mkdir();
} }
// 如果文件不存在则创建文件
if (!file.exists()) { if (!file.exists()) {
file.createNewFile(); file.createNewFile();
} }
@ -340,5 +386,13 @@ public class BackupUtils {
return null; return null;
} }
} }
/*
*
BackupUtils
便
TextExport SD
使getInstanceBackupUtilsexportToText
NotesNoteColumnsDataColumnsDataConstantsR.string
*/

@ -36,85 +36,142 @@ import java.util.HashSet;
public class DataUtils { public class DataUtils {
// 用于日志记录的标签
public static final String TAG = "DataUtils"; public static final String TAG = "DataUtils";
/**
*
*
* @param resolver ContentResolver
* @param ids ID HashSet
* @return true false
*/
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) { public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
// 检查 ids 是否为 null
if (ids == null) { if (ids == null) {
Log.d(TAG, "the ids is null"); Log.d(TAG, "the ids is null");
// 如果为 null视为删除操作成功可能是因为没有要删除的内容
return true; return true;
} }
// 检查 ids 集合是否为空
if (ids.size() == 0) { if (ids.size() == 0) {
Log.d(TAG, "no id is in the hashset"); Log.d(TAG, "no id is in the hashset");
// 如果为空,视为删除操作成功(没有实际要删除的笔记)
return true; return true;
} }
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
// 遍历要删除的笔记 ID 集合
for (long id : ids) { for (long id : ids) {
// 不允许删除系统根文件夹
if(id == Notes.ID_ROOT_FOLDER) { if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root"); Log.e(TAG, "Don't delete system folder root");
continue; continue;
} }
// 创建一个删除操作的构建器,指定要删除的笔记 URI
ContentProviderOperation.Builder builder = ContentProviderOperation ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
operationList.add(builder.build()); operationList.add(builder.build());
} }
try { try {
// 执行批量操作
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
// 检查结果是否为空或长度为 0 或第一个结果为 null
if (results == null || results.length == 0 || results[0] == null) { if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString()); Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false; return false;
} }
// 如果操作成功,返回 true
return true; return true;
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) { } catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} }
// 如果发生异常,返回 false
return false; return false;
} }
/**
*
*
* @param resolver ContentResolver
* @param id ID
* @param srcFolderId ID
* @param desFolderId ID
*/
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
// 设置目标文件夹 ID
values.put(NoteColumns.PARENT_ID, desFolderId); values.put(NoteColumns.PARENT_ID, desFolderId);
// 设置源文件夹 ID
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);
// 设置本地已修改标志
values.put(NoteColumns.LOCAL_MODIFIED, 1); values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 执行更新操作,将笔记移动到目标文件夹
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
} }
/**
*
*
* @param resolver ContentResolver
* @param ids ID HashSet
* @param folderId ID
* @return true false
*/
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids, public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) { long folderId) {
// 检查 ids 是否为 null
if (ids == null) { if (ids == null) {
Log.d(TAG, "the ids is null"); Log.d(TAG, "the ids is null");
return true; return true;
} }
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
// 遍历要移动的笔记 ID 集合
for (long id : ids) { for (long id : ids) {
// 创建一个更新操作的构建器,指定要更新的笔记 URI
ContentProviderOperation.Builder builder = ContentProviderOperation ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
// 设置目标文件夹 ID
builder.withValue(NoteColumns.PARENT_ID, folderId); builder.withValue(NoteColumns.PARENT_ID, folderId);
// 设置本地已修改标志
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
operationList.add(builder.build()); operationList.add(builder.build());
} }
try { try {
// 执行批量操作
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
// 检查结果是否为空或长度为 0 或第一个结果为 null
if (results == null || results.length == 0 || results[0] == null) { if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString()); Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false; return false;
} }
// 如果操作成功,返回 true
return true; return true;
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) { } catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} }
// 如果发生异常,返回 false
return false; return false;
} }
/** /**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/
/**
* {@link Notes#TYPE_SYSTEM}
*
* @param resolver ContentResolver
* @return
*/ */
public static int getUserFolderCount(ContentResolver resolver) { public static int getUserFolderCount(ContentResolver resolver) {
// 查询数据库,计算符合条件的文件夹数量
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" }, new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
@ -125,18 +182,28 @@ public class DataUtils {
if(cursor != null) { if(cursor != null) {
if(cursor.moveToFirst()) { if(cursor.moveToFirst()) {
try { try {
// 获取查询结果中的数量
count = cursor.getInt(0); count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
Log.e(TAG, "get folder count failed:" + e.toString()); Log.e(TAG, "get folder count failed:" + e.toString());
} finally { } finally {
// 关闭游标
cursor.close(); cursor.close();
} }
} }
} }
return count; return count;
} }
/**
*
*
* @param resolver ContentResolver
* @param noteId ID
* @param type
* @return true false
*/
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
// 查询数据库,检查笔记是否存在且符合可见条件
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
@ -152,8 +219,15 @@ public class DataUtils {
} }
return exist; return exist;
} }
/**
*
*
* @param resolver ContentResolver
* @param noteId ID
* @return true false
*/
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
// 查询数据库,检查笔记是否存在
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null); null, null, null, null);
@ -166,8 +240,15 @@ public class DataUtils {
} }
return exist; return exist;
} }
/**
*
*
* @param resolver ContentResolver
* @param dataId ID
* @return true false
*/
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
// 查询数据库,检查数据是否存在
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null); null, null, null, null);
@ -180,8 +261,15 @@ public class DataUtils {
} }
return exist; return exist;
} }
/**
*
*
* @param resolver ContentResolver
* @param name
* @return true false
*/
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
// 查询数据库,检查文件夹是否存在且符合可见条件
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
@ -196,8 +284,15 @@ public class DataUtils {
} }
return exist; return exist;
} }
/**
*
*
* @param resolver ContentResolver
* @param folderId ID
* @return HashSet
*/
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) { public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
// 查询数据库,获取指定文件夹下的笔记小部件信息
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
NoteColumns.PARENT_ID + "=?", NoteColumns.PARENT_ID + "=?",
@ -223,8 +318,15 @@ public class DataUtils {
} }
return set; return set;
} }
/**
* ID
*
* @param resolver ContentResolver
* @param noteId ID
* @return
*/
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
// 查询数据库,获取指定笔记对应的电话号码
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER }, new String [] { CallNote.PHONE_NUMBER },
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?", CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
@ -242,8 +344,16 @@ public class DataUtils {
} }
return ""; return "";
} }
/**
* ID
*
* @param resolver ContentResolver
* @param phoneNumber
* @param callDate
* @return ID 0
*/
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
// 查询数据库,获取指定电话号码和通话日期对应的笔记 ID
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID }, new String [] { CallNote.NOTE_ID },
CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL(" CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL("
@ -263,8 +373,15 @@ public class DataUtils {
} }
return 0; return 0;
} }
/**
* ID
*
* @param resolver ContentResolver
* @param noteId ID
* @return
*/
public static String getSnippetById(ContentResolver resolver, long noteId) { public static String getSnippetById(ContentResolver resolver, long noteId) {
// 查询数据库,获取指定笔记的摘要信息
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String [] { NoteColumns.SNIPPET }, new String [] { NoteColumns.SNIPPET },
NoteColumns.ID + "=?", NoteColumns.ID + "=?",
@ -281,7 +398,12 @@ public class DataUtils {
} }
throw new IllegalArgumentException("Note is not found with id: " + noteId); throw new IllegalArgumentException("Note is not found with id: " + noteId);
} }
/**
*
*
* @param snippet
* @return
*/
public static String getFormattedSnippet(String snippet) { public static String getFormattedSnippet(String snippet) {
if (snippet != null) { if (snippet != null) {
snippet = snippet.trim(); snippet = snippet.trim();

@ -13,101 +13,104 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
// 该类属于 net.micode.notes.tool 包
package net.micode.notes.tool; package net.micode.notes.tool;
// 定义了一个公共类 GTaskStringUtils
public class GTaskStringUtils { public class GTaskStringUtils {
// 以下是一系列公共的静态常量字符串,它们很可能是用于 JSON 数据处理或数据交互时的键Key
// 表示操作的 ID
public final static String GTASK_JSON_ACTION_ID = "action_id"; public final static String GTASK_JSON_ACTION_ID = "action_id";
// 表示操作列表
public final static String GTASK_JSON_ACTION_LIST = "action_list"; 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 = "action_type";
// 操作类型为创建
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; 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_GETALL = "get_all";
// 操作类型为移动
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; 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_ACTION_TYPE_UPDATE = "update";
// 表示创建者的 ID
public final static String GTASK_JSON_CREATOR_ID = "creator_id"; public final static String GTASK_JSON_CREATOR_ID = "creator_id";
// 表示子实体
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; 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_CLIENT_VERSION = "client_version";
// 表示完成状态
public final static String GTASK_JSON_COMPLETED = "completed"; public final static String GTASK_JSON_COMPLETED = "completed";
// 表示当前列表的 ID
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; 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"; public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
// 表示已删除状态
public final static String GTASK_JSON_DELETED = "deleted"; 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_LIST = "dest_list";
// 表示目标父级
public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; 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_DEST_PARENT_TYPE = "dest_parent_type";
// 表示实体的增量
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; 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_ENTITY_TYPE = "entity_type";
// 表示获取已删除的内容
public final static String GTASK_JSON_GET_DELETED = "get_deleted"; public final static String GTASK_JSON_GET_DELETED = "get_deleted";
// 表示 ID
public final static String GTASK_JSON_ID = "id"; public final static String GTASK_JSON_ID = "id";
// 表示索引
public final static String GTASK_JSON_INDEX = "index"; public final static String GTASK_JSON_INDEX = "index";
// 表示最后修改时间
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; 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_LATEST_SYNC_POINT = "latest_sync_point";
// 表示列表的 ID
public final static String GTASK_JSON_LIST_ID = "list_id"; public final static String GTASK_JSON_LIST_ID = "list_id";
// 表示列表
public final static String GTASK_JSON_LISTS = "lists"; public final static String GTASK_JSON_LISTS = "lists";
// 表示名称
public final static String GTASK_JSON_NAME = "name"; public final static String GTASK_JSON_NAME = "name";
// 表示新的 ID
public final static String GTASK_JSON_NEW_ID = "new_id"; public final static String GTASK_JSON_NEW_ID = "new_id";
// 表示笔记
public final static String GTASK_JSON_NOTES = "notes"; public final static String GTASK_JSON_NOTES = "notes";
// 表示父级的 ID
public final static String GTASK_JSON_PARENT_ID = "parent_id"; public final static String GTASK_JSON_PARENT_ID = "parent_id";
// 表示前一个兄弟节点的 ID
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
// 表示结果
public final static String GTASK_JSON_RESULTS = "results"; public final static String GTASK_JSON_RESULTS = "results";
// 表示源列表
public final static String GTASK_JSON_SOURCE_LIST = "source_list"; public final static String GTASK_JSON_SOURCE_LIST = "source_list";
// 表示任务
public final static String GTASK_JSON_TASKS = "tasks"; public final static String GTASK_JSON_TASKS = "tasks";
// 表示类型
public final static String GTASK_JSON_TYPE = "type"; public final static String GTASK_JSON_TYPE = "type";
// 类型为组
public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
// 类型为任务
public final static String GTASK_JSON_TYPE_TASK = "TASK"; public final static String GTASK_JSON_TYPE_TASK = "TASK";
// 表示用户
public final static String GTASK_JSON_USER = "user"; public final static String GTASK_JSON_USER = "user";
// 以下可能是一些应用相关的常量,可能用于文件或文件夹的名称或前缀
// MIUI 笔记的文件夹前缀
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";
// 默认文件夹
public final static String FOLDER_DEFAULT = "Default"; public final static String FOLDER_DEFAULT = "Default";
// 呼叫笔记的文件夹
public final static String FOLDER_CALL_NOTE = "Call_Note"; public final static String FOLDER_CALL_NOTE = "Call_Note";
// 元数据文件夹
public final static String FOLDER_META = "METADATA"; public final static String FOLDER_META = "METADATA";
// 以下是一些元数据的头部信息,可能用于存储一些特殊的元数据
// 元数据的 GTask ID 头部
public final static String META_HEAD_GTASK_ID = "meta_gid"; public final static String META_HEAD_GTASK_ID = "meta_gid";
// 元数据的笔记头部
public final static String META_HEAD_NOTE = "meta_note"; public final static String META_HEAD_NOTE = "meta_note";
// 元数据的数据头部
public final static String META_HEAD_DATA = "meta_data"; public final static String META_HEAD_DATA = "meta_data";
// 元数据的笔记名称
public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE";
} }

@ -21,25 +21,26 @@ import android.preference.PreferenceManager;
import net.micode.notes.R; import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity; import net.micode.notes.ui.NotesPreferenceActivity;
// 资源解析器类,用于管理笔记应用中的各种资源
public class ResourceParser { public class ResourceParser {
// 定义背景颜色的常量,使用整数表示不同颜色
public static final int YELLOW = 0; public static final int YELLOW = 0;
public static final int BLUE = 1; public static final int BLUE = 1;
public static final int WHITE = 2; public static final int WHITE = 2;
public static final int GREEN = 3; public static final int GREEN = 3;
public static final int RED = 4; public static final int RED = 4;
// 默认的背景颜色为黄色
public static final int BG_DEFAULT_COLOR = YELLOW; public static final int BG_DEFAULT_COLOR = YELLOW;
// 定义字体大小的常量,使用整数表示不同大小
public static final int TEXT_SMALL = 0; public static final int TEXT_SMALL = 0;
public static final int TEXT_MEDIUM = 1; public static final int TEXT_MEDIUM = 1;
public static final int TEXT_LARGE = 2; public static final int TEXT_LARGE = 2;
public static final int TEXT_SUPER = 3; public static final int TEXT_SUPER = 3;
// 默认的字体大小为中等
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
// 内部类 NoteBgResources 用于管理笔记编辑界面的背景资源
public static class NoteBgResources { public static class NoteBgResources {
// 存储笔记编辑界面背景的资源 ID 数组,分别对应不同颜色的背景
private final static int [] BG_EDIT_RESOURCES = new int [] { private final static int [] BG_EDIT_RESOURCES = new int [] {
R.drawable.edit_yellow, R.drawable.edit_yellow,
R.drawable.edit_blue, R.drawable.edit_blue,
@ -47,7 +48,7 @@ public class ResourceParser {
R.drawable.edit_green, R.drawable.edit_green,
R.drawable.edit_red R.drawable.edit_red
}; };
// 存储笔记编辑界面标题背景的资源 ID 数组,分别对应不同颜色的标题背景
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
R.drawable.edit_title_yellow, R.drawable.edit_title_yellow,
R.drawable.edit_title_blue, R.drawable.edit_title_blue,
@ -55,26 +56,30 @@ public class ResourceParser {
R.drawable.edit_title_green, R.drawable.edit_title_green,
R.drawable.edit_title_red R.drawable.edit_title_red
}; };
// 根据传入的颜色 ID 获取笔记编辑界面的背景资源 ID
public static int getNoteBgResource(int id) { public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id]; return BG_EDIT_RESOURCES[id];
} }
// 根据传入的颜色 ID 获取笔记编辑界面的标题背景资源 ID
public static int getNoteTitleBgResource(int id) { public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id]; return BG_EDIT_TITLE_RESOURCES[id];
} }
} }
// 根据上下文获取默认的背景 ID
public static int getDefaultBgId(Context context) { public static int getDefaultBgId(Context context) {
// 检查用户偏好设置中是否设置了背景颜色
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
// 如果设置了,随机选择一个背景颜色
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length); return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
} else { } else {
// 否则使用默认的背景颜色
return BG_DEFAULT_COLOR; return BG_DEFAULT_COLOR;
} }
} }
// 内部类 NoteItemBgResources 用于管理笔记列表项的背景资源
public static class NoteItemBgResources { public static class NoteItemBgResources {
// 存储笔记列表项的不同状态(如第一个、正常、最后一个、单个)的背景资源 ID 数组,分别对应不同颜色
private final static int [] BG_FIRST_RESOURCES = new int [] { private final static int [] BG_FIRST_RESOURCES = new int [] {
R.drawable.list_yellow_up, R.drawable.list_yellow_up,
R.drawable.list_blue_up, R.drawable.list_blue_up,
@ -106,29 +111,30 @@ public class ResourceParser {
R.drawable.list_green_single, R.drawable.list_green_single,
R.drawable.list_red_single R.drawable.list_red_single
}; };
// 根据传入的颜色 ID 获取笔记列表项第一个元素的背景资源 ID
public static int getNoteBgFirstRes(int id) { public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id]; return BG_FIRST_RESOURCES[id];
} }
// 根据传入的颜色 ID 获取笔记列表项最后一个元素的背景资源 ID
public static int getNoteBgLastRes(int id) { public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id]; return BG_LAST_RESOURCES[id];
} }
// 根据传入的颜色 ID 获取笔记列表项单个元素的背景资源 ID
public static int getNoteBgSingleRes(int id) { public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id]; return BG_SINGLE_RESOURCES[id];
} }
// 根据传入的颜色 ID 获取笔记列表项正常元素的背景资源 ID
public static int getNoteBgNormalRes(int id) { public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id]; return BG_NORMAL_RESOURCES[id];
} }
// 获取文件夹的背景资源 ID
public static int getFolderBgRes() { public static int getFolderBgRes() {
return R.drawable.list_folder; return R.drawable.list_folder;
} }
} }
// 内部类 WidgetBgResources 用于管理小部件的背景资源
public static class WidgetBgResources { public static class WidgetBgResources {
// 存储 2x 小部件的不同颜色的背景资源 ID 数组
private final static int [] BG_2X_RESOURCES = new int [] { private final static int [] BG_2X_RESOURCES = new int [] {
R.drawable.widget_2x_yellow, R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue, R.drawable.widget_2x_blue,
@ -136,11 +142,11 @@ public class ResourceParser {
R.drawable.widget_2x_green, R.drawable.widget_2x_green,
R.drawable.widget_2x_red, R.drawable.widget_2x_red,
}; };
// 根据传入的颜色 ID 获取 2x 小部件的背景资源 ID
public static int getWidget2xBgResource(int id) { public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id]; return BG_2X_RESOURCES[id];
} }
// 存储 4x 小部件的不同颜色的背景资源 ID 数组
private final static int [] BG_4X_RESOURCES = new int [] { private final static int [] BG_4X_RESOURCES = new int [] {
R.drawable.widget_4x_yellow, R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue, R.drawable.widget_4x_blue,
@ -148,20 +154,21 @@ public class ResourceParser {
R.drawable.widget_4x_green, R.drawable.widget_4x_green,
R.drawable.widget_4x_red R.drawable.widget_4x_red
}; };
// 根据传入的颜色 ID 获取 4x 小部件的背景资源 ID
public static int getWidget4xBgResource(int id) { public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id]; return BG_4X_RESOURCES[id];
} }
} }
// 内部类 TextAppearanceResources 用于管理文本外观资源
public static class TextAppearanceResources { public static class TextAppearanceResources {
// 存储不同字体大小的文本外观资源 ID 数组
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
R.style.TextAppearanceNormal, R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium, R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge, R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper R.style.TextAppearanceSuper
}; };
// 根据传入的字体大小 ID 获取文本外观资源 ID
public static int getTexAppearanceResource(int id) { public static int getTexAppearanceResource(int id) {
/** /**
* HACKME: Fix bug of store the resource id in shared preference. * HACKME: Fix bug of store the resource id in shared preference.
@ -173,6 +180,7 @@ public class ResourceParser {
} }
return TEXTAPPEARANCE_RESOURCES[id]; return TEXTAPPEARANCE_RESOURCES[id];
} }
// 获取文本外观资源的数量
public static int getResourcesSize() { public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length; return TEXTAPPEARANCE_RESOURCES.length;

@ -39,33 +39,42 @@ import net.micode.notes.tool.DataUtils;
import java.io.IOException; import java.io.IOException;
// 闹钟提醒活动类,继承自 Activity 并实现 OnClickListener 和 OnDismissListener 接口
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
// 笔记的 ID
private long mNoteId; private long mNoteId;
// 笔记的摘要
private String mSnippet; private String mSnippet;
// 摘要的最大长度
private static final int SNIPPET_PREW_MAX_LEN = 60; private static final int SNIPPET_PREW_MAX_LEN = 60;
// 媒体播放器,用于播放闹钟声音
MediaPlayer mPlayer; MediaPlayer mPlayer;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// 隐藏标题栏
requestWindowFeature(Window.FEATURE_NO_TITLE); requestWindowFeature(Window.FEATURE_NO_TITLE);
final Window win = getWindow(); final Window win = getWindow();
// 当屏幕锁定时也显示该窗口
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
// 如果屏幕未开启,添加相关标志保持屏幕开启等操作
if (!isScreenOn()) { if (!isScreenOn()) {
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
} }
// 获取启动该活动的 Intent
Intent intent = getIntent(); Intent intent = getIntent();
try { try {
// 从 Intent 的数据中提取笔记 ID
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
// 根据笔记 ID 获取笔记的摘要
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
// 如果摘要长度超过最大长度,截取并添加额外信息
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
: mSnippet; : mSnippet;
@ -73,36 +82,45 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
e.printStackTrace(); e.printStackTrace();
return; return;
} }
// 创建媒体播放器
mPlayer = new MediaPlayer(); mPlayer = new MediaPlayer();
// 检查笔记是否存在于数据库中
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
// 显示操作对话框
showActionDialog(); showActionDialog();
// 播放闹钟声音
playAlarmSound(); playAlarmSound();
} else { } else {
// 若笔记不存在,结束活动
finish(); finish();
} }
} }
// 检查屏幕是否开启的方法
private boolean isScreenOn() { private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn(); return pm.isScreenOn();
} }
// 播放闹钟声音的方法
private void playAlarmSound() { private void playAlarmSound() {
// 获取闹钟的铃声 URI
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
// 获取系统的静音模式设置
int silentModeStreams = Settings.System.getInt(getContentResolver(), int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
// 根据静音模式设置音频流类型
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
mPlayer.setAudioStreamType(silentModeStreams); mPlayer.setAudioStreamType(silentModeStreams);
} else { } else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
} }
try { try {
// 设置媒体播放器的数据源
mPlayer.setDataSource(this, url); mPlayer.setDataSource(this, url);
// 准备播放
mPlayer.prepare(); mPlayer.prepare();
// 设置为循环播放
mPlayer.setLooping(true); mPlayer.setLooping(true);
// 开始播放
mPlayer.start(); mPlayer.start();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
@ -118,39 +136,53 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
e.printStackTrace(); e.printStackTrace();
} }
} }
// 显示操作对话框的方法
private void showActionDialog() { private void showActionDialog() {
// 创建对话框构建器
AlertDialog.Builder dialog = new AlertDialog.Builder(this); AlertDialog.Builder dialog = new AlertDialog.Builder(this);
// 设置对话框标题
dialog.setTitle(R.string.app_name); dialog.setTitle(R.string.app_name);
// 设置对话框消息为笔记摘要
dialog.setMessage(mSnippet); dialog.setMessage(mSnippet);
// 设置对话框的确认按钮及点击事件
dialog.setPositiveButton(R.string.notealert_ok, this); dialog.setPositiveButton(R.string.notealert_ok, this);
// 如果屏幕开启,设置进入按钮及点击事件
if (isScreenOn()) { if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this); dialog.setNegativeButton(R.string.notealert_enter, this);
} }
// 显示对话框并设置对话框关闭监听器
dialog.show().setOnDismissListener(this); dialog.show().setOnDismissListener(this);
} }
// 点击事件处理方法
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
switch (which) { switch (which) {
// 点击进入按钮
case DialogInterface.BUTTON_NEGATIVE: case DialogInterface.BUTTON_NEGATIVE:
// 创建一个新的 Intent 指向 NoteEditActivity
Intent intent = new Intent(this, NoteEditActivity.class); Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW); intent.setAction(Intent.ACTION_VIEW);
// 传递笔记的唯一标识符
intent.putExtra(Intent.EXTRA_UID, mNoteId); intent.putExtra(Intent.EXTRA_UID, mNoteId);
// 启动活动
startActivity(intent); startActivity(intent);
break; break;
default: default:
break; break;
} }
} }
// 对话框关闭时的处理方法
public void onDismiss(DialogInterface dialog) { public void onDismiss(DialogInterface dialog) {
// 停止播放闹钟声音
stopAlarmSound(); stopAlarmSound();
// 结束活动
finish(); finish();
} }
// 停止播放闹钟声音的方法
private void stopAlarmSound() { private void stopAlarmSound() {
if (mPlayer != null) { if (mPlayer != null) {
// 停止播放
mPlayer.stop(); mPlayer.stop();
// 释放资源
mPlayer.release(); mPlayer.release();
mPlayer = null; mPlayer = null;
} }

@ -21,10 +21,24 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver { public class AlarmReceiver extends BroadcastReceiver {
/**
* 广BroadcastReceiver
* 广Activity
*
* @param context Activity
* @param intent 广Intent使
*/
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
// 通过setClass方法修改Intent的目标组件将原本可能指向其他地方或者没有明确指向的Intent
// 重新设置为指向AlarmAlertActivity也就是将广播触发后的下一步操作引导到展示闹钟提醒界面的Activity上。
intent.setClass(context, AlarmAlertActivity.class); intent.setClass(context, AlarmAlertActivity.class);
// 给Intent添加一个标志位FLAG_ACTIVITY_NEW_TASK这个标志表示当启动Activity时
// 如果当前任务栈Task不存在或者不适合启动该Activity系统会创建一个新的任务栈来启动这个Activity。
// 因为BroadcastReceiver的生命周期比较特殊它没有自己的UI界面相关的任务栈所以需要添加这个标志确保Activity能正常启动。
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 使用传入的上下文对象启动设置好的Intent对应的Activity这样就会从接收到闹钟提醒广播
// 跳转到展示具体闹钟提醒信息的AlarmAlertActivity界面让用户看到闹钟提醒相关的内容如对话框、提示信息等
context.startActivity(intent); context.startActivity(intent);
} }
} }

@ -28,85 +28,129 @@ import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.NumberPicker; import android.widget.NumberPicker;
// DateTimePicker类继承自FrameLayout可作为界面中的布局容器使用用于实现日期和时间选择的功能
public class DateTimePicker extends FrameLayout { public class DateTimePicker extends FrameLayout {
// 默认的启用状态设置为true表示默认启用该控件
private static final boolean DEFAULT_ENABLE_STATE = true; private static final boolean DEFAULT_ENABLE_STATE = true;
// 半天包含的小时数即12小时用于12小时制相关逻辑
private static final int HOURS_IN_HALF_DAY = 12; private static final int HOURS_IN_HALF_DAY = 12;
// 一整天包含的小时数即24小时用于24小时制相关逻辑
private static final int HOURS_IN_ALL_DAY = 24; private static final int HOURS_IN_ALL_DAY = 24;
// 一周包含的天数,用于日期选择器相关逻辑,例如循环显示一周内的日期等情况
private static final int DAYS_IN_ALL_WEEK = 7; private static final int DAYS_IN_ALL_WEEK = 7;
// 日期选择器的最小可选值通常设为0比如对应一周内第一天的索引等情况
private static final int DATE_SPINNER_MIN_VAL = 0; private static final int DATE_SPINNER_MIN_VAL = 0;
// 日期选择器的最大可选值根据一周的天数减1来设置因为索引从0开始
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
// 24小时制视图下小时选择器的最小可选值即0点
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
// 24小时制视图下小时选择器的最大可选值即23点
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
// 12小时制视图下小时选择器的最小可选值即1点12小时制中通常显示1 - 12
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
// 12小时制视图下小时选择器的最大可选值即12点
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
// 分钟选择器的最小可选值即0分钟
private static final int MINUT_SPINNER_MIN_VAL = 0; private static final int MINUT_SPINNER_MIN_VAL = 0;
// 分钟选择器的最大可选值即59分钟
private static final int MINUT_SPINNER_MAX_VAL = 59; private static final int MINUT_SPINNER_MAX_VAL = 59;
// AM/PM选择器的最小可选值通常设为0对应AM
private static final int AMPM_SPINNER_MIN_VAL = 0; private static final int AMPM_SPINNER_MIN_VAL = 0;
// AM/PM选择器的最大可选值设为1对应PM
private static final int AMPM_SPINNER_MAX_VAL = 1; private static final int AMPM_SPINNER_MAX_VAL = 1;
// 用于选择日期的NumberPicker控件显示在界面上供用户选择具体日期
private final NumberPicker mDateSpinner; private final NumberPicker mDateSpinner;
// 用于选择小时的NumberPicker控件根据是24小时制还是12小时制显示不同范围的小时值供选择
private final NumberPicker mHourSpinner; private final NumberPicker mHourSpinner;
// 用于选择分钟的NumberPicker控件可选择0 - 59分钟范围内的值
private final NumberPicker mMinuteSpinner; private final NumberPicker mMinuteSpinner;
// 用于选择上午/下午在12小时制下的NumberPicker控件显示AM或PM供选择
private final NumberPicker mAmPmSpinner; private final NumberPicker mAmPmSpinner;
// Calendar对象用于记录当前选中的日期和时间方便进行各种时间相关的计算和操作
private Calendar mDate; private Calendar mDate;
// 用于存储要在日期选择器中显示的一周内各天的字符串表示,例如"周一"、"周二"等
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
// 标记当前是否是上午用于12小时制下时间显示和逻辑处理
private boolean mIsAm; private boolean mIsAm;
// 标记当前是否是24小时制视图如果为true则按24小时制显示时间否则按12小时制显示
private boolean mIs24HourView; private boolean mIs24HourView;
// 记录该DateTimePicker控件的启用状态初始化为默认启用状态
private boolean mIsEnabled = DEFAULT_ENABLE_STATE; private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
// 标记是否正在初始化控件,用于在一些设置方法中避免不必要的重复操作
private boolean mInitialising; private boolean mInitialising;
// 定义一个接口,用于监听日期和时间发生变化的事件,外部类可实现该接口来响应时间变化
private OnDateTimeChangedListener mOnDateTimeChangedListener; private OnDateTimeChangedListener mOnDateTimeChangedListener;
// 日期选择器的值变化监听器,当日期选择器的值改变时触发相应逻辑
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override @Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) { public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 根据新旧值的差值在Calendar对象中调整日期实现日期的增减操作
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
// 更新日期选择器的显示内容,确保显示的是正确的日期信息
updateDateControl(); updateDateControl();
// 通知外部监听器(如果有设置的话)日期发生了变化,传递相关时间参数
onDateTimeChanged(); onDateTimeChanged();
} }
}; };
// 小时选择器的值变化监听器,处理小时值改变时的相关逻辑,如跨天、切换上午/下午等情况
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override @Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) { public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false; boolean isDateChanged = false;
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
// 如果不是24小时制视图即12小时制
if (!mIs24HourView) { if (!mIs24HourView) {
// 如果当前不是上午且旧值是1112小时制下半天的最后一小时新值变为12切换到下午
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis()); cal.setTimeInMillis(mDate.getTimeInMillis());
// 将日期增加一天因为从上午切换到下午跨越了中午12点到了下一天
cal.add(Calendar.DAY_OF_YEAR, 1); cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true; isDateChanged = true;
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis()); cal.setTimeInMillis(mDate.getTimeInMillis());
// 将日期减少一天因为从下午切换到上午跨越了中午12点回到了前一天
cal.add(Calendar.DAY_OF_YEAR, -1); cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true; isDateChanged = true;
} }
// 如果是从上午切换到下午或者从下午切换到上午的情况
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||
oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
// 切换上午/下午的标记
mIsAm = !mIsAm; mIsAm = !mIsAm;
// 更新上午/下午选择器的显示内容确保显示正确的AM/PM状态
updateAmPmControl(); updateAmPmControl();
} }
} else { } else {
// 如果是24小时制视图且从23点变为0点跨天情况
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis()); cal.setTimeInMillis(mDate.getTimeInMillis());
// 将日期增加一天,因为跨越了一天的边界
cal.add(Calendar.DAY_OF_YEAR, 1); cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true; isDateChanged = true;
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis()); cal.setTimeInMillis(mDate.getTimeInMillis());
// 将日期减少一天,因为跨越了一天的边界反向变化
cal.add(Calendar.DAY_OF_YEAR, -1); cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true; isDateChanged = true;
} }
} }
// 根据当前是否是上午以及小时选择器的值计算出正确的小时数用于设置到Calendar对象中
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour); mDate.set(Calendar.HOUR_OF_DAY, newHour);
// 通知外部监听器(如果有设置的话)时间发生了变化,传递相关时间参数
onDateTimeChanged(); onDateTimeChanged();
// 如果日期有改变,更新年、月、日等相关设置,确保整体时间的准确性
if (isDateChanged) { if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR)); setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH)); setCurrentMonth(cal.get(Calendar.MONTH));
@ -115,12 +159,14 @@ public class DateTimePicker extends FrameLayout {
} }
}; };
// 分钟选择器的值变化监听器,处理分钟值改变时涉及的时间调整逻辑,如可能导致小时、日期变化等情况
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override @Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) { public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue(); int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue(); int maxValue = mMinuteSpinner.getMaxValue();
int offset = 0; int offset = 0;
// 如果分钟从最大值变为最小值,意味着时间跨越了一个小时边界(可能需要调整小时、日期等)
if (oldVal == maxValue && newVal == minValue) { if (oldVal == maxValue && newVal == minValue) {
offset += 1; offset += 1;
} else if (oldVal == minValue && newVal == maxValue) { } else if (oldVal == minValue && newVal == maxValue) {
@ -131,6 +177,7 @@ public class DateTimePicker extends FrameLayout {
mHourSpinner.setValue(getCurrentHour()); mHourSpinner.setValue(getCurrentHour());
updateDateControl(); updateDateControl();
int newHour = getCurrentHourOfDay(); int newHour = getCurrentHourOfDay();
// 如果新的小时数大于等于12半天的小时数则标记为下午
if (newHour >= HOURS_IN_HALF_DAY) { if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false; mIsAm = false;
updateAmPmControl(); updateAmPmControl();
@ -140,85 +187,113 @@ public class DateTimePicker extends FrameLayout {
} }
} }
mDate.set(Calendar.MINUTE, newVal); mDate.set(Calendar.MINUTE, newVal);
// 通知外部监听器(如果有设置的话)时间发生了变化,传递相关时间参数
onDateTimeChanged(); onDateTimeChanged();
} }
}; };
// 上午/下午选择器的值变化监听器,处理上午/下午切换时涉及的小时数调整等逻辑
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override @Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) { public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm; mIsAm = !mIsAm;
// 如果切换到下午将小时数增加半天的小时数12小时
if (mIsAm) { if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else { } else {
// 如果切换到上午将小时数减少半天的小时数12小时
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
} }
updateAmPmControl(); updateAmPmControl();
// 通知外部监听器(如果有设置的话)时间发生了变化,传递相关时间参数
onDateTimeChanged(); onDateTimeChanged();
} }
}; };
// 定义一个接口用于外部类监听DateTimePicker的日期和时间变化事件
public interface OnDateTimeChangedListener { public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month, void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute); int dayOfMonth, int hourOfDay, int minute);
} }
// 构造方法接受一个Context参数使用当前时间作为初始日期创建DateTimePicker实例
public DateTimePicker(Context context) { public DateTimePicker(Context context) {
this(context, System.currentTimeMillis()); this(context, System.currentTimeMillis());
} }
// 构造方法接受一个Context参数和日期的时间戳long类型根据指定日期创建DateTimePicker实例默认按当前系统设置的时间格式24小时制或12小时制显示
public DateTimePicker(Context context, long date) { public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context)); this(context, date, DateFormat.is24HourFormat(context));
} }
// 完整的构造方法接受Context、日期时间戳和是否是24小时制视图的布尔值作为参数用于创建DateTimePicker实例并进行初始化设置
public DateTimePicker(Context context, long date, boolean is24HourView) { public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context); super(context);
// 获取当前时间作为初始的日期和时间设置
mDate = Calendar.getInstance(); mDate = Calendar.getInstance();
mInitialising = true; mInitialising = true;
// 根据当前小时数判断是否是上午如果大于等于12小时则为下午
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
// 加载datetime_picker布局文件到该控件中构建其内部的视图结构
inflate(context, R.layout.datetime_picker, this); inflate(context, R.layout.datetime_picker, this);
// 获取布局中的日期选择器控件,并进行相关设置
mDateSpinner = (NumberPicker) findViewById(R.id.date); mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);
// 获取布局中的小时选择器控件,并设置其值变化监听器
mHourSpinner = (NumberPicker) findViewById(R.id.hour); mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
// 获取布局中的分钟选择器控件,设置其最小、最大值以及长按更新间隔等属性,并设置值变化监听器
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100); mMinuteSpinner.setOnLongPressUpdateInterval(100);
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
// 获取用于显示上午/下午的字符串数组根据系统语言等设置获取对应的AM/PM字符串
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
// 获取布局中的上午/下午选择器控件,设置其最小、最大值以及显示的字符串数组,并设置值变化监听器
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm); mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// 更新日期选择器的显示内容,使其显示初始的正确日期信息
// update controls to initial state // update controls to initial state
updateDateControl(); updateDateControl();
// 更新小时选择器的显示范围等设置根据是否是24小时制进行相应调整
updateHourControl(); updateHourControl();
// 更新上午/下午选择器的显示状态根据是否是24小时制显示或隐藏该控件
updateAmPmControl(); updateAmPmControl();
// 设置是否是24小时制视图根据传入参数进行相应的显示和逻辑调整
set24HourView(is24HourView); set24HourView(is24HourView);
// 设置当前日期为传入的指定日期
// set to current time // set to current time
setCurrentDate(date); setCurrentDate(date);
// 设置该DateTimePicker控件及其内部各个选择器的启用状态
setEnabled(isEnabled()); setEnabled(isEnabled());
// 初始化完成将初始化标记设为false
// set the content descriptions // set the content descriptions
mInitialising = false; mInitialising = false;
} }
// 重写父类的setEnabled方法用于设置整个DateTimePicker控件及其内部各个时间选择器的启用状态
@Override @Override
public void setEnabled(boolean enabled) { public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) { if (mIsEnabled == enabled) {
return; return;
} }
super.setEnabled(enabled); super.setEnabled(enabled);
mDateSpinner.setEnabled(enabled); mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled); mMinuteSpinner.setEnabled(enabled);
@ -227,11 +302,13 @@ public class DateTimePicker extends FrameLayout {
mIsEnabled = enabled; mIsEnabled = enabled;
} }
// 重写父类的isEnabled方法用于获取整个DateTimePicker控件的启用状态
@Override @Override
public boolean isEnabled() { public boolean isEnabled() {
return mIsEnabled; return mIsEnabled;
} }
// 获取当前日期的时间戳以毫秒为单位返回当前记录在mDate中的日期对应的时间戳
/** /**
* Get the current date in millis * Get the current date in millis
* *
@ -241,6 +318,7 @@ public class DateTimePicker extends FrameLayout {
return mDate.getTimeInMillis(); return mDate.getTimeInMillis();
} }
// 根据传入的日期时间戳long类型设置当前日期内部会进一步解析并设置具体的年、月、日、时、分等信息
/** /**
* Set the current date * Set the current date
* *
@ -252,6 +330,7 @@ public class DateTimePicker extends FrameLayout {
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), 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)); cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
} }
// 设置当前日期的具体年、月、日、时、分信息,同时会更新
/** /**
* Set the current date * Set the current date
@ -262,28 +341,37 @@ public class DateTimePicker extends FrameLayout {
* @param hourOfDay The current hourOfDay * @param hourOfDay The current hourOfDay
* @param minute The current minute * @param minute The current minute
*/ */
// 设置当前日期的具体年、月、日、时、分信息,通过分别调用对应的设置方法来完成各项设置
public void setCurrentDate(int year, int month, public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) { int dayOfMonth, int hourOfDay, int minute) {
// 调用设置年份的方法
setCurrentYear(year); setCurrentYear(year);
// 调用设置月份的方法
setCurrentMonth(month); setCurrentMonth(month);
// 调用设置日的方法
setCurrentDay(dayOfMonth); setCurrentDay(dayOfMonth);
// 调用设置小时的方法
setCurrentHour(hourOfDay); setCurrentHour(hourOfDay);
// 调用设置分钟的方法
setCurrentMinute(minute); setCurrentMinute(minute);
} }
/** /**
* Get current year *
* *
* @return The current year * @return Calendar
*/ */
public int getCurrentYear() { public int getCurrentYear() {
return mDate.get(Calendar.YEAR); return mDate.get(Calendar.YEAR);
} }
/** /**
* Set current year *
* *
* @param year The current year * @param year
* mInitialisingfalse
* Calendar
* updateDateControlonDateTimeChanged
*/ */
public void setCurrentYear(int year) { public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) { if (!mInitialising && year == getCurrentYear()) {
@ -295,18 +383,21 @@ public class DateTimePicker extends FrameLayout {
} }
/** /**
* Get current month in the year * 0 - 11011
* *
* @return The current month in the year * @return Calendar
*/ */
public int getCurrentMonth() { public int getCurrentMonth() {
return mDate.get(Calendar.MONTH); return mDate.get(Calendar.MONTH);
} }
/** /**
* Set current month in the year *
* *
* @param month The month in the year * @param month 0 - 11
* mInitialisingfalse
* Calendar
* updateDateControlonDateTimeChanged
*/ */
public void setCurrentMonth(int month) { public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) { if (!mInitialising && month == getCurrentMonth()) {
@ -318,18 +409,21 @@ public class DateTimePicker extends FrameLayout {
} }
/** /**
* Get current day of the month *
* *
* @return The day of the month * @return Calendar
*/ */
public int getCurrentDay() { public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH); return mDate.get(Calendar.DAY_OF_MONTH);
} }
/** /**
* Set current day of the month *
* *
* @param dayOfMonth The day of the month * @param dayOfMonth
* mInitialisingfalse
* Calendar
* updateDateControlonDateTimeChanged
*/ */
public void setCurrentDay(int dayOfMonth) { public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) { if (!mInitialising && dayOfMonth == getCurrentDay()) {
@ -341,13 +435,21 @@ public class DateTimePicker extends FrameLayout {
} }
/** /**
* Get current hour in 24 hour mode, in the range (0~23) * 240 - 23
* @return The current hour in 24 hour mode *
* @return 24CalendarHOUR_OF_DAY
*/ */
public int getCurrentHourOfDay() { public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY); return mDate.get(Calendar.HOUR_OF_DAY);
} }
/**
* 24
* 2424getCurrentHourOfDay
* 1224121201212
*
* @return
*/
private int getCurrentHour() { private int getCurrentHour() {
if (mIs24HourView){ if (mIs24HourView){
return getCurrentHourOfDay(); return getCurrentHourOfDay();
@ -362,9 +464,14 @@ public class DateTimePicker extends FrameLayout {
} }
/** /**
* Set current hour in 24 hour mode, in the range (0~23) * 24
* *
* @param hourOfDay * @param hourOfDay 240 - 23
* mInitialisingfalse24
* CalendarHOUR_OF_DAY
* 2412/mIsAm
* 1212updateAmPmControl/
* onDateTimeChanged
*/ */
public void setCurrentHour(int hourOfDay) { public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
@ -390,16 +497,19 @@ public class DateTimePicker extends FrameLayout {
} }
/** /**
* Get currentMinute *
* *
* @return The Current Minute * @return Calendar
*/ */
public int getCurrentMinute() { public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE); return mDate.get(Calendar.MINUTE);
} }
/** /**
* Set current minute *
* mInitialisingfalse
*
* CalendaronDateTimeChanged
*/ */
public void setCurrentMinute(int minute) { public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) { if (!mInitialising && minute == getCurrentMinute()) {
@ -411,16 +521,22 @@ public class DateTimePicker extends FrameLayout {
} }
/** /**
* @return true if this is in 24 hour view else false. * 24
*
* @return 24truefalse
*/ */
public boolean is24HourView () { public boolean is24HourView () {
return mIs24HourView; return mIs24HourView;
} }
/** /**
* Set whether in 24 hour or AM/PM mode. * 24AM/PM12
* *
* @param is24HourView True for 24 hour mode. False for AM/PM mode. * @param is24HourView true24false12AM/PM
* mIs24HourView
* mIs24HourView/mAmPmSpinner2412
* 24updateHourControl2412
* setCurrentHourupdateAmPmControl/
*/ */
public void set24HourView(boolean is24HourView) { public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) { if (mIs24HourView == is24HourView) {
@ -434,6 +550,17 @@ public class DateTimePicker extends FrameLayout {
updateAmPmControl(); updateAmPmControl();
} }
/**
* mDateSpinner
*
* 1. CalendarmDate
* 2. CalendarDAYS_IN_ALL_WEEK / 2 + 1
* 3. null
* 4. Calendar使DateFormat"MM.dd EEEE". X
* mDateDisplayValues
* 5. mDateDisplayValuesDAYS_IN_ALL_WEEK / 2
* invalidate使
*/
private void updateDateControl() { private void updateDateControl() {
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis()); cal.setTimeInMillis(mDate.getTimeInMillis());
@ -448,6 +575,12 @@ public class DateTimePicker extends FrameLayout {
mDateSpinner.invalidate(); mDateSpinner.invalidate();
} }
/**
* 24/mAmPmSpinner
* 24/GONE
* 2412/mIsAm/AM0PM1
* /VISIBLE
*/
private void updateAmPmControl() { private void updateAmPmControl() {
if (mIs24HourView) { if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE); mAmPmSpinner.setVisibility(View.GONE);
@ -458,6 +591,13 @@ public class DateTimePicker extends FrameLayout {
} }
} }
/**
* 24mHourSpinner
* 24HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW0
* HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW23
* 2412HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW1
* HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW12
*/
private void updateHourControl() { private void updateHourControl() {
if (mIs24HourView) { if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
@ -469,13 +609,20 @@ public class DateTimePicker extends FrameLayout {
} }
/** /**
* Set the callback that indicates the 'Set' button has been pressed. * OnDateTimeChangedListener
* @param callback the callback, if null will do nothing * DateTimePicker
* null
*
* @param callback OnDateTimeChangedListener
*/ */
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback; mOnDateTimeChangedListener = callback;
} }
/**
*
* DateTimePickeronDateTimeChanged
*/
private void onDateTimeChanged() { private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) { if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),

@ -28,22 +28,33 @@ import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.text.format.DateUtils; import android.text.format.DateUtils;
// DateTimePickerDialog类继承自AlertDialog同时实现了OnClickListener接口用于创建一个带有日期和时间选择功能的对话框
public class DateTimePickerDialog extends AlertDialog implements OnClickListener { public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
// Calendar对象用于记录当前选择的日期和时间初始化为当前系统时间
private Calendar mDate = Calendar.getInstance(); private Calendar mDate = Calendar.getInstance();
// 标记当前是否是24小时制视图用于控制时间的显示格式
private boolean mIs24HourView; private boolean mIs24HourView;
// 定义一个接口类型的变量,用于监听用户设置好日期和时间后的操作,外部类可实现该接口来响应设置事件
private OnDateTimeSetListener mOnDateTimeSetListener; private OnDateTimeSetListener mOnDateTimeSetListener;
// 用于实际进行日期和时间选择操作的DateTimePicker控件实例
private DateTimePicker mDateTimePicker; private DateTimePicker mDateTimePicker;
// 定义一个接口,用于在用户点击对话框中的确定按钮,设置好日期和时间后进行回调,外部类需实现该接口来处理具体逻辑
public interface OnDateTimeSetListener { public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date); void OnDateTimeSet(AlertDialog dialog, long date);
} }
// 构造方法接受一个Context参数和日期的时间戳long类型用于创建DateTimePickerDialog实例并进行初始化设置
public DateTimePickerDialog(Context context, long date) { public DateTimePickerDialog(Context context, long date) {
super(context); super(context);
// 创建一个DateTimePicker实例用于在对话框中展示日期和时间选择的界面
mDateTimePicker = new DateTimePicker(context); mDateTimePicker = new DateTimePicker(context);
// 将DateTimePicker控件设置为对话框的内容视图使其显示在对话框中供用户操作
setView(mDateTimePicker); setView(mDateTimePicker);
// 为DateTimePicker设置日期和时间变化的监听器当用户在DateTimePicker中选择的日期或时间发生变化时
// 会触发该监听器中的onDateTimeChanged方法在该方法内更新mDate对象记录的日期和时间信息并调用updateTitle方法更新对话框标题显示的当前日期和时间
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month, public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) { int dayOfMonth, int hourOfDay, int minute) {
@ -55,32 +66,56 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener
updateTitle(mDate.getTimeInMillis()); updateTitle(mDate.getTimeInMillis());
} }
}); });
// 设置当前日期和时间为传入的指定日期时间戳对应的时间同时将秒数设置为0通常在选择时间时精确到分钟即可
mDate.setTimeInMillis(date); mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0); mDate.set(Calendar.SECOND, 0);
// 将DateTimePicker显示的当前日期和时间设置为mDate所记录的时间确保初始显示与传入的指定时间一致
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
// 设置对话框的确定按钮按钮文本通过资源字符串获取R.string.datetime_dialog_ok并将当前类实现了OnClickListener接口作为点击监听器传入
// 当用户点击确定按钮时会触发onClick方法进行相应处理
setButton(context.getString(R.string.datetime_dialog_ok), this); setButton(context.getString(R.string.datetime_dialog_ok), this);
// 设置对话框的取消按钮按钮文本通过资源字符串获取R.string.datetime_dialog_cancel传入null作为点击监听器表示使用默认的取消行为关闭对话框
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
// 根据系统当前的时间格式设置是否是24小时制来设置该对话框的时间显示格式调用set24HourView方法进行设置
set24HourView(DateFormat.is24HourFormat(this.getContext())); set24HourView(DateFormat.is24HourFormat(this.getContext()));
// 初次创建对话框时,根据传入的初始日期时间戳更新对话框标题显示的当前日期和时间
updateTitle(mDate.getTimeInMillis()); updateTitle(mDate.getTimeInMillis());
} }
// 设置该对话框的时间显示格式是否为24小时制传入true表示设置为24小时制传入false表示设置为12小时制AM/PM
public void set24HourView(boolean is24HourView) { public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView; mIs24HourView = is24HourView;
} }
// 设置用于监听用户设置好日期和时间后的回调接口外部类实现OnDateTimeSetListener接口后将实例传入该方法
// 以便在用户点击确定按钮完成设置时接收通知并进行相应处理
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack; mOnDateTimeSetListener = callBack;
} }
// 私有方法用于根据当前日期时间以及是否是24小时制视图等信息更新对话框标题显示的日期和时间字符串
private void updateTitle(long date) {
private void updateTitle(long date) { private void updateTitle(long date) {
int flag = int flag =
DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_YEAR |// 表示在格式化日期时间字符串时显示年份
DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_DATE |// 表示显示日期(月、日等)
DateUtils.FORMAT_SHOW_TIME; DateUtils.FORMAT_SHOW_TIME;// 表示显示时间(时、分等)
// 根据当前是否是24小时制视图添加相应的格式化标志用于控制时间的显示格式24小时制或12小时制带AM/PM
flag |= mIs24HourView? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_12HOUR;
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
} }
// 实现OnClickListener接口的onClick方法当用户点击对话框中的按钮这里主要是确定按钮时触发该方法
// 如果设置了OnDateTimeSetListener监听器会调用其OnDateTimeSet方法将当前对话框实例以及选择好的日期时间戳传递给外部监听器
// 以便外部类进行相应的后续处理(比如保存选择的时间等操作)
public void onClick(DialogInterface arg0, int arg1) { public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) { if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());

@ -27,17 +27,35 @@ import android.widget.PopupMenu.OnMenuItemClickListener;
import net.micode.notes.R; import net.micode.notes.R;
// DropdownMenu类用于创建一个下拉菜单的功能组件通常可以关联一个按钮点击按钮弹出包含相应菜单项的菜单
public class DropdownMenu { public class DropdownMenu {
// 用于显示下拉菜单的按钮,用户点击该按钮会触发弹出菜单的操作
private Button mButton; private Button mButton;
// PopupMenu实例用于实现弹出式菜单的功能它会依附在指定的View这里是mButton上显示
private PopupMenu mPopupMenu; private PopupMenu mPopupMenu;
// 代表菜单对象,用于操作和获取菜单中的具体菜单项等相关信息
private Menu mMenu; private Menu mMenu;
// 构造方法用于初始化DropdownMenu实例接受上下文环境Context、关联的按钮Button以及菜单资源IDint类型作为参数
public DropdownMenu(Context context, Button button, int menuId) { public DropdownMenu(Context context, Button button, int menuId) {
// 将传入的按钮赋值给成员变量mButton后续操作会基于这个按钮来触发弹出菜单等行为
mButton = button; mButton = button;
// 为按钮设置背景资源,这里使用的是名为"dropdown_icon"的资源(通常是一个图标,用于提示用户该按钮可弹出菜单)
mButton.setBackgroundResource(R.drawable.dropdown_icon); mButton.setBackgroundResource(R.drawable.dropdown_icon);
// 创建一个PopupMenu实例将传入的上下文环境和关联的按钮作为参数传入使得弹出菜单能够在正确的界面环境下依附该按钮显示
mPopupMenu = new PopupMenu(context, mButton); mPopupMenu = new PopupMenu(context, mButton);
// 获取PopupMenu中的Menu对象赋值给成员变量mMenu以便后续对菜单进行各种操作比如添加菜单项、查找菜单项等
mMenu = mPopupMenu.getMenu(); mMenu = mPopupMenu.getMenu();
// 通过菜单填充器MenuInflater将指定资源ID对应的菜单布局文件通常定义了菜单项等信息填充到mMenu中
// 这样就构建好了具有具体菜单项的菜单
mPopupMenu.getMenuInflater().inflate(menuId, mMenu); mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
// 为按钮设置点击事件监听器当按钮被点击时会触发onClick方法在该方法内调用PopupMenu的show方法
// 从而显示弹出式菜单供用户选择菜单项
mButton.setOnClickListener(new OnClickListener() { mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
mPopupMenu.show(); mPopupMenu.show();
@ -45,16 +63,23 @@ public class DropdownMenu {
}); });
} }
// 设置下拉菜单中菜单项的点击事件监听器外部类实现OnMenuItemClickListener接口后
// 通过传入该接口的实现实例,可以监听每个菜单项被点击时的事件,并进行相应的处理逻辑
// 如果PopupMenu实例不为空则将传入的监听器设置给PopupMenu使其能够响应菜单项的点击操作
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) { if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener); mPopupMenu.setOnMenuItemClickListener(listener);
} }
} }
// 根据传入的菜单项ID在菜单mMenu中查找对应的MenuItem实例并返回方便外部类对特定菜单项进行进一步操作
// 比如获取菜单项的属性、设置菜单项的状态等
public MenuItem findItem(int id) { public MenuItem findItem(int id) {
return mMenu.findItem(id); return mMenu.findItem(id);
} }
// 设置按钮上显示的文本内容,通常可以用于显示当前选中的菜单项相关信息或者作为菜单的标题等,
// 通过修改按钮的文本setText方法来实现显示效果的更新
public void setTitle(CharSequence title) { public void setTitle(CharSequence title) {
mButton.setText(title); mButton.setText(title);
} }

@ -28,26 +28,37 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes; import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
// FoldersListAdapter类继承自CursorAdapter用于为包含文件夹相关数据的Cursor通常来自数据库查询结果提供适配
// 以便在ListView等列表视图组件中展示文件夹信息。
public class FoldersListAdapter extends CursorAdapter { public class FoldersListAdapter extends CursorAdapter {
// 定义一个字符串数组用于指定从数据库查询时需要获取的列信息这里包含了文件夹的ID和片段可能是名称等相关信息
public static final String [] PROJECTION = { public static final String [] PROJECTION = {
NoteColumns.ID, NoteColumns.ID,
NoteColumns.SNIPPET NoteColumns.SNIPPET
}; };
// 定义一个常量表示在查询结果的Cursor中文件夹ID所在列的索引值为0方便后续从Cursor中准确获取对应的数据
public static final int ID_COLUMN = 0; public static final int ID_COLUMN = 0;
// 定义一个常量表示在查询结果的Cursor中文件夹名称这里用NAME_COLUMN指代可能实际对应NoteColumns.SNIPPET等相关列所在列的索引值为1
public static final int NAME_COLUMN = 1; public static final int NAME_COLUMN = 1;
// 构造方法接受上下文环境Context和一个Cursor对象作为参数调用父类CursorAdapter的构造方法进行初始化
// 这里的TODO注释表示后续可能需要完善该构造方法的具体逻辑目前仅完成了基本的初始化调用父类构造函数操作。
public FoldersListAdapter(Context context, Cursor c) { public FoldersListAdapter(Context context, Cursor c) {
super(context, c); super(context, c);
// TODO Auto-generated constructor stub // TODO Auto-generated constructor stub
} }
// 重写CursorAdapter的newView方法该方法用于创建一个新的视图View对象用于展示列表中的每一项数据。
// 在这里返回一个新的FolderListItem实例意味着每个列表项的视图将由FolderListItem来构建和管理。
@Override @Override
public View newView(Context context, Cursor cursor, ViewGroup parent) { public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context); return new FolderListItem(context);
} }
// 重写CursorAdapter的bindView方法该方法用于将数据绑定到已经创建好的视图View即将Cursor中的数据填充到对应的视图控件中进行显示。
// 首先判断传入的视图是否是FolderListItem类型如果是则根据Cursor中获取的数据来设置文件夹名称显示的文本内容。
// 如果文件夹的ID等于特定的根文件夹IDNotes.ID_ROOT_FOLDER则显示一个特定的字符串从资源中获取可能是表示根文件夹的提示文本
// 否则显示从Cursor中获取的对应名称列NAME_COLUMN的字符串内容并通过FolderListItem的bind方法将名称设置到对应的TextView控件中进行显示。
@Override @Override
public void bindView(View view, Context context, Cursor cursor) { public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) { if (view instanceof FolderListItem) {
@ -57,21 +68,31 @@ public class FoldersListAdapter extends CursorAdapter {
} }
} }
// 自定义的方法用于获取指定位置position的文件夹名称。
// 通过调用getItem方法继承自CursorAdapter获取对应位置的Cursor对象然后根据与bindView方法类似的逻辑
// 判断文件夹ID是否为根文件夹ID来决定返回特定字符串还是从Cursor中获取的名称列字符串内容。
public String getFolderName(Context context, int position) { public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position); Cursor cursor = (Cursor) getItem(position);
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
} }
// 定义一个内部类FolderListItem继承自LinearLayout用于表示列表中每个文件夹项对应的视图布局结构以及相关操作。
private class FolderListItem extends LinearLayout { private class FolderListItem extends LinearLayout {
// 定义一个TextView控件用于显示文件夹的名称信息。
private TextView mName; private TextView mName;
// 构造方法接受上下文环境Context作为参数调用父类LinearLayout的构造方法进行初始化
// 然后通过inflate方法加载名为R.layout.folder_list_item的布局文件到当前视图中
// 最后从加载后的布局中找到ID为R.id.tv_folder_name的TextView控件并赋值给mName成员变量以便后续操作。
public FolderListItem(Context context) { public FolderListItem(Context context) {
super(context); super(context);
inflate(context, R.layout.folder_list_item, this); inflate(context, R.layout.folder_list_item, this);
mName = (TextView) findViewById(R.id.tv_folder_name); mName = (TextView) findViewById(R.id.tv_folder_name);
} }
// 自定义的方法用于将传入的文件夹名称字符串设置到mNameTextView控件上进行显示实现数据与视图的绑定操作。
public void bind(String name) { public void bind(String name) {
mName.setText(name); mName.setText(name);
} }

@ -15,7 +15,7 @@
*/ */
package net.micode.notes.ui; package net.micode.notes.ui;
// 这里假设你的包名是com.example.yourpackage实际需替换为正确的包名
import android.app.Activity; import android.app.Activity;
import android.app.AlarmManager; import android.app.AlarmManager;
import android.app.AlertDialog; import android.app.AlertDialog;
@ -71,19 +71,23 @@ import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
// NoteEditActivity类继承自Activity或AppCompatActivity取决于实际的父类继承情况实现了多个接口用于处理笔记编辑相关的各种交互逻辑
// 例如点击事件、设置变更监听、文本变更监听等是一个用于编辑笔记的Activity
public class NoteEditActivity extends Activity implements OnClickListener, public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener { NoteSettingChangedListener, OnTextViewChangeListener {
// 内部类HeadViewHolder用于封装笔记头部视图相关的UI组件方便管理和操作
private class HeadViewHolder { private class HeadViewHolder {
public TextView tvModified; public TextView tvModified;
// 用于显示笔记修改时间的TextView组件
public ImageView ivAlertIcon; public ImageView ivAlertIcon;
// 用于显示提醒图标如闹钟图标等的ImageView组件
public TextView tvAlertDate; public TextView tvAlertDate;
// 用于显示提醒时间相关信息的TextView组件
public ImageView ibSetBgColor; public ImageView ibSetBgColor;
// 用于设置背景颜色的ImageView按钮组件
} }
// 静态的Map用于将背景颜色选择按钮的资源ID如R.id.iv_bg_yellow等映射到对应的颜色常量如ResourceParser.YELLOW等方便根据按钮ID获取对应的颜色标识
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>(); private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>();
static { static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
@ -93,6 +97,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
} }
// 静态的Map用于将颜色常量如ResourceParser.YELLOW等映射到对应的背景颜色选择按钮被选中时的显示资源ID如R.id.iv_bg_yellow_select等
// 用于在选择颜色后显示相应的选中效果
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>(); private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>();
static { static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
@ -102,6 +108,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
} }
// 静态的Map用于将字体大小选择按钮所在的布局资源ID如R.id.ll_font_large等映射到对应的字体大小常量如ResourceParser.TEXT_LARGE等方便根据按钮布局ID获取对应的字体大小标识
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>(); private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>();
static { static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
@ -110,6 +117,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
} }
// 静态的Map用于将字体大小常量如ResourceParser.TEXT_LARGE等映射到对应的字体大小选择按钮被选中时的显示资源ID如R.id.iv_large_select等
// 用于在选择字体大小后显示相应的选中效果
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>(); private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>();
static { static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);
@ -118,46 +127,68 @@ public class NoteEditActivity extends Activity implements OnClickListener,
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
} }
// 用于日志记录的标签方便在日志中识别该Activity相关的输出信息
private static final String TAG = "NoteEditActivity"; private static final String TAG = "NoteEditActivity";
// 用于存储笔记头部视图相关组件的实例,方便后续操作和更新头部视图的显示内容
private HeadViewHolder mNoteHeaderHolder; private HeadViewHolder mNoteHeaderHolder;
// 笔记头部视图的整体布局,包含标题、修改时间、提醒相关等组件的外层容器
private View mHeadViewPanel; private View mHeadViewPanel;
// 用于显示背景颜色选择器的视图,包含各种背景颜色选项按钮等
private View mNoteBgColorSelector; private View mNoteBgColorSelector;
// 用于显示字体大小选择器的视图,包含各种字体大小选项按钮等
private View mFontSizeSelector; private View mFontSizeSelector;
// 用于编辑笔记内容的EditText组件用户在此输入和修改笔记的文本内容
private EditText mNoteEditor; private EditText mNoteEditor;
// 包含笔记编辑区域如mNoteEditor以及相关滚动等功能的外层布局视图用于整体的笔记编辑界面布局管理
private View mNoteEditorPanel; private View mNoteEditorPanel;
// 代表正在编辑的笔记对象,包含笔记的各种属性(如内容、修改时间、背景颜色等)以及相关操作方法(如保存、加载等)
private WorkingNote mWorkingNote; private WorkingNote mWorkingNote;
// 用于获取和操作应用的共享偏好设置,可用于存储和读取如字体大小、用户个性化设置等相关信息
private SharedPreferences mSharedPrefs; private SharedPreferences mSharedPrefs;
// 当前选择的字体大小ID对应于ResourceParser中定义的字体大小常量用于确定笔记编辑区域显示的字体大小
private int mFontSizeId; private int mFontSizeId;
// 存储在共享偏好设置中用于标识字体大小偏好设置的键名常量
private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; private static final String PREFERENCE_FONT_SIZE = "pref_font_size";
// 快捷方式图标标题的最大长度限制,用于生成发送到桌面的快捷方式图标时,截取合适长度的笔记内容作为标题
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;
// 表示复选框被选中状态的字符常量,用于处理复选框列表模式下的笔记内容显示和解析
public static final String TAG_CHECKED = String.valueOf('\u221A'); public static final String TAG_CHECKED = String.valueOf('\u221A');
// 表示复选框未被选中状态的字符常量,用于处理复选框列表模式下的笔记内容显示和解析
public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
// 用于存储笔记编辑中多行文本如果是列表模式等情况的LinearLayout每个子项可能是一个包含文本和复选框的自定义布局方便处理多行文本编辑逻辑
private LinearLayout mEditTextList; private LinearLayout mEditTextList;
// 用户查询的文本内容,可能用于搜索、筛选等功能相关,比如在笔记中查找特定内容等场景
private String mUserQuery; private String mUserQuery;
// 用于正则表达式匹配的Pattern对象可能用于在笔记内容中按照特定规则查找、匹配文本等操作具体取决于业务逻辑中如何使用
private Pattern mPattern; private Pattern mPattern;
// Activity创建时调用的方法进行一些初始化操作如设置布局、恢复Activity状态等
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// 设置该Activity对应的布局文件即加载笔记编辑界面的UI布局
this.setContentView(R.layout.note_edit); this.setContentView(R.layout.note_edit);
// 如果是首次创建savedInstanceState为null且初始化Activity状态失败则直接结束该Activity并返回
if (savedInstanceState == null && !initActivityState(getIntent())) { if (savedInstanceState == null && !initActivityState(getIntent())) {
finish(); finish();
return; return;
} }
// 初始化各种资源如查找UI组件、设置点击事件监听器等
initResources(); initResources();
} }
@ -168,6 +199,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
@Override @Override
protected void onRestoreInstanceState(Bundle savedInstanceState) { protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState); super.onRestoreInstanceState(savedInstanceState);
// 如果保存的实例状态不为null且包含特定的用户ID信息通过Intent.EXTRA_UID标识则尝试恢复Activity状态
if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));
@ -179,16 +211,21 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
} }
// 根据传入的Intent初始化Activity的状态根据不同的Intent动作如查看、新建/编辑等)来加载相应的笔记数据或者创建新的笔记对象
private boolean initActivityState(Intent intent) { private boolean initActivityState(Intent intent) {
// 初始化正在编辑的笔记对象为null
/** /**
* If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id,
* then jump to the NotesListActivity * then jump to the NotesListActivity
*/ */
mWorkingNote = null; mWorkingNote = null;
// 如果Intent的动作是查看笔记Intent.ACTION_VIEW
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
// 获取笔记的ID如果没有传入则默认为0
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);
mUserQuery = ""; mUserQuery = "";
// 如果Intent中包含搜索结果相关的额外数据通过SearchManager.EXTRA_DATA_KEY标识则更新笔记ID和用户查询文本
/** /**
* Starting from the searched result * Starting from the searched result
*/ */
@ -197,6 +234,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY);
} }
// 检查该笔记ID对应的笔记在数据库中是否可见是否存在且满足一定可见条件如果不可见则跳转到笔记列表页面并提示笔记不存在然后结束当前Activity
if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) {
Intent jump = new Intent(this, NotesListActivity.class); Intent jump = new Intent(this, NotesListActivity.class);
startActivity(jump); startActivity(jump);
@ -204,6 +242,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
finish(); finish();
return false; return false;
} else { } else {
// 从数据库加载对应笔记ID的笔记对象如果加载失败则记录错误日志并结束当前Activity
mWorkingNote = WorkingNote.load(this, noteId); mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) { if (mWorkingNote == null) {
Log.e(TAG, "load note failed with note id" + noteId); Log.e(TAG, "load note failed with note id" + noteId);
@ -211,19 +250,28 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return false; return false;
} }
} }
// 设置软键盘的显示模式,初始隐藏软键盘,并且根据内容调整布局大小
getWindow().setSoftInputMode( getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
} else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { } else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) {
// 如果Intent的动作是新建或编辑笔记Intent.ACTION_INSERT_OR_EDIT
// 获取文件夹ID如果没有传入则默认为0
// New note // New note
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);
// 获取小部件ID如果没有传入则默认为无效的小部件IDAppWidgetManager.INVALID_APPWIDGET_ID
int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID); AppWidgetManager.INVALID_APPWIDGET_ID);
// 获取小部件类型如果没有传入则默认为无效的小部件类型Notes.TYPE_WIDGET_INVALIDE
int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE,
Notes.TYPE_WIDGET_INVALIDE); Notes.TYPE_WIDGET_INVALIDE);
// 获取背景资源ID如果没有传入则调用ResourceParser获取默认的背景颜色资源ID
int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID,
ResourceParser.getDefaultBgId(this)); ResourceParser.getDefaultBgId(this));
// 解析通话记录笔记相关信息如果Intent中包含电话号码和通话日期则尝试根据这些信息获取对应的笔记ID
// 如果找到对应笔记ID则加载该笔记对象否则创建一个空的笔记对象并转换为通话笔记格式
// Parse call-record note // Parse call-record note
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0);
@ -241,11 +289,15 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return false; return false;
} }
} else { } else {
// 如果没有通话记录相关信息,则创建一个空的笔记对象
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId,
widgetType, bgResId); widgetType, bgResId);
mWorkingNote.convertToCallNote(phoneNumber, callDate); mWorkingNote.convertToCallNote(phoneNumber, callDate);
} }
// 设置软键盘的显示模式,初始显示软键盘并且根据内容调整布局大小
} else { } else {
// 如果Intent没有指定有效的动作则记录错误日志并结束当前Activity因为不支持这种情况
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType,
bgResId); bgResId);
} }
@ -258,66 +310,96 @@ public class NoteEditActivity extends Activity implements OnClickListener,
finish(); finish();
return false; return false;
} }
// 设置正在编辑的笔记对象的设置状态变更监听器为当前Activity实现了NoteSettingChangedListener接口
mWorkingNote.setOnSettingStatusChangedListener(this); mWorkingNote.setOnSettingStatusChangedListener(this);
return true; return true;
} }
// Activity重新恢复到前台可见时调用的方法用于重新初始化笔记编辑界面的显示内容等
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
initNoteScreen(); initNoteScreen();
} }
// 初始化笔记编辑界面的显示内容,如设置字体大小、根据笔记模式(是否为复选框列表模式等)显示相应内容、更新头部视图的时间显示等
private void initNoteScreen() { private void initNoteScreen() {
// 设置笔记编辑区域的文本外观根据当前选择的字体大小ID获取对应的文本外观资源并应用
mNoteEditor.setTextAppearance(this, TextAppearanceResources mNoteEditor.setTextAppearance(this, TextAppearanceResources
.getTexAppearanceResource(mFontSizeId)); .getTexAppearanceResource(mFontSizeId));
// 如果笔记处于复选框列表模式,则切换到列表模式显示界面,并传入笔记内容进行相应处理
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
switchToListMode(mWorkingNote.getContent()); switchToListMode(mWorkingNote.getContent());
} else { } else {
// 如果不是列表模式,则在编辑区域显示笔记内容,并对用户查询的文本进行高亮显示,然后将光标定位到文本末尾
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
mNoteEditor.setSelection(mNoteEditor.getText().length()); mNoteEditor.setSelection(mNoteEditor.getText().length());
} }
// 遍历背景颜色选择按钮选中状态对应的资源ID映射表将所有选中状态的显示组件设置为不可见用于清除之前可能的选中显示效果
for (Integer id : sBgSelectorSelectionMap.keySet()) { for (Integer id : sBgSelectorSelectionMap.keySet()) {
findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);
} }
// 设置笔记头部视图的背景资源根据正在编辑的笔记对象获取对应的标题背景资源ID并应用
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
// 设置笔记编辑区域的背景资源根据正在编辑的笔记对象获取对应的背景颜色资源ID并应用
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
// 设置笔记头部视图中修改时间的显示文本,根据笔记的修改时间格式化并显示相应的日期和时间信息
mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this,
mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR)); | DateUtils.FORMAT_SHOW_YEAR));
// 显示提醒相关的头部信息(如提醒图标和提醒时间等),根据笔记是否设置了提醒以及当前时间等情况进行相应的显示
/** /**
* TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker * TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker
* is not ready * is not ready
*/ */
// 显示提醒相关的头部信息(如提醒图标和提醒时间等),根据笔记是否设置了提醒以及当前时间等情况进行相应的显示
showAlertHeader(); showAlertHeader();
} }
// 此方法用于根据笔记是否设置了提醒以及当前时间与提醒时间的关系,来设置笔记头部提醒相关视图(提醒时间文本和提醒图标)的显示状态及内容
private void showAlertHeader() {
private void showAlertHeader() { private void showAlertHeader() {
// 判断正在编辑的笔记对象mWorkingNote是否设置了时钟提醒hasClockAlert方法返回true表示设置了提醒
if (mWorkingNote.hasClockAlert()) { if (mWorkingNote.hasClockAlert()) {
// 获取当前系统时间以毫秒为单位从1970年1月1日00:00:00 UTC到当前时间的毫秒数
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
// 如果当前时间大于笔记设置的提醒时间
if (time > mWorkingNote.getAlertDate()) { if (time > mWorkingNote.getAlertDate()) {
// 将提醒时间文本视图mNoteHeaderHolder.tvAlertDate的内容设置为表示提醒已过期的字符串资源R.string.note_alert_expired
mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired);
} else { } else {
// 如果当前时间未超过提醒时间则调用DateUtils的方法获取相对时间跨度字符串用于显示距离提醒时间还有多久
// 参数分别为提醒时间、当前时间以及时间跨度的单位这里是以分钟为单位DateUtils.MINUTE_IN_MILLIS表示一分钟对应的毫秒数
mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString(
mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS));
} }
// 将提醒时间文本视图设置为可见状态,以便用户能看到提醒相关的时间信息
mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE);
// 将提醒图标视图设置为可见状态,展示相应的提醒图标,提示用户有提醒设置
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE);
} else { } else {
// 如果笔记没有设置时钟提醒,则将提醒时间文本视图设置为不可见状态,隐藏相关文本显示
mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE);
// 将提醒图标视图设置为不可见状态,隐藏提醒图标
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE);
}; };
} }
// 当Activity接收到新的Intent时会调用此方法它首先调用父类的onNewIntent方法然后根据新传入的Intent重新初始化Activity的状态
@Override @Override
protected void onNewIntent(Intent intent) { protected void onNewIntent(Intent intent) {
super.onNewIntent(intent); super.onNewIntent(intent);
// 调用initActivityState方法传入新的Intent根据Intent中的信息来重新加载笔记数据、设置相关属性等以适应新的启动意图
// 比如从其他地方重新启动该Activity并传递了不同的参数就会在此处更新Activity的状态
initActivityState(intent); initActivityState(intent);
} }
// 当系统即将销毁Activity以回收内存或者进行配置变更如屏幕旋转等情况时会调用此方法来保存Activity的当前状态信息
// 以便后续可以恢复到当前状态。这里先调用父类的onSaveInstanceState方法来执行默认的保存操作然后进行自定义的保存逻辑
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
@ -328,44 +410,66 @@ public class NoteEditActivity extends Activity implements OnClickListener,
*/ */
if (!mWorkingNote.existInDatabase()) { if (!mWorkingNote.existInDatabase()) {
saveNote(); saveNote();
// 将正在编辑的笔记对象的ID存入传出的BundleoutState键为Intent.EXTRA_UID以便后续恢复状态时能获取到该笔记的标识信息
} }
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());
// 在日志中记录正在保存的工作笔记的ID信息方便调试和查看状态保存情况使用TAG作为日志标签方便识别是该Activity相关的日志输出
Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
} }
// 用于分发触摸事件Activity接收到触摸事件后会先经过此方法处理在这里可以拦截触摸事件或者将其传递给子视图进行处理。
// 主要用于处理背景颜色选择器和字体大小选择器在显示时,触摸事件在其范围外的情况
@Override @Override
public boolean dispatchTouchEvent(MotionEvent ev) { public boolean dispatchTouchEvent(MotionEvent ev) {
// 如果背景颜色选择器mNoteBgColorSelector当前处于可见状态并且触摸事件不在其显示范围内通过inRangeOfView方法判断
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mNoteBgColorSelector, ev)) { && !inRangeOfView(mNoteBgColorSelector, ev)) {
// 则将背景颜色选择器设置为不可见,隐藏该选择器视
mNoteBgColorSelector.setVisibility(View.GONE); mNoteBgColorSelector.setVisibility(View.GONE);
// 返回true表示已经处理了该触摸事件不会再继续传递给其他视图进行处理即拦截了此次触摸事件对背景颜色选择器的后续影响
return true; return true;
} }
if (mFontSizeSelector.getVisibility() == View.VISIBLE if (mFontSizeSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mFontSizeSelector, ev)) { && !inRangeOfView(mFontSizeSelector, ev)) {
// 将字体大小选择器设置为不可见,隐藏该选择器视图
mFontSizeSelector.setVisibility(View.GONE); mFontSizeSelector.setVisibility(View.GONE);
// 返回true表示已经处理了该触摸事件拦截了此次触摸事件对字体大小选择器的后续影响不再继续传递给其他视图
return true; return true;
} }
// 如果上述条件都不满足,即触摸事件不在需要特殊处理的选择器范围外,或者选择器本身就不可见等情况,
// 则调用父类的dispatchTouchEvent方法将触摸事件按照默认的分发逻辑继续传递给其他视图进行处理
return super.dispatchTouchEvent(ev); return super.dispatchTouchEvent(ev);
} }
// 此方法用于判断给定的触摸事件MotionEvent是否在指定的视图view范围内通过比较触摸点的坐标与视图在屏幕上的坐标及尺寸来确定
private boolean inRangeOfView(View view, MotionEvent ev) { private boolean inRangeOfView(View view, MotionEvent ev) {
int []location = new int[2]; int []location = new int[2];
// 获取指定视图在屏幕上的坐标位置将结果存入location数组中通过这种方式可以得到视图左上角顶点相对于屏幕左上角的坐标值
view.getLocationOnScreen(location); view.getLocationOnScreen(location);
// 获取视图在屏幕上的横坐标x坐标即location数组的第一个元素
int x = location[0]; int x = location[0];
// 获取视图在屏幕上的纵坐标y坐标即location数组的第二个元素
int y = location[1]; int y = location[1];
// 判断触摸事件的横坐标ev.getX()获取触摸点相对于触发触摸事件的视图的横坐标是否小于视图的横坐标x
// 如果小于则说明触摸点在视图的左侧不在视图范围内返回false
if (ev.getX() < x if (ev.getX() < x
|| ev.getX() > (x + view.getWidth()) || ev.getX() > (x + view.getWidth())
|| ev.getY() < y || ev.getY() < y
|| ev.getY() > (y + view.getHeight())) { || ev.getY() > (y + view.getHeight())) {
return false; return false;
} }
// 判断触摸事件的横坐标是否大于视图的横坐标加上视图的宽度x + view.getWidth()表示视图的右侧边界横坐标),
// 如果大于则说明触摸点在视图的右侧不在视图范围内返回false
return true; return true;
} }
// 判断触摸事件的纵坐标ev.getY()获取触摸点相对于触发触摸事件的视图的纵坐标是否小于视图的纵坐标y
// 如果小于则说明触摸点在视图的上方不在视图范围内返回false
private void initResources() { private void initResources() {
mHeadViewPanel = findViewById(R.id.note_title); mHeadViewPanel = findViewById(R.id.note_title);
// 创建一个HeadViewHolder实例用于存储笔记头部视图相关的各个子组件方便后续统一操作和管理这些组件
mNoteHeaderHolder = new HeadViewHolder(); mNoteHeaderHolder = new HeadViewHolder();
// 通过findViewById方法查找布局文件中ID为tv_modified_date的TextView组件该组件通常用于显示笔记的修改时间
// 并将其赋值给mNoteHeaderHolder中的tvModified变量方便后续操作该文本视图如设置显示的文本内容等
mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);
mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);
@ -397,15 +501,20 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
} }
// 当Activity失去焦点即将进入暂停状态时调用此方法例如用户切换到其他应用或者按下Home键等情况。
// 先调用父类的onPause方法执行默认的暂停操作然后进行自定义的保存笔记数据和清理设置状态相关操作
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
// 调用saveNote方法保存笔记数据如果保存成功saveNote方法返回true
if(saveNote()) { if(saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
} }
clearSettingState(); clearSettingState();
} }
// 用于更新桌面小部件的显示内容根据正在编辑的笔记对象mWorkingNote的小部件类型
// 构建相应的Intent并发送广播通知系统更新对应的小部件显示
private void updateWidget() { private void updateWidget() {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
@ -417,10 +526,13 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return; return;
} }
// 将正在编辑的笔记对象的小部件ID添加到Intent的额外数据中键为AppWidgetManager.EXTRA_APPWIDGET_IDS
// 这样接收广播的小部件更新逻辑可以知道要更新哪个具体的小部件实例
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
mWorkingNote.getWidgetId() mWorkingNote.getWidgetId()
}); });
// 处理界面上各个视图的点击事件根据点击的视图ID来执行相应的操作逻辑例如显示或隐藏相关视图、更新笔记的属性如背景颜色、字体大小等
sendBroadcast(intent); sendBroadcast(intent);
setResult(RESULT_OK, intent); setResult(RESULT_OK, intent);
} }
@ -452,12 +564,17 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
} }
// 当用户按下返回键时调用此方法,先尝试清理设置状态(如隐藏背景颜色选择器、字体大小选择器等),
// 如果清理成功则直接返回,不再执行默认的返回操作;如果清理失败则先保存笔记数据,再执行默认的返回操作
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if(clearSettingState()) { if(clearSettingState()) {
return; return;
} }
// 用于清理设置状态,检查背景颜色选择器和字体大小选择器是否处于可见状态,如果处于可见则将其设置为不可见,
// 根据是否有进行隐藏操作来返回相应的布尔值true表示有隐藏操作false表示没有需要隐藏的视图
// 如果清理设置状态失败则调用saveNote方法保存笔记数据
saveNote(); saveNote();
super.onBackPressed(); super.onBackPressed();
} }
@ -473,13 +590,19 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return false; return false;
} }
// 当笔记的背景颜色发生改变时调用此方法,用于更新界面上相关视图的显示,以反映背景颜色的变化,
// 例如显示对应的背景颜色选中状态提示、设置笔记编辑区域和头部视图的背景资源等
public void onBackgroundColorChanged() { public void onBackgroundColorChanged() {
// 根据正在编辑的笔记对象mWorkingNote当前的背景颜色ID找到对应的背景颜色选择按钮选中状态显示的视图并将其设置为可见状态
// 用于提示用户当前选择的是哪种背景颜色(显示选中效果)
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE); View.VISIBLE);
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
} }
// 在准备显示选项菜单Options Menu时调用此方法进行一些菜单相关的初始化操作例如清理菜单、根据笔记的属性如是否为通话记录笔记、是否有提醒等加载不同的菜单布局、
// 更新菜单项的标题显示等最后返回true表示菜单准备就绪可以显示
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
if (isFinishing()) { if (isFinishing()) {
@ -505,10 +628,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return true; return true;
} }
// 处理选项菜单中各个菜单项的点击事件根据点击的菜单项ID来执行相应的操作逻辑例如创建新笔记、删除笔记、设置提醒、分享笔记等
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_new_note: case R.id.menu_new_note:
// 如果点击的菜单项是“新建笔记”menu_new_note则调用createNewNote方法创建一个新的笔记
createNewNote(); createNewNote();
break; break;
case R.id.menu_delete: case R.id.menu_delete:
@ -523,6 +648,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
finish(); finish();
} }
}); });
// 设置对话框的“取消”按钮NegativeButton点击时不执行任何操作传入null
builder.setNegativeButton(android.R.string.cancel, null); builder.setNegativeButton(android.R.string.cancel, null);
builder.show(); builder.show();
break; break;
@ -531,10 +657,14 @@ public class NoteEditActivity extends Activity implements OnClickListener,
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
break; break;
case R.id.menu_list_mode: case R.id.menu_list_mode:
// 如果点击的菜单项是“列表模式”menu_list_mode则切换正在编辑的笔记对象的复选框列表模式状态
// 如果当前是普通模式getCheckListMode返回0则切换为复选框列表模式TextNote.MODE_CHECK_LIST否则切换回普通模式0
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0); TextNote.MODE_CHECK_LIST : 0);
break; break;
case R.id.menu_share: case R.id.menu_share:
// 如果点击的菜单项是“分享”menu_share则先调用getWorkingText方法获取笔记的文本内容
// 然后调用sendTo方法将笔记内容分享到支持Intent.ACTION_SEND动作且类型为text/plain的其他应用中
getWorkingText(); getWorkingText();
sendTo(this, mWorkingNote.getContent()); sendTo(this, mWorkingNote.getContent());
break; break;
@ -554,7 +684,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
private void setReminder() { private void setReminder() {
// 创建一个DateTimePickerDialog实例用于显示日期时间选择对话框传入当前Activity的上下文
DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());
// 为DateTimePickerDialog设置日期时间设置监听器当用户在对话框中选择好日期时间并点击确定后会触发该监听器的OnDateTimeSet方法。
d.setOnDateTimeSetListener(new OnDateTimeSetListener() { d.setOnDateTimeSetListener(new OnDateTimeSetListener() {
public void OnDateTimeSet(AlertDialog dialog, long date) { public void OnDateTimeSet(AlertDialog dialog, long date) {
mWorkingNote.setAlertDate(date , true); mWorkingNote.setAlertDate(date , true);
@ -563,6 +695,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
d.show(); d.show();
} }
// 此方法用于将笔记内容分享到支持指定Intent动作Intent.ACTION_SEND和文本类型text/plain的其他应用中比如分享到短信、邮件等应用。
/** /**
* Share note to apps that support {@link Intent#ACTION_SEND} action * Share note to apps that support {@link Intent#ACTION_SEND} action
* and {@text/plain} type * and {@text/plain} type
@ -574,6 +707,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
context.startActivity(intent); context.startActivity(intent);
} }
// 此方法用于创建一个新的笔记操作流程为先保存当前正在编辑的笔记如果有然后关闭当前的NoteEditActivity再启动一个新的NoteEditActivity进入新建笔记的流程。
private void createNewNote() { private void createNewNote() {
// Firstly, save current editing notes // Firstly, save current editing notes
saveNote(); saveNote();
@ -586,6 +720,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
startActivity(intent); startActivity(intent);
} }
// 此方法用于删除当前正在编辑的笔记,根据笔记是否存在于数据库以及当前应用是否处于同步模式等情况
private void deleteCurrentNote() { private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) { if (mWorkingNote.existInDatabase()) {
HashSet<Long> ids = new HashSet<Long>(); HashSet<Long> ids = new HashSet<Long>();
@ -608,10 +743,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mWorkingNote.markDeleted(true); mWorkingNote.markDeleted(true);
} }
// 此方法用于判断当前应用是否处于同步模式通过获取同步账户名称调用NotesPreferenceActivity的getSyncAccountName方法并检查其去除空格后的长度是否大于0来确定。
private boolean isSyncMode() { private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
} }
// 此方法用于处理笔记的时钟提醒相关设置的变更例如设置或取消提醒时间等操作在操作前会先确保笔记已保存如果未保存则先保存然后根据不同情况与系统的闹钟服务AlarmManager交互来设置或取消提醒。
public void onClockAlertChanged(long date, boolean set) { public void onClockAlertChanged(long date, boolean set) {
/** /**
* User could set clock to an unsaved note, so before setting the * User could set clock to an unsaved note, so before setting the
@ -642,10 +779,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
} }
// 此方法用于处理在笔记编辑的多行文本列表中删除某一行文本的操作,会调整后续行的索引,并将删除行的文本内容合并到前一行(如果是第一行则直接处理该行),同时更新焦点和光标位置等。
public void onWidgetChanged() { public void onWidgetChanged() {
updateWidget(); updateWidget();
} }
// 遍历从要删除行的下一行index + 1开始到最后一行的所有行调整它们的索引将其索引减1使其与新的行数顺序对应。
public void onEditTextDelete(int index, String text) { public void onEditTextDelete(int index, String text) {
int childCount = mEditTextList.getChildCount(); int childCount = mEditTextList.getChildCount();
if (childCount == 1) { if (childCount == 1) {
@ -657,6 +796,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
.setIndex(i - 1); .setIndex(i - 1);
} }
// 从多行文本列表中移除指定索引index的行视图。
mEditTextList.removeViewAt(index); mEditTextList.removeViewAt(index);
NoteEditText edit = null; NoteEditText edit = null;
if(index == 0) { if(index == 0) {
@ -672,6 +812,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
edit.setSelection(length); edit.setSelection(length);
} }
// 此方法用于处理在笔记编辑的多行文本列表中插入一行新文本的操作,会在指定索引位置添加新的行视图,更新相关行的索引,并设置新行的文本内容、焦点以及光标位置等。
public void onEditTextEnter(int index, String text) { public void onEditTextEnter(int index, String text) {
/** /**
* Should not happen, check for debug * Should not happen, check for debug
@ -691,7 +832,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
} }
private void switchToListMode(String text) { private void switchToListMode(String text) {
// 首先移除多行文本列表mEditTextList中已有的所有视图清空列表内容为重新构建列表做准备。
mEditTextList.removeAllViews(); mEditTextList.removeAllViews();
String[] items = text.split("\n"); String[] items = text.split("\n");
int index = 0; int index = 0;
@ -701,17 +844,24 @@ public class NoteEditActivity extends Activity implements OnClickListener,
index++; index++;
} }
} }
// 在多行文本列表末尾添加一个空的列表项视图,可能用于方便用户后续继续添加新的内容,提供一个空白的可编辑项。
mEditTextList.addView(getListItem("", index)); mEditTextList.addView(getListItem("", index));
// 获取多行文本列表中最后一个添加的列表项(即索引为 index 的项中的文本编辑框NoteEditText组件并请求获取焦点
// 使得用户切换到列表模式后,光标默认处于最后一个可编辑项处,方便用户直接输入内容。
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();
mNoteEditor.setVisibility(View.GONE); mNoteEditor.setVisibility(View.GONE);
mEditTextList.setVisibility(View.VISIBLE); mEditTextList.setVisibility(View.VISIBLE);
} }
// 此方法用于在给定的完整文本fullText根据用户输入的查询文本userQuery查找匹配的内容并将匹配的内容设置为高亮显示最后返回处理后的包含高亮显示效果的 Spannable 对象。
private Spannable getHighlightQueryResult(String fullText, String userQuery) { private Spannable getHighlightQueryResult(String fullText, String userQuery) {
// 创建一个 SpannableString 对象,将传入的完整文本(如果传入的 fullText 为 null则初始化为空字符串包装起来
// 以便后续可以对其内容进行样式设置如设置高亮显示等操作SpannableString 类实现了 Spannable 接口,支持设置文本样式。
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
if (!TextUtils.isEmpty(userQuery)) { if (!TextUtils.isEmpty(userQuery)) {
mPattern = Pattern.compile(userQuery); mPattern = Pattern.compile(userQuery);
// 创建一个 Matcher 对象,通过调用 Pattern 对象的 matcher 方法并传入完整文本fullText用于在文本中执行具体的匹配查找任务
// 可以通过其方法判断是否找到匹配内容以及获取匹配的位置等信息。
Matcher m = mPattern.matcher(fullText); Matcher m = mPattern.matcher(fullText);
int start = 0; int start = 0;
while (m.find(start)) { while (m.find(start)) {
@ -723,10 +873,14 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
} }
return spannable; return spannable;
// 返回处理后的包含高亮显示效果的 SpannableString 对象,该对象可以用于在界面上展示带有高亮查询结果的文本内容。
} }
private View getListItem(String item, int index) { private View getListItem(String item, int index) {
// 通过LayoutInflater从当前Activity的上下文this加载一个布局资源R.layout.note_edit_list_item创建一个视图对象
// 该布局资源定义了列表项的具体界面结构,包含文本编辑框、复选框等组件的布局样式。
View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);
// 从创建的视图中获取文本编辑框NoteEditText组件用于后续设置文本外观、内容以及添加事件监听器等操作。
final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));
@ -735,11 +889,14 @@ public class NoteEditActivity extends Activity implements OnClickListener,
if (isChecked) { if (isChecked) {
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else { } else {
// 如果复选框未被选中设置文本编辑框edit的绘制标志为默认的文本抗锯齿Paint.ANTI_ALIAS_FLAG和字距调整Paint.DEV_KERN_TEXT_FLAG标志
// 恢复文本的正常显示样式
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
} }
} }
}); });
// 判断列表项文本item是否以特定的标记TAG_CHECKED开头如果是表示该项初始状态为已选中状态。
if (item.startsWith(TAG_CHECKED)) { if (item.startsWith(TAG_CHECKED)) {
cb.setChecked(true); cb.setChecked(true);
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
@ -750,20 +907,31 @@ public class NoteEditActivity extends Activity implements OnClickListener,
item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); item = item.substring(TAG_UNCHECKED.length(), item.length()).trim();
} }
// 为文本编辑框edit设置文本内容变化的监听器OnTextViewChangeListener这里传入当前Activitythis可能在监听器中实现了相应的逻辑
// 用于在文本编辑框内容改变时进行一些处理,比如实时更新相关数据等操作。
edit.setOnTextViewChangeListener(this); edit.setOnTextViewChangeListener(this);
edit.setIndex(index); edit.setIndex(index);
// 设置文本编辑框edit的文本内容通过调用 getHighlightQueryResult 方法根据当前列表项文本item和用户查询文本mUserQuery获取带有高亮显示效果的文本内容
// 并设置到文本编辑框中展示给用户。
edit.setText(getHighlightQueryResult(item, mUserQuery)); edit.setText(getHighlightQueryResult(item, mUserQuery));
return view; return view;
} }
public void onTextChange(int index, boolean hasText) { public void onTextChange(int index, boolean hasText) {
// 判断传入的索引index是否大于等于多行文本列表mEditTextList中的子项数量即行数如果大于等于则说明索引超出范围
// 记录错误日志提示不应该出现这种情况,然后直接返回,不进行后续操作。
if (index >= mEditTextList.getChildCount()) { if (index >= mEditTextList.getChildCount()) {
Log.e(TAG, "Wrong index, should not happen"); Log.e(TAG, "Wrong index, should not happen");
return; return;
} }
// 如果文本编辑框中有文本内容hasText 为 true则获取多行文本列表中对应索引位置的视图并找到其中的复选框CheckBox组件将其设置为可见状态
// 以便用户可以看到并操作该复选框,通常用于有文本内容时可进行选中相关操作。
if(hasText) { if(hasText) {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE);
} else { } else {
// 如果文本编辑框中没有文本内容hasText 为 false则获取多行文本列表中对应索引位置的视图并找到其中的复选框CheckBox组件将其设置为不可见状态
// 隐藏复选框,因为没有文本内容时可能不需要显示复选框进行相关操作。
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);
} }
} }
@ -777,6 +945,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
"")); ""));
} }
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
// 设置笔记编辑框mNoteEditor的文本内容通过调用 getHighlightQueryResult 方法根据正在编辑的笔记对象mWorkingNote的内容和用户查询文本mUserQuery
// 获取带有高亮显示效果的文本内容,并设置到笔记编辑框中展示给用户,恢复普通文本编辑模式下的文本显示。
mEditTextList.setVisibility(View.GONE); mEditTextList.setVisibility(View.GONE);
mNoteEditor.setVisibility(View.VISIBLE); mNoteEditor.setVisibility(View.VISIBLE);
} }
@ -784,6 +954,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private boolean getWorkingText() { private boolean getWorkingText() {
boolean hasChecked = false; boolean hasChecked = false;
// 判断正在编辑的笔记对象mWorkingNote的复选框列表模式通过 getCheckListMode 方法获取)是否为 TextNote.MODE_CHECK_LIST即处于复选框列表模式
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int i = 0; i < mEditTextList.getChildCount(); i++) { for (int i = 0; i < mEditTextList.getChildCount(); i++) {
@ -792,8 +963,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
if (!TextUtils.isEmpty(edit.getText())) { if (!TextUtils.isEmpty(edit.getText())) {
if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) {
sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");
// 如果复选框处于选中状态将特定的选中标记TAG_CHECKED、空格以及文本编辑框中的文本内容拼接起来并添加换行符
// 按照特定格式构建列表项文本内容,添加到 StringBuilder 中,同时将 hasChecked 标记为 true表示存在已选中的列表项。
hasChecked = true; hasChecked = true;
} else { } else {
// 如果笔记对象不处于复选框列表模式则直接将笔记编辑框mNoteEditor中的文本内容通过调用其 getText 方法获取文本内容并转换为字符串),
// 设置为正在编辑的笔记对象mWorkingNote的工作文本内容通过调用其 setWorkingText 方法实现。
sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");
} }
} }
@ -818,6 +993,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
*/ */
setResult(RESULT_OK); setResult(RESULT_OK);
} }
// 返回保存操作是否成功的布尔值,以便调用此方法的地方根据返回值进行相应的后续处理,比如提示用户保存成功与否等情况。
return saved; return saved;
} }
@ -844,6 +1020,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
sender.putExtra("duplicate", true); sender.putExtra("duplicate", true);
sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
showToast(R.string.info_note_enter_desktop); showToast(R.string.info_note_enter_desktop);
// 此方法用于将正在编辑的笔记相关的快捷方式发送到桌面在操作前会先确保笔记已保存如果是新笔记则先调用saveNote方法保存
// 然后根据笔记的ID是否有效大于0表示有效构建相应的Intent并发送广播来创建桌面快捷方式若笔记ID无效则提示用户输入内容。
sendBroadcast(sender); sendBroadcast(sender);
} else { } else {
/** /**
@ -856,9 +1034,13 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
} }
// 此方法用于处理正在编辑的笔记内容文本去除其中特定的标记如选中、未选中标记并根据设定的最大长度限制SHORTCUT_ICON_TITLE_MAX_LEN对文本进行截断处理
// 最终返回处理后的适合作为桌面快捷方式图标的标题文本。
private String makeShortcutIconTitle(String content) { private String makeShortcutIconTitle(String content) {
content = content.replace(TAG_CHECKED, ""); content = content.replace(TAG_CHECKED, "");
content = content.replace(TAG_UNCHECKED, ""); content = content.replace(TAG_UNCHECKED, "");
// 判断处理后的内容文本长度是否大于设定的快捷方式图标标题最大长度SHORTCUT_ICON_TITLE_MAX_LEN如果大于则截取从开头到最大长度位置的子字符串作为最终结果
// 对文本进行截断处理,以满足长度限制要求;如果不大于,则直接返回原内容文本作为最终的快捷方式图标标题文本。
return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0,
SHORTCUT_ICON_TITLE_MAX_LEN) : content; SHORTCUT_ICON_TITLE_MAX_LEN) : content;
} }
@ -867,6 +1049,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
showToast(resId, Toast.LENGTH_SHORT); showToast(resId, Toast.LENGTH_SHORT);
} }
// 此重载方法用于根据传入的提示信息对应的资源IDresId以及显示时长参数duration创建并显示一个Toast提示信息
// 通过调用Toast的makeText方法传入当前Activity的上下文this、提示信息资源ID以及显示时长然后调用show方法将Toast显示出来
// 用于在应用中向用户展示简短的提示信息,如操作成功、失败等提示内容。
private void showToast(int resId, int duration) { private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show(); Toast.makeText(this, resId, duration).show();
} }

@ -37,22 +37,35 @@ import net.micode.notes.R;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
// NoteEditText类继承自EditText是一个自定义的文本编辑视图类在基础的文本编辑功能上添加了一些自定义的交互逻辑和事件处理机制。
public class NoteEditText extends EditText { public class NoteEditText extends EditText {
// 用于日志记录的标签,方便在调试时识别该类输出的日志信息
private static final String TAG = "NoteEditText"; private static final String TAG = "NoteEditText";
// 用于记录当前NoteEditText实例在一组编辑文本中的索引位置可能用于区分不同的编辑文本块等场景
private int mIndex; private int mIndex;
// 记录在按下删除键KEYCODE_DEL之前文本选择的起始位置用于后续判断删除操作相关的逻辑
private int mSelectionStartBeforeDelete; private int mSelectionStartBeforeDelete;
// 定义表示电话号码链接的协议头字符串,用于识别文本中是否包含电话号码链接
private static final String SCHEME_TEL = "tel:" ; private static final String SCHEME_TEL = "tel:" ;
// 定义表示超链接HTTP协议的协议头字符串用于识别文本中是否包含网页链接
private static final String SCHEME_HTTP = "http:" ; private static final String SCHEME_HTTP = "http:" ;
// 定义表示邮件链接的协议头字符串,用于识别文本中是否包含邮件链接
private static final String SCHEME_EMAIL = "mailto:" ; private static final String SCHEME_EMAIL = "mailto:" ;
// 创建一个HashMap用于存储不同协议头字符串与对应的资源ID的映射关系
// 资源ID可能对应着不同链接类型在界面上显示的提示字符串等信息方便后续根据链接类型获取相应的显示资源。
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>(); private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
// 静态代码块用于初始化sSchemaActionResMap将不同协议头字符串与对应的资源ID进行关联。
static { static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
} }
// 定义一个内部接口OnTextViewChangeListener用于与外部类进行交互通知外部关于文本编辑视图的一些重要事件变化
// 外部类需要实现该接口来响应这些事件。
/** /**
* Call by the {@link NoteEditActivity} to delete or add edit text * Call by the {@link NoteEditActivity} to delete or add edit text
*/ */
@ -60,60 +73,88 @@ public class NoteEditText extends EditText {
/** /**
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null * and the text is null
* KEYCODE_DEL
* indexNoteEditTexttext
*/ */
void onEditTextDelete(int index, String text); void onEditTextDelete(int index, String text);
/** /**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen * happen
* KEYCODE_ENTER
* indexNoteEditTexttext
*/ */
void onEditTextEnter(int index, String text); void onEditTextEnter(int index, String text);
/** /**
* Hide or show item option when text change * Hide or show item option when text change
*
* indexNoteEditTexthasTexttruefalse
*/ */
void onTextChange(int index, boolean hasText); void onTextChange(int index, boolean hasText);
} }
// 用于存储实现了OnTextViewChangeListener接口的实例通过设置该实例使得外部类能够监听该文本编辑视图的相关事件变化。
private OnTextViewChangeListener mOnTextViewChangeListener; private OnTextViewChangeListener mOnTextViewChangeListener;
// 构造方法接受一个Context参数用于创建NoteEditText实例调用父类EditText的构造方法进行初始化
// 同时将当前实例的索引mIndex初始化为0。
public NoteEditText(Context context) { public NoteEditText(Context context) {
super(context, null); super(context, null);
mIndex = 0; mIndex = 0;
} }
// 设置当前NoteEditText实例在一组编辑文本中的索引位置方便在事件处理等逻辑中区分不同的文本编辑区域。
public void setIndex(int index) { public void setIndex(int index) {
mIndex = index; mIndex = index;
} }
// 设置用于监听文本编辑视图相关事件变化的监听器实例外部类实现OnTextViewChangeListener接口后通过传入该实例
// 使得本类能够在相应事件发生时通知外部类进行处理。
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener; mOnTextViewChangeListener = listener;
} }
// 构造方法接受一个Context和一个AttributeSet参数用于创建NoteEditText实例
// 调用父类EditText带有特定样式属性android.R.attr.editTextStyle的构造方法进行初始化
// 该样式属性会应用默认的EditText样式到当前实例上。
public NoteEditText(Context context, AttributeSet attrs) { public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle); super(context, attrs, android.R.attr.editTextStyle);
} }
// 构造方法接受一个Context、AttributeSet和defStyle参数用于创建NoteEditText实例
// 调用父类EditText的构造方法进行初始化这里的TODO注释表示后续可能需要完善该构造方法的具体逻辑目前仅完成了基本的调用父类构造函数操作。
public NoteEditText(Context context, AttributeSet attrs, int defStyle) { public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
// TODO Auto-generated constructor stub // TODO Auto-generated constructor stub
} }
// 重写onTouchEvent方法用于处理触摸事件。主要功能是在用户按下ACTION_DOWN屏幕时根据触摸点的位置来设置文本的选择位置。
// 通过一系列坐标计算,获取触摸点所在的文本行以及在该行中的水平偏移位置,进而将文本选择位置设置到对应的位置上,方便用户进行后续操作,
// 最后调用父类的onTouchEvent方法继续处理其他触摸相关的逻辑。
@Override @Override
public boolean onTouchEvent(MotionEvent event) { public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) { switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
// 获取触摸点相对于视图左上角的X坐标
int x = (int) event.getX(); int x = (int) event.getX();
// 获取触摸点相对于视图左上角的Y坐标
int y = (int) event.getY(); int y = (int) event.getY();
// 减去视图左边的内边距,将坐标转换为相对于文本内容区域的坐标
x -= getTotalPaddingLeft(); x -= getTotalPaddingLeft();
// 减去视图上边的内边距,将坐标转换为相对于文本内容区域的坐标
y -= getTotalPaddingTop(); y -= getTotalPaddingTop();
// 加上视图的水平滚动偏移量,考虑到文本内容可能存在滚动情况
x += getScrollX(); x += getScrollX();
// 加上视图的垂直滚动偏移量,考虑到文本内容可能存在滚动情况
y += getScrollY(); y += getScrollY();
// 获取当前文本的布局信息对象,用于后续根据坐标计算文本位置相关信息
Layout layout = getLayout(); Layout layout = getLayout();
// 根据触摸点的垂直坐标获取所在的文本行索引
int line = layout.getLineForVertical(y); int line = layout.getLineForVertical(y);
// 根据触摸点所在的文本行以及水平坐标获取对应的文本偏移位置(字符索引位置)
int off = layout.getOffsetForHorizontal(line, x); int off = layout.getOffsetForHorizontal(line, x);
// 将文本选择位置设置为计算得到的偏移位置,使得用户触摸到的位置对应的文本被选中
Selection.setSelection(getText(), off); Selection.setSelection(getText(), off);
break; break;
} }
@ -121,6 +162,9 @@ public class NoteEditText extends EditText {
return super.onTouchEvent(event); return super.onTouchEvent(event);
} }
// 重写onKeyDown方法用于处理按键按下事件。主要针对回车键KEYCODE_ENTER和删除键KEYCODE_DEL进行特殊处理
// 当按下回车键时如果设置了OnTextViewChangeListener监听器则直接返回false可能后续由外部决定是否处理回车键按下的逻辑
// 当按下删除键时,记录下当前文本选择的起始位置,以便后续在删除键抬起时判断相关删除逻辑,其他按键则按照父类默认逻辑处理。
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) { switch (keyCode) {
@ -138,6 +182,12 @@ public class NoteEditText extends EditText {
return super.onKeyDown(keyCode, event); return super.onKeyDown(keyCode, event);
} }
// 重写onKeyUp方法用于处理按键抬起事件。针对删除键KEYCODE_DEL和回车键KEYCODE_ENTER进行特定逻辑处理
// 当删除键抬起时如果设置了OnTextViewChangeListener监听器并且当前文本选择起始位置为0且当前实例索引不为0可能表示删除当前文本块的情况
// 则调用监听器的onEditTextDelete方法通知外部进行删除操作并返回true表示已处理该事件
// 当回车键抬起时如果设置了OnTextViewChangeListener监听器则获取当前文本选择位置后的文本内容将当前文本内容截取到选择位置之前
// 然后调用监听器的onEditTextEnter方法通知外部添加新的编辑文本并传入相关参数最后返回父类的onKeyUp方法处理结果继续处理其他按键抬起相关逻辑。
@Override
@Override @Override
public boolean onKeyUp(int keyCode, KeyEvent event) { public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) { switch(keyCode) {
@ -167,6 +217,9 @@ public class NoteEditText extends EditText {
return super.onKeyUp(keyCode, event); return super.onKeyUp(keyCode, event);
} }
// 重写onFocusChanged方法用于处理焦点变化事件。当焦点发生变化时如果设置了OnTextViewChangeListener监听器
// 根据当前是否失去焦点以及文本内容是否为空调用监听器的onTextChange方法通知外部进行相应的显示选项等逻辑处理
// 最后调用父类的onFocusChanged方法继续处理其他焦点变化相关逻辑。
@Override @Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) { if (mOnTextViewChangeListener != null) {
@ -179,6 +232,12 @@ public class NoteEditText extends EditText {
super.onFocusChanged(focused, direction, previouslyFocusedRect); super.onFocusChanged(focused, direction, previouslyFocusedRect);
} }
// 重写onCreateContextMenu方法用于创建上下文菜单通常长按文本等操作时弹出
// 如果当前编辑文本内容是Spanned类型表示包含富文本信息比如链接等则获取当前文本选择区域内的URLSpan对象数组可能包含多个链接
// 如果只有一个链接则根据链接的URL判断其协议头如是否是tel、http、mailto等从sSchemaActionResMap中获取对应的资源ID
// 如果没有匹配的协议头则使用默认资源IDR.string.note_link_other然后创建一个菜单项添加到上下文菜单中
// 并设置菜单项的点击监听器当点击该菜单项时调用对应的URLSpan的onClick方法通常会触发相应的链接跳转等操作
// 最后调用父类的onCreateContextMenu方法继续添加其他默认的上下文菜单选项等操作。
@Override @Override
protected void onCreateContextMenu(ContextMenu menu) { protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) { if (getText() instanceof Spanned) {

@ -25,8 +25,9 @@ import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.DataUtils;
// NoteItemData类用于封装笔记相关的数据信息从数据库查询结果Cursor中提取并整理数据同时提供了一些方法用于获取和判断这些数据的相关属性。
public class NoteItemData { public class NoteItemData {
// 定义一个字符串数组用于指定从数据库查询笔记相关信息时需要获取的列名涵盖了笔记的各种属性如ID、提醒日期、背景颜色ID、创建日期等等。
static final String [] PROJECTION = new String [] { static final String [] PROJECTION = new String [] {
NoteColumns.ID, NoteColumns.ID,
NoteColumns.ALERTED_DATE, NoteColumns.ALERTED_DATE,
@ -42,6 +43,7 @@ public class NoteItemData {
NoteColumns.WIDGET_TYPE, NoteColumns.WIDGET_TYPE,
}; };
// 定义一系列常量用于表示各个数据列在查询结果的Cursor中的索引位置方便后续从Cursor中准确获取对应的数据提高代码的可读性和可维护性。
private static final int ID_COLUMN = 0; private static final int ID_COLUMN = 0;
private static final int ALERTED_DATE_COLUMN = 1; private static final int ALERTED_DATE_COLUMN = 1;
private static final int BG_COLOR_ID_COLUMN = 2; private static final int BG_COLOR_ID_COLUMN = 2;
@ -55,14 +57,249 @@ public class NoteItemData {
private static final int WIDGET_ID_COLUMN = 10; private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11; private static final int WIDGET_TYPE_COLUMN = 11;
// 笔记的唯一标识符存储从数据库获取的笔记ID值。
private long mId; private long mId;
// 笔记的提醒日期存储对应的时间戳信息若没有提醒则可能为0等特定值。
private long mAlertDate; private long mAlertDate;
// 笔记的背景颜色ID用于确定笔记在界面上显示的背景颜色相关设置。
private int mBgColorId; private int mBgColorId;
// 笔记的创建日期,存储对应的时间戳信息,用于记录笔记创建的时间。
private long mCreatedDate; private long mCreatedDate;
// 标记笔记是否有附件通过从数据库获取的值转换为布尔类型大于0表示有附件设为true否则为false
private boolean mHasAttachment; private boolean mHasAttachment;
// 笔记的最后修改日期,存储对应的时间戳信息,用于记录笔记最后一次被修改的时间。
private long mModifiedDate; private long mModifiedDate;
// 与笔记相关的数量信息(具体含义可能根据业务逻辑确定,比如关联的子笔记数量等),从数据库获取对应整数值。
private int mNotesCount; private int mNotesCount;
// 笔记的父级ID用于表示笔记所属的文件夹等上级容器的ID通过数据库查询获取对应长整数值。
private long mParentId; private long mParentId;
// 笔记的摘要信息(可能是简短描述等内容),从数据库获取字符串内容,并进行一些特定字符串替换操作(去除一些标记字符串)。
private String mSnippet;
// 笔记的类型,通过从数据库获取的整数值来表示不同类型的笔记(具体类型值的含义由业务逻辑定义,如普通笔记、系统笔记等)。
private int mType;
// 笔记关联的小部件ID如果有相关小部件的话从数据库获取对应整数值。
private int mWidgetId;
// 笔记关联的小部件类型(同样根据业务逻辑确定不同类型小部件的表示值),从数据库获取对应整数值。
private int mWidgetType;
// 联系人姓名相关信息,初始为空字符串,后续根据笔记所属文件夹等情况可能从联系人数据中获取并赋值。
private String mName;
// 电话号码相关信息,初始为空字符串,若笔记属于通话记录文件夹等相关情况,会尝试获取对应的电话号码。
private String mPhoneNumber;
// 标记当前笔记数据对应的记录是否是查询结果中的最后一项通过在构造方法中根据Cursor判断并赋值。
private boolean mIsLastItem;
// 标记当前笔记数据对应的记录是否是查询结果中的第一项通过在构造方法中根据Cursor判断并赋值。
private boolean mIsFirstItem;
// 标记查询结果中是否只有一条记录即当前笔记数据是否是唯一的一条记录通过在构造方法中根据Cursor记录数量判断并赋值。
private boolean mIsOnlyOneItem;
// 标记当前笔记是否是某个文件夹后面仅跟随的一条笔记(根据笔记类型以及前后记录的情况判断),通过在构造方法中特定逻辑判断并赋值。
private boolean mIsOneNoteFollowingFolder;
// 标记当前笔记是否是某个文件夹后面跟随的多条笔记中的一条(根据笔记类型以及前后记录的情况判断),通过在构造方法中特定逻辑判断并赋值。
private boolean mIsMultiNotesFollowingFolder;
// 构造方法接受上下文环境Context和一个Cursor对象作为参数用于从Cursor中提取并初始化笔记相关的数据信息同时进行一些额外的逻辑判断和数据处理。
public NoteItemData(Context context, Cursor cursor) {
// 从Cursor中获取笔记的ID值并赋值给成员变量mId。
mId = cursor.getLong(ID_COLUMN);
// 从Cursor中获取笔记的提醒日期时间戳并赋值给成员变量mAlertDate。
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
// 从Cursor中获取笔记的背景颜色ID并赋值给成员变量mBgColorId。
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
// 从Cursor中获取笔记的创建日期时间戳并赋值给成员变量mCreatedDate。
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
// 根据从Cursor中获取的表示是否有附件的整数值转换为布尔类型赋值给mHasAttachment大于0表示有附件设为true否则为false。
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0)? true : false;
// 从Cursor中获取笔记的最后修改日期时间戳并赋值给成员变量mModifiedDate。
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
// 从Cursor中获取笔记相关的数量信息如子笔记数量等并赋值给成员变量mNotesCount。
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
// 从Cursor中获取笔记的父级ID并赋值给成员变量mParentId。
mParentId = cursor.getLong(PARENT_ID_COLUMN);
// 从Cursor中获取笔记的摘要信息字符串赋值给成员变量mSnippet并进行特定字符串替换操作去除可能存在的一些标记字符串如NoteEditActivity.TAG_CHECKED和NoteEditActivity.TAG_UNCHECKED
mSnippet = cursor.getString(SNIPPET_COLUMN);
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
// 从Cursor中获取笔记的类型整数值并赋值给成员变量mType。
mType = cursor.getInt(TYPE_COLUMN);
// 从Cursor中获取笔记关联的小部件ID并赋值给成员变量mWidgetId。
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
// 从Cursor中获取笔记关联的小部件类型整数值并赋值给成员变量mWidgetType。
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
// 初始化电话号码为空字符串,后续根据笔记所属文件夹情况判断是否获取实际电话号码。
mPhoneNumber = "";
// 如果笔记的父级ID等于特定的通话记录文件夹IDNotes.ID_CALL_RECORD_FOLDER则尝试从数据中获取对应的电话号码。
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
// 如果获取到的电话号码不为空字符串,尝试通过电话号码获取对应的联系人姓名。
if (!TextUtils.isEmpty(mPhoneNumber)) {
mName = Contact.getContact(context, mPhoneNumber);
// 如果获取联系人姓名失败返回null则将电话号码作为联系人姓名显示可能是一种兜底处理方式
if (mName == null) {
mName = mPhoneNumber;
}
}
}
// 如果联系人姓名仍为null可能前面获取过程都失败了则将其设置为空字符串确保该成员变量有合理的默认值。
if (mName == null) {
mName = "";
}
// 调用checkPostion方法根据Cursor的相关信息判断并设置当前笔记数据在查询结果中的位置相关属性如是否是第一项、最后一项等以及与文件夹关联的笔记数量情况等属性。
checkPostion(cursor);
}
// 私有方法用于根据Cursor的相关信息判断并设置当前笔记数据在查询结果中的位置相关属性如是否是第一项、最后一项等以及与文件夹关联的笔记数量情况等属性。
private void checkPostion(Cursor cursor) {
// 根据Cursor的isLast方法判断当前记录是否是最后一项将结果赋值给mIsLastItem成员变量。
mIsLastItem = cursor.isLast()? true : false;
// 根据Cursor的isFirst方法判断当前记录是否是第一项将结果赋值给mIsFirstItem成员变量。
mIsFirstItem = cursor.isFirst()? true : false;
// 根据Cursor的记录数量判断是否只有一条记录将结果赋值给mIsOnlyOneItem成员变量。
mIsOnlyOneItem = (cursor.getCount() == 1);
// 初始设置为当前笔记不是某个文件夹后面跟随的多条笔记中的一条,后续根据具体逻辑判断是否更改该值。
mIsMultiNotesFollowingFolder = false;
// 初始设置为当前笔记不是某个文件夹后面仅跟随的一条笔记,后续根据具体逻辑判断是否更改该值。
mIsOneNoteFollowingFolder = false;
// 如果笔记类型是普通笔记Notes.TYPE_NOTE并且不是第一项记录说明前面可能有其他记录则进行以下判断逻辑。
if (mType == Notes.TYPE_NOTE &&!mIsFirstItem) {
// 获取当前记录在Cursor中的位置索引。
int position = cursor.getPosition();
// 将Cursor指针移动到前一条记录用于查看前一条记录的类型等信息
if (cursor.moveToPrevious()) {
// 判断前一条记录的类型是否是文件夹类型Notes.TYPE_FOLDER或者系统类型Notes.TYPE_SYSTEM如果是则说明当前笔记可能与文件夹存在关联情况继续后续判断。
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
// 判断Cursor的总记录数是否大于当前位置索引加1即当前笔记后面是否还有其他记录如果是则说明当前笔记是某个文件夹后面跟随的多条笔记中的一条设置相应标记为true。
if (cursor.getCount() > (position + 1)) {
mIsMultiNotesFollowingFolder = true;
} else {
// 否则说明当前笔记是某个文件夹后面仅跟随的一条笔记设置相应标记为true。
mIsOneNoteFollowingFolder = true;
}
}
// 将Cursor指针再移回原来的位置确保后续操作不受影响因为前面移动了指针如果移动失败则抛出异常表示出现了不合理的游标操作情况
if (!cursor.moveToNext()) {
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
}
}
// 判断当前笔记是否是某个文件夹后面仅跟随的一条笔记,返回对应的布尔值。
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
// 判断当前笔记是否是某个文件夹后面跟随的多条笔记中的一条,返回对应的布尔值。
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
// 判断当前笔记数据对应的记录是否是查询结果中的最后一项,返回对应的布尔值。
public boolean isLast() {
return mIsLastItem;
}
// 获取联系人姓名(可能是与笔记关联的通话记录对应的联系人姓名等情况),返回对应的字符串。
public String getCallName() {
return mName;
}
// 判断当前笔记数据对应的记录是否是查询结果中的第一项,返回对应的布尔值。
public boolean isFirst() {
return mIsFirstItem;
}
// 判断查询结果中是否只有当前这一条笔记数据记录,返回对应的布尔值。
public boolean isSingle() {
return mIsOnlyOneItem;
}
// 获取笔记的唯一标识符ID返回对应的长整数值。
public long getId() {
return mId;
}
// 获取笔记的提醒日期时间戳,返回对应的长整数值。
public long getAlertDate() {
return mAlertDate;
}
// 获取笔记的创建日期时间戳,返回对应的长整数值。
public long getCreatedDate() {
return mCreatedDate;
}
// 判断笔记是否有附件,返回对应的布尔值。
public boolean hasAttachment() {
return mHasAttachment;
}
// 获取笔记的最后修改日期时间戳,返回对应的长整数值。
public long getModifiedDate() {
return mModifiedDate;
}
// 获取笔记的背景颜色ID返回对应的整数值。
public int getBgColorId() {
return mBgColorId;
}
// 获取笔记的父级ID返回对应的长整数值。
public long getParentId() {
return mParentId;
}
// 获取笔记相关的数量信息(如子笔记数量等),返回对应的整数值。
public int getNotesCount() {
return mNotesCount;
}
// 获取笔记所属文件夹的ID与getParentId方法返回值相同可能为了语义更清晰在不同场景使用返回对应的长整数值。
public long getFolderId () {
return mParentId;
}
// 获取笔记的类型(如普通笔记、文件夹类型等,根据业务逻辑定义的类型值),返回对应的整数值。
public int getType() {
return mType;
}
// 获取笔记关联的小部件类型(根据业务逻辑定义的不同小部件类型值),返回对应的整数值。
public int getWidgetType() {
return mWidgetType;
}
// 获取笔记关联的小部件ID返回对应的整数值。
public int getWidgetId() {
return mWidgetId;
}
// 获取笔记的摘要信息字符串,返回对应的字符串内容。
public String getSnippet() {
return mSnippet;
}
// 判断笔记是否设置了提醒通过提醒日期是否大于0来判断返回对应的布尔值。
public boolean hasAlert() {
return (mAlertDate > 0);
}
// 判断笔记是否属于通话记录通过父级ID是否是通话记录文件夹ID以及电话号码是否为空来判断返回对应的布尔值。
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER &&!TextUtils.isEmpty(mPhoneNumber));
}
// 静态方法用于从给定的Cursor中获取笔记的类型通过获取对应类型列的整数值返回对应的整数值方便在其他地方直接获取笔记类型信息而无需创建NoteItemData实例。
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}
NoteItemData NoteColumnsNotesDataUtilsContactNoteEditActivity
private String mSnippet; private String mSnippet;
private int mType; private int mType;
private int mWidgetId; private int mWidgetId;

@ -147,6 +147,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
setAppInfoFromRawRes(); setAppInfoFromRawRes();
} }
// 当启动的其他Activity返回结果时调用此方法用于处理返回的结果数据根据请求码requestCode和结果码resultCode来执行相应的逻辑。
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK if (resultCode == RESULT_OK
@ -157,6 +158,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
} }
// 此方法用于从原始资源文件可能是文本文件等中读取应用的介绍信息并将其保存为一条笔记记录同时将一个表示是否已添加介绍信息的共享偏好设置项设置为true
// 用于标记已经完成了首次使用时介绍信息的插入操作,下次启动应用时就不会重复执行此操作。
private void setAppInfoFromRawRes() { private void setAppInfoFromRawRes() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {
@ -190,6 +193,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
} }
// 创建一个空的WorkingNote对象通过调用WorkingNote的createEmptyNote静态方法传入当前Activity的上下文this、根文件夹IDNotes.ID_ROOT_FOLDER
// 无效的桌面小部件IDAppWidgetManager.INVALID_APPWIDGET_ID、无效的小部件类型Notes.TYPE_WIDGET_INVALIDE以及默认的颜色资源ResourceParser.RED等参数
// 用于构建一个初始的笔记对象,准备保存介绍信息到该笔记中。
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER,
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
ResourceParser.RED); ResourceParser.RED);
@ -210,7 +216,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
private void initResources() { private void initResources() {
// 获取系统的内容解析器ContentResolver通过调用当前Activity的 `getContentResolver` 方法,
// 内容解析器用于与应用的内容提供器Content Provider交互可对数据库等数据源进行查询、插入、更新、删除等操作
// 在这里可能用于后续查询笔记数据等相关操作。
mContentResolver = this.getContentResolver(); mContentResolver = this.getContentResolver();
// 创建一个 `BackgroundQueryHandler` 实例,传入刚才获取的内容解析器,这个类可能是自定义的用于在后台线程处理查询操作的处理器,
// 以便在执行一些耗时的数据查询等操作时,不会阻塞主线程,保证界面的流畅性。
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
mCurrentFolderId = Notes.ID_ROOT_FOLDER; mCurrentFolderId = Notes.ID_ROOT_FOLDER;
mNotesListView = (ListView) findViewById(R.id.notes_list); mNotesListView = (ListView) findViewById(R.id.notes_list);
@ -232,8 +243,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
// 定义一个 `DropdownMenu` 类型的成员变量,用于展示下拉菜单,可能包含一些与多选操作相关的额外功能选项,
// 具体功能和样式由 `DropdownMenu` 类的实现决定,比如全选、反选等操作可能会放在这个下拉菜单中。
private DropdownMenu mDropDownMenu; private DropdownMenu mDropDownMenu;
private ActionMode mActionMode; private ActionMode mActionMode;
// 当进入多选操作模式时(例如用户长按 `ListView` 中的某个项后进入多选模式),会调用此方法来创建操作菜单等初始化操作。
private MenuItem mMoveMenu; private MenuItem mMoveMenu;
public boolean onCreateActionMode(ActionMode mode, Menu menu) { public boolean onCreateActionMode(ActionMode mode, Menu menu) {
@ -252,6 +266,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mNotesListView.setLongClickable(false); mNotesListView.setLongClickable(false);
mAddNewNote.setVisibility(View.GONE); mAddNewNote.setVisibility(View.GONE);
// 通过 `LayoutInflater` 从当前Activity的上下文NotesListActivity.this这里使用外部类名来明确上下文加载一个布局资源R.layout.note_list_dropdown_menu创建一个视图
// 这个视图将作为操作模式的自定义视图,用于展示一些额外的操作界面元素,比如上述提到的下拉菜单等内容。
View customView = LayoutInflater.from(NotesListActivity.this).inflate( View customView = LayoutInflater.from(NotesListActivity.this).inflate(
R.layout.note_list_dropdown_menu, null); R.layout.note_list_dropdown_menu, null);
mode.setCustomView(customView); mode.setCustomView(customView);
@ -262,6 +278,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
public boolean onMenuItemClick(MenuItem item) { public boolean onMenuItemClick(MenuItem item) {
mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected());
updateMenu(); updateMenu();
// 返回 `true` 表示操作模式创建成功,系统可以继续进行后续的多选模式相关操作,比如显示操作菜单等。
return true; return true;
} }
@ -286,6 +303,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
} }
// 此方法是 `ListView.MultiChoiceModeListener` 接口中的方法,当用户在多选操作模式下点击了操作菜单中的某个菜单项时会被调用,
// 开发者需要在此方法中编写逻辑来处理不同菜单项点击对应的具体业务操作,比如点击“删除”菜单项执行删除选中笔记的操作等。
// 目前方法体中是自动生成的 `TODO` 注释,意味着还需要根据实际需求添加相应的业务逻辑代码来处理菜单项点击事件,
// 返回值为 `false`,同样按照接口的默认约定,如果返回 `false` 可能表示未处理该点击事件(具体也要看使用场景和框架的要求)。
public boolean onPrepareActionMode(ActionMode mode, Menu menu) { public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub // TODO Auto-generated method stub
return false; return false;
@ -296,12 +317,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return false; return false;
} }
// 此方法是 `ListView.MultiChoiceModeListener` 接口中的方法,当多选操作模式被销毁(例如用户按下返回键或者完成多选操作等情况退出多选模式)时会被调用,
// 用于在操作模式结束后进行一些清理和恢复界面初始状态的操作。
public void onDestroyActionMode(ActionMode mode) { public void onDestroyActionMode(ActionMode mode) {
mNotesListAdapter.setChoiceMode(false); mNotesListAdapter.setChoiceMode(false);
mNotesListView.setLongClickable(true); mNotesListView.setLongClickable(true);
mAddNewNote.setVisibility(View.VISIBLE); mAddNewNote.setVisibility(View.VISIBLE);
} }
// 此方法用于手动结束当前的操作模式,通过调用 `mActionMode`(在 `onCreateActionMode` 方法中初始化的 `ActionMode` 对象)的 `finish` 方法来实现,
// 触发操作模式的销毁流程,会进而调用 `onDestroyActionMode` 等相关方法进行后续的清理和界面恢复操作。
public void finishActionMode() { public void finishActionMode() {
mActionMode.finish(); mActionMode.finish();
} }
@ -313,10 +338,17 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
public boolean onMenuItemClick(MenuItem item) { public boolean onMenuItemClick(MenuItem item) {
// 调用 `NotesListAdapter` 的 `setCheckedItem` 方法,传入发生选中状态改变的列表项的位置(`position`)以及新的选中状态(`checked`)参数,
// 适配器内部会根据这些信息更新其内部记录的选中项相关的数据结构,以便准确统计选中的数量、判断是否全选等情况。
// 此方法实现了 `OnMenuItemClickListener` 接口,用于处理菜单项点击事件,当用户点击了设置了此点击监听器的菜单项时会被调用,
// 在这里主要用于判断当前是否有选中的笔记项,如果没有选中任何笔记项,显示一个提示 `Toast`,告知用户需要先选择笔记项才能进行操作,并返回 `true` 表示已处理该点击事件,
// 避免事件继续向上传递导致其他不必要的处理逻辑执行。
if (mNotesListAdapter.getSelectedCount() == 0) { if (mNotesListAdapter.getSelectedCount() == 0) {
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
return true; return true;
// 如果有选中的笔记项,这里后续应该添加具体的业务逻辑代码来处理不同菜单项点击对应的操作,目前代码没有完整实现这部分逻辑,需要开发者进一步补充完善。
// 返回值目前未明确设定,按正常逻辑应该根据具体业务处理情况返回 `true`(表示已处理)或 `false`(表示未处理)相应的点击事件。
} }
switch (item.getItemId()) { switch (item.getItemId()) {
@ -342,15 +374,22 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
default: default:
return false; return false;
} }
return true;
// 如果成功处理了对应的菜单项点击事件(即点击的是“删除”或“移动”菜单项并执行了相应的逻辑),则返回 `true`
// 表示已经处理了该点击操作,通常可以告知调用者或者框架不需要再对该点击事件进行其他额外处理了。
return true; return true;
} }
} }
private class NewNoteOnTouchListener implements OnTouchListener { private class NewNoteOnTouchListener implements OnTouchListener {
// 重写 `OnTouchListener` 接口中的 `onTouch` 方法,当触摸事件发生在绑定了此监听器的视图(这里是 `mAddNewNote` 按钮对应的视图 `v`)上时,该方法会被调用,
// 并传入触摸事件相关的信息(视图 `v` 和 `MotionEvent` 对象 `event`),根据触摸事件的不同类型(如按下、移动、抬起等)来执行相应的逻辑处理,返回值表示是否消费了该触摸事件(`true` 表示消费,`false` 表示未消费,事件可能会继续传递)。
public boolean onTouch(View v, MotionEvent event) { public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) { switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: { case MotionEvent.ACTION_DOWN: {
// 获取当前设备屏幕的默认显示对象,通过调用 `getWindowManager().getDefaultDisplay()` 方法,
// 用于后续获取屏幕相关的尺寸信息等操作,例如屏幕的高度、宽度等,不同设备的屏幕显示属性可能不同,这里获取默认的显示配置。
Display display = getWindowManager().getDefaultDisplay(); Display display = getWindowManager().getDefaultDisplay();
int screenHeight = display.getHeight(); int screenHeight = display.getHeight();
int newNoteViewHeight = mAddNewNote.getHeight(); int newNoteViewHeight = mAddNewNote.getHeight();
@ -404,6 +443,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
} }
return false; return false;
// 如果触摸事件没有在上述任何一个 `switch` 分支中被消费(即没有返回 `true`),则返回 `false`,表示当前监听器没有处理该触摸事件,
// 触摸事件可能会继续向上传递给其他的触摸监听器或者父视图等进行处理,具体行为取决于整个视图层级的触摸事件传递机制。
} }
}; };
@ -416,7 +457,20 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
String.valueOf(mCurrentFolderId) String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
} }
// `FOLDER_NOTE_LIST_QUERY_TOKEN`是一个自定义的查询令牌Token用于标识此次查询的类型方便在查询完成后根据这个令牌来区分不同的查询结果并进行相应处理
// 例如在 `onQueryComplete` 方法中会通过这个令牌判断是哪个查询任务完成了。
// `null`是一个可以传递给查询处理器的额外对象通常称为Cookie在这里没有使用所以传入 `null`,如果有需要可以传入一个自定义的对象,
// 在查询完成后的回调中可以获取到这个对象进行一些额外的相关操作(具体取决于业务逻辑需求)。
// `Notes.CONTENT_NOTE_URI`应该是一个定义好的表示笔记内容的内容提供者Content Provider的统一资源标识符URI
// 指明了要从哪里查询笔记数据,也就是告诉系统要去哪个数据源(通常是数据库对应的内容提供者)查询相应的笔记信息。
// `NoteItemData.PROJECTION`:是一个字符串数组,定义了查询结果中要返回的列名,也就是指定了从数据库中查询笔记数据时,具体要获取哪些字段的信息,
// 例如可能包含笔记的标题、内容、创建时间等字段,只获取需要的字段可以提高查询效率,减少不必要的数据传输和处理。
// `selection`:就是前面根据当前文件夹情况确定的查询筛选条件字符串,用于在数据库中筛选出符合条件的笔记记录,比如筛选出某个文件夹下的笔记等。
// `new String[] { String.valueOf(mCurrentFolderId) }`:是一个字符串数组,用于给查询筛选条件中的占位符(通常在 `selection` 字符串中用 `?` 表示)赋值,
// 在这里将当前文件夹的ID转换为字符串后放入数组中传递给查询操作使得筛选条件能够准确地基于当前文件夹ID进行筛选例如筛选出父ID等于当前文件夹ID的笔记。
// `NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"`:是一个排序规则字符串,指定了查询结果按照笔记的类型(`NoteColumns.TYPE`)降序排列,
// 并且在类型相同的情况下,按照修改日期(`NoteColumns.MODIFIED_DATE`)降序排列,这样查询出来的笔记列表数据在展示时会按照特定的顺序呈现给用户,
// 比如先展示某种特定类型的最新修改的笔记等情况。
private final class BackgroundQueryHandler extends AsyncQueryHandler { private final class BackgroundQueryHandler extends AsyncQueryHandler {
public BackgroundQueryHandler(ContentResolver contentResolver) { public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver); super(contentResolver);
@ -460,6 +514,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
}); });
builder.show(); builder.show();
// 最后调用 `builder` 对象的 `show` 方法,将构建好的包含文件夹列表的对话框显示在界面上,让用户可以进行文件夹选择操作,
// 根据用户的选择执行相应的业务逻辑,实现将笔记移动到所选文件夹等功能。
} }
private void createNewNote() { private void createNewNote() {
@ -555,6 +611,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mTitleBar.setText(data.getSnippet()); mTitleBar.setText(data.getSnippet());
} }
mTitleBar.setVisibility(View.VISIBLE); mTitleBar.setVisibility(View.VISIBLE);
// 创建一个意图Intent对象用于启动一个新的Activity指定要启动的Activity类为 `NoteEditActivity.class`
// 意味着点击相应按钮或者触发相关操作后,系统将启动 `NoteEditActivity` 这个Activity让用户可以在其中查看和编辑指定的笔记。
} }
public void onClick(View v) { public void onClick(View v) {
@ -563,9 +621,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
createNewNote(); createNewNote();
break; break;
default: default:
// 向意图中添加额外的数据信息,通过 `putExtra` 方法,将笔记的唯一标识符(`data.getId()`,这里的 `data` 应该是包含笔记相关信息的对象,通过其 `getId` 方法获取笔记ID添加进去键为 `Intent.EXTRA_UID`
// 这样在 `NoteEditActivity` 中可以获取到这个参数根据笔记ID从数据库等数据源查询并加载相应的笔记数据进行展示和编辑操作。
break; break;
} }
} }
// 使用当前Activity`this`启动刚才创建的意图对应的Activity并传入请求码 `REQUEST_CODE_OPEN_NODE`
// 请求码用于在 `onActivityResult` 方法中区分不同的启动Activity返回的结果这里特定的请求码`REQUEST_CODE_OPEN_NODE`)表示是打开已有笔记这个操作返回的结果,
// 启动后,用户将进入 `NoteEditActivity` 查看和编辑指定的笔记完成后会返回到当前Activity可在 `onActivityResult` 方法中处理返回的相关数据和结果。
private void showSoftInput() { private void showSoftInput() {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
@ -578,11 +641,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
} }
// 此方法用于显示一个创建或修改文件夹的对话框,根据传入的参数 `create` 判断是创建还是修改操作,在对话框中进行相应的界面设置、事件监听以及数据处理逻辑,
// 比如设置标题、按钮文本、文本框初始内容等,并且处理用户点击确定和取消按钮的操作,包括验证文件夹名称的合法性、执行插入或更新文件夹数据等操作。
private void showCreateOrModifyFolderDialog(final boolean create) { private void showCreateOrModifyFolderDialog(final boolean create) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this); final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
final EditText etName = (EditText) view.findViewById(R.id.et_foler_name); final EditText etName = (EditText) view.findViewById(R.id.et_foler_name);
// 此方法用于显示一个创建或修改文件夹的对话框,根据传入的参数 `create` 判断是创建还是修改操作,在对话框中进行相应的界面设置、事件监听以及数据处理逻辑,
// 比如设置标题、按钮文本、文本框初始内容等,并且处理用户点击确定和取消按钮的操作,包括验证文件夹名称的合法性、执行插入或更新文件夹数据等操作。
showSoftInput(); showSoftInput();
if (!create) { if (!create) {
if (mFocusNoteDataItem != null) { if (mFocusNoteDataItem != null) {
@ -596,7 +662,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
etName.setText(""); etName.setText("");
builder.setTitle(this.getString(R.string.menu_create_folder)); builder.setTitle(this.getString(R.string.menu_create_folder));
} }
// 此方法用于显示一个创建或修改文件夹的对话框,根据传入的参数 `create` 判断是创建还是修改操作,在对话框中进行相应的界面设置、事件监听以及数据处理逻辑,
// 比如设置标题、按钮文本、文本框初始内容等,并且处理用户点击确定和取消按钮的操作,包括验证文件夹名称的合法性、执行插入或更新文件夹数据等操作。
builder.setPositiveButton(android.R.string.ok, null); builder.setPositiveButton(android.R.string.ok, null);
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
@ -608,7 +675,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
final Button positive = (Button)dialog.findViewById(android.R.id.button1); final Button positive = (Button)dialog.findViewById(android.R.id.button1);
positive.setOnClickListener(new OnClickListener() { positive.setOnClickListener(new OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
hideSoftInput(etName); hideSoftInput(etName);// 此方法用于显示一个创建或修改文件夹的对话框,根据传入的参数 `create` 判断是创建还是修改操作,在对话框中进行相应的界面设置、事件监听以及数据处理逻辑,
// 比如设置标题、按钮文本、文本框初始内容等,并且处理用户点击确定和取消按钮的操作,包括验证文件夹名称的合法性、执行插入或更新文件夹数据等操作。
String name = etName.getText().toString(); String name = etName.getText().toString();
if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { if (DataUtils.checkVisibleFolderName(mContentResolver, name)) {
Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name), Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name),
@ -639,6 +707,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
if (TextUtils.isEmpty(etName.getText())) { if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false); positive.setEnabled(false);
// 获取系统的输入方法管理器InputMethodManager实例通过调用 `getSystemService` 方法传入 `Context.INPUT_METHOD_SERVICE` 作为服务类型标识来获取,
// 该管理器用于控制软键盘的显示、隐藏等相关操作,是与系统输入方法交互的重要组件。
} }
/** /**
* When the name edit text is null, disable the positive button * When the name edit text is null, disable the positive button
@ -648,7 +718,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
// TODO Auto-generated method stub // TODO Auto-generated method stub
} }
// 调用输入方法管理器的 `hideSoftInputFromWindow` 方法来隐藏软键盘,传入参数 `view.getWindowToken()`,它代表与指定视图(`view`)关联的窗口令牌,
// 用于标识要操作的软键盘所属的窗口,第二个参数 `0` 表示隐藏软键盘的相关选项(这里 `0` 通常表示默认的隐藏方式,不进行额外的特殊处理)。
public void onTextChanged(CharSequence s, int start, int before, int count) { public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.isEmpty(etName.getText())) { if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false); positive.setEnabled(false);
@ -662,7 +733,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
}); });
} }// 调用输入方法管理器的 `hideSoftInputFromWindow` 方法来隐藏软键盘,传入参数 `view.getWindowToken()`,它代表与指定视图(`view`)关联的窗口令牌,
// 用于标识要操作的软键盘所属的窗口,第二个参数 `0` 表示隐藏软键盘的相关选项(这里 `0` 通常表示默认的隐藏方式,不进行额外的特殊处理)。
@Override @Override
public void onBackPressed() { public void onBackPressed() {
@ -686,7 +758,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
default: default:
break; break;
} }
} }// 调用输入方法管理器的 `hideSoftInputFromWindow` 方法来隐藏软键盘,传入参数 `view.getWindowToken()`,它代表与指定视图(`view`)关联的窗口令牌,
// 用于标识要操作的软键盘所属的窗口,第二个参数 `0` 表示隐藏软键盘的相关选项(这里 `0` 通常表示默认的隐藏方式,不进行额外的特殊处理)。
private void updateWidget(int appWidgetId, int appWidgetType) { private void updateWidget(int appWidgetId, int appWidgetType) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
@ -706,7 +779,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
sendBroadcast(intent); sendBroadcast(intent);
setResult(RESULT_OK, intent); setResult(RESULT_OK, intent);
} }
// 调用输入方法管理器的 `hideSoftInputFromWindow` 方法来隐藏软键盘,传入参数 `view.getWindowToken()`,它代表与指定视图(`view`)关联的窗口令牌,
// 用于标识要操作的软键盘所属的窗口,第二个参数 `0` 表示隐藏软键盘的相关选项(这里 `0` 通常表示默认的隐藏方式,不进行额外的特殊处理)。
private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (mFocusNoteDataItem != null) { if (mFocusNoteDataItem != null) {
@ -715,7 +789,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); 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); menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name);
} }
} }// 获取系统的输入方法管理器InputMethodManager实例通过调用 `getSystemService` 方法传入 `Context.INPUT_METHOD_SERVICE` 作为服务类型标识来获取,
// 该管理器用于控制软键盘的显示、隐藏等相关操作,是与系统输入方法交互的重要组件。
}; };
@Override @Override
@ -758,7 +833,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
return true; return true;
} }// 获取系统的输入方法管理器InputMethodManager实例通过调用 `getSystemService` 方法传入 `Context.INPUT_METHOD_SERVICE` 作为服务类型标识来获取,
// 该管理器用于控制软键盘的显示、隐藏等相关操作,是与系统输入方法交互的重要组件。
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
@ -777,7 +853,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
return true; return true;
} }
// 调用输入方法管理器的 `hideSoftInputFromWindow` 方法来隐藏软键盘,传入参数 `view.getWindowToken()`,它代表与指定视图(`view`)关联的窗口令牌,
// 用于标识要操作的软键盘所属的窗口,第二个参数 `0` 表示隐藏软键盘的相关选项(这里 `0` 通常表示默认的隐藏方式,不进行额外的特殊处理)。
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { switch (item.getItemId()) {
@ -817,13 +894,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
return true; return true;
} }
// 获取系统的输入方法管理器InputMethodManager实例通过调用 `getSystemService` 方法传入 `Context.INPUT_METHOD_SERVICE` 作为服务类型标识来获取,
// 该管理器用于控制软键盘的显示、隐藏等相关操作,是与系统输入方法交互的重要组件。
@Override @Override
public boolean onSearchRequested() { public boolean onSearchRequested() {
startSearch(null, false, null /* appData */, false); startSearch(null, false, null /* appData */, false);
return true; return true;
} }
// 调用输入方法管理器的 `hideSoftInputFromWindow` 方法来隐藏软键盘,传入参数 `view.getWindowToken()`,它代表与指定视图(`view`)关联的窗口令牌,
// 用于标识要操作的软键盘所属的窗口,第二个参数 `0` 表示隐藏软键盘的相关选项(这里 `0` 通常表示默认的隐藏方式,不进行额外的特殊处理)。
private void exportNoteToText() { private void exportNoteToText() {
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
new AsyncTask<Void, Void, Integer>() { new AsyncTask<Void, Void, Integer>() {
@ -875,7 +954,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
Intent intent = new Intent(from, NotesPreferenceActivity.class); Intent intent = new Intent(from, NotesPreferenceActivity.class);
from.startActivityIfNeeded(intent, -1); from.startActivityIfNeeded(intent, -1);
} }
// 获取系统的输入方法管理器InputMethodManager实例通过调用 `getSystemService` 方法传入 `Context.INPUT_METHOD_SERVICE` 作为服务类型标识来获取,
// 该管理器用于控制软键盘的显示、隐藏等相关操作,是与系统输入方法交互的重要组件。
private class OnListItemClickListener implements OnItemClickListener { private class OnListItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
@ -916,7 +996,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
} }
} }
// 获取系统的输入方法管理器InputMethodManager实例通过调用 `getSystemService` 方法传入 `Context.INPUT_METHOD_SERVICE` 作为服务类型标识来获取,
// 该管理器用于控制软键盘的显示、隐藏等相关操作,是与系统输入方法交互的重要组件。
private void startQueryDestinationFolders() { private void startQueryDestinationFolders() {
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
selection = (mState == ListEditState.NOTE_LIST) ? selection: selection = (mState == ListEditState.NOTE_LIST) ? selection:

@ -32,56 +32,82 @@ import java.util.Iterator;
public class NotesListAdapter extends CursorAdapter { public class NotesListAdapter extends CursorAdapter {
// 用于日志输出的标记,方便在调试等情况下识别该类相关的日志信息
private static final String TAG = "NotesListAdapter"; private static final String TAG = "NotesListAdapter";
// 上下文对象,用于获取系统相关资源等操作
private Context mContext; private Context mContext;
// 用于记录每个位置(对应数据项)是否被选中的映射表,键为位置索引(整数),值为是否选中(布尔值)
private HashMap<Integer, Boolean> mSelectedIndex; private HashMap<Integer, Boolean> mSelectedIndex;
// 记录笔记的数量
private int mNotesCount; private int mNotesCount;
// 表示是否处于选择模式(例如多选模式等情况)
private boolean mChoiceMode; private boolean mChoiceMode;
// 内部类用于封装与应用小部件相关的属性如小部件的ID和类型
public static class AppWidgetAttribute { public static class AppWidgetAttribute {
public int widgetId; public int widgetId;
public int widgetType; public int widgetType;
}; };
// 构造函数,接收一个上下文对象,初始化一些成员变量
public NotesListAdapter(Context context) { public NotesListAdapter(Context context) {
// 调用父类CursorAdapter的构造函数传入上下文和初始化为null的游标
super(context, null); super(context, null);
// 创建一个新的HashMap用于存储选中状态信息
mSelectedIndex = new HashMap<Integer, Boolean>(); mSelectedIndex = new HashMap<Integer, Boolean>();
mContext = context; mContext = context;
mNotesCount = 0; mNotesCount = 0;
} }
// 创建一个新的视图View对象用于显示列表中的一项数据
@Override @Override
public View newView(Context context, Cursor cursor, ViewGroup parent) { public View newView(Context context, Cursor cursor, ViewGroup parent) {
// 创建并返回一个NotesListItem类型的视图该视图应该是用于展示笔记列表项的自定义视图
return new NotesListItem(context); return new NotesListItem(context);
} }
// 将数据绑定到给定的视图上,用于显示具体的数据内容
@Override @Override
public void bindView(View view, Context context, Cursor cursor) { public void bindView(View view, Context context, Cursor cursor) {
// 判断视图是否是NotesListItem类型的实例
if (view instanceof NotesListItem) { if (view instanceof NotesListItem) {
// 根据给定的上下文和游标创建一个NoteItemData对象用于获取笔记相关的数据
NoteItemData itemData = new NoteItemData(context, cursor); NoteItemData itemData = new NoteItemData(context, cursor);
// 调用NotesListItem的bind方法将相关数据、选择模式状态以及该项是否被选中的信息传递进去进行数据绑定显示等操作
((NotesListItem) view).bind(context, itemData, mChoiceMode, ((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition())); isSelectedItem(cursor.getPosition()));
} }
} }
// 设置指定位置的项的选中状态
public void setCheckedItem(final int position, final boolean checked) { public void setCheckedItem(final int position, final boolean checked) {
// 将指定位置的选中状态存入mSelectedIndex映射表中
mSelectedIndex.put(position, checked); mSelectedIndex.put(position, checked);
// 通知数据集已发生改变以便相关的UI组件如ListView等能相应地更新显示
notifyDataSetChanged(); notifyDataSetChanged();
} }
// 判断是否处于选择模式
public boolean isInChoiceMode() { public boolean isInChoiceMode() {
return mChoiceMode; return mChoiceMode;
} }
// 设置选择模式,同时清空之前的选中状态记录
public void setChoiceMode(boolean mode) { public void setChoiceMode(boolean mode) {
mSelectedIndex.clear(); mSelectedIndex.clear();
mChoiceMode = mode; mChoiceMode = mode;
} }
// 全选或全不选所有符合条件笔记类型为Notes.TYPE_NOTE的项
public void selectAll(boolean checked) { public void selectAll(boolean checked) {
// 获取当前游标对象,用于遍历数据
Cursor cursor = getCursor(); Cursor cursor = getCursor();
// 遍历数据集的每一项
for (int i = 0; i < getCount(); i++) { for (int i = 0; i < getCount(); i++) {
// 将游标移动到指定位置
if (cursor.moveToPosition(i)) { if (cursor.moveToPosition(i)) {
// 判断该项对应的笔记类型是否为Notes.TYPE_NOTE如果是则设置其选中状态
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
setCheckedItem(i, checked); setCheckedItem(i, checked);
} }
@ -89,14 +115,21 @@ public class NotesListAdapter extends CursorAdapter {
} }
} }
// 获取所有已选中项的ID存储在一个HashSet中返回
public HashSet<Long> getSelectedItemIds() { public HashSet<Long> getSelectedItemIds() {
// 创建一个用于存储已选中项ID的HashSet对象
HashSet<Long> itemSet = new HashSet<Long>(); HashSet<Long> itemSet = new HashSet<Long>();
// 遍历mSelectedIndex中记录的所有位置
for (Integer position : mSelectedIndex.keySet()) { for (Integer position : mSelectedIndex.keySet()) {
// 如果该位置对应的项是被选中的
if (mSelectedIndex.get(position) == true) { if (mSelectedIndex.get(position) == true) {
// 获取该项对应的ID
Long id = getItemId(position); Long id = getItemId(position);
// 如果ID是Notes.ID_ROOT_FOLDER可能是一个特殊的、不符合预期的ID则在日志中记录错误信息通常表示不应该出现这种情况
if (id == Notes.ID_ROOT_FOLDER) { if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen"); Log.d(TAG, "Wrong item id, should not happen");
} else { } else {
// 将符合条件的ID添加到HashSet中
itemSet.add(id); itemSet.add(id);
} }
} }
@ -105,21 +138,32 @@ public class NotesListAdapter extends CursorAdapter {
return itemSet; return itemSet;
} }
// 获取所有已选中的应用小部件相关属性信息存储在一个HashSet中返回
public HashSet<AppWidgetAttribute> getSelectedWidget() { public HashSet<AppWidgetAttribute> getSelectedWidget() {
// 创建一个用于存储已选中小部件属性的HashSet对象
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>(); HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
// 遍历mSelectedIndex中记录的所有位置
for (Integer position : mSelectedIndex.keySet()) { for (Integer position : mSelectedIndex.keySet()) {
// 如果该位置对应的项是被选中的
if (mSelectedIndex.get(position) == true) { if (mSelectedIndex.get(position) == true) {
// 获取对应位置的数据项(游标形式)
Cursor c = (Cursor) getItem(position); Cursor c = (Cursor) getItem(position);
if (c != null) { if (c != null) {
// 创建一个AppWidgetAttribute对象用于存储小部件相关属性
AppWidgetAttribute widget = new AppWidgetAttribute(); AppWidgetAttribute widget = new AppWidgetAttribute();
// 根据游标创建一个NoteItemData对象用于获取小部件相关的数据
NoteItemData item = new NoteItemData(mContext, c); NoteItemData item = new NoteItemData(mContext, c);
// 设置小部件的ID属性
widget.widgetId = item.getWidgetId(); widget.widgetId = item.getWidgetId();
// 设置小部件的类型属性
widget.widgetType = item.getWidgetType(); widget.widgetType = item.getWidgetType();
// 将封装好的小部件属性对象添加到HashSet中
itemSet.add(widget); itemSet.add(widget);
/** /**
* Don't close cursor here, only the adapter could close it *
*/ */
} else { } else {
// 如果游标为空,记录错误日志信息,表示游标无效
Log.e(TAG, "Invalid cursor"); Log.e(TAG, "Invalid cursor");
return null; return null;
} }
@ -128,13 +172,18 @@ public class NotesListAdapter extends CursorAdapter {
return itemSet; return itemSet;
} }
// 获取已选中项的数量
public int getSelectedCount() { public int getSelectedCount() {
// 获取mSelectedIndex中所有值即每个位置对应的选中状态布尔值的集合
Collection<Boolean> values = mSelectedIndex.values(); Collection<Boolean> values = mSelectedIndex.values();
// 如果集合为空可能没有任何选中记录等情况返回0
if (null == values) { if (null == values) {
return 0; return 0;
} }
// 获取集合的迭代器,用于遍历集合中的元素(布尔值)
Iterator<Boolean> iter = values.iterator(); Iterator<Boolean> iter = values.iterator();
int count = 0; int count = 0;
// 遍历集合中的每个布尔值元素如果为true表示该项被选中数量加1
while (iter.hasNext()) { while (iter.hasNext()) {
if (true == iter.next()) { if (true == iter.next()) {
count++; count++;
@ -143,30 +192,39 @@ public class NotesListAdapter extends CursorAdapter {
return count; return count;
} }
// 判断是否所有项都被选中
public boolean isAllSelected() { public boolean isAllSelected() {
// 获取已选中项的数量
int checkedCount = getSelectedCount(); int checkedCount = getSelectedCount();
// 如果选中数量不为0有选中项且选中数量等于笔记的总数量说明所有项都被选中了返回true否则返回false
return (checkedCount != 0 && checkedCount == mNotesCount); return (checkedCount != 0 && checkedCount == mNotesCount);
} }
// 判断指定位置的项是否被选中
public boolean isSelectedItem(final int position) { public boolean isSelectedItem(final int position) {
// 如果该位置对应的选中状态记录为null可能还未设置等情况则返回false表示未选中
if (null == mSelectedIndex.get(position)) { if (null == mSelectedIndex.get(position)) {
return false; return false;
} }
// 返回该位置对应的实际选中状态(布尔值)
return mSelectedIndex.get(position); return mSelectedIndex.get(position);
} }
// 当内容发生改变时调用的方法这里调用了calcNotesCount方法来重新计算笔记数量
@Override @Override
protected void onContentChanged() { protected void onContentChanged() {
super.onContentChanged(); super.onContentChanged();
calcNotesCount(); calcNotesCount();
} }
// 当游标发生改变时调用的方法同样调用calcNotesCount方法来重新计算笔记数量
@Override @Override
public void changeCursor(Cursor cursor) { public void changeCursor(Cursor cursor) {
super.changeCursor(cursor); super.changeCursor(cursor);
calcNotesCount(); calcNotesCount();
} }
// 计算笔记的数量遍历数据集统计符合条件笔记类型为Notes.TYPE_NOTE的项的数量
private void calcNotesCount() { private void calcNotesCount() {
mNotesCount = 0; mNotesCount = 0;
for (int i = 0; i < getCount(); i++) { for (int i = 0; i < getCount(); i++) {

@ -37,85 +37,133 @@ public class NotesListItem extends LinearLayout {
private TextView mCallName; private TextView mCallName;
private NoteItemData mItemData; private NoteItemData mItemData;
private CheckBox mCheckBox; private CheckBox mCheckBox;
// NotesListItem类的构造函数用于初始化该列表项相关的视图组件
public NotesListItem(Context context) { public NotesListItem(Context context) {
// 调用父类(应该是某个视图类的构造函数,传递上下文对象进行初始化)
super(context); super(context);
// 从给定的布局资源R.layout.note_item中加载视图层次结构并将其填充到当前的视图this
inflate(context, R.layout.note_item, this); inflate(context, R.layout.note_item, this);
// 通过ID查找并获取布局中的ImageView组件用于显示提醒图标等ID为R.id.iv_alert_icon
mAlert = (ImageView) findViewById(R.id.iv_alert_icon); mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
// 通过ID查找并获取布局中的TextView组件用于显示标题内容ID为R.id.tv_title
mTitle = (TextView) findViewById(R.id.tv_title); mTitle = (TextView) findViewById(R.id.tv_title);
// 通过ID查找并获取布局中的TextView组件用于显示时间相关信息ID为R.id.tv_time
mTime = (TextView) findViewById(R.id.tv_time); mTime = (TextView) findViewById(R.id.tv_time);
// 通过ID查找并获取布局中的TextView组件用于显示呼叫名称相关信息ID为R.id.tv_name
mCallName = (TextView) findViewById(R.id.tv_name); mCallName = (TextView) findViewById(R.id.tv_name);
// 通过安卓系统内置的IDandroid.R.id.checkbox查找并获取布局中的CheckBox组件用于表示该项是否被选中
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
} }
// 用于将数据绑定到该列表项视图上,根据不同的数据情况设置视图的显示内容和状态
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
// 如果处于选择模式choiceMode为true并且数据对应的类型是笔记类型Notes.TYPE_NOTE
if (choiceMode && data.getType() == Notes.TYPE_NOTE) { if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
// 设置复选框CheckBox可见用于在选择模式下让用户选择该项
mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setVisibility(View.VISIBLE);
// 根据传入的checked参数设置复选框的选中状态
mCheckBox.setChecked(checked); mCheckBox.setChecked(checked);
} else { } else {
// 如果不满足上述条件,隐藏复选框,表示不在选择模式或者不是笔记类型的项不需要显示复选框
mCheckBox.setVisibility(View.GONE); mCheckBox.setVisibility(View.GONE);
} }
// 将传入的数据对象保存到成员变量mItemData中方便后续获取和使用
mItemData = data; mItemData = data;
// 如果数据的ID等于Notes.ID_CALL_RECORD_FOLDER可能是特定的文件夹类型的标识
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
// 隐藏呼叫名称相关的TextView因为可能不需要显示
mCallName.setVisibility(View.GONE); mCallName.setVisibility(View.GONE);
// 显示提醒图标相关的ImageView可能用于提示特殊情况
mAlert.setVisibility(View.VISIBLE); mAlert.setVisibility(View.VISIBLE);
// 设置标题的文本外观样式采用R.style.TextAppearancePrimaryItem样式
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
// 设置标题的文本内容,由固定字符串(通话记录文件夹名称)和格式化后的包含文件数量的字符串拼接而成,文件数量从数据对象中获取
mTitle.setText(context.getString(R.string.call_record_folder_name) mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount())); + context.getString(R.string.format_folder_files_count, data.getNotesCount()));
// 设置提醒图标对应的图片资源这里使用R.drawable.call_record图片资源
mAlert.setImageResource(R.drawable.call_record); mAlert.setImageResource(R.drawable.call_record);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
// 如果数据的父ID等于Notes.ID_CALL_RECORD_FOLDER表示该项属于通话记录文件夹下的子项
// 显示呼叫名称相关的TextView并设置其文本内容为从数据对象中获取的呼叫名称
mCallName.setVisibility(View.VISIBLE); mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName()); mCallName.setText(data.getCallName());
// 设置标题的文本外观样式采用R.style.TextAppearanceSecondaryItem样式
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
// 设置标题的文本内容通过DataUtils工具类对数据中的摘要snippet进行格式化后设置
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
// 如果数据有提醒通过hasAlert方法判断则设置提醒图标相关的ImageView可见并设置对应的图片资源为R.drawable.clock
if (data.hasAlert()) { if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock); mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE); mAlert.setVisibility(View.VISIBLE);
} else { } else {
// 如果没有提醒,则隐藏提醒图标
mAlert.setVisibility(View.GONE); mAlert.setVisibility(View.GONE);
} }
} else { } else {
// 如果不属于上述两种情况(既不是通话记录文件夹本身,也不是其直接子项)
// 隐藏呼叫名称相关的TextView因为不需要显示
mCallName.setVisibility(View.GONE); mCallName.setVisibility(View.GONE);
// 设置标题的文本外观样式采用R.style.TextAppearancePrimaryItem样式
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
// 如果数据的类型是文件夹类型Notes.TYPE_FOLDER
if (data.getType() == Notes.TYPE_FOLDER) { if (data.getType() == Notes.TYPE_FOLDER) {
// 设置标题的文本内容由数据中的摘要snippet和格式化后的包含文件数量的字符串拼接而成文件数量从数据对象中获取
mTitle.setText(data.getSnippet() mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count, + context.getString(R.string.format_folder_files_count,
data.getNotesCount())); data.getNotesCount()));
// 隐藏提醒图标相关的ImageView文件夹类型可能不需要显示提醒图标
mAlert.setVisibility(View.GONE); mAlert.setVisibility(View.GONE);
} else { } else {
// 如果是笔记类型(非文件夹类型)
// 设置标题的文本内容通过DataUtils工具类对数据中的摘要snippet进行格式化后设置
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
// 如果数据有提醒通过hasAlert方法判断则设置提醒图标相关的ImageView可见并设置对应的图片资源为R.drawable.clock
if (data.hasAlert()) { if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock); mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE); mAlert.setVisibility(View.VISIBLE);
} else { } else {
// 如果没有提醒,则隐藏提醒图标
mAlert.setVisibility(View.GONE); mAlert.setVisibility(View.GONE);
} }
} }
} }
// 设置时间相关的TextView的文本内容通过DateUtils工具类根据数据中的修改日期获取相对时间格式的字符串进行设置
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 调用setBackground方法根据数据情况设置该项的背景样式
setBackground(data); setBackground(data);
} }
// 根据传入的NoteItemData数据对象设置该项的背景资源根据不同的条件选择不同的背景资源进行设置
private void setBackground(NoteItemData data) { private void setBackground(NoteItemData data) {
// 获取数据中对应的背景颜色ID
int id = data.getBgColorId(); int id = data.getBgColorId();
// 如果数据的类型是笔记类型Notes.TYPE_NOTE
if (data.getType() == Notes.TYPE_NOTE) { if (data.getType() == Notes.TYPE_NOTE) {
// 如果该项是单独的通过isSingle方法判断或者只有一个后续文件夹通过isOneFollowingFolder方法判断
if (data.isSingle() || data.isOneFollowingFolder()) {
// 设置该项的背景资源通过NoteItemBgResources工具类根据背景颜色ID获取对应的单个笔记背景资源进行设置
if (data.isSingle() || data.isOneFollowingFolder()) { if (data.isSingle() || data.isOneFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) { } else if (data.isLast()) {
// 如果该项是最后一个通过isLast方法判断则设置该项的背景资源通过NoteItemBgResources工具类根据背景颜色ID获取对应的最后一个笔记背景资源进行设置
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) { } else if (data.isFirst() || data.isMultiFollowingFolder()) {
// 如果该项是第一个通过isFirst方法判断或者有多个后续文件夹通过isMultiFollowingFolder方法判断则设置该项的背景资源通过NoteItemBgResources工具类根据背景颜色ID获取对应的第一个笔记背景资源进行设置
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
} else { } else {
// 如果不符合上述任何特殊情况则设置该项的背景资源通过NoteItemBgResources工具类根据背景颜色ID获取对应的普通笔记背景资源进行设置
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
} }
// 如果数据的类型不是笔记类型即文件夹类型等情况则设置该项的背景资源通过NoteItemBgResources工具类获取对应的文件夹背景资源进行设置
} else { } else {
setBackgroundResource(NoteItemBgResources.getFolderBgRes()); setBackgroundResource(NoteItemBgResources.getFolderBgRes());
} }
} }
// 获取当前列表项绑定的数据对象,用于在外部获取该列表项对应的数据信息
public NoteItemData getItemData() { public NoteItemData getItemData() {
return mItemData; return mItemData;
} }

@ -47,64 +47,86 @@ import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService; import net.micode.notes.gtask.remote.GTaskSyncService;
// NotesPreferenceActivity类继承自PreferenceActivity用于管理应用中与偏好设置相关的功能和界面展示
public class NotesPreferenceActivity extends PreferenceActivity { public class NotesPreferenceActivity extends PreferenceActivity {
public static final String PREFERENCE_NAME = "notes_preferences";
// 定义偏好设置文件的名称,用于存储和获取应用相关的偏好设置数据
public static final String PREFERENCE_NAME = "notes_preferences";
// 偏好设置中用于存储同步账户名称的键
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
// 偏好设置中用于存储上次同步时间的键
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
// 偏好设置中用于设置背景颜色相关的键(可能与随机背景颜色显示等功能有关)
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
// 偏好设置中与同步账户相关的另一个键(可能用于区分不同的同步账户相关设置项等)
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
// 用于过滤账户相关权限等的键(可能在获取账户信息等操作中使用)
private static final String AUTHORITIES_FILTER_KEY = "authorities"; private static final String AUTHORITIES_FILTER_KEY = "authorities";
// 用于管理偏好设置中账户相关分类的视图组件(可能在界面上对账户相关的偏好设置项进行分组展示等)
private PreferenceCategory mAccountCategory; private PreferenceCategory mAccountCategory;
// 广播接收器,用于接收与同步任务相关的广播消息,以便更新界面等操作
private GTaskReceiver mReceiver; private GTaskReceiver mReceiver;
// 用于记录原始的账户列表(可能用于对比账户变化情况等)
private Account[] mOriAccounts; private Account[] mOriAccounts;
// 标记是否添加了新账户,用于后续相关逻辑判断
private boolean mHasAddedAccount; private boolean mHasAddedAccount;
// 在Activity创建时调用的方法进行一些初始化操作
@Override @Override
protected void onCreate(Bundle icicle) { protected void onCreate(Bundle icicle) {
super.onCreate(icicle); super.onCreate(icicle);
// 设置ActionBar上的返回按钮可用使用应用图标来实现导航返回功能通常是返回上级界面
/* using the app icon for navigation */ /* using the app icon for navigation */
getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setDisplayHomeAsUpEnabled(true);
// 从指定的XML资源文件R.xml.preferences加载偏好设置界面的布局和相关设置项
addPreferencesFromResource(R.xml.preferences); addPreferencesFromResource(R.xml.preferences);
// 通过键PREFERENCE_SYNC_ACCOUNT_KEY查找并获取对应的PreferenceCategory组件用于后续操作账户相关的偏好设置项
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
// 创建一个GTaskReceiver实例用于接收同步相关广播
mReceiver = new GTaskReceiver(); mReceiver = new GTaskReceiver();
// 创建一个IntentFilter用于定义要接收的广播动作
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
// 添加要接收的广播动作这里接收GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME对应的广播
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
// 注册广播接收器,使其能够接收符合条件的广播消息
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
registerReceiver(mReceiver, filter); registerReceiver(mReceiver, filter);
// 初始化为null后续会根据实际情况获取账户信息进行赋值
mOriAccounts = null; mOriAccounts = null;
// 通过LayoutInflater加载一个自定义的头部视图R.layout.settings_header并添加到当前Activity的ListView中作为头部视
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
getListView().addHeaderView(header, null, true); getListView().addHeaderView(header, null, true);
} }
// 在Activity重新恢复可见时调用的方法例如从其他Activity返回等情况进行一些更新操作
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
// 如果添加了新账户mHasAddedAccount为true则进行以下操作
// need to set sync account automatically if user has added a new // need to set sync account automatically if user has added a new
// account // account
if (mHasAddedAccount) { if (mHasAddedAccount) {
// 获取当前的谷歌账户列表
Account[] accounts = getGoogleAccounts(); Account[] accounts = getGoogleAccounts();
// 如果原始账户列表不为null且当前账户数量大于原始账户数量说明可能添加了新账户
if (mOriAccounts != null && accounts.length > mOriAccounts.length) { if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
// 遍历新的账户列表
for (Account accountNew : accounts) { for (Account accountNew : accounts) {
// 标记是否找到匹配的旧账户初始化为false
boolean found = false; boolean found = false;
// 遍历原始账户列表,对比账户名称,查找是否已存在相同名称的账户
for (Account accountOld : mOriAccounts) { for (Account accountOld : mOriAccounts) {
if (TextUtils.equals(accountOld.name, accountNew.name)) { if (TextUtils.equals(accountOld.name, accountNew.name)) {
found = true; found = true;
break; break;
} }
} }
// 如果没有找到匹配的旧账户,说明是新添加的账户,则设置为同步账户,并跳出循环
if (!found) { if (!found) {
setSyncAccount(accountNew.name); setSyncAccount(accountNew.name);
break; break;
@ -113,36 +135,50 @@ public class NotesPreferenceActivity extends PreferenceActivity {
} }
} }
// 调用refreshUI方法更新界面显示加载账户偏好设置项、同步按钮等相关UI元素
refreshUI(); refreshUI();
} }
// 在Activity销毁时调用的方法进行一些清理操作如注销广播接收器
@Override @Override
protected void onDestroy() { protected void onDestroy() {
if (mReceiver != null) { if (mReceiver != null) {
// 注销广播接收器,避免内存泄漏等问题
unregisterReceiver(mReceiver); unregisterReceiver(mReceiver);
} }
super.onDestroy(); super.onDestroy();
} }
// 加载账户偏好设置相关的视图组件和逻辑,例如添加账户相关的偏好设置项到界面上
private void loadAccountPreference() { private void loadAccountPreference() {
// 移除账户分类下的所有已有偏好设置项(可能是为了重新加载最新的设置项等情况)
mAccountCategory.removeAll(); mAccountCategory.removeAll();
// 创建一个新的Preference对象用于表示账户相关的偏好设置项
Preference accountPref = new Preference(this); Preference accountPref = new Preference(this);
// 获取当前设置的同步账户名称(如果有的话),作为默认账户名称
final String defaultAccount = getSyncAccountName(this); final String defaultAccount = getSyncAccountName(this);
// 设置偏好设置项的标题从字符串资源R.string.preferences_account_title中获取
accountPref.setTitle(getString(R.string.preferences_account_title)); accountPref.setTitle(getString(R.string.preferences_account_title));
// 设置偏好设置项的摘要内容从字符串资源R.string.preferences_account_summary中获取
accountPref.setSummary(getString(R.string.preferences_account_summary)); accountPref.setSummary(getString(R.string.preferences_account_summary));
// 设置偏好设置项的点击监听器,当用户点击该设置项时执行以下逻辑
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) { public boolean onPreferenceClick(Preference preference) {
// 如果同步服务没有正在同步通过GTaskSyncService.isSyncing()判断)
if (!GTaskSyncService.isSyncing()) { if (!GTaskSyncService.isSyncing()) {
// 如果默认账户名称为空,说明是第一次设置账户,弹出选择账户的提示对话框
if (TextUtils.isEmpty(defaultAccount)) { if (TextUtils.isEmpty(defaultAccount)) {
// the first time to set account // the first time to set account
showSelectAccountAlertDialog(); showSelectAccountAlertDialog();
} else { } else {
// 如果已经设置过账户,弹出确认更改账户的提示对话框,提示用户相关风险
// if the account has already been set, we need to promp // if the account has already been set, we need to promp
// user about the risk // user about the risk
showChangeAccountConfirmAlertDialog(); showChangeAccountConfirmAlertDialog();
} }
} else { } else {
// 如果正在同步弹出提示Toast告知用户不能更改账户
Toast.makeText(NotesPreferenceActivity.this, Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show(); .show();
@ -151,85 +187,121 @@ public class NotesPreferenceActivity extends PreferenceActivity {
} }
}); });
// 将创建好的账户偏好设置项添加到账户分类mAccountCategory以便在界面上显示
mAccountCategory.addPreference(accountPref); mAccountCategory.addPreference(accountPref);
} }
// 加载同步按钮相关的逻辑和状态设置,包括按钮文本、点击事件以及同步状态显示等
private void loadSyncButton() { private void loadSyncButton() {
// 通过ID查找并获取同步按钮Button组件ID为R.id.preference_sync_button
Button syncButton = (Button) findViewById(R.id.preference_sync_button); Button syncButton = (Button) findViewById(R.id.preference_sync_button);
// 通过ID查找并获取显示上次同步时间的TextView组件ID为R.id.prefenerece_sync_status_textview
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// set button state // set button state
// 根据同步服务是否正在同步来设置同步按钮的文本和点击事件
if (GTaskSyncService.isSyncing()) { if (GTaskSyncService.isSyncing()) {
// 如果正在同步,设置按钮文本为取消同步相关的文本(从字符串资源中获取)
syncButton.setText(getString(R.string.preferences_button_sync_cancel)); syncButton.setText(getString(R.string.preferences_button_sync_cancel));
// 设置按钮的点击事件点击时调用GTaskSyncService.cancelSync方法取消同步
syncButton.setOnClickListener(new View.OnClickListener() { syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
GTaskSyncService.cancelSync(NotesPreferenceActivity.this); GTaskSyncService.cancelSync(NotesPreferenceActivity.this);
} }
}); });
} else { } else {
// 如果没有正在同步,设置按钮文本为立即同步相关的文本(从字符串资源中获取)
syncButton.setText(getString(R.string.preferences_button_sync_immediately)); syncButton.setText(getString(R.string.preferences_button_sync_immediately));
// 设置按钮的点击事件点击时调用GTaskSyncService.startSync方法启动同步
syncButton.setOnClickListener(new View.OnClickListener() { syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
GTaskSyncService.startSync(NotesPreferenceActivity.this); GTaskSyncService.startSync(NotesPreferenceActivity.this);
} }
}); });
} }
// 根据是否设置了同步账户来启用或禁用同步按钮,如果同步账户名称为空则禁用按钮
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
// 根据同步服务状态设置上次同步时间的显示内容和可见性
// set last sync time // set last sync time
if (GTaskSyncService.isSyncing()) { if (GTaskSyncService.isSyncing()) {
// 如果正在同步设置显示上次同步时间的TextView的文本为同步进度相关的字符串从同步服务获取并设置为可见
lastSyncTimeView.setText(GTaskSyncService.getProgressString()); lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE); lastSyncTimeView.setVisibility(View.VISIBLE);
} else { } else {
// 如果没有正在同步获取上次同步时间通过getLastSyncTime方法
long lastSyncTime = getLastSyncTime(this); long lastSyncTime = getLastSyncTime(this);
if (lastSyncTime != 0) { if (lastSyncTime != 0) {
// 如果上次同步时间不为0格式化并设置显示上次同步时间的TextView的文本内容然后设置为可见
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time, lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format), DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime))); lastSyncTime)));
lastSyncTimeView.setVisibility(View.VISIBLE); lastSyncTimeView.setVisibility(View.VISIBLE);
} else { } else {
// 如果上次同步时间为0隐藏该TextView组件
lastSyncTimeView.setVisibility(View.GONE); lastSyncTimeView.setVisibility(View.GONE);
} }
} }
} }
// 用于更新整个界面的显示,调用加载账户偏好设置和同步按钮相关的方法
private void refreshUI() { private void refreshUI() {
loadAccountPreference(); loadAccountPreference();
loadSyncButton(); loadSyncButton();
} }
// 弹出选择账户的提示对话框,用于让用户选择要设置的同步账户
private void showSelectAccountAlertDialog() { private void showSelectAccountAlertDialog() {
// 创建一个AlertDialog.Builder对象用于构建提示对话框
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 通过LayoutInflater加载一个自定义的标题视图R.layout.account_dialog_title用于设置对话框的标题部分
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
// 通过ID查找并获取标题TextView组件设置其文本内容为选择账户相关的标题文本从字符串资源中获取
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); titleTextView.setText(getString(R.string.preferences_dialog_select_account_title));
// 通过ID查找并获取副标题TextView组件设置其文本内容为选择账户相关的提示文本从字符串资源中获取
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));
// 设置对话框的自定义标题视图
dialogBuilder.setCustomTitle(titleView); dialogBuilder.setCustomTitle(titleView);
// 设置对话框的确定按钮这里先设置为null后续可能根据具体需求再处理
dialogBuilder.setPositiveButton(null, null); dialogBuilder.setPositiveButton(null, null);
// 获取当前的谷歌账户列表
Account[] accounts = getGoogleAccounts(); Account[] accounts = getGoogleAccounts();
// 获取当前设置的同步账户名称(如果有的话)
String defAccount = getSyncAccountName(this); String defAccount = getSyncAccountName(this);
// 将当前账户列表赋值给mOriAccounts用于后续对比等操作
mOriAccounts = accounts; mOriAccounts = accounts;
// 标记还没有添加新账户
mHasAddedAccount = false; mHasAddedAccount = false;
// 如果账户列表长度大于0说明有可用账户进行以下操作
if (accounts.length > 0) { if (accounts.length > 0) {
// 创建一个字符序列数组,用于存储账户名称,长度与账户列表长度相同
CharSequence[] items = new CharSequence[accounts.length]; CharSequence[] items = new CharSequence[accounts.length];
// 将items数组赋值给itemMapping方便后续在点击事件中使用这里可能是为了保持引用一致等原因
final CharSequence[] itemMapping = items;
// 标记默认选中的账户索引,初始化为 -1表示没有默认选中项
final CharSequence[] itemMapping = items; final CharSequence[] itemMapping = items;
int checkedItem = -1; int checkedItem = -1;
int index = 0; int index = 0;
// 遍历账户列表设置每个账户名称到items数组中并查找默认选中的账户与当前同步账户名称相同的账户
for (Account account : accounts) { for (Account account : accounts) {
if (TextUtils.equals(account.name, defAccount)) { if (TextUtils.equals(account.name, defAccount)) {
checkedItem = index; checkedItem = index;
} }
items[index++] = account.name; items[index++] = account.name;
} }
// 设置对话框的单选列表项,传入账户名称数组、默认选中索引以及点击事件监听器
dialogBuilder.setSingleChoiceItems(items, checkedItem, dialogBuilder.setSingleChoiceItems(items, checkedItem,
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
// 当用户点击某个账户项时,设置选择的账户为同步账户,关闭对话框,并更新界面显示
setSyncAccount(itemMapping[which].toString()); setSyncAccount(itemMapping[which].toString());
dialog.dismiss(); dialog.dismiss();
refreshUI(); refreshUI();
@ -237,10 +309,13 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}); });
} }
// 通过LayoutInflater加载一个添加账户的视图R.layout.add_account_text并添加到对话框中
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
dialogBuilder.setView(addAccountView); dialogBuilder.setView(addAccountView);
// 创建并显示对话框
final AlertDialog dialog = dialogBuilder.show(); final AlertDialog dialog = dialogBuilder.show();
// 设置添加账户视图的点击事件,当用户点击时,标记添加了新账户,启动添加账户的系统设置界面,并关闭当前对话框
addAccountView.setOnClickListener(new View.OnClickListener() { addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
mHasAddedAccount = true; mHasAddedAccount = true;
@ -254,98 +329,138 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}); });
} }
// 弹出确认更改账户的提示对话框,用于提示用户更改账户的相关操作和风险等信息
private void showChangeAccountConfirmAlertDialog() { private void showChangeAccountConfirmAlertDialog() {
// 创建一个AlertDialog.Builder对象用于构建提示对话框
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 通过LayoutInflater加载一个自定义的标题视图R.layout.account_dialog_title用于设置对话框的标题部分
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
// 通过ID查找并获取标题TextView组件设置其文本内容为更改账户相关的标题文本包含当前同步账户名称从字符串资源中获取并格式化
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
getSyncAccountName(this))); getSyncAccountName(this)));
// 通过ID查找并获取副标题TextView组件设置其文本内容为更改账户相关的警告提示文本从字符串资源中获取
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg));
// 设置对话框的自定义标题视图
dialogBuilder.setCustomTitle(titleView); dialogBuilder.setCustomTitle(titleView);
// 创建一个字符序列数组,包含更改账户、移除账户、取消等菜单项文本(从字符串资源中获取)
CharSequence[] menuItemArray = new CharSequence[] { CharSequence[] menuItemArray = new CharSequence[] {
getString(R.string.preferences_menu_change_account), getString(R.string.preferences_menu_change_account),
getString(R.string.preferences_menu_remove_account), getString(R.string.preferences_menu_remove_account),
getString(R.string.preferences_menu_cancel) getString(R.string.preferences_menu_cancel)
}; };
// 设置对话框的菜单项和点击事件监听器,根据用户点击的不同菜单项执行相应操作
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
if (which == 0) { if (which == 0) {
// 如果点击的是更改账户菜单项,弹出选择账户的提示对话框
showSelectAccountAlertDialog(); showSelectAccountAlertDialog();
} else if (which == 1) { } else if (which == 1) {
// 如果点击的是移除账户菜单项调用removeSyncAccount方法移除同步账户并更新界面显示
removeSyncAccount(); removeSyncAccount();
refreshUI(); refreshUI();
} }
} }
}); });
// 显示对话框
dialogBuilder.show(); dialogBuilder.show();
} }
// 获取当前设备上的谷歌账户列表通过AccountManager获取指定类型"com.google")的账户
private Account[] getGoogleAccounts() { private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this); AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.google"); return accountManager.getAccountsByType("com.google");
} }
// 设置同步账户名称,将指定的账户名称保存到偏好设置中,并进行一些相关的清理和更新操作
private void setSyncAccount(String account) { private void setSyncAccount(String account) {
if (!getSyncAccountName(this).equals(account)) { if (!getSyncAccountName(this).equals(account)) {
// 获取偏好设置对象,用于存储和读取应用的偏好设置数据
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
// 获取偏好设置的编辑器,用于修改偏好设置的值
SharedPreferences.Editor editor = settings.edit(); SharedPreferences.Editor editor = settings.edit();
if (account != null) { if (account != null) {
// 如果账户名称不为null将账户名称保存到偏好设置中键为PREFERENCE_SYNC_ACCOUNT_NAME
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
} else { } else {
// 如果传入的账户名称为null将同步账户名称设置为空字符串存入偏好设置中
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
} }
// 提交对偏好设置的修改,使其生效
editor.commit(); editor.commit();
// clean up last sync time // clean up last sync time
// 调用setLastSyncTime方法将上次同步时间设置为0可能是为了在更改同步账户时重置同步相关的时间记录
setLastSyncTime(this, 0);
setLastSyncTime(this, 0); setLastSyncTime(this, 0);
// 清理本地与GTask相关的信息通过开启一个新线程来执行更新操作
// clean up local gtask related info // clean up local gtask related info
new Thread(new Runnable() { new Thread(new Runnable() {
public void run() { public void run() {
// 创建一个ContentValues对象用于存储要更新的数据
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
// 将NoteColumns.GTASK_ID对应的值设置为空字符串可能是清除之前关联的GTask ID信息
values.put(NoteColumns.GTASK_ID, ""); values.put(NoteColumns.GTASK_ID, "");
// 将NoteColumns.SYNC_ID对应的值设置为0可能是重置同步相关的ID等信息
values.put(NoteColumns.SYNC_ID, 0); values.put(NoteColumns.SYNC_ID, 0);
// 使用内容解析器getContentResolver更新Notes.CONTENT_NOTE_URI对应的内容将上述设置的值进行更新条件为null可能是更新所有符合该URI的记录
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
} }
}).start(); }).start();
// 弹出一个Toast提示信息告知用户成功设置账户提示内容通过字符串资源格式化传入账户名称来生成
Toast.makeText(NotesPreferenceActivity.this, Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account), getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show(); Toast.LENGTH_SHORT).show();
} }
} }
// 移除同步账户相关的偏好设置信息以及清理本地与GTask相关的信息
private void removeSyncAccount() { private void removeSyncAccount() {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); SharedPreferences settings = get
// 获取应用的共享偏好设置对象,用于操作偏好设置数据
SharedPreferences.(PREFERENCE_NAME, Context.MODE_PRIVATE);
// 获取偏好设置的编辑器,用于修改偏好设置中的值
SharedPreferences.Editor editor = settings.edit(); SharedPreferences.Editor editor = settings.edit();
// 如果偏好设置中包含同步账户名称这个键(说明之前有设置过),则移除该键值对
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
} }
// 如果偏好设置中包含上次同步时间这个键(说明之前有记录),则移除该键值对
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) { if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
editor.remove(PREFERENCE_LAST_SYNC_TIME); editor.remove(PREFERENCE_LAST_SYNC_TIME);
} }
// 提交对偏好设置的修改,使其生效
editor.commit(); editor.commit();
// 清理本地与GTask相关的信息通过开启一个新线程来执行更新操作
// clean up local gtask related info // clean up local gtask related info
new Thread(new Runnable() { new Thread(new Runnable() {
public void run() { public void run() {
// 创建一个ContentValues对象用于存储要更新的数据
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
// 将NoteColumns.GTASK_ID对应的值设置为空字符串可能是清除之前关联的GTask ID信息
values.put(NoteColumns.GTASK_ID, ""); values.put(NoteColumns.GTASK_ID, "");
// 将NoteColumns.SYNC_ID对应的值设置为0可能是重置同步相关的ID等信息
values.put(NoteColumns.SYNC_ID, 0); values.put(NoteColumns.SYNC_ID, 0);
// 使用内容解析器getContentResolver更新Notes.CONTENT_NOTE_URI对应的内容将上述设置的值进行更新条件为null可能是更新所有符合该URI的记录
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
} }
}).start(); }).start();
} }
// 获取当前设置的同步账户名称从应用的共享偏好设置中读取对应键PREFERENCE_SYNC_ACCOUNT_NAME的值如果不存在则返回空字符串
public static String getSyncAccountName(Context context) { public static String getSyncAccountName(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE); Context.MODE_PRIVATE);
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
} }
// 设置上次同步时间,将指定的时间值保存到应用的共享偏好设置中
public static void setLastSyncTime(Context context, long time) { public static void setLastSyncTime(Context context, long time) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE); Context.MODE_PRIVATE);
@ -354,18 +469,24 @@ public class NotesPreferenceActivity extends PreferenceActivity {
editor.commit(); editor.commit();
} }
// 获取上次同步时间从应用的共享偏好设置中读取对应键PREFERENCE_LAST_SYNC_TIME的值如果不存在则返回0
public static long getLastSyncTime(Context context) { public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE); Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
} }
// 内部类继承自BroadcastReceiver用于接收与GTask同步服务相关的广播消息并进行相应的界面更新操作
private class GTaskReceiver extends BroadcastReceiver { private class GTaskReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
// 接收到广播后调用refreshUI方法更新界面显示例如加载账户偏好设置、同步按钮状态等
refreshUI(); refreshUI();
// 判断广播中携带的是否正在同步的额外信息键为GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING是否为true如果是则进行以下操作
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
// 通过ID查找并获取用于显示同步状态的TextView组件ID为R.id.prefenerece_sync_status_textview
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
syncStatus.setText(intent syncStatus.setText(intent
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));

@ -32,31 +32,37 @@ import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NoteEditActivity; import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListActivity; import net.micode.notes.ui.NotesListActivity;
// 抽象的笔记小部件提供者类,继承自 AppWidgetProvider
public abstract class NoteWidgetProvider extends AppWidgetProvider { public abstract class NoteWidgetProvider extends AppWidgetProvider {
// 投影数组,用于查询笔记小部件信息时指定要查询的列
public static final String [] PROJECTION = new String [] { public static final String [] PROJECTION = new String [] {
NoteColumns.ID, NoteColumns.ID,
NoteColumns.BG_COLOR_ID, NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET NoteColumns.SNIPPET
}; };
// 投影列的索引
public static final int COLUMN_ID = 0; public static final int COLUMN_ID = 0;
public static final int COLUMN_BG_COLOR_ID = 1; public static final int COLUMN_BG_COLOR_ID = 1;
public static final int COLUMN_SNIPPET = 2; public static final int COLUMN_SNIPPET = 2;
// 日志标记
private static final String TAG = "NoteWidgetProvider"; private static final String TAG = "NoteWidgetProvider";
// 当小部件被删除时调用的方法
@Override @Override
public void onDeleted(Context context, int[] appWidgetIds) { public void onDeleted(Context context, int[] appWidgetIds) {
// 创建一个 ContentValues 对象,将 WIDGET_ID 设为无效
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
// 遍历要删除的小部件 ID 数组
for (int i = 0; i < appWidgetIds.length; i++) { for (int i = 0; i < appWidgetIds.length; i++) {
// 通过 ContentResolver 更新笔记信息,将对应小部件 ID 的 WIDGET_ID 设为无效
context.getContentResolver().update(Notes.CONTENT_NOTE_URI, context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
values, values,
NoteColumns.WIDGET_ID + "=?", NoteColumns.WIDGET_ID + "=?",
new String[] { String.valueOf(appWidgetIds[i])}); new String[] { String.valueOf(appWidgetIds[i])});
} }
} }
// 获取笔记小部件信息的私有方法,通过 ContentResolver 查询
private Cursor getNoteWidgetInfo(Context context, int widgetId) { private Cursor getNoteWidgetInfo(Context context, int widgetId) {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION, PROJECTION,
@ -64,34 +70,45 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null); null);
} }
// 更新小部件的方法,调用另一个重载的 update 方法
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false); update(context, appWidgetManager, appWidgetIds, false);
} }
// 真正执行更新小部件的私有方法
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) { boolean privacyMode) {
// 遍历要更新的小部件 ID 数组
for (int i = 0; i < appWidgetIds.length; i++) { for (int i = 0; i < appWidgetIds.length; i++) {
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {
// 获取默认背景颜色 ID
int bgId = ResourceParser.getDefaultBgId(context); int bgId = ResourceParser.getDefaultBgId(context);
// 初始化 snippet 为一个空字符串
String snippet = ""; String snippet = "";
// 创建一个 Intent指向 NoteEditActivity
Intent intent = new Intent(context, NoteEditActivity.class); Intent intent = new Intent(context, NoteEditActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
// 向 Intent 中添加小部件 ID 和小部件类型信息
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType());
// 获取小部件的笔记信息
Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]);
if (c != null && c.moveToFirst()) { if (c != null && c.moveToFirst()) {
if (c.getCount() > 1) { if (c.getCount() > 1) {
// 如果查询结果数量大于 1打印错误日志并关闭游标
Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]);
c.close(); c.close();
return; return;
} }
// 获取 snippet 信息
snippet = c.getString(COLUMN_SNIPPET); snippet = c.getString(COLUMN_SNIPPET);
// 获取背景颜色 ID
bgId = c.getInt(COLUMN_BG_COLOR_ID); bgId = c.getInt(COLUMN_BG_COLOR_ID);
// 向 Intent 中添加笔记的唯一标识符
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID));
// 设置 Intent 的动作
intent.setAction(Intent.ACTION_VIEW); intent.setAction(Intent.ACTION_VIEW);
} else { } else {
// 如果没有找到笔记信息,设置默认的 snippet 信息并设置 Intent 动作
snippet = context.getResources().getString(R.string.widget_havenot_content); snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
} }
@ -99,34 +116,37 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
if (c != null) { if (c != null) {
c.close(); c.close();
} }
// 创建一个 RemoteViews 对象,使用抽象方法 getLayoutId 获取布局 ID
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
// 设置小部件背景图片资源,使用抽象方法 getBgResourceId 获取资源 ID
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
// 向 Intent 中添加背景颜色 ID 信息
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
/** // 创建一个 PendingIntent根据隐私模式不同创建不同的 PendingIntent
* Generate the pending intent to start host for the widget
*/
PendingIntent pendingIntent = null; PendingIntent pendingIntent = null;
if (privacyMode) { if (privacyMode) {
// 在隐私模式下,设置文本视图内容并创建一个指向 NotesListActivity 的 PendingIntent
rv.setTextViewText(R.id.widget_text, rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode)); context.getString(R.string.widget_under_visit_mode));
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
} else { } else {
// 正常模式下,设置文本视图内容为 snippet 并创建一个指向 NoteEditActivity 的 PendingIntent
rv.setTextViewText(R.id.widget_text, snippet); rv.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent.FLAG_UPDATE_CURRENT);
} }
// 为小部件的文本视图设置点击事件的 PendingIntent
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
// 更新小部件
appWidgetManager.updateAppWidget(appWidgetIds[i], rv); appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
} }
} }
} }
// 抽象方法,用于获取背景资源 ID需要子类实现
protected abstract int getBgResourceId(int bgId); protected abstract int getBgResourceId(int bgId);
// 抽象方法,用于获取布局 ID需要子类实现
protected abstract int getLayoutId(); protected abstract int getLayoutId();
// 抽象方法,用于获取小部件类型,需要子类实现
protected abstract int getWidgetType(); protected abstract int getWidgetType();
} }

@ -23,25 +23,30 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes; import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser; import net.micode.notes.tool.ResourceParser;
// 继承自 NoteWidgetProvider 的 2x 笔记小部件提供者类
public class NoteWidgetProvider_2x extends NoteWidgetProvider { public class NoteWidgetProvider_2x extends NoteWidgetProvider {
// 当小部件更新时调用此方法
@Override @Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的 update 方法进行更新操作
super.update(context, appWidgetManager, appWidgetIds); super.update(context, appWidgetManager, appWidgetIds);
} }
// 实现父类的抽象方法,获取布局 ID
@Override @Override
protected int getLayoutId() { protected int getLayoutId() {
// 返回 2x 小部件的布局资源 ID
return R.layout.widget_2x; return R.layout.widget_2x;
} }
// 实现父类的抽象方法,获取背景资源 ID
@Override @Override
protected int getBgResourceId(int bgId) { protected int getBgResourceId(int bgId) {
// 使用 ResourceParser 的 WidgetBgResources 类获取 2x 小部件的背景资源 ID
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
} }
// 实现父类的抽象方法,获取小部件类型
@Override @Override
protected int getWidgetType() { protected int getWidgetType() {
// 返回 2x 小部件的类型
return Notes.TYPE_WIDGET_2X; return Notes.TYPE_WIDGET_2X;
} }
} }

@ -23,24 +23,29 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes; import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser; import net.micode.notes.tool.ResourceParser;
// 继承自 NoteWidgetProvider 的 4x 笔记小部件提供者类
public class NoteWidgetProvider_4x extends NoteWidgetProvider { public class NoteWidgetProvider_4x extends NoteWidgetProvider {
// 当小部件更新时调用此方法
@Override @Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的 update 方法进行更新操作
super.update(context, appWidgetManager, appWidgetIds); super.update(context, appWidgetManager, appWidgetIds);
} }
// 实现父类的抽象方法,获取布局 ID
protected int getLayoutId() { protected int getLayoutId() {
// 返回 4x 小部件的布局资源 ID
return R.layout.widget_4x; return R.layout.widget_4x;
} }
// 实现父类的抽象方法,获取背景资源 ID
@Override @Override
protected int getBgResourceId(int bgId) { protected int getBgResourceId(int bgId) {
// 使用 ResourceParser 的 WidgetBgResources 类获取 4x 小部件的背景资源 ID
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
} }
// 实现父类的抽象方法,获取小部件类型
@Override @Override
protected int getWidgetType() { protected int getWidgetType() {
// 返回 4x 小部件的类型
return Notes.TYPE_WIDGET_4X; return Notes.TYPE_WIDGET_4X;
} }
} }

Loading…
Cancel
Save