/* * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) * * 版权声明:本文件由MiCode开源社区开发,遵循Apache License, Version 2.0协议; * 您仅在遵守协议的前提下使用本文件,完整协议可通过以下链接获取: * http://www.apache.org/licenses/LICENSE-2.0 * 注:未书面明确要求时,本软件按"原样"提供,不附带任何明示或暗示的保证。 */ package net.micode.notes.model; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.OperationApplicationException; import android.net.Uri; import android.os.RemoteException; 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.NoteColumns; import net.micode.notes.data.Notes.TextNote; import java.util.ArrayList; /** * 笔记模型类 * 负责管理笔记的创建、数据修改(文本/通话记录)以及与数据库的同步操作 */ public class Note { // 存储笔记主表(note表)的差异值(待更新的字段) private ContentValues mNoteDiffValues; // 存储笔记关联数据(文本/通话记录等子表数据)的管理类实例 private NoteData mNoteData; private static final String TAG = "Note"; /** * 静态方法:生成新笔记ID并插入数据库 * @param context 上下文环境 * @param folderId 笔记所属文件夹ID * @return 新笔记的数据库ID(唯一标识) */ public static synchronized long getNewNoteId(Context context, long folderId) { // 创建新笔记的初始值:创建时间、修改时间、类型、本地修改标志、父文件夹ID ContentValues values = new ContentValues(); long createdTime = System.currentTimeMillis(); values.put(NoteColumns.CREATED_DATE, createdTime); // 设置创建时间 values.put(NoteColumns.MODIFIED_DATE, createdTime); // 初始修改时间与创建时间相同 values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 笔记类型为普通笔记 values.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记为本地已修改(待同步) values.put(NoteColumns.PARENT_ID, folderId); // 所属文件夹ID // 插入到note表,获取返回的Uri(包含新笔记ID) Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); long noteId = 0; try { // 从Uri路径中解析笔记ID(格式:content://.../notes/[noteId]) noteId = Long.valueOf(uri.getPathSegments().get(1)); } catch (NumberFormatException e) { Log.e(TAG, "获取笔记ID失败 :" + e.toString()); noteId = 0; } if (noteId == -1) { throw new IllegalStateException("错误的笔记ID:" + noteId); } return noteId; } /** * 构造方法:初始化笔记差异值和数据管理类 */ public Note() { mNoteDiffValues = new ContentValues(); // 初始化主表差异值容器 mNoteData = new NoteData(); // 初始化子表数据管理实例 } /** * 设置笔记主表的属性值(如标题、提醒时间等) * 每次调用会自动更新本地修改标志和修改时间 * @param key 笔记主表字段名(如NoteColumns.TITLE) * @param value 字段对应的值 */ public void setNoteValue(String key, String value) { mNoteDiffValues.put(key, value); // 标记本地修改(用于同步判断) mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 更新修改时间为当前系统时间 mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } /** * 设置文本类型子表数据(如笔记内容) * @param key 文本数据字段名(如TextNote.CONTENT) * @param value 字段对应的值 */ public void setTextData(String key, String value) { mNoteData.setTextData(key, value); } /** * 设置文本子表的ID(用于更新已有文本数据) * @param id 文本数据在data表中的ID(需大于0) */ public void setTextDataId(long id) { mNoteData.setTextDataId(id); } /** * 获取当前文本子表的ID * @return 文本数据在data表中的ID(0表示未创建) */ public long getTextDataId() { return mNoteData.mTextDataId; } /** * 设置通话记录子表的ID(用于更新已有通话数据) * @param id 通话数据在data表中的ID(需大于0) */ public void setCallDataId(long id) { mNoteData.setCallDataId(id); } /** * 设置通话记录类型子表数据(如通话号码、时长等) * @param key 通话数据字段名(如CallNote.NUMBER) * @param value 字段对应的值 */ public void setCallData(String key, String value) { mNoteData.setCallData(key, value); } /** * 判断笔记是否有本地修改(主表或子表数据被修改) * @return true-有修改;false-无修改 */ public boolean isLocalModified() { // 主表差异值非空 或 子表数据有修改时,标记为有本地修改 return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); } /** * 将笔记的修改同步到数据库(主表+子表) * @param context 上下文环境 * @param noteId 待同步的笔记ID * @return 同步成功-true;失败-false */ public boolean syncNote(Context context, long noteId) { if (noteId <= 0) { throw new IllegalArgumentException("错误的笔记ID:" + noteId); } // 无修改时直接返回成功 if (!isLocalModified()) { return true; } /** * 理论上,数据修改时应更新note表的LOCAL_MODIFIED和MODIFIED_DATE字段。 * 为数据安全,即使note表更新失败,仍尝试同步子表数据 */ // 更新note表的差异值(如标题、修改时间等) int updateCount = context.getContentResolver().update( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), // 目标笔记Uri mNoteDiffValues, // 待更新的字段 null, null ); if (updateCount == 0) { Log.e(TAG, "更新笔记主表失败(不应发生)"); // 不返回,继续处理子表同步 } mNoteDiffValues.clear(); // 清空已处理的差异值 // 子表数据有修改时,同步到data表 if (mNoteData.isLocalModified() && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { return false; // 子表同步失败时返回false } return true; } /** * 内部类:管理笔记的子表数据(文本/通话记录) */ private class NoteData { private long mTextDataId; // 文本数据在data表中的ID(0表示未创建) private ContentValues mTextDataValues; // 文本数据的差异值(待更新的字段) private long mCallDataId; // 通话数据在data表中的ID(0表示未创建) private ContentValues mCallDataValues; // 通话数据的差异值(待更新的字段) private static final String TAG = "NoteData"; /** * 构造方法:初始化子表数据容器 */ public NoteData() { mTextDataValues = new ContentValues(); mCallDataValues = new ContentValues(); mTextDataId = 0; mCallDataId = 0; } /** * 判断子表数据是否有本地修改 * @return true-有修改;false-无修改 */ boolean isLocalModified() { // 文本或通话数据差异值非空时,标记为有修改 return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; } /** * 设置文本数据ID(校验ID有效性) * @param id 文本数据在data表中的ID(需>0) */ void setTextDataId(long id) { if (id <= 0) { throw new IllegalArgumentException("文本数据ID必须大于0"); } mTextDataId = id; } /** * 设置通话数据ID(校验ID有效性) * @param id 通话数据在data表中的ID(需>0) */ void setCallDataId(long id) { if (id <= 0) { throw new IllegalArgumentException("通话数据ID必须大于0"); } mCallDataId = id; } /** * 设置通话数据字段值(自动触发笔记修改标志) * @param key 通话数据字段名(如CallNote.DURATION) * @param value 字段值 */ void setCallData(String key, String value) { mCallDataValues.put(key, value); // 同步更新主表的本地修改标志和修改时间 mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } /** * 设置文本数据字段值(自动触发笔记修改标志) * @param key 文本数据字段名(如TextNote.CONTENT) * @param value 字段值 */ void setTextData(String key, String value) { mTextDataValues.put(key, value); // 同步更新主表的本地修改标志和修改时间 mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } /** * 将子表数据(文本/通话)插入或更新到数据库 * @param context 上下文环境 * @param noteId 关联的笔记ID * @return 操作成功返回笔记Uri;失败返回null */ Uri pushIntoContentResolver(Context context, long noteId) { // 校验笔记ID有效性 if (noteId <= 0) { throw new IllegalArgumentException("错误的笔记ID:" + noteId); } // 批量操作容器(用于同时执行多个ContentProvider操作) ArrayList operationList = new ArrayList<>(); ContentProviderOperation.Builder builder = null; // 处理文本数据 if (mTextDataValues.size() > 0) { mTextDataValues.put(DataColumns.NOTE_ID, noteId); // 关联笔记ID if (mTextDataId == 0) { // ID为0表示新增文本数据 // 设置MIME类型为文本笔记 mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); // 插入到data表,获取新数据的Uri Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mTextDataValues); try { // 解析新数据的ID并保存 setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); } catch (NumberFormatException e) { Log.e(TAG, "插入新文本数据失败(笔记ID:" + noteId + ")"); mTextDataValues.clear(); // 清空无效数据 return null; } } else { // ID非0表示更新已有文本数据 // 构建更新操作(指定data表Uri和待更新值) builder = ContentProviderOperation.newUpdate( ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, mTextDataId)); builder.withValues(mTextDataValues); operationList.add(builder.build()); // 添加到批量操作列表 } mTextDataValues.clear(); // 清空已处理的差异值 } // 处理通话数据(逻辑与文本数据类似) if (mCallDataValues.size() > 0) { mCallDataValues.put(DataColumns.NOTE_ID, noteId); // 关联笔记ID if (mCallDataId == 0) { // ID为0表示新增通话数据 // 设置MIME类型为通话记录 mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); // 插入到data表,获取新数据的Uri Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mCallDataValues); try { // 解析新数据的ID并保存 setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); } catch (NumberFormatException e) { Log.e(TAG, "插入新通话数据失败(笔记ID:" + noteId + ")"); mCallDataValues.clear(); // 清空无效数据 return null; } } else { // ID非0表示更新已有通话数据 // 构建更新操作(指定data表Uri和待更新值) builder = ContentProviderOperation.newUpdate( ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, mCallDataId)); builder.withValues(mCallDataValues); operationList.add(builder.build()); // 添加到批量操作列表 } mCallDataValues.clear(); // 清空已处理的差异值 } // 执行批量操作(仅当有操作需要执行时) if (operationList.size() > 0) { try { ContentProviderResult[] results = context.getContentResolver().applyBatch( Notes.AUTHORITY, // 内容提供者的AUTHORITY operationList // 批量操作列表 ); // 操作成功时返回笔记Uri;否则返回null return (results == null || results.length == 0 || results[0] == null) ? null : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); } catch (RemoteException e) { Log.e(TAG, "远程调用异常: " + e.toString() + " - " + e.getMessage()); return null; } catch (OperationApplicationException e) { Log.e(TAG, "操作应用异常: " + e.toString() + " - " + e.getMessage()); return null; } } return null; } } }