/* * 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; /** *

核心笔记数据模型类,负责管理笔记的基本信息和数据变更。

*

设计意图:将笔记的元数据变更和具体内容数据分离管理,提高数据操作的灵活性和效率。

* *

关键关联:

* */ 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时抛出 * *

跨类调用:

* * *

错误处理:

* */ 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类的默认构造函数。

*

功能:初始化笔记的元数据变更记录和内容数据管理对象。

* */ public Note() { mNoteDiffValues = new ContentValues(); mNoteData = new NoteData(); } /** *

设置笔记的元数据字段值。

*

业务逻辑:更新笔记的元数据变更记录,并标记为本地已修改。

* * @param key 元数据字段名 * @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()); } /** *

设置笔记的文本数据字段值。

*

业务逻辑:通过{@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表示未修改 * *

判断条件:

* */ public boolean isLocalModified() { return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); } /** *

将笔记的变更同步到ContentResolver。

*

业务逻辑:将笔记的元数据变更和内容数据变更持久化到数据库。

* * @param context 上下文对象,用于获取ContentResolver * @param noteId 笔记ID,指定要同步的笔记 * @return true表示同步成功,false表示同步失败 * @throws IllegalArgumentException 当noteId无效(<=0)时抛出 * *

同步流程:

*
    *
  1. 检查noteId是否有效
  2. *
  3. 检查笔记是否已修改,未修改则直接返回true
  4. *
  5. 更新笔记的元数据变更到ContentResolver
  6. *
  7. 如果内容数据已修改,将其同步到ContentResolver
  8. *
  9. 返回同步结果
  10. *
* *

数据流转换:

* * *

错误处理:

* * *

跨类调用:

* */ 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}类的内部类,专门负责处理:

* *

关键关联:

* */ 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。

* */ 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。

*

业务逻辑:更新文本数据的唯一标识符。

* * @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 通话数据字段值 * *

自动更新的字段:

* */ 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 文本数据字段值 * *

自动更新的字段:

* */ 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)时抛出 * *

同步流程:

*
    *
  1. 检查noteId是否有效
  2. *
  3. 处理文本数据:
  4. * *
  5. 处理通话数据:
  6. * *
  7. 通过ContentProviderOperation批量执行所有操作
  8. *
* *

跨类调用:

* * *

错误处理:

* */ 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; } } }