/*
* 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.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;
/**
*
核心笔记数据模型类,负责管理笔记的基本信息和数据变更。
* 设计意图:将笔记的元数据变更和具体内容数据分离管理,提高数据操作的灵活性和效率。
*
* - 通过{@link #mNoteDiffValues}跟踪笔记元数据的变更
* - 通过内部类{@link NoteData}管理笔记的具体内容数据(文本、通话记录等)
* - 提供与ContentResolver的交互接口,实现数据持久化和同步
*
* 关键关联:
*
* - 与{@link net.micode.notes.data.NotesProvider}交互,实现数据的CRUD操作
* - 使用{@link android.content.ContentValues}存储数据变更
* - 通过{@link android.content.ContentProviderOperation}执行批量数据更新
*
*/
public class Note {
/**
* 存储笔记元数据的变更记录。
* 业务含义:记录笔记的基本信息变更,如标题、创建时间、修改时间等。
* 约束条件:仅包含需要更新到数据库的字段变更,不包含笔记的具体内容数据。
*/
private ContentValues mNoteDiffValues;
/**
* 管理笔记的具体内容数据。
* 业务含义:处理笔记的文本内容、通话记录等具体数据的存储和变更。
*/
private NoteData mNoteData;
/**
* 日志标签。
* 业务含义:用于在日志中标识Note类的相关操作。
*/
private static final String TAG = "Note";
/**
* 生成新的笔记ID,用于向数据库添加新笔记。
* 业务逻辑:在数据库中创建一条新的笔记记录,返回其生成的ID。
* 线程安全:方法使用synchronized关键字确保在多线程环境下的安全性。
*
* @param context 上下文对象,用于获取ContentResolver
* @param folderId 文件夹ID,新笔记将被添加到该文件夹下
* @return 生成的新笔记ID
* @throws IllegalStateException 当生成的笔记ID为-1时抛出
*
* 跨类调用:
*
* - 通过{@link Context#getContentResolver()}获取ContentResolver
* - 调用{@link android.content.ContentResolver#insert(Uri, ContentValues)}向{@link Notes#CONTENT_NOTE_URI}插入新笔记
*
*
* 错误处理:
*
* - 捕获NumberFormatException异常,记录日志并返回0
* - 当生成的笔记ID为-1时,抛出IllegalStateException异常
*
*/
public static synchronized long getNewNoteId(Context context, long folderId) {
// Create a new note in the database
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);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
long noteId = 0;
try {
noteId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
noteId = 0;
}
if (noteId == -1) {
throw new IllegalStateException("Wrong note id:" + noteId);
}
return noteId;
}
/**
* Note类的默认构造函数。
* 功能:初始化笔记的元数据变更记录和内容数据管理对象。
*
* - 初始化{@link #mNoteDiffValues}为新的ContentValues对象,用于跟踪元数据变更
* - 初始化{@link #mNoteData}为新的NoteData对象,用于管理具体内容数据
*
*/
public Note() {
mNoteDiffValues = new ContentValues();
mNoteData = new NoteData();
}
/**
* 设置笔记的元数据字段值。
* 业务逻辑:更新笔记的元数据变更记录,并标记为本地已修改。
*
* @param key 元数据字段名
* @param value 元数据字段值
*
* 自动更新的字段:
*
* - {@link NoteColumns#LOCAL_MODIFIED}:标记为1(已修改)
* - {@link NoteColumns#MODIFIED_DATE}:更新为当前时间戳
*
*/
public void setNoteValue(String key, String value) {
mNoteDiffValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
* 设置笔记的文本数据字段值。
* 业务逻辑:通过{@link NoteData}对象更新笔记的文本内容数据。
*
* @param key 文本数据字段名
* @param value 文本数据字段值
*/
public void setTextData(String key, String value) {
mNoteData.setTextData(key, value);
}
/**
* 设置笔记文本数据的ID。
* 业务逻辑:通过{@link NoteData}对象设置文本数据的ID。
*
* @param id 文本数据ID
*/
public void setTextDataId(long id) {
mNoteData.setTextDataId(id);
}
/**
* 获取笔记文本数据的ID。
* 业务逻辑:通过{@link NoteData}对象获取文本数据的ID。
*
* @return 文本数据ID
*/
public long getTextDataId() {
return mNoteData.mTextDataId;
}
/**
* 设置笔记通话数据的ID。
* 业务逻辑:通过{@link NoteData}对象设置通话数据的ID。
*
* @param id 通话数据ID
*/
public void setCallDataId(long id) {
mNoteData.setCallDataId(id);
}
/**
* 设置笔记的通话数据字段值。
* 业务逻辑:通过{@link NoteData}对象更新笔记的通话记录数据。
*
* @param key 通话数据字段名
* @param value 通话数据字段值
*/
public void setCallData(String key, String value) {
mNoteData.setCallData(key, value);
}
/**
* 检查笔记是否在本地已修改。
* 业务逻辑:判断笔记的元数据或内容数据是否发生了变更。
*
* @return true表示笔记已修改,false表示未修改
*
* 判断条件:
*
* - 元数据变更:{@link #mNoteDiffValues}的大小大于0
* - 内容数据变更:通过{@link NoteData#isLocalModified()}判断
*
*/
public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
/**
* 将笔记的变更同步到ContentResolver。
* 业务逻辑:将笔记的元数据变更和内容数据变更持久化到数据库。
*
* @param context 上下文对象,用于获取ContentResolver
* @param noteId 笔记ID,指定要同步的笔记
* @return true表示同步成功,false表示同步失败
* @throws IllegalArgumentException 当noteId无效(<=0)时抛出
*
* 同步流程:
*
* - 检查noteId是否有效
* - 检查笔记是否已修改,未修改则直接返回true
* - 更新笔记的元数据变更到ContentResolver
* - 如果内容数据已修改,将其同步到ContentResolver
* - 返回同步结果
*
*
* 数据流转换:
*
* - 元数据变更:从{@link #mNoteDiffValues}转换为ContentResolver.update()的参数
* - 内容数据变更:通过{@link NoteData#pushIntoContentResolver(Context, long)}方法处理
*
*
* 错误处理:
*
* - 元数据更新失败:记录日志,但继续执行内容数据同步(为了数据安全性)
* - 内容数据同步失败:返回false表示同步失败
*
*
* 跨类调用:
*
* - 通过{@link Context#getContentResolver()}获取ContentResolver
* - 调用{@link android.content.ContentResolver#update(Uri, ContentValues, String, String[])}更新元数据
* - 调用{@link NoteData#pushIntoContentResolver(Context, long)}同步内容数据
*
*/
public boolean syncNote(Context context, long noteId) {
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
if (!isLocalModified()) {
return true;
}
/**
* In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the
* note data info
*/
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
Log.e(TAG, "Update note error, should not happen");
// Do not return, fall through
}
mNoteDiffValues.clear();
if (mNoteData.isLocalModified()
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
return false;
}
return true;
}
/**
* 笔记内容数据管理类,处理笔记的具体内容存储和变更。
* 设计意图:将笔记的内容数据与元数据分离管理,提高数据操作的灵活性和效率。
* 作为{@link Note}类的内部类,专门负责处理:
*
* - 笔记的文本内容数据
* - 笔记的通话记录数据
* - 内容数据的变更跟踪
* - 内容数据与ContentResolver的同步
*
* 关键关联:
*
* - 与{@link Note}类紧密协作,共享笔记元数据变更的跟踪
* - 使用{@link ContentValues}存储内容数据的变更
* - 通过{@link ContentProviderOperation}执行批量数据更新
*
*/
private class NoteData {
/**
* 文本数据的ID。
* 业务含义:唯一标识笔记的文本内容数据记录。
* 约束条件:0表示文本数据尚未保存到数据库。
*/
private long mTextDataId;
/**
* 存储文本数据的变更记录。
* 业务含义:记录笔记文本内容的变更,如正文、标题等。
*/
private ContentValues mTextDataValues;
/**
* 通话数据的ID。
* 业务含义:唯一标识笔记的通话记录数据记录。
* 约束条件:0表示通话数据尚未保存到数据库。
*/
private long mCallDataId;
/**
* 存储通话数据的变更记录。
* 业务含义:记录笔记通话记录的变更,如通话号码、通话时间等。
*/
private ContentValues mCallDataValues;
/**
* 日志标签。
* 业务含义:用于在日志中标识NoteData类的相关操作。
*/
private static final String TAG = "NoteData";
/**
* NoteData类的默认构造函数。
* 功能:初始化笔记内容数据的变更记录和ID。
*
* - 初始化{@link #mTextDataValues}为新的ContentValues对象
* - 初始化{@link #mCallDataValues}为新的ContentValues对象
* - 将{@link #mTextDataId}和{@link #mCallDataId}初始化为0
*
*/
public NoteData() {
mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues();
mTextDataId = 0;
mCallDataId = 0;
}
/**
* 检查内容数据是否在本地已修改。
* 业务逻辑:判断文本数据或通话数据是否发生了变更。
*
* @return true表示内容数据已修改,false表示未修改
*
* 判断条件:
*
* - 文本数据变更:{@link #mTextDataValues}的大小大于0
* - 通话数据变更:{@link #mCallDataValues}的大小大于0
*
*/
boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
/**
* 设置文本数据的ID。
* 业务逻辑:更新文本数据的唯一标识符。
*
* @param id 文本数据ID
* @throws IllegalArgumentException 当id无效(<=0)时抛出
*/
void setTextDataId(long id) {
if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
mTextDataId = id;
}
/**
* 设置通话数据的ID。
* 业务逻辑:更新通话数据的唯一标识符。
*
* @param id 通话数据ID
* @throws IllegalArgumentException 当id无效(<=0)时抛出
*/
void setCallDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
}
mCallDataId = id;
}
/**
* 设置通话数据的字段值。
* 业务逻辑:更新通话数据的变更记录,并标记笔记为本地已修改。
*
* @param key 通话数据字段名
* @param value 通话数据字段值
*
* 自动更新的字段:
*
* - 通过外部类{@link Note}的{@link Note#mNoteDiffValues}更新{@link NoteColumns#LOCAL_MODIFIED}为1
* - 通过外部类{@link Note}的{@link Note#mNoteDiffValues}更新{@link NoteColumns#MODIFIED_DATE}为当前时间戳
*
*/
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 文本数据字段名
* @param value 文本数据字段值
*
* 自动更新的字段:
*
* - 通过外部类{@link Note}的{@link Note#mNoteDiffValues}更新{@link NoteColumns#LOCAL_MODIFIED}为1
* - 通过外部类{@link Note}的{@link Note#mNoteDiffValues}更新{@link NoteColumns#MODIFIED_DATE}为当前时间戳
*
*/
void setTextData(String key, String value) {
mTextDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
* 将内容数据的变更同步到ContentResolver。
* 业务逻辑:处理文本数据和通话数据的插入或更新操作。
*
* @param context 上下文对象,用于获取ContentResolver
* @param noteId 笔记ID,指定内容数据所属的笔记
* @return 成功时返回笔记的Uri,失败时返回null
* @throws IllegalArgumentException 当noteId无效(<=0)时抛出
*
* 同步流程:
*
* - 检查noteId是否有效
* - 处理文本数据:
*
* - 如果{@link #mTextDataId}为0,执行插入操作
* - 否则,执行更新操作
*
* - 处理通话数据:
*
* - 如果{@link #mCallDataId}为0,执行插入操作
* - 否则,执行更新操作
*
* - 通过ContentProviderOperation批量执行所有操作
*
*
* 跨类调用:
*
* - 通过{@link Context#getContentResolver()}获取ContentResolver
* - 调用{@link android.content.ContentResolver#insert(Uri, ContentValues)}插入新数据
* - 调用{@link android.content.ContentResolver#applyBatch(String, ArrayList)}执行批量操作
*
*
* 错误处理:
*
* - 插入文本数据失败:记录日志并返回null
* - 批量操作失败:捕获RemoteException和OperationApplicationException,记录日志并返回null
*
*/
Uri pushIntoContentResolver(Context context, long noteId) {
/**
* Check for safety
*/
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
ArrayList operationList = new ArrayList();
ContentProviderOperation.Builder builder = null;
if(mTextDataValues.size() > 0) {
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) {
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mTextDataValues);
try {
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
mTextDataValues.clear();
return null;
}
} else {
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);
if (mCallDataId == 0) {
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mCallDataValues);
try {
setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new call data fail with noteId" + noteId);
mCallDataValues.clear();
return null;
}
} else {
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, operationList);
return (results == null || results.length == 0 || results[0] == null) ? null
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
}
}
return null;
}
}
}