/* * 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}类处理数据库的同步操作; * - 关联本地ContentProvider(Notes.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表查询结果中,DATA1(MODE)列的索引(对应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 关联的组件ID(INVALID_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 boolean:true=保存成功,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 boolean:true=已存在,false=未存在(新笔记) */ public boolean existInDatabase() { return mNoteId > 0; } /** * 判断笔记是否值得保存到数据库(过滤无意义的保存操作) * 不保存的场景: * 1. 标记为删除的笔记; * 2. 新笔记且内容为空; * 3. 现有笔记且未发生本地修改; * @return boolean:true=值得保存,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 boolean:true=有提醒,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:笔记ID(0表示新笔记) */ public long getNoteId() { return mNoteId; } /** * 获取笔记所属的文件夹ID * @return long:文件夹ID */ public long getFolderId() { return mFolderId; } /** * 获取笔记关联的组件ID * @return int:组件ID(INVALID_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); } }