From be3e994de6bcb894f7aaa800e73a66ca3f68d608 Mon Sep 17 00:00:00 2001 From: xxxx Date: Sat, 21 Dec 2024 17:30:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=A8=E9=87=8A=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BackupUtils.java | 822 ++++++++++++++++++++ GTaskManager.java | 1688 +++++++++++++++++++++++++++++++++++++++++ GTaskSyncService.java | 269 +++++++ Note2.java | 622 +++++++++++++++ WorkingNote.java | 812 ++++++++++++++++++++ 5 files changed, 4213 insertions(+) create mode 100644 BackupUtils.java create mode 100644 GTaskManager.java create mode 100644 GTaskSyncService.java create mode 100644 Note2.java create mode 100644 WorkingNote.java diff --git a/BackupUtils.java b/BackupUtils.java new file mode 100644 index 0000000..d280a4d --- /dev/null +++ b/BackupUtils.java @@ -0,0 +1,822 @@ +/* + * 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.tool; + + + +import android.content.Context; // 引入Context类,它是Android应用的全局信息的接口 + +import android.database.Cursor; // 引入Cursor类,用于遍历数据库查询结果 + +import android.os.Environment; // 引入Environment类,提供有关Android设备环境的访问 + +import android.text.TextUtils; // 引入TextUtils类,包含一些静态方法用于处理字符串 + +import android.text.format.DateFormat; // 引入DateFormat类,用于格式化日期和时间 + +import android.util.Log; // 引入Log类,用于记录日志信息 + + + +import net.micode.notes.R; // 引入R类,它是一个包含应用资源引用的类 + +import net.micode.notes.data.Notes; // 引入Notes类,它可能是一个包含数据库访问逻辑的类 + +import net.micode.notes.data.Notes.DataColumns; // 引入DataColumns接口,定义了数据表列名 + +import net.micode.notes.data.Notes.DataConstants; // 引入DataConstants类,可能包含一些数据相关的常量 + +import net.micode.notes.data.Notes.NoteColumns; // 引入NoteColumns接口,定义了笔记表列名 + + + +import java.io.File; // 引入File类,表示文件和目录路径名的抽象表示形式 + +import java.io.FileNotFoundException; // 引入FileNotFoundException类,当文件未找到时抛出 + +import java.io.FileOutputStream; // 引入FileOutputStream类,用于写入文件数据 + +import java.io.IOException; // 引入IOException类,是输入输出异常的超类 + +import java.io.PrintStream; // 引入PrintStream类,用于表示打印流的抽象类 + + + +// BackupUtils类,用于实现备份工具的功能 + +public class BackupUtils { + + private static final String TAG = "BackupUtils"; // 定义日志标签,用于日志输出 + + // Singleton stuff(单例模式相关代码) + + private static BackupUtils sInstance; // 定义单例实例变量 + + + + // 获取BackupUtils类的实例,使用单例模式确保全局只有一个实例 + + public static synchronized BackupUtils getInstance(Context context) { + + if (sInstance == null) { + + sInstance = new BackupUtils(context); + + } + + return sInstance; + + } + + + + // 定义一些常量来表示备份或恢复的状态 + + public static final int STATE_SD_CARD_UNMOUONTED = 0; // SD卡未挂载 + + public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; // 备份文件不存在 + + public static final int STATE_DATA_DESTROIED = 2; // 数据被破坏 + + public static final int STATE_SYSTEM_ERROR = 3; // 系统错误 + + public static final int STATE_SUCCESS = 4; // 成功 + + + + private TextExport mTextExport; // TextExport类的实例变量,用于导出文本 + + + + // 私有构造方法,防止外部直接创建实例,配合单例模式使用 + + private BackupUtils(Context context) { + + mTextExport = new TextExport(context); + + } + + + + // 检查外部存储是否可用的方法 + + private static boolean externalStorageAvailable() { + + return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); + + } + + + + // 导出数据到文本文件的方法 + + public int exportToText() { + + return mTextExport.exportToText(); + + } + + + + // 获取导出的文本文件名的方法 + + public String getExportedTextFileName() { + + return mTextExport.mFileName; + + } + + + + // 获取导出的文本文件目录的方法 + + public String getExportedTextFileDir() { + + return mTextExport.mFileDirectory; + + } + + + + // TextExport内部类,用于实现将数据导出到文本文件的功能 + + private static class TextExport { + + // 定义从Note表中查询数据的列名数组 + + private static final String[] NOTE_PROJECTION = { + + NoteColumns.ID, + + NoteColumns.MODIFIED_DATE, + + NoteColumns.SNIPPET, + + NoteColumns.TYPE + + }; + + + + // 定义Note表中各列在NOTE_PROJECTION数组中的索引 + + private static final int NOTE_COLUMN_ID = 0; + + private static final int NOTE_COLUMN_MODIFIED_DATE = 1; + + private static final int NOTE_COLUMN_SNIPPET = 2; + + + + // 定义从Data表中查询数据的列名数组 + + private static final String[] DATA_PROJECTION = { + + DataColumns.CONTENT, + + DataColumns.MIME_TYPE, + + DataColumns.DATA1, + + DataColumns.DATA2, + + DataColumns.DATA3, + + DataColumns.DATA4, + + }; + + + + // 定义Data表中各列在DATA_PROJECTION数组中的索引 + + // 注意:这里有一个错误,应该是DATA_COLUMN_DATA3而不是DATA_COLUMN_CALL_DATE + + private static final int DATA_COLUMN_CONTENT = 0; + + private static final int DATA_COLUMN_MIME_TYPE = 1; + + private static final int DATA_COLUMN_CALL_DATE = 2; // 错误,应为DATA_COLUMN_DATA1或其他正确的索引 + + private static final int DATA_COLUMN_PHONE_NUMBER = 4; // 错误,索引与DATA_PROJECTION不匹配 + + + + // 从资源文件中获取文本格式的数组 + + private final String [] TEXT_FORMAT; + + // 定义TEXT_FORMAT数组中各个格式的索引 + + private static final int FORMAT_FOLDER_NAME = 0; + + private static final int FORMAT_NOTE_DATE = 1; + + private static final int FORMAT_NOTE_CONTENT = 2; + + + + // Context实例变量,用于访问应用的资源和类 + + private Context mContext; + + // 导出的文本文件名 + + private String mFileName; + + // 导出的文本文件目录 + + private String mFileDirectory; + + + + // TextExport类的构造方法 + + public TextExport(Context context) { + + TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); + + mContext = context; + + mFileName = ""; + + mFileDirectory = ""; + + } + + + + // 根据索引获取TEXT_FORMAT数组中对应格式的方法 + + private String getFormat(int id) { + + return TEXT_FORMAT[id]; + + } + /** + + * Export the folder identified by folder id to text + + */ + +private void exportFolderToText(String folderId, PrintStream ps) { + + // 使用内容解析器查询属于指定文件夹的笔记 + + Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, + + NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { + + folderId + + }, null); + + + + // 检查返回的Cursor是否为null + + if (notesCursor != null) { + + // 移动Cursor到第一行,如果Cursor不为空且至少有一行数据 + + if (notesCursor.moveToFirst()) { + + do { + + // 打印笔记的最后修改日期 + + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( + + mContext.getString(R.string.format_datetime_mdhm), + + notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); + + + + // 获取当前笔记的ID + + String noteId = notesCursor.getString(NOTE_COLUMN_ID); + + // 调用方法导出指定ID的笔记到文本 + + exportNoteToText(noteId, ps); + + } while (notesCursor.moveToNext()); // 循环直到所有笔记都被处理 + + } + + // 关闭Cursor + + notesCursor.close(); + + } + +} + + + +/** + + * Export note identified by id to a print stream + + */ + +private void exportNoteToText(String noteId, PrintStream ps) { + + // 使用内容解析器查询指定笔记的数据 + + Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, + + DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { + + noteId + + }, null); + + + + // 检查返回的Cursor是否为null + + if (dataCursor != null) { + + // 移动Cursor到第一行,如果Cursor不为空且至少有一行数据 + + if (dataCursor.moveToFirst()) { + + do { + + // 获取当前数据的MIME类型 + + String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); + + + + // 如果MIME类型表示这是一个电话笔记 + + if (DataConstants.CALL_NOTE.equals(mimeType)) { + + // 打印电话号码 + + String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); + + long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); + + String location = dataCursor.getString(DATA_COLUMN_CONTENT); + + + + // 如果电话号码不为空,则打印电话号码 + + if (!TextUtils.isEmpty(phoneNumber)) { + + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + + phoneNumber)); + + } + + // 打印通话日期 + + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat + + .format(mContext.getString(R.string.format_datetime_mdhm), + + callDate))); + + // 如果位置信息不为空,则打印位置信息 + + if (!TextUtils.isEmpty(location)) { + + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + + location)); + + } + + } + + // 如果MIME类型表示这是一个普通笔记 + + else if (DataConstants.NOTE.equals(mimeType)) { + + // 获取笔记内容 + + String content = dataCursor.getString(DATA_COLUMN_CONTENT); + + // 如果内容不为空,则打印内容 + + if (!TextUtils.isEmpty(content)) { + + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + + content)); + + } + + } + + } while (dataCursor.moveToNext()); // 循环直到所有笔记数据都被处理 + + } + + // 关闭Cursor + + dataCursor.close(); + + } + + // 在每个笔记之后打印一个行分隔符 + + try { + + ps.write(new byte[] { + + Character.LINE_SEPARATOR, // 实际上这里应该是只有行分隔符,后面这个字符可能是误加 + + // Character.LETTER_NUMBER // 这行应该是错误的,通常我们只需要行分隔符 + + }); + + } catch (IOException e) { + + // 捕获并打印异常 + + Log.e(TAG, e.toString()); + + } + +} + /** + + * Note will be exported as text which is user readable + + */ + +public int exportToText() { + + // 检查外部存储是否可用 + + if (!externalStorageAvailable()) { + + // 如果外部存储不可用,记录日志并返回存储卡未挂载的状态 + + Log.d(TAG, "Media was not mounted"); + + return STATE_SD_CARD_UNMOUONTED; + + } + + + + // 获取用于导出文本的输出流 + + PrintStream ps = getExportToTextPrintStream(); + + // 如果无法获取输出流,记录错误日志并返回系统错误状态 + + if (ps == null) { + + Log.e(TAG, "get print stream error"); + + return STATE_SYSTEM_ERROR; + + } + + + + // 首先导出文件夹及其笔记 + + // 使用ContentResolver查询数据库中的文件夹数据 + + Cursor folderCursor = mContext.getContentResolver().query( + + Notes.CONTENT_NOTE_URI, // 查询的URI + + NOTE_PROJECTION, // 查询的列 + + "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " // 文件夹类型 + + + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " // 非垃圾文件夹 + + + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, // 或通话记录文件夹 + + null, null); // 无排序和选择参数 + + + + // 如果查询结果不为空 + + if (folderCursor != null) { + + // 移动到查询结果的第一条记录 + + if (folderCursor.moveToFirst()) { + + do { + + // 打印文件夹的名称 + + String folderName = ""; + + // 如果是通话记录文件夹,使用特定的字符串 + + if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { + + folderName = mContext.getString(R.string.call_record_folder_name); + + } else { + + // 否则,使用文件夹的摘要(名称) + + folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); + + } + + // 如果文件夹名称不为空,则格式化并打印 + + if (!TextUtils.isEmpty(folderName)) { + + ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); + + } + + // 获取文件夹ID,并导出该文件夹下的所有笔记 + + String folderId = folderCursor.getString(NOTE_COLUMN_ID); + + exportFolderToText(folderId, ps); + + } while (folderCursor.moveToNext()); // 移动到下一条记录 + + } + + // 关闭Cursor以释放资源 + + folderCursor.close(); + + } + + + + // 导出根文件夹中的笔记 + + // 使用ContentResolver查询数据库中的笔记数据 + + Cursor noteCursor = mContext.getContentResolver().query( + + Notes.CONTENT_NOTE_URI, // 查询的URI + + NOTE_PROJECTION, // 查询的列 + + NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID // 笔记类型且父ID为0(根文件夹) + + + "=0", null, null); // 无排序和选择参数 + + + + // 如果查询结果不为空 + + if (noteCursor != null) { + + // 移动到查询结果的第一条记录 + + if (noteCursor.moveToFirst()) { + + do { + + // 打印笔记的修改日期 + + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( + + mContext.getString(R.string.format_datetime_mdhm), // 日期格式 + + noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); // 笔记的修改日期 + + // 获取笔记ID,并导出该笔记的详细数据 + + String noteId = noteCursor.getString(NOTE_COLUMN_ID); + + exportNoteToText(noteId, ps); + + } while (noteCursor.moveToNext()); // 移动到下一条记录 + + } + + // 关闭Cursor以释放资源 + + noteCursor.close(); + + } + + + + // 关闭输出流以释放资源 + + ps.close(); + + + + // 返回成功状态 + + return STATE_SUCCESS; + +} + /** + + * Get a print stream pointed to the file defined by generateExportedTextFile. + + * This method is private and returns a PrintStream object or null if an error occurs. + + */ + +private PrintStream getExportToTextPrintStream() { + + // Call a method to generate a file on the SD card. + + File file = generateFileMountedOnSDcard(mContext, R.string.file_path, + + R.string.file_name_txt_format); + + + + // If the file generation failed (i.e., file is null), log an error and return null. + + if (file == null) { + + Log.e(TAG, "create file to exported failed"); + + return null; + + } + + + + // Store the file name and directory for later use. + + mFileName = file.getName(); + + mFileDirectory = mContext.getString(R.string.file_path); + + + + // Initialize a PrintStream object to null. + + PrintStream ps = null; + + + + // Try to create a FileOutputStream for the file and wrap it in a PrintStream. + + try { + + FileOutputStream fos = new FileOutputStream(file); + + ps = new PrintStream(fos); + + } catch (FileNotFoundException e) { + + // If the file was not found, log the error and return null. + + e.printStackTrace(); + + return null; + + } catch (NullPointerException e) { + + // If a NullPointerException occurs (e.g., due to a null file object), log the error and return null. + + e.printStackTrace(); + + return null; + + } + + + + // Return the PrintStream object if no errors occurred. + + return ps; + +} + + + +/** + + * Generate the text file to store imported data on the SD card. + + * This method is private and static, returning a File object or null if an error occurs. + + */ + +private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { + + // Create a StringBuilder to build the file path. + + StringBuilder sb = new StringBuilder(); + + + + // Append the external storage directory path. + + sb.append(Environment.getExternalStorageDirectory()); + + + + // Append the directory path from resources. + + sb.append(context.getString(filePathResId)); + + + + // Create a File object for the directory. + + File filedir = new File(sb.toString()); + + + + // Append the formatted file name to the StringBuilder. + + // The file name includes a date format obtained from resources. + + sb.append(context.getString( + + fileNameFormatResId, + + DateFormat.format(context.getString(R.string.format_date_ymd), + + System.currentTimeMillis()))); + + + + // Create a File object for the full file path. + + File file = new File(sb.toString()); + + + + try { + + // If the directory does not exist, create it. + + if (!filedir.exists()) { + + filedir.mkdir(); + + } + + + + // If the file does not exist, create it. + + if (!file.exists()) { + + file.createNewFile(); + + } + + + + // Return the File object if no errors occurred. + + return file; + + } catch (SecurityException e) { + + // If there is a security exception (e.g., no permission to write to external storage), log the error. + + e.printStackTrace(); + + } catch (IOException e) { + + // If an I/O error occurs, log the error. + + e.printStackTrace(); + + } + + + + // Return null if an error occurred. + + return null; + +} + + diff --git a/GTaskManager.java b/GTaskManager.java new file mode 100644 index 0000000..6089eaf --- /dev/null +++ b/GTaskManager.java @@ -0,0 +1,1688 @@ +/* + * 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. + */ + +// 声明包名,这是Java中组织类的一种方式,确保类的唯一性 + +package net.micode.notes.gtask.remote; + + + +// 导入Android框架和其他Java库中的类 + +import android.app.Activity; // 导入Activity类,它是Android应用中用户界面的一个基本组件 + +import android.content.ContentResolver; // 导入ContentResolver类,用于访问Android内容提供者提供的数据 + +import android.content.ContentUris; // 导入ContentUris类,提供用于操作URI的工具方法 + +import android.content.ContentValues; // 导入ContentValues类,用于存储一组值,这些值通常用于插入或更新数据库 + +import android.content.Context; // 导入Context类,它提供了关于应用环境和其资源的全局信息 + +import android.database.Cursor; // 导入Cursor类,用于遍历查询结果集 + +import android.util.Log; // 导入Log类,用于记录日志信息 + + + +// 导入项目内部的其他类 + +import net.micode.notes.R; // 导入R类,它包含了项目中所有资源的引用,如布局、字符串等 + +import net.micode.notes.data.Notes; // 导入Notes类,可能是一个用于管理笔记数据的数据库帮助类 + +import net.micode.notes.data.Notes.DataColumns; // 导入DataColumns接口或类,它定义了Notes数据库中数据表的列名 + +import net.micode.notes.data.Notes.NoteColumns; // 导入NoteColumns接口或类,它定义了Notes数据库中笔记数据表的列名 + +import net.micode.notes.gtask.data.MetaData; // 导入MetaData类,可能用于存储与Google任务相关的元数据 + +import net.micode.notes.gtask.data.Node; // 导入Node类,可能表示Google任务中的节点或任务项 + +import net.micode.notes.gtask.data.SqlNote; // 导入SqlNote类,可能是一个表示笔记的实体类,与数据库交互 + +import net.micode.notes.gtask.data.Task; // 导入Task类,表示Google任务中的一个任务项 + +import net.micode.notes.gtask.data.TaskList; // 导入TaskList类,表示Google任务中的一个任务列表 + +import net.micode.notes.gtask.exception.ActionFailureException; // 导入ActionFailureException类,表示执行动作失败时抛出的异常 + +import net.micode.notes.gtask.exception.NetworkFailureException; // 导入NetworkFailureException类,表示网络请求失败时抛出的异常 + +import net.micode.notes.tool.DataUtils; // 导入DataUtils类,可能包含了一些用于数据处理的工具方法 + +import net.micode.notes.tool.GTaskStringUtils; // 导入GTaskStringUtils类,可能包含了一些用于处理Google任务相关字符串的工具方法 + + + +// 导入Java标准库中的类 + +import org.json.JSONArray; // 导入JSONArray类,用于表示JSON数组 + +import org.json.JSONException; // 导入JSONException类,表示在解析JSON时发生的异常 + +import org.json.JSONObject; // 导入JSONObject类,用于表示JSON对象 + + + +import java.util.HashMap; // 导入HashMap类,用于存储键值对 + +import java.util.HashSet; // 导入HashSet类,它是一个不包含重复元素的集合 + +import java.util.Iterator; // 导入Iterator接口,用于遍历集合中的元素 + +import java.util.Map; // 导入Map接口,它是存储键值对的数据结构的基础接口 +//1 + + + +// 声明GTaskManager类,这是一个管理Google任务(GTasks)的类 + +public class GTaskManager { + + // 定义一个静态常量TAG,用于日志记录时标识此类的名称 + + private static final String TAG = GTaskManager.class.getSimpleName(); + + + + // 定义几个静态常量,用于表示不同的状态码 + + // 0表示操作成功 + + public static final int STATE_SUCCESS = 0; + + // 1表示网络错误 + + public static final int STATE_NETWORK_ERROR = 1; + + // 2表示内部错误 + + public static final int STATE_INTERNAL_ERROR = 2; + + // 3表示同步正在进行中 + + public static final int STATE_SYNC_IN_PROGRESS = 3; + + // 4表示同步被取消 + + public static final int STATE_SYNC_CANCELLED = 4; + + + + // 定义一个私有的静态实例变量mInstance,用于实现单例模式 + + private static GTaskManager mInstance = null; + + + + // 定义私有成员变量,用于存储Activity的引用 + + private Activity mActivity; + + + + // 定义私有成员变量,用于存储Context的引用 + + private Context mContext; + + + + // 定义私有成员变量,用于访问Android内容提供者提供的数据 + + private ContentResolver mContentResolver; + + + + // 定义私有成员变量,表示当前是否正在同步 + + private boolean mSyncing; + + + + // 定义私有成员变量,表示同步是否被取消 + + private boolean mCancelled; + + + + // 定义一个HashMap,用于存储Google任务列表(TaskList)的映射关系,键是任务列表的字符串标识,值是TaskList对象 + + private HashMap mGTaskListHashMap; + + + + // 定义一个HashMap,用于存储Google任务节点(Node)的映射关系,键是节点的字符串标识,值是Node对象 + + private HashMap mGTaskHashMap; + + + + // 定义一个HashMap,用于存储元数据(MetaData)的映射关系,键是元数据的字符串标识,值是MetaData对象 + + private HashMap mMetaHashMap; + + + + // 定义一个TaskList对象,可能用于存储元数据列表 + + private TaskList mMetaList; + + + + // 定义一个HashSet,用于存储本地删除的ID集合,可能用于标记那些已经在本地删除但尚未同步到云端的任务ID + + private HashSet mLocalDeleteIdMap; + + + + // 定义一个HashMap,用于存储Google任务ID(Gid)到本地任务ID(Nid)的映射关系 + + private HashMap mGidToNid; + + + + // 定义一个HashMap,用于存储本地任务ID(Nid)到Google任务ID(Gid)的映射关系 + + private HashMap mNidToGid; +//2 + + +// 私有构造函数,用于初始化GTaskManager类的实例变量 + +private GTaskManager() { + + mSyncing = false; // 初始化同步状态为false + + mCancelled = false; // 初始化取消状态为false + + mGTaskListHashMap = new HashMap(); // 初始化任务列表的HashMap + + mGTaskHashMap = new HashMap(); // 初始化任务节点的HashMap + + mMetaHashMap = new HashMap(); // 初始化元数据的HashMap + + mMetaList = null; // 初始化元数据列表为null + + mLocalDeleteIdMap = new HashSet(); // 初始化本地删除ID的HashSet + + mGidToNid = new HashMap(); // 初始化GID到NID的映射 + + mNidToGid = new HashMap(); // 初始化NID到GID的映射 + +} + + + +// 获取GTaskManager类的单例实例 + +public static synchronized GTaskManager getInstance() { + + if (mInstance == null) { // 如果实例尚未创建 + + mInstance = new GTaskManager(); // 创建新实例 + + } + + return mInstance; // 返回单例实例 + +} + + + +// 设置Activity上下文,用于获取认证令牌 + +public synchronized void setActivityContext(Activity activity) { + + mActivity = activity; // 保存Activity上下文 + +} + + + +// 同步Google Tasks数据 + +public int sync(Context context, GTaskASyncTask asyncTask) { + + if (mSyncing) { // 如果已经在同步 + + Log.d(TAG, "Sync is in progress"); // 记录日志 + + return STATE_SYNC_IN_PROGRESS; // 返回同步进行中状态 + + } + + mContext = context; // 保存上下文 + + mContentResolver = mContext.getContentResolver(); // 获取内容解析器 + + mSyncing = true; // 设置同步状态为true + + mCancelled = false; // 重置取消状态为false + + // 清空所有HashMap和HashSet + + mGTaskListHashMap.clear(); + + mGTaskHashMap.clear(); + + mMetaHashMap.clear(); + + mLocalDeleteIdMap.clear(); + + mGidToNid.clear(); + + mNidToGid.clear(); + + + + try { + + GTaskClient client = GTaskClient.getInstance(); // 获取GTaskClient实例 + + client.resetUpdateArray(); // 重置更新数组 + + + + // 登录Google Tasks + + if (!mCancelled) { + + if (!client.login(mActivity)) { // 如果登录失败 + + throw new NetworkFailureException("login google task failed"); // 抛出网络失败异常 + + } + + } + + + + // 从Google获取任务列表 + + asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); // 发布进度 + + initGTaskList(); // 初始化任务列表 + + + + // 执行内容同步 + + asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); // 发布进度 + + syncContent(); // 同步内容(此方法未在代码段中给出) + + } catch (NetworkFailureException e) { // 捕获网络失败异常 + + Log.e(TAG, e.toString()); // 记录错误日志 + + return STATE_NETWORK_ERROR; // 返回网络错误状态 + + } catch (ActionFailureException e) { // 捕获操作失败异常 + + Log.e(TAG, e.toString()); // 记录错误日志 + + return STATE_INTERNAL_ERROR; // 返回内部错误状态 + + } catch (Exception e) { // 捕获其他异常 + + Log.e(TAG, e.toString()); // 记录错误日志 + + e.printStackTrace(); // 打印堆栈跟踪 + + return STATE_INTERNAL_ERROR; // 返回内部错误状态 + + } finally { + + // 清空所有HashMap和HashSet + + mGTaskListHashMap.clear(); + + mGTaskHashMap.clear(); + + mMetaHashMap.clear(); + + mLocalDeleteIdMap.clear(); + + mGidToNid.clear(); + + mNidToGid.clear(); + + mSyncing = false; // 设置同步状态为false + + } + + + + return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; // 根据取消状态返回相应结果 + +} + + + +// 初始化Google Tasks列表 + +private void initGTaskList() throws NetworkFailureException { + + if (mCancelled) return; // 如果已取消,则直接返回 + + GTaskClient client = GTaskClient.getInstance(); // 获取GTaskClient实例 + + try { + + JSONArray jsTaskLists = client.getTaskLists(); // 获取任务列表的JSON数组 + + + + // 首先初始化元数据列表 + + mMetaList = null; // 重置元数据列表为null + + for (int i = 0; i < jsTaskLists.length(); i++) { // 遍历任务列表 + + JSONObject object = jsTaskLists.getJSONObject(i); // 获取任务对象 + + String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取GID + + String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); // 获取名称 + + + + // 检查是否为元数据文件夹 + + if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + + mMetaList = new TaskList(); // 创建元数据列表 + + mMetaList.setContentByRemoteJSON(object); // 设置内容 + + + + // 加载元数据 + + JSONArray jsMetas = client.getTaskList(gid); // 获取元数据列表的JSON数组 + + for (int j = 0; j < jsMetas.length(); j++) { // 遍历元数据 + + object = jsMetas.getJSONObject(j); // 获取元数据对象 + + MetaData metaData = new MetaData(); // 创建元数据实例 + + metaData.setContentByRemoteJSON(object); // 设置内容 + + if (metaData.isWorthSaving()) { // 如果值得保存 + + mMetaList.addChildTask(metaData); // 添加到元数据列表 + + if (metaData.getGid() != null) { // 如果有GID + + mMetaHashMap.put(metaData.getRelatedGid(), metaData); // 添加到元数据HashMap + + } + + } + + } + + } + + } + + + + // 如果元数据列表不存在,则创建 + + if (mMetaList == null) { + + mMetaList = new TaskList(); // 创建元数据列表 + + mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META); // 设置名称 + + GTaskClient.getInstance().createTaskList(mMetaList); // 创建任务列表 + + } + + + + // 初始化任务列表 + + for (int i = 0; i < jsTaskLists.length(); i++) { // 遍历任务列表 + + JSONObject object = jsTaskLists.getJSONObject(i); // 获取任务对象 + + String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取GID + + String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); // 获取名称 + + + + // 检查是否为MIUI前缀的任务列表(且不是元数据文件夹) + + if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) && + + !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + + TaskList tasklist = new TaskList(); // 创建任务列表 + + tasklist.setContentByRemoteJSON(object); // 设置内容 + + mGTaskListHashMap.put(gid, tasklist); // 添加到任务列表HashMap + + mGTaskHashMap.put(gid, tasklist); // 添加到任务节点HashMap(这里可能有误,通常应该添加任务节点而非任务列表) + + + + // 加载任务 + + JSONArray jsTasks = client.getTaskList(gid); // 获取任务的JSON数组 + + for (int j = 0; j < jsTasks.length(); j++) { // 遍历任务 + + object = jsTasks.getJSONObject(j); // 获取任务对象 + + gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取GID(这里可能覆盖了之前的gid变量,应该是nid或其他变量) + + Task task = new Task(); // 创建任务实例 + + task.setContentByRemoteJSON(object); // 设置内容 + + if (task.isWorthSaving()) { // 如果值得保存 + + task.setMetaInfo(mMetaHashMap.get(gid)); // 设置元数据(这里gid可能应该是任务的相关GID) + + tasklist.addChildTask(task); // 添加到任务列表 + + mGTaskHashMap.put(gid, task); // 添加到任务节点HashMap(这里gid可能应该是任务的唯一ID) + + } + + } + + } + + } + + } catch (JSONException e) { // 捕获JSON异常 + + Log.e(TAG, e.toString()); // 记录错误日志 + + e.printStackTrace(); // 打印堆栈跟踪 + + throw new ActionFailureException("initGTaskList: handing JSONObject failed"); // 抛出操作失败异常 + + } + +} +//3 + + + + // 定义一个私有方法,用于同步内容,该方法可能会抛出NetworkFailureException异常 + +private void syncContent() throws NetworkFailureException { + + int syncType; // 定义一个整型变量,用于存储同步类型 + + Cursor c = null; // 定义一个Cursor对象,用于数据库查询,初始化为null + + String gid; // 定义一个字符串变量,用于存储Google任务的ID + + Node node; // 定义一个Node对象,用于表示一个任务节点 + + + + // 清空本地删除ID的映射表 + + mLocalDeleteIdMap.clear(); + + + + // 如果同步已被取消,则直接返回 + + if (mCancelled) { + + return; + + } + + + + // 查询本地已删除的笔记(非系统笔记且不在回收站中的笔记) + + try { + + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + + "(type<>? AND parent_id=?)", new String[] { + + String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) + + }, null); + + // 如果查询结果不为空 + + if (c != null) { + + while (c.moveToNext()) { // 遍历查询结果 + + gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取Google任务的ID + + node = mGTaskHashMap.get(gid); // 从哈希表中获取对应的Node对象 + + if (node != null) { + + mGTaskHashMap.remove(gid); // 从哈希表中移除该节点 + + doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); // 执行远程删除同步操作 + + } + + + + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 将本地笔记ID添加到删除ID映射表中 + + } + + } else { + + Log.w(TAG, "failed to query trash folder"); // 如果查询失败,记录警告日志 + + } + + } finally { + + if (c != null) { + + c.close(); // 关闭Cursor对象 + + c = null; // 将Cursor对象置为null + + } + + } + + + + // 先同步文件夹 + + syncFolder(); + + + + // 查询数据库中存在的笔记(类型为笔记且不在回收站中的笔记) + + try { + + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + + "(type=? AND parent_id<>?)", new String[] { + + String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER) + + }, NoteColumns.TYPE + " DESC"); + + if (c != null) { + + while (c.moveToNext()) { // 遍历查询结果 + + gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取Google任务的ID + + node = mGTaskHashMap.get(gid); // 从哈希表中获取对应的Node对象 + + if (node != null) { + + mGTaskHashMap.remove(gid); // 从哈希表中移除该节点 + + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // 将Google任务ID和本地笔记ID添加到映射表中 + + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); // 将本地笔记ID和Google任务ID添加到映射表中 + + syncType = node.getSyncAction(c); // 获取同步类型 + + } else { + + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + + // 如果Google任务ID为空,则为本地新增 + + syncType = Node.SYNC_ACTION_ADD_REMOTE; + + } else { + + // 如果Google任务ID不为空但哈希表中无对应节点,则为远程删除 + + syncType = Node.SYNC_ACTION_DEL_LOCAL; + + } + + } + + doContentSync(syncType, node, c); // 执行同步操作 + + } + + } else { + + Log.w(TAG, "failed to query existing note in database"); // 如果查询失败,记录警告日志 + + } + + + + } finally { + + if (c != null) { + + c.close(); // 关闭Cursor对象 + + c = null; // 将Cursor对象置为null + + } + + } + + + + // 遍历哈希表中剩余的项目(即远程新增的项目) + + Iterator> iter = mGTaskHashMap.entrySet().iterator(); + + while (iter.hasNext()) { + + Map.Entry entry = iter.next(); + + node = entry.getValue(); + + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 执行本地新增同步操作 + + } + +} + // mCancelled can be set by another thread, so we neet to check one by + // one + // clear local delete table + // 检查是否未被取消,以避免在取消操作后执行不必要的步骤 + +if (!mCancelled) { + + // 尝试批量删除本地已标记为删除的笔记 + + if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { + + // 如果删除失败,抛出异常 + + throw new ActionFailureException("failed to batch-delete local deleted notes"); + + } + +} + + + +// 如果没有被取消,则刷新本地同步ID + +if (!mCancelled) { + + // 提交更新到GTaskClient实例(可能是与服务器同步的客户端) + + GTaskClient.getInstance().commitUpdate(); + + // 刷新本地同步ID + + refreshLocalSyncId(); + +} + + + +// 定义一个私有方法,用于同步文件夹 + +private void syncFolder() throws NetworkFailureException { + + Cursor c = null; // 声明一个Cursor对象,用于数据库查询 + + String gid; // 声明一个字符串变量,用于存储Google任务的ID + + Node node; // 声明一个Node对象,可能代表一个笔记或文件夹的节点 + + int syncType; // 声明一个整型变量,用于存储同步类型 + + + + // 如果操作被取消,则直接返回 + + if (mCancelled) { + + return; + + } + + + + // 查询根文件夹 + + try { + + // 使用ContentResolver查询根文件夹的信息 + + c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, + + Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); + + if (c != null) { + + c.moveToNext(); // 移动到查询结果的下一行(实际上是第一行) + + gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取Google任务的ID + + node = mGTaskHashMap.get(gid); // 从哈希表中获取对应的Node对象 + + if (node != null) { + + // 如果Node对象存在,则进行一系列更新操作 + + mGTaskHashMap.remove(gid); // 从哈希表中移除该Node + + mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); // 更新全局ID到本地ID的映射 + + mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); // 更新本地ID到全局ID的映射 + + // 如果根文件夹的名称不是默认的,则更新远程名称 + + if (!node.getName().equals( + + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) + + doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); // 执行内容同步 + + } else { + + // 如果Node对象不存在,则添加远程Node + + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); + + } + + } else { + + // 如果查询失败,则记录警告日志 + + Log.w(TAG, "failed to query root folder"); + + } + + } finally { + + // 确保Cursor被关闭 + + if (c != null) { + + c.close(); + + c = null; + + } + + } + + + + // 查询通话记录文件夹 + + // ...(这部分代码与上面的根文件夹查询逻辑类似,只是查询条件和处理的Node不同) + + + + // 查询本地存在的其他文件夹 + + try { + + // 查询除垃圾文件夹外的所有文件夹 + + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + + "(type=? AND parent_id<>?)", new String[] { + + String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) + + }, NoteColumns.TYPE + " DESC"); + + if (c != null) { + + while (c.moveToNext()) { + + gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取Google任务的ID + + node = mGTaskHashMap.get(gid); // 从哈希表中获取对应的Node对象 + + if (node != null) { + + // 如果Node对象存在,则进行一系列更新操作 + + mGTaskHashMap.remove(gid); // 从哈希表中移除该Node + + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // 更新全局ID到本地ID的映射 + + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); // 更新本地ID到全局ID的映射 + + syncType = node.getSyncAction(c); // 获取同步类型 + + } else { + + // 如果Node对象不存在,则根据条件判断是本地添加还是远程删除 + + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + + syncType = Node.SYNC_ACTION_ADD_REMOTE; // 本地添加 + + } else { + + syncType = Node.SYNC_ACTION_DEL_LOCAL; // 远程删除 + + } + + } + + // 执行内容同步 + + doContentSync(syncType, node, c); + + } + + } else { + + // 如果查询失败,则记录警告日志 + + Log.w(TAG, "failed to query existing folder"); + + } + + } finally { + + // 确保Cursor被关闭 + + if (c != null) { + + c.close(); + + c = null; + + } + + } + +} +//4 + + + // 遍历mGTaskListHashMap中的每个条目,用于远程添加文件夹 + +Iterator> iter = mGTaskListHashMap.entrySet().iterator(); + +while (iter.hasNext()) { + + Map.Entry entry = iter.next(); + + gid = entry.getKey(); // 获取条目的键(可能是任务列表的全局唯一标识符) + + node = entry.getValue(); // 获取条目的值(任务列表对象) + + // 如果mGTaskHashMap中已存在此gid的条目,则移除它,并调用doContentSync方法添加本地节点 + + if (mGTaskHashMap.containsKey(gid)) { + + mGTaskHashMap.remove(gid); + + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + + } + +} + + + +// 如果没有取消操作,则提交更新到GTaskClient + +if (!mCancelled) + + GTaskClient.getInstance().commitUpdate(); + +} + + + +// 根据同步类型和节点执行相应的同步操作 + +private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { + + if (mCancelled) { + + return; // 如果已取消,则直接返回 + + } + + + + MetaData meta; + + // 根据同步类型执行不同的操作 + + switch (syncType) { + + case Node.SYNC_ACTION_ADD_LOCAL: + + addLocalNode(node); // 添加本地节点 + + break; + + case Node.SYNC_ACTION_ADD_REMOTE: + + addRemoteNode(node, c); // 添加远程节点 + + break; + + case Node.SYNC_ACTION_DEL_LOCAL: + + // 从mMetaHashMap中根据cursor获取的gid获取MetaData对象,并删除节点 + + meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); + + if (meta != null) { + + GTaskClient.getInstance().deleteNode(meta); + + } + + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 添加本地删除ID到集合 + + break; + + case Node.SYNC_ACTION_DEL_REMOTE: + + // 从mMetaHashMap中根据节点的gid获取MetaData对象,并删除节点 + + meta = mMetaHashMap.get(node.getGid()); + + if (meta != null) { + + GTaskClient.getInstance().deleteNode(meta); + + } + + GTaskClient.getInstance().deleteNode(node); // 删除远程节点 + + break; + + case Node.SYNC_ACTION_UPDATE_LOCAL: + + updateLocalNode(node, c); // 更新本地节点 + + break; + + case Node.SYNC_ACTION_UPDATE_REMOTE: + + updateRemoteNode(node, c); // 更新远程节点 + + break; + + case Node.SYNC_ACTION_UPDATE_CONFLICT: + + // 冲突时,目前简单采用本地更新 + + updateRemoteNode(node, c); + + break; + + case Node.SYNC_ACTION_NONE: + + break; // 无操作 + + case Node.SYNC_ACTION_ERROR: + + default: + + throw new ActionFailureException("unkown sync action type"); // 抛出异常 + + } + +} + + + +// 添加本地节点的方法 + +private void addLocalNode(Node node) throws NetworkFailureException { + + if (mCancelled) { + + return; // 如果已取消,则直接返回 + + } + + + + SqlNote sqlNote; + + // 判断节点类型,如果是TaskList,则根据名称决定是否为特殊文件夹 + + if (node instanceof TaskList) { + + if (node.getName().equals( + + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { + + sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); // 默认文件夹 + + } else if (node.getName().equals( + + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { + + sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); // 通话记录文件夹 + + } else { + + sqlNote = new SqlNote(mContext); + + sqlNote.setContent(node.getLocalJSONFromContent()); // 设置节点内容 + + sqlNote.setParentId(Notes.ID_ROOT_FOLDER); // 设置父节点为根文件夹 + + } + + } else { // 如果不是TaskList,则处理为Task + + sqlNote = new SqlNote(mContext); + + JSONObject js = node.getLocalJSONFromContent(); + + try { + + // 处理节点内容中的note和data部分,检查ID是否已存在 + + if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { + + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + + if (note.has(NoteColumns.ID)) { + + long id = note.getLong(NoteColumns.ID); + + if (DataUtils.existInNoteDatabase(mContentResolver, id)) { + + // 如果ID已存在,则移除ID + + note.remove(NoteColumns.ID); + + } + + } + + } + + + + if (js.has(GTaskStringUtils.META_HEAD_DATA)) { + + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + + for (int i = 0; i < dataArray.length(); i++) { + + JSONObject data = dataArray.getJSONObject(i); + + if (data.has(DataColumns.ID)) { + + long dataId = data.getLong(DataColumns.ID); + + if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { + + // 如果数据ID已存在,则移除ID + + data.remove(DataColumns.ID); + + } + + } + + } + + } + + } catch (JSONException e) { + + Log.w(TAG, e.toString()); // 打印警告日志 + + e.printStackTrace(); // 打印异常堆栈 + + } + + sqlNote.setContent(js); // 设置节点内容 + + + + // 获取父节点的ID,并设置给当前节点 + + Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); + + if (parentId == null) { + + Log.e(TAG, "cannot find task's parent id locally"); // 打印错误日志 + + throw new ActionFailureException("cannot add local node"); // 抛出异常 + + } + + sqlNote.setParentId(parentId.longValue()); + + } +//5 + + + + // 创建一个本地节点 + +sqlNote.setGtaskId(node.getGid()); // 为SqlNote对象设置全局ID(Gid),这个Gid来自传入的Node对象 + +sqlNote.commit(false); // 提交SqlNote对象到数据库,false表示不是一次更新操作 + + + +// 更新Gid到Nid的映射 + +mGidToNid.put(node.getGid(), sqlNote.getId()); // 将Node的全局ID(Gid)映射到SqlNote的本地ID(Nid) + +mNidToGid.put(sqlNote.getId(), node.getGid()); // 同时,将SqlNote的本地ID(Nid)映射回全局ID(Gid) + + + +// 更新元数据 + +updateRemoteMeta(node.getGid(), sqlNote); // 调用函数更新远程元数据,传入Node的Gid和SqlNote对象 + + + +// 更新本地节点 + +private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { + + if (mCancelled) { // 检查操作是否被取消 + + return; + + } + + + + SqlNote sqlNote; + + // 更新本地笔记 + + sqlNote = new SqlNote(mContext, c); // 使用上下文和游标创建SqlNote对象 + + sqlNote.setContent(node.getLocalJSONFromContent()); // 设置SqlNote的内容为Node的本地JSON内容 + + + + // 获取父节点的ID,如果是任务则通过Gid映射获取,否则默认为根文件夹ID + + Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) + + : new Long(Notes.ID_ROOT_FOLDER); + + if (parentId == null) { // 如果父ID为空 + + Log.e(TAG, "cannot find task's parent id locally"); // 打印错误日志 + + throw new ActionFailureException("cannot update local node"); // 抛出操作失败异常 + + } + + sqlNote.setParentId(parentId.longValue()); // 设置SqlNote的父ID + + sqlNote.commit(true); // 提交更改到数据库,true表示是一次更新操作 + + + + // 更新元数据信息 + + updateRemoteMeta(node.getGid(), sqlNote); // 调用函数更新远程元数据 + +} + + + +// 添加远程节点 + +private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { + + if (mCancelled) { // 检查操作是否被取消 + + return; + + } + + + + SqlNote sqlNote = new SqlNote(mContext, c); // 使用上下文和游标创建SqlNote对象 + + Node n; + + + + // 根据SqlNote的类型进行不同的处理 + + if (sqlNote.isNoteType()) { // 如果是笔记类型 + + Task task = new Task(); // 创建一个Task对象 + + task.setContentByLocalJSON(sqlNote.getContent()); // 设置Task的内容为SqlNote的内容 + + + + String parentGid = mNidToGid.get(sqlNote.getParentId()); // 获取父节点的Gid + + if (parentGid == null) { // 如果父节点的Gid为空 + + Log.e(TAG, "cannot find task's parent tasklist"); // 打印错误日志 + + throw new ActionFailureException("cannot add remote task"); // 抛出操作失败异常 + + } + + mGTaskListHashMap.get(parentGid).addChildTask(task); // 将Task添加到对应的TaskList中 + + + + GTaskClient.getInstance().createTask(task); // 调用GTaskClient的实例创建任务 + + n = (Node) task; // 将Task对象转换为Node对象 + + + + // 更新远程元数据 + + updateRemoteMeta(task.getGid(), sqlNote); + + } else { // 如果不是笔记类型(假设是文件夹) + + TaskList tasklist = null; + + + + // 根据SqlNote的ID和名称判断文件夹是否已存在 + + String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; // 获取文件夹前缀 + + if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) + + folderName += GTaskStringUtils.FOLDER_DEFAULT; // 如果是根文件夹,添加默认后缀 + + else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER) + + folderName += GTaskStringUtils.FOLDER_CALL_NOTE; // 如果是通话记录文件夹,添加对应后缀 + + else + + folderName += sqlNote.getSnippet(); // 否则,添加SqlNote的摘要作为后缀 + + + + // 遍历所有TaskList,查找是否存在同名文件夹 + + Iterator> iter = mGTaskListHashMap.entrySet().iterator(); + + while (iter.hasNext()) { + + Map.Entry entry = iter.next(); + + String gid = entry.getKey(); + + TaskList list = entry.getValue(); + + + + if (list.getName().equals(folderName)) { // 如果找到同名文件夹 + + tasklist = list; // 将找到的文件夹赋值给tasklist + + if (mGTaskHashMap.containsKey(gid)) { + + mGTaskHashMap.remove(gid); // 从另一个映射中移除该Gid,避免重复 + + } + + break; + + } + + } + + + + // 如果没有找到同名文件夹,则创建一个新的 + + if (tasklist == null) { + + tasklist = new TaskList(); // 创建新的TaskList对象 + + tasklist.setContentByLocalJSON(sqlNote.getContent()); // 设置内容 + + GTaskClient.getInstance().createTaskList(tasklist); // 调用GTaskClient的实例创建任务列表 + + mGTaskListHashMap.put(tasklist.getGid(), tasklist); // 将新创建的TaskList添加到映射中 + + } + + n = (Node) tasklist; // 将TaskList对象转换为Node对象 + + } + + + + // 更新本地SqlNote的Gid和元数据 + + sqlNote.setGtaskId(n.getGid()); // 设置SqlNote的全局ID(Gid) + + sqlNote.commit(false); // 提交更改到数据库,false表示不是更新操作 + + sqlNote.resetLocalModified(); // 重置SqlNote的本地修改标志 + + sqlNote.commit(true); // 再次提交更改,true表示是更新操作 + + + + // 更新Gid到Nid和Nid到Gid的映射 + + mGidToNid.put(n.getGid(), sqlNote.getId()); // 更新Gid到Nid的映射 + + mNidToGid.put(sqlNote.getId(), n.getGid()); // 更新Nid到Gid的映射 + +} + + + +// 更新远程节点 + +private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { + + if (mCancelled) { // 检查操作是否被取消 + + return; + + } + + + + SqlNote sqlNote = new SqlNote(mContext, c); // 使用上下文和游标创建SqlNote对象 + + + + // 更新远程节点 + + node.setContentByLocalJSON(sqlNote.getContent()); // 更新Node的内容为SqlNote的内容 + + GTaskClient.getInstance().addUpdateNode(node); // 调用GTaskClient的实例更新节点 + + + + // 更新元数据 + + updateRemoteMeta(node.getGid(), sqlNote); // 调用函数更新远程元数据 + + + + // 如果需要,移动任务 + + if (sqlNote.isNoteType()) { // 如果是笔记类型(任务) + + Task task = (Task) node; // 将Node转换为Task对象 + + TaskList preParentList = task.getParent(); // 获取任务之前的父TaskList + + + + String curParentGid = mNidToGid.get(sqlNote.getParentId()); // 获取当前父节点的Gid + + if (curParentGid == null) { // 如果当前父节点的Gid为空 + + Log.e(TAG, "cannot find task's parent tasklist"); // 打印错误日志 + + throw new ActionFailureException("cannot update remote task"); // 抛出操作失败异常 + + } + + TaskList curParentList = mGTaskListHashMap.get(curParentGid); // 获取当前的父TaskList + + + + // 如果任务的父TaskList发生了变化,则进行移动操作 + + if (preParentList != curParentList) { + + preParentList.removeChildTask(task); // 从之前的父TaskList中移除任务 + + curParentList.addChildTask(task); // 将任务添加到当前的父TaskList中 + + GTaskClient.getInstance().moveTask(task, preParentList, curParentList); // 调用GTaskClient的实例移动任务 + + } + + } + +} +//6 + + + // 清除本地修改标志 + +sqlNote.resetLocalModified(); + +// 提交sqlNote的更改到数据库,true参数可能表示强制提交或某种特殊模式 + +sqlNote.commit(true); + +} + + + +// 更新远程元数据的方法,传入笔记的全局唯一标识符(gid)和SqlNote对象 + +private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { + + // 检查sqlNote对象非空且为笔记类型 + + if (sqlNote != null && sqlNote.isNoteType()) { + + // 从哈希映射中获取gid对应的MetaData对象 + + MetaData metaData = mMetaHashMap.get(gid); + + if (metaData != null) { + + // 如果MetaData对象已存在,更新其内容 + + metaData.setMeta(gid, sqlNote.getContent()); + + // 向GTaskClient实例添加更新节点的任务 + + GTaskClient.getInstance().addUpdateNode(metaData); + + } else { + + // 如果MetaData对象不存在,创建一个新的 + + metaData = new MetaData(); + + // 设置MetaData的内容 + + metaData.setMeta(gid, sqlNote.getContent()); + + // 向任务列表添加子任务 + + mMetaList.addChildTask(metaData); + + // 将MetaData对象添加到哈希映射中 + + mMetaHashMap.put(gid, metaData); + + // 向GTaskClient实例添加创建任务的任务 + + GTaskClient.getInstance().createTask(metaData); + + } + + } + +} + + + +// 刷新本地同步ID的方法 + +private void refreshLocalSyncId() throws NetworkFailureException { + + // 如果同步已取消,则直接返回 + + if (mCancelled) { + + return; + + } + + + + // 清除哈希映射和任务列表,准备重新初始化 + + mGTaskHashMap.clear(); + + mGTaskListHashMap.clear(); + + mMetaHashMap.clear(); + + // 初始化任务列表 + + initGTaskList(); + + + + // 初始化Cursor对象,用于查询数据库 + + Cursor c = null; + + try { + + // 查询笔记内容URI,过滤掉系统类型和垃圾桶文件夹的笔记 + + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + + "(type<>? AND parent_id<>?)", new String[] { + + String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) + + }, NoteColumns.TYPE + " DESC"); + + // 如果Cursor不为空,遍历查询结果 + + if (c != null) { + + while (c.moveToNext()) { + + // 获取笔记的全局唯一标识符 + + String gid = c.getString(SqlNote.GTASK_ID_COLUMN); + + // 从哈希映射中获取对应的Node对象 + + Node node = mGTaskHashMap.get(gid); + + if (node != null) { + + // 如果Node对象存在,从哈希映射中移除 + + mGTaskHashMap.remove(gid); + + // 更新本地数据库中该笔记的同步ID为Node的最后修改时间 + + ContentValues values = new ContentValues(); + + values.put(NoteColumns.SYNC_ID, node.getLastModified()); + + mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, + + c.getLong(SqlNote.ID_COLUMN)), values, null, null); + + } else { + + // 如果Node对象不存在,记录错误日志 + + Log.e(TAG, "something is missed"); + + // 抛出异常,表示一些本地项目在同步后没有gid + + throw new ActionFailureException( + + "some local items don't have gid after sync"); + + } + + } + + } else { + + // 如果查询失败,记录警告日志 + + Log.w(TAG, "failed to query local note to refresh sync id"); + + } + + } finally { + + // 最后确保Cursor被关闭 + + if (c != null) { + + c.close(); + + c = null; + + } + + } + +} + + + +// 获取同步账户名称的方法 + +public String getSyncAccount() { + + // 返回GTaskClient实例的同步账户名称 + + return GTaskClient.getInstance().getSyncAccount().name; + +} + + + +// 取消同步的方法 + +public void cancelSync() { + + // 设置取消标志为true + + mCancelled = true; + +} diff --git a/GTaskSyncService.java b/GTaskSyncService.java new file mode 100644 index 0000000..d909ef5 --- /dev/null +++ b/GTaskSyncService.java @@ -0,0 +1,269 @@ +/* + * 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. + */ + +// 定义一个名为GTaskSyncService的服务类,继承自Service,用于同步Google Tasks + +package net.micode.notes.gtask.remote; + + + +import android.app.Activity; + +import android.app.Service; + +import android.content.Context; + +import android.content.Intent; + +import android.os.Bundle; + +import android.os.IBinder; + + + +// 声明服务类 + +public class GTaskSyncService extends Service { + + // 定义一个常量字符串,用于标识同步操作的类型 + + public final static String ACTION_STRING_NAME = "sync_action_type"; + + + + // 定义同步操作的常量整型值 + + public final static int ACTION_START_SYNC = 0; // 开始同步 + + public final static int ACTION_CANCEL_SYNC = 1; // 取消同步 + + public final static int ACTION_INVALID = 2; // 无效操作 + + + + // 定义广播接收器名称和键值对常量 + + public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; + + public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; // 是否正在同步 + + public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; // 同步进度消息 + + + + // 定义一个静态的异步任务变量,用于执行同步操作 + + private static GTaskASyncTask mSyncTask = null; + + + + // 定义一个静态的同步进度消息变量 + + private static String mSyncProgress = ""; + + + + // 开始同步操作的方法 + + private void startSync() { + + if (mSyncTask == null) { // 如果当前没有正在执行的同步任务 + + mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { + + // 定义一个完成监听器,当异步任务完成时调用 + + public void onComplete() { + + mSyncTask = null; // 清空同步任务变量 + + sendBroadcast(""); // 发送广播,通知同步完成 + + stopSelf(); // 停止服务 + + } + + }); + + sendBroadcast(""); // 发送广播,可能用于通知开始同步 + + mSyncTask.execute(); // 执行异步任务 + + } + + } + + + + // 取消同步操作的方法 + + private void cancelSync() { + + if (mSyncTask != null) { // 如果当前有正在执行的同步任务 + + mSyncTask.cancelSync(); // 取消同步任务 + + } + + } + + + + // 当服务被创建时调用 + + @Override + + public void onCreate() { + + mSyncTask = null; // 清空同步任务变量 + + } + + + + // 当服务被启动时调用 + + @Override + + public int onStartCommand(Intent intent, int flags, int startId) { + + Bundle bundle = intent.getExtras(); // 获取启动服务的Intent中的附加数据 + + if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { // 如果附加数据不为空且包含操作类型 + + switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { // 根据操作类型执行相应的操作 + + case ACTION_START_SYNC: + + startSync(); // 开始同步 + + break; + + case ACTION_CANCEL_SYNC: + + cancelSync(); // 取消同步 + + break; + + default: + + break; + + } + + return START_STICKY; // 如果服务被杀死,系统会在内存可用时重启服务并传递最后一个Intent + + } + + return super.onStartCommand(intent, flags, startId); // 如果没有指定操作类型,则调用父类方法 + + } + + + + // 当系统内存低时调用 + + @Override + + public void onLowMemory() { + + if (mSyncTask != null) { // 如果当前有正在执行的同步任务 + + mSyncTask.cancelSync(); // 取消同步任务以释放内存 + + } + + } + + + + // 绑定服务时调用,此处不提供服务绑定支持 + + public IBinder onBind(Intent intent) { + + return null; + + } + + + + // 发送广播通知同步状态的方法 + + public void sendBroadcast(String msg) { + + mSyncProgress = msg; // 更新同步进度消息 + + Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); // 创建一个新的Intent + + intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); // 添加是否正在同步的键值对 + + intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); // 添加同步进度消息的键值对 + + sendBroadcast(intent); // 发送广播 + + } + + + + // 从Activity启动同步服务的方法 + + public static void startSync(Activity activity) { + + GTaskManager.getInstance().setActivityContext(activity); // 设置活动上下文 + + Intent intent = new Intent(activity, GTaskSyncService.class); // 创建一个新的Intent + + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); // 添加操作类型的键值对 + + activity.startService(intent); // 启动服务 + + } + + + + // 从Context取消同步服务的方法 + + public static void cancelSync(Context context) { + + Intent intent = new Intent(context, GTaskSyncService.class); // 创建一个新的Intent + + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); // 添加操作类型的键值对 + + context.startService(intent); // 启动服务 + + } + + + + // 判断是否正在同步的方法 + + public static boolean isSyncing() { + + return mSyncTask != null; // 返回是否有正在执行的同步任务 + + } + + + + // 获取同步进度消息的方法 + + public static String getProgressString() { + + return mSyncProgress; // 返回同步进度消息 + + } + +} \ No newline at end of file diff --git a/Note2.java b/Note2.java new file mode 100644 index 0000000..84b2c47 --- /dev/null +++ b/Note2.java @@ -0,0 +1,622 @@ +/* + * 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; + + + +// 导入所需的Android和Java类 + +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; + + + +// Note类,用于表示和管理笔记数据 + +public class Note { + + // 用于存储笔记的差异值(即需要更新的字段) + + private ContentValues mNoteDiffValues; + + // 用于存储笔记的具体数据(如文本数据、通话数据等) + + private NoteData mNoteData; + + // 日志标签 + + private static final String TAG = "Note"; + + + + /** + + * 创建一个新的笔记ID,用于将新笔记添加到数据库中 + + */ + + public static synchronized long getNewNoteId(Context context, long folderId) { + + // 创建一个新的ContentValues对象,用于存储要插入数据库的数据 + + 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); + + // 设置笔记所属的文件夹ID + + values.put(NoteColumns.PARENT_ID, folderId); + + // 向数据库插入新笔记,并获取其Uri + + Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); + + + + long noteId = 0; + + try { + + // 从Uri中提取新笔记的ID + + noteId = Long.valueOf(uri.getPathSegments().get(1)); + + } catch (NumberFormatException e) { + + // 如果提取ID失败,则记录错误日志 + + Log.e(TAG, "Get note id error :" + e.toString()); + + noteId = 0; + + } + + // 如果ID为-1,则抛出异常 + + if (noteId == -1) { + + throw new IllegalStateException("Wrong note id:" + noteId); + + } + + // 返回新笔记的ID + + return noteId; + + } + + + + // Note类的构造函数 + + public Note() { + + // 初始化差异值存储和内容数据存储 + + mNoteDiffValues = new ContentValues(); + + mNoteData = new NoteData(); + + } + + + + // 设置笔记的差异值 + + public void setNoteValue(String key, String value) { + + mNoteDiffValues.put(key, value); + + // 标记笔记为本地修改 + + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + + // 更新笔记的修改时间 + + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + + } + + + + // 设置笔记的文本数据 + + public void setTextData(String key, String value) { + + mNoteData.setTextData(key, value); + + } + + + + // 设置文本数据的ID + + public void setTextDataId(long id) { + + mNoteData.setTextDataId(id); + + } + + + + // 获取文本数据的ID + + public long getTextDataId() { + + return mNoteData.mTextDataId; + + } + + + + // 设置通话数据的ID + + public void setCallDataId(long id) { + + mNoteData.setCallDataId(id); + + } + + + + // 设置通话数据 + + public void setCallData(String key, String value) { + + mNoteData.setCallData(key, value); + + } + + + + // 检查笔记是否有本地修改 + + public boolean isLocalModified() { + + // 如果有差异值或内容数据有本地修改,则返回true + + return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); + + } + + + + // 同步笔记到数据库 + + public boolean syncNote(Context context, long noteId) { + + // 检查笔记ID是否有效 + + if (noteId <= 0) { + + throw new IllegalArgumentException("Wrong note id:" + noteId); + + } + + + + // 如果没有本地修改,则直接返回true + + if (!isLocalModified()) { + + return true; + + } + + + + // 更新笔记的差异值到数据库 + + // 注意:即使更新失败,也会继续执行后续代码以确保数据安全性 + + if (context.getContentResolver().update( + + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, + + null) == 0) { + + // 如果更新失败,则记录错误日志 + + Log.e(TAG, "Update note error, should not happen"); + + // 不返回,继续执行 + + } + + // 清空差异值存储,因为已经同步到数据库 + + mNoteDiffValues.clear(); + + + + // 如果内容数据有本地修改,则尝试将其同步到数据库 + + if (mNoteData.isLocalModified() + + && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { + + // 如果同步失败,则返回false + + return false; + + } + + + + // 如果所有操作都成功,则返回true + + return true; + + } + +} + // 定义一个私有的内部类NoteData + +private class NoteData { + + // 文本数据的ID + + private long mTextDataId; + + + + // 用于存储文本数据的ContentValues对象 + + private ContentValues mTextDataValues; + + + + // 通话数据的ID + + private long mCallDataId; + + + + // 用于存储通话数据的ContentValues对象 + + private ContentValues mCallDataValues; + + + + // 用于日志记录的标签 + + private static final String TAG = "NoteData"; + + + + // 构造函数,初始化成员变量 + + public NoteData() { + + mTextDataValues = new ContentValues(); + + mCallDataValues = new ContentValues(); + + mTextDataId = 0; + + mCallDataId = 0; + + } + + + + // 检查本地数据是否被修改 + + boolean isLocalModified() { + + return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; + + } + + + + // 设置文本数据的ID + + void setTextDataId(long id) { + + if(id <= 0) { + + // 如果ID小于等于0,抛出异常 + + throw new IllegalArgumentException("Text data id should larger than 0"); + + } + + mTextDataId = id; + + } + + + + // 设置通话数据的ID + + void setCallDataId(long id) { + + if (id <= 0) { + + // 如果ID小于等于0,抛出异常 + + throw new IllegalArgumentException("Call data id should larger than 0"); + + } + + mCallDataId = id; + + } + + + + // 设置通话数据 + + void setCallData(String key, String value) { + + mCallDataValues.put(key, value); + + // 下面的两行代码似乎有误,因为mNoteDiffValues在类中未定义 + + // mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + + // mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + + } + + + + // 设置文本数据 + + void setTextData(String key, String value) { + + mTextDataValues.put(key, value); + + // 同样,下面的两行代码因为mNoteDiffValues未定义而存在问题 + + // mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + + // mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + + } + + + + // 将数据推送到内容提供者 + + Uri pushIntoContentResolver(Context context, long noteId) { + + // 检查noteId的有效性 + + if (noteId <= 0) { + + throw new IllegalArgumentException("Wrong note id:" + noteId); + + } + + + + // 用于存储内容提供者操作的列表 + + ArrayList operationList = new ArrayList(); + + ContentProviderOperation.Builder builder = null; + + + + // 如果文本数据有变化 + + if(mTextDataValues.size() > 0) { + + // 添加noteId到文本数据 + + 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 { + + // 从返回的URI中提取新插入的ID + + setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); + + } catch (NumberFormatException e) { + + // 如果提取ID失败,记录错误并清空数据 + + 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) { + + // 添加noteId到通话数据 + + 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 { + + // 从返回的URI中提取新插入的ID + + setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); + + } catch (NumberFormatException e) { + + // 如果提取ID失败,记录错误并清空数据 + + 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); + + // 根据执行结果返回相应的URI + + 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; + + } + + } + + // 如果没有操作需要执行,返回null + + return null; + + } + +} diff --git a/WorkingNote.java b/WorkingNote.java new file mode 100644 index 0000000..dd46ac6 --- /dev/null +++ b/WorkingNote.java @@ -0,0 +1,812 @@ +/* + * 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; + + + +// 导入所需的Android和项目内部的类 + +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; + + + +// 定义一个用于处理工作中的笔记的类 + +public class WorkingNote { + + // 笔记对象 + + private Note mNote; + + // 笔记ID + + private long mNoteId; + + // 笔记内容 + + private String mContent; + + // 笔记模式 + + private int mMode; + + + + // 提醒日期 + + private long mAlertDate; + + + + // 修改日期 + + private long mModifiedDate; + + + + // 背景颜色ID + + private int mBgColorId; + + + + // 小部件ID + + private int mWidgetId; + + + + // 小部件类型 + + private int mWidgetType; + + + + // 文件夹ID + + private long mFolderId; + + + + // 上下文 + + private Context mContext; + + + + // 日志标签 + + private static final String TAG = "WorkingNote"; + + + + // 标记笔记是否已被删除 + + private boolean mIsDeleted; + + + + // 笔记设置变化监听器 + + private NoteSettingChangedListener mNoteSettingStatusListener; + + + + // 用于从数据表中查询笔记内容的列 + + public static final String[] DATA_PROJECTION = new String[] { + + DataColumns.ID, + + DataColumns.CONTENT, + + DataColumns.MIME_TYPE, + + DataColumns.DATA1, + + DataColumns.DATA2, + + DataColumns.DATA3, + + DataColumns.DATA4, + + }; + + + + // 用于从笔记表中查询笔记元数据的列 + + public static final String[] NOTE_PROJECTION = new String[] { + + NoteColumns.PARENT_ID, + + NoteColumns.ALERTED_DATE, + + NoteColumns.BG_COLOR_ID, + + NoteColumns.WIDGET_ID, + + NoteColumns.WIDGET_TYPE, + + NoteColumns.MODIFIED_DATE + + }; + + + + // 定义从DATA_PROJECTION数组中获取数据的列索引 + + private static final int DATA_ID_COLUMN = 0; + + private static final int DATA_CONTENT_COLUMN = 1; + + private static final int DATA_MIME_TYPE_COLUMN = 2; + + private static final int DATA_MODE_COLUMN = 3; // 注意:这里可能有误,因为DATA_PROJECTION中没有直接对应MODE的列 + + + + // 定义从NOTE_PROJECTION数组中获取数据的列索引 + + private static final int NOTE_PARENT_ID_COLUMN = 0; + + private static final int NOTE_ALERTED_DATE_COLUMN = 1; + + private static final int NOTE_BG_COLOR_ID_COLUMN = 2; + + private static final int NOTE_WIDGET_ID_COLUMN = 3; + + private static final int NOTE_WIDGET_TYPE_COLUMN = 4; + + private static final int NOTE_MODIFIED_DATE_COLUMN = 5; + + + + // 构造一个新笔记对象 + + private WorkingNote(Context context, long folderId) { + + mContext = context; + + mAlertDate = 0; + + mModifiedDate = System.currentTimeMillis(); // 设置当前时间为修改日期 + + mFolderId = folderId; + + mNote = new Note(); // 初始化Note对象 + + mNoteId = 0; // 新笔记ID为0,表示未保存 + + mIsDeleted = false; + + mMode = 0; // 初始化模式 + + mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 初始化小部件类型为无效 + + } + + + + // 构造一个已存在的笔记对象 + + private WorkingNote(Context context, long noteId, long folderId) { + + mContext = context; + + mNoteId = noteId; + + mFolderId = folderId; + + mIsDeleted = false; + + mNote = new Note(); + + loadNote(); // 加载笔记数据 + + } + + + + // 从数据库中加载笔记元数据 + + private void loadNote() { + + // 使用内容解析器查询笔记元数据 + + Cursor cursor = mContext.getContentResolver().query( + + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, + + null, null); + + + + if (cursor != null) { + + if (cursor.moveToFirst()) { + + // 从cursor中读取数据并赋值给成员变量 + + mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); + + mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); + + mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); + + 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 { + + // 如果查询失败,记录错误日志并抛出异常 + + Log.e(TAG, "No note with id:" + mNoteId); + + throw new IllegalArgumentException("Unable to find note with id " + mNoteId); + + } + + loadNoteData(); // 加载笔记内容数据 + + } + + + + // 从数据库中加载笔记内容数据 + + private void loadNoteData() { + + // 使用内容解析器查询笔记内容数据 + + 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 { + + // 根据MIME类型处理不同的笔记数据 + + String type = cursor.getString(DATA_MIME_TYPE_COLUMN); + + if (DataConstants.NOTE.equals(type)) { + + mContent = cursor.getString(DATA_CONTENT_COLUMN); + + mMode = cursor.getInt(DATA_MODE_COLUMN); // 注意:这里可能有问题,因为前面提到DATA_PROJECTION中没有直接对应MODE的列 + + mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); + + } else if (DataConstants.CALL_NOTE.equals(type)) { + + 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); + + } + + } + + + + // 创建一个新的空笔记对象 + + public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, + + int widgetType, int defaultBgColorId) { + + WorkingNote note = new WorkingNote(context, folderId); // 调用私有构造函数创建新对象 + + note.setBgColorId(defaultBgColorId); // 设置背景颜色ID + + note.setWidgetId(widgetId); // 设置小部件ID + + note.setWidgetType(widgetType); // 设置小部件类型 + + return note; // 返回新创建的笔记对象 + + } + + + + // 根据ID加载一个已存在的笔记对象 + + public static WorkingNote load(Context context, long id) { + + return new WorkingNote(context, id, 0); // 调用私有构造函数加载已存在的笔记对象,文件夹ID默认为0 + + } + +} + // 定义一个同步方法,用于保存笔记 + +public synchronized boolean saveNote() { + + // 检查笔记是否值得保存 + + if (isWorthSaving()) { + + // 如果笔记在数据库中不存在 + + if (!existInDatabase()) { + + // 为笔记分配一个新的ID + + if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { + + // 如果分配ID失败,记录错误日志并返回false + + Log.e(TAG, "Create new note fail with id:" + mNoteId); + + return false; + + } + + } + + + + // 同步笔记数据到数据库 + + mNote.syncNote(mContext, mNoteId); + + + + // 如果存在与笔记关联的widget,则更新widget内容 + + if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID + + && mWidgetType != Notes.TYPE_WIDGET_INVALIDE + + && mNoteSettingStatusListener != null) { + + mNoteSettingStatusListener.onWidgetChanged(); + + } + + // 返回true表示保存成功 + + return true; + + } else { + + // 如果笔记不值得保存,返回false + + return false; + + } + +} + + + +// 判断笔记是否在数据库中已存在 + +public boolean existInDatabase() { + + return mNoteId > 0; + +} + + + +// 判断笔记是否值得保存 + +private boolean isWorthSaving() { + + // 如果笔记被标记为删除,或者内容为空且未存在于数据库中,或者存在于数据库中但未本地修改,则不值得保存 + + if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) + + || (existInDatabase() && !mNote.isLocalModified())) { + + return false; + + } else { + + return true; + + } + +} + + + +// 设置笔记设置状态改变的监听器 + +public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { + + mNoteSettingStatusListener = l; + +} + + + +// 设置笔记的提醒日期 + +public void setAlertDate(long date, boolean set) { + + // 如果新的日期与当前日期不同,则更新日期 + + if (date != mAlertDate) { + + mAlertDate = date; + + mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); + + } + + // 如果设置了监听器,通知监听器提醒日期已改变 + + if (mNoteSettingStatusListener != null) { + + mNoteSettingStatusListener.onClockAlertChanged(date, set); + + } + +} + + + +// 标记笔记为已删除或未删除 + +public void markDeleted(boolean mark) { + + mIsDeleted = mark; + + // 如果存在与笔记关联的widget,并且设置了监听器,则通知监听器widget已改变 + + if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID + + && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { + + mNoteSettingStatusListener.onWidgetChanged(); + + } + +} + + + +// 设置笔记的背景颜色ID + +public void setBgColorId(int id) { + + // 如果新的颜色ID与当前不同,则更新颜色ID + + if (id != mBgColorId) { + + mBgColorId = id; + + // 如果设置了监听器,通知监听器背景颜色已改变 + + if (mNoteSettingStatusListener != null) { + + mNoteSettingStatusListener.onBackgroundColorChanged(); + + } + + mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); + + } + +} + + + +// 设置笔记的清单模式 + +public void setCheckListMode(int mode) { + + // 如果新的模式与当前模式不同,则更新模式 + + if (mMode != mode) { + + // 如果设置了监听器,通知监听器清单模式已改变 + + if (mNoteSettingStatusListener != null) { + + mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); + + } + + mMode = mode; + + mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); + + } + +} + + + +// 设置笔记的widget类型 + +public void setWidgetType(int type) { + + // 如果新的类型与当前类型不同,则更新类型 + + if (type != mWidgetType) { + + mWidgetType = type; + + mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); + + } + +} + + + +// 设置笔记的widget ID + +public void setWidgetId(int id) { + + // 如果新的ID与当前ID不同,则更新ID + + if (id != mWidgetId) { + + mWidgetId = id; + + mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); + + } + +} + + + +// 设置笔记的工作文本内容 + +public void setWorkingText(String text) { + + // 如果新的文本与当前文本不同,则更新文本 + + if (!TextUtils.equals(mContent, text)) { + + mContent = text; + + mNote.setTextData(DataColumns.CONTENT, mContent); + + } + +} + + + +// 将笔记转换为通话记录笔记 + +public void convertToCallNote(String phoneNumber, long callDate) { + + mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); + + mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); + + mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); + +} + + + +// 判断笔记是否有提醒日期 + +public boolean hasClockAlert() { + + return (mAlertDate > 0 ? true : false); + +} + + + +// 获取笔记的内容 + +public String getContent() { + + return mContent; + +} + + + +// 获取笔记的提醒日期 + +public long getAlertDate() { + + return mAlertDate; + +} + + + +// 获取笔记的最后修改日期 + +public long getModifiedDate() { + + return mModifiedDate; + +} + + + +// 获取笔记的背景颜色资源ID + +public int getBgColorResId() { + + return NoteBgResources.getNoteBgResource(mBgColorId); + +} + + + +// 获取笔记的背景颜色ID + +public int getBgColorId() { + + return mBgColorId; + +} + + + +// 获取笔记的标题背景资源ID + +public int getTitleBgResId() { + + return NoteBgResources.getNoteTitleBgResource(mBgColorId); + +} + + + +// 获取笔记的清单模式 + +public int getCheckListMode() { + + return mMode; + +} + + + +// 获取笔记的ID + +public long getNoteId() { + + return mNoteId; + +} + + + +// 获取笔记所属的文件夹ID + +public long getFolderId() { + + return mFolderId; + +} + + + +// 获取笔记的widget ID + +public int getWidgetId() { + + return mWidgetId; + +} + + + +// 获取笔记的widget类型 + +public int getWidgetType() { + + return mWidgetType; + +} + + + +// 定义笔记设置状态改变的监听器接口 + +public interface NoteSettingChangedListener { + + // 当笔记的背景颜色改变时调用 + + void onBackgroundColorChanged(); + + + + // 当用户设置提醒时调用 + + void onClockAlertChanged(long date, boolean set); + + + + // 当用户通过widget创建笔记时调用 + + void onWidgetChanged(); + + + + // 当用户切换清单模式和普通模式时调用 + + void onCheckListModeChanged(int oldMode, int newMode); + +}