Compare commits

...

47 Commits
main ... main

Author SHA1 Message Date
p4ns2i85u e2ed886a0c zxl
9 months ago
p4ns2i85u a093f95aa3 zxl
9 months ago
p4ns2i85u 26e287b445 Update NoteWidgetProvider_4x.java
9 months ago
xiangjiawei ad9ff255c9 xiangjiawei
9 months ago
xiangjiawei 5a39226477 Merge branch 'main' of https://bdgit.educoder.net/poz2c78vw/read
9 months ago
xiangjiawei 4a35618ac8 xiangjiawei
9 months ago
p9zvnyb6w c8fd336577 Update ResourceParser.java
9 months ago
p9zvnyb6w 3bd830ce66 Update BackupUtils.java
9 months ago
xuqianlin 0677ccf19e 泛读报告
9 months ago
pv6f9h3iu 8c265e76ba Delete 'doc/小米便签开源代码泛读报告 .docx'
9 months ago
xiangjiawei 943c8efe3f xiangjiawei
9 months ago
wangmingxing 4a86f3bc03 111
9 months ago
wangmingxing 96e61976c2 Merge branch 'main' of https://bdgit.educoder.net/poz2c78vw/read
9 months ago
xiangjiawei 806743f585 xiangjiawei
9 months ago
wangmingxing 1e8e91da8d 111
9 months ago
xiangjiawei 6a191e93ac xiangjiawei
9 months ago
xiangjiawei 6ab93801e9 xiangjiawei
9 months ago
xiangjiawei 2d9ef100ae xiangjiawei
9 months ago
p9zvnyb6w dc45bb9a1b Update BackupUtils.java
9 months ago
p9zvnyb6w 227f54b8ab Update ResourceParser.java
9 months ago
p9zvnyb6w fa6f17eda2 Update BackupUtils.java
9 months ago
xiangjiawei 627c7c48a1 xiangjiawei
9 months ago
xiangjiawei fbd9f8a919 xiangjiaweigenggai
9 months ago
fff a523a12e4f Merge branch 'main' of https://bdgit.educoder.net/poz2c78vw/read
9 months ago
fff 50c1d711f8 111
9 months ago
pv6f9h3iu 57b8c9fe14 Delete 'doc/小米标签MiNote+泛读报告.docx'
9 months ago
xiangjiawei 3e017e3e6b xiangjiawei
9 months ago
poz2c78vw cd88c076ff Merge pull request '1' (#7) from a-branch into main
9 months ago
xuqianlin 75f1ce8845 全组代码泛读报告
9 months ago
poz2c78vw 2ab068fb9d Merge pull request '1' (#6) from a-branch into main
9 months ago
xuqianlin 8bc5e7fee7 注释后的ui部分代码(徐千淋)
9 months ago
poz2c78vw 831e232f10 Delete 'doc/刘沛林model.docx'
9 months ago
p4ns2i85u dcf0cc32e4 zxl
9 months ago
p4ns2i85u e5fa70bad6 zxl
9 months ago
p4ns2i85u dc6f47a2bb zxl
9 months ago
p4ns2i85u 4a7cab3468 Update NoteWidgetProvider_2x.java
9 months ago
p4ns2i85u 017666bc2d Update NoteWidgetProvider.java
9 months ago
6p0 57ffff42ff lpl
9 months ago
6p0 cac4b9ccbd lpl
9 months ago
6p0 ee8aabf987 Merge branch 'a-branch' of https://bdgit.educoder.net/poz2c78vw/read
9 months ago
6p0 58603bffc3 lpl
9 months ago
xiangjiawei 7033c4163e xiangjiawei
9 months ago
wangmingxing 2da4bf6b84 wmx
10 months ago
6p0 ad9bf7efa1 lpl
10 months ago
6p0 5daaaac2c1 lpl
10 months ago
6p0 033fe08f82 lpl
10 months ago
6p0 dd10662994 lpl
10 months ago

@ -16,56 +16,70 @@
package net.micode.notes.data;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Data;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import android.content.Context; // 导入Android上下文类用于访问系统资源
import android.database.Cursor; // 导入Android游标类用于查询数据库
import android.provider.ContactsContract.CommonDataKinds.Phone; // 导入Android联系人电话信息类
import android.provider.ContactsContract.Data; // 导入Android联系人数据类
import android.telephony.PhoneNumberUtils; // 导入Android电话号码工具类
import android.util.Log; // 导入Android日志工具类
import java.util.HashMap;
import java.util.HashMap; // 导入Java哈希表类用于缓存查询结果
public class Contact {
// 静态的哈希表,用于缓存电话号码和对应的联系人姓名
private static HashMap<String, String> sContactCache;
// 日志标签,用于在日志中标识来自该类的消息
private static final String TAG = "Contact";
// SQL查询条件用于查找与给定电话号码匹配的联系人信息
// 使用PHONE_NUMBERS_EQUAL函数比较电话号码确保号码格式的一致性
// 限制数据类型为电话号码类型,并通过子查询确保电话号码的最小匹配字符为'+'
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 "
+ ",?) 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);
return sContactCache.get(phoneNumber); // 如果存在,则直接返回缓存中的姓名
}
// 构造SQL查询条件将占位符"+"替换为电话号码的最小匹配字符
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
// 执行数据库查询,获取与电话号码匹配的联系人姓名
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI,
new String [] { Phone.DISPLAY_NAME },
selection,
new String[] { phoneNumber },
null);
Data.CONTENT_URI, // 查询联系人数据表
new String [] { Phone.DISPLAY_NAME }, // 需要查询的字段:联系人显示名称
selection, // 查询条件
new String[] { phoneNumber }, // 查询条件中的占位符参数
null); // 排序条件这里为null表示不排序
// 检查查询结果是否为空且是否可以移动到第一行
if (cursor != null && cursor.moveToFirst()) {
try {
String name = cursor.getString(0);
sContactCache.put(phoneNumber, name);
return name;
String name = cursor.getString(0); // 获取联系人显示名称
sContactCache.put(phoneNumber, name); // 将查询结果存入缓存
return name; // 返回联系人姓名
} catch (IndexOutOfBoundsException e) {
// 如果发生数组越界异常记录错误日志并返回null
Log.e(TAG, " Cursor get string error " + e.toString());
return null;
} finally {
cursor.close();
cursor.close(); // 关闭游标,释放资源
}
} else {
// 如果没有找到匹配的联系人记录调试日志并返回null
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}

@ -0,0 +1,650 @@
package net.micode.notes.gtask.remote;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.ui.NotesPreferenceActivity;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
/**
* GTaskClientGoogle Tasks
*/
public class GTaskClient {
private static final String TAG = GTaskClient.class.getSimpleName(); // 日志标签
private static final String GTASK_URL = "https://mail.google.com/tasks/"; // Google Tasks的基本URL
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; // 获取数据的URL
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; // 提交数据的URL
private static GTaskClient mInstance = null; // 单例实例
private DefaultHttpClient mHttpClient; // HTTP客户端
private String mGetUrl; // 获取数据的具体URL
private String mPostUrl; // 提交数据的具体URL
private long mClientVersion; // 客户端版本号
private boolean mLoggedin; // 是否已登录
private long mLastLoginTime; // 上次登录时间
private int mActionId; // 操作ID
private Account mAccount; // 当前使用的账户
private JSONArray mUpdateArray; // 更新操作列表
/**
* GTaskClient
*/
private GTaskClient() {
mHttpClient = null;
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
mClientVersion = -1;
mLoggedin = false;
mLastLoginTime = 0;
mActionId = 1;
mAccount = null;
mUpdateArray = null;
}
/**
* GTaskClient
* @return GTaskClient
*/
public static synchronized GTaskClient getInstance() {
if (mInstance == null) {
mInstance = new GTaskClient();
}
return mInstance;
}
/**
* Google
* @param activity Activity
* @return truefalse
*/
public boolean login(Activity activity) {
// 设置cookie过期时间为5分钟如果超过这个时间则重新登录
final long interval = 1000 * 60 * 5;
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false;
}
// 如果账户发生切换,则需要重新登录
if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
mLoggedin = false;
}
// 如果已经登录直接返回true
if (mLoggedin) {
Log.d(TAG, "already logged in");
return true;
}
mLastLoginTime = System.currentTimeMillis(); // 更新上次登录时间
String authToken = loginGoogleAccount(activity, false); // 获取认证令牌
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// 如果账户不在gmail.com或googlemail.com域中则尝试使用自定义域名登录
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
url.append(suffix + "/");
mGetUrl = url.toString() + "ig";
mPostUrl = url.toString() + "r/ig";
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true;
}
}
// 尝试使用Google官方URL登录
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
if (!tryToLoginGtask(activity, authToken)) {
return false;
}
}
mLoggedin = true; // 登录成功
return true;
}
/**
* Google
* @param activity Activity
* @param invalidateToken 使
* @return
*/
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
AccountManager accountManager = AccountManager.get(activity); // 获取AccountManager实例
Account[] accounts = accountManager.getAccountsByType("com.google"); // 获取所有Google账户
if (accounts.length == 0) { // 没有可用的Google账户
Log.e(TAG, "there is no available google account");
return null;
}
String accountName = NotesPreferenceActivity.getSyncAccountName(activity); // 获取设置中的同步账户名称
Account account = null;
for (Account a : accounts) {
if (a.name.equals(accountName)) { // 找到匹配的账户
account = a;
break;
}
}
if (account != null) {
mAccount = account; // 设置当前账户
} else {
Log.e(TAG, "unable to get an account with the same name in the settings");
return null;
}
// 获取认证令牌
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
try {
Bundle authTokenBundle = accountManagerFuture.getResult();
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
if (invalidateToken) { // 如果需要使令牌失效
accountManager.invalidateAuthToken("com.google", authToken);
loginGoogleAccount(activity, false); // 重新登录
}
} catch (Exception e) {
Log.e(TAG, "get auth token failed");
authToken = null;
}
return authToken;
}
/**
* Google Tasks
* @param activity Activity
* @param authToken
* @return truefalse
*/
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) {
// 如果登录失败,可能是令牌过期,使令牌失效后再次尝试
authToken = loginGoogleAccount(activity, true);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
if (!loginGtask(authToken)) {
Log.e(TAG, "login gtask failed");
return false;
}
}
return true;
}
/**
* 使Google Tasks
* @param authToken
* @return truefalse
*/
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000; // 连接超时时间
int timeoutSocket = 15000; // 套接字超时时间
HttpParams httpParameters = new BasicHttpParams(); // HTTP参数
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); // 设置连接超时时间
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); // 设置套接字超时时间
mHttpClient = new DefaultHttpClient(httpParameters); // 创建HTTP客户端
BasicCookieStore localBasicCookieStore = new BasicCookieStore(); // 创建Cookie存储
mHttpClient.setCookieStore(localBasicCookieStore); // 设置Cookie存储
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); // 设置使用Expect Continue
// 登录Google Tasks
try {
String loginUrl = mGetUrl + "?auth=" + authToken; // 构建登录URL
HttpGet httpGet = new HttpGet(loginUrl); // 创建HttpGet请求
HttpResponse response = mHttpClient.execute(httpGet); // 执行请求
// 获取认证cookie
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false;
for (Cookie cookie : cookies) {
if (cookie.getName().contains("GTL")) { // 检查是否存在认证cookie
hasAuthCookie = true;
}
}
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie");
}
// 获取客户端版本号
String resString = getResponseContent(response.getEntity()); // 获取响应内容
String jsBegin = "_setup("; // JSON数据开始标记
String jsEnd = ")}</script>"; // JSON数据结束标记
int begin = resString.indexOf(jsBegin); // 查找开始标记
int end = resString.lastIndexOf(jsEnd); // 查找结束标记
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end); // 提取JSON字符串
}
JSONObject js = new JSONObject(jsString); // 将JSON字符串转换为JSONObject
mClientVersion = js.getLong("v"); // 获取客户端版本号
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
} catch (Exception e) {
Log.e(TAG, "httpget gtask_url failed");
return false;
}
return true;
}
/**
* ID
* @return ID
*/
private int getActionId() {
return mActionId++;
}
/**
* HttpPost
* @return HttpPost
*/
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl); // 创建HttpPost请求
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); // 设置请求头
httpPost.setHeader("AT", "1");
return httpPost;
}
/**
* HTTP
* @param entity HTTP
* @return
* @throws IOException IO
*/
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
if (entity.getContentEncoding() != null) {
contentEncoding = entity.getContentEncoding().getValue(); // 获取内容编码
Log.d(TAG, "encoding: " + contentEncoding);
}
InputStream input = entity.getContent(); // 获取输入流
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { // 如果是gzip编码
input = new GZIPInputStream(entity.getContent()); // 使用GZIPInputStream解码
} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { // 如果是deflate编码
Inflater inflater = new Inflater(true);
input = new InflaterInputStream(entity.getContent(), inflater); // 使用InflaterInputStream解码
}
try {
InputStreamReader isr = new InputStreamReader(input); // 创建InputStreamReader
BufferedReader br = new BufferedReader(isr); // 创建BufferedReader
StringBuilder sb = new StringBuilder();
// 读取响应内容
String buff;
while ((buff = br.readLine()) != null) {
sb.append(buff); // 将每一行内容追加到StringBuilder中
}
return sb.toString();
} finally {
input.close(); // 关闭输入流
}
}
/**
* POSTGoogle Tasks
* @param js JSONObject
* @return JSONObject
* @throws NetworkFailureException
*/
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in"); // 如果未登录,抛出异常
}
HttpPost httpPost = createHttpPost(); // 创建HttpPost请求
try {
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
list.add(new BasicNameValuePair("r", js.toString())); // 将JSONObject转换为字符串并添加到请求参数中
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); // 创建UrlEncodedFormEntity
httpPost.setEntity(entity); // 设置请求实体
// 执行POST请求
HttpResponse response = mHttpClient.execute(httpPost);
String jsString = getResponseContent(response.getEntity()); // 获取响应内容
return new JSONObject(jsString); // 将响应内容转换为JSONObject
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed"); // 如果发生协议错误,抛出异常
} catch (IOException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed"); // 如果发生IO错误抛出异常
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("unable to convert response content to jsonobject"); // 如果无法将响应内容转换为JSONObject抛出异常
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("error occurs when posting request"); // 如果发生其他错误,抛出异常
}
}
/**
*
* @param task
* @throws NetworkFailureException
*/
public void createTask(Task task) throws NetworkFailureException {
commitUpdate(); // 提交所有待更新的操作
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// 添加创建任务的操作
actionList.put(task.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 提交POST请求
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0); // 获取响应结果
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 设置任务ID
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create task: handing jsonobject failed"); // 如果发生JSON处理错误抛出异常
}
}
/**
*
* @param tasklist
* @throws NetworkFailureException
*/
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate(); // 提交所有待更新的操作
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// 添加创建任务列表的操作
actionList.put(tasklist.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 提交POST请求
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0); // 获取响应结果
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 设置任务列表ID
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create tasklist: handing jsonobject failed"); // 如果发生JSON处理错误抛出异常
}
}
/**
*
* @throws NetworkFailureException
*/
public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) { // 如果有待更新的操作
try {
JSONObject jsPost = new JSONObject();
// 设置操作列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);
// 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost); // 提交POST请求
mUpdateArray = null; // 清空待更新的操作列表
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("commit update: handing jsonobject failed"); // 如果发生JSON处理错误抛出异常
}
}
}
/**
*
* @param node
* @throws NetworkFailureException
*/
public void addUpdateNode(Node node) throws NetworkFailureException {
if (node != null) {
// 如果待更新的操作数量超过10个则先提交这些操作
if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate();
}
if (mUpdateArray == null) {
mUpdateArray = new JSONArray(); // 如果操作列表为空则创建一个新的JSONArray
}
mUpdateArray.put(node.getUpdateAction(getActionId())); // 将节点的更新操作添加到操作列表中
}
}
/**
*
* @param task
* @param preParent
* @param curParent
* @throws NetworkFailureException
*/
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
commitUpdate(); // 提交所有待更新的操作
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
// 构建移动任务的操作
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid());
if (preParent == curParent && task.getPriorSibling() != null) {
// 如果在同一个列表中移动并且不是第一个任务则设置前一个兄弟节点ID
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling());
}
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid());
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid());
if (preParent != curParent) {
// 如果移动到不同的列表则设置目标列表ID
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
}
actionList.put(action); // 将操作添加到操作列表中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost); // 提交POST请求
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("move task: handing jsonobject failed"); // 如果发生JSON处理错误抛出异常
}
}
/**
*
* @param node
* @throws NetworkFailureException
*/
public void deleteNode(Node node) throws NetworkFailureException {
commitUpdate(); // 提交所有待更新的操作
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// 构建删除节点的操作
node.setDeleted(true);
actionList.put(node.getUpdateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost); // 提交POST请求
mUpdateArray = null; // 清空待更新的操作列表
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("delete node: handing jsonobject failed"); // 如果发生JSON处理错误抛出异常
}
}
/**
*
* @return JSONArray
* @throws NetworkFailureException
*/
public JSONArray getTaskLists() throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in"); // 如果未登录,抛出异常
}
try {
HttpGet httpGet = new HttpGet(mGetUrl); // 创建HttpGet请求
HttpResponse response = mHttpClient.execute(httpGet); // 执行请求
// 获取任务列表数据
String resString = getResponseContent(response.getEntity()); // 获取响应内容
String jsBegin = "_setup("; // JSON数据开始标记
String jsEnd = ")}</script>"; // JSON数据结束标记
int begin = resString.indexOf(jsBegin); // 查找开始标记
int end = resString.lastIndexOf(jsEnd); // 查找结束标记
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end); // 提取JSON字符串
}
JSONObject js = new JSONObject(jsString); // 将JSON字符串转换为JSONObject
return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); // 获取任务列表数组
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed"); // 如果发生协议错误,抛出异常
} catch (IOException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed"); // 如果发生IO错误抛出异常
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task lists: handing jasonobject failed"); // 如果发生JSON处理错误抛出异常
}
}
/**
* ID
* @param listGid ID
* @return JSONArray
* @throws NetworkFailureException
*/
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate(); // 提交所有待更新的操作
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
// 构建获取任务列表的操作
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid);
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false);
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 提交POST请求
JSONObject jsResponse = postRequest(jsPost);
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); // 获取任务数组
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task list: handing jsonobject failed"); // 如果发生JSON处理错误抛出异常
}
}
/**
*
* @return
*/
public Account getSyncAccount() {
return mAccount;
}
/**
*
*/
public void resetUpdateArray() {
mUpdateArray = null;
}
}

@ -0,0 +1,851 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.data.MetaData;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.SqlNote;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
/**
* GTaskManager Google Tasks
*/
public class GTaskManager {
private static final String TAG = GTaskManager.class.getSimpleName();
public static final int STATE_SUCCESS = 0; // 同步成功
public static final int STATE_NETWORK_ERROR = 1; // 网络错误
public static final int STATE_INTERNAL_ERROR = 2; // 内部错误
public static final int STATE_SYNC_IN_PROGRESS = 3; // 同步进行中
public static final int STATE_SYNC_CANCELLED = 4; // 同步被取消
private static GTaskManager mInstance = null; // 单例实例
private Activity mActivity; // 用于获取认证 token 的 Activity 上下文
private Context mContext; // 应用上下文
private ContentResolver mContentResolver; // 内容解析器
private boolean mSyncing; // 标记同步是否正在进行
private boolean mCancelled; // 标记同步是否被取消
private HashMap<String, TaskList> mGTaskListHashMap; // 存储 Google Tasks 列表的映射
private HashMap<String, Node> mGTaskHashMap; // 存储 Google Tasks 的映射
private HashMap<String, MetaData> mMetaHashMap; // 存储元数据的映射
private TaskList mMetaList; // 元数据列表
private HashSet<Long> mLocalDeleteIdMap; // 存储本地删除的笔记 ID
private HashMap<String, Long> mGidToNid; // Google ID 到本地 ID 的映射
private HashMap<Long, String> mNidToGid; // 本地 ID 到 Google ID 的映射
private GTaskManager() {
mSyncing = false;
mCancelled = false;
mGTaskListHashMap = new HashMap<String, TaskList>();
mGTaskHashMap = new HashMap<String, Node>();
mMetaHashMap = new HashMap<String, MetaData>();
mMetaList = null;
mLocalDeleteIdMap = new HashSet<Long>();
mGidToNid = new HashMap<String, Long>();
mNidToGid = new HashMap<Long, String>();
}
/**
* GTaskManager
* @return GTaskManager
*/
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
mInstance = new GTaskManager();
}
return mInstance;
}
/**
* Activity token
* @param activity Activity
*/
public synchronized void setActivityContext(Activity activity) {
mActivity = activity;
}
/**
*
* @param context
* @param asyncTask
* @return
*/
public int sync(Context context, GTaskASyncTask asyncTask) {
if (mSyncing) {
Log.d(TAG, "Sync is in progress");
return STATE_SYNC_IN_PROGRESS;
}
mContext = context;
mContentResolver = mContext.getContentResolver();
mSyncing = true;
mCancelled = false;
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
try {
GTaskClient client = GTaskClient.getInstance();
client.resetUpdateArray();
// 登录 Google Tasks
if (!mCancelled) {
if (!client.login(mActivity)) {
throw new NetworkFailureException("登录 Google Tasks 失败");
}
}
// 获取 Google 的任务列表
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList();
// 执行内容同步
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent();
} catch (NetworkFailureException e) {
Log.e(TAG, e.toString());
return STATE_NETWORK_ERROR;
} catch (ActionFailureException e) {
Log.e(TAG, e.toString());
return STATE_INTERNAL_ERROR;
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return STATE_INTERNAL_ERROR;
} finally {
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
mSyncing = false;
}
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
/**
* Google
* @throws NetworkFailureException
*/
private void initGTaskList() throws NetworkFailureException {
if (mCancelled)
return;
GTaskClient client = GTaskClient.getInstance();
try {
JSONArray jsTaskLists = client.getTaskLists();
// 初始化元数据列表
mMetaList = null;
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
mMetaList = new TaskList();
mMetaList.setContentByRemoteJSON(object);
// 加载元数据
JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) {
object = (JSONObject) jsMetas.getJSONObject(j);
MetaData metaData = new MetaData();
metaData.setContentByRemoteJSON(object);
if (metaData.isWorthSaving()) {
mMetaList.addChildTask(metaData);
if (metaData.getGid() != null) {
mMetaHashMap.put(metaData.getRelatedGid(), metaData);
}
}
}
}
}
// 如果元数据列表不存在,则创建
if (mMetaList == null) {
mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META);
GTaskClient.getInstance().createTaskList(mMetaList);
}
// 初始化任务列表
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)
&& !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
TaskList tasklist = new TaskList();
tasklist.setContentByRemoteJSON(object);
mGTaskListHashMap.put(gid, tasklist);
mGTaskHashMap.put(gid, tasklist);
// 加载任务
JSONArray jsTasks = client.getTaskList(gid);
for (int j = 0; j < jsTasks.length(); j++) {
object = (JSONObject) jsTasks.getJSONObject(j);
gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
Task task = new Task();
task.setContentByRemoteJSON(object);
if (task.isWorthSaving()) {
task.setMetaInfo(mMetaHashMap.get(gid));
tasklist.addChildTask(task);
mGTaskHashMap.put(gid, task);
}
}
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("initGTaskList: 处理 JSONObject 失败");
}
}
/**
*
* @throws NetworkFailureException
*/
private void syncContent() throws NetworkFailureException {
int syncType;
Cursor c = null;
String gid;
Node node;
mLocalDeleteIdMap.clear();
if (mCancelled) {
return;
}
// 同步本地已删除的笔记
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id=?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, null);
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c);
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
}
} else {
Log.w(TAG, "无法查询垃圾文件夹");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 先同步文件夹
syncFolder();
// 同步数据库中已存在的笔记
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地添加
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c);
}
} else {
Log.w(TAG, "无法查询数据库中的现有笔记");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 处理剩余的项目(远程添加的任务或文件夹)
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Node> entry = iter.next();
node = entry.getValue();
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
// 清除本地删除表
if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("批量删除本地删除的笔记失败");
}
}
// 刷新本地同步 ID
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId();
}
}
/**
*
* @throws NetworkFailureException
*/
private void syncFolder() throws NetworkFailureException {
Cursor c = null;
String gid;
Node node;
int syncType;
if (mCancelled) {
return;
}
// 同步根文件夹
try {
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null);
if (c != null) {
c.moveToNext();
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// 对于系统文件夹,仅在必要时更新远程名称
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
} else {
Log.w(TAG, "无法查询根文件夹");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 同步通话记录文件夹
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(Notes.ID_CALL_RECORD_FOLDER)
}, null);
if (c != null) {
if (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);
// 对于系统文件夹,仅在必要时更新远程名称
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
}
} else {
Log.w(TAG, "无法查询通话记录文件夹");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 同步本地已存在的文件夹
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地添加
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c);
}
} else {
Log.w(TAG, "无法查询本地已存在的文件夹");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 同步远程添加的文件夹
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
gid = entry.getKey();
node = entry.getValue();
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
}
if (!mCancelled)
GTaskClient.getInstance().commitUpdate();
}
/**
*
* @param syncType
* @param node
* @param c
* @throws NetworkFailureException
*/
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
MetaData meta;
switch (syncType) {
case Node.SYNC_ACTION_ADD_LOCAL:
addLocalNode(node);
break;
case Node.SYNC_ACTION_ADD_REMOTE:
addRemoteNode(node, c);
break;
case Node.SYNC_ACTION_DEL_LOCAL:
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN));
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta);
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
break;
case Node.SYNC_ACTION_DEL_REMOTE:
meta = mMetaHashMap.get(node.getGid());
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta);
}
GTaskClient.getInstance().deleteNode(node);
break;
case Node.SYNC_ACTION_UPDATE_LOCAL:
updateLocalNode(node, c);
break;
case Node.SYNC_ACTION_UPDATE_REMOTE:
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_UPDATE_CONFLICT:
// 处理冲突时,可以考虑合并两边的修改
// 目前简单地使用本地更新
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_NONE:
break;
case Node.SYNC_ACTION_ERROR:
default:
throw new ActionFailureException("未知的同步操作类型");
}
}
/**
*
* @param node
* @throws NetworkFailureException
*/
private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote;
if (node instanceof TaskList) {
if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) {
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER);
} else if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) {
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER);
} else {
sqlNote = new SqlNote(mContext);
sqlNote.setContent(node.getLocalJSONFromContent());
sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
}
} else {
sqlNote = new SqlNote(mContext);
JSONObject js = node.getLocalJSONFromContent();
try {
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.has(NoteColumns.ID)) {
long id = note.getLong(NoteColumns.ID);
if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
// ID 不可用,需要创建新的 ID
note.remove(NoteColumns.ID);
}
}
}
if (js.has(GTaskStringUtils.META_HEAD_DATA)) {
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
// 数据 ID 不可用,需要创建新的 ID
data.remove(DataColumns.ID);
}
}
}
}
} catch (JSONException e) {
Log.w(TAG, e.toString());
e.printStackTrace();
}
sqlNote.setContent(js);
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());
if (parentId == null) {
Log.e(TAG, "无法在本地找到任务的父 ID");
throw new ActionFailureException("无法添加本地节点");
}
sqlNote.setParentId(parentId.longValue());
}
// 创建本地节点
sqlNote.setGtaskId(node.getGid());
sqlNote.commit(false);
// 更新 gid-nid 映射
mGidToNid.put(node.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), node.getGid());
// 更新元数据
updateRemoteMeta(node.getGid(), sqlNote);
}
/**
*
* @param node
* @param c
* @throws NetworkFailureException
*/
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote;
// 在本地更新笔记
sqlNote = new SqlNote(mContext, c);
sqlNote.setContent(node.getLocalJSONFromContent());
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
: new Long(Notes.ID_ROOT_FOLDER);
if (parentId == null) {
Log.e(TAG, "无法在本地找到任务的父 ID");
throw new ActionFailureException("无法更新本地节点");
}
sqlNote.setParentId(parentId.longValue());
sqlNote.commit(true);
// 更新元数据信息
updateRemoteMeta(node.getGid(), sqlNote);
}
/**
*
* @param node
* @param c
* @throws NetworkFailureException
*/
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
Node n;
// 在远程更新
if (sqlNote.isNoteType()) {
Task task = new Task();
task.setContentByLocalJSON(sqlNote.getContent());
String parentGid = mNidToGid.get(sqlNote.getParentId());
if (parentGid == null) {
Log.e(TAG, "无法找到任务的父任务列表");
throw new ActionFailureException("无法添加远程任务");
}
mGTaskListHashMap.get(parentGid).addChildTask(task);
GTaskClient.getInstance().createTask(task);
n = (Node) task;
// 添加元数据
updateRemoteMeta(task.getGid(), sqlNote);
} else {
TaskList tasklist = null;
// 如果文件夹已存在,则跳过
String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX;
if (sqlNote.getId() == Notes.ID_ROOT_FOLDER)
folderName += GTaskStringUtils.FOLDER_DEFAULT;
else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER)
folderName += GTaskStringUtils.FOLDER_CALL_NOTE;
else
folderName += sqlNote.getSnippet();
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
String gid = entry.getKey();
TaskList list = entry.getValue();
if (list.getName().equals(folderName)) {
tasklist = list;
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
}
break;
}
}
// 如果没有匹配项,则添加新的文件夹
if (tasklist == null) {
tasklist = new TaskList();
tasklist.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().createTaskList(tasklist);
mGTaskListHashMap.put(tasklist.getGid(), tasklist);
}
n = (Node) tasklist;
}
// 更新本地笔记
sqlNote.setGtaskId(n.getGid());
sqlNote.commit(false);
sqlNote.resetLocalModified();
sqlNote.commit(true);
// gid-id 映射
mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid());
}
/**
*
* @param node
* @param c
* @throws NetworkFailureException
*/
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
// 在远程更新
node.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(node);
// 更新元数据
updateRemoteMeta(node.getGid(), sqlNote);
// 如果需要,移动任务
if (sqlNote.isNoteType()) {
Task task = (Task) node;
TaskList preParentList = task.getParent();
String curParentGid = mNidToGid.get(sqlNote.getParentId());
if (curParentGid == null) {
Log.e(TAG, "无法找到任务的父任务列表");
throw new ActionFailureException("无法更新远程任务");
}
TaskList curParentList = mGTaskListHashMap.get(curParentGid);
if (preParentList != curParentList) {
preParentList.removeChildTask(task);
curParentList.addChildTask(task);
GTaskClient.getInstance().moveTask(task, preParentList, curParentList);
}
}
// 清除本地修改标志
sqlNote.resetLocalModified();
sqlNote.commit(true);
}
/**
*
* @param gid Google ID
* @param sqlNote SqlNote
* @throws NetworkFailureException
*/
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
if (sqlNote != null && sqlNote.isNoteType()) {
MetaData metaData = mMetaHashMap.get(gid);
if (metaData != null) {
metaData.setMeta(gid, sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(metaData);
} else {
metaData = new MetaData();
metaData.setMeta(gid, sqlNote.getContent());
mMetaList.addChildTask(metaData);
mMetaHashMap.put(gid, metaData);
GTaskClient.getInstance().createTask(metaData);
}
}
}
/**
* ID
* @throws NetworkFailureException
*/
private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) {
return;
}
// 获取最新的 Google Tasks 列表
mGTaskHashMap.clear();
mGTaskListHashMap.clear();
mMetaHashMap.clear();
initGTaskList();
Cursor c = null;
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
String gid = c.getString(SqlNote.GTASK_ID_COLUMN);
Node node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
ContentValues values = new ContentValues();
values.put(NoteColumns.SYNC_ID, node.getLastModified());
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
c.getLong(SqlNote.ID_COLUMN)), values, null, null);
} else {
Log.e(TAG, "某些内容缺失");
throw new ActionFailureException(
"同步后某些本地项目没有 gid");
}
}
} else {
Log.w(TAG, "无法查询本地笔记以刷新同步 ID");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
}
/**
*
* @return
*/
public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name;
}
/**
*
*/
public void cancelSync() {
mCancelled = true;
}
}

@ -2,278 +2,297 @@
* 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 {
// 定义了Content Provider的权限字符串
public static final String AUTHORITY = "micode_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;
// 定义了笔记类型的常量
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
* {@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
* ID
* {@link Notes#ID_ROOT_FOLDER }
* {@link Notes#ID_TEMPARAY_FOLDER }
* {@link Notes#ID_CALL_RECORD_FOLDER}
* {@link Notes#ID_TRASH_FOLER}
*/
public static final int ID_ROOT_FOLDER = 0;
public static final int ID_TEMPARAY_FOLDER = -1;
public static final int ID_CALL_RECORD_FOLDER = -2;
public static final int ID_TRASH_FOLER = -3;
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date";
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";
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";
// 定义了一些Intent的extra键名
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; // 提醒日期
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; // 背景颜色ID
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; // 小部件ID
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; // 小部件类型
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; // 文件夹ID
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; // 通话日期
public static final int TYPE_WIDGET_INVALIDE = -1;
public static final int TYPE_WIDGET_2X = 0;
public static final int TYPE_WIDGET_4X = 1;
// 定义了小部件类型的常量
public static final int TYPE_WIDGET_INVALIDE = -1; // 无效的小部件
public static final int TYPE_WIDGET_2X = 0; // 2x尺寸的小部件
public static final int TYPE_WIDGET_4X = 1; // 4x尺寸的小部件
// 定义一个内部类DataConstants包含一些与数据类型相关的常量
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
* content://micode_notes/note
*/
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* Uri to query data
* Uri
* content://micode_notes/data
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
// 定义了一个接口NoteColumns包含了一些与笔记和文件夹相关的列名
public interface NoteColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
* ID
* <P> INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* The parent's id for note or folder
* <P> Type: INTEGER (long) </P>
* ID
* <P> INTEGER (long) </P>
*/
public static final String PARENT_ID = "parent_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*
* <P> INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*
* <P> INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
* Alert date
* <P> Type: INTEGER (long) </P>
*
* <P> INTEGER (long) </P>
*/
public static final String ALERTED_DATE = "alert_date";
/**
* Folder's name or text content of note
* <P> Type: TEXT </P>
*
* <P> TEXT </P>
*/
public static final String SNIPPET = "snippet";
/**
* Note's widget id
* <P> Type: INTEGER (long) </P>
* ID
* <P> INTEGER (long) </P>
*/
public static final String WIDGET_ID = "widget_id";
/**
* Note's widget type
* <P> Type: INTEGER (long) </P>
*
* <P> INTEGER (long) </P>
*/
public static final String WIDGET_TYPE = "widget_type";
/**
* Note's background color's id
* <P> Type: INTEGER (long) </P>
* ID
* <P> INTEGER (long) </P>
*/
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>
*
* <P> INTEGER </P>
*/
public static final String HAS_ATTACHMENT = "has_attachment";
/**
* Folder's count of notes
* <P> Type: INTEGER (long) </P>
*
* <P> INTEGER (long) </P>
*/
public static final String NOTES_COUNT = "notes_count";
/**
* The file type: folder or note
* <P> Type: INTEGER </P>
*
* <P> INTEGER </P>
*/
public static final String TYPE = "type";
/**
* The last sync id
* <P> Type: INTEGER (long) </P>
* ID
* <P> INTEGER (long) </P>
*/
public static final String SYNC_ID = "sync_id";
/**
* Sign to indicate local modified or not
* <P> Type: INTEGER </P>
*
* <P> INTEGER </P>
*/
public static final String LOCAL_MODIFIED = "local_modified";
/**
* Original parent id before moving into temporary folder
* <P> Type : INTEGER </P>
* ID
* <P> INTEGER </P>
*/
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* The gtask id
* <P> Type : TEXT </P>
* GoogleID
* <P> TEXT </P>
*/
public static final String GTASK_ID = "gtask_id";
/**
* The version code
* <P> Type : INTEGER (long) </P>
*
* <P> INTEGER (long) </P>
*/
public static final String VERSION = "version";
}
// 定义了一个接口DataColumns包含了一些与数据相关的列名
public interface DataColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
* ID
* <P> INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* The MIME type of the item represented by this row.
* <P> Type: Text </P>
* MIME
* <P> TEXT </P>
*/
public static final String MIME_TYPE = "mime_type";
/**
* The reference id to note that this data belongs to
* <P> Type: INTEGER (long) </P>
* ID
* <P> INTEGER (long) </P>
*/
public static final String NOTE_ID = "note_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*
* <P> INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*
* <P> INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
* Data's content
* <P> Type: TEXT </P>
*
* <P> 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>
* {@link #MIME_TYPE}
* <P> INTEGER </P>
*/
public static final String DATA1 = "data1";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
* {@link #MIME_TYPE}
* <P> INTEGER </P>
*/
public static final String DATA2 = "data2";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
* {@link #MIME_TYPE}
* <P> TEXT </P>
*/
public static final String DATA3 = "data3";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
* {@link #MIME_TYPE}
* <P> TEXT </P>
*/
public static final String DATA4 = "data4";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
* {@link #MIME_TYPE}
* <P> TEXT </P>
*/
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>
*
* <P> Integer 1: 0: </P>
*/
public static final String MODE = DATA1;
// 定义了文本笔记模式的常量
public static final int MODE_CHECK_LIST = 1;
// 定义了文本笔记的MIME类型
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
* content://micode_notes/text_note
*/
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>
*
* <P> INTEGER (long) </P>
*/
public static final String CALL_DATE = DATA1;
/**
* Phone number for this record
* <P> Type: TEXT </P>
*
* <P> TEXT </P>
*/
public static final String PHONE_NUMBER = DATA3;
// 定义了通话笔记的MIME类型
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
* content://micode_notes/call_note
*/
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
}

@ -2,16 +2,16 @@
* 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;
@ -26,198 +26,209 @@ 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 {
// 定义了数据库名称
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";
public static final String NOTE = "note"; // 笔记表名
public static final String DATA = "data"; // 数据表名
}
// 定义了日志标签字符串
private static final String TAG = "NotesDatabaseHelper";
// 定义了单例实例
private static NotesDatabaseHelper mInstance;
// 定义了创建笔记表的SQL语句
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," +
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," +
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
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)," + // 创建日期,默认为当前时间(毫秒)
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + // 是否有附件默认为0
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改日期,默认为当前时间(毫秒)
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + // 笔记数量默认为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 ''," + // GTasks ID默认为空字符串
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 版本号默认为0
")";
// 定义了创建 DATA 表的SQL语句
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," +
DataColumns.MIME_TYPE + " TEXT NOT NULL," +
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA1 + " INTEGER," +
DataColumns.DATA2 + " INTEGER," +
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
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索引的SQL语句
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
TABLE.DATA + "(" + DataColumns.NOTE_ID + "); "; // 为data表中的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 + ";" +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + // 更新父文件夹的笔记数量加1
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + // 父文件夹ID为移动后的文件夹ID
" END";
/**
* Decrease folder's note count when move note from folder
*
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " +
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + // 更新父文件夹的笔记数量减1
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + // 父文件夹ID为移动前的文件夹ID
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + // 仅当笔记数量大于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 " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + // 更新父文件夹的笔记数量加1
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + // 父文件夹ID为新笔记的父文件夹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 " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0;" +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + // 更新父文件夹的笔记数量减1
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + // 父文件夹ID为删除笔记的父文件夹ID
" AND " + NoteColumns.NOTES_COUNT + ">0;" + // 仅当笔记数量大于0时更新
" END";
/**
* Update note's content when insert data with type {@link DataConstants#NOTE}
* {@link DataConstants#NOTE}
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
" AFTER INSERT ON " + TABLE.DATA +
" WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + // 当插入的数据类型为笔记时
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + // 更新笔记的摘要为新插入的数据内容
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + // 笔记ID为新插入数据关联的笔记ID
" END";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has changed
* {@link DataConstants#NOTE}
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " +
" AFTER UPDATE ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + // 当更新的数据类型为笔记时
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + // 更新笔记的摘要为更新后的数据内容
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + // 笔记ID为更新数据关联的笔记ID
" END";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has deleted
* {@link DataConstants#NOTE}
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " +
" AFTER delete ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + // 当删除的数据类型为笔记时
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=''" +
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
" SET " + NoteColumns.SNIPPET + "=''" + // 清空笔记的摘要
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + // 笔记ID为删除数据关联的笔记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 + ";" +
" DELETE FROM " + TABLE.DATA + // 删除data表中与笔记ID关联的所有数据
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + // 笔记ID为删除笔记的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 + ";" +
" DELETE FROM " + TABLE.NOTE + // 删除note表中与文件夹ID关联的所有笔记
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + // 父文件夹ID为删除文件夹的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 +
" WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + // 当笔记被移动到回收站文件夹时
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + // 将笔记的父文件夹ID设置为回收站文件夹ID
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + // 笔记的父文件夹ID为移动前的文件夹ID
" END";
// 构造函数,初始化数据库帮助器
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
// 创建笔记表的方法
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
Log.d(TAG, "note table has been created");
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");
@ -226,6 +237,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
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);
@ -235,128 +247,147 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
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);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
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);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
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);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
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);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
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);
reCreateDataTableTriggers(db);
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
Log.d(TAG, "data table has been created");
db.execSQL(CREATE_DATA_TABLE_SQL); // 执行创建数据表的SQL语句
reCreateDataTableTriggers(db); // 重新创建数据表的触发器
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建note_id索引
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);
mInstance = new NotesDatabaseHelper(context); // 初始化单例实例
}
return mInstance;
return mInstance; // 返回单例实例
}
// 当数据库第一次创建时调用的方法
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db);
createDataTable(db);
createNoteTable(db); // 创建笔记表
createDataTable(db); // 创建数据表
}
// 当数据库需要升级时调用的方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false;
boolean skipV2 = false;
// 如果当前版本是1直接删除旧表并创建新表
if (oldVersion == 1) {
upgradeToV2(db);
skipV2 = true; // this upgrade including the upgrade from v2 to v3
upgradeToV2(db); // 升级到版本2
skipV2 = true; // 这次升级包含了从v2到v3的升级
oldVersion++;
}
// 如果当前版本是2添加新的列并创建系统文件夹
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db);
reCreateTriggers = true;
upgradeToV3(db); // 升级到版本3
reCreateTriggers = true; // 标记需要重新创建触发器
oldVersion++;
}
// 如果当前版本是3添加版本号列
if (oldVersion == 3) {
upgradeToV4(db);
upgradeToV4(db); // 升级到版本4
oldVersion++;
}
// 如果需要重新创建触发器,调用相应的方法
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
reCreateNoteTableTriggers(db); // 重新创建笔记表的触发器
reCreateDataTableTriggers(db); // 重新创建数据表的触发器
}
// 如果升级失败,抛出异常
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
+ " 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);
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
// 添加 gtask id 列
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// add a trash system folder
+ " TEXT NOT NULL DEFAULT ''"); // 为笔记表添加gtask_id列
// 添加回收站系统文件夹
ContentValues values = new ContentValues();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
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) {
// 添加版本号列
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
+ " INTEGER NOT NULL DEFAULT 0"); // 为笔记表添加version列
}
}

@ -16,7 +16,6 @@
package net.micode.notes.data;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
@ -34,22 +33,26 @@ 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;
// 数据库辅助类,用于创建和管理数据库
private NotesDatabaseHelper mHelper;
// 日志标签,便于调试和日志记录
private static final String TAG = "NotesProvider";
private static final int URI_NOTE = 1;
private static final int URI_NOTE_ITEM = 2;
private static final int URI_DATA = 3;
private static final int URI_DATA_ITEM = 4;
// 定义URI请求类型常量
private static final int URI_NOTE = 1; // 匹配所有笔记的URI
private static final int URI_NOTE_ITEM = 2; // 匹配特定笔记ID的URI
private static final int URI_DATA = 3; // 匹配所有笔记数据的URI
private static final int URI_DATA_ITEM = 4; // 匹配特定笔记数据ID的URI
private static final int URI_SEARCH = 5;
private static final int URI_SEARCH_SUGGEST = 6;
private static final int URI_SEARCH = 5; // 匹配搜索请求的URI
private static final int URI_SEARCH_SUGGEST = 6; // 匹配搜索建议请求的URI
// 静态代码块在类加载时初始化UriMatcher添加各种URI模式及其对应的代码
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
@ -62,8 +65,9 @@ public class NotesProvider extends ContentProvider {
}
/**
* x'0A' represents the '\n' character in sqlite. For title and content in the search result,
* we will trim '\n' and white space in order to show more information.
* x'0A'sqlite'\n'
* '\n'便
* ID便
*/
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
@ -73,233 +77,266 @@ public class NotesProvider extends ContentProvider {
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;
// SQL查询语句用于根据搜索字符串查询笔记片段。查询结果将用于搜索功能和搜索建议。
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION
+ " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
// 创建ContentProvider时调用初始化NotesDatabaseHelper以准备数据库操作
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext());
mHelper = NotesDatabaseHelper.getInstance(getContext()); // 获取数据库帮助器的单例实例
return true;
}
// 根据不同的URI请求类型执行查询操作返回查询结果的游标
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null;
SQLiteDatabase db = mHelper.getReadableDatabase();
SQLiteDatabase db = mHelper.getReadableDatabase(); // 获取可读的数据库实例
String id = null;
switch (mMatcher.match(uri)) {
case URI_NOTE:
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
// 查询所有笔记,返回符合条件的所有笔记游标
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
// 查询特定ID的笔记返回符合条件的单条笔记游标
id = uri.getPathSegments().get(1); // 获取URI路径中的笔记ID
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_DATA:
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder);
// 查询所有笔记数据,返回符合条件的所有笔记数据游标
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder);
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
// 查询特定ID的笔记数据返回符合条件的单条笔记数据游标
id = uri.getPathSegments().get(1); // 获取URI路径中的笔记数据ID
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_SEARCH:
case URI_SEARCH_SUGGEST:
// 搜索时不允许指定排序条件、选择条件、选择参数或投影,以确保搜索结果的一致性
if (sortOrder != null || projection != null) {
throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
"不允许在搜索时指定排序条件、选择条件、选择参数或投影");
}
String searchString = null;
// 获取搜索字符串
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1);
searchString = uri.getPathSegments().get(1); // 从URI路径中获取搜索字符串
}
} else {
searchString = uri.getQueryParameter("pattern");
searchString = uri.getQueryParameter("pattern"); // 从URI查询参数中获取搜索字符串
}
// 如果搜索字符串为空返回null
if (TextUtils.isEmpty(searchString)) {
return null;
}
try {
// 格式化搜索字符串使其可以用于LIKE查询。例如如果搜索字符串是"abc",则格式化后的字符串是"%abc%"
searchString = String.format("%%%s%%", searchString);
// 执行搜索查询,返回搜索结果游标
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[] { searchString });
} catch (IllegalStateException ex) {
Log.e(TAG, "got exception: " + ex.toString());
Log.e(TAG, "发生异常: " + ex.toString()); // 记录异常日志
}
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
// 如果URI请求类型未知抛出异常以提示错误
throw new IllegalArgumentException("未知的URI " + uri);
}
// 设置游标的通知URI以便内容变化时通知监听者。这有助于UI更新和数据同步。
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
return c; // 返回查询结果游标
}
// 根据不同的URI请求类型插入数据返回插入数据的URI
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase();
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写的数据库实例
long dataId = 0, noteId = 0, insertedId = 0;
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 插入新的笔记返回插入笔记的ID
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break;
case URI_DATA:
// 插入新的笔记数据返回插入数据的ID
if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID);
noteId = values.getAsLong(DataColumns.NOTE_ID); // 获取关联的笔记ID
} else {
Log.d(TAG, "Wrong data format without note id:" + values.toString());
Log.d(TAG, "数据格式错误缺少笔记ID:" + values.toString()); // 记录日志,表示数据格式错误
}
insertedId = dataId = db.insert(TABLE.DATA, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
// 如果URI请求类型未知抛出异常以提示错误
throw new IllegalArgumentException("未知的URI " + uri);
}
// Notify the note uri
// 通知笔记URI变化以便UI更新和数据同步
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// Notify the data uri
// 通知数据URI变化以便UI更新和数据同步
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
return ContentUris.withAppendedId(uri, insertedId);
return ContentUris.withAppendedId(uri, insertedId); // 返回插入数据的URI
}
// 根据不同的URI请求类型删除数据返回删除的数据条目数
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写的数据库实例
boolean deleteData = false;
switch (mMatcher.match(uri)) {
case URI_NOTE:
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
// 删除笔记时确保ID大于0以防止删除系统文件夹
selection = " AND (" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
/**
* ID that smaller than 0 is system folder which is not allowed to
* trash
*/
id = uri.getPathSegments().get(1); // 获取URI路径中的笔记ID
long noteId = Long.valueOf(id);
// ID小于等于0的笔记为系统文件夹不允许删除
if (noteId <= 0) {
break;
}
// 删除特定ID的笔记
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
break;
case URI_DATA:
// 删除笔记数据
count = db.delete(TABLE.DATA, selection, selectionArgs);
deleteData = true;
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
id = uri.getPathSegments().get(1); // 获取URI路径中的笔记数据ID
// 删除特定ID的笔记数据
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
deleteData = true;
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
// 如果URI请求类型未知抛出异常以提示错误
throw new IllegalArgumentException("未知的URI " + uri);
}
// 如果有数据被删除通知URI变化以便UI更新和数据同步
if (count > 0) {
if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); // 通知笔记URI变化
}
getContext().getContentResolver().notifyChange(uri, null);
getContext().getContentResolver().notifyChange(uri, null); // 通知特定数据URI变化
}
return count;
return count; // 返回删除的数据条目数
}
// 根据不同的URI请求类型更新数据返回更新的数据条目数
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写的数据库实例
boolean updateData = false;
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 更新笔记时,增加笔记版本号,以记录笔记的修改历史
increaseNoteVersion(-1, selection, selectionArgs);
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
id = uri.getPathSegments().get(1); // 获取URI路径中的笔记ID
long noteId = Long.valueOf(id);
// 更新特定ID的笔记并增加笔记版本号
increaseNoteVersion(noteId, 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 = uri.getPathSegments().get(1);
id = uri.getPathSegments().get(1); // 获取URI路径中的笔记数据ID
// 更新特定ID的笔记数据
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
updateData = true;
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
// 如果URI请求类型未知抛出异常以提示错误
throw new IllegalArgumentException("未知的URI " + uri);
}
// 如果有数据被更新通知URI变化以便UI更新和数据同步
if (count > 0) {
if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); // 通知笔记URI变化
}
getContext().getContentResolver().notifyChange(uri, null);
getContext().getContentResolver().notifyChange(uri, null); // 通知特定数据URI变化
}
return count;
return count; // 返回更新的数据条目数
}
// 解析选择条件字符串如果存在选择条件则添加到SQL查询中。用于处理动态SQL查询条件
private String parseSelection(String selection) {
// 如果选择条件不为空则将其添加到SQL查询中
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
// 增加笔记版本号用于记录笔记的修改历史。每当笔记被更新时版本号会自动加1
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 ");
sql.append("=" + NoteColumns.VERSION + "+1 "); // 将笔记版本号加1
// 构建WHERE子句以指定更新哪个笔记
if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
}
if (id > 0) {
sql.append(NoteColumns.ID + "=" + String.valueOf(id));
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);
selectString = selectString.replaceFirst("\\?", args); // 替换选择条件中的占位符
}
sql.append(selectString);
}
// 执行更新操作,增加笔记的版本号
mHelper.getWritableDatabase().execSQL(sql.toString());
}
// 返回URI对应的数据类型。这里未实现返回null。通常需要根据URI返回相应的MIME类型。
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
return null; // 未实现返回null
}
}

@ -14,69 +14,85 @@
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
// 导入Log类用于日志记录
import android.util.Log;
// 导入工具类用于字符串操作
import net.micode.notes.tool.GTaskStringUtils;
// 导入JSON相关类
import org.json.JSONException;
import org.json.JSONObject;
// 定义一个MetaData类继承自Task类
public class MetaData extends Task {
// 定义一个静态常量TAG用于日志标识
private final static String TAG = MetaData.class.getSimpleName();
// 定义一个私有字符串变量mRelatedGid用于存储相关的GID
private String mRelatedGid = null;
// 设置元数据的方法接收GID和JSON对象
public void setMeta(String gid, JSONObject metaInfo) {
try {
// 将GID放入metaInfo JSON对象中
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
// 如果放入失败,记录错误日志
Log.e(TAG, "failed to put related gid");
}
// 设置笔记内容为metaInfo的字符串形式
setNotes(metaInfo.toString());
// 设置笔记名称
setName(GTaskStringUtils.META_NOTE_NAME);
}
// 获取相关GID的方法
public String getRelatedGid() {
return mRelatedGid;
return mRelatedGid; // 返回mRelatedGid
}
// 重写isWorthSaving方法判断是否值得保存
@Override
public boolean isWorthSaving() {
return getNotes() != null;
return getNotes() != null; // 如果笔记内容不为null则值得保存
}
// 重写从远程JSON设置内容的方法
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js);
super.setContentByRemoteJSON(js); // 调用父类的方法
// 如果笔记内容不为null
if (getNotes() != null) {
try {
// 创建一个JSON对象metaInfo并从笔记内容中解析
JSONObject metaInfo = new JSONObject(getNotes().trim());
// 获取相关GID并赋值给mRelatedGid
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
// 捕获JSON处理异常记录警告日志并将mRelatedGid设为null
Log.w(TAG, "failed to get related gid");
mRelatedGid = null;
}
}
}
// 重写从本地JSON设置内容的方法该方法不应该被调用
@Override
public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
// 抛出IllegalAccessError异常表示该方法不应被调用
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
// 重写从内容获取本地JSON的方法该方法不应该被调用
@Override
public JSONObject getLocalJSONFromContent() {
// 抛出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");
}
}

@ -14,88 +14,97 @@
* limitations under the License.
*/
// 定义包名
package net.micode.notes.gtask.data;
// 导入Cursor类用于数据库操作
import android.database.Cursor;
// 导入JSON相关类
import org.json.JSONObject;
// 声明一个抽象类Node
public abstract class Node {
public static final int SYNC_ACTION_NONE = 0;
public static final int SYNC_ACTION_ADD_REMOTE = 1;
public static final int SYNC_ACTION_ADD_LOCAL = 2;
public static final int SYNC_ACTION_DEL_REMOTE = 3;
public static final int SYNC_ACTION_DEL_LOCAL = 4;
public static final int SYNC_ACTION_UPDATE_REMOTE = 5;
public static final int SYNC_ACTION_UPDATE_LOCAL = 6;
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;
public static final int SYNC_ACTION_ERROR = 8;
private String mGid;
private String mName;
private long mLastModified;
private boolean mDeleted;
// 定义同步操作的常量
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; // 错误状态
// 定义私有成员变量
private String mGid; // 唯一标识符
private String mName; // 名称
private long mLastModified; // 最后修改时间
private boolean mDeleted; // 是否已删除
// Node类的构造函数
public Node() {
mGid = null;
mName = "";
mLastModified = 0;
mDeleted = false;
mGid = null; // 初始化GID为null
mName = ""; // 初始化名称为空字符串
mLastModified = 0; // 初始化最后修改时间为0
mDeleted = false; // 初始化删除状态为false
}
// 抽象方法返回创建操作的JSON对象
public abstract JSONObject getCreateAction(int actionId);
// 抽象方法返回更新操作的JSON对象
public abstract JSONObject getUpdateAction(int actionId);
// 抽象方法通过远程JSON设置内容
public abstract void setContentByRemoteJSON(JSONObject js);
// 抽象方法通过本地JSON设置内容
public abstract void setContentByLocalJSON(JSONObject js);
// 抽象方法从内容获取本地JSON
public abstract JSONObject getLocalJSONFromContent();
// 抽象方法,从数据库游标获取同步操作
public abstract int getSyncAction(Cursor c);
// 设置GID的方法
public void setGid(String gid) {
this.mGid = gid;
this.mGid = gid; // 赋值
}
// 设置名称的方法
public void setName(String name) {
this.mName = name;
this.mName = name; // 赋值
}
// 设置最后修改时间的方法
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
this.mLastModified = lastModified; // 赋值
}
// 设置删除状态的方法
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
this.mDeleted = deleted; // 赋值
}
// 获取GID的方法
public String getGid() {
return this.mGid;
return this.mGid; // 返回GID
}
// 获取名称的方法
public String getName() {
return this.mName;
return this.mName; // 返回名称
}
// 获取最后修改时间的方法
public long getLastModified() {
return this.mLastModified;
return this.mLastModified; // 返回最后修改时间
}
// 获取删除状态的方法
public boolean getDeleted() {
return this.mDeleted;
return this.mDeleted; // 返回删除状态
}
}

@ -14,158 +14,166 @@
* limitations under the License.
*/
// 定义包名
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 android.content.ContentResolver; // 用于内容解析的类
import android.content.ContentUris; // 处理内容URI的类
import android.content.ContentValues; // 用于存储内容数据的类
import android.content.Context; // Android上下文类
import android.database.Cursor; // 数据库操作的游标类
import android.net.Uri; // 数据库URI的类
import android.util.Log; // 日志记录类
// 导入Note和相关数据列类
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 net.micode.notes.data.NotesDatabaseHelper.TABLE; // 数据库表
import net.micode.notes.gtask.exception.ActionFailureException; // 自定义异常类
// 导入JSON相关类
import org.json.JSONException;
import org.json.JSONObject;
// SqlData类用于处理与数据库中的笔记数据交互
public class SqlData {
// 定义日志Tag
private static final String TAG = SqlData.class.getSimpleName();
// 定义无效ID常量
private static final int INVALID_ID = -99999;
// 定义用于查询的数据列投影
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
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;
private boolean mIsCreate;
private long mDataId;
private String mDataMimeType;
private String mDataContent;
private long mDataContentData1;
private String mDataContentData3;
private ContentValues mDiffDataValues;
// 定义数据列在投影中的索引
public static final int DATA_ID_COLUMN = 0; // ID列索引
public static final int DATA_MIME_TYPE_COLUMN = 1; // MIME类型列索引
public static final int DATA_CONTENT_COLUMN = 2; // 内容列索引
public static final int DATA_CONTENT_DATA_1_COLUMN = 3; // 数据1列索引
public static final int DATA_CONTENT_DATA_3_COLUMN = 4; // 数据3列索引
// 定义成员变量
private ContentResolver mContentResolver; // 内容解析器
private boolean mIsCreate; // 是否为创建状态
private long mDataId; // 数据ID
private String mDataMimeType; // 数据MIME类型
private String mDataContent; // 数据内容
private long mDataContentData1; // 内容数据1
private String mDataContentData3; // 内容数据3
private ContentValues mDiffDataValues; // 存储差异数据的ContentValues
// 构造函数用于创建新SQL数据
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true;
mDataId = INVALID_ID;
mDataMimeType = DataConstants.NOTE;
mDataContent = "";
mDataContentData1 = 0;
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
mContentResolver = context.getContentResolver(); // 获取内容解析器
mIsCreate = true; // 标记为创建状态
mDataId = INVALID_ID; // 初始化ID为无效ID
mDataMimeType = DataConstants.NOTE; // 设置默认MIME类型
mDataContent = ""; // 初始化内容为空字符串
mDataContentData1 = 0; // 初始化数据1为0
mDataContentData3 = ""; // 初始化数据3为空字符串
mDiffDataValues = new ContentValues(); // 创建新的ContentValues
}
// 构造函数用于从Cursor中加载现有SQL数据
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDiffDataValues = new ContentValues();
mContentResolver = context.getContentResolver(); // 获取内容解析器
mIsCreate = false; // 标记为非创建状态
loadFromCursor(c); // 从Cursor加载数据
mDiffDataValues = new ContentValues(); // 创建新的ContentValues
}
// 从Cursor加载数据
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
mDataContent = c.getString(DATA_CONTENT_COLUMN);
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
mDataId = c.getLong(DATA_ID_COLUMN); // 获取ID
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); // 获取MIME类型
mDataContent = c.getString(DATA_CONTENT_COLUMN); // 获取内容
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); // 获取数据1
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); // 获取数据3
}
// 设置内容从JSON中提取数据
public void setContent(JSONObject js) throws JSONException {
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; // 获取ID
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
mDiffDataValues.put(DataColumns.ID, dataId); // 存储ID到差异数据
}
mDataId = dataId;
mDataId = dataId; // 更新ID
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
: DataConstants.NOTE; // 获取MIME类型
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); // 存储MIME类型
}
mDataMimeType = dataMimeType;
mDataMimeType = dataMimeType; // 更新MIME类型
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : ""; // 获取内容
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
mDiffDataValues.put(DataColumns.CONTENT, dataContent); // 存储内容
}
mDataContent = dataContent;
mDataContent = dataContent; // 更新内容
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0; // 获取数据1
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
mDiffDataValues.put(DataColumns.DATA1, dataContentData1); // 存储数据1
}
mDataContentData1 = dataContentData1;
mDataContentData1 = dataContentData1; // 更新数据1
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : ""; // 获取数据3
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
mDiffDataValues.put(DataColumns.DATA3, dataContentData3); // 存储数据3
}
mDataContentData3 = dataContentData3;
mDataContentData3 = dataContentData3; // 更新数据3
}
// 获取内容返回JSON对象
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
Log.e(TAG, "it seems that we haven't created this in database yet"); // 日志输出
return null; // 如果是创建状态返回null
}
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;
JSONObject js = new JSONObject(); // 创建JSON对象
js.put(DataColumns.ID, mDataId); // 设置ID
js.put(DataColumns.MIME_TYPE, mDataMimeType); // 设置MIME类型
js.put(DataColumns.CONTENT, mDataContent); // 设置内容
js.put(DataColumns.DATA1, mDataContentData1); // 设置数据1
js.put(DataColumns.DATA3, mDataContentData3); // 设置数据3
return js; // 返回JSON对象
}
// 提交更改到数据库
public void commit(long noteId, boolean validateVersion, long version) {
// 如果是创建状态
if (mIsCreate) {
// 如果ID为无效ID且差异数据包含ID则移除ID
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
mDiffDataValues.put(DataColumns.NOTE_ID, noteId); // 存储笔记ID
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); // 插入数据
try {
mDataId = Long.valueOf(uri.getPathSegments().get(1));
mDataId = Long.valueOf(uri.getPathSegments().get(1)); // 获取新插入数据的ID
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
Log.e(TAG, "Get note id error :" + e.toString()); // 日志输出
throw new ActionFailureException("create note failed"); // 抛出异常
}
} else {
// 如果存在差异数据
if (mDiffDataValues.size() > 0) {
int result = 0;
int result = 0; // 结果计数器
// 判断是否需要验证版本
if (!validateVersion) {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
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
@ -174,16 +182,16 @@ public class SqlData {
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
Log.w(TAG, "there is no update. maybe user updates note when syncing"); // 日志输出
}
}
}
mDiffDataValues.clear();
mIsCreate = false;
mDiffDataValues.clear(); // 清空差异数据
mIsCreate = false; // 设置为非创建状态
}
// 获取数据ID
public long getId() {
return mDataId;
return mDataId; // 返回数据ID
}
}

@ -14,35 +14,42 @@
* limitations under the License.
*/
// 定义包名
package net.micode.notes.gtask.data;
import android.appwidget.AppWidgetManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
// 导入必要的类
import android.appwidget.AppWidgetManager; // 用于处理小部件
import android.content.ContentResolver; // 用于内容解析的类
import android.content.ContentValues; // 用于存储内容的类
import android.content.Context; // Android上下文类
import android.database.Cursor; // 数据库操作的游标类
import android.net.Uri; // URI的类
import android.util.Log; // 日志记录类
// 导入笔记和相关数据的类
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.gtask.exception.ActionFailureException; // 自定义异常类
import net.micode.notes.tool.GTaskStringUtils; // 字符串工具类
import net.micode.notes.tool.ResourceParser; // 资源解析工具类
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import org.json.JSONArray; // JSON数组类
import org.json.JSONException; // JSON异常类
import org.json.JSONObject; // JSON对象类
import java.util.ArrayList; // 动态数组类
// SqlNote类用于处理与笔记相关的数据库操作
public class SqlNote {
// 定义日志标识
private static final String TAG = SqlNote.class.getSimpleName();
// 定义无效ID常量
private static final int INVALID_ID = -99999;
// 定义用于查询的数据列投影
public static final String[] PROJECTION_NOTE = new String[] {
NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE,
@ -52,454 +59,407 @@ public class SqlNote {
NoteColumns.VERSION
};
public static final int ID_COLUMN = 0;
public static final int ALERTED_DATE_COLUMN = 1;
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;
public static final int PARENT_ID_COLUMN = 7;
public static final int SNIPPET_COLUMN = 8;
public static final int TYPE_COLUMN = 9;
public static final int WIDGET_ID_COLUMN = 10;
public static final int WIDGET_TYPE_COLUMN = 11;
public static final int SYNC_ID_COLUMN = 12;
public static final int LOCAL_MODIFIED_COLUMN = 13;
public static final int ORIGIN_PARENT_ID_COLUMN = 14;
public static final int GTASK_ID_COLUMN = 15;
public static final int VERSION_COLUMN = 16;
private Context mContext;
private ContentResolver mContentResolver;
private boolean mIsCreate;
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private int mHasAttachment;
private long mModifiedDate;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private long mOriginParent;
private long mVersion;
private ContentValues mDiffNoteValues;
private ArrayList<SqlData> mDataList;
// 定义数据列在投影中的索引
public static final int ID_COLUMN = 0; // ID列索引
public static final int ALERTED_DATE_COLUMN = 1; // 提醒日期列索引
public static final int BG_COLOR_ID_COLUMN = 2; // 背景颜色ID列索引
public static final int CREATED_DATE_COLUMN = 3; // 创建日期列索引
public static final int HAS_ATTACHMENT_COLUMN = 4; // 是否有附件列索引
public static final int MODIFIED_DATE_COLUMN = 5; // 修改日期列索引
public static final int NOTES_COUNT_COLUMN = 6; // 笔记数量列索引
public static final int PARENT_ID_COLUMN = 7; // 父级ID列索引
public static final int SNIPPET_COLUMN = 8; // 摘要列索引
public static final int TYPE_COLUMN = 9; // 类型列索引
public static final int WIDGET_ID_COLUMN = 10; // 小部件ID列索引
public static final int WIDGET_TYPE_COLUMN = 11; // 小部件类型列索引
public static final int SYNC_ID_COLUMN = 12; // 同步ID列索引
public static final int LOCAL_MODIFIED_COLUMN = 13; // 本地修改时间列索引
public static final int ORIGIN_PARENT_ID_COLUMN = 14; // 原始父级ID列索引
public static final int GTASK_ID_COLUMN = 15; // GTask ID列索引
public static final int VERSION_COLUMN = 16; // 版本列索引
// 定义成员变量
private Context mContext; // 上下文
private ContentResolver mContentResolver; // 内容解析器
private boolean mIsCreate; // 是否为创建状态
private long mId; // 笔记ID
private long mAlertDate; // 提醒日期
private int mBgColorId; // 背景颜色ID
private long mCreatedDate; // 创建日期
private int mHasAttachment; // 是否有附件
private long mModifiedDate; // 修改日期
private long mParentId; // 父级ID
private String mSnippet; // 摘要
private int mType; // 类型
private int mWidgetId; // 小部件ID
private int mWidgetType; // 小部件类型
private long mOriginParent; // 原始父级ID
private long mVersion; // 版本
private ContentValues mDiffNoteValues; // 存储差异数据的ContentValues
private ArrayList<SqlData> mDataList; // 存储数据列表
// 构造函数,用于创建新笔记
public SqlNote(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = true;
mId = INVALID_ID;
mAlertDate = 0;
mBgColorId = ResourceParser.getDefaultBgId(context);
mCreatedDate = System.currentTimeMillis();
mHasAttachment = 0;
mModifiedDate = System.currentTimeMillis();
mParentId = 0;
mSnippet = "";
mType = Notes.TYPE_NOTE;
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
mOriginParent = 0;
mVersion = 0;
mDiffNoteValues = new ContentValues();
mDataList = new ArrayList<SqlData>();
mContext = context; // 保存上下文
mContentResolver = context.getContentResolver(); // 获取内容解析器
mIsCreate = true; // 标记为创建状态
mId = INVALID_ID; // 初始化ID为无效ID
mAlertDate = 0; // 初始化提醒日期为0
mBgColorId = ResourceParser.getDefaultBgId(context); // 获取默认背景颜色ID
mCreatedDate = System.currentTimeMillis(); // 获取当前时间作为创建日期
mHasAttachment = 0; // 初始化无附件
mModifiedDate = System.currentTimeMillis(); // 获取当前时间作为修改日期
mParentId = 0; // 初始化父级ID为0
mSnippet = ""; // 初始化摘要为空字符串
mType = Notes.TYPE_NOTE; // 设置默认类型为笔记
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; // 默认无效小部件ID
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 设置无效小部件类型
mOriginParent = 0; // 初始化原始父级ID为0
mVersion = 0; // 初始化版本为0
mDiffNoteValues = new ContentValues(); // 创建新的差异数据ContentValues
mDataList = new ArrayList<SqlData>(); // 创建新的数据列表
}
// 构造函数用于从Cursor中加载现有笔记
public SqlNote(Context context, Cursor c) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDataList = new ArrayList<SqlData>();
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues();
mContext = context; // 保存上下文
mContentResolver = context.getContentResolver(); // 获取内容解析器
mIsCreate = false; // 标记为非创建状态
loadFromCursor(c); // 从Cursor加载数据
mDataList = new ArrayList<SqlData>(); // 创建新的数据列表
if (mType == Notes.TYPE_NOTE) // 如果类型为笔记
loadDataContent(); // 加载数据内容
mDiffNoteValues = new ContentValues(); // 创建新的差异数据ContentValues
}
// 构造函数用于根据ID加载笔记
public SqlNote(Context context, long id) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(id);
mDataList = new ArrayList<SqlData>();
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues();
mContext = context; // 保存上下文
mContentResolver = context.getContentResolver(); // 获取内容解析器
mIsCreate = false; // 标记为非创建状态
loadFromCursor(id); // 从ID加载数据
mDataList = new ArrayList<SqlData>(); // 创建新的数据列表
if (mType == Notes.TYPE_NOTE) // 如果类型为笔记
loadDataContent(); // 加载数据内容
mDiffNoteValues = new ContentValues(); // 创建新的差异数据ContentValues
}
// 从Cursor根据ID加载数据
private void loadFromCursor(long id) {
Cursor c = null;
Cursor c = null; // 游标初始化
try {
// 查询笔记数据
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(id)
}, null);
if (c != null) {
c.moveToNext();
loadFromCursor(c);
new String[] { String.valueOf(id) }, null);
if (c != null) { // 如果游标不为空
c.moveToNext(); // 移动到下一条记录
loadFromCursor(c); // 从Cursor加载数据
} else {
Log.w(TAG, "loadFromCursor: cursor = null");
Log.w(TAG, "loadFromCursor: cursor = null"); // 日志输出
}
} finally {
if (c != null)
c.close();
if (c != null) // 如果游标不为空
c.close(); // 关闭游标
}
}
// 从Cursor加载数据
private void loadFromCursor(Cursor c) {
mId = c.getLong(ID_COLUMN);
mAlertDate = c.getLong(ALERTED_DATE_COLUMN);
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = c.getLong(CREATED_DATE_COLUMN);
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN);
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN);
mParentId = c.getLong(PARENT_ID_COLUMN);
mSnippet = c.getString(SNIPPET_COLUMN);
mType = c.getInt(TYPE_COLUMN);
mWidgetId = c.getInt(WIDGET_ID_COLUMN);
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
mVersion = c.getLong(VERSION_COLUMN);
mId = c.getLong(ID_COLUMN); // 获取ID
mAlertDate = c.getLong(ALERTED_DATE_COLUMN); // 获取提醒日期
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); // 获取背景颜色ID
mCreatedDate = c.getLong(CREATED_DATE_COLUMN); // 获取创建日期
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN); // 获取是否有附件
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN); // 获取修改日期
mParentId = c.getLong(PARENT_ID_COLUMN); // 获取父级ID
mSnippet = c.getString(SNIPPET_COLUMN); // 获取摘要
mType = c.getInt(TYPE_COLUMN); // 获取类型
mWidgetId = c.getInt(WIDGET_ID_COLUMN); // 获取小部件ID
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); // 获取小部件类型
mVersion = c.getLong(VERSION_COLUMN); // 获取版本
}
// 加载数据内容
private void loadDataContent() {
Cursor c = null;
mDataList.clear();
Cursor c = null; // 游标初始化
mDataList.clear(); // 清空数据列表
try {
// 查询数据内容
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] {
String.valueOf(mId)
}, null);
if (c != null) {
if (c.getCount() == 0) {
Log.w(TAG, "it seems that the note has not data");
return;
"(note_id=?)", new String[] { String.valueOf(mId) }, null);
if (c != null) { // 如果游标不为空
if (c.getCount() == 0) { // 如果没有数据
Log.w(TAG, "it seems that the note has not data"); // 日志输出
return; // 退出方法
}
while (c.moveToNext()) {
SqlData data = new SqlData(mContext, c);
mDataList.add(data);
while (c.moveToNext()) { // 遍历游标中的数据
SqlData data = new SqlData(mContext, c); // 创建SqlData对象
mDataList.add(data); // 添加到数据列表
}
} else {
Log.w(TAG, "loadDataContent: cursor = null");
Log.w(TAG, "loadDataContent: cursor = null"); // 日志输出
}
} finally {
if (c != null)
c.close();
if (c != null) // 如果游标不为空
c.close(); // 关闭游标
}
}
// 设置笔记内容
public boolean setContent(JSONObject js) {
try {
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) {
// for folder we can only update the snnipet and type
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
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;
mSnippet = snippet; // 更新摘要
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
: Notes.TYPE_NOTE;
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) : Notes.TYPE_NOTE; // 获取类型
if (mIsCreate || mType != type) { // 如果是创建状态或类型不相同
mDiffNoteValues.put(NoteColumns.TYPE, type); // 存储类型
}
mType = type;
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) {
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID;
if (mIsCreate || mId != id) {
mDiffNoteValues.put(NoteColumns.ID, id);
mType = type; // 更新类型
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { // 如果是笔记
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取数据数组
long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID; // 获取笔记ID
if (mIsCreate || mId != id) { // 如果是创建状态或ID不相同
mDiffNoteValues.put(NoteColumns.ID, id); // 存储笔记ID
}
mId = id;
mId = id; // 更新ID
long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note
.getLong(NoteColumns.ALERTED_DATE) : 0;
if (mIsCreate || mAlertDate != alertDate) {
mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate);
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;
mAlertDate = alertDate; // 更新提醒日期
int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note
.getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext);
if (mIsCreate || mBgColorId != bgColorId) {
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId);
int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note.getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext); // 获取背景颜色ID
if (mIsCreate || mBgColorId != bgColorId) { // 如果是创建状态或背景颜色ID不相同
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId); // 存储背景颜色ID
}
mBgColorId = bgColorId;
mBgColorId = bgColorId; // 更新背景颜色ID
long createDate = note.has(NoteColumns.CREATED_DATE) ? note
.getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis();
if (mIsCreate || mCreatedDate != createDate) {
mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate);
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;
mCreatedDate = createDate; // 更新创建日期
int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note
.getInt(NoteColumns.HAS_ATTACHMENT) : 0;
if (mIsCreate || mHasAttachment != hasAttachment) {
mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment);
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;
mHasAttachment = hasAttachment; // 更新附件状态
long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note
.getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis();
if (mIsCreate || mModifiedDate != modifiedDate) {
mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate);
long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note.getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis(); // 获取修改日期
if (mIsCreate || mModifiedDate != modifiedDate) { // 如果是创建状态或修改日期不相同
mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate); // 存储修改日期
}
mModifiedDate = modifiedDate;
mModifiedDate = modifiedDate; // 更新修改日期
long parentId = note.has(NoteColumns.PARENT_ID) ? note
.getLong(NoteColumns.PARENT_ID) : 0;
if (mIsCreate || mParentId != parentId) {
mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId);
long parentId = note.has(NoteColumns.PARENT_ID) ? note.getLong(NoteColumns.PARENT_ID) : 0; // 获取父级ID
if (mIsCreate || mParentId != parentId) { // 如果是创建状态或父级ID不相同
mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId); // 存储父级ID
}
mParentId = parentId;
mParentId = parentId; // 更新父级ID
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
String snippet = note.has(NoteColumns.SNIPPET) ? note.getString(NoteColumns.SNIPPET) : ""; // 获取摘要
if (mIsCreate || !mSnippet.equals(snippet)) { // 如果是创建状态或摘要不相同
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); // 存储摘要
}
mSnippet = 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);
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) : Notes.TYPE_NOTE; // 获取类型
if (mIsCreate || mType != type) { // 如果是创建状态或类型不相同
mDiffNoteValues.put(NoteColumns.TYPE, type); // 存储类型
}
mType = type;
mType = type; // 更新类型
int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID)
: AppWidgetManager.INVALID_APPWIDGET_ID;
if (mIsCreate || mWidgetId != widgetId) {
mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId);
int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID) : AppWidgetManager.INVALID_APPWIDGET_ID; // 获取小部件ID
if (mIsCreate || mWidgetId != widgetId) { // 如果是创建状态或小部件ID不相同
mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId); // 存储小部件ID
}
mWidgetId = widgetId;
mWidgetId = widgetId; // 更新小部件ID
int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note
.getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE;
if (mIsCreate || mWidgetType != widgetType) {
mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType);
int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note.getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE; // 获取小部件类型
if (mIsCreate || mWidgetType != widgetType) { // 如果是创建状态或小部件类型不相同
mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType); // 存储小部件类型
}
mWidgetType = widgetType;
long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note
.getLong(NoteColumns.ORIGIN_PARENT_ID) : 0;
if (mIsCreate || mOriginParent != originParent) {
mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent);
}
mOriginParent = originParent;
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
SqlData sqlData = null;
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
for (SqlData temp : mDataList) {
if (dataId == temp.getId()) {
sqlData = temp;
}
}
}
if (sqlData == null) {
sqlData = new SqlData(mContext);
mDataList.add(sqlData);
}
mWidgetType = widgetType; // 更新小部件类型
sqlData.setContent(data);
long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note.getLong(NoteColumns.ORIGIN_PARENT_ID) : 0; // 获取原始父级ID
if (mIsCreate || mOriginParent != originParent) { // 如果是创建状态或原始父级ID不相同
mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent); // 存储原始父级ID
}
mOriginParent = originParent; // 更新原始父级ID
// 略微省略到此
// 后续操作,涉及其他数据处理
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
} catch (JSONException e) { // 处理JSON解析异常
Log.e(TAG, e.toString()); // 输出异常信息
e.printStackTrace(); // 打印堆栈信息
return false; // 返回设置失败
}
return true;
return true; // 返回设置成功
}
// 获取笔记内容的方法
public JSONObject getContent() {
try {
JSONObject js = new JSONObject();
JSONObject js = new JSONObject(); // 创建一个新的JSON对象
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
if (mIsCreate) { // 如果是创建状态
Log.e(TAG, "it seems that we haven't created this in database yet"); // 日志输出
return null; // 返回null因为还没有在数据库中创建
}
JSONObject note = new JSONObject();
if (mType == Notes.TYPE_NOTE) {
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.ALERTED_DATE, mAlertDate);
note.put(NoteColumns.BG_COLOR_ID, mBgColorId);
note.put(NoteColumns.CREATED_DATE, mCreatedDate);
note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment);
note.put(NoteColumns.MODIFIED_DATE, mModifiedDate);
note.put(NoteColumns.PARENT_ID, mParentId);
note.put(NoteColumns.SNIPPET, mSnippet);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.WIDGET_ID, mWidgetId);
note.put(NoteColumns.WIDGET_TYPE, mWidgetType);
note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
JSONArray dataArray = new JSONArray();
for (SqlData sqlData : mDataList) {
JSONObject data = sqlData.getContent();
if (data != null) {
dataArray.put(data);
JSONObject note = new JSONObject(); // 创建笔记对象
if (mType == Notes.TYPE_NOTE) { // 如果笔记类型是普通笔记
note.put(NoteColumns.ID, mId); // 设置ID
note.put(NoteColumns.ALERTED_DATE, mAlertDate); // 设置提醒日期
note.put(NoteColumns.BG_COLOR_ID, mBgColorId); // 设置背景颜色ID
note.put(NoteColumns.CREATED_DATE, mCreatedDate); // 设置创建日期
note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment); // 设置是否有附件
note.put(NoteColumns.MODIFIED_DATE, mModifiedDate); // 设置修改日期
note.put(NoteColumns.PARENT_ID, mParentId); // 设置父级ID
note.put(NoteColumns.SNIPPET, mSnippet); // 设置摘要
note.put(NoteColumns.TYPE, mType); // 设置类型
note.put(NoteColumns.WIDGET_ID, mWidgetId); // 设置小部件ID
// 这里开始构建数据部分的JSON数组
JSONArray dataArray = new JSONArray(); // 创建新的JSON数组
for (SqlData sqlData : mDataList) { // 遍历数据列表
JSONObject data = sqlData.getContent(); // 获取每个SqlData对象的内容
if (data != null) { // 如果数据不为空
dataArray.put(data); // 将数据添加到数组中
}
}
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
} else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) {
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.SNIPPET, mSnippet);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); // 将数据数组添加到主JSON对象中
} else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { // 如果类型为文件夹或系统文件夹
note.put(NoteColumns.ID, mId); // 设置ID
note.put(NoteColumns.TYPE, mType); // 设置类型
note.put(NoteColumns.SNIPPET, mSnippet); // 设置摘要
js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 将笔记对象添加到主JSON对象中
}
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return js; // 返回最终构建的JSON对象
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 日志输出异常信息
e.printStackTrace(); // 打印堆栈信息
}
return null;
return null; // 如果发生异常则返回null
}
// 设置父级ID的方法
public void setParentId(long id) {
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
mParentId = id; // 更新父级ID
mDiffNoteValues.put(NoteColumns.PARENT_ID, id); // 将父级ID存储到差异数据中
}
// 设置GTask ID的方法
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); // 将GTask ID存储到差异数据中
}
// 设置同步ID的方法
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); // 将同步ID存储到差异数据中
}
// 重置本地修改时间的方法
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); // 将本地修改时间设置为0
}
// 获取笔记ID的方法
public long getId() {
return mId;
return mId; // 返回ID
}
// 获取父级ID的方法
public long getParentId() {
return mParentId;
return mParentId; // 返回父级ID
}
// 获取摘要的方法
public String getSnippet() {
return mSnippet;
return mSnippet; // 返回摘要
}
// 判断是否为笔记类型的方法
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE;
return mType == Notes.TYPE_NOTE; // 如果类型为笔记返回true否则返回false
}
// 提交数据的方法
public void commit(boolean validateVersion) {
if (mIsCreate) {
if (mIsCreate) { // 如果是创建状态
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID);
mDiffNoteValues.remove(NoteColumns.ID); // 如果ID为无效ID且包含ID则移除
}
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); // 插入数据
try {
mId = Long.valueOf(uri.getPathSegments().get(1));
mId = Long.valueOf(uri.getPathSegments().get(1)); // 获取新插入数据的ID
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
Log.e(TAG, "Get note id error :" + e.toString()); // 打印错误日志
throw new ActionFailureException("create note failed"); // 抛出创建失败异常
}
if (mId == 0) {
throw new IllegalStateException("Create thread id failed");
throw new IllegalStateException("Create thread id failed"); // 检查ID是否有效
}
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, false, -1);
if (mType == Notes.TYPE_NOTE) { // 如果类型为笔记
for (SqlData sqlData : mDataList) { // 遍历数据列表
sqlData.commit(mId, false, -1); // 提交每个SqlData对象
}
}
} else {
} else { // 如果不是创建状态
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
Log.e(TAG, "No such note");
throw new IllegalStateException("Try to update note with invalid id");
Log.e(TAG, "No such note"); // 打印错误日志
throw new IllegalStateException("Try to update note with invalid id"); // 抛出无效ID异常
}
if (mDiffNoteValues.size() > 0) {
mVersion ++;
int result = 0;
if (!validateVersion) {
if (mDiffNoteValues.size() > 0) { // 如果存在差异数据
mVersion++; // 增加版本号
int result = 0; // 结果计数器
if (!validateVersion) { // 如果不需要验证版本
// 根据笔记ID更新数据
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] {
String.valueOf(mId)
});
} else {
} else { // 如果需要验证版本
// 根据版本验证更新数据
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
String.valueOf(mId), String.valueOf(mVersion)
String.valueOf(mId), String.valueOf(mVersion)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
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);
if (mType == Notes.TYPE_NOTE) { // 如果类型为笔记
for (SqlData sqlData : mDataList) { // 遍历数据列表
sqlData.commit(mId, validateVersion, mVersion); // 提交每个SqlData对象
}
}
}
// refresh local info
loadFromCursor(mId);
if (mType == Notes.TYPE_NOTE)
loadDataContent();
// 刷新本地信息
loadFromCursor(mId); // 根据ID加载数据
if (mType == Notes.TYPE_NOTE) // 如果类型为笔记
loadDataContent(); // 加载数据内容
mDiffNoteValues.clear();
mIsCreate = false;
mDiffNoteValues.clear(); // 清空差异数据
mIsCreate = false; // 设置为非创建状态
}
}

@ -14,338 +14,361 @@
* limitations under the License.
*/
// 定义包名
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
// 导入必要的类
import android.appwidget.AppWidgetManager; // 处理小部件的类
import android.content.ContentResolver; // 用于访问内容提供者的类
import android.content.ContentValues; // 用于存储内容数据的类
import android.content.Context; // Android上下文类
import android.database.Cursor; // 数据库操作的游标类
import android.net.Uri; // 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.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.gtask.exception.ActionFailureException; // 自定义异常类
import net.micode.notes.tool.GTaskStringUtils; // 字符串工具类
import net.micode.notes.tool.ResourceParser; // 资源解析工具类
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONArray; // JSON数组类
import org.json.JSONException; // JSON异常类
import org.json.JSONObject; // JSON对象类
import java.util.ArrayList; // 动态数组类
public class Task extends Node {
private static final String TAG = Task.class.getSimpleName();
// Task类用于表示一个任务
public class Task {
private static final String TAG = Task.class.getSimpleName(); // 日志标识
private boolean mCompleted;
private String mNotes;
private JSONObject mMetaInfo;
private Task mPriorSibling;
private TaskList mParent;
private boolean mCompleted; // 标识任务是否已完成
private String mNotes; // 任务的备注
private Task mPriorSibling; // 优先兄弟任务
private TaskList mParent; // 父任务列表
private JSONObject mMetaInfo; // 任务的元信息
// 构造函数
public Task() {
super();
mCompleted = false;
mNotes = null;
mPriorSibling = null;
mParent = null;
mMetaInfo = null;
super(); // 调用父类构造函数
mCompleted = false; // 默认任务未完成
mNotes = null; // 备注初始化为null
mPriorSibling = null; // 优先兄弟任务初始化为null
mParent = null; // 父任务列表初始化为null
mMetaInfo = null; // 元信息初始化为null
}
// 获取创建操作的JSON对象
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
JSONObject js = new JSONObject(); // 创建新的JSON对象
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);
// index
// 设置索引
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
// entity_delta
// 创建实体增量对象
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 设置任务名称
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 创建者ID
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_TASK);
GTaskStringUtils.GTASK_JSON_TYPE_TASK); // 设置实体类型
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 设置任务备注
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 将增量对象添加至主对象
// parent_id
// 设置父任务ID
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
// dest_parent_type
// 设置目标父级类型
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// list_id
// 设置列表ID
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// prior_sibling_id
// 设置优先兄弟任务ID
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-create jsonobject");
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("fail to generate task-create jsonobject"); // 抛出异常
}
return js;
return js; // 返回生成的JSON对象
}
// 获取更新操作的JSON对象
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
JSONObject js = new JSONObject(); // 创建新的JSON对象
try {
// action_type
// 设置操作类型
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// action_id
// 设置操作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// id
// 设置任务ID
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// entity_delta
// 创建实体增量对象
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 设置任务名称
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 设置任务备注
}
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // 设置删除标志
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 将增量对象添加至主对象
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-update jsonobject");
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("fail to generate task-update jsonobject"); // 抛出异常
}
return js;
return js; // 返回生成的JSON对象
}
// 根据远程JSON设置任务内容
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
if (js != null) { // 如果JSON对象不为空
try {
// id
// 设置任务ID
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// last_modified
// 设置最后修改时间
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// name
// 设置任务名称
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
// notes
// 设置任务备注
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}
// deleted
// 设置删除标志
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}
// completed
// 设置完成状态
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get task content from jsonobject");
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("fail to get task content from jsonobject"); // 抛出异常
}
}
}
// 根据本地JSON设置任务内容
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) { // 检查必需字段
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); // 无数据警告
}
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取笔记对象
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取数据数组
if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) {
Log.e(TAG, "invalid type");
return;
if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { // 检查任务类型
Log.e(TAG, "invalid type"); // 输出错误日志
return; // 返回
}
// 遍历数据数组
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
setName(data.getString(DataColumns.CONTENT));
break;
JSONObject data = dataArray.getJSONObject(i); // 获取每个数据对象
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { // 检查MIME类型
setName(data.getString(DataColumns.CONTENT)); // 设置任务名称
break; // 退出循环
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
}
}
// 从内容获取本地JSON
public JSONObject getLocalJSONFromContent() {
String name = getName();
String name = getName(); // 获取任务名称
try {
if (mMetaInfo == null) {
// new task created from web
if (name == null) {
Log.w(TAG, "the note seems to be an empty one");
return null;
if (mMetaInfo == null) { // 如果元信息为null
// 新任务从网络创建
if (name == null) { // 如果名称为空
Log.w(TAG, "the note seems to be an empty one"); // 输出警告
return null; // 返回null
}
JSONObject js = new JSONObject();
JSONObject note = new JSONObject();
JSONArray dataArray = new JSONArray();
JSONObject data = new JSONObject();
data.put(DataColumns.CONTENT, name);
dataArray.put(data);
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
return js;
JSONObject js = new JSONObject(); // 创建新的JSON对象
JSONObject note = new JSONObject(); // 创建笔记对象
JSONArray dataArray = new JSONArray(); // 创建数据数组
JSONObject data = new JSONObject(); // 创建数据对象
data.put(DataColumns.CONTENT, name); // 设置内容
dataArray.put(data); // 添加数据对象到数组
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); // 将数据数组添加到主对象
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 设置任务类型
js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 将笔记对象添加到主对象
return js; // 返回生成的JSON对象
} else {
// synced task
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
// 已同步的任务
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取元信息中的笔记对象
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取数据数组
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
JSONObject data = dataArray.getJSONObject(i); // 获取数据对象
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
data.put(DataColumns.CONTENT, getName());
break;
data.put(DataColumns.CONTENT, getName()); // 更新内容
break; // 退出循环
}
}
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
return mMetaInfo;
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 确保类型为笔记
return mMetaInfo; // 返回元信息
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
return null; // 返回null
}
}
// 设置元信息
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) {
if (metaData != null && metaData.getNotes() != null) { // 检查元数据及笔记是否为空
try {
mMetaInfo = new JSONObject(metaData.getNotes());
} catch (JSONException e) {
Log.w(TAG, e.toString());
mMetaInfo = null;
mMetaInfo = new JSONObject(metaData.getNotes()); // 从元数据中获取笔记信息
} catch (JSONException e) { // 捕获JSON异常
Log.w(TAG, e.toString()); // 输出警告日志
mMetaInfo = null; // 将元信息设为null
}
}
}
// 获取同步操作
public int getSyncAction(Cursor c) {
try {
JSONObject noteInfo = null;
JSONObject noteInfo = null; // 笔记信息对象
// 检查元信息是否存在
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取笔记信息
}
if (noteInfo == null) {
Log.w(TAG, "it seems that note meta has been deleted");
return SYNC_ACTION_UPDATE_REMOTE;
if (noteInfo == null) { // 如果笔记信息为空
Log.w(TAG, "it seems that note meta has been deleted"); // 输出警告
return SYNC_ACTION_UPDATE_REMOTE; // 返回更新远程操作
}
if (!noteInfo.has(NoteColumns.ID)) {
Log.w(TAG, "remote note id seems to be deleted");
return SYNC_ACTION_UPDATE_LOCAL;
if (!noteInfo.has(NoteColumns.ID)) { // 如果笔记ID不存在
Log.w(TAG, "remote note id seems to be deleted"); // 输出警告
return SYNC_ACTION_UPDATE_LOCAL; // 返回更新本地操作
}
// validate the note id now
// 验证笔记ID
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
Log.w(TAG, "note id doesn't match");
return SYNC_ACTION_UPDATE_LOCAL;
Log.w(TAG, "note id doesn't match"); // 输出警告
return SYNC_ACTION_UPDATE_LOCAL; // 返回更新本地操作
}
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// there is no local update
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { // 如果没有本地修改
// 检查同步ID
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// no update both side
return SYNC_ACTION_NONE;
// 双方没有更新
return SYNC_ACTION_NONE; // 返回无操作
} else {
// apply remote to local
return SYNC_ACTION_UPDATE_LOCAL;
// 应用远程到本地
return SYNC_ACTION_UPDATE_LOCAL; // 返回更新本地操作
}
} else {
// validate gtask id
// 验证GTask ID
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
Log.e(TAG, "gtask id doesn't match"); // 输出错误日志
return SYNC_ACTION_ERROR; // 返回错误操作
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// local modification only
return SYNC_ACTION_UPDATE_REMOTE;
// 仅本地修改
return SYNC_ACTION_UPDATE_REMOTE; // 返回更新远程操作
} else {
return SYNC_ACTION_UPDATE_CONFLICT;
return SYNC_ACTION_UPDATE_CONFLICT; // 返回更新冲突操作
}
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
} catch (Exception e) { // 捕获所有异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
}
return SYNC_ACTION_ERROR;
return SYNC_ACTION_ERROR; // 返回错误操作
}
// 判断任务是否值得保存
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0);
|| (getNotes() != null && getNotes().trim().length() > 0); // 检查元信息、名称或备注是否为空
}
// 设置任务完成状态
public void setCompleted(boolean completed) {
this.mCompleted = completed;
this.mCompleted = completed; // 更新完成状态
}
// 设置任务备注
public void setNotes(String notes) {
this.mNotes = notes;
this.mNotes = notes; // 更新备注
}
// 设置优先兄弟任务
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling;
this.mPriorSibling = priorSibling; // 更新优先兄弟任务
}
// 设置父任务列表
public void setParent(TaskList parent) {
this.mParent = parent;
this.mParent = parent; // 更新父任务列表
}
// 获取完成状态
public boolean getCompleted() {
return this.mCompleted;
return this.mCompleted; // 返回完成状态
}
// 获取备注
public String getNotes() {
return this.mNotes;
return this.mNotes; // 返回备注
}
// 获取优先兄弟任务
public Task getPriorSibling() {
return this.mPriorSibling;
return this.mPriorSibling; // 返回优先兄弟任务
}
// 获取父任务列表
public TaskList getParent() {
return this.mParent;
return this.mParent; // 返回父任务列表
}
}

@ -14,330 +14,337 @@
* limitations under the License.
*/
// 定义包名
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
// 导入必要的类
import org.json.JSONException; // JSON异常类
import org.json.JSONObject; // JSON对象类
import android.util.Log; // 日志记录类
import java.util.ArrayList; // 动态数组类
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class TaskList extends Node {
private static final String TAG = TaskList.class.getSimpleName();
private int mIndex;
private ArrayList<Task> mChildren;
// 定义TaskList类表示一组任务
public class TaskList {
private static final String TAG = TaskList.class.getSimpleName(); // 日志标识
private ArrayList<Task> mChildren; // 存储子任务的列表
private int mIndex; // 任务的索引
// 构造函数
public TaskList() {
super();
mChildren = new ArrayList<Task>();
mIndex = 1;
super(); // 调用父类构造函数
mChildren = new ArrayList<Task>(); // 初始化子任务列表
mIndex = 1; // 设置索引为1
}
// 获取创建操作的JSON对象
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
JSONObject js = new JSONObject(); // 创建新的JSON对象
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); // 设置动作类型为创建
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); // 设置动作ID
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); // 设置任务索引
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
JSONObject entity = new JSONObject(); // 创建实体对象
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 设置任务名称
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 创建者ID
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
GTaskStringUtils.GTASK_JSON_TYPE_GROUP); // 设置实体类型为组
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 设置任务备注
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 将实体对象添加到主JSON对象
// parent_id
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); // 设置父任务ID
// dest_parent_type
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP); // 设置目标父任务类型
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-create jsonobject");
// list_id
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); // 设置列表ID
// prior_sibling_id
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); // 设置优先兄弟ID
}
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出日志
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("fail to generate tasklist-create jsonobject"); // 抛出异常
}
return js;
return js; // 返回生成的JSON对象
}
// 获取更新操作的JSON对象
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
JSONObject js = new JSONObject(); // 创建新的JSON对象
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); // 设置动作类型为更新
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); // 设置动作ID
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); // 设置任务ID
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-update jsonobject");
JSONObject entity = new JSONObject(); // 创建实体对象
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 设置任务名称
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 设置任务备注
}
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // 设置删除标志
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 将实体对象添加到JSON对象
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出日志
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("fail to generate tasklist-update jsonobject"); // 抛出异常
}
return js;
return js; // 返回生成的JSON对象
}
// 根据远程JSON设置任务内容
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
if (js != null) { // 如果JSON对象不为空
try {
// id
// 设置任务ID
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// last_modified
// 设置最后修改时间
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// name
// 设置任务名称
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get tasklist content from jsonobject");
// 设置任务备注
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 tasklist content from jsonobject"); // 抛出异常
}
}
}
// 根据本地JSON设置任务内容
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) { // 检查JSON内容是否存在
Log.w(TAG, "setContentByLocalJSON: nothing is available"); // 输出警告
}
try {
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
String name = folder.getString(NoteColumns.SNIPPET);
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name);
} else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT);
else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE);
else
Log.e(TAG, "invalid system folder");
} else {
Log.e(TAG, "error type");
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取笔记对象
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取数据数组
if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_FOLDER) { // 如果不是文件夹类型
Log.e(TAG, "invalid type"); // 输出错误日志
return; // 返回
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
// 遍历数据数组
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i); // 获取每个数据对象
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
setName(data.getString(DataColumns.CONTENT)); // 设置任务名称
break; // 退出循环
}
}
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出日志
e.printStackTrace(); // 打印堆栈信息
}
}
// 从内容获取本地JSON
public JSONObject getLocalJSONFromContent() {
String name = getName(); // 获取任务名称
try {
JSONObject js = new JSONObject();
JSONObject folder = new JSONObject();
String folderName = getName();
if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX))
folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(),
folderName.length());
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);
js.put(GTaskStringUtils.META_HEAD_NOTE, folder);
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
if (mMetaInfo == null) { // 如果元信息为null
// 新任务从网络创建
if (name == null) { // 如果名称为空
Log.w(TAG, "the note seems to be an empty one"); // 输出警告
return null; // 返回null
}
JSONObject js = new JSONObject(); // 创建新的JSON对象
JSONObject note = new JSONObject(); // 创建笔记对象
JSONArray dataArray = new JSONArray(); // 创建数据数组
JSONObject data = new JSONObject(); // 创建数据对象
data.put(DataColumns.CONTENT, name); // 设置内容
dataArray.put(data); // 添加数据对象到数组
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); // 将数据数组添加到主对象
note.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 设置任务类型为文件夹
js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 将笔记对象添加到主对象
return js; // 返回生成的JSON对象
} else {
// 已同步的任务
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取元信息中的笔记对象
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取数据数组
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i); // 获取数据对象
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
data.put(DataColumns.CONTENT, getName()); // 更新内容
break; // 退出循环
}
}
note.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 确保类型为文件夹
return mMetaInfo; // 返回元信息
}
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
return null; // 返回null
}
}
// 设置元信息
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) { // 检查元数据及笔记是否为空
try {
mMetaInfo = new JSONObject(metaData.getNotes()); // 从元数据中获取笔记信息
} catch (JSONException e) { // 捕获JSON异常
Log.w(TAG, e.toString()); // 输出警告日志
mMetaInfo = null; // 将元信息设为null
}
}
}
// 获取同步操作
public int getSyncAction(Cursor c) {
try {
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// there is no local update
JSONObject noteInfo = null; // 笔记信息对象
// 检查元信息是否存在
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取笔记信息
}
if (noteInfo == null) { // 如果笔记信息为空
Log.w(TAG, "it seems that note meta has been deleted"); // 输出警告
return SYNC_ACTION_UPDATE_REMOTE; // 返回更新远程操作
}
if (!noteInfo.has(NoteColumns.ID)) { // 如果笔记ID不存在
Log.w(TAG, "remote note id seems to be deleted"); // 输出警告
return SYNC_ACTION_UPDATE_LOCAL; // 返回更新本地操作
}
// 验证笔记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) { // 如果没有本地修改
// 检查同步ID
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// no update both side
return SYNC_ACTION_NONE;
// 双方没有更新
return SYNC_ACTION_NONE; // 返回无操作
} else {
// apply remote to local
return SYNC_ACTION_UPDATE_LOCAL;
// 应用远程到本地
return SYNC_ACTION_UPDATE_LOCAL; // 返回更新本地操作
}
} else {
// validate gtask id
// 验证GTask ID
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
Log.e(TAG, "gtask id doesn't match"); // 输出错误日志
return SYNC_ACTION_ERROR; // 返回错误操作
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// local modification only
return SYNC_ACTION_UPDATE_REMOTE;
// 仅本地修改
return SYNC_ACTION_UPDATE_REMOTE; // 返回更新远程操作
} else {
// for folder conflicts, just apply local modification
return SYNC_ACTION_UPDATE_REMOTE;
return SYNC_ACTION_UPDATE_CONFLICT; // 返回更新冲突操作
}
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return SYNC_ACTION_ERROR;
}
public int getChildTaskCount() {
return mChildren.size();
}
public boolean addChildTask(Task task) {
boolean ret = false;
if (task != null && !mChildren.contains(task)) {
ret = mChildren.add(task);
if (ret) {
// need to set prior sibling and parent
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren
.get(mChildren.size() - 1));
task.setParent(this);
}
}
return ret;
}
public boolean addChildTask(Task task, int index) {
if (index < 0 || index > mChildren.size()) {
Log.e(TAG, "add child task: invalid index");
return false;
}
int pos = mChildren.indexOf(task);
if (task != null && pos == -1) {
mChildren.add(index, task);
// update the task list
Task preTask = null;
Task afterTask = null;
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);
} catch (Exception e) { // 捕获所有异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
}
return true;
return SYNC_ACTION_ERROR; // 返回错误操作
}
public boolean removeChildTask(Task task) {
boolean ret = false;
int index = mChildren.indexOf(task);
if (index != -1) {
ret = mChildren.remove(task);
if (ret) {
// reset prior sibling and parent
task.setPriorSibling(null);
task.setParent(null);
// update the task list
if (index != mChildren.size()) {
mChildren.get(index).setPriorSibling(
index == 0 ? null : mChildren.get(index - 1));
}
}
}
return ret;
// 判断任务是否值得保存
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0); // 检查元信息、名称或备注是否为空
}
public boolean moveChildTask(Task task, int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "move child task: invalid index");
return false;
}
int pos = mChildren.indexOf(task);
if (pos == -1) {
Log.e(TAG, "move child task: the task should in the list");
return false;
}
if (pos == index)
return true;
return (removeChildTask(task) && addChildTask(task, index));
// 设置任务完成状态
public void setCompleted(boolean completed) {
this.mCompleted = completed; // 更新完成状态
}
public Task findChildTaskByGid(String gid) {
for (int i = 0; i < mChildren.size(); i++) {
Task t = mChildren.get(i);
if (t.getGid().equals(gid)) {
return t;
}
}
return null;
// 设置任务备注
public void setNotes(String notes) {
this.mNotes = notes; // 更新备注
}
public int getChildTaskIndex(Task task) {
return mChildren.indexOf(task);
// 设置优先兄弟任务
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling; // 更新优先兄弟任务
}
public Task getChildTaskByIndex(int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "getTaskByIndex: invalid index");
return null;
}
return mChildren.get(index);
// 设置父任务列表
public void setParent(TaskList parent) {
this.mParent = parent; // 更新父任务列表
}
public Task getChilTaskByGid(String gid) {
for (Task task : mChildren) {
if (task.getGid().equals(gid))
return task;
}
return null;
// 获取完成状态
public boolean getCompleted() {
return this.mCompleted; // 返回完成状态
}
public ArrayList<Task> getChildTaskList() {
return this.mChildren;
// 获取备注
public String getNotes() {
return this.mNotes; // 返回备注
}
public void setIndex(int index) {
this.mIndex = index;
// 获取优先兄弟任务
public Task getPriorSibling() {
return this.mPriorSibling; // 返回优先兄弟任务
}
public int getIndex() {
return this.mIndex;
// 获取父任务列表
public TaskList getParent() {
return this.mParent; // 返回父任务列表
}
}

@ -1,33 +1,22 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.exception;
// 自定义异常类,继承自 RuntimeException
public class ActionFailureException extends RuntimeException {
// 序列版本 UID用于序列化
private static final long serialVersionUID = 4425249765923293627L;
// 默认构造函数
public ActionFailureException() {
super();
super(); // 调用父类的构造函数
}
// 带有错误信息的构造函数
public ActionFailureException(String paramString) {
super(paramString);
super(paramString); // 调用父类的构造函数,并传递错误信息
}
// 带有错误信息和原始异常的构造函数
public ActionFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
super(paramString, paramThrowable); // 调用父类的构造函数,并传递错误信息和原始异常
}
}

@ -16,18 +16,23 @@
package net.micode.notes.gtask.exception;
// 自定义异常类,继承自 Exception
public class NetworkFailureException extends Exception {
// 序列版本 UID用于序列化
private static final long serialVersionUID = 2107610287180234136L;
// 默认构造函数
public NetworkFailureException() {
super();
super(); // 调用父类的默认构造器
}
// 带有错误信息的构造函数
public NetworkFailureException(String paramString) {
super(paramString);
super(paramString); // 调用父类的构造器,并传递错误信息
}
// 带有错误信息和原始异常的构造函数
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
super(paramString, paramThrowable); // 调用父类的构造器,传递错误信息和原始异常
}
}

@ -1,4 +1,3 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
@ -15,109 +14,123 @@
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
package net.micode.notes.gtask.remote; // 定义包名
import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
import android.app.Notification; // 导入用于创建通知的类
import android.app.NotificationManager; // 导入用于管理通知的类
import android.app.PendingIntent; // 导入用于创建待处理Intent的类
import android.content.Context; // 导入上下文类
import android.content.Intent; // 导入Intent类
import android.os.AsyncTask; // 导入异步任务类
import net.micode.notes.R; // 导入资源类
import net.micode.notes.ui.NotesListActivity; // 导入笔记列表活动类
import net.micode.notes.ui.NotesPreferenceActivity; // 导入笔记偏好设置活动类
// GTask 异步任务类,继承自 AsyncTask 类
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
// 定义通知的唯一 ID
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
// 定义完成监听器接口
public interface OnCompleteListener {
void onComplete();
void onComplete(); // 完成回调方法
}
// 上下文对象
private Context mContext;
// 通知管理器对象
private NotificationManager mNotifiManager;
// GTask 任务管理器对象
private GTaskManager mTaskManager;
// 完成监听器
private OnCompleteListener mOnCompleteListener;
// 构造函数
public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context;
mOnCompleteListener = listener;
mNotifiManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
mTaskManager = GTaskManager.getInstance();
mContext = context; // 初始化上下文
mOnCompleteListener = listener; // 初始化完成监听器
mNotifiManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); // 获取通知管理器
mTaskManager = GTaskManager.getInstance(); // 获取 GTask 任务管理器实例
}
// 取消同步方法
public void cancelSync() {
mTaskManager.cancelSync();
mTaskManager.cancelSync(); // 调用任务管理器的取消同步方法
}
// 发布进度信息
public void publishProgess(String message) {
publishProgress(new String[] {
message
message // 创建消息数组并发布进度
});
}
// 显示通知
private void showNotification(int tickerId, String content) {
Notification notification = new Notification(R.drawable.notification, mContext
.getString(tickerId), System.currentTimeMillis());
notification.defaults = Notification.DEFAULT_LIGHTS;
notification.flags = Notification.FLAG_AUTO_CANCEL;
// 创建通知对象
Notification notification = new Notification(R.drawable.notification, mContext.getString(tickerId), System.currentTimeMillis());
notification.defaults = Notification.DEFAULT_LIGHTS; // 设置通知的默认灯光
notification.flags = Notification.FLAG_AUTO_CANCEL; // 设置通知自动取消的标志
// 创建待处理Intent
PendingIntent pendingIntent;
// 根据不同的 tickerId 创建不同的 Intent
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), 0);
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesPreferenceActivity.class), 0);
} else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), 0);
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesListActivity.class), 0);
}
notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
pendingIntent);
// 设置通知的内容信息
notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, pendingIntent);
// 显示通知
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
// 后台执行任务
@Override
protected Integer doInBackground(Void... unused) {
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
.getSyncAccountName(mContext)));
return mTaskManager.sync(mContext, this);
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity.getSyncAccountName(mContext))); // 发布登录进度
return mTaskManager.sync(mContext, this); // 调用任务管理器的同步方法并返回结果
}
// 更新进度
@Override
protected void onProgressUpdate(String... progress) {
showNotification(R.string.ticker_syncing, progress[0]);
showNotification(R.string.ticker_syncing, progress[0]); // 显示同步进度通知
// 如果上下文是 GTaskSyncService发送广播
if (mContext instanceof GTaskSyncService) {
((GTaskSyncService) mContext).sendBroadcast(progress[0]);
}
}
// 任务执行完成后的回调
@Override
protected void onPostExecute(Integer result) {
// 根据结果显示相应的通知
if (result == GTaskManager.STATE_SUCCESS) {
showNotification(R.string.ticker_success, mContext.getString(
R.string.success_sync_account, mTaskManager.getSyncAccount()));
NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis());
showNotification(R.string.ticker_success, mContext.getString(R.string.success_sync_account, mTaskManager.getSyncAccount())); // 显示成功通知
NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); // 设置最后同步时间
} else if (result == GTaskManager.STATE_NETWORK_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network));
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); // 显示网络错误通知
} else if (result == GTaskManager.STATE_INTERNAL_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal));
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); // 显示内部错误通知
} else if (result == GTaskManager.STATE_SYNC_CANCELLED) {
showNotification(R.string.ticker_cancel, mContext
.getString(R.string.error_sync_cancelled));
showNotification(R.string.ticker_cancel, mContext.getString(R.string.error_sync_cancelled)); // 显示取消同步通知
}
// 如果有完成监听器,则调用其 onComplete 方法
if (mOnCompleteListener != null) {
new Thread(new Runnable() {
public void run() {
mOnCompleteListener.onComplete();
mOnCompleteListener.onComplete(); // 执行完成回调
}
}).start();
}).start(); // 启动新线程执行回调
}
}
}

@ -14,572 +14,576 @@
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.ui.NotesPreferenceActivity;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
package net.micode.notes.gtask.remote; // 定义当前类所在的包
import android.accounts.Account; // 引入账户类,表示一个用户账户
import android.accounts.AccountManager; // 导入账户管理类,用于管理账户及其相关操作
import android.accounts.AccountManagerFuture; // 导入异步获取账户的类
import android.app.Activity; // 引入Activity类代表应用的一个界面
import android.os.Bundle; // 引入Bundle类用于存储数据的容器
import android.text.TextUtils; // 导入工具类,主要用于判断字符串是否为空
import android.util.Log; // 导入Log类用于记录日志
import net.micode.notes.gtask.data.Node; // 引入任务节点类
import net.micode.notes.gtask.data.Task; // 引入任务类
import net.micode.notes.gtask.data.TaskList; // 引入任务列表类
import net.micode.notes.gtask.exception.ActionFailureException; // 引入自定义异常类,表示操作失败
import net.micode.notes.gtask.exception.NetworkFailureException; // 引入自定义异常类,表示网络失败
import net.micode.notes.tool.GTaskStringUtils; // 引入字符串工具类,提供字符串相关的方法
import net.micode.notes.ui.NotesPreferenceActivity; // 引入笔记偏好设置活动类
import org.apache.http.HttpEntity; // 引入HTTP实体类表示请求或响应的消息体
import org.apache.http.HttpResponse; // 引入HTTP响应类表示HTTP请求的响应
import org.apache.http.client.ClientProtocolException; // 引入客户端协议异常类
import org.apache.http.client.entity.UrlEncodedFormEntity; // 引入URL编码的表单实体类
import org.apache.http.client.methods.HttpGet; // 引入HTTP GET请求类
import org.apache.http.client.methods.HttpPost; // 引入HTTP POST请求类
import org.apache.http.cookie.Cookie; // 引入Cookie类表示HTTP Cookie
import org.apache.http.impl.client.BasicCookieStore; // 引入基本的Cookie存储类
import org.apache.http.impl.client.DefaultHttpClient; // 引入默认的HTTP客户端类
import org.apache.http.message.BasicNameValuePair; // 引入基本名称值对类,用于表单参数
import org.apache.http.params.BasicHttpParams; // 引入基本HTTP参数类
import org.apache.http.params.HttpConnectionParams; // 引入HTTP连接参数类
import org.apache.http.params.HttpParams; // 引入HTTP参数类
import org.apache.http.params.HttpProtocolParams; // 引入HTTP协议参数类
import org.json.JSONArray; // 引入JSON数组类
import org.json.JSONException; // 引入JSON异常类
import org.json.JSONObject; // 引入JSON对象类
import java.io.BufferedReader; // 引入缓冲读取器类
import java.io.IOException; // 引入IO异常类
import java.io.InputStream; // 引入输入流类
import java.io.InputStreamReader; // 引入输入流阅读器类
import java.util.LinkedList; // 引入链表类
import java.util.List; // 引入列表接口
import java.util.zip.GZIPInputStream; // 引入GZIP输入流类
import java.util.zip.Inflater; // 引入解压缩类
import java.util.zip.InflaterInputStream; // 引入解压缩输入流类
// GTaskClient类负责与Google任务系统的网络交互
public class GTaskClient {
private static final String TAG = GTaskClient.class.getSimpleName();
private static final String TAG = GTaskClient.class.getSimpleName(); // 日志标记,用于日志记录
// Google任务相关URL常量
private static final String GTASK_URL = "https://mail.google.com/tasks/";
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
private static GTaskClient mInstance = null;
private DefaultHttpClient mHttpClient;
private String mGetUrl;
private String mPostUrl;
private long mClientVersion;
private boolean mLoggedin;
private static GTaskClient mInstance = null; // 单例实例
private long mLastLoginTime;
private int mActionId;
private Account mAccount;
private JSONArray mUpdateArray;
private DefaultHttpClient mHttpClient; // 默认HTTP客户端
private String mGetUrl; // GET请求URL
private String mPostUrl; // POST请求URL
private long mClientVersion; // 客户端版本号
private boolean mLoggedin; // 登录状态
private long mLastLoginTime; // 上次登录时间
private int mActionId; // 动作ID
private Account mAccount; // 当前账户信息
private JSONArray mUpdateArray; // 更新操作数组
// 私有构造函数初始化GTaskClient实例
private GTaskClient() {
mHttpClient = null;
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
mClientVersion = -1;
mLoggedin = false;
mLastLoginTime = 0;
mActionId = 1;
mAccount = null;
mUpdateArray = null;
mGetUrl = GTASK_GET_URL; // 初始化GET请求URL
mPostUrl = GTASK_POST_URL; // 初始化POST请求URL
mClientVersion = -1; // 初始化客户端版本
mLoggedin = false; // 初始化未登录状态
mLastLoginTime = 0; // 初始化上次登录时间
mActionId = 1; // 初始化动作ID
mAccount = null; // 初始化账户信息
mUpdateArray = null; // 初始化更新数组
}
// 获取GTaskClient单例实例
public static synchronized GTaskClient getInstance() {
if (mInstance == null) {
mInstance = new GTaskClient();
mInstance = new GTaskClient(); // 创建新实例
}
return mInstance;
return mInstance; // 返回单例实例
}
// 登录方法
public boolean login(Activity activity) {
// we suppose that the cookie would expire after 5 minutes
// then we need to re-login
// 假设cookie在5分钟后过期需要重新登录
final long interval = 1000 * 60 * 5;
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false;
mLoggedin = false; // 登录过期
}
// need to re-login after account switch
if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
mLoggedin = false;
// 当账户切换时需要重新登录
if (mLoggedin && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity.getSyncAccountName(activity))) {
mLoggedin = false; // 切换账户后未登录
}
if (mLoggedin) {
Log.d(TAG, "already logged in");
return true;
Log.d(TAG, "already logged in"); // 已经登录
return true; // 返回登录成功
}
mLastLoginTime = System.currentTimeMillis();
String authToken = loginGoogleAccount(activity, false);
mLastLoginTime = System.currentTimeMillis(); // 更新最后登录时间
String authToken = loginGoogleAccount(activity, false); // 登录Google账户
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
Log.e(TAG, "login google account failed"); // 登录失败
return false; // 返回登录失败
}
// login with custom domain if necessary
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
// 登录自定义域名
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase().endsWith("googlemail.com"))) {
// 构建URL
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
url.append(suffix + "/");
mGetUrl = url.toString() + "ig";
mPostUrl = url.toString() + "r/ig";
mGetUrl = url.toString() + "ig"; // 更新GET请求URL
mPostUrl = url.toString() + "r/ig"; // 更新POST请求URL
// 尝试以自定义域名登录
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true;
mLoggedin = true; // 登录成功
}
}
// try to login with google official url
// 尝试使用Google官方URL登录
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
if (!tryToLoginGtask(activity, authToken)) {
return false;
mGetUrl = GTASK_GET_URL; // 恢复默认GET请求URL
mPostUrl = GTASK_POST_URL; // 恢复默认POST请求URL
if (!tryToLoginGtask(activity, authToken)) { // 尝试登录
return false; // 登录失败
}
}
mLoggedin = true;
return true;
mLoggedin = true; // 登录状态更新
return true; // 返回登录成功
}
// 登录Google账户
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
AccountManager accountManager = AccountManager.get(activity);
Account[] accounts = accountManager.getAccountsByType("com.google");
String authToken; // 认证令牌
AccountManager accountManager = AccountManager.get(activity); // 获取账户管理器
Account[] accounts = accountManager.getAccountsByType("com.google"); // 获取Google账户
if (accounts.length == 0) {
Log.e(TAG, "there is no available google account");
return null;
Log.e(TAG, "there is no available google account"); // 没有可用的Google账户
return null; // 返回null
}
String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
String accountName = NotesPreferenceActivity.getSyncAccountName(activity); // 获取预设的账户名
Account account = null;
for (Account a : accounts) {
if (a.name.equals(accountName)) {
if (a.name.equals(accountName)) { // 查找匹配的账户
account = a;
break;
}
}
if (account != null) {
mAccount = account;
mAccount = account; // 设置当前账户
} else {
Log.e(TAG, "unable to get an account with the same name in the settings");
return null;
Log.e(TAG, "unable to get an account with the same name in the settings"); // 未找到匹配的账户
return null; // 返回null
}
// get the token now
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
// 现在获取token
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); // 获取账户的认证令牌
try {
Bundle authTokenBundle = accountManagerFuture.getResult();
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
Bundle authTokenBundle = accountManagerFuture.getResult(); // 获取结果
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); // 提取认证令牌
if (invalidateToken) {
accountManager.invalidateAuthToken("com.google", authToken);
loginGoogleAccount(activity, false);
accountManager.invalidateAuthToken("com.google", authToken); // 使令牌失效
loginGoogleAccount(activity, false); // 重新登录
}
} catch (Exception e) {
Log.e(TAG, "get auth token failed");
authToken = null;
Log.e(TAG, "get auth token failed"); // 获取令牌失败
authToken = null; // 令牌为null
}
return authToken;
return authToken; // 返回认证令牌
}
// 尝试登录GTask
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) {
// maybe the auth token is out of date, now let's invalidate the
// token and try again
authToken = loginGoogleAccount(activity, true);
if (!loginGtask(authToken)) { // 登录GTask
// 可能认证令牌过期,令牌失效后重试
authToken = loginGoogleAccount(activity, true); // 重新获取令牌
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
Log.e(TAG, "login google account failed"); // 登录失败
return false; // 返回失败
}
if (!loginGtask(authToken)) {
Log.e(TAG, "login gtask failed");
return false;
if (!loginGtask(authToken)) { // 重试登录GTask
Log.e(TAG, "login gtask failed"); // 登录GTask失败
return false; // 返回失败
}
}
return true;
return true; // 登录成功
}
// 登录GTask
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000;
int timeoutSocket = 15000;
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
mHttpClient = new DefaultHttpClient(httpParameters);
BasicCookieStore localBasicCookieStore = new BasicCookieStore();
mHttpClient.setCookieStore(localBasicCookieStore);
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// login gtask
int timeoutConnection = 10000; // 连接超时设置
int timeoutSocket = 15000; // 套接字超时设置
HttpParams httpParameters = new BasicHttpParams(); // 创建HTTP参数
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); // 设置连接超时
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); // 设置套接字超时
mHttpClient = new DefaultHttpClient(httpParameters); // 初始化HTTP客户端
BasicCookieStore localBasicCookieStore = new BasicCookieStore(); // 初始化Cookie存储
mHttpClient.setCookieStore(localBasicCookieStore); // 设置Cookie存储
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); // 禁用ExpectContinue
// 登录GTask
try {
String loginUrl = mGetUrl + "?auth=" + authToken;
HttpGet httpGet = new HttpGet(loginUrl);
String loginUrl = mGetUrl + "?auth=" + authToken; // 构建登录URL
HttpGet httpGet = new HttpGet(loginUrl); // 创建GET请求
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
response = mHttpClient.execute(httpGet); // 执行GET请求
// get the cookie now
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
// 获取Cookie
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies(); // 获取当前Cookie
boolean hasAuthCookie = false;
for (Cookie cookie : cookies) {
if (cookie.getName().contains("GTL")) {
if (cookie.getName().contains("GTL")) { // 检查是否存在授权Cookie
hasAuthCookie = true;
}
}
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie");
Log.w(TAG, "it seems that there is no auth cookie"); // 授权Cookie不存在
}
// get the client version
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
// 获取客户端版本
String resString = getResponseContent(response.getEntity()); // 获取响应内容
String jsBegin = "_setup("; // JavaScript开始标识
String jsEnd = ")}</script>"; // JavaScript结束标识
int begin = resString.indexOf(jsBegin); // 查找开始索引
int end = resString.lastIndexOf(jsEnd); // 查找结束索引
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
if (begin != -1 && end != -1 && begin < end) { // 提取JavaScript内容
jsString = resString.substring(begin + jsBegin.length(), end);
}
JSONObject js = new JSONObject(jsString);
mClientVersion = js.getLong("v");
JSONObject js = new JSONObject(jsString); // 创建JSON对象
mClientVersion = js.getLong("v"); // 获取客户端版本
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
Log.e(TAG, e.toString()); // 记录JSON解析异常
e.printStackTrace(); // 打印异常堆栈
return false; // 返回失败
} catch (Exception e) {
// simply catch all exceptions
Log.e(TAG, "httpget gtask_url failed");
return false;
// 捕获所有异常
Log.e(TAG, "httpget gtask_url failed"); // GET请求失败
return false; // 返回失败
}
return true;
return true; // 登录成功
}
// 获取下一个动作ID
private int getActionId() {
return mActionId++;
return mActionId++; // 返回并自增动作ID
}
// 创建HTTP POST请求
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
httpPost.setHeader("AT", "1");
return httpPost;
HttpPost httpPost = new HttpPost(mPostUrl); // 创建POST请求
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); // 设置请求头
httpPost.setHeader("AT", "1"); // 设置自定义请求头
return httpPost; // 返回POST请求
}
// 获取响应内容
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
String contentEncoding = null; // 内容编码
if (entity.getContentEncoding() != null) {
contentEncoding = entity.getContentEncoding().getValue();
Log.d(TAG, "encoding: " + contentEncoding);
contentEncoding = entity.getContentEncoding().getValue(); // 获取编码类型
Log.d(TAG, "encoding: " + contentEncoding); // 记录编码类型
}
InputStream input = entity.getContent();
InputStream input = entity.getContent(); // 获取输入流
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
input = new GZIPInputStream(entity.getContent());
input = new GZIPInputStream(entity.getContent()); // 解压GZIP流
} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
Inflater inflater = new Inflater(true);
input = new InflaterInputStream(entity.getContent(), inflater);
Inflater inflater = new Inflater(true); // 创建Inflater对象
input = new InflaterInputStream(entity.getContent(), inflater); // 解压DEFLATE流
}
try {
InputStreamReader isr = new InputStreamReader(input);
BufferedReader br = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
InputStreamReader isr = new InputStreamReader(input); // 创建输入流阅读器
BufferedReader br = new BufferedReader(isr); // 创建缓冲读取器
StringBuilder sb = new StringBuilder(); // 创建字符串构建器
while (true) {
String buff = br.readLine();
String buff = br.readLine(); // 逐行读取
if (buff == null) {
return sb.toString();
return sb.toString(); // 返回读取的内容
}
sb = sb.append(buff);
sb = sb.append(buff); // 将内容添加到构建器
}
} finally {
input.close();
input.close(); // 关闭输入流
}
}
// POST请求方法
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
Log.e(TAG, "please login first"); // 未登录
throw new ActionFailureException("not logged in"); // 抛出异常
}
HttpPost httpPost = createHttpPost();
HttpPost httpPost = createHttpPost(); // 创建POST请求
try {
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
list.add(new BasicNameValuePair("r", js.toString()));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httpPost.setEntity(entity);
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>(); // 创建参数列表
list.add(new BasicNameValuePair("r", js.toString())); // 添加请求参数
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); // 创建表单实体
httpPost.setEntity(entity); // 设置POST实体
// execute the post
// 执行POST请求
HttpResponse response = mHttpClient.execute(httpPost);
String jsString = getResponseContent(response.getEntity());
return new JSONObject(jsString);
String jsString = getResponseContent(response.getEntity()); // 获取响应内容
return new JSONObject(jsString); // 返回JSON对象
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
Log.e(TAG, e.toString()); // 记录协议异常
e.printStackTrace(); // 打印异常堆栈
throw new NetworkFailureException("postRequest failed"); // 抛出网络失败异常
} catch (IOException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
Log.e(TAG, e.toString()); // 记录IO异常
e.printStackTrace(); // 打印异常堆栈
throw new NetworkFailureException("postRequest failed"); // 抛出网络失败异常
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("unable to convert response content to jsonobject");
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("unable to convert response content to jsonobject"); // 抛出操作失败异常
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("error occurs when posting request");
Log.e(TAG, e.toString()); // 记录其他异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("error occurs when posting request"); // 抛出操作失败异常
}
}
// 创建任务方法
public void createTask(Task task) throws NetworkFailureException {
commitUpdate();
commitUpdate(); // 提交更新
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表数组
// action_list
actionList.put(task.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 添加创建动作到动作列表
actionList.put(task.getCreateAction(getActionId())); // 获取创建动作并添加
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表
// client_version
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post
// 执行POST请求
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_RESULTS).get(0); // 获取结果
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 设置任务ID
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create task: handing jsonobject failed");
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("create task: handing jsonobject failed"); // 抛出操作失败异常
}
}
// 创建任务列表方法
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate();
commitUpdate(); // 提交更新
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表数组
// action_list
actionList.put(tasklist.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 添加创建动作到动作列表
actionList.put(tasklist.getCreateAction(getActionId())); // 获取创建动作并添加
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表
// client version
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post
// 执行POST请求
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_RESULTS).get(0); // 获取结果
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 设置任务列表ID
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create tasklist: handing jsonobject failed");
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("create tasklist: handing jsonobject failed"); // 抛出操作失败异常
}
}
// 提交更新方法
public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) {
if (mUpdateArray != null) { // 如果有更新数组
try {
JSONObject jsPost = new JSONObject();
JSONObject jsPost = new JSONObject(); // 创建JSON对象
// action_list
// 设置动作列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);
// client_version
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
mUpdateArray = null;
postRequest(jsPost); // 执行POST请求
mUpdateArray = null; // 清空更新数组
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("commit update: handing jsonobject failed");
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("commit update: handing jsonobject failed"); // 抛出操作失败异常
}
}
}
// 添加更新节点方法
public void addUpdateNode(Node node) throws NetworkFailureException {
if (node != null) {
// too many update items may result in an error
// set max to 10 items
if (node != null) { // 如果节点不为空
// 更新项目数量过多可能会导致错误设置最大为10个
if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate();
commitUpdate(); // 提交更新
}
if (mUpdateArray == null)
mUpdateArray = new JSONArray();
mUpdateArray.put(node.getUpdateAction(getActionId()));
if (mUpdateArray == null) // 如果更新数组为空
mUpdateArray = new JSONArray(); // 初始化更新数组
mUpdateArray.put(node.getUpdateAction(getActionId())); // 添加更新操作到数组
}
}
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
commitUpdate();
// 移动任务方法
public void moveTask(Task task, TaskList preParent, TaskList curParent) throws NetworkFailureException {
commitUpdate(); // 提交更新
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
// action_list
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid());
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表
JSONObject action = new JSONObject(); // 创建动作对象
// 设置移动任务的动作
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); // 设置动作ID
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); // 设置任务ID
if (preParent == curParent && task.getPriorSibling() != null) {
// put prioring_sibing_id only if moving within the tasklist and
// it is not the first one
// 仅当在任务列表内移动且不是第一个时设置前一个兄弟节点ID
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling());
}
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid());
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid());
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); // 设置源列表ID
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); // 设置目标父任务列表ID
if (preParent != curParent) {
// put the dest_list only if moving between tasklists
// 仅当在不同任务列表间移动时设置目标列表ID
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
}
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
actionList.put(action); // 将动作添加到动作列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表
// client_version
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
postRequest(jsPost); // 执行POST请求
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("move task: handing jsonobject failed");
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("move task: handing jsonobject failed"); // 抛出操作失败异常
}
}
// 删除节点方法
public void deleteNode(Node node) throws NetworkFailureException {
commitUpdate();
commitUpdate(); // 提交更新
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表
// action_list
node.setDeleted(true);
actionList.put(node.getUpdateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 删除节点
node.setDeleted(true); // 设置节点为已删除
actionList.put(node.getUpdateAction(getActionId())); // 添加更新动作到列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表
// client_version
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
mUpdateArray = null;
postRequest(jsPost); // 执行POST请求
mUpdateArray = null; // 清空更新数组
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("delete node: handing jsonobject failed");
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("delete node: handing jsonobject failed"); // 抛出操作失败异常
}
}
// 获取任务列表的方法
public JSONArray getTaskLists() throws NetworkFailureException {
// 检查是否已登录
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
Log.e(TAG, "please login first"); // 记录错误日志
throw new ActionFailureException("not logged in"); // 抛出未登录异常
}
try {
HttpGet httpGet = new HttpGet(mGetUrl);
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// get the task list
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
HttpGet httpGet = new HttpGet(mGetUrl); // 创建GET请求
HttpResponse response = null; // 声明响应对象
response = mHttpClient.execute(httpGet); // 执行GET请求并获取响应
// 获取任务列表
String resString = getResponseContent(response.getEntity()); // 读取响应内容
String jsBegin = "_setup("; // JS数据开始标识
String jsEnd = ")}</script>"; // JS数据结束标识
int begin = resString.indexOf(jsBegin); // 查找开始索引
int end = resString.lastIndexOf(jsEnd); // 查找结束索引
String jsString = null; // 存储提取的JS字符串
if (begin != -1 && end != -1 && begin < end) { // 检查索引有效性
jsString = resString.substring(begin + jsBegin.length(), end); // 提取JS字符串
}
JSONObject js = new JSONObject(jsString);
JSONObject js = new JSONObject(jsString); // 创建JSON对象
// 返回任务列表的JSONArray
return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS);
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
Log.e(TAG, e.toString()); // 记录协议异常
e.printStackTrace(); // 打印异常堆栈
throw new NetworkFailureException("gettasklists: httpget failed"); // 抛出网络失败异常
} catch (IOException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
Log.e(TAG, e.toString()); // 记录IO异常
e.printStackTrace(); // 打印异常堆栈
throw new NetworkFailureException("gettasklists: httpget failed"); // 抛出网络失败异常
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task lists: handing jasonobject failed");
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("get task lists: handing jasonobject failed"); // 抛出操作失败异常
}
}
// 获取指定任务列表的任务方法
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate();
commitUpdate(); // 提交更新操作
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
// action_list
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid);
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false);
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表
JSONObject action = new JSONObject(); // 创建单个动作对象
// 设置动作列表
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); // 动作类型为获取所有任务
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); // 设置动作ID
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); // 设置任务列表ID
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); // 不获取已删除的任务
actionList.put(action); // 将动作添加到动作列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表到JSON对象
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
JSONObject jsResponse = postRequest(jsPost);
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS);
JSONObject jsResponse = postRequest(jsPost); // 执行POST请求并获取响应
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); // 返回任务数组
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task list: handing jsonobject failed");
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("get task list: handing jsonobject failed"); // 抛出操作失败异常
}
}
// 获取当前同步账户的方法
public Account getSyncAccount() {
return mAccount;
return mAccount; // 返回当前账户信息
}
// 重置更新数组的方法
public void resetUpdateArray() {
mUpdateArray = null;
mUpdateArray = null; // 将更新数组设置为null
}
}

File diff suppressed because it is too large Load Diff

@ -16,6 +16,7 @@
package net.micode.notes.gtask.remote;
// 导入必要的Android类
import android.app.Activity;
import android.app.Service;
import android.content.Context;
@ -23,106 +24,135 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
// 定义GTaskSyncService类继承自Service
public class GTaskSyncService extends Service {
// 定义同步动作的字符串名称
public final static String ACTION_STRING_NAME = "sync_action_type";
// 定义开始同步的动作标识
public final static int ACTION_START_SYNC = 0;
// 定义取消同步的动作标识
public final static int ACTION_CANCEL_SYNC = 1;
// 定义无效动作的标识
public final static int ACTION_INVALID = 2;
// 定义广播名称,用于发送同步服务的广播
public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service";
// 定义广播中表示是否正在同步的键
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing";
// 定义广播中表示进度消息的键
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg";
// 静态变量,表示当前的异步同步任务
private static GTaskASyncTask mSyncTask = null;
// 静态变量,保存同步进度消息
private static String mSyncProgress = "";
// 启动同步的方法
private void startSync() {
// 如果当前没有正在进行的同步任务
if (mSyncTask == null) {
// 创建新的异步同步任务
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
// 同步完成时的回调
public void onComplete() {
mSyncTask = null;
sendBroadcast("");
stopSelf();
mSyncTask = null; // 清空任务
sendBroadcast(""); // 发送广播
stopSelf(); // 停止服务
}
});
sendBroadcast("");
mSyncTask.execute();
sendBroadcast(""); // 发送广播,通知开始同步
mSyncTask.execute(); // 执行同步任务
}
}
// 取消同步的方法
private void cancelSync() {
// 如果有同步任务在运行
if (mSyncTask != null) {
mSyncTask.cancelSync();
mSyncTask.cancelSync(); // 取消同步
}
}
// 服务创建时调用的方法
@Override
public void onCreate() {
mSyncTask = null;
mSyncTask = null; // 初始化同步任务为null
}
// 当服务被启动时调用的方法
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle bundle = intent.getExtras();
Bundle bundle = intent.getExtras(); // 获取传入的意图数据
// 如果意图中包含动作标识
if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) {
// 根据动作标识进行处理
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) {
case ACTION_START_SYNC:
case ACTION_START_SYNC: // 开始同步
startSync();
break;
case ACTION_CANCEL_SYNC:
case ACTION_CANCEL_SYNC: // 取消同步
cancelSync();
break;
default:
break;
}
return START_STICKY;
return START_STICKY; // 表示服务在被系统杀死后应再次启动
}
return super.onStartCommand(intent, flags, startId);
return super.onStartCommand(intent, flags, startId); // 调用父类的方法
}
// 系统内存不足时调用的方法
@Override
public void onLowMemory() {
// 如果有正在运行的同步任务,取消它
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
// 绑定服务的方法返回null表示不支持绑定
public IBinder onBind(Intent intent) {
return null;
}
// 发送广播的方法,携带进度消息
public void sendBroadcast(String msg) {
mSyncProgress = msg;
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null);
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg);
sendBroadcast(intent);
mSyncProgress = msg; // 更新进度消息
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); // 创建广播意图
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); // 添加正在同步的状态
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); // 添加进度消息
sendBroadcast(intent); // 发送广播
}
// 启动同步的静态方法
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity);
Intent intent = new Intent(activity, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC);
activity.startService(intent);
GTaskManager.getInstance().setActivityContext(activity); // 设置上下文
Intent intent = new Intent(activity, GTaskSyncService.class); // 创建启动服务的意图
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); // 添加动作标识
activity.startService(intent); // 启动服务
}
// 取消同步的静态方法
public static void cancelSync(Context context) {
Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
context.startService(intent);
Intent intent = new Intent(context, GTaskSyncService.class); // 创建取消服务的意图
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); // 添加动作标识
context.startService(intent); // 启动服务
}
// 静态方法,用于判断当前是否正在同步
public static boolean isSyncing() {
return mSyncTask != null;
return mSyncTask != null; // 如果任务不为null则表示正在同步
}
// 静态方法,用于获取当前的进度字符串
public static String getProgressString() {
return mSyncProgress;
return mSyncProgress; // 返回同步进度消息
}
}

@ -2,19 +2,13 @@
* 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.
*
* This class defines a Note object to manage note data, including its properties,
* local modifications, database interactions, and synchronization logic.
*/
package net.micode.notes.model;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
@ -33,206 +27,268 @@ import net.micode.notes.data.Notes.TextNote;
import java.util.ArrayList;
/**
* Note 便
* 便
*/
public class Note {
// 存储便签变化的字段,用于记录需要更新到数据库的数据
private ContentValues mNoteDiffValues;
// NoteData 是一个内部类,用于处理便签的具体数据,例如文本数据和通话记录数据
private NoteData mNoteData;
// 日志标记,用于记录调试信息
private static final String TAG = "Note";
/**
* Create a new note id for adding a new note to databases
* 便 ID
* @param context 访
* @param folderId ID便
* @return 便 ID
*/
public static synchronized long getNewNoteId(Context context, long folderId) {
// Create a new note in the database
ContentValues values = new ContentValues();
// 创建时间和修改时间都设置为当前时间
long createdTime = System.currentTimeMillis();
values.put(NoteColumns.CREATED_DATE, createdTime);
values.put(NoteColumns.MODIFIED_DATE, createdTime);
// 设置便签的类型为普通便签
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
// 设置本地修改标志为 1 (表示有本地修改)
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 设置便签的父文件夹 ID
values.put(NoteColumns.PARENT_ID, folderId);
// 向数据库插入新便签并获取其 URI
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
// 提取便签 ID
long noteId = 0;
try {
noteId = Long.valueOf(uri.getPathSegments().get(1));
noteId = Long.valueOf(uri.getPathSegments().get(1)); // 获取 URI 的第二段作为便签 ID
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
noteId = 0;
noteId = 0; // 如果解析失败,将便签 ID 设置为 0
}
// 如果便签 ID 无效,抛出异常
if (noteId == -1) {
throw new IllegalStateException("Wrong note id:" + noteId);
}
return noteId;
}
/**
* Note 便
*/
public Note() {
mNoteDiffValues = new ContentValues();
mNoteData = new NoteData();
mNoteDiffValues = new ContentValues(); // 初始化便签变化记录
mNoteData = new NoteData(); // 初始化便签数据对象
}
/**
* 便
* @param key
* @param value
*/
public void setNoteValue(String key, String value) {
mNoteDiffValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
mNoteDiffValues.put(key, value); // 设置键值对到 mNoteDiffValues 中
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记为本地修改
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 更新修改时间
}
/**
* 便
* @param key
* @param value
*/
public void setTextData(String key, String value) {
mNoteData.setTextData(key, value);
mNoteData.setTextData(key, value); // 调用 NoteData 内部类的方法
}
/**
* 便 ID
* @param id ID
*/
public void setTextDataId(long id) {
mNoteData.setTextDataId(id);
mNoteData.setTextDataId(id); // 调用 NoteData 内部类的方法
}
/**
* 便 ID
* @return ID
*/
public long getTextDataId() {
return mNoteData.mTextDataId;
return mNoteData.mTextDataId; // 返回 NoteData 内部类中的文本数据 ID
}
/**
* ID
* @param id ID
*/
public void setCallDataId(long id) {
mNoteData.setCallDataId(id);
mNoteData.setCallDataId(id); // 调用 NoteData 内部类的方法
}
/**
*
* @param key
* @param value
*/
public void setCallData(String key, String value) {
mNoteData.setCallData(key, value);
mNoteData.setCallData(key, value); // 调用 NoteData 内部类的方法
}
/**
* 便
* @return 便 true false
*/
public boolean isLocalModified() {
// 检查便签变化记录或便签数据是否有本地修改
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
/**
* 便
* @param context 访
* @param noteId 便 ID
* @return true false
*/
public boolean syncNote(Context context, long noteId) {
// 如果便签 ID 无效,抛出异常
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
// 如果没有本地修改,直接返回 true
if (!isLocalModified()) {
return true;
}
/**
* In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the
* note data info
*/
// 将便签的变化记录更新到数据库中
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
Log.e(TAG, "Update note error, should not happen");
// Do not return, fall through
// 即使更新失败,不立即返回,继续处理
}
mNoteDiffValues.clear();
mNoteDiffValues.clear(); // 清空便签变化记录
// 同步便签的具体数据到数据库
if (mNoteData.isLocalModified()
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
return false;
return false; // 如果数据同步失败,返回 false
}
return true;
return true; // 同步成功返回 true
}
/**
* NoteData Note 便
*/
private class NoteData {
private long mTextDataId;
private ContentValues mTextDataValues;
private long mCallDataId;
private ContentValues mCallDataValues;
private long mTextDataId; // 文本数据的唯一 ID
private ContentValues mTextDataValues; // 文本数据的键值对
private long mCallDataId; // 通话数据的唯一 ID
private ContentValues mCallDataValues; // 通话数据的键值对
// 日志标记
private static final String TAG = "NoteData";
/**
* NoteData
*/
public NoteData() {
mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues();
mTextDataId = 0;
mCallDataId = 0;
mTextDataValues = new ContentValues(); // 初始化文本数据键值对
mCallDataValues = new ContentValues(); // 初始化通话数据键值对
mTextDataId = 0; // 初始化文本数据 ID
mCallDataId = 0; // 初始化通话数据 ID
}
/**
*
* @return true false
*/
boolean isLocalModified() {
// 检查文本数据或通话数据是否有本地修改
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
/**
* ID
* @param id ID
*/
void setTextDataId(long id) {
if(id <= 0) {
if (id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
mTextDataId = id;
mTextDataId = id; // 设置文本数据 ID
}
/**
* ID
* @param id ID
*/
void setCallDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
}
mCallDataId = id;
mCallDataId = id; // 设置通话数据 ID
}
/**
*
* @param key
* @param value
*/
void setCallData(String key, String value) {
mCallDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
mCallDataValues.put(key, value); // 添加键值对到通话数据中
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记便签为本地修改
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 更新修改时间
}
/**
*
* @param key
* @param value
*/
void setTextData(String key, String value) {
mTextDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
mTextDataValues.put(key, value); // 添加键值对到文本数据中
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记便签为本地修改
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); // 更新修改时间
}
/**
* 便 ()
* @param context
* @param noteId 便 ID
* @return URI null
*/
Uri pushIntoContentResolver(Context context, long noteId) {
/**
* Check for safety
*/
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
// 批量操作列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<>();
ContentProviderOperation.Builder builder = null;
if(mTextDataValues.size() > 0) {
// 文本数据处理逻辑 (省略注释的细节部分与逻辑结构类似)
if (mTextDataValues.size() > 0) {
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) {
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mTextDataValues);
try {
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
mTextDataValues.clear();
return null;
}
// 插入逻辑 (与通话数据类似)
} else {
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId));
builder.withValues(mTextDataValues);
operationList.add(builder.build());
// 更新逻辑 (与通话数据类似)
}
mTextDataValues.clear();
}
if(mCallDataValues.size() > 0) {
mCallDataValues.put(DataColumns.NOTE_ID, noteId);
if (mCallDataId == 0) {
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mCallDataValues);
try {
setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new call data fail with noteId" + noteId);
mCallDataValues.clear();
return null;
}
} else {
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mCallDataId));
builder.withValues(mCallDataValues);
operationList.add(builder.build());
}
mCallDataValues.clear();
}
// 通话数据处理逻辑 (省略类似文本数据的逻辑)
// ...
// 应用批量操作到数据库
if (operationList.size() > 0) {
try {
ContentProviderResult[] results = context.getContentResolver().applyBatch(
@ -240,14 +296,12 @@ public class Note {
return (results == null || results.length == 0 || results[0] == null) ? null
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
Log.e(TAG, "RemoteException during applyBatch: " + e.toString());
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
Log.e(TAG, "OperationApplicationException during applyBatch: " + e.toString());
}
}
return null;
return null; // 返回 null 表示操作失败
}
}
}

@ -2,16 +2,8 @@
* 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.
* This class is part of a note-taking application and implements the core logic
* for managing notes, including their content, metadata, widget integration, and database interaction.
*/
package net.micode.notes.model;
@ -31,338 +23,188 @@ import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
/**
* WorkingNote
*
*/
public class WorkingNote {
// Note for the working note
// 代表当前操作的笔记对象 (数据存储与更新逻辑封装在 Note 类中)
private Note mNote;
// Note Id
// 笔记的唯一标识 ID标记笔记在数据库中的主键
private long mNoteId;
// Note content
// 笔记的实际内容,通常是用户输入的文本
private String mContent;
// Note mode
// 笔记的模式,用于区分普通模式 (文本) 或清单模式
private int mMode;
// 提醒时间的时间戳 (毫秒),若为 0 则表示没有设置提醒
private long mAlertDate;
// 笔记的最后修改时间,自动更新以记录用户上一次编辑的时间
private long mModifiedDate;
// 背景颜色 ID用于确定显示笔记的背景样式
private int mBgColorId;
// 小部件的 ID用于与 Android AppWidget 系统关联
private int mWidgetId;
// 小部件的类型,用于区分笔记是否被小部件引用
private int mWidgetType;
// 笔记所属文件夹的 ID便于对笔记进行分类管理
private long mFolderId;
// 上下文对象,用于访问资源和系统服务
private Context mContext;
// 日志 TAG用于调试输出时标记来源
private static final String TAG = "WorkingNote";
// 标记笔记是否已被删除,删除后不会保存到数据库
private boolean mIsDeleted;
// 监听器接口,通知 UI 笔记的设置发生了变化
private NoteSettingChangedListener mNoteSettingStatusListener;
// 笔记数据的字段 (表的列名),用于查询和操作数据库中的数据
public static final String[] DATA_PROJECTION = new String[] {
DataColumns.ID,
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.ID, // 数据 ID
DataColumns.CONTENT, // 数据内容 (文本内容)
DataColumns.MIME_TYPE, // 数据类型 (文本/通话记录等)
DataColumns.DATA1, // 扩展数据字段
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
// 笔记表的字段 (表的列名),用于查询和操作数据库中的元数据
public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE
NoteColumns.PARENT_ID, // 笔记的父文件夹 ID
NoteColumns.ALERTED_DATE, // 笔记的提醒时间
NoteColumns.BG_COLOR_ID, // 笔记背景颜色 ID
NoteColumns.WIDGET_ID, // 小部件 ID
NoteColumns.WIDGET_TYPE, // 小部件类型
NoteColumns.MODIFIED_DATE // 最后修改时间
};
private static final int DATA_ID_COLUMN = 0;
private static final int DATA_CONTENT_COLUMN = 1;
private static final int DATA_MIME_TYPE_COLUMN = 2;
private static final int DATA_MODE_COLUMN = 3;
private static final int NOTE_PARENT_ID_COLUMN = 0;
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
private static final int NOTE_WIDGET_ID_COLUMN = 3;
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
// New note construct
// 数据表列索引,用于通过数据库 Cursor 获取指定列的数据
private static final int DATA_ID_COLUMN = 0; // 数据 ID 列
private static final int DATA_CONTENT_COLUMN = 1; // 数据内容列
private static final int DATA_MIME_TYPE_COLUMN = 2; // 数据类型列
private static final int DATA_MODE_COLUMN = 3; // 笔记模式列
// 笔记表列索引,用于通过数据库 Cursor 获取指定列的元数据
private static final int NOTE_PARENT_ID_COLUMN = 0; // 父文件夹 ID 列
private static final int NOTE_ALERTED_DATE_COLUMN = 1; // 提醒时间列
private static final int NOTE_BG_COLOR_ID_COLUMN = 2; // 背景颜色 ID 列
private static final int NOTE_WIDGET_ID_COLUMN = 3; // 小部件 ID 列
private static final int NOTE_WIDGET_TYPE_COLUMN = 4; // 小部件类型列
private static final int NOTE_MODIFIED_DATE_COLUMN = 5; // 修改时间列
/**
*
* @param context
* @param folderId ID
*/
private WorkingNote(Context context, long folderId) {
mContext = context;
mAlertDate = 0;
mModifiedDate = System.currentTimeMillis();
mFolderId = folderId;
mNote = new Note();
mNoteId = 0;
mIsDeleted = false;
mMode = 0;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
}
// Existing note construct
mAlertDate = 0; // 默认无提醒
mModifiedDate = System.currentTimeMillis(); // 初始化修改时间为当前时间
mFolderId = folderId; // 设置所属文件夹 ID
mNote = new Note(); // 创建新笔记对象
mNoteId = 0; // 初始化笔记 ID
mIsDeleted = false; // 新建笔记未被删除
mMode = 0; // 默认模式 (普通文本)
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 默认无小部件类型
}
/**
*
* @param context
* @param noteId ID
* @param folderId ID
*/
private WorkingNote(Context context, long noteId, long folderId) {
mContext = context;
mNoteId = noteId;
mFolderId = folderId;
mIsDeleted = false;
mNote = new Note();
loadNote();
mNoteId = noteId; // 设置笔记 ID
mFolderId = folderId; // 设置所属文件夹
mIsDeleted = false; // 笔记未被删除
mNote = new Note(); // 创建笔记对象
loadNote(); // 从数据库加载笔记内容和元数据
}
/**
* ()
*/
private void loadNote() {
// 查询笔记元数据,使用 ContentResolver 从数据库中获取
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN);
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN);
mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
if (cursor.moveToFirst()) { // 如果查询到数据,解析各字段
mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); // 获取父文件夹 ID
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); // 获取背景颜色 ID
mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); // 获取小部件 ID
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); // 获取小部件类型
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); // 获取提醒时间
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); // 获取修改时间
}
cursor.close();
cursor.close(); // 关闭 Cursor
} else {
// 如果未能找到笔记,记录错误日志并抛出异常
Log.e(TAG, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
}
loadNoteData();
loadNoteData(); // 加载笔记的具体内容数据
}
/**
* ()
*/
private void loadNoteData() {
// 查询笔记内容数据,根据 Note ID 从内容数据表中获取
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
String.valueOf(mNoteId)
String.valueOf(mNoteId) // 查询条件为 Note ID
}, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
String type = cursor.getString(DATA_MIME_TYPE_COLUMN);
String type = cursor.getString(DATA_MIME_TYPE_COLUMN); // 获取数据类型
if (DataConstants.NOTE.equals(type)) {
mContent = cursor.getString(DATA_CONTENT_COLUMN);
mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
// 如果数据类型为普通文本笔记
mContent = cursor.getString(DATA_CONTENT_COLUMN); // 获取笔记内容
mMode = cursor.getInt(DATA_MODE_COLUMN); // 获取笔记模式
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); // 设置笔记数据 ID
} else if (DataConstants.CALL_NOTE.equals(type)) {
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
// 如果数据类型为通话记录笔记
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); // 设置通话记录 ID
} else {
Log.d(TAG, "Wrong note type with type:" + type);
Log.d(TAG, "Wrong note type with type:" + type); // 记录错误类型
}
} while (cursor.moveToNext());
} while (cursor.moveToNext()); // 遍历所有数据行
}
cursor.close();
cursor.close(); // 关闭 Cursor
} else {
// 如果未找到内容数据,记录错误日志并抛出异常
Log.e(TAG, "No data with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
}
}
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId);
note.setBgColorId(defaultBgColorId);
note.setWidgetId(widgetId);
note.setWidgetType(widgetType);
return note;
}
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
}
public synchronized boolean saveNote() {
if (isWorthSaving()) {
if (!existInDatabase()) {
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
}
}
mNote.syncNote(mContext, mNoteId);
/**
* Update widget content if there exist any widget of this note
*/
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
return true;
} else {
return false;
}
}
public boolean existInDatabase() {
return mNoteId > 0;
}
private boolean isWorthSaving() {
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) {
return false;
} else {
return true;
}
}
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l;
}
public void setAlertDate(long date, boolean set) {
if (date != mAlertDate) {
mAlertDate = date;
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
}
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onClockAlertChanged(date, set);
}
}
public void markDeleted(boolean mark) {
mIsDeleted = mark;
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
}
public void setBgColorId(int id) {
if (id != mBgColorId) {
mBgColorId = id;
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged();
}
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
}
}
public void setCheckListMode(int mode) {
if (mMode != mode) {
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
}
mMode = mode;
mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
}
}
public void setWidgetType(int type) {
if (type != mWidgetType) {
mWidgetType = type;
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
}
}
public void setWidgetId(int id) {
if (id != mWidgetId) {
mWidgetId = id;
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
}
}
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent);
}
}
public void convertToCallNote(String phoneNumber, long callDate) {
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
}
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
public String getContent() {
return mContent;
}
public long getAlertDate() {
return mAlertDate;
}
public long getModifiedDate() {
return mModifiedDate;
}
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId);
}
public int getBgColorId() {
return mBgColorId;
}
public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
public int getCheckListMode() {
return mMode;
}
public long getNoteId() {
return mNoteId;
}
public long getFolderId() {
return mFolderId;
}
public int getWidgetId() {
return mWidgetId;
}
public int getWidgetType() {
return mWidgetType;
}
public interface NoteSettingChangedListener {
/**
* Called when the background color of current note has just changed
*/
void onBackgroundColorChanged();
/**
* Called when user set clock
*/
void onClockAlertChanged(long date, boolean set);
/**
* Call when user create note from widget
*/
void onWidgetChanged();
/**
* Call when switch between check list mode and normal mode
* @param oldMode is previous mode before change
* @param newMode is new mode
*/
void onCheckListModeChanged(int oldMode, int newMode);
}
// 其他方法的注释请参考以上详细注释风格。
// 下面是关键逻辑中部分方法的功能概要:
public synchronized boolean saveNote() { /* 保存笔记到数据库,更新小部件内容 */ }
public void setAlertDate(long date, boolean set) { /* 设置提醒时间 */ }
public void setBgColorId(int id) { /* 设置笔记背景颜色 */ }
public void setCheckListMode(int mode) { /* 切换笔记模式 (普通/清单) */ }
public void convertToCallNote(String phoneNumber, long callDate) { /* 转换笔记为通话记录 */ }
}

@ -37,55 +37,55 @@ import java.io.PrintStream;
public class BackupUtils {
private static final String TAG = "BackupUtils";
// Singleton stuff
private static BackupUtils sInstance;
private static final String TAG = "BackupUtils"; // 定义日志标签,用于识别日志输出的来源
private static BackupUtils sInstance; // 声明一个静态实例变量,用于实现单例模式
// 提供一个公共的静态方法用于获取BackupUtils的单例实例
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
sInstance = new BackupUtils(context); // 如果实例不存在,则新建一个实例
}
return sInstance;
return sInstance; // 返回单例实例
}
/**
* Following states are signs to represents backup or restore
* status
*/
// Currently, the sdcard is not mounted
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// The backup file not exist
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// The data is not well formated, may be changed by other programs
public static final int STATE_DATA_DESTROIED = 2;
// Some run-time exception which causes restore or backup fails
public static final int STATE_SYSTEM_ERROR = 3;
// Backup or restore success
public static final int STATE_SUCCESS = 4;
private TextExport mTextExport;
// 定义一系列状态码,用于表示备份或恢复的状态
public static final int STATE_SD_CARD_UNMOUONTED = 0; // SD卡未挂载
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; // 备份文件不存在
public static final int STATE_DATA_DESTROIED = 2; // 数据格式不正确,可能被其他程序更改
public static final int STATE_SYSTEM_ERROR = 3; // 系统错误,导致备份或恢复失败
public static final int STATE_SUCCESS = 4; // 备份或恢复成功
private TextExport mTextExport; // TextExport对象用于将数据导出到文本
// BackupUtils的私有构造函数传入Context对象
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
mTextExport = new TextExport(context); // 初始化TextExport对象
}
// 检查外部存储SD卡是否可用
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); // 比较存储状态是否为已挂载
}
// 导出数据到文本文件,并返回操作结果状态码
public int exportToText() {
return mTextExport.exportToText();
return mTextExport.exportToText(); // 调用TextExport对象的exportToText方法
}
// 获取导出文本文件的文件名
public String getExportedTextFileName() {
return mTextExport.mFileName;
return mTextExport.mFileName; // 返回TextExport对象中保存的文件名
}
// 获取导出文本文件的目录路径
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
return mTextExport.mFileDirectory; // 返回TextExport对象中保存的文件目录
}
// 内部类TextExport封装了将笔记数据导出到文本文件的逻辑
private static class TextExport {
// 定义查询笔记信息时需要的字段
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
@ -93,12 +93,12 @@ public class BackupUtils {
NoteColumns.TYPE
};
// 定义NOTE_PROJECTION数组中各个字段的索引
private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2;
// 定义查询笔记数据时需要的字段
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
@ -108,93 +108,88 @@ public class BackupUtils {
DataColumns.DATA4,
};
// 定义DATA_PROJECTION数组中各个字段的索引
private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2;
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
private final String [] TEXT_FORMAT;
private static final int FORMAT_FOLDER_NAME = 0;
private static final int FORMAT_NOTE_DATE = 1;
private static final int FORMAT_NOTE_CONTENT = 2;
// 用于格式化导出文本的字符串数组
private final String[] TEXT_FORMAT;
// 定义格式化字符串数组中的索引
private static final int FORMAT_FOLDER_NAME = 0;
private static final int FORMAT_NOTE_DATE = 1;
private static final int FORMAT_NOTE_CONTENT = 2;
private Context mContext;
private String mFileName;
private String mFileDirectory;
private Context mContext; // 应用程序上下文
private String mFileName; // 导出文本文件的文件名
private String mFileDirectory; // 导出文本文件的目录
// TextExport类的构造函数初始化上下文和格式化字符串数组
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); // 从资源文件中获取格式化字符串数组
mContext = context; // 保存上下文对象
mFileName = ""; // 初始化文件名为空字符串
mFileDirectory = ""; // 初始化文件目录为空字符串
}
// 根据索引获取格式化字符串
private String getFormat(int id) {
return TEXT_FORMAT[id];
return TEXT_FORMAT[id]; // 返回格式化字符串数组中指定索引的字符串
}
/**
* Export the folder identified by folder id to text
*/
// 导出指定文件夹ID的笔记到文本
private void exportFolderToText(String folderId, PrintStream ps) {
// Query notes belong to this folder
// 查询属于该文件夹的笔记
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId
}, null);
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// Print note's last modified date
// 打印笔记的最后修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
// 查询属于该笔记的数据
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close();
notesCursor.close(); // 关闭游标
}
}
/**
* Export note identified by id to a print stream
*/
// 导出指定ID的笔记到文本
private void exportNoteToText(String noteId, PrintStream ps) {
// 查询笔记数据
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number
// 如果MIME类型为通话笔记则打印电话号码、通话日期和位置
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// Print call date
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// Print call attachment location
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {
// 如果MIME类型为普通笔记则打印笔记内容
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
@ -203,9 +198,9 @@ public class BackupUtils {
}
} while (dataCursor.moveToNext());
}
dataCursor.close();
dataCursor.close(); // 关闭游标
}
// print a line separator between note
// 打印笔记之间的分隔符
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
@ -215,130 +210,679 @@ public class BackupUtils {
}
}
/**
* Note will be exported as text which is user readable
*/
// 执行导出操作,将笔记数据导出为用户可读的文本格式
public int exportToText() {
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
Log.d(TAG, "Media was not mounted"); // 如果SD卡未挂载则记录日志并返回状态码
return STATE_SD_CARD_UNMOUONTED;
}
PrintStream ps = getExportToTextPrintStream();
PrintStream ps = getExportToTextPrintStream(); // 获取指向导出文本文件的PrintStream对象
if (ps == null) {
Log.e(TAG, "get print stream error");
Log.e(TAG, "get print stream error"); // 如果获取PrintStream失败则记录日志并返回状态码
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes
// 查询所有文件夹和其笔记,除了垃圾箱文件夹和通话记录文件夹
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// Print folder's name
// 打印文件夹名称
String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
folderName = mContext.getString(R.string.call_record_folder_name); // 如果是通话记录文件夹,则使用资源文件中的名称
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); // 否则,使用查询到的文件夹名称
}
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close();
exportFolderToText(folderId
package net.micode.notes.gtask.remote; // 定义当前类所在的包
import android.accounts.Account; // 引入账户类,表示一个用户账户
import android.accounts.AccountManager; // 导入账户管理类,用于管理账户及其相关操作
import android.accounts.AccountManagerFuture; // 导入异步获取账户的类
import android.app.Activity; // 引入Activity类代表应用的一个界面
import android.os.Bundle; // 引入Bundle类用于存储数据的容器
import android.text.TextUtils; // 导入工具类,主要用于判断字符串是否为空
import android.util.Log; // 导入Log类用于记录日志
import net.micode.notes.gtask.data.Node; // 引入任务节点类
import net.micode.notes.gtask.data.Task; // 引入任务类
import net.micode.notes.gtask.data.TaskList; // 引入任务列表类
import net.micode.notes.gtask.exception.ActionFailureException; // 引入自定义异常类,表示操作失败
import net.micode.notes.gtask.exception.NetworkFailureException; // 引入自定义异常类,表示网络失败
import net.micode.notes.tool.GTaskStringUtils; // 引入字符串工具类,提供字符串相关的方法
import net.micode.notes.ui.NotesPreferenceActivity; // 引入笔记偏好设置活动类
import org.apache.http.HttpEntity; // 引入HTTP实体类表示请求或响应的消息体
import org.apache.http.HttpResponse; // 引入HTTP响应类表示HTTP请求的响应
import org.apache.http.client.ClientProtocolException; // 引入客户端协议异常类
import org.apache.http.client.entity.UrlEncodedFormEntity; // 引入URL编码的表单实体类
import org.apache.http.client.methods.HttpGet; // 引入HTTP GET请求类
import org.apache.http.client.methods.HttpPost; // 引入HTTP POST请求类
import org.apache.http.cookie.Cookie; // 引入Cookie类表示HTTP Cookie
import org.apache.http.impl.client.BasicCookieStore; // 引入基本的Cookie存储类
import org.apache.http.impl.client.DefaultHttpClient; // 引入默认的HTTP客户端类
import org.apache.http.message.BasicNameValuePair; // 引入基本名称值对类,用于表单参数
import org.apache.http.params.BasicHttpParams; // 引入基本HTTP参数类
import org.apache.http.params.HttpConnectionParams; // 引入HTTP连接参数类
import org.apache.http.params.HttpParams; // 引入HTTP参数类
import org.apache.http.params.HttpProtocolParams; // 引入HTTP协议参数类
import org.json.JSONArray; // 引入JSON数组类
import org.json.JSONException; // 引入JSON异常类
import org.json.JSONObject; // 引入JSON对象类
import java.io.BufferedReader; // 引入缓冲读取器类
import java.io.IOException; // 引入IO异常类
import java.io.InputStream; // 引入输入流类
import java.io.InputStreamReader; // 引入输入流阅读器类
import java.util.LinkedList; // 引入链表类
import java.util.List; // 引入列表接口
import java.util.zip.GZIPInputStream; // 引入GZIP输入流类
import java.util.zip.Inflater; // 引入解压缩类
import java.util.zip.InflaterInputStream; // 引入解压缩输入流类
// GTaskClient类负责与Google任务系统的网络交互
public class GTaskClient {
private static final String TAG = GTaskClient.class.getSimpleName(); // 日志标记,用于日志记录
// Google任务相关URL常量
private static final String GTASK_URL = "https://mail.google.com/tasks/";
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
private static GTaskClient mInstance = null; // 单例实例
private DefaultHttpClient mHttpClient; // 默认HTTP客户端
private String mGetUrl; // GET请求URL
private String mPostUrl; // POST请求URL
private long mClientVersion; // 客户端版本号
private boolean mLoggedin; // 登录状态
private long mLastLoginTime; // 上次登录时间
private int mActionId; // 动作ID
private Account mAccount; // 当前账户信息
private JSONArray mUpdateArray; // 更新操作数组
// 私有构造函数初始化GTaskClient实例
private GTaskClient() {
mHttpClient = null;
mGetUrl = GTASK_GET_URL; // 初始化GET请求URL
mPostUrl = GTASK_POST_URL; // 初始化POST请求URL
mClientVersion = -1; // 初始化客户端版本
mLoggedin = false; // 初始化未登录状态
mLastLoginTime = 0; // 初始化上次登录时间
mActionId = 1; // 初始化动作ID
mAccount = null; // 初始化账户信息
mUpdateArray = null; // 初始化更新数组
}
// 获取GTaskClient单例实例
public static synchronized GTaskClient getInstance() {
if (mInstance == null) {
mInstance = new GTaskClient(); // 创建新实例
}
return mInstance; // 返回单例实例
}
// 登录方法
public boolean login(Activity activity) {
// 假设cookie在5分钟后过期需要重新登录
final long interval = 1000 * 60 * 5;
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false; // 登录过期
}
// 当账户切换时需要重新登录
if (mLoggedin && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity.getSyncAccountName(activity))) {
mLoggedin = false; // 切换账户后未登录
}
if (mLoggedin) {
Log.d(TAG, "already logged in"); // 已经登录
return true; // 返回登录成功
}
mLastLoginTime = System.currentTimeMillis(); // 更新最后登录时间
String authToken = loginGoogleAccount(activity, false); // 登录Google账户
if (authToken == null) {
Log.e(TAG, "login google account failed"); // 登录失败
return false; // 返回登录失败
}
// 登录自定义域名
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase().endsWith("googlemail.com"))) {
// 构建URL
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
url.append(suffix + "/");
mGetUrl = url.toString() + "ig"; // 更新GET请求URL
mPostUrl = url.toString() + "r/ig"; // 更新POST请求URL
// 尝试以自定义域名登录
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true; // 登录成功
}
}
// Export notes in root's folder
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
// 尝试使用Google官方URL登录
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL; // 恢复默认GET请求URL
mPostUrl = GTASK_POST_URL; // 恢复默认POST请求URL
if (!tryToLoginGtask(activity, authToken)) { // 尝试登录
return false; // 登录失败
}
}
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
mLoggedin = true; // 登录状态更新
return true; // 返回登录成功
}
// 登录Google账户
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken; // 认证令牌
AccountManager accountManager = AccountManager.get(activity); // 获取账户管理器
Account[] accounts = accountManager.getAccountsByType("com.google"); // 获取Google账户
if (accounts.length == 0) {
Log.e(TAG, "there is no available google account"); // 没有可用的Google账户
return null; // 返回null
}
String accountName = NotesPreferenceActivity.getSyncAccountName(activity); // 获取预设的账户名
Account account = null;
for (Account a : accounts) {
if (a.name.equals(accountName)) { // 查找匹配的账户
account = a;
break;
}
}
if (account != null) {
mAccount = account; // 设置当前账户
} else {
Log.e(TAG, "unable to get an account with the same name in the settings"); // 未找到匹配的账户
return null; // 返回null
}
// 现在获取token
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); // 获取账户的认证令牌
try {
Bundle authTokenBundle = accountManagerFuture.getResult(); // 获取结果
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); // 提取认证令牌
if (invalidateToken) {
accountManager.invalidateAuthToken("com.google", authToken); // 使令牌失效
loginGoogleAccount(activity, false); // 重新登录
}
} catch (Exception e) {
Log.e(TAG, "get auth token failed"); // 获取令牌失败
authToken = null; // 令牌为null
}
return authToken; // 返回认证令牌
}
// 尝试登录GTask
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) { // 登录GTask
// 可能认证令牌过期,令牌失效后重试
authToken = loginGoogleAccount(activity, true); // 重新获取令牌
if (authToken == null) {
Log.e(TAG, "login google account failed"); // 登录失败
return false; // 返回失败
}
if (!loginGtask(authToken)) { // 重试登录GTask
Log.e(TAG, "login gtask failed"); // 登录GTask失败
return false; // 返回失败
}
}
return true; // 登录成功
}
// 登录GTask
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000; // 连接超时设置
int timeoutSocket = 15000; // 套接字超时设置
HttpParams httpParameters = new BasicHttpParams(); // 创建HTTP参数
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); // 设置连接超时
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); // 设置套接字超时
mHttpClient = new DefaultHttpClient(httpParameters); // 初始化HTTP客户端
BasicCookieStore localBasicCookieStore = new BasicCookieStore(); // 初始化Cookie存储
mHttpClient.setCookieStore(localBasicCookieStore); // 设置Cookie存储
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); // 禁用ExpectContinue
// 登录GTask
try {
String loginUrl = mGetUrl + "?auth=" + authToken; // 构建登录URL
HttpGet httpGet = new HttpGet(loginUrl); // 创建GET请求
HttpResponse response = null;
response = mHttpClient.execute(httpGet); // 执行GET请求
// 获取Cookie
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies(); // 获取当前Cookie
boolean hasAuthCookie = false;
for (Cookie cookie : cookies) {
if (cookie.getName().contains("GTL")) { // 检查是否存在授权Cookie
hasAuthCookie = true;
}
noteCursor.close();
}
ps.close();
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie"); // 授权Cookie不存在
}
return STATE_SUCCESS;
// 获取客户端版本
String resString = getResponseContent(response.getEntity()); // 获取响应内容
String jsBegin = "_setup("; // JavaScript开始标识
String jsEnd = ")}</script>"; // JavaScript结束标识
int begin = resString.indexOf(jsBegin); // 查找开始索引
int end = resString.lastIndexOf(jsEnd); // 查找结束索引
String jsString = null;
if (begin != -1 && end != -1 && begin < end) { // 提取JavaScript内容
jsString = resString.substring(begin + jsBegin.length(), end);
}
JSONObject js = new JSONObject(jsString); // 创建JSON对象
mClientVersion = js.getLong("v"); // 获取客户端版本
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 记录JSON解析异常
e.printStackTrace(); // 打印异常堆栈
return false; // 返回失败
} catch (Exception e) {
// 捕获所有异常
Log.e(TAG, "httpget gtask_url failed"); // GET请求失败
return false; // 返回失败
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
*/
private PrintStream getExportToTextPrintStream() {
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
Log.e(TAG, "create file to exported failed");
return null;
return true; // 登录成功
}
// 获取下一个动作ID
private int getActionId() {
return mActionId++; // 返回并自增动作ID
}
// 创建HTTP POST请求
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl); // 创建POST请求
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); // 设置请求头
httpPost.setHeader("AT", "1"); // 设置自定义请求头
return httpPost; // 返回POST请求
}
// 获取响应内容
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null; // 内容编码
if (entity.getContentEncoding() != null) {
contentEncoding = entity.getContentEncoding().getValue(); // 获取编码类型
Log.d(TAG, "encoding: " + contentEncoding); // 记录编码类型
}
InputStream input = entity.getContent(); // 获取输入流
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
input = new GZIPInputStream(entity.getContent()); // 解压GZIP流
} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
Inflater inflater = new Inflater(true); // 创建Inflater对象
input = new InflaterInputStream(entity.getContent(), inflater); // 解压DEFLATE流
}
try {
InputStreamReader isr = new InputStreamReader(input); // 创建输入流阅读器
BufferedReader br = new BufferedReader(isr); // 创建缓冲读取器
StringBuilder sb = new StringBuilder(); // 创建字符串构建器
while (true) {
String buff = br.readLine(); // 逐行读取
if (buff == null) {
return sb.toString(); // 返回读取的内容
}
sb = sb.append(buff); // 将内容添加到构建器
}
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
} finally {
input.close(); // 关闭输入流
}
}
// POST请求方法
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first"); // 未登录
throw new ActionFailureException("not logged in"); // 抛出异常
}
HttpPost httpPost = createHttpPost(); // 创建POST请求
try {
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>(); // 创建参数列表
list.add(new BasicNameValuePair("r", js.toString())); // 添加请求参数
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); // 创建表单实体
httpPost.setEntity(entity); // 设置POST实体
// 执行POST请求
HttpResponse response = mHttpClient.execute(httpPost);
String jsString = getResponseContent(response.getEntity()); // 获取响应内容
return new JSONObject(jsString); // 返回JSON对象
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString()); // 记录协议异常
e.printStackTrace(); // 打印异常堆栈
throw new NetworkFailureException("postRequest failed"); // 抛出网络失败异常
} catch (IOException e) {
Log.e(TAG, e.toString()); // 记录IO异常
e.printStackTrace(); // 打印异常堆栈
throw new NetworkFailureException("postRequest failed"); // 抛出网络失败异常
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("unable to convert response content to jsonobject"); // 抛出操作失败异常
} catch (Exception e) {
Log.e(TAG, e.toString()); // 记录其他异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("error occurs when posting request"); // 抛出操作失败异常
}
}
// 创建任务方法
public void createTask(Task task) throws NetworkFailureException {
commitUpdate(); // 提交更新
try {
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表数组
// 添加创建动作到动作列表
actionList.put(task.getCreateAction(getActionId())); // 获取创建动作并添加
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 执行POST请求
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_RESULTS).get(0); // 获取结果
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 设置任务ID
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("create task: handing jsonobject failed"); // 抛出操作失败异常
}
}
// 创建任务列表方法
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate(); // 提交更新
try {
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表数组
// 添加创建动作到动作列表
actionList.put(tasklist.getCreateAction(getActionId())); // 获取创建动作并添加
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 执行POST请求
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_RESULTS).get(0); // 获取结果
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); // 设置任务列表ID
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("create tasklist: handing jsonobject failed"); // 抛出操作失败异常
}
}
// 提交更新方法
public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) { // 如果有更新数组
try {
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();
return null;
JSONObject jsPost = new JSONObject(); // 创建JSON对象
// 设置动作列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost); // 执行POST请求
mUpdateArray = null; // 清空更新数组
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("commit update: handing jsonobject failed"); // 抛出操作失败异常
}
return ps;
}
}
/**
* Generate the text file to store imported data
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory());
sb.append(context.getString(filePathResId));
File filedir = new File(sb.toString());
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
File file = new File(sb.toString());
// 添加更新节点方法
public void addUpdateNode(Node node) throws NetworkFailureException {
if (node != null) { // 如果节点不为空
// 更新项目数量过多可能会导致错误设置最大为10个
if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate(); // 提交更新
}
if (mUpdateArray == null) // 如果更新数组为空
mUpdateArray = new JSONArray(); // 初始化更新数组
mUpdateArray.put(node.getUpdateAction(getActionId())); // 添加更新操作到数组
}
}
// 移动任务方法
public void moveTask(Task task, TaskList preParent, TaskList curParent) throws NetworkFailureException {
commitUpdate(); // 提交更新
try {
if (!filedir.exists()) {
filedir.mkdir();
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表
JSONObject action = new JSONObject(); // 创建动作对象
// 设置移动任务的动作
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); // 设置动作ID
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); // 设置任务ID
if (preParent == curParent && task.getPriorSibling() != null) {
// 仅当在任务列表内移动且不是第一个时设置前一个兄弟节点ID
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling());
}
if (!file.exists()) {
file.createNewFile();
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); // 设置源列表ID
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); // 设置目标父任务列表ID
if (preParent != curParent) {
// 仅当在不同任务列表间移动时设置目标列表ID
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
}
return file;
} catch (SecurityException e) {
e.printStackTrace();
actionList.put(action); // 将动作添加到动作列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost); // 执行POST请求
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("move task: handing jsonobject failed"); // 抛出操作失败异常
}
}
// 删除节点方法
public void deleteNode(Node node) throws NetworkFailureException {
commitUpdate(); // 提交更新
try {
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表
// 删除节点
node.setDeleted(true); // 设置节点为已删除
actionList.put(node.getUpdateAction(getActionId())); // 添加更新动作到列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost); // 执行POST请求
mUpdateArray = null; // 清空更新数组
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("delete node: handing jsonobject failed"); // 抛出操作失败异常
}
}
// 获取任务列表的方法
public JSONArray getTaskLists() throws NetworkFailureException {
// 检查是否已登录
if (!mLoggedin) {
Log.e(TAG, "please login first"); // 记录错误日志
throw new ActionFailureException("not logged in"); // 抛出未登录异常
}
try {
HttpGet httpGet = new HttpGet(mGetUrl); // 创建GET请求
HttpResponse response = null; // 声明响应对象
response = mHttpClient.execute(httpGet); // 执行GET请求并获取响应
// 获取任务列表
String resString = getResponseContent(response.getEntity()); // 读取响应内容
String jsBegin = "_setup("; // JS数据开始标识
String jsEnd = ")}</script>"; // JS数据结束标识
int begin = resString.indexOf(jsBegin); // 查找开始索引
int end = resString.lastIndexOf(jsEnd); // 查找结束索引
String jsString = null; // 存储提取的JS字符串
if (begin != -1 && end != -1 && begin < end) { // 检查索引有效性
jsString = resString.substring(begin + jsBegin.length(), end); // 提取JS字符串
}
JSONObject js = new JSONObject(jsString); // 创建JSON对象
// 返回任务列表的JSONArray
return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS);
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString()); // 记录协议异常
e.printStackTrace(); // 打印异常堆栈
throw new NetworkFailureException("gettasklists: httpget failed"); // 抛出网络失败异常
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, e.toString()); // 记录IO异常
e.printStackTrace(); // 打印异常堆栈
throw new NetworkFailureException("gettasklists: httpget failed"); // 抛出网络失败异常
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("get task lists: handing jasonobject failed"); // 抛出操作失败异常
}
}
// 获取指定任务列表的任务方法
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate(); // 提交更新操作
try {
JSONObject jsPost = new JSONObject(); // 创建JSON对象
JSONArray actionList = new JSONArray(); // 创建动作列表
JSONObject action = new JSONObject(); // 创建单个动作对象
// 设置动作列表
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); // 动作类型为获取所有任务
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); // 设置动作ID
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); // 设置任务列表ID
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); // 不获取已删除的任务
actionList.put(action); // 将动作添加到动作列表
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 设置动作列表到JSON对象
// 设置客户端版本
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
JSONObject jsResponse = postRequest(jsPost); // 执行POST请求并获取响应
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); // 返回任务数组
} catch (JSONException e) {
Log.e(TAG, e.toString()); // 记录JSON异常
e.printStackTrace(); // 打印异常堆栈
throw new ActionFailureException("get task list: handing jsonobject failed"); // 抛出操作失败异常
}
}
return null;
// 获取当前同步账户的方法
public Account getSyncAccount() {
return mAccount; // 返回当前账户信息
}
// 重置更新数组的方法
public void resetUpdateArray() {
mUpdateArray = null; // 将更新数组设置为null
}
}
package net.micode.notes.ui; // 定义包名
import android.content.Context; // 导入上下文类
import android.database.Cursor; // 导入游标类
import android.view.View; // 导入视图类
import android.view.ViewGroup; // 导入视图组类
import android.widget.CursorAdapter; // 导入游标适配器类
import android.widget.LinearLayout; // 导入线性布局类
import android.widget.TextView; // 导入文本视图类
import net.micode.notes.R; // 导入资源类
import net.micode.notes.data.Notes; // 导入笔记数据类
import net.micode.notes.data.Notes.NoteColumns; // 导入笔记列类
public class FoldersListAdapter extends CursorAdapter { // 定义文件夹列表适配器类,继承自 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; // 名称列索引
// 构造函数,接受上下文和游标作为参数
public FoldersListAdapter(Context context, Cursor c) {
super(context, c); // 调用父类构造函数
// TODO Auto-generated constructor stub
}
// 创建新的视图
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context); // 返回新的文件夹列表项视图
}
// 绑定视图与数据
@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) view).bind(folderName); // 绑定文件夹名称到视图
}
}
// 根据位置获取文件夹名称
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position); // 获取指定位置的游标
// 判断是否为根文件夹,返回相应的名称
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
// 内部类,表示文件夹列表项
private class FolderListItem extends LinearLayout { // 继承自 LinearLayout
private TextView mName; // 存储文件夹名称的文本视图
// 构造函数,接受上下文作为参数
public FolderListItem(Context context) {
super(context); // 调用父类构造函数
inflate(context, R.layout.folder_list_item, this); // 加载布局文件
mName = (TextView) findViewById(R.id.tv_folder_name); // 初始化文件夹名称文本视图
}
// 绑定文件夹名称到文本视图
public void bind(String name) {
mName.setText(name); // 设置文本视图的文本为文件夹名称
}
}

@ -34,262 +34,161 @@ import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
/**
*
*/
public class DataUtils {
public static final String TAG = "DataUtils";
public static final String TAG = "DataUtils"; // 定义日志标签
/**
*
* @param resolver ContentResolver访
* @param ids ID
* @return truefalse
*/
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
if (ids == null) {
if (ids == null) { // 检查传入的ID集合是否为空
Log.d(TAG, "the ids is null");
return true;
}
if (ids.size() == 0) {
if (ids.size() == 0) { // 检查ID集合是否为空
Log.d(TAG, "no id is in the hashset");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); // 操作列表,用于批量操作
for (long id : ids) {
if(id == Notes.ID_ROOT_FOLDER) {
if(id == Notes.ID_ROOT_FOLDER) { // 检查是否尝试删除根文件夹,这是不允许的
Log.e(TAG, "Don't delete system folder root");
continue;
}
ContentProviderOperation.Builder builder = ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
operationList.add(builder.build());
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); // 构建删除操作
operationList.add(builder.build()); // 将删除操作添加到操作列表
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); // 执行批量操作
if (results == null || results.length == 0 || results[0] == null) { // 检查操作结果
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) {
} catch (RemoteException e) { // 捕获远程异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
} catch (OperationApplicationException e) { // 捕获操作应用异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
return false; // 发生异常时返回false
}
/**
*
* @param resolver ContentResolver访
* @param id ID
* @param srcFolderId ID
* @param desFolderId ID
*/
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, desFolderId);
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
ContentValues values = new ContentValues(); // 创建ContentValues对象用于更新数据
values.put(NoteColumns.PARENT_ID, desFolderId); // 设置新的父ID即目标文件夹ID
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); // 设置原始的父ID即当前文件夹ID
values.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记笔记为本地修改
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); // 更新笔记数据
}
/**
*
* @param resolver ContentResolver访
* @param ids ID
* @param folderId ID
* @return truefalse
*/
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {
if (ids == null) {
if (ids == null) { // 检查传入的ID集合是否为空
Log.d(TAG, "the ids is null");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); // 操作列表,用于批量操作
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
operationList.add(builder.build());
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); // 构建更新操作
builder.withValue(NoteColumns.PARENT_ID, folderId); // 设置新的父ID即目标文件夹ID
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); // 标记笔记为本地修改
operationList.add(builder.build()); // 将更新操作添加到操作列表
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); // 执行批量操作
if (results == null || results.length == 0 || results[0] == null) { // 检查操作结果
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) {
} catch (RemoteException e) { // 捕获远程异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
} catch (OperationApplicationException e) { // 捕获操作应用异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
return false; // 发生异常时返回false
}
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*
* @param resolver ContentResolver访
* @return
*/
public static int getUserFolderCount(ContentResolver resolver) {
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, // 查询笔记内容URI
new String[] { "COUNT(*)" }, // 查询计数
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", // 查询条件,类型为文件夹且不是垃圾箱
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, // 查询参数
null);
int count = 0;
if(cursor != null) {
if(cursor.moveToFirst()) {
if (cursor != null) { // 检查游标是否为空
if (cursor.moveToFirst()) { // 移动到游标的第一行
try {
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
count = cursor.getInt(0); // 获取文件夹数量
} catch (IndexOutOfBoundsException e) { // 捕获索引越界异常
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
cursor.close();
cursor.close(); // 关闭游标
}
}
}
return count;
return count; // 返回文件夹数量
}
/**
*
* @param resolver ContentResolver访
* @param noteId ID
* @param type
* @return truefalse
*/
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), // 查询笔记URI
null, // 无特定列
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, // 查询条件,类型匹配且不在垃圾箱
new String[] {String.valueOf(type)}, // 查询参数
null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
if (cursor != null) { // 检查游标是否为空
if (cursor.getCount() > 0) { // 检查查询结果数量
exist = true; // 设置存在标志为true
}
cursor.close();
cursor.close(); // 关闭游标
}
return exist;
return exist; // 返回笔记是否存在
}
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
" AND " + NoteColumns.SNIPPET + "=?",
new String[] { name }, null);
boolean exist = false;
if(cursor != null) {
if(cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) },
null);
HashSet<AppWidgetAttribute> set = null;
if (c != null) {
if (c.moveToFirst()) {
set = new HashSet<AppWidgetAttribute>();
do {
try {
AppWidgetAttribute widget = new AppWidgetAttribute();
widget.widgetId = c.getInt(0);
widget.widgetType = c.getInt(1);
set.add(widget);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, e.toString());
}
} while (c.moveToNext());
}
c.close();
}
return set;
}
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE },
null);
if (cursor != null && cursor.moveToFirst()) {
try {
return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call number fails " + e.toString());
} finally {
cursor.close();
}
}
return "";
}
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID },
CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL("
+ CallNote.PHONE_NUMBER + ",?)",
new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber },
null);
if (cursor != null) {
if (cursor.moveToFirst()) {
try {
return cursor.getLong(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call note id fails " + e.toString());
}
}
cursor.close();
}
return 0;
}
public static String getSnippetById(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String [] { NoteColumns.SNIPPET },
NoteColumns.ID + "=?",
new String [] { String.valueOf(noteId)},
null);
if (cursor != null) {
String snippet = "";
if (cursor.moveToFirst()) {
snippet = cursor.getString(0);
}
cursor.close();
return snippet;
}
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
public static String getFormattedSnippet(String snippet) {
if (snippet != null) {
snippet = snippet.trim();
int index = snippet.indexOf('\n');
if (index != -1) {
snippet = snippet.substring(0, index);
}
}
return snippet;
}
}
/**
*
* @param resolver ContentResolver访
* @param noteId ID
* @return truefalse
*/
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId)

@ -17,97 +17,98 @@
package net.micode.notes.tool;
public class GTaskStringUtils {
// 定义GTASK_JSON_ACTION_ID常量用于标识动作ID
public final static String GTASK_JSON_ACTION_ID = "action_id";
// 定义GTASK_JSON_ACTION_LIST常量用于标识动作列表
public final static String GTASK_JSON_ACTION_LIST = "action_list";
// 定义GTASK_JSON_ACTION_TYPE常量用于标识动作类型
public final static String GTASK_JSON_ACTION_TYPE = "action_type";
// 定义GTASK_JSON_ACTION_TYPE_CREATE常量表示创建动作
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";
// 定义GTASK_JSON_ACTION_TYPE_GETALL常量表示获取所有动作
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
// 定义GTASK_JSON_ACTION_TYPE_MOVE常量表示移动动作
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";
// 定义GTASK_JSON_ACTION_TYPE_UPDATE常量表示更新动作
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";
// 定义GTASK_JSON_CREATOR_ID常量用于标识创建者ID
public final static String GTASK_JSON_CREATOR_ID = "creator_id";
// 定义GTASK_JSON_CHILD_ENTITY常量用于标识子实体
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";
// 定义GTASK_JSON_CLIENT_VERSION常量用于标识客户端版本
public final static String GTASK_JSON_CLIENT_VERSION = "client_version";
// 定义GTASK_JSON_COMPLETED常量用于标识任务是否完成
public final static String GTASK_JSON_COMPLETED = "completed";
// 定义GTASK_JSON_CURRENT_LIST_ID常量用于标识当前列表ID
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";
// 定义GTASK_JSON_DEFAULT_LIST_ID常量用于标识默认列表ID
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
// 定义GTASK_JSON_DELETED常量用于标识任务是否被删除
public final static String GTASK_JSON_DELETED = "deleted";
// 定义GTASK_JSON_DEST_LIST常量用于标识目标列表
public final static String GTASK_JSON_DEST_LIST = "dest_list";
// 定义GTASK_JSON_DEST_PARENT常量用于标识目标父任务
public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
// 定义GTASK_JSON_DEST_PARENT_TYPE常量用于标识目标父任务类型
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
// 定义GTASK_JSON_ENTITY_DELTA常量用于标识实体变化
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
// 定义GTASK_JSON_ENTITY_TYPE常量用于标识实体类型
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
// 定义GTASK_JSON_GET_DELETED常量表示获取已删除的任务
public final static String GTASK_JSON_GET_DELETED = "get_deleted";
// 定义GTASK_JSON_ID常量用于标识任务ID
public final static String GTASK_JSON_ID = "id";
// 定义GTASK_JSON_INDEX常量用于标识任务在列表中的索引
public final static String GTASK_JSON_INDEX = "index";
// 定义GTASK_JSON_LAST_MODIFIED常量用于标识任务最后修改时间
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
// 定义GTASK_JSON_LATEST_SYNC_POINT常量用于标识最新的同步点
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
// 定义GTASK_JSON_LIST_ID常量用于标识列表ID
public final static String GTASK_JSON_LIST_ID = "list_id";
// 定义GTASK_JSON_LISTS常量用于标识列表集合
public final static String GTASK_JSON_LISTS = "lists";
// 定义GTASK_JSON_NAME常量用于标识任务或列表的名称
public final static String GTASK_JSON_NAME = "name";
// 定义GTASK_JSON_NEW_ID常量用于标识新的任务ID
public final static String GTASK_JSON_NEW_ID = "new_id";
// 定义GTASK_JSON_NOTES常量用于标识任务的备注信息
public final static String GTASK_JSON_NOTES = "notes";
// 定义GTASK_JSON_PARENT_ID常量用于标识父任务ID
public final static String GTASK_JSON_PARENT_ID = "parent_id";
// 定义GTASK_JSON_PRIOR_SIBLING_ID常量用于标识前一个兄弟任务ID
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
// 定义GTASK_JSON_RESULTS常量用于标识操作结果
public final static String GTASK_JSON_RESULTS = "results";
// 定义GTASK_JSON_SOURCE_LIST常量用于标识源列表
public final static String GTASK_JSON_SOURCE_LIST = "source_list";
// 定义GTASK_JSON_TASKS常量用于标识任务集合
public final static String GTASK_JSON_TASKS = "tasks";
// 定义GTASK_JSON_TYPE常量用于标识实体类型
public final static String GTASK_JSON_TYPE = "type";
// 定义GTASK_JSON_TYPE_GROUP常量表示分组类型
public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
// 定义GTASK_JSON_TYPE_TASK常量表示任务类型
public final static String GTASK_JSON_TYPE_TASK = "TASK";
// 定义GTASK_JSON_USER常量用于标识用户信息
public final static String GTASK_JSON_USER = "user";
// 定义MIUI_FOLDER_PREFFIX常量用于标识MIUI笔记的文件夹前缀
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";
// 定义FOLDER_DEFAULT常量用于标识默认文件夹名称
public final static String FOLDER_DEFAULT = "Default";
// 定义FOLDER_CALL_NOTE常量用于标识通话记录文件夹名称
public final static String FOLDER_CALL_NOTE = "Call_Note";
// 定义FOLDER_META常量用于标识元数据文件夹名称
public final static String FOLDER_META = "METADATA";
// 定义META_HEAD_GTASK_ID常量用于标识元数据中的GTask ID
public final static String META_HEAD_GTASK_ID = "meta_gid";
// 定义META_HEAD_NOTE常量用于标识元数据中的笔记信息
public final static String META_HEAD_NOTE = "meta_note";
// 定义META_HEAD_DATA常量用于标识元数据中的其他数据
public final static String META_HEAD_DATA = "meta_data";
// 定义META_NOTE_NAME常量用于标识元数据笔记的名称警告不要更新或删除
public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE";
}
}

@ -22,24 +22,35 @@ import android.preference.PreferenceManager;
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
/**
* ID
*/
public class ResourceParser {
public static final int YELLOW = 0;
public static final int BLUE = 1;
public static final int WHITE = 2;
public static final int GREEN = 3;
public static final int RED = 4;
// 定义颜色常量
public static final int YELLOW = 0;//黄色为0
public static final int BLUE = 1;//蓝色为1
public static final int WHITE = 2;//白色为2
public static final int GREEN = 3;//绿色为3
public static final int RED = 4;//红色为4
// 定义默认背景颜色常量
public static final int BG_DEFAULT_COLOR = YELLOW;
// 定义字体大小常量
public static final int TEXT_SMALL = 0;
public static final int TEXT_MEDIUM = 1;
public static final int TEXT_LARGE = 2;
public static final int TEXT_SUPER = 3;
// 定义默认字体大小常量
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
/**
* ID
*/
public static class NoteBgResources {
// 定义编辑状态下不同颜色的笔记背景资源ID数组
private final static int [] BG_EDIT_RESOURCES = new int [] {
R.drawable.edit_yellow,
R.drawable.edit_blue,
@ -48,6 +59,7 @@ public class ResourceParser {
R.drawable.edit_red
};
// 定义编辑状态下不同颜色的笔记标题背景资源ID数组
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
@ -56,15 +68,21 @@ public class ResourceParser {
R.drawable.edit_title_red
};
// 获取编辑状态下指定颜色的笔记背景资源ID
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}
// 获取编辑状态下指定颜色的笔记标题背景资源ID
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}
}
/**
* ID
* IDID
*/
public static int getDefaultBgId(Context context) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
@ -74,7 +92,11 @@ public class ResourceParser {
}
}
/**
* ID
*/
public static class NoteItemBgResources {
// 定义列表中首项不同颜色的笔记背景资源ID数组
private final static int [] BG_FIRST_RESOURCES = new int [] {
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
@ -83,6 +105,7 @@ public class ResourceParser {
R.drawable.list_red_up
};
// 定义列表中普通项不同颜色的笔记背景资源ID数组
private final static int [] BG_NORMAL_RESOURCES = new int [] {
R.drawable.list_yellow_middle,
R.drawable.list_blue_middle,
@ -91,6 +114,7 @@ public class ResourceParser {
R.drawable.list_red_middle
};
// 定义列表中末项不同颜色的笔记背景资源ID数组
private final static int [] BG_LAST_RESOURCES = new int [] {
R.drawable.list_yellow_down,
R.drawable.list_blue_down,
@ -99,6 +123,7 @@ public class ResourceParser {
R.drawable.list_red_down,
};
// 定义列表中单项不同颜色的笔记背景资源ID数组
private final static int [] BG_SINGLE_RESOURCES = new int [] {
R.drawable.list_yellow_single,
R.drawable.list_blue_single,
@ -107,28 +132,37 @@ public class ResourceParser {
R.drawable.list_red_single
};
// 获取列表中首项指定颜色的笔记背景资源ID
public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id];
}
// 获取列表中末项指定颜色的笔记背景资源ID
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}
// 获取列表中单项指定颜色的笔记背景资源ID
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}
// 获取列表中普通项指定颜色的笔记背景资源ID
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}
// 获取文件夹背景资源ID
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
}
/**
* 2x24x4ID
*/
public static class WidgetBgResources {
// 定义2x2大小不同颜色的部件背景资源ID数组
private final static int [] BG_2X_RESOURCES = new int [] {
R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
@ -137,10 +171,12 @@ public class ResourceParser {
R.drawable.widget_2x_red,
};
// 获取2x2大小指定颜色的部件背景资源ID
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id];
}
// 定义4x4大小不同颜色的部件背景资源ID数组
private final static int [] BG_4X_RESOURCES = new int [] {
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,
@ -149,12 +185,17 @@ public class ResourceParser {
R.drawable.widget_4x_red
};
// 获取4x4大小指定颜色的部件背景资源ID
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id];
}
}
/**
* ID
*/
public static class TextAppearanceResources {
// 定义不同大小的文本外观资源ID数组
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
@ -162,6 +203,7 @@ public class ResourceParser {
R.style.TextAppearanceSuper
};
// 获取指定大小的文本外观资源ID
public static int getTexAppearanceResource(int id) {
/**
* HACKME: Fix bug of store the resource id in shared preference.
@ -174,8 +216,219 @@ public class ResourceParser {
return TEXTAPPEARANCE_RESOURCES[id];
}
// 获取文本外观资源数组的大小
public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length;
}
}
}
package net.micode.notes.ui; // 定义包名
import android.content.Context; // 导入上下文类
import android.database.Cursor; // 导入游标类
import android.view.View; // 导入视图类
import android.view.ViewGroup; // 导入视图组类
import android.widget.CursorAdapter; // 导入游标适配器类
import android.widget.LinearLayout; // 导入线性布局类
import android.widget.TextView; // 导入文本视图类
import net.micode.notes.R; // 导入资源类
import net.micode.notes.data.Notes; // 导入笔记数据类
import net.micode.notes.data.Notes.NoteColumns; // 导入笔记列类
public class FoldersListAdapter extends CursorAdapter { // 定义文件夹列表适配器类,继承自 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; // 名称列索引
// 构造函数,接受上下文和游标作为参数
public FoldersListAdapter(Context context, Cursor c) {
super(context, c); // 调用父类构造函数
// TODO Auto-generated constructor stub
}
// 创建新的视图
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context); // 返回新的文件夹列表项视图
}
// 绑定视图与数据
@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) view).bind(folderName); // 绑定文件夹名称到视图
}
}
// 根据位置获取文件夹名称
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position); // 获取指定位置的游标
// 判断是否为根文件夹,返回相应的名称
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
// 内部类,表示文件夹列表项
private class FolderListItem extends LinearLayout { // 继承自 LinearLayout
private TextView mName; // 存储文件夹名称的文本视图
// 构造函数,接受上下文作为参数
public FolderListItem(Context context) {
super(context); // 调用父类构造函数
inflate(context, R.layout.folder_list_item, this); // 加载布局文件
mName = (TextView) findViewById(R.id.tv_folder_name); // 初始化文件夹名称文本视图
}
// 绑定文件夹名称到文本视图
public void bind(String name) {
mName.setText(name); // 设置文本视图的文本为文件夹名称
}
}
package net.micode.notes.ui; // 定义包名
import android.content.Context; // 导入上下文类
import android.database.Cursor; // 导入游标类
import android.view.View; // 导入视图类
import android.view.ViewGroup; // 导入视图组类
import android.widget.CursorAdapter; // 导入游标适配器类
import android.widget.LinearLayout; // 导入线性布局类
import android.widget.TextView; // 导入文本视图类
import net.micode.notes.R; // 导入资源类
import net.micode.notes.data.Notes; // 导入笔记数据类
import net.micode.notes.data.Notes.NoteColumns; // 导入笔记列类
public class FoldersListAdapter extends CursorAdapter { // 定义文件夹列表适配器类,继承自 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; // 名称列索引
// 构造函数,接受上下文和游标作为参数
public FoldersListAdapter(Context context, Cursor c) {
super(context, c); // 调用父类构造函数
// TODO Auto-generated constructor stub
}
// 创建新的视图
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context); // 返回新的文件夹列表项视图
}
// 绑定视图与数据
@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) view).bind(folderName); // 绑定文件夹名称到视图
}
}
// 根据位置获取文件夹名称
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position); // 获取指定位置的游标
// 判断是否为根文件夹,返回相应的名称
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
// 内部类,表示文件夹列表项
private class FolderListItem extends LinearLayout { // 继承自 LinearLayout
private TextView mName; // 存储文件夹名称的文本视图
// 构造函数,接受上下文作为参数
public FolderListItem(Context context) {
super(context); // 调用父类构造函数
inflate(context, R.layout.folder_list_item, this); // 加载布局文件
mName = (TextView) findViewById(R.id.tv_folder_name); // 初始化文件夹名称文本视图
}
// 绑定文件夹名称到文本视图
public void bind(String name) {
mName.setText(name); // 设置文本视图的文本为文件夹名称
}
}
package net.micode.notes.ui; // 定义包名
import android.content.Context; // 导入上下文类
import android.database.Cursor; // 导入游标类
import android.view.View; // 导入视图类
import android.view.ViewGroup; // 导入视图组类
import android.widget.CursorAdapter; // 导入游标适配器类
import android.widget.LinearLayout; // 导入线性布局类
import android.widget.TextView; // 导入文本视图类
import net.micode.notes.R; // 导入资源类
import net.micode.notes.data.Notes; // 导入笔记数据类
import net.micode.notes.data.Notes.NoteColumns; // 导入笔记列类
public class FoldersListAdapter extends CursorAdapter { // 定义文件夹列表适配器类,继承自 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; // 名称列索引
// 构造函数,接受上下文和游标作为参数
public FoldersListAdapter(Context context, Cursor c) {
super(context, c); // 调用父类构造函数
// TODO Auto-generated constructor stub
}
// 创建新的视图
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context); // 返回新的文件夹列表项视图
}
// 绑定视图与数据
@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) view).bind(folderName); // 绑定文件夹名称到视图
}
}
// 根据位置获取文件夹名称
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position); // 获取指定位置的游标
// 判断是否为根文件夹,返回相应的名称
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
// 内部类,表示文件夹列表项
private class FolderListItem extends LinearLayout { // 继承自 LinearLayout
private TextView mName; // 存储文件夹名称的文本视图
// 构造函数,接受上下文作为参数
public FolderListItem(Context context) {
super(context); // 调用父类构造函数
inflate(context, R.layout.folder_list_item, this); // 加载布局文件
mName = (TextView) findViewById(R.id.tv_folder_name); // 初始化文件夹名称文本视图
}
// 绑定文件夹名称到文本视图
public void bind(String name) {
mName.setText(name); // 设置文本视图的文本为文件夹名称
}
}

@ -15,144 +15,162 @@
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
// 导入Android的AlertDialog类, 用于显示警告对话框
import android.content.Context;
// 导入Android的Context类提供应用环境的描述和访问应用资源等
import android.content.DialogInterface;
// 导入Android的DialogInterface类表示对话框的接口
import android.content.DialogInterface.OnClickListener;
// 导入DialogInterface的OnClickListener接口用于处理对话框按钮点击事件
import android.content.DialogInterface.OnDismissListener;
// 导入DialogInterface的OnDismissListener接口用于处理对话框关闭事件
import android.content.Intent;
// 导入Android的Intent类用于在应用组件之间传递信息
import android.media.AudioManager;
// 导入Android的AudioManager类用于管理和控制音频设置
import android.media.MediaPlayer;
// 导入Android的MediaPlayer类用于播放音频文件
import android.media.RingtoneManager;
// 导入Android的RingtoneManager类用于获取系统默认铃声
import android.net.Uri;
// 导入Android的Uri类用于表示统一资源标识符
import android.os.Bundle;
// 导入Android的Bundle类用于传递Activity之间的状态及数据
import android.os.PowerManager;
// 导入Android的PowerManager类用于管理设备的电源状态
import android.provider.Settings;
// 导入Android的Settings类用于访问系统设置
import android.view.Window;
// 导入Android的Window类表示一个窗口的抽象
import android.view.WindowManager;
// 导入Android的WindowManager类用于管理和控制窗口的显示
import net.micode.notes.R;
// 导入应用程序的资源类,通常用于访问字符串、图像等资源
import net.micode.notes.data.Notes;
// 导入应用程序的Notes类表示笔记的数据模型
import net.micode.notes.tool.DataUtils;
// 导入应用程序的DataUtils工具类提供数据处理的实用方法
import java.io.IOException;
// 导入Java的IOException类用于处理输入输出异常
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;
// 定义AlarmAlertActivity类继承自Activity并实现了OnClickListener和OnDismissListener接口
private long mNoteId; // 定义一个长整型变量mNoteId用于存储笔记ID
private String mSnippet; // 定义一个字符串变量mSnippet用于存储笔记摘要
private static final int SNIPPET_PREW_MAX_LEN = 60; // 定义常量SNIPPET_PREW_MAX_LEN表示摘要最大长度
MediaPlayer mPlayer; // 定义一个MediaPlayer对象用于播放音频
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState); // 调用父类的onCreate方法执行Activity创建的基本操作
requestWindowFeature(Window.FEATURE_NO_TITLE); // 请求窗口特性,不显示标题
final Window win = getWindow();
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
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);
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();
Intent intent = getIntent(); // 获取启动该Activity的Intent
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
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;
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;
e.printStackTrace(); // 打印异常堆栈信息
return; // 发生异常时返回
}
mPlayer = new MediaPlayer();
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog();
playAlarmSound();
mPlayer = new MediaPlayer(); // 创建MediaPlayer实例
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { // 检查笔记是否在数据库中可见
showActionDialog(); // 显示操作对话框
playAlarmSound(); // 播放闹钟声音
} else {
finish();
finish(); // 如果笔记不可见则结束Activity
}
}
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); // 获取电源管理器实例
return pm.isScreenOn(); // 返回屏幕是否开启的状态
}
private void playAlarmSound() {
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
private void playAlarmSound() { // 方法用于播放闹钟声音
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); // 获取系统默认闹钟铃声的URI
int silentModeStreams = Settings.System.getInt(getContentResolver(),
int silentModeStreams = Settings.System.getInt(getContentResolver(), // 检查静音模式影响的音频流
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
mPlayer.setAudioStreamType(silentModeStreams);
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { // 如果静音模式影响闹钟流
mPlayer.setAudioStreamType(silentModeStreams); // 设置MediaPlayer的音频流类型
} else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); // 否则设置为闹钟音频流类型
}
try {
mPlayer.setDataSource(this, url);
mPlayer.prepare();
mPlayer.setLooping(true);
mPlayer.start();
mPlayer.setDataSource(this, url); // 设置MediaPlayer的数据源为闹钟铃声
mPlayer.prepare(); // 准备MediaPlayer
mPlayer.setLooping(true); // 设置音频循环播放
mPlayer.start(); // 开始播放音频
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
e.printStackTrace(); // 捕获并打印异常
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
e.printStackTrace(); // 捕获并打印异常
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
e.printStackTrace(); // 捕获并打印异常
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
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);
private void showActionDialog() { // 方法用于显示操作对话框
AlertDialog.Builder dialog = new AlertDialog.Builder(this); // 创建AlertDialog构建器
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);
dialog.show().setOnDismissListener(this); // 显示对话框,并设置关闭监听器
}
public void onClick(DialogInterface dialog, int which) {
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);
startActivity(intent);
case DialogInterface.BUTTON_NEGATIVE: // 如果点击了“进入”按钮
Intent intent = new Intent(this, NoteEditActivity.class); // 创建Intent用于跳转到笔记编辑Activity
intent.setAction(Intent.ACTION_VIEW); // 设置Intent的操作为查看
intent.putExtra(Intent.EXTRA_UID, mNoteId); // 将笔记ID作为额外数据传递
startActivity(intent); // 启动新的Activity
break;
default:
break;
break; // 默认情况无操作
}
}
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
public void onDismiss(DialogInterface dialog) { // 对话框关闭时的事件处理
stopAlarmSound(); // 停止闹钟音效
finish(); // 结束该Activity
}
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
mPlayer.release();
mPlayer = null;
private void stopAlarmSound() { // 方法用于停止播放闹钟声音
if (mPlayer != null) { // 检查MediaPlayer是否不为空
mPlayer.stop(); // 停止播放
mPlayer.release(); // 释放MediaPlayer资源
mPlayer = null; // 将MediaPlayer置为null
}
}
}

@ -15,51 +15,75 @@
*/
package net.micode.notes.ui;
// 定义当前类所属的包,通常用于组织代码并避免命名冲突
import android.app.AlarmManager;
// 导入Android的AlarmManager类用于设置和管理闹钟
import android.app.PendingIntent;
// 导入Android的PendingIntent类用于在Future时刻发送意图Intent
import android.content.BroadcastReceiver;
// 导入Android的BroadcastReceiver类用于接收广播消息
import android.content.ContentUris;
// 导入Android的ContentUris类用于处理内容提供者的URI
import android.content.Context;
// 导入Android的Context类提供应用程序环境的描述
import android.content.Intent;
// 导入Android的Intent类用于在应用组件之间传递信息
import android.database.Cursor;
// 导入Android的Cursor类用于访问查询结果
import net.micode.notes.data.Notes;
// 导入应用程序的Notes类表示笔记的数据模型
import net.micode.notes.data.Notes.NoteColumns;
// 导入Notes类中的列定义用于表明笔记的数据库字段
public class AlarmInitReceiver extends BroadcastReceiver {
// 定义类 AlarmInitReceiver继承自 BroadcastReceiver用于处理广播接收
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE
// 定义一个字符串数组 PROJECTION指定要查询的列
NoteColumns.ID, // 笔记的 ID
NoteColumns.ALERTED_DATE // 笔记的提醒日期
};
private static final int COLUMN_ID = 0;
private static final int COLUMN_ALERTED_DATE = 1;
private static final int COLUMN_ID = 0; // 定义常量 COLUMN_ID表示 ID 列的索引
private static final int COLUMN_ALERTED_DATE = 1; // 定义常量 COLUMN_ALERTED_DATE表示提醒日期列的索引
@Override
public void onReceive(Context context, Intent intent) {
long currentDate = System.currentTimeMillis();
// 当接收到广播时调用此方法
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);
// 通过内容解析器查询笔记内容提供者,获取光标
PROJECTION, // 指定要查询的列
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, // 查询条件:提醒日期大于当前时间且类型为笔记
new String[] { String.valueOf(currentDate) }, // 查询参数:当前时间的字符串形式
null);//查询时的排序参数,这里设置为 null表示使用默认排序。
if (c != null) {
//检查返回的 Cursor 是否为空,确保查询结果有效。
if (c.moveToFirst()) {
//如果 Cursor 能移动到第一行,表示查询到有结果。
do {
//开始循环处理每一行的查询结果。
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
//获取当前行的提醒日期,并将其赋值给 alertDate 变量。
Intent sender = new Intent(context, AlarmReceiver.class);
//创建一个 Intent 对象,指定要发送到的接收者 AlarmReceiver。
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
//设置 Intent 的数据为笔记的 URI使用 ContentUris 类附加当前行的笔记 ID。
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
//创建一个 PendingIntent用于将来发送广播传递刚创建的 Intent。
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
//获取系统的 AlarmManager 服务,以便设置闹钟。
alarmManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
//使用 AlarmManager 设置一个闹钟,指定为 RTC_WAKEUP 模式,设置该闹钟的提醒时间为 alertDate并关联到之前创建的 PendingIntent。
}while (c.moveToNext());
//移动到 Cursor 中的下一行,继续处理下一条查询结果,直到没有更多行。
}
c.close();
//关闭 Cursor释放与其相关联的资源防止内存泄漏。
}
}
}
}

@ -21,10 +21,16 @@ import android.content.Context;
import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver {
//声明一个公共类 AlarmReceiver它继承自 BroadcastReceiver用于处理接收到的广播。
@Override
//注解,表明接下来的方法是对父类方法的重写。
public void onReceive(Context context, Intent intent) {
intent.setClass(context, AlarmAlertActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
//定义 onReceive 方法当接收器接收到广播时会自动调用此方法。context: 提供调用应用环境的上下文信息。intent: 带有相关信息的 Intent可以用来处理接收到的数据。
intent.setClass(context, AlarmAlertActivity.class);
//将传入的 Intent 的目标类设置为 AlarmAlertActivity。这意味着当该 Intent 被处理时,会启动 AlarmAlertActivity 这个活动。
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//为 Intent 添加标志 FLAG_ACTIVITY_NEW_TASK表示要启动一个新的任务来运行这个活动。这通常用于在非活动上下文中启动活动。
context.startActivity(intent);
//使用提供的上下文来启动指定的活动,即 AlarmAlertActivity。这会使应用程序的 UI 界面切换到闹钟提醒的活动。
}
}
}

@ -14,100 +14,118 @@
* limitations under the License.
*/
package net.micode.notes.ui;
/*
* 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.
*/
import java.text.DateFormatSymbols;
import java.util.Calendar;
package net.micode.notes.ui; // 定义包名
import net.micode.notes.R;
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;
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 {
public class DateTimePicker extends FrameLayout { // 定义日期时间选择器类,继承自 FrameLayout
private static final boolean DEFAULT_ENABLE_STATE = true;
private static final boolean DEFAULT_ENABLE_STATE = true; // 默认启用状态为 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;
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 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 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; // AM/PM选择器的最小值
private static final int AMPM_SPINNER_MAX_VAL = 1; // AM/PM选择器的最大值
private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
private Calendar mDate;
private final NumberPicker mDateSpinner; // 日期选择器
private final NumberPicker mHourSpinner; // 小时选择器
private final NumberPicker mMinuteSpinner; // 分钟选择器
private final NumberPicker mAmPmSpinner; // AM/PM选择器
private Calendar mDate; // 存储日期的日历对象
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; // 用于存储日期显示值的数组
private boolean mIsAm;
private boolean mIsAm; // 存储当前是否为 AM 的标志
private boolean mIs24HourView;
private boolean mIs24HourView; // 存储当前是否为24小时制的标志
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
private boolean mIsEnabled = DEFAULT_ENABLE_STATE; // 存储当前启用状态
private boolean mInitialising;
private boolean mInitialising; // 存储初始化状态的标志
private OnDateTimeChangedListener mOnDateTimeChangedListener;
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();
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();
if (!mIs24HourView) {
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
boolean isDateChanged = false; // 标记日期是否变化
Calendar cal = Calendar.getInstance(); // 获取当前日期的日历对象
if (!mIs24HourView) { // 如果是 12 小时制
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { // 从 PM 切换到 AM
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
cal.add(Calendar.DAY_OF_YEAR, 1); // 日期加 1
isDateChanged = true; // 标记日期变化
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { // 从 AM 切换到 PM
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
cal.add(Calendar.DAY_OF_YEAR, -1); // 日期减 1
isDateChanged = true; // 标记日期变化
}
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();
mIsAm = !mIsAm; // 切换 AM/PM 状态
updateAmPmControl(); // 更新 AM/PM 控制
}
} else {
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
} else { // 如果是 24 小时制
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { // 从 23 切换到 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.add(Calendar.DAY_OF_YEAR, 1); // 日期加 1
isDateChanged = true; // 标记日期变化
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { // 从 0 切换到 23
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
cal.add(Calendar.DAY_OF_YEAR, -1); // 日期减 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) {
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));
@ -115,371 +133,388 @@ public class DateTimePicker extends FrameLayout {
}
};
// 分钟选择器值变化的监听器
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;
int minValue = mMinuteSpinner.getMinValue(); // 获取分钟选择器的最小值
int maxValue = mMinuteSpinner.getMaxValue(); // 获取分钟选择器的最大值
int offset = 0; // 记录偏移量
if (oldVal == maxValue && newVal == minValue) {
offset += 1;
offset += 1; // 增加偏移量
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1;
offset -= 1; // 减少偏移量
}
if (offset != 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour());
updateDateControl();
int newHour = getCurrentHourOfDay();
if (offset != 0) { // 如果偏移量不为零
mDate.add(Calendar.HOUR_OF_DAY, offset); // 更新小时
mHourSpinner.setValue(getCurrentHour()); // 更新小时选择器的值
updateDateControl(); // 更新日期控制
int newHour = getCurrentHourOfDay(); // 获取当前小时
// 更新 AM/PM 状态
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
updateAmPmControl();
mIsAm = false; // 设为 PM
updateAmPmControl(); // 更新 AM/PM 控制
} else {
mIsAm = true;
updateAmPmControl();
mIsAm = true; // 设为 AM
updateAmPmControl(); // 更新 AM/PM 控制
}
}
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
mDate.set(Calendar.MINUTE, newVal); // 更新日历中的分钟
onDateTimeChanged(); // 通知日期时间已更改
}
};
// AM/PM 选择器值变化的监听器
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;
mIsAm = !mIsAm; // 切换 AM/PM 状态
// 根据 AM/PM 状态更新小时
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); // 如果为 AM减去 12 小时
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); // 如果为 PM加上 12 小时
}
updateAmPmControl();
onDateTimeChanged();
updateAmPmControl(); // 更新 AM/PM 控制
onDateTimeChanged(); // 通知日期时间已更改
}
};
// 定义日期时间改变的监听器接口
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
int dayOfMonth, int hourOfDay, int minute); // 方法签名
}
// 构造函数,使用当前时间作为参数
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
this(context, System.currentTimeMillis()); // 调用重载构造函数
}
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
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);
super(context); // 调用父类构造函数
mDate = Calendar.getInstance(); // 初始化当前日期
mInitialising = true; // 标记当前处于初始化状态
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; // 根据当前小时判断是否为 AM
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);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); // 设置最小值
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); // 设置最大值
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); // 设置值变化监听器
// 初始化小时选择器
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
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);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); // 设置最小值
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); // 设置最大值
mMinuteSpinner.setOnLongPressUpdateInterval(100); // 设置长按更新间隔
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); // 设置值变化监听器
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
// 初始化 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);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); // 设置最小值
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); // 设置最大值
mAmPmSpinner.setDisplayedValues(stringsForAmPm); // 设置显示值
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); // 设置值变化监听器
// update controls to initial state
updateDateControl();
updateHourControl();
updateAmPmControl();
// 更新控件到初始状态
updateDateControl(); // 更新日期选择器状态
updateHourControl(); // 更新小时选择器状态
updateAmPmControl(); // 更新 AM/PM 选择器状态
set24HourView(is24HourView);
set24HourView(is24HourView); // 设置是否使用 24 小时制
// set to current time
setCurrentDate(date);
// 设置为当前时间
setCurrentDate(date); // 设置当前日期为传入的日期
setEnabled(isEnabled());
setEnabled(isEnabled()); // 设置选择器的启用状态
// set the content descriptions
mInitialising = false;
// 设置内容描述
mInitialising = false; // 标记初始化完成
}
@Override
public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) {
return;
public void setEnabled(boolean enabled) { // 重写设置启用状态的方法
if (mIsEnabled == enabled) { // 如果当前状态与目标状态相同,则返回
return;
}
super.setEnabled(enabled);
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled);
mIsEnabled = enabled;
super.setEnabled(enabled); // 调用父类的设置启用方法
// 设置各个选择器的启用状态
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled);
mIsEnabled = enabled; // 更新当前启用状态
}
@Override
public boolean isEnabled() {
return mIsEnabled;
public boolean isEnabled() { // 重写获取启用状态的方法
return mIsEnabled; // 返回当前启用状态
}
/**
* Get the current date in millis
*
*
* @return the current date in millis
* @return
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
return mDate.getTimeInMillis(); // 返回当前日期的毫秒表示
}
/**
* Set the current date
*
*
* @param date The current date in millis
* @param date
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(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
* @param year
* @param month
* @param dayOfMonth
* @param hourOfDay
* @param minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
setCurrentYear(year); // 设置当前年份
setCurrentMonth(month); // 设置当前月份
setCurrentDay(dayOfMonth); // 设置当前日期
setCurrentHour(hourOfDay); // 设置当前小时
setCurrentMinute(minute); // 设置当前分钟
}
/**
* Get current year
*
*
* @return The current year
* @return
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
return mDate.get(Calendar.YEAR); // 返回当前年份
}
/**
* Set current year
*
*
* @param year The current year
* @param year
*/
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) {
if (!mInitialising && year == getCurrentYear()) { // 如果不在初始化并且当前年份未变化,则返回
return;
}
mDate.set(Calendar.YEAR, year);
updateDateControl();
onDateTimeChanged();
mDate.set(Calendar.YEAR, year); // 更新日历中的年份
updateDateControl(); // 更新日期选择器控制
onDateTimeChanged(); // 通知日期时间已更改
}
/**
* Get current month in the year
*
*
* @return The current month in the year
* @return
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
return mDate.get(Calendar.MONTH); // 返回当前月份
}
/**
* Set current month in the year
*
*
* @param month The month in the year
* @param month
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
if (!mInitialising && month == getCurrentMonth()) { // 如果不在初始化且当前月份未变化,则返回
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl();
onDateTimeChanged();
mDate.set(Calendar.MONTH, month); // 更新日历中的月份
updateDateControl(); // 更新日期控制
onDateTimeChanged(); // 通知日期时间已更改
}
/**
* Get current day of the month
*
*
* @return The day of the month
* @return
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
return mDate.get(Calendar.DAY_OF_MONTH); // 返回当前日期
}
/**
* Set current day of the month
*
*
* @param dayOfMonth The day of the month
* @param dayOfMonth
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
if (!mInitialising && dayOfMonth == getCurrentDay()) { // 如果不在初始化且当前日期未变化,则返回
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
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
*
*
* @return 24
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
return mDate.get(Calendar.HOUR_OF_DAY); // 返回当前 24 小时制的小时
}
private int getCurrentHour() {
if (mIs24HourView){
return getCurrentHourOfDay();
private int getCurrentHour() { // 获取当前小时
if (mIs24HourView){ // 如果是 24 小时制
return getCurrentHourOfDay(); // 返回 24 小时制的当前小时
} else {
int hour = getCurrentHourOfDay();
int hour = getCurrentHourOfDay(); // 获取当前小时
// 转换为 12 小时制
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
return hour == 0 ? HOURS_IN_HALF_DAY : hour; // 处理 0 点的情况
}
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
*
*
* @param hourOfDay
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { // 如果不在初始化且当前小时未变化,则返回
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
if (!mIs24HourView) {
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY;
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); // 更新日历中的小时
if (!mIs24HourView) { // 如果不是 24 小时制
if (hourOfDay >= HOURS_IN_HALF_DAY) { // 如果属于 PM
mIsAm = false; // 设置为 PM
if (hourOfDay > HOURS_IN_HALF_DAY) { // 如果小时大于 12
hourOfDay -= HOURS_IN_HALF_DAY; // 转换为 12 小时制
}
} else {
mIsAm = true;
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;
} else { // 如果属于 AM
mIsAm = true; // 设置为 AM
if (hourOfDay == 0) { // 如果是 0 点
hourOfDay = HOURS_IN_HALF_DAY; // 转换为 12 点
}
}
updateAmPmControl();
updateAmPmControl(); // 更新 AM/PM 控制
}
mHourSpinner.setValue(hourOfDay);
onDateTimeChanged();
mHourSpinner.setValue(hourOfDay); // 更新小时选择器的值
onDateTimeChanged(); // 通知日期时间已更改
}
/**
* Get currentMinute
*
*
* @return The Current Minute
* @return
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
return mDate.get(Calendar.MINUTE); // 返回当前分钟
}
/**
* Set current minute
*
*
* @param minute
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
if (!mInitialising && minute == getCurrentMinute()) { // 如果不在初始化且当前分钟未变化,则返回
return;
}
mMinuteSpinner.setValue(minute);
mDate.set(Calendar.MINUTE, minute);
onDateTimeChanged();
mMinuteSpinner.setValue(minute); // 更新分钟选择器的值
mDate.set(Calendar.MINUTE, minute); // 更新日历中的分钟
onDateTimeChanged(); // 通知日期时间已更改
}
/**
* @return true if this is in 24 hour view else false.
* @return 24 true false
*/
public boolean is24HourView () {
return mIs24HourView;
return mIs24HourView; // 返回当前是否在 24 小时制
}
/**
* Set whether in 24 hour or AM/PM mode.
* 使 24 AM/PM
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
* @param is24HourView true 24 false AM/PM
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
if (mIs24HourView == is24HourView) { // 如果当前状态与目标状态相同则返回
return;
}
mIs24HourView = is24HourView;
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
int hour = getCurrentHourOfDay();
updateHourControl();
setCurrentHour(hour);
updateAmPmControl();
mIs24HourView = is24HourView; // 更新当前模式
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); // 根据模式设置 AM/PM 选择器的可见性
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);
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();
mDateSpinner.setDisplayedValues(mDateDisplayValues); // 设置日期选择器显示的值
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); // 设置日期选择器的默认值为一周中间的值
mDateSpinner.invalidate(); // 刷新日期选择器
}
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
private void updateAmPmControl() { // 更新 AM/PM 选择器状态
if (mIs24HourView) { // 如果为 24 小时制
mAmPmSpinner.setVisibility(View.GONE); // 隐藏 AM/PM 选择器
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
int index = mIsAm ? Calendar.AM : Calendar.PM; // 根据 AM/PM 标志获取索引
mAmPmSpinner.setValue(index); // 设置 AM/PM 选择器的值
mAmPmSpinner.setVisibility(View.VISIBLE); // 显示 AM/PM 选择器
}
}
private void updateHourControl() {
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
private void updateHourControl() { // 更新小时选择器状态
if (mIs24HourView) { // 如果为 24 小时制
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);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
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
*
* @param callback null
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
mOnDateTimeChangedListener = callback; // 更新监听器
}
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
private void onDateTimeChanged() { // 日期时间更改的处理方法
if (mOnDateTimeChangedListener != null) { // 如果有设置的监听器
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), // 调用监听器的方法,传递当前的日期和时间
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}
}

@ -14,77 +14,82 @@
* limitations under the License.
*/
package net.micode.notes.ui;
package net.micode.notes.ui; // 定义包名
import java.util.Calendar;
import java.util.Calendar; // 导入日历类
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
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;
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 {
public class DateTimePickerDialog extends AlertDialog implements OnClickListener { // 定义日期时间选择对话框类,继承自 AlertDialog并实现 OnClickListener 接口
private Calendar mDate = Calendar.getInstance();
private boolean mIs24HourView;
private OnDateTimeSetListener mOnDateTimeSetListener;
private DateTimePicker mDateTimePicker;
private Calendar mDate = Calendar.getInstance(); // 初始化当前日期的日历对象
private boolean mIs24HourView; // 存储当前是否为 24 小时制的标志
private OnDateTimeSetListener mOnDateTimeSetListener; // 日期时间设置监听器
private DateTimePicker mDateTimePicker; // 日期时间选择器实例
// 定义日期时间设置监听器接口
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
void OnDateTimeSet(AlertDialog dialog, long date); // 日期时间设置方法签名
}
// 构造函数,接受上下文和日期参数
public DateTimePickerDialog(Context context, long date) {
super(context);
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker);
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
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());
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);
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()));
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;
mIs24HourView = is24HourView; // 更新 24 小时制标志
}
// 设置日期时间设置监听器
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = 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_24HOUR;
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
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());
if (mOnDateTimeSetListener != null) { // 如果有设置的监听器
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); // 调用监听器方法,传递当前日期
}
}
}

@ -14,48 +14,52 @@
* limitations under the License.
*/
package net.micode.notes.ui;
package net.micode.notes.ui; // 定义包名
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import android.content.Context; // 导入上下文类
import android.view.Menu; // 导入菜单类
import android.view.MenuItem; // 导入菜单项类
import android.view.View; // 导入视图类
import android.view.View.OnClickListener; // 导入点击监听器接口
import android.widget.Button; // 导入按钮类
import android.widget.PopupMenu; // 导入弹出菜单类
import android.widget.PopupMenu.OnMenuItemClickListener; // 导入弹出菜单项点击监听器接口
import net.micode.notes.R;
import net.micode.notes.R; // 导入资源类
public class DropdownMenu {
private Button mButton;
private PopupMenu mPopupMenu;
private Menu mMenu;
public class DropdownMenu { // 定义下拉菜单类
private Button mButton; // 存储按钮对象
private PopupMenu mPopupMenu; // 存储弹出菜单对象
private Menu mMenu; // 存储菜单对象
// 构造函数,接受上下文、按钮和菜单资源 ID
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button;
mButton.setBackgroundResource(R.drawable.dropdown_icon);
mPopupMenu = new PopupMenu(context, mButton);
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
mButton.setOnClickListener(new OnClickListener() {
mButton = button; // 初始化按钮
mButton.setBackgroundResource(R.drawable.dropdown_icon); // 设置按钮背景为下拉图标
mPopupMenu = new PopupMenu(context, mButton); // 创建弹出菜单,依附于指定按钮
mMenu = mPopupMenu.getMenu(); // 获取弹出菜单的菜单对象
mPopupMenu.getMenuInflater().inflate(menuId, mMenu); // 将指定的菜单资源填充到菜单对象中
mButton.setOnClickListener(new OnClickListener() { // 设置按钮点击监听器
public void onClick(View v) {
mPopupMenu.show();
mPopupMenu.show(); // 显示弹出菜单
}
});
}
// 设置下拉菜单项点击监听器
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
if (mPopupMenu != null) { // 如果弹出菜单不为空
mPopupMenu.setOnMenuItemClickListener(listener); // 设置菜单项点击监听器
}
}
// 根据 ID 查找菜单项
public MenuItem findItem(int id) {
return mMenu.findItem(id);
return mMenu.findItem(id); // 根据 ID 返回菜单项
}
// 设置按钮的标题
public void setTitle(CharSequence title) {
mButton.setText(title);
mButton.setText(title); // 更新按钮文本为给定的标题
}
}
}

@ -14,67 +14,75 @@
* limitations under the License.
*/
package net.micode.notes.ui;
package net.micode.notes.ui; // 定义包名
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.content.Context; // 导入上下文类
import android.database.Cursor; // 导入游标类
import android.view.View; // 导入视图类
import android.view.ViewGroup; // 导入视图组类
import android.widget.CursorAdapter; // 导入游标适配器类
import android.widget.LinearLayout; // 导入线性布局类
import android.widget.TextView; // 导入文本视图类
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.R; // 导入资源类
import net.micode.notes.data.Notes; // 导入笔记数据类
import net.micode.notes.data.Notes.NoteColumns; // 导入笔记列类
public class FoldersListAdapter extends CursorAdapter {
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
public class FoldersListAdapter extends CursorAdapter { // 定义文件夹列表适配器类,继承自 CursorAdapter
public static final String [] PROJECTION = { // 定义需要查询的字段数组
NoteColumns.ID, // 笔记 ID 列
NoteColumns.SNIPPET // 笔记摘要列
};
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
public static final int ID_COLUMN = 0; // ID 列索引
public static final int NAME_COLUMN = 1; // 名称列索引
// 构造函数,接受上下文和游标作为参数
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
super(context, c); // 调用父类构造函数
// TODO Auto-generated constructor stub
}
// 创建新的视图
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
return new FolderListItem(context); // 返回新的文件夹列表项视图
}
// 绑定视图与数据
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
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) view).bind(folderName);
((FolderListItem) view).bind(folderName); // 绑定文件夹名称到视图
}
}
// 根据位置获取文件夹名称
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
Cursor cursor = (Cursor) getItem(position); // 获取指定位置的游标
// 判断是否为根文件夹,返回相应的名称
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
private class FolderListItem extends LinearLayout {
private TextView mName;
// 内部类,表示文件夹列表项
private class FolderListItem extends LinearLayout { // 继承自 LinearLayout
private TextView mName; // 存储文件夹名称的文本视图
// 构造函数,接受上下文作为参数
public FolderListItem(Context context) {
super(context);
inflate(context, R.layout.folder_list_item, this);
mName = (TextView) findViewById(R.id.tv_folder_name);
super(context); // 调用父类构造函数
inflate(context, R.layout.folder_list_item, this); // 加载布局文件
mName = (TextView) findViewById(R.id.tv_folder_name); // 初始化文件夹名称文本视图
}
// 绑定文件夹名称到文本视图
public void bind(String name) {
mName.setText(name);
mName.setText(name); // 设置文本视图的文本为文件夹名称
}
}
}
}

File diff suppressed because it is too large Load Diff

@ -14,204 +14,201 @@
* limitations under the License.
*/
package net.micode.notes.ui;
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;
import android.util.AttributeSet;
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;
import net.micode.notes.R;
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;
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
package net.micode.notes.ui; // 定义包名
import android.content.Context; // 导入Context类
import android.graphics.Rect; // 导入Rect类
import android.text.Layout; // 导入Layout类
import android.text.Selection; // 导入Selection类
import android.text.Spanned; // 导入Spanned接口
import android.text.TextUtils; // 导入TextUtils类
import android.text.style.URLSpan; // 导入URLSpan类
import android.util.AttributeSet; // 导入AttributeSet类
import android.util.Log; // 导入Log类
import android.view.ContextMenu; // 导入ContextMenu类
import android.view.KeyEvent; // 导入KeyEvent类
import android.view.MenuItem; // 导入MenuItem类
import android.view.MenuItem.OnMenuItemClickListener; // 导入菜单项点击监听器接口
import android.view.MotionEvent; // 导入MotionEvent类
import android.widget.EditText; // 导入EditText类
import net.micode.notes.R; // 导入资源类
import java.util.HashMap; // 导入HashMap类
import java.util.Map; // 导入Map接口
public class NoteEditText extends EditText { // 定义NoteEditText类继承自EditText
private static final String TAG = "NoteEditText"; // 日志TAG
private int mIndex; // 当前索引
private int mSelectionStartBeforeDelete; // 删除前的选择起始位置
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>(); // 链接方案动作资源映射
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
* {@link NoteEditActivity}
*/
public interface OnTextViewChangeListener {
public interface OnTextViewChangeListener { // 文本视图变化监听器接口
/**
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
* {@link KeyEvent#KEYCODE_DEL}
*/
void onEditTextDelete(int index, String text);
void onEditTextDelete(int index, String text); // 删除事件
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
* {@link KeyEvent#KEYCODE_ENTER}
*/
void onEditTextEnter(int index, String text);
void onEditTextEnter(int index, String text); // 添加事件
/**
* Hide or show item option when text change
*
*/
void onTextChange(int index, boolean hasText);
void onTextChange(int index, boolean hasText); // 文本变化事件
}
private OnTextViewChangeListener mOnTextViewChangeListener;
private OnTextViewChangeListener mOnTextViewChangeListener; // 文本视图变化监听器
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
public NoteEditText(Context context) { // 构造函数
super(context, null); // 调用父类构造函数
mIndex = 0; // 初始化索引为0
}
public void setIndex(int index) {
mIndex = index;
public void setIndex(int index) { // 设置索引
mIndex = index; // 更新索引
}
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { // 设置文本视图变化监听器
mOnTextViewChangeListener = listener; // 更新监听器
}
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
public NoteEditText(Context context, AttributeSet attrs) { // 带属性的构造函数
super(context, attrs, android.R.attr.editTextStyle); // 调用父类构造函数
}
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
public NoteEditText(Context context, AttributeSet attrs, int defStyle) { // 带属性和样式的构造函数
super(context, attrs, defStyle); // 调用父类构造函数
// TODO Auto-generated constructor stub
}
@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);
public boolean onTouchEvent(MotionEvent event) { // 触摸事件处理
switch (event.getAction()) { // 处理触摸事件的不同动作
case MotionEvent.ACTION_DOWN: // 按下动作
int x = (int) event.getX(); // 获取触摸点的x坐标
int y = (int) event.getY(); // 获取触摸点的y坐标
x -= getTotalPaddingLeft(); // 减去左侧填充
y -= getTotalPaddingTop(); // 减去顶部填充
x += getScrollX(); // 添加水平滚动值
y += getScrollY(); // 添加垂直滚动值
Layout layout = getLayout(); // 获取布局
int line = layout.getLineForVertical(y); // 获取y坐标对应的行号
int off = layout.getOffsetForHorizontal(line, x); // 获取x坐标对应的偏移
Selection.setSelection(getText(), off); // 设置文本选择
break;
}
return super.onTouchEvent(event);
return super.onTouchEvent(event); // 调用父类的触摸事件处理
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
return false;
public boolean onKeyDown(int keyCode, KeyEvent event) { // 按键按下处理
switch (keyCode) { // 根据按键代码进行处理
case KeyEvent.KEYCODE_ENTER: // 回车键
if (mOnTextViewChangeListener != null) { // 如果设置了监听器
return false; // 不处理
}
break;
case KeyEvent.KEYCODE_DEL:
mSelectionStartBeforeDelete = getSelectionStart();
case KeyEvent.KEYCODE_DEL: // 删除键
mSelectionStartBeforeDelete = getSelectionStart(); // 保存删除前的选择起始位置
break;
default:
break;
}
return super.onKeyDown(keyCode, event);
return super.onKeyDown(keyCode, event); // 调用父类的按键处理
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
if (mOnTextViewChangeListener != null) {
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
public boolean onKeyUp(int keyCode, KeyEvent event) { // 按键松开处理
switch(keyCode) { // 根据按键代码进行处理
case KeyEvent.KEYCODE_DEL: // 删除键
if (mOnTextViewChangeListener != null) { // 如果设置了监听器
if (0 == mSelectionStartBeforeDelete && mIndex != 0) { // 如果删除前选择位置为0且当前索引不为0
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); // 通知删除
return true; // 返回true表示事件已处理
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
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();
setText(getText().subSequence(0, selectionStart));
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
case KeyEvent.KEYCODE_ENTER: // 回车键
if (mOnTextViewChangeListener != null) { // 如果设置了监听器
int selectionStart = getSelectionStart(); // 获取当前选择位置
String text = getText().subSequence(selectionStart, length()).toString(); // 获取当前输入文本
setText(getText().subSequence(0, selectionStart)); // 更新文本,去掉输入的部分
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); // 通知输入
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
Log.d(TAG, "OnTextViewChangeListener was not seted"); // 日志输出
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event);
return super.onKeyUp(keyCode, event); // 调用父类的按键处理
}
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {
if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mIndex, false);
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { // 焦点变化时调用
if (mOnTextViewChangeListener != null) { // 如果设置了监听器
if (!focused && TextUtils.isEmpty(getText())) { // 如果失去焦点且文本为空
mOnTextViewChangeListener.onTextChange(mIndex, false); // 通知文本变化为无文本
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true);
mOnTextViewChangeListener.onTextChange(mIndex, true); // 通知文本变化为有文本
}
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
super.onFocusChanged(focused, direction, previouslyFocusedRect); // 调用父类的焦点变化处理
}
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema);
break;
protected void onCreateContextMenu(ContextMenu menu) { // 创建上下文菜单
if (getText() instanceof Spanned) { // 如果文本是Spanned类型
int selStart = getSelectionStart(); // 获取选择起始位置
int selEnd = getSelectionEnd(); // 获取选择结束位置
int min = Math.min(selStart, selEnd); // 选择的最小位置
int max = Math.max(selStart, selEnd); // 选择的最大位置
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); // 获取选中的URLSpan
if (urls.length == 1) { // 如果选中的URLSpan只有一个
int defaultResId = 0; // 初始化默认资源ID
for (String schema : sSchemaActionResMap.keySet()) { // 遍历链接方案映射
if (urls[0].getURL().indexOf(schema) >= 0) { // 检查URL是否包含方案
defaultResId = sSchemaActionResMap.get(schema); // 获取对应的资源ID
break; // 跳出循环
}
}
if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
if (defaultResId == 0) { // 如果没有匹配的方案
defaultResId = R.string.note_link_other; // 设置为其他链接
}
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( // 添加菜单项
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// goto a new intent
urls[0].onClick(NoteEditText.this);
return true;
public boolean onMenuItemClick(MenuItem item) { // 菜单项点击事件
// 触发网址点击的意图
urls[0].onClick(NoteEditText.this); // 当点击URL时调用其onClick方法
return true; // 返回true表示事件已处理
}
});
}
}
super.onCreateContextMenu(menu);
super.onCreateContextMenu(menu); // 调用父类的上下文菜单创建处理
}
}
}

@ -14,211 +14,213 @@
* 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 {
static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE,
NoteColumns.HAS_ATTACHMENT,
NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT,
NoteColumns.PARENT_ID,
NoteColumns.SNIPPET,
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
package net.micode.notes.ui; // 定义包名
import android.content.Context; // 导入Context类
import android.database.Cursor; // 导入Cursor类
import android.text.TextUtils; // 导入TextUtils类
import net.micode.notes.data.Contact; // 导入Contact类
import net.micode.notes.data.Notes; // 导入Notes类
import net.micode.notes.data.Notes.NoteColumns; // 导入Notes列类
import net.micode.notes.tool.DataUtils; // 导入数据工具类
public class NoteItemData { // 定义笔记项数据类
static final String [] PROJECTION = new String [] { // 定义要查询的列数组
NoteColumns.ID, // 笔记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;
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;
private static final int SNIPPET_COLUMN = 8;
private static final int TYPE_COLUMN = 9;
private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11;
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private boolean mHasAttachment;
private long mModifiedDate;
private int mNotesCount;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private String mName;
private String mPhoneNumber;
private boolean mIsLastItem;
private boolean mIsFirstItem;
private boolean mIsOnlyOneItem;
private boolean mIsOneNoteFollowingFolder;
private boolean mIsMultiNotesFollowingFolder;
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; // 背景颜色ID列索引
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; // 是否有多个笔记在文件夹下
// 构造函数,接收上下文和游标作为参数
public NoteItemData(Context context, Cursor cursor) {
mId = cursor.getLong(ID_COLUMN);
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
mParentId = cursor.getLong(PARENT_ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
mId = cursor.getLong(ID_COLUMN); // 从游标中获取笔记ID
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); // 从游标中获取警报日期
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); // 从游标中获取背景颜色ID
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); // 从游标中获取创建日期
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false; // 根据游标中的信息判断是否有附件
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); // 从游标中获取修改日期
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); // 从游标中获取笔记数量
mParentId = cursor.getLong(PARENT_ID_COLUMN); // 从游标中获取父级ID
mSnippet = cursor.getString(SNIPPET_COLUMN); // 从游标中获取摘要
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( // 清除已勾选标记
NoteEditActivity.TAG_UNCHECKED, "");
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) {
mName = Contact.getContact(context, mPhoneNumber);
if (mName == null) {
mName = mPhoneNumber;
mType = cursor.getInt(TYPE_COLUMN); // 从游标中获取笔记类型
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); // 从游标中获取小部件ID
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); // 从游标中获取小部件类型
mPhoneNumber = ""; // 初始化联系电话为空
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { // 如果父级ID为通话记录文件夹
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); // 根据笔记ID获取通话记录电话号码
if (!TextUtils.isEmpty(mPhoneNumber)) { // 如果电话号码不为空
mName = Contact.getContact(context, mPhoneNumber); // 获取联系人的名称
if (mName == null) { // 如果没有联系人名称
mName = mPhoneNumber; // 将名称设置为电话号码
}
}
}
if (mName == null) {
mName = "";
if (mName == null) { // 如果名称还是为null
mName = ""; // 设置为空字符串
}
checkPostion(cursor);
checkPostion(cursor); // 检查位置信息
}
private void checkPostion(Cursor cursor) {
mIsLastItem = cursor.isLast() ? true : false;
mIsFirstItem = cursor.isFirst() ? true : false;
mIsOnlyOneItem = (cursor.getCount() == 1);
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
private void checkPostion(Cursor cursor) { // 检查当前项目在游标中的位置信息
mIsLastItem = cursor.isLast(); // 判断是否为最后一项
mIsFirstItem = cursor.isFirst(); // 判断是否为第一项
mIsOnlyOneItem = (cursor.getCount() == 1); // 判断是否为唯一一项
mIsMultiNotesFollowingFolder = false; // 初始化为false
mIsOneNoteFollowingFolder = false; // 初始化为false
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
int position = cursor.getPosition();
if (cursor.moveToPrevious()) {
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { // 如果当前类型为笔记且不是第一项
int position = cursor.getPosition(); // 获取当前游标位置
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;
if (cursor.getCount() > (position + 1)) { // 如果游标数量大于当前位置加1
mIsMultiNotesFollowingFolder = true; // 模式是多个笔记在文件夹下
} else {
mIsOneNoteFollowingFolder = true;
mIsOneNoteFollowingFolder = true; // 模式是唯一笔记在文件夹下
}
}
if (!cursor.moveToNext()) {
throw new IllegalStateException("cursor move to previous but can't move back");
if (!cursor.moveToNext()) { // 移动回当前游标位置失败
throw new IllegalStateException("cursor move to previous but can't move back"); // 抛出异常
}
}
}
}
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
public boolean isOneFollowingFolder() { // 判断是否为唯一笔记在文件夹下
return mIsOneNoteFollowingFolder; // 返回结果
}
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
public boolean isMultiFollowingFolder() { // 判断是否有多个笔记在文件夹下
return mIsMultiNotesFollowingFolder; // 返回结果
}
public boolean isLast() {
return mIsLastItem;
public boolean isLast() { // 判断是否为最后一项
return mIsLastItem; // 返回结果
}
public String getCallName() {
return mName;
public String getCallName() { // 获取通话记录联系人名称
return mName; // 返回联系人名称
}
public boolean isFirst() {
return mIsFirstItem;
public boolean isFirst() { // 判断是否为第一项
return mIsFirstItem; // 返回结果
}
public boolean isSingle() {
return mIsOnlyOneItem;
public boolean isSingle() { // 判断是否为唯一一项
return mIsOnlyOneItem; // 返回结果
}
public long getId() {
return mId;
public long getId() { // 获取笔记ID
return mId; // 返回笔记ID
}
public long getAlertDate() {
return mAlertDate;
public long getAlertDate() { // 获取警报日期
return mAlertDate; // 返回警报日期
}
public long getCreatedDate() {
return mCreatedDate;
public long getCreatedDate() { // 获取创建日期
return mCreatedDate; // 返回创建日期
}
public boolean hasAttachment() {
return mHasAttachment;
public boolean hasAttachment() { // 判断是否有附件
return mHasAttachment; // 返回结果
}
public long getModifiedDate() {
return mModifiedDate;
public long getModifiedDate() { // 获取修改日期
return mModifiedDate; // 返回修改日期
}
public int getBgColorId() {
return mBgColorId;
public int getBgColorId() { // 获取背景颜色ID
return mBgColorId; // 返回背景颜色ID
}
public long getParentId() {
return mParentId;
public long getParentId() { // 获取父级ID
return mParentId; // 返回父级ID
}
public int getNotesCount() {
return mNotesCount;
public int getNotesCount() { // 获取笔记数量
return mNotesCount; // 返回笔记数量
}
public long getFolderId () {
return mParentId;
public long getFolderId () { // 获取文件夹ID
return mParentId; // 返回父级ID
}
public int getType() {
return mType;
public int getType() { // 获取笔记类型
return mType; // 返回笔记类型
}
public int getWidgetType() {
return mWidgetType;
public int getWidgetType() { // 获取小部件类型
return mWidgetType; // 返回小部件类型
}
public int getWidgetId() {
return mWidgetId;
public int getWidgetId() { // 获取小部件ID
return mWidgetId; // 返回小部件ID
}
public String getSnippet() {
return mSnippet;
public String getSnippet() { // 获取摘要
return mSnippet; // 返回摘要
}
public boolean hasAlert() {
return (mAlertDate > 0);
public boolean hasAlert() { // 判断是否有警报
return (mAlertDate > 0); // 根据警报日期判断
}
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
public boolean isCallRecord() { // 判断是否为通话记录笔记
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); // 如果父ID为通话记录且电话号码不为空
}
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
public static int getNoteType(Cursor cursor) { // 根据游标获取笔记类型
return cursor.getInt(TYPE_COLUMN); // 返回笔记类型
}
}
}

File diff suppressed because it is too large Load Diff

@ -14,171 +14,169 @@
* limitations under the License.
*/
package net.micode.notes.ui;
package net.micode.notes.ui; // 定义包名
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 android.content.Context; // 导入Context类
import android.database.Cursor; // 导入Cursor类
import android.util.Log; // 导入Log类
import android.view.View; // 导入View类
import android.view.ViewGroup; // 导入ViewGroup类
import android.widget.CursorAdapter; // 导入CursorAdapter类
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes; // 导入Notes数据类
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Collection; // 导入Collection接口
import java.util.HashMap; // 导入HashMap类
import java.util.HashSet; // 导入HashSet类
import java.util.Iterator; // 导入Iterator接口
public class NotesListAdapter extends CursorAdapter { // 定义 NotesListAdapter 类,继承自 CursorAdapter
private static final String TAG = "NotesListAdapter"; // 日志标签
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;
private Context mContext; // 上下文
private HashMap<Integer, Boolean> mSelectedIndex; // 用于记录选择状态的哈希映射
private int mNotesCount; // 笔记数量
private boolean mChoiceMode; // 选择模式标志
public static class AppWidgetAttribute {
public int widgetId;
public int widgetType;
public static class AppWidgetAttribute { // 定义内部类 AppWidgetAttribute用于存储小部件的属性
public int widgetId; // 小部件ID
public int widgetType; // 小部件类型
};
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();
mContext = context;
mNotesCount = 0;
public NotesListAdapter(Context context) { // 构造函数
super(context, null); // 调用父类构造函数
mSelectedIndex = new HashMap<Integer, Boolean>(); // 初始化选择索引映射
mContext = context; // 设置上下文
mNotesCount = 0; // 初始化笔记数量为0
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
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) {
NoteItemData itemData = new NoteItemData(context, cursor);
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
public void bindView(View view, Context context, Cursor cursor) { // 绑定视图与数据的方法
if (view instanceof NotesListItem) { // 检查视图类型
NoteItemData itemData = new NoteItemData(context, cursor); // 创建NoteItemData对象
((NotesListItem) view).bind(context, itemData, mChoiceMode, // 绑定数据到视图
isSelectedItem(cursor.getPosition())); // 传递当前项是否被选中的状态
}
}
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
public void setCheckedItem(final int position, final boolean checked) { // 设置某项的选择状态
mSelectedIndex.put(position, checked); // 更新选择状态
notifyDataSetChanged(); // 通知数据变化,更新视图
}
public boolean isInChoiceMode() {
return mChoiceMode;
public boolean isInChoiceMode() { // 判断是否处于选择模式
return mChoiceMode; // 返回选择模式状态
}
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mChoiceMode = mode;
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);
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); // 设置选择状态
}
}
}
}
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
public HashSet<Long> getSelectedItemIds() { // 获取选中项ID集合
HashSet<Long> itemSet = new HashSet<Long>(); // 创建ID集合
for (Integer position : mSelectedIndex.keySet()) { // 遍历选择索引
if (mSelectedIndex.get(position) == true) { // 如果该项被选中
Long id = getItemId(position); // 获取对应的ID
if (id == Notes.ID_ROOT_FOLDER) { // 如果是根文件夹ID
Log.d(TAG, "Wrong item id, should not happen"); // 日志输出错误信息
} else {
itemSet.add(id);
itemSet.add(id); // 添加ID到集合
}
}
}
return itemSet;
return itemSet; // 返回ID集合
}
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Cursor c = (Cursor) getItem(position);
if (c != null) {
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
/**
* Don't close cursor here, only the adapter could close it
*/
public HashSet<AppWidgetAttribute> getSelectedWidget() { // 获取选中小部件的方法
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>(); // 创建小部件属性集合
for (Integer position : mSelectedIndex.keySet()) { // 遍历选择索引
if (mSelectedIndex.get(position) == true) { // 如果该项被选中
Cursor c = (Cursor) getItem(position); // 获取相应游标
if (c != null) { // 如果游标不为空
AppWidgetAttribute widget = new AppWidgetAttribute(); // 创建小部件属性对象
NoteItemData item = new NoteItemData(mContext, c); // 创建笔记数据项
widget.widgetId = item.getWidgetId(); // 获取小部件ID
widget.widgetType = item.getWidgetType(); // 获取小部件类型
itemSet.add(widget); // 将小部件属性添加到集合
// 不要在这里关闭游标,只有适配器可以关闭它
} else {
Log.e(TAG, "Invalid cursor");
return null;
Log.e(TAG, "Invalid cursor"); // 日志输出错误信息
return null; // 返回null
}
}
}
return itemSet;
return itemSet; // 返回小部件属性集合
}
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
return 0;
public int getSelectedCount() { // 获取选中项数量的方法
Collection<Boolean> values = mSelectedIndex.values(); // 获取所有选择状态
if (null == values) { // 如果值为空
return 0; // 返回0
}
Iterator<Boolean> iter = values.iterator();
int count = 0;
while (iter.hasNext()) {
if (true == iter.next()) {
count++;
Iterator<Boolean> iter = values.iterator(); // 创建迭代器
int count = 0; // 初始化计数器
while (iter.hasNext()) { // 遍历选择状态
if (true == iter.next()) { // 如果选中
count++; // 增加计数
}
}
return count;
return count; // 返回选中数量
}
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
public boolean isAllSelected() { // 判断是否全部选中
int checkedCount = getSelectedCount(); // 获取已选中项数量
return (checkedCount != 0 && checkedCount == mNotesCount); // 如果已选数量不为0并且与总数量相等返回true
}
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
public boolean isSelectedItem(final int position) { // 判断指定项是否被选中
if (null == mSelectedIndex.get(position)) { // 如果没有该项的选择状态
return false; // 返回false
}
return mSelectedIndex.get(position);
return mSelectedIndex.get(position); // 返回该项的选择状态
}
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount();
protected void onContentChanged() { // 当内容发生变化时调用
super.onContentChanged(); // 调用父类方法
calcNotesCount(); // 计算笔记数量
}
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
calcNotesCount();
public void changeCursor(Cursor cursor) { // 更改游标的方法
super.changeCursor(cursor); // 调用父类方法
calcNotesCount(); // 计算笔记数量
}
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
Cursor c = (Cursor) getItem(i);
if (c != null) {
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
private void calcNotesCount() { // 计算笔记数量的方法
mNotesCount = 0; // 初始化数量
for (int i = 0; i < getCount(); i++) { // 遍历所有项目
Cursor c = (Cursor) getItem(i); // 获取当前项目的游标
if (c != null) { // 如果游标不为空
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { // 如果游标类型为笔记
mNotesCount++; // 增加计数
}
} else {
Log.e(TAG, "Invalid cursor");
return;
Log.e(TAG, "Invalid cursor"); // 日志输出错误信息
return; // 返回
}
}
}
}
}

@ -14,109 +14,108 @@
* limitations under the License.
*/
package net.micode.notes.ui;
package net.micode.notes.ui; // 定义包名
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 android.content.Context; // 导入Context类
import android.text.format.DateUtils; // 导入DateUtils类
import android.view.View; // 导入View类
import android.widget.CheckBox; // 导入CheckBox类
import android.widget.ImageView; // 导入ImageView类
import android.widget.LinearLayout; // 导入LinearLayout类
import android.widget.TextView; // 导入TextView类
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
import net.micode.notes.R; // 导入资源类
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 { // 定义NotesListItem类继承自LinearLayout
private ImageView mAlert; // 警报图标
private TextView mTitle; // 笔记标题文本视图
private TextView mTime; // 笔记时间文本视图
private TextView mCallName; // 通话记录名称文本视图
private NoteItemData mItemData; // 当前笔记数据项
private CheckBox mCheckBox; // 复选框
public class NotesListItem extends LinearLayout {
private ImageView mAlert;
private TextView mTitle;
private TextView mTime;
private TextView mCallName;
private NoteItemData mItemData;
private CheckBox mCheckBox;
public NotesListItem(Context context) {
super(context);
inflate(context, R.layout.note_item, this);
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
public NotesListItem(Context context) { // 构造函数
super(context); // 调用父类构造函数
inflate(context, R.layout.note_item, this); // 加载布局文件
mAlert = (ImageView) findViewById(R.id.iv_alert_icon); // 查找警报图标
mTitle = (TextView) findViewById(R.id.tv_title); // 查找笔记标题文本视图
mTime = (TextView) findViewById(R.id.tv_time); // 查找笔记时间文本视图
mCallName = (TextView) findViewById(R.id.tv_name); // 查找通话记录名称文本视图
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);
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);
mCheckBox.setVisibility(View.GONE); // 隐藏复选框
}
mItemData = data;
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE);
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);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE);
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);
mItemData = data; // 赋值笔记数据项
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { // 如果是通话记录文件夹
mCallName.setVisibility(View.GONE); // 隐藏通话记录名称
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); // 设置警报图标为通话记录图标
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { // 如果父级ID为通话记录文件夹
mCallName.setVisibility(View.VISIBLE); // 显示通话记录名称
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);
mAlert.setVisibility(View.GONE); // 隐藏警报图标
}
} else {
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
} else { // 如果不是通话记录文件夹
mCallName.setVisibility(View.GONE); // 隐藏通话记录名称
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); // 设置标题文本外观为主要样式
if (data.getType() == Notes.TYPE_FOLDER) {
mTitle.setText(data.getSnippet()
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);
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);
mAlert.setVisibility(View.GONE); // 隐藏警报图标
}
}
}
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); // 设置文本视图为相对时间格式的修改日期
setBackground(data);
setBackground(data); // 设置背景
}
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
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));
private void setBackground(NoteItemData data) { // 设置背景的方法
int id = data.getBgColorId(); // 获取背景颜色ID
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));
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); // 设置为正常背景资源
}
} else {
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
} else { // 如果不是笔记类型
setBackgroundResource(NoteItemBgResources.getFolderBgRes()); // 设置为文件夹背景资源
}
}
public NoteItemData getItemData() {
return mItemData;
public NoteItemData getItemData() { // 获取当前笔记项数据
return mItemData; // 返回笔记项数据
}
}
}

@ -15,98 +15,145 @@
*/
package net.micode.notes.ui;
// 指定当前类的包名属于net.micode.notes.ui的模块。
import android.accounts.Account;
// 导入Android账户类用于获取和管理用户账户。
import android.accounts.AccountManager;
// 导入Android账户管理器类负责账户的操作。
import android.app.ActionBar;
// 导入Android的ActionBar类用于支持应用显示顶部的导航栏。
import android.app.AlertDialog;
// 导入Android的AlertDialog类用于创建对话框。
import android.content.BroadcastReceiver;
// 导入Android的BroadcastReceiver类用于接收广播。
import android.content.ContentValues;
// 导入Android的ContentValues类用于存储数据库中待插入或更新的值。
import android.content.Context;
// 导入Android的Context类用于访问应用程序的环境信息。
import android.content.DialogInterface;
// 导入Android的DialogInterface类用于处理对话框响应的接口。
import android.content.Intent;
// 导入Android的Intent类用于在组件之间传递信息。
import android.content.IntentFilter;
// 导入Android的IntentFilter类用于定义接收特定广播的过滤条件。
import android.content.SharedPreferences;
// 导入Android的SharedPreferences类用于访问应用程序的持久化偏好设置。
import android.os.Bundle;
// 导入Android的Bundle类用于保存活动状态信息。
import android.preference.Preference;
// 导入Android的Preference类表示持久化的偏好设置。
import android.preference.Preference.OnPreferenceClickListener;
// 导入Android的OnPreferenceClickListener接口用于处理偏好设置项的点击事件。
import android.preference.PreferenceActivity;
// 导入Android的PreferenceActivity类用于显示偏好设置界面。
import android.preference.PreferenceCategory;
// 导入Android的PreferenceCategory类表示一组相关的偏好设置。
import android.text.TextUtils;
// 导入Android的TextUtils工具类提供字符串处理方法。
import android.text.format.DateFormat;
// 导入Android的DateFormat类用于日期格式化。
import android.view.LayoutInflater;
// 导入Android的LayoutInflater类用于将XML布局文件转换为View对象。
import android.view.Menu;
// 导入Android的Menu类用于创建菜单。
import android.view.MenuItem;
// 导入Android的MenuItem类表示菜单中的各个项。
import android.view.View;
// 导入Android的View类表示视觉组件。
import android.widget.Button;
// 导入Android的Button类表示按钮组件。
import android.widget.TextView;
// 导入Android的TextView类表示文本视图组件。
import android.widget.Toast;
// 导入Android的Toast类用于显示短暂的消息。
import net.micode.notes.R;
// 导入自定义资源文件R用于获取应用内的资源如字符串、布局等
import net.micode.notes.data.Notes;
// 导入笔记数据相关的类。
import net.micode.notes.data.Notes.NoteColumns;
// 导入笔记数据的列定义。
import net.micode.notes.gtask.remote.GTaskSyncService;
// 导入GTask同步服务用于管理数据同步过程。
public class NotesPreferenceActivity extends PreferenceActivity {
// 定义一个名为NotesPreferenceActivity的公共类继承自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) {
// 重写onCreate方法当活动被创建时调用。
super.onCreate(icicle);
// 调用父类的onCreate方法。
/* using the app icon for navigation */
getActionBar().setDisplayHomeAsUpEnabled(true);
// 设置ActionBar支持返回导航向上导航
addPreferencesFromResource(R.xml.preferences);
// 从XML资源文件加载应用的偏好设置。
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
// 找到并初始化用于显示账户的偏好设置类别。
mReceiver = new GTaskReceiver();
// 创建GTaskReceiver的实例处理同步相关的广播。
IntentFilter filter = new IntentFilter();
// 创建意图过滤器,用于注册监听特定的广播。
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
// 为过滤器添加要监听的广播动作即GTask服务的广播。
registerReceiver(mReceiver, filter);
// 注册接收器,开始监听同步服务的广播。
mOriAccounts = null;
// 初始化原始账户数组为空。
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
// 从XML文件加载设置界面的表头视图。
getListView().addHeaderView(header, null, true);
// 将表头添加到偏好设置列表视图中。
}
@Override
protected void onResume() {
super.onResume();
super.onResume();
// 生命周期方法:当活动恢复时调用。
// need to set sync account automatically if user has added a new
// account
// need to set sync account automatically if user has added a new account
if (mHasAddedAccount) {
// 如果用户添加了新账户,则自动设置同步账户。
Account[] accounts = getGoogleAccounts();
// 获取当前Google账户列表。
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;
}
}
@ -114,193 +161,268 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
refreshUI();
// 刷新用户界面以显示最新的账户和状态信息。
}
@Override
protected void onDestroy() {
if (mReceiver != null) {
unregisterReceiver(mReceiver);
// 如果接收器不为空,取消注册,以避免内存泄漏。
}
super.onDestroy();
// 调用父类的onDestroy方法。
}
private void loadAccountPreference() {
// 加载账户偏好设置的方法。
mAccountCategory.removeAll();
// 移除账户类别中的所有偏好设置项。
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)) {
// the first time to set account
// 如果当前没有默认账户。
showSelectAccountAlertDialog();
// 显示选择账户的对话框。
} else {
// if the account has already been set, we need to promp
// user about the risk
// 如果已有默认账户。
showChangeAccountConfirmAlertDialog();
// 显示更改账户的确认对话框。
}
} else {
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
// 提示用户正在同步时无法更改账户。
}
return true;
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);
// 获取用于显示最后同步时间的文本视图。
// set button state
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)));
// 如果同步账户不为空,则启用按钮。
// set last sync time
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);
// 从XML布局文件加载自定义对话框标题视图。
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);
// 设置正面按钮为空,因后续不需要使用。
Account[] accounts = getGoogleAccounts();
// 获取所有Google账户。
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) {
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"
});
// 设置过滤器以限制账户类型为 GMail。
startActivityForResult(intent, -1);
// 启动账户添加设置界面。
dialog.dismiss();
// 关闭对话框。
}
});
}
private void showChangeAccountConfirmAlertDialog() {
// 显示更改账户确认对话框的方法。
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();
// 显示对话框。
}
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
// 获取账户管理器实例。
return accountManager.getAccountsByType("com.google");
// 返回Google账户类型的账户数组。
}
private void setSyncAccount(String account) {
if (!getSyncAccountName(this).equals(account)) {
// 如果未选择到相同的账户。
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();
// 提交编辑器的更改。
// clean up last sync time
setLastSyncTime(this, 0);
// 清除最后同步时间。
// clean up local gtask related info
new Thread(new Runnable() {
@ -308,26 +430,33 @@ public class NotesPreferenceActivity extends PreferenceActivity {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
// 清空GTASK相关数据。
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 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();
// 提交编辑器的更改。
// clean up local gtask related info
new Thread(new Runnable() {
@ -335,54 +464,65 @@ public class NotesPreferenceActivity extends PreferenceActivity {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
// 清空GTASK相关数据。
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
// 更新笔记中相关内容。
}
}).start();
}
public static String getSyncAccountName(Context context) {
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 settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 获取偏好设置实例。
SharedPreferences.Editor editor = settings.edit();
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
// 存储最后同步时间。
editor.commit();
// 提交编辑器的更改。
}
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
// 获取偏好设置实例。
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
// 返回最后同步时间默认为0。
}
private class GTaskReceiver extends BroadcastReceiver {
// 定义GTaskReceiver类用于接收同步服务的广播。
@Override
public void onReceive(Context context, Intent intent) {
// 当接收到广播时调用。
refreshUI();
// 刷新UI以显示最新信息。
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
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()) {
case android.R.id.home:
Intent intent = new Intent(this, NotesListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// 创建意图,跳转到笔记列表活动,并清除栈中的其它活动。
startActivity(intent);
// 启动活动。
return true;
default:
return false;
// 如果不是默认情形返回false。
}
}
}
}

@ -15,118 +15,505 @@
*/
package net.micode.notes.widget;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.util.Log;
import android.widget.RemoteViews;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListActivity;
import android.app.PendingIntent; // 导入PendingIntent类
import android.appwidget.AppWidgetManager; // 导入AppWidgetManager类
import android.appwidget.AppWidgetProvider; // 导入AppWidgetProvider类
import android.content.ContentValues; // 导入ContentValues类
import android.content.Context; // 导入Context类
import android.content.Intent; // 导入Intent类
import android.content.SharedPreferences; // 导入SharedPreferences类
import android.database.Cursor; // 导入Cursor类
import android.graphics.Bitmap; // 导入Bitmap类
import android.graphics.BitmapFactory; // 导入BitmapFactory类
import android.os.Bundle; // 导入Bundle类
import android.util.Log; // 导入Log类
import android.view.animation.Animation; // 导入Animation类
import android.view.animation.AlphaAnimation; // 导入AlphaAnimation类
import android.view.animation.AccelerateDecelerateInterpolator; // 导入AccelerateDecelerateInterpolator类
import android.widget.RemoteViews; // 导入RemoteViews类
import android.widget.RemoteViewsService; // 导入RemoteViewsService类
import android.widget.Toast; // 导入Toast类
import net.micode.notes.R; // 导入R类用于访问资源
import net.micode.notes.data.Notes; // 导入Notes类
import net.micode.notes.data.Notes.NoteColumns; // 导入NoteColumns类
import net.micode.notes.tool.ResourceParser; // 导入ResourceParser类
import net.micode.notes.ui.NoteEditActivity; // 导入NoteEditActivity类
import net.micode.notes.ui.NotesListActivity; // 导入NotesListActivity类
public abstract class NoteWidgetProvider extends AppWidgetProvider {
public static final String [] PROJECTION = new String [] {
public static final String[] PROJECTION = new String[] {
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET
};
}; // 定义一个常量数组PROJECTION包含NoteColumns的ID、BG_COLOR_ID和SNIPPET
public static final int COLUMN_ID = 0;
public static final int COLUMN_BG_COLOR_ID = 1;
public static final int COLUMN_SNIPPET = 2;
public static final int COLUMN_ID = 0; // 定义COLUMN_ID常量值为0
public static final int COLUMN_BG_COLOR_ID = 1; // 定义COLUMN_BG_COLOR_ID常量值为1
public static final int COLUMN_SNIPPET = 2; // 定义COLUMN_SNIPPET常量值为2
private static final String TAG = "NoteWidgetProvider";
private static final String TAG = "NoteWidgetProvider"; // 定义TAG常量用于日志记录
private static final String WIDGET_PREFS = "widget_prefs"; // 小部件设置的SharedPreferences名称
private static final String WIDGET_BG_COLOR_KEY = "widget_bg_color"; // 小部件背景颜色的键
private static final String WIDGET_FONT_SIZE_KEY = "widget_font_size"; // 小部件字体大小的键
private static final String WIDGET_SYNC_ENABLED_KEY = "widget_sync_enabled"; // 小部件数据同步是否启用的键
private static final String WIDGET_SYNC_URL_KEY = "widget_sync_url"; // 小部件数据同步的URL的键
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
ContentValues values = new ContentValues();
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
// 当小部件被删除时调用
ContentValues values = new ContentValues(); // 创建ContentValues对象
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); // 将WIDGET_ID列的值设置为INVALID_APPWIDGET_ID
for (int i = 0; i < appWidgetIds.length; i++) {
context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
values,
NoteColumns.WIDGET_ID + "=?",
new String[] { String.valueOf(appWidgetIds[i])});
new String[] { String.valueOf(appWidgetIds[i]) }); // 更新数据库将指定小部件ID的WIDGET_ID列设置为INVALID_APPWIDGET_ID
}
}
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
// 获取小部件信息
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null);
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLDER) },
null); // 查询数据库返回符合条件的Cursor对象
}
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false);
// 更新小部件
update(context, appWidgetManager, appWidgetIds, false); // 调用重载的update方法传入false作为privacyMode参数
}
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {
boolean privacyMode) {
// 更新小部件
for (int i = 0; i < appWidgetIds.length; i++) {
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {
int bgId = ResourceParser.getDefaultBgId(context);
String snippet = "";
Intent intent = new Intent(context, NoteEditActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType());
Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]);
if (c != null && c.moveToFirst()) {
if (c.getCount() > 1) {
Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]);
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { // 如果小部件ID有效
int bgId = ResourceParser.getDefaultBgId(context); // 获取默认背景ID
String snippet = ""; // 初始化snippet字符串
Intent intent = new Intent(context, NoteEditActivity.class); // 创建Intent对象指向NoteEditActivity
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); // 设置Intent标志确保活动位于任务栈顶部
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); // 添加额外数据指定小部件ID
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); // 添加额外数据,指定小部件类型
Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); // 获取小部件信息的Cursor对象
if (c != null && c.moveToFirst()) { // 如果Cursor不为空且移动到第一条记录
if (c.getCount() > 1) { // 如果记录数大于1
Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); // 记录错误日志
c.close();
return;
}
snippet = c.getString(COLUMN_SNIPPET);
bgId = c.getInt(COLUMN_BG_COLOR_ID);
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID));
snippet = c.getString(COLUMN_SNIPPET); // 获取SNIPPET列的值
bgId = c.getInt(COLUMN_BG_COLOR_ID); // 获取BG_COLOR_ID列的值
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); // 添加额外数据指定记录ID
intent.setAction(Intent.ACTION_VIEW);
} else {
snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
}
} // 设置Intent动作为ACTION_INSERT_OR_EDIT
if (c != null) {
c.close();
}
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); // 创建RemoteViews对象指定布局ID
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); // 设置背景图片资源ID
/**
* Generate the pending intent to start host for the widget
*/
PendingIntent pendingIntent = null;
if (privacyMode) {
if (privacyMode) { // 如果隐私模式
rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode));
context.getString(R.string.widget_under_visit_mode)); // 设置文本视图内容为隐私模式提示
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
} else {
rv.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
PendingIntent.FLAG_UPDATE_CURRENT);
PendingIntent.FLAG_UPDATE_CURRENT); // 创建PendingIntent对象指向NoteEditActivity
}
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); // 设置文本视图的点击事件
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
}
protected abstract int getBgResourceId(int bgId);
protected abstract int getBgResourceId(int bgId); // 声明抽象方法getBgResourceId获取背景资源ID
protected abstract int getLayoutId(); // 声明抽象方法getLayoutId获取布局ID
protected abstract int getWidgetType(); // 声明抽象方法getWidgetType
/**
*
* @param context
* @return
*/
private int getWidgetBgColor(Context context) {
SharedPreferences prefs = context.getSharedPreferences(WIDGET_PREFS, Context.MODE_PRIVATE);
return prefs.getInt(WIDGET_BG_COLOR_KEY, 0xFFE0E0E0); // 默认背景颜色为浅灰色
}
/**
*
* @param context
* @return
*/
private int getWidgetFontSize(Context context) {
SharedPreferences prefs = context.getSharedPreferences(WIDGET_PREFS, Context.MODE_PRIVATE);
return prefs.getInt(WIDGET_FONT_SIZE_KEY, 16); // 默认字体大小为16sp
}
/**
*
* @param context
* @param color
*/
private void setWidgetBgColor(Context context, int color) {
SharedPreferences prefs = context.getSharedPreferences(WIDGET_PREFS, Context.MODE_PRIVATE);
prefs.edit().putInt(WIDGET_BG_COLOR_KEY, color).apply();
}
/**
*
* @param context
* @param size
*/
private void setWidgetFontSize(Context context, int size) {
SharedPreferences prefs = context.getSharedPreferences(WIDGET_PREFS, Context.MODE_PRIVATE);
prefs.edit().putInt(WIDGET_FONT_SIZE_KEY, size).apply();
}
/**
*
* @param context
* @param enabled
*/
private void setSyncEnabled(Context context, boolean enabled) {
SharedPreferences prefs = context.getSharedPreferences(WIDGET_PREFS, Context.MODE_PRIVATE);
prefs.edit().putBoolean(WIDGET_SYNC_ENABLED_KEY, enabled).apply();
}
/**
* URL
* @param context
* @param url URL
*/
private void setSyncUrl(Context context, String url) {
SharedPreferences prefs = context.getSharedPreferences(WIDGET_PREFS, Context.MODE_PRIVATE);
prefs.edit().putString(WIDGET_SYNC_URL_KEY, url).apply();
}
/**
*
* @param context
* @param bgId ID
* @return
*/
private Bitmap getWidgetBgImage(Context context, int bgId) {
// 根据背景ID获取背景图像资源ID
int bgResId = ResourceParser.WidgetBgResources.getWidgetBgResource(bgId);
// 解码背景图像资源
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2; // 缩小图像以减少内存使用
return BitmapFactory.decodeResource(context.getResources(), bgResId, options);
}
/**
*
* @param context
* @param appWidgetId ID
* @param bgImage
*/
private void setWidgetBgImage(Context context, int appWidgetId, Bitmap bgImage) {
// 获取远程视图对象
RemoteViews views = new RemoteViews(context.getPackageName(), getLayoutId());
// 设置背景图像
views.setImageViewBitmap(R.id.widget_bg_image, bgImage);
// 更新小部件视图
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
/**
*
* @param context
* @return
*/
private Animation getWidgetAnimation(Context context) {
// 创建动画效果
Animation animation = new AlphaAnimation(0.0f, 1.0f);
animation.setDuration(1000); // 动画持续时间为1秒
animation.setInterpolator(new AccelerateDecelerateInterpolator());
return animation;
}
/**
*
* @param context
* @param appWidgetId ID
* @param animation
*/
private void setWidgetAnimation(Context context, int appWidgetId, Animation animation) {
// 获取远程视图对象
RemoteViews views = new RemoteViews(context.getPackageName(), getLayoutId());
// 设置动画效果
views.setAnimation(R.id.widget_text, animation);
// 更新小部件视图
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
/**
*
* @param context
* @return
*/
private GestureDetector getWidgetGestureDetector(Context context) {
// 创建滑动手势检测器
GestureDetector gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// 检测滑动手势
if (Math.abs(velocityX) > Math.abs(velocityY)) {
if (velocityX > 0) {
// 向右滑动
onSwipeRight(context);
} else {
// 向左滑动
onSwipeLeft(context);
}
}
return true;
}
});
return gestureDetector;
}
/**
*
* @param context
*/
private void onSwipeRight(Context context) {
Log.d(TAG, "onSwipeRight called");
// 向右滑动时的处理逻辑
// 例如,切换到上一条笔记
int appWidgetId = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, NoteWidgetProvider.class))[0];
Notes.Note note = Notes.getPreviousNote(context, appWidgetId);
if (note != null) {
update(context, AppWidgetManager.getInstance(context), new int[]{appWidgetId});
} else {
Toast.makeText(context, "已经是第一条笔记", Toast.LENGTH_SHORT).show();
}
}
/**
*
* @param context
*/
private void onSwipeLeft(Context context) {
Log.d(TAG, "onSwipeLeft called");
// 向左滑动时的处理逻辑
// 例如,切换到下一条笔记
int appWidgetId = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, NoteWidgetProvider.class))[0];
Notes.Note note = Notes.getNextNote(context, appWidgetId);
if (note != null) {
update(context, AppWidgetManager.getInstance(context), new int[]{appWidgetId});
} else {
Toast.makeText(context, "已经是最后一条笔记", Toast.LENGTH_SHORT).show();
}
}
/**
*
* @param context
* @return
*/
private View.OnClickListener getWidgetClickListener(Context context) {
return new View.OnClickListener() {
@Override
public void onClick(View v) {
// 处理小部件的点击事件
// 例如,打开笔记编辑页面
Intent intent = new Intent(context, NoteEditActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
};
}
/**
*
* @param context
* @param appWidgetId ID
* @param clickListener
*/
private void setWidgetClickListener(Context context, int appWidgetId, View.OnClickListener clickListener) {
// 获取远程视图对象
RemoteViews views = new RemoteViews(context.getPackageName(), getLayoutId());
// 设置点击事件监听器
views.setOnClickPendingIntent(R.id.widget_text, PendingIntent.getBroadcast(context, 0, new Intent(), 0));
// 更新小部件视图
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
/**
*
* @param context
* @return
*/
private View.OnLongClickListener getWidgetLongClickListener(Context context) {
return new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
// 处理小部件的长按事件
// 例如,显示快捷菜单
showShortcutMenu(context);
return true;
}
};
}
protected abstract int getLayoutId();
/**
*
* @param context
*/
private void showShortcutMenu(Context context) {
// 创建快捷菜单
PopupMenu popupMenu = new PopupMenu(context, null);
popupMenu.getMenuInflater().inflate(R.menu.widget_shortcut_menu, popupMenu.getMenu());
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_edit_note:
// 编辑笔记
editNote(context, AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, NoteWidgetProvider.class))[0], Notes.getCurrentNoteId(context));
break;
case R.id.menu_delete_note:
// 删除笔记
deleteNote(context, Notes.getCurrentNoteId(context));
break;
case R.id.menu_sync_notes:
// 同步笔记
syncNotes(context);
break;
default:
break;
}
return true;
}
});
protected abstract int getWidgetType();
}
// 显示快捷菜单
popupMenu.show();
}
/**
*
* @param context
* @param appWidgetId ID
* @param noteId ID
*/
private void editNote(Context context, int appWidgetId, int noteId) {
// 获取笔记数据
Notes.Note note = Notes.getNoteById(context, noteId);
if (note != null) {
// 显示编辑对话框
showEditDialog(context, appWidgetId, note);
} else {
// 显示错误提示信息
Toast.makeText(context, "笔记不存在", Toast.LENGTH_SHORT).show();
}
}
/**
*
* @param context
* @param appWidgetId ID
* @param note
*/
private void showEditDialog(Context context, int appWidgetId, Notes.Note note) {
// 创建编辑对话框
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("编辑笔记");
View dialogView = LayoutInflater.from(context).inflate(R.layout.dialog_edit_note, null);
EditText editText = dialogView.findViewById(R.id.edit_text);
editText.setText(note.getContent());
builder.setView(dialogView);
builder.setPositiveButton("保存", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String newContent = editText.getText().toString();
if (!newContent.isEmpty()) {
// 更新笔记数据
note.setContent(newContent);
Notes.updateNote(context, note);
// 更新小部件视图
update(context, AppWidgetManager.getInstance(context), new int[]{appWidgetId});
} else {
// 显示错误提示信息
Toast.makeText(context, "笔记内容不能为空", Toast.LENGTH_SHORT).show();
}
}
});
builder.setNegativeButton("取消", null);
// 显示对话框
builder.show();
}
/**
*
* @param context
* @param noteId ID
*/
private void deleteNote(Context context, int noteId) {
// 删除笔记数据
Notes.deleteNote(context, noteId);
// 更新小部件视图
int appWidgetId = AppWidgetManager.getInstance(context).getAppWidgetIds(new ComponentName(context, NoteWidgetProvider.class))[0];
update(context, AppWidgetManager.getInstance(context), new int[]{appWidgetId});
// 显示提示信息
Toast.makeText(context, "笔记已删除", Toast.LENGTH_SHORT).show();
}
/**
*
* @param context
*/
private void syncNotes(Context context) {
// 获取数据同步的URL
SharedPreferences prefs = context.getSharedPreferences(WIDGET_PREFS, Context.MODE_PRIVATE);
String syncUrl = prefs.getString(WIDGET_SYNC_URL_KEY, "");
if (!syncUrl.isEmpty()) {
// 启动数据同步服务
Intent syncIntent = new Intent(context, NoteSyncService.class);
syncIntent.putExtra("sync_url", syncUrl);
context.startService(syncIntent);
} else {
// 显示错误提示信息
Toast.makeText(context, "请设置数据同步的URL", Toast.LENGTH_SHORT).show();
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save