From 950c0bd9a18929f26cc2933ce5e73f0952ddf774 Mon Sep 17 00:00:00 2001 From: TSP <2592553343@qq.com> Date: Sun, 15 Dec 2024 20:33:05 +0800 Subject: [PATCH 01/12] =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test.txt diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..e69de29 From e37688903c77da29734ae34de768846ea59a2ccb Mon Sep 17 00:00:00 2001 From: TSP <2592553343@qq.com> Date: Sun, 15 Dec 2024 20:43:06 +0800 Subject: [PATCH 02/12] =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test1.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test1.txt diff --git a/test1.txt b/test1.txt new file mode 100644 index 0000000..e69de29 From 17aae1da8e4f8970d69c10ac8e6050af5eb3c3bd Mon Sep 17 00:00:00 2001 From: TSP <2592553343@qq.com> Date: Sun, 15 Dec 2024 20:46:38 +0800 Subject: [PATCH 03/12] =?UTF-8?q?=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 新建 文本文档.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 新建 文本文档.txt diff --git a/新建 文本文档.txt b/新建 文本文档.txt new file mode 100644 index 0000000..e69de29 From c633aaddd2921c44808a0bac59b93f29e1bbc99d Mon Sep 17 00:00:00 2001 From: YourUsername Date: Sun, 15 Dec 2024 20:58:35 +0800 Subject: [PATCH 04/12] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NoteWidgetProvider.java | 142 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 NoteWidgetProvider.java diff --git a/NoteWidgetProvider.java b/NoteWidgetProvider.java new file mode 100644 index 0000000..67b7b72 --- /dev/null +++ b/NoteWidgetProvider.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.widget;/*声明了该文件所属的包。*/ +import android.app.PendingIntent;/*引入了所需的Android框架和自定义类的导入语句。*/ +import android.appwidget.AppWidgetManager;/*引入了所需的Android框架和自定义类的导入语句。*/ +import android.appwidget.AppWidgetProvider;/*引入了所需的Android框架和自定义类的导入语句。*/ +import android.content.ContentValues;/*引入了所需的Android框架和自定义类的导入语句。*/ +import android.content.Context;/*引入了所需的Android框架和自定义类的导入语句。*/ +import android.content.Intent;/*引入了所需的Android框架和自定义类的导入语句。*/ +import android.database.Cursor;/*引入了所需的Android框架和自定义类的导入语句。*/ +import android.util.Log;/*引入了所需的Android框架和自定义类的导入语句。*/ +import android.widget.RemoteViews;/*引入了所需的Android框架和自定义类的导入语句。*/ + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.ui.NoteEditActivity; +import net.micode.notes.ui.NotesListActivity;/*引入了所需的Android框架和自定义类的导入语句。*/ + +public abstract class NoteWidgetProvider extends AppWidgetProvider {/*定义了一个名为 NoteWidgetProvider +的抽象类,它继承自 AppWidgetProvider。这意味着 NoteWidgetProvider 是一个小部件提供者,可以处理小部件的更新和广播。*/ + public static final String [] PROJECTION = new String [] {/*定义了一个字符串数组 PROJECTION, + 用于指定查询数据库时返回的列。这里指定了笔记的ID、背景颜色ID和摘要。*/ + NoteColumns.ID, + NoteColumns.BG_COLOR_ID, + NoteColumns.SNIPPET + }; +/*定义了三个常量,分别表示 PROJECTION 数组中ID、背景颜色ID和摘要的索引位置。*/ + public static final int COLUMN_ID = 0; + public static final int COLUMN_BG_COLOR_ID = 1; + public static final int COLUMN_SNIPPET = 2; + + private static final String TAG = "NoteWidgetProvider";/*定义了一个用于日志输出的标签。*/ + + @Override + public void onDeleted(Context context, int[] appWidgetIds) {/*重写了 onDeleted 方法,当小部件被删除时调用。*/ + ContentValues values = new ContentValues();/*创建一个 ContentValues 对象,并设置 WIDGET_ID 为无效的小部件ID, + 用于更新数据库中的相关记录。*/ + values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + for (int i = 0; i < appWidgetIds.length; i++) {/*遍历被删除的小部件ID数组,并更新数据库中相关记录 + 的 WIDGET_ID 为无效值。*/ + context.getContentResolver().update(Notes.CONTENT_NOTE_URI, + values, + NoteColumns.WIDGET_ID + "=?", + new String[] { String.valueOf(appWidgetIds[i])}); + } + } + + private Cursor getNoteWidgetInfo(Context context, int widgetId) {/*定义了一个私有方法,用于查询数据库获 + 取指定小部件ID的笔记信息。*/ + return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, + PROJECTION, + NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", + new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, + null);/*使用 ContentResolver 查询数据库,返回包含笔记信息的 Cursor 对象。*/ + } + + protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + update(context, appWidgetManager, appWidgetIds, false); + }/*定义了一个受保护的方法 update,它调用另一个重载的 update 方法,并传递一个默认的隐私模式值 false。*/ + + private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, + boolean privacyMode) {/*定义了一个私有的重载方法 update,用于根据小部件ID数组更新小部件视图。privacyMode 参数 + 用于指示是否处于隐私模式。*/ + for (int i = 0; i < appWidgetIds.length; i++) { + if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {/*遍历小部件ID数组,并检查每个ID是否有效。*/ + int bgId = ResourceParser.getDefaultBgId(context);/*初始化背景ID和摘要字符串。*/ + String snippet = ""; + Intent intent = new Intent(context, NoteEditActivity.class);/*创建一个意图,用于启动 NoteEditActivity。*/ + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);/*设置意图的标志和额外数据。*/ + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); + + Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]);/*查询数据库获取小部件的笔记信息。*/ + if (c != null && c.moveToFirst()) {/*如果查询结果不为空且能移动到第一条记录。*/ + if (c.getCount() > 1) {/*如果有多条记录,则记录错误日志并关闭游标,返回。*/ + Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); + c.close(); + return; + } + /*根据查询结果设置摘要、背景ID和意图动作。*/ + snippet = c.getString(COLUMN_SNIPPET); + bgId = c.getInt(COLUMN_BG_COLOR_ID); + intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); + intent.setAction(Intent.ACTION_VIEW); + } else { + snippet = context.getResources().getString(R.string.widget_havenot_content); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + } + + if (c != null) {/*如果游标不为空,则关闭它。*/ + c.close(); + } + + RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());/*创建一个 RemoteViews 对象, + 用于定义小部件的远程视图。*/ + rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));/*设置小部件背景图片。*/ + intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);/*向意图添加背景ID的额外数据。*/ + /** + * Generate the pending intent to start host for the widget + */ + PendingIntent pendingIntent = null;/*初始化 PendingIntent 对象。*/ + if (privacyMode) {/*如果处于隐私模式。设置文本视图为隐私模式下的文本, + 并创建指向 NotesListActivity 的 PendingIntent*/ + rv.setTextViewText(R.id.widget_text, + context.getString(R.string.widget_under_visit_mode)); + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( + context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); + } else {/*如果不是隐私模式,则:设置文本视图为摘要文本,并创建指向 NoteEditActivity 的 PendingIntent。*/ + rv.setTextViewText(R.id.widget_text, snippet); + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } + + rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);/*设置文本视图的点击事件为之前创建的 + PendingIntent。*/ + appWidgetManager.updateAppWidget(appWidgetIds[i], rv);/*使用新的远程视图更新小部件。*/ + } + } + } +/*定义了三个抽象方法,子类需要实现这些方法以提供背景资源ID、布局ID和小部件类型。*/ + protected abstract int getBgResourceId(int bgId); + + protected abstract int getLayoutId(); + + protected abstract int getWidgetType(); +} From 385979b4a5023ee90a774c19817e2556655cebfc Mon Sep 17 00:00:00 2001 From: YourUsername Date: Sun, 15 Dec 2024 21:02:30 +0800 Subject: [PATCH 05/12] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NoteWidgetProvider_2x.java | 53 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 NoteWidgetProvider_2x.java diff --git a/NoteWidgetProvider_2x.java b/NoteWidgetProvider_2x.java new file mode 100644 index 0000000..21f57a2 --- /dev/null +++ b/NoteWidgetProvider_2x.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// 声明包名,这有助于组织代码和避免命名冲突 +package net.micode.notes.widget; +// 导入所需的Android和Java类 +import android.appwidget.AppWidgetManager; +import android.content.Context; +// 导入项目内的其他类 +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.tool.ResourceParser; + +// 声明NoteWidgetProvider_2x类,它继承自NoteWidgetProvider +public class NoteWidgetProvider_2x extends NoteWidgetProvider { + // 当小部件需要更新时,系统会调用此方法 + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + // 调用父类的update方法,传入相同的参数,以执行更新逻辑 + super.update(context, appWidgetManager, appWidgetIds); + } +// 重写父类的getLayoutId方法,用于返回此小部件布局的资源ID + @Override + protected int getLayoutId() { + // 返回R.layout.widget_2x,这是预定义在res/layout目录下的XML布局文件 + return R.layout.widget_2x; + } + // 重写父类的getBgResourceId方法,用于根据传入的背景ID获取对应的资源ID + @Override + protected int getBgResourceId(int bgId) { + // 调用ResourceParser.WidgetBgResources.getWidget2xBgResource方法,传入bgId + // 并返回对应的2x小部件背景资源ID + return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); + } +// 重写父类的getWidgetType方法,用于返回此小部件的类型 + @Override + protected int getWidgetType() { + // 返回Notes.TYPE_WIDGET_2X,这是一个在Notes类中定义的常量,表示2x小部件的类型 + return Notes.TYPE_WIDGET_2X; + } +} From c3813a2e674818005aada3fad336fffd97dd4aaf Mon Sep 17 00:00:00 2001 From: YourUsername Date: Sun, 15 Dec 2024 21:03:24 +0800 Subject: [PATCH 06/12] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NoteWidgetProvider_4x.java | 65 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 NoteWidgetProvider_4x.java diff --git a/NoteWidgetProvider_4x.java b/NoteWidgetProvider_4x.java new file mode 100644 index 0000000..a8e2100 --- /dev/null +++ b/NoteWidgetProvider_4x.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// 声明包名,用于组织代码和避免命名冲突 +package net.micode.notes.widget; + +// 导入Android框架中用于管理小部件的类 +import android.appwidget.AppWidgetManager; +// 导入Android框架中提供应用环境信息的类 +import android.content.Context; + +// 导入项目内的资源类和其他相关类 +import net.micode.notes.R; // 包含项目资源引用的类 +import net.micode.notes.data.Notes; // 可能包含与笔记数据相关的常量或方法 +import net.micode.notes.tool.ResourceParser; // 用于解析资源的工具类 + +// 声明NoteWidgetProvider_4x类,它继承自NoteWidgetProvider +public class NoteWidgetProvider_4x extends NoteWidgetProvider { + + // 当小部件需要更新时,系统会调用此方法 + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + // 调用父类的update方法,传入相同的参数,执行更新逻辑 + super.update(context, appWidgetManager, appWidgetIds); + } + + // 重写父类的getLayoutId方法,用于返回此小部件的布局资源ID + // 这里不需要@Override注解,因为该方法在父类中不是抽象的且可见性允许重写, + // 但加上@Override可以提高代码的可读性和维护性,确保方法签名的正确性。 + // 实际上,由于子类直接调用了父类的非抽象方法,这里的重写是可选的, + // 但如果父类中的getLayoutId方法被重写以提供不同的行为,则这里的重写是必要的。 + // 在这个例子中,我们假设父类有一个受保护的getLayoutId方法,这里是为了特定尺寸重写它。 + protected int getLayoutId() { + // 返回R.layout.widget_4x,这是预定义在res/layout目录下的XML布局文件 + return R.layout.widget_4x; + } + + // 重写父类的getBgResourceId方法,用于根据传入的背景ID获取对应的资源ID + @Override + protected int getBgResourceId(int bgId) { + // 调用ResourceParser.WidgetBgResources.getWidget4xBgResource方法,传入bgId + // 并返回对应的4x小部件背景资源ID + return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); + } + + // 重写父类的getWidgetType方法,用于返回此小部件的类型 + @Override + protected int getWidgetType() { + // 返回Notes.TYPE_WIDGET_4X,这是一个在Notes类中定义的常量,表示4x小部件的类型 + return Notes.TYPE_WIDGET_4X; + } +} \ No newline at end of file From fe8584a14d4fc9466cb74b0d3379b32aaaefc12e Mon Sep 17 00:00:00 2001 From: YourUsername Date: Sun, 15 Dec 2024 21:04:21 +0800 Subject: [PATCH 07/12] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BackupUtils.java | 434 ++++++++++++++++++++++++++++++++++++++++++ DataUtils.java | 311 ++++++++++++++++++++++++++++++ GTaskStringUtils.java | 118 ++++++++++++ ResourceParser.java | 186 ++++++++++++++++++ 4 files changed, 1049 insertions(+) create mode 100644 BackupUtils.java create mode 100644 DataUtils.java create mode 100644 GTaskStringUtils.java create mode 100644 ResourceParser.java diff --git a/BackupUtils.java b/BackupUtils.java new file mode 100644 index 0000000..f285f6f --- /dev/null +++ b/BackupUtils.java @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// 声明包名,用于组织代码和避免命名冲突 +package net.micode.notes.tool; + +// 导入Android框架和Java标准库中的类 +import android.content.Context; // 提供应用环境信息的类 +import android.database.Cursor; // 用于在数据库查询结果中遍历行的类 +import android.os.Environment; // 提供有关设备环境的信息,如存储状态 +import android.text.TextUtils; // 提供用于处理文本(如检查空字符串)的实用方法 +import android.text.format.DateFormat; // 提供日期和时间格式化的方法 +import android.util.Log; // 提供日志记录的类 + +import net.micode.notes.R; // 包含项目资源引用的类(尽管在这段代码中未直接使用) +import net.micode.notes.data.Notes; // 可能是一个包含数据模型和相关常量的类 +import net.micode.notes.data.Notes.DataColumns; // Notes类内部的一个静态内部类,定义了数据表的列名 +import net.micode.notes.data.Notes.DataConstants; // Notes类内部的一个静态内部类,定义了数据相关的常量 +import net.micode.notes.data.Notes.NoteColumns; // Notes类内部的一个静态内部类,定义了笔记数据表的列名 + +import java.io.File; // 表示文件和目录路径名的抽象表示形式 +import java.io.FileNotFoundException; // 当尝试打开文件失败时抛出的异常 +import java.io.FileOutputStream; // 文件输出流,用于将数据写入文件 +import java.io.IOException; // 发生I/O错误时抛出的通用异常类 +import java.io.PrintStream; // 打印流,用于打印各种数据表示形式 + +// 声明BackupUtils类 +public class BackupUtils { + // 定义一个静态常量TAG,用于日志记录中的标签 + private static final String TAG = "BackupUtils"; + + // 定义一个静态变量sInstance,用于存储BackupUtils类的单例实例 + private static BackupUtils sInstance; + + // 定义一个私有构造函数,防止外部通过new关键字创建实例 + // 注意:构造函数在这段代码中并未直接给出,但它是单例模式实现的关键部分 + + // 定义一个公开的静态同步方法getInstance,用于获取BackupUtils类的实例 + public static synchronized BackupUtils getInstance(Context context) { + // 如果sInstance为null,说明还没有创建实例,则创建一个新的实例并赋值给sInstance + if (sInstance == null) { + sInstance = new BackupUtils(context); // 注意:这里的构造函数是假设的,实际代码中需要定义 + } + // 返回sInstance,即BackupUtils类的单例实例 + return sInstance; + } +} + + /** + * Following states are signs to represents backup or restore + * status + */ + // Currently, the sdcard is not mounted + // 定义一些静态常量,用于表示不同的状态 +public static final int STATE_SD_CARD_UNMOUONTED = 0; // SD卡未挂载的状态码 +// 备注:这里有一个拼写错误,应该是"UNMOUNTED"而不是"UNMOUONTED" + +public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; // 备份文件不存在的状态码 + +public static final int STATE_DATA_DESTROIED = 2; // 数据被破坏(可能由其他程序更改)的状态码 +// 备注:这里有一个拼写错误,应该是"DESTROYED"而不是"DESTROIED" + +public static final int STATE_SYSTEM_ERROR = 3; // 系统错误导致备份或恢复失败的状态码 + +public static final int STATE_SUCCESS = 4; // 备份或恢复成功的状态码 + +// 定义一个私有成员变量,用于文本导出功能 +private TextExport mTextExport; + +// 私有构造函数,防止外部直接实例化BackupUtils类 +// 备注:这是单例模式实现的一部分,构造函数接受一个Context参数 +private BackupUtils(Context context) { + mTextExport = new TextExport(context); // 初始化mTextExport成员变量 +} + +// 定义一个私有静态方法,用于检查外部存储是否可用 +private static boolean externalStorageAvailable() { + // 通过比较Environment.getExternalStorageState()的返回值和Environment.MEDIA_MOUNTED来判断 + return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); +} + +// 定义一个公开的方法,用于导出数据到文本文件 +// 备注:这个方法返回的是一个状态码,表示导出操作的结果 +public int exportToText() { + return mTextExport.exportToText(); // 调用TextExport类的exportToText方法 +} + +// 定义一个公开的方法,用于获取导出的文本文件的文件名 +public String getExportedTextFileName() { + return mTextExport.mFileName; // 直接访问TextExport类的mFileName成员变量 +} + +// 定义一个公开的方法,用于获取导出的文本文件的目录 +public String getExportedTextFileDir() { + return mTextExport.mFileDirectory; // 直接访问TextExport类的mFileDirectory成员变量 +} + +// 定义一个私有静态内部类,用于实现文本导出的具体逻辑 +private static class TextExport { + // 定义一个静态常量数组,用于指定从数据库查询时需要返回的列 + private static final String[] NOTE_PROJECTION = { + NoteColumns.ID, // 笔记的ID + NoteColumns.MODIFIED_DATE, // 笔记的最后修改日期 + NoteColumns.SNIPPET, // 笔记的摘要或简短内容 + NoteColumns.TYPE // 笔记的类型 + }; + + // 备注:TextExport类的其他部分(如构造函数、exportToText方法以及mFileName和mFileDirectory成员变量) + // 在这段代码中未给出,但我们可以推断它们存在并用于实现文本导出的功能。 +} + + // 定义静态常量,用于表示数据库查询结果中笔记ID的索引位置 +private static final int NOTE_COLUMN_ID = 0; + +// 定义静态常量,用于表示数据库查询结果中笔记修改日期的索引位置 +private static final int NOTE_COLUMN_MODIFIED_DATE = 1; + +// 定义静态常量,用于表示数据库查询结果中笔记摘要的索引位置 +private static final int NOTE_COLUMN_SNIPPET = 2; + +// 定义一个静态常量数组,包含需要从数据库查询的数据列 +private static final String[] DATA_PROJECTION = { + DataColumns.CONTENT, // 数据内容 + DataColumns.MIME_TYPE, // MIME类型 + DataColumns.DATA1, // 第一个数据字段 + DataColumns.DATA2, // 第二个数据字段 + DataColumns.DATA3, // 第三个数据字段 + DataColumns.DATA4 // 第四个数据字段 +}; + +// 定义静态常量,用于表示数据查询结果中内容列的索引位置 +private static final int DATA_COLUMN_CONTENT = 0; + +// 定义静态常量,用于表示数据查询结果中MIME类型列的索引位置 +private static final int DATA_COLUMN_MIME_TYPE = 1; + +// 这里有一个问题:DATA_COLUMN_CALL_DATE(电话日期)与DATA_PROJECTION数组不匹配 +// 因为DATA_PROJECTION中没有与电话日期对应的列,这可能会导致索引越界错误 +private static final int DATA_COLUMN_CALL_DATE = 2; + +// 定义静态常量,用于表示数据查询结果中电话号码列的索引位置 +// 但这里也有问题:根据DATA_PROJECTION数组,DATA_COLUMN_PHONE_NUMBER应该指向索引4, +// 这个定义是正确的,但是之前的DATA_COLUMN_CALL_DATE使用了索引2,可能会导致混淆 +private static final int DATA_COLUMN_PHONE_NUMBER = 4; + +// 定义一个实例变量,用于存储文本格式的字符串数组 +// 这个数组可能从资源文件中获取,用于格式化导出的文本 +private final String[] TEXT_FORMAT; + +// 定义静态常量,用于表示格式化字符串数组中文件夹名称的索引位置 +private static final int FORMAT_FOLDER_NAME = 0; + +// 定义静态常量,用于表示格式化字符串数组中笔记日期的索引位置 +private static final int FORMAT_NOTE_DATE = 1; + +// 定义静态常量,用于表示格式化字符串数组中笔记内容的索引位置 +private static final int FORMAT_NOTE_CONTENT = 2; + +// 定义一个实例变量,用于存储上下文(Context)对象 +private Context mContext; + +// 定义一个实例变量,用于存储导出的文本文件的文件名 +private String mFileName; + +// 定义一个实例变量,用于存储导出的文本文件的目录 +private String mFileDirectory; + +// 构造函数,用于初始化TextExport对象 +public TextExport(Context context) { + // 从资源文件中获取格式化字符串数组,并赋值给TEXT_FORMAT实例变量 + TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); + // 将传入的上下文对象赋值给mContext实例变量 + mContext = context; + // 初始化mFileName和mFileDirectory实例变量为空字符串 + mFileName = ""; + mFileDirectory = ""; +} + // 定义一个私有方法,接收一个文件夹ID(字符串)和一个打印流(PrintStream)作为参数,无返回值 +private void exportFolderToText(String folderId, PrintStream ps) { + // 使用getContentResolver()方法从内容提供者处查询属于指定文件夹的笔记 + // Notes.CONTENT_NOTE_URI是内容提供者的URI,NOTE_PROJECTION是查询返回的列投影 + // NoteColumns.PARENT_ID + "=?"是查询的选择条件,new String[] { folderId }是选择条件的参数 + Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { folderId }, null); + + // 检查查询返回的Cursor是否不为null + if (notesCursor != null) { + // 移动Cursor到第一行,如果查询结果不为空,则进入if语句 + if (notesCursor.moveToFirst()) { + // 使用do-while循环遍历查询结果集 + do { + // 打印笔记的最后修改日期 + // 使用getFormat(FORMAT_NOTE_DATE)获取格式化字符串,然后使用DateFormat.format()和getString(R.string.format_datetime_mdhm) + // 格式化最后修改日期,最后通过PrintStream的println方法打印出来 + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( + mContext.getString(R.string.format_datetime_mdhm), + notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); + + // 查询属于当前笔记的数据 + // 从Cursor中获取笔记的ID + String noteId = notesCursor.getString(NOTE_COLUMN_ID); + // 调用exportNoteToText方法导出当前笔记的文本内容到PrintStream中 + // 注意:exportNoteToText方法需要在当前类中定义,但您没有提供其实现 + exportNoteToText(noteId, ps); + } while (notesCursor.moveToNext()); // 循环直到Cursor遍历完所有行 + } + // 关闭Cursor,释放资源 + notesCursor.close(); + } +} + + /** + * Export note identified by id to a print stream + */ + // 定义一个私有方法,用于将指定笔记ID的笔记内容导出到文本输出流(PrintStream)中 +private void exportNoteToText(String noteId, PrintStream ps) { + // 使用内容解析器查询与指定笔记ID匹配的笔记数据 + // 这里的Notes.CONTENT_DATA_URI是内容提供者的URI,DATA_PROJECTION是我们要查询的列投影 + // DataColumns.NOTE_ID + "=?"是查询条件,表示我们要查询的笔记ID + // new String[] { noteId }是查询条件的参数,即具体的笔记ID值 + Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, + DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { noteId }, null); + + // 检查查询返回的Cursor是否不为空 + if (dataCursor != null) { + // 移动Cursor到第一行数据(如果有的话) + if (dataCursor.moveToFirst()) { + // 使用do-while循环遍历Cursor中的所有行 + do { + // 从当前行中获取MIME类型 + String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); + + // 判断MIME类型是否为CALL_NOTE,即电话笔记 + if (DataConstants.CALL_NOTE.equals(mimeType)) { + // 如果是电话笔记,则打印电话号码 + String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); + long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); + String location = dataCursor.getString(DATA_COLUMN_CONTENT); // 这里location可能表示电话笔记的附加信息或位置,但命名可能有些误导 + + // 如果电话号码不为空,则按照指定格式打印电话号码 + if (!TextUtils.isEmpty(phoneNumber)) { + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), phoneNumber)); + } + + // 打印通话日期,使用指定的日期时间格式 + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat + .format(mContext.getString(R.string.format_datetime_mdhm), callDate))); + + // 如果location(附加信息)不为空,则按照指定格式打印它 + if (!TextUtils.isEmpty(location)) { + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), location)); + } + } + // 判断MIME类型是否为普通笔记 + else if (DataConstants.NOTE.equals(mimeType)) { + // 如果是普通笔记,则打印笔记内容 + String content = dataCursor.getString(DATA_COLUMN_CONTENT); + if (!TextUtils.isEmpty(content)) { + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), content)); + } + } + } while (dataCursor.moveToNext()); // 移动到下一行,继续循环 + } + // 关闭Cursor以释放资源 + dataCursor.close(); + } + +} + // print a line separator between note + try {//这段代码尝试向PrintStream对象ps写入一个字节数组, + //该数组包含一个换行符(Character.LINE_SEPARATOR)和一个字符(Character.LETTER_NUMBER, + //如果写入过程中发生IOException,则捕获异常并通过日志打印错误信息。 + ps.write(new byte[] { + Character.LINE_SEPARATOR, Character.LETTER_NUMBER + }); + } catch (IOException e) { + Log.e(TAG, e.toString()); + } + } + + /** + * Note will be exported as text which is user readable + */ + public int exportToText() { + //这是一个方法的声明,名为exportToText,它返回一个整型值,表示导出操作的状态。方法上方的注释说明笔记将被导出为用户可读的文本格式。 + if (!externalStorageAvailable()) { + //这段代码检查外部存储是否可用。如果不可用,则记录一条调试日志,并返回表示SD卡未挂载的状态码。 + Log.d(TAG, "Media was not mounted"); + return STATE_SD_CARD_UNMOUONTED; + } + + PrintStream ps = getExportToTextPrintStream(); + //获取一个PrintStream对象,用于将文本写入文件。如果获取失败(即ps为null),则记录一条错误日志,并返回表示系统错误的状态码。 + if (ps == null) { + Log.e(TAG, "get print stream error"); + return STATE_SYSTEM_ERROR; + } + // First export folder and its notes + //使用ContentResolver查询数据库,获取所有文件夹(不包括垃圾文件夹)和通话记录文件夹的游标。 + Cursor folderCursor = mContext.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, + "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " + + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " + + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); +//如果游标不为空,遍历所有文件夹,对每个文件夹执行一系列操作 + if (folderCursor != null) { + if (folderCursor.moveToFirst()) { + do { + // Print folder's name + String folderName = ""; + if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { + folderName = mContext.getString(R.string.call_record_folder_name); + } else { + folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); + } + if (!TextUtils.isEmpty(folderName)) { + ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); + } + String folderId = folderCursor.getString(NOTE_COLUMN_ID); + exportFolderToText(folderId, ps); + } while (folderCursor.moveToNext()); + } + folderCursor.close(); + } + + // Export notes in root's folder + //再次使用ContentResolver查询数据库,这次获取所有根目录下的笔记的游标。 + Cursor noteCursor = mContext.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, + NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + + "=0", null, null); +//如果游标不为空,遍历所有根目录下的笔记,对每个笔记执行一系列操作 + if (noteCursor != null) { + if (noteCursor.moveToFirst()) { + do { + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( + mContext.getString(R.string.format_datetime_mdhm), + noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); + // Query data belong to this note + String noteId = noteCursor.getString(NOTE_COLUMN_ID); + exportNoteToText(noteId, ps); + } while (noteCursor.moveToNext()); + } + noteCursor.close(); + } + ps.close();//关闭PrintStream对象,完成文件写入。 + + return STATE_SUCCESS;//返回表示操作成功的状态码。 + } + + /** + * Get a print stream pointed to the file {@generateExportedTextFile} + */ + private PrintStream getExportToTextPrintStream() { + //这是一个私有方法,返回一个PrintStream对象,用于将文本写入文件。 + File file = generateFileMountedOnSDcard(mContext, R.string.file_path, + R.string.file_name_txt_format); + //调用generateFileMountedOnSDcard方法生成一个文件,并返回该文件的File对象。 + //这个方法需要三个参数:上下文mContext,文件路径的资源IDR.string.file_path,和文件名格式的资源IDR.string.file_name_txt_format。 + if (file == null) { + Log.e(TAG, "create file to exported failed"); + return null;//如果generateFileMountedOnSDcard方法返回null,表示文件创建失败,则记录一条错误日志,并返回null。 + } + mFileName = file.getName(); + mFileDirectory = mContext.getString(R.string.file_path);//获取文件的名称和文件所在的目录路径,并将它们分别赋值给mFileName和mFileDirectory变量。 + PrintStream ps = null;//声明一个PrintStream对象ps,初始化为null。 + try { + //尝试创建一个FileOutputStream对象fos,用于向文件写入数据。然后,使用fos创建一个PrintStream对象ps。 + //如果在创建FileOutputStream或PrintStream时发生FileNotFoundException或NullPointerException,则捕获异常,打印堆栈跟踪,并返回null。 + FileOutputStream fos = new FileOutputStream(file); + ps = new PrintStream(fos); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } catch (NullPointerException e) { + e.printStackTrace(); + return null; + } + return ps;//返回PrintStream对象ps。 + } + } + + /** + * Generate the text file to store imported data + */ + private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { + //这是一个私有静态方法,返回一个File对象,表示在SD卡上创建的文件。 + StringBuilder sb = new StringBuilder();//创建一个StringBuilder对象sb,并向其中追加SD卡的根目录路径。 + sb.append(Environment.getExternalStorageDirectory()); + sb.append(context.getString(filePathResId));//向sb中追加文件路径字符串,该字符串是从资源文件中获取的。 + File filedir = new File(sb.toString());//使用sb.toString()创建一个表示文件目录的File对象filedir。 + sb.append(context.getString( + //向sb中追加文件名,文件名是根据提供的格式资源ID和当前时间格式化的。 + fileNameFormatResId, + DateFormat.format(context.getString(R.string.format_date_ymd), + System.currentTimeMillis()))); + File file = new File(sb.toString());//使用sb.toString()创建一个表示文件的File对象file。 + + try { + //尝试检查文件目录是否存在,如果不存在则创建它。然后,检查文件是否存在,如果不存在则创建新文件。 +//如果在检查或创建目录/文件时发生SecurityException或IOException,则捕获异常,打印堆栈跟踪。 + if (!filedir.exists()) { + filedir.mkdir(); + } + if (!file.exists()) { + file.createNewFile(); + } + return file; + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + + return null;//如果方法执行过程中出现任何异常,或者文件无法被创建,则返回null。 + } +} + + diff --git a/DataUtils.java b/DataUtils.java new file mode 100644 index 0000000..b228a39 --- /dev/null +++ b/DataUtils.java @@ -0,0 +1,311 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool;//指定了当前类所在的包名。 + +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.os.RemoteException; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.CallNote; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; + +import java.util.ArrayList; +import java.util.HashSet;//以上导入了代码中使用的其他类。 + + +public class DataUtils {//定义了一个名为DataUtils的公共类,这个类用于封装与数据操作相关的工具方法。 + public static final String TAG = "DataUtils";//定义了一个公共静态最终变量TAG,用于日志记录时的标签。 + public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { + //定义了一个公共静态方法batchDeleteNotes,用于批量删除笔记。该方法接收一个ContentResolver对象和一个包含笔记ID的HashSet集合作为参数,返回一个布尔值表示操作是否成功。 + if (ids == null) { + Log.d(TAG, "the ids is null"); + return true;//定义了一个公共静态方法batchDeleteNotes,用于批量删除笔记。该方法接收一个ContentResolver对象和一个包含笔记ID的HashSet集合作为参数,返回一个布尔值表示操作是否成功。 + } + if (ids.size() == 0) { + Log.d(TAG, "no id is in the hashset"); + return true;//如果ID集合为空,则记录一条调试日志并返回true + } + + ArrayList operationList = new ArrayList(); + //创建一个ArrayList对象,用于存储将要执行的内容提供者操作。 + for (long id : ids) {//遍历ID集合中的每个ID。 + if(id == Notes.ID_ROOT_FOLDER) { + Log.e(TAG, "Don't delete system folder root"); + continue;//如果当前ID是系统根文件夹的ID,则记录一条错误日志并跳过当前循环迭代,不删除根文件夹。 + } + ContentProviderOperation.Builder builder = ContentProviderOperation + .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); + //创建一个新的删除操作构建器,指定要删除的内容URI(通过ContentUris.withAppendedId方法将笔记ID附加到笔记内容URI上)。 + operationList.add(builder.build());//将构建好的删除操作添加到操作列表中。 + } + try { + ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); + //尝试批量执行操作列表中的所有操作。applyBatch方法接收内容提供者的授权字符串和操作列表,返回操作结果数组。 + if (results == null || results.length == 0 || results[0] == null) { + Log.d(TAG, "delete notes failed, ids:" + ids.toString()); + return false; + }//如果操作结果数组为null、长度为0或第一个结果为null,则认为删除操作失败,记录一条调试日志并返回false。 + return true;//如果操作成功执行,返回true。 + } catch (RemoteException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + } catch (OperationApplicationException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + }//如果操作成功执行,返回true。 + return false;//如果发生异常,返回false + } + + public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { + //定义一个静态方法 moveNoteToFoler(注意这里有个拼写错误,应为 moveNoteToFolder),接受四个参数:ContentResolver resolver(用于与内容提供者交互的工具),long id(要移动的笔记的ID),long srcFolderId(源文件夹ID),long desFolderId(目标文件夹ID)。 + ContentValues values = new ContentValues();//创建一个 ContentValues 对象,用于存储要更新的数据。 + values.put(NoteColumns.PARENT_ID, desFolderId);//将目标文件夹ID赋值给 PARENT_ID,表示笔记应该移动到的文件夹。 + values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);//将源文件夹ID赋值给 ORIGIN_PARENT_ID,可能用于记录笔记原来所在的文件夹 + values.put(NoteColumns.LOCAL_MODIFIED, 1);//将 LOCAL_MODIFIED 设置为1 + resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); + //使用 ContentResolver 的 update 方法更新指定ID的笔记。ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id) 用于生成指向特定笔记的URI,values 包含要更新的字段。 + } + + public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids, + long folderId) {//定义一个静态方法 batchMoveToFolder,接受三个参数:ContentResolver resolver,一个包含多个笔记ID的 HashSet ids,以及目标文件夹ID long folderId。返回一个布尔值表示操作是否成功。 + if (ids == null) { + Log.d(TAG, "the ids is null"); + return true;//如果传入的ID集合为null,则记录日志并返回true + } + + ArrayList operationList = new ArrayList(); + //创建一个 ArrayList 来存储一系列的内容提供者操作(ContentProviderOperation),这些操作将批量执行。 + for (long id : ids) {//遍历ID集合,为每个ID创建一个更新操作的构建器,指定要更新的笔记的URI。 + ContentProviderOperation.Builder builder = ContentProviderOperation + .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); + builder.withValue(NoteColumns.PARENT_ID, folderId);//设置目标文件夹ID。 + builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);//设置 LOCAL_MODIFIED 为1。 + operationList.add(builder.build());//将构建好的操作添加到操作列表中。 + } + + try {//尝试批量执行操作列表中的操作。Notes.AUTHORITY 是内容提供者的权限名。 + ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); + if (results == null || results.length == 0 || results[0] == null) { + Log.d(TAG, "delete notes failed, ids:" + ids.toString()); + return false;//如果操作结果为空、长度为0或第一个结果为null,则记录日志并返回false。 + } + return true; + } catch (RemoteException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + } catch (OperationApplicationException e) { + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));//捕获并处理 RemoteException 和 OperationApplicationException 异常,记录错误日志。 + } + return false;//如果出现异常,则返回false。 + } + + /** + * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} + */ + public static int getUserFolderCount(ContentResolver resolver) {//定义一个静态方法getUserFolderCount,它接受一个ContentResolver对象作为参数,用于与内容提供者交互。 + Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,//使用ContentResolver的query方法发起查询。Notes.CONTENT_NOTE_URI是查询的URI,指向笔记数据的集合。 + new String[] { "COUNT(*)" },//指定查询的列,这里只查询一个列,即"COUNT(*)",用于计算满足条件的行数。 + NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",//指定查询的条件。这里条件是笔记类型(NoteColumns.TYPE)等于某个值,并且父ID(NoteColumns.PARENT_ID)不等于另一个值。 + new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, + null);//为查询条件提供具体的值。第一个值是文件夹的类型(Notes.TYPE_FOLDER),第二个值是垃圾文件夹的ID(Notes.ID_TRASH_FOLER)。 +//查询的排序规则为null,表示不需要排序。 + int count = 0;//初始化一个变量count用于存储查询结果。 + if(cursor != null) {//检查Cursor对象是否不为null,即查询是否成功返回了结果。 + if(cursor.moveToFirst()) {//尝试将Cursor移动到第一行,如果Cursor不为空且至少有一行数据,则此操作成功。 + try { + count = cursor.getInt(0);//从Cursor的第一列(索引为0)获取整数值,即满足条件的行数,并将其赋值给count。 + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, "get folder count failed:" + e.toString()); + //捕获IndexOutOfBoundsException异常,这通常发生在尝试访问不存在的列时。使用Log.e打印错误日志。 + } finally { + cursor.close();//无论是否发生异常,都确保关闭Cursor以释放资源。 + } + } + } + return count;//返回计算得到的文件夹数量。 + } + + public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { + //定义一个静态方法visibleInNoteDatabase,它接受ContentResolver对象、笔记ID和笔记类型作为参数。 + Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), + null, + NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, + new String [] {String.valueOf(type)}, + null);//使用ContentUris.withAppendedId方法将笔记ID附加到URI上,以查询特定笔记的详细信息。 + + boolean exist = false;//初始化一个布尔变量exist,用于表示笔记是否存在且满足条件。 + if (cursor != null) { + if (cursor.getCount() > 0) { + exist = true; + } + cursor.close(); + } + return exist;//检查Cursor是否不为null,如果Cursor的计数大于0,则设置exist为true。最后关闭Cursor并返回exist。 + } + + public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {//检查指定ID的笔记是否存在。 + Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), + null, null, null, null); + + boolean exist = false; + if (cursor != null) { + if (cursor.getCount() > 0) { + exist = true; + } + cursor.close(); + } + return exist; + } + + public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { + //定义一个静态方法existInDataDatabase,它接受一个ContentResolver和一个长整型dataId作为参数,返回一个布尔值表示是否存在。 + Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), + null, null, null, null);//使用ContentResolver的query方法查询数据库。ContentUris.withAppendedId方法用于生成带有指定ID的URI。这里查询的是Notes.CONTENT_DATA_URI,即数据项的URI,并且指定了dataId。查询的列、选择条件、选择参数和排序方式均为null,表示查询所有列且不设置过滤条件。 + + boolean exist = false;//初始化一个布尔变量exist为false,用于记录数据项是否存在。 + if (cursor != null) {//检查cursor是否为null,确保查询成功。 + if (cursor.getCount() > 0) { + exist = true;//如果cursor中的记录数大于0,则数据项存在,将exist设置为true。 + } + cursor.close();//关闭cursor,释放资源。 + } + return exist;//返回exist的值。 + } + + public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { + //定义一个静态方法checkVisibleFolderName,它接受一个ContentResolver和一个字符串name作为参数,返回一个布尔值表示是否存在。 + Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.SNIPPET + "=?", + new String[] { name }, null); + //使用ContentResolver的query方法查询数据库。查询的URI是Notes.CONTENT_NOTE_URI,即笔记的URI。 + //查询条件是类型为文件夹(NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER),父ID不等于垃圾文件夹ID(NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER),并且摘要(即名称)等于指定的name。 + boolean exist = false;//初始化一个布尔变量exist为false。 + if(cursor != null) {//检查cursor是否为null。 + if(cursor.getCount() > 0) { + exist = true;//如果cursor中的记录数大于0,则文件夹存在,将exist设置为true。 + } + cursor.close();//关闭cursor。 + } + return exist;//返回exist的值。 + } + + public static HashSet getFolderNoteWidget(ContentResolver resolver, long folderId) {//定义一个静态方法getFolderNoteWidget, + //它接受一个ContentResolver和一个长整型folderId作为参数,返回一个HashSet。 + Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, + new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, + NoteColumns.PARENT_ID + "=?", + new String[] { String.valueOf(folderId) }, + null);//使用ContentResolver的query方法查询数据库。查询的URI是Notes.CONTENT_NOTE_URI。查询的列是WIDGET_ID和WIDGET_TYPE。查询条件是父ID等于指定的folderId。 + + HashSet set = null;//初始化一个HashSet类型的变量set为null。 + if (c != null) {//检查cursor是否为null。 + if (c.moveToFirst()) {//如果cursor移动到第一条记录。 + set = new HashSet();//初始化set。 + do {//开始一个循环,遍历cursor中的所有记录。 + try {//创建一个AppWidgetAttribute对象,并从cursor中获取WIDGET_ID和WIDGET_TYPE的值,添加到set中。如果发生IndexOutOfBoundsException异常,则记录错误日志。 + AppWidgetAttribute widget = new AppWidgetAttribute(); + widget.widgetId = c.getInt(0); + widget.widgetType = c.getInt(1); + set.add(widget); + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, e.toString()); + } + } while (c.moveToNext());//循环直到cursor中没有更多记录。 + } + c.close();//关闭cursor。 + } + return set;//返回set。 + } + + public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { + //定义一个静态方法getCallNumberByNoteId,它接受一个ContentResolver和一个长整型noteId作为参数,返回一个字符串,即电话号码。 + Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, + new String [] { CallNote.PHONE_NUMBER }, + CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?", + new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE }, + null);//使用ContentResolver的query方法查询数据库。查询的URI是Notes.CONTENT_DATA_URI。查询的列是PHONE_NUMBER。查询条件是笔记ID等于指定的noteId,并且MIME类型等于CallNote.CONTENT_ITEM_TYPE。 + + if (cursor != null && cursor.moveToFirst()) {//检查cursor是否为null并且是否能移动到第一条记录。 + try { + return cursor.getString(0); + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, "Get call number fails " + e.toString()); + } finally { + cursor.close(); + }//从cursor中获取电话号码,并返回。如果发生IndexOutOfBoundsException异常,则记录错误日志。无论如何,最后都要关闭cursor。 + } + return "";//如果查询失败或没有找到匹配的记录,则返回空字符串。 + } + + public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { + //定义了一个静态方法,该方法接收一个ContentResolver对象、一个电话号码字符串和一个通话日期(长整型),用于查询笔记ID。 + Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,//使用ContentResolver的query方法查询Notes.CONTENT_DATA_URI,这是一个URI,指向提供笔记数据的内容提供者。 + new String [] { CallNote.NOTE_ID },//指定查询返回的列,这里只请求返回笔记的ID。 + CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL(" + + CallNote.PHONE_NUMBER + ",?)",//定义查询的选择条件,包括通话日期匹配、MIME类型匹配(确保是笔记的正确类型),以及电话号码匹配 + new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber },//为上述条件提供具体的值,分别是通话日期、MIME类型和电话号码。 + null);//不需要排序,所以传递null。 + + if (cursor != null) {//检查Cursor对象是否非空。 + if (cursor.moveToFirst()) {//尝试将游标移动到第一行,检查是否有数据。 + try { + return cursor.getLong(0); + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, "Get call note id fails " + e.toString()); + }//尝试从第一行获取并返回笔记ID。如果发生IndexOutOfBoundsException(可能是因为查询结果没有包含期望的列),则记录错误日志。 + } + cursor.close(); + } + return 0;//关闭Cursor,如果没有找到匹配的笔记ID,则返回0。 + } + + public static String getSnippetById(ContentResolver resolver, long noteId) {//定义了一个静态方法,用于根据笔记ID获取笔记摘要。 + Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,//使用ContentResolver查询Notes.CONTENT_NOTE_URI,这是指向笔记数据的URI。 + new String [] { NoteColumns.SNIPPET },//指定查询返回的列,这里只请求返回笔记的摘要。 + NoteColumns.ID + "=?",//定义查询的选择条件,即笔记ID匹配。 + new String [] { String.valueOf(noteId)},为上述条件提供具体的值,即笔记ID。 + null);//不需要排序,所以传递null。 + + if (cursor != null) {//检查Cursor对象是否非空。 + String snippet = "";//初始化摘要字符串。 + if (cursor.moveToFirst()) {//尝试将游标移动到第一行,检查是否有数据。 + snippet = cursor.getString(0);//从第一行获取并设置摘要。 + } + cursor.close();//关闭Cursor。 + return snippet; + } + throw new IllegalArgumentException("Note is not found with id: " + noteId);//如果没有找到匹配的笔记,则抛出异常。 + } + + public static String getFormattedSnippet(String snippet) {//定义了一个静态方法,用于格式化摘要字符串。 + if (snippet != null) {//检查摘要是否非空。 + snippet = snippet.trim();//去除摘要前后的空格。 + int index = snippet.indexOf('\n');//查找摘要中第一个换行符的位置。 + if (index != -1) {//如果找到了换行符。 + snippet = snippet.substring(0, index);//只保留换行符之前的部分。 + } + } + return snippet;//返回格式化后的摘要。 + } +} diff --git a/GTaskStringUtils.java b/GTaskStringUtils.java new file mode 100644 index 0000000..6e77fc7 --- /dev/null +++ b/GTaskStringUtils.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool;//这一行声明了类所在的包名,即net.micode.notes.tool。这是Java中组织类的一种方式,有助于模块化代码和避免命名冲突。 + +public class GTaskStringUtils { + //这一行定义了一个公开的类GTaskStringUtils。在Java中,public关键字意味着这个类可以被任何其他类访问。 + +//接下来的部分是一系列使用final static String定义的常量。在Java中,final意味着变量的值一旦被赋予就不能被改变,static意味着变量属于类而不是类的任何特定对象实例,String是变量的数据类型。 + + public final static String GTASK_JSON_ACTION_ID = "action_id";//这行代码定义了一个常量GTASK_JSON_ACTION_ID,其值为"action_id"。这个常量可能用于标识JSON对象中的某个动作ID字段。 + + public final static String GTASK_JSON_ACTION_LIST = "action_list";//定义了一个常量GTASK_JSON_ACTION_LIST,值为"action_list",可能用于表示JSON中的动作列表字段。 + + public final static String GTASK_JSON_ACTION_TYPE = "action_type";//定义了一个常量GTASK_JSON_ACTION_TYPE,值为"action_type",用于标识JSON中的动作类型字段。 + + public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";//定义了一个常量GTASK_JSON_ACTION_TYPE_CREATE,值为"create",表示创建动作的类型。 + + public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";//定义了一个常量GTASK_JSON_ACTION_TYPE_GETALL,值为"get_all",表示获取所有数据的动作类型。 + + public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";//定义了一个常量GTASK_JSON_ACTION_TYPE_MOVE,值为"move",表示移动的动作类型。 + + public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";//定义了一个常量GTASK_JSON_ACTION_TYPE_UPDATE,值为"update",表示更新的动作类型。 +//后续的代码继续定义了与GTask相关的各种JSON字段的常量,包括创建者ID、子实体、客户端版本、完成状态、当前列表ID、默认列表ID、删除状态、目标列表、目标父级、目标父级类型、实体增量、 +//实体类型、获取删除标志、ID、索引、最后修改时间、最新同步点、列表ID、列表集合、名称、新ID、笔记、父级ID、前一个兄弟ID、结果集合、源列表、任务集合、类型(分为组和任务两种)和用户等。 + public final static String GTASK_JSON_CREATOR_ID = "creator_id"; + + public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; + + public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; + + public final static String GTASK_JSON_COMPLETED = "completed"; + + public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; + + public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; + + public final static String GTASK_JSON_DELETED = "deleted"; + + public final static String GTASK_JSON_DEST_LIST = "dest_list"; + + public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; + + public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; + + public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; + + public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; + + public final static String GTASK_JSON_GET_DELETED = "get_deleted"; + + public final static String GTASK_JSON_ID = "id"; + + public final static String GTASK_JSON_INDEX = "index"; + + public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; + + public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; + + public final static String GTASK_JSON_LIST_ID = "list_id"; + + public final static String GTASK_JSON_LISTS = "lists"; + + public final static String GTASK_JSON_NAME = "name"; + + public final static String GTASK_JSON_NEW_ID = "new_id"; + + public final static String GTASK_JSON_NOTES = "notes"; + + public final static String GTASK_JSON_PARENT_ID = "parent_id"; + + public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; + + public final static String GTASK_JSON_RESULTS = "results"; + + public final static String GTASK_JSON_SOURCE_LIST = "source_list"; + + public final static String GTASK_JSON_TASKS = "tasks"; + + public final static String GTASK_JSON_TYPE = "type"; + + public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; + + public final static String GTASK_JSON_TYPE_TASK = "TASK"; + + public final static String GTASK_JSON_USER = "user"; + + public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";//定义了一个常量MIUI_FOLDER_PREFFIX,值为"[MIUI_Notes]",可能用于标识MIUI笔记的文件夹前缀。 + + public final static String FOLDER_DEFAULT = "Default";//定义了一个常量FOLDER_DEFAULT,值为"Default",表示默认文件夹的名称。 + + public final static String FOLDER_CALL_NOTE = "Call_Note";//定义了一个常量FOLDER_CALL_NOTE,值为"Call_Note",表示通话记录文件夹的名称。 + + + public final static String FOLDER_META = "METADATA";//定义了一个常量FOLDER_META,值为"METADATA",表示元数据文件夹的名称。 + + public final static String META_HEAD_GTASK_ID = "meta_gid";//定义了一个常量META_HEAD_GTASK_ID,值为"meta_gid",可能用于标识元数据中的GTask ID。 + + public final static String META_HEAD_NOTE = "meta_note";//定义了一个常量META_HEAD_NOTE,值为"meta_note",可能用于标识元数据中的笔记信息。 + + public final static String META_HEAD_DATA = "meta_data";//定义了一个常量META_HEAD_DATA,值为"meta_data",可能用于标识元数据中的其他数据。 + + public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE";//定义了一个常量META_NOTE_NAME,值为"[META INFO] DON'T UPDATE AND DELETE",表示包含元数据的笔记的名称,提示用户不要更新或删除这个笔记。 + +} diff --git a/ResourceParser.java b/ResourceParser.java new file mode 100644 index 0000000..bd0f0b7 --- /dev/null +++ b/ResourceParser.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool;//这行代码声明了类所在的包名,即net.micode.notes.tool。 + +import android.content.Context; +import android.preference.PreferenceManager;//这两行代码导入了Android框架中的Context和PreferenceManager类。 + +import net.micode.notes.R;//这行代码导入了net.micode.notes包下的R类。R类是一个自动生成的类,包含了项目中所有资源的引用,如布局、字符串、图片等。 +import net.micode.notes.ui.NotesPreferenceActivity;//这行代码导入了net.micode.notes.ui包下的NotesPreferenceActivity类。 + +public class ResourceParser {//这行代码定义了一个公开的类ResourceParser。 + + public static final int YELLOW = 0; + public static final int BLUE = 1; + public static final int WHITE = 2; + public static final int GREEN = 3; + public static final int RED = 4;//这几行代码定义了五个公开的静态常量,分别代表不同的颜色,并用整数0到4进行标识。 + + public static final int BG_DEFAULT_COLOR = YELLOW;//这行代码定义了一个公开的静态常量BG_DEFAULT_COLOR,并将其值设置为YELLOW,即默认背景颜色为黄色。 + + public static final int TEXT_SMALL = 0; + public static final int TEXT_MEDIUM = 1; + public static final int TEXT_LARGE = 2; + public static final int TEXT_SUPER = 3;//这几行代码定义了四个公开的静态常量,分别代表不同的文本大小。 + + public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;//这行代码定义了一个公开的静态常量BG_DEFAULT_FONT_SIZE,并将其值设置为TEXT_MEDIUM,即默认字体大小为中等 + + public static class NoteBgResources {//这行代码定义了一个公开的静态内部类NoteBgResources。 + private final static int [] BG_EDIT_RESOURCES = new int [] { + R.drawable.edit_yellow, + R.drawable.edit_blue, + R.drawable.edit_white, + R.drawable.edit_green, + R.drawable.edit_red + };//在NoteBgResources内部类中,定义了一个私有的静态整型数组BG_EDIT_RESOURCES,并初始化为包含五个图片资源ID的数组。这些资源ID对应于五种不同颜色的编辑背景图片。 + + private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { + R.drawable.edit_title_yellow, + R.drawable.edit_title_blue, + R.drawable.edit_title_white, + R.drawable.edit_title_green, + R.drawable.edit_title_red + };//在NoteBgResources内部类中,定义了另一个私有的静态整型数组BG_EDIT_TITLE_RESOURCES,并初始化为包含五个图片资源ID的数组。这些资源ID对应于五种不同颜色的编辑标题背景图片。 + + public static int getNoteBgResource(int id) { + return BG_EDIT_RESOURCES[id]; + }//在NoteBgResources内部类中,定义了一个公开的静态方法getNoteBgResource,它接受一个整数id作为参数,并返回对应id的编辑背景图片资源ID。 + + public static int getNoteTitleBgResource(int id) { + return BG_EDIT_TITLE_RESOURCES[id]; + }//在NoteBgResources内部类中,定义了另一个公开的静态方法getNoteTitleBgResource,它同样接受一个整数id作为参数,并返回对应id的编辑标题背景图片资源ID。 + } + + public static int getDefaultBgId(Context context) {//定义了一个公开的静态方法getDefaultBgId,它接受一个Context对象作为参数,并返回一个整型值。这个方法用于获取默认的背景ID。 + if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(//开始了一个条件判断,使用PreferenceManager获取默认的SharedPreferences实例,然后从中读取一个布尔值。 + NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {//完成条件判断的部分,检查是否设置了背景颜色的偏好(通过PREFERENCE_SET_BG_COLOR_KEY键),如果没有设置,则默认为false。 + return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);//如果条件为真(即用户设置了自定义背景颜色),则返回一个随机整数,这个整数是NoteBgResources.BG_EDIT_RESOURCES数组长度的某个值 + } else {//如果条件为假(即用户没有设置自定义背景颜色),则执行else部分的代码。 + return BG_DEFAULT_COLOR;//返回一个名为BG_DEFAULT_COLOR的整型值,这个值是默认的背景颜色ID。 + } + } + + public static class NoteItemBgResources {//定义了一个公开的静态内部类NoteItemBgResources,用于封装笔记项背景资源的获取方法。 + private final static int [] BG_FIRST_RESOURCES = new int [] {//定义了一个私有的静态最终整型数组BG_FIRST_RESOURCES,包含了一系列背景资源ID,用于表示笔记项在列表中的第一个位置时的背景。 + R.drawable.list_yellow_up, + R.drawable.list_blue_up, + R.drawable.list_white_up, + R.drawable.list_green_up, + R.drawable.list_red_up + }; + + private final static int [] BG_NORMAL_RESOURCES = new int [] {//定义了一个用于表示笔记项在列表中的正常位置时的背景资源ID数组。 + R.drawable.list_yellow_middle, + R.drawable.list_blue_middle, + R.drawable.list_white_middle, + R.drawable.list_green_middle, + R.drawable.list_red_middle + }; + + private final static int [] BG_LAST_RESOURCES = new int [] {//定义了一个用于表示笔记项在列表中的最后一个位置时的背景资源ID数组。 + R.drawable.list_yellow_down, + R.drawable.list_blue_down, + R.drawable.list_white_down, + R.drawable.list_green_down, + R.drawable.list_red_down, + }; + + private final static int [] BG_SINGLE_RESOURCES = new int [] {//定义了一个用于表示单独的笔记项(不在列表中)时的背景资源ID数组。 + R.drawable.list_yellow_single, + R.drawable.list_blue_single, + R.drawable.list_white_single, + R.drawable.list_green_single, + R.drawable.list_red_single + }; + + public static int getNoteBgFirstRes(int id) {//定义了一个公开的静态方法,根据提供的ID返回BG_FIRST_RESOURCES数组中对应的背景资源ID。 + + return BG_FIRST_RESOURCES[id]; + } + + public static int getNoteBgLastRes(int id) {//定义了一个方法,用于返回BG_LAST_RESOURCES数组中对应的背景资源ID。 + return BG_LAST_RESOURCES[id]; + } + + public static int getNoteBgSingleRes(int id) {//定义了一个方法,用于返回BG_SINGLE_RESOURCES数组中对应的背景资源ID。 + return BG_SINGLE_RESOURCES[id]; + } + + public static int getNoteBgNormalRes(int id) {//定义了一个方法,用于返回BG_SINGLE_RESOURCES数组中对应的背景资源ID。 + return BG_NORMAL_RESOURCES[id]; + } + + public static int getFolderBgRes() {//这行定义了一个方法,返回用于表示文件夹的背景资源ID。 + + return R.drawable.list_folder; + } + } + + public static class WidgetBgResources {//定义了一个公开的静态内部类 WidgetBgResources。 + private final static int [] BG_2X_RESOURCES = new int [] {//定义了一个私有的静态最终整型数组 BG_2X_RESOURCES,包含了一系列小部件在2x尺寸下的背景资源ID。这些资源ID通常指向drawable资源,如图片等。 + + R.drawable.widget_2x_yellow, + R.drawable.widget_2x_blue, + R.drawable.widget_2x_white, + R.drawable.widget_2x_green, + R.drawable.widget_2x_red, + }; + + public static int getWidget2xBgResource(int id) {//定义了一个公开的静态方法 getWidget2xBgResource,它接受一个整型参数 id,用于指定想要获取的背景资源的索引。 + return BG_2X_RESOURCES[id];//返回 BG_2X_RESOURCES 数组中指定索引 id 的背景资源ID。 + } + + private final static int [] BG_4X_RESOURCES = new int [] {//定义了一个私有的静态最终整型数组 BG_4X_RESOURCES,包含了一系列小部件在4x尺寸下的背景资源ID。 + R.drawable.widget_4x_yellow, + R.drawable.widget_4x_blue, + R.drawable.widget_4x_white, + R.drawable.widget_4x_green, + R.drawable.widget_4x_red + }; + + public static int getWidget4xBgResource(int id) {//定义了一个公开的静态方法 getWidget4xBgResource,它同样接受一个整型参数 id,用于指定想要获取的背景资源的索引。 + + return BG_4X_RESOURCES[id];//返回 BG_4X_RESOURCES 数组中指定索引 id 的背景资源ID。 + } + }//WidgetBgResources 类结束。 + + public static class TextAppearanceResources {//定义了一个公开的静态内部类 TextAppearanceResources。 + private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { + //定义了一个私有的静态最终整型数组 TEXTAPPEARANCE_RESOURCES,包含了一系列文本外观资源ID。这些资源ID通常指向style资源,定义了文本的字体大小、颜色等属性。 + R.style.TextAppearanceNormal, + R.style.TextAppearanceMedium, + R.style.TextAppearanceLarge, + R.style.TextAppearanceSuper + }; + + public static int getTexAppearanceResource(int id) {//定义了一个公开的静态方法 getTexAppearanceResource 接受一个整型参数 id,用于指定想要获取的文本外观资源的索引。 + /** + * HACKME: Fix bug of store the resource id in shared preference. + * The id may larger than the length of resources, in this case, + * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} + */ + if (id >= TEXTAPPEARANCE_RESOURCES.length) {//检查提供的索引 id 是否超出了 TEXTAPPEARANCE_RESOURCES 数组的长度。 + return BG_DEFAULT_FONT_SIZE;//如果条件为真(即索引超出了数组长度),则返回一个名为 BG_DEFAULT_FONT_SIZE 的整型值。 + } + return TEXTAPPEARANCE_RESOURCES[id];//如果条件为假(即索引在数组长度范围内),则返回 TEXTAPPEARANCE_RESOURCES 数组中指定索引 id 的文本外观资源ID + } + + public static int getResourcesSize() {//定义了一个公开的静态方法 getResourcesSize,它不接受任何参数,并返回 TEXTAPPEARANCE_RESOURCES 数组的长度。 + return TEXTAPPEARANCE_RESOURCES.length;//返回 TEXTAPPEARANCE_RESOURCES 数组的长度。 + } + } +} From 5b2209fea2a63b119b6e303e4eaeb2f3557a70dd Mon Sep 17 00:00:00 2001 From: YourUsername Date: Sun, 15 Dec 2024 21:05:48 +0800 Subject: [PATCH 08/12] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Note.java | 302 ++++++++++++++++++++++++++++++++ WorkingNote.java | 446 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 748 insertions(+) create mode 100644 Note.java create mode 100644 WorkingNote.java diff --git a/Note.java b/Note.java new file mode 100644 index 0000000..ed97d03 --- /dev/null +++ b/Note.java @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.model;//声明了当前代码所在的包(package)名为net.micode.notes.model +import android.content.ContentProviderOperation;//导入Android框架中的ContentProviderOperation类。 +import android.content.ContentProviderResult;//导入ContentProviderResult类。 +import android.content.ContentUris;//导入ContentUris类。这个类提供了用于处理URI(统一资源标识符)和ID之间转换的实用方法。 +import android.content.ContentValues;//导入ContentValues类。这个类用于存储一组键值对,通常用于传递给内容提供者(ContentProvider)进行插入或更新操作。 +import android.content.Context;//导入Context类。在Android中,Context是一个抽象类,它允许访问特定资源和应用级操作,如启动活动、广播和接收意图等。 +import android.content.OperationApplicationException;//导入OperationApplicationException类。这个异常在尝试应用一个操作到内容提供者时,如果内容提供者报告了一个错误,就会被抛出。 +import android.net.Uri;//导入Uri类。这个类表示统一资源标识符(URI),用于唯一标识内容提供者中的数据。 +import android.os.RemoteException;//导入RemoteException类。这个异常是在尝试与远程对象(如另一个应用中的服务)进行通信时,如果通信失败就会被抛出。 +import android.util.Log;//导入Log类。这个类提供了用于记录日志信息的静态方法,帮助开发者在开发过程中调试应用。 + +import net.micode.notes.data.Notes;//导入Notes类。 +import net.micode.notes.data.Notes.CallNote;//导入Notes类中的CallNote内部类。 +import net.micode.notes.data.Notes.DataColumns;//导入Notes类中的DataColumns内部类 +import net.micode.notes.data.Notes.NoteColumns;//导入Notes类中的NoteColumns内部类。 +import net.micode.notes.data.Notes.TextNote;//导入Notes类中的TextNote内部类。 + +import java.util.ArrayList;//导入Java的ArrayList类。 +//这段代码主要是导入了一些必要的类,用于后续操作数据库中的笔记数据。通过导入这些类,开发者可以使用Android框架提供的API来执行数据库操作、处理URI、记录日志等。 + +// 定义一个名为Note的公开类 +public class Note { + // 定义一个私有的ContentValues对象,用于存储要插入数据库的数据差异 + private ContentValues mNoteDiffValues; + // 定义一个私有的NoteData对象,可能用于存储笔记的特定数据 + private NoteData mNoteData; + // 定义一个私有的静态常量TAG,用于日志记录 + private static final String TAG = "Note"; + + /** + * 创建一个新的笔记ID,用于向数据库添加新笔记 + */ + // 定义一个公开的静态同步方法,该方法接受一个Context对象和一个文件夹ID作为参数,并返回一个long类型的笔记ID + public static synchronized long getNewNoteId(Context context, long folderId) { + // 创建一个新的ContentValues对象,用于存储要插入数据库的新笔记的数据 + ContentValues values = new ContentValues(); + // 获取当前系统时间(毫秒) + long createdTime = System.currentTimeMillis(); + // 将创建时间和修改时间设置为当前时间,并存储到values对象中 + values.put(NoteColumns.CREATED_DATE, createdTime); + values.put(NoteColumns.MODIFIED_DATE, createdTime); + // 设置笔记类型为普通笔记,并存储到values对象中 + values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + // 设置本地修改标志为1,并存储到values对象中(可能表示笔记已被本地修改) + values.put(NoteColumns.LOCAL_MODIFIED, 1); + // 设置父文件夹ID,并存储到values对象中 + values.put(NoteColumns.PARENT_ID, folderId); + // 使用ContentResolver向数据库插入新笔记,并返回新笔记的Uri + Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); + + // 定义一个long类型的变量noteId,用于存储新笔记的ID + long noteId = 0; + try { + // 从返回的Uri中提取新笔记的ID(假设ID是Uri路径段的第二个元素) + noteId = Long.valueOf(uri.getPathSegments().get(1)); + } catch (NumberFormatException e) { + // 如果提取ID时发生数字格式异常,则记录错误日志,并将noteId设置为0 + Log.e(TAG, "Get note id error :" + e.toString()); + noteId = 0; + } + // 如果noteId为-1(尽管在正常情况下不太可能,因为Long.valueOf不会返回-1),则抛出异常 + if (noteId == -1) { + throw new IllegalStateException("Wrong note id:" + noteId); + } + // 返回新笔记的ID + return noteId; + } + + // Note类的无参构造函数 + public Note() { + // 在构造函数中初始化mNoteDiffValues和mNoteData对象 + mNoteDiffValues = new ContentValues(); + mNoteData = new NoteData(); + } + + + // 定义一个公开的方法,用于设置笔记的某个键值对,并标记笔记为已修改和更新修改时间 +public void setNoteValue(String key, String value) { + // 将键值对存储到mNoteDiffValues中,这通常用于记录要同步到数据库的数据变化 + mNoteDiffValues.put(key, value); + // 将LOCAL_MODIFIED字段设置为1,表示笔记已被本地修改 + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + // 更新MODIFIED_DATE字段为当前时间,表示笔记的最后修改时间 + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); +} + +// 定义一个公开的方法,用于设置文本数据的某个键值对 +public void setTextData(String key, String value) { + // 调用mNoteData的setTextData方法,将键值对存储到文本数据中 + mNoteData.setTextData(key, value); +} + +// 定义一个公开的方法,用于设置文本数据的ID +public void setTextDataId(long id) { + // 调用mNoteData的setTextDataId方法,设置文本数据的ID + mNoteData.setTextDataId(id); +} + +// 定义一个公开的方法,用于获取文本数据的ID +public long getTextDataId() { + // 返回mNoteData中存储的文本数据ID + return mNoteData.mTextDataId; +} + +// 定义一个公开的方法,用于设置通话数据的ID +public void setCallDataId(long id) { + // 调用mNoteData的setCallDataId方法,设置通话数据的ID + mNoteData.setCallDataId(id); +} + +// 定义一个公开的方法,用于设置通话数据的某个键值对 +public void setCallData(String key, String value) { + // 调用mNoteData的setCallData方法,将键值对存储到通话数据中 + mNoteData.setCallData(key, value); +} + +// 定义一个公开的方法,用于检查笔记是否已被本地修改 +public boolean isLocalModified() { + // 如果mNoteDiffValues中有数据变化,或者mNoteData也被标记为已修改,则返回true + return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); +} + +// 定义一个公开的方法,用于同步笔记数据到数据库 +public boolean syncNote(Context context, long noteId) { + // 检查传入的noteId是否有效(大于0) + if (noteId <= 0) { + // 如果noteId无效,则抛出异常 + throw new IllegalArgumentException("Wrong note id:" + noteId); + } + + // 检查笔记是否已被本地修改 + if (!isLocalModified()) { + // 如果笔记没有被修改,则直接返回true,表示同步成功(因为没有需要同步的变化) + return true; + } + + /** + * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and + * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the + * note data info + */ + // 定义一个名为NoteData的私有内部类,用于管理笔记的文本数据和通话数据 +private class NoteData { + // 文本数据的ID + private long mTextDataId; + // 存储文本数据的ContentValues对象 + private ContentValues mTextDataValues; + // 通话数据的ID + private long mCallDataId; + // 存储通话数据的ContentValues对象 + private ContentValues mCallDataValues; + // 用于日志记录的标签 + private static final String TAG = "NoteData"; + + // NoteData的构造函数,初始化成员变量 + public NoteData() { + mTextDataValues = new ContentValues(); + mCallDataValues = new ContentValues(); + mTextDataId = 0; + mCallDataId = 0; + } + + // 检查文本数据或通话数据是否已被修改 + boolean isLocalModified() { + return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; + } + + // 设置文本数据的ID,并检查ID是否有效 + void setTextDataId(long id) { + if(id <= 0) { + throw new IllegalArgumentException("Text data id should larger than 0"); + } + mTextDataId = id; + } + + // 设置通话数据的ID,并检查ID是否有效 + void setCallDataId(long id) { + if (id <= 0) { + throw new IllegalArgumentException("Call data id should larger than 0"); + } + mCallDataId = id; + } + + void setCallData(String key, String value) { + // 将键值对放入mCallDataValues中,用于存储通话数据 + mCallDataValues.put(key, value); + // 标记笔记在本地已被修改 + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + // 更新笔记的修改日期 + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + } + void setTextData(String key, String value) { + // 将键值对放入mTextDataValues中,用于存储文本数据 + mTextDataValues.put(key, value); + // 标记笔记在本地已被修改 + mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); + // 更新笔记的修改日期 + mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); + } + Uri pushIntoContentResolver(Context context, long noteId) { + // 检查传入的noteId是否有效 + if (noteId <= 0) { + throw new IllegalArgumentException("Wrong note id:" + noteId); + } + + // 创建一个操作列表,用于批量操作内容提供者 + ArrayList operationList = new ArrayList(); + // 初始化一个操作构建器,初始值为null,后面会根据需要赋值 + ContentProviderOperation.Builder builder = null; + + // 如果mTextDataValues中有数据需要插入或更新 + if(mTextDataValues.size() > 0) { + // 将noteId放入mTextDataValues中,以便与特定的笔记关联 + mTextDataValues.put(DataColumns.NOTE_ID, noteId); + // 如果mTextDataId为0,表示需要插入新数据 + if (mTextDataId == 0) { + // 设置MIME类型,表示这是文本笔记数据 + mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); + // 插入新数据到内容提供者,并获取返回的Uri + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mTextDataValues); + // 尝试从Uri中提取新插入数据的ID,并设置到mTextDataId中 + try { + setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); + } catch (NumberFormatException e) { + // 如果提取ID失败,记录错误日志,并清空mTextDataValues + Log.e(TAG, "Insert new text data fail with noteId" + noteId); + mTextDataValues.clear(); + // 返回null表示操作失败 + return null; + } + } else { + // 如果mTextDataId不为0,表示需要更新现有数据 + builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mTextDataId)); + builder.withValues(mTextDataValues); + operationList.add(builder.build()); + } + // 清空mTextDataValues,因为它已经被处理 + mTextDataValues.clear(); + } + + // 如果mCallDataValues中有数据需要插入或更新,处理逻辑与mTextDataValues相同 + if(mCallDataValues.size() > 0) { + mCallDataValues.put(DataColumns.NOTE_ID, noteId); + if (mCallDataId == 0) { + mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); + Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, mCallDataValues); + try { + setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); + } catch (NumberFormatException e) { + Log.e(TAG, "Insert new call data fail with noteId" + noteId); + mCallDataValues.clear(); + return null; + } + } else { + builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( + Notes.CONTENT_DATA_URI, mCallDataId)); + builder.withValues(mCallDataValues); + operationList.add(builder.build()); + } + mCallDataValues.clear(); + } + + // 如果操作列表不为空,表示有数据需要应用到内容提供者 + if (operationList.size() > 0) { + try { + // 批量应用操作到内容提供者,并获取结果 + ContentProviderResult[] results = context.getContentResolver().applyBatch( + Notes.AUTHORITY, operationList); + // 如果结果数组为空、长度为0或第一个结果为null,表示操作失败,返回null + // 否则,返回与noteId关联的笔记URI + return (results == null || results.length == 0 || results[0] == null) ? null + : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); + } catch (RemoteException e) { + // 如果发生远程异常,记录错误日志,并返回null + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + return null; + } catch (OperationApplicationException e) { + // 如果发生操作应用异常,记录错误日志,并返回null + Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); + return null; + } + } + // 如果没有任何数据需要处理,返回null + return null; +} \ No newline at end of file diff --git a/WorkingNote.java b/WorkingNote.java new file mode 100644 index 0000000..863d9e0 --- /dev/null +++ b/WorkingNote.java @@ -0,0 +1,446 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.model; + +import android.appwidget.AppWidgetManager; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.text.TextUtils; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.CallNote; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.data.Notes.TextNote; +import net.micode.notes.tool.ResourceParser.NoteBgResources; + + +public class WorkingNote {//定义了一个公开的类 WorkingNote。这个类是公开的,意味着它可以被其他类访问。 + // Note for the working note + private Note mNote;//定义了一个私有的 Note 类型的成员变量 mNote + // Note Id + private long mNoteId;//定义了一个私有的 long 类型的成员变量 mNoteId,用于存储笔记的唯一标识符。 + // Note content + private String mContent;//定义了一个私有的 String 类型的成员变量 mContent,用于存储笔记的文本内容。 + // Note mode + private int mMode;//定义了一个私有的 int 类型的成员变量 mMode + + private long mAlertDate;//定义了一个私有的 long 类型的成员变量 mAlertDate,用于存储笔记的提醒日期。 + + private long mModifiedDate;//定义了一个私有的 long 类型的成员变量 mModifiedDate,用于存储笔记的最后修改日期。 + + private int mBgColorId;//定义了一个私有的 int 类型的成员变量 mBgColorId,用于存储笔记背景颜色的ID。 + + private int mWidgetId;//定义了一个私有的 int 类型的成员变量 mWidgetId + + private int mWidgetType;//定义了一个私有的 int 类型的成员变量 mWidgetType + + private long mFolderId;//定义了一个私有的 long 类型的成员变量 mFolderId,用于存储笔记所属的文件夹ID。 + + private Context mContext;//定义了一个私有的 Context 类型的成员变量 mContext,Context 是一个抽象类,它允许访问特定资源和应用级操作,如启动活动、广播和接收意图等 + + private static final String TAG = "WorkingNote";//定义了一个私有的静态常量 TAG,其值为字符串 "WorkingNote"。这个常量通常用于日志记录,以便识别日志消息来自哪个类。 + + private boolean mIsDeleted;//定义了一个私有的 boolean 类型的成员变量 mIsDeleted,用于表示笔记是否被删除。 + + private NoteSettingChangedListener mNoteSettingStatusListener;//定义了一个私有的 NoteSettingChangedListener 类型的成员变量 mNoteSettingStatusListener。 + + public static final String[] DATA_PROJECTION = new String[] { + //定义了一个公开的静态常量 DATA_PROJECTION,它是一个字符串数组,包含了用于数据查询的列名。 + DataColumns.ID, + DataColumns.CONTENT, + DataColumns.MIME_TYPE, + DataColumns.DATA1, + DataColumns.DATA2, + DataColumns.DATA3, + DataColumns.DATA4, + }; + + public static final String[] NOTE_PROJECTION = new String[] { + //public static final String[] NOTE_PROJECTION = new String[] { ... }; - 定义了一个公开的静态常量 NOTE_PROJECTION,它也是一个字符串数组,包含了用于笔记查询的列名 + NoteColumns.PARENT_ID, + NoteColumns.ALERTED_DATE, + NoteColumns.BG_COLOR_ID, + NoteColumns.WIDGET_ID, + NoteColumns.WIDGET_TYPE, + NoteColumns.MODIFIED_DATE + }; + + private static final int DATA_ID_COLUMN = 0; + + private static final int DATA_CONTENT_COLUMN = 1; + + private static final int DATA_MIME_TYPE_COLUMN = 2; + + private static final int DATA_MODE_COLUMN = 3; + + private static final int NOTE_PARENT_ID_COLUMN = 0; + + private static final int NOTE_ALERTED_DATE_COLUMN = 1; + + private static final int NOTE_BG_COLOR_ID_COLUMN = 2; + + private static final int NOTE_WIDGET_ID_COLUMN = 3; + + private static final int NOTE_WIDGET_TYPE_COLUMN = 4; + + private static final int NOTE_MODIFIED_DATE_COLUMN = 5; +//以上代码定义了一系列的私有静态常量,它们被用作索引来标识从数据库查询返回的结果集中各个列的位置。 + // New note construct + private WorkingNote(Context context, long folderId) { + //这是一个私有构造函数,用于创建一个新的WorkingNote实例。它接收两个参数:context(应用程序的上下文)和folderId(笔记所属的文件夹ID)。 + mContext = context; //将传入的context赋值给成员变量mContext,以便后续使用。 + mAlertDate = 0;//初始化提醒日期mAlertDate为0,表示当前没有设置提醒。 + mModifiedDate = System.currentTimeMillis();//初始化修改日期mModifiedDate为当前系统时间的毫秒数,表示笔记刚刚被创建。 + mFolderId = folderId;//将传入的folderId赋值给成员变量mFolderId。 + mNote = new Note();//创建一个新的Note实例,并赋值给成员变量mNote。 + mNoteId = 0;//初始化笔记IDmNoteId为0,表示当前是一个新的笔记,还没有被保存到数据库中。 + mIsDeleted = false;//初始化删除标记mIsDeleted为false,表示笔记没有被删除。 + mMode = 0;//初始化模式mMode为0,具体的模式含义取决于业务逻辑。 + mWidgetType = Notes.TYPE_WIDGET_INVALIDE;//初始化小部件类型mWidgetType为Notes.TYPE_WIDGET_INVALIDE,表示当前没有设置小部件类型。 + } + + // Existing note construct + private WorkingNote(Context context, long noteId, long folderId) { + //这是一个私有构造函数,用于加载一个已存在的WorkingNote实例。它接收三个参数:context(应用程序的上下文)、noteId(要加载的笔记ID)和folderId(笔记所属的文件夹ID)。 + mContext = context;// + mNoteId = noteId; + mFolderId = folderId; + mIsDeleted = false; + mNote = new Note();//这部分代码与第一个构造函数中的相应部分类似,只是它直接设置了mNoteId和mFolderId。 + loadNote();//调用loadNote方法来从数据库中加载笔记的详细信息。 + } + + private void loadNote() {//这是一个私有方法,用于从数据库中加载笔记的详细信息。 + Cursor cursor = mContext.getContentResolver().query( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, + null, null);//使用ContentResolver和ContentUris来构建一个查询,以获取指定mNoteId的笔记。NOTE_PROJECTION是一个包含要查询的列名的数组。 + + if (cursor != null) {//检查查询返回的Cursor是否不为null。 + if (cursor.moveToFirst()) {//移动Cursor到第一行,如果有数据的话。 + mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); + mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); + mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); + mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); + mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); + mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); + }//从Cursor中读取笔记的详细信息,并赋值给相应的成员变量。 + cursor.close();//关闭Cursor以释放资源。 + } else { + Log.e(TAG, "No note with id:" + mNoteId); + throw new IllegalArgumentException("Unable to find note with id " + mNoteId); + }//如果Cursor为null,则记录错误日志并抛出一个IllegalArgumentException异常。 + loadNoteData();//调用loadNoteData方法来加载笔记的额外数据 + } + + private void loadNoteData() {//这是一个私有方法,用于从数据库中加载与当前笔记ID (mNoteId) 相关联的笔记数据。 + Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, + DataColumns.NOTE_ID + "=?", new String[] { + String.valueOf(mNoteId) + }, null); +//使用ContentResolver执行查询,以获取与mNoteId相关联的笔记数据。Notes.CONTENT_DATA_URI是数据的URI,DATA_PROJECTION是包含要查询的列名的数组,DataColumns.NOTE_ID + "=?"是选择条件,表示只选择NOTE_ID等于mNoteId的行,new String[] { String.valueOf(mNoteId) }是选择条件的参数,null表示没有排序。 + + if (cursor != null) {//检查查询返回的Cursor是否不为null。 + if (cursor.moveToFirst()) {//移动Cursor到第一行,准备读取数据。 + do {//开始一个循环,用于遍历所有匹配的行。 + String type = cursor.getString(DATA_MIME_TYPE_COLUMN);//从当前行读取数据的MIME类型。 + if (DataConstants.NOTE.equals(type)) {//如果MIME类型表示这是一个普通笔记。 + mContent = cursor.getString(DATA_CONTENT_COLUMN); + mMode = cursor.getInt(DATA_MODE_COLUMN); + mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); + //读取笔记的内容、模式和ID,并设置到相应的成员变量和mNote对象中。 + } else if (DataConstants.CALL_NOTE.equals(type)) {//如果MIME类型表示这是一个与通话相关的笔记。 + mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));//读取并设置与通话相关的数据的ID。 + } else { + Log.d(TAG, "Wrong note type with type:" + type);//如果MIME类型不是预期的任何一种,则记录一条调试日志 + } + } while (cursor.moveToNext());//继续循环,直到遍历完所有匹配的行。 + } + cursor.close();//关闭Cursor以释放资源。 + } else { + Log.e(TAG, "No data with id:" + mNoteId); + throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId); + }//如果Cursor为null,则记录错误日志并抛出一个IllegalArgumentException异常。 + } + + public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, + int widgetType, int defaultBgColorId) { +//这是一个公有的静态方法,用于创建一个空的WorkingNote实例。它接收五个参数:context(应用程序的上下文)、folderId(笔记所属的文件夹ID)、widgetId(小部件ID)、widgetType(小部件类型)和defaultBgColorId(默认背景颜色ID)。 + WorkingNote note = new WorkingNote(context, folderId);//使用提供的context和folderId创建一个新的WorkingNote实例。 + note.setBgColorId(defaultBgColorId); + note.setWidgetId(widgetId); + note.setWidgetType(widgetType); +//设置笔记的背景颜色ID、小部件ID和小部件类型。 + return note;//返回创建并设置好的WorkingNote实例。 + } + + public static WorkingNote load(Context context, long id) { + // 这是一个静态方法,用于根据给定的上下文和ID加载一个WorkingNote实例。 + return new WorkingNote(context, id, 0); + // 创建一个新的WorkingNote实例,并传入上下文、ID和一个默认值0(可能是用于文件夹ID或其他目的)。 + } + public synchronized boolean saveNote() { + // 这是一个同步方法,用于保存当前WorkingNote实例的数据。 + if (isWorthSaving()) { + // 首先检查这个笔记是否值得保存(即是否有必要进行保存操作)。 + if (!existInDatabase()) { + // 如果这个笔记还没有存在于数据库中(即mNoteId小于或等于0,根据existInDatabase方法的实现)。 + if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { + // 尝试获取一个新的笔记ID,如果失败(即返回的ID为0),则记录错误日志。 + Log.e(TAG, "Create new note fail with id:" + mNoteId); + return false; + // 并返回false表示保存失败。 + } + } + + mNote.syncNote(mContext, mNoteId); + // 将当前笔记的数据同步到数据库中(具体实现依赖于mNote对象的syncNote方法)。 + + /** + * Update widget content if there exist any widget of this note + */ + if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && mWidgetType != Notes.TYPE_WIDGET_INVALIDE + && mNoteSettingStatusListener != null) { + // 如果这个笔记关联了一个小部件,并且这个小部件的类型有效,同时存在一个监听器, + // 则调用监听器的onWidgetChanged方法来更新小部件的内容。 + mNoteSettingStatusListener.onWidgetChanged(); + } + return true; + // 如果一切顺利,则返回true表示保存成功。 + } else { + // 如果这个笔记不值得保存(即没有必要进行保存操作),则直接返回false。 + return false; + } + } + + public boolean existInDatabase() { + // 这个方法用于检查这个笔记是否已经存在于数据库中。 + // 通过检查mNoteId是否大于0来判断(假设mNoteId是在数据库中唯一标识笔记的ID)。 + return mNoteId > 0; + } + + private boolean isWorthSaving() { + // 这个私有方法用于检查这个笔记是否值得保存。 + if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) + || (existInDatabase() && !mNote.isLocalModified())) { + // 如果笔记被标记为已删除,或者如果它还没有存在于数据库中但内容为空, + // 或者如果它已经存在于数据库中但本地没有修改过,则认为这个笔记不值得保存。 + return false; + } else { + // 否则,认为这个笔记值得保存。 + return true; + } + } + + public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { + // 设置一个监听器,用于监听笔记设置状态的更改。 + // NoteSettingChangedListener 是一个自定义接口,用于回调通知状态变化。 + mNoteSettingStatusListener = l; + // 将传入的监听器赋值给成员变量 mNoteSettingStatusListener。 + } + + public void setAlertDate(long date, boolean set) { + // 设置笔记的提醒日期。 + // date 是要设置的提醒日期(以长整型表示的时间戳)。 + // set 是一个布尔值,表示是否要设置提醒日期(尽管在这个方法中未直接使用,但可能用于其他逻辑)。 + if (date != mAlertDate) { + // 如果传入的日期与当前设置的提醒日期不同。 + mAlertDate = date; + // 更新成员变量 mAlertDate。 + mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); + // 将更新后的提醒日期同步到笔记对象中(假设 NoteColumns.ALERTED_DATE 是数据库中的列名)。 + } + if (mNoteSettingStatusListener != null) { + // 如果设置了监听器。 + mNoteSettingStatusListener.onClockAlertChanged(date, set); + // 调用监听器的 onClockAlertChanged 方法,通知提醒日期已更改。 + } + } + + public void markDeleted(boolean mark) { + // 标记笔记为已删除或未删除状态。 + // mark 是一个布尔值,true 表示已删除,false 表示未删除。 + mIsDeleted = mark; + // 更新成员变量 mIsDeleted。 + if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { + // 如果笔记关联了一个小部件,并且这个小部件的类型有效,同时存在一个监听器。 + mNoteSettingStatusListener.onWidgetChanged(); + // 调用监听器的 onWidgetChanged 方法,通知小部件需要更新。 + } + } + + public void setBgColorId(int id) { + // 设置笔记的背景颜色ID。 + // id 是背景颜色的唯一标识符。 + if (id != mBgColorId) { + // 如果传入的ID与当前设置的背景颜色ID不同。 + mBgColorId = id; + // 更新成员变量 mBgColorId。 + if (mNoteSettingStatusListener != null) { + // 如果设置了监听器。 + mNoteSettingStatusListener.onBackgroundColorChanged(); + // 调用监听器的 onBackgroundColorChanged 方法,通知背景颜色已更改。 + } + mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); + // 将更新后的背景颜色ID同步到笔记对象中(假设 NoteColumns.BG_COLOR_ID 是数据库中的列名)。 + } + } + + public void setCheckListMode(int mode) { + // 设置笔记的待办事项模式。 + // mode 是待办事项模式的标识符。 + if (mMode != mode) { + // 如果传入的模式与当前设置的待办事项模式不同。 + if (mNoteSettingStatusListener != null) { + // 如果设置了监听器。 + mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); + // 调用监听器的 onCheckListModeChanged 方法,通知待办事项模式已更改。 + // 注意:这里传递了旧模式和新模式作为参数。 + } + mMode = mode; + // 更新成员变量 mMode。 + mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); + // 将更新后的待办事项模式同步到笔记对象中(假设 TextNote.MODE 是用于存储模式的键)。 + } + } + + public void setWidgetType(int type) { + // 设置笔记关联的小部件类型。 + // type 是小部件类型的标识符。 + if (type != mWidgetType) { + // 如果传入的类型与当前设置的小部件类型不同。 + mWidgetType = type; + // 更新成员变量 mWidgetType。 + mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); + // 将更新后的小部件类型同步到笔记对象中(假设 NoteColumns.WIDGET_TYPE 是数据库中的列名)。 + } + } + + // 定义一个方法,用于设置小部件ID +public void setWidgetId(int id) { + // 如果传入的ID与当前的小部件ID不同 + if (id != mWidgetId) { + // 更新当前的小部件ID + mWidgetId = id; + // 将新的小部件ID存储到笔记中 + mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); + } +} + +// 定义一个方法,用于设置工作文本 +public void setWorkingText(String text) { + // 如果传入的文本与当前的内容不同 + if (!TextUtils.equals(mContent, text)) { + // 更新当前的内容 + mContent = text; + // 将新的内容存储到笔记的文本数据中 + mNote.setTextData(DataColumns.CONTENT, mContent); + } +} + +// 定义一个方法,用于将笔记转换为通话笔记 +public void convertToCallNote(String phoneNumber, long callDate) { + // 设置通话日期 + mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); + // 设置电话号码 + mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); + // 设置笔记的父ID为通话记录文件夹的ID + mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); +} + +// 定义一个方法,用于检查是否有闹钟提醒 +public boolean hasClockAlert() { + // 如果闹钟日期大于0,则返回true,否则返回false + return (mAlertDate > 0 ? true : false); +} + +// 定义一个方法,用于获取内容 +public String getContent() { + return mContent; +} + +// 定义一个方法,用于获取闹钟日期 +public long getAlertDate() { + return mAlertDate; +} + +// 定义一个方法,用于获取修改日期 +public long getModifiedDate() { + return mModifiedDate; +} + +// 定义一个方法,用于获取背景颜色资源ID +public int getBgColorResId() { + return NoteBgResources.getNoteBgResource(mBgColorId); +} + +// 定义一个方法,用于获取背景颜色ID +public int getBgColorId() { + return mBgColorId; +} + +// 定义一个方法,用于获取标题背景资源ID +public int getTitleBgResId() { + return NoteBgResources.getNoteTitleBgResource(mBgColorId); +} + +// 定义一个方法,用于获取检查列表模式 +public int getCheckListMode() { + return mMode; +} + +// 定义一个方法,用于获取笔记ID +public long getNoteId() { + return mNoteId; +} + +// 定义一个方法,用于获取文件夹ID +public long getFolderId() { + return mFolderId; +} + +// 定义一个方法,用于获取小部件ID +public int getWidgetId() { + return mWidgetId; +} + +// 定义一个方法,用于获取小部件类型 +public int getWidgetType() { + return mWidgetType; +} + +// 定义一个接口,用于监听笔记设置的更改 +public interface NoteSettingChangedListener { + // 当当前笔记的背景颜色刚刚更改时调用 + void onBackgroundColorChanged(); + + // 当用户设置闹钟时调用 + void onClockAlertChanged(long date, boolean set); + + // 当用户通过小部件创建笔记时调用 + void onWidgetChanged(); + + // 当在检查列表模式和普通模式之间切换时调用 + // @param oldMode 是更改之前的模式 + // @param newMode 是新的模式 + void onCheckListModeChanged(int oldMode, int newMode); +} \ No newline at end of file From 2c300a58d5678be6fe8369c11169906deccb6f92 Mon Sep 17 00:00:00 2001 From: YourUsername Date: Sun, 15 Dec 2024 21:09:14 +0800 Subject: [PATCH 09/12] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GTaskSyncService.java | 144 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 GTaskSyncService.java diff --git a/GTaskSyncService.java b/GTaskSyncService.java new file mode 100644 index 0000000..e6f1781 --- /dev/null +++ b/GTaskSyncService.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.remote; + +import android.app.Activity; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; + +// 定义一个名为GTaskSyncService的服务类,它继承自Service类 +public class GTaskSyncService extends Service { + // 定义一个字符串常量,用于在Intent中标识同步操作类型 + public final static String ACTION_STRING_NAME = "sync_action_type"; + + // 定义同步操作类型的常量 + public final static int ACTION_START_SYNC = 0; // 开始同步 + public final static int ACTION_CANCEL_SYNC = 1; // 取消同步 + public final static int ACTION_INVALID = 2; // 无效操作 + + // 定义广播接收器的名称,用于接收同步服务的广播 + public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; + + // 定义广播中的额外数据键 + public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; // 是否正在同步 + public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; // 同步进度消息 + + // 定义一个静态变量,用于存储同步任务 + private static GTaskASyncTask mSyncTask = null; + + // 定义一个静态变量,用于存储同步进度消息 + private static String mSyncProgress = ""; + + // 定义一个私有方法,用于开始同步 + private void startSync() { + if (mSyncTask == null) { // 如果当前没有正在进行的同步任务 + mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { + // 定义同步任务完成时的回调方法 + public void onComplete() { + mSyncTask = null; // 清空同步任务 + sendBroadcast(""); // 发送广播,通知同步完成(无具体消息) + stopSelf(); // 停止服务 + } + }); + sendBroadcast(""); // 发送广播,通知开始同步(无具体消息,可能用于初始化UI) + mSyncTask.execute(); // 执行同步任务 + } + } + + // 定义一个私有方法,用于取消同步 + private void cancelSync() { + if (mSyncTask != null) { // 如果当前有正在进行的同步任务 + mSyncTask.cancelSync(); // 取消同步任务 + } + } + + // 当服务被创建时调用此方法 + @Override + public void onCreate() { + mSyncTask = null; // 初始化同步任务为null + } + + // 当服务接收到启动命令时调用此方法 + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Bundle bundle = intent.getExtras(); // 获取Intent中的额外数据 + if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { // 如果Intent包含同步操作类型 + switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { // 根据同步操作类型执行相应操作 + case ACTION_START_SYNC: + startSync(); // 开始同步 + break; + case ACTION_CANCEL_SYNC: + cancelSync(); // 取消同步 + break; + default: + break; + } + return START_STICKY; // 如果服务被杀死,系统将会重新创建服务并调用onStartCommand()方法,但传入的是null的Intent + } + return super.onStartCommand(intent, flags, startId); // 如果Intent不包含同步操作类型,则调用父类方法 + } + + // 当设备内存不足时调用此方法 + @Override + public void onLowMemory() { + if (mSyncTask != null) { // 如果当前有正在进行的同步任务 + mSyncTask.cancelSync(); // 取消同步任务以释放内存 + } + } + + // 实现Service的onBind方法,由于此服务不需要绑定,因此返回null + public IBinder onBind(Intent intent) { + return null; + } + + // 定义一个公开方法,用于发送广播通知同步进度 + public void sendBroadcast(String msg) { + mSyncProgress = msg; // 更新同步进度消息 + Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); // 创建广播Intent + intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); // 添加是否正在同步的额外数据 + intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); // 添加同步进度消息的额外数据 + sendBroadcast(intent); // 发送广播 + } + + // 定义一个公开静态方法,用于从Activity启动同步服务 + public static void startSync(Activity activity) { + GTaskManager.getInstance().setActivityContext(activity); // 设置GTaskManager的Activity上下文 + Intent intent = new Intent(activity, GTaskSyncService.class); // 创建启动服务的Intent + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); // 添加同步操作类型的额外数据 + activity.startService(intent); // 启动服务 + } + + // 定义一个公开静态方法,用于从Context(如Activity或Application)取消同步服务 + public static void cancelSync(Context context) { + Intent intent = new Intent(context, GTaskSyncService.class); // 创建取消服务的Intent + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); // 添加同步操作类型的额外数据 + context.startService(intent); // 启动服务以取消同步 + } + + // 定义一个公开静态方法,用于检查是否正在同步 + public static boolean isSyncing() { + return mSyncTask != null; // 返回mSyncTask是否为null的布尔值 + } + + // 定义一个公开静态方法,用于获取同步进度消息 + public static String getProgressString() { + return mSyncProgress; // 返回同步进度消息 + } +} \ No newline at end of file From e8168ebae4a4ff67a19f32264fb4b120638ca660 Mon Sep 17 00:00:00 2001 From: YourUsername Date: Sun, 15 Dec 2024 21:13:41 +0800 Subject: [PATCH 10/12] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GTaskASyncTask.java | 146 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 GTaskASyncTask.java diff --git a/GTaskASyncTask.java b/GTaskASyncTask.java new file mode 100644 index 0000000..96d1111 --- /dev/null +++ b/GTaskASyncTask.java @@ -0,0 +1,146 @@ + +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.remote; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; + +import net.micode.notes.R; +import net.micode.notes.ui.NotesListActivity; +import net.micode.notes.ui.NotesPreferenceActivity; + + +// 定义一个继承自AsyncTask的类,用于异步执行任务,接收Void作为输入参数,String作为进度更新参数,Integer作为返回结果 +public class GTaskASyncTask extends AsyncTask { + + // 定义一个静态常量,用作通知的ID + private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; + + // 定义一个接口,用于任务完成时的回调 + public interface OnCompleteListener { + void onComplete(); + } + + // 声明一个Context对象,用于访问应用的资源和类 + private Context mContext; + + // 声明一个NotificationManager对象,用于管理通知 + private NotificationManager mNotifiManager; + + // 声明一个GTaskManager对象,用于管理任务(假设是一个自定义的任务管理类) + private GTaskManager mTaskManager; + + // 声明一个OnCompleteListener对象,用于任务完成时的回调 + private OnCompleteListener mOnCompleteListener; + + // 构造函数,初始化必要的对象 + public GTaskASyncTask(Context context, OnCompleteListener listener) { + mContext = context; // 初始化mContext + mOnCompleteListener = listener; // 初始化mOnCompleteListener + mNotifiManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); // 初始化mNotifiManager + mTaskManager = GTaskManager.getInstance(); // 初始化mTaskManager,假设这是一个单例模式 + } + + // 提供一个方法用于取消同步任务 + public void cancelSync() { + mTaskManager.cancelSync(); // 调用mTaskManager的cancelSync方法 + } + + // 重写publishProgress方法,用于发布进度更新 + public void publishProgess(String message) { + publishProgress(new String[] { + message + }); // 调用父类的publishProgress方法,传递一个字符串数组 + } + + // 私有方法,用于显示通知 + private void showNotification(int tickerId, String content) { + // 创建一个Notification对象,设置图标、标题和时间戳 + Notification notification = new Notification(R.drawable.notification, mContext + .getString(tickerId), System.currentTimeMillis()); + notification.defaults = Notification.DEFAULT_LIGHTS; // 设置默认灯光效果 + notification.flags = Notification.FLAG_AUTO_CANCEL; // 设置点击通知后自动取消 + PendingIntent pendingIntent; // 声明一个PendingIntent对象 + // 根据tickerId判断点击通知后的跳转页面 + if (tickerId != R.string.ticker_success) { + pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, + NotesPreferenceActivity.class), 0); + } else { + pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, + NotesListActivity.class), 0); + } + // 设置通知的详细信息 + notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, + pendingIntent); + // 显示通知 + mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); + } + + // 重写doInBackground方法,用于在后台线程执行任务 + @Override + protected Integer doInBackground(Void... unused) { + // 发布进度更新,显示登录进度 + publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity + .getSyncAccountName(mContext))); + // 执行同步任务,并返回结果 + return mTaskManager.sync(mContext, this); + } + + // 重写onProgressUpdate方法,用于在UI线程更新进度 + @Override + protected void onProgressUpdate(String... progress) { + // 显示同步中的通知 + showNotification(R.string.ticker_syncing, progress[0]); + // 如果mContext是GTaskSyncService的实例,则发送广播 + if (mContext instanceof GTaskSyncService) { + ((GTaskSyncService) mContext).sendBroadcast(progress[0]); + } + } + + // 重写onPostExecute方法,用于在任务完成后在UI线程执行操作 + @Override + protected void onPostExecute(Integer result) { + // 根据返回结果显示不同的通知 + if (result == GTaskManager.STATE_SUCCESS) { + showNotification(R.string.ticker_success, mContext.getString( + R.string.success_sync_account, mTaskManager.getSyncAccount())); + NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); // 更新最后同步时间 + } else if (result == GTaskManager.STATE_NETWORK_ERROR) { + showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); + } else if (result == GTaskManager.STATE_INTERNAL_ERROR) { + showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); + } else if (result == GTaskManager.STATE_SYNC_CANCELLED) { + showNotification(R.string.ticker_cancel, mContext + .getString(R.string.error_sync_cancelled)); + } + // 如果mOnCompleteListener不为空,则在新线程中调用onComplete方法 + if (mOnCompleteListener != null) { + new Thread(new Runnable() { + + public void run() { + mOnCompleteListener.onComplete(); + } + }).start(); + } + } +} \ No newline at end of file From 2a6b40c7932d2b56d4110133d1a0e33c3c20445e Mon Sep 17 00:00:00 2001 From: YourUsername Date: Thu, 19 Dec 2024 23:24:59 +0800 Subject: [PATCH 11/12] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GTaskClient.java | 767 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 767 insertions(+) create mode 100644 GTaskClient.java diff --git a/GTaskClient.java b/GTaskClient.java new file mode 100644 index 0000000..44c50e9 --- /dev/null +++ b/GTaskClient.java @@ -0,0 +1,767 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.remote; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerFuture; +import android.app.Activity; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; + +import net.micode.notes.gtask.data.Node; +import net.micode.notes.gtask.data.Task; +import net.micode.notes.gtask.data.TaskList; +import net.micode.notes.gtask.exception.ActionFailureException; +import net.micode.notes.gtask.exception.NetworkFailureException; +import net.micode.notes.tool.GTaskStringUtils; +import net.micode.notes.ui.NotesPreferenceActivity; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.cookie.Cookie; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.LinkedList; +import java.util.List; +import java.util.zip.GZIPInputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + + +// 定义一个名为GTaskClient的公开类,这个类似乎是用来与Google Tasks API进行交互的客户端。 +public class GTaskClient { + + // 定义一个静态常量TAG,用于日志记录,其值为当前类的简单名称。 + private static final String TAG = GTaskClient.class.getSimpleName(); + + // 定义一个静态常量GTASK_URL,表示Google Tasks服务的基础URL。 + private static final String GTASK_URL = "https://mail.google.com/tasks/"; + + // 定义一个静态常量GTASK_GET_URL,表示用于获取任务的URL。 + private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; + + // 定义一个静态常量GTASK_POST_URL,表示用于发布(添加/更新)任务的URL。 + private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; + + // 定义一个静态的GTaskClient实例mInstance,用于实现单例模式。 + private static GTaskClient mInstance = null; + + // 定义一个DefaultHttpClient实例mHttpClient,用于发送HTTP请求。 + private DefaultHttpClient mHttpClient; + + // 定义一个字符串mGetUrl,存储用于获取任务的URL。 + private String mGetUrl; + + // 定义一个字符串mPostUrl,存储用于发布任务的URL。 + private String mPostUrl; + + // 定义一个长整型mClientVersion,用于存储客户端版本信息,初始化为-1表示未设置。 + private long mClientVersion; + + // 定义一个布尔型mLoggedin,用于标记用户是否已登录,初始化为false。 + private boolean mLoggedin; + + // 定义一个长整型mLastLoginTime,用于存储用户最后登录的时间,初始化为0。 + private long mLastLoginTime; + + // 定义一个整型mActionId,可能用于标识请求或操作的ID,初始化为1。 + private int mActionId; + + // 定义一个Account类型的mAccount,用于存储用户账户信息,初始化为null。 + private Account mAccount; + + // 定义一个JSONArray类型的mUpdateArray,可能用于存储需要更新的任务信息,初始化为null。 + private JSONArray mUpdateArray; + + // 私有的构造方法,防止外部直接创建GTaskClient实例,这是实现单例模式的一部分。 + private GTaskClient() { + mHttpClient = null; // 初始化mHttpClient为null,可能后续会有具体的初始化逻辑。 + mGetUrl = GTASK_GET_URL; // 将mGetUrl设置为预定义的获取任务URL。 + mPostUrl = GTASK_POST_URL; // 将mPostUrl设置为预定义的发布任务URL。 + mClientVersion = -1; // 初始化客户端版本号为-1。 + mLoggedin = false; // 初始化登录状态为未登录。 + mLastLoginTime = 0; // 初始化最后登录时间为0。 + mActionId = 1; // 初始化操作ID为1。 + mAccount = null; // 初始化账户信息为null。 + mUpdateArray = null; // 初始化更新数组为null。 + } + + // 定义一个公开的静态同步方法getInstance,用于获取GTaskClient的实例。 + // 这个方法实现了单例模式,确保整个应用中只有一个GTaskClient实例。 + public static synchronized GTaskClient getInstance() { + if (mInstance == null) { // 如果实例为null,则创建一个新的实例。 + mInstance = new GTaskClient(); + } + return mInstance; // 返回GTaskClient实例。 + } + + // 定义一个公开的方法login,它接受一个Activity作为参数,返回一个布尔值表示登录是否成功。 + public boolean login(Activity activity) { + // 定义一个常量interval,表示登录有效期为5分钟(以毫秒为单位)。 + final long interval = 1000 * 60 * 5; + + // 如果上次登录时间加上有效期小于当前时间,则认为登录已过期,将登录状态设置为false。 + if (mLastLoginTime + interval < System.currentTimeMillis()) { + mLoggedin = false; + } + + // 如果当前已登录,但账户名称与NotesPreferenceActivity中获取的同步账户名称不匹配, + // 则认为需要重新登录,将登录状态设置为false。 + if (mLoggedin + && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity + .getSyncAccountName(activity))) { + mLoggedin = false; + } + + // 如果当前已登录,则直接打印日志并返回true。 + if (mLoggedin) { + Log.d(TAG, "already logged in"); + return true; + } + + // 更新最后登录时间为当前时间。 + mLastLoginTime = System.currentTimeMillis(); + + // 调用loginGoogleAccount方法尝试登录Google账户,获取认证令牌。 + String authToken = loginGoogleAccount(activity, false); + + // 如果认证令牌为null,表示登录Google账户失败,打印错误日志并返回false。 + if (authToken == null) { + Log.e(TAG, "login google account failed"); + return false; + } + + // 如果账户名称不以gmail.com或googlemail.com结尾,则可能需要使用自定义域进行登录。 + if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() + .endsWith("googlemail.com"))) { + // 构造自定义域的Google Tasks URL。 + StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); + int index = mAccount.name.indexOf('@') + 1; + String suffix = mAccount.name.substring(index); + url.append(suffix + "/"); + mGetUrl = url.toString() + "ig"; + mPostUrl = url.toString() + "r/ig"; + + // 尝试使用自定义域的URL和认证令牌登录Google Tasks。 + if (tryToLoginGtask(activity, authToken)) { + mLoggedin = true; + } + } + + // 如果使用自定义域登录失败,则尝试使用Google官方的URL进行登录。 + if (!mLoggedin) { + mGetUrl = GTASK_GET_URL; + mPostUrl = GTASK_POST_URL; + + // 尝试使用Google官方的URL和认证令牌登录Google Tasks。 + if (!tryToLoginGtask(activity, authToken)) { + // 如果登录失败,则返回false。 + return false; + } + } + + // 将登录状态设置为true,并返回true表示登录成功。 + mLoggedin = true; + return true; + } + + // 定义一个私有方法loginGoogleAccount,它接受一个Activity对象和一个布尔值invalidateToken作为参数, +// 返回一个字符串表示的Google账户认证令牌。 + private String loginGoogleAccount(Activity activity, boolean invalidateToken) { + // 声明一个字符串变量authToken用于存储认证令牌。 + String authToken; + + // 通过Activity获取AccountManager实例,用于管理账户。 + AccountManager accountManager = AccountManager.get(activity); + + // 获取所有类型为"com.google"的账户。 + Account[] accounts = accountManager.getAccountsByType("com.google"); + + // 如果没有可用的Google账户,则打印错误日志并返回null。 + if (accounts.length == 0) { + Log.e(TAG, "there is no available google account"); + return null; + } + + // 从设置中获取同步账户的名称。 + String accountName = NotesPreferenceActivity.getSyncAccountName(activity); + Account account = null; + + // 遍历所有Google账户,查找与设置中的账户名称相匹配的账户。 + for (Account a : accounts) { + if (a.name.equals(accountName)) { + account = a; + break; + } + } + + // 如果找到了匹配的账户,则将其赋值给mAccount成员变量。 + if (account != null) { + mAccount = account; + } else { + // 如果没有找到匹配的账户,则打印错误日志并返回null。 + Log.e(TAG, "unable to get an account with the same name in the settings"); + return null; + } + + // 通过AccountManager获取认证令牌。注意这里的"goanna_mobile"可能是一个特定于应用的scope或authTokenType, + // 它需要与你的Google API项目配置相匹配。然而,"goanna_mobile"并不是一个标准的Google API scope, + // 这里可能是示例代码或特定应用的自定义值。 + AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, + "goanna_mobile", null, activity, null, null); + + try { + // 从Future中获取结果,这个结果是一个Bundle,包含了认证令牌等信息。 + Bundle authTokenBundle = accountManagerFuture.getResult(); + + // 从Bundle中提取认证令牌。 + authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); + + // 如果invalidateToken为true,则使当前认证令牌失效,并递归调用loginGoogleAccount方法重新获取令牌。 + // 注意:这里的递归调用可能会导致无限循环,如果invalidateToken始终为true且没有额外的退出条件。 + // 在实际应用中,通常不会在获取令牌后立即使其失效,除非有特定的需求。 + if (invalidateToken) { + accountManager.invalidateAuthToken("com.google", authToken); + loginGoogleAccount(activity, false); + } + } catch (Exception e) { + // 如果在获取认证令牌的过程中发生异常,则打印错误日志,并将authToken设置为null。 + Log.e(TAG, "get auth token failed"); + authToken = null; + } + + // 返回认证令牌。如果获取失败,则为null。 + return authToken; + } + + // 定义一个私有方法tryToLoginGtask,它尝试使用提供的authToken登录到Google Tasks(Gtask)。 +// 如果登录失败,它会尝试使令牌失效并重新获取一个新的令牌,然后再次尝试登录。 +private boolean tryToLoginGtask(Activity activity, String authToken) { + // 首先尝试使用提供的authToken登录Gtask。 + if (!loginGtask(authToken)) { + // 如果登录失败,可能是因为authToken已经过期。 + // 接下来,我们将使该令牌失效,并尝试重新获取一个新的令牌。 + authToken = loginGoogleAccount(activity, true); + + // 如果重新获取令牌失败(即返回null),则打印错误日志并返回false。 + if (authToken == null) { + Log.e(TAG, "login google account failed"); + return false; + } + + // 使用新获取的authToken再次尝试登录Gtask。 + if (!loginGtask(authToken)) { + // 如果仍然登录失败,则打印错误日志并返回false。 + Log.e(TAG, "login gtask failed"); + return false; + } + } + // 如果登录成功(无论是初次尝试还是重新获取令牌后的尝试),则返回true。 + return true; +} + +// 定义一个私有方法loginGtask,它使用提供的authToken尝试登录到Google Tasks。 + private boolean loginGtask(String authToken) { + // 设置HTTP连接的超时时间(10秒)和套接字超时时间(15秒)。 + int timeoutConnection = 10000; + int timeoutSocket = 15000; + HttpParams httpParameters = new BasicHttpParams(); + HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); + HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); + + // 创建一个HttpClient实例,并使用前面设置的参数进行配置。 + mHttpClient = new DefaultHttpClient(httpParameters); + + // 创建一个CookieStore实例,并将其设置到HttpClient中,以便管理HTTP cookies。 + BasicCookieStore localBasicCookieStore = new BasicCookieStore(); + mHttpClient.setCookieStore(localBasicCookieStore); + + // 禁用HTTP/1.1的"Expect: 100-continue"机制,以提高性能。 + HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); + + // 构造登录Google Tasks的URL,其中包含authToken作为查询参数。 + String loginUrl = mGetUrl + "?auth=" + authToken; + + // 创建一个HttpGet请求,用于向登录URL发送请求。 + HttpGet httpGet = new HttpGet(loginUrl); + + // 执行HttpGet请求,并获取响应。 + HttpResponse response = null; + try { + response = mHttpClient.execute(httpGet); + } catch (Exception e) { + // 如果在执行请求时发生异常,则打印错误日志并返回false。 + Log.e(TAG, "httpget gtask_url failed"); + return false; + } + + // 从HttpClient的CookieStore中获取所有的cookies。 + List cookies = mHttpClient.getCookieStore().getCookies(); + boolean hasAuthCookie = false; + + // 遍历所有的cookies,检查是否存在名为"GTL"的认证cookie。 + for (Cookie cookie : cookies) { + if (cookie.getName().contains("GTL")) { + hasAuthCookie = true; + } + } + + // 如果没有找到认证cookie,则打印警告日志。 + if (!hasAuthCookie) { + Log.w(TAG, "it seems that there is no auth cookie"); + } + + // 从HTTP响应中获取响应体的内容。 + String resString = getResponseContent(response.getEntity()); + + // 定义JavaScript字符串的开始和结束标记,用于从响应内容中提取JavaScript代码。 + String jsBegin = "_setup("; + String jsEnd = ")}"; + + // 在响应内容中查找JavaScript代码的开始和结束位置。 + int begin = resString.indexOf(jsBegin); + int end = resString.lastIndexOf(jsEnd); + + // 如果找到了JavaScript代码的开始和结束位置,并且它们是有效的,则提取JavaScript代码。 + String jsString = null; + if (begin != -1 && end != -1 && begin < end) { + jsString = resString.substring(begin + jsBegin.length(), end); + } + + // 将提取的JavaScript代码解析为JSONObject,并尝试从中获取客户端版本信息。 + try { + JSONObject js = new JSONObject(jsString); + mClientVersion = js.getLong("v"); + } catch (JSONException e) { + // 如果在解析JSON时发生异常,则打印错误日志,打印堆栈跟踪,并返回false。 + Log.e(TAG, e.toString()); + e.printStackTrace(); + return false; + } catch (Exception e) { + // 简单地捕获所有其他异常(这里应该是不会发生的,因为前面已经对jsString进行了有效性检查)。 + // 但为了代码的健壮性,还是打印错误日志并返回false。 + Log.e(TAG, "Unexpected exception"); + return false; + } + + // 如果一切顺利,则返回true表示登录成功。 + return true; + } + + // 定义一个私有方法,用于获取一个动作ID,并每次调用时自增 + private int getActionId() { + return mActionId++; // 返回当前的动作ID,并将成员变量mActionId的值自增 + } + +// 定义一个私有方法,用于创建一个HttpPost对象 + private HttpPost createHttpPost() { + HttpPost httpPost = new HttpPost(mPostUrl); // 使用成员变量mPostUrl创建HttpPost对象 + httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); // 设置请求头,指定内容类型和字符集 + httpPost.setHeader("AT", "1"); // 设置请求头AT的值为1 + return httpPost; // 返回创建的HttpPost对象 + } + +// 定义一个私有方法,用于从HttpEntity对象中获取响应内容 + private String getResponseContent(HttpEntity entity) throws IOException { + String contentEncoding = null; // 初始化内容编码为空 + if (entity.getContentEncoding() != null) { // 如果HttpEntity有内容编码 + contentEncoding = entity.getContentEncoding().getValue(); // 获取内容编码的值 + Log.d(TAG, "encoding: " + contentEncoding); // 打印日志,显示内容编码 + } + + InputStream input = entity.getContent(); // 获取输入流 + if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { // 如果内容编码是gzip + input = new GZIPInputStream(entity.getContent()); // 使用GZIPInputStream包装输入流 + } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { // 如果内容编码是deflate + Inflater inflater = new Inflater(true); // 创建一个Inflater对象,用于解压缩 + input = new InflaterInputStream(entity.getContent(), inflater); // 使用InflaterInputStream包装输入流 + } + + try { + InputStreamReader isr = new InputStreamReader(input); // 创建输入流读取器 + BufferedReader br = new BufferedReader(isr); // 创建缓冲读取器 + StringBuilder sb = new StringBuilder(); // 创建字符串构建器,用于拼接读取的行 + + while (true) { // 循环读取输入流中的每一行 + String buff = br.readLine(); // 读取一行 + if (buff == null) { // 如果读取到null,表示已到达输入流的末尾 + return sb.toString(); // 返回拼接好的字符串 + } + sb = sb.append(buff); // 将读取的行追加到字符串构建器中 + } + } finally { + input.close(); // 关闭输入流 + } + } + +// 定义一个私有方法,用于发送POST请求并返回JSON响应 + private JSONObject postRequest(JSONObject js) throws NetworkFailureException { + if (!mLoggedin) { // 如果未登录 + Log.e(TAG, "please login first"); // 打印错误日志,提示用户先登录 + throw new ActionFailureException("not logged in"); // 抛出未登录的异常 + } + + HttpPost httpPost = createHttpPost(); // 创建HttpPost对象 + try { + LinkedList list = new LinkedList(); // 创建一个链表,用于存储键值对 + list.add(new BasicNameValuePair("r", js.toString())); // 将JSON对象转换为字符串,并添加到链表中,键为"r" + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); // 创建UrlEncodedFormEntity对象,用于POST请求的数据体 + httpPost.setEntity(entity); // 设置HttpPost的数据体 + + // 执行POST请求 + HttpResponse response = mHttpClient.execute(httpPost); // 使用HttpClient执行POST请求 + String jsString = getResponseContent(response.getEntity()); // 获取响应内容 + return new JSONObject(jsString); // 将响应内容转换为JSONObject对象并返回 + + } catch (ClientProtocolException e) { // 如果发生协议异常 + Log.e(TAG, e.toString()); // 打印错误日志 + e.printStackTrace(); // 打印异常堆栈 + throw new NetworkFailureException("postRequest failed"); // 抛出网络请求失败的异常 + } catch (IOException e) { // 如果发生IO异常 + Log.e(TAG, e.toString()); // 打印错误日志 + e.printStackTrace(); // 打印异常堆栈 + throw new NetworkFailureException("postRequest failed"); // 抛出网络请求失败的异常 + } catch (JSONException e) { // 如果发生JSON解析异常 + Log.e(TAG, e.toString()); // 打印错误日志 + e.printStackTrace(); // 打印异常堆栈 + throw new ActionFailureException("unable to convert response content to jsonobject"); // 抛出无法将响应内容转换为JSONObject的异常 + } catch (Exception e) { // 如果发生其他异常 + Log.e(TAG, e.toString()); // 打印错误日志 + e.printStackTrace(); // 打印异常堆栈 + throw new ActionFailureException("error occurs when posting request"); // 抛出发送请求时发生错误的异常 + } + } + // 定义一个方法,用于创建任务,可能会抛出网络失败异常 + public void createTask(Task task) throws NetworkFailureException { + // 提交所有待处理的更新到服务器 + commitUpdate(); + try { + // 创建一个JSON对象用于POST请求 + JSONObject jsPost = new JSONObject(); + // 创建一个JSON数组用于存放动作列表 + JSONArray actionList = new JSONArray(); + + // 向动作列表中添加创建任务的动作,getActionId()可能是获取一个唯一的动作ID + actionList.put(task.getCreateAction(getActionId())); + // 将动作列表添加到JSON对象中 + jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + + // 添加客户端版本号到JSON对象中 + jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + + // 发送POST请求到服务器,并接收响应 + JSONObject jsResponse = postRequest(jsPost); + // 从响应结果中获取第一个结果对象 + JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( + GTaskStringUtils.GTASK_JSON_RESULTS).get(0); + // 从结果对象中获取新创建任务的ID,并设置给任务对象 + task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); + + } catch (JSONException e) { + // 捕获JSON处理异常,记录错误日志 + Log.e(TAG, e.toString()); + e.printStackTrace(); + // 抛出自定义的动作失败异常 + throw new ActionFailureException("create task: handing jsonobject failed"); + } + } + +// 定义一个方法,用于创建任务列表,可能会抛出网络失败异常 + public void createTaskList(TaskList tasklist) throws NetworkFailureException { + // 提交所有待处理的更新到服务器 + commitUpdate(); + try { + // 创建一个JSON对象用于POST请求 + JSONObject jsPost = new JSONObject(); + // 创建一个JSON数组用于存放动作列表 + JSONArray actionList = new JSONArray(); + + // 向动作列表中添加创建任务列表的动作,getActionId()可能是获取一个唯一的动作ID + actionList.put(tasklist.getCreateAction(getActionId())); + // 将动作列表添加到JSON对象中 + jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + + // 添加客户端版本号到JSON对象中 + jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + + // 发送POST请求到服务器,并接收响应 + JSONObject jsResponse = postRequest(jsPost); + // 从响应结果中获取第一个结果对象 + JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( + GTaskStringUtils.GTASK_JSON_RESULTS).get(0); + // 从结果对象中获取新创建任务列表的ID,并设置给任务列表对象 + tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); + + } catch (JSONException e) { + // 捕获JSON处理异常,记录错误日志 + Log.e(TAG, e.toString()); + e.printStackTrace(); + // 抛出自定义的动作失败异常 + throw new ActionFailureException("create tasklist: handing jsonobject failed"); + } + } + +// 定义一个方法,用于提交所有待处理的更新到服务器,可能会抛出网络失败异常 + public void commitUpdate() throws NetworkFailureException { + // 如果存在待处理的更新 + if (mUpdateArray != null) { + try { + // 创建一个JSON对象用于POST请求 + JSONObject jsPost = new JSONObject(); + + // 将待处理的更新动作列表添加到JSON对象中 + jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); + + // 添加客户端版本号到JSON对象中 + jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + + // 发送POST请求到服务器,不需要接收响应(可能是只需要服务器处理更新) + postRequest(jsPost); + // 清空待处理的更新数组 + mUpdateArray = null; + + } catch (JSONException e) { + // 捕获JSON处理异常,记录错误日志 + Log.e(TAG, e.toString()); + e.printStackTrace(); + // 抛出自定义的动作失败异常 + throw new ActionFailureException("commit update: handing jsonobject failed"); + } + } + } + // 定义一个方法,用于添加或更新节点,如果节点不为空 + public void addUpdateNode(Node node) throws NetworkFailureException { + if (node != null) { + // 如果更新数组不为空且长度超过10,则提交更新 + if (mUpdateArray != null && mUpdateArray.length() > 10) { + commitUpdate(); + } + + // 如果更新数组为空,则初始化它 + if (mUpdateArray == null) + mUpdateArray = new JSONArray(); + // 将节点的更新操作添加到更新数组中 + mUpdateArray.put(node.getUpdateAction(getActionId())); + } + } + +// 定义一个方法,用于移动任务 + public void moveTask(Task task, TaskList preParent, TaskList curParent) + throws NetworkFailureException { + // 在移动任务前提交所有待处理的更新 + commitUpdate(); + try { + // 创建一个JSON对象用于POST请求 + JSONObject jsPost = new JSONObject(); + // 创建一个JSON数组用于存放操作列表 + JSONArray actionList = new JSONArray(); + // 创建一个JSON对象表示一个操作 + JSONObject action = new JSONObject(); + + // 设置操作类型为移动 + action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); + // 设置操作ID + action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); + // 设置任务的ID + action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); + // 如果前后父任务列表相同且任务有前一个兄弟节点,则设置前一个兄弟节点的ID + if (preParent == curParent && task.getPriorSibling() != null) { + action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); + } + // 设置源任务列表的ID + action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); + // 设置目标父任务列表的ID + action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); + // 如果前后父任务列表不同,则设置目标任务列表的ID(这里似乎有些重复,可能是特定逻辑需要) + if (preParent != curParent) { + action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); + } + // 将操作添加到操作列表中 + actionList.put(action); + // 将操作列表添加到POST请求中 + jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + + // 设置客户端版本 + jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + + // 发送POST请求 + postRequest(jsPost); + + } catch (JSONException e) { + // 记录异常并抛出自定义异常 + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("move task: handing jsonobject failed"); + } + } + +// 定义一个方法,用于删除节点 + public void deleteNode(Node node) throws NetworkFailureException { + // 在删除节点前提交所有待处理的更新 + commitUpdate(); + try { + // 创建一个JSON对象用于POST请求 + JSONObject jsPost = new JSONObject(); + // 创建一个JSON数组用于存放操作列表 + JSONArray actionList = new JSONArray(); + + // 标记节点为已删除 + node.setDeleted(true); + // 将节点的删除操作添加到操作列表中 + actionList.put(node.getUpdateAction(getActionId())); + // 将操作列表添加到POST请求中 + jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + + // 设置客户端版本 + jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + + // 发送POST请求 + postRequest(jsPost); + // 清空更新数组 + mUpdateArray = null; + } catch (JSONException e) { + // 记录异常并抛出自定义异常 + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("delete node: handing jsonobject failed"); + } + } + +// 定义一个方法,用于获取任务列表 + public JSONArray getTaskLists() throws NetworkFailureException { + // 如果未登录,则抛出异常 + if (!mLoggedin) { + Log.e(TAG, "please login first"); + throw new ActionFailureException("not logged in"); + } + + try { + // 创建一个HttpGet请求 + HttpGet httpGet = new HttpGet(mGetUrl); + // 执行请求并获取响应 + HttpResponse response = null; + response = mHttpClient.execute(httpGet); + + // 获取响应内容 + String resString = getResponseContent(response.getEntity()); + // 定义字符串,用于从响应内容中提取JSON数据 + String jsBegin = "_setup("; + String jsEnd = ")}"; + // 查找JSON数据的开始和结束位置 + int begin = resString.indexOf(jsBegin); + int end = resString.lastIndexOf(jsEnd); + String jsString = null; + // 如果找到了开始和结束位置,则提取JSON字符串 + if (begin != -1 && end != -1 && begin < end) { + jsString = resString.substring(begin + jsBegin.length(), end); + } + // 将JSON字符串转换为JSON对象 + JSONObject js = new JSONObject(jsString); + // 从JSON对象中获取任务列表数组 + return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); + } catch (ClientProtocolException e) { + // 记录异常并抛出自定义网络异常 + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new NetworkFailureException("gettasklists: httpget failed"); + } catch (IOException e) { + // 记录异常并抛出自定义网络异常 + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new NetworkFailureException("gettasklists: httpget failed"); + } catch (JSONException e) { + // 记录异常并抛出自定义操作异常 + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("get task lists: handing jasonobject failed"); + } + } + + // 定义一个方法,用于获取任务列表,接收一个列表的全局唯一标识符作为参数,并声明可能抛出网络失败异常 + public JSONArray getTaskList(String listGid) throws NetworkFailureException { + // 提交更新操作,可能是在执行网络请求前进行一些准备工作,比如更新UI或检查状态 + commitUpdate(); + try { + // 创建一个JSONObject对象,用于构建要发送的JSON数据 + JSONObject jsPost = new JSONObject(); + // 创建一个JSONArray对象,用于存放动作列表 + JSONArray actionList = new JSONArray(); + // 创建一个JSONObject对象,用于描述一个具体的动作 + JSONObject action = new JSONObject(); + + // 在动作对象中设置动作类型为获取所有任务 + action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); + // 设置动作ID,具体ID通过getActionId()方法获取 + action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); + // 设置列表的全局唯一标识符 + action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); + // 设置是否获取已删除的任务,这里设置为false,即不获取 + action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); + // 将动作对象添加到动作列表中 + actionList.put(action); + // 将动作列表添加到要发送的JSON数据中 + jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + + // 在要发送的JSON数据中设置客户端版本 + jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + + // 发送POST请求,并接收响应的JSONObject对象 + JSONObject jsResponse = postRequest(jsPost); + // 从响应的JSONObject对象中获取任务列表的JSONArray对象,并返回 + return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); + } catch (JSONException e) { + // 捕获JSON处理异常,记录错误日志 + Log.e(TAG, e.toString()); + // 打印堆栈跟踪信息 + e.printStackTrace(); + // 抛出动作失败异常,说明在处理JSON对象时出错 + throw new ActionFailureException("get task list: handing jsonobject failed"); + } + } + +// 定义一个方法,用于获取同步账户对象 + public Account getSyncAccount() { + // 返回账户对象 + return mAccount; + } + +// 定义一个方法,用于重置更新数组 + public void resetUpdateArray() { + // 将更新数组设置为null,可能用于清空或重置状态 + mUpdateArray = null; +} \ No newline at end of file From c7fd833278c2cec6d1cbe18a6d4387d81e7c6c76 Mon Sep 17 00:00:00 2001 From: YourUsername Date: Thu, 19 Dec 2024 23:31:34 +0800 Subject: [PATCH 12/12] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GTaskManager.java | 824 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 824 insertions(+) create mode 100644 GTaskManager.java diff --git a/GTaskManager.java b/GTaskManager.java new file mode 100644 index 0000000..e6795fb --- /dev/null +++ b/GTaskManager.java @@ -0,0 +1,824 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.remote; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.util.Log; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.data.MetaData; +import net.micode.notes.gtask.data.Node; +import net.micode.notes.gtask.data.SqlNote; +import net.micode.notes.gtask.data.Task; +import net.micode.notes.gtask.data.TaskList; +import net.micode.notes.gtask.exception.ActionFailureException; +import net.micode.notes.gtask.exception.NetworkFailureException; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.GTaskStringUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; + + +public class GTaskManager { + private static final String TAG = GTaskManager.class.getSimpleName(); + + public static final int STATE_SUCCESS = 0; + + public static final int STATE_NETWORK_ERROR = 1; + + public static final int STATE_INTERNAL_ERROR = 2; + + public static final int STATE_SYNC_IN_PROGRESS = 3; + + public static final int STATE_SYNC_CANCELLED = 4; + + private static GTaskManager mInstance = null; + + private Activity mActivity; + + private Context mContext; + + private ContentResolver mContentResolver; + + private boolean mSyncing; + + private boolean mCancelled; + + private HashMap mGTaskListHashMap; + + private HashMap mGTaskHashMap; + + private HashMap mMetaHashMap; + + private TaskList mMetaList; + + private HashSet mLocalDeleteIdMap; + + private HashMap mGidToNid; + + private HashMap mNidToGid; + + private GTaskManager() {//对象初始化函数 + mSyncing = false;//正在同步,flase代表未执行 + mCancelled = false;//全局标识,flase代表可以执行 + mGTaskListHashMap = new HashMap();//<>代表Java的泛型,就是创建一个用类型作为参数的类 + mGTaskHashMap = new HashMap(); + mMetaHashMap = new HashMap(); + mMetaList = null; + mLocalDeleteIdMap = new HashSet(); + mGidToNid = new HashMap(); + mNidToGid = new HashMap();//通过hashmap散列表建立映射 + } +/** + * 包含关键字synchronized,语言级同步,指明该函数可能运行在多线程的环境下 + * 实现功能:类初始化函数 + */ + public static synchronized GTaskManager getInstance() {//可能运行在多线程环境下,使用语言级同步--synchronized + if (mInstance == null) { + mInstance = new GTaskManager(); + } + return mInstance; + } +/** + * 包含关键字synchronized,语言级同步,指明该函数可能运行在多线程的环境下 + */ + public synchronized void setActivityContext(Activity activity) { + // used for getting authtoken + mActivity = activity; + } +/**实现功能:实现了本地同步操作和远端同步操作 + * context---获取上下文 + * asyncTask---用于同步的异步操作类 +*/ + public int sync(Context context, GTaskASyncTask asyncTask) {//核心函数 + if (mSyncing) { + Log.d(TAG, "Sync is in progress");//创建日志文件(调试信息),debug + return STATE_SYNC_IN_PROGRESS; + } + mContext = context; + mContentResolver = mContext.getContentResolver(); + mSyncing = true; + mCancelled = false; + mGTaskListHashMap.clear(); + mGTaskHashMap.clear(); + mMetaHashMap.clear(); + mLocalDeleteIdMap.clear(); + mGidToNid.clear(); + mNidToGid.clear(); + + try { + GTaskClient client = GTaskClient.getInstance();//getInstance即为创建一个实例,client---客户机 + client.resetUpdateArray();//JSONArray类型,reset即置为NULL + + // login google task + if (!mCancelled) { + if (!client.login(mActivity)) { + throw new NetworkFailureException("login google task failed"); + } + } + + // get the task list from google + asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); + initGTaskList();//获取Google上的JSONtasklist转为本地TaskList + + // do content sync work + asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); + syncContent(); + } catch (NetworkFailureException e) {//分为两种异常,此类异常为网络异常 + Log.e(TAG, e.toString());//创建日志文件(调试信息),error + return STATE_NETWORK_ERROR; + } catch (ActionFailureException e) {//此类异常为操作异常 + Log.e(TAG, e.toString()); + return STATE_INTERNAL_ERROR; + } catch (Exception e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + return STATE_INTERNAL_ERROR; + } finally { + mGTaskListHashMap.clear(); + mGTaskHashMap.clear(); + mMetaHashMap.clear(); + mLocalDeleteIdMap.clear(); + mGidToNid.clear(); + mNidToGid.clear(); + mSyncing = false; + } + + return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; + } +/** + * 实现功能:初始化GtaskList,获取Google上的JSONtasklist转为本地TaskList + * 获得的的数据存储在mMetaList,mGTaskListHashMap,mGTaskHashMap + */ + private void initGTaskList() throws NetworkFailureException { + if (mCancelled) + return; + GTaskClient client = GTaskClient.getInstance();//getInstance即为创建一个实例,client应指远端客户机 + try { + //Json对象是Name Value对(及子元素)的无序集合,相当于一个Map对象,JsonObject类是bantouyan-json库对Json对象的抽象,提供操纵Json对象的各种方法 + //其格式为{"key1":value1,"key2",value2....};key必须是字符串 + //因为ajax请求不刷新页面,但配合js可以实现局部刷新,因此json常常被用来作为异步请求的返回对象使用 + JSONArray jsTaskLists = client.getTaskLists(); + + // init meta list first + mMetaList = null;//TaskList类型 + for (int i = 0; i < jsTaskLists.length(); i++) { + JSONObject object = jsTaskLists.getJSONObject(i);//JSONObject与JSONArray一个为对象,一个为数组,此处取出单个JASONObject + String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); + String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); + + if (name + .equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + mMetaList = new TaskList();//MetaList意为元表,Tasklist类型,此处为初始化 + mMetaList.setContentByRemoteJSON(object);//将JSON中部分数据复制到自己定义的对象中相对应的数据:name->mname + + // load meta data + JSONArray jsMetas = client.getTaskList(gid); + for (int j = 0; j < jsMetas.length(); j++) { + object = (JSONObject) jsMetas.getJSONObject(j); + MetaData metaData = new MetaData();//继承自Node + metaData.setContentByRemoteJSON(object); + if (metaData.isWorthSaving()) {//if not worth to save,metadate将不加入mMetaList + mMetaList.addChildTask(metaData); + if (metaData.getGid() != null) { + mMetaHashMap.put(metaData.getRelatedGid(), metaData); + } + } + } + } + } + + // create meta list if not existed + if (mMetaList == null) { + mMetaList = new TaskList(); + mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_META); + GTaskClient.getInstance().createTaskList(mMetaList); + } + + // init task list + for (int i = 0; i < jsTaskLists.length(); i++) { + JSONObject object = jsTaskLists.getJSONObject(i); + String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);//通过getString函数传入本地某个标志数据的名称,获取其在远端的名称 + String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); + + if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) + && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_META)) { + TaskList tasklist = new TaskList();//继承自Node + tasklist.setContentByRemoteJSON(object); + mGTaskListHashMap.put(gid, tasklist); + mGTaskHashMap.put(gid, tasklist); + + // load tasks + JSONArray jsTasks = client.getTaskList(gid); + for (int j = 0; j < jsTasks.length(); j++) { + object = (JSONObject) jsTasks.getJSONObject(j); + gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); + Task task = new Task(); + task.setContentByRemoteJSON(object); + if (task.isWorthSaving()) { + task.setMetaInfo(mMetaHashMap.get(gid)); + tasklist.addChildTask(task); + mGTaskHashMap.put(gid, task); + } + } + } + } + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("initGTaskList: handing JSONObject failed"); + } + } +/** + * 实现功能:本地内容同步操作 + */ + private void syncContent() throws NetworkFailureException {//本地内容同步操作 + int syncType; + Cursor c = null;//数据库指针 + String gid; + Node node;//Node包含Sync_Action的不同类型 + + mLocalDeleteIdMap.clear();//HashSet类型 + + if (mCancelled) { + return; + } + + // for local deleted note + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type<>? AND parent_id=?)", new String[] { + String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) + }, null); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); + } + + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); + } + } else { + Log.w(TAG, "failed to query trash folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // sync folder first + syncFolder(); + + // for note existing in database + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type=? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));//通过hashmap建立联系 + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);//通过hashmap建立联系 + syncType = node.getSyncAction(c); + } else { + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + // local add + syncType = Node.SYNC_ACTION_ADD_REMOTE; + } else { + // remote delete + syncType = Node.SYNC_ACTION_DEL_LOCAL; + } + } + doContentSync(syncType, node, c); + } + } else { + Log.w(TAG, "failed to query existing note in database"); + } + + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // go through remaining items + Iterator> iter = mGTaskHashMap.entrySet().iterator();//iterator迭代器 + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + node = entry.getValue(); + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + } + + // mCancelled can be set by another thread, so we neet to check one by + // one + // clear local delete table + if (!mCancelled) { + if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { + throw new ActionFailureException("failed to batch-delete local deleted notes"); + } + } + + // refresh local sync id + if (!mCancelled) { + GTaskClient.getInstance().commitUpdate(); + refreshLocalSyncId(); + } + + } + + private void syncFolder() throws NetworkFailureException { + Cursor c = null; + String gid; + Node node; + int syncType; + + if (mCancelled) { + return; + } + + // for root folder + try { + c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, + Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); + if (c != null) { + c.moveToNext(); + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); + mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); + // for system folder, only update remote name if necessary + if (!node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) + doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); + } else { + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); + } + } else { + Log.w(TAG, "failed to query root folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // for call-note folder + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", + new String[] { + String.valueOf(Notes.ID_CALL_RECORD_FOLDER) + }, null); + if (c != null) { + if (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); + mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); + // for system folder, only update remote name if + // necessary + if (!node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_CALL_NOTE)) + doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); + } else { + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); + } + } + } else { + Log.w(TAG, "failed to query call note folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // for local existing folders + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type=? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); + syncType = node.getSyncAction(c); + } else { + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + // local add + syncType = Node.SYNC_ACTION_ADD_REMOTE; + } else { + // remote delete + syncType = Node.SYNC_ACTION_DEL_LOCAL; + } + } + doContentSync(syncType, node, c); + } + } else { + Log.w(TAG, "failed to query existing folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // for remote add folders + Iterator> 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(); + } +/** + * 实现功能:syncType分类,addLocalNode,addRemoteNode,deleteNode,updateLocalNode,updateRemoteNode +*/ + private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + MetaData meta; + switch (syncType) { + case Node.SYNC_ACTION_ADD_LOCAL: + addLocalNode(node); + break; + case Node.SYNC_ACTION_ADD_REMOTE: + addRemoteNode(node, c); + break; + case Node.SYNC_ACTION_DEL_LOCAL: + meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); + if (meta != null) { + GTaskClient.getInstance().deleteNode(meta); + } + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); + break; + case Node.SYNC_ACTION_DEL_REMOTE: + meta = mMetaHashMap.get(node.getGid()); + if (meta != null) { + GTaskClient.getInstance().deleteNode(meta); + } + GTaskClient.getInstance().deleteNode(node); + break; + case Node.SYNC_ACTION_UPDATE_LOCAL: + updateLocalNode(node, c); + break; + case Node.SYNC_ACTION_UPDATE_REMOTE: + updateRemoteNode(node, c); + break; + case Node.SYNC_ACTION_UPDATE_CONFLICT: + // merging both modifications maybe a good idea + // right now just use local update simply + updateRemoteNode(node, c); + break; + case Node.SYNC_ACTION_NONE: + break; + case Node.SYNC_ACTION_ERROR: + default: + throw new ActionFailureException("unkown sync action type"); + } + } +/**实现功能:本地增加Node */ + private void addLocalNode(Node node) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote; + if (node instanceof TaskList) { + if (node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { + sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); + } else if (node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { + sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); + } else { + sqlNote = new SqlNote(mContext); + sqlNote.setContent(node.getLocalJSONFromContent()); + sqlNote.setParentId(Notes.ID_ROOT_FOLDER); + } + } else { + sqlNote = new SqlNote(mContext); + JSONObject js = node.getLocalJSONFromContent(); + try { + if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + if (note.has(NoteColumns.ID)) { + long id = note.getLong(NoteColumns.ID); + if (DataUtils.existInNoteDatabase(mContentResolver, id)) { + // the id is not available, have to create a new one + note.remove(NoteColumns.ID); + } + } + } + + if (js.has(GTaskStringUtils.META_HEAD_DATA)) { + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); + if (data.has(DataColumns.ID)) { + long dataId = data.getLong(DataColumns.ID); + if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { + // the data id is not available, have to create + // a new one + data.remove(DataColumns.ID); + } + } + } + + } + } catch (JSONException e) { + Log.w(TAG, e.toString()); + e.printStackTrace(); + } + sqlNote.setContent(js); + + Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); + if (parentId == null) { + Log.e(TAG, "cannot find task's parent id locally"); + throw new ActionFailureException("cannot add local node"); + } + sqlNote.setParentId(parentId.longValue()); + } + + // create the local node + sqlNote.setGtaskId(node.getGid()); + sqlNote.commit(false); + + // update gid-nid mapping + mGidToNid.put(node.getGid(), sqlNote.getId()); + mNidToGid.put(sqlNote.getId(), node.getGid()); + + // update meta + updateRemoteMeta(node.getGid(), sqlNote); + } +/**实现功能:更新本地Node */ + private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote; + // update the note locally + sqlNote = new SqlNote(mContext, c); + sqlNote.setContent(node.getLocalJSONFromContent()); + + Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) + : new Long(Notes.ID_ROOT_FOLDER); + if (parentId == null) { + Log.e(TAG, "cannot find task's parent id locally"); + throw new ActionFailureException("cannot update local node"); + } + sqlNote.setParentId(parentId.longValue()); + sqlNote.commit(true); + + // update meta info + updateRemoteMeta(node.getGid(), sqlNote); + } +/**实现功能:远程增加node */ + private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote = new SqlNote(mContext, c);//从本地mContext中获取内容 + Node n; + + // update remotely + if (sqlNote.isNoteType()) { + Task task = new Task(); + task.setContentByLocalJSON(sqlNote.getContent()); + + String parentGid = mNidToGid.get(sqlNote.getParentId()); + if (parentGid == null) { + Log.e(TAG, "cannot find task's parent tasklist");//调试信息 + throw new ActionFailureException("cannot add remote task"); + } + mGTaskListHashMap.get(parentGid).addChildTask(task);//在本地生成的GTaskList中增加子结点 +//登录远程服务器,创建Task + GTaskClient.getInstance().createTask(task); + n = (Node) task; + + // add meta + updateRemoteMeta(task.getGid(), sqlNote); + } else { + TaskList tasklist = null; + + // we need to skip folder if it has already existed + String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; + if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) + folderName += GTaskStringUtils.FOLDER_DEFAULT; + else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER) + folderName += GTaskStringUtils.FOLDER_CALL_NOTE; + else + folderName += sqlNote.getSnippet(); +//iterator迭代器,通过统一的接口迭代所有的map元素 + Iterator> iter = mGTaskListHashMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + String gid = entry.getKey(); + TaskList list = entry.getValue(); + + if (list.getName().equals(folderName)) { + tasklist = list; + if (mGTaskHashMap.containsKey(gid)) { + mGTaskHashMap.remove(gid); + } + break; + } + } + + // no match we can add now + if (tasklist == null) { + tasklist = new TaskList(); + tasklist.setContentByLocalJSON(sqlNote.getContent()); + GTaskClient.getInstance().createTaskList(tasklist); + mGTaskListHashMap.put(tasklist.getGid(), tasklist); + } + n = (Node) tasklist; + } + + // update local note + sqlNote.setGtaskId(n.getGid()); + sqlNote.commit(false); + sqlNote.resetLocalModified(); + sqlNote.commit(true); + + // gid-id mapping + //创建id间的映射 + mGidToNid.put(n.getGid(), sqlNote.getId()); + mNidToGid.put(sqlNote.getId(), n.getGid()); + } +/**实现功能:更新远端的Node,包含meta更新 */ + private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote = new SqlNote(mContext, c); + + // update remotely + node.setContentByLocalJSON(sqlNote.getContent()); + GTaskClient.getInstance().addUpdateNode(node);//GTaskClient用途为从本地登录远端服务器 + + // update meta + updateRemoteMeta(node.getGid(), sqlNote); + + // move task if necessary + if (sqlNote.isNoteType()) { + Task task = (Task) node; + TaskList preParentList = task.getParent(); +//preParentList为通过node获取的父节点列表 + String curParentGid = mNidToGid.get(sqlNote.getParentId()); + //curParentGid为通过光标在数据库中找到sqlNote的mParentId,再通过mNidToGid有long类型转为String类型的Gid + if (curParentGid == null) { + Log.e(TAG, "cannot find task's parent tasklist"); + throw new ActionFailureException("cannot update remote task"); + } + TaskList curParentList = mGTaskListHashMap.get(curParentGid); +//通过HashMap找到对应的Gid的TaskList + if (preParentList != curParentList) { + preParentList.removeChildTask(task); + curParentList.addChildTask(task); + GTaskClient.getInstance().moveTask(task, preParentList, curParentList); + } + } + + // clear local modified flag + sqlNote.resetLocalModified(); + //commit到本地数据库 + sqlNote.commit(true); + } +/**实现功能:升级远程meta,meta---元数据---计算机文件系统管理数据---管理数据的数据 */ + private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { + if (sqlNote != null && sqlNote.isNoteType()) { + MetaData metaData = mMetaHashMap.get(gid); + if (metaData != null) { + metaData.setMeta(gid, sqlNote.getContent()); + GTaskClient.getInstance().addUpdateNode(metaData); + } else { + metaData = new MetaData(); + metaData.setMeta(gid, sqlNote.getContent()); + mMetaList.addChildTask(metaData); + mMetaHashMap.put(gid, metaData); + GTaskClient.getInstance().createTask(metaData); + } + } + } +/**实现功能:刷新本地,给sync的ID对应上最后更改过的对象 */ + private void refreshLocalSyncId() throws NetworkFailureException { + if (mCancelled) { + return; + } + + // get the latest gtask list + //获取最近的gtask list + mGTaskHashMap.clear(); + mGTaskListHashMap.clear(); + mMetaHashMap.clear(); + initGTaskList(); + + Cursor c = null; + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type<>? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC");//query语句:五个参数,NoteColumns.TYPE + "DESC"-----为按类型递减顺序返回查询结果,new String[]{String.valueof(Notes.TYPE_SYSTEM), + //String.valueof(Notes.ID_TRASH_FOLER)}-----为选择参数,"(type<>?AND parent_id<>?)"------指明返回行过滤器,SqlNote.PROJECTION_NOTE-----对应返回的数据列的名字, + //Notes.CONTENT_NOTE_URI------contentProvider包含所有数据集所对应的uri + if (c != null) { + while (c.moveToNext()) { + String gid = c.getString(SqlNote.GTASK_ID_COLUMN); + Node node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + ContentValues values = new ContentValues();//在ContentValues中创建键值对,准备通过contentResolver写入数据 + values.put(NoteColumns.SYNC_ID, node.getLastModified()); + mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,//进行批量更改,选择参数为NULL,应该可以用insert替换,参数分别为表名和需要更新的value对象 + c.getLong(SqlNote.ID_COLUMN)), values, null, null); + } else { + Log.e(TAG, "something is missed"); + throw new ActionFailureException( + "some local items don't have gid after sync"); + } + } + } else { + Log.w(TAG, "failed to query local note to refresh sync id"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + } +/**实现功能:获取同步账号,mAccount.name */ + public String getSyncAccount() { + return GTaskClient.getInstance().getSyncAccount().name; + } +/**实现功能:取消同步,置mCancelled 为 true */ + public void cancelSync() { + mCancelled = true; + } +}