diff --git a/src/ActionFailureException.java b/src/ActionFailureException.java deleted file mode 100644 index ed5de58..0000000 --- a/src/ActionFailureException.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.exception; -//这行代码声明了这个类所在的包名是net.micode.notes.gtask.exception,表明这个类是用于处理特定软件模块中与任务相关的 -//异常情况。 -public class ActionFailureException extends RuntimeException { -//这行代码定义了一个名为ActionFailureException的公共类,它继承自RuntimeException,意味着它是一种运行时异常,可以 -//在程序运行过程中被抛出而不需要在方法签名中进行声明。 - private static final long serialVersionUID = 4425249765923293627L; -//这是一个用于序列化和反序列化的版本号标识,确保在类的结构发生变化时,序列化和反序列化的兼容性。 - public ActionFailureException() { - super(); - } -//这是一个无参构造方法,它调用了父类RuntimeException的无参构造方法,创建一个没有特定错误消息的异常对象。 - public ActionFailureException(String paramString) { - super(paramString); - } -//这是一个带一个字符串参数的构造方法,它接受一个错误消息,并将其传递给父类RuntimeException的构造方法,创建一个带 -//有特定错误消息的异常对象。 - public ActionFailureException(String paramString, Throwable paramThrowable) { - super(paramString, paramThrowable); - } -} -//这是一个带两个参数的构造方法,第一个参数是错误消息,第二个参数是一个Throwable类型的对象,代表导致这个异常的原 -//因。它将这两个参数传递给父类RuntimeException的构造方法,创建一个带有特定错误消息和原因的异常对象。 \ No newline at end of file diff --git a/src/net/micode/notes/data/Contact.java b/src/net/micode/notes/data/Contact.java new file mode 100644 index 0000000..79c0018 --- /dev/null +++ b/src/net/micode/notes/data/Contact.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// 这是一个多行注释,用来说明这个文件的版权信息和许可协议。它指出这个文件是由 MiCode 开源社区版权所有,并在 Apache License 2.0 下授权。它还提供了获取许可证的链接,并说明了许可证的条款和条件。 + +package net.micode.notes.data; + +// 声明这个类属于哪个包。在这个例子中,它属于`net.micode.notes.data`包。 + +import android.content.Context; +// 导入 Android 的 Context 类,用于获取应用上下文。 +import android.database.Cursor; +// 导入 Cursor 类,用于在数据库查询结果中进行遍历。 +import android.provider.ContactsContract.CommonDataKinds.Phone; +// 导入 Android Contacts Provider 中与电话号码相关的常量类。 +import android.provider.ContactsContract.Data; +// 导入 Android Contacts Provider 的数据访问类。 +import android.telephony.PhoneNumberUtils; +// 导入用于处理电话号码的工具类。 +import android.util.Log; +// 导入 Android 的日志工具类。 + +import java.util.HashMap; +// 导入 Java 的 HashMap 类,用于存储电话号码和联系人名称的映射关系。 + +public class Contact { + // 定义一个静态的 HashMap,用来缓存电话号码和对应的联系人名称。 + private static HashMap sContactCache; + // 定义一个静态常量 TAG,用于日志输出时标记来源。 + private static final String TAG = "Contact"; + + // 定义一个 SQL 查询语句模板,用于查询电话号码。 + // 这个查询语句使用了 Android Contacts Provider 的内容 URI 和一些过滤条件。 + 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 = '+')"; + + // 定义一个静态方法 getContact,它接受一个 Context 对象和一个电话号码作为参数。 + // 这个方法主要用于根据电话号码查询对应的联系人名称。 + public static String getContact(Context context, String phoneNumber) { + // 如果缓存不存在,则初始化一个空的 HashMap 作为缓存。 + if(sContactCache == null) { + sContactCache = new HashMap(); + } + // 如果电话号码已经在缓存中,则直接返回缓存中的联系人名称,避免重复查询。 + if(sContactCache.containsKey(phoneNumber)) { + return sContactCache.get(phoneNumber); + } + // 将查询语句中的"+"替换为电话号码的最小匹配模式。 + String selection = CALLER_ID_SELECTION.replace("+", + PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + // 使用设备的 ContentResolver 执行查询,查询目标是获取联系人的显示名称。 + Cursor cursor = context.getContentResolver().query( + Data.CONTENT_URI, + new String [] { Phone.DISPLAY_NAME }, + selection, + new String[] { phoneNumber }, + null); + // 如果查询结果不为空并且可以移动到第一条记录。 + if (cursor!= null && cursor.moveToFirst()) { + try { + // 获取查询结果中的联系人名称。 + String name = cursor.getString(0); + // 将电话号码和联系人名称存入缓存。 + sContactCache.put(phoneNumber, name); + // 返回联系人名称。 + return name; + } catch (IndexOutOfBoundsException e) { + // 如果发生索引越界异常,记录错误日志并返回 null。 + Log.e(TAG, " Cursor get string error " + e.toString()); + return null; + } finally { + // 无论是否发生异常,都关闭游标。 + cursor.close(); + } + } else { + // 如果查询结果为空,记录日志并返回 null。 + Log.d(TAG, "No contact matched with number:" + phoneNumber); + return null; + } + } +} \ No newline at end of file diff --git a/src/net/micode/notes/data/Notes.java b/src/net/micode/notes/data/Notes.java new file mode 100644 index 0000000..e366776 --- /dev/null +++ b/src/net/micode/notes/data/Notes.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// 版权声明:2010 - 2011 年,MiCode 开源社区(www.micode.net)。 +// 在 Apache License 2.0 协议下授权;你不得在不符合该协议的情况下使用此文件。你可以在以下地址获取该协议的副本:http://www.apache.org/licenses/LICENSE-2.0。 +// 除非适用法律要求或书面同意,否则根据本许可证分发的软件是基于“按原样”基础提供的,不附带任何明示或暗示的保证或条件。请参阅许可证以了解管理权限和限制的特定语言。 + +package net.micode.notes.data; +// 包名为 net.micode.notes.data。 + +import android.net.Uri; +// 导入安卓网络包中的 Uri 类。 + +public class Notes { +// 定义公共类 Notes。 + + public static final String AUTHORITY = "micode_notes"; + // 定义静态常量 AUTHORITY,值为“micode_notes”。 + + public static final String TAG = "Notes"; + // 定义静态常量 TAG,值为“Notes”。 + + public static final int TYPE_NOTE = 0; + // 定义静态常量 TYPE_NOTE,值为 0。 + + public static final int TYPE_FOLDER = 1; + // 定义静态常量 TYPE_FOLDER,值为 1。 + + public static final int TYPE_SYSTEM = 2; + // 定义静态常量 TYPE_SYSTEM,值为 2。 + + /** + * 以下 ID 是系统文件夹的标识符。 + * {@link Notes#ID_ROOT_FOLDER} 是默认文件夹。 + * {@link Notes#ID_TEMPARAY_FOLDER} 用于不属于任何文件夹的笔记。 + * {@link Notes#ID_CALL_RECORD_FOLDER} 用于存储通话记录。 + */ + // 注释解释了后续常量的用途。 + + public static final int ID_ROOT_FOLDER = 0; + // 定义静态常量 ID_ROOT_FOLDER,值为 0。 + + public static final int ID_TEMPARAY_FOLDER = -1; + // 定义静态常量 ID_TEMPARAY_FOLDER,值为 -1。 + + public static final int ID_CALL_RECORD_FOLDER = -2; + // 定义静态常量 ID_CALL_RECORD_FOLDER,值为 -2。 + + public static final int ID_TRASH_FOLER = -3; + // 定义静态常量 ID_TRASH_FOLER,值为 -3。 + + public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; + // 定义静态常量 INTENT_EXTRA_ALERT_DATE,值为“net.micode.notes.alert_date”。 + + public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; + // 定义静态常量 INTENT_EXTRA_BACKGROUND_ID,值为“net.micode.notes.background_color_id”。 + + public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; + // 定义静态常量 INTENT_EXTRA_WIDGET_ID,值为“net.micode.notes.widget_id”。 + + public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; + // 定义静态常量 INTENT_EXTRA_WIDGET_TYPE,值为“net.micode.notes.widget_type”。 + + public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; + // 定义静态常量 INTENT_EXTRA_FOLDER_ID,值为“net.micode.notes.folder_id”。 + + public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; + // 定义静态常量 INTENT_EXTRA_CALL_DATE,值为“net.micode.notes.call_date”。 + + public static final int TYPE_WIDGET_INVALIDE = -1; + // 定义静态常量 TYPE_WIDGET_INVALIDE,值为 -1。 + + public static final int TYPE_WIDGET_2X = 0; + // 定义静态常量 TYPE_WIDGET_2X,值为 0。 + + public static final int TYPE_WIDGET_4X = 1; + // 定义静态常量 TYPE_WIDGET_4X,值为 1。 + + public static class DataConstants { + // 定义静态内部类 DataConstants。 + + public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; + // 定义静态常量 NOTE,值为 TextNote 类中的 CONTENT_ITEM_TYPE。 + + public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; + // 定义静态常量 CALL_NOTE,值为 CallNote 类中的 CONTENT_ITEM_TYPE。 + + } + + /** + * 用于查询所有笔记和文件夹的 Uri。 + */ + + public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); + // 定义静态常量 CONTENT_NOTE_URI,通过解析字符串“content://micode_notes/note”得到 Uri 对象。 + + /** + * 用于查询数据的 Uri。 + */ + public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); + // 定义静态常量 CONTENT_DATA_URI,通过解析字符串“content://micode_notes/data”得到 Uri 对象。 + + public interface NoteColumns { + // 定义公共接口 NoteColumns。 + + /** + * 一行的唯一 ID。类型:整数(长整型)。 + */ + + public static final String ID = "_id"; + // 定义静态常量 ID,值为“_id”。 + + /** + * 笔记或文件夹的父级 ID。类型:整数(长整型)。 + */ + + public static final String PARENT_ID = "parent_id"; + // 定义静态常量 PARENT_ID,值为“parent_id”。 + + /** + * 笔记或文件夹的创建日期。类型:整数(长整型)。 + */ + + public static final String CREATED_DATE = "created_date"; + // 定义静态常量 CREATED_DATE,值为“created_date”。 + + /** + * 最新修改日期。类型:整数(长整型)。 + */ + + public static final String MODIFIED_DATE = "modified_date"; + // 定义静态常量 MODIFIED_DATE,值为“modified_date”。 + + /** + * 提醒日期。类型:整数(长整型)。 + */ + + public static final String ALERTED_DATE = "alert_date"; + // 定义静态常量 ALERTED_DATE,值为“alert_date”。 + + /** + * 文件夹的名称或笔记的文本内容。类型:文本。 + */ + + public static final String SNIPPET = "snippet"; + // 定义静态常量 SNIPPET,值为“snippet”。 + + /** + * 笔记的小部件 ID。类型:整数(长整型)。 + */ + + public static final String WIDGET_ID = "widget_id"; + // 定义静态常量 WIDGET_ID,值为“widget_id”。 + + /** + * 笔记的小部件类型。类型:整数(长整型)。 + */ + + public static final String WIDGET_TYPE = "widget_type"; + // 定义静态常量 WIDGET_TYPE,值为“widget_type”。 + + /** + * 笔记的背景颜色 ID。类型:整数(长整型)。 + */ + + public static final String BG_COLOR_ID = "bg_color_id"; + // 定义静态常量 BG_COLOR_ID,值为“bg_color_id”。 + + /** + * 对于文本笔记,它没有附件;对于多媒体笔记,它至少有一个附件。类型:整数。 + */ + + public static final String HAS_ATTACHMENT = "has_attachment"; + // 定义静态常量 HAS_ATTACHMENT,值为“has_attachment”。 + + /** + * 文件夹中的笔记数量。类型:整数(长整型)。 + */ + + public static final String NOTES_COUNT = "notes_count"; + // 定义静态常量 NOTES_COUNT,值为“notes_count”。 + + /** + * 文件类型:文件夹或笔记。类型:整数。 + */ + + public static final String TYPE = "type"; + // 定义静态常量 TYPE,值为“type”。 + + /** + * 最后一次同步的 ID。类型:整数(长整型)。 + */ + + public static final String SYNC_ID = "sync_id"; + // 定义静态常量 SYNC_ID,值为“sync_id”。 + + /** + * 表示本地是否已修改的标志。类型:整数。 + */ + + public static final String LOCAL_MODIFIED = "local_modified"; + // 定义静态常量 LOCAL_MODIFIED,值为“local_modified”。 + + /** + * 移动到临时文件夹之前的原始父级 ID。类型:整数。 + */ + + public static final String ORIGIN_PARENT_ID = "origin_parent_id"; + // 定义静态常量 ORIGIN_PARENT_ID,值为“origin_parent_id”。 + + /** + * gtask 的 ID。类型:文本。 + */ + + public static final String GTASK_ID = "gtask_id"; + // 定义静态常量 GTASK_ID,值为“gtask_id”。 + + /** + * 版本代码。类型:整数(长整型)。 + */ + + public static final String VERSION = "version"; + // 定义静态常量 VERSION,值为“version”。 + + } + + public interface DataColumns { + // 定义公共接口 DataColumns。 + + /** + * 一行的唯一 ID。类型:整数(长整型)。 + */ + + public static final String ID = "_id"; + // 定义静态常量 ID,值为“_id”。 + + /** + * 这一行所代表的项目的 MIME 类型。类型:文本。 + */ + + public static final String MIME_TYPE = "mime_type"; + // 定义静态常量 MIME_TYPE,值为“mime_type”。 + + /** + * 此数据所属笔记的引用 ID。类型:整数(长整型)。 + */ + + public static final String NOTE_ID = "note_id"; + // 定义静态常量 NOTE_ID,值为“note_id”。 + + /** + * 笔记或文件夹的创建日期。类型:整数(长整型)。 + */ + + public static final String CREATED_DATE = "created_date"; + // 定义静态常量 CREATED_DATE,值为“created_date”。 + + /** + * 最新修改日期。类型:整数(长整型)。 + */ + + public static final String MODIFIED_DATE = "modified_date"; + // 定义静态常量 MODIFIED_DATE,值为“modified_date”。 + + /** + * 数据的内容。类型:文本。 + */ + + public static final String CONTENT = "content"; + // 定义静态常量 CONTENT,值为“content”。 + + /** + * 通用数据列,其含义特定于{@link #MIMETYPE},用于整数数据类型。类型:整数。 + */ + + public static final String DATA1 = "data1"; + // 定义静态常量 DATA1,值为“data1”。 + + /** + * 通用数据列,其含义特定于{@link #MIMETYPE},用于整数数据类型。类型:整数。 + */ + + public static final String DATA2 = "data2"; + // 定义静态常量 DATA2,值为“data2”。 + + /** + * 通用数据列,其含义特定于{@link #MIMETYPE},用于文本数据类型。类型:文本。 + */ + + public static final String DATA3 = "data3"; + // 定义静态常量 DATA3,值为“data3”。 + + /** + * 通用数据列,其含义特定于{@link #MIMETYPE},用于文本数据类型。类型:文本。 + */ + + public static final String DATA4 = "data4"; + // 定义静态常量 DATA4,值为“data4”。 + + /** + * 通用数据列,其含义特定于{@link #MIMETYPE},用于文本数据类型。类型:文本。 + */ + + public static final String DATA5 = "data5"; + // 定义静态常量 DATA5,值为“data5”。 + + } + + public static final class TextNote implements DataColumns { + // 定义静态内部类 TextNote,实现 DataColumns 接口。 + + /** + * 模式,用于指示文本是否处于清单模式。类型:整数,1 表示清单模式,0 表示正常模式。 + */ + + public static final String MODE = DATA1; + // 定义静态常量 MODE,值为 DATA1。 + + public static final int MODE_CHECK_LIST = 1; + // 定义静态常量 MODE_CHECK_LIST,值为 1。 + + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; + // 定义静态常量 CONTENT_TYPE,值为“vnd.android.cursor.dir/text_note”。 + + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; + // 定义静态常量 CONTENT_ITEM_TYPE,值为“vnd.android.cursor.item/text_note”。 + + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); + // 定义静态常量 CONTENT_URI,通过解析字符串“content://micode_notes/text_note”得到 Uri 对象。 + + } + + public static final class CallNote implements DataColumns { + // 定义静态内部类 CallNote,实现 DataColumns 接口。 + + /** + * 此记录的通话日期。类型:整数(长整型)。 + */ + + public static final String CALL_DATE = DATA1; + // 定义静态常量 CALL_DATE,值为 DATA1。 + + /** + * 此记录的电话号码。类型:文本。 + */ + + public static final String PHONE_NUMBER = DATA3; + // 定义静态常量 PHONE_NUMBER,值为 DATA3。 + + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; + // 定义静态常量 CONTENT_TYPE,值为“vnd.android.cursor.dir/call_note”。 + + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; + // 定义静态常量 CONTENT_ITEM_TYPE,值为“vnd.android.cursor.item/call_note”。 + + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); + // 定义静态常量 CONTENT_URI,通过解析字符串“content://micode_notes/call_note”得到 Uri 对象。 + + } +} \ No newline at end of file diff --git a/src/net/micode/notes/data/NotesDatabaseHelper.java b/src/net/micode/notes/data/NotesDatabaseHelper.java new file mode 100644 index 0000000..e520bcd --- /dev/null +++ b/src/net/micode/notes/data/NotesDatabaseHelper.java @@ -0,0 +1,492 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// 版权声明:2010 - 2011 年,MiCode 开源社区(www.micode.net)。 +// 遵循 Apache License 2.0 许可协议,若不遵守该协议则不可使用此文件。可在以下地址获取许可证副本:http://www.apache.org/licenses/LICENSE-2.0。 +// 除非法律要求或书面同意,否则在该许可证下发布的软件基于“现状”发布,不提供任何形式的保证和条件。 +// 许可证具体条款规定了权限和限制。 + +package net.micode.notes.data; + +// 包声明:这个类属于 net.micode.notes.data 包。 + +import android.content.ContentValues; +// 导入用于存储键值对的数据容器类,通常用于向数据库中插入或更新数据。 +import android.content.Context; +// 导入用于获取应用上下文的类。 +import android.database.sqlite.SQLiteDatabase; +// 导入用于操作 SQLite 数据库的类。 +import android.database.sqlite.SQLiteOpenHelper; +// 导入用于创建和管理 SQLite 数据库的帮助类,继承此类可以方便地创建和升级数据库。 +import android.util.Log; +// 导入 Android 的日志工具类,用于在开发过程中输出调试信息和错误信息。 + +import net.micode.notes.data.Notes.DataColumns; +// 导入 Notes 类中的 DataColumns 内部类,可能包含与数据相关的常量或方法。 +import net.micode.notes.data.Notes.DataConstants; +// 导入 Notes 类中的 DataConstants 内部类,可能包含一些常量定义。 +import net.micode.notes.data.Notes.NoteColumns; +// 导入 Notes 类中的 NoteColumns 内部类,可能包含与笔记相关的常量或方法。 + + +public class NotesDatabaseHelper extends SQLiteOpenHelper { +// 定义一个名为 NotesDatabaseHelper 的类,继承自 SQLiteOpenHelper,用于创建和管理笔记数据库。 + + private static final String DB_NAME = "note.db"; +// 定义数据库名称为“note.db”。 + + private static final int DB_VERSION = 4; +// 定义数据库版本号为 4。 + + public interface TABLE { +// 定义一个内部接口 TABLE,用于表示数据库中的表名。 + public static final String NOTE = "note"; +// 定义名为“note”的表名常量。 + + public static final String DATA = "data"; +// 定义名为“data”的表名常量。 + } + + private static final String TAG = "NotesDatabaseHelper"; +// 用于日志输出的标记。 + + private static NotesDatabaseHelper mInstance; +// 单例实例。 + + private static final String CREATE_NOTE_TABLE_SQL = + "CREATE TABLE " + TABLE.NOTE + "(" + + NoteColumns.ID + " INTEGER PRIMARY KEY," + + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + + NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + + ")"; +// 创建“note”表的 SQL 语句。 + + private static final String CREATE_DATA_TABLE_SQL = + "CREATE TABLE " + TABLE.DATA + "(" + + DataColumns.ID + " INTEGER PRIMARY KEY," + + DataColumns.MIME_TYPE + " TEXT NOT NULL," + + DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + + DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + + DataColumns.DATA1 + " INTEGER," + + DataColumns.DATA2 + " INTEGER," + + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + + ")"; +// 创建“data”表的 SQL 语句。 + + private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = + "CREATE INDEX IF NOT EXISTS note_id_index ON " + + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; +// 创建“data”表中“note_id”索引的 SQL 语句。 + + /** + * Increase folder's note count when move note to the folder + */ + private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER increase_folder_count_on_update "+ + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " END"; +// 当将笔记移动到文件夹时增加文件夹的笔记数量的触发器 SQL。 + + /** + * Decrease folder's note count when move note from folder + */ + private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER decrease_folder_count_on_update " + + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + + " END"; +// 当从文件夹中移动笔记时减少文件夹的笔记数量的触发器 SQL。 + + /** + * Increase folder's note count when insert new note to the folder + */ + private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = + "CREATE TRIGGER increase_folder_count_on_insert " + + " AFTER INSERT ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + + " END"; +// 当向文件夹中插入新笔记时增加文件夹的笔记数量的触发器 SQL。 + + /** + * Decrease folder's note count when delete note from the folder + */ + private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = + "CREATE TRIGGER decrease_folder_count_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN " + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + + " AND " + NoteColumns.NOTES_COUNT + ">0;" + + " END"; +// 当从文件夹中删除笔记时减少文件夹的笔记数量的触发器 SQL。 + + /** + * Update note's content when insert data with type {@link DataConstants#NOTE} + */ + private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = + "CREATE TRIGGER update_note_content_on_insert " + + " AFTER INSERT ON " + TABLE.DATA + + " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END"; +// 当插入数据类型为 DataConstants.NOTE 的数据时更新笔记内容的触发器 SQL。 + + /** + * Update note's content when data with {@link DataConstants#NOTE} type has changed + */ + private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER update_note_content_on_update " + + " AFTER UPDATE ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + + " END"; +// 当数据类型为 DataConstants.NOTE 的数据发生变化时更新笔记内容的触发器 SQL。 + + /** + * Update note's content when data with {@link DataConstants#NOTE} type has deleted + */ + private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = + "CREATE TRIGGER update_note_content_on_delete " + + " AFTER delete ON " + TABLE.DATA + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.SNIPPET + "=''" + + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + + " END"; +// 当数据类型为 DataConstants.NOTE 的数据被删除时更新笔记内容为空的触发器 SQL。 + + /** + * Delete datas belong to note which has been deleted + */ + private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = + "CREATE TRIGGER delete_data_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.DATA + + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + + " END"; +// 当笔记被删除时删除属于该笔记的数据的触发器 SQL。 + + /** + * Delete notes belong to folder which has been deleted + */ + private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = + "CREATE TRIGGER folder_delete_notes_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END"; +// 当文件夹被删除时删除属于该文件夹的笔记的触发器 SQL。 + + /** + * Move notes belong to folder which has been moved to trash folder + */ + private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = + "CREATE TRIGGER folder_move_notes_on_trash " + + " AFTER UPDATE ON " + TABLE.NOTE + + " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " BEGIN" + + " UPDATE " + TABLE.NOTE + + " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + + " END"; +// 当文件夹被移动到垃圾桶时,将属于该文件夹的笔记也移动到垃圾桶的触发器 SQL。 + + public NotesDatabaseHelper(Context context) { +// 构造函数,传入上下文对象。 + super(context, DB_NAME, null, DB_VERSION); +// 调用父类构造函数,传入数据库名称、游标工厂和版本号。 + } + + public void createNoteTable(SQLiteDatabase db) { +// 创建“note”表的方法。 + db.execSQL(CREATE_NOTE_TABLE_SQL); +// 执行创建“note”表的 SQL 语句。 + reCreateNoteTableTriggers(db); +// 重新创建“note”表的触发器。 + createSystemFolder(db); +// 创建系统文件夹。 + Log.d(TAG, "note table has been created"); +// 输出日志,表明“note”表已创建。 + } + + private void reCreateNoteTableTriggers(SQLiteDatabase db) { +// 重新创建“note”表触发器的方法。 + db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); +// 删除可能已存在的特定触发器。 + db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); +// 删除可能已存在的特定触发器。 + db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); +// 删除可能已存在的特定触发器。 + db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete"); +// 删除可能已存在的特定触发器。 + db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); +// 删除可能已存在的特定触发器。 + db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); +// 删除可能已存在的特定触发器。 + db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); +// 删除可能已存在的特定触发器。 + + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); +// 执行创建新的触发器的 SQL 语句。 + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); +// 执行创建新的触发器的 SQL 语句。 + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); +// 执行创建新的触发器的 SQL 语句。 + db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); +// 执行创建新的触发器的 SQL 语句。 + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); +// 执行创建新的触发器的 SQL 语句。 + db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); +// 执行创建新的触发器的 SQL 语句。 + db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); +// 执行创建新的触发器的 SQL 语句。 + } + + private void createSystemFolder(SQLiteDatabase db) { +// 创建系统文件夹的方法。 + ContentValues values = new ContentValues(); +// 创建一个 ContentValues 对象,用于存储要插入数据库的值。 + + /** + * call record foler for call notes + */ + values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); +// 将特定值放入 ContentValues 中,用于插入记录文件夹。 + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); +// 将特定值放入 ContentValues 中,用于插入记录文件夹。 + db.insert(TABLE.NOTE, null, values); +// 将数据插入“note”表。 + + /** + * root folder which is default folder + */ + values.clear(); +// 清空 ContentValues。 + values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); +// 将特定值放入 ContentValues 中,用于插入根文件夹。 + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); +// 将特定值放入 ContentValues 中,用于插入根文件夹。 + db.insert(TABLE.NOTE, null, values); +// 将数据插入“note”表。 + + /** + * temporary folder which is used for moving note + */ + values.clear(); +// 清空 ContentValues。 + values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); +// 将特定值放入 ContentValues 中,用于插入临时文件夹。 + values.put values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); +// 将特定值放入 ContentValues 中,用于插入临时文件夹的类型为系统类型。 + db.insert(TABLE.NOTE, null, values); +// 将数据插入“note”表。 + + /** + * create trash folder + */ + values.clear(); +// 清空 ContentValues。 + values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); +// 将特定值放入 ContentValues 中,用于插入垃圾桶文件夹的 ID。 + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); +// 将特定值放入 ContentValues 中,用于插入垃圾桶文件夹的类型为系统类型。 + db.insert(TABLE.NOTE, null, values); +// 将数据插入“note”表。 + } + + public void createDataTable(SQLiteDatabase db) { +// 创建“data”表的方法。 + db.execSQL(CREATE_DATA_TABLE_SQL); +// 执行创建“data”表的 SQL 语句。 + reCreateDataTableTriggers(db); +// 重新创建“data”表的触发器。 + db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); +// 创建“data”表中“note_id”索引。 + Log.d(TAG, "data table has been created"); +// 输出日志,表明“data”表已创建。 + } + + private void reCreateDataTableTriggers(SQLiteDatabase db) { +// 重新创建“data”表触发器的方法。 + db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); +// 删除可能已存在的特定触发器。 + db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); +// 删除可能已存在的特定触发器。 + db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete"); +// 删除可能已存在的特定触发器。 + + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); +// 执行创建新的触发器的 SQL 语句。 + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); +// 执行创建新的触发器的 SQL 语句。 + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); +// 执行创建新的触发器的 SQL 语句。 + } + + static synchronized NotesDatabaseHelper getInstance(Context context) { +// 获取单例实例的静态方法。 + if (mInstance == null) { +// 如果单例实例为空。 + mInstance = new NotesDatabaseHelper(context); +// 创建一个新的 NotesDatabaseHelper 实例并赋值给单例实例。 + } + return mInstance; +// 返回单例实例。 + } + + @Override + public void onCreate(SQLiteDatabase db) { +// 重写 onCreate 方法,在数据库首次创建时被调用。 + createNoteTable(db); +// 创建“note”表。 + createDataTable(db); +// 创建“data”表。 + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { +// 重写 onUpgrade 方法,在数据库版本升级时被调用。 + boolean reCreateTriggers = false; +// 标记是否需要重新创建触发器。 + boolean skipV2 = false; +// 标记是否跳过版本 2 的升级步骤。 + + if (oldVersion == 1) { +// 如果当前数据库版本为 1。 + upgradeToV2(db); +// 执行升级到版本 2 的方法。 + skipV2 = true; // this upgrade including the upgrade from v2 to v3 +// 设置跳过版本 2 的标记为 true,表示这个升级包含了从版本 2 到版本 3 的升级步骤。 + oldVersion++; +// 将旧版本号加 1。 + } + + if (oldVersion == 2 &&!skipV2) { +// 如果当前数据库版本为 2 且没有跳过版本 2 的升级步骤。 + upgradeToV3(db); +// 执行升级到版本 3 的方法。 + reCreateTriggers = true; +// 设置需要重新创建触发器的标记为 true。 + oldVersion++; +// 将旧版本号加 1。 + } + + if (oldVersion == 3) { +// 如果当前数据库版本为 3。 + upgradeToV4(db); +// 执行升级到版本 4 的方法。 + oldVersion++; +// 将旧版本号加 1。 + } + + if (reCreateTriggers) { +// 如果需要重新创建触发器。 + reCreateNoteTableTriggers(db); +// 重新创建“note”表的触发器。 + reCreateDataTableTriggers(db); +// 重新创建“data”表的触发器。 + } + + if (oldVersion!= newVersion) { +// 如果旧版本号与新版本号不相等。 + throw new IllegalStateException("Upgrade notes database to version " + newVersion + + "fails"); +// 抛出异常,表示数据库升级失败。 + } + } + + private void upgradeToV2(SQLiteDatabase db) { +// 升级到版本 2 的方法。 + db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); +// 如果存在“note”表,则删除它。 + db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); +// 如果存在“data”表,则删除它。 + createNoteTable(db); +// 创建“note”表。 + createDataTable(db); +// 创建“data”表。 + } + + private void upgradeToV3(SQLiteDatabase db) { +// 升级到版本 3 的方法。 + // 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 + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + + " TEXT NOT NULL DEFAULT ''"); +// 在“note”表中添加一个用于存储 gtask id 的列。 + // add a trash system folder + ContentValues values = new ContentValues(); +// 创建一个 ContentValues 对象,用于存储要插入数据库的值。 + values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); +// 将垃圾桶文件夹的 ID 放入 ContentValues 中。 + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); +// 将垃圾桶文件夹的类型为系统类型放入 ContentValues 中。 + db.insert(TABLE.NOTE, null, values); +// 将数据插入“note”表。 + } + + private void upgradeToV4(SQLiteDatabase db) { +// 升级到版本 4 的方法。 + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + + " INTEGER NOT NULL DEFAULT 0"); +// 在“note”表中添加一个版本号列。 + } +} \ No newline at end of file diff --git a/src/net/micode/notes/data/NotesProvider.java b/src/net/micode/notes/data/NotesProvider.java new file mode 100644 index 0000000..d587c7f --- /dev/null +++ b/src/net/micode/notes/data/NotesProvider.java @@ -0,0 +1,547 @@ +/* + * Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// 版权声明:2010 - 2011 年,MiCode 开源社区(www.micode.net)。 +// 在 Apache License 2.0 协议下授权;你不得在不符合该协议的情况下使用此文件。你可以在以下地址获取该协议的副本:http://www.apache.org/licenses/LICENSE-2.0。 +// 除非适用法律要求或书面同意,否则根据本许可证分发的软件是基于“按原样”基础提供的,不附带任何明示或暗示的保证或条件。请参阅许可证以了解管理权限和限制的特定语言。 + +package net.micode.notes.data; + +import android.app.SearchManager; +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Intent; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import net.micode.notes.R; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.data.NotesDatabaseHelper.TABLE; + +// 导入相关的包和类。 + +public class NotesProvider extends ContentProvider { +// 定义公共类 NotesProvider,继承自 ContentProvider。 + + private static final UriMatcher mMatcher; + // 定义静态的 UriMatcher 对象 mMatcher。 + + private NotesDatabaseHelper mHelper; + // 定义 NotesDatabaseHelper 对象 mHelper。 + + private static final String TAG = "NotesProvider"; + // 定义静态常量 TAG,值为“NotesProvider”。 + + private static final int URI_NOTE = 1; + // 定义常量 URI_NOTE,值为 1。 + + private static final int URI_NOTE_ITEM = 2; + // 定义常量 URI_NOTE_ITEM,值为 2。 + + private static final int URI_DATA = 3; + // 定义常量 URI_DATA,值为 3。 + + private static final int URI_DATA_ITEM = 4; + // 定义常量 URI_DATA_ITEM,值为 4。 + + private static final int URI_SEARCH = 5; + // 定义常量 URI_SEARCH,值为 5。 + + private static final int URI_SEARCH_SUGGEST = 6; + // 定义常量 URI_SEARCH_SUGGEST,值为 6。 + + static { + // 静态初始化块。 + + mMatcher = new UriMatcher(UriMatcher.NO_MATCH); + // 创建一个新的 UriMatcher 对象,并设置初始匹配结果为 UriMatcher.NO_MATCH。 + + mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); + // 将 Uri “content://micode_notes/note” 与常量 URI_NOTE 关联。 + + mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); + // 将 Uri “content://micode_notes/note/[ID]” 与常量 URI_NOTE_ITEM 关联。 + + mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); + // 将 Uri “content://micode_notes/data” 与常量 URI_DATA 关联。 + + mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); + // 将 Uri “content://micode_notes/data/[ID]” 与常量 URI_DATA_ITEM 关联。 + + mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); + // 将 Uri “content://micode_notes/search” 与常量 URI_SEARCH 关联。 + + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); + // 将 Uri “content://micode_notes/search_query”(根据 SearchManager.SUGGEST_URI_PATH_QUERY)与常量 URI_SEARCH_SUGGEST 关联。 + + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); + // 将 Uri “content://micode_notes/search_query/[SEARCH_STRING]”(根据 SearchManager.SUGGEST_URI_PATH_QUERY 和通配符)与常量 URI_SEARCH_SUGGEST 关联。 + + } + + /** + * 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. + */ + // 注释说明以下字符串常量的用途。 + + private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," + + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," + + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," + + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; + // 定义常量 NOTES_SEARCH_PROJECTION,用于搜索结果中的投影列。 + + 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; + // 定义常量 NOTES_SNIPPET_SEARCH_QUERY,用于搜索笔记片段的查询语句。 + + @Override + public boolean onCreate() { + // 重写 onCreate 方法。 + + mHelper = NotesDatabaseHelper.getInstance(getContext()); + // 获取 NotesDatabaseHelper 的实例。 + + return true; + // 返回 true,表示创建成功。 + + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + // 重写 query 方法,用于查询数据。 + + Cursor c = null; + // 定义游标对象 c,并初始化为 null。 + + SQLiteDatabase db = mHelper.getReadableDatabase(); + // 获取可读的 SQLiteDatabase 对象。 + + String id = null; + // 定义字符串 id,初始化为 null。 + + switch (mMatcher.match(uri)) { + // 根据 Uri 匹配结果进行分支处理。 + + case URI_NOTE: + c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, + sortOrder); + // 查询所有笔记。 + + break; + case URI_NOTE_ITEM: + id = uri.getPathSegments().get(1); + // 获取笔记的 ID。 + + c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder); + // 根据 ID 查询单个笔记。 + + break; + case URI_DATA: + c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, + sortOrder); + // 查询所有数据。 + + break; + case URI_DATA_ITEM: + id = uri.getPathSegments().get(1); + // 获取数据的 ID。 + + c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder); + // 根据 ID 查询单个数据。 + + break; + case URI_SEARCH: + case URI_SEARCH_SUGGEST: + if (sortOrder!= null || projection!= null) { + // 如果排序顺序或投影不为空,则抛出异常。 + + throw new IllegalArgumentException( + "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); + } + + String searchString = null; + // 定义字符串 searchString,初始化为 null。 + + if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { + // 如果是搜索建议的 Uri。 + + if (uri.getPathSegments().size() > 1) { + // 如果路径段数量大于 1。 + + searchString = uri.getPathSegments().get(1); + // 获取搜索字符串。 + + } + } else { + // 否则是普通搜索的 Uri。 + + searchString = uri.getQueryParameter("pattern"); + // 从查询参数中获取搜索字符串。 + + } + + if (TextUtils.isEmpty(searchString)) { + // 如果搜索字符串为空。 + + return null; + // 返回 null。 + + } + + try { + // 尝试执行查询。 + + searchString = String.format("%%%s%%", searchString); + // 格式化搜索字符串。 + + c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, + new String[]{searchString}); + // 执行查询。 + + } catch (IllegalStateException ex) { + // 捕获异常。 + + Log.e(TAG, "got exception: " + ex.toString()); + // 记录错误日志。 + + } + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + // 如果 Uri 不匹配任何已知的情况,抛出异常。 + + } + if (c!= null) { + // 如果游标不为 null。 + + c.setNotificationUri(getContext().getContentResolver(), uri); + // 设置通知 Uri。 + + } + return c; + // 返回游标。 + + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + // 重写 insert 方法,用于插入数据。 + + SQLiteDatabase db = mHelper.getWritableDatabase(); + // 获取可写的 SQLiteDatabase 对象。 + + long dataId = 0, noteId = 0, insertedId = 0; + // 定义长整型变量 dataId、noteId 和 insertedId,并初始化为 0。 + + switch (mMatcher.match(uri)) { + // 根据 Uri 匹配结果进行分支处理。 + + case URI_NOTE: + insertedId = noteId = db.insert(TABLE.NOTE, null, values); + // 插入笔记。 + + break; + case URI_DATA: + if (values.containsKey(DataColumns.NOTE_ID)) { + // 如果 ContentValues 中包含 NOTE_ID。 + + noteId = values.getAsLong(DataColumns.NOTE_ID); + // 获取笔记 ID。 + + } else { + // 否则。 + + Log.d(TAG, "Wrong data format without note id:" + values.toString()); + // 记录错误日志。 + + } + insertedId = dataId = db.insert(TABLE.DATA, null, values); + // 插入数据。 + + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + // 如果 Uri 不匹配任何已知的情况,抛出异常。 + + } + // Notify the note uri + if (noteId > 0) { + // 如果笔记 ID 大于 0。 + + getContext().getContentResolver().notifyChange( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); + // 通知笔记 Uri 的更改。 + + } + + // Notify the data uri + if (dataId > 0) { + // 如果数据 ID 大于 0。 + + getContext().getContentResolver().notifyChange( + ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); + // 通知数据 Uri 的更改。 + + } + + return ContentUris.withAppendedId(uri, insertedId); + // 返回插入后的 Uri。 + + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + // 重写 delete 方法,用于删除数据。 + + int count = 0; + // 定义整型变量 count,初始化为 0。 + + String id = null; + // 定义字符串 id,初始化为 null。 + + SQLiteDatabase db = mHelper.getWritableDatabase(); + // 获取可写的 SQLiteDatabase 对象。 + + boolean deleteData = false; + // 定义布尔变量 deleteData,初始化为 false。 + + switch (mMatcher.match(uri)) { + // 根据 Uri 匹配结果进行分支处理。 + + case URI_NOTE: + selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; + count = db.delete(TABLE.NOTE, selection, selectionArgs); + // 删除笔记,添加条件 ID 大于 0。 + + break; + case URI_NOTE_ITEM: + id = uri.getPathSegments().get(1); + // 获取笔记的 ID。 + + /** + * ID that smaller than 0 is system folder which is not allowed to + * trash + */ + // 注释说明小于 0 的 ID 是系统文件夹,不允许删除。 + + long noteId = Long.valueOf(id); + // 将 ID 转换为长整型。 + + if (noteId <= 0) { + // 如果笔记 ID 小于等于 0。 + + break; + // 跳出循环。 + + } + count = db.delete(TABLE.NOTE, + NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); + // 删除单个笔记。 + + break; + case URI_DATA: + count = db.delete(TABLE.DATA, selection, selectionArgs); + deleteData = true; + // 删除数据,设置 deleteData 为 true。 + + break; + case URI_DATA_ITEM: + id = uri.getPathSegments().get(1); + // 获取数据的 ID。 + + count = db.delete(TABLE.DATA, + DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); + deleteData = true; + // 删除单个数据,设置 deleteData 为 true。 + + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + // 如果 Uri 不匹配任何已知的情况,抛出异常。 + + } + if (count > 0) { + // 如果删除的数量大于 0。 + + if (deleteData) { + // 如果删除的是数据。 + + getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); + // 通知笔记 Uri 的更改。 + + } + getContext().getContentResolver().notifyChange(uri, null); + // 通知当前 Uri 的更改。 + + } + return count; + // 返回删除的数量。 + + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + // 重写 update 方法,用于更新数据。 + + int count = 0; + // 定义整型变量 count,初始化为 0。 + + String id = null; + // 定义字符串 id,初始化为 null。 + + SQLiteDatabase db = mHelper.getWritableDatabase(); + // 获取可写的 SQLiteDatabase 对象。 + + boolean updateData = false; + // 定义布尔变量 updateData,初始化为 false。 + + switch (mMatcher.match(uri)) { + // 根据 Uri 匹配结果进行分支处理。 + + case URI_NOTE: + increaseNoteVersion(-1, selection, selectionArgs); + // 更新所有笔记的版本号。 + + count = db.update(TABLE.NOTE, values, selection, selectionArgs); + // 更新笔记。 + + break; + case URI_NOTE_ITEM: + id = uri.getPathSegments().get(1); + // 获取笔记的 ID。 + + increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); + // 更新指定笔记的版本号。 + + count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs); + // 更新单个笔记。 + + break; + case URI_DATA: + count = db.update(TABLE.DATA, values, selection, selectionArgs); + updateData = true; + // 更新数据,设置 updateData 为 true。 + + break; + case URI_DATA_ITEM: + id = uri.getPathSegments().get(1); + // 获取数据的 ID。 + + count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs); + updateData = true; + // 更新单个数据,设置 updateData 为 true。 + + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); + // 如果 Uri 不匹配任何已知的情况,抛出 +} + if (count > 0) { +// 如果更新操作影响的记录数大于 0。 + if (updateData) { +// 如果更新了数据记录。 + // 如果更新了数据记录,则通知监听器。 + getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); +// 获取上下文环境,通过内容解析器通知与笔记内容相关的监听器(Notes.CONTENT_NOTE_URI)数据发生了变化,第二个参数为 null 表示没有额外的通知数据。 + } + // 通知监听器指定的 URI 发生了变化。 + getContext().getContentResolver().notifyChange(uri, null); +// 获取上下文环境,通过内容解析器通知本次更新操作对应的 URI(uri)的数据发生了变化,第二个参数为 null 表示没有额外的通知数据。 + } + return count; +// 返回更新操作所影响的记录数。 + + } + +// 解析选择条件的方法。 +private String parseSelection(String selection) { +// 定义一个私有方法,接收一个选择条件字符串参数。 + return (!TextUtils.isEmpty(selection)? " AND (" + selection + ')' : ""); +// 如果选择条件字符串不为空,返回在其前后加上“ AND (”和“)”的字符串;如果为空,返回空字符串。 +} + +// 增加笔记版本号的方法。 +private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { +// 定义一个私有方法,接收笔记的 ID、选择条件字符串和选择条件参数数组。 + StringBuilder sql = new StringBuilder(120); +// 创建一个 StringBuilder 对象,用于构建 SQL 语句,初始容量为 120。 + sql.append("UPDATE "); +// 在 StringBuilder 中添加“UPDATE ”。 + sql.append(TABLE.NOTE); +// 添加表名 TABLE.NOTE。 + sql.append(" SET "); +// 添加“ SET ”。 + sql.append(NoteColumns.VERSION); +// 添加版本号列名 NoteColumns.VERSION。 + sql.append("=" + NoteColumns.VERSION + "+1 "); +// 添加版本号设置的 SQL 片段,将版本号设置为当前版本号加 1。 + + if (id > 0 ||!TextUtils.isEmpty(selection)) { +// 如果传入的 ID 大于 0 或者选择条件字符串不为空。 + sql.append(" WHERE "); +// 在 StringBuilder 中添加“ WHERE ”。 + } + if (id > 0) { +// 如果传入的 ID 大于 0。 + sql.append(NoteColumns.ID + "=" + String.valueOf(id)); +// 添加笔记 ID 的条件到 StringBuilder 中。 + } + if (!TextUtils.isEmpty(selection)) { +// 如果选择条件字符串不为空。 + String selectString = id > 0? parseSelection(selection) : selection; +// 如果 ID 大于 0,调用 parseSelection 方法处理选择条件字符串;否则直接使用选择条件字符串。 + for (String args : selectionArgs) { +// 遍历选择条件参数数组。 + selectString = selectString.replaceFirst("\\?", args); +// 将选择条件字符串中的第一个占位符替换为当前参数值。 + } + sql.append(selectString); +// 将处理后的选择条件字符串添加到 StringBuilder 中。 + } + + mHelper.getWritableDatabase().execSQL(sql.toString()); +// 使用数据库帮助类 mHelper 的可写数据库执行构建好的 SQL 语句。 + +} + +// 内容提供器的 getType 方法,用于返回指定 URI 的 MIME 类型。 +@Override +public String getType(Uri uri) { +// 重写内容提供器的 getType 方法,接收一个 URI 参数。 + // TODO Auto-generated method stub +// 这里是一个待实现的方法注释。 + return null; +// 返回 null,表示当前未实现该方法。 +} +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/data/MetaData.java b/src/net/micode/notes/gtask/data/MetaData.java new file mode 100644 index 0000000..ebead02 --- /dev/null +++ b/src/net/micode/notes/gtask/data/MetaData.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net) + * (版权所有(c)2010 - 2011,MiCode 开源社区(www.micode.net)) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * (根据 Apache License 2.0 版(“许可证”)获得许可;) + * you may not use this file except in compliance with the License. + * (除非符合许可证,否则你不得使用此文件。) + * You may obtain a copy of the License at + * (你可以在以下位置获取许可证副本:) + * + * http://www.apache.org/licenses/LICENSE-2.0 + * (http://www.apache.org/licenses/LICENSE-2.0) + * + * Unless required by applicable law or agreed to in writing, software + * (除非适用法律要求或书面同意,否则软件) + * distributed under the License is distributed on an "AS IS" BASIS, + * (根据许可证分发的软件是基于“原样”分发的,) + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * (不附带任何明示或暗示的保证或条件。) + * See the License for the specific language governing permissions and + * (有关特定语言的权限和限制,请参阅许可证。) + * limitations under the License. + */ + +package net.micode.notes.gtask.data; +// 包 net.micode.notes.gtask.data;(包名为 net.micode.notes.gtask.data;) + +import android.database.Cursor; +// 导入 android.database.Cursor;(导入 Android 的数据库游标类;) +import android.util.Log; +// 导入 android.util.Log;(导入 Android 的日志工具类;) + +import net.micode.notes.tool.GTaskStringUtils; +// 导入 net.micode.notes.tool.GTaskStringUtils;(导入来自 net.micode.notes.tool 的 GTaskStringUtils 类;) + +import org.json.JSONException; +// 导入 org.json.JSONException;(导入 JSON 处理的异常类;) +import org.json.JSONObject; +// 导入 org.json.JSONObject;(导入 JSON 对象类;) + + +public class MetaData extends Task { +// 公共类 MetaData 继承自 Task;(定义一个名为 MetaData 的公共类,它继承自 Task 类;) + private final static String TAG = MetaData.class.getSimpleName(); +// 私有最终静态字符串变量 TAG,赋值为 MetaData 类的简单名称;(定义一个私有最终静态的字符串变量 TAG,值为 MetaData 类的简单名称;) + + private String mRelatedGid = null; +// 私有字符串变量 mRelatedGid,初始值为 null;(定义一个私有字符串变量 mRelatedGid,初始值为 null;) + + public void setMeta(String gid, JSONObject metaInfo) { +// 公共方法 setMeta,接收字符串 gid 和 JSON 对象 metaInfo;(定义一个名为 setMeta 的公共方法,接收一个字符串 gid 和一个 JSONObject 对象 metaInfo;) + try { +// 尝试执行以下代码块;(开始 try 代码块;) + metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); +// 将 gid 放入 metaInfo 的特定键中;(尝试将 gid 放入 metaInfo 对象中,键为 GTaskStringUtils.META_HEAD_GTASK_ID;) + } catch (JSONException e) { +// 如果发生 JSON 异常,进入此代码块;(捕获 JSONException 异常;) + Log.e(TAG, "failed to put related gid"); +// 输出错误日志;(打印错误日志,提示“failed to put related gid”;) + } + setNotes(metaInfo.toString()); +// 设置 notes 属性;(设置当前对象的 notes 属性为 metaInfo 的字符串表示形式;) + setName(GTaskStringUtils.META_NOTE_NAME); +// 设置 name 属性;(设置当前对象的 name 属性为 GTaskStringUtils.META_NOTE_NAME;) + } + + public String getRelatedGid() { +// 公共方法 getRelatedGid;(定义一个名为 getRelatedGid 的公共方法;) + return mRelatedGid; +// 返回 mRelatedGid;(返回 mRelatedGid 的值;) + } + + @Override + public boolean isWorthSaving() { +// 重写方法 isWorthSaving;(重写父类的 isWorthSaving 方法;) + return getNotes()!= null; +// 返回 notes 属性不为 null 的判断结果;(返回当前对象的 notes 属性不为 null 的判断结果;) + } + + @Override + public void setContentByRemoteJSON(JSONObject js) { +// 重写方法 setContentByRemoteJSON;(重写父类的 setContentByRemoteJSON 方法;) + super.setContentByRemoteJSON(js); +// 调用父类的同名方法;(先调用父类的 setContentByRemoteJSON 方法;) + if (getNotes()!= null) { +// 如果 notes 属性不为 null;(如果当前对象的 notes 属性不为 null;) + try { +// 尝试执行以下代码块;(开始 try 代码块;) + JSONObject metaInfo = new JSONObject(getNotes().trim()); +// 创建一个新的 JSON 对象,从 notes 属性的字符串表示形式中获取;(从当前对象的 notes 属性的字符串表示形式中创建一个新的 JSONObject 对象;) + mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); +// 获取相关的 gid;(从新创建的 JSONObject 对象中获取特定键的值,并赋值给 mRelatedGid;) + } catch (JSONException e) { +// 如果发生 JSON 异常,进入此代码块;(捕获 JSONException 异常;) + Log.w(TAG, "failed to get related gid"); +// 输出警告日志;(打印警告日志,提示“failed to get related gid”;) + mRelatedGid = null; +// 将 mRelatedGid 设为 null;(将 mRelatedGid 设为 null;) + } + } + } + + @Override + public void setContentByLocalJSON(JSONObject js) { +// 重写方法 setContentByLocalJSON;(重写父类的 setContentByLocalJSON 方法;) + // this function should not be called +// 此函数不应被调用;(注释说明这个函数不应该被调用;) + throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); +// 抛出非法访问错误;(抛出 IllegalAccessError 异常,提示“MetaData:setContentByLocalJSON should not be called”;) + } + + @Override + public JSONObject getLocalJSONFromContent() { +// 重写方法 getLocalJSONFromContent;(重写父类的 getLocalJSONFromContent 方法;) + throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); +// 抛出非法访问错误;(抛出 IllegalAccessError 异常,提示“MetaData:getLocalJSONFromContent should not be called”;) + } + + @Override + public int getSyncAction(Cursor c) { +// 重写方法 getSyncAction;(重写父类的 getSyncAction 方法;) + throw new IllegalAccessError("MetaData:getSyncAction should not be called"); +// 抛出非法访问错误;(抛出 IllegalAccessError 异常,提示“MetaData:getSyncAction should not be called”;) + } + +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/data/Node.java b/src/net/micode/notes/gtask/data/Node.java new file mode 100644 index 0000000..42b7d09 --- /dev/null +++ b/src/net/micode/notes/gtask/data/Node.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net) + * (版权所有(c)2010 - 2011,MiCode 开源社区(www.micode.net)) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * (根据 Apache License 2.0 版(“许可证”)获得许可;) + * you may not use this file except in compliance with the License. + * (除非符合许可证,否则你不得使用此文件。) + * You may obtain a copy of the License at + * (你可以在以下位置获取许可证副本:) + * + * http://www.apache.org/licenses/LICENSE-2.0 + * (http://www.apache.org/licenses/LICENSE-2.0) + * + * Unless required by applicable law or agreed to in writing, software + * (除非适用法律要求或书面同意,否则软件) + * distributed under the License is distributed on an "AS IS" BASIS, + * (根据许可证分发的软件是基于“原样”分发的,) + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * (不附带任何明示或暗示的保证或条件。) + * See the License for the specific language governing permissions and + * (有关特定语言的权限和限制,请参阅许可证。) + * limitations under the License. + */ + +package net.micode.notes.gtask.data; +// 包 net.micode.notes.gtask.data;(包名为 net.micode.notes.gtask.data;) + +import android.database.Cursor; +// 导入 android.database.Cursor;(导入 Android 的数据库游标类;) + +import org.json.JSONObject; +// 导入 org.json.JSONObject;(导入 JSON 对象类;) + +public abstract class Node { +// 公共抽象类 Node;(定义一个名为 Node 的公共抽象类;) + public static final int SYNC_ACTION_NONE = 0; +// 公共静态最终整型变量 SYNC_ACTION_NONE,赋值为 0;(定义一个公共静态最终的整型变量 SYNC_ACTION_NONE,值为 0;) + + public static final int SYNC_ACTION_ADD_REMOTE = 1; +// 公共静态最终整型变量 SYNC_ACTION_ADD_REMOTE,赋值为 1;(定义一个公共静态最终的整型变量 SYNC_ACTION_ADD_REMOTE,值为 1;) + + public static final int SYNC_ACTION_ADD_LOCAL = 2; +// 公共静态最终整型变量 SYNC_ACTION_ADD_LOCAL,赋值为 2;(定义一个公共静态最终的整型变量 SYNC_ACTION_ADD_LOCAL,值为 2;) + + public static final int SYNC_ACTION_DEL_REMOTE = 3; +// 公共静态最终整型变量 SYNC_ACTION_DEL_REMOTE,赋值为 3;(定义一个公共静态最终的整型变量 SYNC_ACTION_DEL_REMOTE,值为 3;) + + public static final int SYNC_ACTION_DEL_LOCAL = 4; +// 公共静态最终整型变量 SYNC_ACTION_DEL_LOCAL,赋值为 4;(定义一个公共静态最终的整型变量 SYNC_ACTION_DEL_LOCAL,值为 4;) + + public static final int SYNC_ACTION_UPDATE_REMOTE = 5; +// 公共静态最终整型变量 SYNC_ACTION_UPDATE_REMOTE,赋值为 5;(定义一个公共静态最终的整型变量 SYNC_ACTION_UPDATE_REMOTE,值为 5;) + + public static final int SYNC_ACTION_UPDATE_LOCAL = 6; +// 公共静态最终整型变量 SYNC_ACTION_UPDATE_LOCAL,赋值为 6;(定义一个公共静态最终的整型变量 SYNC_ACTION_UPDATE_LOCAL,值为 6;) + + public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; +// 公共静态最终整型变量 SYNC_ACTION_UPDATE_CONFLICT,赋值为 7;(定义一个公共静态最终的整型变量 SYNC_ACTION_UPDATE_CONFLICT,值为 7;) + + public static final int SYNC_ACTION_ERROR = 8; +// 公共静态最终整型变量 SYNC_ACTION_ERROR,赋值为 8;(定义一个公共静态最终的整型变量 SYNC_ACTION_ERROR,值为 8;) + + private String mGid; +// 私有字符串变量 mGid;(定义一个私有字符串变量 mGid;) + + private String mName; +// 私有字符串变量 mName;(定义一个私有字符串变量 mName;) + + private long mLastModified; +// 私有长整型变量 mLastModified;(定义一个私有长整型变量 mLastModified;) + + private boolean mDeleted; +// 私有布尔型变量 mDeleted;(定义一个私有布尔型变量 mDeleted;) + + public Node() { +// 公共构造函数;(定义一个公共的构造函数;) + mGid = null; +// 初始化 mGid 为 null;(将 mGid 初始化为 null;) + mName = ""; +// 初始化 mName 为空字符串;(将 mName 初始化为空字符串;) + mLastModified = 0; +// 初始化 mLastModified 为 0;(将 mLastModified 初始化为 0;) + mDeleted = false; +// 初始化 mDeleted 为 false;(将 mDeleted 初始化为 false;) + } + + public abstract JSONObject getCreateAction(int actionId); +// 公共抽象方法 getCreateAction,接收整型参数 actionId;(定义一个公共抽象方法 getCreateAction,接收一个整型参数 actionId,返回一个 JSONObject 对象;) + + public abstract JSONObject getUpdateAction(int actionId); +// 公共抽象方法 getUpdateAction,接收整型参数 actionId;(定义一个公共抽象方法 getUpdateAction,接收一个整型参数 actionId,返回一个 JSONObject 对象;) + + public abstract void setContentByRemoteJSON(JSONObject js); +// 公共抽象方法 setContentByRemoteJSON,接收 JSONObject 参数 js;(定义一个公共抽象方法 setContentByRemoteJSON,接收一个 JSONObject 参数 js;) + + public abstract void setContentByLocalJSON(JSONObject js); +// 公共抽象方法 setContentByLocalJSON,接收 JSONObject 参数 js;(定义一个公共抽象方法 setContentByLocalJSON,接收一个 JSONObject 参数 js;) + + public abstract JSONObject getLocalJSONFromContent(); +// 公共抽象方法 getLocalJSONFromContent;(定义一个公共抽象方法 getLocalJSONFromContent,返回一个 JSONObject 对象;) + + public abstract int getSyncAction(Cursor c); +// 公共抽象方法 getSyncAction,接收 Cursor 参数 c;(定义一个公共抽象方法 getSyncAction,接收一个 Cursor 参数 c,返回一个整型值;) + + public void setGid(String gid) { +// 公共方法 setGid,接收字符串参数 gid;(定义一个名为 setGid 的公共方法,接收一个字符串参数 gid;) + this.mGid = gid; +// 设置 mGid 的值;(将 this.mGid 赋值为 gid;) + } + + public void setName(String name) { +// 公共方法 setName,接收字符串参数 name;(定义一个名为 setName 的公共方法,接收一个字符串参数 name;) + this.mName = name; +// 设置 mName 的值;(将 this.mName 赋值为 name;) + } + + public void setLastModified(long lastModified) { +// 公共方法 setLastModified,接收长整型参数 lastModified;(定义一个名为 setLastModified 的公共方法,接收一个长整型参数 lastModified;) + this.mLastModified = lastModified; +// 设置 mLastModified 的值;(将 this.mLastModified 赋值为 lastModified;) + } + + public void setDeleted(boolean deleted) { +// 公共方法 setDeleted,接收布尔型参数 deleted;(定义一个名为 setDeleted 的公共方法,接收一个布尔型参数 deleted;) + this.mDeleted = deleted; +// 设置 mDeleted 的值;(将 this.mDeleted 赋值为 deleted;) + } + + public String getGid() { +// 公共方法 getGid;(定义一个名为 getGid 的公共方法;) + return this.mGid; +// 返回 mGid 的值;(返回 this.mGid 的值;) + } + + public String getName() { +// 公共方法 getName;(定义一个名为 getName 的公共方法;) + return this.mName; +// 返回 mName 的值;(返回 this.mName 的值;) + } + + public long getLastModified() { +// 公共方法 getLastModified;(定义一个名为 getLastModified 的公共方法;) + return this.mLastModified; +// 返回 mLastModified 的值;(返回 this.mLastModified 的值;) + } + + public boolean getDeleted() { +// 公共方法 getDeleted;(定义一个名为 getDeleted 的公共方法;) + return this.mDeleted; +// 返回 mDeleted 的值;(返回 this.mDeleted 的值;) + } + +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/data/SqlData.java b/src/net/micode/notes/gtask/data/SqlData.java new file mode 100644 index 0000000..48e6514 --- /dev/null +++ b/src/net/micode/notes/gtask/data/SqlData.java @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net) + * (版权所有(c)2010 - 2011,MiCode 开源社区(www.micode.net)。) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * (根据 Apache License 2.0 版(“许可证”)获得许可;) + * you may not use this file except in compliance with the License. + * (除非符合许可证,否则你不得使用此文件。) + * You may obtain a copy of the License at + * (你可以在以下位置获取许可证副本:) + * + * http://www.apache.org/licenses/LICENSE-2.0 + * (http://www.apache.org/licenses/LICENSE-2.0) + * + * Unless required by applicable law or agreed to in writing, software + * (除非适用法律要求或书面同意,否则软件) + * distributed under the License is distributed on an "AS IS" BASIS, + * (根据许可证分发的软件是基于“原样”分发的,) + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * (不附带任何明示或暗示的保证或条件。) + * See the License for the specific language governing permissions and + * (有关特定语言的权限和限制,请参阅许可证。) + * limitations under the License. + */ + +package net.micode.notes.gtask.data; +// 包 net.micode.notes.gtask.data;(这个包的名字是 net.micode.notes.gtask.data。) + +import android.content.ContentResolver; +// 导入 android.content.ContentResolver;(导入 Android 的内容解析器类。) +import android.content.ContentUris; +// 导入 android.content.ContentUris;(导入 Android 的内容 URI 工具类。) +import android.content.ContentValues; +// 导入 android.content.ContentValues;(导入 Android 的内容值类。) +import android.content.Context; +// 导入 android.content.Context;(导入 Android 的上下文类。) +import android.database.Cursor; +// 导入 android.database.Cursor;(导入 Android 的数据库游标类。) +import android.net.Uri; +// 导入 android.net.Uri;(导入 Android 的统一资源标识符类。) +import android.util.Log; +// 导入 android.util.Log;(导入 Android 的日志工具类。) + +import net.micode.notes.data.Notes; +// 导入 net.micode.notes.data.Notes;(导入来自 net.micode.notes.data 的 Notes 类。) +import net.micode.notes.data.Notes.DataColumns; +// 导入 net.micode.notes.data.Notes.DataColumns;(导入 Notes 类中的数据列类。) +import net.micode.notes.data.Notes.DataConstants; +// 导入 net.micode.notes.data.Notes.DataConstants;(导入 Notes 类中的数据常量类。) +import net.micode.notes.data.Notes.NoteColumns; +// 导入 net.micode.notes.data.Notes.NoteColumns;(导入 Notes 类中的笔记列类。) +import net.micode.notes.data.NotesDatabaseHelper.TABLE; +// 导入 net.micode.notes.data.NotesDatabaseHelper.TABLE;(导入 Notes 数据库助手类中的表枚举。) +import net.micode.notes.gtask.exception.ActionFailureException; +// 导入 net.micode.notes.gtask.exception.ActionFailureException;(导入特定的异常类。) + +import org.json.JSONException; +// 导入 org.json.JSONException;(导入 JSON 处理的异常类。) +import org.json.JSONObject; +// 导入 org.json.JSONObject;(导入 JSON 对象类。) + + +public class SqlData { +// 公共类 SqlData;(定义一个名为 SqlData 的公共类。) + private static final String TAG = SqlData.class.getSimpleName(); +// 私有静态最终字符串变量 TAG,赋值为 SqlData 类的简单名称;(定义一个私有静态最终的字符串变量 TAG,值为 SqlData 类的简单名称。) + + private static final int INVALID_ID = -99999; +// 私有静态最终整型变量 INVALID_ID,赋值为 -99999;(定义一个私有静态最终的整型变量 INVALID_ID,值为 -99999。) + + public static final String[] PROJECTION_DATA = new String[] { + DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, + DataColumns.DATA3 + }; +// 公共静态最终字符串数组变量 PROJECTION_DATA;(定义一个公共静态最终的字符串数组变量 PROJECTION_DATA,包含特定的数据列名称。) + + public static final int DATA_ID_COLUMN = 0; +// 公共静态最终整型变量 DATA_ID_COLUMN,赋值为 0;(定义一个公共静态最终的整型变量 DATA_ID_COLUMN,值为 0。) + + public static final int DATA_MIME_TYPE_COLUMN = 1; +// 公共静态最终整型变量 DATA_MIME_TYPE_COLUMN,赋值为 1;(定义一个公共静态最终的整型变量 DATA_MIME_TYPE_COLUMN,值为 1。) + + public static final int DATA_CONTENT_COLUMN = 2; +// 公共静态最终整型变量 DATA_CONTENT_COLUMN,赋值为 2;(定义一个公共静态最终的整型变量 DATA_CONTENT_COLUMN,值为 2。) + + public static final int DATA_CONTENT_DATA_1_COLUMN = 3; +// 公共静态最终整型变量 DATA_CONTENT_DATA_1_COLUMN,赋值为 3;(定义一个公共静态最终的整型变量 DATA_CONTENT_DATA_1_COLUMN,值为 3。) + + public static final int DATA_CONTENT_DATA_3_COLUMN = 4; +// 公共静态最终整型变量 DATA_CONTENT_DATA_3_COLUMN,赋值为 4;(定义一个公共静态最终的整型变量 DATA_CONTENT_DATA_3_COLUMN,值为 4。) + + private ContentResolver mContentResolver; +// 私有 ContentResolver 变量 mContentResolver;(定义一个私有变量 mContentResolver,类型为 ContentResolver。) + + private boolean mIsCreate; +// 私有布尔型变量 mIsCreate;(定义一个私有布尔型变量 mIsCreate。) + + private long mDataId; +// 私有长整型变量 mDataId;(定义一个私有长整型变量 mDataId。) + + private String mDataMimeType; +// 私有字符串变量 mDataMimeType;(定义一个私有字符串变量 mDataMimeType。) + + private String mDataContent; +// 私有字符串变量 mDataContent;(定义一个私有字符串变量 mDataContent。) + + private long mDataContentData1; +// 私有长整型变量 mDataContentData1;(定义一个私有长整型变量 mDataContentData1。) + + private String mDataContentData3; +// 私有字符串变量 mDataContentData3;(定义一个私有字符串变量 mDataContentData3。) + + private ContentValues mDiffDataValues; +// 私有 ContentValues 变量 mDiffDataValues;(定义一个私有变量 mDiffDataValues,类型为 ContentValues。) + + public SqlData(Context context) { +// 公共构造函数,接收 Context 参数;(定义一个公共构造函数,接收一个 Context 参数。) + mContentResolver = context.getContentResolver(); +// 获取内容解析器;(将 context 的内容解析器赋值给 mContentResolver。) + mIsCreate = true; +// 设置创建标志;(将 mIsCreate 设置为 true。) + mDataId = INVALID_ID; +// 初始化数据 ID;(将 mDataId 初始化为 INVALID_ID。) + mDataMimeType = DataConstants.NOTE; +// 初始化数据 MIME 类型;(将 mDataMimeType 初始化为 DataConstants.NOTE。) + mDataContent = ""; +// 初始化数据内容;(将 mDataContent 初始化为空字符串。) + mDataContentData1 = 0; +// 初始化数据内容数据 1;(将 mDataContentData1 初始化为 0。) + mDataContentData3 = ""; +// 初始化数据内容数据 3;(将 mDataContentData3 初始化为空字符串。) + mDiffDataValues = new ContentValues(); +// 创建新的 ContentValues 对象;(创建一个新的 ContentValues 对象并赋值给 mDiffDataValues。) + } + + public SqlData(Context context, Cursor c) { +// 公共构造函数,接收 Context 和 Cursor 参数;(定义一个公共构造函数,接收一个 Context 参数和一个 Cursor 参数。) + mContentResolver = context.getContentResolver(); +// 获取内容解析器;(将 context 的内容解析器赋值给 mContentResolver。) + mIsCreate = false; +// 设置创建标志;(将 mIsCreate 设置为 false。) + loadFromCursor(c); +// 从游标加载数据;(调用 loadFromCursor 方法,从传入的游标中加载数据。) + mDiffDataValues = new ContentValues(); +// 创建新的 ContentValues 对象;(创建一个新的 ContentValues 对象并赋值给 mDiffDataValues。) + } + + private void loadFromCursor(Cursor c) { +// 私有方法 loadFromCursor,接收 Cursor 参数;(定义一个私有方法 loadFromCursor,接收一个 Cursor 参数。) + mDataId = c.getLong(DATA_ID_COLUMN); +// 获取数据 ID;(从游标中获取指定列的数据 ID,并赋值给 mDataId。) + mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); +// 获取数据 MIME 类型;(从游标中获取指定列的数据 MIME 类型,并赋值给 mDataMimeType。) + mDataContent = c.getString(DATA_CONTENT_COLUMN); +// 获取数据内容;(从游标中获取指定列的数据内容,并赋值给 mDataContent。) + mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); +// 获取数据内容数据 1;(从游标中获取指定列的数据内容数据 1,并赋值给 mDataContentData1。) + mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); +// 获取数据内容数据 3;(从游标中获取指定列的数据内容数据 3,并赋值给 mDataContentData3。) + } + + public void setContent(JSONObject js) throws JSONException { +// 公共方法 setContent,接收 JSONObject 参数;(定义一个公共方法 setContent,接收一个 JSONObject 参数。) + long dataId = js.has(DataColumns.ID)? js.getLong(DataColumns.ID) : INVALID_ID; +// 获取数据 ID;(如果 JSONObject 中包含指定键,则获取对应的值作为数据 ID,否则设置为 INVALID_ID。) + if (mIsCreate || mDataId!= dataId) { +// 判断是否创建或数据 ID 不一致;(如果正在创建或者当前数据 ID 与新的数据 ID 不一致。) + mDiffDataValues.put(DataColumns.ID, dataId); +// 更新差异数据值;(将新的数据 ID 放入差异数据值的对应键中。) + } + mDataId = dataId; +// 更新数据 ID;(将 dataId 赋值给 mDataId。) + + String dataMimeType = js.has(DataColumns.MIME_TYPE)? js.getString(DataColumns.MIME_TYPE) + : DataConstants.NOTE; +// 获取数据 MIME 类型;(如果 JSONObject 中包含指定键,则获取对应的值作为数据 MIME 类型,否则设置为 DataConstants.NOTE。) + if (mIsCreate ||!mDataMimeType.equals(dataMimeType)) { +// 判断是否创建或数据 MIME 类型不一致;(如果正在创建或者当前数据 MIME 类型与新的数据 MIME 类型不一致。) + mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); +// 更新差异数据值;(将新的数据 MIME 类型放入差异数据值的对应键中。) + } + mDataMimeType = dataMimeType; +// 更新数据 MIME 类型;(将 dataMimeType 赋值给 mDataMimeType。) + + String dataContent = js.has(DataColumns.CONTENT)? js.getString(DataColumns.CONTENT) : ""; +// 获取数据内容;(如果 JSONObject 中包含指定键,则获取对应的值作为数据内容,否则设置为空字符串。) + if (mIsCreate ||!mDataContent.equals(dataContent)) { +// 判断是否创建或数据内容不一致;(如果正在创建或者当前数据内容与新的数据内容不一致。) + mDiffDataValues.put(DataColumns.CONTENT, dataContent); +// 更新差异数据值;(将新的数据内容放入差异数据值的对应键中。) + } + mDataContent = dataContent; +// 更新数据内容;(将 dataContent 赋值给 mDataContent。) + + long dataContentData1 = js.has(DataColumns.DATA1)? js.getLong(DataColumns.DATA1) : 0; +// 获取数据内容数据 1;(如果 JSONObject 中包含指定键,则获取对应的值作为数据内容数据 1,否则设置为 0。) + if (mIsCreate || mDataContentData1!= dataContentData1) { +// 判断是否创建或数据内容数据 1 不一致;(如果正在创建或者当前数据内容数据 1 与新的数据内容数据 1 不一致。) + mDiffDataValues.put(DataColumns.DATA1, dataContentData1); +// 更新差异数据值;(将新的数据内容数据 1 放入差异数据值的对应键中。) + } + mDataContentData1 = dataContentData1; +// 更新数据内容数据 1;(将 dataContentData1 赋值给 mDataContentData1。) + + String dataContentData3 = js.has(DataColumns.DATA3)? js.getString(DataColumns.DATA3) : ""; +// 获取数据内容数据 3;(如果 JSONObject 中包含指定键,则获取对应的值作为数据内容数据 3,否则设置为空字符串。) + if (mIsCreate ||!mDataContentData3.equals(dataContentData3)) { +// 判断是否创建或数据内容数据 3 不一致;(如果正在创建或者当前数据内容数据 3 与新的数据内容数据 3 不一致。) + mDiffDataValues.put(DataColumns.DATA3, dataContentData3); +// 更新差异数据值;(将新的数据内容数据 3 放入差异数据值的对应键中。) + } + mDataContentData3 = dataContentData3; +// 更新数据内容数据 3;(将 dataContentData3 赋值给 mDataContentData3。) + } + + public JSONObject getContent() throws JSONException { +// 公共方法 getContent;(定义一个公共方法 getContent。) + if (mIsCreate) { +// 判断是否正在创建;(如果正在创建。) + Log.e(TAG, "it seems that we haven't created this in database yet"); +// 输出错误日志;(打印错误日志,提示“看起来我们还没有在数据库中创建这个。”) + return null; +// 返回 null;(返回 null。) + } + JSONObject js = new JSONObject(); +// 创建新的 JSONObject 对象;(创建一个新的 JSONObject 对象。) + js.put(DataColumns.ID, mDataId); +// 设置数据 ID;(将 mDataId 放入 JSONObject 的对应键中。) + js.put(DataColumns.MIME_TYPE, mDataMimeType); +// 设置数据 MIME 类型;(将 mDataMimeType 放入 JSONObject 的对应键中。) + js.put(DataColumns.CONTENT, mDataContent); +// 设置数据内容;(将 mDataContent 放入 JSONObject 的对应键中。) + js.put(DataColumns.DATA1, mDataContentData1); +// 设置数据内容数据 1;(将 mDataContentData1 放入 JSONObject 的对应键中。) + js.put(DataColumns.DATA3, mDataContentData3); +// 设置数据内容数据 3;(将 mDataContentData3 放入 JSONObject 的对应键中。) + return js; +// 返回 JSONObject 对象;(返回创建好的 JSONObject 对象。) + } + + public void commit(long noteId, boolean validateVersion, long version) { +// 公共方法 commit,接收长整型、布尔型和长整型参数;(定义一个公共方法 commit,接收一个长整型 noteId、一个布尔型 validateVersion 和一个长整型 version 参数。) + + if (mIsCreate) { +// 判断是否正在创建;(如果正在创建。) + if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { +// 判断数据 ID 是否无效且差异数据值包含特定键;(如果数据 ID 无效且差异数据值中包含指定键。) + mDiffDataValues.remove(DataColumns.ID); +// 移除该键值对;(从差异数据值中移除该键值对。) + } + + mDiffDataValues.put(DataColumns.NOTE_ID, noteId); +// 设置笔记 ID;(将 noteId 放入差异数据值的对应键中。) + Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); +// 插入数据;(向 Notes 的内容数据 URI 中插入差异数据值,获取返回的 URI。) + try { +// 尝试执行以下代码块;(开始 try 代码块。) + mDataId = Long.valueOf(uri.getPathSegments().get(1)); +// 获取数据 ID;(从返回的 URI 中获取数据 ID,并赋值给 mDataId。) + } catch (NumberFormatException e) { +// 如果发生数字格式异常,进入此代码块;(捕获 NumberFormatException 异常。) + Log.e(TAG, "Get note id error :" + e.toString()); +// 输出错误日志;(打印错误日志,提示“获取笔记 ID 错误:”加上异常信息。) + throw new ActionFailureException("create note failed"); +// 抛出异常 + } else { +// 如果不是正在创建;(如果不是正在创建的状态。) + if (mDiffDataValues.size() > 0) { +// 如果差异数据值不为空;(如果差异数据值有内容。) + int result = 0; +// 初始化结果变量;(先把结果设为 0。) + if (!validateVersion) { +// 如果不验证版本;(如果不需要验证版本。) + result = mContentResolver.update(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); +// 更新数据;(用内容解析器更新数据,不考虑版本验证。) + } else { +// 如果要验证版本;(如果需要验证版本。) + result = mContentResolver.update(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, + "? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.VERSION + "=?)", new String[]{ + String.valueOf(noteId), String.valueOf(version) + }); +// 更新数据并验证版本;(用内容解析器更新数据,同时验证版本。) + } + if (result == 0) { +// 如果更新结果为 0;(如果更新没有效果。) + Log.w(TAG, "there is no update. maybe user updates note when syncing"); +// 输出警告日志;(打印警告日志,提示可能是用户在同步时更新了笔记。) + } + } + } + + mDiffDataValues.clear(); +// 清空差异数据值;(把差异数据值清空。) + mIsCreate = false; +// 设置不是创建状态;(设置当前不是创建新数据的状态。) + + public long getId() { +// 获取 ID 的方法;(定义一个获取数据 ID 的方法。) + return mDataId; +// 返回数据 ID;(返回当前的数据 ID。) + } \ No newline at end of file diff --git a/src/net/micode/notes/gtask/data/SqlNote.java b/src/net/micode/notes/gtask/data/SqlNote.java new file mode 100644 index 0000000..32e5584 --- /dev/null +++ b/src/net/micode/notes/gtask/data/SqlNote.java @@ -0,0 +1,534 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.micode.notes.gtask.data; +// 包声明:net.micode.notes.gtask.data +import android.appwidget.AppWidgetManager; +// 导入安卓小部件管理器类 +import android.content.ContentResolver; // 导入内容解析器类 +import android.content.ContentValues; // 导入内容值类 +import android.content.Context; // 导入上下文类 +import android.database.Cursor; // 导入游标类 +import android.net.Uri; // 导入统一资源标识符类 +import android.util.Log; // 导入日志类 +import net.micode.notes.data.Notes; // 导入笔记数据类 +import net.micode.notes.data.Notes.DataColumns; // 导入笔记数据列类 +import net.micode.notes.data.Notes.NoteColumns; // 导入笔记列类 +import net.micode.notes.gtask.exception.ActionFailureException; // 导入操作失败异常类 +import net.micode.notes.tool.GTaskStringUtils; // 导入工具类中的字符串工具类 +import net.micode.notes.tool.ResourceParser; // 导入资源解析器类 +import org.json.JSONArray; // 导入 JSON 数组类 +import org.json.JSONException; // 导入 JSON 异常类 +import org.json.JSONObject; // 导入 JSON 对象类 +import java.util.ArrayList; // 导入 ArrayList 类 +public class SqlNote { + // SQL 笔记类定义 + private static final String TAG = SqlNote.class.getSimpleName(); // 定义静态常量 TAG,为当前类名的简写 + private static final int INVALID_ID = -99999; // 定义无效 ID 的常量值 + 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, + NoteColumns.NOTES_COUNT, NoteColumns.PARENT_ID, NoteColumns.SNIPPET, NoteColumns.TYPE, + NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, NoteColumns.SYNC_ID, + NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID, + NoteColumns.VERSION + }; + // 定义笔记投影数组,包含各种列名 + public static final int ID_COLUMN = 0; // ID 列的索引常量 + public static final int ALERTED_DATE_COLUMN = 1; // 提醒日期列的索引常量 + public static final int BG_COLOR_ID_COLUMN = 2; // 背景颜色 ID 列的索引常量 + public static final int CREATED_DATE_COLUMN = 3; // 创建日期列的索引常量 + public static final int HAS_ATTACHMENT_COLUMN = 4; // 是否有附件列的索引常量 + public static final int MODIFIED_DATE_COLUMN = 5; // 修改日期列的索引常量 + public static final int NOTES_COUNT_COLUMN = 6; // 笔记数量列的索引常量 + public static final int PARENT_ID_COLUMN = 7; // 父 ID 列的索引常量 + public static final int SNIPPET_COLUMN = 8; // 摘要列的索引常量 + public static final int TYPE_COLUMN = 9; // 类型列的索引常量 + public static final int WIDGET_ID_COLUMN = 10; // 小部件 ID 列的索引常量 + public static final int WIDGET_TYPE_COLUMN = 11; // 小部件类型列的索引常量 + public static final int SYNC_ID_COLUMN = 12; // 同步 ID 列的索引常量 + public static final int LOCAL_MODIFIED_COLUMN = 13; // 本地修改列的索引常量 + public static final int ORIGIN_PARENT_ID_COLUMN = 14; // 原始父 ID 列的索引常量 + public static final int GTASK_ID_COLUMN = 15; // GTask ID 列的索引常量 + public static final int VERSION_COLUMN = 16; // 版本列的索引常量 + private Context mContext; // 定义上下文变量 + private ContentResolver mContentResolver; // 定义内容解析器变量 + private boolean mIsCreate; // 定义是否为创建状态的变量 + private long mId; // 定义 ID 变量 + private long mAlertDate; // 定义提醒日期变量 + private int mBgColorId; // 定义背景颜色 ID 变量 + private long mCreatedDate; // 定义创建日期变量 + private int mHasAttachment; // 定义是否有附件变量 + private long mModifiedDate; // 定义修改日期变量 + private long mParentId; // 定义父 ID 变量 + private String mSnippet; // 定义摘要变量 + private int mType; // 定义类型变量 + private int mWidgetId; // 定义小部件 ID 变量 + private int mWidgetType; // 定义小部件类型变量 + private long mOriginParent; // 定义原始父 ID 变量 + private long mVersion; // 定义版本变量 + private ContentValues mDiffNoteValues; + // 定义差异笔记值变量 + private ArrayList mDataList; + // 定义 SQL 数据列表变量 + public SqlNote(Context context) { + // 无参构造函数,接受上下文参数 + mContext = context; + // 将传入的上下文赋值给成员变量 + mContentResolver = context.getContentResolver(); + // 获取上下文的内容解析器并赋值给成员变量 + mIsCreate = true; + // 设置为创建状态 + mId = INVALID_ID; + // 设置 ID 为无效值 + mAlertDate = 0; + // 设置提醒日期为 0 + mBgColorId = ResourceParser.getDefaultBgId(context); + // 设置背景颜色 ID 为资源解析器获取的默认背景颜色 ID + mCreatedDate = System.currentTimeMillis(); + // 设置创建日期为当前时间戳 + mHasAttachment = 0; + // 设置是否有附件为 0 + mModifiedDate = System.currentTimeMillis(); + // 设置修改日期为当前时间戳 + mParentId = 0; + // 设置父 ID 为 0 + mSnippet = ""; + // 设置摘要为空字符串 + mType = Notes.TYPE_NOTE; + // 设置类型为笔记类型 + mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; + // 设置小部件 ID 为无效值 + mWidgetType = Notes.TYPE_WIDGET_INVALIDE; + // 设置小部件类型为无效值 + mOriginParent = 0; + // 设置原始父 ID 为 0 + mVersion = 0; + // 设置版本为 0 + mDiffNoteValues = new ContentValues(); + // 创建新的内容值对象并赋值给差异笔记值变量 + mDataList = new ArrayList(); + // 创建新的 SQL 数据列表 + } + public SqlNote(Context context, Cursor c) { + // 接受上下文和游标参数的构造函数 + mContext = context; // 将传入的上下文赋值给成员变量 + mContentResolver = context.getContentResolver(); // 获取上下文的内容解析器并赋值给成员变量 + mIsCreate = false; // 设置为非创建状态 + loadFromCursor(c); // 从游标加载数据 + mDataList = new ArrayList(); // 创建新的 SQL 数据列表 + if (mType == Notes.TYPE_NOTE) + loadDataContent(); // 如果类型为笔记类型,加载数据内容 + mDiffNoteValues = new ContentValues(); // 创建新的内容值对象并赋值给差异笔记值变量 + } + public SqlNote(Context context, long id) { + // 接受上下文和 ID 参数的构造函数 + mContext = context; // 将传入的上下文赋值给成员变量 + mContentResolver = context.getContentResolver(); // 获取上下文的内容解析器并赋值给成员变量 + mIsCreate = false; // 设置为非创建状态 + loadFromCursor(id); + // 从 ID 加载数据 + mDataList = new ArrayList(); + // 创建新的 SQL 数据列表 + if (mType == Notes.TYPE_NOTE) + loadDataContent(); + // 如果类型为笔记类型,加载数据内容 + mDiffNoteValues = new ContentValues(); + // 创建新的内容值对象并赋值给差异笔记值变量 + } + private void loadFromCursor(long id) { + // 从 ID 加载游标数据的私有方法 + Cursor c = null; + // 初始化游标为 null + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)", + new String[]{ + String.valueOf(id) + }, null); + // 查询内容解析器,获取指定 ID 的笔记数据游标 + if (c!= null) { + c.moveToNext(); + loadFromCursor(c); + // 如果游标不为 null,移动到下一行并从游标加载数据 + } else { + Log.w(TAG, "loadFromCursor: cursor = null"); + // 如果游标为 null,记录日志警告 + } + } finally { + if (c!= null) + c.close(); + // 最终,如果游标不为 null,关闭游标 + } + } + private void loadFromCursor(Cursor c) { + // 从游标加载数据的私有方法 + mId = c.getLong(ID_COLUMN); + // 获取游标中 ID 列的值并赋值给成员变量 + mAlertDate = c.getLong(ALERTED_DATE_COLUMN); + // 获取游标中提醒日期列的值并赋值给成员变量 + mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); + // 获取游标中背景颜色 ID 列的值并赋值给成员变量 + mCreatedDate = c.getLong(CREATED_DATE_COLUMN); + // 获取游标中创建日期列的值并赋值给成员变量 + mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN); + // 获取游标中是否有附件列的值并赋值给成员变量 + mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN); + // 获取游标中修改日期列的值并赋值给成员变量 + mParentId = c.getLong(PARENT_ID_COLUMN); + // 获取游标中父 ID 列的值并赋值给成员变量 + mSnippet = c.getString(SNIPPET_COLUMN); + // 获取游标中摘要列的值并赋值给成员变量 + mType = c.getInt(TYPE_COLUMN); + // 获取游标中类型列的值并赋值给成员变量 + mWidgetId = c.getInt(WIDGET_ID_COLUMN); + // 获取游标中小部件 ID 列的值并赋值给成员变量 + mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); + // 获取游标中小部件类型列的值并赋值给成员变量 + mVersion = c.getLong(VERSION_COLUMN); + // 获取游标中版本列的值并赋值给成员变量 + } + private void loadDataContent() { + // 加载数据内容的私有方法 + Cursor c = null; + // 初始化游标为 null + mDataList.clear(); + // 清空数据列表 + try { + c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA, + "(note_id=?)", new String[]{ + String.valueOf(mId) + }, null); + // 查询内容解析器,获取指定笔记 ID 的数据游标 + if (c!= null) { + if (c.getCount() == 0) { + Log.w(TAG, "it seems that the note has not data"); + // 如果游标计数为 0,记录日志警告 + return; + } + while (c.moveToNext()) { + SqlData data = new SqlData(mContext, c); + mDataList.add(data); + // 如果游标不为 null,遍历游标并创建 SQL 数据对象添加到数据列表 + } + } else { + Log.w(TAG, "loadDataContent: cursor = null"); + // 如果游标为 null,记录日志警告 + } + } finally { + if (c!= null) + c.close(); // 最终,如果游标不为 null,关闭游标 + } + } + public boolean setContent(JSONObject js) { + // 设置内容的方法,接受 JSON 对象参数,返回布尔值 + try { + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + // 从传入的 JSON 对象中获取名为 GTaskStringUtils.META_HEAD_NOTE 的 JSON 对象 + if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { + Log.w(TAG, "cannot set system folder"); + // 如果类型为系统文件夹类型,记录日志警告并返回 false + return false; + } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { + // 如果类型为文件夹类型 + // 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)) { + mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); + } + // 如果为创建状态或摘要与当前不同,将摘要添加到差异笔记值中 + mSnippet = snippet; // 更新摘要变量 + int type = note.has(NoteColumns.TYPE)? note.getInt(NoteColumns.TYPE) + : Notes.TYPE_NOTE; + // 获取类型,如果存在则获取,否则为笔记类型 + if (mIsCreate || mType!= type) { + mDiffNoteValues.put(NoteColumns.TYPE, type); + } + // 如果为创建状态或类型与当前不同,将类型添加到差异笔记值中 + mType = type; + // 更新类型变量 + } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { + // 如果类型为笔记类型 + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + // 获取数据数组 + long id = note.has(NoteColumns.ID)? note.getLong(NoteColumns.ID) : INVALID_ID; + // 获取 ID,如果存在则获取,否则为无效值 + if (mIsCreate || mId!= id) { + mDiffNoteValues.put(NoteColumns.ID, id); + } + // 如果为创建状态或 ID 与当前不同,将 ID 添加到差异笔记值中 + mId = id; // 更新 ID 变量 + long alertDate = note.has(NoteColumns.ALERTED_DATE)? note + .getLong(NoteColumns.ALERTED_DATE) : 0; // 获取提醒日期,如果存在则获取,否则为 0 + if (mIsCreate || mAlertDate!= alertDate) { + mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate); + } + // 如果为创建状态或提醒日期与当前不同,将提醒日期添加到差异笔记值中 + mAlertDate = alertDate; // 更新提醒日期变量 + int bgColorId = note.has(NoteColumns.BG_COLOR_ID)? note + .getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext); + // 获取背景颜色 ID,如果存在则获取,否则为默认背景颜色 ID + if (mIsCreate || mBgColorId!= bgColorId) { + mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId); + } + // 如果为创建状态或背景颜色 ID 与当前不同,将背景颜色 ID 添加到差异笔记值中 + mBgColorId = bgColorId; // 更新背景颜色 ID 变量 + long createDate = note.has(NoteColumns.CREATED_DATE)? note + .getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis(); + // 获取创建日期,如果存在则获取,否则为当前时间戳 + if (mIsCreate || mCreatedDate!= createDate) { + mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate); + } + // 如果为创建状态或创建日期与当前不同,将创建日期添加到差异笔记值中 + mCreatedDate = createDate; + // 更新创建日期变量 +int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT)? note + .getInt(NoteColumns.HAS_ATTACHMENT) : 0; +// 判断 JSON 对象 note 中是否存在 HAS_ATTACHMENT 字段,如果有则获取其整数值,否则将 hasAttachment 初始化为 0。 +if (mIsCreate || mHasAttachment!= hasAttachment) { + mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment); +} +// 如果当前处于创建状态或者当前的 hasAttachment 值与新获取的值不同,则将新的 hasAttachment 值放入差异笔记值的 HAS_ATTACHMENT 字段中。 +mHasAttachment = hasAttachment; +// 更新当前对象的 hasAttachment 变量。 +long modifiedDate = note.has(NoteColumns.MODIFIED_DATE)? note + .getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis(); +// 判断 JSON 对象 note 中是否存在 MODIFIED_DATE 字段,如果有则获取其长整数值,否则将 modifiedDate 初始化为当前时间戳。 +if (mIsCreate || mModifiedDate!= modifiedDate) { + mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate); +} +// 如果当前处于创建状态或者当前的 modifiedDate 值与新获取的值不同,则将新的 modifiedDate 值放入差异笔记值的 MODIFIED_DATE 字段中。 +mModifiedDate = modifiedDate; +// 更新当前对象的 modifiedDate 变量。 +long parentId = note.has(NoteColumns.PARENT_ID)? note + .getLong(NoteColumns.PARENT_ID) : 0; +// 判断 JSON 对象 note 中是否存在 PARENT_ID 字段,如果有则获取其长整数值,否则将 parentId 初始化为 0。 +if (mIsCreate || mParentId!= parentId) { + mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId); +} +// 如果当前处于创建状态或者当前的 parentId 值与新获取的值不同,则将新的 parentId 值放入差异笔记值的 PARENT_ID 字段中。 +mParentId = parentId; +// 更新当前对象的 parentId 变量。 +String snippet = note.has(NoteColumns.SNIPPET)? note + .getString(NoteColumns.SNIPPET) : ""; +// 判断 JSON 对象 note 中是否存在 SNIPPET 字段,如果有则获取其字符串值,否则将 snippet 初始化为空字符串。 + +if (mIsCreate ||!mSnippet.equals(snippet)) { + mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); +} +// 如果当前处于创建状态或者当前的 snippet 值与新获取的值不同,则将新的 snippet 值放入差异笔记值的 SNIPPET 字段中 +mSnippet = snippet; +// 更新当前对象的 snippet 变量。 +int type = note.has(NoteColumns.TYPE)? note.getInt(NoteColumns.TYPE) + : Notes.TYPE_NOTE; +// 判断 JSON 对象 note 中是否存在 TYPE 字段,如果有则获取其整数值,否则将 type 初始化为笔记类型。 +if (mIsCreate || mType!= type) { + mDiffNoteValues.put(NoteColumns.TYPE, type); +} +// 如果当前处于创建状态或者当前的 type 值与新获取的值不同,则将新的 type 值放入差异笔记值的 TYPE 字段中。 +mType = type; // 更新当前对象的 type 变量。 +int widgetId = note.has(NoteColumns.WIDGET_ID)? note.getInt(NoteColumns.WIDGET_ID) + : AppWidgetManager.INVALID_APPWIDGET_ID; +// 判断 JSON 对象 note 中是否存在 WIDGET_ID 字段,如果有则获取其整数值,否则将 widgetId 初始化为无效小部件 ID。 +if (mIsCreate || mWidgetId!= widgetId) { + mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId); +} +// 如果当前处于创建状态或者当前的 widgetId 值与新获取的值不同,则将新的 widgetId 值放入差异笔记值的 WIDGET_ID 字段中。 +mWidgetId = widgetId; +// 更新当前对象的 widgetId 变量。 +int widgetType = note.has(NoteColumns.WIDGET_TYPE)? note + .getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE; +// 判断 JSON 对象 note 中是否存在 WIDGET_TYPE 字段,如果有则获取其整数值,否则将 widgetType 初始化为无效小部件类型。 +if (mIsCreate || mWidgetType!= widgetType) { + mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType); +} +// 如果当前处于创建状态或者当前的 widgetType 值与新获取的值不同,则将新的 widgetType 值放入差异笔记值的 WIDGET_TYPE 字段中。 +mWidgetType = widgetType; // 更新当前对象的 widgetType 变量。 +long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID)? note + .getLong(NoteColumns.ORIGIN_PARENT_ID) : 0; // 判断 JSON 对象 note 中是否存在 ORIGIN_PARENT_ID 字段,如果有则获取其长整数值,否则将 originParent 初始化为 0。 +if (mIsCreate || mOriginParent!= originParent) { + mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent); +} +// 如果当前处于创建状态或者当前的 originParent 值与新获取的值不同,则将新的 originParent 值放入差异笔记值的 ORIGIN_PARENT_ID 字段中。 +mOriginParent = originParent; +// 更新当前对象的 originParent 变量。 +for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); // 从 JSON 数组 dataArray 中获取第 i 个元素,即一个 JSON 对象,并赋值给 data。 + SqlData sqlData = null; // 初始化一个 SqlData 对象为 null。 + if (data.has(DataColumns.ID)) { + long dataId = data.getLong(DataColumns.ID); // 如果 data 中包含 ID 字段,则获取其长整数值并赋值给 dataId。 + for (SqlData temp : mDataList) { + if (dataId == temp.getId()) { + sqlData = temp; + } + } + // 在当前对象的 mDataList 中遍历每个 SqlData 对象 temp,如果 dataId 与 temp 的 ID 相等,则将 temp 赋值给 sqlData。 + } + if (sqlData == null) { + sqlData = new SqlData(mContext); + mDataList.add(sqlData); + } + // 如果 sqlData 为 null,即没有找到匹配的 SqlData 对象,则创建一个新的 SqlData 对象并添加到 mDataList 中。 + + sqlData.setContent(data); + // 调用 sqlData 的 setContent 方法,将当前的 data 对象作为参数传入。 +} +public JSONObject getContent() { + try { + JSONObject js = new JSONObject(); + // 创建一个新的 JSON 对象 js。 + if (mIsCreate) { + Log.e(TAG, "it seems that we haven't created this in database yet"); + return null; + } + // 如果当前处于创建状态,则记录日志并返回 null。 + JSONObject note = new JSONObject(); + // 创建一个新的 JSON 对象 note。 + if (mType == Notes.TYPE_NOTE) { + note.put(NoteColumns.ID, mId); + note.put(NoteColumns.ALERTED_DATE, mAlertDate); + note.put(NoteColumns.BG_COLOR_ID, mBgColorId); + note.put(NoteColumns.CREATED_DATE, mCreatedDate); + note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment); + note.put(NoteColumns.MODIFIED_DATE, mModifiedDate); + note.put(NoteColumns.PARENT_ID, mParentId); + note.put(NoteColumns.SNIPPET, mSnippet); + note.put(NoteColumns.TYPE, mType); + note.put(NoteColumns.WIDGET_ID, mWidgetId); + note.put(NoteColumns.WIDGET_TYPE, mWidgetType); + note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent); + js.put(GTaskStringUtils.META_HEAD_NOTE, note); + // 如果当前对象的类型是笔记类型,则将各种属性放入 note 中,并将 note 放入 js 的特定键下。 + + JSONArray dataArray = new JSONArray(); + for (SqlData sqlData : mDataList) { + JSONObject data = sqlData.getContent(); + if (data!= null) { + dataArray.put(data); + } + } + js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); + // 创建一个新的 JSON 数组 dataArray,遍历数据列表获取每个 SqlData 的内容放入 JSON 数组中,再将 dataArray 放入 js 的特定键下。 + } else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { + note.put(NoteColumns.ID, mId); + note.put(NoteColumns.TYPE, mType); + note.put(NoteColumns.SNIPPET, mSnippet); + js.put(GTaskStringUtils.META_HEAD_NOTE, note); + } + // 如果当前对象的类型是文件夹类型或系统类型,则只将 ID、类型和摘要放入 note 中,再将 note 放入 js 的特定键下。 + return js; + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + } + return null; +} +public void setParentId(long id) { + mParentId = id; // 更新当前对象的父 ID 变量。 + mDiffNoteValues.put(NoteColumns.PARENT_ID, id); // 将新的父 ID 放入差异笔记值的 PARENT_ID 字段中。 +} +public void setGtaskId(String gid) { + mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); // 将新的 GTask ID 放入差异笔记值的 GTASK_ID 字段中。 +} +public void setSyncId(long syncId) { + mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); // 将新的同步 ID 放入差异笔记值的 SYNC_ID 字段中。 +} +public void resetLocalModified() { + mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); // 将本地修改状态设置为 0,并放入差异笔记值的 LOCAL_MODIFIED 字段中 +} +public long getId() { + return mId; // 返回当前对象的 ID。 +} +public long getParentId() { + return mParentId; // 返回当前对象的父 ID +} +public String getSnippet() { + return mSnippet; // 返回当前对象的摘要 +} +public boolean isNoteType() { + return mType == Notes.TYPE_NOTE; + // 判断是否是笔记类型的方法。如果当前对象的类型等于笔记类型,则返回 true,否则返回 false。 +} + +public void commit(boolean validateVersion) { + if (mIsCreate) { + if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) { + mDiffNoteValues.remove(NoteColumns.ID); + } + // 如果当前处于创建状态且 ID 为无效值且差异笔记值中包含 ID 字段,则移除该 ID 字段。 + + Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); + try { + mId = Long.valueOf(uri.getPathSegments().get(1)); + } catch (NumberFormatException e) { + Log.e(TAG, "Get note id error :" + e.toString()); + throw new ActionFailureException("create note failed"); + } + // 插入新的笔记数据到内容解析器中,并获取新插入的笔记 ID。如果插入失败则抛出异常。 + + if (mId == 0) { + throw new IllegalStateException("Create thread id failed"); + } + // 如果当前 ID 为 0,则抛出非法状态异常。 + + if (mType == Notes.TYPE_NOTE) { + for (SqlData sqlData : mDataList) { + sqlData.commit(mId, false, -1); + } + } + // 如果类型是笔记类型,则对数据列表中的每个 SqlData 对象调用提交方法。 + } 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"); + } + // 如果不是创建状态且 ID 无效(不等于根文件夹 ID 和通话记录文件夹 ID),则抛出异常。 + + if (mDiffNoteValues.size() > 0) { + mVersion++; + int result = 0; + if (!validateVersion) { + result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" + + NoteColumns.ID + "=?)", new String[]{ + String.valueOf(mId) + }); + } else { + result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" + + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", + new String[]{ + String.valueOf(mId), String.valueOf(mVersion) + }); + } + if (result == 0) { + Log.w(TAG, "there is no update. maybe user updates note when syncing"); + } + } + // 如果差异笔记值不为空,则更新版本号,并根据 validateVersion 的值选择不同的更新方式。如果更新结果为 0,则记录日志警告。 + + if (mType == Notes.TYPE_NOTE) { + for (SqlData sqlData : mDataList) { + sqlData.commit(mId, validateVersion, mVersion); + } + } + // 如果类型是笔记类型,则对数据列表中的每个 SqlData 对象调用提交方法。 + } + // refresh local info + loadFromCursor(mId); + if (mType == Notes.TYPE_NOTE) + loadDataContent(); // 刷新本地信息,根据 ID 重新加载当前对象的数据,如果类型是笔记类型,则加载数据内容。 + mDiffNoteValues.clear(); + mIsCreate = false; // 清空差异笔记值,并设置为非创建状态。 +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/data/Task.java b/src/net/micode/notes/gtask/data/Task.java new file mode 100644 index 0000000..e4ba798 --- /dev/null +++ b/src/net/micode/notes/gtask/data/Task.java @@ -0,0 +1,397 @@ +// 版权声明,表明此代码受Apache License 2.0许可,版权归The MiCode Open Source Community(2010 - 2011)所有 +/* + * Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE - 2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// 包声明,此Task类位于net.micode.notes.gtask.data包下 +package net.micode.notes.gtask.data; +// 导入相关类 +import android.database.Cursor; +import android.text.TextUtils; +import android.util.Log; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.exception.ActionFailureException; +import net.micode.notes.tool.GTaskStringUtils; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +// Task类继承自Node类(此处Node类的定义未给出) +public class Task extends Node { + // 定义一个私有静态常量TAG,其值为Task类的简单名称,用于日志记录 + 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; + // Task类的构造函数,调用父类(Node)的构造函数,并初始化成员变量 + public Task() { + super(); + mCompleted = false; + mNotes = null; + mPriorSibling = null; + mParent = null; + mMetaInfo = null; + } + // 生成创建任务动作的JSONObject的方法 + public JSONObject getCreateAction(int actionId) { + // 创建一个新的JSONObject实例 + JSONObject js = new JSONObject(); + try { + // 在JSON对象中设置"action_type"键值对,值为创建任务类型(通过GTaskStringUtils类中的常量) + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); + // 在JSON对象中设置"action_id"键值对,值为传入的actionId参数 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + // 在JSON对象中设置"index"键值对,值为通过父任务列表获取的此任务的索引 + js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); + // 创建一个名为"entity_delta"的新JSONObject实例,用于存储任务实体的变化信息 + JSONObject entity = new JSONObject(); + // 在"entity_delta"对象中设置"name"键值对,值为任务名称(通过getName()方法获取) + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + // 在"entity_delta"对象中设置"creator_id"键值对,值为"null" + entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); + // 在"entity_delta"对象中设置"entity_type"键值对,值为任务类型(通过GTaskStringUtils类中的常量) + entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_TASK); + // 如果任务备注信息不为null,在"entity_delta"对象中设置"notes"键值对,值为任务备注(通过getNotes()方法获取) + if (getNotes()!= null) { + entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); + } + // 在主JSON对象(js)中设置"entity_delta"键值对,值为刚才创建的"entity"对象 + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + // 在JSON对象中设置"parent_id"键值对,值为父任务的ID(通过父任务的getGid()方法获取) + js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); + // 在JSON对象中设置"dest_parent_type"键值对,值为目标父实体类型(通过GTaskStringUtils类中的常量,这里是组类型) + js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_GROUP); + // 在JSON对象中设置"list_id"键值对,值为父任务的ID(与"parent_id"相同) + js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); + // 如果前一个兄弟任务不为null,在JSON对象中设置"prior_sibling_id"键值对,值为前一个兄弟任务的ID(通过前一个兄弟任务的getGid()方法获取) + if (mPriorSibling!= null) { + js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); + } + } catch (JSONException e) { + // 如果发生JSONException,记录错误日志(使用Log.e,标签为TAG,内容为异常的字符串表示) + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + // 抛出ActionFailureException异常,表示生成创建任务的JSON对象失败 + throw new ActionFailureException("fail to generate task - create jsonobject"); + } + return js; + } + // 生成更新任务动作的JSONObject的方法 + public JSONObject getUpdateAction(int actionId) { + // 创建一个新的JSONObject实例 + JSONObject js = new JSONObject(); + try { + // 在JSON对象中设置"action_type"键值对,值为更新任务类型(通过GTaskStringUtils类中的常量) + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); + // 在JSON对象中设置"action_id"键值对,值为传入的actionId参数 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + // 在JSON对象中设置"id"键值对,值为任务的ID(通过getGid()方法获取) + js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); + // 创建一个名为"entity_delta"的新JSONObject实例,用于存储任务实体的变化信息 + JSONObject entity = new JSONObject(); + // 在"entity_delta"对象中设置"name"键值对,值为任务名称(通过getName()方法获取) + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + // 如果任务备注信息不为null,在"entity_delta"对象中设置"notes"键值对,值为任务备注(通过getNotes()方法获取) + if (getNotes()!= null) { + entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); + } + // 在"entity_delta"对象中设置"deleted"键值对,值为任务的删除标记(通过getDeleted()方法获取,此处未给出getDeleted()方法的实现) + entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); + // 在主JSON对象(js)中设置"entity_delta"键值对,值为刚才创建的"entity"对象 + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + } catch (JSONException e) { + // 如果发生JSONException,记录错误日志(使用Log.e,标签为TAG,内容为异常的字符串表示) + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + // 抛出ActionFailureException异常,表示生成更新任务的JSON对象失败 + throw new ActionFailureException("fail to generate task - update jsonobject"); + } + return js; + } + // 根据远程的JSONObject设置任务内容的方法 + public void setContentByRemoteJSON(JSONObject js) { + // 如果传入的JSON对象不为null + if (js!= null) { + try { + // 如果JSON对象包含特定的"id"键(通过GTaskStringUtils类中的常量) + if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { + // 设置任务的ID(通过setGid()方法,此处未给出setGid()方法的实现,参数为从JSON对象中获取的字符串值) + setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); + } + // 如果JSON对象包含特定的"last_modified"键(通过GTaskStringUtils类中的常量) + if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { + // 设置任务的最后修改时间(通过setLastModified()方法,此处未给出setLastModified()方法的实现,参数为从JSON对象中获取的长整型值) + setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); + } + // 如果JSON对象包含特定的"name"键(通过GTaskStringUtils类中的常量) + if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { + // 设置任务的名称(通过setName()方法,此处未给出setName()方法的实现,参数为从JSON对象中获取的字符串值) + setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); + } + // 如果JSON对象包含特定的"notes"键(通过GTaskStringUtils类中的常量) + if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { + // 设置任务的备注信息(通过setNotes()方法,参数为从JSON对象中获取的字符串值) + setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); + } + // 如果JSON对象包含特定的"deleted"键(通过GTaskStringUtils类中的常量) + if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { + // 设置任务的删除标记(通过setDeleted()方法,此处未给出setDeleted()方法的实现,参数为从JSON对象中获取的布尔值) + setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); + } + // 如果JSON对象包含特定的"completed"键(通过GTaskStringUtils类中的常量) + if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { + // 设置任务的完成状态(通过setCompleted()方法,参数为从JSON对象中获取的布尔值) + setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); + } + } catch (JSONException e) { + // 如果发生JSONException,记录错误日志(使用Log.e,标签为TAG,内容为异常的字符串表示) + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + // 抛出ActionFailureException异常,表示从JSON对象获取任务内容失败 + throw new ActionFailureException("fail to get task content from jsonobject"); + } + } + } + // 根据本地的JSONObject设置任务内容的方法 + public void setContentByLocalJSON(JSONObject js) { + // 如果传入的JSON对象为null或者不包含特定的头信息(通过GTaskStringUtils类中的常量) + if (js == null ||!js.has(GTaskStringUtils.META_HEAD_NOTE) + ||!js.has(GTaskStringUtils.META_HEAD_DATA)) { + // 记录警告日志(使用Log.w,标签为TAG,内容为指定的字符串) + Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); + } + try { + // 从JSON对象中获取名为"note"的JSONObject(通过GTaskStringUtils类中的常量) + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + // 从JSON对象中获取名为"dataArray"的JSONArray(通过GTaskStringUtils类中的常量) + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + // 如果"note"对象中的"type"键对应的值不等于Notes.TYPE_NOTE(此处Notes类和TYPE_NOTE的定义未给出) + if (note.getInt(NoteColumns.TYPE)!= Notes.TYPE_NOTE) { + // 记录错误日志(使用Log.e,标签为TAG,内容为指定的字符串) + Log.e(TAG, "invalid type"); + return; + } + // 遍历"dataArray" + for (int i = 0; i < dataArray.length(); i++) { + // 获取"dataArray"中的每个JSONObject元素 + JSONObject data = dataArray.getJSONObject(i); + // 如果"data"对象中的"mime_type"键(通过DataColumns类中的常量)对应的值等于特定的笔记类型(通过DataConstants类中的常量) + if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { + // 设置任务的名称(通过setName()方法,参数为"data"对象中"content"键对应的值) + setName(data.getString(DataColumns.CONTENT)); + break; + } + } + + } catch (JSONException e) { + // 如果发生JSONException,记录错误日志(使用Log.e,标签为TAG,内容为异常的字符串表示) + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + } + } + // 从任务内容生成本地JSONObject的方法 + public JSONObject getLocalJSONFromContent() { + // 获取任务名称 + String name = getName(); + try { + // 如果元数据信息(mMetaInfo)为null,表示是从网页创建的新任务 + if (mMetaInfo == null) { + // 如果任务名称为null + if (name == null) { + // 记录警告日志(使用Log.w,标签为TAG,内容为指定的字符串) + Log.w(TAG, "the note seems to be an empty one"); + return null; + } + // 创建一个新的JSONObject实例 + JSONObject js = new JSONObject(); + // 创建一个名为"note"的新JSONObject实例 + JSONObject note = new JSONObject(); + // 创建一个新的JSONArray实例 + JSONArray dataArray = new JSONArray(); + // 创建一个名为"data"的新JSONObject实例 + JSONObject data = new JSONObject(); + // 在"data"对象中设置"content"键值对,值为任务名称 + data.put(DataColumns.CONTENT, name); + // 将"data"对象添加到"dataArray"中 + dataArray.put(data); + // 在主JSON对象(js)中设置"dataArray"键值对,值为刚才创建的"dataArray"对象 + js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); + // 在"note"对象中设置"type"键值对,值为Notes.TYPE_NOTE(此处Notes类和TYPE_NOTE的定义未给出) + note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + // 在主JSON对象(js)中设置"note"键值对,值为刚才创建的"note"对象 + js.put(GTaskStringUtils.META_HEAD_NOTE, note); + return js; + } else { + // 如果元数据信息不为null,表示是已同步的任务 + // 从元数据信息中获取名为"note"的JSONObject(通过GTaskStringUtils类中的常量) + JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + // 从元数据信息中获取名为"dataArray"的JSONArray(通过GTaskStringUtils类中的常量) + JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + // 遍历"dataArray" + for (int i = 0; i < dataArray.length(); i++) { + // 获取"dataArray"中的每个JSONObject元素 + JSONObject data = dataArray.getJSONObject(i); + // 如果"data"对象中的"mime_type"键(通过DataColumns类中的常量)对应的值等于特定的笔记类型(通过DataConstants类中的常量) + if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { + // 在"data"对象中设置"content"键值对,值为当前任务名称 + data.put(DataColumns.CONTENT, getName()); + break; + } + } + // 在"note"对象中设置"type"键值对,值为Notes.TYPE_NOTE(此处Notes类和TYPE_NOTE的定义未给出) + note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + return mMetaInfo; + } + } catch (JSONException e) { + // 如果发生JSONException,记录错误日志(使用Log.e,标签为TAG,内容为异常的字符串表示) + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + return null; + } + } + // 设置任务元数据信息的方法 + // 设置任务元数据信息的方法。参数metaData是一个包含元数据的对象 +public void setMetaInfo(MetaData metaData) { + // 如果传入的metaData不为null并且metaData的getNotes()方法返回值也不为null + if (metaData!= null && metaData.getNotes()!= null) { + try { + // 将metaData中的备注信息(通过getNotes()获取)转换为JSONObject,并赋值给mMetaInfo + mMetaInfo = new JSONObject(metaData.getNotes()); + } catch (JSONException e) { + // 如果在转换过程中出现JSONException,记录一个警告日志(包含异常信息) + Log.w(TAG, e.toString()); + // 将mMetaInfo设置为null + mMetaInfo = null; + } + } +} +// 根据数据库游标确定同步操作类型的方法。参数c是一个数据库游标对象 +public int getSyncAction(Cursor c) { + try { + // 用于存储从元数据中获取的笔记信息的JSONObject,初始化为null + JSONObject noteInfo = null; + // 如果mMetaInfo不为null并且包含特定的"note"头信息(通过GTaskStringUtils类中的常量) + if (mMetaInfo!= null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) { + // 从mMetaInfo中获取名为"note"的JSONObject + noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + } + // 如果noteInfo为null,表示笔记元数据似乎已被删除 + if (noteInfo == null) { + Log.w(TAG, "it seems that note meta has been deleted"); + // 返回更新远程操作类型(SYNC_ACTION_UPDATE_REMOTE的值未给出,应该是一个预定义的表示更新远程操作的常量) + return SYNC_ACTION_UPDATE_REMOTE; + } + // 如果noteInfo不包含特定的"id"键(通过NoteColumns类中的常量) + if (!noteInfo.has(NoteColumns.ID)) { + Log.w(TAG, "remote note id seems to be deleted"); + // 返回更新本地操作类型(SYNC_ACTION_UPDATE_LOCAL的值未给出,应该是一个预定义的表示更新本地操作的常量) + return SYNC_ACTION_UPDATE_LOCAL; + } + // 验证数据库中的笔记ID是否与从noteInfo中获取的笔记ID匹配 + if (c.getLong(SqlNote.ID_COLUMN)!= noteInfo.getLong(NoteColumns.ID)) { + Log.w(TAG, "note id doesn't match"); + // 如果不匹配,返回更新本地操作类型 + return SYNC_ACTION_UPDATE_LOCAL; + } + // 如果本地没有更新(通过检查数据库游标中的本地修改标志列,值为0表示无本地修改) + if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { + // 如果数据库中的同步ID与任务的最后修改时间匹配 + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // 两边都无更新,返回无操作类型(SYNC_ACTION_NONE的值未给出,应该是一个预定义的表示无操作的常量) + return SYNC_ACTION_NONE; + } else { + // 将远程数据应用到本地,返回更新本地操作类型 + return SYNC_ACTION_UPDATE_LOCAL; + } + } else { + // 验证gtask id是否匹配,即数据库游标中的gtask id与任务的ID(通过getGid()方法获取)是否相等 + if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { + Log.e(TAG, "gtask id doesn't match"); + // 如果不匹配,返回错误操作类型(SYNC_ACTION_ERROR的值未给出,应该是一个预定义的表示错误操作的常量) + return SYNC_ACTION_ERROR; + } + // 如果数据库中的同步ID与任务的最后修改时间匹配 + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // 仅有本地修改,返回更新远程操作类型 + return SYNC_ACTION_UPDATE_REMOTE; + } else { + // 返回更新冲突操作类型(SYNC_ACTION_UPDATE_CONFLICT的值未给出,应该是一个预定义的表示更新冲突的常量) + return SYNC_ACTION_UPDATE_CONFLICT; + } + } + } catch (Exception e) { + // 如果发生任何异常,记录错误日志(包含异常信息) + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + } + // 如果在方法执行过程中出现错误,返回错误操作类型 + return SYNC_ACTION_ERROR; +} +// 判断任务是否值得保存的方法 +public boolean isWorthSaving() { + // 如果mMetaInfo不为null,或者任务名称不为null且去除空格后的长度大于0,或者备注信息不为null且去除空格后的长度大于0,则任务值得保存,返回true + return mMetaInfo!= null || (getName()!= null && getName().trim().length() > 0) + || (getNotes()!= null && getNotes().trim().length() > 0); +} +// 设置任务完成状态的方法。参数completed是表示完成状态的布尔值 +public void setCompleted(boolean completed) { + this.mCompleted = completed; +} +// 设置任务备注信息的方法。参数notes是包含备注信息的字符串 +public void setNotes(String notes) { + this.mNotes = notes; +} +// 设置任务前一个兄弟任务的方法。参数priorSibling是指向前一个兄弟任务的Task对象引用 +public void setPriorSibling(Task priorSibling) { + this.mPriorSibling = priorSibling; +} +// 设置任务所在任务列表的方法。参数parent是指向任务列表的TaskList对象引用 +public void setParent(TaskList parent) { + this.mParent = parent; +} +// 获取任务完成状态的方法,返回任务的完成状态(布尔值) +public boolean getCompleted() { + return this.mCompleted; +} +// 获取任务备注信息的方法,返回任务的备注信息(字符串) +public String getNotes() { + return this.mNotes; +} +// 获取任务前一个兄弟任务的方法,返回指向前一个兄弟任务的Task对象引用 +public Task getPriorSibling() { + return this.mPriorSibling; +} +// 获取任务所在任务列表的方法,返回指向任务列表的TaskList对象引用 +public TaskList getParent() { + return this.mParent; +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/data/TaskList.java b/src/net/micode/notes/gtask/data/TaskList.java new file mode 100644 index 0000000..75884be --- /dev/null +++ b/src/net/micode/notes/gtask/data/TaskList.java @@ -0,0 +1,400 @@ +// 版权声明,此代码受Apache License 2.0许可,版权归The MiCode Open Source Community(2010 - 2011)所有 +/* + * Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE - 2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// 包声明,此TaskList类位于net.micode.notes.gtask.data包下 +package net.micode.notes.gtask.data; +// 导入相关类 +import android.database.Cursor; +import android.util.Log; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.exception.ActionFailureException; +import net.micode.notes.tool.GTaskStringUtils; +import org.json.JSONException; +import org.json.JSONObject; +import java.util.ArrayList; +// TaskList类继承自Node类(此处Node类的定义未给出) +public class TaskList extends Node { + // 定义一个私有静态常量TAG,其值为TaskList类的简单名称,用于日志记录 + private static final String TAG = TaskList.class.getSimpleName(); + // 存储任务列表索引的整数,初始值为1 + private int mIndex; + // 存储任务列表中Task对象的ArrayList + private ArrayList mChildren; + // TaskList类的构造函数,调用父类(Node)的构造函数,并初始化mChildren和mIndex + public TaskList() { + super(); + mChildren = new ArrayList(); + mIndex = 1; + } + // 生成创建任务列表动作的JSONObject的方法 + public JSONObject getCreateAction(int actionId) { + // 创建一个新的JSONObject实例 + JSONObject js = new JSONObject(); + try { + // 在JSON对象中设置"action_type"键值对,值为创建任务列表类型(通过GTaskStringUtils类中的常量) + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); + // 在JSON对象中设置"action_id"键值对,值为传入的actionId参数 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + // 在JSON对象中设置"index"键值对,值为任务列表的索引(mIndex) + js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); + // 创建一个名为"entity_delta"的新JSONObject实例,用于存储任务列表实体的变化信息 + JSONObject entity = new JSONObject(); + // 在"entity_delta"对象中设置"name"键值对,值为任务列表名称(通过getName()方法获取) + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + // 在"entity_delta"对象中设置"creator_id"键值对,值为"null" + entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); + // 在"entity_delta"对象中设置"entity_type"键值对,值为任务列表类型(通过GTaskStringUtils类中的常量) + entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_GROUP); + // 在主JSON对象(js)中设置"entity_delta"键值对,值为刚才创建的"entity"对象 + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + } catch (JSONException e) { + // 如果发生JSONException,记录错误日志(使用Log.e,标签为TAG,内容为异常的字符串表示) + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + // 抛出ActionFailureException异常,表示生成创建任务列表的JSON对象失败 + throw new ActionFailureException("fail to generate tasklist - create jsonobject"); + } + return js; + } + // 生成更新任务列表动作的JSONObject的方法 + public JSONObject getUpdateAction(int actionId) { + // 创建一个新的JSONObject实例 + JSONObject js = new JSONObject(); + try { + // 在JSON对象中设置"action_type"键值对,值为更新任务列表类型(通过GTaskStringUtils类中的常量) + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); + // 在JSON对象中设置"action_id"键值对,值为传入的actionId参数 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + // 在JSON对象中设置"id"键值对,值为任务列表的ID(通过getGid()方法获取) + js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); + // 创建一个名为"entity_delta"的新JSONObject实例,用于存储任务列表实体的变化信息 + JSONObject entity = new JSONObject(); + // 在"entity_delta"对象中设置"name"键值对,值为任务列表名称(通过getName()方法获取) + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + // 在"entity_delta"对象中设置"deleted"键值对,值为任务列表的删除标记(通过getDeleted()方法获取,此处未给出getDeleted()方法的实现) + entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); + // 在主JSON对象(js)中设置"entity_delta"键值对,值为刚才创建的"entity"对象 + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + } catch (JSONException e) { + // 如果发生JSONException,记录错误日志(使用Log.e,标签为TAG,内容为异常的字符串表示) + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + // 抛出ActionFailureException异常,表示生成更新任务列表的JSON对象失败 + throw new ActionFailureException("fail to generate tasklist - update jsonobject"); + } + return js; + } + // 根据远程的JSONObject设置任务列表内容的方法 + public void setContentByRemoteJSON(JSONObject js) { + // 如果传入的JSON对象不为null + if (js!= null) { + try { + // 如果JSON对象包含特定的"id"键(通过GTaskStringUtils类中的常量) + if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { + // 设置任务列表的ID(通过setGid()方法,此处未给出setGid()方法的实现,参数为从JSON对象中获取的字符串值) + setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); + } + // 如果JSON对象包含特定的"last_modified"键(通过GTaskStringUtils类中的常量) + if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { + // 设置任务列表的最后修改时间(通过setLastModified()方法,此处未给出setLastModified()方法的实现,参数为从JSON对象中获取的长整型值) + setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); + } + // 如果JSON对象包含特定的"name"键(通过GTaskStringUtils类中的常量) + if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { + // 设置任务列表的名称(通过setName()方法,此处未给出setName()方法的实现,参数为从JSON对象中获取的字符串值) + setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); + } + } catch (JSONException e) { + // 如果发生JSONException,记录错误日志(使用Log.e,标签为TAG,内容为异常的字符串表示) + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + // 抛出ActionFailureException异常,表示从JSON对象获取任务列表内容失败 + throw new ActionFailureException("fail to get tasklist content from jsonobject"); + } + } + } + // 根据本地的JSONObject设置任务列表内容的方法 + public void setContentByLocalJSON(JSONObject js) { + // 如果传入的JSON对象为null或者不包含特定的头信息(通过GTaskStringUtils类中的常量) + if (js == null ||!js.has(GTaskStringUtils.META_HEAD_NOTE)) { + // 记录警告日志(使用Log.w,标签为TAG,内容为指定的字符串) + Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); + } + try { + // 从JSON对象中获取名为"folder"的JSONObject(通过GTaskStringUtils类中的常量) + JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + // 如果"folder"对象中的"type"键对应的值等于Notes.TYPE_FOLDER(此处Notes类和TYPE_FOLDER的定义未给出) + if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { + // 从"folder"对象中获取"SNIPPET"键对应的值作为名称,并添加特定前缀(通过GTaskStringUtils类中的常量) + String name = folder.getString(NoteColumns.SNIPPET); + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); + } else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { + // 如果"folder"对象中的"id"键对应的值等于特定的根文件夹ID(Notes.ID_ROOT_FOLDER,此处Notes类和ID_ROOT_FOLDER的定义未给出) + if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) + // 设置任务列表名称为特定前缀(通过GTaskStringUtils类中的常量)加上默认文件夹名称(通过GTaskStringUtils类中的常量) + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); + // 如果"folder"对象中的"id"键对应的值等于特定的通话记录文件夹ID(Notes.ID_CALL_RECORD_FOLDER,此处Notes类和ID_CALL_RECORD_FOLDER的定义未给出) + else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER) + // 设置任务列表名称为特定前缀(通过GTaskStringUtils类中的常量)加上通话记录文件夹名称(通过GTaskStringUtils类中的常量) + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_CALL_NOTE); + else + // 如果"folder"对象的ID不匹配上述情况,记录错误日志(使用Log.e,标签为TAG,内容为指定的字符串) + Log.e(TAG, "invalid system folder"); + } else { + // 如果"folder"对象中的"type"键对应的值不是上述情况,记录错误日志(使用Log.e,标签为TAG,内容为指定的字符串) + Log.e(TAG, "error type"); + } + } catch (JSONException e) { + // 如果发生JSONException,记录错误日志(使用Log.e,标签为TAG,内容为异常的字符串表示) + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + } + } + // 从任务列表内容生成本地JSONObject的方法 + public JSONObject getLocalJSONFromContent() { + try { + // 创建一个新的JSONObject实例 + JSONObject js = new JSONObject(); + // 创建一个名为"folder"的新JSONObject实例 + JSONObject folder = new JSONObject(); + // 获取任务列表名称 + String folderName = getName(); + // 如果任务列表名称以特定前缀(通过GTaskStringUtils类中的常量)开头 + if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) + // 去除前缀 + folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(), + folderName.length()); + // 在"folder"对象中设置"SNIPPET"键值对,值为处理后的名称 + folder.put(NoteColumns.SNIPPET, folderName); + // 如果处理后的名称等于默认文件夹名称(通过GTaskStringUtils类中的常量)或者通话记录文件夹名称(通过GTaskStringUtils类中的常量) + if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) + || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) + // 在"folder"对象中设置"type"键值对,值为Notes.TYPE_SYSTEM(此处Notes类和TYPE_SYSTEM的定义未给出) + folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + else + // 在"folder"对象中设置"type"键值对,值为Notes.TYPE_FOLDER(此处Notes类和TYPE_FOLDER的定义未给出) + folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); + + // 在主JSON对象(js)中设置"folder"键值对,值为刚才创建的"folder"对象 + js.put(GTaskStringUtils.META_HEAD_NOTE, folder); + return js; + } catch (JSONException e) { + // 如果发生JSONException,记录错误日志(使用Log.e,标签为TAG,内容为异常的字符串表示) + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + return null; + } + } + // 根据数据库游标确定同步操作类型的方法。参数c是一个数据库游标对象 + public int getSyncAction(Cursor c) { + try { + // 如果本地没有更新(通过检查数据库游标中的本地修改标志列,值为0表示无本地修改) + if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { + // 如果数据库中的同步ID与任务列表的最后修改时间匹配 + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // 两边都无更新,返回无操作类型(SYNC_ACTION_NONE的值未给出,应该是一个预定义的表示无操作的常量) + return SYNC_ACTION_NONE; + } else { + // 将远程数据应用到本地,返回更新本地操作类型(SYNC_ACTION_UPDATE_LOCAL的值未给出,应该是一个预定义的表示更新本地操作的常量) + return SYNC_ACTION_UPDATE_LOCAL; + } + } else { + // 验证gtask id是否匹配,即数据库游标中的gtask id与任务列表的ID(通过getGid()方法获取)是否相等 + if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { + Log.e(TAG, "gtask id doesn't match"); + return SYNC_ACTION_ERROR; + } + // 如果数据库中的同步ID与任务列表的最后修改时间匹配 + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // 仅有本地修改,返回更新远程操作类型(SYNC_ACTION_UPDATE_REMOTE的值未给出,应该是一个预定义的表示更新远程操作的常量) + return SYNC_ACTION_UPDATE_REMOTE; + } else { + // 对于文件夹冲突,直接应用本地修改(这里返回更新远程操作类型,可能与业务逻辑相关) + return SYNC_ACTION_UPDATE_REMOTE; + } + } + } catch (Exception e) { + // 如果发生任何异常,记录错误日志(包含异常信息) + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + } + // 如果在方法执行过程中出现错误,返回错误操作类型(SYNC_ACTION_ERROR的值未给出,应该是一个预定义的表示错误操作的常量) + return SYNC_ACTION_ERROR; + } + // 获取任务列表中Task对象的数量的方法 + public int getChildTaskCount() { + return mChildren.size(); + } + // 向任务列表中添加一个Task对象的方法。参数task是要添加的Task对象 + public boolean addChildTask(Task task) { + boolean ret = false; + // 如果task不为null且任务列表中不包含该task + if (task!= null &&!mChildren.contains(task)) { + // 将task添加到mChildren列表中,并将添加操作的结果赋值给ret + ret = mChildren.add(task); + if (ret) { + // 如果添加成功,需要设置task的前一个兄弟任务和父任务 + task.setPriorSibling(mChildren.isEmpty()? null : mChildren + .get(mChildren.size() - 1)); + task.setParent(this); + } + } + return ret; + } + // 在指定索引位置向任务列表中添加一个Task对象的方法。参数task是要添加的Task对象,index是添加的索引位置 + public boolean addChildTask(Task task, int index) { + // 如果索引小于0或者大于任务列表中Task对象的数量,记录错误日志并返回false + if (index < 0 || index > mChildren.size()) { + Log.e(TAG, "add child task: invalid index"); + return false; + } + int pos = mChildren.indexOf(task); + // 如果task不为null且任务列表中不包含该task(pos == -1表示不包含) + if (task!= null && pos == -1) { + // 在指定索引位置将task添加到mChildren列表中 + // 在指定索引位置向任务列表中添加任务task +mChildren.add(index, task); +// 更新任务列表 +// 初始化前一个任务为null +Task preTask = null; +// 初始化后一个任务为null +Task afterTask = null; +// 如果索引不为当前0,获取索引前一个位置的任务 +if (index!= 0) + preTask = mChildren.get(index - 1); +// 如果索引不是列表中最后一个位置,获取当前索引后一个位置的任务 +if (index!= mChildren.size() - 1) + afterTask = mChildren.get(index + 1); +// 设置task的前一个兄弟任务为preTask +task.setPriorSibling(preTask); +// 如果afterTask不为null,设置afterTask的前一个兄弟任务为task +if (afterTask!= null) + afterTask.setPriorSibling(task); +} +// 返回添加操作是否成功的结果 +return true; +} +// 从任务列表中移除指定的task的方法 +public boolean removeChildTask(Task task) { + // 用于记录移除操作是否成功,初始化为false + boolean ret = false; + // 获取task在mChildren列表中的索引 + int index = mChildren.indexOf(task); + // 如果索引不为 -1,表示task在列表中 + if (index!= -1) { + // 尝试从mChildren列表中移除task,并将结果赋值给ret + ret = mChildren.remove(task); + // 如果移除成功 + if (ret) { + // 重置task的前一个兄弟任务和父任务为null + task.setPriorSibling(null); + task.setParent(null); + // 更新任务列表 + // 如果索引不是列表的大小(即不是最后一个元素) + if (index!= mChildren.size()) { + // 获取当前索引位置的任务,并设置其前一个兄弟任务 + // 如果索引为0,则前一个兄弟任务为null,否则为前一个位置的任务 + mChildren.get(index).setPriorSibling( + index == 0? null : mChildren.get(index - 1)); + } + } + } + // 返回移除操作是否成功的结果 + return ret; +} +// 将指定的task移动到指定索引位置的方法 +public boolean moveChildTask(Task task, int index) { + // 如果索引小于0或者大于等于任务列表中任务的数量,记录错误日志并返回false + if (index < 0 || index >= mChildren.size()) { + Log.e(TAG, "move child task: invalid index"); + return false; + } + // 获取task在mChildren列表中的位置 + int pos = mChildren.indexOf(task); + // 如果task不在列表中,记录错误日志并返回false + if (pos == -1) { + Log.e(TAG, "move child task: the task should in the list"); + return false; + } + // 如果task当前位置和目标索引相同,直接返回true + if (pos == index) + return true; + // 先移除task,再在指定索引处添加task,并返回操作结果 + return (removeChildTask(task) && addChildTask(task, index)); +} +// 根据给定的gid在任务列表中查找任务的方法 +public Task findChildTaskByGid(String gid) { + // 遍历任务列表中的每个任务 + for (int i = 0; i < mChildren.size(); i++) { + // 获取当前索引位置的任务 + Task t = mChildren.get(i); + // 如果任务的gid与给定的gid相等,返回该任务 + if (t.getGid().equals(gid)) { + return t; + } + } + // 如果未找到匹配的任务,返回null + return null; +} +// 获取指定task在任务列表中的索引的方法 +public int getChildTaskIndex(Task task) { + // 通过mChildren列表的indexOf方法获取task的索引并返回 + return mChildren.indexOf(task); +} +// 根据给定索引获取任务列表中任务的方法 +public Task getChildTaskByIndex(int index) { + // 如果索引小于0或者大于等于任务列表中任务的数量,记录错误日志并返回null + if (index < 0 || index >= mChildren.size()) { + Log.e(TAG, "getTaskByIndex: invalid index"); + return null; + } + // 返回指定索引位置的任务 + return mChildren.get(index); +} +// 根据给定的gid在任务列表中查找任务的方法(与findChildTaskByGid功能类似) +public Task getChilTaskByGid(String gid) { + // 遍历任务列表中的每个任务 + for (Task task : mChildren) { + // 如果任务的gid与给定的gid相等,返回该任务 + if (task.getGid().equals(gid)) + return task; + } + // 如果未找到匹配的任务,返回null + return null; +} +// 获取任务列表中所有任务的列表(即mChildren)的方法 +public ArrayList getChildTaskList() { + return this.mChildren; +} +// 设置任务列表索引的方法,参数index为要设置的索引值 +public void setIndex(int index) { + this.mIndex = index; +} +// 获取任务列表索引的方法 +public int getIndex() { + return this.mIndex; +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/exception/ActionFailureException.java b/src/net/micode/notes/gtask/exception/ActionFailureException.java new file mode 100644 index 0000000..663f8aa --- /dev/null +++ b/src/net/micode/notes/gtask/exception/ActionFailureException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// 版权声明:2010-2011年,MiCode开源社区(www.micode.net) +// 根据Apache License 2.0(“许可证”)授权; +// 除非遵守许可证,否则不得使用此文件。 +// 您可以在以下网址获得许可证: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 除非适用法律要求或书面同意,根据许可证分发的软件按“原样”分发, +// 不提供任何明示或暗示的保证或条件。 +// 请参阅控制权限和限制的许可证具体语言。 +package net.micode.notes.gtask.exception; +// 定义包名为net.micode.notes.gtask.exception。 +public class ActionFailureException extends RuntimeException { + private static final long serialVersionUID = 4425249765923293627L; + // 定义一个公共类ActionFailureException,它继承自RuntimeException。 + // 定义一个私有的静态最终字段serialVersionUID,值为4425249765923293627L。 + + public ActionFailureException() { + super(); + } + // 定义ActionFailureException类的无参数构造函数,调用父类的无参数构造函数。 + public ActionFailureException(String paramString) { + super(paramString); + } + // 定义ActionFailureException类的一个构造函数,接收一个字符串参数paramString,并将其传递给父类的构造函数。 + public ActionFailureException(String paramString, Throwable paramThrowable) { + super(paramString, paramThrowable); + } + // 定义ActionFailureException类的一个构造函数,接收一个字符串参数paramString和一个Throwable类型的参数paramThrowable,并将它们传递给父类的构造函数。 +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/exception/NetworkFailureException.java b/src/net/micode/notes/gtask/exception/NetworkFailureException.java new file mode 100644 index 0000000..8acef4a --- /dev/null +++ b/src/net/micode/notes/gtask/exception/NetworkFailureException.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// 版权声明:2010-2011年,MiCode开源社区(www.micode.net) +// 根据Apache License 2.0(“许可证”)授权; +// 除非遵守许可证,否则不得使用此文件。 +// 您可以在以下网址获得许可证: +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// 除非适用法律要求或书面同意,根据许可证分发的软件按“原样”分发, +// 不提供任何明示或暗示的保证或条件。 +// 请参阅控制权限和限制的许可证具体语言。 +package net.micode.notes.gtask.exception; +// 定义包名为net.micode.notes.gtask.exception。 +public class NetworkFailureException extends Exception { + private static final long serialVersionUID = 2107610287180234136L; + // 定义一个公共类NetworkFailureException,它继承自Exception。 + // 定义一个私有的静态最终字段serialVersionUID,值为2107610287180234136L。 + public NetworkFailureException() { + super(); + } + // 定义NetworkFailureException类的无参数构造函数,调用父类的无参数构造函数。 + public NetworkFailureException(String paramString) { + super(paramString); + } + // 定义NetworkFailureException类的一个构造函数,接收一个字符串参数paramString,并将其传递给父类的构造函数。 + public NetworkFailureException(String paramString, Throwable paramThrowable) { + super(paramString, paramThrowable); + } + // 定义NetworkFailureException类的一个构造函数,接收一个字符串参数paramString和一个Throwable类型的参数paramThrowable,并将它们传递给父类的构造函数。 +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/remote/GTaskASyncTask.java b/src/net/micode/notes/gtask/remote/GTaskASyncTask.java new file mode 100644 index 0000000..cc40dda --- /dev/null +++ b/src/net/micode/notes/gtask/remote/GTaskASyncTask.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * 版权所有,MiCode开源社区。 + */ +/** + * Licensed under the Apache License, Version 2.0 (the "License"); + * 根据Apache License 2.0版本(“许可证”)授权; + */ +/** + * you may not use this file except in compliance with the License. + * 除非遵守许可证,否则不得使用此文件。 + */ +/** + * You may obtain a copy of the License at + * 你可以在以下网址获得许可证的副本: + */ +/** + * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + */ +/** + * Unless required by applicable law or agreed to in writing, software + * 除非适用法律要求或书面同意,软件 + */ +/** + * distributed under the License is distributed on an "AS IS" BASIS, + * 根据许可证分发的软件是基于“按原样”的基础上分发, + */ +/** + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * 没有任何形式的担保或条件,无论是明示或暗示。 + */ + /** + * See the License for the specific language governing permissions and + * 请参阅控制权限和 + */ +/** + * limitations under the License. + * 许可证下的限制。 + */ +package net.micode.notes.gtask.remote; // 定义类的包名。 +import android.app.Notification; // 导入Notification类。 +import android.app.NotificationManager; // 导入NotificationManager类。 +import android.app.PendingIntent; // 导入PendingIntent类。 +import android.content.Context; // 导入Context类。 +import android.content.Intent; // 导入Intent类。 +import android.os.AsyncTask; // 导入AsyncTask类。 +import net.micode.notes.R; // 导入应用资源。 +import net.micode.notes.ui.NotesListActivity; // 导入NotesListActivity。 +import net.micode.notes.ui.NotesPreferenceActivity; // 导入NotesPreferenceActivity。 +public class GTaskASyncTask extends AsyncTask { // 定义异步任务类。 + private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; // 同步通知的ID。 + + public interface OnCompleteListener { // 定义任务完成的监听器接口。 + void onComplete(); // 当任务完成时调用的方法。 + } + private Context mContext; // 存储上下文对象。 + private NotificationManager mNotifiManager; // 存储通知管理器对象。 + private GTaskManager mTaskManager; // 存储GTask管理器对象。 + private OnCompleteListener mOnCompleteListener; // 存储任务完成监听器对象。 + + public GTaskASyncTask(Context context, OnCompleteListener listener) { // 构造函数。 + mContext = context; // 初始化上下文对象。 + mOnCompleteListener = listener; // 初始化任务完成监听器对象。 + mNotifiManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); // 获取通知管理器服务。 + mTaskManager = GTaskManager.getInstance(); // 获取GTask管理器实例。 + } + + public void cancelSync() { // 提供取消同步的方法。 + mTaskManager.cancelSync(); // 调用GTask管理器的取消同步方法。 + } + public void publishProgess(String message) { // 提供发布进度的方法。 + publishProgress(new String[] { message }); // 发布包含消息的进度。 + } + private void showNotification(int tickerId, String content) { // 私有方法,显示通知。 + Notification notification = new Notification(R.drawable.notification, mContext.getString(tickerId), System.currentTimeMillis()); // 创建通知对象。 + notification.defaults = Notification.DEFAULT_LIGHTS; // 设置通知的默认灯光。 + notification.flags = Notification.FLAG_AUTO_CANCEL; // 设置通知的自动取消标志。 + PendingIntent pendingIntent; // 声明PendingIntent对象。 + if (tickerId != R.string.ticker_success) { // 如果通知ID不是成功ID。 + pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesPreferenceActivity.class), 0); // 创建PendingIntent,指向设置活动。 + } else { // 否则。 + pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesListActivity.class), 0); // 创建PendingIntent,指向笔记列表活动。 + } + notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, pendingIntent); // 设置通知的最新事件信息。 + mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); // 发送通知。 + } + @Override + protected Integer doInBackground(Void... unused) { // 重写doInBackground方法,执行后台任务。 + publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity.getSyncAccountName(mContext))); // 发布登录进度。 + return mTaskManager.sync(mContext, this); // 执行同步操作并返回结果。 + } + @Override + protected void onProgressUpdate(String... progress) { // 重写onProgressUpdate方法,处理进度更新。 + showNotification(R.string.ticker_syncing, progress[0]); // 显示同步中的通知。 + if (mContext instanceof GTaskSyncService) { // 如果上下文是GTaskSyncService的实例。 + ((GTaskSyncService) mContext).sendBroadcast(progress[0]); // 发送进度广播。 + } + } + @Override + protected void onPostExecute(Integer result) { // 重写onPostExecute方法,处理任务执行完毕后的操作。 + if (result == GTaskManager.STATE_SUCCESS) { // 如果结果是成功的。 + showNotification(R.string.ticker_success, mContext.getString(R.string.success_sync_account, mTaskManager.getSyncAccount())); // 显示成功通知。 + NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); // 设置最后同步时间。 + } else if (result == GTaskManager.STATE_NETWORK_ERROR) { // 如果结果是网络错误的。 + showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); // 显示网络错误通知。 + } else if (result == GTaskManager.STATE_INTERNAL_ERROR) { // 如果结果是内部错误的。 + showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); // 显示内部错误通知。 + } else if (result == GTaskManager.STATE_SYNC_CANCELLED) { // 如果结果是同步被取消的。 + showNotification(R.string.ticker_cancel, mContext.getString(R.string.error_sync_cancelled)); // 显示同步取消通知。 + } + if (mOnCompleteListener != null) { // 如果有完成监听器。 + new Thread(new Runnable() { // 创建新线程。 + public void run() { // 运行任务完成监听器的onComplete方法。 + mOnCompleteListener.onComplete(); // 调用onComplete方法。 + } + }).start(); // 启动线程。 + } + } +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/remote/GTaskClient.java b/src/net/micode/notes/gtask/remote/GTaskClient.java new file mode 100644 index 0000000..8d3d069 --- /dev/null +++ b/src/net/micode/notes/gtask/remote/GTaskClient.java @@ -0,0 +1,163 @@ +/* + * 版权所有 (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * 根据Apache License, Version 2.0(“许可证”)授权; + * 你可以在遵守许可证的情况下使用此文件。 + * 你可以在以下网址获得许可证的副本: + * http://www.apache.org/licenses/LICENSE-2.0 + * 除非适用法律要求或书面同意,否则按“原样”分发的软件 + * 没有任何形式的担保或条件,无论是明示或暗示。 + * 请参阅控制权限和 + * 许可证下的限制。 + */ +package net.micode.notes.gtask.remote; // 定义包名 +import android.accounts.Account; // 导入Account类 +import android.accounts.AccountManager; // 导入AccountManager类 +import android.accounts.AccountManagerFuture; // 导入AccountManagerFuture类 +import android.app.Activity; // 导入Activity类 +import android.os.Bundle; // 导入Bundle类 +import android.text.TextUtils; // 导入TextUtils类 +import android.util.Log; // 导入Log类 +import net.micode.notes.gtask.data.Node; // 导入Node类 +import net.micode.notes.gtask.data.Task; // 导入Task类 +import net.micode.notes.gtask.data.TaskList; // 导入TaskList类 +import net.micode.notes.gtask.exception.ActionFailureException; // 导入ActionFailureException类 +import net.micode.notes.gtask.exception.NetworkFailureException; // 导入NetworkFailureException类 +import net.micode.notes.tool.GTaskStringUtils; // 导入GTaskStringUtils工具类 +import net.micode.notes.ui.NotesPreferenceActivity; // 导入NotesPreferenceActivity类 +import org.apache.http.HttpEntity; // 导入HttpEntity类 +import org.apache.http.HttpResponse; // 导入HttpResponse类 +import org.apache.http.client.ClientProtocolException; // 导入ClientProtocolException类 +import org.apache.http.client.entity.UrlEncodedFormEntity; // 导入UrlEncodedFormEntity类 +import org.apache.http.client.methods.HttpGet; // 导入HttpGet类 +import org.apache.http.client.methods.HttpPost; // 导入HttpPost类 +import org.apache.http.cookie.Cookie; // 导入Cookie类 +import org.apache.http.impl.client.BasicCookieStore; // 导入BasicCookieStore类 +import org.apache.http.impl.client.DefaultHttpClient; // 导入DefaultHttpClient类 +import org.apache.http.message.BasicNameValuePair; // 导入BasicNameValuePair类 +import org.apache.http.params.BasicHttpParams; // 导入BasicHttpParams类 +import org.apache.http.params.HttpConnectionParams; // 导入HttpConnectionParams类 +import org.apache.http.params.HttpParams; // 导入HttpParams类 +import org.apache.http.params.HttpProtocolParams; // 导入HttpProtocolParams类 +import org.json.JSONArray; // 导入JSONArray类 +import org.json.JSONException; // 导入JSONException类 +import org.json.JSONObject; // 导入JSONObject类 +import java.io.BufferedReader; // 导入BufferedReader类 +import java.io.IOException; // 导入IOException类 +import java.io.InputStream; // 导入InputStream类 +import java.io.InputStreamReader; // 导入InputStreamReader类 +import java.util.LinkedList; // 导入LinkedList类 +import java.util.List; // 导入List类 +import java.util.zip.GZIPInputStream; // 导入GZIPInputStream类 +import java.util.zip.Inflater; // 导入Inflater类 +import java.util.zip.InflaterInputStream; // 导入InflaterInputStream类 +public class GTaskClient { + private static final String TAG = GTaskClient.class.getSimpleName(); // TAG用于日志记录 + private static final String GTASK_URL = "https://mail.google.com/tasks/"; // Google 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"; // 提交任务更新的URL + private static GTaskClient mInstance = null; // GTaskClient的单例对象 + private DefaultHttpClient mHttpClient; // HTTP客户端 + private String mGetUrl; // GET请求的URL + private String mPostUrl; // POST请求的URL + private long mClientVersion; // 客户端版本号 + private boolean mLoggedin; // 是否已经登录 + private long mLastLoginTime; // 上次登录时间 + private int mActionId; // 动作ID + private Account mAccount; // 当前使用的账户 + private JSONArray mUpdateArray; // 更新数组 + private GTaskClient() { + mHttpClient = null; // 初始化HTTP客户端为null + mGetUrl = GTASK_GET_URL; // 设置GET请求的URL + mPostUrl = GTASK_POST_URL; // 设置POST请求的URL + mClientVersion = -1; // 初始化客户端版本号为-1 + mLoggedin = false; // 初始化登录状态为false + mLastLoginTime = 0; // 初始化上次登录时间为0 + mActionId = 1; // 初始化动作ID为1 + mAccount = null; // 初始化账户为null + mUpdateArray = null; // 初始化更新数组为null + } + public static synchronized GTaskClient getInstance() { // 获取GTaskClient实例的方法 + if (mInstance == null) { // 如果实例为null + mInstance = new GTaskClient(); // 创建新的GTaskClient实例 + } + return mInstance; // 返回GTaskClient实例 + } + public boolean login(Activity activity) { // 登录方法 + // 假设cookie在5分钟后过期,需要重新登录 + final long interval = 1000 * 60 * 5; + if (mLastLoginTime + interval < System.currentTimeMillis()) { + mLoggedin = false; // 更新登录状态为false + } + // 账户切换后需要重新登录 + if (mLoggedin + && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity + .getSyncAccountName(activity))) { + mLoggedin = false; // 更新登录状态为false + } + if (mLoggedin) { // 如果已经登录 + Log.d(TAG, "already logged in"); // 打印日志 + return true; // 返回true + } + mLastLoginTime = System.currentTimeMillis(); // 更新上次登录时间为当前时间 + String authToken = loginGoogleAccount(activity, false); // 获取Google账户的认证令牌 + if (authToken == null) { // 如果认证令牌为null + Log.e(TAG, "login google account failed"); // 打印错误日志 + return false; // 返回false + } + // 如有必要,使用自定义域名登录 + if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() + .endsWith("googlemail.com"))) { + StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); // 构建URL + int index = mAccount.name.indexOf('@') + 1; // 获取域名的起始位置 + String suffix = mAccount.name.substring(index); // 获取域名 + url.append(suffix + "/"); // 添加域名到URL + mGetUrl = url.toString() + "ig"; // 设置GET请求的URL + mPostUrl = url.toString() + "r/ig"; // 设置POST请求的URL + + if (tryToLoginGtask(activity, authToken)) { // 尝试登录Gtask + mLoggedin = true; // 更新登录状态为true + } + } + // 尝试使用Google官方URL登录 + if (!mLoggedin) { // 如果还未登录 + mGetUrl = GTASK_GET_URL; // 设置GET请求的URL为官方URL + mPostUrl = GTASK_POST_URL; // 设置POST请求的URL为官方URL + if (!tryToLoginGtask(activity, authToken)) { // 尝试登录Gtask + return false; // 如果登录失败,返回false + } + } + mLoggedin = true; // 更新登录状态为true + return true; // 返回true + } + private String loginGoogleAccount(Activity activity, boolean invalidateToken) { // 获取Google账户的认证令牌 + String authToken; // 认证令牌 + AccountManager accountManager = AccountManager.get(activity); // 获取AccountManager实例 + Account[] accounts = accountManager.getAccountsByType("com.google"); // 获取所有Google账户 + + if (accounts.length == 0) { // 如果没有Google账户 + Log.e(TAG, "there is no available google account"); // 打印错误日志 + return null; // 返回null + } + String accountName = NotesPreferenceActivity.getSyncAccountName(activity); // 获取同步账户名称 + Account account = null; // 声明账户变量 + for (Account a : accounts) { // 遍历所有账户 + if (a.name.equals(accountName)) { // 如果找到匹配的账户 + account = a; // 设置找到的账户 + break; // 退出循环 + } + } + if (account != null) { // 如果找到账户 + mAccount = account; // 设置当前账户 + } else { // 如果未找到账户 + Log.e(TAG, e.toString()); // 打印错误日志,记录异常信息 + e.printStackTrace(); // 输出异常的堆栈跟踪信息 + throw new ActionFailureException("get task list: handing jsonobject failed"); // 抛出ActionFailureException,表示处理JSONObject失败 + } + } + public Account getSyncAccount() { // 获取同步账户的方法 + return mAccount; // 返回当前账户 + } + public void resetUpdateArray() { // 重置更新数组的方法 + mUpdateArray = null; // 将更新数组设置为null + } +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/remote/GTaskManager.java b/src/net/micode/notes/gtask/remote/GTaskManager.java new file mode 100644 index 0000000..f142b81 --- /dev/null +++ b/src/net/micode/notes/gtask/remote/GTaskManager.java @@ -0,0 +1,882 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.mcode.net) + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// 版权所有 (c) 2010-2011,MiCode开源社区(www.mcode.net) +// 根据Apache License, Version 2.0(“许可证”)授权; +// 除非遵守许可证,否则不得使用此文件。 +// 你可以在以下网址获得许可证的副本: +// http://www.apache.org/licenses/LICENSE-2.0 +// 除非适用法律要求或书面同意,否则按“原样”分发的软件 +// 没有明示或暗示的任何形式的保证,包括但不限于对适销性、特定用途适用性 +// 的暗示保证。请参阅许可证,了解有关权限和限制的具体语言控制。 +package net.micode.notes.gtask.remote; +// 这是定义GTaskManager类的包 +import android.app.Activity; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.util.Log; +// 导入Android SDK中用于数据库操作和日志记录的类 +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.data.MetaData; +import net.micode.notes.gtask.data.Node; +import net.micode.notes.gtask.data.SqlNote; +import net.micode.notes.gtask.data.Task; +import net.micode.notes.gtask.data.TaskList; +import net.micode.notes.gtask.exception.ActionFailureException; +import net.micode.notes.gtask.exception.NetworkFailureException; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.GTaskStringUtils; +// 导入项目中定义的数据模型和工具类 +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +// 导入Java标准库中的集合框架,用于存储和管理数据 +public class GTaskManager { + private static final String TAG = GTaskManager.class.getSimpleName(); + // 定义一个常量TAG,用于日志输出时标识类名,通过getSimpleName()获取类名字符串 + public static final int STATE_SUCCESS = 0; + public static final int STATE_NETWORK_ERROR = 1; + public static final int STATE_INTERNAL_ERROR = 2; + public static final int STATE_SYNC_IN_PROGRESS = 3; + public static final int STATE_SYNC_CANCELLED = 4; + // 定义同步状态的常量,用于表示同步操作的结果 + private static GTaskManager mInstance = null; + // 单例模式的实例变量,用于持有GTaskManager类的唯一实例 + private Activity mActivity; + // 定义一个Activity类型的变量,用于存储当前的Activity上下文 + private Context mContext; + // 定义一个Context类型的变量,用于存储当前的应用程序上下文 + private ContentResolver mContentResolver; + // 定义一个ContentResolver类型的变量,用于访问和管理数据库中的数据 + private boolean mSyncing; + // 定义一个布尔类型的变量,用于标记是否正在执行同步操作 + private boolean mCancelled; + // 定义一个布尔类型的变量,用于标记同步操作是否被取消 + private HashMap mGTaskListHashMap; + // 定义一个HashMap,用于存储任务列表,键是任务列表的ID,值是TaskList对象 + private HashMap mGTaskHashMap; + // 定义一个HashMap,用于存储任务节点,键是任务节点的ID,值是Node对象 + private HashMap mMetaHashMap; + // 定义一个HashMap,用于存储元数据,键是元数据的ID,值是MetaData对象 + private TaskList mMetaList; + // 定义一个TaskList类型的变量,用于存储元数据列表 + private HashSet mLocalDeleteIdMap; + // 定义一个HashSet,用于存储本地删除的ID + private HashMap mGidToNid; + // 定义一个HashMap,用于存储全局ID到本地ID的映射 + private HashMap mNidToGid; + // 定义一个HashMap,用于存储本地ID到全局ID的映射 + private GTaskManager() { + mSyncing = false; + mCancelled = false; + mGTaskListHashMap = new HashMap(); + mGTaskHashMap = new HashMap(); + mMetaHashMap = new HashMap(); + mMetaList = null; + mLocalDeleteIdMap = new HashSet(); + mGidToNid = new HashMap(); + mNidToGid = new HashMap(); + } + // 私有构造函数,初始化所有成员变量 + public static synchronized GTaskManager getInstance() { + if (mInstance == null) { + mInstance = new GTaskManager(); + } + return mInstance; + } + // 公共静态方法,用于获取GTaskManager类的唯一实例,实现单例模式 + public synchronized void setActivityContext(Activity activity) { + mActivity = activity; + } + // 公共同步方法,用于设置当前的Activity上下文 + public int sync(Context context, GTaskASyncTask asyncTask) { + if (mSyncing) { + Log.d(TAG, "Sync is in progress"); + return STATE_SYNC_IN_PROGRESS; + } + mContext = context; + mContentResolver = mContext.getContentResolver(); + mSyncing = true; + mCancelled = false; + mGTaskListHashMap.clear(); + mGTaskHashMap.clear(); + mMetaHashMap.clear(); + mLocalDeleteIdMap.clear(); + mGidToNid.clear(); + mNidToGid.clear(); + try { + GTaskClient client = GTaskClient.getInstance(); + client.resetUpdateArray(); + + // 登录Google任务 + + if (!mCancelled) { + if (!client.login(mActivity)) { + throw new NetworkFailureException("login google task failed"); + } + } + // 从Google获取任务列表 + asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); + initGTaskList(); + // 执行内容同步工作 + asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); + syncContent(); + } catch (NetworkFailureException e) { + Log.e(TAG, e.toString()); + return STATE_NETWORK_ERROR; + } catch (ActionFailureException e) { + Log.e(TAG, e.toString()); + return STATE_INTERNAL_ERROR; + } catch (Exception e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + return STATE_INTERNAL_ERROR; + } finally { + mGTaskListHashMap.clear(); + mGTaskHashMap.clear(); + mMetaHashMap.clear(); + mLocalDeleteIdMap.clear(); + mGidToNid.clear(); + mNidToGid.clear(); + mSyncing = false; + } + + return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; + } + // 公共同步方法,用于执行同步操作,接收上下文和异步任务对象,返回同步状态 + + private void initGTaskList() throws NetworkFailureException { + if (mCancelled) + return; + GTaskClient client = GTaskClient.getInstance(); + try { + JSONArray jsTaskLists = client.getTaskLists(); + // 初始化元数据列表 + mMetaList = null; + for (int i = 0; i < jsTaskLists.length(); i++) { + JSONObject object = jsTaskLists.getJSONObject(i); + String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); + String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); + if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + mMetaList = new TaskList(); + mMetaList.setContentByRemoteJSON(object); + // 加载元数据 + JSONArray jsMetas = client.getTaskList(gid); + for (int j = 0; j < jsMetas.length(); j++) { + object = (JSONObject) jsMetas.getJSONObject(j); + MetaData metaData = new MetaData(); + metaData.setContentByRemoteJSON(object); + if (metaData.isWorthSaving()) { + mMetaList.addChildTask(metaData); + if (metaData.getGid() != null) { + mMetaHashMap.put(metaData.getRelatedGid(), metaData); + } + } + } + } + } + // 如果不存在,则创建元数据列表 + if (mMetaList == null) { + mMetaList = new TaskList(); + mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_META); + GTaskClient.getInstance().createTaskList(mMetaList); +} +// 如果mMetaList是空的,那么创建一个新的任务列表mMetaList,并设置其名称为MIUI_FOLDER_PREFFIX加上FOLDER_META。 +// 然后使用GTaskClient的实例创建这个任务列表。 +// 初始化任务列表 +for (int i = 0; i < jsTaskLists.length(); i++) { + JSONObject object = jsTaskLists.getJSONObject(i); + String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); + String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); + if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) + && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_META)) { + TaskList tasklist = new TaskList(); + tasklist.setContentByRemoteJSON(object); + mGTaskListHashMap.put(gid, tasklist); + mGTaskHashMap.put(gid, tasklist); + // 加载任务 + JSONArray jsTasks = client.getTaskList(gid); + for (int j = 0; j < jsTasks.length(); j++) { + object = (JSONObject) jsTasks.getJSONObject(j); + gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); + Task task = new Task(); + task.setContentByRemoteJSON(object); + if (task.isWorthSaving()) { + task.setMetaInfo(mMetaHashMap.get(gid)); + tasklist.addChildTask(task); + mGTaskHashMap.put(gid, task); + } + } + } +} +// 捕获JSONException异常 +} catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("initGTaskList: handing JSONObject failed"); +} +} +private void syncContent() throws NetworkFailureException { + int syncType; + Cursor c = null; + String gid; + Node node; + mLocalDeleteIdMap.clear(); + if (mCancelled) { + return; + } + // 对于本地已删除的笔记 + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type<>? AND parent_id=?)", new String[] { + String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) + }, null); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); + } + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); + } + } else { + Log.w(TAG, "failed to query trash folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + // 首先同步文件夹 + syncFolder(); + // 对于数据库中已存在的笔记 + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type=? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); + syncType = node.getSyncAction(c); + } else { + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + // 本地添加 + syncType = Node.SYNC_ACTION_ADD_REMOTE; + } else { + // 远程删除 + syncType = Node.SYNC_ACTION_DEL_LOCAL; + } + } + doContentSync(syncType, node, c); + } + } else { + Log.w(TAG, "failed to query existing note in database"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + // 遍历剩余项 + Iterator> iter = mGTaskHashMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + node = entry.getValue(); + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + } + // mCancelled可以被另一个线程设置,因此我们需要逐个检查 + // 清空本地删除表 + if (!mCancelled) { + if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { + throw new ActionFailureException("failed to batch-delete local deleted notes"); + } + } + // 刷新本地同步ID + if (!mCancelled) { + GTaskClient.getInstance().commitUpdate(); + refreshLocalSyncId(); + } +} +private void syncFolder() throws NetworkFailureException { + Cursor c = null; + String gid; + Node node; + int syncType; + if (mCancelled) { + return; + } + // 对于根文件夹 + try { + c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, + Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); + if (c != null) { + c.moveToNext(); + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); + mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); + // 对于系统文件夹,只有在必要时才更新远程名称 + if (!node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) + doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); + } else { + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); + } + } else { + Log.w(TAG, "failed to query root folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + // 对于通话记录文件夹 + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", + new String[] { + String.valueOf(Notes.ID_CALL_RECORD_FOLDER) + }, null); + if (c != null) { + if (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); + mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); + // 对于系统文件夹,只有在必要时才更新远程名称 + if (!node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_CALL_NOTE)) + doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); + } else { + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); + } + } + } else { + Log.w(TAG, "failed to query call note folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + // 对于本地已存在的文件夹 + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type=? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); + syncType = node.getSyncAction(c); + } else { + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + // 本地添加操作 + syncType = Node.SYNC_ACTION_ADD_REMOTE; +} else { + // 远程删除操作 + syncType = Node.SYNC_ACTION_DEL_LOCAL; +} +doContentSync(syncType, node, c); // 执行同步操作 +} else { + Log.w(TAG, "failed to query existing note in database"); // 如果查询数据库中的现有笔记失败,则记录警告日志 +} +} finally { + if (c != null) { + c.close(); + c = null; + } +} // 确保在finally代码块中关闭游标c并释放资源 + +// 遍历剩余项 +Iterator> iter = mGTaskHashMap.entrySet().iterator(); +while (iter.hasNext()) { + Map.Entry entry = iter.next(); + node = entry.getValue(); + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 对于剩余的节点,执行同步操作,将节点添加到本地 +} +// 如果mCancelled可以被另一个线程设置,因此我们需要逐个检查 +// 清空本地删除表 +if (!mCancelled) { + if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { + throw new ActionFailureException("failed to batch-delete local deleted notes"); // 如果同步未被取消,尝试批量删除本地已删除的笔记,如果失败则抛出异常 + } +} +// 刷新本地同步ID +if (!mCancelled) { + GTaskClient.getInstance().commitUpdate(); + refreshLocalSyncId(); // 如果同步未被取消,提交更新并刷新本地同步ID +} +private void syncFolder() throws NetworkFailureException { + Cursor c = null; + String gid; + Node node; + int syncType; + + if (mCancelled) { + return; // 如果同步已被取消,则直接返回 + } + // 对于根文件夹 + try { + c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, + Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); + if (c != null) { + c.moveToNext(); + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); + mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); + // 对于系统文件夹,只有在必要时才更新远程名称 + if (!node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) + doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); // 如果名称不是默认值,则执行远程更新同步操作 + } else { + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); // 如果节点为空,则执行远程添加同步操作 + } + } else { + Log.w(TAG, "failed to query root folder"); // 如果查询根文件夹失败,记录警告日志 + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } // 确保在finally代码块中关闭游标c并释放资源 +// 对于通话记录文件夹 +try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", + new String[] { + String.valueOf(Notes.ID_CALL_RECORD_FOLDER) + }, null); + if (c != null) { + if (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); + mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); + // 对于系统文件夹,只有在必要时才更新远程名称 + if (!node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_CALL_NOTE)) + doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); + } else { + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); + } + } + } else { + Log.w(TAG, "failed to query call note folder"); + } +} finally { + if (c != null) { + c.close(); + c = null; + } +} +// 对于本地已存在的文件夹 +try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type=? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); + syncType = node.getSyncAction(c); + } else { + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + // 本地添加 + syncType = Node.SYNC_ACTION_ADD_REMOTE; + } else { + // 远程删除 + syncType = Node.SYNC_ACTION_DEL_LOCAL; + } + } + doContentSync(syncType, node, c); + } + } else { + Log.w(TAG, "failed to query existing folder"); + } +} finally { + if (c != null) { + c.close(); + c = null; + } +} +// 对于远程添加的文件夹 +Iterator> iter = mGTaskListHashMap.entrySet().iterator(); +while (iter.hasNext()) { + Map.Entry entry = iter.next(); + gid = entry.getKey(); + node = entry.getValue(); + if (mGTaskHashMap.containsKey(gid)) { + mGTaskHashMap.remove(gid); + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + } +} +// 如果没有被取消,则提交更新 +if (!mCancelled) + GTaskClient.getInstance().commitUpdate(); +} +private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; // 如果同步已被取消,则直接返回。 + } + MetaData meta; + switch (syncType) { + case Node.SYNC_ACTION_ADD_LOCAL: + addLocalNode(node); // 添加本地节点。 + break; + case Node.SYNC_ACTION_ADD_REMOTE: + addRemoteNode(node, c); // 添加远程节点。 + break; + case Node.SYNC_ACTION_DEL_LOCAL: + meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); // 获取元数据。 + if (meta != null) { + GTaskClient.getInstance().deleteNode(meta); // 删除远程节点。 + } + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 添加到本地删除ID映射。 + break; + case Node.SYNC_ACTION_DEL_REMOTE: + meta = mMetaHashMap.get(node.getGid()); + if (meta != null) { + GTaskClient.getInstance().deleteNode(meta); // 删除远程节点。 + } + GTaskClient.getInstance().deleteNode(node); // 删除远程节点。 + break; + case Node.SYNC_ACTION_UPDATE_LOCAL: + updateLocalNode(node, c); // 更新本地节点。 + break; + case Node.SYNC_ACTION_UPDATE_REMOTE: + updateRemoteNode(node, c); // 更新远程节点。 + break; + case Node.SYNC_ACTION_UPDATE_CONFLICT: + // 合并修改可能是一个好主意,但现在我们简单地使用本地更新。 + updateRemoteNode(node, c); + break; + case Node.SYNC_ACTION_NONE: + break; + case Node.SYNC_ACTION_ERROR: + default: + throw new ActionFailureException("unknown sync action type"); // 抛出未知同步操作类型的异常。 + } +} +private void addLocalNode(Node node) throws NetworkFailureException { + if (mCancelled) { + return; // 如果同步已被取消,则直接返回。 + } + SqlNote sqlNote; + if (node instanceof TaskList) { + if (node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { + sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); // 创建根文件夹的SqlNote。 + } else if (node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { + sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); // 创建通话记录文件夹的SqlNote。 + } else { + sqlNote = new SqlNote(mContext); // 创建SqlNote。 + sqlNote.setContent(node.getLocalJSONFromContent()); // 设置内容。 + sqlNote.setParentId(Notes.ID_ROOT_FOLDER); // 设置父ID为根文件夹。 + } + } else { + sqlNote = new SqlNote(mContext); // 创建SqlNote。 + JSONObject js = node.getLocalJSONFromContent(); // 获取本地JSON内容。 + try { + if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取笔记。 + if (note.has(NoteColumns.ID)) { + long id = note.getLong(NoteColumns.ID); // 获取ID。 + if (DataUtils.existInNoteDatabase(mContentResolver, id)) { // 检查ID是否存在。 + // ID不可用,必须创建一个新的。 + note.remove(NoteColumns.ID); + } + } + } + if (js.has(GTaskStringUtils.META_HEAD_DATA)) { + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取数据数组。 + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); // 获取数据。 + if (data.has(DataColumns.ID)) { + long dataId = data.getLong(DataColumns.ID); // 获取数据ID。 + if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { // 检查数据ID是否存在。 + // 数据ID不可用,必须创建一个新的。 + data.remove(DataColumns.ID); + } + } + } + } + } catch (JSONException e) { + Log.w(TAG, e.toString()); // 记录警告日志。 + e.printStackTrace(); // 打印堆栈跟踪。 + } + sqlNote.setContent(js); // 设置SqlNote的内容。 + + Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); // 获取父ID。 + if (parentId == null) { + Log.e(TAG, "cannot find task's parent id locally"); // 记录错误日志。 + throw new ActionFailureException("cannot add local node"); // 抛出异常。 + } + sqlNote.setParentId(parentId.longValue()); // 设置SqlNote的父ID。 + } + // 创建本地节点。 + sqlNote.setGtaskId(node.getGid()); // 设置SqlNote的GTask ID。 + sqlNote.commit(false); // 提交SqlNote。 + + // 更新gid-nid映射。 + mGidToNid.put(node.getGid(), sqlNote.getId()); // 将GTask ID映射到本地ID。 + mNidToGid.put(sqlNote.getId(), node.getGid()); // 将本地ID映射到GTask ID。 + // 更新元数据。 + updateRemoteMeta(node.getGid(), sqlNote); // 更新远程元数据。 +} +private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; // 如果同步已被取消,则直接返回。 + } + SqlNote sqlNote; // 创建SqlNote。 + sqlNote = new SqlNote(mContext, c); // 从游标c创建SqlNote。 + sqlNote.setContent(node.getLocalJSONFromContent()); // 设置SqlNote的内容。 + Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) : new Long(Notes.ID_ROOT_FOLDER); // 获取父ID。 + if (parentId == null) { + Log.e(TAG, "cannot find task's parent id locally"); // 记录错误日志。 + throw new ActionFailureException("cannot update local node"); // 抛出异常。 + } + sqlNote.setParentId(parentId.longValue()); // 设置SqlNote的父ID。 + sqlNote.commit(true); // 提交SqlNote。 + // 更新元数据信息。 + updateRemoteMeta(node.getGid(), sqlNote); // 更新远程元数据。 +} +private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; // 如果同步已被取消,则直接返回。 + } + SqlNote sqlNote = new SqlNote(mContext, c); // 从游标c创建SqlNote。 + Node n; + // 远程更新。 + if (sqlNote.isNoteType()) { + Task task = new Task(); // 创建任务。 + task.setContentByLocalJSON(sqlNote.getContent()); // 设置任务内容。 + String parentGid = mNidToGid.get(sqlNote.getParentId()); // 获取父GID。 + if (parentGid == null) { + Log.e(TAG, "cannot find task's parent tasklist"); // 记录错误日志。 + throw new ActionFailureException("cannot add remote task"); // 抛出异常。 + } + mGTaskListHashMap.get(parentGid).addChildTask(task); // 添加任务到任务列表。 + GTaskClient.getInstance().createTask(task); // 创建远程任务。 + n = (Node) task; // 转换为节点。 + // 添加元数据。 + updateRemoteMeta(task.getGid(), sqlNote); // 更新远程元数据。 + } else { + TaskList tasklist = null; // 创建任务列表。 + // 如果文件夹已经存在,则跳过。 + String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; // 获取文件夹名称。 + if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) + folderName += GTaskStringUtils.FOLDER_DEFAULT; + else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER) + folderName += GTaskStringUtils.FOLDER_CALL_NOTE; + else + folderName += sqlNote.getSnippet(); + Iterator> iter = mGTaskListHashMap.entrySet().iterator(); // 迭代任务列表映射。 + while (iter.hasNext()) { + Map.Entry entry = iter.next(); // 获取条目。 + String gid = entry.getKey(); // 获取GID。 + TaskList list = entry.getValue(); // 获取任务列表。 + if (list.getName().equals(folderName)) { // 如果任务列表名称匹配。 + tasklist = list; // 设置任务列表。 + if (mGTaskHashMap.containsKey(gid)) { + mGTaskHashMap.remove(gid); // 从映射中移除。 + } + break; // 跳出循环。 + } + } + // 如果没有匹配,现在可以添加。 + if (tasklist == null) { + tasklist = new TaskList(); // 创建任务列表。 + tasklist.setContentByLocalJSON(sqlNote.getContent()); // 设置任务列表内容 + GTaskClient.getInstance().createTaskList(tasklist); + // 创建远程任务列表。 + mGTaskListHashMap.put(tasklist.getGid(), tasklist); + // 将新创建的任务列表的GID和对象存储到HashMap中。 + n = (Node) tasklist; + // 将任务列表转换为节点对象。 + // 更新本地笔记 + sqlNote.setGtaskId(n.getGid()); + // 设置SqlNote的GTask ID为节点的GID。 + sqlNote.commit(false); + // 提交SqlNote,但不标记为已同步。 + sqlNote.resetLocalModified(); + // 重置本地修改标志。 + sqlNote.commit(true); + // 提交SqlNote,标记为已同步。 + // GID到本地ID的映射 + mGidToNid.put(n.getGid(), sqlNote.getId()); + // 将全局ID映射到本地ID。 + mNidToGid.put(sqlNote.getId(), n.getGid()); + // 将本地ID映射到全局ID。 +private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + // 如果同步被取消,则直接返回。 + SqlNote sqlNote = new SqlNote(mContext, c); + // 根据上下文和游标创建SqlNote对象。 + // 远程更新 + node.setContentByLocalJSON(sqlNote.getContent()); + // 将节点的内容设置为SqlNote的本地JSON内容。 + GTaskClient.getInstance().addUpdateNode(node); + // 将更新的节点添加到远程任务列表。 + // 更新元数据 + updateRemoteMeta(node.getGid(), sqlNote); + // 根据节点的GID和SqlNote更新远程元数据。 + // 如果需要,移动任务 + if (sqlNote.isNoteType()) { + Task task = (Task) node; + // 将节点转换为任务对象。 + TaskList preParentList = task.getParent(); + // 获取任务的旧父列表。 + String curParentGid = mNidToGid.get(sqlNote.getParentId()); + // 获取新父列表的GID。 + if (curParentGid == null) { + Log.e(TAG, "cannot find task's parent tasklist"); + // 如果找不到父任务列表,记录错误日志。 + throw new ActionFailureException("cannot update remote task"); + // 抛出异常,无法更新远程任务。 + } + TaskList curParentList = mGTaskListHashMap.get(curParentGid); + // 根据GID获取当前父列表。 + if (preParentList != curParentList) { + // 如果旧父列表与新父列表不同,则移动任务。 + preParentList.removeChildTask(task); + // 从旧父列表中移除任务。 + curParentList.addChildTask(task); + // 将任务添加到新父列表。 + GTaskClient.getInstance().moveTask(task, preParentList, curParentList); + // 远程移动任务。 + } + } + // 清除本地修改标志 + sqlNote.resetLocalModified(); + // 重置SqlNote的本地修改标志。 + sqlNote.commit(true); + // 提交SqlNote,标记为已同步。 +} +private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { + if (sqlNote != null && sqlNote.isNoteType()) { + MetaData metaData = mMetaHashMap.get(gid); + // 根据GID获取元数据。 + if (metaData != null) { + metaData.setMeta(gid, sqlNote.getContent()); + // 设置元数据的内容。 + GTaskClient.getInstance().addUpdateNode(metaData); + // 将更新的元数据添加到远程任务列表。 + } else { + metaData = new MetaData(); + metaData.setMeta(gid, sqlNote.getContent()); + // 创建新的元数据,并设置内容。 + mMetaList.addChildTask(metaData); + // 将元数据添加到元数据列表。 + mMetaHashMap.put(gid, metaData); + // 将元数据添加到HashMap中,以GID为键。 + GTaskClient.getInstance().createTask(metaData); + // 创建远程元数据任务。 + } + } +} +private void refreshLocalSyncId() throws NetworkFailureException { + if (mCancelled) { + return; + } + // 如果同步被取消,则直接返回。 + // 获取最新的gtask列表。 + mGTaskHashMap.clear(); + mGTaskListHashMap.clear(); + mMetaHashMap.clear(); + initGTaskList(); + Cursor c = null; + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type<>? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + // 查询数据库,获取非系统类型且非垃圾箱的笔记。 + if (c != null) { + while (c.moveToNext()) { + String gid = c.getString(SqlNote.GTASK_ID_COLUMN); + Node node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + ContentValues values = new ContentValues(); + values.put(NoteColumns.SYNC_ID, node.getLastModified()); + mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, + c.getLong(SqlNote.ID_COLUMN)), values, null, null); + } else { + Log.e(TAG, "something is missed"); + throw new ActionFailureException( + "some local items don't have gid after sync"); + } + } + } else { + Log.w(TAG, "failed to query local note to refresh sync id"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } +} +public String getSyncAccount() { + return GTaskClient.getInstance().getSyncAccount().name; + // 获取当前同步账户的名称。 +} +public void cancelSync() { + mCancelled = true; + // 取消当前的同步操作。 +} \ No newline at end of file diff --git a/src/net/micode/notes/gtask/remote/GTaskSyncService.java b/src/net/micode/notes/gtask/remote/GTaskSyncService.java new file mode 100644 index 0000000..ba5fd94 --- /dev/null +++ b/src/net/micode/notes/gtask/remote/GTaskSyncService.java @@ -0,0 +1,113 @@ +/* + * 版权所有 (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * 根据Apache License, Version 2.0(“许可证”)授权; + * 除非遵守许可证,否则不得使用此文件。 + * 你可以在以下网址获得许可证的副本: + * http://www.apache.org/licenses/LICENSE-2.0 + * 除非适用法律要求或书面同意,软件 + * 按“原样”分发的基础上,没有任何形式的担保或条件,无论是明示或暗示。 + * 请参阅控制权限和 + * 许可证下的限制。 + */ +package net.micode.notes.gtask.remote; +import android.app.Activity; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +public class GTaskSyncService extends Service { + // 定义同步服务中的常量 + public final static String ACTION_STRING_NAME = "sync_action_type"; + public final static int ACTION_START_SYNC = 0; + public final static int ACTION_CANCEL_SYNC = 1; + public final static int ACTION_INVALID = 2; + public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; + public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; + public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; + private static GTaskASyncTask mSyncTask = null; // 定义同步任务实例 + private static String mSyncProgress = ""; // 定义同步进度字符串 + private void startSync() { + // 开始同步的方法 + if (mSyncTask == null) { + mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { + public void onComplete() { + mSyncTask = null; // 完成后将同步任务设置为null + sendBroadcast(""); // 发送广播 + stopSelf(); // 停止服务 + } + }); + sendBroadcast(""); + mSyncTask.execute(); // 执行同步任务 + } + } + private void cancelSync() { + // 取消同步的方法 + if (mSyncTask != null) { + mSyncTask.cancelSync(); // 调用同步任务的取消方法 + } + } + @Override + public void onCreate() { + mSyncTask = null; // 在服务创建时将同步任务设置为null + } + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // 处理服务命令的方法 + Bundle bundle = intent.getExtras(); + if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { + switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { + case ACTION_START_SYNC: + startSync(); // 开始同步 + break; + case ACTION_CANCEL_SYNC: + cancelSync(); // 取消同步 + break; + default: + break; + } + return START_STICKY; // 返回START_STICKY以保持服务运行 + } + return super.onStartCommand(intent, flags, startId); // 调用父类的onStartCommand方法 + } + @Override + public void onLowMemory() { + // 处理低内存情况的方法 + if (mSyncTask != null) { + mSyncTask.cancelSync(); // 如果同步任务正在进行,则取消同步 + } + } + public IBinder onBind(Intent intent) { + // 返回服务的绑定器,这里返回null表示服务不支持绑定 + return null; + } + public void sendBroadcast(String msg) { + // 发送广播的方法 + mSyncProgress = msg; // 更新同步进度字符串 + Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); // 创建广播Intent + intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); // 添加是否正在同步的额外信息 + intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); // 添加进度消息 + sendBroadcast(intent); // 发送广播 + } + public static void startSync(Activity activity) { + // 静态方法,用于从活动开始同步 + GTaskManager.getInstance().setActivityContext(activity); // 设置GTaskManager的活动上下文 + Intent intent = new Intent(activity, GTaskSyncService.class); // 创建Intent + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); // 添加开始同步的额外信息 + activity.startService(intent); // 启动服务 + } + public static void cancelSync(Context context) { + // 静态方法,用于取消同步 + Intent intent = new Intent(context, GTaskSyncService.class); // 创建Intent + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); // 添加取消同步的额外信息 + context.startService(intent); // 启动服务 + } + public static boolean isSyncing() { + // 静态方法,用于检查是否正在同步 + return mSyncTask != null; // 如果同步任务不为空,则返回true + } + public static String getProgressString() { + // 静态方法,用于获取同步进度字符串 + return mSyncProgress; // 返回同步进度字符串 + } +} \ No newline at end of file diff --git a/src/net/micode/notes/model/Note.java b/src/net/micode/notes/model/Note.java new file mode 100644 index 0000000..6378aa3 --- /dev/null +++ b/src/net/micode/notes/model/Note.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + */ +// 版权所有 (c) 2010-2011,MiCode开源社区(www.micode.net) + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + */ +// 根据Apache License, 版本2.0(“许可证”)授权; +/* + * you may not use this file except in compliance with the License. + */ +// 除非遵守许可证,否则不得使用此文件。 +/* + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +// 您可以在以下网址获得许可证的副本: +// +// http://www.apache.org/licenses/LICENSE-2.0 +/* + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + */ +// 除非适用法律要求或书面同意,否则按“原样”分发的软件是在没有任何明示或暗示的保证或条件的情况下分发的。 +/* + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// 请参阅控制权限和限制的许可证具体条款。 +package net.micode.notes.model; +// 包名声明:net.micode.notes.model +import android.content.ContentProviderOperation; +// 导入ContentProviderOperation类 +import android.content.ContentProviderResult; +// 导入ContentProviderResult类 +import android.content.ContentUris; +// 导入ContentUris类 +import android.content.ContentValues; +// 导入ContentValues类 +import android.content.Context; +// 导入Context类 +import android.content.OperationApplicationException; +// 导入OperationApplicationException类 +import android.net.Uri; +// 导入Uri类 +import android.os.RemoteException; // 导入RemoteException类 +import android.util.Log; // 导入Log类 +import net.micode.notes.data.Notes; // 导入Notes类 +import net.micode.notes.data.Notes.CallNote; // 导入CallNote类 +import net.micode.notes.data.Notes.DataColumns; // 导入DataColumns类 +import net.micode.notes.data.Notes.NoteColumns; // 导入NoteColumns类 +import net.micode.notes.data.Notes.TextNote; // 导入TextNote类 +import java.util.ArrayList; // 导入ArrayList类 +public class Note { + // 定义公共类Note + private ContentValues mNoteDiffValues; + // 定义私有成员变量mNoteDiffValues,类型为ContentValues + private NoteData mNoteData; + // 定义私有成员变量mNoteData,类型为NoteData + private static final String TAG = "Note"; + // 定义私有静态常量TAG,值为"Note",用于日志标记 + /** + * Create a new note id for adding a new note to databases + */ + // 创建一个新的笔记ID,用于将新笔记添加到数据库 + public static synchronized long getNewNoteId(Context context, long folderId) { + // 定义公共静态同步方法getNewNoteId,接收Context和folderId作为参数,返回类型为long + ContentValues values = new ContentValues(); + // 创建ContentValues对象values + long createdTime = System.currentTimeMillis(); + // 获取当前系统时间毫秒数 + values.put(NoteColumns.CREATED_DATE, createdTime); + // 将CREATED_DATE列的值设置为当前时间 + values.put(NoteColumns.MODIFIED_DATE, createdTime); + // 将MODIFIED_DATE列的值设置为当前时间 + values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + // 将TYPE列的值设置为Notes.TYPE_NOTE + values.put(NoteColumns.LOCAL_MODIFIED, 1); + // 将LOCAL_MODIFIED列的值设置为1 + values.put(NoteColumns.PARENT_ID, folderId); + // 将PARENT_ID列的值设置为folderId + Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); + // 将values插入到Notes.CONTENT_NOTE_URI,并返回生成的Uri + long noteId = 0; + // 定义long型变量noteId并初始化为0 + try { + noteId = Long.valueOf(uri.getPathSegments().get(1)); + } catch (NumberFormatException e) { + Log.e(TAG, "Get note id error :" + e.toString()); + noteId = 0; + } + // 尝试从Uri中获取noteId,如果发生NumberFormatException,则记录错误日志,并设置noteId为0 + if (noteId == -1) { + throw new IllegalStateException("Wrong note id:" + noteId); + } + // 如果noteId为-1,则抛出IllegalStateException异常 + return noteId; + // 返回noteId + } + public Note() { + mNoteDiffValues = new ContentValues(); + mNoteData = new NoteData(); + } + // 定义Note类的构造函数,初始化mNoteDiffValues和mNoteData + public void setNoteValue(String key, String value) { + mNoteDiffValues.put(key, value); + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + } + // 定义公共方法setNoteValue,设置笔记的值,并标记为本地修改 + public void setTextData(String key, String value) { + mNoteData.setTextData(key, value); + } + // 定义公共方法setTextData,设置文本数据 + public void setTextDataId(long id) { + mNoteData.setTextDataId(id); + } + // 定义公共方法setTextDataId,设置文本数据ID + public long getTextDataId() { + return mNoteData.mTextDataId; + } + // 定义公共方法getTextDataId,返回文本数据ID + public void setCallDataId(long id) { + mNoteData.setCallDataId(id); + } + // 定义公共方法setCallDataId,设置通话数据ID + public void setCallData(String key, String value) { + mNoteData.setCallData(key, value); + } + // 定义公共方法setCallData,设置通话数据 + public boolean isLocalModified() { + return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); + } + // 定义公共方法isLocalModified,检查笔记是否在本地被修改过 + public boolean syncNote(Context context, long noteId) { + if (noteId <= 0) { + throw new IllegalArgumentException("Wrong note id:" + noteId); + } + if (!isLocalModified()) { + return true; + } + // 理论上,一旦数据改变,就应该更新LOCAL_MODIFIED和MODIFIED_DATE。为了数据安全,即使更新笔记失败,也应该更新笔记数据信息 + if (context.getContentResolver().update( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, + null) == 0) { + Log.e(TAG, "Update note error, should not happen"); + // 如果更新失败,记录错误日志 + } + mNoteDiffValues.clear(); + if (mNoteData.isLocalModified() + && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { + return false; + } + return true; + } + // 定义公共方法syncNote,同步笔记数据到内容提供者 + private class NoteData { + // 定义私有内部类NoteData + private long mTextDataId; + // 定义私有成员变量mTextDataId,类型为long + private ContentValues mTextDataValues; + // 定义私有成员变量mTextDataValues,类型为ContentValues + private long mCallDataId; + // 定义私有成员变量mCallDataId,类型为long + private ContentValues mCallDataValues; + // 定义私有成员变量mCallDataValues,类型为ContentValues + private static final String TAG = "NoteData"; + // 定义私有静态常量TAG,值为"NoteData",用于日志标记 + public NoteData() { + mTextDataValues = new ContentValues(); + mCallDataValues = new ContentValues(); + mTextDataId = 0; + mCallDataId = 0; + } + // 定义NoteData类的构造函数,初始化成员变量 + boolean isLocalModified() { + return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; + } + // 定义私有方法isLocalModified,检查文本数据或通话数据是否在本地被修改过 + void setTextDataId(long id) { + if(id <= 0) { + throw new IllegalArgumentException("Text data id should larger than 0"); + } + mTextDataId = id; + } + // 定义私有方法setTextDataId,设置文本数据ID + void setCallDataId(long id) { + if (id <= 0) { + throw new IllegalArgumentException("Call data id should larger than 0"); + } + mCallDataId = id; + } + void setCallData(String key, String value) { + mCallDataValues.put(key, value); + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); +} +// 定义一个方法setCallData,接收两个字符串参数key和value,将它们存储到mCallDataValues中,并标记笔记为本地修改,记录当前时间。 +void setTextData(String key, String value) { + mTextDataValues.put(key, value); + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); +} +// 定义一个方法setTextData,接收两个字符串参数key和value,将它们存储到mTextDataValues中,并标记笔记为本地修改,记录当前时间。 +Uri pushIntoContentResolver(Context context, long noteId) { + /** + * Check for safety + */ + if (noteId <= 0) { + throw new IllegalArgumentException("Wrong note id:" + noteId); + } + // 定义一个方法pushIntoContentResolver,接收Context对象和笔记ID,首先检查笔记ID是否有效,如果无效则抛出异常。 + ArrayList operationList = new ArrayList(); + ContentProviderOperation.Builder builder = null; + // 初始化一个ContentProviderOperation对象列表operationList和一个Builder对象。 + if(mTextDataValues.size() > 0) { + mTextDataValues.put(DataColumns.NOTE_ID, noteId); + if (mTextDataId == 0) { + mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, + mTextDataValues); + try { + setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); + } catch (NumberFormatException e) { + Log.e(TAG, "Insert new text data fail with noteId" + noteId); + mTextDataValues.clear(); + return null; + } + } else { + builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mTextDataId)); + builder.withValues(mTextDataValues); + operationList.add(builder.build()); + } + mTextDataValues.clear(); + } + // 如果文本数据值非空,则根据是否有文本数据ID来插入或更新文本数据,并添加到操作列表。 + if(mCallDataValues.size() > 0) { + mCallDataValues.put(DataColumns.NOTE_ID, noteId); + if (mCallDataId == 0) { + mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, + mCallDataValues); + try { + setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); + } catch (NumberFormatException e) { + Log.e(TAG, "Insert new call data fail with noteId" + noteId); + mCallDataValues.clear(); + return null; + } + } else { + builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mCallDataId)); + builder.withValues(mCallDataValues); + operationList.add(builder.build()); + } + mCallDataValues.clear(); + } + // 如果通话数据值非空,则根据是否有通话数据ID来插入或更新通话数据,并添加到操作列表。 + if (operationList.size() > 0) { + try { + ContentProviderResult[] results = context.getContentResolver().applyBatch( + Notes.AUTHORITY, operationList); + return (results == null || results.length == 0 || results[0] == null) ? null + : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); + } catch (RemoteException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + return null; + } catch (OperationApplicationException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + return null; + } + } + return null; +} +// 定义一个方法pushIntoContentResolver,用于将文本数据和通话数据的操作列表应用到内容提供者,并返回操作结果的Uri或null。 \ No newline at end of file diff --git a/src/net/micode/notes/model/WorkingNote.java b/src/net/micode/notes/model/WorkingNote.java new file mode 100644 index 0000000..a6b58d2 --- /dev/null +++ b/src/net/micode/notes/model/WorkingNote.java @@ -0,0 +1,387 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + */ +// 版权所有 (c) 2010-2011,MiCode开源社区(www.micode.net) +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +// 根据Apache License, 版本2.0(“许可证”)授权; +// 除非遵守许可证,否则不得使用此文件。 +// 您可以在以下网址获得许可证的副本: +// +// http://www.apache.org/licenses/LICENSE-2.0 +/* + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// 除非适用法律要求或书面同意,否则按“原样”分发的软件是在没有任何明示或暗示的保证或条件的情况下分发的。 +// 请参阅控制权限和限制的许可证具体条款。 +package net.micode.notes.model; // 声明该类属于net.micode.notes.model包。 +import android.appwidget.AppWidgetManager; // 导入Android的AppWidgetManager类 +import android.content.ContentUris; // 导入Android的ContentUris类 +import android.content.Context; // 导入Android的Context类 +import android.database.Cursor; // 导入Android的Cursor类 +import android.text.TextUtils; // 导入Android的TextUtils类 +import android.util.Log; // 导入Android的Log类 +import net.micode.notes.data.Notes; // 导入项目的Notes数据类 +import net.micode.notes.data.Notes.CallNote; // 导入项目的CallNote数据类 +import net.micode.notes.data.Notes.DataColumns; // 导入项目的DataColumns数据类 +import net.micode.notes.data.Notes.DataConstants; // 导入项目的DataConstants数据类 +import net.micode.notes.data.Notes.NoteColumns; // 导入项目的NoteColumns数据类 +import net.micode.notes.data.Notes.TextNote; // 导入项目的TextNote数据类 +import net.micode.notes.tool.ResourceParser.NoteBgResources; // 导入项目的NoteBgResources工具类 +public class WorkingNote { + // 定义一个名为WorkingNote的公共类 + // Note for the working note + private Note mNote; + // 定义一个私有成员变量mNote,类型为Note + // Note Id + private long mNoteId; + // 定义一个私有成员变量mNoteId,类型为long,表示笔记ID + // Note content + private String mContent; + // 定义一个私有成员变量mContent,类型为String,表示笔记内容 + // Note mode + private int mMode; + // 定义一个私有成员变量mMode,类型为int,表示笔记模式 + private long mAlertDate; + // 定义一个私有成员变量mAlertDate,类型为long,表示提醒日期 + private long mModifiedDate; + // 定义一个私有成员变量mModifiedDate,类型为long,表示修改日期 + private int mBgColorId; + // 定义一个私有成员变量mBgColorId,类型为int,表示背景颜色ID + private int mWidgetId; + // 定义一个私有成员变量mWidgetId,类型为int,表示小部件ID、 + private int mWidgetType; + // 定义一个私有成员变量mWidgetType,类型为int,表示小部件类型 + private long mFolderId; + // 定义一个私有成员变量mFolderId,类型为long,表示文件夹ID + private Context mContext; + // 定义一个私有成员变量mContext,类型为Context + private static final String TAG = "WorkingNote"; + // 定义一个私有静态最终字符串TAG,值为"WorkingNote",用于日志标记 + private boolean mIsDeleted; + // 定义一个私有成员变量mIsDeleted,类型为boolean,表示笔记是否被删除 + private NoteSettingChangedListener mNoteSettingStatusListener; + // 定义一个私有成员变量mNoteSettingStatusListener,类型为NoteSettingChangedListener + public static final String[] DATA_PROJECTION = new String[] { + DataColumns.ID, + DataColumns.CONTENT, + DataColumns.MIME_TYPE, + DataColumns.DATA1, + DataColumns.DATA2, + DataColumns.DATA3, + DataColumns.DATA4, + }; + // 定义一个公共静态最终字符串数组DATA_PROJECTION,包含数据库查询所需的列名 + public static final String[] NOTE_PROJECTION = new String[] { + NoteColumns.PARENT_ID, + NoteColumns.ALERTED_DATE, + NoteColumns.BG_COLOR_ID, + NoteColumns.WIDGET_ID, + NoteColumns.WIDGET_TYPE, + NoteColumns.MODIFIED_DATE + }; + // 定义一个公共静态最终字符串数组NOTE_PROJECTION,包含数据库查询所需的列名 + private static final int DATA_ID_COLUMN = 0; + // 定义一个私有静态最终整数DATA_ID_COLUMN,值为0,表示DATA_PROJECTION数组中ID列的索引 + private static final int DATA_CONTENT_COLUMN = 1; + // 定义一个私有静态最终整数DATA_CONTENT_COLUMN,值为1,表示DATA_PROJECTION数组中CONTENT列的索引 + private static final int DATA_MIME_TYPE_COLUMN = 2; + // 定义一个私有静态最终整数DATA_MIME_TYPE_COLUMN,值为2,表示DATA_PROJECTION数组中MIME_TYPE列的索引 + private static final int DATA_MODE_COLUMN = 3; + // 定义一个私有静态最终整数DATA_MODE_COLUMN,值为3,表示DATA_PROJECTION数组中MODE列的索引 + private static final int NOTE_PARENT_ID_COLUMN = 0; + // 定义一个私有静态最终整数NOTE_PARENT_ID_COLUMN,值为0,表示NOTE_PROJECTION数组中PARENT_ID列的索引 + private static final int NOTE_ALERTED_DATE_COLUMN = 1; + // 定义一个私有静态最终整数NOTE_ALERTED_DATE_COLUMN,值为1,表示NOTE_PROJECTION数组中ALERTED_DATE列的索引 + private static final int NOTE_BG_COLOR_ID_COLUMN = 2; + // 定义一个私有静态最终整数NOTE_BG_COLOR_ID_COLUMN,值为2,表示NOTE_PROJECTION数组中BG_COLOR_ID列的索引 + private static final int NOTE_WIDGET_ID_COLUMN = 3; + // 定义一个私有静态最终整数NOTE_WIDGET_ID_COLUMN,值为3,表示NOTE_PROJECTION数组中WIDGET_ID列的索引 + private static final int NOTE_WIDGET_TYPE_COLUMN = 4; + // 定义一个私有静态最终整数NOTE_WIDGET_TYPE_COLUMN,值为4,表示NOTE_PROJECTION数组中WIDGET_TYPE列的索引 + private static final int NOTE_MODIFIED_DATE_COLUMN = 5; + // 定义一个私有静态最终整数NOTE_MODIFIED_DATE_COLUMN,值为5,表示NOTE_PROJECTION数组中MODIFIED_DATE列的索引 + // New note construct + private WorkingNote(Context context, long folderId) { + mContext = context; + mAlertDate = 0; + mModifiedDate = System.currentTimeMillis(); + mFolderId = folderId; + mNote = new Note(); + mNoteId = 0; + mIsDeleted = false; + mMode = 0; + mWidgetType = Notes.TYPE_WIDGET_INVALIDE; + } + // 定义一个私有构造函数,用于创建一个新的WorkingNote对象 + // Existing note construct + private WorkingNote(Context context, long noteId, long folderId) { + mContext = context; + mNoteId = noteId; + mFolderId = folderId; + mIsDeleted = false; + mNote = new Note(); + loadNote(); + } + // 定义一个私有构造函数,用于根据笔记ID和文件夹ID创建一个已存在的WorkingNote对象 + private void loadNote() { + Cursor cursor = mContext.getContentResolver().query( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, + null, null); + if (cursor != null) { + if (cursor.moveToFirst()) { + mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); + mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); + mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); + mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); + mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); + mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); + } + cursor.close(); + } else { + Log.e(TAG, "No note with id:" + mNoteId); + throw new IllegalArgumentException("Unable to find note with id " + mNoteId); + } + loadNoteData(); + } + // 定义一个私有方法loadNote,用于从数据库加载笔记信息 + private void loadNoteData() { + // 定义一个私有方法loadNoteData,用于从数据库加载笔记数据 + Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, + DataColumns.NOTE_ID + "=?", new String[] { + String.valueOf(mNoteId) + }, null); + // 执行查询操作,获取与当前笔记ID关联的数据 + if (cursor != null) { + if (cursor.moveToFirst()) { + do { + String type = cursor.getString(DATA_MIME_TYPE_COLUMN); + // 获取数据类型 + if (DataConstants.NOTE.equals(type)) { + // 如果是普通笔记类型 + mContent = cursor.getString(DATA_CONTENT_COLUMN); + // 获取笔记内容 + mMode = cursor.getInt(DATA_MODE_COLUMN); + // 获取笔记模式 + mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); + // 设置文本数据ID + } else if (DataConstants.CALL_NOTE.equals(type)) { + // 如果是通话笔记类型 + mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); + // 设置通话数据ID + } else { + Log.d(TAG, "Wrong note type with type:" + type); + // 记录日志,表示遇到未知的笔记类型 + } + } while (cursor.moveToNext()); + } + cursor.close(); + } else { + Log.e(TAG, "No data with id:" + mNoteId); + // 记录错误日志,表示没有找到与笔记ID关联的数据 + throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId); + // 抛出异常,表示无法找到笔记数据 + } +} +public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, + int widgetType, int defaultBgColorId) { + // 定义一个公共静态方法createEmptyNote,用于创建一个空的WorkingNote对象 + WorkingNote note = new WorkingNote(context, folderId); + note.setBgColorId(defaultBgColorId); + note.setWidgetId(widgetId); + note.setWidgetType(widgetType); + return note; + // 返回新创建的空WorkingNote对象 +} +public static WorkingNote load(Context context, long id) { + // 定义一个公共静态方法load,用于根据ID加载一个WorkingNote对象 + return new WorkingNote(context, id, 0); + // 返回新加载的WorkingNote对象 +} +public synchronized boolean saveNote() { + // 定义一个公共同步方法saveNote,用于保存笔记 + if (isWorthSaving()) { + if (!existInDatabase()) { + if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { + Log.e(TAG, "Create new note fail with id:" + mNoteId); + return false; + } + } + mNote.syncNote(mContext, mNoteId); + // 如果存在与该笔记关联的小部件,则更新小部件内容 + if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && mWidgetType != Notes.TYPE_WIDGET_INVALIDE + && mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onWidgetChanged(); + } + return true; + } else { + return false; + } +} +public boolean existInDatabase() { + // 定义一个公共方法existInDatabase,用于检查笔记是否存在于数据库中 + return mNoteId > 0; + // 返回笔记ID是否大于0,表示笔记是否存在于数据库中 +} +private boolean isWorthSaving() { + // 定义一个私有方法isWorthSaving,用于判断笔记是否值得保存 + if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) + || (existInDatabase() && !mNote.isLocalModified())) { + return false; + } else { + return true; + } +} +public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { + // 定义一个公共方法setOnSettingStatusChangedListener,用于设置笔记设置状态变化的监听器 + mNoteSettingStatusListener = l; +} +public void setAlertDate(long date, boolean set) { + // 定义一个公共方法setAlertDate,用于设置提醒日期 + if (date != mAlertDate) { + mAlertDate = date; + mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); + } + if (mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onClockAlertChanged(date, set); + } +} +public void markDeleted(boolean mark) { + // 定义一个公共方法markDeleted,用于标记笔记为已删除 + mIsDeleted = mark; + if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onWidgetChanged(); + } +} +public void setBgColorId(int id) { + // 定义一个公共方法setBgColorId,用于设置背景颜色ID + if (id != mBgColorId) { + mBgColorId = id; + if (mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onBackgroundColorChanged(); + } + mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); + } +} +public void setCheckListMode(int mode) { + // 定义一个公共方法setCheckListMode,用于设置检查列表模式 + if (mMode != mode) { + if (mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); + } + mMode = mode; + mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); + } +} +public void setWidgetType(int type) { + // 定义一个公共方法setWidgetType,用于设置小部件类型 + if (type != mWidgetType) { + mWidgetType = type; + mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); + } +} +public void setWidgetId(int id) { + // 定义一个公共方法setWidgetId,用于设置小部件ID + if (id != mWidgetId) { + mWidgetId = id; + mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); + } +} +public void setWorkingText(String text) { + // 定义一个公共方法setWorkingText,用于设置工作文本 + if (!TextUtils.equals(mContent, text)) { + mContent = text; + mNote.setTextData(DataColumns.CONTENT, mContent); + } +} +public void convertToCallNote(String phoneNumber, long callDate) { + // 定义一个公共方法convertToCallNote,用于将笔记转换为通话笔记 + mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); + mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); + mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); +} +public boolean hasClockAlert() { + // 定义一个公共方法hasClockAlert,用于检查笔记是否有时钟提醒 + return (mAlertDate > 0 ? true : false); +} +public String getContent() { + // 定义一个公共方法getContent,用于获取笔记内容 + return mContent; +} +public long getAlertDate() { + // 定义一个公共方法getAlertDate,用于获取提醒日期 + return mAlertDate; +} +public long getModifiedDate() { + // 定义一个公共方法getModifiedDate,用于获取修改日期 + return mModifiedDate; +} +public int getBgColorResId() { + // 定义一个公共方法getBgColorResId,用于获取背景颜色资源ID + return NoteBgResources.getNoteBgResource(mBgColorId); +} +public int getBgColorId() { + // 定义一个公共方法getBgColorId,用于获取背景颜色ID + return mBgColorId; +} +public int getTitleBgResId() { + // 定义一个公共方法getTitleBgResId,用于获取标题背景资源ID + return NoteBgResources.getNoteTitleBgResource(mBgColorId); +} +public int getCheckListMode() { + // 定义一个公共方法getCheckListMode,用于获取检查列表模式 + return mMode; +} +public long getNoteId() { + // 定义一个公共方法getNoteId,用于获取笔记ID + return mNoteId; +} +public long getFolderId() { + // 定义一个公共方法getFolderId,用于获取文件夹ID + return mFolderId; +} +public int getWidgetId() { + // 定义一个公共方法getWidgetId,用于获取小部件ID + return mWidgetId; +} +public int getWidgetType() { + // 定义一个公共方法getWidgetType,用于获取小部件类型 + return mWidgetType; +} +public interface NoteSettingChangedListener { + // 定义一个公共接口NoteSettingChangedListener,用于监听笔记设置变化 + /** + * Called when the background color of current note has just changed + */ + void onBackgroundColorChanged(); + /** +/** + * Called when user set clock + */ +void onClockAlertChanged(long date, boolean set); +// 当用户设置时钟提醒时调用 +// 参数date表示设置的日期,set表示是否设置了提醒 +/** + * Call when user create note from widget + */ +void onWidgetChanged(); +// 当用户从widget创建笔记时调用 +/** + * Call when switch between check list mode and normal mode + * @param oldMode is previous mode before change + * @param newMode is new mode + */ +void onCheckListModeChanged(int oldMode, int newMode); +// 当用户在检查列表模式和普通模式之间切换时调用 +// 参数oldMode表示之前的模式,newMode表示新的模式 \ No newline at end of file diff --git a/src/net/micode/notes/widget/NoteWidgetProvider.java b/src/net/micode/notes/widget/NoteWidgetProvider.java new file mode 100644 index 0000000..8400448 --- /dev/null +++ b/src/net/micode/notes/widget/NoteWidgetProvider.java @@ -0,0 +1,190 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + */ +// 版权所有 (c) 2010-2011,MiCode开源社区(www.micode.net) +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +// 根据Apache License, 版本2.0(“许可证”)授权; +// 除非遵守许可证,否则不得使用此文件。 +// 您可以在以下网址获得许可证的副本: +// +// http://www.apache.org/licenses/LICENSE-2.0 +/* + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// 除非适用法律要求或书面同意,否则按“原样”分发的软件是在没有任何明示或暗示的保证或条件的情况下分发的。 +// 请参阅控制权限和限制的许可证具体条款。 +package net.micode.notes.widget; +// 声明该类属于net.micode.notes.widget包。 +import android.app.PendingIntent; +// 导入PendingIntent类 +import android.appwidget.AppWidgetManager; +// 导入AppWidgetManager类 +import android.appwidget.AppWidgetProvider; +// 导入AppWidgetProvider类 +import android.content.ContentValues; +// 导入ContentValues类 +import android.content.Context; +// 导入Context类 +import android.content.Intent; +// 导入Intent类 +import android.database.Cursor; +// 导入Cursor类 +import android.util.Log; +// 导入Log类 +import android.widget.RemoteViews; +// 导入RemoteViews类 +import net.micode.notes.R; +// 导入项目资源类 +import net.micode.notes.data.Notes; +// 导入Notes数据类 +import net.micode.notes.data.Notes.NoteColumns; +// 导入NoteColumns数据类 +import net.micode.notes.tool.ResourceParser; +// 导入ResourceParser工具类 +import net.micode.notes.ui.NoteEditActivity; +// 导入NoteEditActivity界面类 +import net.micode.notes.ui.NotesListActivity; +// 导入NotesListActivity界面类 +public abstract class NoteWidgetProvider extends AppWidgetProvider { + // 定义一个抽象的NoteWidgetProvider类,继承自AppWidgetProvider + public static final String [] PROJECTION = new String [] { + NoteColumns.ID, + NoteColumns.BG_COLOR_ID, + NoteColumns.SNIPPET + }; + // 定义一个公共静态最终字符串数组PROJECTION,包含数据库查询所需的列名 + public static final int COLUMN_ID = 0; + public static final int COLUMN_BG_COLOR_ID = 1; + public static final int COLUMN_SNIPPET = 2; + // 定义公共静态最终整数COLUMN_ID、COLUMN_BG_COLOR_ID和COLUMN_SNIPPET,表示PROJECTION数组中对应列的索引 + private static final String TAG = "NoteWidgetProvider"; + // 定义一个私有静态最终字符串TAG,值为"NoteWidgetProvider",用于日志标记 + @Override + public void onDeleted(Context context, int[] appWidgetIds) { + // 重写onDeleted方法,接收Context对象和appWidgetIds数组作为参数 + ContentValues values = new ContentValues(); + values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + // 创建ContentValues对象values,并设置WIDGET_ID列为无效的AppWidget ID + for (int i = 0; i < appWidgetIds.length; i++) { + context.getContentResolver().update(Notes.CONTENT_NOTE_URI, + values, + NoteColumns.WIDGET_ID + "=?", + new String[] { String.valueOf(appWidgetIds[i])}); + // 对每个appWidgetIds,更新Notes.CONTENT_NOTE_URI对应的数据,将WIDGET_ID列设置为无效的AppWidget ID + } + } + private Cursor getNoteWidgetInfo(Context context, int widgetId) { + // 定义一个私有方法getNoteWidgetInfo,接收Context对象和widgetId作为参数,返回Cursor对象 + return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, + PROJECTION, + NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", + new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, + null); + // 执行查询操作,返回与widgetId关联的笔记信息 + } + protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + // 定义一个受保护的方法update,接收Context对象、AppWidgetManager对象和appWidgetIds数组作为参数 + update(context, appWidgetManager, appWidgetIds, false); + // 调用另一个update方法,传入false作为privacyMode参数 + } + private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, + boolean privacyMode) { + // 定义一个私有方法update,接收Context对象、AppWidgetManager对象、appWidgetIds数组和privacyMode布尔值作为参数 + for (int i = 0; i < appWidgetIds.length; i++) { + // 遍历appWidgetIds数组 + if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { + // 如果appWidgetIds[i]有效 + int bgId = ResourceParser.getDefaultBgId(context); + // 获取默认背景ID + String snippet = ""; + // 初始化字符串snippet + Intent intent = new Intent(context, NoteEditActivity.class); + // 创建Intent对象,用于启动NoteEditActivity + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + // 设置Intent标志为FLAG_ACTIVITY_SINGLE_TOP + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); + // 将widget ID作为额外数据传递给NoteEditActivity + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); + // 将widget类型作为额外数据传递给NoteEditActivity + + Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); + // 获取与appWidgetIds[i]关联的笔记信息 + if (c != null && c.moveToFirst()) { + // 如果查询结果不为空且可以移动到第一条记录 + if (c.getCount() > 1) { + // 如果查询结果有多条记录 + Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); + // 记录错误日志 + c.close(); + // 关闭Cursor + return; + } + snippet = c.getString(COLUMN_SNIPPET); + // 获取snippet信息 + bgId = c.getInt(COLUMN_BG_COLOR_ID); + // 获取背景ID + intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); + // 将ID作为额外数据传递给NoteEditActivity + intent.setAction(Intent.ACTION_VIEW); + // 设置Intent动作为ACTION_VIEW + } else { + // 如果查询结果为空 + snippet = context.getResources().getString(R.string.widget_havenot_content); + // 设置snippet为默认字符串 + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + // 设置Intent动作为ACTION_INSERT_OR_EDIT + } + if (c != null) { + c.close(); + } + // 关闭Cursor + RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); + // 创建RemoteViews对象,用于更新AppWidget布局 + rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); + // 设置背景图片资源 + intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); + // 将背景ID作为额外数据传递给NoteEditActivity + /** + * Generate the pending intent to start host for the widget + */ + // 生成PendingIntent,用于启动AppWidget的宿主Activity + PendingIntent pendingIntent = null; + if (privacyMode) { + // 如果处于隐私模式 + rv.setTextViewText(R.id.widget_text, + context.getString(R.string.widget_under_visit_mode)); + // 设置文本内容为隐私模式下的提示文本 + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( + context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); + // 创建PendingIntent,用于启动NotesListActivity + } else { + // 如果不处于隐私模式 + rv.setTextViewText(R.id.widget_text, snippet); + // 设置文本内容为snippet + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, + PendingIntent.FLAG_UPDATE_CURRENT); + // 创建PendingIntent,用于启动NoteEditActivity + } + rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); + // 设置RemoteViews的点击事件,关联PendingIntent + appWidgetManager.updateAppWidget(appWidgetIds[i], rv); + // 更新AppWidget + } + } + } +protected abstract int getBgResourceId(int bgId); +// 这是一个抽象方法,需要子类实现。该方法根据传入的背景ID(bgId)返回一个对应的背景资源ID。 +protected abstract int getLayoutId(); +// 这是一个抽象方法,需要子类实现。该方法返回一个整数,表示AppWidget使用的布局资源ID。 +protected abstract int getWidgetType(); +// 这是一个抽象方法,需要子类实现。该方法返回一个整数,表示AppWidget的类型。 \ No newline at end of file diff --git a/src/net/micode/notes/widget/NoteWidgetProvider_2x.java b/src/net/micode/notes/widget/NoteWidgetProvider_2x.java new file mode 100644 index 0000000..6ac2a02 --- /dev/null +++ b/src/net/micode/notes/widget/NoteWidgetProvider_2x.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + */ +// 版权所有 (c) 2010-2011,MiCode开源社区(www.micode.net) +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +// 根据Apache License, 版本2.0(“许可证”)授权; +// 除非遵守许可证,否则不得使用此文件。 +// 您可以在以下网址获得许可证的副本: +// +// http://www.apache.org/licenses/LICENSE-2.0 +/* + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// 除非适用法律要求或书面同意,否则按“原样”分发的软件是在没有任何明示或暗示的保证或条件的情况下分发的。 +// 请参阅控制权限和限制的许可证具体条款。 +package net.micode.notes.widget; +// 声明该类属于net.micode.notes.widget包。 +import android.appwidget.AppWidgetManager; +// 导入AppWidgetManager类 +import android.content.Context; +// 导入Context类 +import net.micode.notes.R; +// 导入项目资源类 +import net.micode.notes.data.Notes; +// 导入Notes数据类 +import net.micode.notes.tool.ResourceParser; +// 导入ResourceParser工具类 +public class NoteWidgetProvider_2x extends NoteWidgetProvider { + // 定义一个名为NoteWidgetProvider_2x的公共类,继承自NoteWidgetProvider + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + // 重写onUpdate方法,接收Context对象、AppWidgetManager对象和appWidgetIds数组作为参数 + super.update(context, appWidgetManager, appWidgetIds); + // 调用父类的update方法 + } + @Override + protected int getLayoutId() { + // 重写getLayoutId方法,返回布局资源ID + return R.layout.widget_2x; + // 返回2x布局资源ID + } + @Override + protected int getBgResourceId(int bgId) { + // 重写getBgResourceId方法,接收一个整数bgId作为参数,返回背景资源ID + return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); + // 根据bgId返回2x小部件的背景资源ID + } + @Override + protected int getWidgetType() { + // 重写getWidgetType方法,返回小部件类型 + return Notes.TYPE_WIDGET_2X; + // 返回2x小部件类型 + } +} +// 结束NoteWidgetProvider_2x类的定义 \ No newline at end of file diff --git a/src/net/micode/notes/widget/NoteWidgetProvider_4x.java b/src/net/micode/notes/widget/NoteWidgetProvider_4x.java new file mode 100644 index 0000000..8990d99 --- /dev/null +++ b/src/net/micode/notes/widget/NoteWidgetProvider_4x.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + */ +// 版权所有 (c) 2010-2011,MiCode开源社区(www.micode.net) +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + */ +// 根据Apache License, 版本2.0(“许可证”)授权; +// 除非遵守许可证,否则不得使用此文件。 +// 您可以在以下网址获得许可证的副本: +// +// http://www.apache.org/licenses/LICENSE-2.0 +/* + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// 除非适用法律要求或书面同意,否则按“原样”分发的软件是在没有任何明示或暗示的保证或条件的情况下分发的。 +// 请参阅控制权限和限制的许可证具体条款。 +package net.micode.notes.widget; +// 声明该类属于net.micode.notes.widget包。 +import android.appwidget.AppWidgetManager; +// 导入Android的AppWidgetManager类 +import android.content.Context; +// 导入Android的Context类 +import net.micode.notes.R; +// 导入项目的R资源类 +import net.micode.notes.data.Notes; +// 导入项目的Notes数据类 +import net.micode.notes.tool.ResourceParser; +// 导入项目的ResourceParser工具类 +public class NoteWidgetProvider_4x extends NoteWidgetProvider { + // 定义一个名为NoteWidgetProvider_4x的公共类,继承自NoteWidgetProvider + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + // 重写onUpdate方法,接收Context对象、AppWidgetManager对象和appWidgetIds数组作为参数 + super.update(context, appWidgetManager, appWidgetIds); + // 调用父类的update方法 + } + @Override + protected int getLayoutId() { + // 重写getLayoutId方法,返回布局资源ID + return R.layout.widget_4x; + // 返回4x布局资源ID + } + @Override + protected int getBgResourceId(int bgId) { + // 重写getBgResourceId方法,接收一个整数bgId作为参数,返回背景资源ID + return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); + // 根据bgId返回4x小部件的背景资源ID + } + @Override + protected int getWidgetType() { + // 重写getWidgetType方法,返回小部件类型 + return Notes.TYPE_WIDGET_4X; + // 返回4x小部件类型 + } +} +// 结束NoteWidgetProvider_4x类的定义 \ No newline at end of file