Compare commits

..

12 Commits
dcy ... develop

Author SHA1 Message Date
董春阳 8aefa300e7 代码功能优化
1 year ago
董春阳 b78b7b37e8 Merge branch 'dcy' into develop
1 year ago
董春阳 8a2ce58c35 添加注释
1 year ago
董春阳 776397ce4a 添加注释,添加错误捕获2
1 year ago
董春阳 37855e16eb 添加注释,添加错误捕获2
1 year ago
董春阳 db1d6b6274 Merge branch 'dcy' into develop
1 year ago
dcy 6817cff036 Merge branch 'cdy' into develop
1 year ago
曹登阳 866d114870 Merge branch 'cdy' of https://bdgit.educoder.net/p5fnrwyt6/micode
1 year ago
曹登阳 79147a1c50 曹登阳 ui widget注释
1 year ago
dcy f3a327d2ad Merge branch 'lcb' into develop
1 year ago
lcb dc844242a3 李长宝model,tool代码注释
1 year ago
dcy 43e6e18fa7 2024/11/30 加注解
1 year ago

@ -1,25 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!--
版权声明:
Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net)
开源协议说明: <!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
本文件遵循Apache License, Version 2.0(“许可协议”)。
您只有在遵守许可协议的情况下才能使用本文件。
您可以通过以下链接获取许可协议的副本:
http://www.apache.org/licenses/LICENSE-2.0
许可协议的使用限制: Licensed under the Apache License, Version 2.0 (the "License");
除非适用法律要求或以书面形式达成协议,否则根据许可协议分发的软件按“原样”分发, you may not use this file except in compliance with the License.
不附带任何形式的明示或暗示的保证或条件。 You may obtain a copy of the License at
请查看许可协议,了解具体的权限和限制规定。
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--> -->
<!-- 定义一个颜色选择器xmlns:android指定了Android命名空间 -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#88555555" /> <item android:state_pressed="true" android:color="#88555555" />
<item android:state_selected="true" android:color="#ff999999" /> <item android:state_selected="true" android:color="#ff999999" />
<item android:color="#ff000000" /> <item android:color="#ff000000" />
</selector> </selector>

@ -14,22 +14,17 @@
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.
--> -->
<!-- 定义一个偏好设置屏幕xmlns:android指定了Android资源的命名空间 -->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 定义一个偏好设置类别,其键为"pref_sync_account_key",该类别目前未包含具体的偏好设置项 --> <PreferenceScreen
<PreferenceCategory android:key="pref_sync_account_key"> xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="pref_sync_account_key">
</PreferenceCategory> </PreferenceCategory>
<!-- 定义另一个偏好设置类别,未指定键(在某些情况下,未指定键可能表示一个通用的分组) -->
<PreferenceCategory> <PreferenceCategory>
<!-- 定义一个复选框偏好设置项 -->
<CheckBoxPreference <CheckBoxPreference
<!-- 设置该复选框偏好设置项的唯一标识键 -->
android:key="pref_key_bg_random_appear" android:key="pref_key_bg_random_appear"
<!-- 设置该复选框偏好设置项在界面上显示的标题,通过字符串资源引用 -->
android:title="@string/preferences_bg_random_appear_title" android:title="@string/preferences_bg_random_appear_title"
<!-- 设置该复选框偏好设置项的默认值为false -->
android:defaultValue="false" /> android:defaultValue="false" />
</PreferenceCategory> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>

@ -40,10 +40,20 @@ import java.util.LinkedHashMap;
// 导入java.util.LinkedHashMap类它继承自HashMap // 导入java.util.LinkedHashMap类它继承自HashMap
// 并维护插入顺序或访问顺序,在代码中用于实现联系人缓存,以便按插入顺序清理较旧的缓存项 // 并维护插入顺序或访问顺序,在代码中用于实现联系人缓存,以便按插入顺序清理较旧的缓存项
import java.util.Map; import java.util.Map;
// 导入java.util.Map接口它是一个用于存储键值对的集合接口HashMap和LinkedHashMap都实现了该接口
// Contact类负责处理与联系人相关的操作主要功能是根据电话号码获取联系人姓名并实现了缓存机制以提高查询效率 import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.LinkedHashMap;
import java.util.Map;
// Contact类用于通过电话号码获取联系人姓名并且实现了一个简单的缓存机制
public class Contact { public class Contact {
// 定义最大缓存数量此值可依据实际需求调整当前设定为最多缓存100个联系人信息 // 定义最大缓存数量,可根据实际情况调整,这里假设最多缓存100个联系人信息
private static final int MAX_CACHE_SIZE = 100; private static final int MAX_CACHE_SIZE = 100;
// 使用LinkedHashMap来实现缓存它能维持元素的插入顺序便于后续按顺序清理较旧的缓存项 // 使用LinkedHashMap来实现缓存它能维持元素的插入顺序便于后续按顺序清理较旧的缓存项
private static LinkedHashMap<String, String> sContactCache; private static LinkedHashMap<String, String> sContactCache;

@ -18,240 +18,241 @@ package net.micode.notes.data;
import android.net.Uri; import android.net.Uri;
// 定义一个名为Notes的公共类用于管理笔记相关的常量和接口 // 定义一个名为Notes的公共类用于管理笔记相关的常量和接口
// Notes类定义了与笔记应用相关的各种常量、接口和内部类用于管理笔记、文件夹、数据等信息
public class Notes { public class Notes {
// 内容提供者的权限名称,用于在应用内标识该内容提供者 // 内容提供者的权限名称,用于在应用内标识该内容提供者,其他组件通过此名称与对应的内容提供者进行交互
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 * ID
* {@link Notes#ID_ROOT_FOLDER } * {@link Notes#ID_ROOT_FOLDER }
* {@link Notes#ID_TEMPARAY_FOLDER } * {@link Notes#ID_TEMPARAY_FOLDER }
* {@link Notes#ID_CALL_RECORD_FOLDER} * {@link Notes#ID_CALL_RECORD_FOLDER}
*/ */
// 根文件夹的ID // 根文件夹的ID,作为整个文件夹结构的根节点
public static final int ID_ROOT_FOLDER = 0; public static final int ID_ROOT_FOLDER = 0;
// 临时文件夹的ID用于存放不属于任何文件夹的笔记 // 临时文件夹的ID用于存放不属于任何文件夹的笔记,方便对孤立笔记进行管理
public static final int ID_TEMPARAY_FOLDER = -1; public static final int ID_TEMPARAY_FOLDER = -1;
// 通话记录文件夹的ID // 通话记录文件夹的ID,用于标识存储通话记录的特定文件夹
public static final int ID_CALL_RECORD_FOLDER = -2; public static final int ID_CALL_RECORD_FOLDER = -2;
// 回收站文件夹的ID // 回收站文件夹的ID,用于标识存放已删除笔记或文件夹的回收站
public static final int ID_TRASH_FOLER = -3; public static final int ID_TRASH_FOLER = -3;
// 用于在Intent中传递提醒日期的额外字段名称 // 用于在Intent中传递提醒日期的额外字段名称当通过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";
// 用于在Intent中传递背景颜色ID的额外字段名称 // 用于在Intent中传递背景颜色ID的额外字段名称通过此名称在Intent中传递笔记的背景颜色ID
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";
// 用于在Intent中传递小部件ID的额外字段名称 // 用于在Intent中传递小部件ID的额外字段名称借助此名称在Intent中携带小部件的ID信息
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中传递小部件类型的额外字段名称 // 用于在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";
// 用于在Intent中传递文件夹ID的额外字段名称 // 用于在Intent中传递文件夹ID的额外字段名称通过此名称在Intent中传递文件夹的ID
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中传递通话日期的额外字段名称 // 用于在Intent中传递通话日期的额外字段名称当涉及通话记录相关的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尺寸的小部件类型 // 2x尺寸的小部件类型,定义了一种常见的小部件尺寸类型
public static final int TYPE_WIDGET_2X = 0; public static final int TYPE_WIDGET_2X = 0;
// 4x尺寸的小部件类型 // 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 {
// 文本笔记的内容类型 // 文本笔记的内容类型,用于标识文本笔记在数据存储和传输中的类型
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
// 通话笔记的内容类型 // 通话笔记的内容类型,用于标识通话笔记在数据存储和传输中的类型
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
} }
/** /**
* Uri * UriUri
*/ */
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 * UriUri
*/ */
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 * ID
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String ID = "_id"; public static final String ID = "_id";
/** /**
* ID * ID
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String PARENT_ID = "parent_id"; public static final String PARENT_ID = "parent_id";
/** /**
* *
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String CREATED_DATE = "created_date"; public static final String CREATED_DATE = "created_date";
/** /**
* *
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String MODIFIED_DATE = "modified_date"; public static final String MODIFIED_DATE = "modified_date";
/** /**
* *
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String ALERTED_DATE = "alert_date"; public static final String ALERTED_DATE = "alert_date";
/** /**
* *
* <P> : TEXT </P> * <P> : TEXT </P>
*/ */
public static final String SNIPPET = "snippet"; public static final String SNIPPET = "snippet";
/** /**
* ID * ID
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String WIDGET_ID = "widget_id"; public static final String WIDGET_ID = "widget_id";
/** /**
* *
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String WIDGET_TYPE = "widget_type"; public static final String WIDGET_TYPE = "widget_type";
/** /**
* ID * ID
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String BG_COLOR_ID = "bg_color_id"; public static final String BG_COLOR_ID = "bg_color_id";
/** /**
* *
* <P> : INTEGER </P> * <P> : INTEGER </P>
*/ */
public static final String HAS_ATTACHMENT = "has_attachment"; public static final String HAS_ATTACHMENT = "has_attachment";
/** /**
* *
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String NOTES_COUNT = "notes_count"; public static final String NOTES_COUNT = "notes_count";
/** /**
* *
* <P> : INTEGER </P> * <P> : INTEGER </P>
*/ */
public static final String TYPE = "type"; public static final String TYPE = "type";
/** /**
* ID * ID
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String SYNC_ID = "sync_id"; public static final String SYNC_ID = "sync_id";
/** /**
* *
* <P> : INTEGER </P> * <P> : INTEGER </P>
*/ */
public static final String LOCAL_MODIFIED = "local_modified"; public static final String LOCAL_MODIFIED = "local_modified";
/** /**
* ID * IDID
* <P> : INTEGER </P> * <P> : INTEGER </P>
*/ */
public static final String ORIGIN_PARENT_ID = "origin_parent_id"; public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/** /**
* gtaskID * gtaskIDgtask
* <P> : TEXT </P> * <P> : TEXT </P>
*/ */
public static final String GTASK_ID = "gtask_id"; public static final String GTASK_ID = "gtask_id";
/** /**
* *
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String VERSION = "version"; public static final String VERSION = "version";
} }
// 数据列的接口,定义了与数据相关的数据库列名 // 数据列的接口,定义了与数据相关的数据库列名,这些列名用于在数据库中存储和检索与笔记相关的具体数据
public interface DataColumns { public interface DataColumns {
/** /**
* ID * ID
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String ID = "_id"; public static final String ID = "_id";
/** /**
* MIME * MIME
* <P> : Text </P> * <P> : Text </P>
*/ */
public static final String MIME_TYPE = "mime_type"; public static final String MIME_TYPE = "mime_type";
/** /**
* ID * ID
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String NOTE_ID = "note_id"; public static final String NOTE_ID = "note_id";
/** /**
* *
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String CREATED_DATE = "created_date"; public static final String CREATED_DATE = "created_date";
/** /**
* *
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String MODIFIED_DATE = "modified_date"; public static final String MODIFIED_DATE = "modified_date";
/** /**
* *
* <P> : TEXT </P> * <P> : TEXT </P>
*/ */
public static final String CONTENT = "content"; public static final String CONTENT = "content";
/** /**
* {@link #MIMETYPE} * {@link #MIMETYPE}MIME
* <P> : INTEGER </P> * <P> : INTEGER </P>
*/ */
public static final String DATA1 = "data1"; public static final String DATA1 = "data1";
/** /**
* {@link #MIMETYPE} * {@link #MIMETYPE}MIME
* <P> : INTEGER </P> * <P> : INTEGER </P>
*/ */
public static final String DATA2 = "data2"; public static final String DATA2 = "data2";
/** /**
* {@link #MIMETYPE} * {@link #MIMETYPE}MIME
* <P> : TEXT </P> * <P> : TEXT </P>
*/ */
public static final String DATA3 = "data3"; public static final String DATA3 = "data3";
/** /**
* {@link #MIMETYPE} * {@link #MIMETYPE}MIME
* <P> : TEXT </P> * <P> : TEXT </P>
*/ */
public static final String DATA4 = "data4"; public static final String DATA4 = "data4";
/** /**
* {@link #MIMETYPE} * {@link #MIMETYPE}MIME
* <P> : TEXT </P> * <P> : TEXT </P>
*/ */
public static final String DATA5 = "data5"; public static final String DATA5 = "data5";
@ -265,42 +266,40 @@ public class Notes {
*/ */
public static final String MODE = DATA1; public static final String MODE = DATA1;
// 复选框列表模式的模式值 // 复选框列表模式的模式值当MODE的值为1时表示文本处于复选框列表模式
public static final int MODE_CHECK_LIST = 1; public static final int MODE_CHECK_LIST = 1;
// 文本笔记的内容类型(目录类型) // 文本笔记的内容类型(目录类型),用于标识文本笔记在数据存储和传输中的目录类型
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
// 文本笔记的内容类型(项目类型) // 文本笔记的内容类型(项目类型),用于标识文本笔记在数据存储和传输中的项目类型
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";
// 文本笔记的内容Uri // 文本笔记的内容Uri通过此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接口定义了通话笔记相关的常量 // 通话笔记的静态内部类实现了DataColumns接口定义了通话笔记相关的常量
public static final class CallNote implements DataColumns { public static final class CallNote implements DataColumns {
/** /**
* *
* <P> : INTEGER (long) </P> * <P> : INTEGER (long) </P>
*/ */
public static final String CALL_DATE = DATA1; public static final String CALL_DATE = DATA1;
/** /**
* *
* <P> : TEXT </P> * <P> : TEXT </P>
*/ */
public static final String PHONE_NUMBER = DATA3; public static final String PHONE_NUMBER = DATA3;
// 通话笔记的内容类型(目录类型) // 通话笔记的内容类型(目录类型),用于标识通话笔记在数据存储和传输中的目录类型
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
// 通话笔记的内容类型(项目类型) // 通话笔记的内容类型(项目类型),用于标识通话笔记在数据存储和传输中的项目类型
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
// 通话笔记的内容Uri // 通话笔记的内容Uri通过此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");
} }
} }
//这段 Notes 类代码整体构建了一个较为完善的笔记相关数据管理的基础框架,涵盖了从基本的类型定义、文件夹标识、用于数据传递
// 的键名、数据操作的 Uri 到具体的笔记(文本笔记和通话记录笔记)的数据结构和字段定义等多个方面。

@ -14,42 +14,17 @@
* limitations under the License. * limitations under the License.
*/ */
// 声明该Java类属于net.micode.notes.data包此包通常用于存放与笔记应用数据处理相关的代码
package net.micode.notes.data; package net.micode.notes.data;
import android.content.ContentValues; import android.content.ContentValues;
// 导入ContentValues类它用于在Android数据库操作中
// 以键值对的形式存储要插入或更新的数据,例如在向数据库表中插入新记录时使用
import android.content.Context; import android.content.Context;
// 导入Context类它是Android应用程序环境的全局信息接口
// 可以用于获取应用资源、启动组件、访问系统服务等,
// 在数据库操作中通常用于创建SQLiteOpenHelper实例等
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
// 导入SQLiteDatabase类它是Android中用于管理SQLite数据库的主要类
// 提供了执行SQL语句、插入、查询、更新和删除数据等方法
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
// 导入SQLiteOpenHelper类它是一个辅助类用于管理SQLite数据库的创建、版本升级等操作
// 通过继承该类,可以自定义数据库的创建和升级逻辑
import android.util.Log; import android.util.Log;
// 导入Log类用于在Android应用中记录日志信息方便开发人员调试和排查问题
// 可以输出不同级别的日志,如错误、警告、信息等
import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataColumns;
// 导入自定义的DataColumns类该类可能定义了与笔记数据相关的列名常量
// 在数据库操作中用于指定具体的列,例如在查询、插入、更新数据时使用
import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.DataConstants;
// 导入自定义的DataConstants类该类可能定义了与笔记数据相关的常量
// 比如数据类型、特定数据标识等,用于在数据处理逻辑中进行判断和操作
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
// 导入自定义的NoteColumns类该类可能定义了与笔记相关的列名常量
// 与DataColumns类可能有所区别专门用于笔记相关的数据库操作
// 例如在创建笔记表、查询笔记数据时使用
// 数据库帮助类,用于管理笔记应用的数据库创建、升级等操作 // 数据库帮助类,用于管理笔记应用的数据库创建、升级等操作
public class NotesDatabaseHelper extends SQLiteOpenHelper { public class NotesDatabaseHelper extends SQLiteOpenHelper {
@ -92,6 +67,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" +
")"; ")";
// 创建笔记表定义了笔记的各种属性列如ID、父ID、提醒日期、背景颜色ID等设置了默认值并指定ID为主键
// 创建数据表的SQL语句 // 创建数据表的SQL语句
private static final String CREATE_DATA_TABLE_SQL = private static final String CREATE_DATA_TABLE_SQL =
@ -108,11 +84,13 @@ 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 ''" +
")"; ")";
// 创建数据表定义了与笔记相关数据的列如ID、MIME类型、所属笔记ID、创建和修改日期、内容及其他通用数据列设置ID为主键
// 创建数据DATA表中根据笔记IDNOTE_ID的索引SQL语句 // 创建数据DATA表中根据笔记IDNOTE_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 + ");";
// 创建一个索引方便根据笔记ID快速查询相关数据
/** /**
* *
@ -125,6 +103,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";
// 创建一个触发器当笔记的父ID更新即移动到新文件夹增加目标文件夹的笔记数量
/** /**
* *
@ -138,6 +117,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";
// 创建一个触发器当笔记的父ID更新即从文件夹移出减少原文件夹的笔记数量但确保数量不小于0
/** /**
* *
@ -150,6 +130,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";
// 创建一个触发器,当新笔记插入时,增加其所属文件夹的笔记数量
/** /**
* *
@ -163,6 +144,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";
// 创建一个触发器当笔记被删除时减少其所属文件夹的笔记数量但确保数量不小于0
/** /**
* {@link DataConstants#NOTE} * {@link DataConstants#NOTE}
@ -176,6 +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";
// 创建一个触发器当插入特定类型DataConstants#NOTE的数据时更新对应笔记的内容
/** /**
* {@link DataConstants#NOTE} * {@link DataConstants#NOTE}
@ -189,6 +172,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";
// 创建一个触发器当特定类型DataConstants#NOTE的数据更新时更新对应笔记的内容
/** /**
* {@link DataConstants#NOTE} * {@link DataConstants#NOTE}
@ -202,6 +186,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";
// 创建一个触发器当特定类型DataConstants#NOTE的数据被删除时清空对应笔记的内容
/** /**
* *
@ -213,6 +198,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";
// 创建一个触发器,当笔记被删除时,删除与之关联的数据
/** /**
* *
@ -224,6 +210,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";
// 创建一个触发器,当文件夹被删除时,删除该文件夹下的所有笔记
/** /**
* *
@ -237,6 +224,7 @@ 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";
// 创建一个触发器,当文件夹被移动到回收站时,将该文件夹下的所有笔记也移动到回收站
// 构造函数,用于创建数据库帮助类实例 // 构造函数,用于创建数据库帮助类实例
public NotesDatabaseHelper(Context context) { public NotesDatabaseHelper(Context context) {
@ -250,6 +238,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
createSystemFolder(db); createSystemFolder(db);
Log.d(TAG, "note table has been created"); Log.d(TAG, "note table has been created");
} }
// 执行创建笔记表的SQL语句重新创建相关触发器并创建系统文件夹记录日志表示笔记表创建完成
// 重新创建笔记表的触发器 // 重新创建笔记表的触发器
private void reCreateNoteTableTriggers(SQLiteDatabase db) { private void reCreateNoteTableTriggers(SQLiteDatabase db) {
@ -269,6 +258,7 @@ 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) {
@ -305,6 +295,7 @@ 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);
} }
// 创建各种系统文件夹,如通话记录文件夹、根文件夹、临时文件夹和回收站文件夹
// 创建数据表 // 创建数据表
public void createDataTable(SQLiteDatabase db) { public void createDataTable(SQLiteDatabase db) {
@ -313,6 +304,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
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");
} }
// 执行创建数据表的SQL语句重新创建相关触发器创建索引并记录日志表示数据表创建完成
// 重新创建数据表的触发器 // 重新创建数据表的触发器
private void reCreateDataTableTriggers(SQLiteDatabase db) { private void reCreateDataTableTriggers(SQLiteDatabase db) {
@ -324,6 +316,7 @@ 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);
} }
// 删除旧的数据表触发器,然后重新创建它们,以确保触发器的一致性和正确性
// 获取单例实例 // 获取单例实例
static synchronized NotesDatabaseHelper getInstance(Context context) { static synchronized NotesDatabaseHelper getInstance(Context context) {
@ -332,6 +325,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
} }
return mInstance; return mInstance;
} }
// 通过单例模式获取`NotesDatabaseHelper`实例,确保全局只有一个实例
// 当数据库首次创建时调用 // 当数据库首次创建时调用
@Override @Override
@ -339,6 +333,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
createNoteTable(db); createNoteTable(db);
createDataTable(db); createDataTable(db);
} }
// 数据库首次创建时,调用创建笔记表和数据表的方法
// 当数据库版本升级时调用 // 当数据库版本升级时调用
@Override @Override

@ -16,38 +16,25 @@
package net.micode.notes.data; package net.micode.notes.data;
// 导入Android搜索管理器相关类用于处理搜索功能例如搜索建议、搜索结果展示等
import android.app.SearchManager; import android.app.SearchManager;
// 导入内容提供者类ContentProvider是Android四大组件之一用于在不同应用间共享数据
import android.content.ContentProvider; import android.content.ContentProvider;
// 导入用于操作Uri的工具类可用于在Uri末尾追加ID等操作常用于构建特定数据项的Uri
import android.content.ContentUris; import android.content.ContentUris;
// 导入用于存储键值对数据的类,常用于在数据库操作中表示要插入或更新的数据
import android.content.ContentValues; import android.content.ContentValues;
// 导入意图类Intent用于在Android组件如Activity、Service等之间传递消息启动组件等
import android.content.Intent; import android.content.Intent;
// 导入Uri匹配器类用于匹配不同的Uri模式以确定执行何种操作例如不同的数据库查询、插入等操作
import android.content.UriMatcher; import android.content.UriMatcher;
// 导入用于遍历数据库查询结果集的类,通过它可以获取查询结果中的数据
import android.database.Cursor; import android.database.Cursor;
// 导入SQLite数据库管理类用于执行SQL语句、管理数据库事务等操作
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
// 导入统一资源标识符类用于唯一标识内容提供者中的数据不同的Uri对应不同的数据操作
import android.net.Uri; import android.net.Uri;
// 导入用于处理文本相关工具方法的类,例如判断字符串是否为空等操作
import android.text.TextUtils; import android.text.TextUtils;
// 导入日志记录类,用于在开发过程中记录调试、错误等信息,方便排查问题
import android.util.Log; import android.util.Log;
// 导入应用资源类R类包含了应用中所有资源的引用例如字符串、布局、图片等资源
import net.micode.notes.R; import net.micode.notes.R;
// 导入自定义的数据列类,该类可能定义了与笔记数据相关的列名常量,用于数据库操作
import net.micode.notes.data.Notes.DataColumns; 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;
// 内容提供者,用于管理笔记应用的数据访问 // 内容提供者,用于管理笔记应用的数据访问
// NotesProvider类是一个内容提供者用于管理笔记应用的数据访问提供了查询、插入、删除和更新数据的功能
public class NotesProvider extends ContentProvider { public class NotesProvider extends ContentProvider {
// 用于匹配Uri的UriMatcher // 用于匹配Uri的UriMatcher
private static final UriMatcher mMatcher; private static final UriMatcher mMatcher;

@ -16,71 +16,51 @@
package net.micode.notes.gtask.data; package net.micode.notes.gtask.data;
// 导入android.database.Cursor类用于遍历和操作数据库查询结果集。
// 在Android开发中当执行数据库查询操作如SQLite数据库的查询
// 会返回一个Cursor对象通过它可以逐行读取查询结果中的数据列值。
import android.database.Cursor; import android.database.Cursor;
// 导入android.util.Log类用于在Android应用开发中记录日志信息。
// 开发人员可以使用不同级别的日志方法如Log.d、Log.e、Log.i等
// 来输出调试信息、错误信息、一般信息等,有助于排查问题和监控程序运行状态。
import android.util.Log; import android.util.Log;
// 导入自定义的GTaskStringUtils类该类位于net.micode.notes.tool包下。
// 推测这个类提供了与GTask相关的字符串处理工具方法
// 例如字符串格式化、解析或其他与GTask业务逻辑相关的字符串操作。
import net.micode.notes.tool.GTaskStringUtils; import net.micode.notes.tool.GTaskStringUtils;
// 导入JSONException类这是在处理JSON数据时可能抛出的异常类。
// 当JSON数据格式不正确、解析过程中出现错误如JSON字符串不符合语法规则
// 或者在JSON对象操作如获取不存在的键值对可能会抛出该异常。
import org.json.JSONException; import org.json.JSONException;
// 导入JSONObject类用于表示和操作JSON对象。
// 在Android开发中常用于处理从网络获取的JSON数据
// 可以通过该类创建JSON对象、设置和获取对象中的键值对以及进行JSON数据的序列化和反序列化操作。
import org.json.JSONObject; import org.json.JSONObject;
// MetaData类继承自Task类主要用于处理与任务相关的元数据操作例如设置、获取元数据以及和JSON数据进行交互等 /**
* MetaDataTask
* JSON
*/
public class MetaData extends Task { public class MetaData extends Task {
// 定义一个私有静态常量TAG其值为类的简单名称用于在日志记录时标识当前类方便查看日志输出确定是该类产生的相关信息 private static final String TAG = MetaData.class.getSimpleName();
private final static String TAG = MetaData.class.getSimpleName(); private String relatedGid = null;
// 用于存储与元数据相关的Gid从代码上下文推测可能是和任务相关的某种唯一标识符初始值为null
private String mRelatedGid = null;
/** /**
* GidJSONObject *
* *
* @param gid Gid * @param gid Gid
* @param metaInfo JSON * @param metaInfo JSON
*/ */
public void setMeta(String gid, JSONObject metaInfo) { public void setMeta(String gid, JSONObject metaInfo) {
try { try {
// 尝试将传入的gid值放入metaInfo这个JSON对象中对应的键是通过GTaskStringUtils.META_HEAD_GTASK_ID获取的
// 具体键值由这个工具类中的常量定义决定如果出现JSONException异常比如JSON对象的结构不符合要求等原因导致无法添加键值对则会记录错误日志
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, "failed to put related gid"); Log.e(TAG, "Failed to put related gid", e);
} }
// 将处理后的metaInfo对象转换为字符串并通过调用setNotes方法推测来自父类Task用于设置笔记相关内容此处把元数据信息当作笔记内容存储保存元数据信息
setNotes(metaInfo.toString()); setNotes(metaInfo.toString());
// 调用setName方法推测来自父类Task相关实现将名称设置为GTaskStringUtils.META_NOTE_NAME所定义的值完成元数据相关设置操作
setName(GTaskStringUtils.META_NOTE_NAME); setName(GTaskStringUtils.META_NOTE_NAME);
} }
/** /**
* Gid * Gid
* *
* @return mRelatedGidGid * @return Gid
*/ */
public String getRelatedGid() { public String getRelatedGid() {
return mRelatedGid; return relatedGid;
} }
/** /**
* TaskisWorthSaving *
* *
* @return nullnulltruefalse * @return nulltruefalse
*/ */
@Override @Override
public boolean isWorthSaving() { public boolean isWorthSaving() {
@ -88,55 +68,58 @@ public class MetaData extends Task {
} }
/** /**
* setContentByRemoteJSONJSON * JSON
* Gid
* *
* @param js JSONObjectJSON * @param js JSONObject
*/ */
@Override @Override
public void setContentByRemoteJSON(JSONObject js) { public void setContentByRemoteJSON(JSONObject js) {
// 首先调用父类的setContentByRemoteJSON方法让父类先进行一些通用的或者默认的基于远程JSON数据的内容设置操作
super.setContentByRemoteJSON(js); super.setContentByRemoteJSON(js);
if (getNotes()!= null) { if (getNotes()!= null) {
try { try {
// 如果通过getNotes方法获取的笔记内容不为null则尝试从这个笔记内容先去除两端空白字符后转换为JSONObject中获取对应的Gid值
JSONObject metaInfo = new JSONObject(getNotes().trim()); JSONObject metaInfo = new JSONObject(getNotes().trim());
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); relatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) { } catch (JSONException e) {
// 如果在获取Gid值过程中出现JSONException异常比如解析JSON对象获取对应键的值失败等情况则记录一条警告级别的日志信息表明获取相关Gid失败并将mRelatedGid设置为null Log.w(TAG, "Failed to get related gid", e);
Log.w(TAG, "failed to get related gid"); relatedGid = null;
mRelatedGid = null;
} }
} }
} }
/** /**
* setContentByLocalJSONIllegalAccessError *
* 使@Deprecated使
* *
* @param js * @param js
*/ */
@Override @Override
@Deprecated
public void setContentByLocalJSON(JSONObject js) { public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
} }
/** /**
* getLocalJSONFromContentIllegalAccessError *
* 使@Deprecated使
* *
* @return * @return
*/ */
@Override @Override
@Deprecated
public JSONObject getLocalJSONFromContent() { public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
} }
/** /**
* getSyncActionIllegalAccessError *
* 使@Deprecated使
* *
* @param c c * @param c
* @return * @return
*/ */
@Override @Override
@Deprecated
public int getSyncAction(Cursor c) { public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called"); throw new IllegalAccessError("MetaData:getSyncAction should not be called");
} }

@ -15,109 +15,161 @@
*/ */
package net.micode.notes.gtask.data; package net.micode.notes.gtask.data;
// 导入android.database.Cursor类该类在Android开发中用于处理数据库查询结果。
// 当执行数据库查询操作例如针对SQLite数据库的查询会返回一个Cursor对象。
// 通过这个对象,你可以遍历结果集,获取每一行数据以及相应列的值,从而实现对数据库查询结果的处理和使用。
import android.database.Cursor;
// 导入org.json.JSONObject类它是JSONJavaScript Object Notation库中的一部分。 import android.database.Cursor;
// 在Android开发中常用于处理JSON格式的数据。可以使用JSONObject类创建JSON对象
// 对JSON对象进行各种操作如添加键值对、获取键对应的值、将JSON对象转换为字符串等
// 方便在不同系统和应用之间进行数据交换和处理。
import org.json.JSONObject; import org.json.JSONObject;
// 定义一个抽象类Node作为其他节点类的基类用于处理与Google Tasks同步相关的数据操作
/**
* NodeGoogle Tasks
*
*
*/
public abstract class Node { public abstract class Node {
// 表示无同步操作
// 同步操作类型常量
public static final int SYNC_ACTION_NONE = 0; public static final int SYNC_ACTION_NONE = 0;
// 表示在远程添加操作
public static final int SYNC_ACTION_ADD_REMOTE = 1; public static final int SYNC_ACTION_ADD_REMOTE = 1;
// 表示在本地添加操作
public static final int SYNC_ACTION_ADD_LOCAL = 2; public static final int SYNC_ACTION_ADD_LOCAL = 2;
// 表示在远程删除操作
public static final int SYNC_ACTION_DEL_REMOTE = 3; public static final int SYNC_ACTION_DEL_REMOTE = 3;
// 表示在本地删除操作
public static final int SYNC_ACTION_DEL_LOCAL = 4; public static final int SYNC_ACTION_DEL_LOCAL = 4;
// 表示在远程更新操作
public static final int SYNC_ACTION_UPDATE_REMOTE = 5; public static final int SYNC_ACTION_UPDATE_REMOTE = 5;
// 表示在本地更新操作
public static final int SYNC_ACTION_UPDATE_LOCAL = 6; public static final int SYNC_ACTION_UPDATE_LOCAL = 6;
// 表示更新冲突操作
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;
// 表示同步错误操作
public static final int SYNC_ACTION_ERROR = 8; public static final int SYNC_ACTION_ERROR = 8;
// 存储Google Tasks的唯一标识符 // Google Tasks的唯一标识符
private String mGid; private String gid;
// 存储节点名称 // 节点名称
private String mName; private String name;
// 存储最后修改时间 // 最后修改时间
private long mLastModified; private long lastModified;
// 表示该节点是否已被删除 // 表示该节点是否已被删除
private boolean mDeleted; private boolean deleted;
// 构造函数,初始化成员变量 /**
*
*/
public Node() { public Node() {
mGid = null; gid = null;
mName = ""; name = "";
mLastModified = 0; lastModified = 0;
mDeleted = false; deleted = false;
} }
// 获取创建操作的JSON对象根据传入的操作ID确定具体的创建操作 /**
* JSONID
*
* @param actionId ID
* @return JSONObject
*/
public abstract JSONObject getCreateAction(int actionId); public abstract JSONObject getCreateAction(int actionId);
// 获取更新操作的JSON对象根据传入的操作ID确定具体的更新操作 /**
* JSONID
*
* @param actionId ID
* @return JSONObject
*/
public abstract JSONObject getUpdateAction(int actionId); public abstract JSONObject getUpdateAction(int actionId);
// 根据从远程获取的JSON数据设置节点内容 /**
* JSON
*
* @param js JSONObject
*/
public abstract void setContentByRemoteJSON(JSONObject js); public abstract void setContentByRemoteJSON(JSONObject js);
// 根据本地生成的JSON数据设置节点内容 /**
* JSON
*
* @param js JSONObject
*/
public abstract void setContentByLocalJSON(JSONObject js); public abstract void setContentByLocalJSON(JSONObject js);
// 从节点内容生成本地JSON对象 /**
* JSON
*
* @return JSONObject
*/
public abstract JSONObject getLocalJSONFromContent(); public abstract JSONObject getLocalJSONFromContent();
// 根据数据库游标获取同步操作类型 /**
*
*
* @param c
* @return
*/
public abstract int getSyncAction(Cursor c); public abstract int getSyncAction(Cursor c);
// 设置Google Tasks的唯一标识符 /**
* Google Tasks
*
* @param gid Google Tasks
*/
public void setGid(String gid) { public void setGid(String gid) {
this.mGid = gid; this.gid = gid;
} }
// 设置节点名称 /**
*
*
* @param name
*/
public void setName(String name) { public void setName(String name) {
this.mName = name; this.name = name;
} }
// 设置最后修改时间 /**
*
*
* @param lastModified
*/
public void setLastModified(long lastModified) { public void setLastModified(long lastModified) {
this.mLastModified = lastModified; this.lastModified = lastModified;
} }
// 设置该节点是否已被删除 /**
*
*
* @param deleted
*/
public void setDeleted(boolean deleted) { public void setDeleted(boolean deleted) {
this.mDeleted = deleted; this.deleted = deleted;
} }
// 获取Google Tasks的唯一标识符 /**
* Google Tasks
*
* @return Google Tasks
*/
public String getGid() { public String getGid() {
return this.mGid; return gid;
} }
// 获取节点名称 /**
*
*
* @return
*/
public String getName() { public String getName() {
return this.mName; return name;
} }
// 获取最后修改时间 /**
*
*
* @return
*/
public long getLastModified() { public long getLastModified() {
return this.mLastModified; return lastModified;
} }
// 获取该节点是否已被删除 /**
*
*
* @return
*/
public boolean getDeleted() { public boolean getDeleted() {
return this.mDeleted; return deleted;
} }
} }

@ -16,63 +16,22 @@
package net.micode.notes.gtask.data; package net.micode.notes.gtask.data;
// 导入ContentResolver类用于在不同的ContentProvider之间进行数据交互
// 例如查询、插入、更新和删除ContentProvider提供的数据。
import android.content.ContentResolver; import android.content.ContentResolver;
// 导入ContentUris类提供了一些用于操作Uri的工具方法特别是与内容URI相关的操作
// 比如在内容URI的末尾追加ID从而获取特定记录的URI。
import android.content.ContentUris; import android.content.ContentUris;
// 导入ContentValues类用于在Android数据库操作中以键值对的形式存储要插入或更新的数据。
import android.content.ContentValues; import android.content.ContentValues;
// 导入Context类它是Android应用程序环境的全局信息接口
// 可用于获取应用资源、启动组件、访问系统服务等在数据库操作和ContentProvider交互中经常用到。
import android.content.Context; import android.content.Context;
// 导入Cursor类用于遍历和访问数据库查询结果集。通过Cursor可以逐行读取查询结果中的数据。
import android.database.Cursor; import android.database.Cursor;
// 导入Uri类用于唯一标识内容提供者中的数据不同的Uri对应不同的数据操作
// 在ContentProvider的交互中Uri用于指定操作的数据对象。
import android.net.Uri; import android.net.Uri;
// 导入Log类用于在Android应用开发中记录日志信息方便开发人员调试和排查问题
// 可以输出不同级别的日志,如错误、警告、信息等。
import android.util.Log; import android.util.Log;
// 导入自定义的Notes类该类可能包含与笔记相关的各种常量、方法或数据结构
// 用于整个笔记应用的数据处理和业务逻辑。
import net.micode.notes.data.Notes; import net.micode.notes.data.Notes;
// 导入自定义的DataColumns类该类可能定义了与笔记数据相关的列名常量
// 在数据库操作中用于指定具体的列,例如在查询、插入、更新数据时使用。
import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataColumns;
// 导入自定义的DataConstants类该类可能定义了与笔记数据相关的常量
// 比如数据类型、特定数据标识等,用于在数据处理逻辑中进行判断和操作。
import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.DataConstants;
// 导入自定义的NoteColumns类该类可能定义了与笔记相关的列名常量
// 与DataColumns类可能有所区别专门用于笔记相关的数据库操作
// 例如在创建笔记表、查询笔记数据时使用。
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
// 导入自定义的TABLE接口该接口定义了数据库中表的名称常量
// 来自NotesDatabaseHelper类用于在数据库操作中指定表名。
import net.micode.notes.data.NotesDatabaseHelper.TABLE; import net.micode.notes.data.NotesDatabaseHelper.TABLE;
// 导入自定义的异常类ActionFailureException
// 当执行某些操作失败时,可能会抛出该异常,用于在业务逻辑中处理异常情况。
import net.micode.notes.gtask.exception.ActionFailureException; import net.micode.notes.gtask.exception.ActionFailureException;
// 导入JSONException类在处理JSON数据时如果JSON数据格式不正确、解析过程中出现错误
// 或者在JSON对象操作如获取不存在的键值对可能会抛出该异常。
import org.json.JSONException; import org.json.JSONException;
// 导入JSONObject类用于表示和操作JSON对象。可以通过该类创建JSON对象、
// 设置和获取对象中的键值对以及进行JSON数据的序列化和反序列化操作。
import org.json.JSONObject; import org.json.JSONObject;
// SqlData类用于处理与数据库中数据记录相关的操作包括数据的加载、设置、提交等 // SqlData类用于处理与数据库中数据记录相关的操作包括数据的加载、设置、提交等

@ -16,134 +16,48 @@
package net.micode.notes.gtask.data; package net.micode.notes.gtask.data;
// 导入AppWidgetManager类用于管理Android应用小部件App Widget
// 例如更新小部件的布局、获取小部件的实例等操作。
import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetManager;
// 导入ContentResolver类用于在不同的ContentProvider之间进行数据交互
// 可以执行查询、插入、更新和删除ContentProvider提供的数据等操作。
import android.content.ContentResolver; import android.content.ContentResolver;
// 导入ContentValues类以键值对的形式存储数据常用于在数据库操作中表示要插入或更新的数据。
import android.content.ContentValues; import android.content.ContentValues;
// 导入Context类它是Android应用程序环境的全局信息接口
// 可以用于获取应用资源、启动组件、访问系统服务等各种操作。
import android.content.Context; import android.content.Context;
// 导入Cursor类用于遍历和操作数据库查询结果集通过它可以逐行读取查询结果中的数据。
import android.database.Cursor; import android.database.Cursor;
// 导入Uri类用于唯一标识内容提供者中的数据在ContentProvider的交互中
// 通过Uri指定要操作的数据对象。
import android.net.Uri; import android.net.Uri;
// 导入Log类用于在Android应用开发中记录日志信息方便开发人员调试和排查问题
// 可以输出不同级别的日志如错误Log.e、警告Log.w、信息Log.i等。
import android.util.Log; import android.util.Log;
// 导入自定义的Notes类该类可能包含与笔记相关的各种常量、方法或数据结构
// 用于整个笔记应用的数据处理和业务逻辑。
import net.micode.notes.data.Notes; import net.micode.notes.data.Notes;
// 导入自定义的DataColumns类该类可能定义了与笔记数据相关的列名常量
// 在数据库操作中用于指定具体的列,比如在查询、插入、更新数据时使用。
import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataColumns;
// 导入自定义的NoteColumns类该类可能定义了与笔记相关的列名常量
// 专门用于笔记相关的数据库操作,例如在创建笔记表、查询笔记数据时使用。
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
// 导入自定义的异常类ActionFailureException当执行某些操作失败时
// 可能会抛出该异常,用于在业务逻辑中处理异常情况。
import net.micode.notes.gtask.exception.ActionFailureException; import net.micode.notes.gtask.exception.ActionFailureException;
// 导入自定义的GTaskStringUtils类推测该类提供了与GTask相关的字符串处理工具方法
// 例如字符串格式化、解析或其他与GTask业务逻辑相关的字符串操作。
import net.micode.notes.tool.GTaskStringUtils; import net.micode.notes.tool.GTaskStringUtils;
// 导入自定义的ResourceParser类推测该类用于解析应用资源
// 比如从资源文件中读取数据、处理资源的特定格式等。
import net.micode.notes.tool.ResourceParser; import net.micode.notes.tool.ResourceParser;
// 导入JSONArray类用于表示和操作JSON数组在处理JSON格式数据时
// 可以通过它来创建、访问和修改JSON数组中的元素。
import org.json.JSONArray; import org.json.JSONArray;
// 导入JSONException类在处理JSON数据时如果JSON数据格式不正确、解析过程中出现错误
// 或者在JSON对象操作如获取不存在的键值对可能会抛出该异常。
import org.json.JSONException; import org.json.JSONException;
// 导入JSONObject类用于表示和操作JSON对象可以通过该类创建JSON对象、
// 设置和获取对象中的键值对以及进行JSON数据的序列化和反序列化操作。
import org.json.JSONObject; import org.json.JSONObject;
// 导入ArrayList类它是Java中常用的动态数组实现用于存储和管理一组对象
// 可以根据需要动态调整大小。
import java.util.ArrayList; import java.util.ArrayList;
// 重复导入ContentResolver类可能是代码整理过程中的疏忽
// 已经导入过一次,无需再次导入,但保留在此处不影响功能。
import android.content.ContentResolver;
// 重复导入ContentValues类可能是代码整理过程中的疏忽 import android.content.ContentResolver;
// 已经导入过一次,无需再次导入,但保留在此处不影响功能。
import android.content.ContentValues; import android.content.ContentValues;
// 重复导入Context类可能是代码整理过程中的疏忽
// 已经导入过一次,无需再次导入,但保留在此处不影响功能。
import android.content.Context; import android.content.Context;
// 重复导入Cursor类可能是代码整理过程中的疏忽
// 已经导入过一次,无需再次导入,但保留在此处不影响功能。
import android.database.Cursor; import android.database.Cursor;
// 重复导入Uri类可能是代码整理过程中的疏忽
// 已经导入过一次,无需再次导入,但保留在此处不影响功能。
import android.net.Uri; import android.net.Uri;
// 导入RemoteViews类用于在不同进程间显示视图常用于App Widget和通知栏的布局设置
// 可以在不直接访问其他进程视图的情况下更新其内容。
import android.widget.RemoteViews; import android.widget.RemoteViews;
// 导入JsonObject类这是Google Gson库中的类用于表示JSON对象
// 与org.json.JSONObject类似但属于Gson库的一部分在处理JSON数据时有不同的特点和用途。
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
// 重复导入JSONArray类可能是代码整理过程中的疏忽
// 已经导入过一次,无需再次导入,但保留在此处不影响功能。
import org.json.JSONArray; import org.json.JSONArray;
// 重复导入JSONException类可能是代码整理过程中的疏忽
// 已经导入过一次,无需再次导入,但保留在此处不影响功能。
import org.json.JSONException; import org.json.JSONException;
// 重复导入JSONObject类可能是代码整理过程中的疏忽
// 已经导入过一次,无需再次导入,但保留在此处不影响功能。
import org.json.JSONObject; import org.json.JSONObject;
// 导入List接口它是Java集合框架中的一部分定义了有序集合的操作规范 import java.util.ArrayList;
// ArrayList等类实现了该接口用于存储和管理一组对象。
import java.util.List; import java.util.List;
// 导入AtomicInteger类它是一个提供原子操作的Integer类
// 可以在多线程环境下安全地进行整数的增减等操作,保证数据的一致性。
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
// 导入NonNull注解用于标记参数或返回值不应该为null
// 通常在Android开发中用于辅助静态分析工具检测潜在的空指针异常。
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
// 导入Nullable注解用于标记参数或返回值可以为null
// 同样在Android开发中用于辅助静态分析工具进行代码检查。
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
// 导入AppCompatDrawableManager类它是AppCompat库中用于管理Drawable资源的类
// 可以帮助在不同版本的Android系统上更方便地加载和管理图像资源。
import androidx.appcompat.widget.AppCompatDrawableManager; import androidx.appcompat.widget.AppCompatDrawableManager;
// 导入SimpleCursorAdapter类它是一个简单的游标适配器
// 用于将数据库查询结果Cursor中的数据绑定到ListView等视图组件上方便显示数据。
import androidx.cursoradapter.widget.SimpleCursorAdapter; import androidx.cursoradapter.widget.SimpleCursorAdapter;
// SqlNote类用于处理与笔记数据的SQL相关操作包括从数据库加载、保存、更新笔记信息等 // SqlNote类用于处理与笔记数据的SQL相关操作包括从数据库加载、保存、更新笔记信息等
@ -483,18 +397,25 @@ public class SqlNote {
return true; return true;
} }
// 获取当前笔记的内容以JSONObject形式返回 // 获取内容的方法返回一个表示当前对象内容的JSONObject
public JSONObject getContent() { public JSONObject getContent() {
try { try {
// 创建一个空的JSONObject
JSONObject js = new JSONObject(); JSONObject js = new JSONObject();
// 如果该对象尚未在数据库中创建
if (mIsCreate) { if (mIsCreate) {
// 记录错误日志
Log.e(TAG, "it seems that we haven't created this in database yet"); Log.e(TAG, "it seems that we haven't created this in database yet");
// 返回null
return null; return null;
} }
// 创建一个表示笔记的JSONObject
JSONObject note = new JSONObject(); JSONObject note = new JSONObject();
// 如果类型是笔记类型
if (mType == Notes.TYPE_NOTE) { if (mType == Notes.TYPE_NOTE) {
// 将笔记的各个属性添加到note JSONObject中
note.put(NoteColumns.ID, mId); note.put(NoteColumns.ID, mId);
note.put(NoteColumns.ALERTED_DATE, mAlertDate); note.put(NoteColumns.ALERTED_DATE, mAlertDate);
note.put(NoteColumns.BG_COLOR_ID, mBgColorId); note.put(NoteColumns.BG_COLOR_ID, mBgColorId);
@ -507,124 +428,179 @@ public class SqlNote {
note.put(NoteColumns.WIDGET_ID, mWidgetId); note.put(NoteColumns.WIDGET_ID, mWidgetId);
note.put(NoteColumns.WIDGET_TYPE, mWidgetType); note.put(NoteColumns.WIDGET_TYPE, mWidgetType);
note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent); note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent);
// 将表示笔记的note JSONObject添加到主JSONObject中使用GTaskStringUtils.META_HEAD_NOTE作为键
js.put(GTaskStringUtils.META_HEAD_NOTE, note); js.put(GTaskStringUtils.META_HEAD_NOTE, note);
// 创建一个JSONArray用于存储数据
JSONArray dataArray = new JSONArray(); JSONArray dataArray = new JSONArray();
// 遍历数据列表
for (SqlData sqlData : mDataList) { for (SqlData sqlData : mDataList) {
// 获取每个数据项的内容JSONObject
JSONObject data = sqlData.getContent(); JSONObject data = sqlData.getContent();
// 如果数据项的内容JSONObject不为空
if (data!= null) { if (data!= null) {
// 将其添加到JSONArray中
dataArray.put(data); dataArray.put(data);
} }
} }
// 将存储数据的JSONArray添加到主JSONObject中使用GTaskStringUtils.META_HEAD_DATA作为键
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
// 如果类型是文件夹或系统类型
} else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { } else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) {
// 将文件夹或系统类型的相关属性添加到note JSONObject中
note.put(NoteColumns.ID, mId); note.put(NoteColumns.ID, mId);
note.put(NoteColumns.TYPE, mType); note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.SNIPPET, mSnippet); note.put(NoteColumns.SNIPPET, mSnippet);
// 将表示文件夹或系统类型的note JSONObject添加到主JSONObject中使用GTaskStringUtils.META_HEAD_NOTE作为键
js.put(GTaskStringUtils.META_HEAD_NOTE, note); js.put(GTaskStringUtils.META_HEAD_NOTE, note);
} }
// 返回构建好的主JSONObject
return js; return js;
} catch (JSONException e) { } catch (JSONException e) {
// 记录异常日志
Log.e(TAG, e.toString()); Log.e(TAG, e.toString());
// 打印异常堆栈信息
e.printStackTrace(); e.printStackTrace();
} }
// 如果发生异常返回null
return null; return null;
} }
// 设置父ID的方法
public void setParentId(long id) { public void setParentId(long id) {
// 设置对象的父ID
mParentId = id; mParentId = id;
// 将父ID的变化记录到mDiffNoteValues中用于后续的更新操作
mDiffNoteValues.put(NoteColumns.PARENT_ID, id); mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
} }
// 设置GTask ID的方法
public void setGtaskId(String gid) { public void setGtaskId(String gid) {
// 将GTask ID的变化记录到mDiffNoteValues中用于后续的更新操作
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
} }
// 设置同步ID的方法
public void setSyncId(long syncId) { public void setSyncId(long syncId) {
// 将同步ID的变化记录到mDiffNoteValues中用于后续的更新操作
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
} }
// 重置本地修改标志的方法
public void resetLocalModified() { public void resetLocalModified() {
// 将本地修改标志设为0并记录到mDiffNoteValues中用于后续的更新操作
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
} }
// 获取ID的方法
public long getId() { public long getId() {
// 返回对象的ID
return mId; return mId;
} }
// 获取父ID的方法
public long getParentId() { public long getParentId() {
// 返回对象的父ID
return mParentId; return mParentId;
} }
// 获取片段内容的方法
public String getSnippet() { public String getSnippet() {
// 返回对象的片段内容
return mSnippet; return mSnippet;
} }
// 判断是否为笔记类型的方法
public boolean isNoteType() { public boolean isNoteType() {
// 如果类型是笔记类型返回true否则返回false
return mType == Notes.TYPE_NOTE; return mType == Notes.TYPE_NOTE;
} }
// 提交更改的方法validateVersion用于指定是否验证版本
public void commit(boolean validateVersion) { public void commit(boolean validateVersion) {
// 如果对象是新创建的
if (mIsCreate) { if (mIsCreate) {
// 如果ID无效且mDiffNoteValues中包含ID移除ID
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) { if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID); mDiffNoteValues.remove(NoteColumns.ID);
} }
// 向笔记内容Uri插入数据返回插入后的Uri
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
try { try {
// 从返回的Uri中获取插入的笔记ID
mId = Long.valueOf(uri.getPathSegments().get(1)); mId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
// 记录获取ID错误的日志
Log.e(TAG, "Get note id error :" + e.toString()); Log.e(TAG, "Get note id error :" + e.toString());
// 抛出操作失败异常
throw new ActionFailureException("create note failed"); throw new ActionFailureException("create note failed");
} }
// 如果获取的ID为0抛出异常
if (mId == 0) { if (mId == 0) {
throw new IllegalStateException("Create thread id failed"); throw new IllegalStateException("Create thread id failed");
} }
// 如果类型是笔记类型
if (mType == Notes.TYPE_NOTE) { if (mType == Notes.TYPE_NOTE) {
// 遍历数据列表,提交每个数据项
for (SqlData sqlData : mDataList) { for (SqlData sqlData : mDataList) {
sqlData.commit(mId, false, -1); sqlData.commit(mId, false, -1);
} }
} }
} else { } else {
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) { // 如果ID无效且不是根文件夹或通话记录文件夹的ID
if (mId <= 0 && mId!= Notes.ID_ROOT_FOLDER && mId!= Notes.ID_CALL_RECORD_FOLDER) {
// 记录错误日志
Log.e(TAG, "No such note"); Log.e(TAG, "No such note");
// 抛出异常
throw new IllegalStateException("Try to update note with invalid id"); throw new IllegalStateException("Try to update note with invalid id");
} }
// 如果有需要更新的变化
if (mDiffNoteValues.size() > 0) { if (mDiffNoteValues.size() > 0) {
mVersion ++; // 版本号加1
mVersion++;
int result = 0; int result = 0;
// 如果不需要验证版本
if (!validateVersion) { if (!validateVersion) {
// 根据ID更新笔记数据
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] { + NoteColumns.ID + "=?)", new String[] {
String.valueOf(mId) String.valueOf(mId)
}); });
} else { } else {
// 根据ID和版本号更新笔记数据
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] { new String[] {
String.valueOf(mId), String.valueOf(mVersion) String.valueOf(mId), String.valueOf(mVersion)
}); });
} }
// 如果更新结果为0记录警告日志
if (result == 0) { if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing"); Log.w(TAG, "there is no update. maybe user updates note when syncing");
} }
} }
// 如果类型是笔记类型
if (mType == Notes.TYPE_NOTE) { if (mType == Notes.TYPE_NOTE) {
// 遍历数据列表,提交每个数据项
for (SqlData sqlData : mDataList) { for (SqlData sqlData : mDataList) {
sqlData.commit(mId, validateVersion, mVersion); sqlData.commit(mId, validateVersion, mVersion);
} }
} }
} }
// refresh local info // 从数据库中重新加载本地信息
loadFromCursor(mId); loadFromCursor(mId);
// 如果类型是笔记类型,加载数据内容
if (mType == Notes.TYPE_NOTE) if (mType == Notes.TYPE_NOTE)
loadDataContent(); loadDataContent();
// 清空记录变化的mDiffNoteValues
mDiffNoteValues.clear(); mDiffNoteValues.clear();
// 设置对象不再是新创建的状态
mIsCreate = false; mIsCreate = false;
} }
} }

@ -15,66 +15,22 @@
*/ */
package net.micode.notes.gtask.data; package net.micode.notes.gtask.data;
// 导入android.database.Cursor类用于处理数据库查询结果集。
// 在Android开发中当执行数据库查询操作如针对SQLite数据库的查询
// 会返回一个Cursor对象通过它可以遍历结果集获取每一行数据以及相应列的值
// 从而对查询到的数据进行进一步处理和使用。
import android.database.Cursor;
// 导入android.text.TextUtils类该类提供了一系列用于处理文本的实用方法。 import android.database.Cursor;
// 例如判断字符串是否为空TextUtils.isEmpty())、连接字符串数组等,
// 在处理文本数据时经常会用到这些方法。
import android.text.TextUtils; import android.text.TextUtils;
// 导入android.util.Log类用于在Android应用开发中记录日志信息。
// 开发人员可以使用不同级别的日志方法如Log.d(调试日志)、Log.e(错误日志)、Log.i(信息日志)等)
// 来输出程序运行过程中的各种信息,这有助于调试程序和排查问题。
import android.util.Log; import android.util.Log;
// 导入自定义的Notes类推测该类包含与笔记应用相关的核心数据结构、常量或方法
// 可能用于管理笔记的各种属性、操作逻辑等,是整个笔记应用数据处理的重要组成部分。
import net.micode.notes.data.Notes; import net.micode.notes.data.Notes;
// 导入自定义的DataColumns类该类可能定义了与笔记数据相关的列名常量。
// 在数据库操作(如查询、插入、更新数据)中,这些常量用于指定具体的列,
// 使代码更具可读性和维护性。
import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataColumns;
// 导入自定义的DataConstants类该类可能包含与笔记数据相关的常量
// 例如数据类型标识、特定的数据状态值等。这些常量在数据处理逻辑中用于判断、比较等操作,
// 以确保数据的一致性和正确性。
import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.DataConstants;
// 导入自定义的NoteColumns类该类可能专门定义了与笔记相关的列名常量
// 与DataColumns类可能有所区别更侧重于笔记特定的数据库操作
// 比如在创建笔记表、查询笔记详细信息时使用这些常量来指定列。
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
// 导入自定义的异常类ActionFailureException当执行某些特定操作失败时
// 代码可能会抛出该异常。通过捕获和处理这个异常,可以在应用中进行适当的错误处理,
// 例如向用户显示错误信息或进行特定的恢复操作。
import net.micode.notes.gtask.exception.ActionFailureException; import net.micode.notes.gtask.exception.ActionFailureException;
// 导入自定义的GTaskStringUtils类推测该类提供了与GTask可能是笔记应用中的特定功能模块
// 相关的字符串处理工具方法。比如,字符串格式化、解析特定格式的字符串等,
// 用于满足GTask业务逻辑中对字符串处理的需求。
import net.micode.notes.tool.GTaskStringUtils; import net.micode.notes.tool.GTaskStringUtils;
// 导入org.json.JSONArray类用于表示和操作JSON数组。
// 在处理JSON格式的数据时如果数据结构包含数组部分就可以使用这个类来创建、
// 访问和修改JSON数组中的元素方便与以JSON格式传输或存储的数据进行交互。
import org.json.JSONArray; import org.json.JSONArray;
// 导入org.json.JSONException类在处理JSON数据时如果JSON数据格式不正确、
// 解析过程中出现错误如JSON字符串不符合语法规则或者在JSON对象/数组操作(如获取不存在的键值对、索引越界等)时,
// 可能会抛出该异常。通过捕获这个异常可以对JSON数据处理过程中的错误进行适当处理。
import org.json.JSONException; import org.json.JSONException;
// 导入org.json.JSONObject类用于表示和操作JSON对象。
// 在Android开发中常用于处理从网络获取的JSON数据
// 可以通过该类创建JSON对象、设置和获取对象中的键值对以及进行JSON数据的序列化和反序列化操作
// 实现与其他系统或服务之间的数据交互。
import org.json.JSONObject; import org.json.JSONObject;
// Task类继承自Node类用于表示任务包含任务的各种属性和操作方法 // Task类继承自Node类用于表示任务包含任务的各种属性和操作方法
public class Task extends Node { public class Task extends Node {
// 日志标签,用于标识该类的日志输出 // 日志标签,用于标识该类的日志输出

@ -1,39 +1,16 @@
package net.micode.notes.gtask.data; package net.micode.notes.gtask.data;
// 导入Android数据库游标类用于遍历数据库查询结果集
// 当执行数据库查询操作后返回的Cursor对象可用于逐行读取数据列的值
import android.database.Cursor; import android.database.Cursor;
// 导入Android日志记录类方便在开发过程中输出调试、错误等信息
// 可通过不同方法如Log.d、Log.e等记录不同级别的日志
import android.util.Log; import android.util.Log;
// 导入自定义的笔记数据类,该类可能包含笔记相关的核心数据结构、方法或常量
// 用于管理和操作笔记的各种属性及业务逻辑
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;
// 导入自定义的异常类当与GTask相关的操作失败时可能抛出此异常
// 以便在代码中进行相应的异常处理,如提示用户或执行特定恢复操作
import net.micode.notes.gtask.exception.ActionFailureException; import net.micode.notes.gtask.exception.ActionFailureException;
// 导入自定义的GTask字符串工具类推测用于提供与GTask相关的字符串处理方法
// 例如字符串格式化、解析等以满足GTask业务逻辑对字符串处理的需求
import net.micode.notes.tool.GTaskStringUtils; import net.micode.notes.tool.GTaskStringUtils;
// 导入JSON异常类在处理JSON数据时如果出现格式错误、解析失败等情况会抛出该异常
// 可通过捕获此异常进行相应的错误处理,避免应用崩溃
import org.json.JSONException; import org.json.JSONException;
// 导入JSON对象类用于创建、操作和解析JSON格式的数据
// 例如从服务器获取JSON数据后使用该类解析数据并提取所需信息
import org.json.JSONObject; import org.json.JSONObject;
// 导入Java的动态数组类可用于存储和管理一组对象且对象数量可动态变化
// 在处理多个笔记对象或数据库查询结果集时,可使用它来方便地进行数据存储和操作
import java.util.ArrayList; import java.util.ArrayList;
// TaskList类继承自Node类用于表示任务列表包含任务列表的相关属性和操作方法 // TaskList类继承自Node类用于表示任务列表包含任务列表的相关属性和操作方法

@ -16,52 +16,31 @@
*/ */
package net.micode.notes.gtask.remote; package net.micode.notes.gtask.remote;
// 导入通知类,用于构建和管理通知,通知可以在设备状态栏显示,向用户传达信息
import android.app.Notification; import android.app.Notification;
// 导入通知管理器类,用于管理通知的发布、取消等操作 import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
// 导入待定意图类用于创建一个可以在未来某个时间点执行的Intent
// 例如在用户点击通知时触发特定的操作
import android.app.PendingIntent; import android.app.PendingIntent;
// 导入上下文类它是Android应用程序环境的全局信息接口
// 用于访问应用资源、启动组件、获取系统服务等
import android.content.Context; import android.content.Context;
// 导入意图类用于在Android组件如Activity、Service、Broadcast Receiver之间传递消息
// 启动组件或执行特定操作
import android.content.Intent; import android.content.Intent;
// 导入异步任务类用于在后台线程执行操作避免阻塞主线程UI线程
// 确保应用在执行耗时操作时保持响应性
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
// 导入应用资源类R类包含了应用中所有资源如字符串、布局、图片等的引用
import net.micode.notes.R; 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;
// 以下重复导入部分应是疏忽,可删除,但不影响功能
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
// 导入笔记列表活动类(重复导入),可能是疏忽,可删除
import net.micode.notes.NotesListActivity;
// 导入笔记偏好设置活动类(重复导入),可能是疏忽,可删除
import net.micode.notes.NotesPreferenceActivity;
// 导入GTask管理器类推测用于管理与GTask相关的功能
// GTask可能是应用中的特定任务模块如与云端同步等功能
import net.micode.notes.gtask.GTaskManager; import net.micode.notes.gtask.GTaskManager;
// GTaskASyncTask类继承自AsyncTask用于在后台执行Google Tasks同步任务并通过通知和回调机制处理同步结果 // GTaskASyncTask类继承自AsyncTask用于在后台执行Google Tasks同步任务并通过通知和回调机制处理同步结果
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> { public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
// 定义Google Tasks同步通知的唯一ID // 定义Google Tasks同步通知的唯一ID
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; private static final int GTASK_SYNC_NOTIFICATION_ID = 5234235;
// 定义通知渠道ID
private static final String NOTIFICATION_CHANNEL_ID = "gtask_sync_channel";
// 定义通知渠道名称
private static final String NOTIFICATION_CHANNEL_NAME = "Google Tasks Sync";
// 定义一个接口,用于在同步任务完成时通知调用者 // 定义一个接口,用于在同步任务完成时通知调用者
public interface OnCompleteListener { public interface OnCompleteListener {
@ -70,121 +49,110 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
} }
// 上下文对象,用于获取系统服务和资源 // 上下文对象,用于获取系统服务和资源
private Context mContext; private final Context context;
// 通知管理器,用于管理通知的显示和取消 // 通知管理器,用于管理通知的显示和取消
private NotificationManager mNotifiManager; private final NotificationManager notificationManager;
// Google Tasks管理器负责执行同步任务 // Google Tasks管理器负责执行同步任务
private GTaskManager mTaskManager; private final GTaskManager taskManager;
// 同步完成监听器,用于在同步任务完成时执行回调 // 同步完成监听器,用于在同步任务完成时执行回调
private OnCompleteListener mOnCompleteListener; private final OnCompleteListener onCompleteListener;
// 构造函数初始化GTaskASyncTask实例 // 构造函数初始化GTaskASyncTask实例
public GTaskASyncTask(Context context, OnCompleteListener listener) { public GTaskASyncTask(Context context, OnCompleteListener listener) {
// 保存上下文对象 this.context = context;
mContext = context; this.onCompleteListener = listener;
// 保存同步完成监听器 this.notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mOnCompleteListener = listener; this.taskManager = GTaskManager.getInstance();
// 获取通知管理器服务 createNotificationChannel();
mNotifiManager = (NotificationManager) mContext }
.getSystemService(Context.NOTIFICATION_SERVICE);
// 获取Google Tasks管理器实例 // 创建通知渠道的方法用于Android 8.0及以上系统
mTaskManager = GTaskManager.getInstance(); private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
);
notificationManager.createNotificationChannel(channel);
}
} }
// 取消同步任务的方法调用GTaskManager的cancelSync方法 // 取消同步任务的方法调用GTaskManager的cancelSync方法,并等待任务真正取消
public void cancelSync() { public void cancelSync() {
mTaskManager.cancelSync(); taskManager.cancelSync();
// 这里可以添加等待任务取消的逻辑,例如通过循环检查任务状态
} }
// 发布同步进度消息的方法调用父类的publishProgress方法 // 发布同步进度消息的方法调用父类的publishProgress方法
public void publishProgess(String message) { public void publishProgess(String message) {
publishProgress(new String[]{message}); publishProgress(message);
} }
// 显示通知的方法,根据不同的通知类型和内容创建并显示通知 // 显示通知的方法,根据不同的通知类型和内容创建并显示通知
private void showNotification(int tickerId, String content) { private void showNotification(int tickerId, String content, Class<?> targetActivityClass) {
// 创建一个通知对象,设置图标、滚动提示信息和时间戳 Notification.Builder builder = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
Notification notification = new Notification(R.drawable.notification, mContext .setSmallIcon(R.drawable.notification)
.getString(tickerId), System.currentTimeMillis()); .setContentTitle(context.getString(R.string.app_name))
// 设置通知的默认灯光效果 .setContentText(content)
notification.defaults = Notification.DEFAULT_LIGHTS; .setAutoCancel(true)
// 设置通知在被点击后自动取消 .setDefaults(Notification.DEFAULT_LIGHTS);
notification.flags = Notification.FLAG_AUTO_CANCEL;
PendingIntent pendingIntent; Intent intent = new Intent(context, targetActivityClass);
// 根据通知类型决定点击通知后打开的Activity PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
if (tickerId!= R.string.ticker_success) { builder.setContentIntent(pendingIntent);
// 如果不是同步成功的通知点击后打开NotesPreferenceActivity
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotesPreferenceActivity.class), 0); builder.setChannelId(NOTIFICATION_CHANNEL_ID);
} else {
// 如果是同步成功的通知点击后打开NotesListActivity
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), 0);
} }
// 设置通知的详细信息包括标题、内容和点击后的PendingIntent
notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, notificationManager.notify(GTASK_SYNC_NOTIFICATION_ID, builder.build());
pendingIntent);
// 显示通知
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
} }
// 在后台线程执行同步任务的方法 // 在后台线程执行同步任务的方法
@Override @Override
protected Integer doInBackground(Void... unused) { protected Integer doInBackground(Void... unused) {
// 发布同步进度消息,提示正在登录同步账户 publishProgess(context.getString(R.string.sync_progress_login, NotesPreferenceActivity.getSyncAccountName(context)));
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity return taskManager.sync(context, this);
.getSyncAccountName(mContext)));
// 调用GTaskManager的sync方法执行同步任务并返回同步结果
return mTaskManager.sync(mContext, this);
} }
// 在主线程更新同步进度的方法,用于显示同步过程中的通知 // 在主线程更新同步进度的方法,用于显示同步过程中的通知
@Override @Override
protected void onProgressUpdate(String... progress) { protected void onProgressUpdate(String... progress) {
// 显示同步中的通知,提示当前同步进度 showNotification(R.string.ticker_syncing, progress[0], NotesPreferenceActivity.class);
showNotification(R.string.ticker_syncing, progress[0]); if (context instanceof GTaskSyncService) {
// 如果上下文是GTaskSyncService发送广播通知同步进度 ((GTaskSyncService) context).sendBroadcast(progress[0]);
if (mContext instanceof GTaskSyncService) {
((GTaskSyncService) mContext).sendBroadcast(progress[0]);
} }
} }
// 在同步任务完成后执行的方法,根据同步结果显示相应的通知,并执行回调 // 在同步任务完成后执行的方法,根据同步结果显示相应的通知,并执行回调
@Override @Override
protected void onPostExecute(Integer result) { protected void onPostExecute(Integer result) {
// 如果同步成功 switch (result) {
if (result == GTaskManager.STATE_SUCCESS) { case GTaskManager.STATE_SUCCESS:
// 显示同步成功的通知,提示成功同步的账户信息 showNotification(R.string.ticker_success, context.getString(R.string.success_sync_account, taskManager.getSyncAccount()), NotesListActivity.class);
showNotification(R.string.ticker_success, mContext.getString( NotesPreferenceActivity.setLastSyncTime(context, System.currentTimeMillis());
R.string.success_sync_account, mTaskManager.getSyncAccount())); Log.d("GTaskASyncTask", "Sync success");
// 更新最后同步时间 break;
NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); case GTaskManager.STATE_NETWORK_ERROR:
} showNotification(R.string.ticker_fail, context.getString(R.string.error_sync_network), NotesPreferenceActivity.class);
// 如果同步出现网络错误 Log.e("GTaskASyncTask", "Network error during sync");
else if (result == GTaskManager.STATE_NETWORK_ERROR) { break;
// 显示网络错误的通知 case GTaskManager.STATE_INTERNAL_ERROR:
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); showNotification(R.string.ticker_fail, context.getString(R.string.error_sync_internal), NotesPreferenceActivity.class);
Log.e("GTaskASyncTask", "Internal error during sync");
break;
case GTaskManager.STATE_SYNC_CANCELLED:
showNotification(R.string.ticker_cancel, context.getString(R.string.error_sync_cancelled), NotesPreferenceActivity.class);
Log.d("GTaskASyncTask", "Sync cancelled");
break;
default:
Log.w("GTaskASyncTask", "Unknown sync result: " + result);
break;
} }
// 如果同步出现内部错误
else if (result == GTaskManager.STATE_INTERNAL_ERROR) { if (onCompleteListener!= null) {
// 显示内部错误的通知 new Thread(() -> onCompleteListener.onComplete()).start();
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal));
}
// 如果同步被取消
else if (result == GTaskManager.STATE_SYNC_CANCELLED) {
// 显示同步取消的通知
showNotification(R.string.ticker_cancel, mContext
.getString(R.string.error_sync_cancelled));
}
// 如果同步完成监听器不为空
if (mOnCompleteListener!= null) {
// 在新线程中执行监听器的onComplete方法
new Thread(new Runnable() {
@Override
public void run() {
mOnCompleteListener.onComplete();
}
}).start();
} }
} }
} }

@ -1,27 +1,8 @@
// 导入android.accounts.Account类用于表示Android系统中的账户。
// 在应用需要与系统账户进行交互,如获取账户信息、进行账户验证等操作时会用到。
// 例如某些应用可能需要使用用户的Google账户进行登录或同步数据此时就会涉及到Account类的使用。
import android.accounts.Account; import android.accounts.Account;
// 导入android.app.Activity类它是Android应用中最基本的组件之一
// 代表一个可视化的用户界面屏幕。每个Activity通常负责一个特定的用户交互任务
// 例如显示主界面、设置页面、详情页等。应用中的多个Activity可以通过Intent进行切换和交互。
import android.app.Activity; import android.app.Activity;
// 导入android.text.TextUtils类该类提供了一系列用于处理文本的实用方法。
// 比如判断字符串是否为空TextUtils.isEmpty()比较字符串TextUtils.equals()
// 以及处理字符串连接等操作,在处理文本相关逻辑时经常会用到。
import android.text.TextUtils; import android.text.TextUtils;
// 导入android.util.Log类用于在Android应用开发过程中记录日志信息。
// 开发人员可以通过不同级别的日志方法如Log.d调试日志、Log.e错误日志、Log.i信息日志
// 输出程序运行时的各种信息,方便调试和排查问题。
import android.util.Log; import android.util.Log;
// 导入org.json.JSONArray类用于处理JSONJavaScript Object Notation格式数据中的数组部分。
// 在Android开发中当从服务器获取的数据是JSON格式且其中包含数组结构时
// 可以使用JSONArray类来解析、创建和操作这些数组数据。例如获取包含多个用户信息的JSON数组
// 就可以通过JSONArray类来遍历和处理每个用户的数据。
import org.json.JSONArray; import org.json.JSONArray;
// GTaskClient类用于与Google Tasks服务进行交互管理登录、请求URL、版本等信息并提供单例实例 // GTaskClient类用于与Google Tasks服务进行交互管理登录、请求URL、版本等信息并提供单例实例

@ -2,112 +2,38 @@
package net.micode.notes.gtask.remote; package net.micode.notes.gtask.remote;
// 导入Activity类它是Android应用中负责提供可视化用户界面的组件
// 每个Activity通常代表一个屏幕的内容用于与用户进行交互。
import android.app.Activity; import android.app.Activity;
// 导入ContentResolver类用于在不同的ContentProvider之间进行数据交互
// 可以执行查询、插入、更新和删除ContentProvider所提供的数据等操作。
import android.content.ContentResolver; import android.content.ContentResolver;
// 导入ContentUris类提供了一些用于操作Uri的工具方法
// 特别是在处理与内容URI相关的操作时很有用例如在内容URI末尾追加ID。
import android.content.ContentUris; import android.content.ContentUris;
// 导入ContentValues类用于以键值对的形式存储数据
// 常用于在数据库操作中表示要插入或更新的数据。
import android.content.ContentValues; import android.content.ContentValues;
// 导入Context类它是Android应用程序环境的全局信息接口
// 可用于获取应用资源、启动组件、访问系统服务等各种操作。
import android.content.Context; import android.content.Context;
// 导入Cursor类用于遍历和操作数据库查询结果集
// 通过它可以逐行读取查询结果中的数据。
import android.database.Cursor; import android.database.Cursor;
// 导入Log类用于在Android应用开发中记录日志信息
// 方便开发人员调试和排查问题,可输出不同级别的日志。
import android.util.Log; import android.util.Log;
// 导入应用资源类RR类包含了应用中所有资源如字符串、布局、图片等的引用。
import net.micode.notes.R; import net.micode.notes.R;
// 导入自定义的Notes类可能包含与笔记相关的各种常量、方法或数据结构
// 用于整个笔记应用的数据处理和业务逻辑。
import net.micode.notes.data.Notes; import net.micode.notes.data.Notes;
// 导入自定义的DataColumns类该类可能定义了与笔记数据相关的列名常量
// 在数据库操作中用于指定具体的列。
import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataColumns;
// 导入自定义的NoteColumns类该类可能定义了与笔记相关的列名常量
// 专门用于笔记相关的数据库操作。
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
// 导入自定义的MetaData类推测与GTask相关的数据元数据有关
// 可能包含一些描述数据属性或配置的信息。
import net.micode.notes.gtask.data.MetaData; import net.micode.notes.gtask.data.MetaData;
// 导入自定义的Node类可能是GTask数据结构中的一个节点
// 具体用途可能与数据组织、任务结构等相关。
import net.micode.notes.gtask.data.Node; import net.micode.notes.gtask.data.Node;
// 导入自定义的SqlNote类可能用于处理与SQLite数据库中笔记数据相关的操作
// 如数据库表的创建、查询、插入、更新等。
import net.micode.notes.gtask.data.SqlNote; import net.micode.notes.gtask.data.SqlNote;
// 导入自定义的Task类可能代表GTask中的任务包含任务的相关属性和方法。
import net.micode.notes.gtask.data.Task; import net.micode.notes.gtask.data.Task;
// 导入自定义的TaskList类可能用于管理和操作GTask中的任务列表
// 例如添加、删除、查询任务等操作。
import net.micode.notes.gtask.data.TaskList; import net.micode.notes.gtask.data.TaskList;
// 导入自定义的异常类ActionFailureException当执行某些操作失败时
// 可能会抛出该异常,用于在业务逻辑中处理异常情况。
import net.micode.notes.gtask.exception.ActionFailureException; import net.micode.notes.gtask.exception.ActionFailureException;
// 导入自定义的异常类NetworkFailureException当网络操作失败时
// 可能会抛出该异常,用于处理网络相关的错误情况。
import net.micode.notes.gtask.exception.NetworkFailureException; import net.micode.notes.gtask.exception.NetworkFailureException;
// 导入自定义的DataUtils类推测提供了一些数据处理的工具方法
// 例如数据转换、验证等操作。
import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.DataUtils;
// 导入自定义的GTaskStringUtils类推测提供了与GTask相关的字符串处理工具方法
// 例如字符串格式化、解析等。
import net.micode.notes.tool.GTaskStringUtils; import net.micode.notes.tool.GTaskStringUtils;
// 导入JSONArray类用于表示和操作JSON数组
// 在处理JSON格式的数据时如果数据包含数组结构会用到此类。
import org.json.JSONArray; import org.json.JSONArray;
// 导入JSONException类在处理JSON数据时如果JSON数据格式不正确、
// 解析过程中出现错误等情况,可能会抛出该异常。
import org.json.JSONException; import org.json.JSONException;
// 导入JSONObject类用于表示和操作JSON对象
// 可以创建JSON对象、设置和获取对象中的键值对以及进行JSON数据的序列化和反序列化操作。
import org.json.JSONObject; import org.json.JSONObject;
// 导入HashMap类它是Java中基于哈希表实现的Map接口
// 用于存储键值对数据,提供快速的查找和插入操作。
import java.util.HashMap; import java.util.HashMap;
// 导入HashSet类它是Java中基于哈希表实现的Set接口
// 用于存储不重复的元素,提供快速的插入和查找操作。
import java.util.HashSet; import java.util.HashSet;
// 导入Iterator类用于遍历集合如List、Set等中的元素
// 可以按顺序逐个访问集合中的元素。
import java.util.Iterator; import java.util.Iterator;
// 导入Map接口它是Java集合框架中的一部分定义了键值对映射的操作规范
// HashMap等类实现了该接口。
import java.util.Map; import java.util.Map;
public class GTaskManager { public class GTaskManager {
private static final String TAG = GTaskManager.class.getSimpleName(); private static final String TAG = GTaskManager.class.getSimpleName();

@ -1,183 +1,171 @@
package net.micode.notes.gtask.remote; package net.micode.notes.gtask.remote;
// 导入Activity类它是Android应用中最基本的组件之一
// 用于实现可视化的用户界面每个Activity通常对应一个屏幕的内容
// 负责与用户进行交互,例如展示信息、接收用户输入等。
import android.app.Activity; import android.app.Activity;
// 导入Service类这是Android应用组件用于在后台执行长时间运行的操作
// 不提供用户界面。例如,音乐播放服务、文件下载服务等,它在后台运行,
// 不会影响用户与其他组件如Activity的交互。
import android.app.Service; import android.app.Service;
// 导入Context类它是Android应用程序环境的全局信息接口
// 提供了对应用资源、系统服务(如电源管理、网络管理等)的访问,
// 以及启动组件如Activity、Service、Broadcast Receiver的能力。
import android.content.Context; import android.content.Context;
// 导入Intent类用于在Android组件如Activity、Service、Broadcast Receiver之间传递消息
// 可以启动组件、在组件间传递数据。例如通过Intent启动一个新的Activity
// 或者向Service传递参数等。
import android.content.Intent; import android.content.Intent;
// 导入Bundle类用于在不同组件之间传递数据它可以存储各种基本数据类型
// 以及可序列化的对象。通常与Intent一起使用例如在启动Activity时
// 可以将数据封装在Bundle中通过Intent传递给目标Activity。
import android.os.Bundle; import android.os.Bundle;
// 导入IBinder类它是一个接口用于在不同进程间进行通信
// 当Service需要与其他组件如Activity进行交互时可能会用到它。
// Service通过返回一个实现了IBinder接口的对象让其他组件可以调用Service中的方法。
import android.os.IBinder; import android.os.IBinder;
import android.util.Log;
// GTaskSyncService类是一个服务用于管理Google Tasks的同步操作包括启动、取消同步并通过广播通知同步状态 /**
* GTaskSyncServiceGoogle Tasks
* 广
*/
public class GTaskSyncService extends Service { public class GTaskSyncService extends Service {
// 用于在Intent中传递同步操作类型的字符串名称 // 用于在Intent中传递同步操作类型的字符串名称
public final static String ACTION_STRING_NAME = "sync_action_type"; public static final String ACTION_STRING_NAME = "sync_action_type";
// 表示启动同步操作的常量 // 表示启动同步操作的常量
public final static int ACTION_START_SYNC = 0; public static final int ACTION_START_SYNC = 0;
// 表示取消同步操作的常量 // 表示取消同步操作的常量
public final static int ACTION_CANCEL_SYNC = 1; public static final int ACTION_CANCEL_SYNC = 1;
// 表示无效操作的常量 // 表示无效操作的常量
public final static int ACTION_INVALID = 2; public static final int ACTION_INVALID = 2;
// 用于发送广播的名称用于标识与Google Tasks同步服务相关的广播 // 用于发送广播的名称用于标识与Google Tasks同步服务相关的广播
public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; public static final String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service";
// 在广播Intent中用于表示是否正在同步的Extra键 // 在广播Intent中用于表示是否正在同步的Extra键
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; public static final String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing";
// 在广播Intent中用于传递同步进度消息的Extra键 // 在广播Intent中用于传递同步进度消息的Extra键
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; public static final String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg";
// 静态变量,用于存储当前正在执行的同步任务 // 静态变量,用于存储当前正在执行的同步任务
private static GTaskASyncTask mSyncTask = null; private static GTaskASyncTask syncTask = null;
// 静态变量,用于存储同步进度消息 // 静态变量,用于存储同步进度消息
private static String mSyncProgress = ""; private static String syncProgress = "";
// 启动同步任务的方法 /**
*
* 广
*
*/
private void startSync() { private void startSync() {
// 如果同步任务尚未创建 if (syncTask == null) {
if (mSyncTask == null) { syncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
// 创建一个新的GTaskASyncTask实例并传入当前服务上下文和同步完成监听器 @Override
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
// 同步完成时的回调方法
public void onComplete() { public void onComplete() {
// 同步完成后将mSyncTask设为null syncTask = null;
mSyncTask = null;
// 发送广播,通知同步完成
sendBroadcast(""); sendBroadcast("");
// 停止当前服务
stopSelf(); stopSelf();
} }
}); });
// 发送广播,通知同步开始
sendBroadcast(""); sendBroadcast("");
// 执行同步任务 syncTask.execute();
mSyncTask.execute(); } else {
Log.w("GTaskSyncService", "startSync called while a sync task is already running");
} }
} }
// 取消同步任务的方法 /**
*
*
*
*/
private void cancelSync() { private void cancelSync() {
// 如果同步任务正在执行 if (syncTask!= null) {
if (mSyncTask!= null) { syncTask.cancelSync();
// 调用同步任务的取消方法
mSyncTask.cancelSync();
} }
} }
// 服务创建时的回调方法 /**
*
* null
*/
@Override @Override
public void onCreate() { public void onCreate() {
// 初始化时将同步任务设为null syncTask = null;
mSyncTask = null;
} }
// 服务启动命令的回调方法 /**
*
* Intent
* START_STICKY便
*/
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(Intent intent, int flags, int startId) {
// 从Intent中获取附加的Bundle
Bundle bundle = intent.getExtras(); Bundle bundle = intent.getExtras();
// 如果Bundle存在且包含同步操作类型的键
if (bundle!= null && bundle.containsKey(ACTION_STRING_NAME)) { if (bundle!= null && bundle.containsKey(ACTION_STRING_NAME)) {
// 根据同步操作类型进行相应处理 int action = bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID);
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { switch (action) {
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;
} }
// 返回START_STICKY以便在系统资源允许时重新启动服务
return START_STICKY; return START_STICKY;
} }
// 如果Bundle不包含同步操作类型的键调用父类的onStartCommand方法
return super.onStartCommand(intent, flags, startId); return super.onStartCommand(intent, flags, startId);
} }
// 系统内存不足时的回调方法 /**
*
*
*/
@Override @Override
public void onLowMemory() { public void onLowMemory() {
// 如果同步任务正在执行 if (syncTask!= null) {
if (mSyncTask!= null) { cancelSync();
// 取消同步任务
mSyncTask.cancelSync();
} }
} }
// 绑定服务时的回调方法该服务不支持绑定返回null /**
* null
*/
@Override
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
return null; return null;
} }
// 发送广播的方法,用于通知同步状态和进度 /**
* 广
* IntentExtra广
*/
public void sendBroadcast(String msg) { public void sendBroadcast(String msg) {
// 更新同步进度消息 syncProgress = msg;
mSyncProgress = msg;
// 创建一个新的Intent用于发送广播
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
// 在Intent中添加是否正在同步的Extra intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, syncTask!= null);
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask!= null);
// 在Intent中添加同步进度消息的Extra
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg);
// 发送广播
sendBroadcast(intent); sendBroadcast(intent);
} }
// 外部调用启动同步服务的静态方法 /**
*
* GTaskManagerIntent
*/
public static void startSync(Activity activity) { public static void startSync(Activity activity) {
// 设置GTaskManager的活动上下文
GTaskManager.getInstance().setActivityContext(activity); GTaskManager.getInstance().setActivityContext(activity);
// 创建一个启动服务的Intent
Intent intent = new Intent(activity, GTaskSyncService.class); Intent intent = new Intent(activity, GTaskSyncService.class);
// 在Intent中添加启动同步操作的类型 intent.putExtra(ACTION_STRING_NAME, ACTION_START_SYNC);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC);
// 启动服务
activity.startService(intent); activity.startService(intent);
} }
// 外部调用取消同步服务的静态方法 /**
*
* Intent
*/
public static void cancelSync(Context context) { public static void cancelSync(Context context) {
// 创建一个启动服务的Intent
Intent intent = new Intent(context, GTaskSyncService.class); Intent intent = new Intent(context, GTaskSyncService.class);
// 在Intent中添加取消同步操作的类型 intent.putExtra(ACTION_STRING_NAME, ACTION_CANCEL_SYNC);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
// 启动服务
context.startService(intent); context.startService(intent);
} }
// 判断同步是否正在进行的静态方法 /**
*
* null
*/
public static boolean isSyncing() { public static boolean isSyncing() {
// 如果同步任务不为null说明同步正在进行 return syncTask!= null;
return mSyncTask!= null;
} }
// 获取同步进度字符串的静态方法 /**
*
*
*/
public static String getProgressString() { public static String getProgressString() {
// 返回同步进度消息 return syncProgress;
return mSyncProgress;
} }
} }

@ -35,71 +35,137 @@ import java.util.ArrayList;
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 * ID
* 线ID线
*
* @param context
* @param folderId ID
* @return ID0
*/ */
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 // 在数据库中创建一个新笔记对应的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 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的字符串表示形式并转换为长整型
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;
} }
/**
* Note
* ContentValuesNoteData
*/
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());
} }
/**
* NoteData
*
* @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);
} }
/**
* IDNoteDataID0
*
* @param id ID0
*/
public void setTextDataId(long id) { public void setTextDataId(long id) {
mNoteData.setTextDataId(id); mNoteData.setTextDataId(id);
} }
/**
* IDNoteDataID
*
* @return ID
*/
public long getTextDataId() { public long getTextDataId() {
return mNoteData.mTextDataId; return mNoteData.mTextDataId;
} }
/**
* Call DataIDNoteDataID0
*
* @param id ID0
*/
public void setCallDataId(long id) { public void setCallDataId(long id) {
mNoteData.setCallDataId(id); mNoteData.setCallDataId(id);
} }
/**
* NoteDataCall Data
*
* @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);
} }
/**
* NoteData
*
* @return 0NoteDatatruefalse
*/
public boolean isLocalModified() { public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
} }
/**
* ID
* ID0
* true
*
*
* @param context
* @param noteId ID0
* @return truefalse
*/
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 +176,14 @@ public class Note {
} }
/** /**
* In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and * {@link NoteColumns#LOCAL_MODIFIED}
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the * {@link NoteColumns#MODIFIED_DATE}使
* 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();
@ -130,17 +195,23 @@ public class Note {
return true; return true;
} }
// 内部类NoteData用于封装笔记相关的更具体的数据如文本数据、呼叫数据等及其操作逻辑
private class NoteData { private class NoteData {
// 存储文本数据的ID
private long mTextDataId; private long mTextDataId;
// 用于存储文本数据的ContentValues对象以键值对形式保存文本数据相关属性
private ContentValues mTextDataValues; private ContentValues mTextDataValues;
// 存储呼叫数据的ID
private long mCallDataId; private long mCallDataId;
// 用于存储呼叫数据的ContentValues对象以键值对形式保存呼叫数据相关属性
private ContentValues mCallDataValues; private ContentValues mCallDataValues;
// 用于日志记录的标签,方便在日志中识别该内部类相关的输出信息
private static final String TAG = "NoteData"; private static final String TAG = "NoteData";
/**
* NoteData
* ContentValuesIDID0
*/
public NoteData() { public NoteData() {
mTextDataValues = new ContentValues(); mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues(); mCallDataValues = new ContentValues();
@ -148,17 +219,32 @@ public class Note {
mCallDataId = 0; mCallDataId = 0;
} }
/**
* NoteDataContentValues0
*
* @return ContentValues0ContentValues0truefalse
*/
boolean isLocalModified() { boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
} }
/**
* IDID0
*
* @param id ID0
*/
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;
} }
/**
* IDID0
*
* @param id ID0
*/
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");
@ -166,21 +252,42 @@ public class Note {
mCallDataId = id; mCallDataId = id;
} }
/**
* ContentValuesNote
*
* @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());
} }
/**
* ContentValuesNote
*
* @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());
} }
/**
* NoteData
* ID
* null
*
* @param context
* @param noteId ID0
* @return Uri{@link ContentUris#withAppendedId(Uri, long)}null
*/
Uri pushIntoContentResolver(Context context, long noteId) { Uri pushIntoContentResolver(Context context, long noteId) {
/** /**
* Check for safety * ID0
*/ */
if (noteId <= 0) { if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId); throw new IllegalArgumentException("Wrong note id:" + noteId);
@ -189,7 +296,7 @@ public class Note {
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder = null; ContentProviderOperation.Builder builder = null;
if(mTextDataValues.size() > 0) { if (mTextDataValues.size() > 0) {
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);
@ -211,7 +318,7 @@ public class Note {
mTextDataValues.clear(); mTextDataValues.clear();
} }
if(mCallDataValues.size() > 0) { if (mCallDataValues.size() > 0) {
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);
@ -237,7 +344,7 @@ public class Note {
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
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
} 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()));

@ -33,35 +33,36 @@ import net.micode.notes.tool.ResourceParser.NoteBgResources;
public class WorkingNote { public class WorkingNote {
// Note for the working note // 用于关联基础的Note对象借助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;
// 以时间戳形式记录的提醒日期用于设置该笔记在特定时间提醒用户相关事项初始值设为0表示未设置提醒
private long mAlertDate; private long mAlertDate;
// 同样以时间戳形式记录笔记最后一次被修改的时间,方便后续进行数据同步、版本管理等操作
private long mModifiedDate; private long mModifiedDate;
// 用于标识笔记的背景颜色通过该ID可以关联到对应的颜色资源以设置笔记的背景显示效果
private int mBgColorId; private int mBgColorId;
// 与桌面小部件相关联的ID如果笔记和某个小部件有对应关系通过该ID来确定具体是哪个小部件
private int mWidgetId; private int mWidgetId;
// 用于区分不同类型的小部件,不同类型的小部件可能对笔记有着不同的展示方式、交互逻辑等
private int mWidgetType; private int mWidgetType;
// 表明笔记所属的文件夹的ID用于在文件结构体系里对笔记进行分类管理方便用户整理和查找笔记
private long mFolderId; private long mFolderId;
// Android应用中的上下文对象用于获取系统资源、内容解析器等是很多操作的基础依赖
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,
@ -72,6 +73,7 @@ public class WorkingNote {
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,
@ -81,27 +83,35 @@ public class WorkingNote {
NoteColumns.MODIFIED_DATE NoteColumns.MODIFIED_DATE
}; };
// 定义常量表示在DATA_PROJECTION数组中数据ID列对应的索引位置方便在获取游标数据时准确取值
private static final int DATA_ID_COLUMN = 0; private static final int DATA_ID_COLUMN = 0;
// 对应DATA_PROJECTION数组中内容列的索引位置便于从游标中获取笔记的文本内容数据
private static final int DATA_CONTENT_COLUMN = 1; private static final int DATA_CONTENT_COLUMN = 1;
// 对应DATA_PROJECTION数组中MIME类型列的索引位置用于判断数据的具体类型如文本笔记、通话记录笔记等
private static final int DATA_MIME_TYPE_COLUMN = 2; private static final int DATA_MIME_TYPE_COLUMN = 2;
// 对应DATA_PROJECTION数组中模式列的索引位置获取笔记相关模式信息如清单模式等相关设置
private static final int DATA_MODE_COLUMN = 3; private static final int DATA_MODE_COLUMN = 3;
// 对应NOTE_PROJECTION数组中父文件夹ID列的索引位置用于获取笔记所属文件夹的ID信息
private static final int NOTE_PARENT_ID_COLUMN = 0; private static final int NOTE_PARENT_ID_COLUMN = 0;
// 对应NOTE_PROJECTION数组中提醒日期列的索引位置便于从游标中获取笔记的提醒时间数据
private static final int NOTE_ALERTED_DATE_COLUMN = 1; private static final int NOTE_ALERTED_DATE_COLUMN = 1;
// 对应NOTE_PROJECTION数组中背景颜色ID列的索引位置获取笔记的背景颜色设置相关信息
private static final int NOTE_BG_COLOR_ID_COLUMN = 2; private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
// 对应NOTE_PROJECTION数组中小部件ID列的索引位置用于获取与笔记关联的小部件的ID
private static final int NOTE_WIDGET_ID_COLUMN = 3; private static final int NOTE_WIDGET_ID_COLUMN = 3;
// 对应NOTE_PROJECTION数组中小部件类型列的索引位置获取小部件的类型信息
private static final int NOTE_WIDGET_TYPE_COLUMN = 4; private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
// 对应NOTE_PROJECTION数组中最后修改日期列的索引位置用于获取笔记最后一次修改的时间戳数据
private static final int NOTE_MODIFIED_DATE_COLUMN = 5; private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
// New note construct /**
*
* 0Note
* ID00
*
* @param context
* @param folderId ID
*/
private WorkingNote(Context context, long folderId) { private WorkingNote(Context context, long folderId) {
mContext = context; mContext = context;
mAlertDate = 0; mAlertDate = 0;
@ -114,7 +124,15 @@ public class WorkingNote {
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
} }
// Existing note construct /**
*
* IDWorkingNote
* loadNote
*
* @param context
* @param noteId ID
* @param folderId ID
*/
private WorkingNote(Context context, long noteId, long folderId) { private WorkingNote(Context context, long noteId, long folderId) {
mContext = context; mContext = context;
mNoteId = noteId; mNoteId = noteId;
@ -124,12 +142,19 @@ public class WorkingNote {
loadNote(); loadNote();
} }
/**
* WorkingNote
* 使IDNOTE_PROJECTION
* IDID
* loadNoteData
* ID
*/
private void loadNote() { private void loadNote() {
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) {
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN);
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN);
@ -146,13 +171,20 @@ public class WorkingNote {
loadNoteData(); loadNoteData();
} }
/**
* Note
* 使IDDATA_PROJECTION
* MIME_TYPE
* mContentmNoteID
*
*/
private void loadNoteData() { private void loadNoteData() {
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) {
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
do { do {
String type = cursor.getString(DATA_MIME_TYPE_COLUMN); String type = cursor.getString(DATA_MIME_TYPE_COLUMN);
@ -174,6 +206,18 @@ public class WorkingNote {
} }
} }
/**
* IDID
* setBgColorIdsetWidgetIdsetWidgetType
* IDIDWorkingNote
*
* @param context
* @param folderId ID
* @param widgetId ID
* @param widgetType
* @param defaultBgColorId ID
* @return WorkingNote
*/
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);
@ -183,10 +227,29 @@ public class WorkingNote {
return note; return note;
} }
/**
* ID
* IDID0WorkingNote
*
*
* @param context
* @param id ID
* @return WorkingNoteID
*/
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);
} }
/**
*
* isWorthSavingexistInDatabase
* NotegetNewNoteIdIDID0false
* mNotesyncNote
* mWidgetIdmWidgetTypemNoteSettingStatusListener
* onWidgetChangedtruefalse
*
* @return truefalse
*/
public synchronized boolean saveNote() { public synchronized boolean saveNote() {
if (isWorthSaving()) { if (isWorthSaving()) {
if (!existInDatabase()) { if (!existInDatabase()) {
@ -201,9 +264,9 @@ public class WorkingNote {
/** /**
* Update widget content if there exist any widget of this note * 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
&& mNoteSettingStatusListener != null) { && mNoteSettingStatusListener!= null) {
mNoteSettingStatusListener.onWidgetChanged(); mNoteSettingStatusListener.onWidgetChanged();
} }
return true; return true;
@ -212,54 +275,117 @@ public class WorkingNote {
} }
} }
/**
* mNoteId0
* mNoteId0truefalse
*
* @return truefalse
*/
public boolean existInDatabase() { public boolean existInDatabase() {
return mNoteId > 0; return mNoteId > 0;
} }
/**
*
* mIsDeletedtrueexistInDatabaseTextUtils.isEmpty
* existInDatabasemNote.isLocalModifiedfalse
* true
*
* @return truefalse
*/
private boolean isWorthSaving() { private boolean isWorthSaving() {
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) { || (existInDatabase() &&!mNote.isLocalModified())) {
return false; return false;
} else { } else {
return true; return true;
} }
} }
/**
*
* NoteSettingChangedListenermNoteSettingStatusListener
* 便
*
* @param l NoteSettingChangedListener
*/
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l; mNoteSettingStatusListener = l;
} }
/**
*
* `date` `mAlertDate`
* `mAlertDate` `mNote` `setNoteValue`
*
* `mNoteSettingStatusListener`
* `onClockAlertChanged` 便
*
*
* @param date
* @param set
*
*/
public void setAlertDate(long date, boolean set) { public void setAlertDate(long date, boolean set) {
if (date != mAlertDate) { if (date!= mAlertDate) {
mAlertDate = date; mAlertDate = date;
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
} }
if (mNoteSettingStatusListener != null) { if (mNoteSettingStatusListener!= null) {
mNoteSettingStatusListener.onClockAlertChanged(date, set); mNoteSettingStatusListener.onClockAlertChanged(date, set);
} }
} }
/**
* `mark` `mIsDeleted`
* `mark` `true` `mWidgetId` ID
* `mWidgetType` `mNoteSettingStatusListener`
* `onWidgetChanged`
*
*
* @param mark `true` `false`
*/
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
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { && mWidgetType!= Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener!= null) {
mNoteSettingStatusListener.onWidgetChanged(); mNoteSettingStatusListener.onWidgetChanged();
} }
} }
/**
* ID `id` `mBgColorId`
* ID `mBgColorId` `id`
* `mNoteSettingStatusListener` `onBackgroundColorChanged`
* 便
* `mNote` `setNoteValue` ID
*
*
* @param id ID
*/
public void setBgColorId(int id) { public void setBgColorId(int id) {
if (id != mBgColorId) { if (id!= mBgColorId) {
mBgColorId = id; mBgColorId = id;
if (mNoteSettingStatusListener != null) { if (mNoteSettingStatusListener!= null) {
mNoteSettingStatusListener.onBackgroundColorChanged(); mNoteSettingStatusListener.onBackgroundColorChanged();
} }
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
} }
} }
/**
* `mode` `mMode`
* `mNoteSettingStatusListener`
* `onCheckListModeChanged` `mMode``mode`
* 便
* `mMode` `mode` `mNote` `setTextData`
*
*
* @param mode
*/
public void setCheckListMode(int mode) { public void setCheckListMode(int mode) {
if (mMode != mode) { if (mMode!= mode) {
if (mNoteSettingStatusListener != null) { if (mNoteSettingStatusListener!= null) {
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
} }
mMode = mode; mMode = mode;
@ -267,20 +393,44 @@ public class WorkingNote {
} }
} }
/**
* `type` `mWidgetType`
* `mWidgetType` `type`
* `mNote` `setNoteValue`
*
*
* @param type
*/
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 `id` `mWidgetId`
* ID `mWidgetId` `id`
* `mNote` `setNoteValue` ID
* ID
*
* @param id 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));
} }
} }
/**
* `text` `mContent`
* `mContent` `text`
* `mNote` `setTextData`
* 便
*
* @param text
*/
public void setWorkingText(String text) { public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) { if (!TextUtils.equals(mContent, text)) {
mContent = text; mContent = text;
@ -288,80 +438,194 @@ public class WorkingNote {
} }
} }
/**
* Call Note
* `mNote` `setCallData` `CallNote.CALL_DATE``CallNote.PHONE_NUMBER`
* `callDate` `phoneNumber`
* `mNote` `setNoteValue` ID`NoteColumns.PARENT_ID`ID`Notes.ID_CALL_RECORD_FOLDER`
* `Notes`
*
* @param phoneNumber
* @param callDate
*/
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));
} }
/**
* `mAlertDate` 0
* `mAlertDate` 0 `true` `false`
*
* @return `true` `false`
*/
public boolean hasClockAlert() { public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false); return (mAlertDate > 0? true : false);
} }
/**
* `mContent`
* 便使
*
* @return `String`
*/
public String getContent() { public String getContent() {
return mContent; return mContent;
} }
/**
* `mAlertDate`
*
*
* @return `long`
*/
public long getAlertDate() { public long getAlertDate() {
return mAlertDate; return mAlertDate;
} }
/**
* `mModifiedDate`
*
*
* @return `long`
*/
public long getModifiedDate() { public long getModifiedDate() {
return mModifiedDate; return mModifiedDate;
} }
/**
* ID`mBgColorId`ID `NoteBgResources` `getNoteBgResource`
* ID
*
* @return ID `int` `NoteBgResources` `mBgColorId`
*/
public int getBgColorResId() { public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId); return NoteBgResources.getNoteBgResource(mBgColorId);
} }
/**
* ID `mBgColorId`
* 使
*
* @return ID `int`
*/
public int getBgColorId() { public int getBgColorId() {
return mBgColorId; return mBgColorId;
} }
/**
* ID`mBgColorId`ID `NoteBgResources` `getNoteTitleBgResource`
* ID
*
* @return ID `int` `NoteBgResources` `mBgColorId`
*/
public int getTitleBgResId() { public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId); return NoteBgResources.getNoteTitleBgResource(mBgColorId);
} }
/**
* `mMode`
*
*
* @return `int`
*/
public int getCheckListMode() { public int getCheckListMode() {
return mMode; return mMode;
} }
/**
* ID `mNoteId`
* 便ID
*
* @return `long`
*/
public long getNoteId() { public long getNoteId() {
return mNoteId; return mNoteId;
} }
/**
* ID `mFolderId`
* 便
*
* @return ID `long`
*/
public long getFolderId() { public long getFolderId() {
return mFolderId; return mFolderId;
} }
/**
* ID `mWidgetId`
*
*
* @return ID `int`
*/
public int getWidgetId() { public int getWidgetId() {
return mWidgetId; return mWidgetId;
} }
/**
* Widget
* `mWidgetType`
* 便
*
*
* @return
*/
public int getWidgetType() { public int getWidgetType() {
return mWidgetType; return mWidgetType;
} }
/**
* `NoteSettingChangedListener`
* `WorkingNote` `setOnSettingStatusChangedListener`
* 使
*/
public interface NoteSettingChangedListener { public interface NoteSettingChangedListener {
/** /**
* Called when the background color of current note has just changed *
* `setBgColorId` ID
*
* - 使
* -
* -
*/ */
void onBackgroundColorChanged(); void onBackgroundColorChanged();
/** /**
* Called when user set clock * `setAlertDate`
*
* - `date`
* -
* - 便
* `date` 便 `set`
*
*
* @param date
* @param set
*/ */
void onClockAlertChanged(long date, boolean set); void onClockAlertChanged(long date, boolean set);
/** /**
* Call when user create note from widget * ID
*
* -
* - ID
*
*/ */
void onWidgetChanged(); void onWidgetChanged();
/** /**
* Call when switch between check list mode and normal mode * `setCheckListMode`
* @param oldMode is previous mode before change * `oldMode` `newMode`
* @param newMode is new mode * - 便
*
* -
*
*
* @param oldMode 便
* @param newMode 使
*/ */
void onCheckListModeChanged(int oldMode, int newMode); void onCheckListModeChanged(int oldMode, int newMode);
} }

@ -37,10 +37,20 @@ import java.io.PrintStream;
public class BackupUtils { public class BackupUtils {
// 用于日志记录的标签,方便在日志输出中准确识别该类相关的操作记录,利于调试和问题排查
private static final String TAG = "BackupUtils"; private static final String TAG = "BackupUtils";
// Singleton stuff
// 单例模式相关用于保存唯一的BackupUtils实例对象
private static BackupUtils sInstance; private static BackupUtils sInstance;
/**
* BackupUtils
* 线
* sInstancenull
*
* @param context 访
* @return BackupUtils
*/
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);
@ -49,43 +59,75 @@ public class BackupUtils {
} }
/** /**
* Following states are signs to represents backup or restore *
* status * 便
*/ */
// 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 // 表示备份文件不存在的状态码
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 // 表示数据格式不正确,可能被其他程序篡改或破坏的状态码
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 // 表示出现一些运行时异常,导致备份或恢复操作失败的状态码
public static final int STATE_SYSTEM_ERROR = 3; public static final int STATE_SYSTEM_ERROR = 3;
// Backup or restore success // 表示备份或恢复操作成功的状态码
public static final int STATE_SUCCESS = 4; public static final int STATE_SUCCESS = 4;
// 用于文本导出相关操作的对象,负责具体的将数据导出为文本的功能实现
private TextExport mTextExport; private TextExport mTextExport;
/**
* BackupUtils
* TextExport便TextExport使
*
* @param context TextExport
*/
private BackupUtils(Context context) { private BackupUtils(Context context) {
mTextExport = new TextExport(context); mTextExport = new TextExport(context);
} }
/**
* SDEnvironment.MEDIA_MOUNTED
*
* @return truefalse
*/
private static boolean externalStorageAvailable() { private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
} }
/**
* TextExportexportToText
*
*
* @return STATE_*
*/
public int exportToText() { public int exportToText() {
return mTextExport.exportToText(); return mTextExport.exportToText();
} }
/**
* TextExport
*
* @return String
*/
public String getExportedTextFileName() { public String getExportedTextFileName() {
return mTextExport.mFileName; return mTextExport.mFileName;
} }
/**
* TextExport
*
* @return String
*/
public String getExportedTextFileDir() { public String getExportedTextFileDir() {
return mTextExport.mFileDirectory; return mTextExport.mFileDirectory;
} }
/**
* TextExport
*/
private static class TextExport { private static class TextExport {
// 定义用于查询笔记基本信息时的投影Projection数组指定要获取的列如笔记ID、修改日期、摘要、类型等
private static final String[] NOTE_PROJECTION = { private static final String[] NOTE_PROJECTION = {
NoteColumns.ID, NoteColumns.ID,
NoteColumns.MODIFIED_DATE, NoteColumns.MODIFIED_DATE,
@ -93,12 +135,14 @@ public class BackupUtils {
NoteColumns.TYPE NoteColumns.TYPE
}; };
// 对应NOTE_PROJECTION数组中笔记ID列的索引常量方便在获取游标数据时准确取值
private static final int NOTE_COLUMN_ID = 0; private static final int NOTE_COLUMN_ID = 0;
// 对应NOTE_PROJECTION数组中修改日期列的索引常量便于从游标中获取笔记的最后修改时间数据
private static final int NOTE_COLUMN_MODIFIED_DATE = 1; private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
// 对应NOTE_PROJECTION数组中摘要列的索引常量用于获取笔记的摘要信息可能用于展示等用途
private static final int NOTE_COLUMN_SNIPPET = 2; private static final int NOTE_COLUMN_SNIPPET = 2;
// 定义用于查询笔记详细数据如内容、MIME类型等时的投影Projection数组明确要获取的数据列
private static final String[] DATA_PROJECTION = { private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT, DataColumns.CONTENT,
DataColumns.MIME_TYPE, DataColumns.MIME_TYPE,
@ -108,23 +152,37 @@ public class BackupUtils {
DataColumns.DATA4, DataColumns.DATA4,
}; };
// 对应DATA_PROJECTION数组中内容列的索引常量用于从游标中获取笔记的文本内容数据
private static final int DATA_COLUMN_CONTENT = 0; private static final int DATA_COLUMN_CONTENT = 0;
// 对应DATA_PROJECTION数组中MIME类型列的索引常量用于判断数据的具体类型如通话记录笔记、普通文本笔记等
private static final int DATA_COLUMN_MIME_TYPE = 1; private static final int DATA_COLUMN_MIME_TYPE = 1;
// 对应DATA_PROJECTION数组中通话日期列的索引常量用于获取通话记录笔记的通话日期数据如果是通话记录类型
private static final int DATA_COLUMN_CALL_DATE = 2; private static final int DATA_COLUMN_CALL_DATE = 2;
// 对应DATA_PROJECTION数组中电话号码列的索引常量用于获取通话记录笔记的电话号码数据如果是通话记录类型
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;
// 对应TEXT_FORMAT数组中文件夹名称格式化模板的索引常量用于格式化文件夹名称的输出格式
private static final int FORMAT_FOLDER_NAME = 0; private static final int FORMAT_FOLDER_NAME = 0;
// 对应TEXT_FORMAT数组中笔记日期格式化模板的索引常量用于格式化笔记修改日期等日期信息的输出格式
private static final int FORMAT_NOTE_DATE = 1; private static final int FORMAT_NOTE_DATE = 1;
// 对应TEXT_FORMAT数组中笔记内容格式化模板的索引常量用于格式化笔记具体内容如文本内容、电话号码等的输出格式
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;
/**
* TextExport
* TEXT_FORMAT
*
* @param context
*/
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;
@ -132,28 +190,40 @@ public class BackupUtils {
mFileDirectory = ""; mFileDirectory = "";
} }
/**
*
*
* @param id TEXT_FORMAT
* @return
*/
private String getFormat(int id) { private String getFormat(int id) {
return TEXT_FORMAT[id]; return TEXT_FORMAT[id];
} }
/** /**
* Export the folder identified by folder id to text * IDPrintStream
*
* 1.
* 2. exportNoteToTextPrintStream
*
* @param folderId
* @param ps PrintStream
*/ */
private void exportFolderToText(String folderId, PrintStream ps) { private void exportFolderToText(String folderId, PrintStream ps) {
// Query notes belong to this folder // 使用内容解析器查询属于该文件夹的所有笔记根据NOTE_PROJECTION指定的列和通过NoteColumns.PARENT_ID条件筛选出对应文件夹下的笔记记录
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
}, null); }, null);
if (notesCursor != null) { if (notesCursor!= null) {
if (notesCursor.moveToFirst()) { if (notesCursor.moveToFirst()) {
do { do {
// Print note's last modified date // 按照格式化模板输出笔记的最后修改日期信息先获取格式化模板再通过DateFormat对日期进行格式化处理最后将格式化后的字符串输出到PrintStream中
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 // 获取当前笔记的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());
@ -163,40 +233,52 @@ public class BackupUtils {
} }
/** /**
* Export note identified by id to a print stream * IDPrintStream
* MIME
* 1. DataConstants.CALL_NOTE
* 2. DataConstants.NOTE
*
*
* @param noteId
* @param ps PrintStream
*/ */
private void exportNoteToText(String noteId, PrintStream ps) { private void exportNoteToText(String noteId, PrintStream ps) {
// 使用内容解析器查询该笔记对应的详细数据根据DATA_PROJECTION指定的列和通过DataColumns.NOTE_ID条件筛选出对应笔记的详细数据记录
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
}, null); }, null);
if (dataCursor != null) { if (dataCursor!= null) {
if (dataCursor.moveToFirst()) { if (dataCursor.moveToFirst()) {
do { do {
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 // 获取通话记录笔记的电话号码信息
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);
if (!TextUtils.isEmpty(phoneNumber)) { if (!TextUtils.isEmpty(phoneNumber)) {
// 按照格式化模板输出电话号码信息到PrintStream中
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber)); phoneNumber));
} }
// Print call date // 按照格式化模板输出通话日期信息到PrintStream中先通过DateFormat对日期进行格式化处理
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
if (!TextUtils.isEmpty(location)) { if (!TextUtils.isEmpty(location)) {
// 按照格式化模板输出通话附件位置信息到PrintStream中
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)) {
// 按照格式化模板输出普通文本笔记的内容信息到PrintStream中
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
content)); content));
} }
@ -205,7 +287,7 @@ public class BackupUtils {
} }
dataCursor.close(); dataCursor.close();
} }
// 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
@ -215,8 +297,45 @@ public class BackupUtils {
} }
} }
/** /**
* Note will be exported as text which is user readable *
*
*
* 1. SD `externalStorageAvailable`
* DEBUG `STATE_SD_CARD_UNMOUONTED`
*
*
* 2. `PrintStream` `getExportToTextPrintStream`
* ERROR
* `STATE_SYSTEM_ERROR`
*
* 3.
* - `mContext.getContentResolver().query`
* `NoteColumns.TYPE = Notes.TYPE_FOLDER`IDID`NoteColumns.PARENT_ID <> Notes.ID_TRASH_FOLER`
* IDID`NoteColumns.ID = Notes.ID_CALL_RECORD_FOLDER` `NOTE_PROJECTION`
* -
* - IDIDID
* `mContext.getString(R.string.call_record_folder_name)`
* `NOTE_COLUMN_SNIPPET`
* - `getFormat(FORMAT_FOLDER_NAME)`
* 使 `String.format` `PrintStream`
* - ID `exportFolderToText` `PrintStream`
* -
*
* 4.
* - `mContext.getContentResolver().query`
* `NoteColumns.TYPE = Notes.TYPE_NOTE`ID0 `NOTE_PROJECTION`
* -
* - `getFormat(FORMAT_NOTE_DATE)` `DateFormat`
* 使 `String.format` `PrintStream`
* - ID `exportNoteToText`
* `PrintStream`
* -
*
* 5. `PrintStream` `STATE_SUCCESS`
*
* @return `STATE_*` `STATE_SD_CARD_UNMOUONTED``STATE_SYSTEM_ERROR` `STATE_SUCCESS`
*/ */
public int exportToText() { public int exportToText() {
if (!externalStorageAvailable()) { if (!externalStorageAvailable()) {
@ -237,12 +356,12 @@ public class BackupUtils {
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) { if (folderCursor!= null) {
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);
} else { } else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
@ -261,10 +380,10 @@ public class BackupUtils {
Cursor noteCursor = mContext.getContentResolver().query( Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI, Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + "AND" + NoteColumns.PARENT_ID
+ "=0", null, null); + "=0", null, null);
if (noteCursor != null) { if (noteCursor!= null) {
if (noteCursor.moveToFirst()) { if (noteCursor.moveToFirst()) {
do { do {
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
@ -283,7 +402,26 @@ public class BackupUtils {
} }
/** /**
* Get a print stream pointed to the file {@generateExportedTextFile} * `PrintStream`
*
*
* 1. `generateFileMountedOnSDcard` SD
* IDID
*
* 2. `generateFileMountedOnSDcard` `null`ERROR
* `null` `PrintStream`
*
* 3. `mFileName` 便
* `mContext.getString(R.string.file_path)` `mFileDirectory`
* 便
*
* 4. `FileOutputStream` `FileOutputStream` `PrintStream`
* `FileOutputStream` `PrintStream` `FileNotFoundException`
* `NullPointerException` `null` `PrintStream`
*
* 5. `PrintStream` 使 `PrintStream`
*
* @return `PrintStream` `null`
*/ */
private PrintStream getExportToTextPrintStream() { private PrintStream getExportToTextPrintStream() {
File file = generateFileMountedOnSDcard(mContext, R.string.file_path, File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
@ -310,7 +448,32 @@ public class BackupUtils {
} }
/** /**
* Generate the text file to store imported data * SD
*
*
* 1. `StringBuilder`
* `Environment.getExternalStorageDirectory()` `StringBuilder`
* `context.getString(filePathResId)`
*
* 2. `StringBuilder`
* 使 `DateFormat.format` `System.currentTimeMillis()` `format_date_ymd`
* 便
*
* 3. `File`
*
* 4. `filedir.mkdir()` `SecurityException`
*
*
* 5. `file.createNewFile()` `IOException`
*
*
* 6. `File`
* `null`
*
* @param context
* @param filePathResId ID
* @param fileNameFormatResId ID
* @return `null`
*/ */
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();
@ -339,6 +502,6 @@ public class BackupUtils {
return null; return null;
} }
} }}

@ -36,107 +36,185 @@ import java.util.HashSet;
public class DataUtils { public class DataUtils {
// 用于日志记录的标签,方便在日志输出中准确识别该类相关的操作记录,利于调试和问题排查
public static final String TAG = "DataUtils"; public static final String TAG = "DataUtils";
/**
* ContentResolverIDHashSet<Long>
*
* @param resolver 访
* @param ids LongHashSetnull
* @return truefalse
*/
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) { public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
// 如果传入的ids集合为null说明没有指定要删除的笔记ID在日志中记录相应信息日志级别为DEBUG并直接返回true表示无需进行实际删除操作。
if (ids == null) { if (ids == null) {
Log.d(TAG, "the ids is null"); Log.d(TAG, "the ids is null");
return true; return true;
} }
// 如果ids集合大小为0意味着集合中没有包含有效的笔记ID同样在日志中记录信息日志级别为DEBUG然后返回true表示没有笔记可删除。
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;
} }
// 创建一个用于存储内容提供器操作ContentProviderOperation的列表后续将把要执行的删除操作添加到这个列表中。
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
// 遍历传入的ids集合对每个要删除的笔记ID进行处理
for (long id : ids) { for (long id : ids) {
if(id == Notes.ID_ROOT_FOLDER) { // 如果笔记ID是系统根文件夹的IDNotes.ID_ROOT_FOLDER则不允许删除该系统文件夹在日志中记录错误信息日志级别为ERROR并跳过本次循环继续处理下一个ID。
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;
} }
// 创建一个用于删除操作的ContentProviderOperation构建器指定要删除的笔记的URI通过ContentUris.withAppendedId方法根据笔记ID构建对应的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 {
// 通过内容解析器执行批量操作传入内容提供器的授权信息Notes.AUTHORITY和操作列表获取操作结果数组ContentProviderResult[])。
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
// 如果操作结果数组为null或者数组长度为0或者数组中第一个元素为null表示删除操作失败在日志中记录相应信息日志级别为DEBUG并返回false。
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) {
// 如果出现远程异常RemoteException通常是在与内容提供器进行远程通信时出现问题在日志中记录详细的异常信息包括异常的类名和具体消息异常级别为ERROR。
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) {
// 如果出现操作应用异常OperationApplicationException一般是在应用执行批量操作时出现问题同样在日志中记录详细异常信息日志级别为ERROR
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;
} }
/**
* ID
*
* @param resolver 访
* @param id Long
* @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对象用于存储要更新的键值对即笔记的相关属性及其新的值。
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
// 设置笔记的新父文件夹ID即目标文件夹ID将其放入ContentValues中键为NoteColumns.PARENT_ID。
values.put(NoteColumns.PARENT_ID, desFolderId); values.put(NoteColumns.PARENT_ID, desFolderId);
// 设置笔记的原始父文件夹ID即当前所在文件夹ID键为NoteColumns.ORIGIN_PARENT_ID可能用于后续追溯笔记的移动历史等情况。
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);
// 设置笔记的本地修改标志为1表示该笔记有本地修改这里的修改指的是移动操作导致的变化键为NoteColumns.LOCAL_MODIFIED。
values.put(NoteColumns.LOCAL_MODIFIED, 1); values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 通过内容解析器根据笔记的URI通过ContentUris.withAppendedId方法根据笔记ID构建更新笔记的属性传入ContentValues对象以及其他相关参数此处后两个参数为null表示无额外的筛选条件和排序方式
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 访
* @param ids LongHashSetnull
* @param folderId ID
* @return truefalse
*/
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids, public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) { long folderId) {
// 如果传入的ids集合为null说明没有指定要移动的笔记ID在日志中记录相应信息日志级别为DEBUG并直接返回true表示无需进行实际移动操作。
if (ids == null) { if (ids == null) {
Log.d(TAG, "the ids is null"); Log.d(TAG, "the ids is null");
return true; return true;
} }
// 创建一个用于存储内容提供器操作ContentProviderOperation的列表后续将把要执行的批量移动操作添加到这个列表中。
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
// 遍历传入的ids集合对每个要移动的笔记ID进行处理构建相应的更新操作。
for (long id : ids) { for (long id : ids) {
// 创建一个用于更新操作的ContentProviderOperation构建器指定要更新的笔记的URI通过ContentUris.withAppendedId方法根据笔记ID构建对应的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即目标文件夹ID通过withValue方法将其添加到更新操作构建器中键为NoteColumns.PARENT_ID。
builder.withValue(NoteColumns.PARENT_ID, folderId); builder.withValue(NoteColumns.PARENT_ID, folderId);
// 设置笔记的本地修改标志为1表示该笔记有本地修改这里的修改指的是移动操作导致的变化键为NoteColumns.LOCAL_MODIFIED。
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
// 将构建好的更新操作添加到操作列表中。
operationList.add(builder.build()); operationList.add(builder.build());
} }
try { try {
// 通过内容解析器执行批量操作传入内容提供器的授权信息Notes.AUTHORITY和操作列表获取操作结果数组ContentProviderResult[])。
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
// 如果操作结果数组为null或者数组长度为0或者数组中第一个元素为null表示批量移动操作失败在日志中记录相应信息日志级别为DEBUG并返回false。
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) {
// 如果出现远程异常RemoteException通常是在与内容提供器进行远程通信时出现问题在日志中记录详细的异常信息包括异常的类名和具体消息异常级别为ERROR。
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) {
// 如果出现操作应用异常OperationApplicationException一般是在应用执行批量操作时出现问题同样在日志中记录详细异常信息日志级别为ERROR
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}} *
*
* @param resolver 访
* @return int
*/ */
public static int getUserFolderCount(ContentResolver resolver) { public static int getUserFolderCount(ContentResolver resolver) {
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, // 通过内容解析器查询数据库指定查询的URI为Notes.CONTENT_NOTE_URI表示笔记相关的数据表因为文件夹信息也存储在该表中可能通过类型等字段区分
// 要获取的列是"COUNT(*)",表示统计符合条件的记录数量。
// 查询条件是类型为文件夹NoteColumns.TYPE + "=?且父文件夹ID不等于回收站文件夹IDNoteColumns.PARENT_ID + "<>?"),通过传入对应的参数值来筛选出用户创建的非回收站文件夹。
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 + "<>?",
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
null); null);
int count = 0; int count = 0;
if(cursor != null) { // 如果查询到的游标Cursor不为null说明获取到了有效的查询结果尝试获取数据。
if(cursor.moveToFirst()) { if (cursor!= null) {
// 将游标移动到第一条记录(如果有数据的话),因为查询结果只有一条记录(统计数量的结果),所以只需要获取这一条记录中的数据即可。
if (cursor.moveToFirst()) {
try { try {
// 尝试从游标中获取第一个列索引为0的数据这里就是统计得到的文件夹数量将其赋值给count变量。
count = cursor.getInt(0); count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
// 如果出现索引越界异常IndexOutOfBoundsException通常是获取数据的索引超出了游标实际包含的列范围在日志中记录详细的异常信息日志级别为ERROR
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 访
* @param noteId Long
* @param type
* @return truefalse
*/
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
// 通过内容解析器查询数据库指定查询的URI为Notes.CONTENT_NOTE_URI对应笔记数据表查询条件是类型为指定类型NoteColumns.TYPE + "=?且父文件夹ID不等于回收站文件夹IDNoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER传入对应的参数值进行筛选。
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,
@ -144,60 +222,114 @@ public class DataUtils {
null); null);
boolean exist = false; boolean exist = false;
if (cursor != null) { // 如果查询到的游标Cursor不为null说明获取到了有效的查询结果进一步判断是否有符合条件的记录存在。
if (cursor!= null) {
// 通过获取游标中的记录数量来判断是否存在符合条件的笔记如果记录数量大于0表示存在将exist变量设置为true。
if (cursor.getCount() > 0) { if (cursor.getCount() > 0) {
exist = true; exist = true;
} }
// 关闭游标,释放相关资源。
cursor.close(); cursor.close();
} }
// 返回笔记是否存在(即是否可见)的判断结果。
return exist; return exist;
} }
/**
* ID
*
* @param resolver 访
* @param noteId Long
* @return truefalse
*/
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
// 通过内容解析器查询数据库指定查询的URI为Notes.CONTENT_NOTE_URI对应笔记数据表此处查询条件为空即查询所有记录尝试查找是否存在指定笔记ID的记录。
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);
boolean exist = false; boolean exist = false;
if (cursor != null) { // 如果查询到的游标Cursor不为null说明获取到了有效的查询结果进一步判断是否有符合条件的记录存在。
if (cursor!= null) {
// 通过获取游标中的记录数量来判断是否存在符合条件的笔记如果记录数量大于0表示存在将exist变量设置为true。
if (cursor.getCount() > 0) { if (cursor.getCount() > 0) {
exist = true; exist = true;
} }
// 关闭游标,释放相关资源。
cursor.close(); cursor.close();
} }
// 返回笔记是否存在的判断结果。
return exist; return exist;
} }
/**
*
* 使 `ContentResolver` `Notes.CONTENT_DATA_URI`
* `null`ID
*
* @param resolver `ContentResolver`
* @param dataId
* @return `dataId` `true` `false`
*/
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
// 使用ContentResolver发起查询操作查询的URI是Notes.CONTENT_DATA_URI对应特定的数据表
// 传入的查询条件等参数都为null表示查询所有记录只是依据传入的数据ID来定位可能存在的记录。
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);
boolean exist = false; boolean exist = false;
if (cursor != null) { // 如果查询返回的游标Cursor不为null说明查询有结果进一步判断游标中是否有记录即记录数量大于0
if (cursor!= null) {
if (cursor.getCount() > 0) { if (cursor.getCount() > 0) {
exist = true; exist = true;
} }
// 关闭游标,释放相关资源,避免内存泄漏等问题。
cursor.close(); cursor.close();
} }
return exist; return exist;
} }
/**
*
* `ContentResolver` `Notes.CONTENT_NOTE_URI`
*
* @param resolver `ContentResolver`
* @param name
* @return `name` `true` `false`
*/
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
// 使用ContentResolver发起查询操作查询的URI为Notes.CONTENT_NOTE_URI对应笔记相关的数据表
// 查询条件设定为筛选出类型是文件夹NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER、父文件夹ID不等于回收站文件夹IDNoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
// 且文件夹摘要(可能用于显示文件夹名称等,此处对应条件判断)等于传入的 `name` 的记录,通过传入的 `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 +
" AND " + NoteColumns.SNIPPET + "=?", " AND " + NoteColumns.SNIPPET + "=?",
new String[] { name }, null); new String[] { name }, null);
boolean exist = false; boolean exist = false;
if(cursor != null) { // 如果查询返回的游标Cursor不为null说明查询有结果进一步判断游标中是否有记录即记录数量大于0
if(cursor.getCount() > 0) { if (cursor!= null) {
if (cursor.getCount() > 0) {
exist = true; exist = true;
} }
// 关闭游标,释放相关资源。
cursor.close(); cursor.close();
} }
return exist; return exist;
} }
/**
* Widget `HashSet`
* `ContentResolver` `Notes.CONTENT_NOTE_URI` `folderId`ID
* `AppWidgetAttribute` `HashSet`
*
* @param resolver `ContentResolver`
* @param folderId
* @return `HashSet` `null`
*/
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) { public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
// 使用ContentResolver发起查询操作查询的URI为Notes.CONTENT_NOTE_URI对应笔记相关数据表
// 查询的列指定为NoteColumns.WIDGET_ID小部件ID和NoteColumns.WIDGET_TYPE小部件类型
// 查询条件设定为筛选出父文件夹ID等于传入的 `folderId` 的记录,通过传入的 `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 + "=?",
@ -205,45 +337,81 @@ public class DataUtils {
null); null);
HashSet<AppWidgetAttribute> set = null; HashSet<AppWidgetAttribute> set = null;
if (c != null) { // 如果查询返回的游标Cursor不为null说明查询有结果进一步处理获取到的数据。
if (c!= null) {
if (c.moveToFirst()) { if (c.moveToFirst()) {
set = new HashSet<AppWidgetAttribute>(); set = new HashSet<AppWidgetAttribute>();
do { do {
try { try {
// 创建一个AppWidgetAttribute对象用于封装小部件的属性信息。
AppWidgetAttribute widget = new AppWidgetAttribute(); AppWidgetAttribute widget = new AppWidgetAttribute();
// 从游标中获取小部件ID并赋值给AppWidgetAttribute对象的widgetId属性这里依据查询时指定的列顺序获取对应列的数据。
widget.widgetId = c.getInt(0); widget.widgetId = c.getInt(0);
// 从游标中获取小部件类型并赋值给AppWidgetAttribute对象的widgetType属性。
widget.widgetType = c.getInt(1); widget.widgetType = c.getInt(1);
// 将封装好小部件属性信息的AppWidgetAttribute对象添加到HashSet集合中。
set.add(widget); set.add(widget);
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
// 如果在获取游标数据时出现索引越界异常例如获取的列索引超出了实际查询到的列范围则记录错误日志日志级别为ERROR。
Log.e(TAG, e.toString()); Log.e(TAG, e.toString());
} }
} while (c.moveToNext()); } while (c.moveToNext());
} }
// 关闭游标,释放相关资源。
c.close(); c.close();
} }
return set; return set;
} }
/**
* ID
* `ContentResolver` `Notes.CONTENT_DATA_URI` IDMIME
*
* @param resolver `ContentResolver`
* @param noteId
* @return ID ""
*/
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
// 使用ContentResolver发起查询操作查询的URI为Notes.CONTENT_DATA_URI对应笔记相关的数据表可能包含通话记录等详细数据
// 查询的列指定为CallNote.PHONE_NUMBER表示电话号码的列
// 查询条件设定为筛选出笔记ID等于传入的 `noteId` 且MIME类型等于CallNote.CONTENT_ITEM_TYPE特定的MIME类型可能用于标识通话记录相关的数据类型的记录
// 通过传入的 `noteId` 和 `CallNote.CONTENT_ITEM_TYPE` 参数构建查询条件中的参数值。
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 + "=?",
new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE }, new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE },
null); null);
if (cursor != null && cursor.moveToFirst()) { if (cursor!= null && cursor.moveToFirst()) {
try { try {
// 尝试从游标中获取第一个列因为查询时只指定了获取电话号码这一列索引为0的数据即电话号码信息并返回。
return cursor.getString(0); return cursor.getString(0);
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
// 如果在获取游标数据时出现索引越界异常则记录错误日志日志级别为ERROR记录详细的异常信息。
Log.e(TAG, "Get call number fails " + e.toString()); Log.e(TAG, "Get call number fails " + e.toString());
} finally { } finally {
// 无论是否成功获取到电话号码,都要关闭游标,释放相关资源。
cursor.close(); cursor.close();
} }
} }
return ""; return "";
} }
/**
* ID
* `ContentResolver` `Notes.CONTENT_DATA_URI` MIMEID
*
* @param resolver `ContentResolver` ID
* @param phoneNumber ID
* @param callDate
* @return MIMEID0
*/
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
// 使用ContentResolver发起查询操作查询的URI为Notes.CONTENT_DATA_URI对应笔记相关的数据表包含通话记录等详细数据
// 查询的列指定为CallNote.NOTE_ID表示笔记ID的列
// 查询条件设定为筛选出通话日期等于传入的 `callDate`、MIME类型等于CallNote.CONTENT_ITEM_TYPE特定的MIME类型标识通话记录相关数据类型
// 并且电话号码通过自定义函数PHONE_NUMBERS_EQUAL具体实现可能在别处用于更准确地匹配电话号码匹配传入的 `phoneNumber` 的记录,
// 通过传入的 `callDate`、`CallNote.CONTENT_ITEM_TYPE` 和 `phoneNumber` 参数构建查询条件中的参数值。
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("
@ -251,42 +419,68 @@ public class DataUtils {
new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber }, new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber },
null); null);
if (cursor != null) { if (cursor!= null) {
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
try { try {
// 尝试从游标中获取第一个列因为查询时只指定了获取笔记ID这一列索引为0的数据即笔记ID信息并返回。
return cursor.getLong(0); return cursor.getLong(0);
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
// 如果在获取游标数据时出现索引越界异常则记录错误日志日志级别为ERROR记录详细的异常信息。
Log.e(TAG, "Get call note id fails " + e.toString()); Log.e(TAG, "Get call note id fails " + e.toString());
} }
} }
// 关闭游标,释放相关资源。
cursor.close(); cursor.close();
} }
return 0; return 0;
} }
/**
* IDSnippet
* `ContentResolver` `Notes.CONTENT_NOTE_URI` ID
* ID `IllegalArgumentException`
*
* @param resolver `ContentResolver`
* @param noteId
* @return ID `IllegalArgumentException`
*/
public static String getSnippetById(ContentResolver resolver, long noteId) { public static String getSnippetById(ContentResolver resolver, long noteId) {
// 使用ContentResolver发起查询操作查询的URI为Notes.CONTENT_NOTE_URI对应笔记相关的数据表
// 查询的列指定为NoteColumns.SNIPPET表示摘要的列
// 查询条件设定为筛选出笔记ID等于传入的 `noteId` 的记录,通过传入的 `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 + "=?",
new String [] { String.valueOf(noteId)}, new String [] { String.valueOf(noteId)},
null); null);
if (cursor != null) { if (cursor!= null) {
String snippet = ""; String snippet = "";
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
// 尝试从游标中获取第一个列因为查询时只指定了获取摘要这一列索引为0的数据即摘要信息并赋值给变量 `snippet`。
snippet = cursor.getString(0); snippet = cursor.getString(0);
} }
// 关闭游标,释放相关资源。
cursor.close(); cursor.close();
return snippet; return snippet;
} }
throw new IllegalArgumentException("Note is not found with id: " + noteId); throw new IllegalArgumentException("Note is not found with id: " + noteId);
} }
/**
* Snippet
* `\n`
*
* @param snippet
* @return
*/
public static String getFormattedSnippet(String snippet) { public static String getFormattedSnippet(String snippet) {
if (snippet != null) { if (snippet!= null) {
// 去除字符串前后的空白字符调用trim方法进行处理。
snippet = snippet.trim(); snippet = snippet.trim();
int index = snippet.indexOf('\n'); int index = snippet.indexOf('\n');
if (index != -1) { if (index!= -1) {
// 如果字符串中包含换行符截取换行符之前的部分作为最终的格式化后的字符串通过substring方法实现截取操作。
snippet = snippet.substring(0, index); snippet = snippet.substring(0, index);
} }
} }

@ -18,96 +18,141 @@ package net.micode.notes.tool;
public class GTaskStringUtils { public class GTaskStringUtils {
// 用于表示在 GTask 相关 JSON 数据中 "action_id" 字段的名称,可能在处理 GTask 操作的唯一标识符相关逻辑时使用。
public final static String GTASK_JSON_ACTION_ID = "action_id"; public final static String GTASK_JSON_ACTION_ID = "action_id";
// 用于表示在 GTask 相关 JSON 数据中 "action_list" 字段的名称,推测可能用于存放一系列操作相关信息的列表,具体用途取决于 GTask 相关业务逻辑。
public final static String GTASK_JSON_ACTION_LIST = "action_list"; public final static String GTASK_JSON_ACTION_LIST = "action_list";
// 用于表示在 GTask 相关 JSON 数据中 "action_type" 字段的名称,该字段用于标识操作的类型,不同的操作类型对应不同的业务动作。
public final static String GTASK_JSON_ACTION_TYPE = "action_type"; public final static String GTASK_JSON_ACTION_TYPE = "action_type";
// 用于表示在 GTask 相关 JSON 数据中一种具体的操作类型 "create",即创建操作,可能用于创建任务、列表等 GTask 相关实体。
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";
// 用于表示在 GTask 相关 JSON 数据中一种具体的操作类型 "get_all",即获取所有操作,可能用于获取所有任务、列表等相关数据的场景。
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
// 用于表示在 GTask 相关 JSON 数据中一种具体的操作类型 "move",即移动操作,可能用于移动任务、列表等在不同位置(如不同列表之间)的场景。
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";
// 用于表示在 GTask 相关 JSON 数据中一种具体的操作类型 "update",即更新操作,用于更新任务、列表等的属性信息。
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";
// 用于表示在 GTask 相关 JSON 数据中 "creator_id" 字段的名称,可能用于标识创建某个任务、列表等实体的用户的唯一标识符。
public final static String GTASK_JSON_CREATOR_ID = "creator_id"; public final static String GTASK_JSON_CREATOR_ID = "creator_id";
// 用于表示在 GTask 相关 JSON 数据中 "child_entity" 字段的名称,推测是用于表示某个实体(如任务组等)的子实体相关信息,具体内容取决于业务逻辑。
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";
// 用于表示在 GTask 相关 JSON 数据中 "client_version" 字段的名称,可能用于记录客户端的版本信息,便于在不同版本间进行数据兼容等处理。
public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; public final static String GTASK_JSON_CLIENT_VERSION = "client_version";
// 用于表示在 GTask 相关 JSON 数据中 "completed" 字段的名称,可能用于标记某个任务等是否已完成的状态信息。
public final static String GTASK_JSON_COMPLETED = "completed"; public final static String GTASK_JSON_COMPLETED = "completed";
// 用于表示在 GTask 相关 JSON 数据中 "current_list_id" 字段的名称,推测是表示当前所在列表的唯一标识符,例如当前任务所属列表的 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";
// 用于表示在 GTask 相关 JSON 数据中 "default_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";
// 用于表示在 GTask 相关 JSON 数据中 "deleted" 字段的名称,可能用于标记某个实体(如任务、列表等)是否已被删除的状态信息。
public final static String GTASK_JSON_DELETED = "deleted"; public final static String GTASK_JSON_DELETED = "deleted";
// 用于表示在 GTask 相关 JSON 数据中 "dest_list" 字段的名称,推测在移动操作等场景下,用于表示目标列表相关信息,比如移动任务到的目标列表的相关属性等。
public final static String GTASK_JSON_DEST_LIST = "dest_list"; public final static String GTASK_JSON_DEST_LIST = "dest_list";
// 用于表示在 GTask 相关 JSON 数据中 "dest_parent" 字段的名称,可能在移动或关联实体等操作中,用于表示目标父级实体的相关信息,比如目标父任务、父列表等的情况。
public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
// 用于表示在 GTask 相关 JSON 数据中 "dest_parent_type" 字段的名称,结合 "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";
// 用于表示在 GTask 相关 JSON 数据中 "entity_delta" 字段的名称,推测是用于表示实体变化量相关信息,比如某个实体在更新前后的属性差异等情况。
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
// 用于表示在 GTask 相关 JSON 数据中 "entity_type" 字段的名称,用于标识实体的类型,例如是任务类型还是任务组类型等,便于在不同类型实体的处理逻辑中进行区分。
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
// 用于表示在 GTask 相关 JSON 数据中 "get_deleted" 字段的名称,可能用于获取已删除实体相关信息的操作,比如查询已删除任务、列表等的记录。
public final static String GTASK_JSON_GET_DELETED = "get_deleted"; public final static String GTASK_JSON_GET_DELETED = "get_deleted";
// 用于表示在 GTask 相关 JSON 数据中 "id" 字段的名称,通常是用于标识各个实体(如任务、列表等)的唯一标识符,是数据关联和操作定位的重要依据。
public final static String GTASK_JSON_ID = "id"; public final static String GTASK_JSON_ID = "id";
// 用于表示在 GTask 相关 JSON 数据中 "index" 字段的名称,可能用于表示某个实体在列表等结构中的顺序索引,例如任务在任务列表中的排列顺序位置等情况。
public final static String GTASK_JSON_INDEX = "index"; public final static String GTASK_JSON_INDEX = "index";
// 用于表示在 GTask 相关 JSON 数据中 "last_modified" 字段的名称,通常用于记录某个实体(如任务、列表等)最后一次被修改的时间信息,便于进行数据同步、版本控制等操作。
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
// 用于表示在 GTask 相关 JSON 数据中 "latest_sync_point" 字段的名称,推测是用于记录最新的同步时间点或相关标识信息,在多端数据同步等场景下会用到。
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
// 用于表示在 GTask 相关 JSON 数据中 "list_id" 字段的名称,类似前面的列表相关 ID可能用于标识具体某个列表的唯一标识符用于区分不同的任务列表等情况。
public final static String GTASK_JSON_LIST_ID = "list_id"; public final static String GTASK_JSON_LIST_ID = "list_id";
// 用于表示在 GTask 相关 JSON 数据中 "lists" 字段的名称,推测是用于存放多个列表相关信息的集合,例如包含多个任务列表的详细数据等情况。
public final static String GTASK_JSON_LISTS = "lists"; public final static String GTASK_JSON_LISTS = "lists";
// 用于表示在 GTask 相关 JSON 数据中 "name" 字段的名称,可能用于表示任务、列表、用户等各种实体的名称信息,方便在界面展示等场景使用。
public final static String GTASK_JSON_NAME = "name"; public final static String GTASK_JSON_NAME = "name";
// 用于表示在 GTask 相关 JSON 数据中 "new_id" 字段的名称,可能在某些创建、复制或重命名等操作后,用于标识新生成的实体的唯一标识符情况。
public final static String GTASK_JSON_NEW_ID = "new_id"; public final static String GTASK_JSON_NEW_ID = "new_id";
// 用于表示在 GTask 相关 JSON 数据中 "notes" 字段的名称,推测是用于存放与某个实体(如任务等)相关的备注、说明等文本信息内容。
public final static String GTASK_JSON_NOTES = "notes"; public final static String GTASK_JSON_NOTES = "notes";
// 用于表示在 GTask 相关 JSON 数据中 "parent_id" 字段的名称,用于标识某个实体(如任务等)的父级实体的唯一标识符,便于构建任务层级结构等情况。
public final static String GTASK_JSON_PARENT_ID = "parent_id"; public final static String GTASK_JSON_PARENT_ID = "parent_id";
// 用于表示在 GTask 相关 JSON 数据中 "prior_sibling_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";
// 用于表示在 GTask 相关 JSON 数据中 "results" 字段的名称,推测是用于存放操作结果相关信息的集合,比如执行某个任务操作后返回的结果详情等情况。
public final static String GTASK_JSON_RESULTS = "results"; public final static String GTASK_JSON_RESULTS = "results";
// 用于表示在 GTask 相关 JSON 数据中 "source_list" 字段的名称,在移动操作等场景下,与 "dest_list" 相对应,用于表示源列表相关信息,即操作发起时实体所在的原始列表情况。
public final static String GTASK_JSON_SOURCE_LIST = "source_list"; public final static String GTASK_JSON_SOURCE_LIST = "source_list";
// 用于表示在 GTask 相关 JSON 数据中 "tasks" 字段的名称,推测是用于存放多个任务相关信息的集合,例如包含多个任务的详细属性、状态等数据情况。
public final static String GTASK_JSON_TASKS = "tasks"; public final static String GTASK_JSON_TASKS = "tasks";
// 用于表示在 GTask 相关 JSON 数据中 "type" 字段的名称,再次强调用于标识实体类型相关信息,与前面的 "entity_type" 等类似,但可能在不同的 JSON 结构层次或业务逻辑中有不同的使用场景。
public final static String GTASK_JSON_TYPE = "type"; public final static String GTASK_JSON_TYPE = "type";
// 用于表示在 GTask 相关 JSON 数据中一种具体的实体类型 "GROUP",即任务组类型,用于区分不同类型的任务组织形式,比如任务组和普通单个任务的区别。
public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
// 用于表示在 GTask 相关 JSON 数据中一种具体的实体类型 "TASK",即普通任务类型,是最基本的任务单元表示,与任务组等相对应。
public final static String GTASK_JSON_TYPE_TASK = "TASK"; public final static String GTASK_JSON_TYPE_TASK = "TASK";
// 用于表示在 GTask 相关 JSON 数据中 "user" 字段的名称,可能用于存放与用户相关的信息,比如用户的基本资料、权限等情况,具体取决于业务逻辑。
public final static String GTASK_JSON_USER = "user"; public final static String GTASK_JSON_USER = "user";
// 用于表示特定的文件夹前缀 "[MIUI_Notes]",可能在涉及 MIUI 系统下笔记相关功能时,用于标识特定的文件夹或者区分不同来源的笔记数据等情况。
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";
// 用于表示默认文件夹的名称 "Default",可能在笔记、任务等应用中,作为默认创建的文件夹名称,或者在没有指定特定文件夹时的默认归属文件夹等情况使用。
public final static String FOLDER_DEFAULT = "Default"; public final static String FOLDER_DEFAULT = "Default";
// 用于表示通话记录笔记相关的文件夹名称 "Call_Note",可能用于专门存放通话记录相关笔记的文件夹,便于分类管理和查找相关数据。
public final static String FOLDER_CALL_NOTE = "Call_Note"; public final static String FOLDER_CALL_NOTE = "Call_Note";
// 用于表示元数据相关的文件夹名称 "METADATA",可能用于存放各种实体(如任务、笔记等)的元数据信息,用于记录额外的描述、配置等数据情况。
public final static String FOLDER_META = "METADATA"; public final static String FOLDER_META = "METADATA";
// 用于表示在元数据中与 GTask 的 ID 相关的头部标识 "meta_gid",可能用于在元数据结构中明确某个数据项对应的 GTask 的唯一标识符情况,便于数据关联和查找。
public final static String META_HEAD_GTASK_ID = "meta_gid"; public final static String META_HEAD_GTASK_ID = "meta_gid";
// 用于表示在元数据中与笔记相关的头部标识 "meta_note",可能用于在元数据里区分和定位与笔记相关的数据内容,例如笔记的属性、备注等信息在元数据中的标识。
public final static String META_HEAD_NOTE = "meta_note"; public final static String META_HEAD_NOTE = "meta_note";
// 用于表示在元数据中与通用数据相关的头部标识 "meta_data",可能用于更广泛的数据项标识,涵盖除了特定的 GTask ID、笔记等之外的其他数据在元数据中的表示情况。
public final static String META_HEAD_DATA = "meta_data"; public final static String META_HEAD_DATA = "meta_data";
// 用于表示元数据中关于笔记的特定提示信息 "[META INFO] DON'T UPDATE AND DELETE",可能是用于提醒用户或者开发者不要对该部分元数据中的笔记相关信息进行更新和删除操作,可能有特定的业务用途或数据完整性要求。
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";
} }

@ -24,23 +24,39 @@ 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;
// 定义默认的背景颜色,使用前面定义的颜色常量中的 `YELLOW` 作为默认值,
// 意味着在某些没有明确指定背景颜色的情况下,将采用黄色作为默认的背景颜色,
// 具体应用场景可能涉及笔记、界面元素等的背景颜色设置。
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;
// 定义默认的字体大小,使用 `TEXT_MEDIUM` 作为默认值,即在没有特殊指定字体大小时,
// 将采用中等大小的字体作为默认设置,应用于各类文本显示场景中。
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
/**
* `NoteBgResources`Drawable
* ID便
*/
public static class NoteBgResources { public static class NoteBgResources {
private final static int [] BG_EDIT_RESOURCES = new int [] { // 定义一个私有的静态数组,用于存储编辑状态下笔记背景的资源 IDDrawable资源
// 数组中的每个元素对应一种颜色的编辑背景图片资源,索引与前面定义的颜色常量相对应,
// 例如 `R.drawable.edit_yellow` 对应黄色背景的编辑状态下的笔记背景资源。
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,
R.drawable.edit_white, R.drawable.edit_white,
@ -48,7 +64,10 @@ public class ResourceParser {
R.drawable.edit_red R.drawable.edit_red
}; };
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { // 类似地,定义一个私有的静态数组,用于存储编辑状态下笔记标题背景的资源 ID
// 同样每个元素对应一种颜色的编辑标题背景图片资源,索引与颜色常量匹配,
// 用于设置笔记标题部分的背景图片显示,以达到不同颜色风格的视觉效果。
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,
R.drawable.edit_title_white, R.drawable.edit_title_white,
@ -56,15 +75,38 @@ public class ResourceParser {
R.drawable.edit_title_red R.drawable.edit_title_red
}; };
/**
* `id` `BG_EDIT_RESOURCES` ID
* 使
*
* @param id `YELLOW``BLUE`
* @return ID `int` Android ID
*/
public static int getNoteBgResource(int id) { public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id]; return BG_EDIT_RESOURCES[id];
} }
/**
* `id` `BG_EDIT_TITLE_RESOURCES` ID
*
*
* @param id
* @return ID `int` Android 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];
} }
} }
/**
* IDSharedPreferences
* `NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY` `true`
* `NoteBgResources.BG_EDIT_RESOURCES` ID
* ID`BG_DEFAULT_COLOR`
*
* @param context SharedPreferences便
* @return 使 ID `int`
*/
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)) {
@ -74,8 +116,15 @@ public class ResourceParser {
} }
} }
/**
* `NoteItemBgResources`
* ID便
*/
public static class NoteItemBgResources { public static class NoteItemBgResources {
private final static int [] BG_FIRST_RESOURCES = new int [] { // 定义一个私有的静态数组,用于存储列表中第一个笔记项的背景资源 IDDrawable资源
// 数组中的每个元素对应一种颜色的第一个笔记项的背景图片资源,索引与颜色常量相对应,
// 例如 `R.drawable.list_yellow_up` 对应黄色背景的列表中第一个笔记项的背景资源,用于实现不同颜色风格的列表第一项背景显示效果。
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,
R.drawable.list_white_up, R.drawable.list_white_up,
@ -83,7 +132,10 @@ public class ResourceParser {
R.drawable.list_red_up R.drawable.list_red_up
}; };
private final static int [] BG_NORMAL_RESOURCES = new int [] { // 定义一个私有的静态数组,用于存储列表中普通笔记项(非第一个和最后一个)的背景资源 ID
// 每个元素对应一种颜色的普通笔记项背景图片资源,按照颜色索引匹配,
// 用于设置列表中间部分笔记项的背景图片,实现统一的列表项背景风格。
private final static int[] BG_NORMAL_RESOURCES = new int[]{
R.drawable.list_yellow_middle, R.drawable.list_yellow_middle,
R.drawable.list_blue_middle, R.drawable.list_blue_middle,
R.drawable.list_white_middle, R.drawable.list_white_middle,
@ -91,7 +143,10 @@ public class ResourceParser {
R.drawable.list_red_middle R.drawable.list_red_middle
}; };
private final static int [] BG_LAST_RESOURCES = new int [] { // 定义一个私有的静态数组,用于存储列表中最后一个笔记项的背景资源 ID
// 数组元素对应不同颜色的最后一个笔记项的背景图片资源,索引与颜色常量相关联,
// 用于设置列表最后一项的背景显示,与列表中其他位置的笔记项背景区分开来,达到整体美观的列表显示效果。
private final static int[] BG_LAST_RESOURCES = new int[]{
R.drawable.list_yellow_down, R.drawable.list_yellow_down,
R.drawable.list_blue_down, R.drawable.list_blue_down,
R.drawable.list_white_down, R.drawable.list_white_down,
@ -99,7 +154,10 @@ public class ResourceParser {
R.drawable.list_red_down, R.drawable.list_red_down,
}; };
private final static int [] BG_SINGLE_RESOURCES = new int [] { // 定义一个私有的静态数组,用于存储单个笔记项(单独显示,可能不在列表中情况)的背景资源 ID
// 各元素对应不同颜色的单个笔记项背景图片资源,通过颜色索引获取相应资源,
// 用于在单独展示笔记等场景下设置背景图片显示。
private final static int[] BG_SINGLE_RESOURCES = new int[]{
R.drawable.list_yellow_single, R.drawable.list_yellow_single,
R.drawable.list_blue_single, R.drawable.list_blue_single,
R.drawable.list_white_single, R.drawable.list_white_single,
@ -107,29 +165,69 @@ public class ResourceParser {
R.drawable.list_red_single R.drawable.list_red_single
}; };
/**
* `id` `BG_FIRST_RESOURCES` ID
* 使
*
* @param id
* @return ID `int` Android ID
*/
public static int getNoteBgFirstRes(int id) { public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id]; return BG_FIRST_RESOURCES[id];
} }
/**
* `id` `BG_LAST_RESOURCES` ID
*
*
* @param id
* @return ID `int` Android ID
*/
public static int getNoteBgLastRes(int id) { public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id]; return BG_LAST_RESOURCES[id];
} }
/**
* `id` `BG_SINGLE_RESOURCES` ID
*
*
* @param id
* @return ID `int` Android ID
*/
public static int getNoteBgSingleRes(int id) { public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id]; return BG_SINGLE_RESOURCES[id];
} }
/**
* `id` `BG_NORMAL_RESOURCES` ID
*
*
* @param id
* @return ID `int` Android ID
*/
public static int getNoteBgNormalRes(int id) { public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id]; return BG_NORMAL_RESOURCES[id];
} }
/**
* ID
*
* @return ID `int` Android ID `R.drawable.list_folder`
*/
public static int getFolderBgRes() { public static int getFolderBgRes() {
return R.drawable.list_folder; return R.drawable.list_folder;
} }
} }
/**
* `WidgetBgResources`Widget `2x``4x`
* ID便
*/
public static class WidgetBgResources { public static class WidgetBgResources {
private final static int [] BG_2X_RESOURCES = new int [] { // 定义一个私有的静态数组,用于存储 `2x` 尺寸小部件的背景资源 IDDrawable资源
// 数组中的每个元素对应一种颜色的 `2x` 尺寸小部件背景图片资源,索引与颜色常量相对应,
// 例如 `R.drawable.widget_2x_yellow` 对应黄色背景的 `2x` 尺寸小部件的背景资源,用于在相应尺寸小部件上设置背景显示。
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,
R.drawable.widget_2x_white, R.drawable.widget_2x_white,
@ -137,11 +235,21 @@ public class ResourceParser {
R.drawable.widget_2x_red, R.drawable.widget_2x_red,
}; };
/**
* `id` `BG_2X_RESOURCES` `2x` ID
* `2x` 使
*
* @param id
* @return `2x` ID `int` Android ID
*/
public static int getWidget2xBgResource(int id) { public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id]; return BG_2X_RESOURCES[id];
} }
private final static int [] BG_4X_RESOURCES = new int [] { // 定义一个私有的静态数组,用于存储 `4x` 尺寸小部件的背景资源 ID
// 每个元素对应一种颜色的 `4x` 尺寸小部件背景图片资源,按照颜色索引匹配,
// 用于在 `4x` 尺寸小部件上设置背景图片,以适配不同的屏幕显示情况和用户对颜色的选择。
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,
R.drawable.widget_4x_white, R.drawable.widget_4x_white,
@ -149,19 +257,51 @@ public class ResourceParser {
R.drawable.widget_4x_red R.drawable.widget_4x_red
}; };
/**
* `id` `BG_4X_RESOURCES` `4x` ID
* `4x` 使
*
* @param id
* @return `4x` ID `int` Android ID
*/
public static int getWidget4xBgResource(int id) { public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id]; return BG_4X_RESOURCES[id];
} }
} }
/**
* `TextAppearanceResources`
* ID 便
*
*/
public static class TextAppearanceResources { public static class TextAppearanceResources {
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
// 定义一个私有的静态数组 `TEXTAPPEARANCE_RESOURCES`,用于存储不同文本外观对应的资源 ID。
// 这里的资源 ID 通常是指 Android 中的 `TextAppearance` 样式资源(在 `styles.xml` 等资源文件中定义),
// 每个元素对应一种特定的文本外观样式,通过索引来区分不同规格(如大小等不同情况)的文本外观。
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
* ID
*
* <p>
* `HACKME` SharedPreference ID
* ID 访
* ID `ResourceParser#BG_DEFAULT_FONT_SIZE`
*
*
* @param id `TEXTAPPEARANCE_RESOURCES.length`
*
* @return ID `int` Android 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.
@ -174,8 +314,15 @@ public class ResourceParser {
return TEXTAPPEARANCE_RESOURCES[id]; return TEXTAPPEARANCE_RESOURCES[id];
} }
/**
*
*
*
* @return `TEXTAPPEARANCE_RESOURCES` `int`
*/
public static int getResourcesSize() { public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length; return TEXTAPPEARANCE_RESOURCES.length;
} }
} }
} }

@ -40,20 +40,29 @@ import net.micode.notes.tool.DataUtils;
import java.io.IOException; import java.io.IOException;
// AlarmAlertActivity类继承自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;
// 定义摘要信息的最大展示长度为60个字符
private static final int SNIPPET_PREW_MAX_LEN = 60; private static final int SNIPPET_PREW_MAX_LEN = 60;
// 用于播放闹钟提醒声音的MediaPlayer对象
MediaPlayer mPlayer; MediaPlayer mPlayer;
// Activity被创建时调用的方法进行一些初始化操作
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// 设置Activity无标题栏去除默认的标题显示
requestWindowFeature(Window.FEATURE_NO_TITLE); requestWindowFeature(Window.FEATURE_NO_TITLE);
final Window win = getWindow(); final Window win = getWindow();
// 设置窗口属性使得在屏幕锁定时也能显示该Activity
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
@ -64,9 +73,12 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
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;
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@ -74,7 +86,9 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
return; return;
} }
// 创建MediaPlayer对象用于后续播放闹钟声音
mPlayer = new MediaPlayer(); mPlayer = new MediaPlayer();
// 判断笔记是否在笔记数据库中可见如果可见则展示操作对话框并播放闹钟声音否则结束该Activity
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog(); showActionDialog();
playAlarmSound(); playAlarmSound();
@ -83,18 +97,20 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
} }
} }
// 判断屏幕是否处于开启状态的方法通过获取PowerManager服务来检查屏幕状态
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();
} }
// 播放闹钟提醒声音的方法获取默认的闹钟铃声Uri并根据系统设置配置音频流类型然后加载并播放声音同时设置为循环播放
private void playAlarmSound() { private void playAlarmSound() {
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);
@ -119,6 +135,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
} }
} }
// 展示操作对话框的方法创建一个AlertDialog设置标题、消息内容、正按钮以及根据屏幕是否开启来决定是否设置负按钮并设置对话框消失时的监听器
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);
@ -130,6 +147,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
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:
@ -143,13 +161,15 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
} }
} }
// 处理对话框消失事件的方法停止正在播放的闹钟声音并结束当前Activity
public void onDismiss(DialogInterface dialog) { public void onDismiss(DialogInterface dialog) {
stopAlarmSound(); stopAlarmSound();
finish(); finish();
} }
// 停止闹钟声音播放的方法释放MediaPlayer资源将其置为null
private void stopAlarmSound() { private void stopAlarmSound() {
if (mPlayer != null) { if (mPlayer!= null) {
mPlayer.stop(); mPlayer.stop();
mPlayer.release(); mPlayer.release();
mPlayer = null; mPlayer = null;

@ -28,37 +28,55 @@ import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
// AlarmInitReceiver类继承自BroadcastReceiver用于接收系统广播并执行与闹钟初始化相关的操作比如根据条件查询数据库中的笔记信息为符合条件的笔记设置对应的闹钟提醒
public class AlarmInitReceiver extends BroadcastReceiver { public class AlarmInitReceiver extends BroadcastReceiver {
// 定义一个字符串数组用于指定从数据库查询时要获取的列名这里包含笔记的ID和提醒日期两列
private static final String [] PROJECTION = new String [] { private static final String [] PROJECTION = new String [] {
NoteColumns.ID, NoteColumns.ID,
NoteColumns.ALERTED_DATE NoteColumns.ALERTED_DATE
}; };
// 定义常量表示查询结果中笔记ID所在列的索引值为0方便后续从游标中获取对应数据
private static final int COLUMN_ID = 0; private static final int COLUMN_ID = 0;
// 定义常量表示查询结果中提醒日期所在列的索引值为1方便后续从游标中获取对应数据
private static final int COLUMN_ALERTED_DATE = 1; private static final int COLUMN_ALERTED_DATE = 1;
// 重写BroadcastReceiver的onReceive方法当该广播接收器接收到相应广播时会执行此方法内的逻辑
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
// 获取当前系统时间的毫秒数,用于后续作为查询条件去筛选数据库中的记录
long currentDate = System.currentTimeMillis(); long currentDate = System.currentTimeMillis();
// 通过ContentResolver执行数据库查询操作从指定的UriNotes.CONTENT_NOTE_URI中查询数据按照PROJECTION定义的列进行查询
// 使用提供的查询条件筛选出提醒日期大于当前时间且类型为笔记Notes.TYPE_NOTE的记录查询参数使用当前时间的字符串表示形式
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION, PROJECTION,
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
new String[] { String.valueOf(currentDate) }, new String[] { String.valueOf(currentDate) },
null); null);
if (c != null) { // 判断游标是否不为空,即是否查询到了符合条件的数据
if (c!= null) {
// 将游标移动到第一条查询结果记录位置,如果有结果则开始遍历处理
if (c.moveToFirst()) { if (c.moveToFirst()) {
do { do {
// 从游标中获取提醒日期对应的列数据,得到该笔记的提醒时间(以毫秒为单位的时间戳形式)
long alertDate = c.getLong(COLUMN_ALERTED_DATE); long alertDate = c.getLong(COLUMN_ALERTED_DATE);
// 创建一个新的Intent用于启动AlarmReceiver这个广播接收器传递与当前笔记相关的信息
Intent sender = new Intent(context, AlarmReceiver.class); Intent sender = new Intent(context, AlarmReceiver.class);
// 为Intent设置数据将当前笔记的ID附加到对应的Uri上以便接收方可以识别是哪个笔记的闹钟相关操作
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
// 创建一个PendingIntent用于在合适的时候触发指定的广播这里是AlarmReceiver设置相关的上下文、请求码、Intent以及标志位
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 获取系统的AlarmManager服务用于设置闹钟提醒相关的操作
AlarmManager alermManager = (AlarmManager) context AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE); .getSystemService(Context.ALARM_SERVICE);
// 通过AlarmManager设置闹钟提醒使用RTC_WAKEUP模式在指定的绝对时间唤醒设备来触发提醒传入提醒时间和对应的PendingIntent
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext()); } while (c.moveToNext());
} }
// 关闭游标,释放相关资源,避免内存泄漏等问题
c.close(); c.close();
} }
} }

@ -20,11 +20,18 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
// AlarmReceiver类继承自BroadcastReceiver它的主要作用是接收广播并在接收到广播后进行相应的页面跳转操作以展示闹钟提醒相关的界面
public class AlarmReceiver extends BroadcastReceiver { public class AlarmReceiver extends BroadcastReceiver {
// 重写BroadcastReceiver的onReceive方法当该广播接收器接收到广播时此方法内的逻辑将会被执行
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
// 将传入的Intent的目标类设置为AlarmAlertActivity.class也就是当这个Intent被启动时将会启动AlarmAlertActivity这个Activity
// 通常用于根据接收到的广播信息跳转到对应的展示界面,此处就是跳转到闹钟提醒相关的界面
intent.setClass(context, AlarmAlertActivity.class); intent.setClass(context, AlarmAlertActivity.class);
// 为Intent添加标志位FLAG_ACTIVITY_NEW_TASK这是因为从广播接收器中启动Activity需要这个标志位来确保Activity能够在新的任务栈中被正确启动
// 否则可能会出现启动失败等问题,特别是在应用处于后台或者未运行的一些场景下
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 通过传入的上下文对象启动设置好的Intent从而启动对应的Activity实现从接收到广播到展示闹钟提醒界面的流程
context.startActivity(intent); context.startActivity(intent);
} }
} }

@ -28,85 +28,128 @@ import android.view.View;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.NumberPicker; import android.widget.NumberPicker;
// DateTimePicker类继承自FrameLayout它是一个自定义的视图组件用于实现日期和时间的选择功能支持24小时制和12小时制含AM/PM的显示与操作并且可以响应各种时间组件值变化的事件。
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小时制相关的时间计算和判断
private static final int HOURS_IN_HALF_DAY = 12; private static final int HOURS_IN_HALF_DAY = 12;
// 一整天包含的小时数用于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;
// 日期选择器NumberPicker的最小值通常从0开始表示一周中的第一天等情况
private static final int DATE_SPINNER_MIN_VAL = 0; private static final int DATE_SPINNER_MIN_VAL = 0;
// 日期选择器NumberPicker的最大值设置为一周天数减1对应一周中的最后一天
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小时制视图下小时选择器NumberPicker的最小值即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小时制视图下小时选择器NumberPicker的最大值即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小时制视图下小时选择器NumberPicker的最小值即1时通常12小时制显示从1开始
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小时制视图下小时选择器NumberPicker的最大值即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;
// 分钟选择器NumberPicker的最小值即0分
private static final int MINUT_SPINNER_MIN_VAL = 0; private static final int MINUT_SPINNER_MIN_VAL = 0;
// 分钟选择器NumberPicker的最大值即59分表示一分钟的最大数值
private static final int MINUT_SPINNER_MAX_VAL = 59; private static final int MINUT_SPINNER_MAX_VAL = 59;
// AM/PM选择器NumberPicker的最小值通常0表示AM
private static final int AMPM_SPINNER_MIN_VAL = 0; private static final int AMPM_SPINNER_MIN_VAL = 0;
// AM/PM选择器NumberPicker的最大值通常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;
// 用于选择上午AM或下午PM的NumberPicker组件仅在12小时制下可见并起作用
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];
// 用于标记当前时间是否处于上午AM初始值根据当前小时数判断大于等于12小时则为false即下午PM
private boolean mIsAm; private boolean mIsAm;
// 用于标记当前是否处于24小时制视图初始值根据系统设置来确定通过调用DateFormat.is24HourFormat(context)获取)
private boolean mIs24HourView; private boolean mIs24HourView;
// 用于标记该日期时间选择器是否启用初始值为默认启用状态DEFAULT_ENABLE_STATE
private boolean mIsEnabled = DEFAULT_ENABLE_STATE; private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
// 用于标记是否正在进行初始化操作初始化为true在初始化完成后会设置为false用于避免一些不必要的重复操作或逻辑判断干扰
private boolean mInitialising; private boolean mInitialising;
// 定义一个接口类型的成员变量,用于设置当日期时间发生变化时的回调监听器,外部类可以实现该接口来响应时间变化事件
private OnDateTimeChangedListener mOnDateTimeChangedListener; private OnDateTimeChangedListener mOnDateTimeChangedListener;
// 日期选择器NumberPicker的值变化监听器当日期选择器的值发生改变时会触发此监听器内的逻辑
// 主要作用是根据选择的日期变化来更新内部的日期数据mDate并调用相关方法更新界面显示以及通知日期时间变化的监听器如果有设置
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) {
// 根据新选择的日期与旧日期的差值调整内部存储的日期mDate实现日期的增减操作
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
// 更新日期选择器的显示相关内容,比如更新显示的一周日期字符串等
updateDateControl(); updateDateControl();
// 通知日期时间发生了变化,触发设置的回调监听器(如果有)执行相应逻辑
onDateTimeChanged(); onDateTimeChanged();
} }
}; };
// 小时选择器NumberPicker的值变化监听器当小时选择器的值发生改变时会触发此监听器内的逻辑
// 会根据是否是24小时制以及新旧小时值的情况处理日期的变化跨天等情况、AM/PM的切换12小时制下以及更新内部日期时间数据和通知相关监听器等操作
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) {
// 从上午切换到下午11点到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());
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) { }
// 从下午切换到上午12点到11点且回退到上一天的情况
else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_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;
} }
// 处理11点到12点或者12点到11点切换时AM/PM状态的改变并更新对应的AM/PM控制显示
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;
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) { }
// 24小时制下从0时切换到23时回退一天的情况
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;
} }
} }
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); // 根据是否是12小时制以及当前的AM/PM状态计算出正确的小时数并设置到内部的日期对象mDate
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();
// 如果日期发生了变化更新当前的年、月、日信息到内部的日期对象mDate
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,18 +158,23 @@ public class DateTimePicker extends FrameLayout {
} }
}; };
// 分钟选择器NumberPicker的值变化监听器当分钟选择器的值发生改变时会触发此监听器内的逻辑
// 会处理分钟变化导致的小时变化跨小时等情况、AM/PM的切换影响到跨12点时以及更新内部日期时间数据和通知相关监听器等操作
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;
// 分钟从最大值变为最小值如59分变为0分可能跨小时的情况
if (oldVal == maxValue && newVal == minValue) { if (oldVal == maxValue && newVal == minValue) {
offset += 1; offset += 1;
} else if (oldVal == minValue && newVal == maxValue) { }
// 分钟从最小值变为最大值如0分变为59分可能跨小时的情况
else if (oldVal == minValue && newVal == maxValue) {
offset -= 1; offset -= 1;
} }
if (offset != 0) { if (offset!= 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset); mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour()); mHourSpinner.setValue(getCurrentHour());
updateDateControl(); updateDateControl();
@ -144,10 +192,12 @@ public class DateTimePicker extends FrameLayout {
} }
}; };
// AM/PM选择器NumberPicker的值变化监听器当AM/PM选择器的值发生改变时会触发此监听器内的逻辑
// 主要作用是根据AM/PM的切换来调整内部日期时间数据增减12小时更新AM/PM控制显示以及通知日期时间变化的监听器如果有执行相应逻辑
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;
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 {
@ -158,39 +208,51 @@ public class DateTimePicker extends FrameLayout {
} }
}; };
// 定义一个接口用于外部类实现当日期时间在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);
} }
// 构造函数使用默认的当前时间System.currentTimeMillis()来初始化DateTimePicker调用另一个构造函数传递当前时间和系统默认的时间显示格式24小时制或12小时制来完成初始化
public DateTimePicker(Context context) { public DateTimePicker(Context context) {
this(context, System.currentTimeMillis()); this(context, System.currentTimeMillis());
} }
// 构造函数使用指定的时间date参数来初始化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));
} }
// 主要的构造函数完成DateTimePicker的初始化工作包括加载布局、初始化各个NumberPicker组件及其监听器、设置初始时间、设置启用状态以及一些初始的显示控制等操作
public DateTimePicker(Context context, long date, boolean is24HourView) { public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context); super(context);
// 获取一个默认的Calendar实例用于存储和操作当前的日期时间信息
mDate = Calendar.getInstance(); mDate = Calendar.getInstance();
mInitialising = true; mInitialising = true;
// 根据当前小时数判断初始的AM/PM状态大于等于12小时则为下午PM即mIsAm为false
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
// 加载指定的布局文件R.layout.datetime_picker到该FrameLayout中用于显示日期时间选择的各个组件
inflate(context, R.layout.datetime_picker, this); inflate(context, R.layout.datetime_picker, this);
// 获取布局中的日期选择器NumberPicker组件并进行相关设置如设置最小值、最大值以及值变化监听器
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);
// 获取布局中的小时选择器NumberPicker组件并设置值变化监听器
mHourSpinner = (NumberPicker) findViewById(R.id.hour); mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
// 获取布局中的分钟选择器NumberPicker组件设置最小值、最大值、长按更新间隔等并设置值变化监听器
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的字符串数组根据系统语言环境等获取本地化的AM/PM表示并设置到AM/PM选择器NumberPicker组件中包括设置最小值、最大值、显示值以及值变化监听器
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);
@ -198,288 +260,22 @@ public class DateTimePicker extends FrameLayout {
mAmPmSpinner.setDisplayedValues(stringsForAmPm); mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// update controls to initial state // 更新各个相关的控制显示使其处于初始状态比如日期选择器显示的一周日期字符串、小时选择器的范围显示、AM/PM选择器的初始可见性等
updateDateControl(); updateDateControl();
updateHourControl(); updateHourControl();
updateAmPmControl(); updateAmPmControl();
// 设置是否为24小时制视图根据传入的参数决定并相应地调整相关组件的显示和值范围等
set24HourView(is24HourView); set24HourView(is24HourView);
// set to current time // 设置当前显示的日期时间为传入的指定日期date参数通过设置年、月、日、小时、分钟等各个部分来完成
setCurrentDate(date); setCurrentDate(date);
// 设置该日期时间选择器的启用状态根据当前的启用状态mIsEnabled来决定
setEnabled(isEnabled()); setEnabled(isEnabled());
// set the content descriptions // 初始化完成将标记设置为false表示不再处于初始化阶段
mInitialising = false; mInitialising = false;
} }
@Override // 重写父类的setEnabled方法用于设置整个日期时间选择器及其内部
public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) {
return;
}
super.setEnabled(enabled);
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled);
mIsEnabled = enabled;
}
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
* Get the current date in millis
*
* @return the current date in millis
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}
/**
* Set the current date
*
* @param date The current date in millis
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}
/**
* Set the current date
*
* @param year The current year
* @param month The current month
* @param dayOfMonth The current dayOfMonth
* @param hourOfDay The current hourOfDay
* @param minute The current minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
}
/**
* Get current year
*
* @return The current year
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
/**
* Set current year
*
* @param year The current year
*/
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) {
return;
}
mDate.set(Calendar.YEAR, year);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current month in the year
*
* @return The current month in the year
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* Set current month in the year
*
* @param month The month in the year
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current day of the month
*
* @return The day of the month
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Set current day of the month
*
* @param dayOfMonth The day of the month
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
private int getCurrentHour() {
if (mIs24HourView){
return getCurrentHourOfDay();
} else {
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
*
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
if (!mIs24HourView) {
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
mIsAm = true;
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;
}
}
updateAmPmControl();
}
mHourSpinner.setValue(hourOfDay);
onDateTimeChanged();
}
/**
* Get currentMinute
*
* @return The Current Minute
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* Set current minute
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute);
mDate.set(Calendar.MINUTE, minute);
onDateTimeChanged();
}
/**
* @return true if this is in 24 hour view else false.
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* Set whether in 24 hour or AM/PM mode.
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView;
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
int hour = getCurrentHourOfDay();
updateHourControl();
setCurrentHour(hour);
updateAmPmControl();
}
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null);
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1);
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
mDateSpinner.setDisplayedValues(mDateDisplayValues);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
mDateSpinner.invalidate();
}
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}
private void updateHourControl() {
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
}
/**
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}

@ -29,21 +29,34 @@ 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对象初始化为获取当前系统时间对应的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);
} }
// 构造函数用于创建DateTimePickerDialog实例接收上下文和初始日期以毫秒表示作为参数完成对话框的基本初始化设置工作
// 包括创建DateTimePicker组件、设置其相关监听器、初始化显示的日期时间、设置对话框的按钮以及标题等内容。
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中改变了日期、时间等任何相关值时会触发此监听器内的逻辑。
// 主要作用是根据用户选择的新值更新内部存储的日期时间信息mDate并更新对话框的标题显示以实时反映当前选择的日期时间。
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,34 +68,48 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener
updateTitle(mDate.getTimeInMillis()); updateTitle(mDate.getTimeInMillis());
} }
}); });
// 设置初始显示的日期时间将传入的日期以毫秒表示设置到内部的日期时间对象mDate并将秒数设置为0确保初始时间的准确性。
mDate.setTimeInMillis(date); mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0); mDate.set(Calendar.SECOND, 0);
// 将DateTimePicker组件显示的日期时间设置为当前初始化的日期时间使其在界面上初始展示正确的时间内容。
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
// 设置对话框的确认按钮(通常显示为"确定"之类的文本传入对应文本资源的字符串并设置按钮点击事件的监听器为当前类自身因为实现了OnClickListener接口
setButton(context.getString(R.string.datetime_dialog_ok), this); setButton(context.getString(R.string.datetime_dialog_ok), this);
// 设置对话框的取消按钮(通常显示为"取消"之类的文本传入对应文本资源的字符串并传入null作为点击事件监听器表示默认的取消行为。
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
// 根据系统当前设置判断是否为24小时制视图调用DateFormat.is24HourFormat方法获取并设置以决定时间选择器的显示格式。
set24HourView(DateFormat.is24HourFormat(this.getContext())); set24HourView(DateFormat.is24HourFormat(this.getContext()));
// 初始更新一次对话框的标题使其显示当前设置的日期时间信息调用updateTitle方法传入当前日期时间的毫秒数来完成更新。
updateTitle(mDate.getTimeInMillis()); updateTitle(mDate.getTimeInMillis());
} }
// 用于设置当前是否为24小时制视图的方法接收一个布尔值参数更新内部的标记变量mIs24HourView外部可以通过调用此方法来动态切换时间显示格式。
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;
} }
// 用于更新对话框标题显示的私有方法,根据传入的日期时间(以毫秒表示)以及当前的时间显示格式等设置,格式化日期时间字符串并设置为对话框的标题内容,
// 使其能准确展示当前选择的日期时间信息,方便用户查看确认。
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;
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接口的方法用于处理对话框按钮点击事件当用户点击确认按钮通过传入的参数判断
// 如果设置了日期时间确认回调监听器mOnDateTimeSetListener不为null则调用该监听器的方法传递当前对话框实例和当前选择的日期时间以毫秒表示参数
// 通知外部用户已完成日期时间设置并确认的操作。
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,31 @@ import android.widget.PopupMenu.OnMenuItemClickListener;
import net.micode.notes.R; import net.micode.notes.R;
// DropdownMenu类用于创建一个下拉菜单的功能组件它将一个按钮与一个PopupMenu关联起来点击按钮时弹出菜单
// 并且可以设置菜单选项的点击监听器、查找菜单项以及设置按钮显示的标题等操作。
public class DropdownMenu { public class DropdownMenu {
// 用于显示下拉菜单的触发按钮,用户点击此按钮会弹出对应的下拉菜单。
private Button mButton; private Button mButton;
// 实际的PopupMenu对象用于承载和展示下拉菜单的内容包含多个菜单项供用户选择操作。
private PopupMenu mPopupMenu; private PopupMenu mPopupMenu;
// 对应PopupMenu中的菜单对象通过它可以进行如查找菜单项等与菜单内容相关的操作。
private Menu mMenu; private Menu mMenu;
// 构造函数用于初始化DropdownMenu实例接收上下文、触发按钮以及菜单资源ID作为参数完成下拉菜单的基本构建工作
// 包括设置按钮背景、创建PopupMenu、加载菜单资源以及为按钮设置点击监听器以弹出菜单等操作。
public DropdownMenu(Context context, Button button, int menuId) { public DropdownMenu(Context context, Button button, int menuId) {
// 将传入的按钮对象赋值给成员变量,后续通过此按钮来触发下拉菜单的显示。
mButton = button; mButton = button;
// 为按钮设置背景资源使用指定的资源IDR.drawable.dropdown_icon来设置按钮的外观样式使其显示为一个具有下拉箭头等特征的图标样式通常
mButton.setBackgroundResource(R.drawable.dropdown_icon); mButton.setBackgroundResource(R.drawable.dropdown_icon);
// 创建一个PopupMenu实例传入上下文和触发按钮作为参数使得PopupMenu与对应的按钮关联起来并且会根据按钮的位置等情况来显示弹出菜单。
mPopupMenu = new PopupMenu(context, mButton); mPopupMenu = new PopupMenu(context, mButton);
// 获取PopupMenu中的菜单对象赋值给成员变量方便后续对菜单内容进行操作比如查找菜单项等。
mMenu = mPopupMenu.getMenu(); mMenu = mPopupMenu.getMenu();
// 通过菜单填充器MenuInflater将指定的菜单资源由menuId指定加载到PopupMenu的菜单对象中从而显示出具体的菜单项内容。
mPopupMenu.getMenuInflater().inflate(menuId, mMenu); mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
// 为按钮设置点击监听器当按钮被点击时触发内部的逻辑即显示与之关联的PopupMenu实现点击按钮弹出下拉菜单的功能。
mButton.setOnClickListener(new OnClickListener() { mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) { public void onClick(View v) {
mPopupMenu.show(); mPopupMenu.show();
@ -45,16 +59,22 @@ public class DropdownMenu {
}); });
} }
// 用于设置下拉菜单中菜单项点击监听器的方法接收一个实现了OnMenuItemClickListener接口的对象作为参数
// 将其设置给PopupMenu使得当用户点击下拉菜单中的任意菜单项时能够触发相应的逻辑处理外部可以通过此方法来响应菜单项的点击操作。
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) { if (mPopupMenu!= null) {
mPopupMenu.setOnMenuItemClickListener(listener); mPopupMenu.setOnMenuItemClickListener(listener);
} }
} }
// 用于在菜单中查找指定ID的菜单项的方法接收一个菜单项的ID作为参数通过调用菜单对象mMenu的findItem方法来查找对应的菜单项
// 返回查找到的MenuItem对象方便外部对特定菜单项进行进一步的操作比如获取菜单项属性、修改菜单项状态等。
public MenuItem findItem(int id) { public MenuItem findItem(int id) {
return mMenu.findItem(id); return mMenu.findItem(id);
} }
// 用于设置触发下拉菜单的按钮显示标题的方法,接收一个字符序列(通常是字符串)作为参数,将其设置为按钮显示的文本内容,
// 这样可以根据需要动态改变按钮上显示的提示信息,例如显示当前选中的菜单项相关内容等。
public void setTitle(CharSequence title) { public void setTitle(CharSequence title) {
mButton.setText(title); mButton.setText(title);
} }

@ -29,49 +29,71 @@ import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
// FoldersListAdapter类继承自CursorAdapter它主要用于将数据库查询得到的游标数据Cursor适配显示到特定的视图列表中
// 在这里是用于展示文件夹相关信息的列表,比如文件夹名称等内容,并且提供了获取文件夹名称等相关的辅助方法。
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
}; };
// 定义常量表示查询结果中笔记ID所在列的索引值为0方便后续从游标中获取对应数据这里命名为ID_COLUMN。
public static final int ID_COLUMN = 0; public static final int ID_COLUMN = 0;
// 定义常量表示查询结果中摘要信息在这里用于表示文件夹名称相关内容所在列的索引值为1方便后续从游标中获取对应数据命名为NAME_COLUMN。
public static final int NAME_COLUMN = 1; public static final int NAME_COLUMN = 1;
// 构造函数接收上下文和游标Cursor作为参数调用父类的构造函数来完成基本的初始化工作
// 这里暂时没有额外的自定义初始化逻辑(除了调用父类构造函数),只是保留了构造函数的标准写法,注释中提示了待完善的部分(可能后续会添加更多初始化相关操作)。
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视图意味着每一项列表数据将会使用这个自定义视图来展示相关信息。
@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方法该方法用于将游标Cursor中的数据绑定到已创建的视图View也就是填充具体的数据到视图中进行显示
// 在这里根据游标中的数据情况判断如果是根文件夹通过ID判断则显示特定的字符串可能是代表根文件夹的固定文本否则显示游标中对应列的字符串内容文件夹名称
// 然后调用自定义视图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) {
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context String folderName = (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) view).bind(folderName); ((FolderListItem) view).bind(folderName);
} }
} }
// 提供的一个公共方法,用于获取指定位置的文件夹名称,接收上下文和位置索引作为参数,
// 通过调用getItem方法获取对应位置的游标对象然后根据游标中数据判断是根文件夹还是普通文件夹返回相应的名称字符串。
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用于作为列表中每一项数据展示的自定义视图布局
// 包含了一个TextView用于显示文件夹名称并且提供了一个bind方法用于将名称设置到这个TextView上进行展示。
private class FolderListItem extends LinearLayout { private class FolderListItem extends LinearLayout {
// 用于显示文件夹名称的TextView组件通过它将文件夹相关的名称信息展示给用户。
private TextView mName; private TextView mName;
// 构造函数接收上下文作为参数调用父类LinearLayout的构造函数完成基本初始化
// 然后通过LayoutInflater将指定的布局文件R.layout.folder_list_item加载到这个视图中
// 最后获取布局中的TextView组件用于后续设置名称显示。
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);
} }
// 用于将传入的文件夹名称设置到内部的TextViewmName上进行显示的方法外部通过调用此方法来更新视图中显示的文件夹名称内容。
public void bind(String name) { public void bind(String name) {
mName.setText(name); mName.setText(name);
} }

@ -72,18 +72,19 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
// NoteEditActivity类继承自Activity实现了多个接口用于处理笔记编辑相关的各种操作包括界面初始化、数据保存、样式设置、提醒设置、分享等众多功能。
public class NoteEditActivity extends Activity implements OnClickListener, public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener { NoteSettingChangedListener, OnTextViewChangeListener {
// 用于存储笔记头部相关视图组件的内部类,方便对这些组件进行统一管理和操作,例如显示修改日期、提醒图标及日期等组件。
private class HeadViewHolder { private class HeadViewHolder {
public TextView tvModified; public TextView tvModified;
public ImageView ivAlertIcon; public ImageView ivAlertIcon;
public TextView tvAlertDate; public TextView tvAlertDate;
public ImageView ibSetBgColor; public ImageView ibSetBgColor;
} }
// 背景颜色选择按钮与对应颜色资源ID的映射关系以按钮ID为键颜色资源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 +94,7 @@ 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);
} }
// 背景颜色选择按钮与对应选中状态显示按钮ID的映射关系用于在选择背景颜色后显示对应的选中标识按钮。
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 +104,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);
} }
// 字体大小选择按钮与对应字体大小资源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 +113,7 @@ 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);
} }
// 字体大小资源ID与对应选中状态显示按钮ID的映射关系用于在选择字体大小后显示对应的选中标识按钮。
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,43 +122,62 @@ 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);
} }
// 用于日志记录的标签,方便在调试等场景下识别该类相关的日志输出。
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;
// 笔记编辑区域所在的视图面板,可能包含编辑文本的滚动等相关功能,用于设置背景等操作。
private View mNoteEditorPanel; private View mNoteEditorPanel;
// 代表正在编辑的笔记对象,封装了笔记相关的数据和操作方法,如保存、获取内容、设置各种属性等。
private WorkingNote mWorkingNote; private WorkingNote mWorkingNote;
// 用于存储应用的共享偏好设置,可通过它获取和保存一些用户相关的设置信息,例如字体大小偏好设置等。
private SharedPreferences mSharedPrefs; private SharedPreferences mSharedPrefs;
// 当前选择的字体大小资源ID初始值从共享偏好设置中获取用于控制笔记编辑文本的字体大小显示。
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;
// 表示复选框选中状态的字符用于在笔记内容以特定格式表示复选框选中情况时使用这里是一个Unicode字符表示对勾。
public static final String TAG_CHECKED = String.valueOf('\u221A'); public static final String TAG_CHECKED = String.valueOf('\u221A');
// 表示复选框未选中状态的字符用于在笔记内容以特定格式表示复选框未选中情况时使用这里是一个Unicode字符表示方块。
public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
// 用于存储笔记内容以列表形式编辑时的多个EditText子项的线性布局容器方便处理列表模式下的笔记内容展示和编辑。
private LinearLayout mEditTextList; private LinearLayout mEditTextList;
// 用户查询的字符串内容,可能用于搜索等相关功能,比如在笔记中查找特定内容,用于后续的文本高亮等处理。
private String mUserQuery; private String mUserQuery;
// 用于匹配用户查询内容的正则表达式模式对象,根据用户查询字符串编译生成,用于在笔记文本中查找匹配的内容进行相应处理,如高亮显示。
private Pattern mPattern; private Pattern mPattern;
// Activity被创建时调用的方法进行一些初始化操作如设置布局、根据Intent初始化Activity状态等如果初始化失败则结束该Activity。
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// 设置该Activity对应的布局文件用于展示笔记编辑相关的界面内容。
this.setContentView(R.layout.note_edit); this.setContentView(R.layout.note_edit);
if (savedInstanceState == null && !initActivityState(getIntent())) { if (savedInstanceState == null &&!initActivityState(getIntent())) {
finish(); finish();
return; return;
} }
@ -162,13 +185,14 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
/** /**
* Current activity may be killed when the memory is low. Once it is killed, for another time * ActivityActivity
* user load this activity, we should restore the former state * Intent.EXTRA_UIDActivity
* Activity
*/ */
@Override @Override
protected void onRestoreInstanceState(Bundle savedInstanceState) { protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState); super.onRestoreInstanceState(savedInstanceState);
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));
if (!initActivityState(intent)) { if (!initActivityState(intent)) {
@ -179,10 +203,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} }
} }
// 根据传入的Intent初始化Activity的状态根据不同的Intent动作ACTION_VIEW、ACTION_INSERT_OR_EDIT等来处理不同的情况
// 例如加载已存在的笔记、创建新笔记、处理通话记录相关笔记等若操作过程中出现加载笔记失败等问题则结束该Activity成功则返回true。
private boolean initActivityState(Intent intent) { private boolean initActivityState(Intent intent) {
/** /**
* If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, * {@link Intent#ACTION_VIEW}ID
* then jump to the NotesListActivity * NotesListActivity
*/ */
mWorkingNote = null; mWorkingNote = null;
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
@ -190,7 +216,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mUserQuery = ""; mUserQuery = "";
/** /**
* Starting from the searched result * ID
*/ */
if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) {
noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
@ -211,11 +237,12 @@ 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())) {
// New note // 新建笔记的情况获取相关的额外数据如文件夹ID、小部件ID、小部件类型、背景资源ID等用于创建新的空白笔记对象。
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);
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);
@ -224,10 +251,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID,
ResourceParser.getDefaultBgId(this)); ResourceParser.getDefaultBgId(this));
// 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);
if (callDate != 0 && phoneNumber != null) { if (callDate!= 0 && phoneNumber!= null) {
if (TextUtils.isEmpty(phoneNumber)) { if (TextUtils.isEmpty(phoneNumber)) {
Log.w(TAG, "The call record number is null"); Log.w(TAG, "The call record number is null");
} }
@ -250,6 +277,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
bgResId); bgResId);
} }
// 设置软键盘的显示模式,初始时显示软键盘,并根据内容调整布局大小。
getWindow().setSoftInputMode( getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
@ -258,16 +286,20 @@ public class NoteEditActivity extends Activity implements OnClickListener,
finish(); finish();
return false; return false;
} }
// 设置笔记相关设置状态变化的监听器为当前Activity自身以便在笔记设置发生改变时能响应处理。
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() {
mNoteEditor.setTextAppearance(this, TextAppearanceResources mNoteEditor.setTextAppearance(this, TextAppearanceResources
.getTexAppearanceResource(mFontSizeId)); .getTexAppearanceResource(mFontSizeId));
@ -289,12 +321,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
| DateUtils.FORMAT_SHOW_YEAR)); | DateUtils.FORMAT_SHOW_YEAR));
/** /**
* TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker * TODO: DateTimePicker
* is not ready
*/ */
showAlertHeader(); showAlertHeader();
} }
// 根据笔记是否设置了提醒以及提醒时间与当前时间的关系,显示或隐藏提醒相关的头部信息,如提醒日期文本和提醒图标等。
private void showAlertHeader() { private void showAlertHeader() {
if (mWorkingNote.hasClockAlert()) { if (mWorkingNote.hasClockAlert()) {
long time = System.currentTimeMillis(); long time = System.currentTimeMillis();
@ -308,566 +340,5 @@ public class NoteEditActivity extends Activity implements OnClickListener,
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.ivAlert
};
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
initActivityState(intent);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
/**
* For new note without note id, we should firstly save it to
* generate a id. If the editing note is not worth saving, there
* is no id which is equivalent to create new note
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());
Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mNoteBgColorSelector, ev)) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
}
if (mFontSizeSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mFontSizeSelector, ev)) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return super.dispatchTouchEvent(ev);
}
private boolean inRangeOfView(View view, MotionEvent ev) {
int []location = new int[2];
view.getLocationOnScreen(location);
int x = location[0];
int y = location[1];
if (ev.getX() < x
|| ev.getX() > (x + view.getWidth())
|| ev.getY() < y
|| ev.getY() > (y + view.getHeight())) {
return false;
}
return true;
}
private void initResources() {
mHeadViewPanel = findViewById(R.id.note_title);
mNoteHeaderHolder = new HeadViewHolder();
mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);
mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);
mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
mNoteEditor = (EditText) findViewById(R.id.note_edit_view);
mNoteEditorPanel = findViewById(R.id.sv_note_edit);
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
for (int id : sBgSelectorBtnsMap.keySet()) {
ImageView iv = (ImageView) findViewById(id);
iv.setOnClickListener(this);
}
mFontSizeSelector = findViewById(R.id.font_size_selector);
for (int id : sFontSizeBtnsMap.keySet()) {
View view = findViewById(id);
view.setOnClickListener(this);
};
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
}
@Override
protected void onPause() {
super.onPause();
if(saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}
clearSettingState();
}
private void updateWidget() {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
Log.e(TAG, "Unspported widget type");
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
mWorkingNote.getWidgetId()
});
sendBroadcast(intent);
setResult(RESULT_OK, intent);
}
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
mNoteBgColorSelector.setVisibility(View.GONE);
} else if (sFontSizeBtnsMap.containsKey(id)) {
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
mFontSizeId = sFontSizeBtnsMap.get(id);
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
getWorkingText();
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setTextAppearance(this,
TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
}
mFontSizeSelector.setVisibility(View.GONE);
}
}
@Override
public void onBackPressed() {
if(clearSettingState()) {
return;
}
saveNote();
super.onBackPressed();
}
private boolean clearSettingState() {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return false;
}
public void onBackgroundColorChanged() {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE);
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (isFinishing()) {
return true;
}
clearSettingState();
menu.clear();
if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_note_edit, menu);
} else {
getMenuInflater().inflate(R.menu.note_edit, menu);
}
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);
} else {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
}
if (mWorkingNote.hasClockAlert()) {
menu.findItem(R.id.menu_alert).setVisible(false);
} else {
menu.findItem(R.id.menu_delete_remind).setVisible(false);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_note:
createNewNote();
break;
case R.id.menu_delete:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_note));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteCurrentNote();
finish();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.menu_font_size:
mFontSizeSelector.setVisibility(View.VISIBLE);
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
break;
case R.id.menu_list_mode:
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);
break;
case R.id.menu_share:
getWorkingText();
sendTo(this, mWorkingNote.getContent());
break;
case R.id.menu_send_to_desktop:
sendToDesktop();
break;
case R.id.menu_alert:
setReminder();
break;
case R.id.menu_delete_remind:
mWorkingNote.setAlertDate(0, false);
break;
default:
break;
}
return true;
}
private void setReminder() {
DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());
d.setOnDateTimeSetListener(new OnDateTimeSetListener() {
public void OnDateTimeSet(AlertDialog dialog, long date) {
mWorkingNote.setAlertDate(date , true);
}
});
d.show();
}
/**
* Share note to apps that support {@link Intent#ACTION_SEND} action
* and {@text/plain} type
*/
private void sendTo(Context context, String info) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, info);
intent.setType("text/plain");
context.startActivity(intent);
}
private void createNewNote() {
// Firstly, save current editing notes
saveNote();
// For safety, start a new NoteEditActivity
finish();
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
startActivity(intent);
}
private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) {
HashSet<Long> ids = new HashSet<Long>();
long id = mWorkingNote.getNoteId();
if (id != Notes.ID_ROOT_FOLDER) {
ids.add(id);
} else {
Log.d(TAG, "Wrong note id, should not happen");
}
if (!isSyncMode()) {
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "Delete Note error");
}
} else {
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
}
mWorkingNote.markDeleted(true);
}
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
public void onClockAlertChanged(long date, boolean set) {
/**
* User could set clock to an unsaved note, so before setting the
* alert clock, we should save the note first
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent intent = new Intent(this, AlarmReceiver.class);
intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
showAlertHeader();
if(!set) {
alarmManager.cancel(pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
}
} else {
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Clock alert setting error");
showToast(R.string.error_note_empty_for_clock);
}
}
public void onWidgetChanged() {
updateWidget();
}
public void onEditTextDelete(int index, String text) {
int childCount = mEditTextList.getChildCount();
if (childCount == 1) {
return;
}
for (int i = index + 1; i < childCount; i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i - 1);
}
mEditTextList.removeViewAt(index);
NoteEditText edit = null;
if(index == 0) {
edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(
R.id.et_edit_text);
} else {
edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById(
R.id.et_edit_text);
}
int length = edit.length();
edit.append(text);
edit.requestFocus();
edit.setSelection(length);
}
public void onEditTextEnter(int index, String text) {
/**
* Should not happen, check for debug
*/
if(index > mEditTextList.getChildCount()) {
Log.e(TAG, "Index out of mEditTextList boundrary, should not happen");
}
View view = getListItem(text, index);
mEditTextList.addView(view, index);
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.requestFocus();
edit.setSelection(0);
for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i);
}
}
private void switchToListMode(String text) {
mEditTextList.removeAllViews();
String[] items = text.split("\n");
int index = 0;
for (String item : items) {
if(!TextUtils.isEmpty(item)) {
mEditTextList.addView(getListItem(item, index));
index++;
}
}
mEditTextList.addView(getListItem("", index));
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();
mNoteEditor.setVisibility(View.GONE);
mEditTextList.setVisibility(View.VISIBLE);
}
private Spannable getHighlightQueryResult(String fullText, String userQuery) {
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
if (!TextUtils.isEmpty(userQuery)) {
mPattern = Pattern.compile(userQuery);
Matcher m = mPattern.matcher(fullText);
int start = 0;
while (m.find(start)) {
spannable.setSpan(
new BackgroundColorSpan(this.getResources().getColor(
R.color.user_query_highlight)), m.start(), m.end(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
start = m.end();
}
}
return spannable;
}
private View getListItem(String item, int index) {
View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);
final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
}
}
});
if (item.startsWith(TAG_CHECKED)) {
cb.setChecked(true);
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
item = item.substring(TAG_CHECKED.length(), item.length()).trim();
} else if (item.startsWith(TAG_UNCHECKED)) {
cb.setChecked(false);
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
item = item.substring(TAG_UNCHECKED.length(), item.length()).trim();
}
edit.setOnTextViewChangeListener(this);
edit.setIndex(index);
edit.setText(getHighlightQueryResult(item, mUserQuery));
return view;
}
public void onTextChange(int index, boolean hasText) {
if (index >= mEditTextList.getChildCount()) {
Log.e(TAG, "Wrong index, should not happen");
return;
}
if(hasText) {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE);
} else {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);
}
}
public void onCheckListModeChanged(int oldMode, int newMode) {
if (newMode == TextNote.MODE_CHECK_LIST) {
switchToListMode(mNoteEditor.getText().toString());
} else {
if (!getWorkingText()) {
mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ",
""));
}
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
mEditTextList.setVisibility(View.GONE);
mNoteEditor.setVisibility(View.VISIBLE);
}
}
private boolean getWorkingText() {
boolean hasChecked = false;
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mEditTextList.getChildCount(); i++) {
View view = mEditTextList.getChildAt(i);
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
if (!TextUtils.isEmpty(edit.getText())) {
if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) {
sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");
hasChecked = true;
} else {
sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");
}
}
}
mWorkingNote.setWorkingText(sb.toString());
} else {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
}
return hasChecked;
}
private boolean saveNote() {
getWorkingText();
boolean saved = mWorkingNote.saveNote();
if (saved) {
/**
* There are two modes from List view to edit view, open one note,
* create/edit a node. Opening node requires to the original
* position in the list when back from edit view, while creating a
* new node requires to the top of the list. This code
* {@link #RESULT_OK} is used to identify the create/edit state
*/
setResult(RESULT_OK);
}
return saved;
}
private void sendToDesktop() {
/**
* Before send message to home, we should make sure that current
* editing note is exists in databases. So, for new note, firstly
* save it
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent sender = new Intent();
Intent shortcutIntent = new Intent(this, NoteEditActivity.class);
shortcutIntent.setAction(Intent.ACTION_VIEW);
shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId());
sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
sender.putExtra(Intent.EXTRA_SHORTCUT_NAME,
makeShortcutIconTitle(mWorkingNote.getContent()));
sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app));
sender.putExtra("duplicate", true);
sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
showToast(R.string.info_note_enter_desktop);
sendBroadcast(sender);
} else {
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Send to desktop error");
showToast(R.string.error_note_empty_for_send_to_desktop);
}
}
private String makeShortcutIconTitle(String content) {
content = content.replace(TAG_CHECKED, "");
content = content.replace(TAG_UNCHECKED, "");
return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0,
SHORTCUT_ICON_TITLE_MAX_LEN) : content;
}
private void showToast(int resId) {
showToast(resId, Toast.LENGTH_SHORT);
}
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show();
}
}

@ -37,15 +37,25 @@ 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";
// 当前文本编辑框在父容器(如列表布局中多个编辑框的情况)中的索引位置,用于标识自身顺序以及在一些操作中确定与其他编辑框的关系。
private int mIndex; private int mIndex;
// 在处理删除键按下事件时,记录删除操作前文本的起始选择位置,用于后续判断是否触发特定的删除逻辑,比如删除当前编辑框内容等情况。
private int mSelectionStartBeforeDelete; private int mSelectionStartBeforeDelete;
private static final String SCHEME_TEL = "tel:" ; // 定义表示电话号码链接的协议头字符串,用于识别文本中电话号码类型的链接,格式为"tel:"。
private static final String SCHEME_HTTP = "http:" ; private static final String SCHEME_TEL = "tel:";
private static final String SCHEME_EMAIL = "mailto:" ; // 定义表示网页链接的协议头字符串,用于识别文本中网页类型的链接,格式为"http:"。
private static final String SCHEME_HTTP = "http:";
// 定义表示邮件链接的协议头字符串,用于识别文本中邮件类型的链接,格式为"mailto:"。
private static final String SCHEME_EMAIL = "mailto:";
// 用于建立链接协议头与对应提示字符串资源ID的映射关系的集合方便根据链接类型获取相应的显示文本告知用户链接类型。
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>(); private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static { static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
@ -53,6 +63,8 @@ public class NoteEditText extends EditText {
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
} }
// 定义一个接口,用于外部类实现,该接口提供了几个方法,当文本编辑框内发生特定文本操作事件(如删除、回车键按下、文本内容变化等)时,
// 会回调相应的方法,以便外部可以根据这些事件做出相应的处理,实现与外部的交互逻辑。
/** /**
* Call by the {@link NoteEditActivity} to delete or add edit text * Call by the {@link NoteEditActivity} to delete or add edit text
*/ */
@ -75,35 +87,45 @@ public class NoteEditText extends EditText {
void onTextChange(int index, boolean hasText); void onTextChange(int index, boolean hasText);
} }
// 用于存储实现了OnTextViewChangeListener接口的对象实例通过设置此监听器使得外部可以接收该文本编辑框内发生相关文本操作事件的通知进行相应处理。
private OnTextViewChangeListener mOnTextViewChangeListener; private OnTextViewChangeListener mOnTextViewChangeListener;
// 构造函数接收上下文作为参数调用父类EditText的构造函数进行初始化同时设置当前编辑框的索引初始值为0。
public NoteEditText(Context context) { public NoteEditText(Context context) {
super(context, null); super(context, null);
mIndex = 0; mIndex = 0;
} }
// 用于设置当前文本编辑框在父容器中的索引位置的方法,外部可通过调用此方法来更新索引值,方便后续基于索引的相关操作逻辑。
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;
} }
// 构造函数接收上下文和属性集AttributeSet作为参数调用父类EditText的构造函数并指定默认的编辑框样式android.R.attr.editTextStyle进行初始化。
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);
} }
// 构造函数接收上下文、属性集AttributeSet和默认样式defStyle作为参数调用父类EditText的构造函数进行初始化
// 这里暂时没有额外的自定义初始化逻辑(除了调用父类构造函数),只是保留了构造函数的标准写法,注释中提示了待完善的部分(可能后续会添加更多初始化相关操作)。
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根据触摸位置来设置文本的选择位置
// 使得用户点击文本区域时能准确地定位到相应位置进行后续操作,如复制、粘贴等文本选择相关操作,其他触摸动作则按照父类默认逻辑处理。
@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坐标并减去文本编辑框的左内边距得到相对文本内容区域的X坐标再加上滚动的X偏移量处理滚动情况
int x = (int) event.getX(); int x = (int) event.getX();
int y = (int) event.getY(); int y = (int) event.getY();
x -= getTotalPaddingLeft(); x -= getTotalPaddingLeft();
@ -111,6 +133,8 @@ public class NoteEditText extends EditText {
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);
@ -121,11 +145,14 @@ public class NoteEditText extends EditText {
return super.onTouchEvent(event); return super.onTouchEvent(event);
} }
// 重写父类的onKeyDown方法用于处理按键按下事件在这里主要是记录删除键KEYCODE_DEL按下时文本的起始选择位置
// 对于回车键KEYCODE_ENTER按下的情况如果设置了文本操作事件监听器OnTextViewChangeListener则直接返回false
// 可能是让外部来进一步处理回车键按下的逻辑,其他按键则按照父类默认逻辑处理。
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) { switch (keyCode) {
case KeyEvent.KEYCODE_ENTER: case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) { if (mOnTextViewChangeListener!= null) {
return false; return false;
} }
break; break;
@ -138,12 +165,17 @@ public class NoteEditText extends EditText {
return super.onKeyDown(keyCode, event); return super.onKeyDown(keyCode, event);
} }
// 重写父类的onKeyUp方法用于处理按键抬起事件在这里重点处理删除键KEYCODE_DEL和回车键KEYCODE_ENTER抬起时的逻辑
// 如果设置了文本操作事件监听器OnTextViewChangeListener对于删除键抬起情况当起始选择位置为0且当前编辑框不是第一个索引不为0
// 调用监听器的onEditTextDelete方法通知外部删除当前编辑框内容对于回车键抬起情况获取当前选择位置后的文本内容
// 然后将文本编辑框内容更新为选择位置前的部分并调用监听器的onEditTextEnter方法通知外部在当前编辑框后添加新的编辑框及相应文本内容
// 其他按键则按照父类默认逻辑处理。
@Override @Override
public boolean onKeyUp(int keyCode, KeyEvent event) { public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) { switch (keyCode) {
case KeyEvent.KEYCODE_DEL: case KeyEvent.KEYCODE_DEL:
if (mOnTextViewChangeListener != null) { if (mOnTextViewChangeListener!= null) {
if (0 == mSelectionStartBeforeDelete && mIndex != 0) { if (0 == mSelectionStartBeforeDelete && mIndex!= 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true; return true;
} }
@ -152,7 +184,7 @@ public class NoteEditText extends EditText {
} }
break; break;
case KeyEvent.KEYCODE_ENTER: case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) { if (mOnTextViewChangeListener!= null) {
int selectionStart = getSelectionStart(); int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString(); String text = getText().subSequence(selectionStart, length()).toString();
setText(getText().subSequence(0, selectionStart)); setText(getText().subSequence(0, selectionStart));
@ -167,9 +199,12 @@ public class NoteEditText extends EditText {
return super.onKeyUp(keyCode, event); return super.onKeyUp(keyCode, event);
} }
// 重写父类的onFocusChanged方法用于处理焦点变化事件当焦点改变时如果设置了文本操作事件监听器OnTextViewChangeListener
// 根据当前是否获取焦点以及文本内容是否为空调用监听器的onTextChange方法通知外部相应的文本显示相关选项如是否隐藏、显示某些操作按钮等的变化情况
// 然后再按照父类默认逻辑处理焦点变化的其他相关操作。
@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) {
if (!focused && TextUtils.isEmpty(getText())) { if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mIndex, false); mOnTextViewChangeListener.onTextChange(mIndex, false);
} else { } else {
@ -179,6 +214,10 @@ public class NoteEditText extends EditText {
super.onFocusChanged(focused, direction, previouslyFocusedRect); super.onFocusChanged(focused, direction, previouslyFocusedRect);
} }
// 重写父类的onCreateContextMenu方法用于创建上下文菜单当文本被长按选中时弹出的菜单内容相关逻辑在此处理。
// 如果文本内容是Spanned类型包含富文本等情况如包含链接获取选中的文本范围查找其中的URLSpan表示链接的一种形式
// 如果只找到一个链接,根据链接的协议头(如"tel:"、"http:"、"mailto:"等获取对应的提示字符串资源ID若找不到对应协议头则使用默认的资源ID
// 然后添加一个菜单项到上下文菜单中点击该菜单项会触发相应的链接跳转逻辑通过调用URLSpan的onClick方法最后再按照父类默认逻辑处理菜单创建的其他相关操作。
@Override @Override
protected void onCreateContextMenu(ContextMenu menu) { protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) { if (getText() instanceof Spanned) {
@ -191,8 +230,8 @@ public class NoteEditText extends EditText {
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) { if (urls.length == 1) {
int defaultResId = 0; int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) { for (String schema : sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) { if (urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema); defaultResId = sSchemaActionResMap.get(schema);
break; break;
} }

@ -26,7 +26,11 @@ 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,60 +46,108 @@ public class NoteItemData {
NoteColumns.WIDGET_TYPE, NoteColumns.WIDGET_TYPE,
}; };
// 定义常量表示查询结果中笔记ID所在列的索引值为0方便后续从游标中准确获取对应数据这里命名为ID_COLUMN。
private static final int ID_COLUMN = 0; private static final int ID_COLUMN = 0;
// 定义常量表示查询结果中提醒日期所在列的索引值为1用于后续获取提醒日期数据命名为ALERTED_DATE_COLUMN。
private static final int ALERTED_DATE_COLUMN = 1; private static final int ALERTED_DATE_COLUMN = 1;
// 定义常量表示查询结果中背景颜色ID所在列的索引值为2便于获取背景颜色相关设置信息命名为BG_COLOR_ID_COLUMN。
private static final int BG_COLOR_ID_COLUMN = 2; private static final int BG_COLOR_ID_COLUMN = 2;
// 定义常量表示查询结果中创建日期所在列的索引值为3用于获取笔记创建时间数据命名为CREATED_DATE_COLUMN。
private static final int CREATED_DATE_COLUMN = 3; private static final int CREATED_DATE_COLUMN = 3;
// 定义常量表示查询结果中是否有附件所在列的索引值为4通过判断该列的值来确定笔记是否包含附件命名为HAS_ATTACHMENT_COLUMN。
private static final int HAS_ATTACHMENT_COLUMN = 4; private static final int HAS_ATTACHMENT_COLUMN = 4;
// 定义常量表示查询结果中修改日期所在列的索引值为5用于获取笔记最后修改时间信息命名为MODIFIED_DATE_COLUMN。
private static final int MODIFIED_DATE_COLUMN = 5; private static final int MODIFIED_DATE_COLUMN = 5;
// 定义常量表示查询结果中笔记数量所在列的索引值为6可能用于某些与笔记数量相关的业务逻辑判断命名为NOTES_COUNT_COLUMN。
private static final int NOTES_COUNT_COLUMN = 6; private static final int NOTES_COUNT_COLUMN = 6;
// 定义常量表示查询结果中父文件夹ID所在列的索引值为7可通过该值确定笔记所属的父文件夹命名为PARENT_ID_COLUMN。
private static final int PARENT_ID_COLUMN = 7; private static final int PARENT_ID_COLUMN = 7;
// 定义常量表示查询结果中摘要片段信息所在列的索引值为8通常用于展示笔记的简短内容描述命名为SNIPPET_COLUMN。
private static final int SNIPPET_COLUMN = 8; private static final int SNIPPET_COLUMN = 8;
// 定义常量表示查询结果中笔记类型所在列的索引值为9用于判断笔记是普通笔记、文件夹还是其他类型命名为TYPE_COLUMN。
private static final int TYPE_COLUMN = 9; private static final int TYPE_COLUMN = 9;
// 定义常量表示查询结果中小部件ID所在列的索引值为10可能涉及与桌面小部件相关的业务逻辑命名为WIDGET_ID_COLUMN。
private static final int WIDGET_ID_COLUMN = 10; private static final int WIDGET_ID_COLUMN = 10;
// 定义常量表示查询结果中小部件类型所在列的索引值为11同样用于小部件相关的类型判断等操作命名为WIDGET_TYPE_COLUMN。
private static final int WIDGET_TYPE_COLUMN = 11; private static final int WIDGET_TYPE_COLUMN = 11;
// 笔记的唯一标识符存储从游标中获取的笔记ID数据用于区分不同的笔记记录。
private long mId; private long mId;
// 笔记设置的提醒日期,以时间戳的形式存储,通过游标获取相应列的数据,可用于判断笔记是否有提醒以及提醒时间相关的业务逻辑。
private long mAlertDate; private long mAlertDate;
// 笔记的背景颜色ID用于确定笔记在展示时的背景颜色设置从游标对应列获取该值便于后续界面显示相关的操作。
private int mBgColorId; private int mBgColorId;
// 笔记的创建日期,以时间戳形式保存,通过游标获取此数据后可用于展示笔记创建时间等功能需求。
private long mCreatedDate; private long mCreatedDate;
// 用于标记笔记是否包含附件根据游标中对应列的值大于0表示有附件进行设置方便在业务逻辑中判断是否需要处理附件相关操作。
private boolean mHasAttachment; private boolean mHasAttachment;
// 笔记的最后修改日期,同样以时间戳形式存储,可用于展示笔记的更新情况以及相关业务逻辑判断,比如判断笔记是否被修改等。
private long mModifiedDate; private long mModifiedDate;
// 可能表示与笔记相关的数量信息(具体含义取决于业务场景),从游标对应列获取该整数值,用于相应的业务逻辑处理。
private int mNotesCount; private int mNotesCount;
// 笔记所属的父文件夹ID通过游标获取该值可以确定笔记在文件夹层级结构中的位置关系方便进行分类、查找等操作。
private long mParentId; private long mParentId;
// 笔记的摘要(片段)内容,从游标获取后存储,通常是笔记内容的简短描述,用于在列表等场景展示给用户一个大致的内容提示,
// 并且在这里会去除一些特定的标记字符(如复选框相关的标记)进行规范化处理。
private String mSnippet; private String mSnippet;
// 笔记的类型,通过游标获取对应列的整数值,用于区分是普通笔记、文件夹还是其他特定类型的记录,以便执行不同的业务逻辑。
private int mType; private int mType;
// 与笔记关联的小部件ID可能用于桌面小部件相关的业务逻辑比如根据小部件ID来更新对应的小部件显示内容等操作。
private int mWidgetId; private int mWidgetId;
// 笔记关联的小部件类型,同样用于小部件相关的业务逻辑判断和处理,例如不同类型小部件展示方式可能不同等情况。
private int mWidgetType; private int mWidgetType;
// 用于存储通话记录相关的联系人姓名,如果笔记属于通话记录文件夹,则尝试获取对应的联系人姓名,若获取失败则使用电话号码作为替代。
private String mName; private String mName;
// 用于存储通话记录相关的电话号码,当笔记属于通话记录文件夹时,通过工具方法获取对应的电话号码信息,用于通话记录相关的业务逻辑处理。
private String mPhoneNumber; private String mPhoneNumber;
// 标记当前笔记是否是列表中的最后一项,通过游标的相关方法判断并设置,方便在列表展示等场景中进行相应的界面处理或业务逻辑判断。
private boolean mIsLastItem; private boolean mIsLastItem;
// 标记当前笔记是否是列表中的第一项,同样依据游标状态进行判断和设置,可用于列表相关的展示逻辑以及操作逻辑区分。
private boolean mIsFirstItem; private boolean mIsFirstItem;
// 标记当前笔记所在的列表是否只包含这一项通过判断游标获取的总记录数是否为1来确定用于一些特殊业务场景下的逻辑判断。
private boolean mIsOnlyOneItem; private boolean mIsOnlyOneItem;
// 标记当前笔记是否是某个文件夹下唯一的一条笔记(后续跟着文件夹的情况),通过特定的游标移动和类型判断逻辑来确定,用于相关业务逻辑处理。
private boolean mIsOneNoteFollowingFolder; private boolean mIsOneNoteFollowingFolder;
// 标记当前笔记是否是某个文件夹后跟着多条笔记中的一条(即所在文件夹下有多条笔记的情况),同样基于游标操作和类型判断逻辑来设置,用于对应业务场景的处理。
private boolean mIsMultiNotesFollowingFolder; private boolean mIsMultiNotesFollowingFolder;
// 构造函数接收上下文和数据库游标作为参数用于从游标中获取各项笔记数据并进行初始化同时调用checkPostion方法来判断笔记在列表中的相关位置状态属性。
public NoteItemData(Context context, Cursor cursor) { public NoteItemData(Context context, Cursor cursor) {
// 从游标中获取笔记的ID并赋值给对应的成员变量通过使用预定义的列索引常量ID_COLUMN来确保准确获取相应数据。
mId = cursor.getLong(ID_COLUMN); mId = cursor.getLong(ID_COLUMN);
// 从游标获取笔记的提醒日期并存储使用ALERTED_DATE_COLUMN索引确保获取正确列的数据。
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
// 获取笔记的背景颜色ID依据BG_COLOR_ID_COLUMN索引从游标中提取对应数据并赋值。
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
// 获取笔记的创建日期按照CREATED_DATE_COLUMN索引从游标获取相应时间戳数据进行存储。
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false; // 根据游标中对应列的值判断笔记是否有附件大于0则设置为true否则为false使用HAS_ATTACHMENT_COLUMN索引来定位数据列。
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0)? true : false;
// 获取笔记的修改日期通过MODIFIED_DATE_COLUMN索引从游标获取相应时间戳并赋值给成员变量。
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
// 获取笔记相关的数量信息具体含义取决于业务场景利用NOTES_COUNT_COLUMN索引从游标中获取整数值存储起来。
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
// 获取笔记所属的父文件夹ID借助PARENT_ID_COLUMN索引从游标获取对应数据赋值给成员变量用于后续确定笔记的层级关系等操作。
mParentId = cursor.getLong(PARENT_ID_COLUMN); mParentId = cursor.getLong(PARENT_ID_COLUMN);
// 获取笔记的摘要片段内容通过SNIPPET_COLUMN索引从游标获取字符串数据并去除特定的复选框相关标记字符进行规范化处理后存储。
mSnippet = cursor.getString(SNIPPET_COLUMN); mSnippet = cursor.getString(SNIPPET_COLUMN);
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, ""); NoteEditActivity.TAG_UNCHECKED, "");
// 获取笔记的类型根据TYPE_COLUMN索引从游标获取整数值来确定笔记是何种类型如普通笔记、文件夹等用于后续不同类型的业务逻辑处理。
mType = cursor.getInt(TYPE_COLUMN); mType = cursor.getInt(TYPE_COLUMN);
// 获取与笔记关联的小部件ID使用WIDGET_ID_COLUMN索引从游标获取对应整数值存储起来用于小部件相关业务逻辑。
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
// 获取笔记关联的小部件类型依靠WIDGET_TYPE_COLUMN索引从游标获取相应整数值便于针对不同小部件类型的处理操作。
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
// 初始化通话记录相关的电话号码为空字符串,后续根据笔记所属文件夹等情况判断是否需要获取实际电话号码。
mPhoneNumber = ""; mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
// 如果笔记所属的父文件夹是通话记录文件夹通过工具方法DataUtils.getCallNumberByNoteId尝试从内容解析器获取对应的电话号码信息。
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) { if (!TextUtils.isEmpty(mPhoneNumber)) {
// 如果获取到了电话号码尝试通过联系人工具类Contact根据电话号码获取对应的联系人姓名若获取失败则使用电话号码作为姓名显示。
mName = Contact.getContact(context, mPhoneNumber); mName = Contact.getContact(context, mPhoneNumber);
if (mName == null) { if (mName == null) {
mName = mPhoneNumber; mName = mPhoneNumber;
@ -109,14 +161,19 @@ public class NoteItemData {
checkPostion(cursor); checkPostion(cursor);
} }
// 用于判断当前笔记在列表中的位置相关状态属性的私有方法,通过游标操作来判断是否是最后一项、第一项、是否唯一一项以及是否是某个文件夹后的笔记情况等,
// 并设置相应的成员变量标记,方便外部通过对应的访问方法获取这些状态信息进行业务逻辑处理。
private void checkPostion(Cursor cursor) { private void checkPostion(Cursor cursor) {
mIsLastItem = cursor.isLast() ? true : false; // 判断游标是否指向最后一条记录若是则将mIsLastItem设置为true表示当前笔记是列表中的最后一项否则为false。
mIsFirstItem = cursor.isFirst() ? true : false; mIsLastItem = cursor.isLast()? true : false;
// 判断游标是否指向第一条记录若为是则将mIsFirstItem设置为true表明当前笔记是列表中的第一项反之则为false。
mIsFirstItem = cursor.isFirst()? true : false;
// 通过判断游标获取的记录总数是否为1来确定当前笔记所在的列表是否只有这一项若是则将mIsOnlyOneItem设置为true。
mIsOnlyOneItem = (cursor.getCount() == 1); mIsOnlyOneItem = (cursor.getCount() == 1);
mIsMultiNotesFollowingFolder = false; mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false; mIsOneNoteFollowingFolder = false;
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { if (mType == Notes.TYPE_NOTE &&!mIsFirstItem) {
int position = cursor.getPosition(); int position = cursor.getPosition();
if (cursor.moveToPrevious()) { if (cursor.moveToPrevious()) {
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
@ -134,91 +191,70 @@ public class NoteItemData {
} }
} }
// 用于判断当前笔记是否是某个文件夹下唯一的一条笔记(后续跟着文件夹的情况)的方法,返回对应的成员变量值,外部可通过调用此方法获取该状态信息进行相应逻辑处理。
public boolean isOneFollowingFolder() { public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder; return mIsOneNoteFollowingFolder;
} }
// 用于判断当前笔记是否是某个文件夹后跟着多条笔记中的一条(即所在文件夹下有多条笔记的情况)的方法,返回相应的成员变量值,方便外部根据此状态进行业务逻辑操作。
public boolean isMultiFollowingFolder() { public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder; return mIsMultiNotesFollowingFolder;
} }
// 用于判断当前笔记是否是列表中的最后一项的方法,返回对应的成员变量值,可在列表展示等场景中根据此结果进行界面处理或其他相关逻辑判断。
public boolean isLast() { public boolean isLast() {
return mIsLastItem; return mIsLastItem;
} }
// 用于获取通话记录相关的联系人姓名的方法,如果笔记属于通话记录文件夹,则返回获取到的联系人姓名(若获取失败则返回电话号码或者空字符串),方便外部展示相关信息。
public String getCallName() { public String getCallName() {
return mName; return mName;
} }
// 用于判断当前笔记是否是列表中的第一项的方法,返回对应的成员变量值,在列表相关的业务逻辑中可依据此结果进行不同的处理操作。
public boolean isFirst() { public boolean isFirst() {
return mIsFirstItem; return mIsFirstItem;
} }
// 用于判断当前笔记所在的列表是否只包含这一项的方法,返回对应的成员变量值,可用于特定业务场景下的逻辑判断和处理。
public boolean isSingle() { public boolean isSingle() {
return mIsOnlyOneItem; return mIsOnlyOneItem;
} }
// 用于获取笔记的唯一标识符ID的方法返回存储的笔记ID值外部可通过调用此方法获取该笔记的ID进行如查找、关联等相关操作。
public long getId() { public long getId() {
return mId; return mId;
} }
// 用于获取笔记设置的提醒日期的方法,返回存储的提醒日期时间戳数据,可用于判断笔记是否有提醒以及相关提醒时间相关的业务逻辑处理。
public long getAlertDate() { public long getAlertDate() {
return mAlertDate; return mAlertDate;
} }
// 用于获取笔记的创建日期的方法,返回存储的创建日期时间戳,便于在展示笔记信息等场景中显示创建时间相关内容。
public long getCreatedDate() { public long getCreatedDate() {
return mCreatedDate; return mCreatedDate;
} }
// 用于判断笔记是否包含附件的方法,返回对应的成员变量值,外部可依据此结果决定是否展示附件相关操作入口等业务逻辑处理。
public boolean hasAttachment() { public boolean hasAttachment() {
return mHasAttachment; return mHasAttachment;
} }
// 用于获取笔记的最后修改日期的方法,返回存储的修改日期时间戳,可用于展示笔记更新情况以及相关业务逻辑判断,比如判断笔记是否被修改等操作。
public long getModifiedDate() { public long getModifiedDate() {
return mModifiedDate; return mModifiedDate;
} }
// 用于获取笔记的背景颜色ID的方法返回存储的背景颜色ID值方便在界面展示等场景中根据该ID设置笔记的背景颜色相关操作。
public int getBgColorId() { public int getBgColorId() {
return mBgColorId; return mBgColorId;
} }
// 用于获取笔记所属的父文件夹ID的方法返回存储的父文件夹ID值通过此方法可确定笔记在文件夹层级结构中的位置关系方便进行分类、查找等操作。
public long getParentId() { public long getParentId() {
return mParentId; return mParentId;
} }
public int getNotesCount() { // 用于获取与笔记相关的数量信息(具体含义取决于业务场景)的方法,返回存储的整数值,用于相应的业务逻辑处理。
return mNotesCount; public
}
public long getFolderId () {
return mParentId;
}
public int getType() {
return mType;
}
public int getWidgetType() {
return mWidgetType;
}
public int getWidgetId() {
return mWidgetId;
}
public String getSnippet() {
return mSnippet;
}
public boolean hasAlert() {
return (mAlertDate > 0);
}
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

@ -32,153 +32,29 @@ 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) {
// 调用父类构造方法传入上下文并将游标初始化为null
super(context, null); super(context, null);
// 初始化选中索引的哈希映射
mSelectedIndex = new HashMap<Integer, Boolean>(); mSelectedIndex = new HashMap<Integer, Boolean>();
// 保存传入的上下文对象
mContext = context; mContext = context;
// 初始化笔记数量为0
mNotesCount = 0; mNotesCount = 0;
} }
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) {
NoteItemData itemData = new NoteItemData(context, cursor);
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
}
}
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
}
public boolean isInChoiceMode() {
return mChoiceMode;
}
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mChoiceMode = mode;
}
public void selectAll(boolean checked) {
Cursor cursor = getCursor();
for (int i = 0; i < getCount(); i++) {
if (cursor.moveToPosition(i)) {
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
setCheckedItem(i, checked);
}
}
}
}
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
}
}
return itemSet;
}
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Cursor c = (Cursor) getItem(position);
if (c != null) {
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
/**
* Don't close cursor here, only the adapter could close it
*/
} else {
Log.e(TAG, "Invalid cursor");
return null;
}
}
}
return itemSet;
}
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
return 0;
}
Iterator<Boolean> iter = values.iterator();
int count = 0;
while (iter.hasNext()) {
if (true == iter.next()) {
count++;
}
}
return count;
}
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
}
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
}
return mSelectedIndex.get(position);
}
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount();
}
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
calcNotesCount();
}
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
Cursor c = (Cursor) getItem(i);
if (c != null) {
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
}
} else {
Log.e(TAG, "Invalid cursor");
return;
}
}
}
}

@ -31,33 +31,42 @@ import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
public class NotesListItem extends LinearLayout { public class NotesListItem extends LinearLayout {
// 用于显示提醒相关图标的 ImageView可能表示笔记有提醒等情况
private ImageView mAlert; private ImageView mAlert;
// 用于显示笔记或文件夹标题的 TextView
private TextView mTitle; private TextView mTitle;
// 用于显示时间相关信息(如修改时间等)的 TextView
private TextView mTime; private TextView mTime;
// 用于显示通话记录相关名称的 TextView可能在特定的通话记录相关场景下使用
private TextView mCallName; private TextView mCallName;
// 封装了笔记或文件夹相关数据的对象,用于在列表项中展示具体内容
private NoteItemData mItemData; private NoteItemData mItemData;
// 复选框,用于在选择模式下标记该项是否被选中
private CheckBox mCheckBox; private CheckBox mCheckBox;
public NotesListItem(Context context) { public NotesListItem(Context context) {
super(context); super(context);
// 通过布局资源文件来填充当前的LinearLayout即NotesListItem自身使其具备相应的子视图布局结构
inflate(context, R.layout.note_item, this); inflate(context, R.layout.note_item, this);
// 通过ID查找并初始化对应的子视图组件
mAlert = (ImageView) findViewById(R.id.iv_alert_icon); mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mTitle = (TextView) findViewById(R.id.tv_title); mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time); mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name); mCallName = (TextView) findViewById(R.id.tv_name);
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) {
if (choiceMode && data.getType() == Notes.TYPE_NOTE) { if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
// 如果处于选择模式且当前数据对应的是笔记类型,则显示复选框,并根据传入的选中状态设置复选框的勾选状态
mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked); mCheckBox.setChecked(checked);
} else { } else {
// 否则隐藏复选框,比如在非选择模式或者是文件夹类型等情况时不需要显示复选框
mCheckBox.setVisibility(View.GONE); mCheckBox.setVisibility(View.GONE);
} }
mItemData = data; mItemData = data;
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
// 如果当前数据对应的是通话记录文件夹
mCallName.setVisibility(View.GONE); mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE); mAlert.setVisibility(View.VISIBLE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
@ -65,6 +74,7 @@ public class NotesListItem extends LinearLayout {
+ context.getString(R.string.format_folder_files_count, data.getNotesCount())); + context.getString(R.string.format_folder_files_count, data.getNotesCount()));
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) {
// 如果当前数据的父文件夹是通话记录文件夹,通常表示属于通话记录相关的具体内容
mCallName.setVisibility(View.VISIBLE); mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName()); mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
@ -80,11 +90,13 @@ public class NotesListItem extends LinearLayout {
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
if (data.getType() == Notes.TYPE_FOLDER) { if (data.getType() == Notes.TYPE_FOLDER) {
// 如果当前数据对应的是普通文件夹类型,设置标题文本包含文件夹名称及其中文件数量信息,并隐藏提醒图标
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()));
mAlert.setVisibility(View.GONE); mAlert.setVisibility(View.GONE);
} else { } else {
// 如果是普通笔记类型,设置标题为格式化后的笔记摘要内容,并根据是否有提醒来显示或隐藏提醒图标
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) { if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock); mAlert.setImageResource(R.drawable.clock);
@ -94,11 +106,12 @@ public class NotesListItem extends LinearLayout {
} }
} }
} }
// 设置时间文本,显示相对时间(可能是相对于当前时间的修改时间等)
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 根据数据设置列表项的背景样式,区分笔记和文件夹以及笔记的不同状态等情况
setBackground(data); setBackground(data);
} }
private void setBackground(NoteItemData data) { private void setBackground(NoteItemData data) {
int id = data.getBgColorId(); int id = data.getBgColorId();
if (data.getType() == Notes.TYPE_NOTE) { if (data.getType() == Notes.TYPE_NOTE) {
@ -115,8 +128,6 @@ public class NotesListItem extends LinearLayout {
setBackgroundResource(NoteItemBgResources.getFolderBgRes()); setBackgroundResource(NoteItemBgResources.getFolderBgRes());
} }
} }
public NoteItemData getItemData() { public NoteItemData getItemData() {
return mItemData; return mItemData;
} }
}

@ -49,42 +49,51 @@ import net.micode.notes.gtask.remote.GTaskSyncService;
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;
@Override @Override
protected void onCreate(Bundle icicle) { protected void onCreate(Bundle icicle) {
super.onCreate(icicle); super.onCreate(icicle);
/* using the app icon for navigation */ // 使用应用图标进行导航启用返回上级页面功能ActionBar上显示返回箭头
getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setDisplayHomeAsUpEnabled(true);
// 从指定的XML资源文件加载偏好设置界面布局
addPreferencesFromResource(R.xml.preferences); addPreferencesFromResource(R.xml.preferences);
// 获取同步账号相关的偏好设置分类
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
// 创建广播接收器实例
mReceiver = new GTaskReceiver(); mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter(); IntentFilter filter = new IntentFilter();
// 注册接收同步服务广播的过滤器,监听同步服务广播的特定动作
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
// 注册广播接收器,用于接收同步服务相关广播
registerReceiver(mReceiver, filter); registerReceiver(mReceiver, filter);
mOriAccounts = null; mOriAccounts = null;
// 加载设置界面头部视图
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
// 将头部视图添加到ListView用于显示偏好设置的列表视图并设置相关参数
getListView().addHeaderView(header, null, true); getListView().addHeaderView(header, null, true);
} }
@ -92,19 +101,23 @@ public class NotesPreferenceActivity extends PreferenceActivity {
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
// need to set sync account automatically if user has added a new // 如果用户添加了新账号,需要自动设置同步账号
// account
if (mHasAddedAccount) { if (mHasAddedAccount) {
// 获取当前的谷歌账号列表
Account[] accounts = getGoogleAccounts(); Account[] accounts = getGoogleAccounts();
if (mOriAccounts != null && accounts.length > mOriAccounts.length) { // 判断原始账号列表不为空且当前账号数量比原始账号数量多(意味着可能添加了新账号)
if (mOriAccounts!= null && accounts.length > mOriAccounts.length) {
// 遍历新账号列表
for (Account accountNew : accounts) { for (Account accountNew : accounts) {
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 +126,49 @@ public class NotesPreferenceActivity extends PreferenceActivity {
} }
} }
// 刷新界面UI更新相关显示内容比如账号信息、同步按钮状态等
refreshUI(); refreshUI();
} }
@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 accountPref = new Preference(this); Preference accountPref = new Preference(this);
// 获取当前设置的同步账号名称(如果有的话)
final String defaultAccount = getSyncAccountName(this); final String defaultAccount = getSyncAccountName(this);
// 设置账号偏好设置项的标题
accountPref.setTitle(getString(R.string.preferences_account_title)); accountPref.setTitle(getString(R.string.preferences_account_title));
// 设置账号偏好设置项的摘要内容
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) {
// 如果同步服务当前没有在同步中
if (!GTaskSyncService.isSyncing()) { if (!GTaskSyncService.isSyncing()) {
// 如果还没有设置过同步账号(账号名称为空)
if (TextUtils.isEmpty(defaultAccount)) { if (TextUtils.isEmpty(defaultAccount)) {
// the first time to set account // 首次设置账号,弹出选择账号的对话框
showSelectAccountAlertDialog(); showSelectAccountAlertDialog();
} else { } else {
// if the account has already been set, we need to promp // 如果已经设置过账号,弹出确认修改账号的对话框(提示相关风险等)
// user about the risk
showChangeAccountConfirmAlertDialog(); showChangeAccountConfirmAlertDialog();
} }
} else { } else {
// 如果同步服务正在同步,显示提示信息告知用户不能修改账号
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,82 +177,113 @@ public class NotesPreferenceActivity extends PreferenceActivity {
} }
}); });
// 将账号偏好设置项添加到账号分类下
mAccountCategory.addPreference(accountPref); mAccountCategory.addPreference(accountPref);
} }
/**
*
*/
private void loadSyncButton() { private void loadSyncButton() {
// 获取同步按钮实例
Button syncButton = (Button) findViewById(R.id.preference_sync_button); Button syncButton = (Button) findViewById(R.id.preference_sync_button);
// 获取显示上次同步时间的文本视图实例
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// 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));
// 设置按钮点击事件,点击时取消同步操作
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));
// 设置按钮点击事件,点击时开始同步操作
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 // 根据同步服务状态设置上次同步时间文本视图的显示内容和可见性
if (GTaskSyncService.isSyncing()) { if (GTaskSyncService.isSyncing()) {
// 如果正在同步,显示同步进度相关信息
lastSyncTimeView.setText(GTaskSyncService.getProgressString()); lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE); lastSyncTimeView.setVisibility(View.VISIBLE);
} else { } else {
// 如果没有同步,获取上次同步时间
long lastSyncTime = getLastSyncTime(this); long lastSyncTime = getLastSyncTime(this);
if (lastSyncTime != 0) { if (lastSyncTime!= 0) {
// 如果上次同步时间不为0格式化时间并设置显示文本
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隐藏该文本视图
lastSyncTimeView.setVisibility(View.GONE); lastSyncTimeView.setVisibility(View.GONE);
} }
} }
} }
/**
* UI
*/
private void refreshUI() { private void refreshUI() {
loadAccountPreference(); loadAccountPreference();
loadSyncButton(); loadSyncButton();
} }
/**
*
*/
private void showSelectAccountAlertDialog() { private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 加载对话框标题视图布局
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
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));
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 = accounts; mOriAccounts = accounts;
mHasAddedAccount = false; mHasAddedAccount = false;
if (accounts.length > 0) { if (accounts.length > 0) {
// 创建用于显示账号名称的字符序列数组(长度与账号列表长度一致)
CharSequence[] items = new CharSequence[accounts.length]; CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items; final CharSequence[] itemMapping = items;
int checkedItem = -1; int checkedItem = -1;
int index = 0; int index = 0;
// 遍历账号列表,填充显示账号名称的数组,并记录当前已设置账号在数组中的位置(用于默认选中)
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) {
@ -237,10 +294,12 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}); });
} }
// 加载添加账号相关视图布局(可能用于提示用户添加账号等操作)
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,22 +313,30 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}); });
} }
/**
*
*/
private void showChangeAccountConfirmAlertDialog() { private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 加载对话框标题视图布局
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
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)));
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) {
@ -283,26 +350,40 @@ public class NotesPreferenceActivity extends PreferenceActivity {
dialogBuilder.show(); dialogBuilder.show();
} }
/**
*
*
* @return
*/
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");
} }
/**
*
*
* @param account
*/
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) {
// 如果账号不为空,将账号名称保存到偏好设置中
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
} else { } else {
// 如果账号为空,清除已保存的账号名称
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
} }
editor.commit(); editor.commit();
// clean up last sync time // 清理上次同步时间设置为0
setLastSyncTime(this, 0); setLastSyncTime(this, 0);
// clean up local gtask related info // 清理本地与GTask相关的信息在新线程中执行数据库更新操作
new Thread(new Runnable() { new Thread(new Runnable() {
public void run() { public void run() {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
@ -312,12 +393,16 @@ public class NotesPreferenceActivity extends PreferenceActivity {
} }
}).start(); }).start();
// 显示设置账号成功的提示信息
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 = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit(); SharedPreferences.Editor editor = settings.edit();
@ -329,7 +414,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
} }
editor.commit(); editor.commit();
// clean up local gtask related info // 清理本地与GTask相关的信息在新线程中执行数据库更新操作
new Thread(new Runnable() { new Thread(new Runnable() {
public void run() { public void run() {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
@ -340,49 +425,18 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}).start(); }).start();
} }
/**
*
*
* @param context
* @return
*/
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) { /**
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, *
Context.MODE_PRIVATE); *
SharedPreferences.Editor editor = settings.edit();
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
editor.commit();
}
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
refreshUI();
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
syncStatus.setText(intent
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
}
}
}
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Intent intent = new Intent(this, NotesListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
default:
return false;
}
}
}

@ -32,24 +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;
// NoteWidgetProvider抽象类继承自AppWidgetProvider用于处理与笔记相关的桌面小部件逻辑
public abstract class NoteWidgetProvider extends AppWidgetProvider { public abstract class NoteWidgetProvider extends AppWidgetProvider {
// 定义一个字符串数组,用于指定从数据库查询时要获取的列名
// 包含笔记的ID、背景颜色ID以及摘要信息等列
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
}; };
// 定义常量表示PROJECTION数组中对应列的索引方便后续从查询结果如Cursor中获取具体数据
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";
// 重写AppWidgetProvider的onDeleted方法当桌面小部件被删除时触发该方法
@Override @Override
public void onDeleted(Context context, int[] appWidgetIds) { public void onDeleted(Context context, int[] appWidgetIds) {
// 创建一个ContentValues对象用于存储要更新的数据
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
// 将NoteColumns.WIDGET_ID对应的字段值设为AppWidgetManager.INVALID_APPWIDGET_ID标记小部件已无效
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条件去更新数据库中相关记录
// 将对应的小部件ID标记为无效Notes.CONTENT_NOTE_URI表示数据源
context.getContentResolver().update(Notes.CONTENT_NOTE_URI, context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
values, values,
NoteColumns.WIDGET_ID + "=?", NoteColumns.WIDGET_ID + "=?",
@ -57,7 +70,12 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
} }
} }
// 私有方法用于根据给定的小部件IDwidgetId从内容提供器查询相关的笔记小部件信息
private Cursor getNoteWidgetInfo(Context context, int widgetId) { private Cursor getNoteWidgetInfo(Context context, int widgetId) {
// 通过ContentResolver进行查询操作
// 查询的数据源是Notes.CONTENT_NOTE_URI查询的列由PROJECTION指定
// 筛选条件要求NoteColumns.WIDGET_ID等于传入的小部件ID且NoteColumns.PARENT_ID不等于特定的垃圾文件夹IDNotes.ID_TRASH_FOLER
// 最后返回查询得到的Cursor对象如果没有符合条件的数据则返回null
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION, PROJECTION,
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
@ -65,68 +83,95 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
null); null);
} }
// 对外可调用的更新小部件的方法它内部调用另一个带有更多参数的update方法并传入false作为隐私模式privacyMode的参数值
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) { // 只处理有效的小部件ID不等于AppWidgetManager.INVALID_APPWIDGET_ID的情况
if (appWidgetIds[i]!= AppWidgetManager.INVALID_APPWIDGET_ID) {
// 获取默认的背景ID具体获取逻辑由ResourceParser.getDefaultBgId方法实现
int bgId = ResourceParser.getDefaultBgId(context); int bgId = ResourceParser.getDefaultBgId(context);
// 初始化摘要信息为空字符串
String snippet = ""; String snippet = "";
// 创建一个用于启动NoteEditActivity的Intent设置相关标志位和额外数据
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.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
Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]);
if (c != null && c.moveToFirst()) { if (c!= null && c.moveToFirst()) {
// 如果查询结果中存在多条符合条件的数据理论上小部件ID应该唯一对应一条记录则记录错误日志并关闭Cursor直接返回
if (c.getCount() > 1) { if (c.getCount() > 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;
} }
// 从Cursor中获取摘要信息
snippet = c.getString(COLUMN_SNIPPET); snippet = c.getString(COLUMN_SNIPPET);
// 从Cursor中获取背景颜色ID
bgId = c.getInt(COLUMN_BG_COLOR_ID); bgId = c.getInt(COLUMN_BG_COLOR_ID);
// 向Intent中添加额外数据用户ID等信息
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 = context.getResources().getString(R.string.widget_havenot_content); snippet = context.getResources().getString(R.string.widget_havenot_content);
// 设置Intent的动作表示是插入/编辑操作
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
} }
if (c != null) { // 如果Cursor不为空关闭它以释放资源
if (c!= null) {
c.close(); c.close();
} }
// 创建一个RemoteViews对象用于构建小部件的界面显示传入当前应用的包名和通过抽象方法获取的布局资源ID
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
// 根据抽象方法获取的背景资源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);
/**
* Generate the pending intent to start host for the widget // 用于生成点击小部件时启动对应Activity的PendingIntent初始化为null
*/
PendingIntent pendingIntent = null; PendingIntent pendingIntent = null;
if (privacyMode) { if (privacyMode) {
// 如果处于隐私模式,设置小部件文本显示特定的隐私提示信息
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));
// 创建一个用于启动NotesListActivity的PendingIntent用于隐私模式下的点击响应
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
rv.setTextViewText(R.id.widget_text, snippet); rv.setTextViewText(R.id.widget_text, snippet);
// 创建一个用于启动NoteEditActivity由前面intent定义的PendingIntent用于正常模式下的点击响应
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更新小部件的实际显示内容
appWidgetManager.updateAppWidget(appWidgetIds[i], rv); appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
} }
} }
} }
// 抽象方法要求子类实现该方法根据传入的背景ID获取对应的背景资源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();
} }

@ -24,22 +24,34 @@ import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser; import net.micode.notes.tool.ResourceParser;
// NoteWidgetProvider_2x类继承自NoteWidgetProvider抽象类用于实现特定尺寸可能是2倍尺寸相关从类名推测的笔记桌面小部件相关逻辑
public class NoteWidgetProvider_2x extends NoteWidgetProvider { public class NoteWidgetProvider_2x extends NoteWidgetProvider {
// 重写onUpdate方法当桌面小部件需要更新时会触发该方法
// 这里调用了父类NoteWidgetProvider的update方法来执行具体的更新操作
@Override @Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds); super.update(context, appWidgetManager, appWidgetIds);
} }
// 实现父类NoteWidgetProvider中定义的抽象方法getLayoutId
// 返回用于该特定小部件2x尺寸相关的布局资源ID对应布局资源为R.layout.widget_2x
@Override @Override
protected int getLayoutId() { protected int getLayoutId() {
return R.layout.widget_2x; return R.layout.widget_2x;
} }
// 实现父类NoteWidgetProvider中定义的抽象方法getBgResourceId
// 根据传入的背景IDbgId通过ResourceParser.WidgetBgResources.getWidget2xBgResource方法来获取对应的2x尺寸小部件的背景资源ID
// 用于设置该特定小部件的背景显示资源
@Override @Override
protected int getBgResourceId(int bgId) { protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
} }
// 实现父类NoteWidgetProvider中定义的抽象方法getWidgetType
// 返回该小部件的类型标识此处返回值为Notes.TYPE_WIDGET_2X表示这是2x尺寸类型的小部件
// 在整个小部件相关的处理逻辑中,可能会根据这个类型做不同的操作(例如不同类型小部件显示不同样式、功能等)
@Override @Override
protected int getWidgetType() { protected int getWidgetType() {
return Notes.TYPE_WIDGET_2X; return Notes.TYPE_WIDGET_2X;

@ -24,21 +24,32 @@ import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser; import net.micode.notes.tool.ResourceParser;
// NoteWidgetProvider_4x类继承自NoteWidgetProvider抽象类它主要负责实现特定的推测为4倍尺寸相关从类名判断笔记桌面小部件相关的具体逻辑。
public class NoteWidgetProvider_4x extends NoteWidgetProvider { public class NoteWidgetProvider_4x extends NoteWidgetProvider {
// 重写onUpdate方法该方法会在桌面小部件需要进行更新操作时被触发。
// 在这里它调用了父类NoteWidgetProvider的update方法来执行实际的小部件更新逻辑利用了父类中已经定义好的通用更新流程。
@Override @Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds); super.update(context, appWidgetManager, appWidgetIds);
} }
// 重写父类NoteWidgetProvider中定义的抽象方法getLayoutId用于返回此特定小部件4x尺寸相关对应的布局资源ID。
// 通过返回R.layout.widget_4x指定了该小部件所使用的布局文件这个布局文件应该是按照4x尺寸的设计需求进行定制的。
// 注意:这里方法的修饰符应该是@Override原代码中缺少该注解可能会导致代码逻辑与预期不符若后续在父类中修改了抽象方法的签名等情况编译器无法准确检查此处是否正确重写。
protected int getLayoutId() { protected int getLayoutId() {
return R.layout.widget_4x; return R.layout.widget_4x;
} }
// 重写父类NoteWidgetProvider中定义的抽象方法getBgResourceId根据传入的背景IDbgId来获取对应的背景资源ID。
// 具体是通过调用ResourceParser.WidgetBgResources.getWidget4xBgResource方法来获取适用于4x尺寸小部件的背景资源以用于设置该小部件的背景显示效果。
@Override @Override
protected int getBgResourceId(int bgId) { protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
} }
// 重写父类NoteWidgetProvider中定义的抽象方法getWidgetType用于返回该小部件的类型标识。
// 这里返回Notes.TYPE_WIDGET_4X表示这是一个4x尺寸类型的小部件在整个小部件相关的处理流程中可能会依据这个类型标识进行不同的业务处理比如展现不同的样式、启用不同的功能等。
@Override @Override
protected int getWidgetType() { protected int getWidgetType() {
return Notes.TYPE_WIDGET_4X; return Notes.TYPE_WIDGET_4X;

Loading…
Cancel
Save