Compare commits

...

66 Commits

Author SHA1 Message Date
p537am6nb 86bc8aede7 质量分析报告
7 months ago
p537am6nb d0787fb6a1 Delete '小米便签开源代码的质量分析报告 .docx'
7 months ago
p537am6nb 8bc8bf67df Delete 'test1.txt'
7 months ago
p537am6nb 68f4656081 Delete 'test.txt'
7 months ago
p537am6nb 2194c41278 Delete '新建 文本文档.txt'
7 months ago
p537am6nb f8b381530a Merge pull request 'Tianshuping' (#6) from Tianshuping into main
7 months ago
p537am6nb 22545fe501 Delete 'test123.txt'
7 months ago
p537am6nb 8c28c0da40 Delete 'test.txt'
7 months ago
p537am6nb 55d426516c Delete '泛读报告.docx'
7 months ago
p537am6nb 262fb4353d Delete '小米便签开源代码的质量分析报告 - 副本.docx'
7 months ago
px5un8fk3 dfa31af012 Merge pull request 'GaoXiaoRui分支合并' (#5) from gaoxiaorui into main
7 months ago
p537am6nb 17568fc5ce Merge pull request 'Zhangying分支合并' (#4) from zhangying into main
7 months ago
git1 b2fe51255a 泛读报告
7 months ago
git1 1b07cff530 质量分析报告
7 months ago
git1 e3642bec52 注释
7 months ago
git1 c3d6b9ec7c 测试
7 months ago
p537am6nb d64df43354 Delete 'test.txt'
7 months ago
p537am6nb 8838956b8e Delete 'TaskList.java'
7 months ago
p537am6nb 123af3fb20 Delete 'Task.java'
7 months ago
p537am6nb cd68849ffd Delete 'SqlNote.java'
7 months ago
p537am6nb 9da56a265a Delete 'SqlData.java'
7 months ago
p537am6nb 035bd53ede Delete 'README.md'
7 months ago
p537am6nb 28ec6fd4f4 Delete 'NotesProvider.java'
7 months ago
p537am6nb 3f2e7e0095 Delete 'NotesDatabaseHelper.java'
7 months ago
p537am6nb e37aeeae8e Delete 'Notes.java'
7 months ago
p537am6nb 7e95c7ffe7 Delete 'Node.java'
7 months ago
p537am6nb 46bb8f30d9 Delete 'NetworkFailureException.java'
7 months ago
p537am6nb 6e72ddf8b9 Delete 'MetaData.java'
7 months ago
p537am6nb cfbf2a4cf4 Delete 'Contact.java'
7 months ago
p537am6nb 02e3690d32 Delete 'ActionFailureException.java'
7 months ago
p537am6nb 85be1a1c2f Delete '小米便签开源代码的质量分析报告 .docx'
7 months ago
p537am6nb d509a5e0f3 Delete '小米便签开源代码的泛读报告.docx'
7 months ago
p537am6nb 06452b5b83 Merge pull request 'Zhangying分支的合并' (#1) from zhangying into main
7 months ago
git1 93918c3607 泛读报告
8 months ago
git1 cb58f36278 质量分析报告
8 months ago
git1 9a6a640454 泛读报告
8 months ago
git1 24c2e8edf4 Merge branch 'zhangying' of https://bdgit.educoder.net/p537am6nb/xiaomi-Notes into zhangying
8 months ago
git1 40282f5ed2 小米便签开源代码的泛读报告
8 months ago
YourUsername c7fd833278 注释
8 months ago
YourUsername 2a6b40c793 注释
8 months ago
YourUsername e8168ebae4 注释
8 months ago
YourUsername 2c300a58d5 注释
8 months ago
YourUsername 5b2209fea2 注释
8 months ago
YourUsername fe8584a14d 注释
8 months ago
YourUsername c3813a2e67 注释
8 months ago
YourUsername 385979b4a5 注释
8 months ago
YourUsername c633aaddd2 注释
8 months ago
TSP 17aae1da8e 测试
8 months ago
TSP e37688903c 测试
8 months ago
TSP 950c0bd9a1 测试
8 months ago
p537am6nb a5eaca8173 注释
8 months ago
p537am6nb 7a55ecf76b Update Contact.java
8 months ago
git1 12138f8035 注释
8 months ago
git1 2258d7fe83 注释
8 months ago
git1 bd22fa320b 注释
8 months ago
git1 8ad87c46db 注释
8 months ago
git1 3de7521689 注释
8 months ago
git1 52ee881247 注释
8 months ago
git1 318c0bf133 注释
8 months ago
git1 7876ce7681 注释
8 months ago
git1 745cffb593 注释
8 months ago
git1 55fe643b86 注释
10 months ago
git1 aa9422ea79 注释
10 months ago
git1 ad0e8103d9 注释
10 months ago
git1 8cde369c9e test
10 months ago
git1 82bb6319a2 test
10 months ago

@ -0,0 +1,87 @@
/*
* 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.
*/
/*
`ActionFailureException` Java `RuntimeException`
`net.micode.notes.gtask.exception`
*/
// **包声明和导入**
package net.micode.notes.gtask.exception;
//这行代码定义了该类所在的包名:`net.micode.notes.gtask.exception`。通常包用于组织类和接口,并为类提供命名空间。
//**类声明**
public class ActionFailureException extends RuntimeException {
//`ActionFailureException` 继承自 `RuntimeException`,意味着它是一个 **未检查异常unchecked exception**。
// `RuntimeException` 是 `Exception` 类的子类,通常用于表示程序中的错误,通常不需要强制捕获或声明。
//- 异常的名字 `ActionFailureException` 表示某种操作失败的情况。
//**序列化 ID**
private static final long serialVersionUID = 4425249765923293627L;
/*
`serialVersionUID`
- Java `Serializable` `serialVersionUID`
- `ActionFailureException` `Serializable`
*/
//**构造函数**
// 默认构造函数
public ActionFailureException() {
super();
}
// 默认构造函数,调用 `RuntimeException` 的无参构造函数,表示没有具体的错误信息时抛出此异常。
//带消息构造函数
public ActionFailureException(String paramString) {
super(paramString);
}
//- 这个构造函数允许传入一个字符串参数 `paramString`,用来描述异常的具体信息。这会调用 `RuntimeException` 的带有消息的构造函数。
//带消息和原因构造函数
public ActionFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}
//- 这个构造函数允许传入一个字符串参数和另一个异常(`Throwable`)作为参数。
// 通过这种方式,可以指定异常的描述信息和导致当前异常的根本原因(例如,另一个异常)。
/* . ** `RuntimeException` **
- `ActionFailureException` `RuntimeException` **unchecked exception**
-
**使**
`ActionFailureException`
- `ActionFailureException`
-
`ActionFailureException` `RuntimeException`
-
- `RuntimeException`
*/

@ -0,0 +1,434 @@
/*
* 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;
// 导入Android框架和Java标准库中的类
import android.content.Context; // 提供应用环境信息的类
import android.database.Cursor; // 用于在数据库查询结果中遍历行的类
import android.os.Environment; // 提供有关设备环境的信息,如存储状态
import android.text.TextUtils; // 提供用于处理文本(如检查空字符串)的实用方法
import android.text.format.DateFormat; // 提供日期和时间格式化的方法
import android.util.Log; // 提供日志记录的类
import net.micode.notes.R; // 包含项目资源引用的类(尽管在这段代码中未直接使用)
import net.micode.notes.data.Notes; // 可能是一个包含数据模型和相关常量的类
import net.micode.notes.data.Notes.DataColumns; // Notes类内部的一个静态内部类定义了数据表的列名
import net.micode.notes.data.Notes.DataConstants; // Notes类内部的一个静态内部类定义了数据相关的常量
import net.micode.notes.data.Notes.NoteColumns; // Notes类内部的一个静态内部类定义了笔记数据表的列名
import java.io.File; // 表示文件和目录路径名的抽象表示形式
import java.io.FileNotFoundException; // 当尝试打开文件失败时抛出的异常
import java.io.FileOutputStream; // 文件输出流,用于将数据写入文件
import java.io.IOException; // 发生I/O错误时抛出的通用异常类
import java.io.PrintStream; // 打印流,用于打印各种数据表示形式
// 声明BackupUtils类
public class BackupUtils {
// 定义一个静态常量TAG用于日志记录中的标签
private static final String TAG = "BackupUtils";
// 定义一个静态变量sInstance用于存储BackupUtils类的单例实例
private static BackupUtils sInstance;
// 定义一个私有构造函数防止外部通过new关键字创建实例
// 注意:构造函数在这段代码中并未直接给出,但它是单例模式实现的关键部分
// 定义一个公开的静态同步方法getInstance用于获取BackupUtils类的实例
public static synchronized BackupUtils getInstance(Context context) {
// 如果sInstance为null说明还没有创建实例则创建一个新的实例并赋值给sInstance
if (sInstance == null) {
sInstance = new BackupUtils(context); // 注意:这里的构造函数是假设的,实际代码中需要定义
}
// 返回sInstance即BackupUtils类的单例实例
return sInstance;
}
}
/**
* Following states are signs to represents backup or restore
* status
*/
// Currently, the sdcard is not mounted
// 定义一些静态常量,用于表示不同的状态
public static final int STATE_SD_CARD_UNMOUONTED = 0; // SD卡未挂载的状态码
// 备注:这里有一个拼写错误,应该是"UNMOUNTED"而不是"UNMOUONTED"
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; // 备份文件不存在的状态码
public static final int STATE_DATA_DESTROIED = 2; // 数据被破坏(可能由其他程序更改)的状态码
// 备注:这里有一个拼写错误,应该是"DESTROYED"而不是"DESTROIED"
public static final int STATE_SYSTEM_ERROR = 3; // 系统错误导致备份或恢复失败的状态码
public static final int STATE_SUCCESS = 4; // 备份或恢复成功的状态码
// 定义一个私有成员变量,用于文本导出功能
private TextExport mTextExport;
// 私有构造函数防止外部直接实例化BackupUtils类
// 备注这是单例模式实现的一部分构造函数接受一个Context参数
private BackupUtils(Context context) {
mTextExport = new TextExport(context); // 初始化mTextExport成员变量
}
// 定义一个私有静态方法,用于检查外部存储是否可用
private static boolean externalStorageAvailable() {
// 通过比较Environment.getExternalStorageState()的返回值和Environment.MEDIA_MOUNTED来判断
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
// 定义一个公开的方法,用于导出数据到文本文件
// 备注:这个方法返回的是一个状态码,表示导出操作的结果
public int exportToText() {
return mTextExport.exportToText(); // 调用TextExport类的exportToText方法
}
// 定义一个公开的方法,用于获取导出的文本文件的文件名
public String getExportedTextFileName() {
return mTextExport.mFileName; // 直接访问TextExport类的mFileName成员变量
}
// 定义一个公开的方法,用于获取导出的文本文件的目录
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory; // 直接访问TextExport类的mFileDirectory成员变量
}
// 定义一个私有静态内部类,用于实现文本导出的具体逻辑
private static class TextExport {
// 定义一个静态常量数组,用于指定从数据库查询时需要返回的列
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID, // 笔记的ID
NoteColumns.MODIFIED_DATE, // 笔记的最后修改日期
NoteColumns.SNIPPET, // 笔记的摘要或简短内容
NoteColumns.TYPE // 笔记的类型
};
// 备注TextExport类的其他部分如构造函数、exportToText方法以及mFileName和mFileDirectory成员变量
// 在这段代码中未给出,但我们可以推断它们存在并用于实现文本导出的功能。
}
// 定义静态常量用于表示数据库查询结果中笔记ID的索引位置
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;
// 定义一个静态常量数组,包含需要从数据库查询的数据列
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT, // 数据内容
DataColumns.MIME_TYPE, // MIME类型
DataColumns.DATA1, // 第一个数据字段
DataColumns.DATA2, // 第二个数据字段
DataColumns.DATA3, // 第三个数据字段
DataColumns.DATA4 // 第四个数据字段
};
// 定义静态常量,用于表示数据查询结果中内容列的索引位置
private static final int DATA_COLUMN_CONTENT = 0;
// 定义静态常量用于表示数据查询结果中MIME类型列的索引位置
private static final int DATA_COLUMN_MIME_TYPE = 1;
// 这里有一个问题DATA_COLUMN_CALL_DATE电话日期与DATA_PROJECTION数组不匹配
// 因为DATA_PROJECTION中没有与电话日期对应的列这可能会导致索引越界错误
private static final int DATA_COLUMN_CALL_DATE = 2;
// 定义静态常量,用于表示数据查询结果中电话号码列的索引位置
// 但这里也有问题根据DATA_PROJECTION数组DATA_COLUMN_PHONE_NUMBER应该指向索引4
// 这个定义是正确的但是之前的DATA_COLUMN_CALL_DATE使用了索引2可能会导致混淆
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
// 定义一个实例变量,用于存储文本格式的字符串数组
// 这个数组可能从资源文件中获取,用于格式化导出的文本
private final String[] 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实例变量
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
// 将传入的上下文对象赋值给mContext实例变量
mContext = context;
// 初始化mFileName和mFileDirectory实例变量为空字符串
mFileName = "";
mFileDirectory = "";
}
// 定义一个私有方法接收一个文件夹ID字符串和一个打印流PrintStream作为参数无返回值
private void exportFolderToText(String folderId, PrintStream ps) {
// 使用getContentResolver()方法从内容提供者处查询属于指定文件夹的笔记
// Notes.CONTENT_NOTE_URI是内容提供者的URINOTE_PROJECTION是查询返回的列投影
// NoteColumns.PARENT_ID + "=?"是查询的选择条件new String[] { folderId }是选择条件的参数
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { folderId }, null);
// 检查查询返回的Cursor是否不为null
if (notesCursor != null) {
// 移动Cursor到第一行如果查询结果不为空则进入if语句
if (notesCursor.moveToFirst()) {
// 使用do-while循环遍历查询结果集
do {
// 打印笔记的最后修改日期
// 使用getFormat(FORMAT_NOTE_DATE)获取格式化字符串然后使用DateFormat.format()和getString(R.string.format_datetime_mdhm)
// 格式化最后修改日期最后通过PrintStream的println方法打印出来
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 查询属于当前笔记的数据
// 从Cursor中获取笔记的ID
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
// 调用exportNoteToText方法导出当前笔记的文本内容到PrintStream中
// 注意exportNoteToText方法需要在当前类中定义但您没有提供其实现
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext()); // 循环直到Cursor遍历完所有行
}
// 关闭Cursor释放资源
notesCursor.close();
}
}
/**
* Export note identified by id to a print stream
*/
// 定义一个私有方法用于将指定笔记ID的笔记内容导出到文本输出流PrintStream
private void exportNoteToText(String noteId, PrintStream ps) {
// 使用内容解析器查询与指定笔记ID匹配的笔记数据
// 这里的Notes.CONTENT_DATA_URI是内容提供者的URIDATA_PROJECTION是我们要查询的列投影
// DataColumns.NOTE_ID + "=?"是查询条件表示我们要查询的笔记ID
// new String[] { noteId }是查询条件的参数即具体的笔记ID值
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { noteId }, null);
// 检查查询返回的Cursor是否不为空
if (dataCursor != null) {
// 移动Cursor到第一行数据如果有的话
if (dataCursor.moveToFirst()) {
// 使用do-while循环遍历Cursor中的所有行
do {
// 从当前行中获取MIME类型
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
// 判断MIME类型是否为CALL_NOTE即电话笔记
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); // 这里location可能表示电话笔记的附加信息或位置但命名可能有些误导
// 如果电话号码不为空,则按照指定格式打印电话号码
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)));
// 如果location附加信息不为空则按照指定格式打印它
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();
}
}
// print a line separator between note
try {//这段代码尝试向PrintStream对象ps写入一个字节数组
//该数组包含一个换行符Character.LINE_SEPARATOR和一个字符Character.LETTER_NUMBER
//如果写入过程中发生IOException则捕获异常并通过日志打印错误信息。
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() {
//这是一个方法的声明名为exportToText它返回一个整型值表示导出操作的状态。方法上方的注释说明笔记将被导出为用户可读的文本格式。
if (!externalStorageAvailable()) {
//这段代码检查外部存储是否可用。如果不可用则记录一条调试日志并返回表示SD卡未挂载的状态码。
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
PrintStream ps = getExportToTextPrintStream();
//获取一个PrintStream对象用于将文本写入文件。如果获取失败即ps为null则记录一条错误日志并返回表示系统错误的状态码。
if (ps == null) {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes
//使用ContentResolver查询数据库获取所有文件夹不包括垃圾文件夹和通话记录文件夹的游标。
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_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 {
// Print folder's name
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));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close();
}
// Export notes in root's folder
//再次使用ContentResolver查询数据库这次获取所有根目录下的笔记的游标。
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=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))));
// Query data belong to this note
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
noteCursor.close();
}
ps.close();//关闭PrintStream对象完成文件写入。
return STATE_SUCCESS;//返回表示操作成功的状态码。
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
*/
private PrintStream getExportToTextPrintStream() {
//这是一个私有方法返回一个PrintStream对象用于将文本写入文件。
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
//调用generateFileMountedOnSDcard方法生成一个文件并返回该文件的File对象。
//这个方法需要三个参数上下文mContext文件路径的资源IDR.string.file_path和文件名格式的资源IDR.string.file_name_txt_format。
if (file == null) {
Log.e(TAG, "create file to exported failed");
return null;//如果generateFileMountedOnSDcard方法返回null表示文件创建失败则记录一条错误日志并返回null。
}
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);//获取文件的名称和文件所在的目录路径并将它们分别赋值给mFileName和mFileDirectory变量。
PrintStream ps = null;//声明一个PrintStream对象ps初始化为null。
try {
//尝试创建一个FileOutputStream对象fos用于向文件写入数据。然后使用fos创建一个PrintStream对象ps。
//如果在创建FileOutputStream或PrintStream时发生FileNotFoundException或NullPointerException则捕获异常打印堆栈跟踪并返回null。
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();
return null;
}
return ps;//返回PrintStream对象ps。
}
}
/**
* Generate the text file to store imported data
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
//这是一个私有静态方法返回一个File对象表示在SD卡上创建的文件。
StringBuilder sb = new StringBuilder();//创建一个StringBuilder对象sb并向其中追加SD卡的根目录路径。
sb.append(Environment.getExternalStorageDirectory());
sb.append(context.getString(filePathResId));//向sb中追加文件路径字符串该字符串是从资源文件中获取的。
File filedir = new File(sb.toString());//使用sb.toString()创建一个表示文件目录的File对象filedir。
sb.append(context.getString(
//向sb中追加文件名文件名是根据提供的格式资源ID和当前时间格式化的。
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
File file = new File(sb.toString());//使用sb.toString()创建一个表示文件的File对象file。
try {
//尝试检查文件目录是否存在,如果不存在则创建它。然后,检查文件是否存在,如果不存在则创建新文件。
//如果在检查或创建目录/文件时发生SecurityException或IOException则捕获异常打印堆栈跟踪。
if (!filedir.exists()) {
filedir.mkdir();
}
if (!file.exists()) {
file.createNewFile();
}
return file;
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;//如果方法执行过程中出现任何异常或者文件无法被创建则返回null。
}
}

@ -0,0 +1,119 @@
/*
* 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.data;//定义包名
import android.content.Context;//导入Context类用于访问应用环境
import android.database.Cursor;// 导入 Cursor 类,用于操作查询结果
import android.provider.ContactsContract.CommonDataKinds.Phone;// 导入电话常量
import android.provider.ContactsContract.Data;// 导入数据常量
import android.telephony.PhoneNumberUtils;// 导入电话工具类
import android.util.Log; // 导入日志工具类
import java.util.HashMap;// 导入 HashMap 类,用于存储联系人缓存
public class Contact { // 定义 Contact 类
private static HashMap<String, String> sContactCache;
// 静态 HashMap 用于缓存联系人姓名
private static final String TAG = "Contact";
// 日志标签,用于标识日志信息
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN "
+ "(SELECT raw_contact_id "
+ " FROM phone_lookup"
+ " WHERE min_match = '+')"; // 查询选项字符串,用于匹配来电显示的电话号码
//根据电话号码获取联系人姓名的方法
public static String getContact(Context context, String phoneNumber) {
//初始化联系人缓存
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
//如果缓存中已存在该电话号码,则直接返回对应的联系人姓名
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}
//替换查询字符串中的“+”,使用电话号码的最小匹配形式
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
//执行内容解析器查询,获取与电话号码匹配的联系人姓名
Cursor cursor = context.getContentResolver().query(
//查询的 UIR
Data.CONTENT_URI,
//查询的列,这里只查询显示姓名
new String [] { Phone.DISPLAY_NAME },
//查询条件
selection,
//查询参数
new String[] { phoneNumber },
//排序方式
null);
//检查游标是否有效,并尝试移动到第一条记录
if (cursor != null && cursor.moveToFirst()) {
try {
//获取联系人姓名
String name = cursor.getString(0);
//将联系人的电话号码和姓名存入缓存
sContactCache.put(phoneNumber, name);
//返回联系人姓名
return name;
}
//捕获索引越界异常
catch (IndexOutOfBoundsException e) {
// 记录错误日志
Log.e(TAG, " Cursor get string error " + e.toString());
// 返回 null
return null;
} finally {
// 确保游标被关闭以释放资源
cursor.close();
}
} else { // 如果没有找到匹配的联系人
// 记录调试日志
Log.d(TAG, "No contact matched with number:" + phoneNumber);
// 返回 null
return null;
}
}
}
/*
sContactCache HashMap
selection PhoneNumberUtils.toCallerIDMinMatch(phoneNumber) +
context.getContentResolver().query selectionPhone.DISPLAY_NAME
便
IndexOutOfBoundsException null
null
ContentResolver
*/

@ -0,0 +1,311 @@
/*
* 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.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
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.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;//以上导入了代码中使用的其他类。
public class DataUtils {//定义了一个名为DataUtils的公共类这个类用于封装与数据操作相关的工具方法。
public static final String TAG = "DataUtils";//定义了一个公共静态最终变量TAG用于日志记录时的标签。
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
//定义了一个公共静态方法batchDeleteNotes用于批量删除笔记。该方法接收一个ContentResolver对象和一个包含笔记ID的HashSet集合作为参数返回一个布尔值表示操作是否成功。
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;//定义了一个公共静态方法batchDeleteNotes用于批量删除笔记。该方法接收一个ContentResolver对象和一个包含笔记ID的HashSet集合作为参数返回一个布尔值表示操作是否成功。
}
if (ids.size() == 0) {
Log.d(TAG, "no id is in the hashset");
return true;//如果ID集合为空则记录一条调试日志并返回true
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
//创建一个ArrayList对象用于存储将要执行的内容提供者操作。
for (long id : ids) {//遍历ID集合中的每个ID。
if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
continue;//如果当前ID是系统根文件夹的ID则记录一条错误日志并跳过当前循环迭代不删除根文件夹。
}
ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
//创建一个新的删除操作构建器指定要删除的内容URI通过ContentUris.withAppendedId方法将笔记ID附加到笔记内容URI上
operationList.add(builder.build());//将构建好的删除操作添加到操作列表中。
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
//尝试批量执行操作列表中的所有操作。applyBatch方法接收内容提供者的授权字符串和操作列表返回操作结果数组。
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}//如果操作结果数组为null、长度为0或第一个结果为null则认为删除操作失败记录一条调试日志并返回false。
return true;//如果操作成功执行返回true。
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}//如果操作成功执行返回true。
return false;//如果发生异常返回false
}
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
//定义一个静态方法 moveNoteToFoler注意这里有个拼写错误应为 moveNoteToFolder接受四个参数ContentResolver resolver用于与内容提供者交互的工具long id要移动的笔记的IDlong srcFolderId源文件夹IDlong desFolderId目标文件夹ID
ContentValues values = new ContentValues();//创建一个 ContentValues 对象,用于存储要更新的数据。
values.put(NoteColumns.PARENT_ID, desFolderId);//将目标文件夹ID赋值给 PARENT_ID表示笔记应该移动到的文件夹。
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);//将源文件夹ID赋值给 ORIGIN_PARENT_ID可能用于记录笔记原来所在的文件夹
values.put(NoteColumns.LOCAL_MODIFIED, 1);//将 LOCAL_MODIFIED 设置为1
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
//使用 ContentResolver 的 update 方法更新指定ID的笔记。ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id) 用于生成指向特定笔记的URIvalues 包含要更新的字段。
}
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {//定义一个静态方法 batchMoveToFolder接受三个参数ContentResolver resolver一个包含多个笔记ID的 HashSet<Long> ids以及目标文件夹ID long folderId。返回一个布尔值表示操作是否成功。
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;//如果传入的ID集合为null则记录日志并返回true
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
//创建一个 ArrayList 来存储一系列的内容提供者操作ContentProviderOperation这些操作将批量执行。
for (long id : ids) {//遍历ID集合为每个ID创建一个更新操作的构建器指定要更新的笔记的URI。
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId);//设置目标文件夹ID。
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);//设置 LOCAL_MODIFIED 为1。
operationList.add(builder.build());//将构建好的操作添加到操作列表中。
}
try {//尝试批量执行操作列表中的操作。Notes.AUTHORITY 是内容提供者的权限名。
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;//如果操作结果为空、长度为0或第一个结果为null则记录日志并返回false。
}
return true;
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));//捕获并处理 RemoteException 和 OperationApplicationException 异常,记录错误日志。
}
return false;//如果出现异常则返回false。
}
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/
public static int getUserFolderCount(ContentResolver resolver) {//定义一个静态方法getUserFolderCount它接受一个ContentResolver对象作为参数用于与内容提供者交互。
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,//使用ContentResolver的query方法发起查询。Notes.CONTENT_NOTE_URI是查询的URI指向笔记数据的集合。
new String[] { "COUNT(*)" },//指定查询的列,这里只查询一个列,即"COUNT(*)",用于计算满足条件的行数。
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",//指定查询的条件。这里条件是笔记类型NoteColumns.TYPE等于某个值并且父IDNoteColumns.PARENT_ID不等于另一个值。
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
null);//为查询条件提供具体的值。第一个值是文件夹的类型Notes.TYPE_FOLDER第二个值是垃圾文件夹的IDNotes.ID_TRASH_FOLER
//查询的排序规则为null表示不需要排序。
int count = 0;//初始化一个变量count用于存储查询结果。
if(cursor != null) {//检查Cursor对象是否不为null即查询是否成功返回了结果。
if(cursor.moveToFirst()) {//尝试将Cursor移动到第一行如果Cursor不为空且至少有一行数据则此操作成功。
try {
count = cursor.getInt(0);//从Cursor的第一列索引为0获取整数值即满足条件的行数并将其赋值给count。
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "get folder count failed:" + e.toString());
//捕获IndexOutOfBoundsException异常这通常发生在尝试访问不存在的列时。使用Log.e打印错误日志。
} finally {
cursor.close();//无论是否发生异常都确保关闭Cursor以释放资源。
}
}
}
return count;//返回计算得到的文件夹数量。
}
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
//定义一个静态方法visibleInNoteDatabase它接受ContentResolver对象、笔记ID和笔记类型作为参数。
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
null);//使用ContentUris.withAppendedId方法将笔记ID附加到URI上以查询特定笔记的详细信息。
boolean exist = false;//初始化一个布尔变量exist用于表示笔记是否存在且满足条件。
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;//检查Cursor是否不为null如果Cursor的计数大于0则设置exist为true。最后关闭Cursor并返回exist。
}
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {//检查指定ID的笔记是否存在。
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
//定义一个静态方法existInDataDatabase它接受一个ContentResolver和一个长整型dataId作为参数返回一个布尔值表示是否存在。
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);//使用ContentResolver的query方法查询数据库。ContentUris.withAppendedId方法用于生成带有指定ID的URI。这里查询的是Notes.CONTENT_DATA_URI即数据项的URI并且指定了dataId。查询的列、选择条件、选择参数和排序方式均为null表示查询所有列且不设置过滤条件。
boolean exist = false;//初始化一个布尔变量exist为false用于记录数据项是否存在。
if (cursor != null) {//检查cursor是否为null确保查询成功。
if (cursor.getCount() > 0) {
exist = true;//如果cursor中的记录数大于0则数据项存在将exist设置为true。
}
cursor.close();//关闭cursor释放资源。
}
return exist;//返回exist的值。
}
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
//定义一个静态方法checkVisibleFolderName它接受一个ContentResolver和一个字符串name作为参数返回一个布尔值表示是否存在。
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
" AND " + NoteColumns.SNIPPET + "=?",
new String[] { name }, null);
//使用ContentResolver的query方法查询数据库。查询的URI是Notes.CONTENT_NOTE_URI即笔记的URI。
//查询条件是类型为文件夹(NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER)父ID不等于垃圾文件夹ID(NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER),并且摘要(即名称)等于指定的name。
boolean exist = false;//初始化一个布尔变量exist为false。
if(cursor != null) {//检查cursor是否为null。
if(cursor.getCount() > 0) {
exist = true;//如果cursor中的记录数大于0则文件夹存在将exist设置为true。
}
cursor.close();//关闭cursor。
}
return exist;//返回exist的值。
}
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {//定义一个静态方法getFolderNoteWidget
//它接受一个ContentResolver和一个长整型folderId作为参数返回一个HashSet<AppWidgetAttribute>。
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) },
null);//使用ContentResolver的query方法查询数据库。查询的URI是Notes.CONTENT_NOTE_URI。查询的列是WIDGET_ID和WIDGET_TYPE。查询条件是父ID等于指定的folderId。
HashSet<AppWidgetAttribute> set = null;//初始化一个HashSet<AppWidgetAttribute>类型的变量set为null。
if (c != null) {//检查cursor是否为null。
if (c.moveToFirst()) {//如果cursor移动到第一条记录。
set = new HashSet<AppWidgetAttribute>();//初始化set。
do {//开始一个循环遍历cursor中的所有记录。
try {//创建一个AppWidgetAttribute对象并从cursor中获取WIDGET_ID和WIDGET_TYPE的值添加到set中。如果发生IndexOutOfBoundsException异常则记录错误日志。
AppWidgetAttribute widget = new AppWidgetAttribute();
widget.widgetId = c.getInt(0);
widget.widgetType = c.getInt(1);
set.add(widget);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, e.toString());
}
} while (c.moveToNext());//循环直到cursor中没有更多记录。
}
c.close();//关闭cursor。
}
return set;//返回set。
}
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
//定义一个静态方法getCallNumberByNoteId它接受一个ContentResolver和一个长整型noteId作为参数返回一个字符串即电话号码。
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE },
null);//使用ContentResolver的query方法查询数据库。查询的URI是Notes.CONTENT_DATA_URI。查询的列是PHONE_NUMBER。查询条件是笔记ID等于指定的noteId并且MIME类型等于CallNote.CONTENT_ITEM_TYPE。
if (cursor != null && cursor.moveToFirst()) {//检查cursor是否为null并且是否能移动到第一条记录。
try {
return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call number fails " + e.toString());
} finally {
cursor.close();
}//从cursor中获取电话号码并返回。如果发生IndexOutOfBoundsException异常则记录错误日志。无论如何最后都要关闭cursor。
}
return "";//如果查询失败或没有找到匹配的记录,则返回空字符串。
}
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
//定义了一个静态方法该方法接收一个ContentResolver对象、一个电话号码字符串和一个通话日期长整型用于查询笔记ID。
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,//使用ContentResolver的query方法查询Notes.CONTENT_DATA_URI这是一个URI指向提供笔记数据的内容提供者。
new String [] { CallNote.NOTE_ID },//指定查询返回的列这里只请求返回笔记的ID。
CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL("
+ CallNote.PHONE_NUMBER + ",?)",//定义查询的选择条件包括通话日期匹配、MIME类型匹配确保是笔记的正确类型以及电话号码匹配
new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber },//为上述条件提供具体的值分别是通话日期、MIME类型和电话号码。
null);//不需要排序所以传递null。
if (cursor != null) {//检查Cursor对象是否非空。
if (cursor.moveToFirst()) {//尝试将游标移动到第一行,检查是否有数据。
try {
return cursor.getLong(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call note id fails " + e.toString());
}//尝试从第一行获取并返回笔记ID。如果发生IndexOutOfBoundsException可能是因为查询结果没有包含期望的列则记录错误日志。
}
cursor.close();
}
return 0;//关闭Cursor如果没有找到匹配的笔记ID则返回0。
}
public static String getSnippetById(ContentResolver resolver, long noteId) {//定义了一个静态方法用于根据笔记ID获取笔记摘要。
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,//使用ContentResolver查询Notes.CONTENT_NOTE_URI这是指向笔记数据的URI。
new String [] { NoteColumns.SNIPPET },//指定查询返回的列,这里只请求返回笔记的摘要。
NoteColumns.ID + "=?",//定义查询的选择条件即笔记ID匹配。
new String [] { String.valueOf(noteId)},ID
null);//不需要排序所以传递null。
if (cursor != null) {//检查Cursor对象是否非空。
String snippet = "";//初始化摘要字符串。
if (cursor.moveToFirst()) {//尝试将游标移动到第一行,检查是否有数据。
snippet = cursor.getString(0);//从第一行获取并设置摘要。
}
cursor.close();//关闭Cursor。
return snippet;
}
throw new IllegalArgumentException("Note is not found with id: " + noteId);//如果没有找到匹配的笔记,则抛出异常。
}
public static String getFormattedSnippet(String snippet) {//定义了一个静态方法,用于格式化摘要字符串。
if (snippet != null) {//检查摘要是否非空。
snippet = snippet.trim();//去除摘要前后的空格。
int index = snippet.indexOf('\n');//查找摘要中第一个换行符的位置。
if (index != -1) {//如果找到了换行符。
snippet = snippet.substring(0, index);//只保留换行符之前的部分。
}
}
return snippet;//返回格式化后的摘要。
}
}

@ -0,0 +1,146 @@
/*
* 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.gtask.remote;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
// 定义一个继承自AsyncTask的类用于异步执行任务接收Void作为输入参数String作为进度更新参数Integer作为返回结果
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
// 定义一个静态常量用作通知的ID
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
// 定义一个接口,用于任务完成时的回调
public interface OnCompleteListener {
void onComplete();
}
// 声明一个Context对象用于访问应用的资源和类
private Context mContext;
// 声明一个NotificationManager对象用于管理通知
private NotificationManager mNotifiManager;
// 声明一个GTaskManager对象用于管理任务假设是一个自定义的任务管理类
private GTaskManager mTaskManager;
// 声明一个OnCompleteListener对象用于任务完成时的回调
private OnCompleteListener mOnCompleteListener;
// 构造函数,初始化必要的对象
public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context; // 初始化mContext
mOnCompleteListener = listener; // 初始化mOnCompleteListener
mNotifiManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE); // 初始化mNotifiManager
mTaskManager = GTaskManager.getInstance(); // 初始化mTaskManager假设这是一个单例模式
}
// 提供一个方法用于取消同步任务
public void cancelSync() {
mTaskManager.cancelSync(); // 调用mTaskManager的cancelSync方法
}
// 重写publishProgress方法用于发布进度更新
public void publishProgess(String message) {
publishProgress(new String[] {
message
}); // 调用父类的publishProgress方法传递一个字符串数组
}
// 私有方法,用于显示通知
private void showNotification(int tickerId, String content) {
// 创建一个Notification对象设置图标、标题和时间戳
Notification notification = new Notification(R.drawable.notification, mContext
.getString(tickerId), System.currentTimeMillis());
notification.defaults = Notification.DEFAULT_LIGHTS; // 设置默认灯光效果
notification.flags = Notification.FLAG_AUTO_CANCEL; // 设置点击通知后自动取消
PendingIntent pendingIntent; // 声明一个PendingIntent对象
// 根据tickerId判断点击通知后的跳转页面
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), 0);
} else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), 0);
}
// 设置通知的详细信息
notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
pendingIntent);
// 显示通知
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
// 重写doInBackground方法用于在后台线程执行任务
@Override
protected Integer doInBackground(Void... unused) {
// 发布进度更新,显示登录进度
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
.getSyncAccountName(mContext)));
// 执行同步任务,并返回结果
return mTaskManager.sync(mContext, this);
}
// 重写onProgressUpdate方法用于在UI线程更新进度
@Override
protected void onProgressUpdate(String... progress) {
// 显示同步中的通知
showNotification(R.string.ticker_syncing, progress[0]);
// 如果mContext是GTaskSyncService的实例则发送广播
if (mContext instanceof GTaskSyncService) {
((GTaskSyncService) mContext).sendBroadcast(progress[0]);
}
}
// 重写onPostExecute方法用于在任务完成后在UI线程执行操作
@Override
protected void onPostExecute(Integer result) {
// 根据返回结果显示不同的通知
if (result == GTaskManager.STATE_SUCCESS) {
showNotification(R.string.ticker_success, mContext.getString(
R.string.success_sync_account, mTaskManager.getSyncAccount()));
NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); // 更新最后同步时间
} else if (result == GTaskManager.STATE_NETWORK_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network));
} else if (result == GTaskManager.STATE_INTERNAL_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal));
} else if (result == GTaskManager.STATE_SYNC_CANCELLED) {
showNotification(R.string.ticker_cancel, mContext
.getString(R.string.error_sync_cancelled));
}
// 如果mOnCompleteListener不为空则在新线程中调用onComplete方法
if (mOnCompleteListener != null) {
new Thread(new Runnable() {
public void run() {
mOnCompleteListener.onComplete();
}
}).start();
}
}
}

@ -0,0 +1,767 @@
/*
* 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.gtask.remote;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.ui.NotesPreferenceActivity;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
// 定义一个名为GTaskClient的公开类这个类似乎是用来与Google Tasks API进行交互的客户端。
public class GTaskClient {
// 定义一个静态常量TAG用于日志记录其值为当前类的简单名称。
private static final String TAG = GTaskClient.class.getSimpleName();
// 定义一个静态常量GTASK_URL表示Google Tasks服务的基础URL。
private static final String GTASK_URL = "https://mail.google.com/tasks/";
// 定义一个静态常量GTASK_GET_URL表示用于获取任务的URL。
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
// 定义一个静态常量GTASK_POST_URL表示用于发布添加/更新任务的URL。
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
// 定义一个静态的GTaskClient实例mInstance用于实现单例模式。
private static GTaskClient mInstance = null;
// 定义一个DefaultHttpClient实例mHttpClient用于发送HTTP请求。
private DefaultHttpClient mHttpClient;
// 定义一个字符串mGetUrl存储用于获取任务的URL。
private String mGetUrl;
// 定义一个字符串mPostUrl存储用于发布任务的URL。
private String mPostUrl;
// 定义一个长整型mClientVersion用于存储客户端版本信息初始化为-1表示未设置。
private long mClientVersion;
// 定义一个布尔型mLoggedin用于标记用户是否已登录初始化为false。
private boolean mLoggedin;
// 定义一个长整型mLastLoginTime用于存储用户最后登录的时间初始化为0。
private long mLastLoginTime;
// 定义一个整型mActionId可能用于标识请求或操作的ID初始化为1。
private int mActionId;
// 定义一个Account类型的mAccount用于存储用户账户信息初始化为null。
private Account mAccount;
// 定义一个JSONArray类型的mUpdateArray可能用于存储需要更新的任务信息初始化为null。
private JSONArray mUpdateArray;
// 私有的构造方法防止外部直接创建GTaskClient实例这是实现单例模式的一部分。
private GTaskClient() {
mHttpClient = null; // 初始化mHttpClient为null可能后续会有具体的初始化逻辑。
mGetUrl = GTASK_GET_URL; // 将mGetUrl设置为预定义的获取任务URL。
mPostUrl = GTASK_POST_URL; // 将mPostUrl设置为预定义的发布任务URL。
mClientVersion = -1; // 初始化客户端版本号为-1。
mLoggedin = false; // 初始化登录状态为未登录。
mLastLoginTime = 0; // 初始化最后登录时间为0。
mActionId = 1; // 初始化操作ID为1。
mAccount = null; // 初始化账户信息为null。
mUpdateArray = null; // 初始化更新数组为null。
}
// 定义一个公开的静态同步方法getInstance用于获取GTaskClient的实例。
// 这个方法实现了单例模式确保整个应用中只有一个GTaskClient实例。
public static synchronized GTaskClient getInstance() {
if (mInstance == null) { // 如果实例为null则创建一个新的实例。
mInstance = new GTaskClient();
}
return mInstance; // 返回GTaskClient实例。
}
// 定义一个公开的方法login它接受一个Activity作为参数返回一个布尔值表示登录是否成功。
public boolean login(Activity activity) {
// 定义一个常量interval表示登录有效期为5分钟以毫秒为单位
final long interval = 1000 * 60 * 5;
// 如果上次登录时间加上有效期小于当前时间则认为登录已过期将登录状态设置为false。
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false;
}
// 如果当前已登录但账户名称与NotesPreferenceActivity中获取的同步账户名称不匹配
// 则认为需要重新登录将登录状态设置为false。
if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
mLoggedin = false;
}
// 如果当前已登录则直接打印日志并返回true。
if (mLoggedin) {
Log.d(TAG, "already logged in");
return true;
}
// 更新最后登录时间为当前时间。
mLastLoginTime = System.currentTimeMillis();
// 调用loginGoogleAccount方法尝试登录Google账户获取认证令牌。
String authToken = loginGoogleAccount(activity, false);
// 如果认证令牌为null表示登录Google账户失败打印错误日志并返回false。
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// 如果账户名称不以gmail.com或googlemail.com结尾则可能需要使用自定义域进行登录。
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
// 构造自定义域的Google Tasks URL。
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
url.append(suffix + "/");
mGetUrl = url.toString() + "ig";
mPostUrl = url.toString() + "r/ig";
// 尝试使用自定义域的URL和认证令牌登录Google Tasks。
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true;
}
}
// 如果使用自定义域登录失败则尝试使用Google官方的URL进行登录。
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
// 尝试使用Google官方的URL和认证令牌登录Google Tasks。
if (!tryToLoginGtask(activity, authToken)) {
// 如果登录失败则返回false。
return false;
}
}
// 将登录状态设置为true并返回true表示登录成功。
mLoggedin = true;
return true;
}
// 定义一个私有方法loginGoogleAccount它接受一个Activity对象和一个布尔值invalidateToken作为参数
// 返回一个字符串表示的Google账户认证令牌。
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
// 声明一个字符串变量authToken用于存储认证令牌。
String authToken;
// 通过Activity获取AccountManager实例用于管理账户。
AccountManager accountManager = AccountManager.get(activity);
// 获取所有类型为"com.google"的账户。
Account[] accounts = accountManager.getAccountsByType("com.google");
// 如果没有可用的Google账户则打印错误日志并返回null。
if (accounts.length == 0) {
Log.e(TAG, "there is no available google account");
return null;
}
// 从设置中获取同步账户的名称。
String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
Account account = null;
// 遍历所有Google账户查找与设置中的账户名称相匹配的账户。
for (Account a : accounts) {
if (a.name.equals(accountName)) {
account = a;
break;
}
}
// 如果找到了匹配的账户则将其赋值给mAccount成员变量。
if (account != null) {
mAccount = account;
} else {
// 如果没有找到匹配的账户则打印错误日志并返回null。
Log.e(TAG, "unable to get an account with the same name in the settings");
return null;
}
// 通过AccountManager获取认证令牌。注意这里的"goanna_mobile"可能是一个特定于应用的scope或authTokenType
// 它需要与你的Google API项目配置相匹配。然而"goanna_mobile"并不是一个标准的Google API scope
// 这里可能是示例代码或特定应用的自定义值。
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
try {
// 从Future中获取结果这个结果是一个Bundle包含了认证令牌等信息。
Bundle authTokenBundle = accountManagerFuture.getResult();
// 从Bundle中提取认证令牌。
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
// 如果invalidateToken为true则使当前认证令牌失效并递归调用loginGoogleAccount方法重新获取令牌。
// 注意这里的递归调用可能会导致无限循环如果invalidateToken始终为true且没有额外的退出条件。
// 在实际应用中,通常不会在获取令牌后立即使其失效,除非有特定的需求。
if (invalidateToken) {
accountManager.invalidateAuthToken("com.google", authToken);
loginGoogleAccount(activity, false);
}
} catch (Exception e) {
// 如果在获取认证令牌的过程中发生异常则打印错误日志并将authToken设置为null。
Log.e(TAG, "get auth token failed");
authToken = null;
}
// 返回认证令牌。如果获取失败则为null。
return authToken;
}
// 定义一个私有方法tryToLoginGtask它尝试使用提供的authToken登录到Google TasksGtask
// 如果登录失败,它会尝试使令牌失效并重新获取一个新的令牌,然后再次尝试登录。
private boolean tryToLoginGtask(Activity activity, String authToken) {
// 首先尝试使用提供的authToken登录Gtask。
if (!loginGtask(authToken)) {
// 如果登录失败可能是因为authToken已经过期。
// 接下来,我们将使该令牌失效,并尝试重新获取一个新的令牌。
authToken = loginGoogleAccount(activity, true);
// 如果重新获取令牌失败即返回null则打印错误日志并返回false。
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// 使用新获取的authToken再次尝试登录Gtask。
if (!loginGtask(authToken)) {
// 如果仍然登录失败则打印错误日志并返回false。
Log.e(TAG, "login gtask failed");
return false;
}
}
// 如果登录成功无论是初次尝试还是重新获取令牌后的尝试则返回true。
return true;
}
// 定义一个私有方法loginGtask它使用提供的authToken尝试登录到Google Tasks。
private boolean loginGtask(String authToken) {
// 设置HTTP连接的超时时间10秒和套接字超时时间15秒
int timeoutConnection = 10000;
int timeoutSocket = 15000;
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
// 创建一个HttpClient实例并使用前面设置的参数进行配置。
mHttpClient = new DefaultHttpClient(httpParameters);
// 创建一个CookieStore实例并将其设置到HttpClient中以便管理HTTP cookies。
BasicCookieStore localBasicCookieStore = new BasicCookieStore();
mHttpClient.setCookieStore(localBasicCookieStore);
// 禁用HTTP/1.1的"Expect: 100-continue"机制,以提高性能。
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// 构造登录Google Tasks的URL其中包含authToken作为查询参数。
String loginUrl = mGetUrl + "?auth=" + authToken;
// 创建一个HttpGet请求用于向登录URL发送请求。
HttpGet httpGet = new HttpGet(loginUrl);
// 执行HttpGet请求并获取响应。
HttpResponse response = null;
try {
response = mHttpClient.execute(httpGet);
} catch (Exception e) {
// 如果在执行请求时发生异常则打印错误日志并返回false。
Log.e(TAG, "httpget gtask_url failed");
return false;
}
// 从HttpClient的CookieStore中获取所有的cookies。
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false;
// 遍历所有的cookies检查是否存在名为"GTL"的认证cookie。
for (Cookie cookie : cookies) {
if (cookie.getName().contains("GTL")) {
hasAuthCookie = true;
}
}
// 如果没有找到认证cookie则打印警告日志。
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie");
}
// 从HTTP响应中获取响应体的内容。
String resString = getResponseContent(response.getEntity());
// 定义JavaScript字符串的开始和结束标记用于从响应内容中提取JavaScript代码。
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
// 在响应内容中查找JavaScript代码的开始和结束位置。
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
// 如果找到了JavaScript代码的开始和结束位置并且它们是有效的则提取JavaScript代码。
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
// 将提取的JavaScript代码解析为JSONObject并尝试从中获取客户端版本信息。
try {
JSONObject js = new JSONObject(jsString);
mClientVersion = js.getLong("v");
} catch (JSONException e) {
// 如果在解析JSON时发生异常则打印错误日志打印堆栈跟踪并返回false。
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
} catch (Exception e) {
// 简单地捕获所有其他异常这里应该是不会发生的因为前面已经对jsString进行了有效性检查
// 但为了代码的健壮性还是打印错误日志并返回false。
Log.e(TAG, "Unexpected exception");
return false;
}
// 如果一切顺利则返回true表示登录成功。
return true;
}
// 定义一个私有方法用于获取一个动作ID并每次调用时自增
private int getActionId() {
return mActionId++; // 返回当前的动作ID并将成员变量mActionId的值自增
}
// 定义一个私有方法用于创建一个HttpPost对象
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl); // 使用成员变量mPostUrl创建HttpPost对象
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); // 设置请求头,指定内容类型和字符集
httpPost.setHeader("AT", "1"); // 设置请求头AT的值为1
return httpPost; // 返回创建的HttpPost对象
}
// 定义一个私有方法用于从HttpEntity对象中获取响应内容
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null; // 初始化内容编码为空
if (entity.getContentEncoding() != null) { // 如果HttpEntity有内容编码
contentEncoding = entity.getContentEncoding().getValue(); // 获取内容编码的值
Log.d(TAG, "encoding: " + contentEncoding); // 打印日志,显示内容编码
}
InputStream input = entity.getContent(); // 获取输入流
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { // 如果内容编码是gzip
input = new GZIPInputStream(entity.getContent()); // 使用GZIPInputStream包装输入流
} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { // 如果内容编码是deflate
Inflater inflater = new Inflater(true); // 创建一个Inflater对象用于解压缩
input = new InflaterInputStream(entity.getContent(), inflater); // 使用InflaterInputStream包装输入流
}
try {
InputStreamReader isr = new InputStreamReader(input); // 创建输入流读取器
BufferedReader br = new BufferedReader(isr); // 创建缓冲读取器
StringBuilder sb = new StringBuilder(); // 创建字符串构建器,用于拼接读取的行
while (true) { // 循环读取输入流中的每一行
String buff = br.readLine(); // 读取一行
if (buff == null) { // 如果读取到null表示已到达输入流的末尾
return sb.toString(); // 返回拼接好的字符串
}
sb = sb.append(buff); // 将读取的行追加到字符串构建器中
}
} finally {
input.close(); // 关闭输入流
}
}
// 定义一个私有方法用于发送POST请求并返回JSON响应
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) { // 如果未登录
Log.e(TAG, "please login first"); // 打印错误日志,提示用户先登录
throw new ActionFailureException("not logged in"); // 抛出未登录的异常
}
HttpPost httpPost = createHttpPost(); // 创建HttpPost对象
try {
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>(); // 创建一个链表,用于存储键值对
list.add(new BasicNameValuePair("r", js.toString())); // 将JSON对象转换为字符串并添加到链表中键为"r"
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); // 创建UrlEncodedFormEntity对象用于POST请求的数据体
httpPost.setEntity(entity); // 设置HttpPost的数据体
// 执行POST请求
HttpResponse response = mHttpClient.execute(httpPost); // 使用HttpClient执行POST请求
String jsString = getResponseContent(response.getEntity()); // 获取响应内容
return new JSONObject(jsString); // 将响应内容转换为JSONObject对象并返回
} catch (ClientProtocolException e) { // 如果发生协议异常
Log.e(TAG, e.toString()); // 打印错误日志
e.printStackTrace(); // 打印异常堆栈
throw new NetworkFailureException("postRequest failed"); // 抛出网络请求失败的异常
} catch (IOException e) { // 如果发生IO异常
Log.e(TAG, e.toString()); // 打印错误日志
e.printStackTrace(); // 打印异常堆栈
throw new NetworkFailureException("postRequest failed"); // 抛出网络请求失败的异常
} catch (JSONException e) { // 如果发生JSON解析异常
Log.e(TAG, e.toString()); // 打印错误日志
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("unable to convert response content to jsonobject"); // 抛出无法将响应内容转换为JSONObject的异常
} catch (Exception e) { // 如果发生其他异常
Log.e(TAG, e.toString()); // 打印错误日志
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("error occurs when posting request"); // 抛出发送请求时发生错误的异常
}
}
// 定义一个方法,用于创建任务,可能会抛出网络失败异常
public void createTask(Task task) throws NetworkFailureException {
// 提交所有待处理的更新到服务器
commitUpdate();
try {
// 创建一个JSON对象用于POST请求
JSONObject jsPost = new JSONObject();
// 创建一个JSON数组用于存放动作列表
JSONArray actionList = new JSONArray();
// 向动作列表中添加创建任务的动作getActionId()可能是获取一个唯一的动作ID
actionList.put(task.getCreateAction(getActionId()));
// 将动作列表添加到JSON对象中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 添加客户端版本号到JSON对象中
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送POST请求到服务器并接收响应
JSONObject jsResponse = postRequest(jsPost);
// 从响应结果中获取第一个结果对象
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
// 从结果对象中获取新创建任务的ID并设置给任务对象
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
// 捕获JSON处理异常记录错误日志
Log.e(TAG, e.toString());
e.printStackTrace();
// 抛出自定义的动作失败异常
throw new ActionFailureException("create task: handing jsonobject failed");
}
}
// 定义一个方法,用于创建任务列表,可能会抛出网络失败异常
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
// 提交所有待处理的更新到服务器
commitUpdate();
try {
// 创建一个JSON对象用于POST请求
JSONObject jsPost = new JSONObject();
// 创建一个JSON数组用于存放动作列表
JSONArray actionList = new JSONArray();
// 向动作列表中添加创建任务列表的动作getActionId()可能是获取一个唯一的动作ID
actionList.put(tasklist.getCreateAction(getActionId()));
// 将动作列表添加到JSON对象中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 添加客户端版本号到JSON对象中
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送POST请求到服务器并接收响应
JSONObject jsResponse = postRequest(jsPost);
// 从响应结果中获取第一个结果对象
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
// 从结果对象中获取新创建任务列表的ID并设置给任务列表对象
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
// 捕获JSON处理异常记录错误日志
Log.e(TAG, e.toString());
e.printStackTrace();
// 抛出自定义的动作失败异常
throw new ActionFailureException("create tasklist: handing jsonobject failed");
}
}
// 定义一个方法,用于提交所有待处理的更新到服务器,可能会抛出网络失败异常
public void commitUpdate() throws NetworkFailureException {
// 如果存在待处理的更新
if (mUpdateArray != null) {
try {
// 创建一个JSON对象用于POST请求
JSONObject jsPost = new JSONObject();
// 将待处理的更新动作列表添加到JSON对象中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);
// 添加客户端版本号到JSON对象中
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送POST请求到服务器不需要接收响应可能是只需要服务器处理更新
postRequest(jsPost);
// 清空待处理的更新数组
mUpdateArray = null;
} catch (JSONException e) {
// 捕获JSON处理异常记录错误日志
Log.e(TAG, e.toString());
e.printStackTrace();
// 抛出自定义的动作失败异常
throw new ActionFailureException("commit update: handing jsonobject failed");
}
}
}
// 定义一个方法,用于添加或更新节点,如果节点不为空
public void addUpdateNode(Node node) throws NetworkFailureException {
if (node != null) {
// 如果更新数组不为空且长度超过10则提交更新
if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate();
}
// 如果更新数组为空,则初始化它
if (mUpdateArray == null)
mUpdateArray = new JSONArray();
// 将节点的更新操作添加到更新数组中
mUpdateArray.put(node.getUpdateAction(getActionId()));
}
}
// 定义一个方法,用于移动任务
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
// 在移动任务前提交所有待处理的更新
commitUpdate();
try {
// 创建一个JSON对象用于POST请求
JSONObject jsPost = new JSONObject();
// 创建一个JSON数组用于存放操作列表
JSONArray actionList = new JSONArray();
// 创建一个JSON对象表示一个操作
JSONObject action = new JSONObject();
// 设置操作类型为移动
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);
// 设置操作ID
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
// 设置任务的ID
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid());
// 如果前后父任务列表相同且任务有前一个兄弟节点则设置前一个兄弟节点的ID
if (preParent == curParent && task.getPriorSibling() != null) {
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling());
}
// 设置源任务列表的ID
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid());
// 设置目标父任务列表的ID
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid());
// 如果前后父任务列表不同则设置目标任务列表的ID这里似乎有些重复可能是特定逻辑需要
if (preParent != curParent) {
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
}
// 将操作添加到操作列表中
actionList.put(action);
// 将操作列表添加到POST请求中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送POST请求
postRequest(jsPost);
} catch (JSONException e) {
// 记录异常并抛出自定义异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("move task: handing jsonobject failed");
}
}
// 定义一个方法,用于删除节点
public void deleteNode(Node node) throws NetworkFailureException {
// 在删除节点前提交所有待处理的更新
commitUpdate();
try {
// 创建一个JSON对象用于POST请求
JSONObject jsPost = new JSONObject();
// 创建一个JSON数组用于存放操作列表
JSONArray actionList = new JSONArray();
// 标记节点为已删除
node.setDeleted(true);
// 将节点的删除操作添加到操作列表中
actionList.put(node.getUpdateAction(getActionId()));
// 将操作列表添加到POST请求中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送POST请求
postRequest(jsPost);
// 清空更新数组
mUpdateArray = null;
} catch (JSONException e) {
// 记录异常并抛出自定义异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("delete node: handing jsonobject failed");
}
}
// 定义一个方法,用于获取任务列表
public JSONArray getTaskLists() throws NetworkFailureException {
// 如果未登录,则抛出异常
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
try {
// 创建一个HttpGet请求
HttpGet httpGet = new HttpGet(mGetUrl);
// 执行请求并获取响应
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// 获取响应内容
String resString = getResponseContent(response.getEntity());
// 定义字符串用于从响应内容中提取JSON数据
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
// 查找JSON数据的开始和结束位置
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
String jsString = null;
// 如果找到了开始和结束位置则提取JSON字符串
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
// 将JSON字符串转换为JSON对象
JSONObject js = new JSONObject(jsString);
// 从JSON对象中获取任务列表数组
return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS);
} catch (ClientProtocolException e) {
// 记录异常并抛出自定义网络异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
} catch (IOException e) {
// 记录异常并抛出自定义网络异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
} catch (JSONException e) {
// 记录异常并抛出自定义操作异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task lists: handing jasonobject failed");
}
}
// 定义一个方法,用于获取任务列表,接收一个列表的全局唯一标识符作为参数,并声明可能抛出网络失败异常
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
// 提交更新操作可能是在执行网络请求前进行一些准备工作比如更新UI或检查状态
commitUpdate();
try {
// 创建一个JSONObject对象用于构建要发送的JSON数据
JSONObject jsPost = new JSONObject();
// 创建一个JSONArray对象用于存放动作列表
JSONArray actionList = new JSONArray();
// 创建一个JSONObject对象用于描述一个具体的动作
JSONObject action = new JSONObject();
// 在动作对象中设置动作类型为获取所有任务
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL);
// 设置动作ID具体ID通过getActionId()方法获取
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
// 设置列表的全局唯一标识符
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid);
// 设置是否获取已删除的任务这里设置为false即不获取
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false);
// 将动作对象添加到动作列表中
actionList.put(action);
// 将动作列表添加到要发送的JSON数据中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 在要发送的JSON数据中设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送POST请求并接收响应的JSONObject对象
JSONObject jsResponse = postRequest(jsPost);
// 从响应的JSONObject对象中获取任务列表的JSONArray对象并返回
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS);
} catch (JSONException e) {
// 捕获JSON处理异常记录错误日志
Log.e(TAG, e.toString());
// 打印堆栈跟踪信息
e.printStackTrace();
// 抛出动作失败异常说明在处理JSON对象时出错
throw new ActionFailureException("get task list: handing jsonobject failed");
}
}
// 定义一个方法,用于获取同步账户对象
public Account getSyncAccount() {
// 返回账户对象
return mAccount;
}
// 定义一个方法,用于重置更新数组
public void resetUpdateArray() {
// 将更新数组设置为null可能用于清空或重置状态
mUpdateArray = null;
}

@ -0,0 +1,824 @@
/*
* 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.gtask.remote;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.data.MetaData;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.SqlNote;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
public class GTaskManager {
private static final String TAG = GTaskManager.class.getSimpleName();
public static final int STATE_SUCCESS = 0;
public static final int STATE_NETWORK_ERROR = 1;
public static final int STATE_INTERNAL_ERROR = 2;
public static final int STATE_SYNC_IN_PROGRESS = 3;
public static final int STATE_SYNC_CANCELLED = 4;
private static GTaskManager mInstance = null;
private Activity mActivity;
private Context mContext;
private ContentResolver mContentResolver;
private boolean mSyncing;
private boolean mCancelled;
private HashMap<String, TaskList> mGTaskListHashMap;
private HashMap<String, Node> mGTaskHashMap;
private HashMap<String, MetaData> mMetaHashMap;
private TaskList mMetaList;
private HashSet<Long> mLocalDeleteIdMap;
private HashMap<String, Long> mGidToNid;
private HashMap<Long, String> mNidToGid;
private GTaskManager() {//对象初始化函数
mSyncing = false;//正在同步flase代表未执行
mCancelled = false;//全局标识flase代表可以执行
mGTaskListHashMap = new HashMap<String, TaskList>();//<>代表Java的泛型就是创建一个用类型作为参数的类
mGTaskHashMap = new HashMap<String, Node>();
mMetaHashMap = new HashMap<String, MetaData>();
mMetaList = null;
mLocalDeleteIdMap = new HashSet<Long>();
mGidToNid = new HashMap<String, Long>();
mNidToGid = new HashMap<Long, String>();//通过hashmap散列表建立映射
}
/**
* synchronized线
*
*/
public static synchronized GTaskManager getInstance() {//可能运行在多线程环境下,使用语言级同步--synchronized
if (mInstance == null) {
mInstance = new GTaskManager();
}
return mInstance;
}
/**
* synchronized线
*/
public synchronized void setActivityContext(Activity activity) {
// used for getting authtoken
mActivity = activity;
}
/**
* context---
* asyncTask---
*/
public int sync(Context context, GTaskASyncTask asyncTask) {//核心函数
if (mSyncing) {
Log.d(TAG, "Sync is in progress");//创建日志文件调试信息debug
return STATE_SYNC_IN_PROGRESS;
}
mContext = context;
mContentResolver = mContext.getContentResolver();
mSyncing = true;
mCancelled = false;
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
try {
GTaskClient client = GTaskClient.getInstance();//getInstance即为创建一个实例client---客户机
client.resetUpdateArray();//JSONArray类型reset即置为NULL
// login google task
if (!mCancelled) {
if (!client.login(mActivity)) {
throw new NetworkFailureException("login google task failed");
}
}
// get the task list from google
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList();//获取Google上的JSONtasklist转为本地TaskList
// do content sync work
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent();
} catch (NetworkFailureException e) {//分为两种异常,此类异常为网络异常
Log.e(TAG, e.toString());//创建日志文件调试信息error
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 {
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
mSyncing = false;
}
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
/**
* GtaskListGoogleJSONtasklistTaskList
* mMetaListmGTaskListHashMap,mGTaskHashMap
*/
private void initGTaskList() throws NetworkFailureException {
if (mCancelled)
return;
GTaskClient client = GTaskClient.getInstance();//getInstance即为创建一个实例client应指远端客户机
try {
//Json对象是Name Value对及子元素的无序集合相当于一个Map对象JsonObject类是bantouyan-json库对Json对象的抽象提供操纵Json对象的各种方法
//其格式为{"key1":value1,"key2",value2....};key必须是字符串
//因为ajax请求不刷新页面但配合js可以实现局部刷新因此json常常被用来作为异步请求的返回对象使用
JSONArray jsTaskLists = client.getTaskLists();
// init meta list first
mMetaList = null;//TaskList类型
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);//JSONObject与JSONArray一个为对象一个为数组此处取出单个JASONObject
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
if (name
.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
mMetaList = new TaskList();//MetaList意为元表Tasklist类型此处为初始化
mMetaList.setContentByRemoteJSON(object);//将JSON中部分数据复制到自己定义的对象中相对应的数据name->mname
// load meta data
JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) {
object = (JSONObject) jsMetas.getJSONObject(j);
MetaData metaData = new MetaData();//继承自Node
metaData.setContentByRemoteJSON(object);
if (metaData.isWorthSaving()) {//if not worth to savemetadate将不加入mMetaList
mMetaList.addChildTask(metaData);
if (metaData.getGid() != null) {
mMetaHashMap.put(metaData.getRelatedGid(), metaData);
}
}
}
}
}
// create meta list if not existed
if (mMetaList == null) {
mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META);
GTaskClient.getInstance().createTaskList(mMetaList);
}
// init task list
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);//通过getString函数传入本地某个标志数据的名称获取其在远端的名称
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)
&& !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META)) {
TaskList tasklist = new TaskList();//继承自Node
tasklist.setContentByRemoteJSON(object);
mGTaskListHashMap.put(gid, tasklist);
mGTaskHashMap.put(gid, tasklist);
// load tasks
JSONArray jsTasks = client.getTaskList(gid);
for (int j = 0; j < jsTasks.length(); j++) {
object = (JSONObject) jsTasks.getJSONObject(j);
gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
Task task = new Task();
task.setContentByRemoteJSON(object);
if (task.isWorthSaving()) {
task.setMetaInfo(mMetaHashMap.get(gid));
tasklist.addChildTask(task);
mGTaskHashMap.put(gid, task);
}
}
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("initGTaskList: handing JSONObject failed");
}
}
/**
*
*/
private void syncContent() throws NetworkFailureException {//本地内容同步操作
int syncType;
Cursor c = null;//数据库指针
String gid;
Node node;//Node包含Sync_Action的不同类型
mLocalDeleteIdMap.clear();//HashSet<Long>类型
if (mCancelled) {
return;
}
// for local deleted note
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);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c);
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
}
} else {
Log.w(TAG, "failed to query trash folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// sync folder first
syncFolder();
// for note existing in database
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);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));//通过hashmap建立联系
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);//通过hashmap建立联系
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// remote delete
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();
c = null;
}
}
// go through remaining items
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();//iterator迭代器
while (iter.hasNext()) {
Map.Entry<String, Node> 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");
}
}
// refresh local sync id
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId();
}
}
private void syncFolder() throws NetworkFailureException {
Cursor c = null;
String gid;
Node node;
int syncType;
if (mCancelled) {
return;
}
// for root folder
try {
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);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// for system folder, only update remote name if necessary
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
} else {
Log.w(TAG, "failed to query root folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// for call-note folder
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(Notes.ID_CALL_RECORD_FOLDER)
}, null);
if (c != null) {
if (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);
// for system folder, only update remote name if
// necessary
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
}
} else {
Log.w(TAG, "failed to query call note folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// for local existing folders
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);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// remote delete
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c);
}
} else {
Log.w(TAG, "failed to query existing folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// for remote add folders
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
gid = entry.getKey();
node = entry.getValue();
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
}
if (!mCancelled)
GTaskClient.getInstance().commitUpdate();
}
/**
* syncTypeaddLocalNode,addRemoteNode,deleteNode,updateLocalNode,updateRemoteNode
*/
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:
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN));
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta);
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
break;
case Node.SYNC_ACTION_DEL_REMOTE:
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:
// merging both modifications maybe a good idea
// right now just use local update simply
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_NONE:
break;
case Node.SYNC_ACTION_ERROR:
default:
throw new ActionFailureException("unkown sync action type");
}
}
/**实现功能本地增加Node */
private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote;
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 {
sqlNote = new SqlNote(mContext);
JSONObject js = node.getLocalJSONFromContent();
try {
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)) {
// the id is not available, have to create a new one
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)) {
// the data id is not available, have to create
// a new one
data.remove(DataColumns.ID);
}
}
}
}
} catch (JSONException e) {
Log.w(TAG, e.toString());
e.printStackTrace();
}
sqlNote.setContent(js);
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());
}
// create the local node
sqlNote.setGtaskId(node.getGid());
sqlNote.commit(false);
// update gid-nid mapping
mGidToNid.put(node.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), node.getGid());
// update meta
updateRemoteMeta(node.getGid(), sqlNote);
}
/**实现功能更新本地Node */
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote;
// update the note locally
sqlNote = new SqlNote(mContext, c);
sqlNote.setContent(node.getLocalJSONFromContent());
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
: new Long(Notes.ID_ROOT_FOLDER);
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot update local node");
}
sqlNote.setParentId(parentId.longValue());
sqlNote.commit(true);
// update meta info
updateRemoteMeta(node.getGid(), sqlNote);
}
/**实现功能远程增加node */
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);//从本地mContext中获取内容
Node n;
// update remotely
if (sqlNote.isNoteType()) {
Task task = new Task();
task.setContentByLocalJSON(sqlNote.getContent());
String parentGid = mNidToGid.get(sqlNote.getParentId());
if (parentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist");//调试信息
throw new ActionFailureException("cannot add remote task");
}
mGTaskListHashMap.get(parentGid).addChildTask(task);//在本地生成的GTaskList中增加子结点
//登录远程服务器创建Task
GTaskClient.getInstance().createTask(task);
n = (Node) task;
// add meta
updateRemoteMeta(task.getGid(), sqlNote);
} else {
TaskList tasklist = null;
// we need to skip folder if it has already existed
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();
//iterator迭代器通过统一的接口迭代所有的map元素
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
String gid = entry.getKey();
TaskList list = entry.getValue();
if (list.getName().equals(folderName)) {
tasklist = list;
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
}
break;
}
}
// no match we can add now
if (tasklist == null) {
tasklist = new TaskList();
tasklist.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().createTaskList(tasklist);
mGTaskListHashMap.put(tasklist.getGid(), tasklist);
}
n = (Node) tasklist;
}
// update local note
sqlNote.setGtaskId(n.getGid());
sqlNote.commit(false);
sqlNote.resetLocalModified();
sqlNote.commit(true);
// gid-id mapping
//创建id间的映射
mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid());
}
/**实现功能更新远端的Node包含meta更新 */
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
// update remotely
node.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(node);//GTaskClient用途为从本地登录远端服务器
// update meta
updateRemoteMeta(node.getGid(), sqlNote);
// move task if necessary
if (sqlNote.isNoteType()) {
Task task = (Task) node;
TaskList preParentList = task.getParent();
//preParentList为通过node获取的父节点列表
String curParentGid = mNidToGid.get(sqlNote.getParentId());
//curParentGid为通过光标在数据库中找到sqlNote的mParentId再通过mNidToGid有long类型转为String类型的Gid
if (curParentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot update remote task");
}
TaskList curParentList = mGTaskListHashMap.get(curParentGid);
//通过HashMap找到对应的Gid的TaskList
if (preParentList != curParentList) {
preParentList.removeChildTask(task);
curParentList.addChildTask(task);
GTaskClient.getInstance().moveTask(task, preParentList, curParentList);
}
}
// clear local modified flag
sqlNote.resetLocalModified();
//commit到本地数据库
sqlNote.commit(true);
}
/**实现功能升级远程metameta---元数据---计算机文件系统管理数据---管理数据的数据 */
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
if (sqlNote != null && sqlNote.isNoteType()) {
MetaData metaData = mMetaHashMap.get(gid);
if (metaData != null) {
metaData.setMeta(gid, sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(metaData);
} else {
metaData = new MetaData();
metaData.setMeta(gid, sqlNote.getContent());
mMetaList.addChildTask(metaData);
mMetaHashMap.put(gid, metaData);
GTaskClient.getInstance().createTask(metaData);
}
}
}
/**实现功能刷新本地给sync的ID对应上最后更改过的对象 */
private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) {
return;
}
// get the latest gtask list
//获取最近的gtask list
mGTaskHashMap.clear();
mGTaskListHashMap.clear();
mMetaHashMap.clear();
initGTaskList();
Cursor c = null;
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)
}, NoteColumns.TYPE + " DESC");//query语句五个参数NoteColumns.TYPE + "DESC"-----为按类型递减顺序返回查询结果new String[]{String.valueof(Notes.TYPE_SYSTEM),
//String.valueof(Notes.ID_TRASH_FOLER)}-----为选择参数,"(type<>?AND parent_id<>?)"------指明返回行过滤器SqlNote.PROJECTION_NOTE-----对应返回的数据列的名字,
//Notes.CONTENT_NOTE_URI------contentProvider包含所有数据集所对应的uri
if (c != null) {
while (c.moveToNext()) {
String gid = c.getString(SqlNote.GTASK_ID_COLUMN);
Node node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
ContentValues values = new ContentValues();//在ContentValues中创建键值对准备通过contentResolver写入数据
values.put(NoteColumns.SYNC_ID, node.getLastModified());
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,//进行批量更改选择参数为NULL应该可以用insert替换参数分别为表名和需要更新的value对象
c.getLong(SqlNote.ID_COLUMN)), values, null, null);
} else {
Log.e(TAG, "something is missed");
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 {
if (c != null) {
c.close();
c = null;
}
}
}
/**实现功能获取同步账号mAccount.name */
public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name;
}
/**实现功能取消同步置mCancelled 为 true */
public void cancelSync() {
mCancelled = true;
}
}

@ -0,0 +1,118 @@
/*
* 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;//这一行声明了类所在的包名即net.micode.notes.tool。这是Java中组织类的一种方式有助于模块化代码和避免命名冲突。
public class GTaskStringUtils {
//这一行定义了一个公开的类GTaskStringUtils。在Java中public关键字意味着这个类可以被任何其他类访问。
//接下来的部分是一系列使用final static String定义的常量。在Java中final意味着变量的值一旦被赋予就不能被改变static意味着变量属于类而不是类的任何特定对象实例String是变量的数据类型。
public final static String GTASK_JSON_ACTION_ID = "action_id";//这行代码定义了一个常量GTASK_JSON_ACTION_ID其值为"action_id"。这个常量可能用于标识JSON对象中的某个动作ID字段。
public final static String GTASK_JSON_ACTION_LIST = "action_list";//定义了一个常量GTASK_JSON_ACTION_LIST值为"action_list"可能用于表示JSON中的动作列表字段。
public final static String GTASK_JSON_ACTION_TYPE = "action_type";//定义了一个常量GTASK_JSON_ACTION_TYPE值为"action_type"用于标识JSON中的动作类型字段。
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";//定义了一个常量GTASK_JSON_ACTION_TYPE_CREATE值为"create",表示创建动作的类型。
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";//定义了一个常量GTASK_JSON_ACTION_TYPE_GETALL值为"get_all",表示获取所有数据的动作类型。
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";//定义了一个常量GTASK_JSON_ACTION_TYPE_MOVE值为"move",表示移动的动作类型。
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";//定义了一个常量GTASK_JSON_ACTION_TYPE_UPDATE值为"update",表示更新的动作类型。
//后续的代码继续定义了与GTask相关的各种JSON字段的常量包括创建者ID、子实体、客户端版本、完成状态、当前列表ID、默认列表ID、删除状态、目标列表、目标父级、目标父级类型、实体增量、
//实体类型、获取删除标志、ID、索引、最后修改时间、最新同步点、列表ID、列表集合、名称、新ID、笔记、父级ID、前一个兄弟ID、结果集合、源列表、任务集合、类型分为组和任务两种和用户等。
public final static String GTASK_JSON_CREATOR_ID = "creator_id";
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";
public final static String GTASK_JSON_CLIENT_VERSION = "client_version";
public final static String GTASK_JSON_COMPLETED = "completed";
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
public final static String GTASK_JSON_DELETED = "deleted";
public final static String GTASK_JSON_DEST_LIST = "dest_list";
public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
public final static String GTASK_JSON_GET_DELETED = "get_deleted";
public final static String GTASK_JSON_ID = "id";
public final static String GTASK_JSON_INDEX = "index";
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
public final static String GTASK_JSON_LIST_ID = "list_id";
public final static String GTASK_JSON_LISTS = "lists";
public final static String GTASK_JSON_NAME = "name";
public final static String GTASK_JSON_NEW_ID = "new_id";
public final static String GTASK_JSON_NOTES = "notes";
public final static String GTASK_JSON_PARENT_ID = "parent_id";
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
public final static String GTASK_JSON_RESULTS = "results";
public final static String GTASK_JSON_SOURCE_LIST = "source_list";
public final static String GTASK_JSON_TASKS = "tasks";
public final static String GTASK_JSON_TYPE = "type";
public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
public final static String GTASK_JSON_TYPE_TASK = "TASK";
public final static String GTASK_JSON_USER = "user";
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";//定义了一个常量MIUI_FOLDER_PREFFIX值为"[MIUI_Notes]"可能用于标识MIUI笔记的文件夹前缀。
public final static String FOLDER_DEFAULT = "Default";//定义了一个常量FOLDER_DEFAULT值为"Default",表示默认文件夹的名称。
public final static String FOLDER_CALL_NOTE = "Call_Note";//定义了一个常量FOLDER_CALL_NOTE值为"Call_Note",表示通话记录文件夹的名称。
public final static String FOLDER_META = "METADATA";//定义了一个常量FOLDER_META值为"METADATA",表示元数据文件夹的名称。
public final static String META_HEAD_GTASK_ID = "meta_gid";//定义了一个常量META_HEAD_GTASK_ID值为"meta_gid"可能用于标识元数据中的GTask ID。
public final static String META_HEAD_NOTE = "meta_note";//定义了一个常量META_HEAD_NOTE值为"meta_note",可能用于标识元数据中的笔记信息。
public final static String META_HEAD_DATA = "meta_data";//定义了一个常量META_HEAD_DATA值为"meta_data",可能用于标识元数据中的其他数据。
public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE";//定义了一个常量META_NOTE_NAME值为"[META INFO] DON'T UPDATE AND DELETE",表示包含元数据的笔记的名称,提示用户不要更新或删除这个笔记。
}

@ -0,0 +1,144 @@
/*
* 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.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;
// 定义一个名为GTaskSyncService的服务类它继承自Service类
public class GTaskSyncService extends Service {
// 定义一个字符串常量用于在Intent中标识同步操作类型
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(""); // 发送广播通知开始同步无具体消息可能用于初始化UI
mSyncTask.execute(); // 执行同步任务
}
}
// 定义一个私有方法,用于取消同步
private void cancelSync() {
if (mSyncTask != null) { // 如果当前有正在进行的同步任务
mSyncTask.cancelSync(); // 取消同步任务
}
}
// 当服务被创建时调用此方法
@Override
public void onCreate() {
mSyncTask = null; // 初始化同步任务为null
}
// 当服务接收到启动命令时调用此方法
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle bundle = intent.getExtras(); // 获取Intent中的额外数据
if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { // 如果Intent包含同步操作类型
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; // 如果服务被杀死系统将会重新创建服务并调用onStartCommand()方法但传入的是null的Intent
}
return super.onStartCommand(intent, flags, startId); // 如果Intent不包含同步操作类型则调用父类方法
}
// 当设备内存不足时调用此方法
@Override
public void onLowMemory() {
if (mSyncTask != null) { // 如果当前有正在进行的同步任务
mSyncTask.cancelSync(); // 取消同步任务以释放内存
}
}
// 实现Service的onBind方法由于此服务不需要绑定因此返回null
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); // 设置GTaskManager的Activity上下文
Intent intent = new Intent(activity, GTaskSyncService.class); // 创建启动服务的Intent
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); // 添加同步操作类型的额外数据
activity.startService(intent); // 启动服务
}
// 定义一个公开静态方法用于从Context如Activity或Application取消同步服务
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; // 返回mSyncTask是否为null的布尔值
}
// 定义一个公开静态方法,用于获取同步进度消息
public static String getProgressString() {
return mSyncProgress; // 返回同步进度消息
}
}

@ -0,0 +1,131 @@
/*
* 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.data;
//导入 Android 的 Cursor 类Cursor 是用于访问和操作数据库查询结果的工具。
import android.database.Cursor;
//导入 Android 的 Log 类,用于在代码中输出日志信息。
import android.util.Log;
//导入一个自定义的工具类 GTaskStringUtils它包含与 Google Tasks 相关的常量和方法。
import net.micode.notes.tool.GTaskStringUtils;
//导入 JSON 处理类。JSONObject 用于创建和解析 JSON 数据JSONException 用于捕获解析 JSON 时的错误。
import org.json.JSONException;
import org.json.JSONObject;
//定义一个公共类 MetaData该类继承自 Task 类,表示与 Google 任务相关的元数据(例如任务的 gid
public class MetaData extends Task {
//定义一个 TAG 字符串常量,存储当前类的简短类名 "MetaData",用于在日志中输出调试信息。
private final static String TAG = MetaData.class.getSimpleName();
//声明一个私有成员变量 mRelatedGid用于保存与任务相关的 Google 任务 IDgid。初始值为 null。
private String mRelatedGid = null;
// 定义一个公共方法 setMeta用于设置任务的元数据。接收两个参数
// gid一个字符串表示任务的 Google ID。
// metaInfo一个 JSONObject包含其他元数据如任务的附加信息
public void setMeta(String gid, JSONObject metaInfo) {
// 尝试将传入的 gid 插入到 metaInfo 中。GTaskStringUtils.META_HEAD_GTASK_ID 是用于存储任务 ID 的常量。
// 如果插入失败,捕获 JSONException 异常并打印错误日志。
try {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");
}
//将 metaInfo 转换为字符串并调用 setNotes() 方法保存。这将任务的元数据作为字符串保存在任务的 notes 字段中。
setNotes(metaInfo.toString());
//调用 setName() 设置任务的名称为 GTaskStringUtils.META_NOTE_NAME。这个名称可能是一个预定义的常量表示任务的默认名称。
setName(GTaskStringUtils.META_NOTE_NAME);
}
//定义一个公共方法 getRelatedGid用于获取与任务相关的 Google 任务 ID。
// return mRelatedGid;:返回之前保存的 mRelatedGid。
public String getRelatedGid() {
return mRelatedGid;
}
//@Override表示该方法是重写父类 Task 中的方法。
//public boolean isWorthSaving():定义一个方法 isWorthSaving用于判断当前任务是否值得保存。通常它会根据某些条件来判断任务是否需要被同步或保存到数据库中。
//return getNotes() != null;:判断任务的 notes 字段是否为 null。如果 notes 字段不为空,说明任务有有效内容,返回 true否则返回 false。
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
@Override
//定义方法 setContentByRemoteJSON用于从远程获取 JSON 数据并设置任务内容。
public void setContentByRemoteJSON(JSONObject js) {
//调用父类的同名方法来处理远程 JSON 数据的基本设置。
super.setContentByRemoteJSON(js);
//检查任务的 notes 字段是否不为空。
if (getNotes() != null) {
//如果 notes 不为空,尝试将其转换为 JSONObject。
//然后,从中提取出 gid 并保存到 mRelatedGid 中。
// 如果解析过程中出现错误(例如格式问题),会捕获 JSONException 并输出警告日志,同时将 mRelatedGid 设为 null。
try {
JSONObject metaInfo = new JSONObject(getNotes().trim());
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
mRelatedGid = null;
}
}
}
//@Override
//public void setContentByLocalJSON(JSONObject js)
//throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
@Override
//该方法定义了从本地 JSON 数据设置任务内容的行为。
public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
//该方法被设计为不可调用,如果被调用,抛出 IllegalAccessError 异常。
// 该类不允许使用本地 JSON 数据设置任务内容,因此抛出异常来提醒开发者
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
//表示重写父类的方法。
@Override
//该方法定义了获取本地 JSON 数据的行为。
public JSONObject getLocalJSONFromContent() {
//与 setContentByLocalJSON 类似,该方法不应该被调用,如果被调用,抛出 IllegalAccessError 异常.
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
}
//表示重写父类的方法。
@Override
//定义获取同步操作的方法,通常会返回一个与同步操作相关的标志值。
public int getSyncAction(Cursor c) {
//该方法不应被调用,抛出 IllegalAccessError 异常。
throw new IllegalAccessError("MetaData:getSyncAction should not be called");
}
}

@ -0,0 +1,88 @@
/*
* 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.
*/
/*
NetworkFailureException Java Exception
net.micode.notes.gtask.exception 使 Apache 2.0
*/
//包声明导入
//这行代码声明了类 NetworkFailureException 所在的包:
// net.micode.notes.gtask.exception。包用于组织类和接口避免命名冲突并可以根据需要进行访问控制。
package net.micode.notes.gtask.exception;
/*
NetworkFailureException Exception checked exception
Exception try-catch throws
*/
public class NetworkFailureException extends Exception {
/*
serialVersionUID
I/O serialVersionUID
InvalidClassException
*/
private static final long serialVersionUID = 2107610287180234136L;
/*
Exception
*/
public NetworkFailureException() {
super();
}
/*
paramString Exception
*/
public NetworkFailureException(String paramString) {
super(paramString);
}
/*
Throwable paramThrowable
Throwable Exception Error
*/
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}
}
/*
Checked Exception
NetworkFailureException Exception checked exception RuntimeException
NetworkFailureException throws
使
NetworkFailureException
访 NetworkFailureException
/
NetworkFailureException Exception
Exception
*/

@ -0,0 +1,137 @@
/*
* 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.gtask.data;
// 导入必要的包
import android.database.Cursor;
import org.json.JSONObject;
public abstract class Node {
//// 定义同步操作常量,表示不同的同步动作
//这段代码定义了不同的常量,表示不同的同步操作类型。这些常量用于表示与远程和本地数据同步的动作。每个动作都用一个整数值标识,
// 例如SYNC_ACTION_ADD_REMOTE 表示添加远程任务,
// SYNC_ACTION_UPDATE_LOCAL 表示更新本地任务,
// SYNC_ACTION_ERROR 表示同步错误等。
public static final int SYNC_ACTION_NONE = 0;
public static final int SYNC_ACTION_ADD_REMOTE = 1;
public static final int SYNC_ACTION_ADD_LOCAL = 2;
public static final int SYNC_ACTION_DEL_REMOTE = 3;
public static final int SYNC_ACTION_DEL_LOCAL = 4;
public static final int SYNC_ACTION_UPDATE_REMOTE = 5;
public static final int SYNC_ACTION_UPDATE_LOCAL = 6;
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;
public static final int SYNC_ACTION_ERROR = 8;
//mGid一个字符串存储任务的唯一标识符Google ID用于区分不同的任务。
//mName任务的名称存储任务的标题或名称。
//mLastModified任务的最后修改时间使用 long 类型(通常是 Unix 时间戳表示自1970年1月1日以来的毫秒数
//mDeleted布尔值表示该任务是否被删除。
// 任务的唯一标识符
private String mGid;
// 任务的名称
private String mName;
// 最后修改时间
private long mLastModified;
// 任务是否已删除
private boolean mDeleted;
public Node() {
// 默认构造函数,初始化 mGid 为 null
mGid = null;
// 默认构造函数,初始化 mName 为空字符串
mName = "";
// 默认构造函数,初始化 mLastModified 为 0
mLastModified = 0;
// 默认构造函数,初始化 mDeleted 为 false表示任务未删除
mDeleted = false;
}
//getCreateAction(int actionId):根据给定的 actionId返回一个 JSONObject表示创建操作的同步动作。具体内容依赖子类实现。
//getUpdateAction(int actionId):根据给定的 actionId返回一个 JSONObject表示更新操作的同步动作。具体内容依赖子类实现。
//setContentByRemoteJSON(JSONObject js):从远程的 JSON 数据中设置内容。js 是传入的 JSONObject该方法的具体实现依赖子类。
//setContentByLocalJSON(JSONObject js):从本地的 JSON 数据中设置内容。js 是传入的 JSONObject该方法的具体实现依赖子类。
//getLocalJSONFromContent():将本地内容转换为 JSONObject返回一个 JSON 对象。
//getSyncAction(Cursor c):从 Cursor 对象中获取同步操作的动作,返回一个整数,表示当前同步操作的类型。
public abstract JSONObject getCreateAction(int actionId);
public abstract JSONObject getUpdateAction(int actionId);
public abstract void setContentByRemoteJSON(JSONObject js);
public abstract void setContentByLocalJSON(JSONObject js);
public abstract JSONObject getLocalJSONFromContent();
public abstract int getSyncAction(Cursor c);
//这些是 Node 类的 setter 方法,用于设置成员变量的值:
//setGid(String gid):设置任务的 GidGoogle ID
//setName(String name):设置任务的名称。
//setLastModified(long lastModified):设置任务的最后修改时间。
//setDeleted(boolean deleted):设置任务是否被删除。
public void setGid(String gid) {
this.mGid = gid;
}
public void setName(String name) {
this.mName = name;
}
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
}
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
}
//这些是 Node 类的 getter 方法,用于获取成员变量的值:
//getGid():返回任务的 GidGoogle ID
//getName():返回任务的名称。
//getLastModified():返回任务的最后修改时间。
//getDeleted():返回任务是否已删除的布尔值。
public String getGid() {
return this.mGid;
}
public String getName() {
return this.mName;
}
public long getLastModified() {
return this.mLastModified;
}
public boolean getDeleted() {
return this.mDeleted;
}
}

@ -0,0 +1,302 @@
/*
* 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;//声明了当前代码所在的包package名为net.micode.notes.model
import android.content.ContentProviderOperation;//导入Android框架中的ContentProviderOperation类。
import android.content.ContentProviderResult;//导入ContentProviderResult类。
import android.content.ContentUris;//导入ContentUris类。这个类提供了用于处理URI统一资源标识符和ID之间转换的实用方法。
import android.content.ContentValues;//导入ContentValues类。这个类用于存储一组键值对通常用于传递给内容提供者ContentProvider进行插入或更新操作。
import android.content.Context;//导入Context类。在Android中Context是一个抽象类它允许访问特定资源和应用级操作如启动活动、广播和接收意图等。
import android.content.OperationApplicationException;//导入OperationApplicationException类。这个异常在尝试应用一个操作到内容提供者时如果内容提供者报告了一个错误就会被抛出。
import android.net.Uri;//导入Uri类。这个类表示统一资源标识符URI用于唯一标识内容提供者中的数据。
import android.os.RemoteException;//导入RemoteException类。这个异常是在尝试与远程对象如另一个应用中的服务进行通信时如果通信失败就会被抛出。
import android.util.Log;//导入Log类。这个类提供了用于记录日志信息的静态方法帮助开发者在开发过程中调试应用。
import net.micode.notes.data.Notes;//导入Notes类。
import net.micode.notes.data.Notes.CallNote;//导入Notes类中的CallNote内部类。
import net.micode.notes.data.Notes.DataColumns;//导入Notes类中的DataColumns内部类
import net.micode.notes.data.Notes.NoteColumns;//导入Notes类中的NoteColumns内部类。
import net.micode.notes.data.Notes.TextNote;//导入Notes类中的TextNote内部类。
import java.util.ArrayList;//导入Java的ArrayList类。
//这段代码主要是导入了一些必要的类用于后续操作数据库中的笔记数据。通过导入这些类开发者可以使用Android框架提供的API来执行数据库操作、处理URI、记录日志等。
// 定义一个名为Note的公开类
public class Note {
// 定义一个私有的ContentValues对象用于存储要插入数据库的数据差异
private ContentValues mNoteDiffValues;
// 定义一个私有的NoteData对象可能用于存储笔记的特定数据
private NoteData mNoteData;
// 定义一个私有的静态常量TAG用于日志记录
private static final String TAG = "Note";
/**
* ID
*/
// 定义一个公开的静态同步方法该方法接受一个Context对象和一个文件夹ID作为参数并返回一个long类型的笔记ID
public static synchronized long getNewNoteId(Context context, long folderId) {
// 创建一个新的ContentValues对象用于存储要插入数据库的新笔记的数据
ContentValues values = new ContentValues();
// 获取当前系统时间(毫秒)
long createdTime = System.currentTimeMillis();
// 将创建时间和修改时间设置为当前时间并存储到values对象中
values.put(NoteColumns.CREATED_DATE, createdTime);
values.put(NoteColumns.MODIFIED_DATE, createdTime);
// 设置笔记类型为普通笔记并存储到values对象中
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
// 设置本地修改标志为1并存储到values对象中可能表示笔记已被本地修改
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 设置父文件夹ID并存储到values对象中
values.put(NoteColumns.PARENT_ID, folderId);
// 使用ContentResolver向数据库插入新笔记并返回新笔记的Uri
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
// 定义一个long类型的变量noteId用于存储新笔记的ID
long noteId = 0;
try {
// 从返回的Uri中提取新笔记的ID假设ID是Uri路径段的第二个元素
noteId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
// 如果提取ID时发生数字格式异常则记录错误日志并将noteId设置为0
Log.e(TAG, "Get note id error :" + e.toString());
noteId = 0;
}
// 如果noteId为-1尽管在正常情况下不太可能因为Long.valueOf不会返回-1则抛出异常
if (noteId == -1) {
throw new IllegalStateException("Wrong note id:" + noteId);
}
// 返回新笔记的ID
return noteId;
}
// Note类的无参构造函数
public Note() {
// 在构造函数中初始化mNoteDiffValues和mNoteData对象
mNoteDiffValues = new ContentValues();
mNoteData = new NoteData();
}
// 定义一个公开的方法,用于设置笔记的某个键值对,并标记笔记为已修改和更新修改时间
public void setNoteValue(String key, String value) {
// 将键值对存储到mNoteDiffValues中这通常用于记录要同步到数据库的数据变化
mNoteDiffValues.put(key, value);
// 将LOCAL_MODIFIED字段设置为1表示笔记已被本地修改
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
// 更新MODIFIED_DATE字段为当前时间表示笔记的最后修改时间
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
// 定义一个公开的方法,用于设置文本数据的某个键值对
public void setTextData(String key, String value) {
// 调用mNoteData的setTextData方法将键值对存储到文本数据中
mNoteData.setTextData(key, value);
}
// 定义一个公开的方法用于设置文本数据的ID
public void setTextDataId(long id) {
// 调用mNoteData的setTextDataId方法设置文本数据的ID
mNoteData.setTextDataId(id);
}
// 定义一个公开的方法用于获取文本数据的ID
public long getTextDataId() {
// 返回mNoteData中存储的文本数据ID
return mNoteData.mTextDataId;
}
// 定义一个公开的方法用于设置通话数据的ID
public void setCallDataId(long id) {
// 调用mNoteData的setCallDataId方法设置通话数据的ID
mNoteData.setCallDataId(id);
}
// 定义一个公开的方法,用于设置通话数据的某个键值对
public void setCallData(String key, String value) {
// 调用mNoteData的setCallData方法将键值对存储到通话数据中
mNoteData.setCallData(key, value);
}
// 定义一个公开的方法,用于检查笔记是否已被本地修改
public boolean isLocalModified() {
// 如果mNoteDiffValues中有数据变化或者mNoteData也被标记为已修改则返回true
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
// 定义一个公开的方法,用于同步笔记数据到数据库
public boolean syncNote(Context context, long noteId) {
// 检查传入的noteId是否有效大于0
if (noteId <= 0) {
// 如果noteId无效则抛出异常
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
// 检查笔记是否已被本地修改
if (!isLocalModified()) {
// 如果笔记没有被修改则直接返回true表示同步成功因为没有需要同步的变化
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
*/
// 定义一个名为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";
// NoteData的构造函数初始化成员变量
public NoteData() {
mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues();
mTextDataId = 0;
mCallDataId = 0;
}
// 检查文本数据或通话数据是否已被修改
boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
// 设置文本数据的ID并检查ID是否有效
void setTextDataId(long id) {
if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
mTextDataId = id;
}
// 设置通话数据的ID并检查ID是否有效
void setCallDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
}
mCallDataId = id;
}
void setCallData(String key, String value) {
// 将键值对放入mCallDataValues中用于存储通话数据
mCallDataValues.put(key, value);
// 标记笔记在本地已被修改
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
// 更新笔记的修改日期
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
void setTextData(String key, String value) {
// 将键值对放入mTextDataValues中用于存储文本数据
mTextDataValues.put(key, value);
// 标记笔记在本地已被修改
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<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
// 初始化一个操作构建器初始值为null后面会根据需要赋值
ContentProviderOperation.Builder builder = null;
// 如果mTextDataValues中有数据需要插入或更新
if(mTextDataValues.size() > 0) {
// 将noteId放入mTextDataValues中以便与特定的笔记关联
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
// 如果mTextDataId为0表示需要插入新数据
if (mTextDataId == 0) {
// 设置MIME类型表示这是文本笔记数据
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
// 插入新数据到内容提供者并获取返回的Uri
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mTextDataValues);
// 尝试从Uri中提取新插入数据的ID并设置到mTextDataId中
try {
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
// 如果提取ID失败记录错误日志并清空mTextDataValues
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
mTextDataValues.clear();
// 返回null表示操作失败
return null;
}
} else {
// 如果mTextDataId不为0表示需要更新现有数据
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId));
builder.withValues(mTextDataValues);
operationList.add(builder.build());
}
// 清空mTextDataValues因为它已经被处理
mTextDataValues.clear();
}
// 如果mCallDataValues中有数据需要插入或更新处理逻辑与mTextDataValues相同
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);
// 如果结果数组为空、长度为0或第一个结果为null表示操作失败返回null
// 否则返回与noteId关联的笔记URI
return (results == null || results.length == 0 || results[0] == null) ? null
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
} catch (RemoteException e) {
// 如果发生远程异常记录错误日志并返回null
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
} catch (OperationApplicationException e) {
// 如果发生操作应用异常记录错误日志并返回null
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
}
}
// 如果没有任何数据需要处理返回null
return null;
}

@ -0,0 +1,142 @@
/*
* 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.widget;/*声明了该文件所属的包。*/
import android.app.PendingIntent;/*引入了所需的Android框架和自定义类的导入语句。*/
import android.appwidget.AppWidgetManager;/*引入了所需的Android框架和自定义类的导入语句。*/
import android.appwidget.AppWidgetProvider;/*引入了所需的Android框架和自定义类的导入语句。*/
import android.content.ContentValues;/*引入了所需的Android框架和自定义类的导入语句。*/
import android.content.Context;/*引入了所需的Android框架和自定义类的导入语句。*/
import android.content.Intent;/*引入了所需的Android框架和自定义类的导入语句。*/
import android.database.Cursor;/*引入了所需的Android框架和自定义类的导入语句。*/
import android.util.Log;/*引入了所需的Android框架和自定义类的导入语句。*/
import android.widget.RemoteViews;/*引入了所需的Android框架和自定义类的导入语句。*/
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListActivity;/*引入了所需的Android框架和自定义类的导入语句。*/
public abstract class NoteWidgetProvider extends AppWidgetProvider {/* NoteWidgetProvider
AppWidgetProvider NoteWidgetProvider 广*/
public static final String [] PROJECTION = new String [] {/* PROJECTION
IDID*/
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET
};
/*定义了三个常量,分别表示 PROJECTION 数组中ID、背景颜色ID和摘要的索引位置。*/
public static final int COLUMN_ID = 0;
public static final int COLUMN_BG_COLOR_ID = 1;
public static final int COLUMN_SNIPPET = 2;
private static final String TAG = "NoteWidgetProvider";/*定义了一个用于日志输出的标签。*/
@Override
public void onDeleted(Context context, int[] appWidgetIds) {/*重写了 onDeleted 方法,当小部件被删除时调用。*/
ContentValues values = new ContentValues();/* ContentValues WIDGET_ID ID
*/
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
for (int i = 0; i < appWidgetIds.length; i++) {/*ID
WIDGET_ID */
context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
values,
NoteColumns.WIDGET_ID + "=?",
new String[] { String.valueOf(appWidgetIds[i])});
}
}
private Cursor getNoteWidgetInfo(Context context, int widgetId) {/*
ID*/
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null);/*使用 ContentResolver 查询数据库,返回包含笔记信息的 Cursor 对象。*/
}
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false);
}/*定义了一个受保护的方法 update它调用另一个重载的 update 方法,并传递一个默认的隐私模式值 false。*/
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {/* updateIDprivacyMode
*/
for (int i = 0; i < appWidgetIds.length; i++) {
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {/*遍历小部件ID数组并检查每个ID是否有效。*/
int bgId = ResourceParser.getDefaultBgId(context);/*初始化背景ID和摘要字符串。*/
String snippet = "";
Intent intent = new Intent(context, NoteEditActivity.class);/*创建一个意图,用于启动 NoteEditActivity。*/
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);/*设置意图的标志和额外数据。*/
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType());
Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]);/*查询数据库获取小部件的笔记信息。*/
if (c != null && c.moveToFirst()) {/*如果查询结果不为空且能移动到第一条记录。*/
if (c.getCount() > 1) {/*如果有多条记录,则记录错误日志并关闭游标,返回。*/
Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]);
c.close();
return;
}
/*根据查询结果设置摘要、背景ID和意图动作。*/
snippet = c.getString(COLUMN_SNIPPET);
bgId = c.getInt(COLUMN_BG_COLOR_ID);
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID));
intent.setAction(Intent.ACTION_VIEW);
} else {
snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
}
if (c != null) {/*如果游标不为空,则关闭它。*/
c.close();
}
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());/* RemoteViews
*/
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));/*设置小部件背景图片。*/
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);/*向意图添加背景ID的额外数据。*/
/**
* Generate the pending intent to start host for the widget
*/
PendingIntent pendingIntent = null;/*初始化 PendingIntent 对象。*/
if (privacyMode) {/*
NotesListActivity PendingIntent*/
rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode));
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
} else {/*如果不是隐私模式,则:设置文本视图为摘要文本,并创建指向 NoteEditActivity 的 PendingIntent。*/
rv.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);/*
PendingIntent*/
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);/*使用新的远程视图更新小部件。*/
}
}
}
/*定义了三个抽象方法子类需要实现这些方法以提供背景资源ID、布局ID和小部件类型。*/
protected abstract int getBgResourceId(int bgId);
protected abstract int getLayoutId();
protected abstract int getWidgetType();
}

@ -0,0 +1,53 @@
/*
* 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.widget;
// 导入所需的Android和Java类
import android.appwidget.AppWidgetManager;
import android.content.Context;
// 导入项目内的其他类
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
// 声明NoteWidgetProvider_2x类它继承自NoteWidgetProvider
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
// 当小部件需要更新时,系统会调用此方法
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的update方法传入相同的参数以执行更新逻辑
super.update(context, appWidgetManager, appWidgetIds);
}
// 重写父类的getLayoutId方法用于返回此小部件布局的资源ID
@Override
protected int getLayoutId() {
// 返回R.layout.widget_2x这是预定义在res/layout目录下的XML布局文件
return R.layout.widget_2x;
}
// 重写父类的getBgResourceId方法用于根据传入的背景ID获取对应的资源ID
@Override
protected int getBgResourceId(int bgId) {
// 调用ResourceParser.WidgetBgResources.getWidget2xBgResource方法传入bgId
// 并返回对应的2x小部件背景资源ID
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
}
// 重写父类的getWidgetType方法用于返回此小部件的类型
@Override
protected int getWidgetType() {
// 返回Notes.TYPE_WIDGET_2X这是一个在Notes类中定义的常量表示2x小部件的类型
return Notes.TYPE_WIDGET_2X;
}
}

@ -0,0 +1,65 @@
/*
* 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.widget;
// 导入Android框架中用于管理小部件的类
import android.appwidget.AppWidgetManager;
// 导入Android框架中提供应用环境信息的类
import android.content.Context;
// 导入项目内的资源类和其他相关类
import net.micode.notes.R; // 包含项目资源引用的类
import net.micode.notes.data.Notes; // 可能包含与笔记数据相关的常量或方法
import net.micode.notes.tool.ResourceParser; // 用于解析资源的工具类
// 声明NoteWidgetProvider_4x类它继承自NoteWidgetProvider
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
// 当小部件需要更新时,系统会调用此方法
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的update方法传入相同的参数执行更新逻辑
super.update(context, appWidgetManager, appWidgetIds);
}
// 重写父类的getLayoutId方法用于返回此小部件的布局资源ID
// 这里不需要@Override注解因为该方法在父类中不是抽象的且可见性允许重写
// 但加上@Override可以提高代码的可读性和维护性确保方法签名的正确性。
// 实际上,由于子类直接调用了父类的非抽象方法,这里的重写是可选的,
// 但如果父类中的getLayoutId方法被重写以提供不同的行为则这里的重写是必要的。
// 在这个例子中我们假设父类有一个受保护的getLayoutId方法这里是为了特定尺寸重写它。
protected int getLayoutId() {
// 返回R.layout.widget_4x这是预定义在res/layout目录下的XML布局文件
return R.layout.widget_4x;
}
// 重写父类的getBgResourceId方法用于根据传入的背景ID获取对应的资源ID
@Override
protected int getBgResourceId(int bgId) {
// 调用ResourceParser.WidgetBgResources.getWidget4xBgResource方法传入bgId
// 并返回对应的4x小部件背景资源ID
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
}
// 重写父类的getWidgetType方法用于返回此小部件的类型
@Override
protected int getWidgetType() {
// 返回Notes.TYPE_WIDGET_4X这是一个在Notes类中定义的常量表示4x小部件的类型
return Notes.TYPE_WIDGET_4X;
}
}

@ -0,0 +1,355 @@
/*
* 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.data;
import android.net.Uri;
// Notes 类用于定义与笔记管理相关的常量和数据结构
public class Notes {
// 内容提供者的授权字符串,用于标识应用的数据
public static final String AUTHORITY = "micode_notes";
// 日志标记,用于标识与 Notes 类相关的日志消息
public static final String TAG = "Notes";
// 常量,表示不同类型的项目:笔记、文件夹和系统类型
public static final int TYPE_NOTE = 0;
//常规笔记的标识符
public static final int TYPE_FOLDER = 1;
//文件夹的标识符
public static final int TYPE_SYSTEM = 2;
//系统级实体(如默认文件夹)的标识符
/**
* Following IDs are system folders' identifiers id
* {@link Notes#ID_ROOT_FOLDER } is default folder
* {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
public static final int ID_ROOT_FOLDER = 0;
//根文件夹的id
public static final int ID_TEMPARAY_FOLDER = -1;
//临时笔记的id
public static final int ID_CALL_RECORD_FOLDER = -2;
//存储通话记录的id
public static final int ID_TRASH_FOLER = -3;
//回收站文件夹的id用于存储删除的笔记
//提醒日期的键
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date";
//背景颜色颜色id的键
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id";
//小部件的键
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id";
//小部件类型的键
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type";
//文件夹id的键
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id";
//通话日期的键
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date";
//定义文件类型及其大小的常量
//无效的小部件类型
public static final int TYPE_WIDGET_INVALIDE = -1;
//2x 大小的小部件类型
public static final int TYPE_WIDGET_2X = 0;
//4x 大小的小部件类型
public static final int TYPE_WIDGET_4X = 1;
//内部类,用于保护与数据相关的常量,特别是笔记的类型
public static class DataConstants {
// 常规笔记的内容类型
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
//通话记录的内容类型
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
}
/**
* Uri to query all notes and folders
*/
//查询所有笔记和文件夹的 uri ,此 uri 可与内容解析器一起使用,以访问笔记数据
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* Uri to query data
*/
//查询与笔记关联的数据的 uri ,可以访问以笔记相关的附加信息
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
// 接口定义笔记数据库表的架构,包括列名
public interface NoteColumns {
/**
* The unique ID for a row id
* <P> Type: INTEGER (long) </P> INREGER (long)
*/
//唯一标识符的列名
public static final String ID = "_id";
/**
* The parent's id for note or folder id·
* <P> Type: INTEGER (long) </P>
*/
//笔记或文件夹的父 id 名称
public static final String PARENT_ID = "parent_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
//创建日期的列名
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
// 修改日期的列名
public static final String MODIFIED_DATE = "modified_date";
/**
* Alert date
* <P> Type: INTEGER (long) </P>
*/
//提醒日期的列名
public static final String ALERTED_DATE = "alert_date";
/**
* Folder's name or text content of note
* <P> Type: TEXT </P>
*/
//文本内容或文件夹名称的列名
public static final String SNIPPET = "snippet";
/**
* Note's widget id id
* <P> Type: INTEGER (long) </P>
*/
//笔记小部件的列名
public static final String WIDGET_ID = "widget_id";
/**
* Note's widget type
* <P> Type: INTEGER (long) </P>
*/
//笔记的小部件类型的列名
public static final String WIDGET_TYPE = "widget_type";
/**
* Note's background color's id id
* <P> Type: INTEGER (long) </P>
*/
//笔记的背景颜色 id 的列名
public static final String BG_COLOR_ID = "bg_color_id";
/**
* For text note, it doesn't has attachment, for multi-media
* note, it has at least one attachment
* <P> Type: INTEGER </P>
*/
// 表示笔记是否有附件。对于文本笔记此值为0无附件
// 对于多媒体笔记此值为1至少有一个附件。类型为整数。
public static final String HAS_ATTACHMENT = "has_attachment";
/**
* Folder's count of notes
* <P> Type: INTEGER (long) </P>
*/
// 表示文件夹中笔记的数量,类型为整数(长整型),
// 用于跟踪文件夹内包含的笔记总数。
public static final String NOTES_COUNT = "notes_count";
/**
* The file type: folder or note
* <P> Type: INTEGER </P>
*/
// 表示文件的类型,可以是文件夹或笔记。类型为整数,
// 通过不同的整数值来区分文件夹和笔记。
public static final String TYPE = "type";
/**
* The last sync id
* <P> Type: INTEGER (long) </P>
*/
// 表示最后一次同步的ID。类型为整数长整型
// 用于标识最近的一次数据同步状态,以便进行版本控制。
public static final String SYNC_ID = "sync_id";
/**
* Sign to indicate local modified or not
* <P> Type: INTEGER </P>
*/
// 表示本地是否有修改的标志。类型为整数,
// 如果此值为1表示本地数据已被修改如果为0则未修改。
public static final String LOCAL_MODIFIED = "local_modified";
/**
* Original parent id before moving into temporary folder
* <P> Type : INTEGER </P>
*/
// 表示在移动到临时文件夹之前的原始父级ID。类型为整数
// 这有助于在需要恢复或参考原始位置时使用。
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* The gtask id
* <P> Type : TEXT </P>
*/
// 表示与 Google 任务GTask相关联的ID类型为文本
// 用于链接或引用与此笔记相关的Google任务
public static final String GTASK_ID = "gtask_id";
/**
* The version code
* <P> Type : INTEGER (long) </P>
*/
// 表示笔记或文件夹的版本号,类型为整数(长整型)
// 用于版本控制和管理更新,以确保数据的一致性
public static final String VERSION = "version";
}
// DataColumns 接口用于统一管理与数据列相关的常量,这使得在多个类之间共享相同的列名和类型变得更加简洁和一致
public interface DataColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
// 表示数据库中每一行的唯一标识符,类型为整数(长整型)
public static final String ID = "_id";
/**
* The MIME type of the item represented by this row.
* <P> Type: Text </P>
*/
// 表示此行数据的MIME类型用于描述数据的格式类型为文本
public static final String MIME_TYPE = "mime_type";
/**
* The reference id to note that this data belongs to
* <P> Type: INTEGER (long) </P>
*/
// 表示与此数据相关联的笔记的ID类型为整型
public static final String NOTE_ID = "note_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
// 表示笔记或文件夹的创建日期,类型为整型
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
// 表示笔记或文件夹的最后修改日期,类型为整型
public static final String MODIFIED_DATE = "modified_date";
/**
* Data's content
* <P> Type: TEXT </P>
*/
// 表示数据的具体内容,类型为文本型
public static final String CONTENT = "content";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
// 通用数据列具体含义取决于MIME类型通常用于存储整数类型的数据类型为整数
public static final String DATA1 = "data1";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
// 通用数据列具体含义取决于MIME类型通常用于存储整数类型的数据类型为整数
public static final String DATA2 = "data2";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
// 通用数据列具体含义取决于MIME类型通常用于存储文本类型的数据类型为文本
public static final String DATA3 = "data3";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
// 通用数据列具体含义取决于MIME类型通常用于存储文本类型的数据类型为文本
public static final String DATA4 = "data4";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
// 通用数据列具体含义取决于MIME类型通常用于存储文本类型的数据类型为文本
public static final String DATA5 = "data5";
}
// 定义一个静态类 TextNote 实现 DataColumns 接口
public static final class TextNote implements DataColumns {
/**
* Mode to indicate the text in check list mode or not
* <P> Type: Integer 1:check list mode 0: normal mode </P>
*/
// 这一常量表示文本的模式,是检查列表模式还是普通模式,类型为整数
public static final String MODE = DATA1;
// 指示检查列表模式的常量值
public static final int MODE_CHECK_LIST = 1;
// 表示文本笔记的内容类型
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
// 表示单个文本笔记的内容项类型
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";
// 表示文本笔记内容的uri
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
// 定义一个静态类 CallNote 实现 DataColumns 接口
public static final class CallNote implements DataColumns {
/**
* Call date for this record
* <P> Type: INTEGER (long) </P>
*/
// 表示记录的通话日期,类型为长整型
public static final String CALL_DATE = DATA1;
/**
* Phone number for this record
* <P> Type: TEXT </P>
*/
// 表示记录的电话号码,类型为文本
public static final String PHONE_NUMBER = DATA3;
// 表示记录的文本内容类型
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
// 表示单个通话笔记的内容项类型
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
// 表示通话笔记的内容URI
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
}

@ -0,0 +1,462 @@
/*
* 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.data;
//引入必要的类
// 用于封装一组键值对
import android.content.ContentValues;
// 上下文类
import android.content.Context;
// SQLite数据库类
import android.database.sqlite.SQLiteDatabase;
// SQLite数据库助手类
import android.database.sqlite.SQLiteOpenHelper;
// 日志类
import android.util.Log;
// 导入数据列相关常量
import net.micode.notes.data.Notes.DataColumns;
// 导入数据相关常量
import net.micode.notes.data.Notes.DataConstants;
// 导入笔记列相关常量
import net.micode.notes.data.Notes.NoteColumns;
//创建 NotesDatabaseHelper 类 ,继承 SQLiteOpenHelper
public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 定义数据库名称,只能在 NotesDatabaseHelper 类内部访问,并且在程序运行过程中无法修改。
private static final String DB_NAME = "note.db";
// 数据库版本
private static final int DB_VERSION = 4;
// 定义表名 TABLE 接口
public interface TABLE {
// 笔记表名
public static final String NOTE = "note";
// 数据表名
public static final String DATA = "data";
}
//日志标签
private static final String TAG = "NotesDatabaseHelper";
// 单例模式实例
private static NotesDatabaseHelper mInstance;
// 在数据库创建笔记表
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," + // 笔记 id主键
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 笔记的父级id默认为 0
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 警告日期,默认为 0
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + // 背景颜色 id 默认为0
// 笔记创建日期,默认为当前时间,单位是毫秒
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
// 是否有附件,默认为 0
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," +
// 修改日期,默认为当前时间,单位是毫秒
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
// 笔记的数量,默认为 0
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," +
// 摘要,默认为空字符串
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + //笔记类型,默认为 0
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + // 小部件 id默认为 0
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 小部件类型,默认为 -1
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步 id 默认为0
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + // 本地修改标识,默认为 0
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 原始父级 id ,默认为 0
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // GTASK ID,默认为空字符串
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 版本,默认为 0
")";
// 在数据库创建数据表
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," + // 数据 id主键
DataColumns.MIME_TYPE + " TEXT NOT NULL," + // MIME 类型,不能为空
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 笔记 id默认为0
// 创建日期,默认为当前时间(毫秒)
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
// 修改日期,默认为当前时间(毫秒)
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + // 内容,默认为空字符串
DataColumns.DATA1 + " INTEGER," + // 自定义数据 1
DataColumns.DATA2 + " INTEGER," + // 自定义数据 2
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + // 自定义数据 3默认为空字符串
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + // 自定义数据 4默认为空字符串
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + // 自定义数据 5默认为空字符串
")";
// 创建索引以加速根据 NOTE_ID 查询的数据表
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
/**
* Increase folder's note count when move note to the folder
*/
// 增加文件夹中的笔记计数,当笔记移动到该文件夹时
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_update "+
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + // 更新笔记数量
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + // 按照新父级 id 更新
" END"; // 结束触发器
/**
* Decrease folder's note count when move note from folder
*/
// 创建一个触发器,该触发器在更新笔记的父 id 字段后执行
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " +
// 在更新父 id 字段时触发
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
// 更新目标笔记的子笔记数量,减少 1
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
// 条件:只在旧的父 id 等于当前笔记 id 时进行更新,且子笔记数量大于 0
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
" END";
/**
* Increase folder's note count when insert new note to the folder
*/
// 创建一个触发器,该触发器在向笔记表插入新笔记后执行
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " +
// 在插入操作后触发
" AFTER INSERT ON " + TABLE.NOTE +
" BEGIN " +
// 更新目标笔记的子笔记数量,增加 1
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
// 条件: 通过新插入笔记的父 id 找到对应的笔记并更新
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
/**
* Decrease folder's note count when delete note from the folder
*/
// 创建一个触发器,该触发器在从笔记删除笔记后执行
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_delete " +
// 在删除操作后触发
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN " +
// 更新目标笔记的子笔记数量,数量减 1
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
// 条件:当前笔记 id 等于 旧的父 id并且子笔记数量大于 0
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END";
/**
* Update note's content when insert data with type {@link DataConstants#NOTE}
*/
// 创建一个触发器,该触发器在向数据表插入新数据时执行
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
// 在插入操作后触发
" AFTER INSERT ON " + TABLE.DATA +
// 当新插入的数据类型为 NOTE 时触发
" WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
// 更新目标笔记的内容快照为新插入数据的内容
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
// 条件:通过新插入数据的 NOTE_ID 找到对应的笔记进行更新
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has changed
*/
// 创建一个触发器,该触发器在更新数据表中的数据时执行
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " +
// 在更新操作后触发
" AFTER UPDATE ON " + TABLE.DATA +
// 当旧数据类型为 NOTE 时触发
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
// 更新目标笔记的内容快照为新更新数据的内容
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
// 条件:通过新更新数据的 NOTE_ID 找到对应的笔记进行更新
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has deleted
*/
// 创建一个触发器,该触发器在删除数据表中的数据时执行。
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " +
// 在删除操作后触发
" AFTER delete ON " + TABLE.DATA +
// 当旧数据类型为NOTE时触发
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
// 将目标笔记的内容快照更新为空字符串
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=''" +
// 条件通过旧数据的NOTE_ID找到对应的笔记进行更新
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
" END";
/**
* Delete datas belong to note which has been deleted
*/
// 创建一个触发器,该触发器在删除笔记时执行。
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
"CREATE TRIGGER delete_data_on_delete " +
// 在删除操作后触发
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
// 删除与被删除笔记相关联的所有数据
" DELETE FROM " + TABLE.DATA +
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
* Delete notes belong to folder which has been deleted
*/
// 创建一个触发器,当删除笔记时执行
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
"CREATE TRIGGER folder_delete_notes_on_delete " +
// 在删除操作后触发
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
// 在删除操作后触发
" DELETE FROM " + TABLE.NOTE +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
* Move notes belong to folder which has been moved to trash folder
*/
// 创建一个触发器,用于移动到垃圾箱文件夹的笔记
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
"CREATE TRIGGER folder_move_notes_on_trash " +
" AFTER UPDATE ON " + TABLE.NOTE + // 在更新操作后触发
// 当新父ID为垃圾箱文件夹时触发
" WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" BEGIN" +
" UPDATE " + TABLE.NOTE + // 更新所有子笔记的父ID为垃圾箱文件夹ID
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
// NotesDatabaseHelper类的构造函数初始化数据库
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
// 创建笔记表
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL); // 执行创建笔记表的SQL语句
reCreateNoteTableTriggers(db); // 重新创建与笔记表相关的触发器
createSystemFolder(db); // 创建系统文件夹
Log.d(TAG, "note table has been created"); // 打印日志,指示笔记表已创建
}
// 重新创建笔记表的触发器
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
// 删除已存在的触发器,防止重复创建
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
// 创建新的触发器
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER);
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER);
db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
// 创建系统文件夹
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues();
/**
* call record foler for call notes
*/
//创建通话记录文件夹用于存储通话笔记
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); // 设置文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置文件夹类型为系统
db.insert(TABLE.NOTE, null, values); // 插入文件夹记录
/**
* root folder which is default folder
*/
// 创建根文件夹,作为默认文件夹
values.clear(); // 清空上次插入的内容
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); // 设置根文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置文件夹类型为系统
db.insert(TABLE.NOTE, null, values); // 插入根文件夹记录
/**
* temporary folder which is used for moving note
*/
// 创建临时文件夹,用于移动笔记
values.clear(); // 清空内容
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); // 设置临时文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置文件夹类型为系统
db.insert(TABLE.NOTE, null, values); // 插入临时文件夹记录
/**
* create trash folder
*/
// 创建垃圾箱文件夹
values.clear(); // 清空内容
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); //设置垃圾箱文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); //设置文件夹类型为系统
db.insert(TABLE.NOTE, null, values); // 插入垃圾箱文件夹记录
}
//创建数据表的方法
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL); // 执行创建数据表的SQL语句
reCreateDataTableTriggers(db); // 重新创建数据表的触发器
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建数据表的索引
Log.d(TAG, "data table has been created"); // 打印日志,指示数据表已创建
}
// 重新创建数据表的触发器的方法
private void reCreateDataTableTriggers(SQLiteDatabase db) {
// 如果触发器存在,则删除触发器
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete");
// 创建新的插入触发器
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER);
// 创建新的更新触发器
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
// 创建新的删除触发器
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
}
// 获取NotesDatabaseHelper实例的静态同步方法
static synchronized NotesDatabaseHelper getInstance(Context context) {
// 如果实例尚未创建,则创建一个新的实例
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
}
return mInstance; // 返回现有或新创建的实例
}
// 数据库创建时调用的方法
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db); //创建笔记表
createDataTable(db); // 创建数据表
}
// 数据库升级时调用的方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false; // 标识是否需要重新创建触发器
boolean skipV2 = false; // 标识是否跳过v2的升级
// 从版本1升级到版本2
if (oldVersion == 1) {
upgradeToV2(db); // 执行升级到v2的方法
// 设置跳过v2的标志因为此升级包含了从v2到v3的升级
skipV2 = true; // this upgrade including the upgrade from v2 to v3
oldVersion++; // 版本号自增
}
// 从版本2升级到版本3如果没有跳过v2
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db); // 执行升级到v3的方法
reCreateTriggers = true; // 设置需要重新创建触发器的标志
oldVersion++; // 版本号自增
}
// 从版本3升级到版本4
if (oldVersion == 3) {
upgradeToV4(db); // 执行升级到v4的方法
oldVersion++; // 版本号自增
}
// 如果需要重新创建触发器,则执行相关方法
if (reCreateTriggers) {
reCreateNoteTableTriggers(db); // 重新创建笔记表的触发器
reCreateDataTableTriggers(db); // 重新创建数据表的触发器
}
// 检查最终的版本号是否与目标版本一致
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails"); // 抛出异常,表示升级失败
}
}
// 升级到版本2的方法
private void upgradeToV2(SQLiteDatabase db) {
// 如果存在,则删除笔记表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
// 如果存在,则删除数据表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
// 创建新的笔记表
createNoteTable(db);
// 创建新的数据表
createDataTable(db);
}
// 升级到版本3的方法
private void upgradeToV3(SQLiteDatabase db) {
// drop unused triggers 删除不再使用的触发器
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// add a column for gtask id 在笔记表中添加用于谷歌任务的id列
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// add a trash system folder 添加一个垃圾箱系统文件
ContentValues values = new ContentValues(); // 创建ContentValues对象
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); // 设置垃圾箱文件夹的ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置类型为系统文件夹
db.insert(TABLE.NOTE, null, values); // 将垃圾箱文件夹插入到笔记表中
}
// 升级到版本4的方法
private void upgradeToV4(SQLiteDatabase db) {
// 在笔记表中添加版本列
//这行代码执行一个 SQL 命令,使用 ALTER TABLE 语句来修改 TABLE.NOTE 表
//ADD COLUMN 指令用于添加新列
//NoteColumns.VERSION 是新列的名称应该是一个整型INTEGER
//NOT NULL 约束确保该列不能为空
//DEFAULT 0 表示新列的默认值为0这意味着如果未提供值则该列会自动设置为0
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
}

@ -0,0 +1,382 @@
/*
* 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.data;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
public class NotesProvider extends ContentProvider {
// UriMatcher用于根据URI识别请求的类型
private static final UriMatcher mMatcher;
// 数据库助手类用于操作SQLite数据库
private NotesDatabaseHelper mHelper;
// 日志标签
private static final String TAG = "NotesProvider";
// 定义URI匹配的常量
private static final int URI_NOTE = 1; // 匹配"note" URI
private static final int URI_NOTE_ITEM = 2; // 匹配"note/#" URI#表示ID
private static final int URI_DATA = 3; // 匹配"data" URI
private static final int URI_DATA_ITEM = 4; // 匹配"data/#" URI#表示ID
private static final int URI_SEARCH = 5; // 匹配"search" URI用于搜索功能
private static final int URI_SEARCH_SUGGEST = 6; // 匹配搜索建议的URI
// 静态代码块初始化UriMatcher
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH); // 初始化UriMatcher默认为NO_MATCH
// 添加不同的URI模式并与对应的常量匹配
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); // 匹配 "note" URI
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); // 匹配 "note/#" URI#是ID占位符
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); // 匹配 "data" URI
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); // 匹配 "data/#" URI#是ID占位符
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); // 匹配 "search" URI
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); // 匹配搜索建议URI
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); // 匹配搜索建议带查询字符串的URI
}
/**
* ('\n')
* x'0A'SQLite('\n')
*/
// 定义一个搜索投影,用于处理搜索结果中的显示字段
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," // 查询笔记的ID
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," // 用ID作为搜索建议的附加数据
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," // 去除换行符并去掉多余空格作为搜索文本1
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," // 同样处理第二个文本字段
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," // 设置搜索结果的图标
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," // 搜索建议的操作,设置为查看
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; // 搜索建议的内容类型
// 定义搜索查询SQL语句
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION
+ " FROM " + TABLE.NOTE // 查询Notes表中的数据
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?" // 根据笔记内容的摘要SNIPPET进行模糊查询
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER // 排除属于垃圾箱的笔记
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; // 仅返回普通笔记,而不是其他类型(如语音、图片等)
@Override
public boolean onCreate() {
// 初始化数据库助手类
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true; // 成功创建ContentProvider
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
// 定义一个Cursor对象用于返回查询结果
Cursor c = null;
// 获取可读的SQLiteDatabase实例
SQLiteDatabase db = mHelper.getReadableDatabase();
// 用于存储从URI中获取的ID
String id = null;
// 根据URI路径匹配不同的查询请求
switch (mMatcher.match(uri)) {
// 匹配URI_NOTE表示查询所有笔记
case URI_NOTE:
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder);
break;
// 匹配URI_NOTE_ITEM表示查询单个笔记根据ID查询
case URI_NOTE_ITEM:
// 从URI路径中获取笔记的ID
id = uri.getPathSegments().get(1);
// 执行查询条件是笔记ID与URI中提供的ID匹配并且可以追加其他选择条件
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + parseSelection(selection),
selectionArgs, null, null, sortOrder);
break;
// 匹配URI_DATA表示查询所有数据
case URI_DATA:
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder);
break;
// 匹配URI_DATA_ITEM表示查询单条数据根据ID查询
case URI_DATA_ITEM:
// 从URI路径中获取数据的ID
id = uri.getPathSegments().get(1);
// 执行查询条件是数据ID与URI中提供的ID匹配并且可以追加其他选择条件
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + parseSelection(selection),
selectionArgs, null, null, sortOrder);
break;
// 匹配URI_SEARCH和URI_SEARCH_SUGGEST表示处理搜索请求
case URI_SEARCH:
case URI_SEARCH_SUGGEST:
// 对搜索请求,检查是否设置了不允许的参数
if (sortOrder != null || projection != null) {
throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
}
// 定义搜索字符串
String searchString = null;
// 如果是搜索建议的URIURI_SEARCH_SUGGEST则从URI路径中提取查询字符串
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1);
}
} else {
// 否则,从查询参数中提取搜索模式("pattern"
searchString = uri.getQueryParameter("pattern");
}
// 如果没有提供搜索字符串则返回null
if (TextUtils.isEmpty(searchString)) {
return null;
}
// 对搜索字符串进行格式化,添加通配符,确保模糊查询
try {
searchString = String.format("%%%s%%", searchString);
// 执行原生SQL查询查询笔记摘要中符合条件的记录
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, new String[] { searchString });
} catch (IllegalStateException ex) {
// 捕获异常并打印日志
Log.e(TAG, "got exception: " + ex.toString());
}
break;
// 如果没有匹配的URI路径抛出非法参数异常
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果查询成功并返回了Cursor对象设置通知URI
if (c != null) {
// 通知ContentResolver该URI的内容发生了变化ContentResolver会根据这个信息更新UI
c.setNotificationUri(getContext().getContentResolver(), uri);
}
// 返回查询结果
return c;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写的数据库实例
long dataId = 0, noteId = 0, insertedId = 0; // 初始化id变量插入的ID将存储在这些变量中
// 根据URI路径匹配来判断要插入的数据类型
switch (mMatcher.match(uri)) {
// 如果是URI_NOTE插入一条新的笔记
case URI_NOTE:
insertedId = noteId = db.insert(TABLE.NOTE, null, values); // 向笔记表插入数据返回插入的ID
break;
// 如果是URI_DATA插入一条新的数据项
case URI_DATA:
// 检查values中是否包含Note ID这是数据与笔记的关联键
if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID); // 获取关联的笔记ID
} else {
// 如果没有提供笔记ID则打印日志
Log.d(TAG, "Wrong data format without note id:" + values.toString());
}
// 向数据表插入数据返回插入的ID
insertedId = dataId = db.insert(TABLE.DATA, null, values);
break;
// 如果URI未知抛出非法参数异常
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果插入的笔记ID大于0通知ContentResolver该笔记数据已发生变化
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// 如果插入的数据ID大于0通知ContentResolver该数据项已发生变化
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
// 返回插入的记录的URI通常是ContentProvider的URI附加上插入的ID
return ContentUris.withAppendedId(uri, insertedId);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0; // 记录删除的行数
String id = null; // 存储从URI中提取的ID
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写的数据库实例
boolean deleteData = false; // 标记是否删除了数据项
// 根据URI路径匹配来判断删除的对象类型
switch (mMatcher.match(uri)) {
// 删除所有笔记
case URI_NOTE:
// 在原有的选择条件上附加“ID > 0”的限制确保不会删除系统保留的笔记
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs); // 删除笔记表中的数据
break;
// 删除单个笔记根据ID
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1); // 从URI路径中获取笔记ID
long noteId = Long.valueOf(id); // 转换为long类型的ID
if (noteId <= 0) {
break; // 如果ID小于等于0跳过删除操作
}
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); // 删除指定ID的笔记
break;
// 删除所有数据项
case URI_DATA:
count = db.delete(TABLE.DATA, selection, selectionArgs); // 删除数据表中的数据
deleteData = true; // 标记删除的是数据项
break;
// 删除单个数据项根据ID
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1); // 从URI路径中获取数据ID
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); // 删除指定ID的数据项
deleteData = true; // 标记删除的是数据项
break;
// 如果URI无法匹配抛出非法参数异常
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果删除成功,且删除的是数据项,则通知更新笔记相关数据
if (count > 0) {
if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
getContext().getContentResolver().notifyChange(uri, null); // 通知ContentResolver相关数据已变化
}
return count; // 返回删除的行数
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0; // 记录更新的行数
String id = null; // 用于存储从URI中提取的ID
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写的数据库实例
boolean updateData = false; // 标记是否更新了数据项
// 根据URI匹配来决定更新操作的具体内容
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 对笔记表进行更新时调用increaseNoteVersion方法更新笔记版本号
increaseNoteVersion(-1, selection, selectionArgs);
count = db.update(TABLE.NOTE, values, selection, selectionArgs); // 执行更新操作
break;
case URI_NOTE_ITEM:
// 更新指定ID的笔记项
id = uri.getPathSegments().get(1); // 从URI路径中获取笔记ID
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); // 更新笔记版本号
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs); // 执行更新操作
break;
case URI_DATA:
// 更新所有数据项
count = db.update(TABLE.DATA, values, selection, selectionArgs); // 执行更新操作
updateData = true; // 标记更新了数据项
break;
case URI_DATA_ITEM:
// 更新指定ID的数据项
id = uri.getPathSegments().get(1); // 从URI路径中获取数据ID
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs); // 执行更新操作
updateData = true; // 标记更新了数据项
break;
default:
// 如果URI无法匹配抛出异常
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果更新成功即有行被更新则通知ContentResolver相关数据已变化
if (count > 0) {
// 如果更新的是数据项,通知相关笔记数据变化
if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
// 通知ContentResolverURI发生变化触发UI更新
getContext().getContentResolver().notifyChange(uri, null);
}
return count; // 返回更新的行数
}
//该方法用于解析传入的selection条件并确保如果selection不为空则将其包裹在" AND (...)"内。这主要是为了构建更新时的SQL条件。
private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
sql.append(TABLE.NOTE);
sql.append(" SET ");
sql.append(NoteColumns.VERSION);
sql.append("=" + NoteColumns.VERSION + "+1 "); // 增加版本号
// 如果指定了ID或selection则追加WHERE条件
if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
}
if (id > 0) {
sql.append(NoteColumns.ID + "=" + String.valueOf(id)); // 使用指定的ID
}
if (!TextUtils.isEmpty(selection)) {
String selectString = id > 0 ? parseSelection(selection) : selection;
for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args); // 替换选择条件中的占位符
}
sql.append(selectString); // 将最终的selection添加到SQL中
}
// 执行更新SQL语句
mHelper.getWritableDatabase().execSQL(sql.toString());
}

@ -1,2 +0,0 @@
# xiaomi-Notes

@ -0,0 +1,186 @@
/*
* 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;//这行代码声明了类所在的包名即net.micode.notes.tool。
import android.content.Context;
import android.preference.PreferenceManager;//这两行代码导入了Android框架中的Context和PreferenceManager类。
import net.micode.notes.R;//这行代码导入了net.micode.notes包下的R类。R类是一个自动生成的类包含了项目中所有资源的引用如布局、字符串、图片等。
import net.micode.notes.ui.NotesPreferenceActivity;//这行代码导入了net.micode.notes.ui包下的NotesPreferenceActivity类。
public class ResourceParser {//这行代码定义了一个公开的类ResourceParser。
public static final int YELLOW = 0;
public static final int BLUE = 1;
public static final int WHITE = 2;
public static final int GREEN = 3;
public static final int RED = 4;//这几行代码定义了五个公开的静态常量分别代表不同的颜色并用整数0到4进行标识。
public static final int BG_DEFAULT_COLOR = YELLOW;//这行代码定义了一个公开的静态常量BG_DEFAULT_COLOR并将其值设置为YELLOW即默认背景颜色为黄色。
public static final int TEXT_SMALL = 0;
public static final int TEXT_MEDIUM = 1;
public static final int TEXT_LARGE = 2;
public static final int TEXT_SUPER = 3;//这几行代码定义了四个公开的静态常量,分别代表不同的文本大小。
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;//这行代码定义了一个公开的静态常量BG_DEFAULT_FONT_SIZE并将其值设置为TEXT_MEDIUM即默认字体大小为中等
public static class NoteBgResources {//这行代码定义了一个公开的静态内部类NoteBgResources。
private final static int [] BG_EDIT_RESOURCES = new int [] {
R.drawable.edit_yellow,
R.drawable.edit_blue,
R.drawable.edit_white,
R.drawable.edit_green,
R.drawable.edit_red
};//在NoteBgResources内部类中定义了一个私有的静态整型数组BG_EDIT_RESOURCES并初始化为包含五个图片资源ID的数组。这些资源ID对应于五种不同颜色的编辑背景图片。
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
R.drawable.edit_title_white,
R.drawable.edit_title_green,
R.drawable.edit_title_red
};//在NoteBgResources内部类中定义了另一个私有的静态整型数组BG_EDIT_TITLE_RESOURCES并初始化为包含五个图片资源ID的数组。这些资源ID对应于五种不同颜色的编辑标题背景图片。
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}//在NoteBgResources内部类中定义了一个公开的静态方法getNoteBgResource它接受一个整数id作为参数并返回对应id的编辑背景图片资源ID。
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}//在NoteBgResources内部类中定义了另一个公开的静态方法getNoteTitleBgResource它同样接受一个整数id作为参数并返回对应id的编辑标题背景图片资源ID。
}
public static int getDefaultBgId(Context context) {//定义了一个公开的静态方法getDefaultBgId它接受一个Context对象作为参数并返回一个整型值。这个方法用于获取默认的背景ID。
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(//开始了一个条件判断使用PreferenceManager获取默认的SharedPreferences实例然后从中读取一个布尔值。
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {//完成条件判断的部分检查是否设置了背景颜色的偏好通过PREFERENCE_SET_BG_COLOR_KEY键如果没有设置则默认为false。
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);//如果条件为真即用户设置了自定义背景颜色则返回一个随机整数这个整数是NoteBgResources.BG_EDIT_RESOURCES数组长度的某个值
} else {//如果条件为假即用户没有设置自定义背景颜色则执行else部分的代码。
return BG_DEFAULT_COLOR;//返回一个名为BG_DEFAULT_COLOR的整型值这个值是默认的背景颜色ID。
}
}
public static class NoteItemBgResources {//定义了一个公开的静态内部类NoteItemBgResources用于封装笔记项背景资源的获取方法。
private final static int [] BG_FIRST_RESOURCES = new int [] {//定义了一个私有的静态最终整型数组BG_FIRST_RESOURCES包含了一系列背景资源ID用于表示笔记项在列表中的第一个位置时的背景。
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
R.drawable.list_white_up,
R.drawable.list_green_up,
R.drawable.list_red_up
};
private final static int [] BG_NORMAL_RESOURCES = new int [] {//定义了一个用于表示笔记项在列表中的正常位置时的背景资源ID数组。
R.drawable.list_yellow_middle,
R.drawable.list_blue_middle,
R.drawable.list_white_middle,
R.drawable.list_green_middle,
R.drawable.list_red_middle
};
private final static int [] BG_LAST_RESOURCES = new int [] {//定义了一个用于表示笔记项在列表中的最后一个位置时的背景资源ID数组。
R.drawable.list_yellow_down,
R.drawable.list_blue_down,
R.drawable.list_white_down,
R.drawable.list_green_down,
R.drawable.list_red_down,
};
private final static int [] BG_SINGLE_RESOURCES = new int [] {//定义了一个用于表示单独的笔记项不在列表中时的背景资源ID数组。
R.drawable.list_yellow_single,
R.drawable.list_blue_single,
R.drawable.list_white_single,
R.drawable.list_green_single,
R.drawable.list_red_single
};
public static int getNoteBgFirstRes(int id) {//定义了一个公开的静态方法根据提供的ID返回BG_FIRST_RESOURCES数组中对应的背景资源ID。
return BG_FIRST_RESOURCES[id];
}
public static int getNoteBgLastRes(int id) {//定义了一个方法用于返回BG_LAST_RESOURCES数组中对应的背景资源ID。
return BG_LAST_RESOURCES[id];
}
public static int getNoteBgSingleRes(int id) {//定义了一个方法用于返回BG_SINGLE_RESOURCES数组中对应的背景资源ID。
return BG_SINGLE_RESOURCES[id];
}
public static int getNoteBgNormalRes(int id) {//定义了一个方法用于返回BG_SINGLE_RESOURCES数组中对应的背景资源ID。
return BG_NORMAL_RESOURCES[id];
}
public static int getFolderBgRes() {//这行定义了一个方法返回用于表示文件夹的背景资源ID。
return R.drawable.list_folder;
}
}
public static class WidgetBgResources {//定义了一个公开的静态内部类 WidgetBgResources。
private final static int [] BG_2X_RESOURCES = new int [] {//定义了一个私有的静态最终整型数组 BG_2X_RESOURCES包含了一系列小部件在2x尺寸下的背景资源ID。这些资源ID通常指向drawable资源如图片等。
R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
R.drawable.widget_2x_white,
R.drawable.widget_2x_green,
R.drawable.widget_2x_red,
};
public static int getWidget2xBgResource(int id) {//定义了一个公开的静态方法 getWidget2xBgResource它接受一个整型参数 id用于指定想要获取的背景资源的索引。
return BG_2X_RESOURCES[id];//返回 BG_2X_RESOURCES 数组中指定索引 id 的背景资源ID。
}
private final static int [] BG_4X_RESOURCES = new int [] {//定义了一个私有的静态最终整型数组 BG_4X_RESOURCES包含了一系列小部件在4x尺寸下的背景资源ID。
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,
R.drawable.widget_4x_white,
R.drawable.widget_4x_green,
R.drawable.widget_4x_red
};
public static int getWidget4xBgResource(int id) {//定义了一个公开的静态方法 getWidget4xBgResource它同样接受一个整型参数 id用于指定想要获取的背景资源的索引。
return BG_4X_RESOURCES[id];//返回 BG_4X_RESOURCES 数组中指定索引 id 的背景资源ID。
}
}//WidgetBgResources 类结束。
public static class TextAppearanceResources {//定义了一个公开的静态内部类 TextAppearanceResources。
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
//定义了一个私有的静态最终整型数组 TEXTAPPEARANCE_RESOURCES包含了一系列文本外观资源ID。这些资源ID通常指向style资源定义了文本的字体大小、颜色等属性。
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
};
public static int getTexAppearanceResource(int id) {//定义了一个公开的静态方法 getTexAppearanceResource 接受一个整型参数 id用于指定想要获取的文本外观资源的索引。
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if (id >= TEXTAPPEARANCE_RESOURCES.length) {//检查提供的索引 id 是否超出了 TEXTAPPEARANCE_RESOURCES 数组的长度。
return BG_DEFAULT_FONT_SIZE;//如果条件为真(即索引超出了数组长度),则返回一个名为 BG_DEFAULT_FONT_SIZE 的整型值。
}
return TEXTAPPEARANCE_RESOURCES[id];//如果条件为假(即索引在数组长度范围内),则返回 TEXTAPPEARANCE_RESOURCES 数组中指定索引 id 的文本外观资源ID
}
public static int getResourcesSize() {//定义了一个公开的静态方法 getResourcesSize它不接受任何参数并返回 TEXTAPPEARANCE_RESOURCES 数组的长度。
return TEXTAPPEARANCE_RESOURCES.length;//返回 TEXTAPPEARANCE_RESOURCES 数组的长度。
}
}
}

@ -0,0 +1,257 @@
/*
* 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.gtask.data;:定义当前类所属的包。
//导入了与内容提供者交互的 ContentResolver、ContentUris、ContentValues、Cursor 等 Android 类,
// 还包括 JSON 处理类 JSONObject以及自定义异常类 ActionFailureException。
package net.micode.notes.gtask.data;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
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.NotesDatabaseHelper.TABLE;
import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
//字段解释:
//TAG日志标识通常用于调试时输出日志。
//INVALID_ID一个无效的 ID 值,通常用于初始化时表示数据未找到或未设置。
//PROJECTION_DATA定义了查询时返回的列数据表的列与常量 DataColumns 中定义的列名相对应。
//mContentResolverContentResolver 是用来访问 Android 内容提供者的工具。
//mIsCreate布尔值表示当前 SqlData 对象是新创建的true还是从数据库中加载的false
//mDataId、mDataMimeType、mDataContent、mDataContentData1 和 mDataContentData3 用来存储与特定数据项相关的字段。
//mDiffDataValues用于存储不同数据项的差异通常在插入或更新数据库时使用。
public class SqlData {
private static final String TAG = SqlData.class.getSimpleName(); // 日志标识,用于调试和打印日志
private static final int INVALID_ID = -99999; // 无效的ID常量用于初始化时标记无效ID
// 定义用于查询数据表的投影列PROJECTION即查询结果中返回的列
public static final String[] PROJECTION_DATA = new String[]{
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
// 定义数据表中各列的索引值通过索引访问Cursor中的数据
public static final int DATA_ID_COLUMN = 0;
public static final int DATA_MIME_TYPE_COLUMN = 1;
public static final int DATA_CONTENT_COLUMN = 2;
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
private ContentResolver mContentResolver; // ContentResolver用于访问和操作内容提供者中的数据
private boolean mIsCreate; // 标记当前对象是否是新创建的
// 存储数据的ID
private long mDataId;
// 存储数据的MIME类型
private String mDataMimeType;
// 存储数据的内容
private String mDataContent;
// 存储数据的附加信息
private long mDataContentData1;
// 存储数据的附加信息
private String mDataContentData3;
// 存储不同的数据值,用于更新或插入时的差异
private ContentValues mDiffDataValues;
//构造函数:
//第一个构造函数用于创建一个新的 SqlData 实例,初始化一些默认值。
//第二个构造函数接收一个 Cursor 对象,表示从数据库查询到的数据行。它将通过 loadFromCursor 方法加载 Cursor 中的数据。
// 默认构造函数,用于创建一个新的 SqlData 实例
public SqlData(Context context) {
// 获取 Context 中的 ContentResolver
mContentResolver = context.getContentResolver();
// 标记这是一个新创建的实例
mIsCreate = true;
// 初始化 ID 为无效 ID
mDataId = INVALID_ID;
// 设置默认的 MIME 类型
mDataMimeType = DataConstants.NOTE;
// 初始化内容为空字符串
mDataContent = "";
// 初始化附加数据 1 为 0
mDataContentData1 = 0;
// 初始化附加数据 3 为空字符串
mDataContentData3 = "";
// 初始化差异数据的 ContentValues 对象
mDiffDataValues = new ContentValues();
}
// 构造函数,用于从 Cursor 中加载数据
public SqlData(Context context, Cursor c) {
// 获取 Context 中的 ContentResolver
mContentResolver = context.getContentResolver();
// 标记这个实例是从 Cursor 中加载数据而来的
mIsCreate = false;
// 从 Cursor 中加载数据到对象的成员变量
loadFromCursor(c);
// 初始化差异数据的 ContentValues 对象
mDiffDataValues = new ContentValues();
}
// 从 Cursor 中加载数据,填充到当前 SqlData 对象的成员变量中
//loadFromCursor 方法:
//这个方法从数据库查询结果 Cursor 中获取数据,并填充到当前 SqlData 对象的成员变量中。
//Cursor 是 Android 数据库查询结果的一个游标getLong() 和 getString() 方法用于从 Cursor 中提取具体的列值,按照列的索引顺序获取。
private void loadFromCursor(Cursor c) {
// 获取数据 ID
mDataId = c.getLong(DATA_ID_COLUMN);
// 获取数据的 MIME 类型
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
// 获取数据内容
mDataContent = c.getString(DATA_CONTENT_COLUMN);
// 获取数据的附加信息 1
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
// 获取数据的附加信息 3
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); // 获取数据的附加信息 3
}
// 设置内容的方法将传入的JSON对象中的数据提取并更新类的成员变量
public void setContent(JSONObject js) throws JSONException {
// 获取ID如果JSON中不存在则使用INVALID_ID
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
// 如果是新创建或者ID发生变化则更新差异数据
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
}
mDataId = dataId;
// 获取MIME类型如果JSON中没有则使用默认值"NOTE"
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
// 如果是新创建或MIME类型不同则更新差异数据
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
mDataMimeType = dataMimeType;
// 获取内容数据如果JSON中没有则使用空字符串
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
// 如果是新创建或内容不同,则更新差异数据
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
mDataContent = dataContent;
// 获取DATA1字段的数值如果JSON中没有则默认为0
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
// 如果是新创建或DATA1不同则更新差异数据
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
mDataContentData1 = dataContentData1;
// 获取DATA3字段的内容如果JSON中没有则使用空字符串
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
// 如果是新创建或DATA3不同则更新差异数据
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
mDataContentData3 = dataContentData3;
}
// 获取内容的方法返回一个JSONObject包含当前对象的各个字段
public JSONObject getContent() throws JSONException {
// 如果是新创建抛出错误日志并返回null
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
// 创建新的JSONObject并填充数据
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId);
js.put(DataColumns.MIME_TYPE, mDataMimeType);
js.put(DataColumns.CONTENT, mDataContent);
js.put(DataColumns.DATA1, mDataContentData1);
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
// 提交数据到数据库,如果是新创建则插入,否则更新现有记录
public void commit(long noteId, boolean validateVersion, long version) {
// 如果是新创建,插入数据
if (mIsCreate) {
// 如果ID无效且有差异数据移除ID
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
// 添加noteId并执行插入操作
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
try {
// 获取新插入的ID
mDataId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
} else {
// 如果有差异数据,则执行更新操作
if (mDiffDataValues.size() > 0) {
int result = 0;
// 如果不验证版本,则直接更新
if (!validateVersion) {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
// 如果验证版本,执行带版本检查的更新
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[]{
String.valueOf(noteId), String.valueOf(version)
});
}
// 如果没有更新,打印警告
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
}
// 清空差异数据并标记为非创建状态
mDiffDataValues.clear();
mIsCreate = false;
}
// 获取当前记录的ID
public long getId() {
return mDataId;
}
}

@ -0,0 +1,865 @@
/*
* 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.
*/
// 包含了处理与Google任务GTask相关的数据操作的类通常涉及数据的增、删、改、查等操作。
package net.micode.notes.gtask.data;
// 引入Android应用开发中所需的类库和工具
// 用于管理应用小部件
import android.appwidget.AppWidgetManager;
// 用于通过内容提供者与数据库交互
import android.content.ContentResolver;
// 存储数据行的值
import android.content.ContentValues;
// 用于获取应用的上下文
import android.content.Context;
// 用于操作数据库查询结果
import android.database.Cursor;
// 用于操作数据的URI
import android.net.Uri;
// 用于记录日志
import android.util.Log;
// 引入与笔记数据相关的类
// 笔记相关数据操作
import net.micode.notes.data.Notes;
// 用于标识笔记数据列
import net.micode.notes.data.Notes.DataColumns;
// 用于标识笔记属性列
import net.micode.notes.data.Notes.NoteColumns;
// 引入异常类,用于处理操作失败的情况
import net.micode.notes.gtask.exception.ActionFailureException;
// 引入工具类
// 用于处理GTask字符串的工具类
import net.micode.notes.tool.GTaskStringUtils;
// 用于解析资源文件的工具类
import net.micode.notes.tool.ResourceParser;
// 引入JSON处理相关的类
// 处理JSON数组
import org.json.JSONArray;
// 异常处理
import org.json.JSONException;
// 处理JSON对象
import org.json.JSONObject;
// 引入一个常用的集合类
// 用于操作动态数组
import java.util.ArrayList;
public class SqlNote {
// 定义一个静态常量 TAG用于记录日志时标识当前类通常是类名
private static final String TAG = SqlNote.class.getSimpleName();
// 定义一个静态常量 INVALID_ID通常表示一个无效的ID值
private static final int INVALID_ID = -99999;
// 定义一个静态常量 PROJECTION_NOTE这是一个字符串数组包含了多个列名
// 这些列名通常用于查询数据库时作为SELECT语句中所需的数据列
// 在这个例子中列名可能与一个笔记Note相关的数据库表字段对应
public static final String[] PROJECTION_NOTE = new String[] {
// 每个字符串代表数据库表中的一列,以下列举了相关列名
// 笔记的ID
NoteColumns.ID,
// 提醒时间
NoteColumns.ALERTED_DATE,
// 背景颜色ID
NoteColumns.BG_COLOR_ID,
// 创建时间
NoteColumns.CREATED_DATE,
// 是否有附件
NoteColumns.HAS_ATTACHMENT,
// 修改时间
NoteColumns.MODIFIED_DATE,
// 笔记数量
NoteColumns.NOTES_COUNT,
// 父级ID
NoteColumns.PARENT_ID,
// 摘要/片段内容
NoteColumns.SNIPPET,
// 笔记类型
NoteColumns.TYPE,
// 小部件ID
NoteColumns.WIDGET_ID,
// 小部件类型
NoteColumns.WIDGET_TYPE,
// 同步ID
NoteColumns.SYNC_ID,
// 本地修改标志
NoteColumns.LOCAL_MODIFIED,
// 原始父级ID
NoteColumns.ORIGIN_PARENT_ID,
// 与Google任务关联的ID
NoteColumns.GTASK_ID,
// 笔记的版本
NoteColumns.VERSION
}
// 定义各个列的索引值,表示每个列在数据库查询结果中的位置
// 笔记的ID列位置
public static final int ID_COLUMN = 0;
// 提醒日期列位置
public static final int ALERTED_DATE_COLUMN = 1;
// 背景颜色ID列位置
public static final int BG_COLOR_ID_COLUMN = 2;
// 创建日期列位置
public static final int CREATED_DATE_COLUMN = 3;
// 是否有附件列位置
public static final int HAS_ATTACHMENT_COLUMN = 4;
// 修改日期列位置
public static final int MODIFIED_DATE_COLUMN = 5;
// 笔记数量列位置
public static final int NOTES_COUNT_COLUMN = 6;
// 父级ID列位置
public static final int PARENT_ID_COLUMN = 7;
// 笔记摘要列位置
public static final int SNIPPET_COLUMN = 8;
// 笔记类型列位置
public static final int TYPE_COLUMN = 9;
// 小部件ID列位置
public static final int WIDGET_ID_COLUMN = 10;
// 小部件类型列位置
public static final int WIDGET_TYPE_COLUMN = 11;
// 同步ID列位置
public static final int SYNC_ID_COLUMN = 12;
// 本地修改标志列位置
public static final int LOCAL_MODIFIED_COLUMN = 13;
// 原始父级ID列位置
public static final int ORIGIN_PARENT_ID_COLUMN = 14;
// Google任务ID列位置
public static final int GTASK_ID_COLUMN = 15;
// 笔记版本列位置
public static final int VERSION_COLUMN = 16;
// 定义该类的成员变量
// 应用程序的上下文,通常用来访问系统服务和资源
private Context mContext;
// 内容解析器,用于与系统或应用程序的数据库进行交互
private ContentResolver mContentResolver;
// 标识该笔记是否是新创建的,布尔值标识
private boolean mIsCreate;
// 笔记的唯一标识符ID
private long mId;
// 笔记的提醒日期时间戳,表示笔记的提醒时间
private long mAlertDate;
// 笔记的背景颜色ID用于设置笔记的视觉背景颜色
private int mBgColorId;
// 笔记的创建日期时间戳,表示笔记被创建的时间
private long mCreatedDate;
// 一个整数值表示该笔记是否包含附件通常0表示没有附件1表示有附件
private int mHasAttachment;
// 笔记的最后修改日期时间戳,表示笔记被修改的时间
private long mModifiedDate;
// 父级笔记的ID通常用于表示该笔记所属的父级笔记如果存在的话
private long mParentId;
// 笔记的摘要或片段内容,通常是笔记的简短描述
private String mSnippet;
// 笔记的类型,通常是一个整数,表示笔记的类型(例如文本、图片等)
private int mType;
// 小部件的ID表示与笔记相关联的小部件的唯一标识符
private int mWidgetId;
// 小部件的类型,表示小部件的种类或用途
private int mWidgetType;
// 原始父级笔记的ID用于记录笔记的初始父级ID如果有父级笔记
private long mOriginParent;
// 笔记的版本号,用于表示笔记的版本,通常用于版本控制
private long mVersion;
// 存储笔记的差异值,通常用于记录笔记的变化(例如修改后的字段值)
private ContentValues mDiffNoteValues;
// 一个 `SqlData` 对象的列表,通常用于存储与该笔记相关的多条数据(例如附加数据、历史记录等)
private ArrayList<SqlData> mDataList;
public SqlNote(Context context) {
// 初始化应用程序上下文
mContext = context;
// 获取内容解析器,用于与应用程序的数据库交互
mContentResolver = context.getContentResolver();
// 将该笔记标识为新创建的笔记
mIsCreate = true;
// 设置该笔记的唯一标识符ID。通常初始为一个无效 ID用于数据库插入之前的标识符占位
mId = INVALID_ID;
// 默认的提醒日期时间戳为 0表示没有设置提醒
mAlertDate = 0;
// 从资源中获取默认的背景颜色ID
mBgColorId = ResourceParser.getDefaultBgId(context);
// 设置笔记的创建日期时间戳,使用当前系统时间的毫秒数
mCreatedDate = System.currentTimeMillis();
// 默认没有附件
mHasAttachment = 0;
// 设置笔记的最后修改日期时间戳,使用当前系统时间的毫秒数
mModifiedDate = System.currentTimeMillis();
// 默认的父级ID为 0表示该笔记没有父级笔记
mParentId = 0;
// 笔记的摘要部分,初始化为空字符串
mSnippet = "";
// 笔记类型,默认为普通笔记
mType = Notes.TYPE_NOTE;
// 小部件ID默认无效值
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
// 小部件类型,默认为无效值
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
// 原始父级ID默认为 0
mOriginParent = 0;
// 笔记的版本,初始版本为 0
mVersion = 0;
// 创建一个新的 `ContentValues` 实例,用于存储笔记数据的差异
mDiffNoteValues = new ContentValues();
// 初始化一个 `ArrayList`,用于存储与该笔记相关的其他数据(如附件数据、笔记历史记录等)
mDataList = new ArrayList<SqlData>();
}
public SqlNote(Context context, Cursor c) {
// 初始化上下文和内容解析器
mContext = context;
mContentResolver = context.getContentResolver();
// 标记当前笔记对象不是新创建的,通常从数据库查询出来的笔记对象
mIsCreate = false;
// 从 Cursor 中加载数据填充 SqlNote 对象
loadFromCursor(c);
// 初始化一个 ArrayList用于存储笔记相关的其他数据如附件、历史记录等
mDataList = new ArrayList<SqlData>();
// 如果笔记类型是普通笔记(非小部件或其他类型),则加载笔记的内容
if (mType == Notes.TYPE_NOTE)
loadDataContent();
// 初始化 ContentValues用于存储笔记的变化数据
mDiffNoteValues = new ContentValues();
}
public SqlNote(Context context, long id) {
// 初始化上下文和内容解析器
mContext = context;
mContentResolver = context.getContentResolver();
// 标记当前笔记对象不是新创建的
mIsCreate = false;
// 根据 id 从数据库中加载笔记数据
loadFromCursor(id);
// 初始化一个 ArrayList用于存储笔记相关的其他数据如附件、历史记录等
mDataList = new ArrayList<SqlData>();
// 如果笔记类型是普通笔记(非小部件或其他类型),则加载笔记的内容
if (mType == Notes.TYPE_NOTE)
loadDataContent();
// 初始化 ContentValues用于存储笔记的变化数据
mDiffNoteValues = new ContentValues();
}
// 根据提供的 id 从数据库加载笔记数据
private void loadFromCursor(long id) {
Cursor c = null;
try {
// 查询数据库,获取对应 id 的笔记数据
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] { String.valueOf(id) }, null);
// 如果查询到数据
if (c != null) {
// 将 Cursor 移动到第一行(一般只有一行数据)
c.moveToNext();
// 从 Cursor 中加载数据到 SqlNote 对象
loadFromCursor(c);
} else {
// 如果查询结果为空,输出警告日志
Log.w(TAG, "loadFromCursor: cursor = null");
}
} finally {
// 无论如何,确保在方法结束时关闭 Cursor
if (c != null)
c.close();
}
}
private void loadFromCursor(Cursor c) {
// 从 Cursor 中提取各列的值并赋给相应的成员变量。
// ID 列的值
mId = c.getLong(ID_COLUMN);
// 提醒时间列的值
mAlertDate = c.getLong(ALERTED_DATE_COLUMN);
// 背景颜色 ID 列的值
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN);
// 创建日期列的值
mCreatedDate = c.getLong(CREATED_DATE_COLUMN);
// 是否包含附件的标志0 或 1
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN);
// 修改日期列的值
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN);
// 父笔记 ID 列的值
mParentId = c.getLong(PARENT_ID_COLUMN);
// 摘要内容列的值
mSnippet = c.getString(SNIPPET_COLUMN);
// 笔记类型(例如:普通笔记、待办事项等)
mType = c.getInt(TYPE_COLUMN);
// 小部件 ID 列的值
mWidgetId = c.getInt(WIDGET_ID_COLUMN);
// 小部件类型列的值
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
// 版本号列的值
mVersion = c.getLong(VERSION_COLUMN);
}
private void loadDataContent() {
// 定义一个 Cursor 用于查询数据
Cursor c = null;
// 清空原有的数据列表,准备加载新的数据
mDataList.clear();
try {
// 查询与当前笔记 ID 相关的数据
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] { String.valueOf(mId) }, null);
// 如果查询返回的 Cursor 不为空
if (c != null) {
// 如果查询结果为空,输出警告日志
if (c.getCount() == 0) {
Log.w(TAG, "it seems that the note has not data");
return;
}
// 遍历查询结果,将每一行数据封装成 SqlData 对象并添加到 mDataList 中
while (c.moveToNext()) {
SqlData data = new SqlData(mContext, c);
mDataList.add(data);
}
} else {
// 如果查询的 Cursor 为 null输出警告日志
Log.w(TAG, "loadDataContent: cursor = null");
}
} finally {
// 确保查询结束后关闭 Cursor释放资源
if (c != null)
c.close();
}
}
public boolean setContent(JSONObject js) {
try {
// 从传入的 JSON 对象中获取与笔记相关的元数据部分
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
// 判断笔记类型,如果是系统文件夹,则无法修改
if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
Log.w(TAG, "cannot set system folder");
}
// 如果是文件夹类型,只能更新摘要和类型字段
else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
// 获取摘要字段的值,如果没有则设为空字符串
String snippet = note.has(NoteColumns.SNIPPET) ? note.getString(NoteColumns.SNIPPET) : "";
// 如果是创建的笔记,或摘要字段发生变化,则记录变化
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
// 更新当前笔记对象的摘要
mSnippet = snippet;
// 获取笔记类型字段的值,如果没有则默认为普通笔记类型
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) : Notes.TYPE_NOTE;
// 如果是创建的笔记,或类型字段发生变化,则记录变化
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
// 更新当前笔记对象的类型
mType = type;
}
// 如果是普通笔记类型
else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) {
// 从传入的 JSON 中获取数据部分(如果有)
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
// 获取笔记的唯一 ID如果没有则设为无效 ID
long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID;
// 如果是创建的笔记,或 ID 字段发生变化,则记录变化
if (mIsCreate || mId != id) {
mDiffNoteValues.put(NoteColumns.ID, id);
}
// 更新当前笔记对象的 ID
mId = id;
// 获取提醒时间字段的值,如果没有则默认为 0
long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note.getLong(NoteColumns.ALERTED_DATE) : 0;
// 如果是创建的笔记,或提醒时间字段发生变化,则记录变化
if (mIsCreate || mAlertDate != alertDate) {
mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate);
}
// 更新当前笔记对象的提醒时间
mAlertDate = alertDate;
// 获取背景颜色 ID 字段的值,如果没有则使用默认的背景颜色 ID
int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note.getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext);
// 如果是创建的笔记,或背景颜色 ID 字段发生变化,则记录变化
if (mIsCreate || mBgColorId != bgColorId) {
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId);
}
// 更新当前笔记对象的背景颜色 ID
mBgColorId = bgColorId;
// 获取创建时间字段的值,如果没有则使用当前时间戳
long createDate = note.has(NoteColumns.CREATED_DATE) ? note.getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis();
// 如果是创建的笔记,或创建时间字段发生变化,则记录变化
if (mIsCreate || mCreatedDate != createDate) {
mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate);
}
// 更新当前笔记对象的创建时间
mCreatedDate = createDate;
// 获取附件标志(是否包含附件),默认为 0没有附件
int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note.getInt(NoteColumns.HAS_ATTACHMENT) : 0;
// 如果是创建的笔记,或附件字段发生变化,则记录变化
if (mIsCreate || mHasAttachment != hasAttachment) {
mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment);
}
// 更新当前笔记对象的附件标志
mHasAttachment = hasAttachment;
}
// 1. 获取修改日期,如果没有该字段则使用当前系统时间戳
long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note
.getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis();
// 2. 如果是创建操作,或者修改日期发生变化,则记录该字段的变化
if (mIsCreate || mModifiedDate != modifiedDate) {
mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate);
}
// 3. 更新当前笔记对象的修改日期
mModifiedDate = modifiedDate;
// 4. 获取父级 ID如果没有该字段则默认为 0即记录笔记的父层级信息
long parentId = note.has(NoteColumns.PARENT_ID) ? note
.getLong(NoteColumns.PARENT_ID) : 0;
// 5. 如果是创建操作,或者父级 ID 发生变化,则记录该字段的变化
if (mIsCreate || mParentId != parentId) {
mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId);
}
// 6. 更新当前笔记对象的父级 ID
mParentId = parentId;
// 7. 获取笔记摘要,如果没有该字段则默认为空字符串
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
// 8. 如果是创建操作,或者摘要发生变化,则记录该字段的变化
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
// 9. 更新当前笔记对象的摘要
mSnippet = snippet;
// 10. 获取笔记类型(如果没有该字段则默认为普通笔记类型)
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
: Notes.TYPE_NOTE;
// 11. 如果是创建操作,或者笔记类型发生变化,则记录该字段的变化
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
// 12. 更新当前笔记对象的类型
mType = type;
// 13. 获取小部件 ID如果没有该字段则使用无效值
int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID)
: AppWidgetManager.INVALID_APPWIDGET_ID;
// 14. 如果是创建操作,或者小部件 ID 发生变化,则记录该字段的变化
if (mIsCreate || mWidgetId != widgetId) {
mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId);
}
// 15. 更新当前笔记对象的小部件 ID
mWidgetId = widgetId;
// 16. 获取小部件类型(如果没有该字段则默认为无效类型)
int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note
.getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE;
// 17. 如果是创建操作,或者小部件类型发生变化,则记录该字段的变化
if (mIsCreate || mWidgetType != widgetType) {
mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType);
}
// 18. 更新当前笔记对象的小部件类型
mWidgetType = widgetType;
// 19. 获取原始父级 ID如果没有该字段则默认为 0通常用于记录笔记的移动历史或原始位置
long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note
.getLong(NoteColumns.ORIGIN_PARENT_ID) : 0;
// 20. 如果是创建操作,或者原始父级 ID 发生变化,则记录该字段的变化
if (mIsCreate || mOriginParent != originParent) {
mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent);
}
// 21. 更新当前笔记对象的原始父级 ID
mOriginParent = originParent;
// 22. 遍历 `dataArray` 数组中的所有数据(可能是笔记的附加数据或附件等)
for (int i = 0; i < dataArray.length(); i++) {
// 23. 获取当前的数据项JSONObject
JSONObject data = dataArray.getJSONObject(i);
SqlData sqlData = null;
// 24. 如果数据项包含 ID 字段,则尝试在当前的数据列表中查找相应的 `SqlData`
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
for (SqlData temp : mDataList) {
// 25. 如果在列表中找到了对应 ID 的 `SqlData`,则赋值
if (dataId == temp.getId()) {
sqlData = temp;
}
}
}
// 26. 如果没有找到对应的 `SqlData`,则创建一个新的 `SqlData` 实例
if (sqlData == null) {
sqlData = new SqlData(mContext);
mDataList.add(sqlData); // 将新创建的 `SqlData` 添加到数据列表中
}
// 27. 将当前的 JSON 数据传入 `sqlData` 对象进行处理
sqlData.setContent(data);
}
}
}
catch (JSONException e) {
// 1. 捕获并处理 JSON 异常JSONException这是针对在解析 JSON 数据过程中可能发生的异常
// 2. 将异常信息记录到日志中,使用 Log.e 方法打印错误日志。TAG 是日志标签,通常用于标识日志来源
Log.e(TAG, e.toString());
// 3. 打印堆栈跟踪信息,以便开发人员可以追踪异常发生的调用链和位置
e.printStackTrace();
// 4. 返回 false表示在处理过程中发生了异常操作失败
return false;
}
// 5. 如果没有异常发生,表示操作成功,返回 true
return true;
}
public JSONObject getContent() {
try {
// 1. 创建一个新的 JSON 对象,用于存储最终返回的数据
JSONObject js = new JSONObject();
// 2. 检查是否是创建状态,如果是创建状态,直接返回 null
if (mIsCreate) {
// 记录日志,提示该项尚未创建
Log.e(TAG, "it seems that we haven't created this in database yet");
// 由于没有创建记录,返回 null
return null;
}
// 3. 创建一个用于存储 note 信息的 JSON 对象
JSONObject note = new JSONObject();
// 4. 如果 mType 为 Notes.TYPE_NOTE表示这是一个普通笔记
if (mType == Notes.TYPE_NOTE) {
// 将笔记的各个属性添加到 JSON 对象中
// 添加 ID
note.put(NoteColumns.ID, mId);
// 添加提醒日期
note.put(NoteColumns.ALERTED_DATE, mAlertDate);
// 添加背景颜色 ID
note.put(NoteColumns.BG_COLOR_ID, mBgColorId);
// 添加创建日期
note.put(NoteColumns.CREATED_DATE, mCreatedDate);
// 添加是否有附件的标识
note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment);
// 添加修改日期
note.put(NoteColumns.MODIFIED_DATE, mModifiedDate);
// 添加父 ID
note.put(NoteColumns.PARENT_ID, mParentId);
// 添加笔记摘录
note.put(NoteColumns.SNIPPET, mSnippet);
// 添加笔记类型
note.put(NoteColumns.TYPE, mType);
// 添加小部件 ID
note.put(NoteColumns.WIDGET_ID, mWidgetId);
// 添加小部件类型
note.put(NoteColumns.WIDGET_TYPE, mWidgetType);
// 添加原始父 ID
note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent);
// 将 note JSON 对象添加到最终返回的 JSON 对象中
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
// 5. 创建一个 JSON 数组,用于存储数据项
JSONArray dataArray = new JSONArray();
for (SqlData sqlData : mDataList) {
// 获取 sqlData 对象的内容
JSONObject data = sqlData.getContent();
if (data != null) {
// 将数据项添加到 JSON 数组中
dataArray.put(data);
}
}
// 将数据数组添加到最终返回的 JSON 对象中
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
}
// 6. 如果 mType 是 TYPE_FOLDER 或 TYPE_SYSTEM表示这是一个文件夹或系统类型
else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) {
// 将文件夹或系统类型的基本信息添加到 JSON 对象中
note.put(NoteColumns.ID, mId); // 添加 ID
note.put(NoteColumns.TYPE, mType); // 添加类型
note.put(NoteColumns.SNIPPET, mSnippet); // 添加摘录
js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 将 note JSON 对象添加到最终返回的 JSON 对象中
}
// 7. 返回最终的 JSON 对象
return js;
} catch (JSONException e) {
// 8. 捕获并处理 JSON 解析时的异常
Log.e(TAG, e.toString()); // 记录异常信息
e.printStackTrace(); // 打印异常的堆栈跟踪信息
}
// 如果发生异常,则返回 null
return null;
}
public void setParentId(long id) {
// 9. 设置父 ID
// 更新实例的 mParentId 属性
mParentId = id;
// 将父 ID 放入 mDiffNoteValues 中,可能用于后续的数据库更新
mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
}
public void setGtaskId(String gid) {
// 设置 Gtask ID 到 mDiffNoteValues 中
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
}
public void setSyncId(long syncId) {
// 设置同步 ID 到 mDiffNoteValues 中
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
}
public void resetLocalModified() {
// 重置本地修改标志,将其设置为 0
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
}
public long getId() {
// 返回笔记的 ID
return mId;
}
public long getParentId() {
// 返回父 ID
return mParentId;
}
public String getSnippet() {
// 返回笔记的摘录
return mSnippet;
}
public boolean isNoteType() {
// 判断当前对象是否是普通笔记类型
return mType == Notes.TYPE_NOTE;
}
public void commit(boolean validateVersion) {
// 如果是新创建的笔记
if (mIsCreate) {
// 如果笔记 ID 是无效的,且 mDiffNoteValues 中包含 ID移除它
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
// 移除无效的 ID
mDiffNoteValues.remove(NoteColumns.ID);
}
// 尝试将笔记插入数据库mDiffNoteValues 存储了笔记的所有变化
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
try {
// 从 URI 获取生成的 ID
// 获取 URI 路径段中的 ID 部分
mId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
// 如果发生异常,记录错误并抛出自定义异常
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
// 如果获取到的 ID 为 0说明创建失败抛出异常
if (mId == 0) {
throw new IllegalStateException("Create thread id failed");
}
// 如果是笔记类型(不是文件夹或系统类型),则提交相关的 SQL 数据
if (mType == Notes.TYPE_NOTE) {
// 对每个数据项进行提交
for (SqlData sqlData : mDataList) {
// 提交数据项,传递笔记 ID
sqlData.commit(mId, false, -1);
}
}
}
// 如果是更新现有的笔记
else {
// 如果笔记 ID 无效,并且不是根文件夹或通话记录文件夹,抛出异常
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
Log.e(TAG, "No such note");
throw new IllegalStateException("Try to update note with invalid id");
}
// 如果 mDiffNoteValues 中有更改数据
if (mDiffNoteValues.size() > 0) {
// 增加版本号
mVersion ++;
int result = 0;
// 如果不进行版本验证,直接更新数据
if (!validateVersion) {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] {
String.valueOf(mId)
});
}
// 如果进行版本验证,确保更新的版本号不大于当前版本号
else {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
String.valueOf(mId), String.valueOf(mVersion)
});
}
// 如果更新失败,输出警告信息
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
// 如果是笔记类型,提交与笔记相关的每个数据项
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, validateVersion, mVersion); // 提交数据项
}
}
}
// 刷新本地数据
// 从数据库中加载当前笔记的信息
loadFromCursor(mId);
if (mType == Notes.TYPE_NOTE)
// 如果是笔记类型,加载相关数据内容
loadDataContent();
// 清空更改的数据记录
mDiffNoteValues.clear();
// 标记为非创建状态
mIsCreate = false;
}
}

@ -0,0 +1,696 @@
/*
* 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.
*/
//包声明:
// 该类属于 net.micode.notes.gtask.data 包,通常表示这是一个与 Google Tasks 或某个任务管理相关的数据模型类。
package net.micode.notes.gtask.data;
//导入 Cursor 类:
// Cursor 是 Android 中用于从数据库中查询数据的接口,通常用于访问 SQLite 数据库中的记录。
import android.database.Cursor;
//导入 TextUtils 类:
// TextUtils 是 Android 提供的工具类,包含了一些常用的字符串操作方法,如判断字符串是否为空或是否匹配某些模式等。
import android.text.TextUtils;
//导入 Log 类:
// Log 类用于 Android 中的日志输出,开发者可以使用它记录调试信息、错误日志等。
import android.util.Log;
//导入 Notes 类:
// 这个类可能是应用中用于表示便签或任务的类。Notes 类中的常量和方法通常用于访问或操作任务和便签相关的数据。
import net.micode.notes.data.Notes;
//导入 DataColumns, DataConstants, NoteColumns
// 这些导入语句表明,这些类包含 Notes 类中的常量和字段,它们可能用于表示与任务或便签相关的列名、常量定义等。
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
//导入 ActionFailureException 类:
// 这个自定义异常类 ActionFailureException 可能用于处理在执行某些操作时发生的错误,表示某些操作失败。
import net.micode.notes.gtask.exception.ActionFailureException;
//导入 GTaskStringUtils 工具类:
// 这个类看起来是与 Google Tasks 相关的字符串工具类,可能包含处理任务相关字符串的实用方法。
import net.micode.notes.tool.GTaskStringUtils;
//导入 org.json 库的类:这些类用于处理 JSON 数据。
// 在任务管理应用中,任务通常是以 JSON 格式存储或传输的JSONArray, JSONException, 和 JSONObject 提供了处理 JSON 数据所需的工具。
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
//类Task 类继承自 Node 类,表示任务的基本模型,包含任务的状态、备注、父任务、优先级等信息。
//Task 类的构造函数:
//初始化了 mCompleted任务是否完成、mNotes任务备注、mPriorSibling前一个兄弟任务
// mParent父任务列表和 mMetaInfo任务的元数据信息等属性。
public class Task extends Node {
// 用于日志输出时标识当前类名
private static final String TAG = Task.class.getSimpleName();
// 任务是否完成
private boolean mCompleted;
// 任务的备注信息
private String mNotes;
// 任务的元数据信息,可能包含额外的任务信息
private JSONObject mMetaInfo;
// 当前任务的前一个兄弟任务
private Task mPriorSibling;
// 当前任务所属的任务列表(父任务列表)
private TaskList mParent;
// 构造函数:初始化 Task 对象的默认值
public Task() {
// 调用父类构造函数(假设父类 Node 也有构造函数)
super();
// 默认任务未完成
mCompleted = false;
// 默认没有备注
mNotes = null;
// 默认没有前一个兄弟任务
mPriorSibling = null;
// 默认没有父任务列表
mParent = null;
// 默认没有元数据
mMetaInfo = null;
}
/**
* JSON
*
* @param actionId ID
* @return JSONObject
*/
/*
getCreateAction(int actionId) JSONObject
getCreateAction(int actionId)
JSONObjectcreate action
JSON ID ID ID
JSONObject JSONObjectjs
action_type
action_id ID actionId
index使 mParent.getChildTaskIndex(this)
entity JSON ID null "task"
mNotes null entity
ID ID
mParent.getGid() ID JSON
ID
mPriorSibling != null ID JSON
JSON JSONException ActionFailureException
JSONException ActionFailureException
JSONException
JSON JSON
ActionFailureException
JSON
使 Log.e(TAG, e.toString()) 便
e.printStackTrace()
*/
public JSONObject getCreateAction(int actionId) {
// 创建一个新的 JSON 对象,用于保存任务的创建信息
JSONObject js = new JSONObject();
try {
// 设置 action_type表示这是一个任务创建的操作
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 设置 action_id任务创建动作的唯一 ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务在父任务列表中的索引位置
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
// 创建一个 JSON 对象 entity用于表示任务的基本信息任务名称、创建者 ID、任务类型等
JSONObject entity = new JSONObject();
// 设置任务的名称
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
// 设置任务创建者 ID这里默认为 "null"
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_TASK); // 设置任务的类型为 "task"
// 如果任务有备注信息,添加备注到 entity 中
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
// 将任务的 entity 信息放入 JSON 中
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
// 设置任务的父任务 ID当前任务所在的任务列表的 ID
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
// 设置目标父级类型,这里固定为 "group" 类型
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// 设置任务列表的 ID父任务列表的 ID
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// 如果当前任务有前一个兄弟任务,则设置 prior_sibling_id
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
} catch (JSONException e) {
// 如果生成 JSON 时发生错误,记录错误日志,并抛出自定义异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-create jsonobject");
}
// 返回生成的 JSON 对象
return js;
}
}
/*
getUpdateAction(int actionId) JSON JSON ID
actionId ID
JSONObject
js.put()
使 js.put() JSON
put() JSONObject API JSON
GTaskStringUtils.GTASK_JSON_ACTION_TYPE GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE
action_id
使 actionId ID
ID
GTaskStringUtils.GTASK_JSON_ID getGid() IDgetGid() ID
entity JSON
GTaskStringUtils.GTASK_JSON_NAME 使 getName()
getNotes() != null JSON
GTaskStringUtils.GTASK_JSON_DELETED 使 getDeleted()
JSON JSONException ActionFailureException
ActionFailureException "fail to generate task-update jsonobject"
JSONObject JSON
*/
public JSONObject getUpdateAction(int actionId) {
// 创建一个新的 JSON 对象,表示任务更新动作
JSONObject js = new JSONObject();
try {
// 设置 action_type 字段,表示这是一个更新操作
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// 设置 action_id 字段,表示当前更新操作的唯一标识符
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务的 ID使用 `getGid()` 方法获取当前任务的唯一 ID
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// 创建一个 entity 对象,表示任务更新的实际内容(包括任务的名称、备注和删除标记)
JSONObject entity = new JSONObject();
// 设置任务的名称
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
// 如果任务有备注信息,则添加备注字段
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
// 设置任务的删除标记,使用 `getDeleted()` 方法获取任务是否已删除
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
// 将 entity 对象放入 JSON 中,作为任务更新的实际内容
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
// 捕获 JSON 构建过程中的异常
// 打印错误日志
Log.e(TAG, e.toString());
// 输出堆栈信息
e.printStackTrace();
// 如果发生异常,抛出自定义的更新失败异常
throw new ActionFailureException("fail to generate task-update jsonobject");
}
// 返回生成的任务更新 JSON 对象
return js;
}
/*
`setContentByRemoteJSON(JSONObject js)` JSON
- ****`setContentByRemoteJSON(JSONObject js)` JSON JSON setter
- ****`js` `JSONObject`
- ****
1. ** JSON **
- `js` `null` `null`退
2. ****
- `js.has()` JSON JSON setter
- `GTaskStringUtils` `GTASK_JSON_ID``GTASK_JSON_LAST_MODIFIED``GTASK_JSON_NAME`
3. ****
- ** ID**使 `getString()` ID `setGid()` ID
- ****使 `getLong()` `setLastModified()`
- ****使 `getString()` `setName()`
- ****使 `getString()`
- ****使 `getBoolean()` `setDeleted()`
- ****使 `getBoolean()` `setCompleted()`
4. ****
- JSON `JSONException` `ActionFailureException` JSON
- JSON "fail to get task content from jsonobject"
5. **setter **
- `setGid()`, `setLastModified()`, `setName()`, `setNotes()`, `setDeleted()`, `setCompleted()`
- JSON ID setter
- 使 `js.has()`
- JSON 便
*/
public void setContentByRemoteJSON(JSONObject js) {
// 如果传入的 JSON 对象不为 null
if (js != null) {
try {
// 处理任务 ID获取并设置任务的全局 ID
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// 处理任务的最后修改时间,获取并设置
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// 处理任务名称,获取并设置
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
// 处理任务备注,获取并设置
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}
// 处理删除标记,获取并设置
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}
// 处理完成标记,获取并设置
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
}
} catch (JSONException e) {
// 如果解析 JSON 过程中发生异常,打印错误信息并抛出自定义异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get task content from jsonobject");
}
}
}
/*
JSON JSON name
js JSONObject META_HEAD_NOTE META_HEAD_DATA
*/
public void setContentByLocalJSON(JSONObject js) {
// 检查传入的 JSON 是否为 null且是否包含所需的两个字段
/*
js nullMETA_HEAD_NOTE META_HEAD_DATA
js null
*/
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) {
// 如果条件不满足,打印警告信息并返回
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}
try {
/* JSON
JSON META_HEAD_NOTE note META_HEAD_DATA dataArray
*/
// 从 JSON 中提取出 META_HEAD_NOTE 对应的 JSONObject
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
// 从 JSON 中提取出 META_HEAD_DATA 对应的 JSONArray
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
// 检查 note 对象中的 TYPE 是否为 NOTE 类型
//通过 note.getInt(NoteColumns.TYPE) 获取 note 对象中的 TYPE 字段,并与 Notes.TYPE_NOTE 常量进行比较。
// 如果 TYPE 不等于 Notes.TYPE_NOTE假设 Notes.TYPE_NOTE 是预定义的常量,表示有效的 note 类型),
// 则认为该数据无效,打印错误日志并退出方法。
if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) {
// 如果 TYPE 不等于 NOTE 类型,打印错误信息并返回
Log.e(TAG, "invalid type");
return;
}
// 遍历 dataArray 数组中的每个 JSONObject
/* dataArray.length() 使 for data
data.getString(DataColumns.MIME_TYPE) MIME_TYPE
DataConstants.NOTE MIME_TYPE NOTE data CONTENT
setName()
data 便使 break
*/
for (int i = 0; i < dataArray.length(); i++) {
// 获取当前遍历的 data 对象
JSONObject data = dataArray.getJSONObject(i);
// 判断 data 中的 MIME_TYPE 是否为 NOTE 类型
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
// 如果 MIME_TYPE 为 NOTE 类型,从 data 中获取 CONTENT 字段的值,并设置为任务的名称
setName(data.getString(DataColumns.CONTENT));
// 找到第一个符合条件的记录后跳出循环
break;
}
}
}
//如果在解析 JSON 数据时发生异常(例如字段缺失或类型不匹配),会捕获 JSONException 并打印错误信息,确保应用不会崩溃。
catch (JSONException e) {
// 如果在解析过程中发生异常,打印错误信息
Log.e(TAG, e.toString());
// 打印堆栈跟踪信息
e.printStackTrace();
}
}
/*
getLocalJSONFromContent() JSON JSONObject
mMetaInfo JSON
*/
public JSONObject getLocalJSONFromContent() {
// 获取当前任务的名称
String name = getName();
try {
// 如果 mMetaInfo 为 null表示任务还未同步或者是一个新任务
if (mMetaInfo == null) {
// new task created from web
// 如果任务的名称为空,输出警告并返回 null
if (name == null) {
Log.w(TAG, "the note seems to be an empty one");
return null;
}
// 创建一个新的 JSON 对象来表示任务的元数据
JSONObject js = new JSONObject();
JSONObject note = new JSONObject();
JSONArray dataArray = new JSONArray();
JSONObject data = new JSONObject();
// 将任务的名称添加到 data 对象中
data.put(DataColumns.CONTENT, name);
// 将 data 对象添加到 dataArray 中
dataArray.put(data);
// 将 dataArray 添加到 js 对象中的 META_HEAD_DATA 字段
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
// 设置 note 对象的 TYPE 字段为 NOTE 类型
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
// 将 note 对象添加到 js 对象中的 META_HEAD_NOTE 字段
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
return js; // 返回生成的 JSON 对象
} else {
// 如果 mMetaInfo 已经存在,表示这是一个已经同步的任务
// 从 mMetaInfo 中获取 META_HEAD_NOTE 和 META_HEAD_DATA
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
// 遍历 dataArray 中的每个 data 对象,更新 MIME_TYPE 为 NOTE 的对象中的 CONTENT 字段
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
// 更新 data 对象中的 CONTENT 字段为当前任务的名称
data.put(DataColumns.CONTENT, getName());
break; // 找到并更新后跳出循环
}
}
// 确保 note 对象中的 TYPE 字段仍然为 NOTE 类型
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
// 返回已经更新的 mMetaInfo 对象
return mMetaInfo;
}
}
// 捕获并处理 JSON 解析过程中的异常
catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
// 如果发生异常,返回 null
return null;
}
}
/*
setMetaInfo(MetaData metaData)
MetaData metaData notes JSONObject mMetaInfo
metaData metaData.getNotes() null getNotes() JSONObject mMetaInfo
JSON JSONException mMetaInfo null
*/
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) {
try {
// 将 notes 字符串转换为 JSONObject
mMetaInfo = new JSONObject(metaData.getNotes());
}
catch (JSONException e) { // 如果 JSON 转换失败
// 输出警告日志
Log.w(TAG, e.toString());
// 设置 mMetaInfo 为 null
mMetaInfo = null;
}
}
}
/*
getSyncAction(Cursor c)
mMetaInfo note
Cursor mMetaInfo note
SYNC_ACTION_UPDATE_REMOTE
SYNC_ACTION_UPDATE_LOCAL
SYNC_ACTION_NONE
SYNC_ACTION_UPDATE_CONFLICT
SYNC_ACTION_ERROR
*/
public int getSyncAction(Cursor c) {
try {
JSONObject noteInfo = null;
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
// 获取 note 元数据
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
}
// 如果没有获取到 note 信息
if (noteInfo == null) {
Log.w(TAG, "it seems that note meta has been deleted");
// 需要从远程更新
return SYNC_ACTION_UPDATE_REMOTE;
}
// 如果 note 缺少 ID 字段
if (!noteInfo.has(NoteColumns.ID)) {
Log.w(TAG, "remote note id seems to be deleted");
// 需要从本地更新
return SYNC_ACTION_UPDATE_LOCAL;
}
// 校验 note ID 是否匹配
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
Log.w(TAG, "note id doesn't match");
// 如果不匹配,从本地更新
return SYNC_ACTION_UPDATE_LOCAL;
}
// 如果本地未修改
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// 本地和远程都没有变化
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 无需操作
return SYNC_ACTION_NONE;
} else { // 远程有更新
// 从远程更新
return SYNC_ACTION_UPDATE_LOCAL;
}
} else { // 本地已修改
// 校验 gtask ID
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
// gtask ID 不匹配,返回错误
return SYNC_ACTION_ERROR;
}
// 本地修改且没有远程修改
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 将本地修改同步到远程
return SYNC_ACTION_UPDATE_REMOTE;
} else {
// 本地和远程都有修改,发生冲突
return SYNC_ACTION_UPDATE_CONFLICT;
}
}
} catch (Exception e) {
// 异常处理
Log.e(TAG, e.toString());
e.printStackTrace();
}
// 出现异常或无法处理时返回错误
return SYNC_ACTION_ERROR;
}
/*
setCompleted(boolean completed)
completed
*/
public void setCompleted(boolean completed) {
//将传入的 completed 参数赋值给任务对象的 mCompleted 属性,用于标识任务是否完成
this.mCompleted = completed;
}
/*
setNotes(String notes)
notes
*/
public void setNotes(String notes) {
//将传入的 notes 参数赋值给任务对象的 mNotes 属性,用于存储任务的备注内容。
this.mNotes = notes;
}
/*
setPriorSibling(Task priorSibling)
priorSibling Task
*/
public void setPriorSibling(Task priorSibling) {
//将传入的 priorSibling 参数赋值给任务对象的 mPriorSibling 属性,表示当前任务在任务列表中的前一个任务。
this.mPriorSibling = priorSibling;
}
/*
setParent(TaskList parent)
parent TaskList
*/
public void setParent(TaskList parent) {
//将传入的 parent 参数赋值给任务对象的 mParent 属性,表示当前任务所属的父任务列表。
this.mParent = parent;
}
/*
getCompleted()
*/
public boolean getCompleted() {
//返回 mCompleted 属性的值,表示任务是否已完成。
return this.mCompleted;
}
/*
getNotes()
*/
public String getNotes() {
//返回 mNotes 属性的值,表示任务的备注或附加说明。
return this.mNotes;
}
/*
getPriorSibling()
Task
*/
public Task getPriorSibling() {
//返回 mPriorSibling 属性的值,表示当前任务在任务列表中的前一个任务。
return this.mPriorSibling;
}
/*
getParent()
TaskList
*/
public TaskList getParent() {
// 返回 mParent 属性的值,表示当前任务所属的父任务列表。
return this.mParent;
}
}

@ -0,0 +1,747 @@
/*
* 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.gtask.data;
// 引入 Android 数据库操作类Cursor 用于处理数据库查询结果
import android.database.Cursor;
// 引入 Android 的日志工具类
import android.util.Log;
// 引入自定义的 Notes 数据模型,特别是 NoteColumns 类,包含笔记相关的列名
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
// 引入 GTask 的异常处理类,用于处理 GTask 相关操作失败的情况
import net.micode.notes.gtask.exception.ActionFailureException;
// 引入 GTask 的字符串工具类,可能提供与 GTask 数据同步的字符串处理功能
import net.micode.notes.tool.GTaskStringUtils;
// 引入 JSON 处理库,处理 JSON 格式的数据
import org.json.JSONException;
import org.json.JSONObject;
// 引入 ArrayList 类,用于存储动态大小的列表
import java.util.ArrayList;
/*
private static final String TAG = TaskList.class.getSimpleName();
TAG便getSimpleName() TaskList
private int mIndex;
mIndex
1
private ArrayList<Task> mChildren;
mChildren ArrayList
public TaskList()
TaskList Node super(),
mChildren ArrayList
mIndex 1*/
public class TaskList extends Node {
// 用于日志记录的 TAG便于调试时查看输出
private static final String TAG = TaskList.class.getSimpleName();
// 任务列表的索引,用于区分不同的任务列表
private int mIndex;
// 存储任务列表中所有任务对象的 ArrayList
private ArrayList<Task> mChildren;
// 构造函数,初始化任务列表对象
public TaskList() {
// 调用父类 Node 的构造函数
super();
// 初始化任务列表
mChildren = new ArrayList<Task>();
// 设置默认索引为 1
mIndex = 1;
}
/*
public JSONObject getCreateAction(int actionId)
JSON JSON GTask
actionId ID
JSONObject js = new JSONObject();
JSONObject
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
JSON action_type "create"
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
JSON action_id actionId ID
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
JSON index mIndex
JSONObject entity = new JSONObject();
JSONObject
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
getName() Node
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
ID使 "null"
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
"group"
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
entity JSON JSON js entity_delta
JSON JSONException
ActionFailureException
return js;
JSON
*/
/**
* JSON
*
* @param actionId ID
* @return JSONObject JSON
*/
public JSONObject getCreateAction(int actionId) {
// 创建一个新的 JSON 对象,用于存储创建任务列表的相关数据
JSONObject js = new JSONObject();
try {
// 设置 action_type 为 "create",表示这是一个创建任务列表的动作
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 设置 action_id表示这次创建任务的唯一标识符
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务列表的索引,这个索引用于区分任务列表
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
// 创建一个子 JSON 对象,表示任务实体的变动信息
JSONObject entity = new JSONObject();
// 设置任务列表的名称
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
// 设置任务列表的创建者 ID这里使用 "null" 字符串表示未知或没有指定创建者
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
// 设置任务实体的类型,这里是任务列表组的类型
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// 将任务实体信息放入 JSON 对象中
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
// 捕获 JSON 异常,打印错误日志并抛出自定义异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-create jsonobject");
}
// 返回生成的 JSON 对象
return js;
}
}
/*js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
JSON action_type "update"
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
action_id actionId
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
idgetGid()
JSONObject entity = new JSONObject();
JSONObject
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity JSON getName()
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
entity JSON getDeleted()
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
entity JSON js entity_delta
JSON JSONException ActionFailureException
JSON
JSON js*/
public JSONObject getUpdateAction(int actionId) {
// 创建一个空的 JSON 对象,用于存放更新请求的数据
JSONObject js = new JSONObject();
try {
// 设置 JSON 中的 action_type 字段,表示这是一个更新操作
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// 设置 JSON 中的 action_id 字段,表示此次更新操作的唯一标识符
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务列表的唯一标识符 id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// 创建一个新的 JSON 对象表示任务实体的变动信息entity_delta
JSONObject entity = new JSONObject();
// 设置任务列表的名称,使用当前对象的 `getName()` 方法获取任务列表名称
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
// 设置任务列表的删除状态,使用当前对象的 `getDeleted()` 方法获取删除状态
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
// 将包含任务实体信息的 `entity` 对象放入主 JSON 对象中
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
// 捕获 JSON 处理过程中的异常,打印错误信息并抛出自定义的 ActionFailureException
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-update jsonobject");
}
// 返回生成的 JSON 对象,包含了更新任务列表所需的所有信息
return js;
}
/* if (js != null)
JSON js
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); }
JSON id setGid() ID JSON ID
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); }
JSON last_modified setLastModified() JSON last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); }
JSON name setName() JSON name
JSON JSONException ActionFailureException JSON
setGidsetLastModified setName */
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) { // 确保传入的 JSON 对象不为空
try {
// 如果 JSON 中包含任务列表的唯一标识符 (id),则更新当前对象的 GID
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// 如果 JSON 中包含任务列表的最后修改时间 (last_modified),则更新当前对象的最后修改时间
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// 如果 JSON 中包含任务列表的名称 (name),则更新当前对象的名称
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
} catch (JSONException e) {
// 捕获 JSON 处理过程中的异常,打印错误信息并抛出自定义的 ActionFailureException
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get tasklist content from jsonobject");
}
}
}
/* JSON
JSONObject js folder
getName() folderName
MIUI
MIUI MIUI_FOLDER_PREFFIX
SNIPPET
folderName folder JSON "SNIPPET"
folderName Notes.TYPE_SYSTEM
Notes.TYPE_FOLDER
JSON
folder js META_HEAD_NOTE JSON
JSON JSONException null*/
public JSONObject getLocalJSONFromContent() {
try {
// 创建一个空的 JSON 对象,用于存放结果数据
JSONObject js = new JSONObject();
// 创建一个空的 JSON 对象,用于存放文件夹信息
JSONObject folder = new JSONObject();
// 获取当前任务列表的名称
String folderName = getName();
// 如果任务列表名称以 MIUI 文件夹前缀开头,则去除前缀
if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX))
folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(),
folderName.length());
// 将任务列表名称作为文件夹的“SNIPPET”字段放入 folder 对象中
folder.put(NoteColumns.SNIPPET, folderName);
// 判断任务列表名称,如果是默认文件夹或电话笔记文件夹,则设置类型为系统类型
if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT)
|| folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE))
// 类型为系统
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
else
// 否则类型为普通文件夹
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
// 将 folder 对象放入主 JSON 对象中,键为 "META_HEAD_NOTE"
js.put(GTaskStringUtils.META_HEAD_NOTE, folder);
// 返回构建好的 JSON 对象
return js;
} catch (JSONException e) {
// 捕获 JSON 处理中的异常,打印错误日志并返回 null
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}
}
/*
LOCAL_MODIFIED_COLUMN 0
LOCAL_MODIFIED_COLUMN == 0 SYNC_ID_COLUMN ID
getLastModified()
SYNC_ACTION_NONE
SYNC_ACTION_UPDATE_LOCAL
LOCAL_MODIFIED_COLUMN != 0 GTask ID (GTASK_ID_COLUMN)
GTask ID
SYNC_ACTION_ERROR ID
GTask ID ID
ID SYNC_ACTION_UPDATE_REMOTE
SYNC_ACTION_UPDATE_REMOTE
SYNC_ACTION_ERROR */
public int getSyncAction(Cursor c) {
try {
// 如果数据库中没有本地修改LOCAL_MODIFIED_COLUMN == 0
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// 如果本地和远程的同步 ID 相同(没有更新)
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 本地和远程都没有更新,不需要同步
return SYNC_ACTION_NONE;
} else {
// 远程数据有更新,需要将远程数据同步到本地
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// 如果有本地修改
// 检查 GTask ID 是否匹配
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
// 如果 GTask ID 不匹配,返回错误
return SYNC_ACTION_ERROR;
}
// 如果同步 ID 与本地修改时间相同,说明只有本地修改
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 本地修改需要更新远程数据
return SYNC_ACTION_UPDATE_REMOTE;
} else {
// 对于文件夹冲突的情况,选择应用本地修改
return SYNC_ACTION_UPDATE_REMOTE;
}
}
} catch (Exception e) {
// 捕获任何异常,打印日志并返回错误
Log.e(TAG, e.toString());
e.printStackTrace();
}
// 默认返回错误,表示同步操作失败
return SYNC_ACTION_ERROR;
}
// mChildren 是一个存储子任务的集合(通常是 List<Task> 类型),该方法返回其当前大小(即子任务的数量)。
public int getChildTaskCount() {
// 返回子任务的数量,即 mChildren 列表的元素数量
return mChildren.size();
}
/*
task mChildren
mChildren.add(task) mChildren ret
priorSibling:
mChildren mChildren
parent
ret
*/
public boolean addChildTask(Task task) {
boolean ret = false;
// 确保任务不为空且尚未添加到子任务列表中
if (task != null && !mChildren.contains(task)) {
// 添加任务到子任务列表
ret = mChildren.add(task);
if (ret) {
// 如果添加成功,需要设置该任务的 priorSibling前置兄弟任务和 parent父任务
// 设置前置兄弟任务
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren.get(mChildren.size() - 1));
// 设置当前任务作为任务的父任务
task.setParent(this);
}
}
// 返回添加任务是否成功
return ret;
}
/*
index 0 mChildren
false
task mChildren task pos == -1
mChildren.add(index, task)
0
true
*/
public boolean addChildTask(Task task, int index) {
// 检查索引是否合法
if (index < 0 || index > mChildren.size()) {
// 如果索引不合法,打印错误日志
Log.e(TAG, "add child task: invalid index");
// 返回 false表示添加失败
return false;
}
// 查找任务在当前子任务列表中的位置
int pos = mChildren.indexOf(task);
// 确保任务不为空,并且任务不在列表中
if (task != null && pos == -1) {
// 在指定索引位置添加任务
mChildren.add(index, task);
// 更新前置任务和后置任务
Task preTask = null;
Task afterTask = null;
// 如果索引不是 0则获取前一个任务
if (index != 0)
preTask = mChildren.get(index - 1);
// 如果索引不是最后一个,则获取下一个任务
if (index != mChildren.size() - 1)
afterTask = mChildren.get(index + 1);
// 设置当前任务的前置兄弟任务
task.setPriorSibling(preTask);
// 如果存在后置任务,设置后置任务的前置兄弟为当前任务
if (afterTask != null)
afterTask.setPriorSibling(task);
}
// 返回 true表示任务成功添加
return true;
}
/*
使 mChildren.indexOf(task)
mChildren.remove(task)
priorSibling parent null
priorSibling使
true false
*/
public boolean removeChildTask(Task task) {
boolean ret = false;
// 查找任务在子任务列表中的索引
int index = mChildren.indexOf(task);
// 如果任务存在于子任务列表中
if (index != -1) {
// 移除该任务
ret = mChildren.remove(task);
// 如果移除成功
if (ret) {
// 重置任务的 priorSibling前置兄弟任务和 parent父任务
task.setPriorSibling(null);
task.setParent(null);
// 更新任务列表中的其他任务
// 如果移除的任务不是最后一个
if (index != mChildren.size()) {
mChildren.get(index).setPriorSibling(
// 更新后续任务的前置兄弟任务
index == 0 ? null : mChildren.get(index - 1));
}
}
}
// 返回任务是否成功移除
return ret;
}
/*
使 mChildren.indexOf(task)
true
removeChildTask(task) addChildTask(task, index)
true false
*/
public boolean moveChildTask(Task task, int index) {
// 检查索引是否有效
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "move child task: invalid index");
// 如果索引无效,打印错误日志并返回 false
return false;
}
// 查找任务在子任务列表中的位置
int pos = mChildren.indexOf(task);
// 如果任务不在列表中
if (pos == -1) {
Log.e(TAG, "move child task: the task should in the list");
// 返回 false
return false;
}
// 如果任务已经在目标位置
if (pos == index)
// 不需要移动,直接返回 true
return true;
// 移除任务并将其添加到新的索引位置
// 调用 removeChildTask 和 addChildTask 方法进行移动
return (removeChildTask(task) && addChildTask(task, index));
}
/*
使 for mChildren
t.getGid().equals(gid) gid gid
null
*/
public Task findChildTaskByGid(String gid) {
// 遍历子任务列表
for (int i = 0; i < mChildren.size(); i++) {
Task t = mChildren.get(i);
// 如果任务的 gid 与指定的 gid 匹配
if (t.getGid().equals(gid)) {
// 返回找到的任务
return t;
}
}
// 如果没有找到,返回 null
return null;
}
/*
mChildren.indexOf(task)
*/
public int getChildTaskIndex(Task task) {
// 返回任务在子任务列表中的索引
return mChildren.indexOf(task);
}
/*
mChildren.get(index) null
*/
public Task getChildTaskByIndex(int index) {
// 检查索引是否有效
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "getTaskByIndex: invalid index");
// 如果索引无效,返回 null
return null;
}
// 返回指定索引位置的任务
return mChildren.get(index);
}
/*
使 for-each mChildren
gid gid
null
*/
public Task getChilTaskByGid(String gid) {
// 遍历子任务列表
for (Task task : mChildren) {
// 如果任务的 gid 匹配
if (task.getGid().equals(gid))
// 返回匹配的任务
return task;
}
// 如果没有找到,返回 null
return null;
}
//返回子任务列表:直接返回存储子任务的 mChildren 列表。
public ArrayList<Task> getChildTaskList() {
// 返回子任务列表
return this.mChildren;
}
//设置索引:将传入的 index 值设置为当前任务的索引。
public void setIndex(int index) {
// 设置当前任务的索引
this.mIndex = index;
}
//返回索引:返回当前任务的 mIndex 值。
public int getIndex() {
// 返回当前任务的索引
return this.mIndex;
}
}

@ -0,0 +1,446 @@
/*
* 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.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 {//定义了一个公开的类 WorkingNote。这个类是公开的意味着它可以被其他类访问。
// Note for the working note
private Note mNote;//定义了一个私有的 Note 类型的成员变量 mNote
// Note Id
private long mNoteId;//定义了一个私有的 long 类型的成员变量 mNoteId用于存储笔记的唯一标识符。
// Note content
private String mContent;//定义了一个私有的 String 类型的成员变量 mContent用于存储笔记的文本内容。
// Note mode
private int mMode;//定义了一个私有的 int 类型的成员变量 mMode
private long mAlertDate;//定义了一个私有的 long 类型的成员变量 mAlertDate用于存储笔记的提醒日期。
private long mModifiedDate;//定义了一个私有的 long 类型的成员变量 mModifiedDate用于存储笔记的最后修改日期。
private int mBgColorId;//定义了一个私有的 int 类型的成员变量 mBgColorId用于存储笔记背景颜色的ID。
private int mWidgetId;//定义了一个私有的 int 类型的成员变量 mWidgetId
private int mWidgetType;//定义了一个私有的 int 类型的成员变量 mWidgetType
private long mFolderId;//定义了一个私有的 long 类型的成员变量 mFolderId用于存储笔记所属的文件夹ID。
private Context mContext;//定义了一个私有的 Context 类型的成员变量 mContextContext 是一个抽象类,它允许访问特定资源和应用级操作,如启动活动、广播和接收意图等
private static final String TAG = "WorkingNote";//定义了一个私有的静态常量 TAG其值为字符串 "WorkingNote"。这个常量通常用于日志记录,以便识别日志消息来自哪个类。
private boolean mIsDeleted;//定义了一个私有的 boolean 类型的成员变量 mIsDeleted用于表示笔记是否被删除。
private NoteSettingChangedListener mNoteSettingStatusListener;//定义了一个私有的 NoteSettingChangedListener 类型的成员变量 mNoteSettingStatusListener。
public static final String[] DATA_PROJECTION = new String[] {
//定义了一个公开的静态常量 DATA_PROJECTION它是一个字符串数组包含了用于数据查询的列名。
DataColumns.ID,
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
public static final String[] NOTE_PROJECTION = new String[] {
//public static final String[] NOTE_PROJECTION = new String[] { ... }; - 定义了一个公开的静态常量 NOTE_PROJECTION它也是一个字符串数组包含了用于笔记查询的列名
NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE
};
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;
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;
//以上代码定义了一系列的私有静态常量,它们被用作索引来标识从数据库查询返回的结果集中各个列的位置。
// New note construct
private WorkingNote(Context context, long folderId) {
//这是一个私有构造函数用于创建一个新的WorkingNote实例。它接收两个参数context应用程序的上下文和folderId笔记所属的文件夹ID
mContext = context; //将传入的context赋值给成员变量mContext以便后续使用。
mAlertDate = 0;//初始化提醒日期mAlertDate为0表示当前没有设置提醒。
mModifiedDate = System.currentTimeMillis();//初始化修改日期mModifiedDate为当前系统时间的毫秒数表示笔记刚刚被创建。
mFolderId = folderId;//将传入的folderId赋值给成员变量mFolderId。
mNote = new Note();//创建一个新的Note实例并赋值给成员变量mNote。
mNoteId = 0;//初始化笔记IDmNoteId为0表示当前是一个新的笔记还没有被保存到数据库中。
mIsDeleted = false;//初始化删除标记mIsDeleted为false表示笔记没有被删除。
mMode = 0;//初始化模式mMode为0具体的模式含义取决于业务逻辑。
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;//初始化小部件类型mWidgetType为Notes.TYPE_WIDGET_INVALIDE表示当前没有设置小部件类型。
}
// Existing note construct
private WorkingNote(Context context, long noteId, long folderId) {
//这是一个私有构造函数用于加载一个已存在的WorkingNote实例。它接收三个参数context应用程序的上下文、noteId要加载的笔记ID和folderId笔记所属的文件夹ID
mContext = context;//
mNoteId = noteId;
mFolderId = folderId;
mIsDeleted = false;
mNote = new Note();//这部分代码与第一个构造函数中的相应部分类似只是它直接设置了mNoteId和mFolderId。
loadNote();//调用loadNote方法来从数据库中加载笔记的详细信息。
}
private void loadNote() {//这是一个私有方法,用于从数据库中加载笔记的详细信息。
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
null, null);//使用ContentResolver和ContentUris来构建一个查询以获取指定mNoteId的笔记。NOTE_PROJECTION是一个包含要查询的列名的数组。
if (cursor != null) {//检查查询返回的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中读取笔记的详细信息并赋值给相应的成员变量。
cursor.close();//关闭Cursor以释放资源。
} else {
Log.e(TAG, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
}//如果Cursor为null则记录错误日志并抛出一个IllegalArgumentException异常。
loadNoteData();//调用loadNoteData方法来加载笔记的额外数据
}
private void loadNoteData() {//这是一个私有方法用于从数据库中加载与当前笔记ID (mNoteId) 相关联的笔记数据。
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
String.valueOf(mNoteId)
}, null);
//使用ContentResolver执行查询以获取与mNoteId相关联的笔记数据。Notes.CONTENT_DATA_URI是数据的URIDATA_PROJECTION是包含要查询的列名的数组DataColumns.NOTE_ID + "=?"是选择条件表示只选择NOTE_ID等于mNoteId的行new String[] { String.valueOf(mNoteId) }是选择条件的参数null表示没有排序。
if (cursor != null) {//检查查询返回的Cursor是否不为null。
if (cursor.moveToFirst()) {//移动Cursor到第一行准备读取数据。
do {//开始一个循环,用于遍历所有匹配的行。
String type = cursor.getString(DATA_MIME_TYPE_COLUMN);//从当前行读取数据的MIME类型。
if (DataConstants.NOTE.equals(type)) {//如果MIME类型表示这是一个普通笔记。
mContent = cursor.getString(DATA_CONTENT_COLUMN);
mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
//读取笔记的内容、模式和ID并设置到相应的成员变量和mNote对象中。
} else if (DataConstants.CALL_NOTE.equals(type)) {//如果MIME类型表示这是一个与通话相关的笔记。
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));//读取并设置与通话相关的数据的ID。
} else {
Log.d(TAG, "Wrong note type with type:" + type);//如果MIME类型不是预期的任何一种则记录一条调试日志
}
} 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);
}//如果Cursor为null则记录错误日志并抛出一个IllegalArgumentException异常。
}
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
//这是一个公有的静态方法用于创建一个空的WorkingNote实例。它接收五个参数context应用程序的上下文、folderId笔记所属的文件夹ID、widgetId小部件ID、widgetType小部件类型和defaultBgColorId默认背景颜色ID
WorkingNote note = new WorkingNote(context, folderId);//使用提供的context和folderId创建一个新的WorkingNote实例。
note.setBgColorId(defaultBgColorId);
note.setWidgetId(widgetId);
note.setWidgetType(widgetType);
//设置笔记的背景颜色ID、小部件ID和小部件类型。
return note;//返回创建并设置好的WorkingNote实例。
}
public static WorkingNote load(Context context, long id) {
// 这是一个静态方法用于根据给定的上下文和ID加载一个WorkingNote实例。
return new WorkingNote(context, id, 0);
// 创建一个新的WorkingNote实例并传入上下文、ID和一个默认值0可能是用于文件夹ID或其他目的
}
public synchronized boolean saveNote() {
// 这是一个同步方法用于保存当前WorkingNote实例的数据。
if (isWorthSaving()) {
// 首先检查这个笔记是否值得保存(即是否有必要进行保存操作)。
if (!existInDatabase()) {
// 如果这个笔记还没有存在于数据库中即mNoteId小于或等于0根据existInDatabase方法的实现
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
// 尝试获取一个新的笔记ID如果失败即返回的ID为0则记录错误日志。
Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
// 并返回false表示保存失败。
}
}
mNote.syncNote(mContext, mNoteId);
// 将当前笔记的数据同步到数据库中具体实现依赖于mNote对象的syncNote方法
/**
* Update widget content if there exist any widget of this note
*/
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
// 如果这个笔记关联了一个小部件,并且这个小部件的类型有效,同时存在一个监听器,
// 则调用监听器的onWidgetChanged方法来更新小部件的内容。
mNoteSettingStatusListener.onWidgetChanged();
}
return true;
// 如果一切顺利则返回true表示保存成功。
} else {
// 如果这个笔记不值得保存即没有必要进行保存操作则直接返回false。
return false;
}
}
public boolean existInDatabase() {
// 这个方法用于检查这个笔记是否已经存在于数据库中。
// 通过检查mNoteId是否大于0来判断假设mNoteId是在数据库中唯一标识笔记的ID
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) {
// 设置一个监听器,用于监听笔记设置状态的更改。
// NoteSettingChangedListener 是一个自定义接口,用于回调通知状态变化。
mNoteSettingStatusListener = l;
// 将传入的监听器赋值给成员变量 mNoteSettingStatusListener。
}
public void setAlertDate(long date, boolean set) {
// 设置笔记的提醒日期。
// date 是要设置的提醒日期(以长整型表示的时间戳)。
// set 是一个布尔值,表示是否要设置提醒日期(尽管在这个方法中未直接使用,但可能用于其他逻辑)。
if (date != mAlertDate) {
// 如果传入的日期与当前设置的提醒日期不同。
mAlertDate = date;
// 更新成员变量 mAlertDate。
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
// 将更新后的提醒日期同步到笔记对象中(假设 NoteColumns.ALERTED_DATE 是数据库中的列名)。
}
if (mNoteSettingStatusListener != null) {
// 如果设置了监听器。
mNoteSettingStatusListener.onClockAlertChanged(date, set);
// 调用监听器的 onClockAlertChanged 方法,通知提醒日期已更改。
}
}
public void markDeleted(boolean mark) {
// 标记笔记为已删除或未删除状态。
// mark 是一个布尔值true 表示已删除false 表示未删除。
mIsDeleted = mark;
// 更新成员变量 mIsDeleted。
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) {
// 如果笔记关联了一个小部件,并且这个小部件的类型有效,同时存在一个监听器。
mNoteSettingStatusListener.onWidgetChanged();
// 调用监听器的 onWidgetChanged 方法,通知小部件需要更新。
}
}
public void setBgColorId(int id) {
// 设置笔记的背景颜色ID。
// id 是背景颜色的唯一标识符。
if (id != mBgColorId) {
// 如果传入的ID与当前设置的背景颜色ID不同。
mBgColorId = id;
// 更新成员变量 mBgColorId。
if (mNoteSettingStatusListener != null) {
// 如果设置了监听器。
mNoteSettingStatusListener.onBackgroundColorChanged();
// 调用监听器的 onBackgroundColorChanged 方法,通知背景颜色已更改。
}
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
// 将更新后的背景颜色ID同步到笔记对象中假设 NoteColumns.BG_COLOR_ID 是数据库中的列名)。
}
}
public void setCheckListMode(int mode) {
// 设置笔记的待办事项模式。
// mode 是待办事项模式的标识符。
if (mMode != mode) {
// 如果传入的模式与当前设置的待办事项模式不同。
if (mNoteSettingStatusListener != null) {
// 如果设置了监听器。
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
// 调用监听器的 onCheckListModeChanged 方法,通知待办事项模式已更改。
// 注意:这里传递了旧模式和新模式作为参数。
}
mMode = mode;
// 更新成员变量 mMode。
mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
// 将更新后的待办事项模式同步到笔记对象中(假设 TextNote.MODE 是用于存储模式的键)。
}
}
public void setWidgetType(int type) {
// 设置笔记关联的小部件类型。
// type 是小部件类型的标识符。
if (type != mWidgetType) {
// 如果传入的类型与当前设置的小部件类型不同。
mWidgetType = type;
// 更新成员变量 mWidgetType。
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
// 将更新后的小部件类型同步到笔记对象中(假设 NoteColumns.WIDGET_TYPE 是数据库中的列名)。
}
}
// 定义一个方法用于设置小部件ID
public void setWidgetId(int id) {
// 如果传入的ID与当前的小部件ID不同
if (id != mWidgetId) {
// 更新当前的小部件ID
mWidgetId = id;
// 将新的小部件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);
// 设置笔记的父ID为通话记录文件夹的ID
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
}
// 定义一个方法,用于检查是否有闹钟提醒
public boolean hasClockAlert() {
// 如果闹钟日期大于0则返回true否则返回false
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;
}
// 定义一个方法用于获取小部件ID
public int getWidgetId() {
return mWidgetId;
}
// 定义一个方法,用于获取小部件类型
public int getWidgetType() {
return mWidgetType;
}
// 定义一个接口,用于监听笔记设置的更改
public interface NoteSettingChangedListener {
// 当当前笔记的背景颜色刚刚更改时调用
void onBackgroundColorChanged();
// 当用户设置闹钟时调用
void onClockAlertChanged(long date, boolean set);
// 当用户通过小部件创建笔记时调用
void onWidgetChanged();
// 当在检查列表模式和普通模式之间切换时调用
// @param oldMode 是更改之前的模式
// @param newMode 是新的模式
void onCheckListModeChanged(int oldMode, int newMode);
}
Loading…
Cancel
Save