Compare commits

..

64 Commits

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

@ -0,0 +1,87 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
`ActionFailureException` Java `RuntimeException`
`net.micode.notes.gtask.exception`
*/
// **包声明和导入**
package net.micode.notes.gtask.exception;
//这行代码定义了该类所在的包名:`net.micode.notes.gtask.exception`。通常包用于组织类和接口,并为类提供命名空间。
//**类声明**
public class ActionFailureException extends RuntimeException {
//`ActionFailureException` 继承自 `RuntimeException`,意味着它是一个 **未检查异常unchecked exception**。
// `RuntimeException` 是 `Exception` 类的子类,通常用于表示程序中的错误,通常不需要强制捕获或声明。
//- 异常的名字 `ActionFailureException` 表示某种操作失败的情况。
//**序列化 ID**
private static final long serialVersionUID = 4425249765923293627L;
/*
`serialVersionUID`
- Java `Serializable` `serialVersionUID`
- `ActionFailureException` `Serializable`
*/
//**构造函数**
// 默认构造函数
public ActionFailureException() {
super();
}
// 默认构造函数,调用 `RuntimeException` 的无参构造函数,表示没有具体的错误信息时抛出此异常。
//带消息构造函数
public ActionFailureException(String paramString) {
super(paramString);
}
//- 这个构造函数允许传入一个字符串参数 `paramString`,用来描述异常的具体信息。这会调用 `RuntimeException` 的带有消息的构造函数。
//带消息和原因构造函数
public ActionFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}
//- 这个构造函数允许传入一个字符串参数和另一个异常(`Throwable`)作为参数。
// 通过这种方式,可以指定异常的描述信息和导致当前异常的根本原因(例如,另一个异常)。
/* . ** `RuntimeException` **
- `ActionFailureException` `RuntimeException` **unchecked exception**
-
**使**
`ActionFailureException`
- `ActionFailureException`
-
`ActionFailureException` `RuntimeException`
-
- `RuntimeException`
*/

@ -0,0 +1,197 @@
/*
* 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.ui;
import android.app.Activity;// 活动的基类
import android.app.AlertDialog;// 用于创建警告对话框
import android.content.Context; // 提供访问应用程序特定资源的上下文
import android.content.DialogInterface; // 对话框交互接口
import android.content.Intent; // 创建启动新活动的意图
import android.media.AudioManager; // 管理音频设置
import android.media.MediaPlayer; // 播放音频文件
import android.media.RingtoneManager; // 访问系统铃声
import android.net.Uri; // 表示URI
import android.os.Bundle;// 在活动之间传递数据
import android.os.PowerManager; // 管理电源状态
import android.provider.Settings; // 系统设置
import android.view.Window; // 窗口管理
import android.view.WindowManager;// 管理窗口
import net.micode.notes.R; // 导入资源引用
import net.micode.notes.data.Notes; // 导入笔记数据模型
import net.micode.notes.tool.DataUtils; // 数据操作的工具函数
import java.io.IOException; // 处理IO异常
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
private long mNoteId;
// 笔记的唯一标识
private String mSnippet;
// 笔记的内容片段
private static final int SNIPPET_PREW_MAX_LEN = 60;
// 内容片段的最大长度
MediaPlayer mPlayer;
// 媒体播放器,用于播放警报音
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
// 请求无标题窗口
final Window win = getWindow();
// 设置窗口标志,使其在锁屏时可见
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
// 如果屏幕未亮,则设置相关标志
if (!isScreenOn()) {
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
// 保持屏幕常亮
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
// 打开屏幕
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
// 允许锁定屏幕
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
// 窗口布局插入装饰
}
Intent intent = getIntent();
// 获取启动活动的意图
try {
// 从意图中提取笔记ID
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
// 根据笔记ID获取内容片段
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
// 如果内容片段超过最大长度,则进行截断处理
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
: mSnippet;
} catch (IllegalArgumentException e) {
e.printStackTrace(); // 打印异常堆栈信息
return; // 发生异常时返回
}
mPlayer = new MediaPlayer(); // 初始化媒体播放器
// 检查笔记是否在数据库中可见
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog(); // 显示操作对话框
playAlarmSound(); // 播放警报声音
} else {
finish();
// 如果不可见,则结束活动
}
}
// 检查屏幕是否开启的方法
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
// 返回屏幕状态
}
}
private void playAlarmSound() {
// 获取默认的闹钟铃声URI
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
// 获取影响静音模式的流类型
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
// 检查静音模式下是否包括闹钟流
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
mPlayer.setAudioStreamType(silentModeStreams);
// 设置音频流类型为静音模式
} else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
// 设置为闹钟流
}
try {
// 设置数据源为铃声URI
mPlayer.setDataSource(this, url);
mPlayer.prepare();
// 准备播放器
mPlayer.setLooping(true);
// 设置循环播放
mPlayer.start();
// 开始播放
} catch (IllegalArgumentException e) {
e.printStackTrace(); // 打印异常信息
} catch (SecurityException e) {
e.printStackTrace(); // 打印异常信息
} catch (IllegalStateException e) {
e.printStackTrace(); // 打印异常信息
} catch (IOException e) {
e.printStackTrace(); // 打印异常信息
}
}
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.app_name);
// 设置对话框标题
dialog.setMessage(mSnippet);
// 设置对话框消息内容
dialog.setPositiveButton(R.string.notealert_ok, this);
// 设置确认按钮
// 如果屏幕已开启,设置取消按钮
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);
}
dialog.show().setOnDismissListener(this);
// 显示对话框并设置消失监听器
}
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
// 点击取消按钮时,启动编辑笔记活动
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
// 传递笔记ID
startActivity(intent);
// 启动活动
break;
default:
break; // 默认情况不处理
}
}
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
// 停止播放警报声音
finish();
// 结束当前活动
}
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
// 停止播放
mPlayer.release();
// 释放媒体播放器资源
mPlayer = null;
// 清空播放器引用
}
}

@ -0,0 +1,81 @@
/*
* 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.ui;
// 导入所需的Android类
import android.app.AlarmManager; // 用于管理闹钟服务
import android.app.PendingIntent; // 用于延迟执行的意图
import android.content.BroadcastReceiver; // 广播接收器基类
import android.content.ContentUris; // 用于处理内容URI
import android.content.Context; // 上下文类
import android.content.Intent; // 意图类
import android.database.Cursor; // 数据库结果集游标
// 导入笔记相关的数据类
import net.micode.notes.data.Notes; // 笔记数据类
import net.micode.notes.data.Notes.NoteColumns; // 笔记列定义类
public class AlarmInitReceiver extends BroadcastReceiver {
// 定义要查询的列
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
// 笔记ID
NoteColumns.ALERTED_DATE
// 警报日期
};
// 列索引
private static final int COLUMN_ID = 0;
// ID列索引
private static final int COLUMN_ALERTED_DATE = 1;
// 警报日期列索引
@Override
public void onReceive(Context context, Intent intent) {
long currentDate = System.currentTimeMillis(); // 获取当前时间戳
// 查询提醒日期大于当前时间的笔记
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
new String[] { String.valueOf(currentDate) },
null);
if (c != null) {
// 如果结果集不为空
if (c.moveToFirst()) {
do {
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
// 获取警报日期
Intent sender = new Intent(context, AlarmReceiver.class); // 创建意图,用于发送警报
// 设置数据为对应的笔记URI
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
// 创建PendingIntent注册广播接收器
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 获取AlarmManager服务
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
// 设置闹钟
alarmManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
// 遍历结果集
}
c.close();
// 关闭Cursor
}
}
}

@ -0,0 +1,36 @@
/*
* 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.ui;
// 指定当前类所在的包名
import android.content.BroadcastReceiver; // 导入广播接收器基类
import android.content.Context; // 导入上下文类
import android.content.Intent; // 导入意图类
// 定义AlarmReceiver类继承自BroadcastReceiver
public class AlarmReceiver extends BroadcastReceiver {
@Override
// 当接收到广播时调用的方法
public void onReceive(Context context, Intent intent) {
// 设置要启动的活动为AlarmAlertActivity
intent.setClass(context, AlarmAlertActivity.class);
// 添加标志,使新的活动在新的任务中启动
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 启动AlarmAlertActivity
context.startActivity(intent);
}
}

@ -0,0 +1,119 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;//定义包名
import android.content.Context;//导入Context类用于访问应用环境
import android.database.Cursor;// 导入 Cursor 类,用于操作查询结果
import android.provider.ContactsContract.CommonDataKinds.Phone;// 导入电话常量
import android.provider.ContactsContract.Data;// 导入数据常量
import android.telephony.PhoneNumberUtils;// 导入电话工具类
import android.util.Log; // 导入日志工具类
import java.util.HashMap;// 导入 HashMap 类,用于存储联系人缓存
public class Contact { // 定义 Contact 类
private static HashMap<String, String> sContactCache;
// 静态 HashMap 用于缓存联系人姓名
private static final String TAG = "Contact";
// 日志标签,用于标识日志信息
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN "
+ "(SELECT raw_contact_id "
+ " FROM phone_lookup"
+ " WHERE min_match = '+')"; // 查询选项字符串,用于匹配来电显示的电话号码
//根据电话号码获取联系人姓名的方法
public static String getContact(Context context, String phoneNumber) {
//初始化联系人缓存
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
//如果缓存中已存在该电话号码,则直接返回对应的联系人姓名
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}
//替换查询字符串中的“+”,使用电话号码的最小匹配形式
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
//执行内容解析器查询,获取与电话号码匹配的联系人姓名
Cursor cursor = context.getContentResolver().query(
//查询的 UIR
Data.CONTENT_URI,
//查询的列,这里只查询显示姓名
new String [] { Phone.DISPLAY_NAME },
//查询条件
selection,
//查询参数
new String[] { phoneNumber },
//排序方式
null);
//检查游标是否有效,并尝试移动到第一条记录
if (cursor != null && cursor.moveToFirst()) {
try {
//获取联系人姓名
String name = cursor.getString(0);
//将联系人的电话号码和姓名存入缓存
sContactCache.put(phoneNumber, name);
//返回联系人姓名
return name;
}
//捕获索引越界异常
catch (IndexOutOfBoundsException e) {
// 记录错误日志
Log.e(TAG, " Cursor get string error " + e.toString());
// 返回 null
return null;
} finally {
// 确保游标被关闭以释放资源
cursor.close();
}
} else { // 如果没有找到匹配的联系人
// 记录调试日志
Log.d(TAG, "No contact matched with number:" + phoneNumber);
// 返回 null
return null;
}
}
}
/*
sContactCache HashMap
selection PhoneNumberUtils.toCallerIDMinMatch(phoneNumber) +
context.getContentResolver().query selectionPhone.DISPLAY_NAME
便
IndexOutOfBoundsException null
null
ContentResolver
*/

@ -0,0 +1,599 @@
/*
* 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.ui;
// 定义包名
import java.text.DateFormatSymbols; // 导入用于日期格式符号的类
import java.util.Calendar; // 导入日历类
import net.micode.notes.R; // 导入资源类,通常包含布局和字符串资源
import android.content.Context; // 导入上下文类
import android.text.format.DateFormat; // 导入日期格式化类
import android.view.View; // 导入视图类
import android.widget.FrameLayout; // 导入框架布局类
import android.widget.NumberPicker; // 导入数字选择器类
public class DateTimePicker extends FrameLayout {
// 定义DateTimePicker类继承自FrameLayout
private static final boolean DEFAULT_ENABLE_STATE = true; // 默认启用状态
private static final int HOURS_IN_HALF_DAY = 12; // 半天的小时数
private static final int HOURS_IN_ALL_DAY = 24; // 全天的小时数
private static final int DAYS_IN_ALL_WEEK = 7; // 一周的天数
private static final int DATE_SPINNER_MIN_VAL = 0; // 日期选择器的最小值
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
// 日期选择器的最大值
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
// 24小时制小时选择器的最小值
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
// 24小时制小时选择器的最大值
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
// 12小时制小时选择器的最小值
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
// 12小时制小时选择器的最大值
private static final int MINUT_SPINNER_MIN_VAL = 0; // 分钟选择器的最小值
private static final int MINUT_SPINNER_MAX_VAL = 59; // 分钟选择器的最大值
private static final int AMPM_SPINNER_MIN_VAL = 0; // 上午/下午选择器的最小值
private static final int AMPM_SPINNER_MAX_VAL = 1; // 上午/下午选择器的最大值
private final NumberPicker mDateSpinner; // 日期选择器
private final NumberPicker mHourSpinner; // 小时选择器
private final NumberPicker mMinuteSpinner; // 分钟选择器
private final NumberPicker mAmPmSpinner; // 上午/下午选择器
private Calendar mDate; // 日期对象
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
// 存储日期显示值的数组
private boolean mIsAm;
// 标识是否为上午
private boolean mIs24HourView;
// 标识是否为24小时制视图
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
// 控件是否启用
private boolean mInitialising;
// 控件是否正在初始化
private OnDateTimeChangedListener mOnDateTimeChangedListener;
// 日期时间改变监听器
// 日期变化监听器
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); // 更新日期
updateDateControl(); // 更新日期控制
onDateTimeChanged(); // 通知日期时间已改变
}
};
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false; // 标识日期是否改变
Calendar cal = Calendar.getInstance(); // 获取当前日历实例
// 如果不是24小时制
if (!mIs24HourView) {
// 如果从 PM 切换到 AM日期需要加一天
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true; // 日期改变标识为真
}
// 如果从 AM 切换到 PM日期需要减一天
else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true; // 日期改变标识为真
}
// 切换 AM/PM 标识
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm; // 切换上午/下午状态
updateAmPmControl(); // 更新 AM/PM 控件
}
} else {
// 如果在24小时制下处理日期变化
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true; // 日期改变标识为真
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true; // 日期改变标识为真
}
}
// 设置新小时
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour); // 更新日期对象中的小时
onDateTimeChanged(); // 通知日期时间已改变
// 如果日期发生了变化,更新日期控件
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
}
};
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue(); // 获取分钟选择器的最小值
int maxValue = mMinuteSpinner.getMaxValue(); // 获取分钟选择器的最大值
int offset = 0; // 偏移量初始化为0
// 判断是否从最大值切换到最小值
if (oldVal == maxValue && newVal == minValue) {
offset += 1; // 增加偏移量
}
// 判断是否从最小值切换到最大值
else if (oldVal == minValue && newVal == maxValue) {
offset -= 1; // 减少偏移量
}
// 如果有偏移量,更新小时
if (offset != 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset); // 更新日期对象中的小时
mHourSpinner.setValue(getCurrentHour()); // 更新小时选择器的值
updateDateControl(); // 更新日期控件
// 根据当前小时判断 AM/PM 状态
int newHour = getCurrentHourOfDay();
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false; // 设置为下午
updateAmPmControl(); // 更新 AM/PM 控件
} else {
mIsAm = true; // 设置为上午
updateAmPmControl(); // 更新 AM/PM 控件
}
}
mDate.set(Calendar.MINUTE, newVal); // 更新日期对象中的分钟
onDateTimeChanged(); // 通知日期时间已改变
}
};
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm; // 切换 AM/PM 状态
// 根据 AM/PM 状态调整小时
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
// 设置为上午减去12小时
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
// 设置为下午加上12小时
}
updateAmPmControl(); // 更新 AM/PM 控件
onDateTimeChanged(); // 通知日期时间已改变
}
};
// 定义一个接口,用于日期时间变化的监听
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
}
// DateTimePicker 类的构造函数
public DateTimePicker(Context context) {
// 默认调用带有当前时间的构造函数
this(context, System.currentTimeMillis());
}
// 带有日期参数的构造函数
public DateTimePicker(Context context, long date) {
// 调用带有日期和24小时制标志的构造函数
this(context, date, DateFormat.is24HourFormat(context));
}
// 带有日期和24小时制标志的构造函数
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context); // 调用父类构造函数
mDate = Calendar.getInstance(); // 获取当前日历实例
mInitialising = true; // 初始化标志
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; // 判断当前是否为上午
inflate(context, R.layout.datetime_picker, this); // 加载自定义布局
// 日期选择器的初始化
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); // 设置最小值
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); // 设置最大值
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); // 设置日期变化监听器
// 小时选择器的初始化
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); // 设置小时变化监听器
// 分钟选择器的初始化
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); // 设置最小值
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); // 设置最大值
mMinuteSpinner.setOnLongPressUpdateInterval(100); // 长按更新间隔
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); // 设置分钟变化监听器
// AM/PM 选择器的初始化
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); // 获取 AM/PM 字符串
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); // 设置最小值
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); // 设置最大值
mAmPmSpinner.setDisplayedValues(stringsForAmPm); // 显示 AM/PM 值
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); // 设置 AM/PM 变化监听器
}
// update controls to initial state
updateDateControl(); // 更新日期控件
updateHourControl(); // 更新小时控件
updateAmPmControl(); // 更新 AM/PM 控件
set24HourView(is24HourView); // 设置为24小时制或12小时制
// set to current time
setCurrentDate(date);// 根据传入的日期设置当前日期
setEnabled(isEnabled());// 根据是否启用状态设置控件
// set the content descriptions
mInitialising = false;// 初始化完成标志设为false
}
@Override
public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) { // 如果当前状态与目标状态相同,则不做任何操作
return;
}
super.setEnabled(enabled); // 调用父类的设置启用状态方法
mDateSpinner.setEnabled(enabled); // 设置日期选择器的启用状态
mMinuteSpinner.setEnabled(enabled); // 设置分钟选择器的启用状态
mHourSpinner.setEnabled(enabled); // 设置小时选择器的启用状态
mAmPmSpinner.setEnabled(enabled); // 设置 AM/PM 选择器的启用状态
mIsEnabled = enabled; // 更新当前启用状态
}
@Override
public boolean isEnabled() {
return mIsEnabled;// 返回当前启用状态
}
/**
* Get the current date in millis
*
* @return the current date in millis
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();// 返回日期对象的毫秒值
}
/**
* Set the current date
*
* @param date The current date in millis
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance(); // 获取当前日历实例
cal.setTimeInMillis(date); // 根据传入的毫秒值设置时间
// 设置当前日期和时间
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}
/**
* Set the current date
*
* @param year The current year
* @param month The current month
* @param dayOfMonth The current dayOfMonth
* @param hourOfDay The current hourOfDay
* @param minute The current minute
*/
public void setCurrentDate(int year, int month,int dayOfMonth, int hourOfDay, int minute) {
// 设置当前年份
setCurrentYear(year);
// 设置当前月份
setCurrentMonth(month);
// 设置当前日期
setCurrentDay(dayOfMonth);
// 设置当前小时
setCurrentHour(hourOfDay);
// 设置当前分钟
setCurrentMinute(minute);
}
/**
* Get current year
*
* @return The current year
*/
public int getCurrentYear() { // 返回当前日期对象中的年份
return mDate.get(Calendar.YEAR);
}
/**
* Set current year
*
* @param year The current year
*/
public void setCurrentYear(int year) {
// 如果不是初始化状态并且输入的年份与当前年份相同,则不做任何操作
if (!mInitialising && year == getCurrentYear()) {
return;
}
// 设置当前年份
mDate.set(Calendar.YEAR, year);
// 更新日期控件
updateDateControl();
// 通知日期和时间已改变
onDateTimeChanged();
}
/**
* Get current month in the year
*
* @return The current month in the year
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* Set current month in the year
*
* @param month The month in the year
*/
public void setCurrentMonth(int month) {
// 如果不是初始化状态并且输入的月份与当前月份相同,则不做任何操作
if (!mInitialising && month == getCurrentMonth()) {
return;
}
// 设置当前月份
mDate.set(Calendar.MONTH, month);
// 更新日期控件
updateDateControl();
// 通知日期和时间已改变
onDateTimeChanged();
}
/**
* Get current day of the month
*
* @return The day of the month
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Set current day of the month
*
* @param dayOfMonth The day of the month
*/
public void setCurrentDay(int dayOfMonth) {
// 如果不是初始化状态,并且输入的日期与当前日期相同,则不做任何操作
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
// 设置当前日期
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
// 更新日期控件
updateDateControl();
// 通知日期和时间已改变
onDateTimeChanged();
}
/**
* Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
private int getCurrentHour() {
if (mIs24HourView) {
// 如果是24小时制直接返回当前小时
return getCurrentHourOfDay();
} else {
// 如果是12小时制
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) {
// 如果小时大于12则转换为12小时制
return hour - HOURS_IN_HALF_DAY;
} else {
// 如果小时为0返回12否则返回当前小时
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
*
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
// 如果不是初始化状态,并且输入的小时与当前小时相同,则不做任何操作
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
// 设置当前小时
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
// 如果不是24小时制处理AM/PM的显示
if (!mIs24HourView) {
if (hourOfDay >= HOURS_IN_HALF_DAY) {
// 设置为PM
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
// 转换为12小时制
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
// 设置为AM
mIsAm = true;
if (hourOfDay == 0) {
// 0点转换为12点
hourOfDay = HOURS_IN_HALF_DAY;
}
}
// 更新AM/PM控件
updateAmPmControl();
}
// 更新小时选择器的值
mHourSpinner.setValue(hourOfDay);
// 通知时间已改变
onDateTimeChanged();
}
/**
* Get currentMinute
*
* @return The Current Minute
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* Set current minute
*/
public void setCurrentMinute(int minute) {
// 如果不是初始化状态,并且输入的分钟与当前分钟相同,则不做任何操作
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
// 更新分钟选择器的值
mMinuteSpinner.setValue(minute);
// 设置当前分钟
mDate.set(Calendar.MINUTE, minute);
// 通知时间已改变
onDateTimeChanged();
}
/**
* @return true if this is in 24 hour view else false.
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* Set whether in 24 hour or AM/PM mode.
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
*/
public void set24HourView(boolean is24HourView) {
// 如果当前状态与传入参数相同,则不做任何操作
if (mIs24HourView == is24HourView) {
return;
}
// 更新24小时制标志
mIs24HourView = is24HourView;
// 根据24小时制的选择设置AM/PM选择器的可见性
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
// 获取当前小时,并更新控件
int hour = getCurrentHourOfDay();
updateHourControl(); // 更新小时选择器的设置
setCurrentHour(hour); // 设置当前小时
updateAmPmControl(); // 更新AM/PM控件的显示
}
/**
*
*/
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
// 将日期设置为当前日期前一半周的开始
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null);// 清空旧的显示值
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1); // 增加一天、
// 格式化日期,存入显示数组
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
// 设置新的显示值
mDateSpinner.setDisplayedValues(mDateDisplayValues);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); // 设置初始显示值为中间日期
mDateSpinner.invalidate();// 刷新日期选择器
}
/**
* AM/PM
*/
private void updateAmPmControl() {
// 如果是24小时制则隐藏AM/PM选择器
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
// 否则根据当前时间选择AM或PM
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);// 设置AM/PM选择器的值
mAmPmSpinner.setVisibility(View.VISIBLE);// 显示选择器
}
}
/**
*
*/
private void updateHourControl() {
// 根据是否为24小时制更新小时选择器的最小和最大值
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
// 设置24小时制的最小值
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
// 设置24小时制的最大值
} else {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
// 设置12小时制的最小值
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
// 设置12小时制的最大值
}
}
/**
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/
// 设置日期时间变化监听器
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;// 将回调对象赋值给成员变量
}
// 当日期时间发生变化时调用
private void onDateTimeChanged() {
// 检查监听器是否不为空
if (mOnDateTimeChangedListener != null) {
// 调用监听器的方法,并传递当前的年、月、日、小时和分钟
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
// 当前年份、 当前月份、当前日期、当前小时、当前分钟
}

@ -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.ui;
// 导入所需的类
import java.util.Calendar; // 导入日历类,用于日期和时间的管理
import net.micode.notes.R; // 导入资源类,用于访问字符串资源等
import net.micode.notes.ui.DateTimePicker; // 导入自定义的日期时间选择器类
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; // 导入日期时间变化监听器接口
import android.app.AlertDialog; // 导入对话框类,用于创建对话框
import android.content.Context; // 导入上下文类,用于获取应用环境信息
import android.content.DialogInterface; // 导入对话框接口类,用于处理对话框事件
import android.content.DialogInterface.OnClickListener; // 导入对话框点击事件监听器接口
import android.text.format.DateFormat; // 导入日期格式化类,用于格式化日期和时间
import android.text.format.DateUtils; // 导入日期工具类,提供日期格式化和显示功能
// 自定义日期时间选择对话框类
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance();
// 用于存储当前选中的日期和时间
private boolean mIs24HourView;
// 标识是否使用24小时制
private OnDateTimeSetListener mOnDateTimeSetListener;
// 日期时间设置的监听器
private DateTimePicker mDateTimePicker;
// 日期时间选择器实例
// 日期时间设置监听器接口
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
// 设置日期时间的回调方法
}
// 构造函数,初始化对话框并设置初始日期
public DateTimePickerDialog(Context context, long date) {
super(context); // 调用父类构造函数
mDateTimePicker = new DateTimePicker(context);
// 创建日期时间选择器
setView(mDateTimePicker);
// 将选择器设置为对话框的视图
// 设置日期时间变化的监听器
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
// 更新日历对象中的年、月、日、小时和分钟
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
updateTitle(mDate.getTimeInMillis());
// 更新对话框标题
}
});
mDate.setTimeInMillis(date); // 设置初始日期时间
mDate.set(Calendar.SECOND, 0); // 将秒数设为0
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
// 设置选择器的当前日期时间
setButton(context.getString(R.string.datetime_dialog_ok), this);
// 设置“确定”按钮
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
// 设置“取消”按钮
set24HourView(DateFormat.is24HourFormat(this.getContext()));
// 根据系统设置决定是否使用24小时制
updateTitle(mDate.getTimeInMillis());
// 初始化对话框标题
}
// 设置是否使用24小时制
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView; // 更新24小时制标识
}
// 设置日期时间设置的监听器
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack; // 保存监听器
}
// 更新对话框的标题,显示当前选中的日期和时间
private void updateTitle(long date) {
// 设置显示格式,包括年份、日期和时间
int flag =
DateUtils.FORMAT_SHOW_YEAR | // 显示年份
DateUtils.FORMAT_SHOW_DATE | // 显示日期
DateUtils.FORMAT_SHOW_TIME; // 显示时间
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_12HOUR;
// 根据设置选择小时制
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
// 格式化日期时间并设置为对话框标题
}
// 点击事件处理
public void onClick(DialogInterface arg0, int arg1) {
// 如果设置了监听器,则调用其方法并传递当前时间戳
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

@ -0,0 +1,90 @@
/*
* 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.ui;
import android.content.Context;
// 引入 Context 类,提供访问应用环境的功能,如启动活动、获取系统服务、访问资源等。
import android.view.Menu;
// 引入 Menu 类,定义菜单的结构,通常用于上下文菜单、选项菜单等。
import android.view.MenuItem;
// 引入 MenuItem 类,代表菜单中的一项,可以设置标题、图标等属性并绑定点击事件。
import android.view.View;
// 引入 View 类,所有 UI 控件的基类,处理控件的显示和用户交互(如点击、拖动等)。
import android.view.View.OnClickListener;
// 引入 OnClickListener 接口,用于处理视图(如按钮)的点击事件。
import android.widget.Button;
// 引入 Button 类,用于在界面中创建按钮控件,用户点击按钮时可以触发某个操作。
import android.widget.PopupMenu;
// 引入 PopupMenu 类,表示一个弹出式菜单,通常用于显示上下文菜单或按钮点击后的菜单。
import android.widget.PopupMenu.OnMenuItemClickListener;
// 引入 OnMenuItemClickListener 接口,用于处理 PopupMenu 中菜单项的点击事件。
import net.micode.notes.R;
// 引入 R 类自动生成的资源类用于访问应用的资源文件如布局、字符串、ID 等)。
// DropdownMenu 类用于显示一个下拉菜单
public class DropdownMenu {
// 成员变量
private Button mButton; // 按钮,用于触发下拉菜单显示
private PopupMenu mPopupMenu; // 弹出菜单
private Menu mMenu; // 菜单对象,用于管理菜单项
// 构造函数,初始化按钮和菜单
public DropdownMenu(Context context, Button button, int menuId) {
// 将传入的按钮实例赋值给 mButton
mButton = button;
// 设置按钮的背景资源(这里使用的是一个下拉图标)
mButton.setBackgroundResource(R.drawable.dropdown_icon);
// 初始化 PopupMenu 对象,关联按钮
mPopupMenu = new PopupMenu(context, mButton);
// 获取菜单对象
mMenu = mPopupMenu.getMenu();
// 使用菜单 ID通过菜单资源 XML 文件)填充菜单项
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
// 设置按钮的点击事件监听器,当按钮被点击时显示弹出菜单
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
// 显示弹出菜单
mPopupMenu.show();
}
});
}
// 设置菜单项点击事件监听器
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
// 如果 mPopupMenu 已经初始化,则设置菜单项点击事件监听器
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
// 根据 ID 查找菜单项
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}
// 设置按钮的文本(通常用于显示当前菜单的标题或提示)
public void setTitle(CharSequence title) {
mButton.setText(title);
}
}

@ -0,0 +1,109 @@
/*
* 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.ui;
import android.content.Context;
// 引入 Context 类,用于访问应用的环境、资源、启动活动等。
import android.database.Cursor;
// 引入 Cursor 类,表示查询结果集,通常用于访问数据库中的记录。
import android.view.View;
// 引入 View 类,所有 UI 控件的基类。
import android.view.ViewGroup;
// 引入 ViewGroup 类,表示可以包含其他视图的容器类。
import android.widget.CursorAdapter;
// 引入 CursorAdapter 类,用于绑定数据库查询结果与 UI 元素。
import android.widget.LinearLayout;
// 引入 LinearLayout 类,线性布局容器,用于按顺序排列视图。
import android.widget.TextView;
// 引入 TextView 类,用于显示文本。
import net.micode.notes.R;
// 引入 R 类自动生成的资源类访问应用的资源如字符串、布局、ID 等)。
import net.micode.notes.data.Notes;
// 引入 Notes 类,表示笔记相关的数据模型。
import net.micode.notes.data.Notes.NoteColumns;
// 引入 NoteColumns 类,表示与笔记相关的数据库字段。
// FoldersListAdapter 类是一个自定义的 CursorAdapter用于展示文件夹列表。
public class FoldersListAdapter extends CursorAdapter {
// 定义查询投影(即数据库查询时需要返回的列)。
public static final String[] PROJECTION = {
NoteColumns.ID, // 文件夹的唯一 ID
NoteColumns.SNIPPET // 文件夹的简短名称(或者其他相关信息)
};
// 定义列的索引,方便后续获取对应列的数据。
public static final int ID_COLUMN = 0; // ID 列索引
public static final int NAME_COLUMN = 1; // 文件夹名称列索引
// 构造函数,接收 Context 和 Cursor传递给父类的构造函数。
public FoldersListAdapter(Context context, Cursor c) {
super(context, c); // 调用父类 CursorAdapter 的构造函数
}
// newView 方法负责创建新的视图(即每一行的数据项)。
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
// 返回一个新的 FolderListItem 视图(表示文件夹项)。
}
// bindView 方法负责绑定数据到视图上,通常是处理显示和数据的绑定。
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
// 确保视图是 FolderListItem 类型
// 获取文件夹的名称。如果是根文件夹,则显示特定的名称(菜单中的 "父文件夹"),否则显示文件夹名称。
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER)
? context.getString(R.string.menu_move_parent_folder)
: cursor.getString(NAME_COLUMN);
// 将获取的名称绑定到 FolderListItem 中
((FolderListItem) view).bind(folderName);
}
}
// getFolderName 方法用于根据位置获取文件夹名称。
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
// 获取指定位置的 Cursor 数据
// 根据文件夹的 ID 返回对应的名称
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER)
? context.getString(R.string.menu_move_parent_folder)
: cursor.getString(NAME_COLUMN);
}
// FolderListItem 类表示文件夹列表项的视图,它是一个 LinearLayout。
private class FolderListItem extends LinearLayout {
private TextView mName;
// 用于显示文件夹名称的 TextView 控件。
// FolderListItem 的构造函数,初始化视图和控件。
public FolderListItem(Context context) {
super(context);
inflate(context, R.layout.folder_list_item, this);
// 加载 folder_list_item 布局文件
mName = (TextView) findViewById(R.id.tv_folder_name);
// 获取文件夹名称显示控件
}
// bind 方法用于将文件夹名称绑定到 TextView 上。
public void bind(String name) {
mName.setText(name); // 设置文件夹名称
}
}
}

@ -0,0 +1,131 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//定义该类所在的包。包是 Java 中组织类的方式,有助于代码的组织和管理。
package net.micode.notes.gtask.data;
//导入 Android 的 Cursor 类Cursor 是用于访问和操作数据库查询结果的工具。
import android.database.Cursor;
//导入 Android 的 Log 类,用于在代码中输出日志信息。
import android.util.Log;
//导入一个自定义的工具类 GTaskStringUtils它包含与 Google Tasks 相关的常量和方法。
import net.micode.notes.tool.GTaskStringUtils;
//导入 JSON 处理类。JSONObject 用于创建和解析 JSON 数据JSONException 用于捕获解析 JSON 时的错误。
import org.json.JSONException;
import org.json.JSONObject;
//定义一个公共类 MetaData该类继承自 Task 类,表示与 Google 任务相关的元数据(例如任务的 gid
public class MetaData extends Task {
//定义一个 TAG 字符串常量,存储当前类的简短类名 "MetaData",用于在日志中输出调试信息。
private final static String TAG = MetaData.class.getSimpleName();
//声明一个私有成员变量 mRelatedGid用于保存与任务相关的 Google 任务 IDgid。初始值为 null。
private String mRelatedGid = null;
// 定义一个公共方法 setMeta用于设置任务的元数据。接收两个参数
// gid一个字符串表示任务的 Google ID。
// metaInfo一个 JSONObject包含其他元数据如任务的附加信息
public void setMeta(String gid, JSONObject metaInfo) {
// 尝试将传入的 gid 插入到 metaInfo 中。GTaskStringUtils.META_HEAD_GTASK_ID 是用于存储任务 ID 的常量。
// 如果插入失败,捕获 JSONException 异常并打印错误日志。
try {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");
}
//将 metaInfo 转换为字符串并调用 setNotes() 方法保存。这将任务的元数据作为字符串保存在任务的 notes 字段中。
setNotes(metaInfo.toString());
//调用 setName() 设置任务的名称为 GTaskStringUtils.META_NOTE_NAME。这个名称可能是一个预定义的常量表示任务的默认名称。
setName(GTaskStringUtils.META_NOTE_NAME);
}
//定义一个公共方法 getRelatedGid用于获取与任务相关的 Google 任务 ID。
// return mRelatedGid;:返回之前保存的 mRelatedGid。
public String getRelatedGid() {
return mRelatedGid;
}
//@Override表示该方法是重写父类 Task 中的方法。
//public boolean isWorthSaving():定义一个方法 isWorthSaving用于判断当前任务是否值得保存。通常它会根据某些条件来判断任务是否需要被同步或保存到数据库中。
//return getNotes() != null;:判断任务的 notes 字段是否为 null。如果 notes 字段不为空,说明任务有有效内容,返回 true否则返回 false。
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
@Override
//定义方法 setContentByRemoteJSON用于从远程获取 JSON 数据并设置任务内容。
public void setContentByRemoteJSON(JSONObject js) {
//调用父类的同名方法来处理远程 JSON 数据的基本设置。
super.setContentByRemoteJSON(js);
//检查任务的 notes 字段是否不为空。
if (getNotes() != null) {
//如果 notes 不为空,尝试将其转换为 JSONObject。
//然后,从中提取出 gid 并保存到 mRelatedGid 中。
// 如果解析过程中出现错误(例如格式问题),会捕获 JSONException 并输出警告日志,同时将 mRelatedGid 设为 null。
try {
JSONObject metaInfo = new JSONObject(getNotes().trim());
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
mRelatedGid = null;
}
}
}
//@Override
//public void setContentByLocalJSON(JSONObject js)
//throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
@Override
//该方法定义了从本地 JSON 数据设置任务内容的行为。
public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
//该方法被设计为不可调用,如果被调用,抛出 IllegalAccessError 异常。
// 该类不允许使用本地 JSON 数据设置任务内容,因此抛出异常来提醒开发者
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
//表示重写父类的方法。
@Override
//该方法定义了获取本地 JSON 数据的行为。
public JSONObject getLocalJSONFromContent() {
//与 setContentByLocalJSON 类似,该方法不应该被调用,如果被调用,抛出 IllegalAccessError 异常.
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
}
//表示重写父类的方法。
@Override
//定义获取同步操作的方法,通常会返回一个与同步操作相关的标志值。
public int getSyncAction(Cursor c) {
//该方法不应被调用,抛出 IllegalAccessError 异常。
throw new IllegalAccessError("MetaData:getSyncAction should not be called");
}
}

@ -0,0 +1,88 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
NetworkFailureException Java Exception
net.micode.notes.gtask.exception 使 Apache 2.0
*/
//包声明导入
//这行代码声明了类 NetworkFailureException 所在的包:
// net.micode.notes.gtask.exception。包用于组织类和接口避免命名冲突并可以根据需要进行访问控制。
package net.micode.notes.gtask.exception;
/*
NetworkFailureException Exception checked exception
Exception try-catch throws
*/
public class NetworkFailureException extends Exception {
/*
serialVersionUID
I/O serialVersionUID
InvalidClassException
*/
private static final long serialVersionUID = 2107610287180234136L;
/*
Exception
*/
public NetworkFailureException() {
super();
}
/*
paramString Exception
*/
public NetworkFailureException(String paramString) {
super(paramString);
}
/*
Throwable paramThrowable
Throwable Exception Error
*/
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}
}
/*
Checked Exception
NetworkFailureException Exception checked exception RuntimeException
NetworkFailureException throws
使
NetworkFailureException
访 NetworkFailureException
/
NetworkFailureException Exception
Exception
*/

@ -0,0 +1,137 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
// 导入必要的包
import android.database.Cursor;
import org.json.JSONObject;
public abstract class Node {
//// 定义同步操作常量,表示不同的同步动作
//这段代码定义了不同的常量,表示不同的同步操作类型。这些常量用于表示与远程和本地数据同步的动作。每个动作都用一个整数值标识,
// 例如SYNC_ACTION_ADD_REMOTE 表示添加远程任务,
// SYNC_ACTION_UPDATE_LOCAL 表示更新本地任务,
// SYNC_ACTION_ERROR 表示同步错误等。
public static final int SYNC_ACTION_NONE = 0;
public static final int SYNC_ACTION_ADD_REMOTE = 1;
public static final int SYNC_ACTION_ADD_LOCAL = 2;
public static final int SYNC_ACTION_DEL_REMOTE = 3;
public static final int SYNC_ACTION_DEL_LOCAL = 4;
public static final int SYNC_ACTION_UPDATE_REMOTE = 5;
public static final int SYNC_ACTION_UPDATE_LOCAL = 6;
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;
public static final int SYNC_ACTION_ERROR = 8;
//mGid一个字符串存储任务的唯一标识符Google ID用于区分不同的任务。
//mName任务的名称存储任务的标题或名称。
//mLastModified任务的最后修改时间使用 long 类型(通常是 Unix 时间戳表示自1970年1月1日以来的毫秒数
//mDeleted布尔值表示该任务是否被删除。
// 任务的唯一标识符
private String mGid;
// 任务的名称
private String mName;
// 最后修改时间
private long mLastModified;
// 任务是否已删除
private boolean mDeleted;
public Node() {
// 默认构造函数,初始化 mGid 为 null
mGid = null;
// 默认构造函数,初始化 mName 为空字符串
mName = "";
// 默认构造函数,初始化 mLastModified 为 0
mLastModified = 0;
// 默认构造函数,初始化 mDeleted 为 false表示任务未删除
mDeleted = false;
}
//getCreateAction(int actionId):根据给定的 actionId返回一个 JSONObject表示创建操作的同步动作。具体内容依赖子类实现。
//getUpdateAction(int actionId):根据给定的 actionId返回一个 JSONObject表示更新操作的同步动作。具体内容依赖子类实现。
//setContentByRemoteJSON(JSONObject js):从远程的 JSON 数据中设置内容。js 是传入的 JSONObject该方法的具体实现依赖子类。
//setContentByLocalJSON(JSONObject js):从本地的 JSON 数据中设置内容。js 是传入的 JSONObject该方法的具体实现依赖子类。
//getLocalJSONFromContent():将本地内容转换为 JSONObject返回一个 JSON 对象。
//getSyncAction(Cursor c):从 Cursor 对象中获取同步操作的动作,返回一个整数,表示当前同步操作的类型。
public abstract JSONObject getCreateAction(int actionId);
public abstract JSONObject getUpdateAction(int actionId);
public abstract void setContentByRemoteJSON(JSONObject js);
public abstract void setContentByLocalJSON(JSONObject js);
public abstract JSONObject getLocalJSONFromContent();
public abstract int getSyncAction(Cursor c);
//这些是 Node 类的 setter 方法,用于设置成员变量的值:
//setGid(String gid):设置任务的 GidGoogle ID
//setName(String name):设置任务的名称。
//setLastModified(long lastModified):设置任务的最后修改时间。
//setDeleted(boolean deleted):设置任务是否被删除。
public void setGid(String gid) {
this.mGid = gid;
}
public void setName(String name) {
this.mName = name;
}
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
}
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
}
//这些是 Node 类的 getter 方法,用于获取成员变量的值:
//getGid():返回任务的 GidGoogle ID
//getName():返回任务的名称。
//getLastModified():返回任务的最后修改时间。
//getDeleted():返回任务是否已删除的布尔值。
public String getGid() {
return this.mGid;
}
public String getName() {
return this.mName;
}
public long getLastModified() {
return this.mLastModified;
}
public boolean getDeleted() {
return this.mDeleted;
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,276 @@
/*
* 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.ui;
// 引入相关的 Android 和 Java 库
import android.content.Context;
import android.graphics.Rect; // 用于绘制矩形
import android.text.Layout; // 用于文本布局
import android.text.Selection; // 用于操作文本选择
import android.text.Spanned; // 用于处理带有格式的文本
import android.text.TextUtils; // 工具类,提供与文本相关的辅助方法
import android.text.style.URLSpan; // 用于处理文本中的 URL 链接
import android.util.AttributeSet; // 用于解析 XML 布局文件中的自定义属性
import android.util.Log; // 用于日志打印
import android.view.ContextMenu; // 上下文菜单类
import android.view.KeyEvent; // 处理键盘事件
import android.view.MenuItem; // 菜单项
import android.view.MenuItem.OnMenuItemClickListener; // 菜单项点击监听
import android.view.MotionEvent; // 处理触摸事件
import android.widget.EditText; // Android 中的编辑文本控件
// 引入项目中的资源文件
import net.micode.notes.R; // 自定义的资源文件,可能是图片、字符串或布局
// 引入 HashMap 和 Map用于存储一些键值对数据
import java.util.HashMap;
import java.util.Map;
public class NoteEditText extends EditText {
// 定义日志标签,用于调试时输出日志
private static final String TAG = "NoteEditText";
// 变量:当前光标所在的索引
private int mIndex;
// 变量:记录删除操作之前光标的起始位置
private int mSelectionStartBeforeDelete;
// 定义常量,表示不同的 URL scheme
private static final String SCHEME_TEL = "tel:"; // 电话链接
private static final String SCHEME_HTTP = "http:"; // HTTP链接
private static final String SCHEME_EMAIL = "mailto:"; // 邮件链接
// 创建一个映射,存储不同链接的处理方式
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
// 静态初始化块,将不同的 URL scheme 与对应的字符串资源 ID 关联
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); // 电话链接的处理方式
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); // HTTP链接的处理方式
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); // 邮件链接的处理方式
}
/**
* Call by the {@link NoteEditActivity} to delete or add edit text
*/
public interface OnTextViewChangeListener {
/**
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
*/
void onEditTextDelete(int index, String text);
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*/
void onEditTextEnter(int index, String text);
/**
* Hide or show item option when text change
*/
void onTextChange(int index, boolean hasText);
}
private OnTextViewChangeListener mOnTextViewChangeListener;
public class NoteEditText extends EditText {
private int mIndex; // 用于存储索引
private int mSelectionStartBeforeDelete; // 记录删除前的光标位置
private OnTextViewChangeListener mOnTextViewChangeListener; // 用于监听文本变动的接口
private static final String TAG = "NoteEditText"; // 日志标签
// 构造函数1仅接受上下文作为参数
public NoteEditText(Context context) {
super(context, null);
mIndex = 0; // 默认索引为0
}
// 构造函数2接受上下文和属性集作为参数
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
// 构造函数3接受上下文、属性集和样式作为参数
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// 可以根据需要添加初始化代码
}
// 设置索引值的方法
public void setIndex(int index) {
mIndex = index;
}
// 设置监听器的方法
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
// 处理触摸事件,主要是设置光标位置
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 获取触摸位置
int x = (int) event.getX();
int y = (int) event.getY();
// 计算相对位置,考虑了内边距和滚动偏移
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
// 获取文本布局对象
Layout layout = getLayout();
// 获取触摸点所在的行
int line = layout.getLineForVertical(y);
// 获取该行中触摸点对应的字符偏移位置
int off = layout.getOffsetForHorizontal(line, x);
// 设置光标到指定位置
Selection.setSelection(getText(), off);
break;
}
return super.onTouchEvent(event); // 默认处理
}
// 处理按键事件
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
// 如果设置了监听器,按下回车键时返回
if (mOnTextViewChangeListener != null) {
return false; // 返回false表示继续处理事件
}
break;
case KeyEvent.KEYCODE_DEL:
// 记录删除前的光标位置
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
return super.onKeyDown(keyCode, event); // 默认处理
}
// 处理按键抬起事件
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
// 删除键处理
if (mOnTextViewChangeListener != null) {
// 如果删除前光标位置为0并且索引不为0调用删除监听器
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true; // 返回true表示事件已处理
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER:
// 回车键处理
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();
// 获取光标后的文本
String text = getText().subSequence(selectionStart, length()).toString();
// 截取光标前的文本并设置回TextView
setText(getText().subSequence(0, selectionStart));
// 调用监听器传递回车后剩余的文本
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event); // 默认处理
}
}
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
// 检查是否有外部设置的文本改变监听器
if (mOnTextViewChangeListener != null) {
// 如果文本框失去焦点并且文本为空
if (!focused && TextUtils.isEmpty(getText())) {
// 调用文本改变监听器,传入索引和一个布尔值 'false' 表示文本为空
mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
// 否则,调用监听器,传入 'true' 表示文本不为空
mOnTextViewChangeListener.onTextChange(mIndex, true);
}
}
// 调用父类的 onFocusChanged 方法,以确保焦点改变时的默认行为(例如更新视觉效果)得到执行
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
@Override
protected void onCreateContextMenu(ContextMenu menu) {
// 检查当前文本是否是 Spanned 类型Spanned 是一个支持富文本的接口(例如超链接)
if (getText() instanceof Spanned) {
// 获取选中的文本范围
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
// 确定选中的最小和最大位置(防止选区反向)
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
// 获取选中的文本中所有的 URLSpan超链接
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
// 如果选中的区域包含一个 URLSpan即只有一个超链接
if (urls.length == 1) {
// 默认的资源 ID
int defaultResId = 0;
// 遍历所有已知的 URL schema 类型(如 http://, https:// 等),匹配 URL
for(String schema: sSchemaActionResMap.keySet()) {
// 如果 URL 中包含某个 schema 类型
if(urls[0].getURL().indexOf(schema) >= 0) {
// 根据匹配的 schema 获取对应的资源 ID
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
// 如果没有找到匹配的 schema使用一个默认的字符串资源 ID
if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
}
// 向上下文菜单中添加一个菜单项,默认文本为链接的类型描述
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// 在菜单项点击时触发超链接的点击事件
urls[0].onClick(NoteEditText.this);
return true;
}
});
}
}
// 调用父类的 onCreateContextMenu 方法,以确保默认的上下文菜单行为
super.onCreateContextMenu(menu);
}
}

@ -0,0 +1,306 @@
/*
* 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.ui;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
// 这个类表示一个笔记项目的数据模型,用于存储与笔记项相关的信息
public class NoteItemData {
// 定义了一个查询投影Projection包括从数据库中获取笔记的相关字段
static final String [] PROJECTION = new String [] {
NoteColumns.ID, // 笔记的唯一标识符
NoteColumns.ALERTED_DATE, // 提醒日期
NoteColumns.BG_COLOR_ID, // 背景颜色ID
NoteColumns.CREATED_DATE, // 创建日期
NoteColumns.HAS_ATTACHMENT, // 是否包含附件
NoteColumns.MODIFIED_DATE, // 修改日期
NoteColumns.NOTES_COUNT, // 笔记数量
NoteColumns.PARENT_ID, // 父项ID
NoteColumns.SNIPPET, // 笔记的摘要
NoteColumns.TYPE, // 笔记的类型
NoteColumns.WIDGET_ID, // 小部件ID
NoteColumns.WIDGET_TYPE, // 小部件类型
};
// 定义常量,表示数据库查询结果的每一列索引位置
private static final int ID_COLUMN = 0; // 笔记ID
private static final int ALERTED_DATE_COLUMN = 1; // 提醒日期列索引
private static final int BG_COLOR_ID_COLUMN = 2; // 背景颜色列索引
private static final int CREATED_DATE_COLUMN = 3; // 创建日期列索引
private static final int HAS_ATTACHMENT_COLUMN = 4; // 附件标志列索引
private static final int MODIFIED_DATE_COLUMN = 5; // 修改日期列索引
private static final int NOTES_COUNT_COLUMN = 6; // 笔记数量列索引
private static final int PARENT_ID_COLUMN = 7; // 父项ID列索引
private static final int SNIPPET_COLUMN = 8; // 摘要列索引
private static final int TYPE_COLUMN = 9; // 类型列索引
private static final int WIDGET_ID_COLUMN = 10; // 小部件ID列索引
private static final int WIDGET_TYPE_COLUMN = 11; // 小部件类型列索引
// 定义了类中各个属性,用来存储从数据库查询出来的笔记项数据
private long mId; // 笔记的ID
private long mAlertDate; // 提醒时间
private int mBgColorId; // 背景颜色ID
private long mCreatedDate; // 创建时间
private boolean mHasAttachment; // 是否有附件
private long mModifiedDate; // 修改时间
private int mNotesCount; // 笔记数量
private long mParentId; // 父笔记ID
private String mSnippet; // 笔记摘要
private int mType; // 笔记类型
private int mWidgetId; // 小部件ID
private int mWidgetType; // 小部件类型
private String mName; // 笔记项的名称
private String mPhoneNumber; // 笔记项关联的电话号码
// 以下是用于标记笔记在显示列表中的状态的布尔值
private boolean mIsLastItem; // 是否是最后一个笔记项
private boolean mIsFirstItem; // 是否是第一个笔记项
private boolean mIsOnlyOneItem; // 是否是唯一的一个笔记项
private boolean mIsOneNoteFollowingFolder; // 是否是跟随文件夹的第一个笔记
private boolean mIsMultiNotesFollowingFolder; // 是否是跟随文件夹的多个笔记
// 这里可以写上构造函数、getter和setter方法等用于初始化和访问这些字段
// 构造函数,初始化 NoteItemData 对象,使用 Context 和 Cursor 作为输入参数
public NoteItemData(Context context, Cursor cursor) {
// 从 Cursor 中获取 ID存储到 mId
mId = cursor.getLong(ID_COLUMN);
// 获取警告日期ALERTED_DATE_COLUMN并存储为 mAlertDate
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
// 获取背景颜色IDBG_COLOR_ID_COLUMN并存储为 mBgColorId
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
// 获取创建日期CREATED_DATE_COLUMN并存储为 mCreatedDate
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
// 判断是否有附件HAS_ATTACHMENT_COLUMN存储为 mHasAttachment
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
// 获取修改日期MODIFIED_DATE_COLUMN并存储为 mModifiedDate
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
// 获取笔记数量NOTES_COUNT_COLUMN并存储为 mNotesCount
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
// 获取父项 IDPARENT_ID_COLUMN并存储为 mParentId
mParentId = cursor.getLong(PARENT_ID_COLUMN);
// 获取笔记的内容摘要SNIPPET_COLUMN并清除标记如果存在
mSnippet = cursor.getString(SNIPPET_COLUMN);
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(NoteEditActivity.TAG_UNCHECKED, "");
// 获取笔记类型TYPE_COLUMN并存储为 mType
mType = cursor.getInt(TYPE_COLUMN);
// 获取小部件 IDWIDGET_ID_COLUMN并存储为 mWidgetId
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
// 获取小部件类型WIDGET_TYPE_COLUMN并存储为 mWidgetType
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
// 默认初始化手机号为空字符串
mPhoneNumber = "";
// 如果该笔记的父级 ID 是电话记录文件夹的 ID获取电话记录的号码并尝试获取联系人名称
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
// 获取与该笔记 ID 对应的电话记录号码
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
// 如果手机号不为空,尝试获取联系人名称
if (!TextUtils.isEmpty(mPhoneNumber)) {
mName = Contact.getContact(context, mPhoneNumber);
// 如果没有找到联系人,使用手机号作为名称
if (mName == null) {
mName = mPhoneNumber;
}
}
}
// 如果联系人名称为空,初始化为空字符串
if (mName == null) {
mName = "";
}
// 调用 checkPostion 方法来判断该笔记在 Cursor 中的位置
checkPostion(cursor);
}
// 检查该笔记在 Cursor 中的位置
private void checkPostion(Cursor cursor) {
// 判断是否是 Cursor 中的最后一项
mIsLastItem = cursor.isLast() ? true : false;
// 判断是否是 Cursor 中的第一项
mIsFirstItem = cursor.isFirst() ? true : false;
// 判断 Cursor 中是否只有一项
mIsOnlyOneItem = (cursor.getCount() == 1);
// 默认设置为 false表示不是多个笔记跟随文件夹
mIsMultiNotesFollowingFolder = false;
// 默认设置为 false表示不是一个笔记跟随文件夹
mIsOneNoteFollowingFolder = false;
// 如果当前笔记类型是普通笔记TYPE_NOTE并且不是第一项
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
int position = cursor.getPosition(); // 获取当前笔记在 Cursor 中的位置
// 尝试将 Cursor 移动到上一项
if (cursor.moveToPrevious()) {
// 如果上一项是文件夹或系统类型的笔记,检查后续笔记的数量
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
// 如果后续还有笔记,设置为多个笔记跟随文件夹
if (cursor.getCount() > (position + 1)) {
mIsMultiNotesFollowingFolder = true;
} else {
// 否则,设置为一个笔记跟随文件夹
mIsOneNoteFollowingFolder = true;
}
}
// 如果无法移动到上一项,抛出异常
if (!cursor.moveToNext()) {
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
}
}
// 判断是否是单个笔记文件夹
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder; // 返回是否是“单个笔记文件夹”状态的布尔值
}
// 判断是否是多个笔记文件夹
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder; // 返回是否是“多个笔记文件夹”状态的布尔值
}
// 判断当前项是否是最后一个项
public boolean isLast() {
return mIsLastItem; // 返回当前项是否是最后一个项的布尔值
}
// 获取笔记的名称
public String getCallName() {
return mName; // 返回笔记名称
}
// 判断当前项是否是第一个项
public boolean isFirst() {
return mIsFirstItem; // 返回当前项是否是第一个项的布尔值
}
// 判断是否是唯一一项
public boolean isSingle() {
return mIsOnlyOneItem; // 返回是否是唯一一项的布尔值
}
// 获取笔记的ID
public long getId() {
return mId; // 返回笔记的唯一标识符ID
}
// 获取笔记的提醒时间
public long getAlertDate() {
return mAlertDate; // 返回笔记的提醒时间
}
// 获取笔记的创建时间
public long getCreatedDate() {
return mCreatedDate; // 返回笔记的创建时间
}
// 判断笔记是否有附件
public boolean hasAttachment() {
return mHasAttachment; // 返回笔记是否有附件的布尔值
}
// 获取笔记的修改时间
public long getModifiedDate() {
return mModifiedDate; // 返回笔记的最后修改时间
}
// 获取笔记的背景颜色ID
public int getBgColorId() {
return mBgColorId; // 返回笔记的背景颜色ID
}
// 获取父文件夹ID
public long getParentId() {
return mParentId; // 返回笔记的父文件夹ID
}
// 获取笔记中的内容数量
public int getNotesCount() {
return mNotesCount; // 返回笔记的内容数量
}
// 获取父文件夹的ID重复调用了getParentId方法功能与getParentId相同
public long getFolderId () {
return mParentId; // 返回笔记的文件夹ID等同于getParentId
}
// 获取笔记的类型
public int getType() {
return mType; // 返回笔记的类型ID
}
// 获取笔记的widget类型
public int getWidgetType() {
return mWidgetType; // 返回笔记的widget类型ID
}
// 获取笔记的widget ID
public int getWidgetId() {
return mWidgetId; // 返回笔记的widget ID
}
// 获取笔记的摘要内容
public String getSnippet() {
return mSnippet; // 返回笔记的摘要信息
}
// 判断笔记是否设置了提醒
public boolean hasAlert() {
return (mAlertDate > 0); // 如果提醒日期大于0说明有提醒
}
// 判断是否是通话记录
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
// 判断父文件夹ID是否为通话记录文件夹且手机号码不为空
}
// 获取笔记类型从数据库Cursor中提取笔记类型
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN); // 从Cursor中获取笔记类型字段的值
}
}

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

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

File diff suppressed because it is too large Load Diff

@ -0,0 +1,215 @@
/*
* 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.ui;
// 导入Android相关的库用于处理数据库查询和视图的适配
import android.content.Context; // 用于处理应用程序上下文
import android.database.Cursor; // 用于数据库查询的游标
import android.util.Log; // 用于记录日志
import android.view.View; // 用于视图组件
import android.view.ViewGroup; // 用于视图容器
import android.widget.CursorAdapter; // 用于适配器,绑定数据库游标到视图
// 导入与笔记相关的类
import net.micode.notes.data.Notes; // 用于操作笔记数据
// 导入集合类,用于数据存储和处理
import java.util.Collection; // 用于操作一组对象
import java.util.HashMap; // 用于存储键值对
import java.util.HashSet; // 用于存储唯一元素集合
import java.util.Iterator; // 用于遍历集合元素
// 自定义适配器,用于显示笔记列表
public class NotesListAdapter extends CursorAdapter {
private static final String TAG = "NotesListAdapter"; // 日志标签
private Context mContext; // 上下文对象,用于访问应用资源
private HashMap<Integer, Boolean> mSelectedIndex; // 用于存储选中项的索引和状态
private int mNotesCount; // 当前笔记数量
private boolean mChoiceMode; // 是否处于选择模式
// 内部类,用于存储小部件相关的属性
public static class AppWidgetAttribute {
public int widgetId; // 小部件的 ID
public int widgetType; // 小部件的类型
};
// 构造函数,初始化适配器
public NotesListAdapter(Context context) {
super(context, null); // 调用父类构造函数,传入上下文和空游标
mSelectedIndex = new HashMap<Integer, Boolean>(); // 初始化存储选中项的 HashMap
mContext = context; // 设置上下文
mNotesCount = 0; // 初始化笔记数量
}
// 创建新视图,当每一项数据被绑定时,调用此方法
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context); // 返回一个新的 NotesListItem 视图
}
// 绑定数据到视图上
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) { // 如果视图是 NotesListItem 类型
// 从游标中提取数据,构建 NoteItemData 对象
NoteItemData itemData = new NoteItemData(context, cursor);
// 将数据绑定到视图中,并考虑选择模式和选中状态
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
}
}
// 设置指定位置的选中状态
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked); // 更新选中状态
notifyDataSetChanged(); // 通知数据发生变化,刷新视图
}
// 判断是否处于选择模式
public boolean isInChoiceMode() {
return mChoiceMode;
}
// 设置是否进入选择模式
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear(); // 清除所有选中状态
mChoiceMode = mode; // 设置选择模式
}
// 选择所有项目或取消选择
public void selectAll(boolean checked) {
Cursor cursor = getCursor(); // 获取当前游标
for (int i = 0; i < getCount(); i++) { // 遍历所有项
if (cursor.moveToPosition(i)) { // 移动游标到指定位置
// 如果该项是笔记类型
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
setCheckedItem(i, checked); // 更新选中状态
}
}
}
}
}
// 获取已选择的项的ID集合
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>(); // 创建一个HashSet来存储选中的ID
// 遍历所有选中的项的索引
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) { // 判断该项是否被选中
Long id = getItemId(position); // 获取该项的ID
if (id == Notes.ID_ROOT_FOLDER) { // 如果ID为根文件夹ID记录日志并跳过该项
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id); // 将ID添加到itemSet中
}
}
}
return itemSet; // 返回所有选中项的ID集合
}
// 获取已选择的小部件AppWidget属性集合
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>(); // 创建一个HashSet来存储选中的小部件属性
// 遍历所有选中的项的索引
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) { // 判断该项是否被选中
Cursor c = (Cursor) getItem(position); // 获取该项的Cursor对象
if (c != null) { // 如果Cursor不为空则处理该项
AppWidgetAttribute widget = new AppWidgetAttribute(); // 创建一个AppWidgetAttribute对象
NoteItemData item = new NoteItemData(mContext, c); // 使用Cursor对象初始化NoteItemData
widget.widgetId = item.getWidgetId(); // 获取小部件ID
widget.widgetType = item.getWidgetType(); // 获取小部件类型
itemSet.add(widget); // 将小部件属性添加到itemSet中
// 注意不要在这里关闭Cursor由适配器负责关闭
} else {
Log.e(TAG, "Invalid cursor"); // 如果Cursor为空记录错误日志并返回null
return null;
}
}
}
return itemSet; // 返回所有选中项的小部件属性集合
}
// 获取已选中项的数量
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values(); // 获取选中项的状态集合
if (null == values) {
return 0; // 如果没有选中项返回0
}
Iterator<Boolean> iter = values.iterator();
int count = 0;
// 遍历选中项状态如果为true则计数
while (iter.hasNext()) {
if (true == iter.next()) {
count++;
}
}
return count; // 返回选中项的数量
}
// 判断是否所有项都被选中
public boolean isAllSelected() {
int checkedCount = getSelectedCount(); // 获取已选中项的数量
return (checkedCount != 0 && checkedCount == mNotesCount); // 如果已选中项的数量等于总项数则返回true
}
// 判断某个位置的项是否被选中
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false; // 如果该位置没有被选中返回false
}
return mSelectedIndex.get(position); // 返回该位置的选中状态
@Override
protected void onContentChanged() {
// 当内容发生变化时调用通常用于刷新UI或更新数据
super.onContentChanged();
// 重新计算笔记的数量
calcNotesCount();
}
@Override
public void changeCursor(Cursor cursor) {
// 当游标Cursor发生变化时调用通常在数据更新时触发
super.changeCursor(cursor);
// 重新计算笔记的数量
calcNotesCount();
}
private void calcNotesCount() {
// 初始化笔记数量为0
mNotesCount = 0;
// 遍历当前的数据项假设这些数据项是通过Cursor获取的
for (int i = 0; i < getCount(); i++) {
// 获取当前位置的Cursor对象
Cursor c = (Cursor) getItem(i);
if (c != null) {
// 如果当前游标存在,检查数据项类型是否为笔记类型
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
// 如果是笔记类型,增加笔记数量
mNotesCount++;
}
} else {
// 如果游标为null打印错误日志并返回
Log.e(TAG, "Invalid cursor");
return;
}
}
}
}

@ -0,0 +1,153 @@
/*
* 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.ui;
// 引入Android库和自定义类、工具类
import android.content.Context; // 用于访问应用的上下文
import android.text.format.DateUtils; // 用于格式化日期时间
import android.view.View; // 用于视图操作
import android.widget.CheckBox; // 复选框控件
import android.widget.ImageView; // 图像视图控件
import android.widget.LinearLayout; // 线性布局控件
import android.widget.TextView; // 文本视图控件
// 引入项目内的其他类
import net.micode.notes.R; // 引用资源ID文件
import net.micode.notes.data.Notes; // Notes类表示笔记数据
import net.micode.notes.tool.DataUtils; // 数据工具类,可能用于处理或转换数据
import net.micode.notes.tool.ResourceParser.NoteItemBgResources; // 资源解析器,用于获取笔记项背景资源
public class NotesListItem extends LinearLayout {
// 定义UI控件
private ImageView mAlert; // 用于显示警告图标
private TextView mTitle; // 用于显示标题
private TextView mTime; // 用于显示时间
private TextView mCallName; // 用于显示来电名称
private NoteItemData mItemData; // 用于存储与该视图项相关的数据
private CheckBox mCheckBox; // 用于显示复选框,支持选择模式
// 构造函数,初始化视图
public NotesListItem(Context context) {
super(context); // 调用父类LinearLayout的构造函数
inflate(context, R.layout.note_item, this); // 从布局文件加载视图
mAlert = (ImageView) findViewById(R.id.iv_alert_icon); // 绑定警告图标
mTitle = (TextView) findViewById(R.id.tv_title); // 绑定标题TextView
mTime = (TextView) findViewById(R.id.tv_time); // 绑定时间TextView
mCallName = (TextView) findViewById(R.id.tv_name); // 绑定来电名称TextView
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); // 绑定复选框
}
// 绑定数据到视图
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
// 如果处于选择模式且数据类型为笔记,则显示复选框并设置其选中状态
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE); // 显示复选框
mCheckBox.setChecked(checked); // 设置复选框的选中状态
} else {
mCheckBox.setVisibility(View.GONE); // 否则隐藏复选框
}
mItemData = data; // 存储绑定的数据
// 如果数据ID为通话记录文件夹ID
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE); // 隐藏来电名称TextView
mAlert.setVisibility(View.VISIBLE); // 显示警告图标
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); // 设置标题样式
mTitle.setText(context.getString(R.string.call_record_folder_name) // 设置标题文本
+ context.getString(R.string.format_folder_files_count, data.getNotesCount())); // 添加文件数量
mAlert.setImageResource(R.drawable.call_record); // 设置警告图标为通话记录图标
}
// 如果数据的父ID是通话记录文件夹ID
else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE); // 显示来电名称TextView
mCallName.setText(data.getCallName()); // 设置来电名称
mTitle.setTextAppearance(context, R.style.TextAppearanceSecondaryItem); // 设置标题样式
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); // 设置标题文本
if (data.hasAlert()) { // 如果有提醒
mAlert.setImageResource(R.drawable.clock); // 设置警告图标为时钟图标
mAlert.setVisibility(View.VISIBLE); // 显示警告图标
} else {
mAlert.setVisibility(View.GONE); // 否则隐藏警告图标
}
}
// 处理其他普通笔记或文件夹项
else {
mCallName.setVisibility(View.GONE); // 隐藏来电名称TextView
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); // 设置标题样式
// 如果是文件夹类型的笔记
if (data.getType() == Notes.TYPE_FOLDER) {
mTitle.setText(data.getSnippet() // 设置文件夹标题
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount())); // 添加文件夹中文件数
mAlert.setVisibility(View.GONE); // 隐藏警告图标
} else { // 否则是普通的笔记项
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); // 设置笔记内容预览
if (data.hasAlert()) { // 如果有提醒
mAlert.setImageResource(R.drawable.clock); // 设置警告图标为时钟图标
mAlert.setVisibility(View.VISIBLE); // 显示警告图标
} else {
mAlert.setVisibility(View.GONE); // 否则隐藏警告图标
}
}
}
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); // 设置时间文本
setBackground(data); // 根据数据设置背景(可能是不同的样式或颜色)
}
private void setBackground(NoteItemData data) {
// 获取背景颜色的ID根据 NoteItemData 对象的不同状态来设置不同的背景资源
int id = data.getBgColorId();
// 判断数据类型如果是普通笔记TYPE_NOTE
if (data.getType() == Notes.TYPE_NOTE) {
// 如果该笔记是单一的,或者是跟随文件夹的第一个笔记
if (data.isSingle() || data.isOneFollowingFolder()) {
// 设置背景为 "单个笔记" 的背景资源
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
}
// 如果该笔记是最后一个笔记
else if (data.isLast()) {
// 设置背景为 "最后一个笔记" 的背景资源
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
}
// 如果该笔记是第一个,或者是多重跟随文件夹中的笔记
else if (data.isFirst() || data.isMultiFollowingFolder()) {
// 设置背景为 "第一个笔记" 的背景资源
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
}
// 否则,设置普通笔记的背景
else {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
}
// 如果不是普通笔记(即为文件夹类型)
else {
// 设置为文件夹的背景资源
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
public NoteItemData getItemData() {
return mItemData;
}
}

@ -0,0 +1,568 @@
/*
* 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.ui;
// 导入与账户相关的类,用于账户管理和认证
import android.accounts.Account;
import android.accounts.AccountManager;
// 导入用于操作 Android 应用中的 ActionBar顶部导航栏功能
import android.app.ActionBar;
// 导入用于显示和管理弹出对话框的类
import android.app.AlertDialog;
// 导入用于接收广播消息的类
import android.content.BroadcastReceiver;
// 导入用于在 ContentProvider 中存储和插入数据的类
import android.content.ContentValues;
// 导入 Android 应用的上下文类,用于访问全局应用信息和资源
import android.content.Context;
// 导入用于显示和管理对话框按钮点击事件的接口
import android.content.DialogInterface;
// 导入用于启动新活动或广播的 Intent 类
import android.content.Intent;
// 导入用于过滤广播消息的 IntentFilter 类
import android.content.IntentFilter;
// 导入用于访问和操作应用设置的类
import android.content.SharedPreferences;
// 导入用于传递数据和保存状态的类
import android.os.Bundle;
// 导入与偏好设置界面相关的类,用于创建设置项和响应设置点击事件
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
// 导入用于文本操作的工具类,提供字符串处理方法
import android.text.TextUtils;
// 导入用于日期时间格式化的工具类
import android.text.format.DateFormat;
// 导入用于视图管理和菜单操作的类
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
// 导入常用的 UI 控件类,如按钮、文本视图和弹出提示框
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
// 导入应用的资源文件引用类,包含应用的所有资源(如字符串、布局等)
import net.micode.notes.R;
// 导入与笔记相关的数据模型类,定义了笔记数据结构
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
// 导入与 Google 任务同步服务相关的类,用于同步任务数据
import net.micode.notes.gtask.remote.GTaskSyncService;
public class NotesPreferenceActivity extends PreferenceActivity {
// 定义常量,存储偏好设置的名称
public static final String PREFERENCE_NAME = "notes_preferences";
// 定义常量,存储同步账户名称的键
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
// 定义常量,存储最后同步时间的键
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
// 定义常量,存储背景颜色随机显示的设置键
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
// 私有常量,存储同步账户的键
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
// 私有常量,存储账户信息的过滤键
private static final String AUTHORITIES_FILTER_KEY = "authorities";
// 存储账户相关设置的类别
private PreferenceCategory mAccountCategory;
// 用于接收同步服务广播的接收器
private GTaskReceiver mReceiver;
// 存储原始账户列表
private Account[] mOriAccounts;
// 标记是否添加了新账户
private boolean mHasAddedAccount;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
// 使用应用图标作为导航栏的“向上”按钮
getActionBar().setDisplayHomeAsUpEnabled(true);
// 加载偏好设置界面资源
addPreferencesFromResource(R.xml.preferences);
// 获取账户设置类别对象
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
// 初始化 GTaskReceiver 接收器,用于接收同步相关广播
mReceiver = new GTaskReceiver();
// 创建 IntentFilter注册 GTaskSyncService 广播
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
registerReceiver(mReceiver, filter);
// 初始化原始账户列表为空
mOriAccounts = null;
// 加载设置界面的头部视图
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
getListView().addHeaderView(header, null, true);
}
@Override
protected void onResume() {
super.onResume();
// 如果添加了新账户,则尝试自动设置同步账户
if (mHasAddedAccount) {
// 获取当前所有的 Google 账户
Account[] accounts = getGoogleAccounts();
// 如果原始账户列表不为空且当前账户数量大于原始账户数量
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
// 遍历当前账户列表,查找新增的账户
for (Account accountNew : accounts) {
boolean found = false;
// 遍历原始账户列表,检查新增账户是否已存在
for (Account accountOld : mOriAccounts) {
// 如果找到匹配的账户,则标记为已找到
if (TextUtils.equals(accountOld.name, accountNew.name)) {
found = true;
break;
}
}
// 如果新增账户没有在原始账户列表中找到,则设置为同步账户
if (!found) {
setSyncAccount(accountNew.name);
break;
}
}
}
}
refreshUI();
}
@Override
protected void onDestroy() {
// 如果 mReceiver 已初始化,则注销广播接收器,防止内存泄漏
if (mReceiver != null) {
unregisterReceiver(mReceiver);
}
// 调用父类的 onDestroy确保正确销毁 Activity
super.onDestroy();
}
private void loadAccountPreference() {
// 清空当前账户类别中的所有项
mAccountCategory.removeAll();
// 创建一个新的 Preference 用于账户设置
Preference accountPref = new Preference(this);
// 获取当前同步账户的名称
final String defaultAccount = getSyncAccountName(this);
// 设置账户项的标题和摘要
accountPref.setTitle(getString(R.string.preferences_account_title));
accountPref.setSummary(getString(R.string.preferences_account_summary));
// 设置点击该项时的处理逻辑
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
// 如果当前没有进行同步操作
if (!GTaskSyncService.isSyncing()) {
if (TextUtils.isEmpty(defaultAccount)) {
// 如果没有设置账户,则首次设置账户,弹出选择账户对话框
showSelectAccountAlertDialog();
} else {
// 如果已有账户,弹出更改账户的风险提示对话框
showChangeAccountConfirmAlertDialog();
}
} else {
// 如果正在同步,提示用户无法更改账户
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
}
return true;
}
});
// 将账户设置项添加到账户类别中
mAccountCategory.addPreference(accountPref);
}
private void loadSyncButton() {
// 获取同步按钮和最后同步时间的文本视图
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// 根据同步状态设置按钮文本和点击事件
if (GTaskSyncService.isSyncing()) {
// 如果当前正在同步,按钮文本为“取消同步”,点击时取消同步
syncButton.setText(getString(R.string.preferences_button_sync_cancel));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// 调用同步服务取消同步操作
GTaskSyncService.cancelSync(NotesPreferenceActivity.this);
}
});
} else {
// 如果当前没有同步操作,按钮文本为“立即同步”,点击时开始同步
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// 调用同步服务开始同步操作
GTaskSyncService.startSync(NotesPreferenceActivity.this);
}
});
}
// 设置同步按钮的启用状态:只有在账户不为空时,按钮才启用
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
// 根据同步状态设置最后同步时间文本视图
if (GTaskSyncService.isSyncing()) {
// 如果正在同步,显示当前同步进度的文本
lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
// 如果没有同步操作,检查最后同步时间
long lastSyncTime = getLastSyncTime(this);
if (lastSyncTime != 0) {
// 如果有最后同步时间,显示格式化的时间
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime)));
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
// 如果没有同步时间,隐藏最后同步时间文本
lastSyncTimeView.setVisibility(View.GONE);
}
}
}
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 设置自定义的对话框标题
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); // 设置标题文本
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); // 设置副标题文本
// 将自定义标题设置到对话框
dialogBuilder.setCustomTitle(titleView);
// 设置对话框的“确定”按钮为空操作,实际没有使用
dialogBuilder.setPositiveButton(null, null);
// 获取当前设备上的 Google 账户列表
Account[] accounts = getGoogleAccounts();
String defAccount = getSyncAccountName(this); // 获取当前同步的账户名
mOriAccounts = accounts;
mHasAddedAccount = false;
// 如果有账户可选,显示选择项
if (accounts.length > 0) {
CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items; // 用于存储账户的名称
int checkedItem = -1;
int index = 0;
// 填充账户列表,并找到默认的账户索引
for (Account account : accounts) {
if (TextUtils.equals(account.name, defAccount)) {
checkedItem = index; // 默认选中的账户
}
items[index++] = account.name; // 将账户名称添加到选项列表
}
// 设置单选框列表
dialogBuilder.setSingleChoiceItems(items, checkedItem,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 选择账户后,设置同步账户,并刷新 UI
setSyncAccount(itemMapping[which].toString());
dialog.dismiss();
refreshUI(); // 刷新界面
}
});
}
// 添加“添加账户”按钮
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
dialogBuilder.setView(addAccountView); // 将其添加到对话框中
// 显示对话框
final AlertDialog dialog = dialogBuilder.show();
// 设置“添加账户”点击事件
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mHasAddedAccount = true;
// 打开添加账户设置界面
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { "gmail-ls" });
startActivityForResult(intent, -1); // 启动账户添加页面
dialog.dismiss(); // 关闭当前对话框
}
});
}
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
dialogBuilder.setView(addAccountView);
final AlertDialog dialog = dialogBuilder.show();
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mHasAddedAccount = true;
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
"gmail-ls"
});
startActivityForResult(intent, -1);
dialog.dismiss();
}
});
private void showChangeAccountConfirmAlertDialog() {
// 创建一个 AlertDialog.Builder 实例,用于构建弹出的确认对话框
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 创建自定义标题的视图
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
// 获取自定义标题视图中的标题文本控件,并设置其显示的文本
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
getSyncAccountName(this))); // 显示当前同步账户的名称
// 获取自定义标题视图中的副标题文本控件,并设置其显示的警告信息
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); // 设置警告信息
// 将自定义的标题视图设置为对话框的标题
dialogBuilder.setCustomTitle(titleView);
// 创建对话框的菜单选项
CharSequence[] menuItemArray = new CharSequence[] {
getString(R.string.preferences_menu_change_account), // 更改账户选项
getString(R.string.preferences_menu_remove_account), // 删除账户选项
getString(R.string.preferences_menu_cancel) // 取消选项
};
// 为菜单选项设置点击事件监听器
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == 0) { // 如果用户选择了 "更改账户"
showSelectAccountAlertDialog(); // 显示选择账户的对话框
} else if (which == 1) { // 如果用户选择了 "删除账户"
removeSyncAccount(); // 删除当前同步的账户
refreshUI(); // 刷新 UI更新界面
}
}
});
// 显示对话框
dialogBuilder.show();
}
// 获取所有Google账户
private Account[] getGoogleAccounts() {
// 获取 AccountManager 实例,这个对象用来管理设备上的账户
AccountManager accountManager = AccountManager.get(this);
// 返回所有Google账户"com.google" 表示Google账户类型
return accountManager.getAccountsByType("com.google");
}
// 设置同步账户
private void setSyncAccount(String account) {
// 如果新设置的账户与当前同步账户不同
if (!getSyncAccountName(this).equals(account)) {
// 获取 SharedPreferences用于保存同步账户的配置信息
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
// 如果账户非空,则将其保存为同步账户
if (account != null) {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
} else {
// 如果账户为空,则清除同步账户设置
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
// 提交更改
editor.commit();
// 清除上次同步时间将其设置为0
setLastSyncTime(this, 0);
// 清除与Google任务相关的本地信息
new Thread(new Runnable() {
public void run() {
// 创建一个 ContentValues 对象来更新 Google 任务相关的字段
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, ""); // 清除任务ID
values.put(NoteColumns.SYNC_ID, 0); // 清除同步ID
// 更新 Notes 内容提供者中的数据,清除与同步相关的信息
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
// 弹出提示,告知用户账户设置成功
Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show();
}
}
// 删除同步账户
private void removeSyncAccount() {
// 获取 SharedPreferences删除同步账户设置
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
// 如果存在同步账户的设置,则移除
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
}
// 如果存在上次同步时间的设置,则移除
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
editor.remove(PREFERENCE_LAST_SYNC_TIME);
}
// 提交更改
editor.commit();
// 清除与Google任务相关的本地信息
new Thread(new Runnable() {
public void run() {
// 创建一个 ContentValues 对象来更新 Google 任务相关的字段
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, ""); // 清除任务ID
values.put(NoteColumns.SYNC_ID, 0); // 清除同步ID
// 更新 Notes 内容提供者中的数据,清除与同步相关的信息
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
}
// 获取同步账户名称
public static String getSyncAccountName(Context context) {
// 获取 SharedPreferences 实例,存储同步账户名称
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
// 返回同步账户名称,如果没有保存该名称,返回空字符串
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
// 设置上次同步时间
public static void setLastSyncTime(Context context, long time) {
// 获取 SharedPreferences 实例,存储同步信息
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
// 获取 SharedPreferences 的编辑器,用于修改存储的数据
SharedPreferences.Editor editor = settings.edit();
// 将时间值存储到 PREFERENCE_LAST_SYNC_TIME 键中
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
// 提交更改
editor.commit();
}
// 获取上次同步时间
public static long getLastSyncTime(Context context) {
// 获取 SharedPreferences 实例,读取同步信息
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
// 获取上次同步时间,如果没有保存则返回 0表示没有同步
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
// 广播接收器,用于接收来自 GTaskSyncService 的广播消息
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 刷新UI显示
refreshUI();
// 检查是否正在同步
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
// 获取同步状态显示的 TextView
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// 更新同步状态信息(进度消息)
syncStatus.setText(intent.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
}
}
}
// 处理菜单项点击事件
public boolean onOptionsItemSelected(MenuItem item) {
// 根据点击的菜单项进行相应的处理
switch (item.getItemId()) {
// 点击返回按钮,返回到 NotesListActivity
case android.R.id.home:
// 创建返回 Intent设置标志位清除栈顶活动
Intent intent = new Intent(this, NotesListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// 启动 NotesListActivity
startActivity(intent);
return true;
default:
// 如果没有匹配的菜单项,返回 false
return false;
}
}
}

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

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

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

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

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

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