|
|
|
|
@ -37,304 +37,369 @@ import org.json.JSONObject;
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 本地便签(主表)操作工具
|
|
|
|
|
* 通俗说:这个类专门管本地便签数据库里“便签主表”的内容,
|
|
|
|
|
* 包含便签的基本信息(比如标题、背景色、创建时间),还关联着便签的明细内容(比如正文),
|
|
|
|
|
* 负责新便签创建、已有便签加载、内容修改记录、打包和保存到数据库
|
|
|
|
|
* 本地便签实体类 (Data Layer - Note Master Table Management)
|
|
|
|
|
* <p>
|
|
|
|
|
* 核心职责:封装便签主表(note表)的CRUD操作,管理与便签相关的所有元数据,
|
|
|
|
|
* 包括便签的基本属性、文件夹关系、桌面Widget关联以及详细内容数据。
|
|
|
|
|
* 架构说明:作为便签数据的核心业务对象,实现JSON序列化/反序列化,
|
|
|
|
|
* 支持增量更新和版本控制,为GTask同步提供数据支撑。
|
|
|
|
|
* 设计要点:采用差异跟踪机制,支持离线编辑和批量同步。
|
|
|
|
|
*/
|
|
|
|
|
public class SqlNote {
|
|
|
|
|
// 日志标签:打印这个类的日志时,用来标识日志来自这个类
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 日志标识,使用类名作为TAG
|
|
|
|
|
*/
|
|
|
|
|
private static final String TAG = SqlNote.class.getSimpleName();
|
|
|
|
|
|
|
|
|
|
// 无效的便签ID:用来标记便签还没有有效的唯一编号
|
|
|
|
|
/**
|
|
|
|
|
* 无效ID常量,表示便签尚未在数据库中存在
|
|
|
|
|
*/
|
|
|
|
|
private static final int INVALID_ID = -99999;
|
|
|
|
|
|
|
|
|
|
// 查询便签主表时,要获取的字段清单(相当于要查的列名,对应便签的各项基本信息)
|
|
|
|
|
/**
|
|
|
|
|
* 便签表查询投影列定义
|
|
|
|
|
* <p>
|
|
|
|
|
* 架构说明:定义查询note表时需要返回的所有列,
|
|
|
|
|
* 完整覆盖便签的所有业务属性,用于数据恢复和同步对比。
|
|
|
|
|
*/
|
|
|
|
|
public static final String[] PROJECTION_NOTE = new String[] {
|
|
|
|
|
NoteColumns.ID, // 便签唯一ID
|
|
|
|
|
NoteColumns.ALERTED_DATE, // 提醒时间
|
|
|
|
|
NoteColumns.BG_COLOR_ID, // 背景颜色ID
|
|
|
|
|
NoteColumns.CREATED_DATE, // 创建时间
|
|
|
|
|
NoteColumns.HAS_ATTACHMENT, // 是否有附件(0=没有,1=有)
|
|
|
|
|
NoteColumns.MODIFIED_DATE, // 最后修改时间
|
|
|
|
|
NoteColumns.NOTES_COUNT, // 子便签数量
|
|
|
|
|
NoteColumns.PARENT_ID, // 所属文件夹ID
|
|
|
|
|
NoteColumns.SNIPPET, // 便签摘要(标题/部分正文)
|
|
|
|
|
NoteColumns.TYPE, // 便签类型(普通便签/文件夹/系统文件夹)
|
|
|
|
|
NoteColumns.WIDGET_ID, // 桌面小组件ID
|
|
|
|
|
NoteColumns.WIDGET_TYPE, // 桌面小组件类型
|
|
|
|
|
NoteColumns.SYNC_ID, // 同步编号
|
|
|
|
|
NoteColumns.LOCAL_MODIFIED, // 本地修改标记
|
|
|
|
|
NoteColumns.ORIGIN_PARENT_ID, // 原始所属文件夹ID
|
|
|
|
|
NoteColumns.GTASK_ID, // 对应云端GTask的ID
|
|
|
|
|
NoteColumns.VERSION // 便签版本号(用来避免同步冲突)
|
|
|
|
|
NoteColumns.ID, // 便签ID
|
|
|
|
|
NoteColumns.ALERTED_DATE, // 提醒时间戳
|
|
|
|
|
NoteColumns.BG_COLOR_ID, // 背景颜色ID
|
|
|
|
|
NoteColumns.CREATED_DATE, // 创建时间戳
|
|
|
|
|
NoteColumns.HAS_ATTACHMENT, // 附件标记
|
|
|
|
|
NoteColumns.MODIFIED_DATE, // 最后修改时间戳
|
|
|
|
|
NoteColumns.NOTES_COUNT, // 子便签数量
|
|
|
|
|
NoteColumns.PARENT_ID, // 父文件夹ID
|
|
|
|
|
NoteColumns.SNIPPET, // 便签摘要
|
|
|
|
|
NoteColumns.TYPE, // 便签类型
|
|
|
|
|
NoteColumns.WIDGET_ID, // 桌面Widget ID
|
|
|
|
|
NoteColumns.WIDGET_TYPE, // 桌面Widget类型
|
|
|
|
|
NoteColumns.SYNC_ID, // 同步ID
|
|
|
|
|
NoteColumns.LOCAL_MODIFIED, // 本地修改标记
|
|
|
|
|
NoteColumns.ORIGIN_PARENT_ID, // 原始父文件夹ID
|
|
|
|
|
NoteColumns.GTASK_ID, // Google Tasks关联ID
|
|
|
|
|
NoteColumns.VERSION // 数据版本号
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 上面字段清单对应的索引位置,方便从查询结果里快速取值
|
|
|
|
|
public static final int ID_COLUMN = 0; // 便签ID在查询结果里的位置
|
|
|
|
|
public static final int ALERTED_DATE_COLUMN = 1; // 提醒时间在查询结果里的位置
|
|
|
|
|
public static final int BG_COLOR_ID_COLUMN = 2; // 背景颜色ID在查询结果里的位置
|
|
|
|
|
public static final int CREATED_DATE_COLUMN = 3; // 创建时间在查询结果里的位置
|
|
|
|
|
public static final int HAS_ATTACHMENT_COLUMN = 4; // 是否有附件在查询结果里的位置
|
|
|
|
|
public static final int MODIFIED_DATE_COLUMN = 5; // 最后修改时间在查询结果里的位置
|
|
|
|
|
public static final int NOTES_COUNT_COLUMN = 6; // 子便签数量在查询结果里的位置
|
|
|
|
|
public static final int PARENT_ID_COLUMN = 7; // 所属文件夹ID在查询结果里的位置
|
|
|
|
|
public static final int SNIPPET_COLUMN = 8; // 便签摘要在查询结果里的位置
|
|
|
|
|
public static final int TYPE_COLUMN = 9; // 便签类型在查询结果里的位置
|
|
|
|
|
public static final int WIDGET_ID_COLUMN = 10; // 桌面小组件ID在查询结果里的位置
|
|
|
|
|
public static final int WIDGET_TYPE_COLUMN = 11; // 桌面小组件类型在查询结果里的位置
|
|
|
|
|
public static final int SYNC_ID_COLUMN = 12; // 同步编号在查询结果里的位置
|
|
|
|
|
public static final int LOCAL_MODIFIED_COLUMN = 13; // 本地修改标记在查询结果里的位置
|
|
|
|
|
public static final int ORIGIN_PARENT_ID_COLUMN = 14; // 原始所属文件夹ID在查询结果里的位置
|
|
|
|
|
public static final int GTASK_ID_COLUMN = 15; // 云端GTaskID在查询结果里的位置
|
|
|
|
|
public static final int VERSION_COLUMN = 16; // 便签版本号在查询结果里的位置
|
|
|
|
|
|
|
|
|
|
// 上下文:用来获取数据库操作工具、默认配置等
|
|
|
|
|
/**
|
|
|
|
|
* 查询结果列索引常量
|
|
|
|
|
*/
|
|
|
|
|
public static final int ID_COLUMN = 0;
|
|
|
|
|
public static final int ALERTED_DATE_COLUMN = 1;
|
|
|
|
|
public static final int BG_COLOR_ID_COLUMN = 2;
|
|
|
|
|
public static final int CREATED_DATE_COLUMN = 3;
|
|
|
|
|
public static final int HAS_ATTACHMENT_COLUMN = 4;
|
|
|
|
|
public static final int MODIFIED_DATE_COLUMN = 5;
|
|
|
|
|
public static final int NOTES_COUNT_COLUMN = 6;
|
|
|
|
|
public static final int PARENT_ID_COLUMN = 7;
|
|
|
|
|
public static final int SNIPPET_COLUMN = 8;
|
|
|
|
|
public static final int TYPE_COLUMN = 9;
|
|
|
|
|
public static final int WIDGET_ID_COLUMN = 10;
|
|
|
|
|
public static final int WIDGET_TYPE_COLUMN = 11;
|
|
|
|
|
public static final int SYNC_ID_COLUMN = 12;
|
|
|
|
|
public static final int LOCAL_MODIFIED_COLUMN = 13;
|
|
|
|
|
public static final int ORIGIN_PARENT_ID_COLUMN = 14;
|
|
|
|
|
public static final int GTASK_ID_COLUMN = 15;
|
|
|
|
|
public static final int VERSION_COLUMN = 16;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 应用上下文,用于访问系统服务和资源
|
|
|
|
|
*/
|
|
|
|
|
private Context mContext;
|
|
|
|
|
// 数据库操作工具:用来和本地便签数据库打交道(增删改查)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* ContentResolver实例,用于访问ContentProvider
|
|
|
|
|
*/
|
|
|
|
|
private ContentResolver mContentResolver;
|
|
|
|
|
// 是否是新便签:true=新创建的(还没存到数据库),false=已存在的(从数据库加载的)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建标记,true表示新便签(未提交到数据库)
|
|
|
|
|
*/
|
|
|
|
|
private boolean mIsCreate;
|
|
|
|
|
// 便签唯一ID
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 便签ID
|
|
|
|
|
*/
|
|
|
|
|
private long mId;
|
|
|
|
|
// 提醒时间(比如设置了早上8点提醒,这里存的是对应的时间戳)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 提醒时间戳
|
|
|
|
|
*/
|
|
|
|
|
private long mAlertDate;
|
|
|
|
|
// 背景颜色ID(对应不同的便签背景色,比如白色、黄色)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 背景颜色ID
|
|
|
|
|
*/
|
|
|
|
|
private int mBgColorId;
|
|
|
|
|
// 创建时间(存的是时间戳,记录便签什么时候被创建)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建时间戳
|
|
|
|
|
*/
|
|
|
|
|
private long mCreatedDate;
|
|
|
|
|
// 是否有附件(0=没有附件,1=有附件)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 附件标记(0=无附件,1=有附件)
|
|
|
|
|
*/
|
|
|
|
|
private int mHasAttachment;
|
|
|
|
|
// 最后修改时间(存的是时间戳,记录便签最后一次被修改的时间)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 最后修改时间戳
|
|
|
|
|
*/
|
|
|
|
|
private long mModifiedDate;
|
|
|
|
|
// 所属文件夹ID(标记这个便签在哪个文件夹里)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 父文件夹ID
|
|
|
|
|
*/
|
|
|
|
|
private long mParentId;
|
|
|
|
|
// 便签摘要(一般是便签的标题,或者正文的前几句)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 便签摘要(用于列表显示)
|
|
|
|
|
*/
|
|
|
|
|
private String mSnippet;
|
|
|
|
|
// 便签类型(普通便签/文件夹/系统文件夹)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 便签类型(TYPE_NOTE/TYPE_FOLDER/TYPE_SYSTEM)
|
|
|
|
|
*/
|
|
|
|
|
private int mType;
|
|
|
|
|
// 桌面小组件ID(如果这个便签添加到桌面,这里存小组件的编号)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 桌面Widget ID
|
|
|
|
|
*/
|
|
|
|
|
private int mWidgetId;
|
|
|
|
|
// 桌面小组件类型(标记桌面小组件的样式)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 桌面Widget类型
|
|
|
|
|
*/
|
|
|
|
|
private int mWidgetType;
|
|
|
|
|
// 原始所属文件夹ID(记录便签最初在哪个文件夹里)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 原始父文件夹ID(用于回收站恢复)
|
|
|
|
|
*/
|
|
|
|
|
private long mOriginParent;
|
|
|
|
|
// 便签版本号(每次修改都会递增,用来避免同步时多人同时修改导致冲突)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 数据版本号(用于乐观锁控制)
|
|
|
|
|
*/
|
|
|
|
|
private long mVersion;
|
|
|
|
|
// 便签主表的变更记录:只存和原来不一样的内容,后续只更新这些变更,提高效率
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 便签主表差异数据值容器
|
|
|
|
|
*/
|
|
|
|
|
private ContentValues mDiffNoteValues;
|
|
|
|
|
// 便签的明细数据列表(比如普通便签的正文内容,用SqlData对象存储)
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 便签详细数据列表(data表记录)
|
|
|
|
|
*/
|
|
|
|
|
private ArrayList<SqlData> mDataList;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构造方法:创建一条新的便签(还没存到数据库)
|
|
|
|
|
* @param context 上下文(用来获取数据库操作工具、默认背景色等)
|
|
|
|
|
* 构造函数:创建新便签实例
|
|
|
|
|
* <p>
|
|
|
|
|
* 业务逻辑:初始化一个未保存到数据库的便签对象,
|
|
|
|
|
* 设置默认属性值,包括默认背景色、当前时间戳等。
|
|
|
|
|
*
|
|
|
|
|
* @param context 应用上下文
|
|
|
|
|
*/
|
|
|
|
|
public SqlNote(Context context) {
|
|
|
|
|
mContext = context;
|
|
|
|
|
// 获取数据库操作工具
|
|
|
|
|
mContentResolver = context.getContentResolver();
|
|
|
|
|
// 标记为新便签(还没存数据库)
|
|
|
|
|
|
|
|
|
|
// 初始化默认值
|
|
|
|
|
mIsCreate = true;
|
|
|
|
|
// 初始化为无效ID(还没有数据库分配的唯一ID)
|
|
|
|
|
mId = INVALID_ID;
|
|
|
|
|
// 初始提醒时间为0(没有设置提醒)
|
|
|
|
|
mAlertDate = 0;
|
|
|
|
|
// 初始背景色为系统默认背景色
|
|
|
|
|
mBgColorId = ResourceParser.getDefaultBgId(context);
|
|
|
|
|
// 初始创建时间为当前时间(获取系统当前时间戳)
|
|
|
|
|
mCreatedDate = System.currentTimeMillis();
|
|
|
|
|
// 初始没有附件
|
|
|
|
|
mHasAttachment = 0;
|
|
|
|
|
// 初始最后修改时间为当前时间
|
|
|
|
|
mModifiedDate = System.currentTimeMillis();
|
|
|
|
|
// 初始所属文件夹ID为0(默认在根目录)
|
|
|
|
|
mParentId = 0;
|
|
|
|
|
// 初始摘要为空字符串
|
|
|
|
|
mSnippet = "";
|
|
|
|
|
// 初始类型为普通便签
|
|
|
|
|
mType = Notes.TYPE_NOTE;
|
|
|
|
|
// 初始桌面小组件ID为无效ID(没有添加到桌面)
|
|
|
|
|
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
|
|
|
|
|
// 初始桌面小组件类型为无效类型
|
|
|
|
|
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
|
|
|
|
|
// 初始原始所属文件夹ID为0
|
|
|
|
|
mOriginParent = 0;
|
|
|
|
|
// 初始版本号为0
|
|
|
|
|
mVersion = 0;
|
|
|
|
|
// 初始化便签主表的变更记录容器
|
|
|
|
|
|
|
|
|
|
mDiffNoteValues = new ContentValues();
|
|
|
|
|
// 初始化明细数据列表
|
|
|
|
|
mDataList = new ArrayList<SqlData>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构造方法:从数据库查询结果里加载已有便签
|
|
|
|
|
* @param context 上下文(用来获取数据库操作工具)
|
|
|
|
|
* @param c 数据库查询结果(里面存着已有便签的基本信息)
|
|
|
|
|
* 构造函数:从数据库游标加载便签实例
|
|
|
|
|
* <p>
|
|
|
|
|
* 业务逻辑:从已有数据库记录创建对象,自动加载便签详细数据。
|
|
|
|
|
*
|
|
|
|
|
* @param context 应用上下文
|
|
|
|
|
* @param c 数据库查询游标,指向note表的记录
|
|
|
|
|
*/
|
|
|
|
|
public SqlNote(Context context, Cursor c) {
|
|
|
|
|
mContext = context;
|
|
|
|
|
// 获取数据库操作工具
|
|
|
|
|
mContentResolver = context.getContentResolver();
|
|
|
|
|
// 标记为已有便签(不是新创建的)
|
|
|
|
|
mIsCreate = false;
|
|
|
|
|
// 从查询结果里加载便签基本信息
|
|
|
|
|
loadFromCursor(c);
|
|
|
|
|
// 初始化明细数据列表
|
|
|
|
|
|
|
|
|
|
mDataList = new ArrayList<SqlData>();
|
|
|
|
|
// 如果是普通便签,加载对应的明细内容(比如正文)
|
|
|
|
|
if (mType == Notes.TYPE_NOTE)
|
|
|
|
|
loadDataContent();
|
|
|
|
|
// 初始化便签主表的变更记录容器
|
|
|
|
|
mDiffNoteValues = new ContentValues();
|
|
|
|
|
|
|
|
|
|
// 加载基础数据
|
|
|
|
|
loadFromCursor(c);
|
|
|
|
|
|
|
|
|
|
// 如果是普通便签,加载详细数据内容
|
|
|
|
|
if (mType == Notes.TYPE_NOTE) {
|
|
|
|
|
loadDataContent();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构造方法:通过便签ID加载数据库里已有的便签
|
|
|
|
|
* @param context 上下文(用来获取数据库操作工具)
|
|
|
|
|
* @param id 要加载的便签ID
|
|
|
|
|
* 构造函数:通过便签ID加载便签实例
|
|
|
|
|
* <p>
|
|
|
|
|
* 业务逻辑:根据便签ID查询数据库,创建便签对象。
|
|
|
|
|
* 用于通过ID直接获取便签数据的场景。
|
|
|
|
|
*
|
|
|
|
|
* @param context 应用上下文
|
|
|
|
|
* @param id 便签ID
|
|
|
|
|
*/
|
|
|
|
|
public SqlNote(Context context, long id) {
|
|
|
|
|
mContext = context;
|
|
|
|
|
// 获取数据库操作工具
|
|
|
|
|
mContentResolver = context.getContentResolver();
|
|
|
|
|
// 标记为已有便签(不是新创建的)
|
|
|
|
|
mIsCreate = false;
|
|
|
|
|
// 通过便签ID查询数据库,再加载便签基本信息
|
|
|
|
|
loadFromCursor(id);
|
|
|
|
|
// 初始化明细数据列表
|
|
|
|
|
|
|
|
|
|
mDataList = new ArrayList<SqlData>();
|
|
|
|
|
// 如果是普通便签,加载对应的明细内容(比如正文)
|
|
|
|
|
if (mType == Notes.TYPE_NOTE)
|
|
|
|
|
loadDataContent();
|
|
|
|
|
// 初始化便签主表的变更记录容器
|
|
|
|
|
mDiffNoteValues = new ContentValues();
|
|
|
|
|
|
|
|
|
|
// 通过ID加载便签
|
|
|
|
|
loadFromCursor(id);
|
|
|
|
|
|
|
|
|
|
// 如果是普通便签,加载详细数据内容
|
|
|
|
|
if (mType == Notes.TYPE_NOTE) {
|
|
|
|
|
loadDataContent();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 通过便签ID查询数据库,再加载便签基本信息
|
|
|
|
|
* @param id 要查询的便签ID
|
|
|
|
|
* 通过便签ID从数据库加载数据
|
|
|
|
|
* <p>
|
|
|
|
|
* DB操作:根据便签ID查询数据库,并调用loadFromCursor(Cursor)加载数据。
|
|
|
|
|
* 设计说明:封装数据库查询逻辑,提供统一的数据加载入口。
|
|
|
|
|
*
|
|
|
|
|
* @param id 便签ID
|
|
|
|
|
*/
|
|
|
|
|
private void loadFromCursor(long id) {
|
|
|
|
|
Cursor c = null; // 数据库查询结果容器
|
|
|
|
|
Cursor c = null;
|
|
|
|
|
try {
|
|
|
|
|
// 根据便签ID查询便签主表,获取该便签的基本信息
|
|
|
|
|
// DB操作:根据ID查询便签
|
|
|
|
|
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
|
|
|
|
|
new String[] { String.valueOf(id) }, null);
|
|
|
|
|
if (c != null) {
|
|
|
|
|
// 移动到查询结果的第一条(因为ID是唯一的,只有一条结果)
|
|
|
|
|
c.moveToNext();
|
|
|
|
|
// 从查询结果里加载数据到当前对象
|
|
|
|
|
loadFromCursor(c);
|
|
|
|
|
if (c.moveToNext()) {
|
|
|
|
|
loadFromCursor(c);
|
|
|
|
|
} else {
|
|
|
|
|
Log.w(TAG, "loadFromCursor: cursor = null");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 查询结果为空,打印警告日志
|
|
|
|
|
Log.w(TAG, "loadFromCursor: cursor = null");
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
// 不管查询成功与否,最后都关闭查询结果,避免占用资源
|
|
|
|
|
if (c != null)
|
|
|
|
|
if (c != null) {
|
|
|
|
|
c.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 从数据库查询结果里加载便签基本信息
|
|
|
|
|
* 通俗说:把查询结果里的各项便签信息取出来,赋值给当前对象的属性
|
|
|
|
|
* @param c 数据库查询结果
|
|
|
|
|
* 从数据库游标加载便签字段
|
|
|
|
|
* <p>
|
|
|
|
|
* DB操作:从Cursor对象提取note表的字段值,
|
|
|
|
|
* 填充到当前对象的成员变量中。
|
|
|
|
|
*
|
|
|
|
|
* @param c 数据库查询游标,必须包含PROJECTION_NOTE定义的列
|
|
|
|
|
*/
|
|
|
|
|
private void loadFromCursor(Cursor c) {
|
|
|
|
|
mId = c.getLong(ID_COLUMN); // 取便签ID
|
|
|
|
|
mAlertDate = c.getLong(ALERTED_DATE_COLUMN); // 取提醒时间
|
|
|
|
|
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); // 取背景颜色ID
|
|
|
|
|
mCreatedDate = c.getLong(CREATED_DATE_COLUMN); // 取创建时间
|
|
|
|
|
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN); // 取是否有附件
|
|
|
|
|
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN); // 取最后修改时间
|
|
|
|
|
mParentId = c.getLong(PARENT_ID_COLUMN); // 取所属文件夹ID
|
|
|
|
|
mSnippet = c.getString(SNIPPET_COLUMN); // 取便签摘要
|
|
|
|
|
mType = c.getInt(TYPE_COLUMN); // 取便签类型
|
|
|
|
|
mWidgetId = c.getInt(WIDGET_ID_COLUMN); // 取桌面小组件ID
|
|
|
|
|
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); // 取桌面小组件类型
|
|
|
|
|
mVersion = c.getLong(VERSION_COLUMN); // 取便签版本号
|
|
|
|
|
mId = c.getLong(ID_COLUMN);
|
|
|
|
|
mAlertDate = c.getLong(ALERTED_DATE_COLUMN);
|
|
|
|
|
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN);
|
|
|
|
|
mCreatedDate = c.getLong(CREATED_DATE_COLUMN);
|
|
|
|
|
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN);
|
|
|
|
|
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN);
|
|
|
|
|
mParentId = c.getLong(PARENT_ID_COLUMN);
|
|
|
|
|
mSnippet = c.getString(SNIPPET_COLUMN);
|
|
|
|
|
mType = c.getInt(TYPE_COLUMN);
|
|
|
|
|
mWidgetId = c.getInt(WIDGET_ID_COLUMN);
|
|
|
|
|
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
|
|
|
|
|
mVersion = c.getLong(VERSION_COLUMN);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 加载当前便签关联的明细内容(比如普通便签的正文)
|
|
|
|
|
* 通俗说:根据便签ID查询数据明细表,把关联的明细数据加载到mDataList里
|
|
|
|
|
* 加载便签详细数据内容
|
|
|
|
|
* <p>
|
|
|
|
|
* DB操作:查询data表,加载与当前便签关联的所有详细数据。
|
|
|
|
|
* 架构说明:便签内容采用主从表设计,note表存储元数据,data表存储详细内容。
|
|
|
|
|
*/
|
|
|
|
|
private void loadDataContent() {
|
|
|
|
|
Cursor c = null; // 数据库查询结果容器
|
|
|
|
|
// 先清空现有的明细数据列表
|
|
|
|
|
Cursor c = null;
|
|
|
|
|
mDataList.clear();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// 根据便签ID查询数据明细表,获取该便签的明细内容
|
|
|
|
|
// DB操作:查询关联的详细数据
|
|
|
|
|
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
|
|
|
|
|
"(note_id=?)", new String[] { String.valueOf(mId) }, null);
|
|
|
|
|
if (c != null) {
|
|
|
|
|
// 如果没有查询到明细数据,打印警告日志并返回
|
|
|
|
|
if (c.getCount() == 0) {
|
|
|
|
|
Log.w(TAG, "it seems that the note has not data");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 遍历所有明细数据,逐个加载到mDataList
|
|
|
|
|
while (c.moveToNext()) {
|
|
|
|
|
SqlData data = new SqlData(mContext, c); // 从查询结果创建明细数据对象
|
|
|
|
|
mDataList.add(data); // 添加到明细数据列表
|
|
|
|
|
SqlData data = new SqlData(mContext, c);
|
|
|
|
|
mDataList.add(data);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 查询结果为空,打印警告日志
|
|
|
|
|
Log.w(TAG, "loadDataContent: cursor = null");
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
// 不管查询成功与否,最后都关闭查询结果,避免占用资源
|
|
|
|
|
if (c != null)
|
|
|
|
|
if (c != null) {
|
|
|
|
|
c.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 从JSON数据包里设置便签内容,并记录变更
|
|
|
|
|
* 通俗说:把JSON里的便签信息解析出来,更新当前对象的属性,
|
|
|
|
|
* 有变更的内容就记录到变更容器里,同时处理明细数据(正文)
|
|
|
|
|
* @param js 包含便签信息的JSON数据包
|
|
|
|
|
* @return true=设置成功,false=设置失败(JSON解析出错)
|
|
|
|
|
* 从JSON对象设置便签内容
|
|
|
|
|
* <p>
|
|
|
|
|
* 架构说明:将JSON格式的数据转换为本地对象,
|
|
|
|
|
* 根据便签类型(普通便签/文件夹/系统文件夹)进行差异化处理。
|
|
|
|
|
* 业务逻辑:支持GTask同步和本地数据导入场景。
|
|
|
|
|
*
|
|
|
|
|
* @param js 包含便签数据的JSON对象
|
|
|
|
|
* @return true表示设置成功,false表示设置失败
|
|
|
|
|
*/
|
|
|
|
|
public boolean setContent(JSONObject js) {
|
|
|
|
|
try {
|
|
|
|
|
// 从JSON里取出便签基本信息的数据包
|
|
|
|
|
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
|
|
|
|
|
|
|
|
|
|
// 如果是系统文件夹,不允许修改,打印警告日志
|
|
|
|
|
// Case 1: 系统文件夹 - 禁止修改
|
|
|
|
|
if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
|
|
|
|
|
Log.w(TAG, "cannot set system folder");
|
|
|
|
|
}
|
|
|
|
|
// 如果是普通文件夹,只能更新摘要和类型
|
|
|
|
|
// Case 2: 文件夹 - 只能更新摘要和类型
|
|
|
|
|
else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
|
|
|
|
|
// 从JSON里取文件夹摘要,没有的话为空字符串
|
|
|
|
|
String snippet = note.has(NoteColumns.SNIPPET) ? note
|
|
|
|
|
.getString(NoteColumns.SNIPPET) : "";
|
|
|
|
|
// 如果是新文件夹,或者摘要有变化,记录变更
|
|
|
|
|
String snippet = note.has(NoteColumns.SNIPPET) ?
|
|
|
|
|
note.getString(NoteColumns.SNIPPET) : "";
|
|
|
|
|
if (mIsCreate || !mSnippet.equals(snippet)) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
|
|
|
|
|
}
|
|
|
|
|
// 更新当前文件夹摘要
|
|
|
|
|
mSnippet = snippet;
|
|
|
|
|
|
|
|
|
|
// 从JSON里取文件夹类型,没有的话默认是普通便签
|
|
|
|
|
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
|
|
|
|
|
: Notes.TYPE_NOTE;
|
|
|
|
|
// 如果是新文件夹,或者类型有变化,记录变更
|
|
|
|
|
int type = note.has(NoteColumns.TYPE) ?
|
|
|
|
|
note.getInt(NoteColumns.TYPE) : Notes.TYPE_NOTE;
|
|
|
|
|
if (mIsCreate || mType != type) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.TYPE, type);
|
|
|
|
|
}
|
|
|
|
|
// 更新当前文件夹类型
|
|
|
|
|
mType = type;
|
|
|
|
|
}
|
|
|
|
|
// 如果是普通便签,更新所有基本信息和明细内容
|
|
|
|
|
// Case 3: 普通便签 - 更新所有字段和详细数据
|
|
|
|
|
else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) {
|
|
|
|
|
// 取出明细数据的JSON数组(比如正文内容)
|
|
|
|
|
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
|
|
|
|
|
|
|
|
|
|
// 处理便签ID
|
|
|
|
|
@ -344,149 +409,149 @@ public class SqlNote {
|
|
|
|
|
}
|
|
|
|
|
mId = id;
|
|
|
|
|
|
|
|
|
|
// 处理提醒时间
|
|
|
|
|
long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note
|
|
|
|
|
.getLong(NoteColumns.ALERTED_DATE) : 0;
|
|
|
|
|
// 处理提醒日期
|
|
|
|
|
long alertDate = note.has(NoteColumns.ALERTED_DATE) ?
|
|
|
|
|
note.getLong(NoteColumns.ALERTED_DATE) : 0;
|
|
|
|
|
if (mIsCreate || mAlertDate != alertDate) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate);
|
|
|
|
|
}
|
|
|
|
|
mAlertDate = alertDate;
|
|
|
|
|
|
|
|
|
|
// 处理背景颜色ID
|
|
|
|
|
int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note
|
|
|
|
|
.getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext);
|
|
|
|
|
int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ?
|
|
|
|
|
note.getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext);
|
|
|
|
|
if (mIsCreate || mBgColorId != bgColorId) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId);
|
|
|
|
|
}
|
|
|
|
|
mBgColorId = bgColorId;
|
|
|
|
|
|
|
|
|
|
// 处理创建时间
|
|
|
|
|
long createDate = note.has(NoteColumns.CREATED_DATE) ? note
|
|
|
|
|
.getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis();
|
|
|
|
|
// 处理创建日期
|
|
|
|
|
long createDate = note.has(NoteColumns.CREATED_DATE) ?
|
|
|
|
|
note.getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis();
|
|
|
|
|
if (mIsCreate || mCreatedDate != createDate) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate);
|
|
|
|
|
}
|
|
|
|
|
mCreatedDate = createDate;
|
|
|
|
|
|
|
|
|
|
// 处理是否有附件
|
|
|
|
|
int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note
|
|
|
|
|
.getInt(NoteColumns.HAS_ATTACHMENT) : 0;
|
|
|
|
|
// 处理附件标记
|
|
|
|
|
int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ?
|
|
|
|
|
note.getInt(NoteColumns.HAS_ATTACHMENT) : 0;
|
|
|
|
|
if (mIsCreate || mHasAttachment != hasAttachment) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment);
|
|
|
|
|
}
|
|
|
|
|
mHasAttachment = hasAttachment;
|
|
|
|
|
|
|
|
|
|
// 处理最后修改时间
|
|
|
|
|
long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note
|
|
|
|
|
.getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis();
|
|
|
|
|
// 处理修改日期
|
|
|
|
|
long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ?
|
|
|
|
|
note.getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis();
|
|
|
|
|
if (mIsCreate || mModifiedDate != modifiedDate) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate);
|
|
|
|
|
}
|
|
|
|
|
mModifiedDate = modifiedDate;
|
|
|
|
|
|
|
|
|
|
// 处理所属文件夹ID
|
|
|
|
|
long parentId = note.has(NoteColumns.PARENT_ID) ? note
|
|
|
|
|
.getLong(NoteColumns.PARENT_ID) : 0;
|
|
|
|
|
// 处理父文件夹ID
|
|
|
|
|
long parentId = note.has(NoteColumns.PARENT_ID) ?
|
|
|
|
|
note.getLong(NoteColumns.PARENT_ID) : 0;
|
|
|
|
|
if (mIsCreate || mParentId != parentId) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId);
|
|
|
|
|
}
|
|
|
|
|
mParentId = parentId;
|
|
|
|
|
|
|
|
|
|
// 处理便签摘要
|
|
|
|
|
String snippet = note.has(NoteColumns.SNIPPET) ? note
|
|
|
|
|
.getString(NoteColumns.SNIPPET) : "";
|
|
|
|
|
String snippet = note.has(NoteColumns.SNIPPET) ?
|
|
|
|
|
note.getString(NoteColumns.SNIPPET) : "";
|
|
|
|
|
if (mIsCreate || !mSnippet.equals(snippet)) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
|
|
|
|
|
}
|
|
|
|
|
mSnippet = snippet;
|
|
|
|
|
|
|
|
|
|
// 处理便签类型
|
|
|
|
|
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
|
|
|
|
|
: Notes.TYPE_NOTE;
|
|
|
|
|
int type = note.has(NoteColumns.TYPE) ?
|
|
|
|
|
note.getInt(NoteColumns.TYPE) : Notes.TYPE_NOTE;
|
|
|
|
|
if (mIsCreate || mType != type) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.TYPE, type);
|
|
|
|
|
}
|
|
|
|
|
mType = type;
|
|
|
|
|
|
|
|
|
|
// 处理桌面小组件ID
|
|
|
|
|
int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID)
|
|
|
|
|
: AppWidgetManager.INVALID_APPWIDGET_ID;
|
|
|
|
|
// 处理Widget ID
|
|
|
|
|
int widgetId = note.has(NoteColumns.WIDGET_ID) ?
|
|
|
|
|
note.getInt(NoteColumns.WIDGET_ID) : AppWidgetManager.INVALID_APPWIDGET_ID;
|
|
|
|
|
if (mIsCreate || mWidgetId != widgetId) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId);
|
|
|
|
|
}
|
|
|
|
|
mWidgetId = widgetId;
|
|
|
|
|
|
|
|
|
|
// 处理桌面小组件类型
|
|
|
|
|
int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note
|
|
|
|
|
.getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE;
|
|
|
|
|
// 处理Widget类型
|
|
|
|
|
int widgetType = note.has(NoteColumns.WIDGET_TYPE) ?
|
|
|
|
|
note.getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE;
|
|
|
|
|
if (mIsCreate || mWidgetType != widgetType) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType);
|
|
|
|
|
}
|
|
|
|
|
mWidgetType = widgetType;
|
|
|
|
|
|
|
|
|
|
// 处理原始所属文件夹ID
|
|
|
|
|
long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note
|
|
|
|
|
.getLong(NoteColumns.ORIGIN_PARENT_ID) : 0;
|
|
|
|
|
// 处理原始父文件夹ID
|
|
|
|
|
long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ?
|
|
|
|
|
note.getLong(NoteColumns.ORIGIN_PARENT_ID) : 0;
|
|
|
|
|
if (mIsCreate || mOriginParent != originParent) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent);
|
|
|
|
|
}
|
|
|
|
|
mOriginParent = originParent;
|
|
|
|
|
|
|
|
|
|
// 处理明细数据(比如正文内容)
|
|
|
|
|
// 处理详细数据
|
|
|
|
|
for (int i = 0; i < dataArray.length(); i++) {
|
|
|
|
|
JSONObject data = dataArray.getJSONObject(i);
|
|
|
|
|
SqlData sqlData = null;
|
|
|
|
|
|
|
|
|
|
// 如果明细数据有ID,先在现有列表里找对应的明细对象
|
|
|
|
|
// 查找已存在的详细数据
|
|
|
|
|
if (data.has(DataColumns.ID)) {
|
|
|
|
|
long dataId = data.getLong(DataColumns.ID);
|
|
|
|
|
for (SqlData temp : mDataList) {
|
|
|
|
|
if (dataId == temp.getId()) {
|
|
|
|
|
sqlData = temp;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果没找到对应的明细对象,创建新的并加入列表
|
|
|
|
|
// 如果未找到,创建新的详细数据
|
|
|
|
|
if (sqlData == null) {
|
|
|
|
|
sqlData = new SqlData(mContext);
|
|
|
|
|
mDataList.add(sqlData);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 给明细对象设置内容,并记录变更
|
|
|
|
|
// 设置详细数据内容
|
|
|
|
|
sqlData.setContent(data);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (JSONException e) {
|
|
|
|
|
// JSON解析出错,打印错误日志并返回false
|
|
|
|
|
Log.e(TAG, e.toString());
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// 设置成功,返回true
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 把当前便签内容打包成JSON格式
|
|
|
|
|
* 通俗说:把便签的基本信息和明细内容整理成JSON数据包,方便后续同步或存储
|
|
|
|
|
* @return 包含便签信息的JSON数据包(新便签/打包失败返回null)
|
|
|
|
|
* 将便签内容转换为JSON对象
|
|
|
|
|
* <p>
|
|
|
|
|
* 架构说明:将本地对象序列化为JSON格式,
|
|
|
|
|
* 用于数据导出、GTask同步或网络传输。
|
|
|
|
|
*
|
|
|
|
|
* @return 包含所有便签数据的JSON对象,失败返回null
|
|
|
|
|
*/
|
|
|
|
|
public JSONObject getContent() {
|
|
|
|
|
try {
|
|
|
|
|
// 创建空的JSON数据包
|
|
|
|
|
JSONObject js = new JSONObject();
|
|
|
|
|
|
|
|
|
|
// 如果是新便签(还没存到数据库),打印错误日志并返回null
|
|
|
|
|
// 安全检查:新建便签不能转换为JSON
|
|
|
|
|
if (mIsCreate) {
|
|
|
|
|
Log.e(TAG, "it seems that we haven't created this in database yet");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 创建存储便签基本信息的JSON对象
|
|
|
|
|
JSONObject note = new JSONObject();
|
|
|
|
|
|
|
|
|
|
// 如果是普通便签,打包所有基本信息和明细内容
|
|
|
|
|
// Case 1: 普通便签 - 包含所有字段和详细数据
|
|
|
|
|
if (mType == Notes.TYPE_NOTE) {
|
|
|
|
|
note.put(NoteColumns.ID, mId);
|
|
|
|
|
note.put(NoteColumns.ALERTED_DATE, mAlertDate);
|
|
|
|
|
@ -500,44 +565,41 @@ public class SqlNote {
|
|
|
|
|
note.put(NoteColumns.WIDGET_ID, mWidgetId);
|
|
|
|
|
note.put(NoteColumns.WIDGET_TYPE, mWidgetType);
|
|
|
|
|
note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent);
|
|
|
|
|
// 把便签基本信息存入总JSON
|
|
|
|
|
|
|
|
|
|
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
|
|
|
|
|
|
|
|
|
|
// 创建存储明细数据的JSON数组
|
|
|
|
|
JSONArray dataArray = new JSONArray();
|
|
|
|
|
for (SqlData sqlData : mDataList) {
|
|
|
|
|
// 把每个明细数据打包成JSON
|
|
|
|
|
JSONObject data = sqlData.getContent();
|
|
|
|
|
if (data != null) {
|
|
|
|
|
dataArray.put(data);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 把明细数据数组存入总JSON
|
|
|
|
|
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
|
|
|
|
|
}
|
|
|
|
|
// 如果是文件夹/系统文件夹,只打包ID、类型和摘要
|
|
|
|
|
// Case 2: 文件夹或系统文件夹 - 仅包含ID、类型和摘要
|
|
|
|
|
else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) {
|
|
|
|
|
note.put(NoteColumns.ID, mId);
|
|
|
|
|
note.put(NoteColumns.TYPE, mType);
|
|
|
|
|
note.put(NoteColumns.SNIPPET, mSnippet);
|
|
|
|
|
// 把文件夹信息存入总JSON
|
|
|
|
|
|
|
|
|
|
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 返回打包好的JSON
|
|
|
|
|
return js;
|
|
|
|
|
} catch (JSONException e) {
|
|
|
|
|
// JSON打包出错,打印错误日志
|
|
|
|
|
Log.e(TAG, e.toString());
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
// 打包失败,返回null
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置便签所属的文件夹ID,并记录变更
|
|
|
|
|
* @param id 文件夹ID
|
|
|
|
|
* 设置便签的父文件夹ID
|
|
|
|
|
* <p>
|
|
|
|
|
* 业务逻辑:修改便签的归属关系,同时记录变更用于后续提交。
|
|
|
|
|
*
|
|
|
|
|
* @param id 父文件夹ID
|
|
|
|
|
*/
|
|
|
|
|
public void setParentId(long id) {
|
|
|
|
|
mParentId = id;
|
|
|
|
|
@ -545,30 +607,39 @@ public class SqlNote {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置便签对应的云端GTask ID,并记录变更
|
|
|
|
|
* @param gid 云端GTask的唯一ID
|
|
|
|
|
* 设置Google Tasks关联ID
|
|
|
|
|
* <p>
|
|
|
|
|
* 架构说明:用于建立本地便签与云端Google Tasks任务的关联。
|
|
|
|
|
*
|
|
|
|
|
* @param gid Google Tasks任务ID
|
|
|
|
|
*/
|
|
|
|
|
public void setGtaskId(String gid) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 设置便签的同步编号,并记录变更
|
|
|
|
|
* @param syncId 同步编号
|
|
|
|
|
* 设置同步ID
|
|
|
|
|
* <p>
|
|
|
|
|
* 业务逻辑:用于标识便签在同步系统中的版本,支持增量同步。
|
|
|
|
|
*
|
|
|
|
|
* @param syncId 同步ID
|
|
|
|
|
*/
|
|
|
|
|
public void setSyncId(long syncId) {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 重置本地修改标记(标记为未修改),并记录变更
|
|
|
|
|
* 重置本地修改标记
|
|
|
|
|
* <p>
|
|
|
|
|
* 业务逻辑:在同步完成后调用,将便签标记为"已同步"状态。
|
|
|
|
|
*/
|
|
|
|
|
public void resetLocalModified() {
|
|
|
|
|
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取便签的唯一ID
|
|
|
|
|
* 获取便签ID
|
|
|
|
|
*
|
|
|
|
|
* @return 便签ID
|
|
|
|
|
*/
|
|
|
|
|
public long getId() {
|
|
|
|
|
@ -576,15 +647,17 @@ public class SqlNote {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取便签所属的文件夹ID
|
|
|
|
|
* @return 文件夹ID
|
|
|
|
|
* 获取父文件夹ID
|
|
|
|
|
*
|
|
|
|
|
* @return 父文件夹ID
|
|
|
|
|
*/
|
|
|
|
|
public long getParentId() {
|
|
|
|
|
return mParentId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取便签的摘要
|
|
|
|
|
* 获取便签摘要
|
|
|
|
|
*
|
|
|
|
|
* @return 便签摘要
|
|
|
|
|
*/
|
|
|
|
|
public String getSnippet() {
|
|
|
|
|
@ -592,82 +665,91 @@ public class SqlNote {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 判断当前是否是普通便签类型
|
|
|
|
|
* @return true=普通便签,false=文件夹/系统文件夹
|
|
|
|
|
* 判断是否为普通便签类型
|
|
|
|
|
*
|
|
|
|
|
* @return true表示普通便签,false表示文件夹或系统文件夹
|
|
|
|
|
*/
|
|
|
|
|
public boolean isNoteType() {
|
|
|
|
|
return mType == Notes.TYPE_NOTE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 把便签的变更提交到数据库
|
|
|
|
|
* 通俗说:新便签就插入到主表,再插入明细数据;已有便签就更新变更的部分,
|
|
|
|
|
* 之后刷新便签信息,清空变更记录
|
|
|
|
|
* @param validateVersion 是否验证便签版本(避免同步时多人同时修改导致冲突)
|
|
|
|
|
* 提交便签到数据库
|
|
|
|
|
* <p>
|
|
|
|
|
* DB操作:根据便签状态执行插入或更新操作,
|
|
|
|
|
* 支持版本验证,确保数据一致性。
|
|
|
|
|
* 设计说明:采用差异更新策略,只修改有变化的字段。
|
|
|
|
|
*
|
|
|
|
|
* @param validateVersion 是否验证版本号(用于并发控制)
|
|
|
|
|
*/
|
|
|
|
|
public void commit(boolean validateVersion) {
|
|
|
|
|
// 如果是新便签(要插入数据库)
|
|
|
|
|
if (mIsCreate) {
|
|
|
|
|
// 如果便签ID是无效的,就把ID从变更记录里移除(数据库会自动生成唯一ID)
|
|
|
|
|
// Case A: 新建便签,执行插入操作
|
|
|
|
|
|
|
|
|
|
// 移除无效ID,让数据库自动生成
|
|
|
|
|
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
|
|
|
|
|
mDiffNoteValues.remove(NoteColumns.ID);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 把便签基本信息插入到便签主表,返回新便签的URI
|
|
|
|
|
// DB操作:插入新便签
|
|
|
|
|
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
|
|
|
|
|
try {
|
|
|
|
|
// 从返回的URI里提取数据库分配的唯一便签ID
|
|
|
|
|
// 从返回的URI中提取新生成的ID
|
|
|
|
|
mId = Long.valueOf(uri.getPathSegments().get(1));
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
// 提取ID失败,打印错误日志并抛出异常
|
|
|
|
|
Log.e(TAG, "Get note id error :" + e.toString());
|
|
|
|
|
throw new ActionFailureException("create note failed");
|
|
|
|
|
}
|
|
|
|
|
// 如果提取的ID为0,说明创建失败,抛出异常
|
|
|
|
|
|
|
|
|
|
// ID为0表示创建失败
|
|
|
|
|
if (mId == 0) {
|
|
|
|
|
throw new IllegalStateException("Create thread id failed");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果是普通便签,把明细数据也插入到数据明细表
|
|
|
|
|
// 如果是普通便签,提交详细数据
|
|
|
|
|
if (mType == Notes.TYPE_NOTE) {
|
|
|
|
|
for (SqlData sqlData : mDataList) {
|
|
|
|
|
sqlData.commit(mId, false, -1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 如果是已有便签(要更新数据库)
|
|
|
|
|
else {
|
|
|
|
|
// 验证便签ID是否有效(除了根文件夹和通话记录文件夹,其他ID不能<=0)
|
|
|
|
|
} else {
|
|
|
|
|
// Case B: 更新已有便签
|
|
|
|
|
|
|
|
|
|
// ID验证:除了特殊系统文件夹,ID必须大于0
|
|
|
|
|
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
|
|
|
|
|
Log.e(TAG, "No such note");
|
|
|
|
|
throw new IllegalStateException("Try to update note with invalid id");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果有变更内容,才执行更新操作
|
|
|
|
|
// 如果有字段变化才执行更新
|
|
|
|
|
if (mDiffNoteValues.size() > 0) {
|
|
|
|
|
// 版本号递增(每次修改都升级版本)
|
|
|
|
|
mVersion ++;
|
|
|
|
|
// 递增版本号
|
|
|
|
|
mVersion++;
|
|
|
|
|
int result = 0;
|
|
|
|
|
|
|
|
|
|
// 如果不需要验证版本,直接更新便签主表
|
|
|
|
|
if (!validateVersion) {
|
|
|
|
|
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
|
|
|
|
|
+ NoteColumns.ID + "=?)", new String[] { String.valueOf(mId) });
|
|
|
|
|
}
|
|
|
|
|
// 如果需要验证版本,只有版本匹配时才更新(避免同步冲突)
|
|
|
|
|
else {
|
|
|
|
|
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
|
|
|
|
|
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
|
|
|
|
|
// 无需版本验证的直接更新
|
|
|
|
|
result = mContentResolver.update(
|
|
|
|
|
Notes.CONTENT_NOTE_URI,
|
|
|
|
|
mDiffNoteValues,
|
|
|
|
|
"(" + NoteColumns.ID + "=?)",
|
|
|
|
|
new String[] { String.valueOf(mId) });
|
|
|
|
|
} else {
|
|
|
|
|
// 需要版本验证的更新(乐观锁控制)
|
|
|
|
|
result = mContentResolver.update(
|
|
|
|
|
Notes.CONTENT_NOTE_URI,
|
|
|
|
|
mDiffNoteValues,
|
|
|
|
|
"(" + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
|
|
|
|
|
new String[] { String.valueOf(mId), String.valueOf(mVersion) });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果更新结果为0,说明没有更新成功(可能版本不匹配或数据已被修改)
|
|
|
|
|
// 更新结果为0表示没有记录被更新
|
|
|
|
|
if (result == 0) {
|
|
|
|
|
Log.w(TAG, "there is no update. maybe user updates note when syncing");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果是普通便签,更新对应的明细数据
|
|
|
|
|
// 如果是普通便签,提交详细数据更新
|
|
|
|
|
if (mType == Notes.TYPE_NOTE) {
|
|
|
|
|
for (SqlData sqlData : mDataList) {
|
|
|
|
|
sqlData.commit(mId, validateVersion, mVersion);
|
|
|
|
|
@ -675,14 +757,14 @@ public class SqlNote {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 刷新便签信息(重新从数据库加载最新数据)
|
|
|
|
|
// 重新加载数据,确保内存状态与数据库一致
|
|
|
|
|
loadFromCursor(mId);
|
|
|
|
|
if (mType == Notes.TYPE_NOTE)
|
|
|
|
|
if (mType == Notes.TYPE_NOTE) {
|
|
|
|
|
loadDataContent();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 清空便签主表的变更记录(本次提交完成,下次变更重新记录)
|
|
|
|
|
// 重置状态
|
|
|
|
|
mDiffNoteValues.clear();
|
|
|
|
|
// 标记为非新便签(就算是刚插入的,现在也已经存到数据库了)
|
|
|
|
|
mIsCreate = false;
|
|
|
|
|
}
|
|
|
|
|
}
|