Compare commits

..

48 Commits

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

@ -0,0 +1,87 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
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; // 导入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 "
+ "(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); // 如果存在,则直接返回缓存中的姓名
}
// 构造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); // 排序条件这里为null表示不排序
// 检查查询结果是否为空且是否可以移动到第一行
if (cursor != null && cursor.moveToFirst()) {
try {
String name = cursor.getString(0); // 获取联系人显示名称
sContactCache.put(phoneNumber, name); // 将查询结果存入缓存
return name; // 返回联系人姓名
} catch (IndexOutOfBoundsException e) {
// 如果发生数组越界异常记录错误日志并返回null
Log.e(TAG, " Cursor get string error " + e.toString());
return null;
} finally {
cursor.close(); // 关闭游标,释放资源
}
} else {
// 如果没有找到匹配的联系人记录调试日志并返回null
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}
}
}

@ -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;
}
}

@ -0,0 +1,298 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*
*
*/
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; // 系统类型
/**
* 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;
// 定义了一些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; // 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
* content://micode_notes/note
*/
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* Uri
* content://micode_notes/data
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
// 定义了一个接口NoteColumns包含了一些与笔记和文件夹相关的列名
public interface NoteColumns {
/**
* ID
* <P> INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* ID
* <P> INTEGER (long) </P>
*/
public static final String PARENT_ID = "parent_id";
/**
*
* <P> INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
*
* <P> INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
*
* <P> INTEGER (long) </P>
*/
public static final String ALERTED_DATE = "alert_date";
/**
*
* <P> TEXT </P>
*/
public static final String SNIPPET = "snippet";
/**
* ID
* <P> INTEGER (long) </P>
*/
public static final String WIDGET_ID = "widget_id";
/**
*
* <P> INTEGER (long) </P>
*/
public static final String WIDGET_TYPE = "widget_type";
/**
* ID
* <P> INTEGER (long) </P>
*/
public static final String BG_COLOR_ID = "bg_color_id";
/**
*
* <P> INTEGER </P>
*/
public static final String HAS_ATTACHMENT = "has_attachment";
/**
*
* <P> INTEGER (long) </P>
*/
public static final String NOTES_COUNT = "notes_count";
/**
*
* <P> INTEGER </P>
*/
public static final String TYPE = "type";
/**
* ID
* <P> INTEGER (long) </P>
*/
public static final String SYNC_ID = "sync_id";
/**
*
* <P> INTEGER </P>
*/
public static final String LOCAL_MODIFIED = "local_modified";
/**
* ID
* <P> INTEGER </P>
*/
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* GoogleID
* <P> TEXT </P>
*/
public static final String GTASK_ID = "gtask_id";
/**
*
* <P> INTEGER (long) </P>
*/
public static final String VERSION = "version";
}
// 定义了一个接口DataColumns包含了一些与数据相关的列名
public interface DataColumns {
/**
* ID
* <P> INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* MIME
* <P> TEXT </P>
*/
public static final String MIME_TYPE = "mime_type";
/**
* ID
* <P> INTEGER (long) </P>
*/
public static final String NOTE_ID = "note_id";
/**
*
* <P> INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
*
* <P> INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
*
* <P> TEXT </P>
*/
public static final String CONTENT = "content";
/**
* {@link #MIME_TYPE}
* <P> INTEGER </P>
*/
public static final String DATA1 = "data1";
/**
* {@link #MIME_TYPE}
* <P> INTEGER </P>
*/
public static final String DATA2 = "data2";
/**
* {@link #MIME_TYPE}
* <P> TEXT </P>
*/
public static final String DATA3 = "data3";
/**
* {@link #MIME_TYPE}
* <P> TEXT </P>
*/
public static final String DATA4 = "data4";
/**
* {@link #MIME_TYPE}
* <P> TEXT </P>
*/
public static final String DATA5 = "data5";
}
// 定义了一个内部类TextNote继承自DataColumns表示文本笔记的数据结构
public static final class TextNote implements DataColumns {
/**
*
* <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 {
/**
*
* <P> INTEGER (long) </P>
*/
public static final String CALL_DATE = DATA1;
/**
*
* <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");
}
}

@ -0,0 +1,393 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 使
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
*
*
*
*
*/
package net.micode.notes.data;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
// NotesDatabaseHelper 是一个 SQLiteOpenHelper 类的子类,用于管理笔记应用的数据库
public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 定义了数据库名称
private static final String DB_NAME = "note.db";
// 定义了数据库版本号
private static final int DB_VERSION = 4;
// 定义了一个内部接口TABLE包含了表名常量
public interface TABLE {
public static final String NOTE = "note"; // 笔记表名
public static final String DATA = "data"; // 数据表名
}
// 定义了日志标签字符串
private static final String TAG = "NotesDatabaseHelper";
// 定义了单例实例
private static NotesDatabaseHelper mInstance;
// 定义了创建笔记表的SQL语句
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," + // 笔记ID主键
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 父文件夹ID默认为0
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 提醒日期默认为0
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + // 背景颜色ID默认为0
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建日期,默认为当前时间(毫秒)
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," + // 数据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 + "); "; // 为data表中的note_id列创建索引
/**
*
*/
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" + // 更新父文件夹的笔记数量加1
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + // 父文件夹ID为移动后的文件夹ID
" END";
/**
*
*/
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" + // 更新父文件夹的笔记数量减1
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + // 父文件夹ID为移动前的文件夹ID
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + // 仅当笔记数量大于0时更新
" END";
/**
*
*/
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" + // 更新父文件夹的笔记数量加1
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + // 父文件夹ID为新笔记的父文件夹ID
" END";
/**
*
*/
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" + // 更新父文件夹的笔记数量减1
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + // 父文件夹ID为删除笔记的父文件夹ID
" AND " + NoteColumns.NOTES_COUNT + ">0;" + // 仅当笔记数量大于0时更新
" END";
/**
* {@link DataConstants#NOTE}
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
" AFTER INSERT ON " + TABLE.DATA +
" WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + // 当插入的数据类型为笔记时
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + // 更新笔记的摘要为新插入的数据内容
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + // 笔记ID为新插入数据关联的笔记ID
" END";
/**
* {@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 + "'" + // 当更新的数据类型为笔记时
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + // 更新笔记的摘要为更新后的数据内容
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + // 笔记ID为更新数据关联的笔记ID
" END";
/**
* {@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 + "'" + // 当删除的数据类型为笔记时
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=''" + // 清空笔记的摘要
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + // 笔记ID为删除数据关联的笔记ID
" END";
/**
*
*/
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 + // 删除data表中与笔记ID关联的所有数据
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + // 笔记ID为删除笔记的ID
" END";
/**
*
*/
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 + // 删除note表中与文件夹ID关联的所有笔记
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + // 父文件夹ID为删除文件夹的ID
" END";
/**
*
*/
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
"CREATE TRIGGER folder_move_notes_on_trash " +
" AFTER UPDATE ON " + TABLE.NOTE +
" WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + // 当笔记被移动到回收站文件夹时
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + // 将笔记的父文件夹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); // 执行创建笔记表的SQL语句
reCreateNoteTableTriggers(db); // 重新创建笔记表的触发器
createSystemFolder(db); // 创建系统文件夹
Log.d(TAG, "note table has been created"); // 记录日志,表示笔记表已创建
}
// 重新创建笔记表触发器的方法
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
// 删除旧的触发器
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
// 创建新的触发器
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER);
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER);
db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
// 创建系统文件夹的方法
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues();
/**
*
*/
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); // 设置系统文件夹ID
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); // 插入根文件夹到笔记表
/**
*
*/
values.clear(); // 清空之前插入的值
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); // 设置系统文件夹ID
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); // 执行创建数据表的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); // 初始化单例实例
}
return mInstance; // 返回单例实例
}
// 当数据库第一次创建时调用的方法
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db); // 创建笔记表
createDataTable(db); // 创建数据表
}
// 当数据库需要升级时调用的方法
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false;
boolean skipV2 = false;
// 如果当前版本是1直接删除旧表并创建新表
if (oldVersion == 1) {
upgradeToV2(db); // 升级到版本2
skipV2 = true; // 这次升级包含了从v2到v3的升级
oldVersion++;
}
// 如果当前版本是2添加新的列并创建系统文件夹
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db); // 升级到版本3
reCreateTriggers = true; // 标记需要重新创建触发器
oldVersion++;
}
// 如果当前版本是3添加版本号列
if (oldVersion == 3) {
upgradeToV4(db); // 升级到版本4
oldVersion++;
}
// 如果需要重新创建触发器,调用相应的方法
if (reCreateTriggers) {
reCreateNoteTableTriggers(db); // 重新创建笔记表的触发器
reCreateDataTableTriggers(db); // 重新创建数据表的触发器
}
// 如果升级失败,抛出异常
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ " fails"); // 抛出异常,表示数据库升级失败
}
}
// 升级到数据库版本2的方法
private void upgradeToV2(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); // 删除旧的笔记表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); // 删除旧的数据表
createNoteTable(db); // 创建新的笔记表
createDataTable(db); // 创建新的数据表
}
// 升级到数据库版本3的方法
private void upgradeToV3(SQLiteDatabase db) {
// 删除不再使用的触发器
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");
// 添加 gtask id 列
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''"); // 为笔记表添加gtask_id列
// 添加回收站系统文件夹
ContentValues values = new ContentValues();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); // 设置系统文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置文件夹类型为系统类型
db.insert(TABLE.NOTE, null, values); // 插入回收站文件夹到笔记表
}
// 升级到数据库版本4的方法
private void upgradeToV4(SQLiteDatabase db) {
// 添加版本号列
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0"); // 为笔记表添加version列
}
}

@ -0,0 +1,342 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
public class NotesProvider extends ContentProvider {
// UriMatcher用于匹配不同的URI请求帮助识别是请求笔记还是笔记数据或是搜索请求等并返回相应的请求类型代码
private static final UriMatcher mMatcher;
// 数据库辅助类,用于创建和管理数据库
private NotesDatabaseHelper mHelper;
// 日志标签,便于调试和日志记录
private static final String TAG = "NotesProvider";
// 定义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; // 匹配搜索请求的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);
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
}
/**
* x'0A'sqlite'\n'
* '\n'便
* ID便
*/
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + ","
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + ","
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;
// 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()); // 获取数据库帮助器的单例实例
return true;
}
// 根据不同的URI请求类型执行查询操作返回查询结果的游标
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null;
SQLiteDatabase db = mHelper.getReadableDatabase(); // 获取可读的数据库实例
String id = null;
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 查询所有笔记,返回符合条件的所有笔记游标
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder);
break;
case URI_NOTE_ITEM:
// 查询特定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);
break;
case URI_DATA_ITEM:
// 查询特定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(
"不允许在搜索时指定排序条件、选择条件、选择参数或投影");
}
String searchString = null;
// 获取搜索字符串
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1); // 从URI路径中获取搜索字符串
}
} else {
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, "发生异常: " + ex.toString()); // 记录异常日志
}
break;
default:
// 如果URI请求类型未知抛出异常以提示错误
throw new IllegalArgumentException("未知的URI " + uri);
}
// 设置游标的通知URI以便内容变化时通知监听者。这有助于UI更新和数据同步。
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c; // 返回查询结果游标
}
// 根据不同的URI请求类型插入数据返回插入数据的URI
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写的数据库实例
long dataId = 0, noteId = 0, insertedId = 0;
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); // 获取关联的笔记ID
} else {
Log.d(TAG, "数据格式错误缺少笔记ID:" + values.toString()); // 记录日志,表示数据格式错误
}
insertedId = dataId = db.insert(TABLE.DATA, null, values);
break;
default:
// 如果URI请求类型未知抛出异常以提示错误
throw new IllegalArgumentException("未知的URI " + uri);
}
// 通知笔记URI变化以便UI更新和数据同步
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// 通知数据URI变化以便UI更新和数据同步
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
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(); // 获取可写的数据库实例
boolean deleteData = false;
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 删除笔记时确保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); // 获取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); // 获取URI路径中的笔记数据ID
// 删除特定ID的笔记数据
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
deleteData = true;
break;
default:
// 如果URI请求类型未知抛出异常以提示错误
throw new IllegalArgumentException("未知的URI " + uri);
}
// 如果有数据被删除通知URI变化以便UI更新和数据同步
if (count > 0) {
if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); // 通知笔记URI变化
}
getContext().getContentResolver().notifyChange(uri, null); // 通知特定数据URI变化
}
return count; // 返回删除的数据条目数
}
// 根据不同的URI请求类型更新数据返回更新的数据条目数
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写的数据库实例
boolean updateData = false;
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); // 获取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); // 获取URI路径中的笔记数据ID
// 更新特定ID的笔记数据
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
updateData = true;
break;
default:
// 如果URI请求类型未知抛出异常以提示错误
throw new IllegalArgumentException("未知的URI " + uri);
}
// 如果有数据被更新通知URI变化以便UI更新和数据同步
if (count > 0) {
if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); // 通知笔记URI变化
}
getContext().getContentResolver().notifyChange(uri, null); // 通知特定数据URI变化
}
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 "); // 将笔记版本号加1
// 构建WHERE子句以指定更新哪个笔记
if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
}
if (id > 0) {
sql.append(NoteColumns.ID + "=" + String.valueOf(id)); // 指定更新特定ID的笔记
}
if (!TextUtils.isEmpty(selection)) {
String selectString = id > 0 ? parseSelection(selection) : selection;
for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args); // 替换选择条件中的占位符
}
sql.append(selectString);
}
// 执行更新操作,增加笔记的版本号
mHelper.getWritableDatabase().execSQL(sql.toString());
}
// 返回URI对应的数据类型。这里未实现返回null。通常需要根据URI返回相应的MIME类型。
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null; // 未实现返回null
}
}

@ -0,0 +1,98 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 导入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; // 返回mRelatedGid
}
// 重写isWorthSaving方法判断是否值得保存
@Override
public boolean isWorthSaving() {
return getNotes() != null; // 如果笔记内容不为null则值得保存
}
// 重写从远程JSON设置内容的方法
@Override
public void setContentByRemoteJSON(JSONObject 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) {
// 抛出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");
}
}

@ -0,0 +1,110 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 定义包名
package net.micode.notes.gtask.data;
// 导入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; // 是否已删除
// Node类的构造函数
public Node() {
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; // 赋值
}
// 设置名称的方法
public void setName(String name) {
this.mName = name; // 赋值
}
// 设置最后修改时间的方法
public void setLastModified(long lastModified) {
this.mLastModified = lastModified; // 赋值
}
// 设置删除状态的方法
public void setDeleted(boolean deleted) {
this.mDeleted = deleted; // 赋值
}
// 获取GID的方法
public String getGid() {
return this.mGid; // 返回GID
}
// 获取名称的方法
public String getName() {
return this.mName; // 返回名称
}
// 获取最后修改时间的方法
public long getLastModified() {
return this.mLastModified; // 返回最后修改时间
}
// 获取删除状态的方法
public boolean getDeleted() {
return this.mDeleted; // 返回删除状态
}
}

@ -0,0 +1,197 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 定义包名
package net.micode.notes.gtask.data;
// 导入所需的类
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; // 自定义异常类
// 导入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; // 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; // 初始化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); // 从Cursor加载数据
mDiffDataValues = new ContentValues(); // 创建新的ContentValues
}
// 从Cursor加载数据
private void loadFromCursor(Cursor c) {
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; // 获取ID
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId); // 存储ID到差异数据
}
mDataId = dataId; // 更新ID
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE; // 获取MIME类型
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); // 存储MIME类型
}
mDataMimeType = dataMimeType; // 更新MIME类型
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : ""; // 获取内容
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent); // 存储内容
}
mDataContent = dataContent; // 更新内容
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0; // 获取数据1
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1); // 存储数据1
}
mDataContentData1 = dataContentData1; // 更新数据1
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : ""; // 获取数据3
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3); // 存储数据3
}
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; // 如果是创建状态返回null
}
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); // 存储笔记ID
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); // 插入数据
try {
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"); // 抛出异常
}
} else {
// 如果存在差异数据
if (mDiffDataValues.size() > 0) {
int result = 0; // 结果计数器
// 判断是否需要验证版本
if (!validateVersion) {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); // 更新数据
} else {
// 根据版本验证更新数据
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
String.valueOf(noteId), String.valueOf(version)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing"); // 日志输出
}
}
}
mDiffDataValues.clear(); // 清空差异数据
mIsCreate = false; // 设置为非创建状态
}
// 获取数据ID
public long getId() {
return mDataId; // 返回数据ID
}
}

@ -0,0 +1,465 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 定义包名
package net.micode.notes.gtask.data;
// 导入必要的类
import android.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 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,
NoteColumns.NOTES_COUNT, NoteColumns.PARENT_ID, NoteColumns.SNIPPET, NoteColumns.TYPE,
NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, NoteColumns.SYNC_ID,
NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID,
NoteColumns.VERSION
};
// 定义数据列在投影中的索引
public static final int ID_COLUMN = 0; // ID列索引
public static final int ALERTED_DATE_COLUMN = 1; // 提醒日期列索引
public static final int BG_COLOR_ID_COLUMN = 2; // 背景颜色ID列索引
public static final int CREATED_DATE_COLUMN = 3; // 创建日期列索引
public static final int HAS_ATTACHMENT_COLUMN = 4; // 是否有附件列索引
public static final int MODIFIED_DATE_COLUMN = 5; // 修改日期列索引
public static final int NOTES_COUNT_COLUMN = 6; // 笔记数量列索引
public static final int PARENT_ID_COLUMN = 7; // 父级ID列索引
public static final int SNIPPET_COLUMN = 8; // 摘要列索引
public static final int TYPE_COLUMN = 9; // 类型列索引
public static final int WIDGET_ID_COLUMN = 10; // 小部件ID列索引
public static final int WIDGET_TYPE_COLUMN = 11; // 小部件类型列索引
public static final int SYNC_ID_COLUMN = 12; // 同步ID列索引
public static final int LOCAL_MODIFIED_COLUMN = 13; // 本地修改时间列索引
public static final int ORIGIN_PARENT_ID_COLUMN = 14; // 原始父级ID列索引
public static final int GTASK_ID_COLUMN = 15; // GTask ID列索引
public static final int VERSION_COLUMN = 16; // 版本列索引
// 定义成员变量
private Context mContext; // 上下文
private ContentResolver mContentResolver; // 内容解析器
private boolean mIsCreate; // 是否为创建状态
private long mId; // 笔记ID
private long mAlertDate; // 提醒日期
private int mBgColorId; // 背景颜色ID
private long mCreatedDate; // 创建日期
private int mHasAttachment; // 是否有附件
private long mModifiedDate; // 修改日期
private long mParentId; // 父级ID
private String mSnippet; // 摘要
private int mType; // 类型
private int mWidgetId; // 小部件ID
private int mWidgetType; // 小部件类型
private long mOriginParent; // 原始父级ID
private long mVersion; // 版本
private ContentValues mDiffNoteValues; // 存储差异数据的ContentValues
private ArrayList<SqlData> mDataList; // 存储数据列表
// 构造函数,用于创建新笔记
public SqlNote(Context context) {
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); // 从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); // 从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; // 游标初始化
try {
// 查询笔记数据
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] { String.valueOf(id) }, null);
if (c != null) { // 如果游标不为空
c.moveToNext(); // 移动到下一条记录
loadFromCursor(c); // 从Cursor加载数据
} else {
Log.w(TAG, "loadFromCursor: cursor = null"); // 日志输出
}
} finally {
if (c != null) // 如果游标不为空
c.close(); // 关闭游标
}
}
// 从Cursor加载数据
private void loadFromCursor(Cursor c) {
mId = c.getLong(ID_COLUMN); // 获取ID
mAlertDate = c.getLong(ALERTED_DATE_COLUMN); // 获取提醒日期
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); // 获取背景颜色ID
mCreatedDate = c.getLong(CREATED_DATE_COLUMN); // 获取创建日期
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN); // 获取是否有附件
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN); // 获取修改日期
mParentId = c.getLong(PARENT_ID_COLUMN); // 获取父级ID
mSnippet = c.getString(SNIPPET_COLUMN); // 获取摘要
mType = c.getInt(TYPE_COLUMN); // 获取类型
mWidgetId = c.getInt(WIDGET_ID_COLUMN); // 获取小部件ID
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); // 获取小部件类型
mVersion = c.getLong(VERSION_COLUMN); // 获取版本
}
// 加载数据内容
private void loadDataContent() {
Cursor c = null; // 游标初始化
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; // 退出方法
}
while (c.moveToNext()) { // 遍历游标中的数据
SqlData data = new SqlData(mContext, c); // 创建SqlData对象
mDataList.add(data); // 添加到数据列表
}
} else {
Log.w(TAG, "loadDataContent: cursor = null"); // 日志输出
}
} finally {
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) { // 如果是文件夹
// 仅更新摘要和类型
String snippet = note.has(NoteColumns.SNIPPET) ? note.getString(NoteColumns.SNIPPET) : ""; // 获取摘要
if (mIsCreate || !mSnippet.equals(snippet)) { // 如果是创建状态或摘要不相同
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); // 存储摘要
}
mSnippet = snippet; // 更新摘要
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) : Notes.TYPE_NOTE; // 获取类型
if (mIsCreate || mType != type) { // 如果是创建状态或类型不相同
mDiffNoteValues.put(NoteColumns.TYPE, type); // 存储类型
}
mType = type; // 更新类型
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { // 如果是笔记
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // 获取数据数组
long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID; // 获取笔记ID
if (mIsCreate || mId != id) { // 如果是创建状态或ID不相同
mDiffNoteValues.put(NoteColumns.ID, id); // 存储笔记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); // 存储提醒日期
}
mAlertDate = alertDate; // 更新提醒日期
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; // 更新背景颜色ID
long createDate = note.has(NoteColumns.CREATED_DATE) ? note.getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis(); // 获取创建日期
if (mIsCreate || mCreatedDate != createDate) { // 如果是创建状态或创建日期不相同
mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate); // 存储创建日期
}
mCreatedDate = createDate; // 更新创建日期
int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note.getInt(NoteColumns.HAS_ATTACHMENT) : 0; // 获取是否有附件
if (mIsCreate || mHasAttachment != hasAttachment) { // 如果是创建状态或附件状态不相同
mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, 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); // 存储修改日期
}
mModifiedDate = modifiedDate; // 更新修改日期
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; // 更新父级ID
String snippet = note.has(NoteColumns.SNIPPET) ? note.getString(NoteColumns.SNIPPET) : ""; // 获取摘要
if (mIsCreate || !mSnippet.equals(snippet)) { // 如果是创建状态或摘要不相同
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); // 存储摘要
}
mSnippet = snippet; // 更新摘要
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) : Notes.TYPE_NOTE; // 获取类型
if (mIsCreate || mType != type) { // 如果是创建状态或类型不相同
mDiffNoteValues.put(NoteColumns.TYPE, type); // 存储类型
}
mType = type; // 更新类型
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; // 更新小部件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); // 存储小部件类型
}
mWidgetType = widgetType; // 更新小部件类型
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) { // 处理JSON解析异常
Log.e(TAG, e.toString()); // 输出异常信息
e.printStackTrace(); // 打印堆栈信息
return false; // 返回设置失败
}
return true; // 返回设置成功
}
// 获取笔记内容的方法
public JSONObject getContent() {
try {
JSONObject js = new JSONObject(); // 创建一个新的JSON对象
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); // 设置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); // 将数据数组添加到主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; // 返回最终构建的JSON对象
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 日志输出异常信息
e.printStackTrace(); // 打印堆栈信息
}
return null; // 如果发生异常则返回null
}
// 设置父级ID的方法
public void setParentId(long id) {
mParentId = id; // 更新父级ID
mDiffNoteValues.put(NoteColumns.PARENT_ID, id); // 将父级ID存储到差异数据中
}
// 设置GTask ID的方法
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); // 将GTask ID存储到差异数据中
}
// 设置同步ID的方法
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); // 将同步ID存储到差异数据中
}
// 重置本地修改时间的方法
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); // 将本地修改时间设置为0
}
// 获取笔记ID的方法
public long getId() {
return mId; // 返回ID
}
// 获取父级ID的方法
public long getParentId() {
return mParentId; // 返回父级ID
}
// 获取摘要的方法
public String getSnippet() {
return mSnippet; // 返回摘要
}
// 判断是否为笔记类型的方法
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE; // 如果类型为笔记返回true否则返回false
}
// 提交数据的方法
public void commit(boolean validateVersion) {
if (mIsCreate) { // 如果是创建状态
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID); // 如果ID为无效ID且包含ID则移除
}
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); // 插入数据
try {
mId = Long.valueOf(uri.getPathSegments().get(1)); // 获取新插入数据的ID
} catch (NumberFormatException e) {
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"); // 检查ID是否有效
}
if (mType == Notes.TYPE_NOTE) { // 如果类型为笔记
for (SqlData sqlData : mDataList) { // 遍历数据列表
sqlData.commit(mId, false, -1); // 提交每个SqlData对象
}
}
} else { // 如果不是创建状态
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
Log.e(TAG, "No such note"); // 打印错误日志
throw new IllegalStateException("Try to update note with invalid id"); // 抛出无效ID异常
}
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 { // 如果需要验证版本
// 根据版本验证更新数据
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
String.valueOf(mId), String.valueOf(mVersion)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing"); // 日志输出
}
}
if (mType == Notes.TYPE_NOTE) { // 如果类型为笔记
for (SqlData sqlData : mDataList) { // 遍历数据列表
sqlData.commit(mId, validateVersion, mVersion); // 提交每个SqlData对象
}
}
}
// 刷新本地信息
loadFromCursor(mId); // 根据ID加载数据
if (mType == Notes.TYPE_NOTE) // 如果类型为笔记
loadDataContent(); // 加载数据内容
mDiffNoteValues.clear(); // 清空差异数据
mIsCreate = false; // 设置为非创建状态
}
}

@ -0,0 +1,374 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 定义包名
package net.micode.notes.gtask.data;
// 导入必要的类
import android.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 org.json.JSONArray; // JSON数组类
import org.json.JSONException; // JSON异常类
import org.json.JSONObject; // JSON对象类
import java.util.ArrayList; // 动态数组类
// Task类用于表示一个任务
public class Task {
private static final String TAG = Task.class.getSimpleName(); // 日志标识
private boolean mCompleted; // 标识任务是否已完成
private String mNotes; // 任务的备注
private Task mPriorSibling; // 优先兄弟任务
private TaskList mParent; // 父任务列表
private JSONObject mMetaInfo; // 任务的元信息
// 构造函数
public Task() {
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(); // 创建新的JSON对象
try {
// 设置操作类型
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 设置操作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置索引
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
// 创建实体增量对象
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_TASK); // 设置实体类型
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 设置任务备注
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 将增量对象添加至主对象
// 设置父任务ID
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
// 设置目标父级类型
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// 设置列表ID
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// 设置优先兄弟任务ID
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("fail to generate task-create jsonobject"); // 抛出异常
}
return js; // 返回生成的JSON对象
}
// 获取更新操作的JSON对象
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject(); // 创建新的JSON对象
try {
// 设置操作类型
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// 设置操作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务ID
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// 创建实体增量对象
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); // 将增量对象添加至主对象
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("fail to generate task-update jsonobject"); // 抛出异常
}
return js; // 返回生成的JSON对象
}
// 根据远程JSON设置任务内容
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) { // 如果JSON对象不为空
try {
// 设置任务ID
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// 设置最后修改时间
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// 设置任务名称
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
// 设置任务备注
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}
// 设置删除标志
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}
// 设置完成状态
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
}
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("fail to get task content from jsonobject"); // 抛出异常
}
}
}
// 根据本地JSON设置任务内容
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"); // 无数据警告
}
try {
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; // 返回
}
// 遍历数据数组
for (int i = 0; i < dataArray.length(); i++) {
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) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
}
}
// 从内容获取本地JSON
public JSONObject getLocalJSONFromContent() {
String name = getName(); // 获取任务名称
try {
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_NOTE); // 设置任务类型
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_NOTE); // 确保类型为笔记
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 {
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()) {
// 双方没有更新
return SYNC_ACTION_NONE; // 返回无操作
} else {
// 应用远程到本地
return SYNC_ACTION_UPDATE_LOCAL; // 返回更新本地操作
}
} else {
// 验证GTask ID
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match"); // 输出错误日志
return SYNC_ACTION_ERROR; // 返回错误操作
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 仅本地修改
return SYNC_ACTION_UPDATE_REMOTE; // 返回更新远程操作
} else {
return SYNC_ACTION_UPDATE_CONFLICT; // 返回更新冲突操作
}
}
} catch (Exception e) { // 捕获所有异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
}
return SYNC_ACTION_ERROR; // 返回错误操作
}
// 判断任务是否值得保存
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0); // 检查元信息、名称或备注是否为空
}
// 设置任务完成状态
public void setCompleted(boolean completed) {
this.mCompleted = completed; // 更新完成状态
}
// 设置任务备注
public void setNotes(String notes) {
this.mNotes = notes; // 更新备注
}
// 设置优先兄弟任务
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling; // 更新优先兄弟任务
}
// 设置父任务列表
public void setParent(TaskList parent) {
this.mParent = parent; // 更新父任务列表
}
// 获取完成状态
public boolean getCompleted() {
return this.mCompleted; // 返回完成状态
}
// 获取备注
public String getNotes() {
return this.mNotes; // 返回备注
}
// 获取优先兄弟任务
public Task getPriorSibling() {
return this.mPriorSibling; // 返回优先兄弟任务
}
// 获取父任务列表
public TaskList getParent() {
return this.mParent; // 返回父任务列表
}
}

@ -0,0 +1,350 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// 定义包名
package net.micode.notes.gtask.data;
// 导入必要的类
import org.json.JSONException; // JSON异常类
import org.json.JSONObject; // JSON对象类
import android.util.Log; // 日志记录类
import java.util.ArrayList; // 动态数组类
// 定义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; // 设置索引为1
}
// 获取创建操作的JSON对象
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject(); // 创建新的JSON对象
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); // 设置动作类型为创建
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); // 设置动作ID
// index
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"); // 创建者ID
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
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); // 设置目标父任务类型
// 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; // 返回生成的JSON对象
}
// 获取更新操作的JSON对象
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject(); // 创建新的JSON对象
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); // 设置动作类型为更新
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); // 设置动作ID
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); // 设置任务ID
// entity_delta
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; // 返回生成的JSON对象
}
// 根据远程JSON设置任务内容
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) { // 如果JSON对象不为空
try {
// 设置任务ID
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// 设置最后修改时间
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// 设置任务名称
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
// 设置任务备注
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}
// 设置删除标志
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}
// 设置完成状态
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
}
} catch (JSONException e) { // 捕获JSON异常
Log.e(TAG, e.toString()); // 输出日志
e.printStackTrace(); // 打印堆栈信息
throw new ActionFailureException("fail to get tasklist content from jsonobject"); // 抛出异常
}
}
}
// 根据本地JSON设置任务内容
public void setContentByLocalJSON(JSONObject js) {
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 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; // 返回
}
// 遍历数据数组
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 {
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 {
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()) {
// 双方没有更新
return SYNC_ACTION_NONE; // 返回无操作
} else {
// 应用远程到本地
return SYNC_ACTION_UPDATE_LOCAL; // 返回更新本地操作
}
} else {
// 验证GTask ID
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match"); // 输出错误日志
return SYNC_ACTION_ERROR; // 返回错误操作
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 仅本地修改
return SYNC_ACTION_UPDATE_REMOTE; // 返回更新远程操作
} else {
return SYNC_ACTION_UPDATE_CONFLICT; // 返回更新冲突操作
}
}
} catch (Exception e) { // 捕获所有异常
Log.e(TAG, e.toString()); // 输出错误日志
e.printStackTrace(); // 打印堆栈信息
}
return SYNC_ACTION_ERROR; // 返回错误操作
}
// 判断任务是否值得保存
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0); // 检查元信息、名称或备注是否为空
}
// 设置任务完成状态
public void setCompleted(boolean completed) {
this.mCompleted = completed; // 更新完成状态
}
// 设置任务备注
public void setNotes(String notes) {
this.mNotes = notes; // 更新备注
}
// 设置优先兄弟任务
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling; // 更新优先兄弟任务
}
// 设置父任务列表
public void setParent(TaskList parent) {
this.mParent = parent; // 更新父任务列表
}
// 获取完成状态
public boolean getCompleted() {
return this.mCompleted; // 返回完成状态
}
// 获取备注
public String getNotes() {
return this.mNotes; // 返回备注
}
// 获取优先兄弟任务
public Task getPriorSibling() {
return this.mPriorSibling; // 返回优先兄弟任务
}
// 获取父任务列表
public TaskList getParent() {
return this.mParent; // 返回父任务列表
}
}

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

@ -0,0 +1,38 @@
/*
* 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;
// 自定义异常类,继承自 Exception
public class NetworkFailureException extends Exception {
// 序列版本 UID用于序列化
private static final long serialVersionUID = 2107610287180234136L;
// 默认构造函数
public NetworkFailureException() {
super(); // 调用父类的默认构造器
}
// 带有错误信息的构造函数
public NetworkFailureException(String paramString) {
super(paramString); // 调用父类的构造器,并传递错误信息
}
// 带有错误信息和原始异常的构造函数
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable); // 调用父类的构造器,传递错误信息和原始异常
}
}

@ -0,0 +1,136 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote; // 定义包名
import android.app.Notification; // 导入用于创建通知的类
import android.app.NotificationManager; // 导入用于管理通知的类
import android.app.PendingIntent; // 导入用于创建待处理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(); // 完成回调方法
}
// 上下文对象
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(); // 获取 GTask 任务管理器实例
}
// 取消同步方法
public void cancelSync() {
mTaskManager.cancelSync(); // 调用任务管理器的取消同步方法
}
// 发布进度信息
public void publishProgess(String message) {
publishProgress(new String[] {
message // 创建消息数组并发布进度
});
}
// 显示通知
private void showNotification(int tickerId, String content) {
// 创建通知对象
Notification notification = new Notification(R.drawable.notification, mContext.getString(tickerId), System.currentTimeMillis());
notification.defaults = Notification.DEFAULT_LIGHTS; // 设置通知的默认灯光
notification.flags = Notification.FLAG_AUTO_CANCEL; // 设置通知自动取消的标志
// 创建待处理Intent
PendingIntent pendingIntent;
// 根据不同的 tickerId 创建不同的 Intent
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesPreferenceActivity.class), 0);
} else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesListActivity.class), 0);
}
// 设置通知的内容信息
notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, pendingIntent);
// 显示通知
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
// 后台执行任务
@Override
protected Integer doInBackground(Void... unused) {
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]); // 显示同步进度通知
// 如果上下文是 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()); // 设置最后同步时间
} else if (result == GTaskManager.STATE_NETWORK_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); // 显示网络错误通知
} else if (result == GTaskManager.STATE_INTERNAL_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); // 显示内部错误通知
} else if (result == GTaskManager.STATE_SYNC_CANCELLED) {
showNotification(R.string.ticker_cancel, mContext.getString(R.string.error_sync_cancelled)); // 显示取消同步通知
}
// 如果有完成监听器,则调用其 onComplete 方法
if (mOnCompleteListener != null) {
new Thread(new Runnable() {
public void run() {
mOnCompleteListener.onComplete(); // 执行完成回调
}
}).start(); // 启动新线程执行回调
}
}
}

@ -0,0 +1,589 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote; // 定义当前类所在的包
import android.accounts.Account; // 引入账户类,表示一个用户账户
import android.accounts.AccountManager; // 导入账户管理类,用于管理账户及其相关操作
import android.accounts.AccountManagerFuture; // 导入异步获取账户的类
import android.app.Activity; // 引入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; // 登录成功
}
}
// 尝试使用Google官方URL登录
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL; // 恢复默认GET请求URL
mPostUrl = GTASK_POST_URL; // 恢复默认POST请求URL
if (!tryToLoginGtask(activity, authToken)) { // 尝试登录
return false; // 登录失败
}
}
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;
}
}
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie"); // 授权Cookie不存在
}
// 获取客户端版本
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; // 返回失败
}
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); // 将内容添加到构建器
}
} 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 {
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"); // 抛出操作失败异常
}
}
}
// 添加更新节点方法
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 {
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());
}
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());
}
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) {
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"); // 抛出操作失败异常
}
}
// 获取当前同步账户的方法
public Account getSyncAccount() {
return mAccount; // 返回当前账户信息
}
// 重置更新数组的方法
public void resetUpdateArray() {
mUpdateArray = null; // 将更新数组设置为null
}
}

@ -0,0 +1,781 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Apache License 2.0
*/
package net.micode.notes.gtask.remote; // 定义包名
import android.app.Activity; // 导入 Android Activity 类
import android.content.ContentResolver; // 导入 ContentResolver 类
import android.content.ContentUris; // 导入 ContentUris 类
import android.content.ContentValues; // 导入 ContentValues 类
import android.content.Context; // 导入 Context 类
import android.database.Cursor; // 导入 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; // 导入 SQL 笔记类
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; // 导入 Google 任务字符串工具类
import org.json.JSONArray; // 导入 JSON 数组类
import org.json.JSONException; // 导入 JSON 异常类
import org.json.JSONObject; // 导入 JSON 对象类
import java.util.HashMap; // 导入哈希表类
import java.util.HashSet; // 导入哈希集合类
import java.util.Iterator; // 导入迭代器类
import java.util.Map; // 导入映射类
// GTaskManager 类负责管理 Google 任务的同步操作
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; // 当前 Activity
private Context mContext; // 上下文对象
private ContentResolver mContentResolver; // 内容解析器
private boolean mSyncing; // 是否正在同步
private boolean mCancelled; // 是否被取消
private HashMap<String, TaskList> mGTaskListHashMap; // 任务列表哈希表
private HashMap<String, Node> mGTaskHashMap; // 任务哈希表
private HashMap<String, MetaData> mMetaHashMap; // 元数据哈希表
private TaskList mMetaList; // 元任务列表
private HashSet<Long> mLocalDeleteIdMap; // 本地删除 ID 集合
private HashMap<String, Long> mGidToNid; // GID 到 NID 映射
private HashMap<Long, String> mNidToGid; // NID 到 GID 映射
// 构造函数
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>(); // 初始化本地删除 ID 集合
mGidToNid = new HashMap<String, Long>(); // 初始化 GID 到 NID 映射
mNidToGid = new HashMap<Long, String>(); // 初始化 NID 到 GID 映射
}
// 获取单例实例
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
mInstance = new GTaskManager(); // 创建新实例
}
return mInstance; // 返回实例
}
// 设置 Activity 上下文,主要用于获取认证令牌
public synchronized void setActivityContext(Activity activity) {
mActivity = activity; // 存储当前 Activity
}
// 同步方法,执行实际的同步操作
public int sync(Context context, GTaskASyncTask asyncTask) {
if (mSyncing) { // 如果已经在同步则返回
Log.d(TAG, "Sync is in progress");
return STATE_SYNC_IN_PROGRESS;
}
mContext = context; // 设置上下文
mContentResolver = mContext.getContentResolver(); // 获取内容解析器
mSyncing = true; // 设置为正在同步
mCancelled = false; // 重置取消标志
// 清空各种哈希表和集合
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
try {
GTaskClient client = GTaskClient.getInstance(); // 获取 Google 任务客户端
client.resetUpdateArray(); // 重置更新数组
// 登录 Google 任务
if (!mCancelled) {
if (!client.login(mActivity)) { // 如果登录失败
throw new NetworkFailureException("login google task failed"); // 抛出网络异常
}
}
// 从 Google 获取任务列表
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); // 发布进度信息
initGTaskList(); // 初始化任务列表
// 进行内容同步
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); // 发布同步进度
syncContent(); // 执行内容同步
} catch (NetworkFailureException e) { // 捕获网络异常
Log.e(TAG, e.toString());
return STATE_NETWORK_ERROR; // 返回网络错误状态
} catch (ActionFailureException e) { // 捕获操作失败异常
Log.e(TAG, e.toString());
return STATE_INTERNAL_ERROR; // 返回内部错误状态
} catch (Exception e) { // 捕获所有其他异常
Log.e(TAG, e.toString());
e.printStackTrace(); // 打印堆栈跟踪
return STATE_INTERNAL_ERROR; // 返回内部错误状态
} finally { // 最终块,无论如何都会执行
// 清空各种哈希表和集合
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
mSyncing = false; // 重置同步状态
}
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; // 返回同步结果
}
// 初始化任务列表
private void initGTaskList() throws NetworkFailureException {
if (mCancelled) // 如果已取消则返回
return;
GTaskClient client = GTaskClient.getInstance(); // 获取 Google 任务客户端
try {
JSONArray jsTaskLists = client.getTaskLists(); // 获取任务列表的 JSON 数据
// 初始化元任务列表
mMetaList = null; // 初始化元任务列表为 null
for (int i = 0; i < jsTaskLists.length(); i++) { // 遍历任务列表
JSONObject object = jsTaskLists.getJSONObject(i); // 获取每个任务列表的 JSON 对象
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取任务的 GID
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); // 获取每个元数据的 JSON 对象
MetaData metaData = new MetaData(); // 创建元数据对象
metaData.setContentByRemoteJSON(object); // 设置元数据内容
if (metaData.isWorthSaving()) { // 如果元数据值得保存
mMetaList.addChildTask(metaData); // 将元数据添加到元任务列表中
if (metaData.getGid() != null) { // 如果 GID 不为 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++) { // 遍历 JSON 任务列表
JSONObject object = jsTaskLists.getJSONObject(i); // 获取每个任务的 JSON 对象
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取 GID
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); // 获取每个任务的 JSON 对象
gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取 GID
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) { // 捕获 JSON 相关异常
Log.e(TAG, e.toString()); // 记录错误信息
e.printStackTrace(); // 打印堆栈跟踪
throw new ActionFailureException("initGTaskList: handing JSONObject failed"); // 抛出操作失败异常
}
}
// 同步内容方法
private void syncContent() throws NetworkFailureException {
int syncType; // 同步类型
Cursor c = null; // 声明游标
String gid; // 存储 GID
Node node; // 声明节点
mLocalDeleteIdMap.clear(); // 清空本地删除 ID 集合
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); // 获取 GID
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)); // 添加到本地删除 ID 集合
}
} else {
Log.w(TAG, "failed to query trash folder"); // 查询回收站失败
}
} finally { // 确保游标关闭
if (c != null) {
c.close(); // 关闭游标
c = null;
}
}
// 首先同步文件夹
syncFolder();
// 处理数据库中存在的笔记
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER) // 查询类型为笔记且不在回收站的记录
}, NoteColumns.TYPE + " DESC"); // 根据类型降序排列
if (c != null) { // 如果查询成功
while (c.moveToNext()) { // 遍历查询结果
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取 GID
node = mGTaskHashMap.get(gid); // 从哈希表中获取节点
if (node != null) { // 如果节点存在
mGTaskHashMap.remove(gid); // 从哈希表中移除节点
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // GID 到 NID 映射
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); // NID 到 GID 映射
syncType = node.getSyncAction(c); // 获取同步类型
} else { // 如果节点不存在
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地添加
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c); // 执行内容同步
}
} else {
Log.w(TAG, "failed to query existing note in database"); // 查询数据库中的笔记失败
}
} finally { // 确保游标关闭
if (c != null) {
c.close(); // 关闭游标
c = null;
}
}
// 遍历剩余项
Iterator<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); // 执行本地添加同步
}
// mCancelled 可能由另一个线程设置,因此需要逐个检查
// 清除本地删除表
if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { // 执行批量删除
throw new ActionFailureException("failed to batch-delete local deleted notes"); // 抛出操作失败异常
}
}
// 刷新本地同步 ID
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate(); // 提交更新
refreshLocalSyncId(); // 刷新本地同步 ID
}
}
// 同步文件夹的方法
private void syncFolder() throws NetworkFailureException {
Cursor c = null; // 声明游标
String gid; // 存储 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); // 获取 GID
node = mGTaskHashMap.get(gid); // 从哈希表中获取节点
if (node != null) { // 如果节点存在
mGTaskHashMap.remove(gid); // 从哈希表中移除节点
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); // GID 到 NID 映射
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); // NID 到 GID 映射
// 对于系统文件夹,仅在必要时更新远程名称
if (!node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); // 执行更新同步
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); // 执行添加同步
}
} else {
Log.w(TAG, "failed to query root folder"); // 查询根文件夹失败
}
} finally { // 确保游标关闭
if (c != null) {
c.close(); // 关闭游标
c = null;
}
}
// 处理通话记录文件夹
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", new String[] {
String.valueOf(Notes.ID_CALL_RECORD_FOLDER) // 根据 ID 查询通话记录文件夹
}, null);
if (c != null) { // 如果查询成功
if (c.moveToNext()) { // 移动到第一条记录
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取 GID
node = mGTaskHashMap.get(gid); // 从哈希表中获取节点
if (node != null) { // 如果节点存在
mGTaskHashMap.remove(gid); // 从哈希表中移除节点
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); // GID 到 NID 映射
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); // NID 到 GID 映射
// 对于系统文件夹,仅在必要时更新远程名称
if (!node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); // 执行更新同步
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); // 执行添加同步
}
}
} else {
Log.w(TAG, "failed to query call note folder"); // 查询通话记录文件夹失败
}
} finally { // 确保游标关闭
if (c != null) {
c.close(); // 关闭游标
c = null;
}
}
// 处理本地现有的文件夹
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) // 查询类型为文件夹且不在回收站的记录
}, NoteColumns.TYPE + " DESC"); // 根据类型降序排列
if (c != null) { // 如果查询成功
while (c.moveToNext()) { // 遍历查询结果
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取 GID
node = mGTaskHashMap.get(gid); // 从哈希表中获取节点
if (node != null) { // 如果节点存在
mGTaskHashMap.remove(gid); // 从哈希表中移除节点
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // GID 到 NID 映射
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); // NID 到 GID 映射
syncType = node.getSyncAction(c); // 获取同步类型
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地添加
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c); // 执行内容同步
}
} else {
Log.w(TAG, "failed to query existing folder"); // 查询已有文件夹失败
}
} finally { // 确保游标关闭
if (c != null) {
c.close(); // 关闭游标
c = null;
}
}
// 处理远程添加的文件夹
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) { // 遍历哈希表
Map.Entry<String, TaskList> entry = iter.next(); // 获取每个条目
gid = entry.getKey(); // 获取 GID
node = entry.getValue(); // 获取节点
if (mGTaskHashMap.containsKey(gid)) { // 如果哈希表中包含该 GID
mGTaskHashMap.remove(gid); // 从哈希表中移除该节点
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 执行本地添加同步
}
}
if (!mCancelled) // 如果未取消
GTaskClient.getInstance().commitUpdate(); // 提交更新
}
// 执行内容同步的具体操作
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) { // 如果已取消则返回
return;
}
MetaData meta; // 声明元数据
switch (syncType) { // 根据同步类型执行不同操作
case Node.SYNC_ACTION_ADD_LOCAL: // 本地添加
addLocalNode(node); // 调用添加本地节点的方法
break;
case Node.SYNC_ACTION_ADD_REMOTE: // 远程添加
addRemoteNode(node, c); // 调用添加远程节点的方法
break;
case Node.SYNC_ACTION_DEL_LOCAL: // 本地删除
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); // 获取元数据
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta); // 删除远程节点
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 添加到本地删除 ID 集合
break;
case Node.SYNC_ACTION_DEL_REMOTE: // 远程删除
meta = mMetaHashMap.get(node.getGid()); // 获取元数据
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta); // 删除远程元节点
}
GTaskClient.getInstance().deleteNode(node); // 删除远程节点
break;
case Node.SYNC_ACTION_UPDATE_LOCAL: // 本地更新
updateLocalNode(node, c); // 调用更新本地节点的方法
break;
case Node.SYNC_ACTION_UPDATE_REMOTE: // 远程更新
updateRemoteNode(node, c); // 调用更新远程节点的方法
break;
case Node.SYNC_ACTION_UPDATE_CONFLICT: // 更新冲突
// 将两种修改合并可能是个好主意
// 目前简单地使用本地更新
updateRemoteNode(node, c); // 调用更新远程节点的方法
break;
case Node.SYNC_ACTION_NONE: // 无需操作
break;
case Node.SYNC_ACTION_ERROR: // 错误操作类型
default:
throw new ActionFailureException("unknown sync action type"); // 抛出操作失败异常
}
}
// 添加本地节点的方法
private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) { // 如果已取消则返回
return;
}
SqlNote sqlNote; // 声明 SQL 笔记对象
if (node instanceof TaskList) { // 如果节点是任务列表
if (node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { // 如果是默认文件夹
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); // 创建 SQL 笔记对象
} else if (node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { // 如果是通话记录文件夹
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); // 创建 SQL 笔记对象
} else {
sqlNote = new SqlNote(mContext); // 创建新的 SQL 笔记对象
sqlNote.setContent(node.getLocalJSONFromContent()); // 设置内容
sqlNote.setParentId(Notes.ID_ROOT_FOLDER); // 设置父 ID 为根文件夹
}
} else { // 如果节点是普通任务
sqlNote = new SqlNote(mContext); // 创建新的 SQL 笔记对象
JSONObject js = node.getLocalJSONFromContent(); // 获取任务的本地 JSON 内容
try {
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { // 如果 JSON 内容中有 HEAD_NOTE
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取 NOTE 对象
if (note.has(NoteColumns.ID)) { // 如果 NOTE 对象中有 ID
long id = note.getLong(NoteColumns.ID); // 获取 ID
if (DataUtils.existInNoteDatabase(mContentResolver, id)) { // 检查 ID 是否存在于数据库中
// ID 不可用,必须创建一个新 ID
note.remove(NoteColumns.ID); // 移除 ID
}
}
}
// 检查 JSON 对象是否包含 META_HEAD_DATA 字段
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); // 获取每个数据的 JSON 对象
if (data.has(DataColumns.ID)) { // 如果数据对象中包含 ID 字段
long dataId = data.getLong(DataColumns.ID); // 获取数据的 ID
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { // 检查数据 ID 是否在数据库中存在
// 如果数据 ID 不可用,则移除 ID 字段
data.remove(DataColumns.ID);
}
}
}
}
// 捕获 JSON 异常
} catch (JSONException e) {
Log.w(TAG, e.toString()); // 记录警告日志
e.printStackTrace(); // 打印堆栈跟踪
}
// 设置 SQL 笔记对象的内容为 JSON
sqlNote.setContent(js);
// 获取父任务的 ID
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());
if (parentId == null) { // 如果找不到父任务 ID
Log.e(TAG, "cannot find task's parent id locally"); // 记录错误日志
throw new ActionFailureException("cannot add local node"); // 抛出操作失败异常
}
sqlNote.setParentId(parentId.longValue()); // 设置 SQL 笔记的父 ID
// 创建本地节点
sqlNote.setGtaskId(node.getGid()); // 设置 GTask ID
sqlNote.commit(false); // 提交笔记内容,不同步到远程
// 更新 GID-NID 映射
mGidToNid.put(node.getGid(), sqlNote.getId()); // GID 到 NID 的映射
mNidToGid.put(sqlNote.getId(), node.getGid()); // NID 到 GID 的映射
// 更新元数据
updateRemoteMeta(node.getGid(), sqlNote); // 更新远程元数据
// 更新本地节点的方法
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) { // 如果已取消则返回
return;
}
SqlNote sqlNote; // 声明 SQL 笔记对象
// 在本地更新笔记
sqlNote = new SqlNote(mContext, c); // 根据游标初始化 SQL 笔记
sqlNote.setContent(node.getLocalJSONFromContent()); // 设置内容为节点的本地 JSON
// 获取父 ID
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
: new Long(Notes.ID_ROOT_FOLDER); // 如果节点是任务,则获取任务的父 ID否则使用根文件夹 ID
if (parentId == null) { // 如果找不到父任务 ID
Log.e(TAG, "cannot find task's parent id locally"); // 记录错误日志
throw new ActionFailureException("cannot update local node"); // 抛出操作失败异常
}
sqlNote.setParentId(parentId.longValue()); // 设置 SQL 笔记的父 ID
sqlNote.commit(true); // 提交笔记内容并同步到远程
// 更新元信息
updateRemoteMeta(node.getGid(), sqlNote); // 更新远程元数据
}
// 添加远程节点的方法
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) { // 如果已取消则返回
return;
}
SqlNote sqlNote = new SqlNote(mContext, c); // 根据游标初始化 SQL 笔记
Node n; // 声明节点
// 更新远程内容
if (sqlNote.isNoteType()) { // 如果是笔记类型
Task task = new Task(); // 创建任务对象
task.setContentByLocalJSON(sqlNote.getContent()); // 从 SQL 笔记获取本地内容
String parentGid = mNidToGid.get(sqlNote.getParentId()); // 获取父任务的 GID
if (parentGid == null) { // 如果找不到父任务 GID
Log.e(TAG, "cannot find task's parent tasklist"); // 记录错误日志
throw new ActionFailureException("cannot add remote task"); // 抛出操作失败异常
}
mGTaskListHashMap.get(parentGid).addChildTask(task); // 将任务添加到父任务列表
GTaskClient.getInstance().createTask(task); // 在远程创建任务
n = (Node) task; // 将任务赋值给节点 n
// 添加元数据
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(); // 获取 GID
TaskList list = entry.getValue(); // 获取任务列表
if (list.getName().equals(folderName)) { // 如果找到匹配的文件夹名称
tasklist = list; // 获取文件夹
if (mGTaskHashMap.containsKey(gid)) { // 如果任务哈希表中包含该 GID
mGTaskHashMap.remove(gid); // 移除该 GID
}
break; // 退出循环
}
}
// 如果没有找到匹配的文件夹,则创建新的文件夹
if (tasklist == null) {
tasklist = new TaskList(); // 创建新的任务列表
tasklist.setContentByLocalJSON(sqlNote.getContent()); // 设置任务列表内容
GTaskClient.getInstance().createTaskList(tasklist); // 在远程创建任务列表
mGTaskListHashMap.put(tasklist.getGid(), tasklist); // 将任务列表添加到哈希表
}
n = (Node) tasklist; // 将任务列表赋值给节点 n
}
// 更新本地笔记
sqlNote.setGtaskId(n.getGid()); // 设置 GTask ID
sqlNote.commit(false); // 提交笔记内容,不同步到远程
sqlNote.resetLocalModified(); // 重置本地修改标记
sqlNote.commit(true); // 提交并同步到远程
// 更新 GID-ID 映射
mGidToNid.put(n.getGid(), sqlNote.getId()); // GID 到 NID 的映射
mNidToGid.put(sqlNote.getId(), n.getGid()); // NID 到 GID 的映射
}
// 更新远程节点的方法
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) { // 如果已取消则返回
return;
}
SqlNote sqlNote = new SqlNote(mContext, c); // 根据游标初始化 SQL 笔记
// 更新远程内容
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()); // 获取当前父任务 GID
if (curParentGid == null) { // 如果找不到父任务 GID
Log.e(TAG, "cannot find task's parent tasklist"); // 记录错误日志
throw new ActionFailureException("cannot update remote task"); // 抛出操作失败异常
}
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); // 提交并同步到远程
}
// 更新远程元数据的方法
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
if (sqlNote != null && sqlNote.isNoteType()) { // 如果 SQL 笔记不为 null 且是笔记类型
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 的方法
private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) { // 如果已取消则返回
return;
}
// 获取最新的 GTask 列表
mGTaskHashMap.clear(); // 清空任务哈希表
mGTaskListHashMap.clear(); // 清空任务列表哈希表
mMetaHashMap.clear(); // 清空元数据哈希表
initGTaskList(); // 初始化 GTask 列表
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); // 获取 GID
Node node = mGTaskHashMap.get(gid); // 从哈希表中获取节点
if (node != null) { // 如果节点存在
mGTaskHashMap.remove(gid); // 从哈希表中移除节点
ContentValues values = new ContentValues(); // 创建内容值对象
values.put(NoteColumns.SYNC_ID, node.getLastModified()); // 更新同步 ID
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
c.getLong(SqlNote.ID_COLUMN)), values, null, null); // 更新内容
} else {
Log.e(TAG, "something is missed"); // 记录错误日志
throw new ActionFailureException("some local items don't have gid after sync"); // 抛出操作失败异常
}
}
} else {
Log.w(TAG, "failed to query local note to refresh sync id"); // 查询本地笔记以刷新同步 ID 失败
}
} finally { // 确保游标关闭
if (c != null) {
c.close(); // 关闭游标
c = null;
}
}
}
// 获取同步帐户信息的方法
public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name; // 返回同步帐户名
}
// 取消同步操作的方法
public void cancelSync() {
mCancelled = true; // 设置取消标志
}

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

@ -0,0 +1,307 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (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;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.net.Uri;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
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";
/**
* 便 ID
* @param context 访
* @param folderId ID便
* @return 便 ID
*/
public static synchronized long getNewNoteId(Context context, long folderId) {
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)); // 获取 URI 的第二段作为便签 ID
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
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(); // 初始化便签数据对象
}
/**
* 便
* @param key
* @param value
*/
public void setNoteValue(String key, String value) {
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); // 调用 NoteData 内部类的方法
}
/**
* 便 ID
* @param id ID
*/
public void setTextDataId(long id) {
mNoteData.setTextDataId(id); // 调用 NoteData 内部类的方法
}
/**
* 便 ID
* @return ID
*/
public long getTextDataId() {
return mNoteData.mTextDataId; // 返回 NoteData 内部类中的文本数据 ID
}
/**
* ID
* @param id ID
*/
public void setCallDataId(long id) {
mNoteData.setCallDataId(id); // 调用 NoteData 内部类的方法
}
/**
*
* @param key
* @param value
*/
public void setCallData(String key, String 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;
}
// 将便签的变化记录更新到数据库中
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
Log.e(TAG, "Update note error, should not happen");
// 即使更新失败,不立即返回,继续处理
}
mNoteDiffValues.clear(); // 清空便签变化记录
// 同步便签的具体数据到数据库
if (mNoteData.isLocalModified()
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
return false; // 如果数据同步失败,返回 false
}
return true; // 同步成功返回 true
}
/**
* NoteData Note 便
*/
private class NoteData {
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; // 初始化文本数据 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) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
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; // 设置通话数据 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()); // 更新修改时间
}
/**
*
* @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()); // 更新修改时间
}
/**
* 便 ()
* @param context
* @param noteId 便 ID
* @return URI null
*/
Uri pushIntoContentResolver(Context context, long noteId) {
// 批量操作列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<>();
ContentProviderOperation.Builder builder = null;
// 文本数据处理逻辑 (省略注释的细节部分与逻辑结构类似)
if (mTextDataValues.size() > 0) {
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) {
// 插入逻辑 (与通话数据类似)
} else {
// 更新逻辑 (与通话数据类似)
}
mTextDataValues.clear();
}
// 通话数据处理逻辑 (省略类似文本数据的逻辑)
// ...
// 应用批量操作到数据库
if (operationList.size() > 0) {
try {
ContentProviderResult[] results = context.getContentResolver().applyBatch(
Notes.AUTHORITY, operationList);
return (results == null || results.length == 0 || results[0] == null) ? null
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
} catch (RemoteException e) {
Log.e(TAG, "RemoteException during applyBatch: " + e.toString());
} catch (OperationApplicationException e) {
Log.e(TAG, "OperationApplicationException during applyBatch: " + e.toString());
}
}
return null; // 返回 null 表示操作失败
}
}
}

@ -0,0 +1,210 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (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;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
/**
* WorkingNote
*
*/
public class WorkingNote {
// 代表当前操作的笔记对象 (数据存储与更新逻辑封装在 Note 类中)
private Note mNote;
// 笔记的唯一标识 ID标记笔记在数据库中的主键
private long mNoteId;
// 笔记的实际内容,通常是用户输入的文本
private String mContent;
// 笔记的模式,用于区分普通模式 (文本) 或清单模式
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, // 数据 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, // 笔记的父文件夹 ID
NoteColumns.ALERTED_DATE, // 笔记的提醒时间
NoteColumns.BG_COLOR_ID, // 笔记背景颜色 ID
NoteColumns.WIDGET_ID, // 小部件 ID
NoteColumns.WIDGET_TYPE, // 小部件类型
NoteColumns.MODIFIED_DATE // 最后修改时间
};
// 数据表列索引,用于通过数据库 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; // 设置所属文件夹 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; // 设置笔记 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); // 获取父文件夹 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
} else {
// 如果未能找到笔记,记录错误日志并抛出异常
Log.e(TAG, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
}
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) // 查询条件为 Note ID
}, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
String type = cursor.getString(DATA_MIME_TYPE_COLUMN); // 获取数据类型
if (DataConstants.NOTE.equals(type)) {
// 如果数据类型为普通文本笔记
mContent = cursor.getString(DATA_CONTENT_COLUMN); // 获取笔记内容
mMode = cursor.getInt(DATA_MODE_COLUMN); // 获取笔记模式
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); // 设置笔记数据 ID
} else if (DataConstants.CALL_NOTE.equals(type)) {
// 如果数据类型为通话记录笔记
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); // 设置通话记录 ID
} else {
Log.d(TAG, "Wrong note type with type:" + type); // 记录错误类型
}
} while (cursor.moveToNext()); // 遍历所有数据行
}
cursor.close(); // 关闭 Cursor
} else {
// 如果未找到内容数据,记录错误日志并抛出异常
Log.e(TAG, "No data with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
}
}
// 其他方法的注释请参考以上详细注释风格。
// 下面是关键逻辑中部分方法的功能概要:
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) { /* 转换笔记为通话记录 */ }
}

@ -0,0 +1,888 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class BackupUtils {
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); // 如果实例不存在,则新建一个实例
}
return sInstance; // 返回单例实例
}
// 定义一系列状态码,用于表示备份或恢复的状态
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); // 初始化TextExport对象
}
// 检查外部存储SD卡是否可用
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); // 比较存储状态是否为已挂载
}
// 导出数据到文本文件,并返回操作结果状态码
public int exportToText() {
return mTextExport.exportToText(); // 调用TextExport对象的exportToText方法
}
// 获取导出文本文件的文件名
public String getExportedTextFileName() {
return mTextExport.mFileName; // 返回TextExport对象中保存的文件名
}
// 获取导出文本文件的目录路径
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory; // 返回TextExport对象中保存的文件目录
}
// 内部类TextExport封装了将笔记数据导出到文本文件的逻辑
private static class TextExport {
// 定义查询笔记信息时需要的字段
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET,
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,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
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 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 = ""; // 初始化文件目录为空字符串
}
// 根据索引获取格式化字符串
private String getFormat(int id) {
return TEXT_FORMAT[id]; // 返回格式化字符串数组中指定索引的字符串
}
// 导出指定文件夹ID的笔记到文本
private void exportFolderToText(String folderId, PrintStream ps) {
// 查询属于该文件夹的笔记
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 {
// 打印笔记的最后修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 查询属于该笔记的数据
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close(); // 关闭游标
}
}
// 导出指定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)) {
// 如果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));
}
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
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),
content));
}
}
} while (dataCursor.moveToNext());
}
dataCursor.close(); // 关闭游标
}
// 打印笔记之间的分隔符
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
// 执行导出操作,将笔记数据导出为用户可读的文本格式
public int exportToText() {
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted"); // 如果SD卡未挂载则记录日志并返回状态码
return STATE_SD_CARD_UNMOUONTED;
}
PrintStream ps = getExportToTextPrintStream(); // 获取指向导出文本文件的PrintStream对象
if (ps == null) {
Log.e(TAG, "get print stream error"); // 如果获取PrintStream失败则记录日志并返回状态码
return STATE_SYSTEM_ERROR;
}
// 查询所有文件夹和其笔记,除了垃圾箱文件夹和通话记录文件夹
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 {
// 打印文件夹名称
String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name); // 如果是通话记录文件夹,则使用资源文件中的名称
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); // 否则,使用查询到的文件夹名称
}
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId
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; // 登录成功
}
}
// 尝试使用Google官方URL登录
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL; // 恢复默认GET请求URL
mPostUrl = GTASK_POST_URL; // 恢复默认POST请求URL
if (!tryToLoginGtask(activity, authToken)) { // 尝试登录
return false; // 登录失败
}
}
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;
}
}
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie"); // 授权Cookie不存在
}
// 获取客户端版本
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; // 返回失败
}
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); // 将内容添加到构建器
}
} 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 {
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"); // 抛出操作失败异常
}
}
}
// 添加更新节点方法
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 {
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());
}
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());
}
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) {
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"); // 抛出操作失败异常
}
}
// 获取当前同步账户的方法
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); // 设置文本视图的文本为文件夹名称
}
}

@ -0,0 +1,194 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
/**
*
*/
public class DataUtils {
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) { // 检查传入的ID集合是否为空
Log.d(TAG, "the ids is null");
return true;
}
if (ids.size() == 0) { // 检查ID集合是否为空
Log.d(TAG, "no id is in the hashset");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); // 操作列表,用于批量操作
for (long id : ids) {
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()); // 将删除操作添加到操作列表
}
try {
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) { // 捕获远程异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) { // 捕获操作应用异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
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(); // 创建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) { // 检查传入的ID集合是否为空
Log.d(TAG, "the ids is null");
return true;
}
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); // 设置新的父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) { // 检查操作结果
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) { // 捕获远程异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) { // 捕获操作应用异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false; // 发生异常时返回false
}
/**
*
* @param resolver ContentResolver访
* @return
*/
public static int getUserFolderCount(ContentResolver resolver) {
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()) { // 移动到游标的第一行
try {
count = cursor.getInt(0); // 获取文件夹数量
} catch (IndexOutOfBoundsException e) { // 捕获索引越界异常
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
cursor.close(); // 关闭游标
}
}
}
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), // 查询笔记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; // 设置存在标志为true
}
cursor.close(); // 关闭游标
}
return exist; // 返回笔记是否存在
}
/**
*
* @param resolver ContentResolver访
* @param noteId ID
* @return truefalse
*/
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId)

@ -0,0 +1,114 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
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";
}

@ -0,0 +1,434 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
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;//黄色为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,
R.drawable.edit_white,
R.drawable.edit_green,
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,
R.drawable.edit_title_white,
R.drawable.edit_title_green,
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)) {
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
} else {
return BG_DEFAULT_COLOR;
}
}
/**
* 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,
R.drawable.list_white_up,
R.drawable.list_green_up,
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,
R.drawable.list_white_middle,
R.drawable.list_green_middle,
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,
R.drawable.list_white_down,
R.drawable.list_green_down,
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,
R.drawable.list_white_single,
R.drawable.list_green_single,
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,
R.drawable.widget_2x_white,
R.drawable.widget_2x_green,
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,
R.drawable.widget_4x_white,
R.drawable.widget_4x_green,
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,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
};
// 获取指定大小的文本外观资源ID
public static int getTexAppearanceResource(int id) {
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if (id >= TEXTAPPEARANCE_RESOURCES.length) {
return BG_DEFAULT_FONT_SIZE;
}
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); // 设置文本视图的文本为文件夹名称
}
}

@ -0,0 +1,176 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
// 导入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 {
// 定义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); // 调用父类的onCreate方法执行Activity创建的基本操作
requestWindowFeature(Window.FEATURE_NO_TITLE); // 请求窗口特性,不显示标题
final Window win = getWindow(); // 获取当前窗口对象
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); // 在锁屏状态下显示窗口
if (!isScreenOn()) { // 检查屏幕是否处于开启状态
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON // 保持屏幕常亮
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON // 打开屏幕
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON // 允许锁定时屏幕常亮
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); // 添加装饰物
}
Intent intent = getIntent(); // 获取启动该Activity的Intent
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); // 解析笔记ID
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); // 从数据库获取笔记摘要
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, // 如果摘要超过最大长度,进行截断
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) // 添加信息提示
: mSnippet; // 否则保持原样
} catch (IllegalArgumentException e) {
e.printStackTrace(); // 打印异常堆栈信息
return; // 发生异常时返回
}
mPlayer = new MediaPlayer(); // 创建MediaPlayer实例
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { // 检查笔记是否在数据库中可见
showActionDialog(); // 显示操作对话框
playAlarmSound(); // 播放闹钟声音
} else {
finish(); // 如果笔记不可见则结束Activity
}
}
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); // 获取电源管理器实例
return pm.isScreenOn(); // 返回屏幕是否开启的状态
}
private void playAlarmSound() { // 方法用于播放闹钟声音
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); // 获取系统默认闹钟铃声的URI
int silentModeStreams = Settings.System.getInt(getContentResolver(), // 检查静音模式影响的音频流
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { // 如果静音模式影响闹钟流
mPlayer.setAudioStreamType(silentModeStreams); // 设置MediaPlayer的音频流类型
} else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); // 否则设置为闹钟音频流类型
}
try {
mPlayer.setDataSource(this, url); // 设置MediaPlayer的数据源为闹钟铃声
mPlayer.prepare(); // 准备MediaPlayer
mPlayer.setLooping(true); // 设置音频循环播放
mPlayer.start(); // 开始播放音频
} catch (IllegalArgumentException e) {
e.printStackTrace(); // 捕获并打印异常
} catch (SecurityException e) {
e.printStackTrace(); // 捕获并打印异常
} catch (IllegalStateException e) {
e.printStackTrace(); // 捕获并打印异常
} catch (IOException e) {
e.printStackTrace(); // 捕获并打印异常
}
}
private void showActionDialog() { // 方法用于显示操作对话框
AlertDialog.Builder dialog = new AlertDialog.Builder(this); // 创建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); // 显示对话框,并设置关闭监听器
}
public void onClick(DialogInterface dialog, int which) { // 点击对话框按钮的事件处理
switch (which) {
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; // 默认情况无操作
}
}
public void onDismiss(DialogInterface dialog) { // 对话框关闭时的事件处理
stopAlarmSound(); // 停止闹钟音效
finish(); // 结束该Activity
}
private void stopAlarmSound() { // 方法用于停止播放闹钟声音
if (mPlayer != null) { // 检查MediaPlayer是否不为空
mPlayer.stop(); // 停止播放
mPlayer.release(); // 释放MediaPlayer资源
mPlayer = null; // 将MediaPlayer置为null
}
}
}

@ -0,0 +1,89 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
// 定义当前类所属的包,通常用于组织代码并避免命名冲突
import android.app.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 [] {
// 定义一个字符串数组 PROJECTION指定要查询的列
NoteColumns.ID, // 笔记的 ID
NoteColumns.ALERTED_DATE // 笔记的提醒日期
};
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(); // 获取当前时间的毫秒值
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);//查询时的排序参数,这里设置为 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);
//创建一个 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释放与其相关联的资源防止内存泄漏。
}
}
}

@ -0,0 +1,36 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver {
//声明一个公共类 AlarmReceiver它继承自 BroadcastReceiver用于处理接收到的广播。
@Override
//注解,表明接下来的方法是对父类方法的重写。
public void onReceive(Context context, Intent 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 界面切换到闹钟提醒的活动。
}
}

@ -0,0 +1,520 @@
/*
* 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.
*/
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui; // 定义包名
import java.text.DateFormatSymbols; // 导入日期格式符号类
import java.util.Calendar; // 导入日历类
import net.micode.notes.R; // 导入资源类
import android.content.Context; // 导入上下文类
import android.text.format.DateFormat; // 导入日期格式类
import android.view.View; // 导入视图类
import android.widget.FrameLayout; // 导入框架布局类
import android.widget.NumberPicker; // 导入数字选择器类
public class DateTimePicker extends FrameLayout { // 定义日期时间选择器类,继承自 FrameLayout
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; // 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; // AM/PM选择器
private Calendar mDate; // 存储日期的日历对象
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; // 用于存储日期显示值的数组
private boolean mIsAm; // 存储当前是否为 AM 的标志
private boolean mIs24HourView; // 存储当前是否为24小时制的标志
private boolean mIsEnabled = DEFAULT_ENABLE_STATE; // 存储当前启用状态
private boolean mInitialising; // 存储初始化状态的标志
private OnDateTimeChangedListener mOnDateTimeChangedListener; // 日期时间改变的监听器
// 日期选择器值变化的监听器
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); // 更新日期
updateDateControl(); // 更新日期控制
onDateTimeChanged(); // 通知日期时间已更改
}
};
// 小时选择器值变化的监听器
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false; // 标记日期是否变化
Calendar cal = Calendar.getInstance(); // 获取当前日期的日历对象
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); // 日期加 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); // 日期减 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; // 切换 AM/PM 状态
updateAmPmControl(); // 更新 AM/PM 控制
}
} else { // 如果是 24 小时制
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { // 从 23 切换到 0
cal.setTimeInMillis(mDate.getTimeInMillis());
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); // 日期减 1
isDateChanged = true; // 标记日期变化
}
}
// 设置小时
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour); // 更新日历中的小时
onDateTimeChanged(); // 通知日期时间已更改
if (isDateChanged) { // 如果日期变化,则更新日期
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
}
};
// 分钟选择器值变化的监听器
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue(); // 获取分钟选择器的最小值
int maxValue = mMinuteSpinner.getMaxValue(); // 获取分钟选择器的最大值
int offset = 0; // 记录偏移量
if (oldVal == maxValue && newVal == minValue) {
offset += 1; // 增加偏移量
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1; // 减少偏移量
}
if (offset != 0) { // 如果偏移量不为零
mDate.add(Calendar.HOUR_OF_DAY, offset); // 更新小时
mHourSpinner.setValue(getCurrentHour()); // 更新小时选择器的值
updateDateControl(); // 更新日期控制
int newHour = getCurrentHourOfDay(); // 获取当前小时
// 更新 AM/PM 状态
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false; // 设为 PM
updateAmPmControl(); // 更新 AM/PM 控制
} else {
mIsAm = true; // 设为 AM
updateAmPmControl(); // 更新 AM/PM 控制
}
}
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; // 切换 AM/PM 状态
// 根据 AM/PM 状态更新小时
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); // 如果为 AM减去 12 小时
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); // 如果为 PM加上 12 小时
}
updateAmPmControl(); // 更新 AM/PM 控制
onDateTimeChanged(); // 通知日期时间已更改
}
};
// 定义日期时间改变的监听器接口
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute); // 方法签名
}
// 构造函数,使用当前时间作为参数
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis()); // 调用重载构造函数
}
public DateTimePicker(Context context, long date) {
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; // 根据当前小时判断是否为 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); // 设置值变化监听器
// 初始化小时选择器
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); // 设置值变化监听器
// 初始化分钟选择器
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); // 设置最小值
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); // 设置最大值
mMinuteSpinner.setOnLongPressUpdateInterval(100); // 设置长按更新间隔
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); // 设置值变化监听器
// 初始化 AM/PM 选择器
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); // 获取 AM/PM 字符串
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); // 设置最小值
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); // 设置最大值
mAmPmSpinner.setDisplayedValues(stringsForAmPm); // 设置显示值
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); // 设置值变化监听器
// 更新控件到初始状态
updateDateControl(); // 更新日期选择器状态
updateHourControl(); // 更新小时选择器状态
updateAmPmControl(); // 更新 AM/PM 选择器状态
set24HourView(is24HourView); // 设置是否使用 24 小时制
// 设置为当前时间
setCurrentDate(date); // 设置当前日期为传入的日期
setEnabled(isEnabled()); // 设置选择器的启用状态
// 设置内容描述
mInitialising = false; // 标记初始化完成
}
@Override
public void setEnabled(boolean enabled) { // 重写设置启用状态的方法
if (mIsEnabled == enabled) { // 如果当前状态与目标状态相同,则返回
return;
}
super.setEnabled(enabled); // 调用父类的设置启用方法
// 设置各个选择器的启用状态
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled);
mIsEnabled = enabled; // 更新当前启用状态
}
@Override
public boolean isEnabled() { // 重写获取启用状态的方法
return mIsEnabled; // 返回当前启用状态
}
/**
*
*
* @return
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis(); // 返回当前日期的毫秒表示
}
/**
*
*
* @param date
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance(); // 获取当前日期的日历对象
cal.setTimeInMillis(date); // 将毫秒值设置到日历中
// 设置当前日期的年、月、日、小时和分钟
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}
/**
*
*
* @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); // 设置当前分钟
}
/**
*
*
* @return
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR); // 返回当前年份
}
/**
*
*
* @param year
*/
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) { // 如果不在初始化并且当前年份未变化,则返回
return;
}
mDate.set(Calendar.YEAR, year); // 更新日历中的年份
updateDateControl(); // 更新日期选择器控制
onDateTimeChanged(); // 通知日期时间已更改
}
/**
*
*
* @return
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH); // 返回当前月份
}
/**
*
*
* @param month
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) { // 如果不在初始化且当前月份未变化,则返回
return;
}
mDate.set(Calendar.MONTH, month); // 更新日历中的月份
updateDateControl(); // 更新日期控制
onDateTimeChanged(); // 通知日期时间已更改
}
/**
*
*
* @return
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH); // 返回当前日期
}
/**
*
*
* @param dayOfMonth
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) { // 如果不在初始化且当前日期未变化,则返回
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); // 更新日历中的日期
updateDateControl(); // 更新日期控制
onDateTimeChanged(); // 通知日期时间已更改
}
/**
*
*
* @return 24
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY); // 返回当前 24 小时制的小时
}
private int getCurrentHour() { // 获取当前小时
if (mIs24HourView){ // 如果是 24 小时制
return getCurrentHourOfDay(); // 返回 24 小时制的当前小时
} else {
int hour = getCurrentHourOfDay(); // 获取当前小时
// 转换为 12 小时制
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour; // 处理 0 点的情况
}
}
}
/**
*
*
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { // 如果不在初始化且当前小时未变化,则返回
return;
}
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 { // 如果属于 AM
mIsAm = true; // 设置为 AM
if (hourOfDay == 0) { // 如果是 0 点
hourOfDay = HOURS_IN_HALF_DAY; // 转换为 12 点
}
}
updateAmPmControl(); // 更新 AM/PM 控制
}
mHourSpinner.setValue(hourOfDay); // 更新小时选择器的值
onDateTimeChanged(); // 通知日期时间已更改
}
/**
*
*
* @return
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE); // 返回当前分钟
}
/**
*
*
* @param minute
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) { // 如果不在初始化且当前分钟未变化,则返回
return;
}
mMinuteSpinner.setValue(minute); // 更新分钟选择器的值
mDate.set(Calendar.MINUTE, minute); // 更新日历中的分钟
onDateTimeChanged(); // 通知日期时间已更改
}
/**
* @return 24 true false
*/
public boolean is24HourView () {
return mIs24HourView; // 返回当前是否在 24 小时制
}
/**
* 使 24 AM/PM
*
* @param is24HourView true 24 false AM/PM
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) { // 如果当前状态与目标状态相同则返回
return;
}
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); // 格式化日期
}
mDateSpinner.setDisplayedValues(mDateDisplayValues); // 设置日期选择器显示的值
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); // 设置日期选择器的默认值为一周中间的值
mDateSpinner.invalidate(); // 刷新日期选择器
}
private void updateAmPmControl() { // 更新 AM/PM 选择器状态
if (mIs24HourView) { // 如果为 24 小时制
mAmPmSpinner.setVisibility(View.GONE); // 隐藏 AM/PM 选择器
} else {
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) { // 如果为 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); // 设置 12 小时制的最小值
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); // 设置 12 小时制的最大值
}
}
/**
*
* @param callback null
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback; // 更新监听器
}
private void onDateTimeChanged() { // 日期时间更改的处理方法
if (mOnDateTimeChangedListener != null) { // 如果有设置的监听器
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), // 调用监听器的方法,传递当前的日期和时间
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}

@ -0,0 +1,95 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui; // 定义包名
import java.util.Calendar; // 导入日历类
import net.micode.notes.R; // 导入资源类
import net.micode.notes.ui.DateTimePicker; // 导入日期时间选择器类
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; // 导入日期时间改变监听器接口
import android.app.AlertDialog; // 导入警告对话框类
import android.content.Context; // 导入上下文类
import android.content.DialogInterface; // 导入对话框接口
import android.content.DialogInterface.OnClickListener; // 导入点击监听器接口
import android.text.format.DateFormat; // 导入日期格式类
import android.text.format.DateUtils; // 导入日期工具类
public class DateTimePickerDialog extends AlertDialog implements OnClickListener { // 定义日期时间选择对话框类,继承自 AlertDialog并实现 OnClickListener 接口
private Calendar mDate = Calendar.getInstance(); // 初始化当前日期的日历对象
private boolean mIs24HourView; // 存储当前是否为 24 小时制的标志
private OnDateTimeSetListener mOnDateTimeSetListener; // 日期时间设置监听器
private DateTimePicker mDateTimePicker; // 日期时间选择器实例
// 定义日期时间设置监听器接口
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date); // 日期时间设置方法签名
}
// 构造函数,接受上下文和日期参数
public DateTimePickerDialog(Context context, long date) {
super(context); // 调用父类构造函数
mDateTimePicker = new DateTimePicker(context); // 创建日期时间选择器实例
setView(mDateTimePicker); // 设置对话框视图为日期时间选择器
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { // 设置日期时间改变监听器
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) { // 当日期时间改变时调用
mDate.set(Calendar.YEAR, year); // 更新日历中的年份
mDate.set(Calendar.MONTH, month); // 更新日历中的月份
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); // 更新日历中的日期
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); // 更新日历中的小时
mDate.set(Calendar.MINUTE, minute); // 更新日历中的分钟
updateTitle(mDate.getTimeInMillis()); // 更新对话框标题
}
});
mDate.setTimeInMillis(date); // 设置日历的时间为传入的日期
mDate.set(Calendar.SECOND, 0); // 将秒设置为 0
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); // 设置日期时间选择器的当前日期
setButton(context.getString(R.string.datetime_dialog_ok), this); // 设置确定按钮
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); // 设置取消按钮
set24HourView(DateFormat.is24HourFormat(this.getContext())); // 设置是否使用 24 小时制
updateTitle(mDate.getTimeInMillis()); // 更新对话框标题
}
// 设置 24 小时制模式
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView; // 更新 24 小时制标志
}
// 设置日期时间设置监听器
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack; // 更新监听器
}
// 更新对话框标题
private void updateTitle(long date) {
int flag = // 设置日期时间格式标志
DateUtils.FORMAT_SHOW_YEAR | // 显示年份
DateUtils.FORMAT_SHOW_DATE | // 显示日期
DateUtils.FORMAT_SHOW_TIME; // 显示时间
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_12HOUR; // 根据模式添加相应的标志
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); // 设置对话框标题为格式化的日期时间
}
// 点击事件处理
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) { // 如果有设置的监听器
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); // 调用监听器方法,传递当前日期
}
}
}

@ -0,0 +1,65 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.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 net.micode.notes.R; // 导入资源类
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() { // 设置按钮点击监听器
public void onClick(View v) {
mPopupMenu.show(); // 显示弹出菜单
}
});
}
// 设置下拉菜单项点击监听器
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) { // 如果弹出菜单不为空
mPopupMenu.setOnMenuItemClickListener(listener); // 设置菜单项点击监听器
}
}
// 根据 ID 查找菜单项
public MenuItem findItem(int id) {
return mMenu.findItem(id); // 根据 ID 返回菜单项
}
// 设置按钮的标题
public void setTitle(CharSequence title) {
mButton.setText(title); // 更新按钮文本为给定的标题
}
}

@ -0,0 +1,88 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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); // 设置文本视图的文本为文件夹名称
}
}
}

@ -0,0 +1,865 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui; // 定义包名
import android.app.Activity; // 导入Activity类
import android.app.AlarmManager; // 导入AlarmManager类
import android.app.AlertDialog; // 导入AlertDialog类
import android.app.PendingIntent; // 导入PendingIntent类
import android.app.SearchManager; // 导入SearchManager类
import android.appwidget.AppWidgetManager; // 导入AppWidgetManager类
import android.content.ContentUris; // 导入ContentUris类
import android.content.Context; // 导入Context类
import android.content.DialogInterface; // 导入DialogInterface类
import android.content.Intent; // 导入Intent类
import android.content.SharedPreferences; // 导入SharedPreferences类
import android.graphics.Paint; // 导入Paint类
import android.os.Bundle; // 导入Bundle类
import android.preference.PreferenceManager; // 导入PreferenceManager类
import android.text.Spannable; // 导入Spannable类
import android.text.SpannableString; // 导入SpannableString类
import android.text.TextUtils; // 导入TextUtils类
import android.text.format.DateUtils; // 导入DateUtils类
import android.text.style.BackgroundColorSpan; // 导入BackgroundColorSpan类
import android.util.Log; // 导入Log类
import android.view.LayoutInflater; // 导入LayoutInflater类
import android.view.Menu; // 导入Menu类
import android.view.MenuItem; // 导入MenuItem类
import android.view.MotionEvent; // 导入MotionEvent类
import android.view.View; // 导入View类
import android.view.ViewGroup; // 导入ViewGroup类
import android.view.WindowManager; // 导入WindowManager类
import android.widget.CheckBox; // 导入CheckBox类
import android.widget.CompoundButton; // 导入CompoundButton类
import android.widget.EditText; // 导入EditText类
import android.widget.ImageView; // 导入ImageView类
import android.widget.LinearLayout; // 导入LinearLayout类
import android.widget.TextView; // 导入TextView类
import android.widget.Toast; // 导入Toast类
import net.micode.notes.R; // 导入资源类
import net.micode.notes.data.Notes; // 导入Notes数据类
import net.micode.notes.data.Notes.TextNote; // 导入笔记文本类
import net.micode.notes.model.WorkingNote; // 导入WorkingNote模型类
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener; // 导入设置改变监听器接口
import net.micode.notes.tool.DataUtils; // 导入数据工具类
import net.micode.notes.tool.ResourceParser; // 导入资源解析工具类
import net.micode.notes.tool.ResourceParser.TextAppearanceResources; // 导入文本外观资源
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; // 导入日期时间设置监听器接口
import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; // 导入文本视图变化监听器接口
import net.micode.notes.widget.NoteWidgetProvider_2x; // 导入 2x 笔记小部件提供者
import net.micode.notes.widget.NoteWidgetProvider_4x; // 导入 4x 笔记小部件提供者
import java.util.HashMap; // 导入HashMap类
import java.util.HashSet; // 导入HashSet类
import java.util.Map; // 导入Map接口
import java.util.regex.Matcher; // 导入Matcher类
import java.util.regex.Pattern; // 导入Pattern类
public class NoteEditActivity extends Activity implements OnClickListener, // 定义NoteEditActivity类继承Activity并实现点击监听和其他接口
NoteSettingChangedListener, OnTextViewChangeListener {
private class HeadViewHolder { // 内部类用于存储头部视图的信息
public TextView tvModified; // 修改日期文本视图
public ImageView ivAlertIcon; // 警告图标
public TextView tvAlertDate; // 警告日期文本视图
public ImageView ibSetBgColor; // 设置背景颜色的图标按钮
}
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>(); // 背景选择按钮映射
static { // 静态初始化块
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); // 添加背景颜色与按钮的映射
sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
}
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>(); // 背景选择颜色选中映射
static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); // 添加背景颜色与选中状态按钮的映射
sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
}
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>(); // 字体大小按钮映射
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); // 添加字体大小与按钮的映射
sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);
sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM);
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
}
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>(); // 字体选择器选中映射
static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); // 添加字体大小与选中状态按钮的映射
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
}
private static final String TAG = "NoteEditActivity"; // 定义日志 TAG
private HeadViewHolder mNoteHeaderHolder; // 头部视图持有者
private View mHeadViewPanel; // 头部视图面板
private View mNoteBgColorSelector; // 笔记背景颜色选择视图
private View mFontSizeSelector; // 字体大小选择视图
private EditText mNoteEditor; // 笔记编辑器
private View mNoteEditorPanel; // 笔记编辑器面板
private WorkingNote mWorkingNote; // 当前正在编辑的笔记
private SharedPreferences mSharedPrefs; // 共享偏好设置
private int mFontSizeId; // 当前字体大小 ID
private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; // 字体大小偏好设置键
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; // 快捷方式图标标题最大长度
public static final String TAG_CHECKED = String.valueOf('\u221A'); // 完成标记
public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); // 未完成标记
private LinearLayout mEditTextList; // 编辑文本列表
private String mUserQuery; // 用户查询字符串
private Pattern mPattern; // 正则表达式模式
@Override
protected void onCreate(Bundle savedInstanceState) { // Activity 创建时调用
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit); // 设置内容视图为笔记编辑布局
if (savedInstanceState == null && !initActivityState(getIntent())) { // 如果没有保存的状态且初始化失败
finish(); // 结束Activity
return; // 返回
}
initResources(); // 初始化资源
}
/**
* ActivityActivity
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) { // 当Activity状态恢复时调用
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { // 如果包含特定的ID
Intent intent = new Intent(Intent.ACTION_VIEW); // 创建查看意图
intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); // 添加ID到意图中
if (!initActivityState(intent)) { // 初始化Activity状态若失败
finish(); // 结束Activity
return;
}
Log.d(TAG, "Restoring from killed activity"); // 日志输出
}
}
private boolean initActivityState(Intent intent) { // 初始化Activity状态的方法
/**
* {@link Intent#ACTION_VIEW}ID
* NotesListActivity
*/
mWorkingNote = null; // 清空笔记对象
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { // 如果Intent的操作是查看
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); // 获取笔记ID
mUserQuery = ""; // 初始化用户查询字符串
/**
*
*/
if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { // 如果Intent包含搜索数据
noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); // 获取数据关键字
mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); // 获取用户查询字符串
}
if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { // 检查笔记是否可见
Intent jump = new Intent(this, NotesListActivity.class); // 创建跳转Intent
startActivity(jump); // 启动跳转Activity
showToast(R.string.error_note_not_exist); // 显示笔记不存在的提示
finish(); // 结束此Activity
return false; // 返回false
} else {
mWorkingNote = WorkingNote.load(this, noteId); // 加载正在编辑的笔记
if (mWorkingNote == null) { // 如果加载失败
Log.e(TAG, "load note failed with note id" + noteId); // 日志错误输出
finish(); // 结束此Activity
return false; // 返回false
}
}
getWindow().setSoftInputMode( // 设置窗口输入模式
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
} else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { // 如果Intent的操作是插入或编辑
// 新建笔记
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); // 获取文件夹ID
int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, // 获取小部件ID
AppWidgetManager.INVALID_APPWIDGET_ID);
int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, // 获取小部件类型
Notes.TYPE_WIDGET_INVALIDE);
int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, // 获取背景资源ID
ResourceParser.getDefaultBgId(this));
// 解析通话记录笔记
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); // 获取电话号码
long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); // 获取通话日期
if (callDate != 0 && phoneNumber != null) { // 如果通话日期有效且电话号码不为空
if (TextUtils.isEmpty(phoneNumber)) { // 如果电话号码为空
Log.w(TAG, "The call record number is null");
}
long noteId = 0; // 初始化笔记ID
if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), // 根据电话号码和日期获取笔记ID
phoneNumber, callDate)) > 0) {
mWorkingNote = WorkingNote.load(this, noteId); // 加载已存在通话记录的笔记
if (mWorkingNote == null) { // 如果加载失败
Log.e(TAG, "load call note failed with note id" + noteId); // 日志错误输出
finish(); // 结束此Activity
return false; // 返回false
}
} else {
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, // 创建新笔记
widgetType, bgResId);
mWorkingNote.convertToCallNote(phoneNumber, callDate); // 转换为通话记录笔记
}
} else {
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType,
bgResId); // 创建空笔记
}
getWindow().setSoftInputMode( // 设置窗口输入模式
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
} else {
Log.e(TAG, "Intent not specified action, should not support"); // 日志错误输出
finish(); // 结束Activity
return false; // 返回false
}
mWorkingNote.setOnSettingStatusChangedListener(this); // 设置修改状态监听器
return true; // 返回true
}
@Override
protected void onResume() { // 当Activity恢复时调用
super.onResume();
initNoteScreen(); // 初始化笔记屏幕
}
private void initNoteScreen() { // 初始化笔记屏幕的方法
mNoteEditor.setTextAppearance(this, TextAppearanceResources // 设置文本编辑器的样式
.getTexAppearanceResource(mFontSizeId));
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { // 如果是检查列表模式
switchToListMode(mWorkingNote.getContent()); // 切换到列表模式
} else {
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); // 获取并设置高亮显示的文本
mNoteEditor.setSelection(mNoteEditor.getText().length()); // 将光标移动到文本末尾
}
for (Integer id : sBgSelectorSelectionMap.keySet()) { // 遍历背景选择器选中状态映射
findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); // 隐藏所有选中状态
}
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); // 设置头部视图背景
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); // 设置编辑器面板背景
mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, // 显示修改日期
mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR));
/**
* TODO: DateTimePicker
*
*/
showAlertHeader(); // 显示警报头
}
private void showAlertHeader() { // 显示警报头的方法
if (mWorkingNote.hasClockAlert()) { // 如果笔记有 clock 警报
long time = System.currentTimeMillis(); // 获取当前时间
if (time > mWorkingNote.getAlertDate()) { // 如果当前时间晚于警报设置时间
mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); // 更新文本为警报过期
} else {
mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( // 设置警报距离当前时间的相对时间
mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS));
}
mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); // 显示警报日期
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); // 显示警报图标
} else {
mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); // 隐藏警报日期
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); // 隐藏警报图标
}
}
@Override
protected void onNewIntent(Intent intent) { // 当有新的Intent传入时调用
super.onNewIntent(intent);
initActivityState(intent); // 初始化Activity状态
}
@Override
protected void onSaveInstanceState(Bundle outState) { // 当保存Instance状态时调用
super.onSaveInstanceState(outState);
/**
* IDID
* ID
*/
if (!mWorkingNote.existInDatabase()) {
saveNote(); // 保存笔记
}
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); // 保存笔记ID
Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); // 日志输出
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) { // 处理触摸事件
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE // 检查背景颜色选择器的可见性
&& !inRangeOfView(mNoteBgColorSelector, ev)) { // 如果触摸事件不在该视图范围内
mNoteBgColorSelector.setVisibility(View.GONE); // 隐藏背景选择器
return true; // 返回true表示事件已处理
}
if (mFontSizeSelector.getVisibility() == View.VISIBLE // 检查字体大小选择器的可见性
&& !inRangeOfView(mFontSizeSelector, ev)) { // 如果触摸事件不在该视图范围内
mFontSizeSelector.setVisibility(View.GONE); // 隐藏字体选择器
return true; // 返回true表示事件已处理
}
return super.dispatchTouchEvent(ev); // 调用父类方法处理其他触摸事件
}
private boolean inRangeOfView(View view, MotionEvent ev) { // 检查触摸事件是否在视图范围内
int []location = new int[2];
view.getLocationOnScreen(location); // 获取视图在屏幕上的位置
int x = location[0]; // 获取x坐标
int y = location[1]; // 获取y坐标
if (ev.getX() < x // 如果触摸点x坐标小于视图左上角x坐标
|| ev.getX() > (x + view.getWidth()) // 或者触摸点x坐标大于视图右下角x坐标
|| ev.getY() < y // 或者触摸点y坐标小于视图左上角y坐标
|| ev.getY() > (y + view.getHeight())) { // 或者触摸点y坐标大于视图右下角y坐标
return false; // 返回false表示不在范围内
}
return true; // 返回true表示在范围内
}
private void initResources() { // 初始化资源的方法
mHeadViewPanel = findViewById(R.id.note_title); // 查找头部视图
mNoteHeaderHolder = new HeadViewHolder(); // 初始化头部视图持有者
mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); // 查找修改日期文本视图
mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); // 查找警告图标
mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); // 查找警告日期文本视图
mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); // 查找设置背景颜色的图标按钮
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); // 设置背景颜色按钮的点击监听器
mNoteEditor = (EditText) findViewById(R.id.note_edit_view); // 查找笔记编辑器
mNoteEditorPanel = findViewById(R.id.sv_note_edit); // 查找笔记编辑器面板
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); // 查找笔记背景颜色选择视图
for (int id : sBgSelectorBtnsMap.keySet()) { // 遍历背景选择按钮映射
ImageView iv = (ImageView) findViewById(id); // 查找每个背景按钮
iv.setOnClickListener(this); // 设置每个按钮的点击监听器
}
mFontSizeSelector = findViewById(R.id.font_size_selector); // 查找字体大小选择视图
for (int id : sFontSizeBtnsMap.keySet()) { // 遍历字体大小按钮映射
View view = findViewById(id); // 查找每个字体按钮
view.setOnClickListener(this); // 设置每个按钮的点击监听器
};
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); // 获取默认共享偏好设置
mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); // 获取字体大小
/**
* HACKME: ID
* ID
* {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { // 如果字体大小超出范围
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; // 设置为默认字体大小
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); // 查找笔记编辑列表
}
@Override
protected void onPause() { // 当Activity暂停时调用
super.onPause();
if(saveNote()) { // 保存笔记
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); // 日志输出
}
clearSettingState(); // 清空设置状态
}
private void updateWidget() { // 更新小部件的方法
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); // 创建更新小部件的Intent
if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { // 如果小部件是2x类型
intent.setClass(this, NoteWidgetProvider_2x.class); // 设置类为2x小部件提供者
} else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { // 如果小部件是4x类型
intent.setClass(this, NoteWidgetProvider_4x.class); // 设置类为4x小部件提供者
} else {
Log.e(TAG, "Unspported widget type"); // 日志错误输出,表示不支持的小部件类型
return; // 返回
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { // 将小部件ID附加到Intent中
mWorkingNote.getWidgetId()
});
sendBroadcast(intent); // 发送广播以更新小部件
setResult(RESULT_OK, intent); // 设置返回结果为确定
}
public void onClick(View v) { // 点击事件处理
int id = v.getId(); // 获取点击视图的ID
if (id == R.id.btn_set_bg_color) { // 如果点击的是设置背景颜色按钮
mNoteBgColorSelector.setVisibility(View.VISIBLE); // 显示背景颜色选择器
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE); // 显示当前选中的背景颜色
} else if (sBgSelectorBtnsMap.containsKey(id)) { // 如果点击的是背景选择按钮
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE); // 隐藏当前选中的背景颜色
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); // 设置所选的背景颜色ID
mNoteBgColorSelector.setVisibility(View.GONE); // 隐藏背景颜色选择器
} else if (sFontSizeBtnsMap.containsKey(id)) { // 如果点击的是字体大小选择按钮
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); // 隐藏当前选中的字体大小
mFontSizeId = sFontSizeBtnsMap.get(id); // 设置所选的字体大小ID
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); // 更新共享偏好设置
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); // 显示新选中的字体大小
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { // 如果是检查列表模式
getWorkingText(); // 获取当前工作文本
switchToListMode(mWorkingNote.getContent()); // 切换到列表模式
} else {
mNoteEditor.setTextAppearance(this, // 更新笔记编辑器的文本外观
TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
}
mFontSizeSelector.setVisibility(View.GONE); // 隐藏字体大小选择器
}
}
@Override
public void onBackPressed() { // 当返回按钮按下时调用
if(clearSettingState()) { // 如果有设置状态需要清空
return; // 返回
}
saveNote(); // 保存笔记
super.onBackPressed(); // 调用父类返回方法
}
private boolean clearSettingState() { // 清空设置状态
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { // 如果背景颜色选择器可见
mNoteBgColorSelector.setVisibility(View.GONE); // 隐藏它
return true; // 返回true表示有状态被处理
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { // 如果字体大小选择器可见
mFontSizeSelector.setVisibility(View.GONE); // 隐藏它
return true; // 返回true表示有状态被处理
}
return false; // 返回false表示没有状态被处理
}
public void onBackgroundColorChanged() { // 背景颜色变化时更新视图
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE); // 显示新选中的背景颜色
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); // 更新编辑面板的背景颜色
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); // 更新头部视图的背景颜色
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) { // 准备选项菜单
if (isFinishing()) { // 如果Activity正在结束
return true; // 返回true表示不处理菜单
}
clearSettingState(); // 清空设置状态
menu.clear(); // 清空菜单
if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { // 如果笔记位于通话记录文件夹
getMenuInflater().inflate(R.menu.call_note_edit, menu); // 加载通话记录编辑菜单
} else {
getMenuInflater().inflate(R.menu.note_edit, menu); // 加载笔记编辑菜单
}
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { // 如果是检查列表模式
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); // 将菜单选项标题设置为正常模式
} else {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); // 将菜单选项标题设置为列表模式
}
if (mWorkingNote.hasClockAlert()) { // 如果有时钟提醒
menu.findItem(R.id.menu_alert).setVisible(false); // 隐藏提醒菜单项
} else {
menu.findItem(R.id.menu_delete_remind).setVisible(false); // 隐藏删除提醒菜单项
}
return true; // 返回true表示菜单准备成功
}
@Override
public boolean onOptionsItemSelected(MenuItem item) { // 处理菜单项选择
switch (item.getItemId()) { // 根据菜单项的ID执行相应操作
case R.id.menu_new_note: // 新建笔记菜单项
createNewNote(); // 创建新笔记
break;
case R.id.menu_delete: // 删除菜单项
AlertDialog.Builder builder = new AlertDialog.Builder(this); // 创建删除确认对话框构建器
builder.setTitle(getString(R.string.alert_title_delete)); // 设置对话框标题
builder.setIcon(android.R.drawable.ic_dialog_alert); // 设置对话框图标
builder.setMessage(getString(R.string.alert_message_delete_note)); // 设置对话框消息
builder.setPositiveButton(android.R.string.ok, // 设置确认按钮
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteCurrentNote(); // 删除当前笔记
finish(); // 结束Activity
}
});
builder.setNegativeButton(android.R.string.cancel, null); // 设置取消按钮
builder.show(); // 显示对话框
break;
case R.id.menu_font_size: // 字体大小菜单项
mFontSizeSelector.setVisibility(View.VISIBLE); // 显示字体大小选择器
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); // 显示当前选中的字体大小
break;
case R.id.menu_list_mode: // 切换到列表模式的菜单项
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0); // 切换检查列表模式
break;
case R.id.menu_share: // 分享菜单项
getWorkingText(); // 获取当前工作文本
sendTo(this, mWorkingNote.getContent()); // 发送文本到其他应用
break;
case R.id.menu_send_to_desktop: // 发送到桌面菜单项
sendToDesktop(); // 发送到桌面
break;
case R.id.menu_alert: // 设置提醒菜单项
setReminder(); // 设置提醒
break;
case R.id.menu_delete_remind: // 删除提醒菜单项
mWorkingNote.setAlertDate(0, false); // 清除警报日期
break;
default: // 默认情况
break;
}
return true; // 返回true表示菜单项处理成功
}
private void setReminder() { // 设置提醒的方法
DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); // 创建日期时间选择对话框
d.setOnDateTimeSetListener(new OnDateTimeSetListener() { // 设置对话框的日期时间设置监听器
public void OnDateTimeSet(AlertDialog dialog, long date) {
mWorkingNote.setAlertDate(date, true); // 设置笔记的警报日期
}
});
d.show(); // 显示对话框
}
/**
* {@link Intent#ACTION_SEND}{@text/plain}
*/
private void sendTo(Context context, String info) { // 发送文本到其他应用的方法
Intent intent = new Intent(Intent.ACTION_SEND); // 创建发送意图
intent.putExtra(Intent.EXTRA_TEXT, info); // 添加文本内容
intent.setType("text/plain"); // 设置Mime类型为纯文本
context.startActivity(intent); // 启动分享Activity
}
private void createNewNote() { // 创建新笔记的方法
// 首先保存当前正在编辑的笔记
saveNote(); // 保存笔记
// 为安全起见启动新的NoteEditActivity
finish(); // 结束当前Activity
Intent intent = new Intent(this, NoteEditActivity.class); // 创建新的Intent
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 设置动作为插入或编辑
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); // 传递当前文件夹ID
startActivity(intent); // 启动新Activity
}
private void deleteCurrentNote() { // 删除当前笔记的方法
if (mWorkingNote.existInDatabase()) { // 如果笔记在数据库中存在
HashSet<Long> ids = new HashSet<Long>(); // 创建ID集合
long id = mWorkingNote.getNoteId(); // 获取当前笔记ID
if (id != Notes.ID_ROOT_FOLDER) { // 如果ID不是根文件夹ID
ids.add(id); // 将ID添加到集合
} else {
Log.d(TAG, "Wrong note id, should not happen"); // 日志输出错误信息
}
if (!isSyncMode()) { // 如果不是同步模式
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { // 批量删除笔记
Log.e(TAG, "Delete Note error"); // 日志输出错误信息
}
} else { // 如果是同步模式
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { // 批量移动到回收站
Log.e(TAG, "Move notes to trash folder error, should not happens"); // 日志输出错误信息
}
}
}
mWorkingNote.markDeleted(true); // 标记笔记为已删除
}
private boolean isSyncMode() { // 判断是否是同步模式的方法
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; // 如果同步账户名称不为空则返回true
}
public void onClockAlertChanged(long date, boolean set) { // 时钟提醒变化时调用的方法
/**
*
*
*/
if (!mWorkingNote.existInDatabase()) { // 如果笔记不存在于数据库中
saveNote(); // 保存笔记
}
if (mWorkingNote.getNoteId() > 0) { // 如果笔记ID有效
Intent intent = new Intent(this, AlarmReceiver.class); // 创建闹钟接收器的Intent
intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); // 设置数据URI
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); // 创建PendingIntent
AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); // 获取AlarmManager实例
showAlertHeader(); // 显示警报头
if (!set) { // 如果不设置警报
alarmManager.cancel(pendingIntent); // 取消警报
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); // 设置警报
}
} else { // 如果当前笔记没有ID
/**
* ID
*/
Log.e(TAG, "Clock alert setting error"); // 日志输出错误信息
showToast(R.string.error_note_empty_for_clock); // 显示错误提示
}
}
public void onWidgetChanged() { // 当小部件变化时调用
updateWidget(); // 更新小部件
}
public void onEditTextDelete(int index, String text) { // 处理编辑文本删除的事件
int childCount = mEditTextList.getChildCount(); // 获取子视图数量
if (childCount == 1) { // 如果只有一个子视图
return; // 返回
}
for (int i = index + 1; i < childCount; i++) { // 从删除的索引开始遍历
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) // 更新List中的索引
.setIndex(i - 1);
}
mEditTextList.removeViewAt(index); // 移除指定索引的子视图
NoteEditText edit = null; // 初始化家编辑文本
if(index == 0) { // 如果删除的是第一个视图
edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(
R.id.et_edit_text);
} else {
edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById(
R.id.et_edit_text);
}
int length = edit.length(); // 获取被选中文字的长度
edit.append(text); // 在文本框中追加文本
edit.requestFocus(); // 请求焦点
edit.setSelection(length); // 设置光标位置到文本末尾
}
public void onEditTextEnter(int index, String text) { // 处理编辑文本输入的事件
/**
*
*/
if (index > mEditTextList.getChildCount()) { // 如果索引超出边界
Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); // 日志输出错误信息
}
View view = getListItem(text, index); // 获取新的列表项视图
mEditTextList.addView(view, index); // 在指定索引添加新视图
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); // 查找新的NoteEditText
edit.requestFocus(); // 请求焦点
edit.setSelection(0); // 设置光标位置到文本开始处
for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { // 遍历后续子视图
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) // 更新前面的索引
.setIndex(i);
}
}
private void switchToListMode(String text) { // 切换到列表模式并更新内容
mEditTextList.removeAllViews(); // 移除所有子视图
String[] items = text.split("\n"); // 按行分割笔记内容
int index = 0; // 索引初始化
for (String item : items) { // 遍历每一行
if (!TextUtils.isEmpty(item)) { // 如果行不为空
mEditTextList.addView(getListItem(item, index), index); // 将新的项目添加到列表
index++; // 增加索引
}
}
mEditTextList.addView(getListItem("", index)); // 添加空项
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); // 请求光标焦点到新添加的文本框
mNoteEditor.setVisibility(View.GONE); // 隐藏笔记编辑器
mEditTextList.setVisibility(View.VISIBLE); // 显示编辑文本列表
}
private Spannable getHighlightQueryResult(String fullText, String userQuery) { // 获取高亮结果的方法
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); // 创建可变化文本
if (!TextUtils.isEmpty(userQuery)) { // 如果用户查询不为空
mPattern = Pattern.compile(userQuery); // 编译用户查询为正则表达式
Matcher m = mPattern.matcher(fullText); // 创建匹配器
int start = 0; // 起始位置初始化
while (m.find(start)) { // 查找匹配项
spannable.setSpan( // 设置高亮背景
new BackgroundColorSpan(this.getResources().getColor(
R.color.user_query_highlight)), m.start(), m.end(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
start = m.end(); // 更新起始位置
}
}
return spannable; // 返回高亮信息
}
private View getListItem(String item, int index) { // 获取列表项视图的方法
View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); // 加载列表项布局
final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); // 查找文本编辑视图
edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); // 设置文本外观
CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); // 查找复选框视图
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { // 设置复选框勾选变化监听器
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // 勾选变化时调用
if (isChecked) { // 如果被勾选
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); // 在文本上添加划线
} else { // 如果未勾选
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); // 去除划线
}
}
});
// 根据是否已勾选更新复选框和文本
if (item.startsWith(TAG_CHECKED)) { // 如果以已完成标记开头
cb.setChecked(true); // 设置复选框为勾选
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); // 添加划线
item = item.substring(TAG_CHECKED.length(), item.length()).trim(); // 移除标记
} else if (item.startsWith(TAG_UNCHECKED)) { // 如果以未完成标记开头
cb.setChecked(false); // 设置复选框为未勾选
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); // 去除划线
item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); // 移除标记
}
edit.setOnTextViewChangeListener(this); // 设置文本变化监听器
edit.setIndex(index); // 设置索引
edit.setText(getHighlightQueryResult(item, mUserQuery)); // 设置高亮显示的文本
return view; // 返回视图
}
public void onTextChange(int index, boolean hasText) { // 文本变化时调用
if (index >= mEditTextList.getChildCount()) { // 如果索引超出范围
Log.e(TAG, "Wrong index, should not happen"); // 日志输出错误
return; // 返回
}
if (hasText) { // 如果有文本
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); // 显示复选框
} else { // 如果没有文本
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); // 隐藏复选框
}
}
public void onCheckListModeChanged(int oldMode, int newMode) { // 检查列表模式变化时调用
if (newMode == TextNote.MODE_CHECK_LIST) { // 如果新模式是检查列表
switchToListMode(mNoteEditor.getText().toString()); // 切换到列表模式
} else { // 否则
if (!getWorkingText()) { // 获取当前工作文本,并检查文本是否存在
mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", "")); // 设置工作文本
}
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); // 设置高亮显示文本
mEditTextList.setVisibility(View.GONE); // 隐藏编辑文本列表
mNoteEditor.setVisibility(View.VISIBLE); // 显示笔记编辑器
}
}
private boolean getWorkingText() { // 获取当前工作文本并更新状态
boolean hasChecked = false; // 标记是否有勾选
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { // 如果是检查列表模式
StringBuilder sb = new StringBuilder(); // 创建字符串构建器
for (int i = 0; i < mEditTextList.getChildCount(); i++) { // 循环获取每个子视图
View view = mEditTextList.getChildAt(i); // 获取子视图
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); // 获取文本编辑视图
if (!TextUtils.isEmpty(edit.getText())) { // 如果文本不为空
if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { // 如果复选框已勾选
sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); // 添加勾选标记和文本
hasChecked = true; // 更新已勾选标记
} else {
sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); // 添加未勾选标记和文本
}
}
}
mWorkingNote.setWorkingText(sb.toString()); // 设置工作文本
} else {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); // 设置工作文本为笔记编辑器文本
}
return hasChecked; // 返回是否有勾选
}
private boolean saveNote() { // 保存笔记的方法
getWorkingText(); // 获取当前工作文本
boolean saved = mWorkingNote.saveNote(); // 尝试保存笔记
if (saved) { // 如果保存成功
/**
*
* /
*
* RESULT_OK/
*/
setResult(RESULT_OK); // 设置结果为成功
}
return saved; // 返回保存状态
}
private void sendToDesktop() { // 发送到桌面的方法
/**
*
*
*/
if (!mWorkingNote.existInDatabase()) { // 如果笔记在数据库中不存在
saveNote(); // 保存笔记
}
if (mWorkingNote.getNoteId() > 0) { // 如果笔记ID有效
Intent sender = new Intent(); // 创建发送意图
Intent shortcutIntent = new Intent(this, NoteEditActivity.class); // 创建快捷方式意图
shortcutIntent.setAction(Intent.ACTION_VIEW); // 设置快捷方式的操作为查看
shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); // 添加笔记ID到意图
sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); // 添加快捷方式目标意图
sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, // 添加快捷方式名称
makeShortcutIconTitle(mWorkingNote.getContent()));
sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, // 添加快捷方式图标资源
Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app));
sender.putExtra("duplicate", true); // 设置重复属性为true
sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); // 设置操作为安装快捷方式
showToast(R.string.info_note_enter_desktop); // 显示信息提示
sendBroadcast(sender); // 发送广播以安装快捷方式
} else { // 如果笔记不存在(例如,新笔记没有内容)
/**
* ID
*
*/
Log.e(TAG, "Send to desktop error"); // 日志输出错误信息
showToast(R.string.error_note_empty_for_send_to_desktop); // 显示错误提示
}
}
private String makeShortcutIconTitle(String content) { // 制作快捷方式图标标题的方法
content = content.replace(TAG_CHECKED, ""); // 移除已勾选标记
content = content.replace(TAG_UNCHECKED, ""); // 移除未勾选标记
return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, // 如果内容超过最大长度,则截断
SHORTCUT_ICON_TITLE_MAX_LEN) : content;
}
private void showToast(int resId) { // 显示提示消息的方法
showToast(resId, Toast.LENGTH_SHORT); // 默认短时间显示
}
private void showToast(int resId, int duration) { // 自定义显示提示消息的方法
Toast.makeText(this, resId, duration).show(); // 显示Toast消息
}
}

@ -0,0 +1,214 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui; // 定义包名
import android.content.Context; // 导入Context类
import android.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); // 邮件链接
}
/**
* {@link NoteEditActivity}
*/
public interface OnTextViewChangeListener { // 文本视图变化监听器接口
/**
* {@link KeyEvent#KEYCODE_DEL}
*/
void onEditTextDelete(int index, String text); // 删除事件
/**
* {@link KeyEvent#KEYCODE_ENTER}
*/
void onEditTextEnter(int index, String text); // 添加事件
/**
*
*/
void onTextChange(int index, boolean hasText); // 文本变化事件
}
private OnTextViewChangeListener mOnTextViewChangeListener; // 文本视图变化监听器
public NoteEditText(Context context) { // 构造函数
super(context, null); // 调用父类构造函数
mIndex = 0; // 初始化索引为0
}
public void setIndex(int index) { // 设置索引
mIndex = index; // 更新索引
}
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, 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(); // 获取触摸点的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); // 调用父类的触摸事件处理
}
@Override
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(); // 保存删除前的选择起始位置
break;
default:
break;
}
return super.onKeyDown(keyCode, event); // 调用父类的按键处理
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) { // 按键松开处理
switch(keyCode) { // 根据按键代码进行处理
case KeyEvent.KEYCODE_DEL: // 删除键
if (mOnTextViewChangeListener != null) { // 如果设置了监听器
if (0 == mSelectionStartBeforeDelete && mIndex != 0) { // 如果删除前选择位置为0且当前索引不为0
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); // 通知删除
return true; // 返回true表示事件已处理
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted"); // 日志输出
}
break;
case KeyEvent.KEYCODE_ENTER: // 回车键
if (mOnTextViewChangeListener != null) { // 如果设置了监听器
int selectionStart = getSelectionStart(); // 获取当前选择位置
String text = getText().subSequence(selectionStart, length()).toString(); // 获取当前输入文本
setText(getText().subSequence(0, selectionStart)); // 更新文本,去掉输入的部分
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); // 通知输入
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted"); // 日志输出
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event); // 调用父类的按键处理
}
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { // 焦点变化时调用
if (mOnTextViewChangeListener != null) { // 如果设置了监听器
if (!focused && TextUtils.isEmpty(getText())) { // 如果失去焦点且文本为空
mOnTextViewChangeListener.onTextChange(mIndex, false); // 通知文本变化为无文本
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true); // 通知文本变化为有文本
}
}
super.onFocusChanged(focused, direction, previouslyFocusedRect); // 调用父类的焦点变化处理
}
@Override
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; // 设置为其他链接
}
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( // 添加菜单项
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) { // 菜单项点击事件
// 触发网址点击的意图
urls[0].onClick(NoteEditText.this); // 当点击URL时调用其onClick方法
return true; // 返回true表示事件已处理
}
});
}
}
super.onCreateContextMenu(menu); // 调用父类的上下文菜单创建处理
}
}

@ -0,0 +1,226 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui; // 定义包名
import android.content.Context; // 导入Context类
import android.database.Cursor; // 导入Cursor类
import android.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; // 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); // 从游标中获取笔记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); // 从游标中获取小部件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) { // 如果名称还是为null
mName = ""; // 设置为空字符串
}
checkPostion(cursor); // 检查位置信息
}
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 (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
if (cursor.getCount() > (position + 1)) { // 如果游标数量大于当前位置加1
mIsMultiNotesFollowingFolder = true; // 模式是多个笔记在文件夹下
} else {
mIsOneNoteFollowingFolder = true; // 模式是唯一笔记在文件夹下
}
}
if (!cursor.moveToNext()) { // 移动回当前游标位置失败
throw new IllegalStateException("cursor move to previous but can't move back"); // 抛出异常
}
}
}
}
public boolean isOneFollowingFolder() { // 判断是否为唯一笔记在文件夹下
return mIsOneNoteFollowingFolder; // 返回结果
}
public boolean isMultiFollowingFolder() { // 判断是否有多个笔记在文件夹下
return mIsMultiNotesFollowingFolder; // 返回结果
}
public boolean isLast() { // 判断是否为最后一项
return mIsLastItem; // 返回结果
}
public String getCallName() { // 获取通话记录联系人名称
return mName; // 返回联系人名称
}
public boolean isFirst() { // 判断是否为第一项
return mIsFirstItem; // 返回结果
}
public boolean isSingle() { // 判断是否为唯一一项
return mIsOnlyOneItem; // 返回结果
}
public long getId() { // 获取笔记ID
return mId; // 返回笔记ID
}
public long getAlertDate() { // 获取警报日期
return mAlertDate; // 返回警报日期
}
public long getCreatedDate() { // 获取创建日期
return mCreatedDate; // 返回创建日期
}
public boolean hasAttachment() { // 判断是否有附件
return mHasAttachment; // 返回结果
}
public long getModifiedDate() { // 获取修改日期
return mModifiedDate; // 返回修改日期
}
public int getBgColorId() { // 获取背景颜色ID
return mBgColorId; // 返回背景颜色ID
}
public long getParentId() { // 获取父级ID
return mParentId; // 返回父级ID
}
public int getNotesCount() { // 获取笔记数量
return mNotesCount; // 返回笔记数量
}
public long getFolderId () { // 获取文件夹ID
return mParentId; // 返回父级ID
}
public int getType() { // 获取笔记类型
return mType; // 返回笔记类型
}
public int getWidgetType() { // 获取小部件类型
return mWidgetType; // 返回小部件类型
}
public int getWidgetId() { // 获取小部件ID
return mWidgetId; // 返回小部件ID
}
public String getSnippet() { // 获取摘要
return mSnippet; // 返回摘要
}
public boolean hasAlert() { // 判断是否有警报
return (mAlertDate > 0); // 根据警报日期判断
}
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); // 返回笔记类型
}
}

@ -0,0 +1,949 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui; // 定义包名
import android.app.Activity; // 导入Activity类
import android.app.AlertDialog; // 导入AlertDialog类
import android.app.Dialog; // 导入Dialog类
import android.appwidget.AppWidgetManager; // 导入AppWidgetManager类
import android.content.AsyncQueryHandler; // 导入AsyncQueryHandler类
import android.content.ContentResolver; // 导入ContentResolver类
import android.content.ContentValues; // 导入ContentValues类
import android.content.Context; // 导入Context类
import android.content.DialogInterface; // 导入DialogInterface类
import android.content.Intent; // 导入Intent类
import android.content.SharedPreferences; // 导入SharedPreferences类
import android.database.Cursor; // 导入Cursor类
import android.os.AsyncTask; // 导入AsyncTask类
import android.os.Bundle; // 导入Bundle类
import android.preference.PreferenceManager; // 导入PreferenceManager类
import android.text.Editable; // 导入Editable接口
import android.text.TextUtils; // 导入TextUtils类
import android.text.TextWatcher; // 导入TextWatcher接口
import android.util.Log; // 导入Log类
import android.view.ActionMode; // 导入ActionMode类
import android.view.ContextMenu; // 导入ContextMenu类
import android.view.ContextMenu.ContextMenuInfo; // 导入上下文菜单信息类
import android.view.Display; // 导入Display类
import android.view.LayoutInflater; // 导入LayoutInflater类
import android.view.Menu; // 导入Menu类
import android.view.MenuItem; // 导入MenuItem类
import android.view.MenuItem.OnMenuItemClickListener; // 导入菜单项点击监听器接口
import android.view.MotionEvent; // 导入MotionEvent类
import android.view.View; // 导入View类
import android.view.View.OnClickListener; // 导入视图点击监听器接口
import android.view.View.OnCreateContextMenuListener; // 导入创建上下文菜单监听器接口
import android.view.View.OnTouchListener; // 导入触摸监听器接口
import android.view.inputmethod.InputMethodManager; // 导入输入法管理器类
import android.widget.AdapterView; // 导入AdapterView类
import android.widget.AdapterView.OnItemClickListener; // 导入适配器视图项目点击监听器接口
import android.widget.AdapterView.OnItemLongClickListener; // 导入适配器视图项目长按监听器接口
import android.widget.Button; // 导入Button类
import android.widget.EditText; // 导入EditText类
import android.widget.ListView; // 导入ListView类
import android.widget.PopupMenu; // 导入PopupMenu类
import android.widget.TextView; // 导入TextView类
import android.widget.Toast; // 导入Toast类
import net.micode.notes.R; // 导入资源类
import net.micode.notes.data.Notes; // 导入Notes数据类
import net.micode.notes.data.Notes.NoteColumns; // 导入Notes列类
import net.micode.notes.gtask.remote.GTaskSyncService; // 导入GTaskSyncService类
import net.micode.notes.model.WorkingNote; // 导入WorkingNote模型类
import net.micode.notes.tool.BackupUtils; // 导入BackupUtils工具类
import net.micode.notes.tool.DataUtils; // 导入DataUtils工具类
import net.micode.notes.tool.ResourceParser; // 导入ResourceParser解析工具类
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; // 导入小部件属性类
import net.micode.notes.widget.NoteWidgetProvider_2x; // 导入2x笔记小部件提供者
import net.micode.notes.widget.NoteWidgetProvider_4x; // 导入4x笔记小部件提供者
import java.io.BufferedReader; // 导入BufferedReader类
import java.io.IOException; // 导入IOException类
import java.io.InputStream; // 导入InputStream类
import java.io.InputStreamReader; // 导入InputStreamReader类
import java.util.HashSet; // 导入HashSet类
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { // 定义NotesListActivity类继承Activity并实现点击和长按监听接口
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; // 文件夹笔记列表查询令牌
private static final int FOLDER_LIST_QUERY_TOKEN = 1; // 文件夹列表查询令牌
private static final int MENU_FOLDER_DELETE = 0; // 文件夹删除菜单项ID
private static final int MENU_FOLDER_VIEW = 1; // 文件夹查看菜单项ID
private static final int MENU_FOLDER_CHANGE_NAME = 2; // 文件夹更名菜单项ID
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; // 添加介绍的偏好设置键
private enum ListEditState { // 列表编辑状态枚举
NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER // 状态: 笔记列表、子文件夹、通话记录文件夹
};
private ListEditState mState; // 当前状态
private BackgroundQueryHandler mBackgroundQueryHandler; // 后台查询处理类
private NotesListAdapter mNotesListAdapter; // 笔记列表适配器
private ListView mNotesListView; // 笔记列表视图
private Button mAddNewNote; // 新增笔记按钮
private boolean mDispatch; // 是否分发事件的标志
private int mOriginY; // 事件起始Y坐标
private int mDispatchY; // 分发事件的Y坐标
private TextView mTitleBar; // 标题栏文本视图
private long mCurrentFolderId; // 当前文件夹ID
private ContentResolver mContentResolver; // 内容解析器
private ModeCallback mModeCallBack; // 模式回调
private static final String TAG = "NotesListActivity"; // 日志标签
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; // 笔记列表滚动速率
private NoteItemData mFocusNoteDataItem; // 当前聚焦的笔记数据项
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; // 正常查询条件
private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>"
+ Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR ("
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND "
+ NoteColumns.NOTES_COUNT + ">0)"; // 根文件夹查询条件
private final static int REQUEST_CODE_OPEN_NODE = 102; // 打开节点请求代码
private final static int REQUEST_CODE_NEW_NODE = 103; // 新增节点请求代码
@Override
protected void onCreate(Bundle savedInstanceState) { // 当Activity创建时调用
super.onCreate(savedInstanceState); // 调用父类的onCreate方法
setContentView(R.layout.note_list); // 设置内容视图为笔记列表布局
initResources(); // 初始化资源
/**
* 使
*/
setAppInfoFromRawRes(); // 从原始资源中设置应用信息
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { // 当Activity的结果回调时调用
if (resultCode == RESULT_OK
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { // 如果结果成功且请求码是打开节点或新建节点
mNotesListAdapter.changeCursor(null); // 更新适配器的光标
} else {
super.onActivityResult(requestCode, resultCode, data); // 调用父类的onActivityResult方法
}
}
private void setAppInfoFromRawRes() { // 从原始资源设置应用信息
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); // 获取默认的共享偏好设置
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { // 检查是否已经添加介绍
StringBuilder sb = new StringBuilder(); // 创建字符串构建器
InputStream in = null; // 初始化输入流
try {
in = getResources().openRawResource(R.raw.introduction); // 打开原始资源文件
if (in != null) {
InputStreamReader isr = new InputStreamReader(in); // 输入流读取器
BufferedReader br = new BufferedReader(isr); // 缓冲读取器
char [] buf = new char[1024]; // 创建字符缓冲区
int len = 0; // 字符长度初始化
while ((len = br.read(buf)) > 0) { // 逐行读取
sb.append(buf, 0, len); // 将读取的内容追加到字符串构建器
}
} else {
Log.e(TAG, "Read introduction file error"); // 日志输出读取介绍文件错误
return; // 返回
}
} catch (IOException e) { // 捕获IO异常
e.printStackTrace(); // 打印异常
return; // 返回
} finally { // 在finally中确保关闭输入流
if(in != null) { // 如果输入流不为空
try {
in.close(); // 关闭输入流
} catch (IOException e) { // 捕获关闭过程中出现的IO异常
e.printStackTrace(); // 打印异常
}
}
}
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, // 创建空笔记
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
ResourceParser.RED); // 使用红色背景
note.setWorkingText(sb.toString()); // 设置工作文本为读取的介绍内容
if (note.saveNote()) { // 如果保存成功
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); // 更新共享偏好设置为已添加介绍
} else {
Log.e(TAG, "Save introduction note error"); // 日志输出保存介绍笔记错误
return; // 返回
}
}
}
@Override
protected void onStart() { // 当Activity启动时调用
super.onStart(); // 调用父类的onStart方法
startAsyncNotesListQuery(); // 启动异步笔记列表查询
}
private void initResources() { // 初始化资源的方法
mContentResolver = this.getContentResolver(); // 获取内容解析器
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); // 初始化后台查询处理器
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 初始化当前文件夹ID为根文件夹ID
mNotesListView = (ListView) findViewById(R.id.notes_list); // 查找笔记列表视图
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), // 添加页脚视图
null, false);
mNotesListView.setOnItemClickListener(new OnListItemClickListener()); // 设置列表项点击监听器
mNotesListView.setOnItemLongClickListener(this); // 设置列表项长按监听器
mNotesListAdapter = new NotesListAdapter(this); // 创建笔记列表适配器
mNotesListView.setAdapter(mNotesListAdapter); // 将适配器设置到列表视图
mAddNewNote = (Button) findViewById(R.id.btn_new_note); // 查找新增笔记按钮
mAddNewNote.setOnClickListener(this); // 设置新增笔记按钮的点击监听器
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); // 设置新增笔记按钮的触摸监听器
mDispatch = false; // 初始化事件分发标志
mDispatchY = 0; // 初始化Y坐标
mOriginY = 0; // 初始化起始Y坐标
mTitleBar = (TextView) findViewById(R.id.tv_title_bar); // 查找标题栏文本视图
mState = ListEditState.NOTE_LIST; // 初始化为笔记列表状态
mModeCallBack = new ModeCallback(); // 初始化模式回调
}
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { // 定义模式回调类,处理多选择模式和菜单项点击
private DropdownMenu mDropDownMenu; // 下拉菜单
private ActionMode mActionMode; // 动作模式
private MenuItem mMoveMenu; // 移动菜单项
public boolean onCreateActionMode(ActionMode mode, Menu menu) { // 创建动作模式时调用
getMenuInflater().inflate(R.menu.note_list_options, menu); // 加载菜单
menu.findItem(R.id.delete).setOnMenuItemClickListener(this); // 设置删除菜单项的点击监听器
mMoveMenu = menu.findItem(R.id.move); // 查找移动菜单项
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER // 判断当前笔记的父ID是否为通话记录文件夹
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
mMoveMenu.setVisible(false); // 若是,则隐藏移动菜单项
} else {
mMoveMenu.setVisible(true); // 否则,显示移动菜单项
mMoveMenu.setOnMenuItemClickListener(this); // 设置移动菜单项的点击监听器
}
mActionMode = mode; // 设置当前动作模式
mNotesListAdapter.setChoiceMode(true); // 启用适配器的选择模式
mNotesListView.setLongClickable(false); // 禁用列表长按
mAddNewNote.setVisibility(View.GONE); // 隐藏新增笔记按钮
View customView = LayoutInflater.from(NotesListActivity.this).inflate( // 创建自定义视图
R.layout.note_list_dropdown_menu, null);
mode.setCustomView(customView); // 设置自定义视图
mDropDownMenu = new DropdownMenu(NotesListActivity.this, // 创建下拉菜单
(Button) customView.findViewById(R.id.selection_menu),
R.menu.note_list_dropdown);
mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ // 设置下拉菜单项点击监听器
public boolean onMenuItemClick(MenuItem item) {
mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); // 切换全选状态
updateMenu(); // 更新菜单
return true; // 返回true表示事件已处理
}
});
return true; // 返回true表示创建成功
}
private void updateMenu() { // 更新菜单的方法
int selectedCount = mNotesListAdapter.getSelectedCount(); // 获取选中项数量
// 更新下拉菜单
String format = getResources().getString(R.string.menu_select_title, selectedCount); // 格式化标题
mDropDownMenu.setTitle(format); // 设置下拉菜单标题
MenuItem item = mDropDownMenu.findItem(R.id.action_select_all); // 查找全选菜单项
if (item != null) {
if (mNotesListAdapter.isAllSelected()) { // 如果全选状态为真
item.setChecked(true); // 设置为已选中
item.setTitle(R.string.menu_deselect_all); // 更新标题为取消全选
} else {
item.setChecked(false); // 否则设置为未选中
item.setTitle(R.string.menu_select_all); // 更新标题为全选
}
}
}
public boolean onPrepareActionMode(ActionMode mode, Menu menu) { // 准备动作模式时调用
return false; // 默认返回false表示不改变菜单
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { // 动作模式中菜单项点击事件
return false; // 默认返回false
}
public void onDestroyActionMode(ActionMode mode) { // 动作模式销毁时调用
mNotesListAdapter.setChoiceMode(false); // 关闭适配器选择模式
mNotesListView.setLongClickable(true); // 启用长按
mAddNewNote.setVisibility(View.VISIBLE); // 显示新增笔记按钮
}
public void finishActionMode() { // 结束动作模式
mActionMode.finish(); // 结束当前动作模式
}
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { // 选中状态变化时调用
mNotesListAdapter.setCheckedItem(position, checked); // 更新选中项状态
updateMenu(); // 更新菜单
}
public boolean onMenuItemClick(MenuItem item) { // 菜单项点击事件
if (mNotesListAdapter.getSelectedCount() == 0) { // 如果没有选中项
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), // 显示提示
Toast.LENGTH_SHORT).show();
return true; // 返回true表示事件已处理
}
switch (item.getItemId()) { // 根据菜单项ID处理事件
case R.id.delete: // 删除菜单项
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); // 创建删除确认对话框
builder.setTitle(getString(R.string.alert_title_delete)); // 设置对话框标题
builder.setIcon(android.R.drawable.ic_dialog_alert); // 设置对话框图标
builder.setMessage(getString(R.string.alert_message_delete_notes, // 设置对话框消息
mNotesListAdapter.getSelectedCount()));
builder.setPositiveButton(android.R.string.ok, // 点击确认按钮时的事件
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
batchDelete(); // 批量删除选中的笔记
}
});
builder.setNegativeButton(android.R.string.cancel, null); // 设置取消按钮
builder.show(); // 显示对话框
break;
case R.id.move: // 移动菜单项
startQueryDestinationFolders(); // 查询可移动的文件夹
break;
default:
return false; // 默认返回false表示未处理
}
return true; // 返回true表示菜单项已处理
}
}
private class NewNoteOnTouchListener implements OnTouchListener { // 新增笔记按钮的触摸监听器
public boolean onTouch(View v, MotionEvent event) { // 触摸事件处理
switch (event.getAction()) { // 根据触摸事件类型进行处理
case MotionEvent.ACTION_DOWN: { // 按下事件
Display display = getWindowManager().getDefaultDisplay(); // 获取窗口显示
int screenHeight = display.getHeight(); // 获取屏幕高度
int newNoteViewHeight = mAddNewNote.getHeight(); // 获取新增笔记按钮的高度
int start = screenHeight - newNoteViewHeight; // 计算按钮起始位置
int eventY = start + (int) event.getY(); // 事件Y坐标
/**
*
*/
if (mState == ListEditState.SUB_FOLDER) { // 如果当前状态为子文件夹
eventY -= mTitleBar.getHeight(); // 在Y坐标上减去标题栏高度
start -= mTitleBar.getHeight();
}
/**
* HACKME:
* y=-0.12x+94(
* :)线
* 94
*
* UI
*/
if (event.getY() < (event.getX() * (-0.12) + 94)) { // 如果触摸点在透明区域
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1 // 获取列表最后一个子视图
- mNotesListView.getFooterViewsCount());
if (view != null && view.getBottom() > start // 检查触摸点是否在列表最底部的范围内
&& (view.getTop() < (start + 94))) {
mOriginY = (int) event.getY(); // 记录触摸事件的Y坐标
mDispatchY = eventY; // 更新分发Y坐标
event.setLocation(event.getX(), mDispatchY); // 设置触摸事件位置
mDispatch = true; // 将分发标志设置为true
return mNotesListView.dispatchTouchEvent(event); // 将事件分发给列表视图
}
}
break;
}
case MotionEvent.ACTION_MOVE: { // 移动事件
if (mDispatch) { // 如果分发标志为true
mDispatchY += (int) event.getY() - mOriginY; // 更新分发Y坐标
event.setLocation(event.getX(), mDispatchY); // 设置触摸事件位置
return mNotesListView.dispatchTouchEvent(event); // 将事件分发给列表视图
}
break;
}
default: { // 默认事件处理
if (mDispatch) { // 如果分发标志为true
event.setLocation(event.getX(), mDispatchY); // 设置触摸事件位置
mDispatch = false; // 重置分发标志
return mNotesListView.dispatchTouchEvent(event); // 将事件分发给列表视图
}
break;
}
}
return false; // 返回false表示未处理
}
};
private void startAsyncNotesListQuery() { // 启动异步笔记列表查询的方法
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION // 根据当前文件夹ID选择查询条件
: NORMAL_SELECTION;
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, // 启动查询
null,
Notes.CONTENT_NOTE_URI, // 查询URI
NoteItemData.PROJECTION, // 查询的列
selection, // 查询条件
new String[] { // 查询参数
String.valueOf(mCurrentFolderId)
},
NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); // 排序条件
}
private final class BackgroundQueryHandler extends AsyncQueryHandler { // 定义后台查询处理器类
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver); // 调用父类构造函数
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) { // 查询完成时调用
switch (token) { // 根据查询令牌处理
case FOLDER_NOTE_LIST_QUERY_TOKEN:
mNotesListAdapter.changeCursor(cursor); // 更新适配器的光标
break;
case FOLDER_LIST_QUERY_TOKEN:
if (cursor != null && cursor.getCount() > 0) { // 如果游标不为空且有结果
showFolderListMenu(cursor); // 显示文件夹列表菜单
} else {
Log.e(TAG, "Query folder failed"); // 日志错误输出
}
break;
default:
return; // 默认返回
}
}
}
private void showFolderListMenu(Cursor cursor) { // 显示文件夹列表菜单的方法
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); // 创建对话框构建器
builder.setTitle(R.string.menu_title_select_folder); // 设置对话框标题
final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor); // 创建文件夹列表适配器
builder.setAdapter(adapter, new DialogInterface.OnClickListener() { // 设置适配器和点击监听器
public void onClick(DialogInterface dialog, int which) { // 对话框项目点击事件
DataUtils.batchMoveToFolder(mContentResolver, // 批量移动已选笔记到目标文件夹
mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which));
Toast.makeText( // 显示成功提示
NotesListActivity.this,
getString(R.string.format_move_notes_to_folder,
mNotesListAdapter.getSelectedCount(),
adapter.getFolderName(NotesListActivity.this, which)),
Toast.LENGTH_SHORT).show();
mModeCallBack.finishActionMode(); // 结束模式回调
}
});
builder.show(); // 显示对话框
}
private void createNewNote() { // 创建新笔记的方法
Intent intent = new Intent(this, NoteEditActivity.class); // 创建意图
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 设置操作为插入或编辑
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); // 添加当前文件夹ID到意图
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); // 启动新Activity并请求结果
}
private void batchDelete() { // 批量删除的方法
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() { // 创建异步任务
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) { // 背景线程执行的任务
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget(); // 获取已选中的小部件
if (!isSyncMode()) { // 如果不在同步模式
// 不在同步模式时,直接删除笔记
if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
.getSelectedItemIds())) {
} else {
Log.e(TAG, "Delete Note error"); // 日志输出删除笔记错误
}
} else { // 在同步模式
// 在同步模式下,将删除的笔记移动到回收站文件夹
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLER)) { // 移动到回收站
Log.e(TAG, "Move notes to trash folder error, should not happens"); // 日志输出错误信息
}
}
return widgets; // 返回小部件集合
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) { // 任务完成后调用
if (widgets != null) { // 如果小部件不为空
for (AppWidgetAttribute widget : widgets) { // 遍历小部件集合
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID // 检查小部件ID是否有效
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { // 检查小部件类型是否有效
updateWidget(widget.widgetId, widget.widgetType); // 更新小部件
}
}
}
mModeCallBack.finishActionMode(); // 结束模式回调
}
}.execute(); // 执行异步任务
}
private void deleteFolder(long folderId) { // 删除指定文件夹的方法
if (folderId == Notes.ID_ROOT_FOLDER) { // 如果是根文件夹ID
Log.e(TAG, "Wrong folder id, should not happen " + folderId); // 日志错误输出
return; // 返回
}
HashSet<Long> ids = new HashSet<Long>(); // 创建ID集合
ids.add(folderId); // 将文件夹ID添加到集合
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver,
folderId); // 获取该文件夹相关的小部件
if (!isSyncMode()) { // 如果不在同步模式
// 如果不在同步模式,直接删除文件夹
DataUtils.batchDeleteNotes(mContentResolver, ids); // 批量删除笔记
} else { // 在同步模式
// 在同步模式下,将删除的文件夹移动到回收站
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
}
if (widgets != null) { // 如果小部件不为空
for (AppWidgetAttribute widget : widgets) { // 遍历小部件集合
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID // 检查小部件ID是否有效
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { // 检查小部件类型是否有效
updateWidget(widget.widgetId, widget.widgetType); // 更新小部件
}
}
}
}
private void openNode(NoteItemData data) { // 打开指定笔记的方法
Intent intent = new Intent(this, NoteEditActivity.class); // 创建打开笔记的意图
intent.setAction(Intent.ACTION_VIEW); // 设置操作为查看
intent.putExtra(Intent.EXTRA_UID, data.getId()); // 添加笔记ID到意图
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); // 启动笔记编辑Activity并请求结果
}
private void openFolder(NoteItemData data) { // 打开指定文件夹的方法
mCurrentFolderId = data.getId(); // 设置当前文件夹ID
startAsyncNotesListQuery(); // 启动异步笔记列表查询
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { // 如果是通话记录文件夹
mState = ListEditState.CALL_RECORD_FOLDER; // 更新状态为通话记录
mAddNewNote.setVisibility(View.GONE); // 隐藏新增笔记按钮
} else {
mState = ListEditState.SUB_FOLDER; // 更新状态为子文件夹
}
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { // 如果是通话记录文件夹
mTitleBar.setText(R.string.call_record_folder_name); // 设置标题为通话记录文件夹名称
} else {
mTitleBar.setText(data.getSnippet()); // 设置标题为文件夹的摘要
}
mTitleBar.setVisibility(View.VISIBLE); // 显示标题栏
}
public void onClick(View v) { // 按钮点击处理
switch (v.getId()) { // 根据视图ID处理不同的点击事件
case R.id.btn_new_note: // 新增笔记按钮
createNewNote(); // 创建新笔记
break;
default:
break; // 默认情况下不处理
}
}
private void showSoftInput() { // 显示软输入法的方法
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); // 获取输入法管理器
if (inputMethodManager != null) { // 如果输入法管理器不为null
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); // 切换软输入法显示
}
}
private void hideSoftInput(View view) { // 隐藏软输入法的方法
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); // 获取输入法管理器
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); // 隐藏软输入法
}
private void showCreateOrModifyFolderDialog(final boolean create) { // 显示创建或修改文件夹对话框的方法
final AlertDialog.Builder builder = new AlertDialog.Builder(this); // 创建对话框构建器
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); // 加载对话框布局
final EditText etName = (EditText) view.findViewById(R.id.et_foler_name); // 获取文件夹名称输入框
showSoftInput(); // 显示软输入法
if (!create) { // 如果是修改操作
if (mFocusNoteDataItem != null) { // 如果当前聚焦的笔记数据项不为null
etName.setText(mFocusNoteDataItem.getSnippet()); // 设置输入框文本为当前笔记摘要
builder.setTitle(getString(R.string.menu_folder_change_name)); // 设置对话框标题为修改名称
} else {
Log.e(TAG, "The long click data item is null"); // 日志输出错误信息
return; // 返回
}
} else {
etName.setText(""); // 清空输入框
builder.setTitle(this.getString(R.string.menu_create_folder)); // 设置对话框标题为创建文件夹
}
builder.setPositiveButton(android.R.string.ok, null); // 设置确认按钮
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { // 设置取消按钮
public void onClick(DialogInterface dialog, int which) {
hideSoftInput(etName); // 隐藏软输入法
}
});
final Dialog dialog = builder.setView(view).show(); // 显示对话框
final Button positive = (Button)dialog.findViewById(android.R.id.button1); // 获取确认按钮
positive.setOnClickListener(new OnClickListener() { // 设置确认按钮的点击事件
public void onClick(View v) {
hideSoftInput(etName); // 隐藏软输入法
String name = etName.getText().toString(); // 获取输入框文本
if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { // 检查文件夹名称是否存在
Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name),
Toast.LENGTH_LONG).show(); // 显示文件夹已存在的提示
etName.setSelection(0, etName.length()); // 设置焦点到输入框
return; // 返回
}
if (!create) { // 如果是修改文件夹名称
if (!TextUtils.isEmpty(name)) { // 如果输入框不为空
ContentValues values = new ContentValues(); // 创建ContentValues
values.put(NoteColumns.SNIPPET, name); // 添加名称
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 添加类型
values.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记为本地修改
mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID // 更新文件夹信息
+ "=?", new String[] {
String.valueOf(mFocusNoteDataItem.getId()) // 用当前文件夹ID更新
});
}
} else if (!TextUtils.isEmpty(name)) { // 如果是创建新文件夹且输入框不为空
ContentValues values = new ContentValues(); // 创建ContentValues
values.put(NoteColumns.SNIPPET, name); // 添加文件夹名称
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 设置类型为文件夹
mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); // 插入新文件夹到数据库
}
dialog.dismiss(); // 关闭对话框
}
});
if (TextUtils.isEmpty(etName.getText())) { // 如果输入框没有内容
positive.setEnabled(false); // 禁用确认按钮
}
/**
*
*/
etName.addTextChangedListener(new TextWatcher() { // 添加文本变化监听器
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// 不处理
}
public void onTextChanged(CharSequence s, int start, int before, int count) { // 文本变化时调用
if (TextUtils.isEmpty(etName.getText())) { // 如果输入框为空
positive.setEnabled(false); // 禁用确认按钮
} else {
positive.setEnabled(true); // 否则启用确认按钮
}
}
public void afterTextChanged(Editable s) {
// 不处理
}
});
}
@Override
public void onBackPressed() { // 当按下返回按钮时调用
switch (mState) { // 根据当前状态处理
case SUB_FOLDER: // 如果为子文件夹状态
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 设置当前文件夹ID为根文件夹ID
mState = ListEditState.NOTE_LIST; // 更新状态为笔记列表
startAsyncNotesListQuery(); // 启动异步笔记列表查询
mTitleBar.setVisibility(View.GONE); // 隐藏标题栏
break;
case CALL_RECORD_FOLDER: // 如果为通话记录文件夹状态
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 设置当前文件夹ID为根文件夹ID
mState = ListEditState.NOTE_LIST; // 更新状态为笔记列表
mAddNewNote.setVisibility(View.VISIBLE); // 显示新增笔记按钮
mTitleBar.setVisibility(View.GONE); // 隐藏标题栏
startAsyncNotesListQuery(); // 启动异步笔记列表查询
break;
case NOTE_LIST: // 如果为笔记列表状态
super.onBackPressed(); // 调用父类返回
break;
default:
break; // 默认情况不处理
}
}
private void updateWidget(int appWidgetId, int appWidgetType) { // 更新小部件的方法
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); // 创建更新小部件的Intent
if (appWidgetType == Notes.TYPE_WIDGET_2X) { // 如果小部件类型为2x
intent.setClass(this, NoteWidgetProvider_2x.class); // 设置为2x小部件提供者
} else if (appWidgetType == Notes.TYPE_WIDGET_4X) { // 如果小部件类型为4x
intent.setClass(this, NoteWidgetProvider_4x.class); // 设置为4x小部件提供者
} else {
Log.e(TAG, "Unspported widget type"); // 日志输出不支持的小部件类型
return; // 返回
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { // 将小部件ID放入Intent中
appWidgetId
});
sendBroadcast(intent); // 发送广播以更新小部件
setResult(RESULT_OK, intent); // 设置结果为成功
}
private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { // 创建文件夹上下文菜单的监听器
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { // 创建上下文菜单时调用
if (mFocusNoteDataItem != null) { // 如果当前聚焦的笔记数据项不为null
menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); // 设置菜单标题为当前笔记摘要
menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); // 添加查看文件夹菜单项
menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); // 添加删除文件夹菜单项
menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); // 添加更改文件夹名称菜单项
}
}
};
@Override
public void onContextMenuClosed(Menu menu) { // 当上下文菜单关闭时调用
if (mNotesListView != null) { // 如果笔记列表视图不为空
mNotesListView.setOnCreateContextMenuListener(null); // 清除上下文菜单创建监听器
}
super.onContextMenuClosed(menu); // 调用父类的处理方法
}
@Override
public boolean onContextItemSelected(MenuItem item) { // 当上下文菜单项被选中时调用
if (mFocusNoteDataItem == null) { // 如果当前聚焦的笔记数据项为空
Log.e(TAG, "The long click data item is null"); // 日志输出错误信息
return false; // 返回false
}
switch (item.getItemId()) { // 根据菜单项ID处理操作
case MENU_FOLDER_VIEW: // 查看文件夹菜单项
openFolder(mFocusNoteDataItem); // 打开文件夹
break;
case MENU_FOLDER_DELETE: // 删除文件夹菜单项
AlertDialog.Builder builder = new AlertDialog.Builder(this); // 创建删除确认对话框
builder.setTitle(getString(R.string.alert_title_delete)); // 设置对话框标题
builder.setIcon(android.R.drawable.ic_dialog_alert); // 设置对话框图标
builder.setMessage(getString(R.string.alert_message_delete_folder)); // 设置对话框消息
builder.setPositiveButton(android.R.string.ok, // 设置确认按钮
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteFolder(mFocusNoteDataItem.getId()); // 删除当前聚焦的文件夹
}
});
builder.setNegativeButton(android.R.string.cancel, null); // 设置取消按钮
builder.show(); // 显示对话框
break;
case MENU_FOLDER_CHANGE_NAME: // 更改文件夹名称菜单项
showCreateOrModifyFolderDialog(false); // 显示修改文件夹对话框
break;
default:
break; // 默认不处理
}
return true; // 返回true表示操作已处理
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) { // 准备选项菜单时调用
menu.clear(); // 清空菜单
if (mState == ListEditState.NOTE_LIST) { // 如果当前状态是笔记列表
getMenuInflater().inflate(R.menu.note_list, menu); // 加载笔记列表菜单
// 设置同步或取消同步
menu.findItem(R.id.menu_sync).setTitle(
GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync);
} else if (mState == ListEditState.SUB_FOLDER) { // 如果当前状态是子文件夹
getMenuInflater().inflate(R.menu.sub_folder, menu); // 加载子文件夹菜单
} else if (mState == ListEditState.CALL_RECORD_FOLDER) { // 如果当前状态是通话记录文件夹
getMenuInflater().inflate(R.menu.call_record_folder, menu); // 加载通话记录文件夹菜单
} else {
Log.e(TAG, "Wrong state:" + mState); // 日志输出错误状态
}
return true; // 返回true表示菜单准备成功
}
@Override
public boolean onOptionsItemSelected(MenuItem item) { // 处理选项菜单项选择的事件
switch (item.getItemId()) { // 根据菜单项的ID执行相应操作
case R.id.menu_new_folder: { // 新建文件夹菜单项
showCreateOrModifyFolderDialog(true); // 显示创建文件夹对话框
break;
}
case R.id.menu_export_text: { // 导出文本菜单项
exportNoteToText(); // 导出笔记为文本
break;
}
case R.id.menu_sync: { // 同步菜单项
if (isSyncMode()) { // 如果是同步模式
if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) { // 如果菜单标题为同步
GTaskSyncService.startSync(this); // 启动同步服务
} else {
GTaskSyncService.cancelSync(this); // 取消同步
}
} else {
startPreferenceActivity(); // 启动设置Activity
}
break;
}
case R.id.menu_setting: { // 设置菜单项
startPreferenceActivity(); // 启动设置Activity
break;
}
case R.id.menu_new_note: { // 新建笔记菜单项
createNewNote(); // 创建新笔记
break;
}
case R.id.menu_search: // 搜索菜单项
onSearchRequested(); // 请求搜索
break;
default:
break; // 默认不处理
}
return true; // 返回true表示菜单项已处理
}
@Override
public boolean onSearchRequested() { // 当搜索请求时被调用
startSearch(null, false, null /* appData */, false); // 启动搜索
return true; // 返回true表示事件已处理
}
private void exportNoteToText() { // 导出笔记为文本的方法
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); // 获取备份工具实例
new AsyncTask<Void, Void, Integer>() { // 创建异步任务
@Override
protected Integer doInBackground(Void... unused) { // 在后台执行的任务
return backup.exportToText(); // 调用导出方法
}
@Override
protected void onPostExecute(Integer result) { // 任务执行完毕后调用
if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { // 如果SD卡未挂载
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); // 创建警告对话框
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export)); // 设置对话框标题
builder.setMessage(NotesListActivity.this
.getString(R.string.error_sdcard_unmounted)); // 设置对话框消息
builder.setPositiveButton(android.R.string.ok, null); // 设置确认按钮
builder.show(); // 显示对话框
} else if (result == BackupUtils.STATE_SUCCESS) { // 如果导出成功
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); // 创建成功对话框
builder.setTitle(NotesListActivity.this
.getString(R.string.success_sdcard_export)); // 设置标题
builder.setMessage(NotesListActivity.this.getString(
R.string.format_exported_file_location, backup
.getExportedTextFileName(), backup.getExportedTextFileDir())); // 设置导出文件位置消息
builder.setPositiveButton(android.R.string.ok, null); // 设置确认按钮
builder.show(); // 显示对话框
} else if (result == BackupUtils.STATE_SYSTEM_ERROR) { // 如果系统错误
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); // 创建错误对话框
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export)); // 设置标题
builder.setMessage(NotesListActivity.this
.getString(R.string.error_sdcard_export)); // 设置消息
builder.setPositiveButton(android.R.string.ok, null); // 设置确认按钮
builder.show(); // 显示对话框
}
}
}.execute(); // 执行异步任务
}
private boolean isSyncMode() { // 判断是否在同步模式的方法
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; // 如果同步账户名称不为空则返回true
}
private void startPreferenceActivity() { // 启动设置Activity的方法
Activity from = getParent() != null ? getParent() : this; // 获取父Activity如果没有则使用当前Activity
Intent intent = new Intent(from, NotesPreferenceActivity.class); // 创建意图
from.startActivityIfNeeded(intent, -1); // 启动设置Activity
}
private class OnListItemClickListener implements OnItemClickListener { // 定义内部类 OnListItemClickListener实现列表项点击监听器
public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // 处理点击事件
if (view instanceof NotesListItem) { // 检查点击的视图是否为 NotesListItem 实例
NoteItemData item = ((NotesListItem) view).getItemData(); // 获取点击的项数据
if (mNotesListAdapter.isInChoiceMode()) { // 如果适配器处于选择模式
if (item.getType() == Notes.TYPE_NOTE) { // 如果点击的项是笔记类型
position = position - mNotesListView.getHeaderViewsCount(); // 调整位置,以考虑头部视图的数量
mModeCallBack.onItemCheckedStateChanged(null, position, id, // 通知模式回调选中状态变化
!mNotesListAdapter.isSelectedItem(position)); // 设置选中状态为相反
}
return; // 返回,不再处理其他情况
}
switch (mState) { // 根据当前状态处理不同的逻辑
case NOTE_LIST: // 笔记列表状态
if (item.getType() == Notes.TYPE_FOLDER // 如果为文件夹类型
|| item.getType() == Notes.TYPE_SYSTEM) { // 或者是系统类型
openFolder(item); // 打开文件夹
} else if (item.getType() == Notes.TYPE_NOTE) { // 如果是笔记类型
openNode(item); // 打开笔记
} else {
Log.e(TAG, "Wrong note type in NOTE_LIST"); // 日志输出错误信息
}
break;
case SUB_FOLDER: // 子文件夹状态
case CALL_RECORD_FOLDER: // 通话记录文件夹状态
if (item.getType() == Notes.TYPE_NOTE) { // 如果是笔记类型
openNode(item); // 打开笔记
} else {
Log.e(TAG, "Wrong note type in SUB_FOLDER"); // 日志输出错误信息
}
break;
default: // 默认情况
break;
}
}
}
}
private void startQueryDestinationFolders() { // 启动查询目标文件夹的方法
// 定义选择条件选择类型为文件夹的项且父ID不等于当前文件夹ID且ID不等于通话记录文件夹ID
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
selection = (mState == ListEditState.NOTE_LIST) ? selection : // 如果当前状态是笔记列表
"(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; // 否则也查询根文件夹
mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, // 启动异步查询
null,
Notes.CONTENT_NOTE_URI, // 查询的URI
FoldersListAdapter.PROJECTION, // 查询的列
selection, // 查询条件
new String[] { // 查询参数
String.valueOf(Notes.TYPE_FOLDER), // 当前选择的文件夹
String.valueOf(Notes.ID_TRASH_FOLER), // 回收站文件夹ID
String.valueOf(mCurrentFolderId) // 当前文件夹ID
},
NoteColumns.MODIFIED_DATE + " DESC"); // 按修改日期降序排序
}
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { // 处理长按事件
if (view instanceof NotesListItem) { // 如果视图是 NotesListItem 实例
mFocusNoteDataItem = ((NotesListItem) view).getItemData(); // 获取当前聚焦的笔记数据项
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { // 如果为笔记类型且不在选择模式
if (mNotesListView.startActionMode(mModeCallBack) != null) { // 启动动作模式
mModeCallBack.onItemCheckedStateChanged(null, position, id, true); // 通知选中项
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); // 执行触觉反馈
} else {
Log.e(TAG, "startActionMode fails"); // 日志输出错误信息
}
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { // 如果为文件夹类型
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); // 设置上下文菜单监听器
}
}
return false; // 返回false表示未处理
}

@ -0,0 +1,182 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui; // 定义包名
import android.content.Context; // 导入Context类
import android.database.Cursor; // 导入Cursor类
import android.util.Log; // 导入Log类
import android.view.View; // 导入View类
import android.view.ViewGroup; // 导入ViewGroup类
import android.widget.CursorAdapter; // 导入CursorAdapter类
import net.micode.notes.data.Notes; // 导入Notes数据类
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"; // 日志标签
private Context mContext; // 上下文
private HashMap<Integer, Boolean> mSelectedIndex; // 用于记录选择状态的哈希映射
private int mNotesCount; // 笔记数量
private boolean mChoiceMode; // 选择模式标志
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; // 初始化笔记数量为0
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) { // 创建新视图的方法
return new NotesListItem(context); // 返回新的NotesListItem实例
}
@Override
public void bindView(View view, Context context, Cursor cursor) { // 绑定视图与数据的方法
if (view instanceof NotesListItem) { // 检查视图类型
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 boolean isInChoiceMode() { // 判断是否处于选择模式
return mChoiceMode; // 返回选择模式状态
}
public void setChoiceMode(boolean mode) { // 设置选择模式
mSelectedIndex.clear(); // 清除之前的选择状态
mChoiceMode = mode; // 更新选择模式状态
}
public void selectAll(boolean checked) { // 全选或取消全选的方法
Cursor cursor = getCursor(); // 获取当前游标
for (int i = 0; i < getCount(); i++) { // 遍历所有项
if (cursor.moveToPosition(i)) { // 移动游标到指定位置
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { // 如果项的类型为笔记
setCheckedItem(i, checked); // 设置选择状态
}
}
}
}
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); // 添加ID到集合
}
}
}
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(); // 获取小部件ID
widget.widgetType = item.getWidgetType(); // 获取小部件类型
itemSet.add(widget); // 将小部件属性添加到集合
// 不要在这里关闭游标,只有适配器可以关闭它
} else {
Log.e(TAG, "Invalid cursor"); // 日志输出错误信息
return null; // 返回null
}
}
}
return itemSet; // 返回小部件属性集合
}
public int getSelectedCount() { // 获取选中项数量的方法
Collection<Boolean> values = mSelectedIndex.values(); // 获取所有选择状态
if (null == values) { // 如果值为空
return 0; // 返回0
}
Iterator<Boolean> iter = values.iterator(); // 创建迭代器
int count = 0; // 初始化计数器
while (iter.hasNext()) { // 遍历选择状态
if (true == iter.next()) { // 如果选中
count++; // 增加计数
}
}
return count; // 返回选中数量
}
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; // 返回false
}
return mSelectedIndex.get(position); // 返回该项的选择状态
}
@Override
protected void onContentChanged() { // 当内容发生变化时调用
super.onContentChanged(); // 调用父类方法
calcNotesCount(); // 计算笔记数量
}
@Override
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++; // 增加计数
}
} else {
Log.e(TAG, "Invalid cursor"); // 日志输出错误信息
return; // 返回
}
}
}
}

@ -0,0 +1,121 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui; // 定义包名
import android.content.Context; // 导入Context类
import android.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; // 导入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 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); // 设置复选框的选中状态
} else {
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) { // 如果父级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); // 隐藏警报图标
}
} else { // 如果不是通话记录文件夹
mCallName.setVisibility(View.GONE); // 隐藏通话记录名称
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); // 设置标题文本外观为主要样式
if (data.getType() == Notes.TYPE_FOLDER) { // 如果数据类型为文件夹
mTitle.setText(data.getSnippet() // 设置标题为文件夹摘要和文件夹中的笔记数量
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE); // 隐藏警报图标
} else { // 如果是笔记类型
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); // 设置标题为摘要
if (data.hasAlert()) { // 如果有警报
mAlert.setImageResource(R.drawable.clock); // 设置警报图标为时钟
mAlert.setVisibility(View.VISIBLE); // 显示警报图标
} else {
mAlert.setVisibility(View.GONE); // 隐藏警报图标
}
}
}
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); // 设置文本视图为相对时间格式的修改日期
setBackground(data); // 设置背景
}
private void setBackground(NoteItemData data) { // 设置背景的方法
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)); // 设置为正常背景资源
}
} else { // 如果不是笔记类型
setBackgroundResource(NoteItemBgResources.getFolderBgRes()); // 设置为文件夹背景资源
}
}
public NoteItemData getItemData() { // 获取当前笔记项数据
return mItemData; // 返回笔记项数据
}
}

@ -0,0 +1,528 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
// 指定当前类的包名属于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();
// 生命周期方法:当活动恢复时调用。
// 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;
}
}
}
}
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)) {
// 如果当前没有默认账户。
showSelectAccountAlertDialog();
// 显示选择账户的对话框。
} else {
// 如果已有默认账户。
showChangeAccountConfirmAlertDialog();
// 显示更改账户的确认对话框。
}
} else {
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
// 提示用户正在同步时无法更改账户。
}
return true; // 表示事件已处理。
}
});
mAccountCategory.addPreference(accountPref);
// 将创建的账户偏好设置项添加到账户类别中。
}
private void loadSyncButton() {
// 加载同步按钮的方法。
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
// 获取用于同步的按钮实例。
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// 获取用于显示最后同步时间的文本视图。
// 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() {
public void run() {
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() {
public void run() {
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。
}
}
}

@ -0,0 +1,519 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.widget;
import android.app.PendingIntent; // 导入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[] {
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET
}; // 定义一个常量数组PROJECTION包含NoteColumns的ID、BG_COLOR_ID和SNIPPET
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"; // 定义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(); // 创建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]) }); // 更新数据库将指定小部件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_FOLDER) },
null); // 查询数据库返回符合条件的Cursor对象
}
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 更新小部件
update(context, appWidgetManager, appWidgetIds, false); // 调用重载的update方法传入false作为privacyMode参数
}
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {
// 更新小部件
for (int i = 0; i < appWidgetIds.length; 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); // 获取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对象指定布局ID
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); // 设置背景图片资源ID
/**
* Generate the pending intent to start host for the widget
*/
PendingIntent pendingIntent = null;
if (privacyMode) { // 如果隐私模式
rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode)); // 设置文本视图内容为隐私模式提示
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
} else {
rv.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
PendingIntent.FLAG_UPDATE_CURRENT); // 创建PendingIntent对象指向NoteEditActivity
}
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); // 设置文本视图的点击事件
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
}
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;
}
};
}
/**
*
* @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;
}
});
// 显示快捷菜单
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