张昌盛 202201002062

zcs
zcs 8 months ago committed by yunci
parent 0f1c2eb9fe
commit 1a3da5bc84

@ -24,29 +24,34 @@ import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.HashMap;
//ceshihebing2
// Contact类主要用于根据电话号码获取对应的联系人姓名
public class Contact {
// 用于缓存联系人信息(电话号码与对应姓名的映射)的哈希表,以避免重复查询数据库
private static HashMap<String, String> sContactCache;
// 用于日志记录的标签,方便在日志中识别相关输出所属的类
private static final String TAG = "Contact";
// 构建查询联系人的SQL语句的选择条件部分用于从联系人数据中筛选出符合条件的记录
// 条件基于电话号码匹配、MIME类型匹配以及原始联系人ID的相关限定等
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 = '+')";
// 根据给定的上下文Context和电话号码获取对应的联系人姓名
public static String getContact(Context context, String phoneNumber) {
// 如果缓存为空,则初始化缓存哈希表,用于存储已查询过的电话号码与对应联系人姓名的映射关系
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
// 首先检查缓存中是否已经存在该电话号码对应的联系人姓名,如果存在则直接返回缓存中的姓名
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}
// 根据给定的电话号码,替换查询条件中的占位符(将'+'替换为适合查询的最小匹配格式)
// 这里使用PhoneNumberUtils工具类来转换电话号码格式使其符合查询要求
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
Cursor cursor = context.getContentResolver().query(
@ -55,19 +60,28 @@ public class Contact {
selection,
new String[] { phoneNumber },
null);
// 通过上下文的内容解析器ContentResolver发起数据库查询操作
// 查询联系人数据的相关表Data.CONTENT_URI只获取联系人的显示名称Phone.DISPLAY_NAME字段
// 使用上面构建好的选择条件selection并传入电话号码作为查询参数
// 如果查询结果游标Cursor不为空并且游标能够移动到第一条记录意味着有匹配的联系人数据
if (cursor != null && cursor.moveToFirst()) {
try {
// 从游标中获取联系人的显示名称假设显示名称在结果集的第0个位置
String name = cursor.getString(0);
// 从游标中获取联系人的显示名称假设显示名称在结果集的第0个位置
sContactCache.put(phoneNumber, name);
// 返回获取到的联系人姓名
return name;
} catch (IndexOutOfBoundsException e) {
// 如果在从游标获取数据时发生越界异常(比如结果集格式不符合预期等情况),记录错误日志
Log.e(TAG, " Cursor get string error " + e.toString());
return null;
} finally {
// 无论是否发生异常,都要关闭游标,释放相关资源
cursor.close();
}
} else {
// 如果游标为空或者游标中没有匹配的记录,记录调试日志表示没有找到对应联系人
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}

@ -25,23 +25,26 @@ 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 {
// 定义数据库的名称,这里是"note.db",即该应用对应的数据库文件名
private static final String DB_NAME = "note.db";
// 定义数据库的版本号用于数据库升级等场景判断当前版本为4
private static final int DB_VERSION = 4;
// TABLE接口用于定义数据库中不同表的名称常量方便在代码中统一引用避免硬编码表名
public interface TABLE {
public static final String NOTE = "note";
public static final String DATA = "data";
}
// TABLE接口用于定义数据库中不同表的名称常量方便在代码中统一引用避免硬编码表名
private static final String TAG = "NotesDatabaseHelper";
// 采用单例模式,保存该类的唯一实例,确保整个应用中只有一个数据库帮助类实例存在
private static NotesDatabaseHelper mInstance;
// 创建"note"表的SQL语句定义了表的各个列名、数据类型以及默认值等信息
// 例如定义了笔记的ID、父级ID、提醒日期、背景颜色ID等列为笔记相关数据存储做准备
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," +
@ -62,7 +65,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")";
// 创建"data"表的SQL语句同样定义了表的各列信息用于存储笔记相关的数据内容等信息如MIME类型、所属笔记ID、创建日期等
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," +
@ -77,13 +80,14 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
")";
// 创建针对"data"表中NOTE_ID列的索引的SQL语句有助于提高基于笔记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
* SQL
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_update "+
@ -96,6 +100,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Decrease folder's note count when move note from folder
* SQL
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " +
@ -109,6 +114,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Increase folder's note count when insert new note to the folder
* SQL0
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " +
@ -121,6 +127,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Decrease folder's note count when delete note from the folder
* DataConstants.NOTESQL
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_delete " +
@ -134,6 +141,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Update note's content when insert data with type {@link DataConstants#NOTE}
* DataConstants.NOTESQL
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
@ -147,6 +155,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Update note's content when data with {@link DataConstants#NOTE} type has changed
* DataConstants.NOTESQL
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " +
@ -184,6 +193,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Delete notes belong to folder which has been deleted
* SQL
*/
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
"CREATE TRIGGER folder_delete_notes_on_delete " +
@ -195,6 +205,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
/**
* Move notes belong to folder which has been moved to trash folder
* SQL
*/
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
"CREATE TRIGGER folder_move_notes_on_trash " +
@ -205,18 +216,18 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
// 构造函数调用父类SQLiteOpenHelper的构造函数传入数据库名称、版本号等信息
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
// 在给定的SQLiteDatabase对象上执行创建"note"表的操作,包括创建表、重新创建相关触发器以及创建系统文件夹,并输出日志表示表已创建
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
Log.d(TAG, "note table has been created");
}
// 先删除已存在的"note"表相关的触发器(如果有),然后再重新创建所有"note"表对应的触发器
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");
@ -234,17 +245,17 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
// 向"note"表中插入代表不同系统文件夹的记录如通话记录文件夹、根文件夹、临时文件夹、回收站文件夹等通过构造ContentValues对象并调用db.insert方法来实现
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues();
// 插入通话记录文件夹记录
/**
* call record foler for call notes
*/
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
// 插入根文件夹记录,这是默认文件夹
/**
* root folder which is default folder
*/
@ -252,7 +263,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
// 插入回收站文件夹记录
/**
* temporary folder which is used for moving note
*/
@ -269,14 +280,14 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
// 在给定数据库对象上创建"data"表,创建表后会重新创建"data"表相关的触发器以及创建针对"data"表NOTE_ID列的索引并输出日志表示表已创建
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL);
reCreateDataTableTriggers(db);
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
Log.d(TAG, "data table has been created");
}
// 先删除已存在的"data"表相关触发器(如果有),再重新创建相应触发器
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");
@ -286,6 +297,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
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) {
@ -293,59 +305,61 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
}
return mInstance;
}
// 重写SQLiteOpenHelper的onCreate方法在数据库首次创建时调用内部会分别调用createNoteTable(db)和createDataTable(db)来创建"note"表和"data"表
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db);
createDataTable(db);
}
// 重写SQLiteOpenHelper的onUpgrade方法用于处理数据库升级操作根据旧版本号和新版本号进行不同的升级逻辑判断与处理
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false;
boolean skipV2 = false;
// 重写SQLiteOpenHelper的onUpgrade方法用于处理数据库升级操作根据旧版本号和新版本号进行不同的升级逻辑判断与处理
if (oldVersion == 1) {
upgradeToV2(db);
skipV2 = true; // this upgrade including the upgrade from v2 to v3
oldVersion++;
}
// 如果旧版本是2且没有跳过v2的升级即不是从版本1升上来的情况执行升级到版本3的操作设置需要重新创建触发器的标记为true再将旧版本号加1
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db);
reCreateTriggers = true;
oldVersion++;
}
// 如果旧版本是3执行升级到版本4的操作然后将旧版本号加1
if (oldVersion == 3) {
upgradeToV4(db);
oldVersion++;
}
// 如果需要重新创建触发器(根据前面的升级逻辑判断),则分别重新创建"note"表和"data"表相关的触发器
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
}
// 如果最终旧版本号和新版本号不一致,说明升级出现问题,抛出异常表示升级失败
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
}
}
// 升级到版本2的具体操作先删除已存在的"note"表和"data"表(如果有),然后重新创建这两个表
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的具体操作包括删除一些不再使用的触发器向"note"表添加gtask id列以及插入一个回收站系统文件夹记录
private void upgradeToV3(SQLiteDatabase db) {
// drop unused triggers删除不再使用的用于更新笔记修改日期的相关触发器
// 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
// 向"note"表添加gtask id列数据类型为文本默认值为空字符串
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// add a trash system folder
@ -354,7 +368,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
// 升级到版本4的具体操作向"note"表添加version列数据类型为整数默认值为0
private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");

@ -33,15 +33,17 @@ 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;
// NotesProvider类继承自ContentProvider用于作为内容提供者对外提供对笔记相关数据的增删改查等操作
// 并处理不同类型的Uri请求以区分操作的具体对象如笔记、笔记数据等以及搜索相关功能。
public class NotesProvider extends ContentProvider {
// 用于匹配不同的Uri请求的UriMatcher对象通过静态代码块进行初始化配置
private static final UriMatcher mMatcher;
// 用于匹配不同的Uri请求的UriMatcher对象通过静态代码块进行初始化配置
private NotesDatabaseHelper mHelper;
// 用于日志输出的标识字符串,便于在调试和记录运行情况时准确识别相关日志信息
private static final String TAG = "NotesProvider";
// 定义不同的Uri匹配码用于区分不同类型的Uri请求对应不同的笔记或数据相关操作
private static final int URI_NOTE = 1;
private static final int URI_NOTE_ITEM = 2;
private static final int URI_DATA = 3;
@ -49,7 +51,7 @@ public class NotesProvider extends ContentProvider {
private static final int URI_SEARCH = 5;
private static final int URI_SEARCH_SUGGEST = 6;
// 静态代码块初始化UriMatcher对象添加各种不同的Uri匹配规则以便后续根据传入的Uri来确定具体的操作类型
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
@ -64,6 +66,8 @@ public class NotesProvider extends ContentProvider {
/**
* x'0A' represents the '\n' character in sqlite. For title and content in the search result,
* we will trim '\n' and white space in order to show more information.
*
* ID
*/
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
@ -72,46 +76,53 @@ public class NotesProvider extends ContentProvider {
+ 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
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
// 在ContentProvider创建时调用获取NotesDatabaseHelper的单例实例用于后续数据库操作返回true表示初始化成功
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
// 根据传入的Uri等参数执行查询操作根据不同的Uri匹配情况从相应的数据库表笔记表或数据表中查询数据并返回结果游标
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null;
// 获取可读的数据库连接对象
SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null;
// 通过UriMatcher匹配传入的Uri根据匹配结果执行不同的查询逻辑
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 查询笔记表,使用传入的投影、选择条件等参数进行查询
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_NOTE_ITEM:
// 获取具体笔记项的ID从Uri路径中解析然后根据该ID构建更精确的查询条件进行查询
id = uri.getPathSegments().get(1);
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_DATA:
// 查询数据表,同样使用传入的相关参数进行查询
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_DATA_ITEM:
// 获取具体数据项的ID从Uri路径中解析然后基于该ID构建查询条件查询数据表
id = uri.getPathSegments().get(1);
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_SEARCH:
case URI_SEARCH_SUGGEST:
// 对于搜索相关的Uri请求如果传入了排序、投影等额外参数则抛出异常因为搜索操作有其固定的逻辑
if (sortOrder != null || projection != null) {
throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
@ -119,10 +130,12 @@ public class NotesProvider extends ContentProvider {
String searchString = null;
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
// 如果是搜索建议的Uri且路径中有搜索字符串则获取该字符串
if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1);
}
} else {
// 否则从Uri的查询参数中获取名为"pattern"的搜索字符串
searchString = uri.getQueryParameter("pattern");
}
@ -131,7 +144,9 @@ public class NotesProvider extends ContentProvider {
}
try {
// 格式化搜索字符串,添加通配符用于模糊匹配查询
searchString = String.format("%%%s%%", searchString);
// 使用格式化后的搜索字符串执行原始查询,查询符合条件的笔记信息
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[] { searchString });
} catch (IllegalStateException ex) {
@ -141,21 +156,25 @@ public class NotesProvider extends ContentProvider {
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果查询结果游标不为空设置其通知Uri以便在数据变化时能收到通知更新相关UI等
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
// 根据传入的Uri和数据值向相应的数据库表笔记表或数据表插入数据并返回插入后生成的新记录的Uri
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase();
long dataId = 0, noteId = 0, insertedId = 0;
// 通过UriMatcher匹配Uri根据匹配结果执行不同的插入逻辑
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 向笔记表插入数据获取插入后生成的记录ID
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break;
case URI_DATA:
// 如果插入的数据中包含笔记ID则获取该ID然后向数据表插入数据并获取插入后的记录ID
if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID);
} else {
@ -167,32 +186,36 @@ public class NotesProvider extends ContentProvider {
throw new IllegalArgumentException("Unknown URI " + uri);
}
// Notify the note uri
// 如果插入的是笔记数据且笔记ID大于0通知笔记相关的Uri数据发生了变化
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// 如果插入的是数据且数据ID大于0通知数据相关的Uri数据发生了变化
// Notify the data uri
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
// 返回插入后新记录对应的Uri通过将插入的记录ID附加到原始Uri上生成
return ContentUris.withAppendedId(uri, insertedId);
}
// 根据传入的Uri和选择条件从相应的数据库表笔记表或数据表中删除数据并返回删除的记录数量
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean deleteData = false;
// 通过UriMatcher匹配Uri根据匹配结果执行不同的删除逻辑
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 构建笔记表删除的选择条件确保ID大于0可能有额外的传入选择条件拼接然后执行删除操作并获取删除的记录数量
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
// 获取要删除的具体笔记项的ID先判断是否为不允许删除的系统文件夹ID小于等于0如果不是则构建精确的删除条件进行删除操作
id = uri.getPathSegments().get(1);
/**
* ID that smaller than 0 is system folder which is not allowed to
@ -206,10 +229,12 @@ public class NotesProvider extends ContentProvider {
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
break;
case URI_DATA:
// 直接从数据表中根据选择条件执行删除操作标记要通知数据相关的Uri变化并获取删除的记录数量
count = db.delete(TABLE.DATA, selection, selectionArgs);
deleteData = true;
break;
case URI_DATA_ITEM:
// 获取要删除的具体数据项的ID构建精确的删除条件从数据表中删除数据同样标记要通知数据相关的Uri变化并获取删除的记录数量
id = uri.getPathSegments().get(1);
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
@ -220,35 +245,42 @@ public class NotesProvider extends ContentProvider {
}
if (count > 0) {
if (deleteData) {
// 如果是更新数据相关操作且有数据被更新通知笔记相关的Uri数据发生了变化可能关联的笔记数据有变动
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
// 通知传入的Uri对应的数据发生了变化
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
// // 根据传入的Uri、更新的数据值和选择条件对相应的数据库表笔记表或数据表中的数据进行更新并返回更新的记录数量
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean updateData = false;
// 通过UriMatcher匹配Uri根据匹配结果执行不同的更新逻辑
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 在更新笔记表数据前,调用方法更新笔记的版本号(可能根据选择条件等情况),然后执行更新操作并获取更新的记录数量
increaseNoteVersion(-1, selection, selectionArgs);
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
// 获取要更新的具体笔记项的ID调用方法根据该ID等情况更新笔记的版本号然后构建精确的更新条件对笔记表执行更新操作并获取记录数量
id = uri.getPathSegments().get(1);
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
break;
case URI_DATA:
// 直接从数据表中根据选择条件执行删除操作标记要通知数据相关的Uri变化并获取删除的记录数量
count = db.update(TABLE.DATA, values, selection, selectionArgs);
updateData = true;
break;
case URI_DATA_ITEM:
// 获取要删除的具体数据项的ID构建精确的删除条件从数据表中删除数据同样标记要通知数据相关的Uri变化并获取删除的记录数量
id = uri.getPathSegments().get(1);
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
@ -260,17 +292,18 @@ public class NotesProvider extends ContentProvider {
if (count > 0) {
if (updateData) {
// 如果是删除数据相关操作且有数据被删除通知笔记相关的Uri数据发生了变化可能关联的笔记数据有变动
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
} // 通知传入的Uri对应的数据发生了变化
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
// 根据传入的Uri、更新的数据值和选择条件对相应的数据库表笔记表或数据表中的数据进行更新并返回更新的记录数量
private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
// 根据传入的笔记ID、选择条件和选择参数构建并执行更新笔记版本号的SQL语句实现笔记版本号的增加操作
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
@ -292,7 +325,7 @@ public class NotesProvider extends ContentProvider {
}
sql.append(selectString);
}
// 用于获取
mHelper.getWritableDatabase().execSQL(sql.toString());
}

@ -14,7 +14,7 @@
* limitations under the License.
*/
package net.micode.notes.gtask.data;//测试
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
@ -23,38 +23,51 @@ import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
// MetaData类继承自Task类主要用于处理与任务相关的元数据操作例如设置元数据、获取关联的GID可能是某种任务相关的唯一标识
// 并且重写了一些父类的方法来适配自身对于元数据处理的特殊逻辑。
public class MetaData extends Task {
// 用于日志输出的标识字符串,其值为类的简单名称,便于在调试和记录运行情况时准确识别相关日志信息
private final static String TAG = MetaData.class.getSimpleName();
// 用于存储与该元数据相关联的GID可能是Google Tasks等相关任务系统中的任务唯一标识之类的信息初始化为null
private String mRelatedGid = null;
// 设置元数据的方法接收一个GID任务标识和一个JSONObject类型的元数据信息对象
public void setMeta(String gid, JSONObject metaInfo) {
try {
// 将传入的GID放入元数据信息对象中对应的键由GTaskStringUtils.META_HEAD_GTASK_ID指定
// 如果放入过程中出现JSON异常则记录错误日志
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");
}
// 将处理后的元数据信息对象转换为字符串,并调用父类的方法(假设父类有相应的设置笔记内容的方法)设置为笔记内容
setNotes(metaInfo.toString());
// 设置名称为特定的字符串该字符串由GTaskStringUtils.META_NOTE_NAME指定可能用于标识这是元数据相关的名称
setName(GTaskStringUtils.META_NOTE_NAME);
}
}
// 获取关联的GID的方法返回之前存储的mRelatedGid字符串
public String getRelatedGid() {
return mRelatedGid;
}
// 重写父类的方法用于判断当前的元数据是否值得保存判断依据是看获取到的笔记内容通过父类方法获取可能存储着关键元数据信息是否为null
// 如果不为null则表示有值得保存的数据返回true否则返回false
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
// 重写父类的方法用于根据远程的JSON对象来设置自身的内容包括解析出关联的GID等元数据相关操作
@Override
public void setContentByRemoteJSON(JSONObject js) {
// 先调用父类的同名方法来执行一些通用的设置内容的基础操作(假设父类该方法有相应逻辑)
super.setContentByRemoteJSON(js);
// 如果获取到的笔记内容不为null说明有元数据信息
if (getNotes() != null) {
try {
// 将笔记内容字符串转换为JSONObject对象以便解析其中的元数据信息
JSONObject metaInfo = new JSONObject(getNotes().trim());
// 从解析后的元数据信息对象中获取关联的GID并赋值给mRelatedGid成员变量
// 如果获取过程中出现JSON异常则记录警告日志并将mRelatedGid设置为null
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
@ -62,18 +75,21 @@ public class MetaData extends Task {
}
}
}
// 重写父类的方法,这里明确表示该方法不应该被调用,直接抛出 IllegalAccessError异常
// 可能是因为对于元数据对象来说通过本地JSON来设置内容不符合其业务逻辑设计
@Override
public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
// 重写父类的方法,同样明确表示该方法不应该被调用,直接抛出 IllegalAccessError异常
// 可能是因为获取本地JSON对象的操作在元数据对象的业务逻辑中没有对应的合理实现或者不需要这样的操作
@Override
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
}
// 重写父类的方法,再次明确表示该方法不应该被调用,直接抛出 IllegalAccessError异常
// 可能是因为获取同步操作相关信息在元数据对象的业务逻辑中没有对应的处理逻辑或者不需要这样的操作
@Override
public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called");

@ -13,83 +13,91 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 包声明表明该类所属的包名此处是net.micode.notes.gtask.data包
package net.micode.notes.gtask.data;
import android.database.Cursor;
import org.json.JSONObject;
// 定义抽象类Node通常作为节点相关数据结构或业务逻辑的抽象表示
// 具体的节点类可能会继承该抽象类并实现其中的抽象方法
public abstract class Node {
// 定义同步操作的各种类型常量,用于表示不同的同步相关动作
// 表示无同步操作
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;
// 用于存储节点的全局唯一标识符可能是对应任务等的唯一标识初始化为null
private String mGid;
// 存储节点的名称,初始化为空字符串
private String mName;
// 记录节点最后一次被修改的时间戳初始化为0
private long mLastModified;
// 标记该节点是否已被删除初始化为false
private boolean mDeleted;
// 默认构造函数用于初始化Node对象的各个属性的初始值
public Node() {
mGid = null;
mName = "";
mLastModified = 0;
mDeleted = false;
}
// 抽象方法用于获取创建动作对应的JSONObject具体的创建动作由actionId参数指定
// 不同的子类需要根据自身业务逻辑实现该方法来构造相应的创建动作数据
public abstract JSONObject getCreateAction(int actionId);
// 抽象方法用于获取更新动作对应的JSONObject根据传入的actionId来确定具体的更新动作
// 子类需实现该方法以按照实际情况生成合适的更新动作相关的JSON数据
public abstract JSONObject getUpdateAction(int actionId);
// 抽象方法根据远程传来的JSONObject数据设置节点的内容
// 具体的设置逻辑由继承该抽象类的子类来实现,以适配不同类型节点的数据解析和设置
public abstract void setContentByRemoteJSON(JSONObject js);
// 抽象方法根据本地的JSONObject数据设置节点的内容同样子类要实现该方法来处理本地数据到节点内容的设置
public abstract void setContentByLocalJSON(JSONObject js);
// 抽象方法从节点当前内容获取对应的本地JSON表示形式
// 子类需根据自身保存的数据结构等情况将节点内容转换为合适的JSONObject返回
public abstract JSONObject getLocalJSONFromContent();
// 抽象方法根据给定的Cursor通常用于从数据库查询结果中获取数据来确定节点的同步操作类型
// 子类要实现该方法以解析Cursor中的数据从而判断出对应的同步动作
public abstract int getSyncAction(Cursor c);
// 设置节点的全局唯一标识符Gid的方法
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;
}
// 获取节点的全局唯一标识符Gid的方法
public String getGid() {
return this.mGid;
}
// 获取节点名称的方法
public String getName() {
return this.mName;
}
// 获取节点最后一次修改时间戳的方法
public long getLastModified() {
return this.mLastModified;
}

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 包声明表明该类所属的包名为net.micode.notes.gtask.data
package net.micode.notes.gtask.data;
import android.content.ContentResolver;
@ -33,45 +33,48 @@ import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
// SqlData类主要用于处理与数据库相关的数据操作可能涉及到将数据在数据库和JSON格式之间进行转换、保存等功能
public class SqlData {
// 用于日志记录的标签,取当前类的简单名称
private static final String TAG = SqlData.class.getSimpleName();
// 表示无效的ID值通常用于初始化或者标识某个非法的ID状态
private static final int INVALID_ID = -99999;
// 定义一个字符串数组,用于指定从数据库查询数据时的投影(即要查询的列),包含了数据相关的多个列名
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
// 定义常量表示在投影数组中ID列对应的索引位置方便后续从查询结果的Cursor中获取对应列的数据
public static final int DATA_ID_COLUMN = 0;
// 定义常量表示在投影数组中MIME_TYPE列对应的索引位置
public static final int DATA_MIME_TYPE_COLUMN = 1;
// 定义常量表示在投影数组中CONTENT列对应的索引位置
public static final int DATA_CONTENT_COLUMN = 2;
// 定义常量表示在投影数组中DATA1列对应的索引位置
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
// 定义常量表示在投影数组中DATA3列对应的索引位置
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
// 用于与内容提供器进行交互,以便执行数据库相关的操作,如查询、插入、更新等
private ContentResolver mContentResolver;
// 标记当前操作是否是创建新数据的操作初始化为true
private boolean mIsCreate;
// 存储数据对应的ID初始化为无效ID值
private long mDataId;
// 存储数据的MIME类型初始化为默认的Note类型可能对应某种笔记的数据类型
private String mDataMimeType;
// 存储数据的具体内容,初始化为空字符串
private String mDataContent;
// 存储数据内容中相关的一个长整型数据具体含义可能根据业务而定初始化为0
private long mDataContentData1;
// 存储数据内容中相关的一个字符串数据(具体含义可能根据业务而定),初始化为空字符串
private String mDataContentData3;
// 用于存储要进行差异更新的数据值即记录数据有变化的部分初始化为一个新的ContentValues对象
private ContentValues mDiffDataValues;
// 构造函数用于创建一个新的SqlData对象通常在创建新数据时使用
// 接收一个Context上下文对象用于获取ContentResolver来操作数据库
public SqlData(Context context) {
// 获取上下文对应的ContentResolver以便后续进行数据库操作
mContentResolver = context.getContentResolver();
mIsCreate = true;
mDataId = INVALID_ID;
@ -81,14 +84,15 @@ public class SqlData {
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
}
// 另一个构造函数用于根据已有的Cursor通常是从数据库查询返回的结果集来创建SqlData对象常用于读取已有数据
// 接收一个Context上下文对象和一个Cursor对象用于初始化该对象的各个属性
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDiffDataValues = new ContentValues();
}
// 私有方法用于从给定的Cursor中加载数据到当前对象的各个属性中根据之前定义的列索引常量来获取对应列的数据
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
@ -96,40 +100,47 @@ public class SqlData {
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
// 用于根据传入的JSONObject来设置当前对象的数据内容比较传入数据与当前已有数据的差异
// 将有差异的部分放入mDiffDataValues中以便后续进行更新操作
public void setContent(JSONObject js) throws JSONException {
// 获取JSON对象中ID字段的值如果不存在则使用无效ID值
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
// 如果是创建操作或者当前对象的ID与传入JSON中的ID不同则将新的ID放入差异数据值中
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
}
mDataId = dataId;
// 获取JSON对象中MIME_TYPE字段的值如果不存在则使用默认的Note类型
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
// 如果是创建操作或者当前对象的MIME_TYPE与传入JSON中的不同则将新的MIME_TYPE放入差异数据值中
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
} // 获取JSON对象中CONTENT字段的值如果不存在则使用空字符串
mDataMimeType = dataMimeType;
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
// 如果是创建操作或者当前对象的CONTENT与传入JSON中的不同则将新的CONTENT放入差异数据值中
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
mDataContent = dataContent;
// 获取JSON对象中DATA1字段的值如果不存在则使用0
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
// 如果是创建操作或者当前对象的DATA1值与传入JSON中的不同则将新的DATA1放入差异数据值中
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
} // 获取JSON对象中DATA3字段的值如果不存在则使用空字符串
mDataContentData1 = dataContentData1;
// 如果是创建操作或者当前对象的DATA3与传入JSON中的不同则将新的DATA3放入差异数据值中
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
mDataContentData3 = dataContentData3;
}
// 用于获取当前对象的数据内容并将其转换为JSONObject格式返回
// 如果当前操作是创建操作还未在数据库中创建该数据则记录错误日志并返回null
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
@ -143,46 +154,49 @@ public class SqlData {
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
// 用于将当前对象的数据提交到数据库中,根据是创建操作还是更新操作执行不同的逻辑
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
// 如果是创建操作且当前数据ID是无效ID且差异数据值中包含ID字段则移除该ID字段可能有默认生成机制
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
// 将关联的笔记ID添加到差异数据值中
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
// 使用ContentResolver插入数据到指定的数据库URI并获取插入后生成的URI
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
try {
// 尝试从插入后的URI中获取生成的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 {
} else { // 如果是更新操作,且差异数据值中有数据需要更新
if (mDiffDataValues.size() > 0) {
int result = 0;
if (!validateVersion) {
if (!validateVersion) {// 如果不需要验证版本则直接更新指定ID的数据
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
} else {// 如果需要验证版本则根据指定的版本条件更新数据通过SQL语句的条件筛选来确保更新的准确性
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)
});
}
} // 如果更新结果为0即没有数据被更新记录警告日志提示可能用户在同步时更新了笔记
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;
}

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 包声明表明该类所属的包名为net.micode.notes.gtask.data
package net.micode.notes.gtask.data;
import android.appwidget.AppWidgetManager;
@ -37,12 +37,12 @@ import org.json.JSONObject;
import java.util.ArrayList;
public class SqlNote {
// SqlNote类主要用于处理与笔记相关的数据操作包括从数据库读取笔记信息、设置笔记内容、将笔记内容转换为JSON格式以及将笔记数据提交到数据库等功能
public class SqlNote {// 用于日志记录的标签,取当前类的简单名称
private static final String TAG = SqlNote.class.getSimpleName();
// 表示无效的ID值通常用于初始化或者标识某个非法的ID状态
private static final int INVALID_ID = -99999;
// 定义一个字符串数组,用于指定从数据库查询笔记数据时的投影(即要查询的列),包含了笔记相关的多个列名
public static final String[] PROJECTION_NOTE = new String[] {
NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE,
@ -51,77 +51,80 @@ public class SqlNote {
NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID,
NoteColumns.VERSION
};
// 定义常量表示在投影数组中ID列对应的索引位置方便后续从查询结果的Cursor中获取对应列的数据
public static final int ID_COLUMN = 0;
// 定义常量表示在投影数组中ALERTED_DATE列对应的索引位置
public static final int ALERTED_DATE_COLUMN = 1;
// 定义常量表示在投影数组中BG_COLOR_ID列对应的索引位置
public static final int BG_COLOR_ID_COLUMN = 2;
// 定义常量表示在投影数组中CREATED_DATE列对应的索引位置
public static final int CREATED_DATE_COLUMN = 3;
// 定义常量表示在投影数组中HAS_ATTACHMENT列对应的索引位置
public static final int HAS_ATTACHMENT_COLUMN = 4;
// 定义常量表示在投影数组中MODIFIED_DATE列对应的索引位置
public static final int MODIFIED_DATE_COLUMN = 5;
// 定义常量表示在投影数组中NOTES_COUNT列对应的索引位置
public static final int NOTES_COUNT_COLUMN = 6;
// 定义常量表示在投影数组中PARENT_ID列对应的索引位置
public static final int PARENT_ID_COLUMN = 7;
// 定义常量表示在投影数组中SNIPPET列对应的索引位置
public static final int SNIPPET_COLUMN = 8;
// 定义常量表示在投影数组中TYPE列对应的索引位置
public static final int TYPE_COLUMN = 9;
// 定义常量表示在投影数组中WIDGET_ID列对应的索引位置
public static final int WIDGET_ID_COLUMN = 10;
// 定义常量表示在投影数组中WIDGET_TYPE列对应的索引位置
public static final int WIDGET_TYPE_COLUMN = 11;
// 定义常量表示在投影数组中SYNC_ID列对应的索引位置
public static final int SYNC_ID_COLUMN = 12;
// 定义常量表示在投影数组中LOCAL_MODIFIED列对应的索引位置
public static final int LOCAL_MODIFIED_COLUMN = 13;
// 定义常量表示在投影数组中ORIGIN_PARENT_ID列对应的索引位置
public static final int ORIGIN_PARENT_ID_COLUMN = 14;
// 定义常量表示在投影数组中GTASK_ID列对应的索引位置
public static final int GTASK_ID_COLUMN = 15;
// 定义常量表示在投影数组中VERSION列对应的索引位置
public static final int VERSION_COLUMN = 16;
// 上下文对象,用于获取系统资源、执行与应用上下文相关的操作等
private Context mContext;
// 用于与内容提供器进行交互,以便执行数据库相关的操作,如查询、插入、更新等
private ContentResolver mContentResolver;
// 标记当前操作是否是创建新笔记的操作初始化为true
private boolean mIsCreate;
// 存储笔记的ID初始化为无效ID值
private long mId;
// 存储笔记的提醒日期时间戳相关具体含义根据业务而定初始化为0
private long mAlertDate;
// 存储笔记的背景颜色ID初始化为通过ResourceParser获取的默认背景颜色ID
private int mBgColorId;
// 存储笔记的创建日期(时间戳),初始化为当前系统时间
private long mCreatedDate;
// 存储笔记是否有附件的标识0表示没有初始化为0
private int mHasAttachment;
// 存储笔记的最后修改日期(时间戳),初始化为当前系统时间
private long mModifiedDate;
// 存储笔记的父级ID可能用于表示笔记的层级关系等初始化为0
private long mParentId;
// 存储笔记的摘要信息(简短描述等),初始化为空字符串
private String mSnippet;
// 存储笔记的类型初始化为普通笔记类型Notes.TYPE_NOTE
private int mType;
// 存储笔记关联的桌面小部件ID初始化为无效的小部件ID
private int mWidgetId;
// 存储笔记关联的桌面小部件类型初始化为无效的小部件类型Notes.TYPE_WIDGET_INVALIDE
private int mWidgetType;
// 存储笔记的原始父级ID具体用途可能与笔记的来源、历史关系等相关初始化为0
private long mOriginParent;
// 存储笔记的版本号初始化为0
private long mVersion;
// 用于存储要进行差异更新的笔记数据值即记录笔记数据有变化的部分初始化为一个新的ContentValues对象
private ContentValues mDiffNoteValues;
// 存储与该笔记相关的数据列表可能是笔记包含的多个具体数据项等每个数据项用SqlData类表示
private ArrayList<SqlData> mDataList;
// 构造函数用于创建一个新的SqlNote对象通常在创建新笔记时使用
// 接收一个Context上下文对象用于获取ContentResolver等资源来操作数据库以及进行其他相关操作
public SqlNote(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
@ -142,7 +145,8 @@ public class SqlNote {
mDiffNoteValues = new ContentValues();
mDataList = new ArrayList<SqlData>();
}
// 构造函数用于根据已有的Cursor通常是从数据库查询返回的结果集来创建SqlNote对象常用于读取已有笔记数据
// 接收一个Context上下文对象和一个Cursor对象用于初始化该对象的各个属性
public SqlNote(Context context, Cursor c) {
mContext = context;
mContentResolver = context.getContentResolver();
@ -153,7 +157,8 @@ public class SqlNote {
loadDataContent();
mDiffNoteValues = new ContentValues();
}
// 构造函数用于根据指定的笔记ID从数据库中加载笔记数据来创建SqlNote对象
// 接收一个Context上下文对象和一个笔记IDlong类型用于初始化该对象的各个属性
public SqlNote(Context context, long id) {
mContext = context;
mContentResolver = context.getContentResolver();
@ -165,10 +170,11 @@ public class SqlNote {
mDiffNoteValues = new ContentValues();
}
// 私有方法根据给定的笔记ID从数据库中查询并加载笔记数据到当前对象的各个属性中通过调用另一个loadFromCursor方法实现具体的属性赋值
private void loadFromCursor(long id) {
Cursor c = null;
try {
try {// 使用ContentResolver根据指定的URI、查询条件通过ID匹配查询笔记数据
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(id)
@ -184,6 +190,7 @@ public class SqlNote {
c.close();
}
}
// 私有方法用于从给定的Cursor中加载笔记数据到当前对象的各个属性中根据之前定义的列索引常量来获取对应列的数据
private void loadFromCursor(Cursor c) {
mId = c.getLong(ID_COLUMN);
@ -199,11 +206,13 @@ public class SqlNote {
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
mVersion = c.getLong(VERSION_COLUMN);
}
// 私有方法用于加载与当前笔记相关的数据内容具体数据项用SqlData类表示通过查询数据库并将结果封装到SqlData对象中添加到mDataList列表里
private void loadDataContent() {
Cursor c = null;
mDataList.clear();
try {
// 使用ContentResolver根据指定的URI、查询条件通过笔记ID匹配查询与笔记相关的数据
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] {
String.valueOf(mId)
@ -225,6 +234,8 @@ public class SqlNote {
c.close();
}
}
// 用于根据传入的JSONObject来设置当前笔记对象的数据内容根据笔记类型如普通笔记、文件夹等进行不同的处理
// 将有差异的部分放入mDiffNoteValues中以便后续进行更新操作返回设置是否成功的布尔值
public boolean setContent(JSONObject js) {
try {
@ -232,7 +243,7 @@ public class SqlNote {
if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
Log.w(TAG, "cannot set system folder");
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
// for folder we can only update the snnipet and type
// for folder we can only update the snnipet and type// 如果是文件夹类型,只能更新摘要和类型信息
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
@ -247,6 +258,7 @@ public class SqlNote {
}
mType = type;
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) {
// 如果是普通笔记类型
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID;
if (mIsCreate || mId != id) {
@ -330,24 +342,24 @@ public class SqlNote {
mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent);
}
mOriginParent = originParent;
// 遍历JSON数组中的每个数据项对应笔记包含的具体数据内容
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
SqlData sqlData = null;
SqlData sqlData = null; // 如果数据项中有ID字段尝试在已有的数据列表中查找对应ID的数据对象
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
for (SqlData temp : mDataList) {
if (dataId == temp.getId()) {
sqlData = temp;
}
}
}// 如果未找到则创建一个新的SqlData对象并添加到数据列表中
}
if (sqlData == null) {
sqlData = new SqlData(mContext);
mDataList.add(sqlData);
}
// 调用SqlData对象的方法设置其具体内容
sqlData.setContent(data);
}
}
@ -358,7 +370,8 @@ public class SqlNote {
}
return true;
}
// 用于获取当前笔记对象的数据内容并将其转换为JSONObject格式返回
// 如果当前操作是创建操作还未在数据库中创建该笔记则记录错误日志并返回null
public JSONObject getContent() {
try {
JSONObject js = new JSONObject();
@ -406,46 +419,47 @@ public class SqlNote {
}
return null;
}
// 设置笔记的父级ID的方法同时将该变化记录到差异更新数据值中以便后续更新数据库时使用
public void setParentId(long id) {
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
}
// 设置笔记的Gtask ID具体含义可能与相关任务关联等有关的方法将该值记录到差异更新数据值中
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
}
// 设置笔记的同步ID的方法将该值记录到差异更新数据值中
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
}
// 重置笔记的本地修改标记将其设置为0的方法通过更新差异数据值来实现
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
}
// 获取笔记ID的方法
public long getId() {
return mId;
}
// 获取笔记父级ID的方法
public long getParentId() {
return mParentId;
}
// 获取笔记摘要信息的方法
public String getSnippet() {
return mSnippet;
}
// 判断笔记是否为普通笔记类型的方法,返回布尔值
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE;
}
// 用于将当前笔记对象的数据提交到数据库中,根据是创建操作还是更新操作执行不同的逻辑,
// 同时也会处理与之关联的数据通过调用SqlData的commit方法的提交操作
public void commit(boolean validateVersion) {
if (mIsCreate) {
if (mIsCreate) {// 如果是创建操作且当前笔记ID是无效ID且差异数据值中包含ID字段则移除该ID字段可能有默认生成机制
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID);
}
// 使用ContentResolver插入笔记数据到指定的数据库URI并获取插入后生成的URI
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
try {
mId = Long.valueOf(uri.getPathSegments().get(1));
@ -458,18 +472,19 @@ public class SqlNote {
}
if (mType == Notes.TYPE_NOTE) {
// 如果是普通笔记类型遍历与之关联的数据列表调用每个SqlData对象的commit方法提交数据
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, false, -1);
}
}
} else {
} else {// 如果是更新操作
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");
}
if (mDiffNoteValues.size() > 0) {
mVersion ++;
int result = 0;
int result = 0;// 根据是否验证版本来决定更新数据库的条件
if (!validateVersion) {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] {
@ -493,12 +508,12 @@ public class SqlNote {
}
}
}
// 重新从数据库加载笔记数据,刷新本地信息(通过调用相关加载方法)
// refresh local info
loadFromCursor(mId);
if (mType == Notes.TYPE_NOTE)
loadDataContent();
// 清空差异数据值,准备下一次的数据操作
mDiffNoteValues.clear();
mIsCreate = false;
}

@ -30,21 +30,22 @@ import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
// Task类继承自抽象类Node代表一个任务相关的实体包含任务自身的各种属性以及与任务操作相关的方法
// 例如创建、更新任务的JSON表示从JSON数据设置任务内容等功能用于处理任务相关的数据逻辑
public class Task extends Node {
public class Task extends Node { // 用于日志记录的标签,取当前类的简单名称
private static final String TAG = Task.class.getSimpleName();
// 标记任务是否已完成初始化为false
private boolean mCompleted;
// 存储任务的备注信息可能是对任务的详细描述等初始化为null
private String mNotes;
// 存储任务的元信息以JSONObject形式具体内容结构可能根据业务而定初始化为null
private JSONObject mMetaInfo;
// 指向当前任务的前一个兄弟任务在任务列表等场景下用于表示顺序关系初始化为null
private Task mPriorSibling;
// 指向当前任务所属的父任务列表表明任务的归属层级关系初始化为null
private TaskList mParent;
// 构造函数调用父类Node类的构造函数进行初始化并对Task类特有的属性进行默认初始化
public Task() {
super();
mCompleted = false;
@ -53,21 +54,24 @@ public class Task extends Node {
mParent = null;
mMetaInfo = null;
}
// 实现抽象类Node中定义的抽象方法用于获取创建任务的操作对应的JSONObject该JSON对象包含了创建任务所需的各种信息
// 如操作类型、任务的基本属性(名称、备注等)、所属父任务等相关信息,按照特定的格式进行组装
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
try { // 设置操作类型为创建任务,使用预定义的字符串常量标识
// action_type
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 设置操作的唯一标识符(具体业务中用于区分不同的创建操作实例)
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务在父任务列表中的索引位置(通过调用父任务列表的方法获取)
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
// 构建表示任务实体的详细信息的JSONObject包含任务名称、创建者ID此处设为"null",可能需根据实际情况调整)、
// 实体类型(标识为任务类型)以及任务备注(如果存在)等信息
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
@ -78,17 +82,17 @@ public class Task extends Node {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
// 设置任务所属父任务的全局唯一标识符Gid
// parent_id
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
// 设置目标父任务的类型(此处设为组类型,可能与任务的分组、归属等概念相关,具体依业务而定)
// dest_parent_type
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// 设置任务所属列表的全局唯一标识符通常与父任务列表的Gid一致
// list_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());
@ -102,21 +106,22 @@ public class Task extends Node {
return js;
}
// 实现抽象类Node中定义的抽象方法用于获取更新任务的操作对应的JSONObject包含了更新任务所需的关键信息
// 如操作类型、任务的唯一标识符、任务实体的更新内容如名称、备注、是否删除等信息按照特定格式组装JSON数据
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
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
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// 构建表示任务实体更新内容的JSONObject包含任务名称、任务备注如果存在以及任务是否已删除的标记等信息
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
@ -134,35 +139,38 @@ public class Task extends Node {
return js;
}
// 实现抽象类Node中定义的抽象方法用于根据远程传来的JSONObject数据设置任务的内容
// 从JSON对象中解析出任务的各个属性值如ID、最后修改时间、名称、备注、是否删除、是否完成等并设置到当前任务对象中
// 如果解析过程出现JSONException异常则记录错误日志并抛出相应异常
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
try {// 从JSON对象中获取任务的全局唯一标识符Gid并设置到当前任务对象中
// id
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));
}
// 从JSON对象中获取任务的备注信息并设置到当前任务对象中
// notes
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}
// 从JSON对象中获取任务是否已删除的标记并设置到当前任务对象中
// deleted
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}
// 从JSON对象中获取任务是否已完成的标记并设置到当前任务对象中
// completed
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
@ -174,7 +182,10 @@ public class Task extends Node {
}
}
}
// 实现抽象类Node中定义的抽象方法用于根据本地的JSONObject数据设置任务的内容
// 首先进行一些基本的校验判断JSON对象是否可用是否包含必要的头部信息等然后尝试从JSON数据中解析出笔记类型
// 如果类型为普通笔记类型则遍历数据数组找到MIME类型为笔记类型的数据项从中获取内容并设置为任务的名称
// 如果解析过程出现JSONException异常则记录错误日志
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) {
@ -203,11 +214,14 @@ public class Task extends Node {
e.printStackTrace();
}
}
// 实现抽象类Node中定义的抽象方法用于从任务当前内容获取对应的本地JSON表示形式
// 根据任务的元信息是否存在分为两种情况处理如果是新创建的任务元信息为空则按照一定格式构建包含任务名称的JSON对象
// 如果是已同步过的任务元信息不为空则从元信息中提取相关数据并更新任务名称所在的数据项最后返回构建好的JSON对象
// 如果在构建JSON对象过程中出现JSONException异常则记录错误日志并返回null
public JSONObject getLocalJSONFromContent() {
String name = getName();
try {
if (mMetaInfo == null) {
if (mMetaInfo == null) { // 新任务创建自网络(可能表示从远程创建后还未完全同步等情况)
// new task created from web
if (name == null) {
Log.w(TAG, "the note seems to be an empty one");
@ -224,7 +238,7 @@ public class Task extends Node {
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
return js;
} else {
} else { // 已同步的任务(已经有过同步操作,存在相关元信息)
// synced task
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
@ -246,7 +260,8 @@ public class Task extends Node {
return null;
}
}
// 用于设置任务的元信息将传入的MetaData对象中的笔记信息如果不为空且可转换为JSONObject转换为JSONObject并赋值给mMetaInfo属性
// 如果转换过程出现JSONException异常则记录警告日志并将mMetaInfo设为null
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) {
try {
@ -257,7 +272,9 @@ public class Task extends Node {
}
}
}
// 实现抽象类Node中定义的抽象方法用于根据给定的Cursor通常用于从数据库查询结果中获取数据来确定任务的同步操作类型
// 通过一系列的条件判断来分析任务在本地和远程数据之间的差异情况,从而确定是无同步操作、更新本地、更新远程、更新冲突还是出现错误等同步动作,
// 如果在判断过程中出现异常,则记录错误日志并返回表示错误的同步操作类型
public int getSyncAction(Cursor c) {
try {
JSONObject noteInfo = null;
@ -274,7 +291,7 @@ public class Task extends Node {
Log.w(TAG, "remote note id seems to be deleted");
return SYNC_ACTION_UPDATE_LOCAL;
}
// 验证笔记的ID是否匹配本地数据库中的ID与从元信息中获取的远程ID进行比较
// validate the note id now
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
Log.w(TAG, "note id doesn't match");
@ -282,17 +299,22 @@ public class Task extends Node {
}
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// 本地没有更新通过判断本地修改标记是否为0来确定
// there is no local update
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 两边都没有更新本地同步ID与任务最后修改时间戳相同
// no update both side
return SYNC_ACTION_NONE;
} else {
// 应用远程到本地(本地没有更新但远程有更新,需将远程数据应用到本地)
// apply remote to local
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// 验证任务的Gtask ID是否匹配本地数据库中的Gtask ID与当前任务对象的Gid进行比较
// validate gtask id
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
// 只有本地修改(本地修改了但远程没有更新,只需将本地修改同步到远程)
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
}
@ -310,36 +332,37 @@ public class Task extends Node {
return SYNC_ACTION_ERROR;
}
// 判断任务是否值得保存的方法根据任务的元信息是否存在、任务名称以及任务备注是否非空去除空格后长度大于0来综合判断
// 如果满足上述条件之一则认为任务值得保存返回true否则返回false
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0);
}
// 设置任务是否完成的方法,用于更新任务的完成状态
public void setCompleted(boolean completed) {
this.mCompleted = completed;
}
// 设置任务备注信息的方法,用于更新任务的备注内容
public void setNotes(String notes) {
this.mNotes = notes;
}
// 设置任务前一个兄弟任务的方法,用于建立任务之间的顺序关联
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling;
}
// 设置任务所属父任务列表的方法,用于明确任务的层级归属关系
public void setParent(TaskList parent) {
this.mParent = parent;
}
// 获取任务是否完成的方法返回任务的完成状态true表示已完成false表示未完成
public boolean getCompleted() {
return this.mCompleted;
}
// 获取任务备注信息的方法,返回任务的备注内容
public String getNotes() {
return this.mNotes;
}
// 获取任务所属父任务列表的方法,返回指向当前任务所属父任务列表的引用
public Task getPriorSibling() {
return this.mPriorSibling;
}

@ -28,35 +28,41 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
// TaskList类继承自抽象类Node代表一个任务列表相关的实体包含任务列表自身的各种属性以及与任务列表操作相关的方法
// 例如创建、更新任务列表的JSON表示从JSON数据设置任务列表内容对任务列表中的子任务进行添加、删除、移动等操作的功能用于处理任务列表相关的数据逻辑
public class TaskList extends Node {
// 用于日志记录的标签,取当前类的简单名称
private static final String TAG = TaskList.class.getSimpleName();
// 存储任务列表的索引具体含义可能与任务列表在某个整体结构中的顺序、位置等相关依业务而定初始化为1
private int mIndex;
// 存储该任务列表包含的所有子任务以ArrayList形式存储初始化为一个空的ArrayList
private ArrayList<Task> mChildren;
// 构造函数调用父类Node类的构造函数进行初始化并对TaskList类特有的属性进行默认初始化创建一个空的任务列表对象并设置初始索引值
public TaskList() {
super();
mChildren = new ArrayList<Task>();
mIndex = 1;
}
// 实现抽象类Node中定义的抽象方法用于获取创建任务列表的操作对应的JSONObject该JSON对象包含了创建任务列表所需的各种信息
// 如操作类型、操作的唯一标识符、任务列表的索引位置、任务列表实体的基本属性名称、创建者ID、实体类型等按照特定的格式进行组装
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// 设置操作类型为创建任务列表,使用预定义的字符串常量标识
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 设置操作的唯一标识符(具体业务中用于区分不同的创建操作实例)
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务列表的索引位置
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
// 构建表示任务列表实体的详细信息的JSONObject包含任务列表名称、创建者ID此处设为"null",可能需根据实际情况调整)、
// 实体类型(标识为组类型,表示任务列表在整体结构中的类型为组)等信息
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
@ -73,21 +79,24 @@ public class TaskList extends Node {
return js;
}
// 实现抽象类Node中定义的抽象方法用于获取更新任务列表的操作对应的JSONObject包含了更新任务列表所需的关键信息
// 如操作类型、操作的唯一标识符、任务列表的唯一标识符、任务列表实体的更新内容如名称、是否删除等信息按照特定格式组装JSON数据
public JSONObject getUpdateAction(int actionId) {
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
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// 构建表示任务列表实体更新内容的JSONObject包含任务列表名称以及任务列表是否已删除的标记等信息
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
@ -101,21 +110,24 @@ public class TaskList extends Node {
}
return js;
}
} // 实现抽象类Node中定义的抽象方法用于根据远程传来的JSONObject数据设置任务列表的内容
// 从JSON对象中解析出任务列表的各个属性值如ID、最后修改时间、名称等并设置到当前任务列表对象中
// 如果解析过程出现JSONException异常则记录错误日志并抛出相应异常
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// 从JSON对象中获取任务列表的全局唯一标识符Gid并设置到当前任务列表对象中
// id
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));
@ -128,7 +140,12 @@ public class TaskList extends Node {
}
}
}
// 实现抽象类Node中定义的抽象方法用于根据本地的JSONObject数据设置任务列表的内容
// 首先进行一些基本的校验判断JSON对象是否可用是否包含必要的头部信息等然后根据笔记类型进行不同的处理
// 如果是文件夹类型从JSON中获取摘要信息并设置任务列表名称添加特定前缀
// 如果是系统类型根据特定的系统文件夹ID设置对应的名称
// 如果类型不符合预期,则记录错误日志,
// 如果解析过程出现JSONException异常则记录错误日志并打印异常堆栈信息
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
@ -156,7 +173,9 @@ public class TaskList extends Node {
e.printStackTrace();
}
}
// 实现抽象类Node中定义的抽象方法用于从任务列表当前内容获取对应的本地JSON表示形式
// 按照特定格式构建包含任务列表名称和类型信息的JSON对象根据名称是否包含特定前缀来判断并设置类型文件夹类型或系统类型
// 如果构建JSON对象过程中出现JSONException异常则记录错误日志并返回null
public JSONObject getLocalJSONFromContent() {
try {
JSONObject js = new JSONObject();
@ -182,28 +201,37 @@ public class TaskList extends Node {
return null;
}
}
// 实现抽象类Node中定义的抽象方法根据给定的Cursor通常用于从数据库查询结果中获取数据来确定任务列表的同步操作类型
// 通过判断本地是否有更新、任务列表的Gtask ID是否匹配以及两边的修改时间戳是否一致等条件来确定是无同步操作、更新本地、更新远程还是出现错误等同步动作
// 如果在判断过程中出现异常,则记录错误日志并返回表示错误的同步操作类型,对于文件夹冲突的情况,默认应用本地修改(返回更新远程操作类型)
public int getSyncAction(Cursor c) {
try {
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// 本地没有更新通过判断本地修改标记是否为0来确定
// there is no local update
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 两边都没有更新本地同步ID与任务列表最后修改时间戳相同
// no update both side
return SYNC_ACTION_NONE;
} else {
// 应用远程到本地(本地没有更新但远程有更新,需将远程数据应用到本地)
// apply remote to local
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// 验证任务列表的Gtask ID是否匹配本地数据库中的Gtask ID与当前任务列表对象的Gid进行比较
// validate gtask id
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 只有本地修改(本地修改了但远程没有更新,只需将本地修改同步到远程)
// local modification only
return SYNC_ACTION_UPDATE_REMOTE;
} else {
// 对于文件夹冲突的情况,直接应用本地修改
// for folder conflicts, just apply local modification
return SYNC_ACTION_UPDATE_REMOTE;
}
@ -215,16 +243,19 @@ public class TaskList extends Node {
return SYNC_ACTION_ERROR;
}
// 获取任务列表中子任务的数量的方法直接返回存储子任务的ArrayList的大小
public int getChildTaskCount() {
return mChildren.size();
}
// 向任务列表中添加子任务的方法,将传入的任务对象添加到子任务列表中,
// 如果任务对象不为空且不在子任务列表中则添加成功后设置该任务的前一个兄弟任务如果列表为空则为null否则为列表中最后一个任务以及父任务当前任务列表
// 返回添加操作是否成功的布尔值
public boolean addChildTask(Task task) {
boolean ret = false;
if (task != null && !mChildren.contains(task)) {
ret = mChildren.add(task);
if (ret) {
// 需要设置前一个兄弟任务和父任务
// need to set prior sibling and parent
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren
.get(mChildren.size() - 1));
@ -233,7 +264,11 @@ public class TaskList extends Node {
}
return ret;
}
// 在指定索引位置向任务列表中添加子任务的方法,
// 首先进行索引合法性校验如果索引无效则记录错误日志并返回false
// 然后判断任务是否已在列表中,如果不在且任务对象不为空,则将任务添加到指定索引位置,
// 接着更新任务列表中相关任务的前一个兄弟任务关系(设置新添加任务的前一个兄弟任务以及后续任务的前一个兄弟任务指向新添加任务),
// 返回添加操作是否成功的布尔值
public boolean addChildTask(Task task, int index) {
if (index < 0 || index > mChildren.size()) {
Log.e(TAG, "add child task: invalid index");
@ -243,7 +278,7 @@ public class TaskList extends Node {
int pos = mChildren.indexOf(task);
if (task != null && pos == -1) {
mChildren.add(index, task);
// 更新任务列表
// update the task list
Task preTask = null;
Task afterTask = null;
@ -259,7 +294,10 @@ public class TaskList extends Node {
return true;
}
// 从任务列表中移除指定子任务的方法,
// 首先查找任务在列表中的索引位置如果存在则尝试移除该任务移除成功后重置该任务的前一个兄弟任务和父任务为null
// 并且更新任务列表中后续任务的前一个兄弟任务关系(如果移除位置不是列表末尾),
// 返回移除操作是否成功的布尔值
public boolean removeChildTask(Task task) {
boolean ret = false;
int index = mChildren.indexOf(task);
@ -267,10 +305,11 @@ public class TaskList extends Node {
ret = mChildren.remove(task);
if (ret) {
// 重置前一个兄弟任务和父任务
// reset prior sibling and parent
task.setPriorSibling(null);
task.setParent(null);
// 更新任务列表
// update the task list
if (index != mChildren.size()) {
mChildren.get(index).setPriorSibling(
@ -280,6 +319,9 @@ public class TaskList extends Node {
}
return ret;
}
// 在任务列表中移动指定子任务到新的索引位置的方法,
// 首先进行索引合法性校验以及判断任务是否在列表中如果校验不通过则记录错误日志并返回false
// 如果任务当前位置和目标位置相同则直接返回true否则先移除该任务再添加到新的索引位置返回操作是否成功的布尔值
public boolean moveChildTask(Task task, int index) {
@ -298,6 +340,7 @@ public class TaskList extends Node {
return true;
return (removeChildTask(task) && addChildTask(task, index));
}
// 根据任务的全局唯一标识符Gid查找子任务的方法遍历子任务列表找到Gid匹配的任务并返回如果没找到则返回null
public Task findChildTaskByGid(String gid) {
for (int i = 0; i < mChildren.size(); i++) {
@ -308,10 +351,12 @@ public class TaskList extends Node {
}
return null;
}
// 获取指定子任务在任务列表中的索引位置的方法通过调用ArrayList的indexOf方法来查找并返回任务在列表中的索引若不存在则返回 -1
public int getChildTaskIndex(Task task) {
return mChildren.indexOf(task);
}
// 根据索引获取子任务的方法先对索引进行合法性校验如果索引无效则记录错误日志并返回null否则返回对应索引位置的子任务
public Task getChildTaskByIndex(int index) {
if (index < 0 || index >= mChildren.size()) {
@ -320,7 +365,8 @@ public class TaskList extends Node {
}
return mChildren.get(index);
}
// 根据任务的全局唯一标识符Gid获取子任务的方法与findChildTaskByGid方法功能类似可能是重复定义或者不同使用场景下的同名方法
// 遍历子任务列表找到Gid匹配的任务并返回如果没找到则返回null
public Task getChilTaskByGid(String gid) {
for (Task task : mChildren) {
if (task.getGid().equals(gid))
@ -328,15 +374,16 @@ public class TaskList extends Node {
}
return null;
}
// 获取整个子任务列表的方法直接返回存储子任务的ArrayList对象外部可通过该方法获取任务列表包含的所有子任务进行进一步操作
public ArrayList<Task> getChildTaskList() {
return this.mChildren;
}
// 设置任务列表索引的方法,用于更新任务列表的索引值(可能用于改变其在相关结构中的顺序、位置等属性)
public void setIndex(int index) {
this.mIndex = index;
}
// 获取任务列表索引的方法,用于获取当前任务列表的索引值
public int getIndex() {
return this.mIndex;
}

@ -15,18 +15,25 @@
*/
package net.micode.notes.gtask.exception;
// ActionFailureException类继承自RuntimeException意味着它是一个运行时异常不需要在方法签名中显式声明抛出该异常与受检异常相对
// 此类用于表示在执行某个操作时发生了失败的情况,方便在代码中针对操作失败进行统一的异常处理和错误信息传递
public class ActionFailureException extends RuntimeException {
// 用于序列化版本控制的唯一标识符在Java的序列化机制中使用保证不同版本的类在序列化和反序列化时的兼容性
private static final long serialVersionUID = 4425249765923293627L;
// 无参构造函数调用父类RuntimeException的无参构造函数用于创建一个默认的ActionFailureException实例
// 通常在不需要传递具体错误信息时使用
public ActionFailureException() {
super();
}
// 带有字符串参数的构造函数接收一个表示错误信息的字符串参数调用父类RuntimeException的对应构造函数
// 用于创建一个带有特定错误信息的ActionFailureException实例方便在出现操作失败时传递相应的错误提示内容
public ActionFailureException(String paramString) {
super(paramString);
}
// 带有字符串参数和Throwable参数的构造函数接收一个表示错误信息的字符串参数以及一个Throwable对象通常是导致当前异常的底层异常
// 调用父类RuntimeException的对应构造函数用于创建一个既能传递自定义错误信息又能关联底层引发异常原因的ActionFailureException实例
// 方便在复杂的异常链场景中进行准确的异常追踪和错误分析
public ActionFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}

@ -15,18 +15,29 @@
*/
package net.micode.notes.gtask.exception;
// NetworkFailureException类继承自Exception属于受检异常Checked Exception这意味着在方法中如果可能抛出该异常
// 必须在方法签名中使用throws关键字显式声明调用该方法的代码也需要进行相应的异常处理try-catch块或者继续向上抛出
// 此类专门用于表示在网络相关操作出现故障、失败时抛出的异常情况,方便对网络故障进行统一的异常处理和错误信息传递。
public class NetworkFailureException extends Exception {
// 用于序列化版本控制的唯一标识符在Java的序列化机制中发挥作用。当类实现了Serializable接口Exception类间接实现了该接口
// 这个标识符可以确保不同版本的类在进行序列化和反序列化操作时能够正确匹配,避免因类结构变化等原因导致的兼容性问题。
private static final long serialVersionUID = 2107610287180234136L;
// 无参构造函数调用父类Exception的无参构造函数用于创建一个默认的NetworkFailureException实例
// 一般在不需要传递具体错误信息,仅表示网络出现故障这种通用情况时使用。
public NetworkFailureException() {
super();
}
// 带有字符串参数的构造函数接收一个表示错误信息的字符串参数调用父类Exception的对应构造函数
// 这样就可以创建一个带有明确错误提示内容的NetworkFailureException实例便于在网络操作失败时
// 根据具体的失败原因传递相应有意义的错误信息给调用者或者进行日志记录等操作。
public NetworkFailureException(String paramString) {
super(paramString);
}
// 带有字符串参数和Throwable参数的构造函数接收一个表示错误信息的字符串参数以及一个Throwable对象通常是导致当前网络故障异常的底层异常
// 比如IOException等更具体的网络相关底层异常调用父类Exception的对应构造函数
// 此构造函数用于创建一个既能传递自定义的网络故障相关错误信息又能关联底层引发异常原因的NetworkFailureException实例
// 在复杂的网络异常处理场景中,方便进行准确的异常追踪、错误分析以及更完善的异常处理逻辑编写。
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}

@ -27,24 +27,33 @@ import android.os.AsyncTask;
import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
// GTaskASyncTask类继承自AsyncTask用于在后台线程执行一些耗时的操作通常与远程任务同步相关并能在操作过程中更新进度、在操作完成后处理结果
// 同时通过接口回调的方式通知外部操作已完成,还涉及到与通知系统的交互来展示同步状态相关的通知信息
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
// 定义一个静态的整型常量作为同步相关通知的唯一标识符ID用于在通知系统中区分不同的通知此处固定赋值为5234235
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
// 定义一个内部接口OnCompleteListener用于定义当异步任务完成时需要执行的回调方法外部类实现该接口来处理任务完成后的逻辑
public interface OnCompleteListener {
void onComplete();
}
// 存储上下文对象用于获取系统服务、资源等方便在类中进行各种与Android系统相关的操作如创建通知、启动Activity等
private Context mContext;
// 用于管理通知的显示、取消等操作,通过获取系统的通知服务来实例化,负责向用户展示同步进度、结果等相关通知信息
private NotificationManager mNotifiManager;
// 用于管理远程任务相关的操作可能涉及与服务器交互、任务同步等具体逻辑具体功能由GTaskManager类实现通过单例模式获取实例
private GTaskManager mTaskManager;
// 存储实现了OnCompleteListener接口的对象引用用于在异步任务完成时回调相应的方法通知外部任务已结束
private OnCompleteListener mOnCompleteListener;
// 构造函数接收上下文对象和OnCompleteListener接口实现对象作为参数用于初始化类中的相关成员变量
// 同时获取通知管理器实例并获取GTaskManager的单例实例为后续操作做准备
public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context;
mOnCompleteListener = listener;
@ -52,16 +61,20 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
.getSystemService(Context.NOTIFICATION_SERVICE);
mTaskManager = GTaskManager.getInstance();
}
// 用于取消正在进行的同步操作的方法通过调用GTaskManager中的取消同步方法来实现具体的取消逻辑外部可调用此方法来中断同步过程
public void cancelSync() {
mTaskManager.cancelSync();
}
// 用于发布进度信息的方法接收一个字符串消息参数将其包装成字符串数组后调用AsyncTask的publishProgress方法
// 触发onProgressUpdate方法的执行从而实现向UI线程传递进度信息以便更新界面展示等相关操作
public void publishProgess(String message) {
publishProgress(new String[] {
message
});
}
// 私有方法用于显示通知信息根据传入的通知文本资源IDtickerId和通知具体内容content创建并配置一个Notification对象
// 然后通过通知管理器将通知显示出来设置了通知的图标、默认灯光效果、自动取消等属性并根据不同的文本资源ID设置点击通知后的跳转意图PendingIntent
private void showNotification(int tickerId, String content) {
Notification notification = new Notification(R.drawable.notification, mContext
@ -81,13 +94,17 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
pendingIntent);
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
// 在后台线程执行的方法重写了AsyncTask的抽象方法是异步任务的核心逻辑所在。
// 首先发布一条表示正在登录进行同步的进度消息包含同步账号名称然后调用GTaskManager的sync方法进行实际的同步操作
// 并返回同步结果以整数形式表示不同的状态如成功、网络错误等状态码该结果将传递给onPostExecute方法进行后续处理
@Override
protected Integer doInBackground(Void... unused) {
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
.getSyncAccountName(mContext)));
return mTaskManager.sync(mContext, this);
}
// 在UI线程执行的方法用于接收并处理在后台线程通过publishProgress方法传递过来的进度信息
// 调用showNotification方法展示同步进度通知并且如果当前上下文是GTaskSyncService类型还会发送广播可能用于通知其他组件同步进度情况
@Override
protected void onProgressUpdate(String... progress) {
@ -96,6 +113,10 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
((GTaskSyncService) mContext).sendBroadcast(progress[0]);
}
}
// 在UI线程执行的方法当异步任务执行完毕后无论是正常完成还是出现异常结束根据返回的结果状态码进行不同的处理
// 如果结果是成功状态,展示成功同步的通知信息,并记录最后同步时间;
// 如果是网络错误状态、内部错误状态或同步取消状态,分别展示对应的错误通知信息;
// 最后如果存在实现了OnCompleteListener接口的对象即外部注册了任务完成回调则通过开启一个新线程来执行回调方法通知外部任务已完成
@Override
protected void onPostExecute(Integer result) {

@ -59,37 +59,40 @@ import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
// GTaskClient类用于与Google Tasks等相关服务进行交互实现诸如登录、创建任务、创建任务列表、更新任务、获取任务列表等功能
// 内部管理着与服务器通信的HttpClient对象、登录状态、账户信息等是应用与远程任务服务交互的核心类之一
public class GTaskClient {
// 用于日志记录的标签,取当前类的简单名称
private static final String TAG = GTaskClient.class.getSimpleName();
// Google Tasks服务的基础URL
private static final String GTASK_URL = "https://mail.google.com/tasks/";
// 用于获取任务相关数据的URL可能是获取初始数据等情况
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
// 用于向服务器提交任务相关操作如创建、更新等的URL
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
// 单例模式存储唯一的GTaskClient实例初始化为null
private static GTaskClient mInstance = null;
// 用于与服务器进行HTTP通信的HttpClient对象初始化为null后续根据需要进行初始化和配置
private DefaultHttpClient mHttpClient;
// 当前用于获取数据的URL初始化为GTASK_GET_URL可能会根据登录情况如自定义域名等进行调整
private String mGetUrl;
// 当前用于提交数据的URL初始化为GTASK_POST_URL可能会根据登录情况如自定义域名等进行调整
private String mPostUrl;
// 客户端版本号,初始化为 -1登录成功后会从服务器响应中获取实际版本号
private long mClientVersion;
// 标记用户是否已登录初始化为false
private boolean mLoggedin;
// 记录上次登录的时间戳初始化为0
private long mLastLoginTime;
// 用于为每个操作生成唯一的标识符每次操作后自增初始化为1
private int mActionId;
// 存储当前登录的账户信息初始化为null
private Account mAccount;
// 用于存储待提交的更新操作对应的JSON数组多个更新操作可批量提交初始化为null
private JSONArray mUpdateArray;
// 私有构造函数用于初始化GTaskClient类的各个成员变量采用单例模式限制外部直接实例化该类
private GTaskClient() {
mHttpClient = null;
mGetUrl = GTASK_GET_URL;
@ -101,6 +104,7 @@ public class GTaskClient {
mAccount = null;
mUpdateArray = null;
}
// 静态方法用于获取GTaskClient的单例实例如果实例不存在则创建一个新的实例保证整个应用中只有一个GTaskClient对象在使用
public static synchronized GTaskClient getInstance() {
if (mInstance == null) {
@ -108,22 +112,24 @@ public class GTaskClient {
}
return mInstance;
}
// 用于登录到Google Tasks服务的方法根据一些条件判断是否需要重新登录如登录间隔超过5分钟、账户切换等情况
// 先获取Google账户的认证令牌然后根据账户域名情况尝试不同的登录方式自定义域名或官方URL最后返回登录是否成功的布尔值
public boolean login(Activity activity) {
// 假设Cookie在5分钟后过期超过此时间间隔则需要重新登录
// we suppose that the cookie would expire after 5 minutes
// then we need to re-login
final long interval = 1000 * 60 * 5;
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false;
}
// 在账户切换后需要重新登录
// need to re-login after account switch
if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
mLoggedin = false;
}
if (mLoggedin) {
Log.d(TAG, "already logged in");
return true;
@ -135,7 +141,7 @@ public class GTaskClient {
Log.e(TAG, "login google account failed");
return false;
}
// 如果账户名不是以gmail.com或googlemail.com结尾尝试使用自定义域名登录
// login with custom domain if necessary
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
@ -150,7 +156,7 @@ public class GTaskClient {
mLoggedin = true;
}
}
// 如果使用自定义域名登录失败尝试使用Google官方URL登录
// try to login with google official url
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
@ -163,6 +169,9 @@ public class GTaskClient {
mLoggedin = true;
return true;
}
// 用于获取Google账户的认证令牌的私有方法通过AccountManager获取所有Google类型的账户
// 根据应用设置中指定的同步账户名称查找对应的账户然后向AccountManager请求获取认证令牌
// 如果获取失败则返回null若需要使令牌失效invalidateToken为true时则先失效令牌再重新获取
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
@ -188,7 +197,7 @@ public class GTaskClient {
Log.e(TAG, "unable to get an account with the same name in the settings");
return null;
}
// 获取令牌
// get the token now
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
@ -206,9 +215,12 @@ public class GTaskClient {
return authToken;
}
// 尝试登录到Google Tasks服务的私有方法先调用loginGtask方法进行登录如果登录失败可能是认证令牌过期等原因
// 则使令牌失效后重新获取并再次尝试登录,返回最终登录是否成功的布尔值
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) {
// 可能认证令牌已过期,现在使令牌失效并再次尝试
// maybe the auth token is out of date, now let's invalidate the
// token and try again
authToken = loginGoogleAccount(activity, true);
@ -224,6 +236,9 @@ public class GTaskClient {
}
return true;
}
// 实际执行登录到Google Tasks服务的私有方法配置HttpClient的连接超时、读取超时等参数设置Cookie存储
// 禁用Expect-Continue机制然后向服务器发送带有认证令牌的GET请求进行登录从响应中获取Cookie判断是否登录成功
// 同时从响应内容中解析出客户端版本号若在过程中出现JSONException或其他异常则返回false表示登录失败
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000;
@ -235,14 +250,14 @@ public class GTaskClient {
BasicCookieStore localBasicCookieStore = new BasicCookieStore();
mHttpClient.setCookieStore(localBasicCookieStore);
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// 登录Google Tasks
// login gtask
try {
String loginUrl = mGetUrl + "?auth=" + authToken;
HttpGet httpGet = new HttpGet(loginUrl);
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// 获取Cookie
// get the cookie now
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false;
@ -254,7 +269,7 @@ public class GTaskClient {
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie");
}
// 获取客户端版本号
// get the client version
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
@ -272,6 +287,7 @@ public class GTaskClient {
e.printStackTrace();
return false;
} catch (Exception e) {
// 简单捕获所有异常
// simply catch all exceptions
Log.e(TAG, "httpget gtask_url failed");
return false;
@ -279,18 +295,20 @@ public class GTaskClient {
return true;
}
// 获取下一个操作的唯一标识符(每次调用自增)的私有方法,用于区分不同的操作请求
private int getActionId() {
return mActionId++;
}
// 创建一个用于POST请求的HttpPost对象的私有方法设置请求头的Content-Type和AT字段返回配置好的HttpPost对象
// 方便后续设置请求体并发送POST请求
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
httpPost.setHeader("AT", "1");
return httpPost;
}
// 从HttpEntity中获取响应内容的私有方法根据响应内容的编码方式如gzip、deflate等对输入流进行相应的解压处理
// 然后逐行读取内容并拼接成字符串返回最后关闭输入流若在读取过程中出现IOException则抛出异常
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
if (entity.getContentEncoding() != null) {
@ -322,6 +340,9 @@ public class GTaskClient {
input.close();
}
}
// 向服务器发送POST请求并处理响应的私有方法首先检查是否已登录若未登录则抛出异常然后创建HttpPost对象
// 设置请求体将传入的JSONObject转换为表单数据发送请求并获取响应内容将响应内容转换为JSONObject返回
// 在过程中若出现ClientProtocolException、IOException、JSONException等异常则分别抛出对应的异常表示请求失败
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
@ -335,7 +356,7 @@ public class GTaskClient {
list.add(new BasicNameValuePair("r", js.toString()));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httpPost.setEntity(entity);
// 执行POST请求
// execute the post
HttpResponse response = mHttpClient.execute(httpPost);
String jsString = getResponseContent(response.getEntity());
@ -359,7 +380,9 @@ public class GTaskClient {
throw new ActionFailureException("error occurs when posting request");
}
}
// 创建任务的方法首先调用commitUpdate方法提交之前可能积累的更新操作然后构建包含创建任务操作的JSON数据
// 设置客户端版本号等信息向服务器发送POST请求从响应中获取新创建任务的全局唯一标识符Gid并设置到任务对象中
// 若在处理JSON数据过程中出现异常则抛出相应异常表示创建任务失败
public void createTask(Task task) throws NetworkFailureException {
commitUpdate();
try {
@ -385,7 +408,8 @@ public class GTaskClient {
throw new ActionFailureException("create task: handing jsonobject failed");
}
}
// 创建任务列表的方法与创建任务的逻辑类似先提交已有更新构建包含创建任务列表操作的JSON数据发送POST请求
// 从响应中获取新创建任务列表的Gid并设置到任务列表对象中若处理JSON出现问题则抛出异常表示创建失败
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate();
try {
@ -411,6 +435,8 @@ public class GTaskClient {
throw new ActionFailureException("create tasklist: handing jsonobject failed");
}
}
// 提交更新操作的方法若存在待提交的更新操作数组mUpdateArray不为null则构建包含更新操作列表和客户端版本号的JSON数据
// 发送POST请求将更新提交到服务器之后将更新操作数组置为null表示已提交若处理JSON过程中出现异常则抛出相应异常表示提交失败
public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) {
@ -432,6 +458,8 @@ public class GTaskClient {
}
}
}
// 添加待更新节点任务、任务列表等操作到更新操作数组的方法首先判断节点是否为空若不为空且更新操作数组长度超过10个避免过多更新导致错误
// 则先提交已有的更新操作若更新操作数组为空则创建一个新的JSONArray然后将节点的更新操作对应的JSON数据添加到数组中用于后续批量提交更新
public void addUpdateNode(Node node) throws NetworkFailureException {
if (node != null) {
@ -446,6 +474,8 @@ public class GTaskClient {
mUpdateArray.put(node.getUpdateAction(getActionId()));
}
}
// 移动任务的方法先提交已有更新操作然后构建包含移动任务操作相关信息如操作类型、任务ID、源任务列表、目标任务列表等的JSON数据
// 设置客户端版本号后发送POST请求到服务器若处理JSON数据过程中出现异常则抛出相应异常表示移动任务失败
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
@ -485,6 +515,8 @@ public class GTaskClient {
throw new ActionFailureException("move task: handing jsonobject failed");
}
}
// 删除节点任务、任务列表等的方法先提交已有更新操作将节点标记为已删除后构建包含删除操作的JSON数据设置客户端版本号并发送POST请求到服务器
// 提交成功后将更新操作数组置为null若处理JSON过程中出现异常则抛出相应异常表示删除节点失败
public void deleteNode(Node node) throws NetworkFailureException {
commitUpdate();
@ -508,6 +540,8 @@ public class GTaskClient {
throw new ActionFailureException("delete node: handing jsonobject failed");
}
}
// 获取所有任务列表的方法首先检查是否已登录若未登录则抛出异常然后发送GET请求到服务器获取任务列表相关数据
// 从响应内容中解析出任务列表的JSON数组并返回若在请求过程中出现ClientProtocolException、IOException、JSONException等异常则抛出相应异常表示获取失败
public JSONArray getTaskLists() throws NetworkFailureException {
if (!mLoggedin) {
@ -546,6 +580,8 @@ public class GTaskClient {
throw new ActionFailureException("get task lists: handing jasonobject failed");
}
}
// 获取指定任务列表根据任务列表的Gid的方法先提交已有更新操作然后构建包含获取指定任务列表操作相关信息的JSON数据发送POST请求到服务器
// 从响应中获取任务列表包含的任务的JSON数组并返回若处理JSON过程中出现异常则抛出相应异常表示获取任务列表失败
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate();
@ -574,11 +610,12 @@ public class GTaskClient {
throw new ActionFailureException("get task list: handing jsonobject failed");
}
}
// 获取当前同步账户的方法,直接返回存储的账户对象,外部可通过此方法获取当前登录用于同步的账户信息
public Account getSyncAccount() {
return mAccount;
}
// 重置更新操作数组的方法将更新操作数组置为null用于清空之前积累的待更新操作通常在一些特定场景下如重新开始一轮更新操作等使用
public void resetUpdateArray() {
mUpdateArray = null;
}

@ -46,46 +46,58 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
// GTaskManager类用于管理与远程任务服务如Google Tasks的同步操作以及相关数据的处理包括任务列表、任务、元数据等的同步逻辑
// 通过协调本地数据和远程数据的差异来实现数据的一致性,是整个应用中任务同步功能的核心管理类,采用单例模式确保在应用内只有一个实例进行管理操作。
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;
// 单例模式存储唯一的GTaskManager实例初始化为null
private static GTaskManager mInstance = null;
// 用于获取认证令牌等相关操作的Activity对象可在不同的同步相关操作中使用例如登录时获取账户认证信息等
private Activity mActivity;
// 应用上下文对象用于获取内容解析器ContentResolver等系统服务方便与本地数据库等进行交互操作
private Context mContext;
// 用于与本地内容提供者Content Provider进行交互实现对本地数据如数据库中的笔记、任务等相关数据的查询、更新等操作
private ContentResolver mContentResolver;
// 标记当前是否正在进行同步操作初始化为false在同步开始时设置为true结束时设置为false
private boolean mSyncing;
// 标记当前同步操作是否已被取消初始化为false可通过相应方法设置为true来取消正在进行的同步过程
private boolean mCancelled;
// 以任务列表的全局唯一标识符Gid为键存储任务列表对象的哈希映射用于缓存从远程获取的任务列表信息以及在同步过程中管理本地和远程任务列表的对应关系
private HashMap<String, TaskList> mGTaskListHashMap;
// 以节点任务、任务列表、元数据等都可视为节点通过共同的父类Node进行抽象的Gid为键存储节点对象的哈希映射方便在同步过程中快速查找和操作各个节点
private HashMap<String, Node> mGTaskHashMap;
// 以相关Gid为键存储元数据MetaData对象的哈希映射用于管理和跟踪与任务等相关的元数据信息例如可能包含一些额外的描述、配置等数据
private HashMap<String, MetaData> mMetaHashMap;
// 用于存储特殊的元数据列表可能是具有特定用途、结构的元数据集合初始化为null在同步过程中根据情况进行创建和填充
private TaskList mMetaList;
// 用于存储本地已删除的任务或数据对应的唯一标识符(可能是本地数据库中的主键等),通过哈希集合来避免重复记录,方便后续统一处理删除相关逻辑
private HashSet<Long> mLocalDeleteIdMap;
// 以节点的Gid为键存储对应本地唯一标识符如数据库中的记录ID的哈希映射用于建立远程节点Gid和本地数据ID之间的对应关系便于同步时查找和关联数据
private HashMap<String, Long> mGidToNid;
// 以本地唯一标识符为键存储对应节点Gid的哈希映射与mGidToNid作用类似是反向的映射关系用于在不同的同步操作场景下根据需要进行查找和转换
private HashMap<Long, String> mNidToGid;
// 私有构造函数用于初始化GTaskManager类的各个成员变量采用单例模式限制外部直接实例化该类在这里初始化了各种用于存储数据和管理同步状态的集合、映射等变量。
private GTaskManager() {
mSyncing = false;
@ -98,6 +110,7 @@ public class GTaskManager {
mGidToNid = new HashMap<String, Long>();
mNidToGid = new HashMap<Long, String>();
}
// 静态方法用于获取GTaskManager的单例实例如果实例不存在则创建一个新的实例保证整个应用中只有一个GTaskManager对象在管理任务同步等相关操作
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
@ -105,11 +118,15 @@ public class GTaskManager {
}
return mInstance;
}
// 用于设置关联的Activity上下文对象的方法主要是为了后续在需要获取认证令牌等与Activity相关的操作时能够获取到相应的上下文环境
// 该方法是同步的,以保证在多线程环境下设置上下文对象的操作的原子性和一致性。
public synchronized void setActivityContext(Activity activity) {
// used for getting authtoken
mActivity = activity;
}
// 执行同步操作的核心方法,负责协调本地数据和远程数据的同步过程,首先检查是否正在同步,如果正在同步则直接返回相应状态码,
// 然后初始化一系列用于同步的数据结构进行登录操作通过GTaskClient获取远程任务列表执行具体的内容同步逻辑包括处理本地删除、新增、更新等不同情况的同步
// 最后根据同步过程中的各种情况如是否取消、是否出现异常等返回对应的同步状态码并且在finally块中清理相关的数据结构重置同步状态。
public int sync(Context context, GTaskASyncTask asyncTask) {
if (mSyncing) {
@ -130,18 +147,18 @@ public class GTaskManager {
try {
GTaskClient client = GTaskClient.getInstance();
client.resetUpdateArray();
// 登录Google任务服务
// login google task
if (!mCancelled) {
if (!client.login(mActivity)) {
throw new NetworkFailureException("login google task failed");
}
}
// 获取Google任务列表
// get the task list from google
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList();
// 执行内容同步工作
// do content sync work
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent();
@ -167,14 +184,16 @@ public class GTaskManager {
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
// 初始化远程任务列表相关数据的私有方法首先检查同步是否已取消如果取消则直接返回然后通过GTaskClient获取远程任务列表的JSON数据
// 接着分别处理元数据列表如果不存在则创建以及普通任务列表解析JSON数据创建对应的任务列表和任务对象并建立相关的映射关系如将任务列表、任务等存入对应的哈希映射中
// 若在处理JSON数据过程中出现异常则抛出相应异常表示初始化任务列表失败。
private void initGTaskList() throws NetworkFailureException {
if (mCancelled)
return;
GTaskClient client = GTaskClient.getInstance();
try {
JSONArray jsTaskLists = client.getTaskLists();
// 首先初始化元数据列表
// init meta list first
mMetaList = null;
for (int i = 0; i < jsTaskLists.length(); i++) {
@ -186,7 +205,7 @@ public class GTaskManager {
.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
mMetaList = new TaskList();
mMetaList.setContentByRemoteJSON(object);
// 加载元数据
// load meta data
JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) {
@ -202,7 +221,7 @@ public class GTaskManager {
}
}
}
// 如果元数据列表不存在则创建它
// create meta list if not existed
if (mMetaList == null) {
mMetaList = new TaskList();
@ -210,7 +229,7 @@ public class GTaskManager {
+ GTaskStringUtils.FOLDER_META);
GTaskClient.getInstance().createTaskList(mMetaList);
}
// 初始化任务列表
// init task list
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
@ -224,7 +243,7 @@ public class GTaskManager {
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++) {
@ -246,7 +265,9 @@ public class GTaskManager {
throw new ActionFailureException("initGTaskList: handing JSONObject failed");
}
}
// 执行具体内容同步的私有方法,负责协调本地和远程数据在内容层面的同步操作,包括处理本地已删除的笔记、文件夹同步、数据库中现有笔记的同步等多种情况,
// 在不同的同步场景下调用相应的辅助方法如doContentSync来执行具体的添加、删除、更新等操作并且在合适的时机提交更新到远程通过GTaskClient以及清理本地删除记录等操作
// 整个过程中会不断检查同步是否已被取消,若已取消则及时终止操作。
private void syncContent() throws NetworkFailureException {
int syncType;
Cursor c = null;
@ -258,7 +279,7 @@ public class GTaskManager {
if (mCancelled) {
return;
}
// 处理本地已删除的笔记
// for local deleted note
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
@ -285,10 +306,10 @@ public class GTaskManager {
c = null;
}
}
// 同步文件夹
// sync folder first
syncFolder();
// 处理数据库中已存在的笔记
// for note existing in database
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
@ -306,9 +327,11 @@ public class GTaskManager {
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;
}
@ -325,7 +348,7 @@ public class GTaskManager {
c = null;
}
}
// 处理剩余的项目(可能是本地新增但还未处理完全的等情况)
// go through remaining items
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) {
@ -333,16 +356,16 @@ public class GTaskManager {
node = entry.getValue();
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
// mCancelled可以被其他线程设置所以我们需要逐个检查
// mCancelled can be set by another thread, so we neet to check one by
// one
// one // 清除本地删除表(如果同步未取消)
// clear local delete table
if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("failed to batch-delete local deleted notes");
}
}
// 刷新本地同步ID如果同步未取消
// refresh local sync id
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate();
@ -350,7 +373,9 @@ public class GTaskManager {
}
}
// 专门用于文件夹同步的私有方法,处理各种不同类型的文件夹(如根文件夹、通话记录文件夹、本地已存在的文件夹以及远程新增的文件夹等)的同步逻辑,
// 在不同情况下判断文件夹在本地和远程的存在情况调用相应的辅助方法如doContentSync来执行添加、更新等操作并且在合适的时机提交更新到远程通过GTaskClient
// 整个过程中会不断检查同步是否已被取消,若已取消则及时终止操作。
private void syncFolder() throws NetworkFailureException {
Cursor c = null;
String gid;
@ -360,7 +385,7 @@ public class GTaskManager {
if (mCancelled) {
return;
}
// 处理根文件夹
// for root folder
try {
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
@ -373,6 +398,7 @@ public class GTaskManager {
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))
@ -389,7 +415,7 @@ public class GTaskManager {
c = null;
}
}
// 处理通话记录文件夹
// for call-note folder
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
@ -404,6 +430,7 @@ public class GTaskManager {
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(
@ -423,7 +450,7 @@ public class GTaskManager {
c = null;
}
}
// 处理本地已存在的文件夹
// for local existing folders
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
@ -441,9 +468,11 @@ public class GTaskManager {
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;
}
@ -459,7 +488,7 @@ public class GTaskManager {
c = null;
}
}
// 处理远程新增的文件夹
// for remote add folders
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
@ -475,6 +504,8 @@ public class GTaskManager {
if (!mCancelled)
GTaskClient.getInstance().commitUpdate();
}
// 根据不同的同步操作类型执行具体同步动作的私有方法通过一个switch语句根据传入的同步类型syncType来决定调用相应的添加、删除、更新等具体操作方法
// 整个过程中会不断检查同步是否已被取消,若已取消则及时终止操作,若传入的同步类型不合法则抛出异常表示未知的同步操作类型。
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
@ -510,6 +541,7 @@ public class GTaskManager {
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);
@ -521,6 +553,9 @@ public class GTaskManager {
throw new ActionFailureException("unkown sync action type");
}
}
// 将本地节点添加到本地数据库以及处理相关同步逻辑的私有方法,首先判断同步是否已取消,如果取消则直接返回,
// 然后根据节点类型是任务列表还是普通任务等进行不同的处理创建对应的本地数据记录通过SqlNote设置相关属性如父节点ID、全局唯一标识符等
// 将数据提交到本地数据库更新本地和远程标识符的映射关系并且调用updateRemoteMeta方法来处理与元数据相关的更新操作若在过程中出现找不到父节点ID等问题则抛出异常表示无法添加本地节点。
private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) {
@ -549,6 +584,7 @@ public class GTaskManager {
if (note.has(NoteColumns.ID)) {
long id = note.getLong(NoteColumns.ID);
if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
// 该ID不可用必须创建一个新的
// the id is not available, have to create a new one
note.remove(NoteColumns.ID);
}
@ -562,6 +598,7 @@ public class GTaskManager {
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
// 数据ID不可用必须创建一个新的
// the data id is not available, have to create
// a new one
data.remove(DataColumns.ID);
@ -583,18 +620,21 @@ public class GTaskManager {
}
sqlNote.setParentId(parentId.longValue());
}
// 创建本地节点
// create the local node
sqlNote.setGtaskId(node.getGid());
sqlNote.commit(false);
// 更新Gid-Nid映射
// update gid-nid mapping
mGidToNid.put(node.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), node.getGid());
// 更新元数据
// update meta
updateRemoteMeta(node.getGid(), sqlNote);
}
// 更新本地节点数据以及相关同步逻辑的私有方法,首先判断同步是否已取消,如果取消则直接返回,
// 然后根据节点类型是任务还是其他类型等获取对应的本地数据记录通过SqlNote更新其内容、父节点ID等属性将更新后的数据提交到本地数据库
// 并调用updateRemoteMeta方法来处理与元数据相关的更新操作若在过程中出现找不到父节点ID等问题则抛出异常表示无法更新本地节点。
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
@ -602,6 +642,7 @@ public class GTaskManager {
}
SqlNote sqlNote;
// 更新本地笔记
// update the note locally
sqlNote = new SqlNote(mContext, c);
sqlNote.setContent(node.getLocalJSONFromContent());
@ -614,10 +655,14 @@ public class GTaskManager {
}
sqlNote.setParentId(parentId.longValue());
sqlNote.commit(true);
// 更新元数据信息
// update meta info
updateRemoteMeta(node.getGid(), sqlNote);
}
// 将远程节点添加到本地数据库以及处理相关同步逻辑的私有方法,首先判断同步是否已取消,如果取消则直接返回,
// 然后根据节点类型是任务还是任务列表等进行不同的处理创建对应的本地数据记录通过SqlNote
// 对于任务找到其对应的父任务列表添加到任务列表中然后向远程服务创建任务通过GTaskClient对于任务列表判断是否已存在不存在则创建
// 接着更新本地数据记录的相关属性(如全局唯一标识符等),提交到本地数据库,更新本地和远程标识符的映射关系,若在过程中出现找不到父任务列表等问题则抛出异常表示无法添加远程节点。
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
@ -626,7 +671,7 @@ public class GTaskManager {
SqlNote sqlNote = new SqlNote(mContext, c);
Node n;
// 远程更新
// update remotely
if (sqlNote.isNoteType()) {
Task task = new Task();
@ -641,12 +686,12 @@ public class GTaskManager {
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)
@ -670,7 +715,7 @@ public class GTaskManager {
break;
}
}
// 没有匹配的则添加
// no match we can add now
if (tasklist == null) {
tasklist = new TaskList();
@ -680,17 +725,21 @@ public class GTaskManager {
}
n = (Node) tasklist;
}
// 更新本地笔记
// update local note
sqlNote.setGtaskId(n.getGid());
sqlNote.commit(false);
sqlNote.resetLocalModified();
sqlNote.commit(true);
// gid-id映射
// gid-id mapping
mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid());
}
// 更新远程节点数据以及相关同步逻辑的私有方法,首先判断同步是否已取消,如果取消则直接返回,
// 然后获取对应的本地数据记录通过SqlNote将本地数据内容设置到远程节点向远程服务添加更新节点的请求通过GTaskClient
// 接着调用updateRemoteMeta方法处理元数据更新对于任务类型的节点还会判断是否需要移动任务根据父任务列表的变化若需要则执行移动任务操作通过GTaskClient
// 最后清除本地数据的修改标记并提交到本地数据库,若在过程中出现找不到父任务列表等问题则抛出异常表示无法更新远程任务。
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
@ -698,14 +747,14 @@ public class GTaskManager {
}
SqlNote sqlNote = new SqlNote(mContext, c);
// 远程更新
// update remotely
node.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(node);
// 更新元数据
// update meta
updateRemoteMeta(node.getGid(), sqlNote);
// 如果必要的话移动任务
// move task if necessary
if (sqlNote.isNoteType()) {
Task task = (Task) node;
@ -724,11 +773,14 @@ public class GTaskManager {
GTaskClient.getInstance().moveTask(task, preParentList, curParentList);
}
}
// 清除本地修改标记
// clear local modified flag
sqlNote.resetLocalModified();
sqlNote.commit(true);
}
// 更新远程元数据的私有方法首先判断传入的本地数据记录sqlNote是否为笔记类型且不为空
// 如果是则先从元数据映射中查找对应Gid的元数据对象若存在则更新其元数据内容并向远程服务添加更新节点请求通过GTaskClient
// 若不存在则创建新的元数据对象添加到元数据列表中然后向远程服务创建元数据任务通过GTaskClient以此来保证元数据在本地和远程的同步更新。
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
if (sqlNote != null && sqlNote.isNoteType()) {
@ -745,12 +797,15 @@ public class GTaskManager {
}
}
}
// 刷新本地同步ID的私有方法用于在同步完成后根据最新的远程任务列表数据来更新本地数据的同步ID首先判断同步是否已取消如果取消则直接返回
// 然后清空之前缓存的相关任务数据结构如任务哈希映射、任务列表哈希映射、元数据哈希映射等重新初始化任务列表数据通过initGTaskList方法
// 接着查询本地数据库中的相关数据对于在远程任务列表中能找到对应节点的数据更新其同步ID若在过程中出现找不到对应节点等问题则抛出异常表示同步后本地部分数据缺失对应的Gid。
private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) {
return;
}
// 获取最新的gtask列表
// get the latest gtask list
mGTaskHashMap.clear();
mGTaskListHashMap.clear();
@ -789,11 +844,13 @@ public class GTaskManager {
}
}
}
// 获取当前同步账户名称的方法通过调用GTaskClient的实例获取当前同步账户对象然后返回其账户名称外部可通过此方法获取当前正在用于同步的账户信息。
public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name;
}
// 用于取消正在进行的同步操作的方法将同步取消标记mCancelled设置为true在同步过程中的各个关键步骤都会检查这个标记
// 一旦发现被设置为true则会及时终止相应的同步操作避免不必要的资源消耗和数据不一致问题。
public void cancelSync() {
mCancelled = true;
}

@ -22,25 +22,36 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
// GTaskSyncService类继承自Service是一个用于处理任务同步相关操作的后台服务类它可以接收不同的意图Intent来启动或取消任务同步
// 并且在同步过程中能够发送广播来通知同步状态、进度等信息,同时对外提供了一些静态方法方便在不同的地方控制同步操作以及获取同步相关的状态信息。
public class GTaskSyncService extends Service {
// 用于在Intent中传递同步操作类型的字符串常量名称外部组件如Activity等可以通过在Intent中设置该名称对应的额外数据来指定要执行的同步相关操作。
public final static String ACTION_STRING_NAME = "sync_action_type";
// 表示启动同步操作的常量值在通过Intent传递同步操作类型时使用该值表示要开始进行任务同步。
public final static int ACTION_START_SYNC = 0;
// 表示取消同步操作的常量值,用于指示服务取消正在进行的任务同步过程。
public final static int ACTION_CANCEL_SYNC = 1;
// 表示无效的同步操作类型的常量值,当接收到无法识别的操作类型时可能用到。
public final static int ACTION_INVALID = 2;
// 用于发送广播的意图Intent的动作Action名称通过这个名称可以在应用内识别来自该服务发送的广播其他组件可以注册接收该广播来获取同步服务相关的信息。
public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service";
// 用于在广播中传递是否正在同步这个状态信息的键Key名称接收广播的组件可以通过该键从广播附带的数据中获取当前是否正在进行同步的状态。
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing";
// 用于在广播中传递同步进度消息的键Key名称服务可以在同步过程中更新并发送相应的进度消息其他组件接收广播后能展示给用户同步的进展情况。
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg";
// 静态变量用于存储当前正在执行的同步任务GTaskASyncTask类型实例初始化为null在启动同步时创建并赋值取消或完成同步后设置为null用于跟踪同步任务的执行情况。
private static GTaskASyncTask mSyncTask = null;
// 静态变量,用于存储同步进度相关的消息字符串,初始为空字符串,服务在同步过程中可以更新该字符串内容,并通过广播发送出去,方便外部组件展示同步进度给用户。
private static String mSyncProgress = "";
// 私有方法用于启动同步任务的逻辑首先判断当前是否已经存在正在执行的同步任务mSyncTask是否为null
// 如果不存在则创建一个新的GTaskASyncTask实例并传入当前服务this以及一个完成监听器OnCompleteListener
// 在监听器中当同步任务完成时会将mSyncTask设置为null发送一个空消息的广播并停止该服务自身然后发送广播通知同步开始最后执行同步任务。
private void startSync() {
if (mSyncTask == null) {
@ -55,17 +66,23 @@ public class GTaskSyncService extends Service {
mSyncTask.execute();
}
}
// 私有方法用于取消正在进行的同步任务如果当前存在正在执行的同步任务mSyncTask不为null则调用其cancelSync方法来取消同步操作。
private void cancelSync() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
// 重写Service的onCreate方法在服务创建时被调用这里将mSyncTask设置为null确保每次服务启动时都处于初始状态准备执行新的同步任务。
@Override
public void onCreate() {
mSyncTask = null;
}
// 重写Service的onStartCommand方法当通过startService方法启动服务时会调用此方法用于处理传入的意图Intent并根据其中携带的同步操作类型执行相应操作
// 首先从意图中获取额外数据的Bundle对象然后判断Bundle是否存在且包含指定的同步操作类型键ACTION_STRING_NAME
// 如果满足条件则根据操作类型的值进行switch判断若是ACTION_START_SYNC则调用startSync方法启动同步若是ACTION_CANCEL_SYNC则调用cancelSync方法取消同步
// 最后返回START_STICKY表示服务在被系统意外终止后应该尝试重新启动以保证同步功能的持续可用性若不满足条件则调用父类的onStartCommand方法进行默认处理。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
@ -85,18 +102,22 @@ public class GTaskSyncService extends Service {
}
return super.onStartCommand(intent, flags, startId);
}
// 重写Service的onLowMemory方法当系统内存不足时会调用此方法在这里如果存在正在执行的同步任务mSyncTask不为null则调用其cancelSync方法取消同步操作
// 以释放内存资源,避免因内存不足导致应用出现异常或性能问题。
@Override
public void onLowMemory() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
// 重写Service的onBind方法用于处理服务的绑定操作这里返回null表示该服务不支持绑定操作即它是通过startService方式启动来执行后台任务的而不是通过绑定来与其他组件交互。
public IBinder onBind(Intent intent) {
return null;
}
// 用于发送广播的方法更新同步进度消息mSyncProgress创建一个新的意图Intent设置其动作Action为预定义的广播名称GTASK_SERVICE_BROADCAST_NAME
// 并将是否正在同步mSyncTask是否为null以及同步进度消息mSyncProgress作为额外数据添加到意图中最后通过sendBroadcast方法发送广播
// 以便其他组件能够接收到这些同步相关的状态和进度信息。
public void sendBroadcast(String msg) {
mSyncProgress = msg;
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
@ -104,6 +125,8 @@ public class GTaskSyncService extends Service {
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg);
sendBroadcast(intent);
}
// 静态方法用于方便地从Activity中启动同步操作首先通过GTaskManager的单例实例设置关联的Activity上下文对象用于后续可能的与账户认证等相关操作
// 然后创建一个意图Intent指定要启动的服务为GTaskSyncService并在意图中设置同步操作类型为ACTION_START_SYNC最后通过Activity的startService方法启动服务来开始同步。
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity);
@ -111,16 +134,20 @@ public class GTaskSyncService extends Service {
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC);
activity.startService(intent);
}
// 静态方法用于从任意上下文Context中取消同步操作创建一个意图Intent指定要操作的服务为GTaskSyncService并在意图中设置同步操作类型为ACTION_CANCEL_SYNC
// 最后通过上下文的startService方法启动服务来触发取消同步的逻辑这样在应用的不同地方都可以方便地取消正在进行的同步任务。
public static void cancelSync(Context context) {
Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
context.startService(intent);
}
// 静态方法用于外部组件判断当前是否正在进行同步操作通过检查静态变量mSyncTask是否为null来返回相应的布尔值表示当前是否有同步任务正在执行。
public static boolean isSyncing() {
return mSyncTask != null;
}
// 静态方法用于外部组件获取当前的同步进度消息字符串直接返回存储同步进度消息的静态变量mSyncProgress以便展示给用户同步的进展情况。
public static String getProgressString() {
return mSyncProgress;

@ -33,11 +33,24 @@ import net.micode.notes.data.Notes.TextNote;
import java.util.ArrayList;
// Note类代表笔记的数据模型用于管理笔记的各种属性数据如文本数据、通话记录数据等以及与本地数据库通过Content Provider的交互操作
// 包括创建新笔记、更新笔记、判断笔记是否有本地修改等功能内部还嵌套了一个NoteData类用于更细致地管理笔记相关的数据内容。
public class Note {
// 用于存储笔记中发生变化的属性值以键值对形式通过ContentValues来方便后续与内容提供者进行更新操作记录笔记在本地修改后产生的数据差异。
private ContentValues mNoteDiffValues;
// NoteData类型的实例用于管理笔记中的具体数据内容如文本数据、通话数据等将不同类型的数据操作封装在这个内部类中使代码结构更清晰。
private NoteData mNoteData;
// 用于日志记录的标签,方便在日志中识别该类相关的输出信息,取固定的字符串"Note"。
private static final String TAG = "Note";
// 静态同步方法用于创建一个新的笔记并返回其在数据库中的唯一标识符ID接收应用上下文Context和所属文件夹的ID作为参数
// 在方法内部首先创建一个ContentValues对象设置笔记的创建时间、修改时间、类型、本地修改标记以及父文件夹ID等初始属性值
// 然后通过内容解析器ContentResolver将这些数据插入到本地数据库对应的笔记内容URINotes.CONTENT_NOTE_URI
// 接着尝试从插入后返回的Uri中解析出笔记的ID如果解析出现异常或者ID值为非法值如 -1则进行相应的错误处理并返回0或抛出异常正常情况下返回新创建笔记的ID。
public static synchronized long getNewNoteId(Context context, long folderId) {
/**
* Create a new note id for adding a new note to databases
*/
@ -64,42 +77,55 @@ public class Note {
}
return noteId;
}
// Note类的默认构造函数用于初始化Note对象创建一个新的ContentValues对象用于存储笔记的差异数据同时创建一个NoteData对象来管理具体的笔记数据内容。
public Note() {
mNoteDiffValues = new ContentValues();
mNoteData = new NoteData();
}
// 用于设置笔记的通用属性值的方法接收属性的键key和值value作为参数将键值对添加到mNoteDiffValues中
// 同时更新笔记的本地修改标记LOCAL_MODIFIED为已修改状态并设置修改日期MODIFIED_DATE为当前时间以便后续能准确判断笔记是否有修改以及进行相应的更新操作。
public void setNoteValue(String key, String value) {
mNoteDiffValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
// 用于设置笔记文本数据的方法调用内部NoteData对象的setTextData方法将文本数据的键值对传递给它进行处理内部会根据情况更新相关的修改标记和日期等信息。
public void setTextData(String key, String value) {
mNoteData.setTextData(key, value);
}
// 用于设置笔记文本数据的唯一标识符ID的方法直接调用内部NoteData对象的setTextDataId方法将传入的ID值传递给它进行设置同时会进行参数合法性检查ID需大于0
public void setTextDataId(long id) {
mNoteData.setTextDataId(id);
}
// 用于获取笔记文本数据的唯一标识符ID的方法直接返回内部NoteData对象中存储的文本数据IDmTextDataId
public long getTextDataId() {
return mNoteData.mTextDataId;
}
// 用于设置笔记通话数据的唯一标识符ID的方法调用内部NoteData对象的setCallDataId方法将传入的ID值传递给它进行设置同时会进行参数合法性检查ID需大于0
public void setCallDataId(long id) {
mNoteData.setCallDataId(id);
}
// 用于设置笔记通话数据的方法调用内部NoteData对象的setCallData方法将通话数据的键值对传递给它进行处理内部会根据情况更新相关的修改标记和日期等信息。
public void setCallData(String key, String value) {
mNoteData.setCallData(key, value);
}
// 用于判断笔记是否在本地有修改的方法通过检查mNoteDiffValues中是否有数据即是否有属性被修改以及内部NoteData对象是否有本地修改来综合判断
// 如果两者中任意一个有数据变化则表示笔记在本地有修改返回true否则返回false。
public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
// 用于将本地修改后的笔记数据同步到本地数据库的方法接收应用上下文Context和笔记的IDnoteId作为参数首先进行参数合法性检查noteId需大于0
// 如果笔记没有本地修改则直接返回true表示无需同步操作。若有本地修改则先尝试通过内容解析器ContentResolver根据mNoteDiffValues中的数据更新笔记的基本属性信息
// 如果更新失败返回0表示影响的行数为0即更新未成功则记录错误日志但不立即返回继续往下执行接着清空mNoteDiffValues已同步的数据。
// 然后检查内部NoteData对象是否有本地修改如果有且将其数据推送到内容解析器通过pushIntoContentResolver方法失败则返回false表示同步失败
// 否则返回true表示整个笔记数据包括基本属性和具体数据内容同步成功。
public boolean syncNote(Context context, long noteId) {
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
@ -129,17 +155,23 @@ public class Note {
return true;
}
// 内部私有类NoteData用于更细致地管理笔记中的具体数据内容包括文本数据和通话数据相关的属性、操作以及与内容提供者的交互逻辑将这些与数据内容相关的操作封装在内部类中
// 使Note类整体结构更清晰职责更明确避免不同类型数据操作的代码相互混杂。
private class NoteData {
private long mTextDataId;
// 用于存储笔记文本数据的唯一标识符ID初始化为0在插入或更新文本数据时会根据情况进行赋值或更新。
// 用于存储笔记文本数据的具体属性值以键值对形式通过ContentValues方便后续与内容提供者进行插入或更新操作记录文本数据的相关内容。
private ContentValues mTextDataValues;
// 用于存储笔记通话数据的唯一标识符ID初始化为0在插入或更新通话数据时会根据情况进行赋值或更新。
private long mCallDataId;
// 用于存储笔记通话数据的具体属性值以键值对形式通过ContentValues方便后续与内容提供者进行插入或更新操作记录通话数据的相关内容。
private ContentValues mCallDataValues;
// 用于日志记录的标签,方便在日志中识别该内部类相关的输出信息,取固定的字符串"NoteData"。
private static final String TAG = "NoteData";
// NoteData类的默认构造函数用于初始化NoteData对象创建新的ContentValues对象分别用于存储文本数据和通话数据的属性值同时将文本数据和通话数据的ID初始化为0。
public NoteData() {
mTextDataValues = new ContentValues();
@ -147,17 +179,20 @@ public class Note {
mTextDataId = 0;
mCallDataId = 0;
}
// 用于判断NoteData对象中的文本数据或通话数据是否有本地修改的方法通过检查mTextDataValues和mCallDataValues中是否有数据即是否有属性被修改来综合判断
// 如果两者中任意一个有数据变化则表示有本地修改返回true否则返回false。
boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
// 用于设置笔记文本数据的唯一标识符ID的方法进行参数合法性检查ID需大于0如果合法则将传入的ID值赋值给mTextDataId用于后续对文本数据的操作定位等。
void setTextDataId(long id) {
if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
mTextDataId = id;
}
} // 用于设置笔记通话数据的唯一标识符ID的方法进行参数合法性检查ID需大于0如果合法则将传入的ID值赋值给mCallDataId用于后续对通话数据的操作定位等。
void setCallDataId(long id) {
if (id <= 0) {
@ -165,18 +200,29 @@ public class Note {
}
mCallDataId = id;
}
// 用于设置笔记通话数据的方法接收通话数据的键key和值value作为参数将键值对添加到mCallDataValues中
// 同时更新笔记的本地修改标记通过外部的mNoteDiffValues间接更新因为NoteData是内部类可以访问外部类的成员以及修改日期为当前时间以便能准确判断笔记整体是否有修改以及进行相应的更新操作。
void setCallData(String key, String value) {
mCallDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
// 用于设置笔记文本数据的方法接收文本数据的键key和值value作为参数将键值对添加到mTextDataValues中
// 同时更新笔记的本地修改标记通过外部的mNoteDiffValues间接更新以及修改日期为当前时间以便能准确判断笔记整体是否有修改以及进行相应的更新操作。
void setTextData(String key, String value) {
mTextDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
// 用于将NoteData对象中管理的文本数据和通话数据推送到内容解析器即更新或插入到本地数据库的方法接收应用上下文Context和笔记的IDnoteId作为参数
// 首先进行参数合法性检查noteId需大于0然后创建一个ContentProviderOperation列表用于批量操作根据mTextDataValues和mCallDataValues中的数据情况进行不同的操作
// 如果文本数据有值mTextDataValues.size() > 0则设置笔记ID到文本数据属性中如果文本数据的ID为0表示是新数据需要插入则设置文本数据的MIME类型
// 通过内容解析器将文本数据插入到本地数据库对应的内容URINotes.CONTENT_DATA_URI并尝试从插入后返回的Uri中解析出文本数据的ID如果解析失败则记录错误日志并清空文本数据属性值返回null表示操作失败
// 如果文本数据的ID不为0表示是已有数据需要更新则构建一个更新操作并添加到操作列表中。然后对通话数据进行类似的处理根据mCallDataValues的情况进行插入或更新操作
// 最后如果操作列表中有操作需要执行则通过内容解析器批量应用这些操作applyBatch方法根据操作结果返回相应的Uri如果成功则返回笔记对应的Uri失败则返回null
// 如果在批量操作过程中出现RemoteException或OperationApplicationException异常则记录错误日志并返回null表示操作失败。如果操作列表为空则直接返回null表示无需进行数据推送操作。
Uri pushIntoContentResolver(Context context, long noteId) {
/**

@ -30,37 +30,54 @@ 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;
// `WorkingNote` 类代表正在操作编辑、修改等的笔记对象它封装了笔记的各种属性如内容、ID、提醒日期、背景颜色等以及相关的操作方法如保存、加载、设置属性值等
// 同时定义了一个接口用于监听笔记设置相关属性变化的情况,以便在属性改变时能做出相应的业务逻辑处理,是笔记在应用中实际使用场景下的一个综合数据和操作模型。
public class WorkingNote {
// 关联的 `Note` 类对象,用于处理笔记底层的数据操作(如同步到数据库等),借助 `Note` 类已有的功能来实现 `WorkingNote` 的相关持久化等操作。
// Note for the working note
private Note mNote;
// 笔记的唯一标识符ID用于在数据库等场景中唯一标识该笔记初始值根据不同的构造函数可能为0新创建笔记时或从已有数据中获取的具体ID值。
// Note Id
private long mNoteId;
// 笔记的具体内容,比如用户输入的文本等信息,用于展示和编辑笔记的核心文本数据。
// Note content
private String mContent;
// 笔记的模式可能用于表示不同的展示或编辑模式例如是否为清单模式等具体含义由应用业务逻辑决定初始化为0。
// Note mode
private int mMode;
// 笔记的提醒日期用于设置和记录该笔记是否有提醒以及提醒的时间点初始化为0表示无提醒。
private long mAlertDate;
// 笔记的最后修改日期,用于记录笔记最后一次被修改的时间,初始化为当前系统时间(在构造新笔记时)。
private long mModifiedDate;
// 笔记的背景颜色资源ID用于设置和获取笔记展示时的背景颜色初始值会根据具体业务场景设定或默认值赋值。
private int mBgColorId;
// 关联的桌面小部件Widget的ID用于标识该笔记是否与某个桌面小部件相关联以及是哪个小部件初始值根据情况设定若未关联则可能为无效值`AppWidgetManager.INVALID_APPWIDGET_ID`)。
private int mWidgetId;
// 桌面小部件的类型,用于区分不同类型的小部件与笔记的关联情况,初始值根据业务逻辑设定,可能有特定的枚举值(如 `Notes.TYPE_WIDGET_INVALIDE` 表示无效类型等)。
private int mWidgetType;
// 笔记所属文件夹的ID用于表示笔记在文件系统结构中的所属位置便于分类管理笔记初始值根据构造函数传入参数确定。
private long mFolderId;
// 应用的上下文Context对象用于获取系统服务、资源以及与内容提供者Content Provider等进行交互是整个类中很多操作的基础依赖。
private Context mContext;
// 用于日志记录的标签,方便在日志中识别该类相关的输出信息,取固定的字符串"WorkingNote"。
private static final String TAG = "WorkingNote";
// 标记笔记是否已被删除的布尔值,初始化为 `false`,通过相关方法可以设置为 `true` 来表示笔记已被删除状态,在保存等操作中会根据此标记进行相应处理。
private boolean mIsDeleted;
// 定义一个接口类型的变量,用于监听笔记设置相关属性(如背景颜色、提醒等)变化的情况,外部类可以实现该接口并注册进来,以便在属性改变时能收到通知并执行相应逻辑。
private NoteSettingChangedListener mNoteSettingStatusListener;
// 定义一个字符串数组用于查询笔记数据时指定要获取的列包含了数据的ID、内容、MIME类型以及一些其他自定义的数据字段DATA1 - DATA4方便从数据库中获取完整的笔记数据信息。
public static final String[] DATA_PROJECTION = new String[] {
DataColumns.ID,
@ -71,6 +88,7 @@ public class WorkingNote {
DataColumns.DATA3,
DataColumns.DATA4,
};
// 定义一个字符串数组用于查询笔记基本属性时指定要获取的列包含了父文件夹ID、提醒日期、背景颜色ID、小部件ID、小部件类型以及修改日期等字段用于从数据库中获取笔记的关键属性信息。
public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID,
@ -80,26 +98,38 @@ public class WorkingNote {
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE
};
// 定义一个常量,表示在 `DATA_PROJECTION` 数组中数据ID列的索引位置方便在查询结果的游标Cursor中准确获取对应的数据值为0对应数组的第一个元素位置。
private static final int DATA_ID_COLUMN = 0;
// 定义一个常量,表示在 `DATA_PROJECTION` 数组中数据内容列的索引位置用于从游标中获取笔记的具体内容数据值为1。
private static final int DATA_CONTENT_COLUMN = 1;
// 定义一个常量,表示在 `DATA_PROJECTION` 数组中数据MIME类型列的索引位置用于判断数据的类型如文本笔记、通话笔记等值为2。
private static final int DATA_MIME_TYPE_COLUMN = 2;
// 定义一个常量,表示在 `DATA_PROJECTION` 数组中数据模式列可能用于表示笔记的特定展示或编辑模式的索引位置值为3。
private static final int DATA_MODE_COLUMN = 3;
// 定义一个常量,表示在 `NOTE_PROJECTION` 数组中父文件夹ID列的索引位置用于从游标中获取笔记所属文件夹的ID信息值为0。
private static final int NOTE_PARENT_ID_COLUMN = 0;
// 定义一个常量,表示在 `NOTE_PROJECTION` 数组中提醒日期列的索引位置用于获取笔记的提醒时间信息值为1。
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
// 定义一个常量,表示在 `NOTE_PROJECTION` 数组中背景颜色ID列的索引位置用于获取笔记的背景颜色设置信息值为2。
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
// 定义一个常量,表示在 `NOTE_PROJECTION` 数组中小部件ID列的索引位置用于获取与笔记关联的小部件的ID信息值为3。
private static final int NOTE_WIDGET_ID_COLUMN = 3;
// 定义一个常量,表示在 `NOTE_PROJECTION` 数组中小部件类型列的索引位置用于获取小部件的类型信息值为4。
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
private static fina int NOTE_WIDGET_TYPE_COLUMN = 4;
// 定义一个常量,表示在 `NOTE_PROJECTION` 数组中修改日期列的索引位置用于获取笔记最后一次被修改的时间信息值为5。
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
// 私有构造函数用于创建一个新的空的工作笔记对象接收应用上下文Context和所属文件夹的ID作为参数初始化各种属性的初始值
// 如提醒日期设为0修改日期设为当前系统时间创建一个新的 `Note` 对象用于后续数据操作笔记ID初始化为0标记为未删除状态笔记模式设为0小部件类型设为无效类型等。
// New note construct
private WorkingNote(Context context, long folderId) {
@ -113,6 +143,8 @@ public class WorkingNote {
mMode = 0;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
}
// 私有构造函数用于加载一个已存在的笔记对象接收应用上下文Context、笔记的IDnoteId以及所属文件夹的ID作为参数初始化相关属性
// 然后调用 `loadNote` 方法从数据库中加载笔记的基本属性信息,再调用 `loadNoteData` 方法加载笔记的具体数据内容(如文本内容等)。
// Existing note construct
private WorkingNote(Context context, long noteId, long folderId) {
@ -124,7 +156,11 @@ public class WorkingNote {
loadNote();
}
private void loadNote() {
// 私有方法用于从数据库中加载笔记的基本属性信息通过内容解析器ContentResolver根据笔记的ID查询指定列`NOTE_PROJECTION`)的数据,
// 如果查询到游标Cursor不为空且能移动到第一条记录表示有数据则从游标中获取父文件夹ID、背景颜色ID、小部件ID、小部件类型、提醒日期以及修改日期等属性值
// 最后关闭游标若游标为空则表示找不到对应的笔记记录错误日志并抛出异常表示无法找到指定ID的笔记。
private void loadNote()
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
null, null);
@ -145,6 +181,9 @@ public class WorkingNote {
}
loadNoteData();
}
// 私有方法,用于从数据库中加载笔记的具体数据内容,通过内容解析器查询笔记对应的数据(根据 `DATA_PROJECTION` 指定列条件是数据所属的笔记ID与当前笔记ID匹配
// 如果查询到游标不为空且能移动到第一条记录则循环遍历游标因为可能有多条数据比如不同类型的数据根据数据的MIME类型判断是文本笔记还是通话笔记等
// 分别进行相应的处理如设置文本数据ID、通话数据ID或者记录错误日志表示类型错误最后关闭游标若游标为空则表示找不到对应笔记的数据记录错误日志并抛出异常。
private void loadNoteData() {
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
@ -173,6 +212,8 @@ public class WorkingNote {
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
}
}
// 静态工厂方法用于创建一个新的空笔记对象接收应用上下文Context、所属文件夹的ID、小部件ID、小部件类型以及默认背景颜色资源ID作为参数
// 创建一个 `WorkingNote` 对象设置其背景颜色ID、小部件ID和小部件类型属性值最后返回创建好的对象方便在需要创建新笔记时统一调用此方法进行初始化操作。
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
@ -182,6 +223,8 @@ public class WorkingNote {
note.setWidgetType(widgetType);
return note;
}
// 静态工厂方法用于加载一个已存在的笔记对象接收应用上下文Context和笔记的ID作为参数通过调用私有构造函数传入相应参数创建并返回对应的 `WorkingNote` 对象,
// 方便在需要获取并操作已有笔记时统一使用此方法进行加载操作。
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
@ -211,10 +254,16 @@ public class WorkingNote {
return false;
}
}
// 同步方法,用于保存笔记的相关数据到数据库,首先通过 `isWorthSaving` 方法判断笔记是否值得保存(比如是否已删除、内容是否为空且不存在于数据库、是否有本地修改等情况),
// 如果值得保存,若笔记不存在于数据库中(通过 `existInDatabase` 方法判断即笔记ID小于等于0则调用 `Note` 类的 `getNewNoteId` 方法尝试创建一个新的笔记ID
// 如果创建失败则记录错误日志并返回 `false`若创建成功则获取到新的笔记ID。然后调用 `mNote``Note` 类对象)的 `syncNote` 方法将笔记数据同步到数据库中。
// 最后如果笔记关联了有效的小部件小部件ID不为无效值且小部件类型有效并且设置了 `NoteSettingChangedListener` 监听器,则调用监听器的 `onWidgetChanged` 方法通知小部件相关内容可能已改变,
// 整个保存操作成功则返回 `true`,否则返回 `false`。
public boolean existInDatabase() {
return mNoteId > 0;
}
// 用于判断笔记是否已经存在于数据库中的方法通过检查笔记的ID是否大于0来返回相应的布尔值如果大于0则表示已存在否则表示不存在。
private boolean isWorthSaving() {
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
@ -224,10 +273,14 @@ public class WorkingNote {
return true;
}
}
// 私有方法,用于判断笔记是否值得保存,根据多种情况综合判断,比如如果笔记已被标记为删除(`mIsDeleted` 为 `true`
// 或者笔记不存在于数据库中且内容为空(通过 `TextUtils.isEmpty` 判断内容是否为空字符串),或者笔记已存在于数据库中但没有本地修改(通过 `mNote.isLocalModified` 判断),
// 这些情况下返回 `false`,表示不值得保存,否则返回 `true`,表示值得保存。
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l;
}
// 用于设置笔记设置状态变化监听器的方法,接收一个实现了 `NoteSettingChangedListener` 接口的对象作为参数,
// 将其赋值给 `mNoteSettingStatusListener` 变量,这样当笔记的相关设置属性(如背景颜色、提醒等)发生变化时,对应的实现类中的方法就会被调用,实现相应的业务逻辑处理。
public void setAlertDate(long date, boolean set) {
if (date != mAlertDate) {
@ -238,6 +291,9 @@ public class WorkingNote {
mNoteSettingStatusListener.onClockAlertChanged(date, set);
}
}
// 用于标记笔记是否被删除的方法,接收一个布尔值(`mark`)作为参数,将 `mIsDeleted` 属性设置为传入的布尔值,
// 如果笔记关联了有效的小部件小部件ID不为无效值且小部件类型有效并且设置了 `NoteSettingChangedListener` 监听器,则调用监听器的 `onWidgetChanged` 方法通知小部件相关内容可能已改变,
// 以此来应对笔记删除状态变化后可能需要的业务逻辑处理(比如小部件中对应笔记的显示更新等)。
public void markDeleted(boolean mark) {
mIsDeleted = mark;
@ -246,6 +302,9 @@ public class WorkingNote {
mNoteSettingStatusListener.onWidgetChanged();
}
}
// 用于设置笔记背景颜色资源ID的方法接收一个整数`id`)作为参数,
// 首先比较传入的ID和当前的背景颜色ID是否不同如果不同则更新 `mBgColorId` 属性为传入的ID并通过 `mNote` 对象调用 `setNoteValue` 方法将背景颜色ID的值设置到笔记的对应属性中
// 然后如果设置了 `NoteSettingChangedListener` 监听器,则调用其 `onBackgroundColorChanged` 方法通知背景颜色已改变,以便外部进行相应的界面更新等操作。
public void setBgColorId(int id) {
if (id != mBgColorId) {
@ -256,6 +315,9 @@ public class WorkingNote {
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
}
}
// 用于设置笔记的清单模式(可能用于切换笔记的展示或编辑模式为清单形式等)的方法,接收一个整数(`mode`)作为参数,
// 首先比较传入的模式和当前的模式是否不同,如果不同则更新 `mMode` 属性为传入的模式,并通过 `mNote` 对象调用 `setTextData` 方法将模式的值设置到笔记的对应属性中,
// 然后如果设置了 `NoteSettingChangedListener` 监听器,则调用其 `onCheckListModeChanged` 方法通知清单模式已改变,同时传入旧模式和新模式的值,方便外部进行相应的业务逻辑处理(比如界面切换显示等)。
public void setCheckListMode(int mode) {
if (mMode != mode) {
@ -266,13 +328,17 @@ public class WorkingNote {
mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
}
}
// 用于设置笔记关联的桌面小部件类型的方法,接收一个整数(`type`)作为参数,
// 比较传入的类型和当前的小部件类型是否不同,如果不同则更新 `mWidgetType` 属性为传入的类型,并通过 `mNote` 对象调用 `setNoteValue` 方法将小部件类型的值设置到笔记的对应属性中。
public void setWidgetType(int type) {
if (type != mWidgetType) {
mWidgetType = type;
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
}
}
} // 用于设置笔记关联的桌面小部件ID的方法接收一个整数`id`)作为参数,
// 比较传入的ID和当前的小部件ID是否不同如果不同则更新 `mWidgetId` 属性为传入的ID并通过 `mNote` 对象调用 `setNoteValue` 方法将小部件ID的值设置到笔记的对应属性中。
public void setWidgetId(int id) {
if (id != mWidgetId) {
@ -280,6 +346,8 @@ public class WorkingNote {
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
}
}
// 用于设置笔记的工作文本内容(即核心文本数据)的方法,接收一个字符串(`text`)作为参数,
// 比较传入的文本和当前的文本内容是否相同(通过 `TextUtils.equals` 判断),如果不同则更新 `mContent` 属性为传入的文本,并通过 `mNote` 对象调用 `setTextData` 方法将文本内容设置到笔记的对应属性中。
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
@ -287,60 +355,79 @@ public class WorkingNote {
mNote.setTextData(DataColumns.CONTENT, mContent);
}
}
// 用于将笔记转换为通话笔记的方法,接收电话号码(`phoneNumber`)和通话日期(`callDate`)作为参数,
// 通过 `mNote` 对象分别调用 `setCallData` 方法设置通话日期和电话号码到笔记的对应属性中,同时调用 `setNoteValue` 方法将笔记的父文件夹ID设置为通话记录文件夹的ID`Notes.ID_CALL_RECORD_FOLDER`
// 以此来完成笔记类型向通话笔记的转换,可能涉及到对应数据结构和展示等方面的改变,具体由应用的业务逻辑决定。
public void convertToCallNote(String phoneNumber, long callDate) {
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
}
// 用于判断笔记是否设置了时钟提醒的方法,通过检查 `mAlertDate` 是否大于0来返回相应的布尔值如果大于0则表示设置了提醒返回 `true`,否则返回 `false`。
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
// 用于获取笔记内容的方法,直接返回 `mContent` 属性,即笔记的核心文本数据,方便外部获取并展示笔记的具体内容。
public String getContent() {
return mContent;
}
// 用于获取笔记提醒日期的方法,直接返回 `mAlertDate` 属性,即笔记设置的提醒时间点,方便外部查询和使用该提醒日期信息。
public long getAlertDate() {
return mAlertDate;
}
// 用于获取笔记最后修改日期的方法,直接返回 `mModifiedDate` 属性,即笔记最后一次被修改的时间记录,可用于展示、排序等业务场景。
public long getModifiedDate() {
return mModifiedDate;
}
// 用于获取笔记背景颜色资源ID对应的实际背景颜色资源的方法通过调用 `NoteBgResources` 类的 `getNoteBgResource` 方法(具体实现可能根据资源映射等逻辑获取实际颜色资源),
// 将 `mBgColorId` 作为参数传入返回获取到的背景颜色资源ID方便外部进行界面绘制等操作时使用正确的背景颜色资源。
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId);
}
// 用于获取笔记背景颜色资源ID的方法直接返回 `mBgColorId` 属性,方便外部获取并记录笔记的背景颜色设置情况。
public int getBgColorId() {
return mBgColorId;
}
// 用于获取笔记标题背景颜色资源ID的方法通过调用 `NoteBgResources` 类的 `getNoteTitleBgResource` 方法具体实现类似获取背景颜色资源的逻辑根据ID获取对应的标题背景颜色资源
// 将 `mBgColorId` 作为参数传入返回获取到的标题背景颜色资源ID方便外部进行界面绘制等操作时使用正确的标题背景颜色资源。
public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
// 用于获取笔记的清单模式的方法,直接返回 `mMode` 属性,即笔记当前设置的清单模式值,方便外部获取并判断笔记处于何种展示或编辑模式。
public int getCheckListMode() {
return mMode;
}
} // 用于获取笔记的唯一标识符ID的方法直接返回 `mNoteId` 属性,方便外部在数据库操作、关联查询等场景中使用该笔记的唯一标识。
public long getNoteId() {
return mNoteId;
}
// 用于获取笔记所属文件夹的ID的方法直接返回 `mFolderId` 属性,方便外部了解笔记在文件系统结构中的位置分类情况,比如用于文件夹相关的查询、统计等操作。
public long getFolderId() {
return mFolderId;
}
// 用于获取笔记关联的桌面小部件ID的方法直接返回 `mWidgetId` 属性,方便外部获取并操作与笔记相关联的桌面小部件(比如更新小部件显示内容等)。
public int getWidgetId() {
return mWidgetId;
}
// 用于获取笔记关联的桌面小部件类型的方法,直接返回 `mWidgetType` 属性,方便外部获取并判断笔记关联的小部件属于何种类型,以进行相应的业务逻辑处理。
public int getWidgetType() {
return mWidgetType;
}
// 定义一个接口,用于监听笔记设置相关属性变化的情况,外部类需要实现该接口并通过 `setOnSettingStatusChangedListener` 方法注册进来,
// 接口中定义了多个方法,分别对应不同属性变化时的回调通知,比如背景颜色改变、时钟提醒改变、小部件相关改变以及清单模式改变等情况,方便在这些属性变化时执行相应的业务逻辑处理。
public interface NoteSettingChangedListener {
/**

@ -35,57 +35,66 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
// BackupUtils类用于处理笔记数据的备份相关功能支持将笔记数据导出为文本格式等操作
public class BackupUtils {
private static final String TAG = "BackupUtils";
// 单例模式相关用于保存唯一的BackupUtils实例
// Singleton stuff
private static BackupUtils sInstance;
// 获取BackupUtils的单例实例若不存在则创建新的实例
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
return sInstance;
}
/**
*
*/
// 当前外部存储SD卡未挂载的状态码
/**
* 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;
// 备份文件不存在的状态码
// The backup file not exist
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// The data is not well formated, may be changed by other programs
// 数据格式不正确,可能被其他程序修改的状态码
public static final int STATE_DATA_DESTROIED = 2;
// Some run-time exception which causes restore or backup fails
// 运行时出现异常导致备份或恢复失败的状态码
public static final int STATE_SYSTEM_ERROR = 3;
// Backup or restore success
// 备份或恢复成功的状态码
public static final int STATE_SUCCESS = 4;
private TextExport mTextExport;
// 私有构造函数初始化时创建TextExport对象用于文本导出相关操作
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
// 判断外部存储通常指SD卡是否可用通过比较外部存储状态与MEDIA_MOUNTED常量来判断
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
// 调用TextExport的exportToText方法进行文本导出并返回导出结果状态码
public int exportToText() {
return mTextExport.exportToText();
}
// 获取导出的文本文件名实际是通过TextExport对象获取其内部保存的文件名
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
// 获取导出的文本文件所在目录通过TextExport对象获取其内部保存的文件目录
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
// TextExport内部类主要负责具体的文本导出逻辑实现
private static class TextExport {
// 查询笔记相关信息的投影数组,定义了要从数据库中获取的笔记字段
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
@ -98,7 +107,7 @@ public class BackupUtils {
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,
@ -116,6 +125,7 @@ public class BackupUtils {
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;
@ -124,22 +134,25 @@ public class BackupUtils {
private Context mContext;
private String mFileName;
private String mFileDirectory;
// 构造函数初始化TEXT_FORMAT数组以及保存传入的上下文对象等信息
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
// 根据传入的索引获取对应的格式化字符串
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
/**
* folderIdPrintStream
*/
/**
* Export the folder identified by folder id to text
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// 通过内容解析器查询属于该文件夹的笔记信息
// Query notes belong to this folder
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
@ -148,24 +161,29 @@ public class BackupUtils {
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
do { // 格式化并打印笔记的最后修改日期
// Print note's last modified date
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 获取当前笔记的ID用于进一步查询该笔记相关的数据信息并导出
// Query data belong to this note
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
// 关闭游标,释放资源
notesCursor.close();
}
}
/**
* IDPrintStream
*/
/**
* Export note identified by id to a print stream
*/
private void exportNoteToText(String noteId, PrintStream ps) {
// 通过内容解析器查询属于该笔记的相关数据信息
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
@ -174,6 +192,7 @@ public class BackupUtils {
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
// 获取电话号码并打印(如果非空)
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number
@ -185,10 +204,12 @@ public class BackupUtils {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// 格式化并打印通话日期
// Print call date
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// 打印通话附件位置(如果非空)
// Print call attachment location
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
@ -203,8 +224,10 @@ public class BackupUtils {
}
} while (dataCursor.moveToNext());
}
// 关闭游标,释放资源
dataCursor.close();
}
// 在每个笔记内容之间写入换行符,用于分隔不同笔记内容
// print a line separator between note
try {
ps.write(new byte[] {
@ -214,11 +237,14 @@ public class BackupUtils {
Log.e(TAG, e.toString());
}
}
/**
*
*/
/**
* Note will be exported as text which is user readable
*/
public int exportToText() {
// 首先判断外部存储是否可用,如果不可用则返回对应状态码并记录日志
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
@ -229,6 +255,7 @@ public class BackupUtils {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// 第一步:查询并导出文件夹(特定条件筛选的文件夹,如通话记录文件夹等)及其内部笔记信息
// First export folder and its notes
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
@ -253,10 +280,10 @@ public class BackupUtils {
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
} // 关闭游标,释放资源
folderCursor.close();
}
// 第二步:查询并导出根文件夹下的笔记信息
// Export notes in root's folder
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
@ -274,14 +301,16 @@ public class BackupUtils {
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
} // 关闭游标,释放资源
noteCursor.close();
}
ps.close();
// 关闭PrintStream完成文件写入操作
return STATE_SUCCESS;
}
/**
* PrintStream
*/
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
*/
@ -308,7 +337,9 @@ public class BackupUtils {
return ps;
}
}
/**
* SDnull
*/
/**
* Generate the text file to store imported data
*/

@ -33,88 +33,118 @@ import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
// DataUtils类提供了一系列与笔记数据操作相关的实用方法例如批量删除笔记、移动笔记到文件夹、查询各种数据状态等
public class DataUtils {
public static final String TAG = "DataUtils";
// 批量删除给定ID集合对应的笔记跳过系统根文件夹Notes.ID_ROOT_FOLDER的删除操作
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
// 如果传入的ID集合为null记录日志并返回true表示无需执行删除操作
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
}// 如果ID集合为空记录日志并返回true表示没有要删除的笔记
if (ids.size() == 0) {
Log.d(TAG, "no id is in the hashset");
return true;
}
// 用于存储一系列内容提供器操作的列表,后续将批量执行这些操作来删除笔记
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
// 不允许删除系统根文件夹若当前ID是系统根文件夹ID则记录错误日志并跳过该ID的删除操作
if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
continue;
}
} // 创建一个用于删除指定笔记的内容提供器操作构建器指定要删除的笔记的URI通过ID构建
ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
// 将构建好的操作添加到操作列表中
operationList.add(builder.build());
}
try {
// 批量应用内容提供器操作列表,执行删除笔记的操作,并获取操作结果数组
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
// 如果结果数组为null或者长度为0或者第一个结果为null表示删除笔记失败记录日志并返回false
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
// 如果成功执行删除操作返回true
return true;
} catch (RemoteException e) {
// 捕获远程异常记录详细的错误日志包含异常的toString和getMessage信息
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
// 捕获操作应用异常记录详细的错误日志包含异常的toString和getMessage信息
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
}
// 将指定ID的笔记从源文件夹移动到目标文件夹通过更新笔记的相关字段来实现移动操作
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
// 创建一个ContentValues对象用于存储要更新的字段和对应的值
ContentValues values = new ContentValues();
// 设置笔记的新父文件夹ID目标文件夹ID
values.put(NoteColumns.PARENT_ID, desFolderId);
// 设置笔记的原始父文件夹ID源文件夹ID
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);
// 设置本地修改标志为1表示该笔记有本地修改操作
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 通过内容解析器更新指定笔记根据ID确定的相关字段信息
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
// 批量将给定ID集合中的笔记移动到指定的文件夹通过构建一系列更新操作并批量执行来实现
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {
// 如果传入的ID集合为null记录日志并返回true表示无需执行移动操作
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
// 用于存储一系列内容提供器操作的列表,后续将批量执行这些操作来移动笔记
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
// 创建一个用于更新指定笔记的内容提供器操作构建器指定要更新的笔记的URI通过ID构建
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
// 设置笔记的新父文件夹ID目标文件夹ID
builder.withValue(NoteColumns.PARENT_ID, folderId);
// 设置本地修改标志为1表示该笔记有本地修改操作
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
// 将构建好的操作添加到操作列表中
operationList.add(builder.build());
}
try {
// 批量应用内容提供器操作列表,执行移动笔记的操作,并获取操作结果数组
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
// 如果结果数组为null或者长度为0或者第一个结果为null表示移动笔记失败记录日志并返回false
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
// 如果成功执行移动操作返回true
return true;
} catch (RemoteException e) {
// 捕获远程异常记录详细的错误日志包含异常的toString和getMessage信息
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
// 捕获操作应用异常记录详细的错误日志包含异常的toString和getMessage信息
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
}
/**
* {@link Notes#TYPE_SYSTEM}
*/
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/
public static int getUserFolderCount(ContentResolver resolver) {
// 执行数据库查询从Notes.CONTENT_NOTE_URI对应的表中查询符合条件的文件夹数量
// 查询条件是文件夹类型为普通文件夹Notes.TYPE_FOLDER且父文件夹ID不是回收站文件夹IDNotes.ID_TRASH_FOLER
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
@ -125,18 +155,23 @@ public class DataUtils {
if(cursor != null) {
if(cursor.moveToFirst()) {
try {
// 尝试从查询结果游标中获取第一列即统计的数量值并赋值给count变量
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
// 若获取数据时出现越界异常,记录错误日志
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
// 无论是否出现异常,都关闭游标,释放资源
cursor.close();
}
}
}
return count;
}
// 检查指定ID和类型的笔记是否在笔记数据库中可见不在回收站文件夹中通过查询数据库判断是否存在符合条件的笔记
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
// 执行数据库查询从Notes.CONTENT_NOTE_URI对应的表中查询指定ID和类型且不在回收站文件夹中的笔记是否存在
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
@ -145,43 +180,53 @@ public class DataUtils {
boolean exist = false;
if (cursor != null) {
// 如果查询结果游标中的记录数量大于0表示存在符合条件的笔记将exist设为true
if (cursor.getCount() > 0) {
exist = true;
}
// 关闭游标,释放资源
cursor.close();
}
return exist;
}
// 检查指定ID的笔记是否存在于笔记数据库中通过简单查询判断是否有对应记录
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
// 执行数据库查询从Notes.CONTENT_NOTE_URI对应的表中查询指定ID的笔记是否存在无其他额外查询条件
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
// 如果查询结果游标中的记录数量大于0表示存在符合条件的笔记将exist设为true
if (cursor.getCount() > 0) {
exist = true;
}
// 关闭游标,释放资源
cursor.close();
}
return exist;
}
// 检查指定ID的数据是否存在于数据数据库中此处指与笔记相关的数据例如可能是附件等数据通过查询判断是否有对应记录
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
// 执行数据库查询从Notes.CONTENT_DATA_URI对应的表中查询指定ID的数据是否存在无其他额外查询条件
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
// 如果查询结果游标中的记录数量大于0表示存在符合条件的数据将exist设为true
if (cursor.getCount() > 0) {
exist = true;
}
// 关闭游标,释放资源
cursor.close();
}
return exist;
}
// 检查指定名称的文件夹是否在可见文件夹中(非系统文件夹且不在回收站文件夹中),通过查询数据库判断是否存在符合条件的文件夹
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
// 执行数据库查询从Notes.CONTENT_NOTE_URI对应的表中查询指定名称、类型为文件夹且不在回收站文件夹中的文件夹是否存在
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
@ -189,15 +234,18 @@ public class DataUtils {
new String[] { name }, null);
boolean exist = false;
if(cursor != null) {
// 如果查询结果游标中的记录数量大于0表示存在符合条件的文件夹将exist设为true
if(cursor.getCount() > 0) {
exist = true;
}
// 关闭游标,释放资源
cursor.close();
}
return exist;
}
// 获取指定文件夹下所有笔记对应的小部件属性集合,通过查询数据库获取相关信息并构建属性集合
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
// 执行数据库查询从Notes.CONTENT_NOTE_URI对应的表中查询指定文件夹下笔记的小部件ID和小部件类型信息
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
NoteColumns.PARENT_ID + "=?",
@ -210,21 +258,27 @@ public class DataUtils {
set = new HashSet<AppWidgetAttribute>();
do {
try {
// 构建一个AppWidgetAttribute对象用于存储小部件的属性信息
AppWidgetAttribute widget = new AppWidgetAttribute();
// 从查询结果游标中获取小部件ID并赋值给widget对象的对应属性
widget.widgetId = c.getInt(0);
// 从查询结果游标中获取小部件类型并赋值给widget对象的对应属性
widget.widgetType = c.getInt(1);
// 将构建好的widget对象添加到属性集合中
set.add(widget);
} catch (IndexOutOfBoundsException e) {
// 若获取数据时出现越界异常,记录错误日志
Log.e(TAG, e.toString());
}
} while (c.moveToNext());
}
} // 关闭游标,释放资源
c.close();
}
return set;
}
} // 根据笔记ID获取对应的电话号码从通话记录相关的笔记数据中获取通过数据库查询获取电话号码信息
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
// 执行数据库查询从Notes.CONTENT_DATA_URI对应的表中查询指定笔记ID且类型为通话记录相关的电话号码信息
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
@ -232,18 +286,20 @@ public class DataUtils {
null);
if (cursor != null && cursor.moveToFirst()) {
try {
try { // 尝试从查询结果游标中获取电话号码字符串并返回
return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
} catch (IndexOutOfBoundsException e) { // 若获取数据时出现越界异常,记录错误日志
Log.e(TAG, "Get call number fails " + e.toString());
} finally {
} finally { // 关闭游标,释放资源
cursor.close();
}
}
return "";
}
// 根据电话号码和通话日期获取对应的笔记ID从通话记录相关数据中查找匹配的笔记通过数据库查询获取笔记ID信息
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
// 执行数据库查询从Notes.CONTENT_DATA_URI对应的表中查询指定电话号码、通话日期且类型为通话记录相关的笔记ID信息
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID },
CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL("
@ -253,18 +309,19 @@ public class DataUtils {
if (cursor != null) {
if (cursor.moveToFirst()) {
try {
try { // 尝试从查询结果游标中获取笔记ID并返回
return cursor.getLong(0);
} catch (IndexOutOfBoundsException e) {
} catch (IndexOutOfBoundsException e) { // 若获取数据时出现越界异常,记录错误日志
Log.e(TAG, "Get call note id fails " + e.toString());
}
}
} // 关闭游标,释放资源
cursor.close();
}
return 0;
}
// 根据笔记ID获取对应的摘要信息通常是笔记的简短描述等内容通过数据库查询获取摘要字符串
public static String getSnippetById(ContentResolver resolver, long noteId) {
// 执行数据库查询从Notes.CONTENT_NOTE_URI对应的表中查询指定笔记ID的摘要信息
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String [] { NoteColumns.SNIPPET },
NoteColumns.ID + "=?",
@ -272,16 +329,18 @@ public class DataUtils {
null);
if (cursor != null) {
// 若查询到结果,从游标中获取摘要字符串
String snippet = "";
if (cursor.moveToFirst()) {
snippet = cursor.getString(0);
}
// 关闭游标,释放资源
cursor.close();
return snippet;
}
} // 如果查询失败游标为null表示没有找到对应笔记抛出异常表示未找到指定ID的笔记
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
// 对传入的摘要字符串进行格式化处理,去除两端空白字符,并截取到换行符之前的内容(如果有换行符)
public static String getFormattedSnippet(String snippet) {
if (snippet != null) {
snippet = snippet.trim();

@ -15,99 +15,154 @@
*/
package net.micode.notes.tool;
// GTaskStringUtils类用于定义一系列与GTask操作、数据结构以及相关文件夹、元数据等有关的字符串常量。
// 这些常量在涉及到GTask相关的JSON数据处理、文件夹标识和元数据管理等功能中会被使用到。
public class GTaskStringUtils {
// 以下是与GTask JSON数据中动作action相关的字段名及对应动作类型值的常量定义
public final static String GTASK_JSON_ACTION_ID = "action_id";
// JSON数据中表示动作action的唯一标识符的字段名在整个操作流程中可用于区分不同的具体操作。
public final static String GTASK_JSON_ACTION_ID = "action_id";
// JSON数据中表示动作列表action_list的字段名通常可能用来存放一组操作相关信息的集合
// 比如包含多个具体操作的详细描述等内容。
public final static String GTASK_JSON_ACTION_LIST = "action_list";
// JSON数据中表示动作类型action_type的字段名通过不同的值来区分具体是哪种操作行为
// 例如创建、获取、移动、更新等操作。
public final static String GTASK_JSON_ACTION_TYPE = "action_type";
// JSON数据中表示创建create类型动作的具体值当"action_type"字段取值为此常量时,
// 意味着对应的操作是创建相关的数据实体等操作。
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";
// JSON数据中表示获取全部get_all类型动作的具体值用于指示要获取所有相关数据的操作
// 比如获取所有任务、所有列表等情况。
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
// JSON数据中表示移动move类型动作的具体值表明对应的操作是将某个数据实体从一个位置移动到另一个位置
// 例如移动任务到不同的列表等情况。
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";
// JSON数据中表示更新update类型动作的具体值代表要对已存在的数据实体进行更新修改的操作
// 像更新任务的名称、属性等操作会用到此标识。
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";
// JSON数据中表示创建者creator的ID字段名可用于确定执行创建操作的具体用户或者相关主体的唯一标识
// 在涉及多用户或者多来源数据创建时,可借此追踪创建源头
public final static String GTASK_JSON_CREATOR_ID = "creator_id";
// JSON数据中表示子实体child_entity的字段名可能用于描述某个主实体下包含的子级相关的数据结构
// 例如一个任务列表下包含的多个子任务等情况可以通过此字段来关联和表示。
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";
// JSON数据中表示客户端版本client_version的字段名主要用于记录使用该GTask相关功能的客户端应用的版本信息
// 在数据同步、兼容性处理等场景中可根据此版本信息来判断是否支持某些操作或进行相应的适配。
public final static String GTASK_JSON_CLIENT_VERSION = "client_version";
// JSON数据中表示是否完成completed的字段名常用于任务相关的数据中用来标记某个任务是否已经完成
// 例如任务完成状态的记录与判断会依赖于此字段。
public final static String GTASK_JSON_COMPLETED = "completed";
// JSON数据中表示当前列表current_list_id的ID字段名可能用于标识当前操作所涉及的列表的唯一标识
// 比如在任务移动操作中,可指明当前所在的列表以及要移动到的目标列表等情况。
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";
// JSON数据中表示默认列表default_list_id的ID字段名用于指定某个默认的列表
// 例如新创建任务时默认归属的列表等情况可以通过此标识来确定。
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
// JSON数据中表示是否已删除deleted的字段名可用于标记某个数据实体是否已经被删除
// 在数据清理、回收站相关功能或者同步时判断数据是否已不存在等场景中会用到。
public final static String GTASK_JSON_DELETED = "deleted";
// JSON数据中表示目标列表dest_list的字段名常用于操作涉及到将数据移动或关联到其他列表的情况
// 比如在移动任务操作中,此字段指明任务要移动到的目标列表的相关信息。
public final static String GTASK_JSON_DEST_LIST = "dest_list";
// JSON数据中表示目标父级dest_parent的字段名可用于表示某个数据实体在移动、关联等操作后的目标父级对象相关信息
// 例如任务移动后所属的新的父任务或者父列表等情况通过此字段来体现。
public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
// JSON数据中表示目标父级类型dest_parent_type的字段名配合"dest_parent"字段使用,
// 用于明确目标父级对象具体是什么类型,比如是任务组还是普通任务列表等类型区分。
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
// JSON数据中表示实体变化量entity_delta的字段名可能用于记录某个数据实体在更新、修改等操作前后的变化差异情况
// 例如任务的某些属性值发生了改变,通过此字段可以详细记录具体的变化内容。
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
// JSON数据中表示实体类型entity_type的字段名用于区分不同的数据实体类型
// 像任务TASK、任务组GROUP等不同类型的数据实体可以通过此字段来标识区分。
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
// JSON数据中表示获取已删除数据get_deleted的字段名可能用于发起获取已被标记为删除的数据的相关操作
// 比如在回收站功能或者数据同步时,查看哪些数据已被删除等情况会用到。
public final static String GTASK_JSON_GET_DELETED = "get_deleted";
// JSON数据中表示数据实体的唯一标识符id的字段名用于唯一确定某个具体的数据实体
// 无论是任务、列表还是其他相关数据结构都可以通过此ID进行查找、关联等操作。
public final static String GTASK_JSON_ID = "id";
// JSON数据中表示索引index的字段名可用于在有序的数据集合如任务列表等中确定某个数据实体的位置顺序
// 例如在排序、定位特定位置的任务等场景中会使用到。
public final static String GTASK_JSON_INDEX = "index";
// JSON数据中表示最后修改时间last_modified的字段名用于记录某个数据实体最后一次被修改的时间戳
// 在数据同步、版本控制以及判断数据是否有更新等场景中,此时间信息非常关键。
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
// JSON数据中表示最新同步点latest_sync_point的字段名用于标记在数据同步过程中的最新有效同步位置或者时间点
// 可帮助确定下次同步从何处开始、哪些数据需要重新同步等情况。
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
// JSON数据中表示列表list_id的ID字段名用于唯一标识不同的列表
// 在涉及到多个列表管理、任务在不同列表间移动等操作时通过此ID来区分不同的列表。
public final static String GTASK_JSON_LIST_ID = "list_id";
// JSON数据中表示列表lists的字段名通常可能用来存放多个列表相关信息的集合
// 例如获取所有列表信息或者返回一组列表数据时,可通过此字段进行表示。
public final static String GTASK_JSON_LISTS = "lists";
// JSON数据中表示名称name的字段名常用于表示数据实体如任务、列表等的名称属性
// 方便展示、查找以及用户识别不同的数据实体。
public final static String GTASK_JSON_NAME = "name";
// JSON数据中表示新的标识符new_id的字段名可能在某些操作如数据复制、克隆或者重新生成ID等情况
// 用于标记新产生的数据实体的唯一标识。
public final static String GTASK_JSON_NEW_ID = "new_id";
// JSON数据中表示笔记notes的字段名可能用于存放与任务、列表等相关的笔记信息
// 比如用户针对某个任务添加的备注、说明等内容可通过此字段关联存储。
public final static String GTASK_JSON_NOTES = "notes";
// JSON数据中表示父级IDparent_id的字段名用于确定某个数据实体所属的父级对象的唯一标识
// 例如任务所属的任务组或者列表的ID等情况通过此字段关联表示。
public final static String GTASK_JSON_PARENT_ID = "parent_id";
// JSON数据中表示前一个兄弟节点IDprior_sibling_id的字段名在有序的数据结构如任务列表按顺序排列
// 可用于定位某个数据实体前一个相邻的兄弟节点的标识,方便进行顺序调整、插入等操作。
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
// JSON数据中表示操作结果results的字段名常用于存放执行某个操作如创建、更新、移动等操作后的返回结果信息
// 比如操作是否成功、返回的数据等情况通过此字段进行传递和展示。
public final static String GTASK_JSON_RESULTS = "results";
// JSON数据中表示源列表source_list的字段名在涉及数据移动、复制等操作时
// 用于指明操作数据的原始来源列表的相关信息与目标列表dest_list相对应。
public final static String GTASK_JSON_SOURCE_LIST = "source_list";
// JSON数据中表示任务tasks的字段名通常用于存放一组任务相关信息的集合
// 比如获取所有任务列表、返回一批任务数据等情况会通过此字段来表示。
public final static String GTASK_JSON_TASKS = "tasks";
// JSON数据中表示类型type的字段名和前面提到的"entity_type"类似,用于区分不同的数据实体类型,
// 不过可能使用场景更通用一些,可用于各种需要区分类型的情况。
public final static String GTASK_JSON_TYPE = "type";
// JSON数据中表示任务组GROUP类型的具体值用于明确某个数据实体是任务组类型
// 在通过"type"或者"entity_type"字段判断类型时,此常量可作为对应的值来标识任务组。
public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
// JSON数据中表示任务TASK类型的具体值用于确定某个数据实体是普通任务类型
// 同样在类型判断场景中,以此常量来标识任务类型的数据实体。
public final static String GTASK_JSON_TYPE_TASK = "TASK";
// JSON数据中表示用户user的字段名可能用于存放与操作相关的用户信息
// 比如操作执行者的用户名、用户ID等相关属性便于记录操作的归属主体等情况。
public final static String GTASK_JSON_USER = "user";
// 以下是与特定文件夹相关的字符串常量定义,可能用于标识不同功能或用途的文件夹
// 表示与MIUI笔记相关的文件夹前缀字符串用于在文件夹命名或者标识中区分出属于MIUI笔记相关的文件夹
// 方便进行针对性的处理或者识别。
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";
// 表示默认文件夹的名称常量,可能用于指代某个默认创建或者默认使用的文件夹,
// 例如新笔记、新任务等默认存放的文件夹可以用此名称来表示。
public final static String FOLDER_DEFAULT = "Default";
// 表示通话记录笔记相关的文件夹名称常量,用于专门存放与通话记录笔记相关内容的文件夹标识,
// 便于对通话记录相关笔记进行分类管理和查找等操作。
public final static String FOLDER_CALL_NOTE = "Call_Note";
// 表示元数据METADATA相关的文件夹名称常量可能用于存放各种数据的元数据信息的文件夹
// 在数据管理、备份恢复以及数据关联等场景中,此文件夹的元数据起着重要作用。
public final static String FOLDER_META = "METADATA";
// 以下是与元数据头部相关的字段名常量定义,用于明确元数据中不同部分的标识
// 表示元数据中GTask的IDmeta_gid的字段名在元数据结构里用于存放与之关联的GTask的唯一标识信息
// 可用于数据关联、同步以及根据GTask查找对应元数据等操作。
public final static String META_HEAD_GTASK_ID = "meta_gid";
// 表示元数据中笔记meta_note相关信息的字段名可能用于存放具体的笔记内容或者笔记相关属性等元数据信息
// 在处理笔记的元数据时,通过此字段来获取对应的笔记相关数据。
public final static String META_HEAD_NOTE = "meta_note";
// 表示元数据中其他数据meta_data相关信息的字段名可用于存放除了前面特定标识之外的其他各种数据的元数据
// 例如一些自定义的扩展数据、辅助数据等的元数据都可以通过此字段来表示和管理。
public final static String META_HEAD_DATA = "meta_data";
// 表示元数据中其他数据meta_data相关信息的字段名可用于存放除了前面特定标识之外的其他各种数据的元数据
// 例如一些自定义的扩展数据、辅助数据等的元数据都可以通过此字段来表示和管理。
public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE";
}

Loading…
Cancel
Save