You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
git/src/notes/model/WorkingNote.java

653 lines
25 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.model;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
/**
* 笔记工作状态管理类,是笔记编辑过程中的核心数据模型
* 主要职责:
* 1. 管理笔记的各项属性ID、内容、文件夹ID、提醒时间、背景色、组件信息、修改时间等
* 2. 支持新建空白笔记和加载现有笔记两种场景的初始化;
* 3. 从本地数据库加载笔记的基本信息和数据内容(文本、通话记录);
* 4. 将编辑后的笔记保存到本地数据库,并处理组件更新逻辑;
* 5. 提供笔记属性的设置/获取方法,以及设置变化的回调监听器;
* 6. 处理笔记的删除标记、提醒时间、复选框模式、通话笔记转换等业务逻辑。
*
* 数据关联:
* - 关联{@link Note}类处理数据库的同步操作;
* - 关联本地ContentProviderNotes.CONTENT_NOTE_URI/Notes.CONTENT_DATA_URI获取/保存数据;
* - 关联{@link NoteBgResources}处理笔记背景色的资源映射。
*
* @author MiCode Open Source Community
* @date 2010-2011
*/
public class WorkingNote {
// ====================== 成员变量(笔记核心属性) ======================
/**
* 关联的Note实例用于处理笔记与数据库的同步操作增删改
*/
private Note mNote;
/**
* 笔记的唯一ID本地数据库主键0表示新笔记未保存到数据库
*/
private long mNoteId;
/**
* 笔记的文本内容(核心数据)
*/
private String mContent;
/**
* 笔记的模式(如普通文本模式、复选框列表模式,对应{@link TextNote#MODE}
*/
private int mMode;
/**
* 笔记的提醒时间时间戳0表示无提醒
*/
private long mAlertDate;
/**
* 笔记的最后修改时间(时间戳)
*/
private long mModifiedDate;
/**
* 笔记的背景色ID对应{@link NoteBgResources}中的颜色标识非资源ID
*/
private int mBgColorId;
/**
* 关联的桌面组件ID{@link AppWidgetManager#INVALID_APPWIDGET_ID}表示无关联组件)
*/
private int mWidgetId;
/**
* 关联的桌面组件类型({@link Notes#TYPE_WIDGET_INVALIDE}表示无效类型)
*/
private int mWidgetType;
/**
* 笔记所属的文件夹ID对应本地数据库中的文件夹主键
*/
private long mFolderId;
/**
* 上下文对象用于访问ContentResolver、资源等
*/
private Context mContext;
/**
* 笔记的删除标记true表示标记为删除false表示正常状态
*/
private boolean mIsDeleted;
/**
* 笔记设置变化的回调监听器,用于通知外部组件属性变化(背景色、提醒、组件、复选框模式)
*/
private NoteSettingChangedListener mNoteSettingStatusListener;
// ====================== 常量定义(数据库查询投影、日志标签) ======================
/**
* 日志标签,使用类名便于调试时定位日志来源
*/
private static final String TAG = "WorkingNote";
/**
* 笔记数据Data表的查询投影指定需要查询的列减少数据传输
* 对应列DataColumns.ID, CONTENT, MIME_TYPE, DATA1(MODE), DATA2, DATA3, DATA4
*/
public static final String[] DATA_PROJECTION = new String[] {
DataColumns.ID,
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1, // 存储笔记模式MODE
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
/**
* 笔记主表Note表的查询投影指定需要查询的列
* 对应列PARENT_ID(FOLDER_ID), ALERTED_DATE, BG_COLOR_ID, WIDGET_ID, WIDGET_TYPE, MODIFIED_DATE
*/
public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID, // 文件夹ID
NoteColumns.ALERTED_DATE, // 提醒时间
NoteColumns.BG_COLOR_ID, // 背景色ID
NoteColumns.WIDGET_ID, // 组件ID
NoteColumns.WIDGET_TYPE, // 组件类型
NoteColumns.MODIFIED_DATE // 修改时间
};
// ====================== 数据库查询列索引常量(简化代码,避免魔法数字) ======================
/**
* Data表查询结果中ID列的索引对应DATA_PROJECTION[0]
*/
private static final int DATA_ID_COLUMN = 0;
/**
* Data表查询结果中CONTENT列的索引对应DATA_PROJECTION[1]
*/
private static final int DATA_CONTENT_COLUMN = 1;
/**
* Data表查询结果中MIME_TYPE列的索引对应DATA_PROJECTION[2]
*/
private static final int DATA_MIME_TYPE_COLUMN = 2;
/**
* Data表查询结果中DATA1MODE列的索引对应DATA_PROJECTION[3]
*/
private static final int DATA_MODE_COLUMN = 3;
/**
* Note表查询结果中PARENT_ID文件夹ID列的索引对应NOTE_PROJECTION[0]
*/
private static final int NOTE_PARENT_ID_COLUMN = 0;
/**
* Note表查询结果中ALERTED_DATE提醒时间列的索引对应NOTE_PROJECTION[1]
*/
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
/**
* Note表查询结果中BG_COLOR_ID背景色ID列的索引对应NOTE_PROJECTION[2]
*/
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
/**
* Note表查询结果中WIDGET_ID组件ID列的索引对应NOTE_PROJECTION[3]
*/
private static final int NOTE_WIDGET_ID_COLUMN = 3;
/**
* Note表查询结果中WIDGET_TYPE组件类型列的索引对应NOTE_PROJECTION[4]
*/
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
/**
* Note表查询结果中MODIFIED_DATE修改时间列的索引对应NOTE_PROJECTION[5]
*/
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
// ====================== 私有构造方法(封装初始化逻辑,通过静态方法对外提供实例) ======================
/**
* 私有构造方法:初始化新笔记的默认属性(新建空白笔记时使用)
* @param context 上下文对象
* @param folderId 笔记所属的文件夹ID
*/
private WorkingNote(Context context, long folderId) {
mContext = context;
mAlertDate = 0; // 初始无提醒
mModifiedDate = System.currentTimeMillis(); // 初始修改时间为当前时间
mFolderId = folderId; // 设置所属文件夹ID
mNote = new Note(); // 初始化Note实例
mNoteId = 0; // 新笔记ID为0未保存到数据库
mIsDeleted = false; // 初始未删除
mMode = 0; // 初始模式为普通文本
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 初始无关联组件类型
}
/**
* 私有构造方法:初始化现有笔记的属性(加载数据库中已存在的笔记时使用)
* @param context 上下文对象
* @param noteId 现有笔记的ID数据库主键
* @param folderId 笔记所属的文件夹ID暂时传0后续从数据库加载时覆盖
*/
private WorkingNote(Context context, long noteId, long folderId) {
mContext = context;
mNoteId = noteId; // 设置现有笔记的ID
mFolderId = folderId; // 临时赋值,后续从数据库加载时更新
mIsDeleted = false; // 初始未删除
mNote = new Note(); // 初始化Note实例
loadNote(); // 从数据库加载笔记的基本信息和数据内容
}
// ====================== 核心私有方法(加载笔记数据) ======================
/**
* 从数据库加载笔记的基本信息Note表
* 逻辑:
* 1. 根据笔记ID查询Note表的指定列NOTE_PROJECTION
* 2. 若查询结果不为空赋值文件夹ID、背景色ID、组件信息、提醒时间、修改时间
* 3. 若查询结果为空抛出非法参数异常笔记ID不存在
* 4. 加载完成后,调用{@link #loadNoteData()}加载笔记的具体数据Data表
*/
private void loadNote() {
// 根据笔记ID构建Uri查询Note表
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
// 从查询结果中赋值各项属性
mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); // 文件夹ID
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); // 背景色ID
mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); // 组件ID
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); // 组件类型
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); // 提醒时间
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); // 修改时间
}
cursor.close(); // 关闭Cursor释放资源
} else {
// 笔记ID不存在打印日志并抛出异常
Log.e(TAG, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
}
// 加载笔记的具体数据(文本、通话记录)
loadNoteData();
}
/**
* 从数据库加载笔记的具体数据Data表
* 逻辑:
* 1. 根据笔记ID查询Data表的指定列DATA_PROJECTION
* 2. 遍历查询结果,区分数据类型(文本笔记、通话笔记):
* - 文本笔记DataConstants.NOTE赋值内容、模式设置文本数据ID
* - 通话笔记DataConstants.CALL_NOTE设置通话数据ID
* - 未知类型:打印日志;
* 3. 若查询结果为空,抛出非法参数异常(笔记数据不存在)。
*/
private void loadNoteData() {
// 根据笔记ID查询Data表NoteId=?
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
String.valueOf(mNoteId)
}, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
// 遍历所有数据行
do {
String type = cursor.getString(DATA_MIME_TYPE_COLUMN); // 获取数据类型
if (DataConstants.NOTE.equals(type)) {
// 文本笔记赋值内容、模式设置文本数据ID
mContent = cursor.getString(DATA_CONTENT_COLUMN);
mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
} else if (DataConstants.CALL_NOTE.equals(type)) {
// 通话笔记设置通话数据ID
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
} else {
// 未知类型,打印日志
Log.d(TAG, "Wrong note type with type:" + type);
}
} while (cursor.moveToNext());
}
cursor.close(); // 关闭Cursor释放资源
} else {
// 笔记数据不存在,打印日志并抛出异常
Log.e(TAG, "No data with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
}
}
// ====================== 公共静态方法(对外提供实例,工厂模式) ======================
/**
* 创建空白笔记的静态工厂方法(对外暴露,用于新建笔记)
* @param context 上下文对象
* @param folderId 笔记所属的文件夹ID
* @param widgetId 关联的组件IDINVALID_APPWIDGET_ID表示无
* @param widgetType 关联的组件类型TYPE_WIDGET_INVALIDE表示无
* @param defaultBgColorId 默认背景色ID
* @return WorkingNote实例初始化后的空白笔记
*/
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId);
note.setBgColorId(defaultBgColorId); // 设置默认背景色
note.setWidgetId(widgetId); // 设置组件ID
note.setWidgetType(widgetType); // 设置组件类型
return note;
}
/**
* 加载现有笔记的静态工厂方法(对外暴露,用于编辑现有笔记)
* @param context 上下文对象
* @param id 现有笔记的ID数据库主键
* @return WorkingNote实例加载后的现有笔记
*/
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
}
// ====================== 核心公共方法(保存笔记) ======================
/**
* 保存笔记到数据库(线程安全,同步方法)
* 逻辑:
* 1. 判断笔记是否值得保存({@link #isWorthSaving()}
* - 若不值得保存返回false
* - 若值得保存,继续执行;
* 2. 若笔记未保存到数据库ID=0创建新笔记ID
* 3. 调用Note的syncNote方法同步笔记到数据库
* 4. 若笔记关联组件,通知监听器更新组件;
* 5. 返回true表示保存成功。
* @return booleantrue=保存成功false=保存失败/无需保存
*/
public synchronized boolean saveNote() {
if (isWorthSaving()) {
// 笔记未保存到数据库创建新笔记ID
if (!existInDatabase()) {
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
}
}
// 同步笔记到数据库Note类处理具体的增删改操作
mNote.syncNote(mContext, mNoteId);
/**
* 若笔记关联组件,通知监听器更新组件内容
*/
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
return true;
} else {
// 笔记不值得保存返回false
return false;
}
}
// ====================== 辅助方法(判断笔记状态) ======================
/**
* 判断笔记是否已存在于数据库中ID>0表示已存在
* @return booleantrue=已存在false=未存在(新笔记)
*/
public boolean existInDatabase() {
return mNoteId > 0;
}
/**
* 判断笔记是否值得保存到数据库(过滤无意义的保存操作)
* 不保存的场景:
* 1. 标记为删除的笔记;
* 2. 新笔记且内容为空;
* 3. 现有笔记且未发生本地修改;
* @return booleantrue=值得保存false=不值得保存
*/
private boolean isWorthSaving() {
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) {
return false;
} else {
return true;
}
}
// ====================== 监听器设置方法 ======================
/**
* 设置笔记设置变化的回调监听器
* @param l 监听器实例(实现{@link NoteSettingChangedListener}接口)
*/
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l;
}
// ====================== 公共属性设置方法(包含变化回调) ======================
/**
* 设置笔记的提醒时间,并通知监听器
* @param date 提醒时间戳0表示取消提醒
* @param set 是否设置提醒true=设置false=取消)
*/
public void setAlertDate(long date, boolean set) {
if (date != mAlertDate) {
mAlertDate = date; // 更新提醒时间
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); // 同步到Note实例
}
// 通知监听器提醒时间变化
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onClockAlertChanged(date, set);
}
}
/**
* 标记笔记为删除/取消删除,并通知监听器更新组件
* @param mark true=标记为删除false=取消删除
*/
public void markDeleted(boolean mark) {
mIsDeleted = mark; // 更新删除标记
// 若笔记关联组件,通知监听器更新组件
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
}
/**
* 设置笔记的背景色ID并通知监听器
* @param id 背景色ID对应{@link NoteBgResources}中的标识)
*/
public void setBgColorId(int id) {
if (id != mBgColorId) {
mBgColorId = id; // 更新背景色ID
// 通知监听器背景色变化
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged();
}
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); // 同步到Note实例
}
}
/**
* 设置笔记的复选框模式,并通知监听器
* @param mode 新模式(如普通文本、复选框列表)
*/
public void setCheckListMode(int mode) {
if (mMode != mode) {
// 通知监听器复选框模式变化(传递旧模式和新模式)
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
}
mMode = mode; // 更新模式
mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); // 同步到Note实例
}
}
/**
* 设置笔记关联的组件类型
* @param type 组件类型({@link Notes#TYPE_WIDGET_INVALIDE}表示无效)
*/
public void setWidgetType(int type) {
if (type != mWidgetType) {
mWidgetType = type; // 更新组件类型
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); // 同步到Note实例
}
}
/**
* 设置笔记关联的组件ID
* @param id 组件ID{@link AppWidgetManager#INVALID_APPWIDGET_ID}表示无)
*/
public void setWidgetId(int id) {
if (id != mWidgetId) {
mWidgetId = id; // 更新组件ID
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); // 同步到Note实例
}
}
/**
* 设置笔记的文本内容并同步到Note实例
* @param text 新的文本内容
*/
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
mContent = text; // 更新内容
mNote.setTextData(DataColumns.CONTENT, mContent); // 同步到Note实例
}
}
/**
* 将笔记转换为通话笔记(设置通话记录相关数据,并移动到通话记录文件夹)
* @param phoneNumber 电话号码
* @param callDate 通话时间戳
*/
public void convertToCallNote(String phoneNumber, long callDate) {
// 设置通话记录数据(通话时间、电话号码)
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
// 将笔记移动到通话记录文件夹ID_CALL_RECORD_FOLDER
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
}
// ====================== 公共属性获取方法 ======================
/**
* 判断笔记是否有提醒(提醒时间>0表示有
* @return booleantrue=有提醒false=无提醒
*/
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
/**
* 获取笔记的文本内容
* @return String笔记内容
*/
public String getContent() {
return mContent;
}
/**
* 获取笔记的提醒时间戳
* @return long提醒时间0表示无
*/
public long getAlertDate() {
return mAlertDate;
}
/**
* 获取笔记的最后修改时间戳
* @return long修改时间
*/
public long getModifiedDate() {
return mModifiedDate;
}
/**
* 获取笔记背景色对应的资源ID通过{@link NoteBgResources}映射)
* @return int背景色资源ID
*/
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId);
}
/**
* 获取笔记的背景色ID对应{@link NoteBgResources}中的标识)
* @return int背景色ID
*/
public int getBgColorId() {
return mBgColorId;
}
/**
* 获取笔记标题栏的背景色资源ID通过{@link NoteBgResources}映射)
* @return int标题栏背景色资源ID
*/
public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
/**
* 获取笔记的复选框模式
* @return int模式普通文本/复选框列表)
*/
public int getCheckListMode() {
return mMode;
}
/**
* 获取笔记的ID数据库主键
* @return long笔记ID0表示新笔记
*/
public long getNoteId() {
return mNoteId;
}
/**
* 获取笔记所属的文件夹ID
* @return long文件夹ID
*/
public long getFolderId() {
return mFolderId;
}
/**
* 获取笔记关联的组件ID
* @return int组件IDINVALID_APPWIDGET_ID表示无
*/
public int getWidgetId() {
return mWidgetId;
}
/**
* 获取笔记关联的组件类型
* @return int组件类型TYPE_WIDGET_INVALIDE表示无
*/
public int getWidgetType() {
return mWidgetType;
}
// ====================== 回调接口(笔记设置变化通知) ======================
/**
* 笔记设置变化的回调接口用于通知外部组件如UI属性变化
* 包含背景色、提醒时间、组件、复选框模式的变化回调
*/
public interface NoteSettingChangedListener {
/**
* 笔记背景色变化时的回调
*/
void onBackgroundColorChanged();
/**
* 笔记提醒时间设置/取消时的回调
* @param date 新的提醒时间戳0表示取消
* @param set 是否设置提醒true=设置false=取消)
*/
void onClockAlertChanged(long date, boolean set);
/**
* 笔记关联的组件变化时的回调(如从组件创建笔记、删除笔记)
*/
void onWidgetChanged();
/**
* 笔记复选框模式切换时的回调(普通文本↔复选框列表)
* @param oldMode 切换前的旧模式
* @param newMode 切换后的新模式
*/
void onCheckListModeChanged(int oldMode, int newMode);
}
}