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.text/src/model/Note.java

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