diff --git a/doc/minode.docx b/doc/minode.docx new file mode 100644 index 0000000..6e006c9 Binary files /dev/null and b/doc/minode.docx differ diff --git a/doc/minode2.docx b/doc/minode2.docx new file mode 100644 index 0000000..681b31f Binary files /dev/null and b/doc/minode2.docx differ diff --git a/doc/小米便签开源软件泛读报告文档(1).docx b/doc/小米便签开源软件泛读报告文档(1).docx new file mode 100644 index 0000000..473000d Binary files /dev/null and b/doc/小米便签开源软件泛读报告文档(1).docx differ diff --git a/src/Notes-master/src/net/micode/notes/gtask/exception/ActionFailureException.java b/src/Notes-master/src/net/micode/notes/gtask/exception/ActionFailureException.java index 07b798b..be517f6 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/exception/ActionFailureException.java +++ b/src/Notes-master/src/net/micode/notes/gtask/exception/ActionFailureException.java @@ -1,40 +1,72 @@ /* * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * // 版权声明,指定代码的版权所有者和代码编写的时间范围 * * Licensed under the Apache License, Version 2.0 (the "License"); + * // 声明该代码是根据Apache License 2.0版本授权的 * you may not use this file except in compliance with the License. + * // 提示使用者,除非符合该许可证的要求,否则不能使用此文件 * You may obtain a copy of the License at + * // 指出如何获取许可证副本 * * http://www.apache.org/licenses/LICENSE-2.0 + * // 许可证的URL链接 * * 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. + * // 指出根据许可证分发的软件是“按现状”提供的,没有明示或暗示的任何保证或条件 */ +<<<<<<< HEAD package net.micode.notes.gtask.exception; // 定义该类所在的包 +======= +package net.micode.notes.gtask.exception; +// 指定该Java文件的包路径 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae /** * ActionFailureException 是一个运行时异常,表示执行操作失败的情况。 * 这个异常可以通过不同的构造函数来创建,允许提供详细的错误信息和导致异常的原因。 */ public class ActionFailureException extends RuntimeException { +<<<<<<< HEAD private static final long serialVersionUID = 4425249765923293627L; // 序列化版本UID,用于序列化机制 +======= + // 声明一个名为ActionFailureException的公共类,该类继承自RuntimeException + private static final long serialVersionUID = 4425249765923293627L; + // 声明一个常量serialVersionUID,用于序列化时保持版本的兼容性 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae // 默认构造函数 public ActionFailureException() { +<<<<<<< HEAD super(); // 调用父类的构造函数 +======= + super(); + // 默认构造函数,调用父类RuntimeException的无参构造函数 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae } // 带有错误信息的构造函数 public ActionFailureException(String paramString) { +<<<<<<< HEAD super(paramString); // 调用父类构造函数,传递错误信息 +======= + super(paramString); + // 带有一个字符串参数的构造函数,调用父类RuntimeException的带字符串参数的构造函数 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae } // 带有错误信息和根本原因的构造函数 public ActionFailureException(String paramString, Throwable paramThrowable) { +<<<<<<< HEAD super(paramString, paramThrowable); // 调用父类构造函数,传递错误信息和原因 +======= + super(paramString, paramThrowable); + // 带有一个字符串和一个Throwable参数的构造函数,调用父类RuntimeException的相应构造函数 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae } } diff --git a/src/Notes-master/src/net/micode/notes/gtask/exception/NetworkFailureException.java b/src/Notes-master/src/net/micode/notes/gtask/exception/NetworkFailureException.java index 4ec8d96..278a3bb 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/exception/NetworkFailureException.java +++ b/src/Notes-master/src/net/micode/notes/gtask/exception/NetworkFailureException.java @@ -14,6 +14,7 @@ * limitations under the License. */ +<<<<<<< HEAD package net.micode.notes.gtask.exception; // 定义包路径 /** @@ -25,17 +26,43 @@ public class NetworkFailureException extends Exception { private static final long serialVersionUID = 2107610287180234136L; // 默认构造函数 +======= +// 该类所属的包名,表明这个类是位于net.micode.notes.gtask.exception包下,用于存放相关的异常类 +package net.micode.notes.gtask.exception; + +// NetworkFailureException类继承自Exception,用于表示网络相关的操作出现故障时抛出的异常情况 +public class NetworkFailureException extends Exception { + // 序列化版本号,用于在对象序列化和反序列化过程中确保版本的兼容性,这里是一个固定的长整型数值 + private static final long serialVersionUID = 2107610287180234136L; + + // 无参构造函数,调用父类(Exception)的无参构造函数,用于创建一个默认的NetworkFailureException实例,不附带任何详细信息 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public NetworkFailureException() { super(); // 调用父类的构造函数 } +<<<<<<< HEAD // 带有消息的构造函数 +======= + // 带有一个字符串参数的构造函数,该字符串参数通常用于传递异常相关的详细描述信息, + // 调用父类(Exception)的对应构造函数,将传入的字符串作为异常的详细消息 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public NetworkFailureException(String paramString) { super(paramString); // 调用父类的构造函数,传入异常消息 } +<<<<<<< HEAD // 带有消息和原因的构造函数 +======= + // 带有一个字符串参数和一个Throwable参数的构造函数,字符串参数用于传递异常相关的详细描述信息, + // Throwable参数通常用于关联引起当前异常的其他异常(例如底层网络库抛出的原始异常等), + // 调用父类(Exception)的对应构造函数进行初始化,将两个参数传递给父类构造函数 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public NetworkFailureException(String paramString, Throwable paramThrowable) { super(paramString, paramThrowable); // 调用父类的构造函数,传入异常消息和原因 } +<<<<<<< HEAD } +======= +} +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskASyncTask.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskASyncTask.java index e01bc11..bd3553e 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskASyncTask.java +++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskASyncTask.java @@ -1,17 +1,15 @@ /* * 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 + * 根据Apache License, Version 2.0(以下简称“许可证”)授权; + * 除非遵守许可证,否则不得使用此文件。 + * 你可以在以下网址获得许可证的副本: * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 除非适用法律要求或书面同意,否则按“原样”提供软件 + * 在许可证下分发,不附带任何明示或暗示的保证或条件。 + * 请参阅许可证,了解有关权限和限制的具体语言。 */ package net.micode.notes.gtask.remote; // 定义包名 @@ -23,6 +21,7 @@ import android.content.Context; // 导入上下文类 import android.content.Intent; // 导入意图类 import android.os.AsyncTask; // 导入异步任务类 +<<<<<<< HEAD import net.micode.notes.R; // 导入资源文件 import net.micode.notes.ui.NotesListActivity; // 导入笔记列表活动 import net.micode.notes.ui.NotesPreferenceActivity; // 导入笔记偏好设置活动 @@ -33,16 +32,38 @@ public class GTaskASyncTask extends AsyncTask { private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; // 定义用于同步通知的ID // 定义完成监听器接口 +======= +// 异步任务类,用于执行GTask同步操作 +public class GTaskASyncTask extends AsyncTask { + + // 定义GTASK同步通知的ID + private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; + + // 定义完成监听接口 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public interface OnCompleteListener { void onComplete(); // 监听完成事件 } +<<<<<<< HEAD private Context mContext; // 上下文 private NotificationManager mNotifiManager; // 通知管理器 private GTaskManager mTaskManager; // 任务管理器 private OnCompleteListener mOnCompleteListener; // 完成监听器 // 构造函数,初始化上下文和监听器 +======= + // 上下文对象 + private Context mContext; + // 通知管理器 + private NotificationManager mNotifiManager; + // 任务管理器 + private GTaskManager mTaskManager; + // 完成监听器 + private OnCompleteListener mOnCompleteListener; + + // 构造函数,初始化上下文、通知管理器、任务管理器和完成监听器 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public GTaskASyncTask(Context context, OnCompleteListener listener) { mContext = context; // 设置上下文 mOnCompleteListener = listener; // 设置完成监听器 @@ -51,15 +72,26 @@ public class GTaskASyncTask extends AsyncTask { mTaskManager = GTaskManager.getInstance(); // 获取任务管理器实例 } +<<<<<<< HEAD // 取消同步 +======= + // 取消同步的方法 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public void cancelSync() { mTaskManager.cancelSync(); // 调用任务管理器的取消同步方法 } +<<<<<<< HEAD // 发布进度 public void publishProgess(String message) { publishProgress(new String[] { message // 发布进度消息 +======= + // 发布进度消息的方法 + public void publishProgess(String message) { + publishProgress(new String[] { + message +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae }); } @@ -68,6 +100,7 @@ public class GTaskASyncTask extends AsyncTask { // 创建通知对象 Notification notification = new Notification(R.drawable.notification, mContext .getString(tickerId), System.currentTimeMillis()); +<<<<<<< HEAD notification.defaults = Notification.DEFAULT_LIGHTS; // 设置默认灯光效果 notification.flags = Notification.FLAG_AUTO_CANCEL; // 设置自动取消标志 PendingIntent pendingIntent; // 定义待处理意图 @@ -76,6 +109,15 @@ public class GTaskASyncTask extends AsyncTask { pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesPreferenceActivity.class), 0); // 启动偏好设置活动 +======= + notification.defaults = Notification.DEFAULT_LIGHTS; + notification.flags = Notification.FLAG_AUTO_CANCEL; + // 根据tickerId创建不同的PendingIntent + PendingIntent pendingIntent; + if (tickerId != R.string.ticker_success) { + pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, + NotesPreferenceActivity.class), 0); +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae } else { pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, NotesListActivity.class), 0); // 启动笔记列表活动 @@ -83,9 +125,15 @@ public class GTaskASyncTask extends AsyncTask { // 设置通知的最新事件信息 notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, pendingIntent); +<<<<<<< HEAD mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); // 发送通知 +======= + // 发送通知 + mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae } + // 后台执行同步操作的方法 @Override protected Integer doInBackground(Void... unused) { // 发布登录进度 @@ -94,6 +142,7 @@ public class GTaskASyncTask extends AsyncTask { return mTaskManager.sync(mContext, this); // 调用同步方法并返回状态 } + // 更新进度的方法 @Override protected void onProgressUpdate(String... progress) { showNotification(R.string.ticker_syncing, progress[0]); // 显示同步进度通知 @@ -103,9 +152,14 @@ public class GTaskASyncTask extends AsyncTask { } } + // 执行完毕后的方法 @Override protected void onPostExecute(Integer result) { +<<<<<<< HEAD // 根据同步结果显示相应的通知 +======= + // 根据结果展示不同的通知 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae if (result == GTaskManager.STATE_SUCCESS) { showNotification(R.string.ticker_success, mContext.getString( R.string.success_sync_account, mTaskManager.getSyncAccount())); @@ -118,7 +172,11 @@ public class GTaskASyncTask extends AsyncTask { showNotification(R.string.ticker_cancel, mContext .getString(R.string.error_sync_cancelled)); // 显示取消同步通知 } +<<<<<<< HEAD // 如果存在完成监听器,则在新线程中调用其onComplete方法 +======= + // 如果存在完成监听器,则通知监听器任务完成 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae if (mOnCompleteListener != null) { new Thread(new Runnable() { public void run() { @@ -127,4 +185,4 @@ public class GTaskASyncTask extends AsyncTask { }).start(); } } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskClient.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskClient.java index b025565..beb8e62 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskClient.java +++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskClient.java @@ -1,3 +1,4 @@ +<<<<<<< HEAD /* * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) * @@ -15,6 +16,8 @@ */ // 定义包名 +======= +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae package net.micode.notes.gtask.remote; // 导入相关类和接口 @@ -55,6 +58,7 @@ import org.json.JSONArray; // 引入JSON数组类 import org.json.JSONException; // 引入JSON异常类 import org.json.JSONObject; // 引入JSON对象类 +<<<<<<< HEAD // 引入JDK类 import java.io.BufferedReader; // 引入缓冲读取器类 import java.io.IOException; // 引入IO异常类 @@ -67,18 +71,30 @@ import java.util.zip.Inflater; // 引入解压缩类 import java.util.zip.InflaterInputStream; // 引入解压缩输入流类 // GTaskClient类负责处理与Google任务(GTask)的通信 +======= +// GTaskClient类用于与Google Tasks进行交互,实现诸如登录、任务和任务列表的创建、更新、移动、删除以及获取相关数据等功能 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public class GTaskClient { // 日志标签 private static final String TAG = GTaskClient.class.getSimpleName(); +<<<<<<< HEAD // 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"; +======= + // Google Tasks的基础URL + 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"; + // 用于向Google Tasks发送POST请求的URL +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; // 单例实例 private static GTaskClient mInstance = null; +<<<<<<< HEAD // HTTP客户端 private DefaultHttpClient mHttpClient; @@ -97,6 +113,28 @@ public class GTaskClient { private JSONArray mUpdateArray; // 私有构造函数,初始化类的成员变量 +======= + // 用于发送HTTP请求的HttpClient对象 + private DefaultHttpClient mHttpClient; + // 获取数据的具体URL + private String mGetUrl; + // 发送POST请求的具体URL + private String mPostUrl; + // 客户端版本号 + private long mClientVersion; + // 标记是否已登录 + private boolean mLoggedin; + // 上次登录的时间戳 + private long mLastLoginTime; + // 用于标识操作的ID,每次操作自增 + private int mActionId; + // 当前使用的账户 + private Account mAccount; + // 用于暂存更新操作相关数据的JSON数组 + private JSONArray mUpdateArray; + + // 私有构造函数,初始化相关成员变量 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae private GTaskClient() { mHttpClient = null; mGetUrl = GTASK_GET_URL; @@ -109,7 +147,11 @@ public class GTaskClient { mUpdateArray = null; } +<<<<<<< HEAD // 获取单例实例 +======= + // 获取GTaskClient的单例实例 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public static synchronized GTaskClient getInstance() { if (mInstance == null) { mInstance = new GTaskClient(); @@ -117,17 +159,31 @@ public class GTaskClient { return mInstance; } +<<<<<<< HEAD // 登录方法 public boolean login(Activity activity) { // 假设cookie在5分钟后过期 +======= + // 执行登录操作 + public boolean login(Activity activity) { + // 假设Cookie在5分钟后过期,若距离上次登录时间超过5分钟,则需要重新登录 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae final long interval = 1000 * 60 * 5; if (mLastLoginTime + interval < System.currentTimeMillis()) { mLoggedin = false; // 如果超时,设置未登录 } +<<<<<<< HEAD // 如果账户切换,需要重新登录 if (mLoggedin && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity.getSyncAccountName(activity))) { mLoggedin = false; // 账户切换,重新登录 +======= + // 如果已登录,但当前账户与设置中的同步账户不一致,也需要重新登录 + if (mLoggedin + && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity + .getSyncAccountName(activity))) { + mLoggedin = false; +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae } // 若已经登录,无需再次登录 @@ -136,15 +192,27 @@ public class GTaskClient { return true; } +<<<<<<< HEAD mLastLoginTime = System.currentTimeMillis(); // 更新最后登录时间 String authToken = loginGoogleAccount(activity, false); // 获取Google账户的认证令牌 +======= + mLastLoginTime = System.currentTimeMillis(); + // 登录Google账户获取授权令牌 + String authToken = loginGoogleAccount(activity, false); +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae if (authToken == null) { Log.e(TAG, "login google account failed"); return false; // 登录失败 } +<<<<<<< HEAD // 如果为自定义域,则尝试进行登录 if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase().endsWith("googlemail.com"))) { +======= + // 如果账户不是以gmail.com或googlemail.com结尾(可能是自定义域名),则使用自定义域名相关的URL进行登录 + if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() + .endsWith("googlemail.com"))) { +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); int index = mAccount.name.indexOf('@') + 1; String suffix = mAccount.name.substring(index); @@ -157,7 +225,11 @@ public class GTaskClient { } } +<<<<<<< HEAD // 尝试以 Google 官方 URL 登录 +======= + // 如果使用自定义域名登录失败,则尝试使用Google官方URL登录 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae if (!mLoggedin) { mGetUrl = GTASK_GET_URL; // 恢复默认的GET请求URL mPostUrl = GTASK_POST_URL; // 恢复默认的POST请求URL @@ -170,11 +242,21 @@ public class GTaskClient { return true; } +<<<<<<< HEAD // 获取Google账户认证令牌的方法 private String loginGoogleAccount(Activity activity, boolean invalidateToken) { String authToken; AccountManager accountManager = AccountManager.get(activity); // 获取账户管理器 Account[] accounts = accountManager.getAccountsByType("com.google"); // 获取Google账户 +======= + // 登录Google账户获取授权令牌 + private String loginGoogleAccount(Activity activity, boolean invalidateToken) { + String authToken; + // 获取账户管理器 + AccountManager accountManager = AccountManager.get(activity); + // 获取所有Google类型的账户 + Account[] accounts = accountManager.getAccountsByType("com.google"); +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae // 如果没有可用的Google账户,日志记录并返回null if (accounts.length == 0) { @@ -182,7 +264,12 @@ public class GTaskClient { return null; } +<<<<<<< HEAD String accountName = NotesPreferenceActivity.getSyncAccountName(activity); // 获取同步的账户名 +======= + // 获取设置中的同步账户名称 + String accountName = NotesPreferenceActivity.getSyncAccountName(activity); +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae Account account = null; // 找到设置中与账户名匹配的账户 @@ -199,14 +286,21 @@ public class GTaskClient { return null; // 找不到匹配账户,返回null } +<<<<<<< HEAD // 获取认证令牌 AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); +======= + // 获取授权令牌 + AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, + "goanna_mobile", null, activity, null, null); +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae try { Bundle authTokenBundle = accountManagerFuture.getResult(); // 获取结果 authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); // 读取令牌 // 如果需要无效化令牌,则重新获取 if (invalidateToken) { + // 如果需要使令牌失效,则先失效再重新获取 accountManager.invalidateAuthToken("com.google", authToken); loginGoogleAccount(activity, false); // 递归调用获取新令牌 } @@ -218,10 +312,17 @@ public class GTaskClient { return authToken; // 返回令牌 } +<<<<<<< HEAD // 尝试登录到GTask private boolean tryToLoginGtask(Activity activity, String authToken) { if (!loginGtask(authToken)) { // 如果认证令牌过期,则无效化并尝试重新登录 +======= + // 尝试登录Google Tasks + private boolean tryToLoginGtask(Activity activity, String authToken) { + if (!loginGtask(authToken)) { + // 如果登录失败,可能是授权令牌过期,先使令牌失效再重新尝试登录Google账户获取新令牌,然后再次登录Google Tasks +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae authToken = loginGoogleAccount(activity, true); if (authToken == null) { Log.e(TAG, "login google account failed"); @@ -236,6 +337,7 @@ public class GTaskClient { return true; // 登录成功 } +<<<<<<< HEAD // 使用认证令牌登录到GTask private boolean loginGtask(String authToken) { int timeoutConnection = 10000; // 连接超时设置 @@ -249,12 +351,32 @@ public class GTaskClient { HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); // 设置HTTP协议参数 // 登录GTask +======= + // 实际执行登录Google Tasks的操作,获取相关信息(如客户端版本号等) + private boolean loginGtask(String authToken) { + int timeoutConnection = 10000; + int timeoutSocket = 15000; + // 设置HTTP请求的参数,包括连接超时和读取超时时间 + HttpParams httpParameters = new BasicHttpParams(); + HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); + HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); + mHttpClient = new DefaultHttpClient(httpParameters); + BasicCookieStore localBasicCookieStore = new BasicCookieStore(); + mHttpClient.setCookieStore(localBasicCookieStore); + HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); + + // 登录Google Tasks,发送带有授权令牌的GET请求 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae try { String loginUrl = mGetUrl + "?auth=" + authToken; // 创建登录URL HttpGet httpGet = new HttpGet(loginUrl); // 创建GET请求 HttpResponse response = mHttpClient.execute(httpGet); // 执行请求 +<<<<<<< HEAD // 获取Cookie +======= + // 获取登录后的Cookie信息,检查是否包含认证相关的Cookie(名称包含"GTL") +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae List cookies = mHttpClient.getCookieStore().getCookies(); boolean hasAuthCookie = false; // 验证是否存在认证Cookie for (Cookie cookie : cookies) { @@ -266,7 +388,11 @@ public class GTaskClient { Log.w(TAG, "it seems that there is no auth cookie"); // 没有认证Cookie的警告 } +<<<<<<< HEAD // 从响应中获取客户端版本 +======= + // 从响应中获取客户端版本号等信息,通过解析返回的JavaScript代码中的相关数据 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -283,6 +409,10 @@ public class GTaskClient { e.printStackTrace(); return false; // 处理JSON异常,返回登录失败 } catch (Exception e) { +<<<<<<< HEAD +======= + // 捕获其他所有异常,若发生异常则登录失败 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae Log.e(TAG, "httpget gtask_url failed"); return false; // 处理其他异常,返回登录失败 } @@ -290,12 +420,20 @@ public class GTaskClient { return true; // 登录成功 } +<<<<<<< HEAD // 获取下一个动作ID +======= + // 获取下一个操作的ID +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae private int getActionId() { return mActionId++; // 返回当前ID并递增 } +<<<<<<< HEAD // 创建HTTP POST请求 +======= + // 创建用于发送POST请求的HttpPost对象,并设置相关请求头 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae private HttpPost createHttpPost() { HttpPost httpPost = new HttpPost(mPostUrl); // 创建POST请求 httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); // 设置请求头 @@ -303,7 +441,11 @@ public class GTaskClient { return httpPost; // 返回POST请求 } +<<<<<<< HEAD // 获取HTTP响应内容 +======= + // 从HTTP实体中获取响应内容,根据内容编码(如gzip、deflate等)进行相应的解压处理 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae private String getResponseContent(HttpEntity entity) throws IOException { String contentEncoding = null; // 声明内容编码 if (entity.getContentEncoding() != null) { @@ -338,7 +480,11 @@ public class GTaskClient { } } +<<<<<<< HEAD // 发送POST请求 +======= + // 发送POST请求,将JSON数据发送到Google Tasks服务器,并处理响应返回的JSON数据 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae private JSONObject postRequest(JSONObject js) throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); // 如果未登录,记录错误 @@ -352,7 +498,11 @@ public class GTaskClient { UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); // 创建实体 httpPost.setEntity(entity); // 设置POST实体 +<<<<<<< HEAD // 执行POST请求并获取响应 +======= + // 执行POST请求并获取响应,然后解析响应内容为JSONObject返回 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae HttpResponse response = mHttpClient.execute(httpPost); String jsString = getResponseContent(response.getEntity()); // 获取响应内容 return new JSONObject(jsString); // 返回响应的JSON对象 @@ -376,21 +526,36 @@ public class GTaskClient { } } +<<<<<<< HEAD // 创建任务 +======= + // 创建一个任务并发送到Google Tasks服务器 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public void createTask(Task task) throws NetworkFailureException { commitUpdate(); // 提交更新 try { JSONObject jsPost = new JSONObject(); // 创建JSON对象 JSONArray actionList = new JSONArray(); // 创建动作列表 +<<<<<<< HEAD // 添加创建动作到动作列表 +======= + // 将任务的创建操作添加到操作列表中 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae actionList.put(task.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 添加动作列表到JSON对象 +<<<<<<< HEAD // 添加客户端版本到JSON对象 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 执行POST请求 +======= + // 设置客户端版本号 + jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + + // 发送POST请求并处理响应,获取新创建任务的ID并设置到任务对象中 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae 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 @@ -402,21 +567,36 @@ public class GTaskClient { } } +<<<<<<< HEAD // 创建任务列表 +======= + // 创建一个任务列表并发送到Google Tasks服务器 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public void createTaskList(TaskList tasklist) throws NetworkFailureException { commitUpdate(); // 提交更新 try { JSONObject jsPost = new JSONObject(); // 创建JSON对象 JSONArray actionList = new JSONArray(); // 创建动作列表 +<<<<<<< HEAD // 添加创建动作到动作列表 +======= + // 将任务列表的创建操作添加到操作列表中 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae actionList.put(tasklist.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 添加动作列表到JSON对象 +<<<<<<< HEAD // 添加客户端版本到JSON对象 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 执行POST请求 +======= + // 设置客户端版本号 + jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + + // 发送POST请求并处理响应,获取新创建任务列表的ID并设置到任务列表对象中 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae 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 @@ -428,16 +608,27 @@ public class GTaskClient { } } +<<<<<<< HEAD // 提交更新 +======= + // 提交更新操作,将暂存的更新操作数组发送到服务器 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public void commitUpdate() throws NetworkFailureException { // 如果存在更新数组 if (mUpdateArray != null) { try { JSONObject jsPost = new JSONObject(); // 创建JSON对象 +<<<<<<< HEAD // 添加动作列表到JSON对象 jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); // 添加客户端版本到JSON对象 +======= + // 设置操作列表 + jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); + + // 设置客户端版本号 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); postRequest(jsPost); // 执行POST请求 @@ -450,15 +641,25 @@ public class GTaskClient { } } +<<<<<<< HEAD // 添加更新节点 public void addUpdateNode(Node node) throws NetworkFailureException { if (node != null) { // 更新项目过多可能会导致错误,最多设为10个项目 +======= + // 添加一个节点的更新操作到暂存的更新操作数组中,限制最多10个更新项 + // 将节点的更新操作添加到更新数组中,如果更新数组中的元素超过10个,则先提交更新 + public void addUpdateNode(Node node) throws NetworkFailureException { + if (node != null) { + // 过多的更新项可能会导致错误,所以设置最大更新项数量为10个 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae if (mUpdateArray != null && mUpdateArray.length() > 10) { commitUpdate(); // 提交更新 } + // 如果更新数组为空,则创建一个新的JSONArray对象用于存放更新操作 if (mUpdateArray == null) +<<<<<<< HEAD mUpdateArray = new JSONArray(); // 初始化更新数组 mUpdateArray.put(node.getUpdateAction(getActionId())); // 将更新的节点添加到数组 } @@ -467,11 +668,24 @@ public class GTaskClient { // 移动任务 public void moveTask(Task task, TaskList preParent, TaskList curParent) throws NetworkFailureException { commitUpdate(); // 提交更新 +======= + mUpdateArray = new JSONArray(); + // 将节点的更新操作(通过节点获取,操作ID使用当前的操作ID)添加到更新数组中 + mUpdateArray.put(node.getUpdateAction(getActionId())); + } + } + + // 移动任务,将任务从一个任务列表移动到另一个任务列表(可能是同一个列表内移动位置) + public void moveTask(Task task, TaskList preParent, TaskList curParent) + throws NetworkFailureException { + commitUpdate(); +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae try { JSONObject jsPost = new JSONObject(); // 创建JSON对象 JSONArray actionList = new JSONArray(); // 创建动作列表 JSONObject action = new JSONObject(); // 创建动作对象 +<<<<<<< HEAD // 构建移动动作 action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); @@ -495,21 +709,64 @@ public class GTaskClient { jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); postRequest(jsPost); // 执行POST请求 +======= + // 操作列表相关设置 + // 设置操作类型为移动任务(对应预定义的移动操作类型常量) + action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); + // 设置操作的唯一ID,通过获取下一个可用的操作ID来赋值 + action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); + // 设置要移动的任务的全局唯一ID + action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); + // 如果移动前后的父任务列表相同(即在同一个任务列表内移动),并且任务不是该列表中的第一个任务, + // 则设置其前一个兄弟任务的ID,用于确定移动后的顺序 + if (preParent == curParent && task.getPriorSibling() != null) { + action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); + } + // 设置任务原来所在的任务列表的全局唯一ID(源列表) + action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); + // 设置任务要移动到的目标任务列表的父级任务列表的全局唯一ID(目标父列表) + action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); + // 如果移动是在不同的任务列表之间进行,则设置目标任务列表的全局唯一ID(目标列表) + if (preParent != curParent) { + action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); + } + // 将这个移动操作添加到操作列表中 + actionList.put(action); + // 将操作列表添加到要发送的JSON数据对象中,对应预定义的操作列表的JSON键 + jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + + // 设置客户端版本号,对应预定义的客户端版本号的JSON键 + jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + + // 发送POST请求,将包含移动任务操作信息的JSON数据发送到服务器进行处理 + postRequest(jsPost); +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); +<<<<<<< HEAD throw new ActionFailureException("move task: handing jsonobject failed"); // 抛出JSON处理异常 } } // 删除节点 +======= + // 如果JSON处理出现异常,抛出操作失败异常,并说明是移动任务时处理JSON对象失败 + throw new ActionFailureException("move task: handing jsonobject failed"); + } + } + + // 删除节点(比如任务、任务列表等),通过设置节点的删除标记为true,并将其更新操作发送到服务器来实现删除 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public void deleteNode(Node node) throws NetworkFailureException { commitUpdate(); // 提交更新 try { JSONObject jsPost = new JSONObject(); // 创建JSON对象 JSONArray actionList = new JSONArray(); // 创建动作列表 +<<<<<<< HEAD // 标记节点为已删除并添加到动作列表 node.setDeleted(true); actionList.put(node.getUpdateAction(getActionId())); // 获取节点更新动作并添加到列表 @@ -528,6 +785,32 @@ public class GTaskClient { } // 获取任务列表 +======= + // 操作列表相关设置 + // 将节点标记为已删除 + node.setDeleted(true); + // 将节点的更新操作(此时因为已标记为删除,对应的就是删除操作)添加到操作列表中 + actionList.put(node.getUpdateAction(getActionId())); + // 将操作列表添加到要发送的JSON数据对象中,对应预定义的操作列表的JSON键 + jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + + // 设置客户端版本号,对应预定义的客户端版本号的JSON键 + jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + + // 发送POST请求,将包含删除节点操作信息的JSON数据发送到服务器进行处理 + postRequest(jsPost); + // 发送成功后,将更新数组置空,因为相关更新操作已提交完成 + mUpdateArray = null; + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + // 如果JSON处理出现异常,抛出操作失败异常,并说明是删除节点时处理JSON对象失败 + throw new ActionFailureException("delete node: handing jsonobject failed"); + } + } + + // 获取所有的任务列表信息,前提是已经登录成功,否则会抛出异常提示需要先登录 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public JSONArray getTaskLists() throws NetworkFailureException { if (!mLoggedin) { Log.e(TAG, "please login first"); // 如果未登录,记录错误 @@ -535,11 +818,22 @@ public class GTaskClient { } try { +<<<<<<< HEAD HttpGet httpGet = new HttpGet(mGetUrl); // 创建GET请求 HttpResponse response = mHttpClient.execute(httpGet); // 执行请求 // 获取任务列表 String resString = getResponseContent(response.getEntity()); // 获取响应内容 +======= + // 创建一个HTTP GET请求对象,用于获取任务列表数据,请求的URL是之前设置好的获取数据的URL + HttpGet httpGet = new HttpGet(mGetUrl); + HttpResponse response = null; + // 执行HTTP GET请求,获取服务器的响应 + response = mHttpClient.execute(httpGet); + + // 从响应的实体中获取内容,并进行处理,提取出包含任务列表信息的JSON数据部分 + String resString = getResponseContent(response.getEntity()); +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae String jsBegin = "_setup("; String jsEnd = ")}"; int begin = resString.indexOf(jsBegin); @@ -549,6 +843,7 @@ public class GTaskClient { if (begin != -1 && end != -1 && begin < end) { jsString = resString.substring(begin + jsBegin.length(), end); } +<<<<<<< HEAD JSONObject js = new JSONObject(jsString); // 创建JSON对象 return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); // 返回任务列表 } catch (ClientProtocolException e) { @@ -567,6 +862,30 @@ public class GTaskClient { } // 获取指定任务列表 +======= + JSONObject js = new JSONObject(jsString); + // 从解析后的JSON对象中获取任务列表数组并返回,对应预定义的任务列表的JSON键 + return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); + } catch (ClientProtocolException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + // 如果HTTP协议相关操作出现异常,抛出网络失败异常,并说明是获取任务列表时HTTP GET请求失败 + throw new NetworkFailureException("gettasklists: httpget failed"); + } catch (IOException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + // 如果输入输出操作出现异常,抛出网络失败异常,并说明是获取任务列表时HTTP GET请求失败 + throw new NetworkFailureException("gettasklists: httpget failed"); + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + // 如果JSON处理出现异常,抛出操作失败异常,并说明是获取任务列表时处理JSON对象失败 + throw new ActionFailureException("get task lists: handing jasonobject failed"); + } + } + + // 根据给定的任务列表全局唯一ID,获取该任务列表中的任务信息,需要先提交之前暂存的更新操作 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public JSONArray getTaskList(String listGid) throws NetworkFailureException { commitUpdate(); // 提交更新 try { @@ -574,6 +893,7 @@ public class GTaskClient { JSONArray actionList = new JSONArray(); // 创建动作列表 JSONObject action = new JSONObject(); // 创建动作对象 +<<<<<<< HEAD // 构建获取所有任务的动作 action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); @@ -595,12 +915,52 @@ public class GTaskClient { } // 获取同步账户 +======= + // 操作列表相关设置 + // 设置操作类型为获取所有任务(对应预定义的获取所有任务操作类型常量) + action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); + // 设置操作的唯一ID,通过获取下一个可用的操作ID来赋值 + action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); + // 设置要获取任务的任务列表的全局唯一ID + action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); + // 设置是否获取已删除的任务,这里设置为false,表示不获取已删除任务 + action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); + // 将这个获取任务操作添加到操作列表中 + actionList.put(action); + // 将操作列表添加到要发送的JSON数据对象中,对应预定义的操作列表的JSON键 + jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); + + // 设置客户端版本号,对应预定义的客户端版本号的JSON键 + jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + + // 发送POST请求,将包含获取任务列表中任务操作信息的JSON数据发送到服务器进行处理 + JSONObject jsResponse = postRequest(jsPost); + // 从服务器响应的JSON数据中获取任务数组并返回,对应预定义的任务的JSON键 + return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + // 如果JSON处理出现异常,抛出操作失败异常,并说明是获取任务列表时处理JSON对象失败 + throw new ActionFailureException("get task list: handing jsonobject failed"); + } + } + + // 获取当前用于同步的账户信息 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public Account getSyncAccount() { return mAccount; // 返回账户信息 } +<<<<<<< HEAD // 重置更新数组 public void resetUpdateArray() { mUpdateArray = null; // 清空更新数组 } } +======= + // 重置更新数组,即将其置为空,一般用于清除之前暂存的更新操作相关数据 +public void resetUpdateArray() { + mUpdateArray = null; +} +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java index 9376e78..83b6a11 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java +++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskManager.java @@ -28,6 +28,7 @@ import android.util.Log; // 记录日志的工具类 // 导入项目中的相关类 import net.micode.notes.R; import net.micode.notes.data.Notes; +<<<<<<< HEAD import net.micode.notes.data.Notes.DataColumns; // 数据列常量 import net.micode.notes.data.Notes.NoteColumns; // 笔记列常量 import net.micode.notes.gtask.data.MetaData; // 任务元数据类 @@ -47,3 +48,898 @@ import org.json.JSONObject; // JSON 对象类 import java.util.HashMap; // 哈希表实现 import java.util.HashSet; // 哈希集合实现 import java.util.Iterator; // 迭代器类 +======= +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; + + // 关联的Activity,用于获取授权令牌等操作 + private Activity mActivity; + // 应用上下文 + private Context mContext; + // 用于操作内容提供器,进行数据查询、更新等操作 + private ContentResolver mContentResolver; + // 标记是否正在同步 + private boolean mSyncing; + // 标记同步是否被取消 + private boolean mCancelled; + // 存储Google Tasks任务列表的哈希表,以任务列表的全局唯一ID(GID)为键,任务列表对象为值 + private HashMap mGTaskListHashMap; + // 存储Google Tasks节点(任务、任务列表等)的哈希表,以节点的全局唯一ID(GID)为键,节点对象为值 + private HashMap mGTaskHashMap; + // 存储元数据的哈希表,以相关的全局唯一ID为键,元数据对象为值 + private HashMap mMetaHashMap; + // 元数据对应的任务列表 + private TaskList mMetaList; + // 存储本地已删除记录的ID集合,用于后续清理本地数据 + private HashSet mLocalDeleteIdMap; + // 存储从Google Tasks的全局唯一ID(GID)到本地记录ID(NID)的映射关系 + private HashMap mGidToNid; + // 存储从本地记录ID(NID)到Google Tasks的全局唯一ID(GID)的映射关系 + private HashMap mNidToGid; + + // 私有构造函数,初始化相关成员变量 + private GTaskManager() { + mSyncing = false; + mCancelled = false; + mGTaskListHashMap = new HashMap(); + mGTaskHashMap = new HashMap(); + mMetaHashMap = new HashMap(); + mMetaList = null; + mLocalDeleteIdMap = new HashSet(); + mGidToNid = new HashMap(); + mNidToGid = new HashMap(); + } + + // 获取GTaskManager的单例实例 + public static synchronized GTaskManager getInstance() { + if (mInstance == null) { + mInstance = new GTaskManager(); + } + return mInstance; + } + + // 设置关联的Activity上下文,主要用于获取授权令牌等相关操作 + public synchronized void setActivityContext(Activity activity) { + // used for getting authtoken + mActivity = activity; + } + + // 执行同步操作,与Google Tasks进行数据同步 + 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; + // 清除之前同步相关的数据缓存,如任务列表、节点、元数据等的哈希表以及ID映射关系等 + 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("login google task failed"); + } + } + + // 发布同步进度信息,初始化任务列表阶段 + 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 Tasks任务列表相关信息,包括获取任务列表、元数据列表以及加载任务等操作 + private void initGTaskList() throws NetworkFailureException { + if (mCancelled) + return; + GTaskClient client = GTaskClient.getInstance(); + try { + // 从Google Tasks获取任务列表信息,返回JSON数组形式的数据 + JSONArray jsTaskLists = client.getTaskLists(); + + // 先初始化元数据列表,查找名为特定前缀加上"meta"的任务列表作为元数据列表 + 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); + } + } + } + } + } + + // 如果元数据列表不存在,则创建一个新的元数据列表,并发送到Google Tasks服务器创建对应的任务列表 + 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: handing JSONObject failed"); + } + } + + // 进行内容同步操作,处理本地与Google Tasks之间的数据同步,包括本地删除、文件夹同步、数据库中已有记录的同步等情况 + private void syncContent() throws NetworkFailureException { + int syncType; + Cursor c = null; + String gid; + Node node; + + mLocalDeleteIdMap.clear(); + + if (mCancelled) { + return; + } + + // 处理本地已删除的笔记(记录),查询本地回收站文件夹中的记录,对比Google Tasks中的对应节点进行相应的同步操作 + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type<>? AND parent_id=?)", new String[] { + String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) + }, null); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); + } + + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); + } + } else { + Log.w(TAG, "failed to query trash folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // 同步文件夹相关信息 + syncFolder(); + + // 处理数据库中已存在的笔记(记录),根据其在Google Tasks中的对应情况确定同步类型,然后进行相应的同步操作 + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type=? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); + syncType = node.getSyncAction(c); + } else { + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + // 本地新增情况 + syncType = Node.SYNC_ACTION_ADD_REMOTE; + } else { + // 远程已删除情况 + syncType = Node.SYNC_ACTION_DEL_LOCAL; + } + } + doContentSync(syncType, node, c); + } + } else { + Log.w(TAG, "failed to query existing note in database"); + } + + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // 处理剩余的在Google Tasks中存在但本地未处理的节点,进行相应的同步操作(一般是添加到本地) + Iterator> iter = mGTaskHashMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + node = entry.getValue(); + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + } + + // mCancelled可能被其他线程设置,所以需要逐个检查,清除本地已删除记录的相关数据(如果同步未取消) + if (!mCancelled) { + if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { + throw new ActionFailureException("failed to batch-delete local deleted notes"); + } + } + + // 刷新本地同步ID(如果同步未取消),提交更新并进行相关操作来更新本地记录的同步ID + if (!mCancelled) { + GTaskClient.getInstance().commitUpdate(); + refreshLocalSyncId(); + } + + } + + // 同步文件夹相关信息,包括根文件夹、通话记录文件夹、本地已存在的文件夹以及远程新增的文件夹等情况的处理 + private void syncFolder() throws NetworkFailureException { + Cursor c = null; + String gid; + Node node; + int syncType; + + if (mCancelled) { + return; + } + + // 处理根文件夹的同步情况,根据其在Google Tasks中的对应情况进行相应的同步操作(添加、更新等) + try { + c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, + Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); + if (c != null) { + c.moveToNext(); + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); + mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); + // 对于系统文件夹,仅在必要时更新远程名称 + if (!node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) + doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); + } else { + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); + } + } else { + Log.w(TAG, "failed to query root folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // for call-note文件夹相关的同步操作处理 + // 查询通话记录文件夹在本地数据库中的信息,根据其在Google Tasks中的对应情况进行相应的同步操作(添加、更新等) + // 如果查询到该文件夹信息存在(即游标c不为空且能移动到下一条记录),则进一步处理 + try { + // 通过ContentResolver查询本地数据库中通话记录文件夹的记录信息,指定查询条件为_id等于通话记录文件夹的特定ID + 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()) { + // 获取该文件夹在Google Tasks中对应的全局唯一ID(GID) + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + // 根据GID从存储Google Tasks节点的哈希表中获取对应的节点对象 + node = mGTaskHashMap.get(gid); + if (node != null) { + // 如果节点存在,从哈希表中移除该节点(表示已处理) + mGTaskHashMap.remove(gid); + // 在本地ID到Google Tasks的GID映射表中记录该文件夹的映射关系,键为GID,值为通话记录文件夹的本地ID + mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); + // 在Google Tasks的GID到本地ID映射表中记录映射关系,键为本地ID,值为GID + 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 { + // 如果节点不存在,则执行添加远程节点的操作(意味着本地有该文件夹记录,但在Google Tasks中还未创建对应节点) + 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; + } + } + + // 处理本地已存在的文件夹的同步情况 + // 查询本地除回收站外的文件夹记录信息,根据其在Google Tasks中的对应情况进行相应的同步操作 + try { + // 通过ContentResolver查询本地数据库中符合条件的文件夹记录信息,条件为类型是文件夹且父ID不等于回收站文件夹的ID + 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()) { + // 获取文件夹在Google Tasks中对应的全局唯一ID(GID) + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + // 根据GID从存储Google Tasks节点的哈希表中获取对应的节点对象 + node = mGTaskHashMap.get(gid); + if (node != null) { + // 如果节点存在,从哈希表中移除该节点(表示已处理) + mGTaskHashMap.remove(gid); + // 在本地ID到Google Tasks的GID映射表中记录该文件夹的映射关系,键为GID,值为文件夹的本地ID + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); + // 在Google Tasks的GID到本地ID映射表中记录映射关系,键为本地ID,值为GID + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); + // 获取该节点对应的同步操作类型(根据节点和游标信息判断是添加、更新、删除等哪种类型) + syncType = node.getSyncAction(c); + } else { + // 如果节点不存在,根据其GID是否为空字符串判断是本地新增还是远程删除情况,进而确定同步类型 + 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; + } + } + + // 处理远程新增文件夹的同步情况 + // 遍历存储Google Tasks任务列表的哈希表,对于在Google + // Tasks中存在但本地未处理(还在mGTaskHashMap中)的文件夹,进行添加本地节点的同步操作 + Iterator> iter = mGTaskListHashMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + gid = entry.getKey(); + node = entry.getValue(); + if (mGTaskHashMap.containsKey(gid)) { + mGTaskHashMap.remove(gid); + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + } + } + + // 如果同步未被取消,提交Google Tasks客户端的更新操作(将之前暂存的更新操作发送到服务器执行) + 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; + // 同步类型为删除本地节点,先从元数据哈希表中获取对应的元数据,若存在则调用Google Tasks客户端删除该元数据节点, + // 同时将本地记录的ID添加到本地已删除记录集合中 + 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; + // 同步类型为删除远程节点,先从元数据哈希表中获取对应节点的元数据,若存在则调用Google Tasks客户端删除该元数据节点, + // 然后再删除该节点本身 + case Node.SYNC_ACTION_DEL_REMOTE: + meta = mMetaHashMap.get(node.getGid()); + if (meta != null) { + GTaskClient.getInstance().deleteNode(meta); + } + GTaskClient.getInstance().deleteNode(node); + break; + // 同步类型为更新本地节点,调用相应方法进行本地节点更新操作,并传入节点和游标信息 + case Node.SYNC_ACTION_UPDATE_LOCAL: + updateLocalNode(node, c); + break; + // 同步类型为更新远程节点,调用相应方法进行远程节点更新操作,并传入节点和游标信息 + case Node.SYNC_ACTION_UPDATE_REMOTE: + updateRemoteNode(node, c); + break; + // 同步类型为更新冲突,目前简单采用本地更新的方式(可能后续可以考虑合并修改等更好的处理方式),调用远程节点更新方法 + case Node.SYNC_ACTION_UPDATE_CONFLICT: + // merging both modifications maybe a good idea + // right now just use local update simply + updateRemoteNode(node, c); + break; + // 同步类型为无操作,直接跳过不做任何处理 + case Node.SYNC_ACTION_NONE: + break; + // 同步类型为错误或其他未知类型,抛出操作失败异常,表示遇到了未知的同步操作类型 + case Node.SYNC_ACTION_ERROR: + default: + throw new ActionFailureException("unkown sync action type"); + } + } + + // 添加本地节点的操作方法 + private void addLocalNode(Node node) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote; + // 如果节点是任务列表类型(TaskList) + if (node instanceof TaskList) { + // 如果节点名称是默认根文件夹的名称,创建一个对应根文件夹的SqlNote对象,传入上下文和根文件夹的本地ID + 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对象,传入上下文和通话记录文件夹的本地ID + sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); + } else { + // 其他普通任务列表情况,创建一个新的SqlNote对象,传入上下文 + sqlNote = new SqlNote(mContext); + // 设置SqlNote的内容为节点的本地JSON格式内容(从节点获取) + sqlNote.setContent(node.getLocalJSONFromContent()); + // 设置父ID为根文件夹的本地ID + sqlNote.setParentId(Notes.ID_ROOT_FOLDER); + } + } else { + // 如果节点是普通任务(Task)类型,创建一个新的SqlNote对象,传入上下文 + sqlNote = new SqlNote(mContext); + JSONObject js = node.getLocalJSONFromContent(); + try { + // 如果节点的本地JSON数据中包含特定的笔记头部信息(META_HEAD_NOTE) + if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + if (note.has(NoteColumns.ID)) { + long id = note.getLong(NoteColumns.ID); + // 检查该笔记ID在本地笔记数据库中是否已存在,如果存在则移除该ID(可能需要重新生成新的ID) + if (DataUtils.existInNoteDatabase(mContentResolver, id)) { + // the id is not available, have to create a new one + note.remove(NoteColumns.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); + if (data.has(DataColumns.ID)) { + long dataId = data.getLong(DataColumns.ID); + // 检查该数据ID在本地数据数据库中是否已存在,如果存在则移除该ID(可能需要重新生成新的ID) + if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { + // the data id is not available, have to create + // a new one + data.remove(DataColumns.ID); + } + } + } + + } + } catch (JSONException e) { + Log.w(TAG, e.toString()); + e.printStackTrace(); + } + // 设置SqlNote的内容为处理后的JSON数据 + sqlNote.setContent(js); + + // 获取任务节点的父节点在本地ID到Google Tasks的GID映射表中的对应父ID,如果不存在则抛出异常表示找不到本地父ID + Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); + if (parentId == null) { + Log.e(TAG, "cannot find task's parent id locally"); + throw new ActionFailureException("cannot add local node"); + } + // 设置SqlNote的父ID为获取到的父节点的本地ID + sqlNote.setParentId(parentId.longValue()); + } + + // 设置SqlNote的Google Tasks全局唯一ID(GID)为节点的GID + sqlNote.setGtaskId(node.getGid()); + // 将SqlNote对象提交保存到本地数据库(传入参数false可能表示不触发某些额外的相关操作) + sqlNote.commit(false); + + // 在本地ID到Google Tasks的GID映射表中记录该节点的映射关系,键为节点的GID,值为SqlNote的本地ID + mGidToNid.put(node.getGid(), sqlNote.getId()); + // 在Google Tasks的GID到本地ID映射表中记录映射关系,键为SqlNote的本地ID,值为节点的GID + mNidToGid.put(sqlNote.getId(), node.getGid()); + + // 更新远程元数据,传入节点的GID和对应的SqlNote对象 + updateRemoteMeta(node.getGid(), sqlNote); + } + + // 更新本地节点的操作方法 + private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote; + // 根据传入的上下文和游标信息创建一个新的SqlNote对象,用于更新本地节点信息 + sqlNote = new SqlNote(mContext, c); + // 设置SqlNote的内容为节点的本地JSON格式内容(从节点获取) + sqlNote.setContent(node.getLocalJSONFromContent()); + + Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) + : new Long(Notes.ID_ROOT_FOLDER); + if (parentId == null) { + Log.e(TAG, "cannot find task's parent id locally"); + throw new ActionFailureException("cannot update local node"); + } + // 设置SqlNote的父ID为获取到的父节点的本地ID + sqlNote.setParentId(parentId.longValue()); + // 将更新后的SqlNote对象提交保存到本地数据库(传入参数true可能表示触发某些额外的相关操作) + sqlNote.commit(true); + + // 更新远程元数据,传入节点的GID和对应的SqlNote对象 + updateRemoteMeta(node.getGid(), sqlNote); + } + + // 添加远程节点的操作方法 + private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote = new SqlNote(mContext, c); + Node n; + + // 如果SqlNote对应的是笔记类型(即普通任务) + if (sqlNote.isNoteType()) { + Task task = new Task(); + // 设置任务的内容为从本地SqlNote获取的JSON格式内容 + task.setContentByLocalJSON(sqlNote.getContent()); + + String parentGid = mNidToGid.get(sqlNote.getParentId()); + if (parentGid == null) { + Log.e(TAG, "cannot find task's parent tasklist"); + throw new ActionFailureException("cannot add remote task"); + } + // 将任务添加到对应的父任务列表中(通过本地ID到Google Tasks的GID映射表找到父任务列表) + mGTaskListHashMap.get(parentGid).addChildTask(task); + + // 通过Google Tasks客户端创建该任务(发送到服务器创建对应的任务节点) + GTaskClient.getInstance().createTask(task); + n = (Node) task; + + // 更新远程元数据,传入任务的GID和对应的SqlNote对象 + updateRemoteMeta(task.getGid(), sqlNote); + } else { + TaskList tasklist = null; + + // 构建文件夹名称,先添加特定的前缀,然后根据SqlNote的本地ID判断是否是根文件夹或通话记录文件夹,添加相应的后缀名称, + // 否则添加SqlNote的摘要信息作为后缀 + 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(); + + // 遍历存储Google Tasks任务列表的哈希表(mGTaskListHashMap)的迭代器 + Iterator> iter = mGTaskListHashMap.entrySet().iterator(); + while (iter.hasNext()) { + // 获取哈希表中的每一个键值对,键是任务列表的全局唯一ID(GID),值是对应的任务列表对象(TaskList) + Map.Entry entry = iter.next(); + String gid = entry.getKey(); + TaskList list = entry.getValue(); + + // 判断当前任务列表的名称是否与之前构建的文件夹名称(folderName)相等 + if (list.getName().equals(folderName)) { + // 如果相等,说明找到了对应的任务列表,将其赋值给tasklist变量 + tasklist = list; + // 如果在存储Google Tasks节点的哈希表(mGTaskHashMap)中包含该任务列表的GID,说明已经处理过,将其移除 + if (mGTaskHashMap.containsKey(gid)) { + mGTaskHashMap.remove(gid); + } + // 找到匹配的任务列表后就可以结束循环了 + break; + } + } + + // 如果经过上述查找没有找到匹配的任务列表(tasklist仍为null),则创建一个新的任务列表对象 + if (tasklist == null) { + tasklist = new TaskList(); + // 设置新任务列表的内容为从本地SqlNote获取的JSON格式内容 + tasklist.setContentByLocalJSON(sqlNote.getContent()); + // 通过Google Tasks客户端创建该任务列表(发送到服务器创建对应的任务列表节点) + GTaskClient.getInstance().createTaskList(tasklist); + // 将新创建的任务列表添加到存储Google Tasks任务列表的哈希表中,键为任务列表的GID,值为任务列表对象 + mGTaskListHashMap.put(tasklist.getGid(), tasklist); + } + // 将创建或找到的任务列表对象转换为Node类型并赋值给n(因为Node是更通用的节点类型,TaskList是一种特殊的Node) + n = (Node) tasklist; + } + + // 更新本地笔记相关信息 + // 设置本地SqlNote的Google Tasks全局唯一ID(GID)为n的GID,即与远程任务列表或任务对应的GID保持一致 + sqlNote.setGtaskId(n.getGid()); + // 将SqlNote对象提交保存到本地数据库(传入参数false可能表示不触发某些额外的相关操作,此处用于保存基本的关联信息等) + sqlNote.commit(false); + // 重置本地修改标志(可能表示将本地记录标记为已与远程同步,不再是本地修改状态) + sqlNote.resetLocalModified(); + // 再次提交保存SqlNote对象到本地数据库(传入参数true可能表示触发一些更新相关的额外操作,确保修改标志等信息更新成功) + sqlNote.commit(true); + + // gid-id映射更新 + // 在本地ID到Google Tasks的GID映射表(mGidToNid)中记录该节点(n)的映射关系,键为节点的GID,值为SqlNote的本地ID + mGidToNid.put(n.getGid(), sqlNote.getId()); + // 在Google Tasks的GID到本地ID映射表(mNidToGid)中记录映射关系,键为SqlNote的本地ID,值为节点的GID + mNidToGid.put(sqlNote.getId(), n.getGid()); + } + + // 更新远程节点的操作方法 + private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + // 根据传入的上下文和游标信息创建一个新的SqlNote对象,用于后续操作 + SqlNote sqlNote = new SqlNote(mContext, c); + + // 远程更新操作 + // 设置远程节点(node)的内容为从本地SqlNote获取的JSON格式内容,即将本地修改同步到远程节点 + node.setContentByLocalJSON(sqlNote.getContent()); + // 通过Google Tasks客户端将更新后的节点添加到更新队列中(后续会批量提交更新到服务器) + GTaskClient.getInstance().addUpdateNode(node); + + // 更新远程元数据,传入节点的GID和对应的SqlNote对象 + updateRemoteMeta(node.getGid(), sqlNote); + + // 如果SqlNote对应的是笔记类型(即普通任务),则可能需要处理任务移动相关操作 + if (sqlNote.isNoteType()) { + Task task = (Task) node; + // 获取任务当前的父任务列表 + TaskList preParentList = task.getParent(); + + // 通过本地ID到Google Tasks的GID映射表,根据本地记录的父ID获取当前任务在远程对应的父任务列表的GID + String curParentGid = mNidToGid.get(sqlNote.getParentId()); + if (curParentGid == null) { + Log.e(TAG, "cannot find task's parent tasklist"); + throw new ActionFailureException("cannot update remote task"); + } + // 根据获取到的GID从存储Google Tasks任务列表的哈希表中获取当前任务在远程对应的父任务列表对象 + TaskList curParentList = mGTaskListHashMap.get(curParentGid); + + // 如果当前任务的前后父任务列表不一致,说明任务需要在远程进行移动操作 + if (preParentList != curParentList) { + // 先从原父任务列表中移除该任务 + preParentList.removeChildTask(task); + // 将任务添加到新的父任务列表中 + curParentList.addChildTask(task); + // 通过Google Tasks客户端执行任务移动操作,告知服务器任务在远程的位置变更 + GTaskClient.getInstance().moveTask(task, preParentList, curParentList); + } + } + + // 清除本地修改标志,标记本地记录已同步到远程,不再处于本地修改状态 + sqlNote.resetLocalModified(); + // 将更新后的SqlNote对象提交保存到本地数据库(传入参数true可能表示触发一些更新相关的额外操作,确保修改标志等信息更新成功) + sqlNote.commit(true); + } + + // 更新远程元数据的操作方法 + private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { + if (sqlNote != null && sqlNote.isNoteType()) { + // 从存储元数据的哈希表(mMetaHashMap)中根据GID获取对应的元数据对象(MetaData) + MetaData metaData = mMetaHashMap.get(gid); + if (metaData != null) { + // 如果元数据对象存在,设置其元数据内容,传入GID和从本地SqlNote获取的JSON格式内容 + metaData.setMeta(gid, sqlNote.getContent()); + // 通过Google Tasks客户端将更新后的元数据添加到更新队列中(后续会批量提交更新到服务器) + GTaskClient.getInstance().addUpdateNode(metaData); + } else { + // 如果元数据对象不存在,则创建一个新的元数据对象 + metaData = new MetaData(); + // 设置新元数据对象的元数据内容,传入GID和从本地SqlNote获取的JSON格式内容 + metaData.setMeta(gid, sqlNote.getContent()); + // 将新创建的元数据对象添加到元数据列表(mMetaList)中作为子任务(从逻辑上关联起来) + mMetaList.addChildTask(metaData); + // 将新创建的元数据对象添加到存储元数据的哈希表中,键为GID,值为元数据对象 + mMetaHashMap.put(gid, metaData); + // 通过Google Tasks客户端创建该元数据任务(发送到服务器创建对应的元数据节点) + GTaskClient.getInstance().createTask(metaData); + } + } + } + + // 刷新本地同步ID的操作方法 + private void refreshLocalSyncId() throws NetworkFailureException { + if (mCancelled) { + return; + } + + // 为了获取最新的Google Tasks列表信息,先清空之前存储相关信息的哈希表 + mGTaskHashMap.clear(); + mGTaskListHashMap.clear(); + mMetaHashMap.clear(); + // 重新初始化Google Tasks列表信息(重新获取任务列表、元数据等相关内容) + initGTaskList(); + + Cursor c = null; + try { + // 通过ContentResolver查询本地数据库中除系统类型且不在回收站的笔记记录信息,按照类型降序排列 + 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()) { + // 获取笔记在Google Tasks中对应的全局唯一ID(GID) + String gid = c.getString(SqlNote.GTASK_ID_COLUMN); + // 根据GID从存储Google Tasks节点的哈希表中获取对应的节点对象 + Node node = mGTaskHashMap.get(gid); + if (node != null) { + // 如果节点存在,从哈希表中移除该节点(表示已处理) + mGTaskHashMap.remove(gid); + // 创建一个ContentValues对象用于存放要更新的数据,此处将同步ID设置为节点的最后修改时间 + ContentValues values = new ContentValues(); + values.put(NoteColumns.SYNC_ID, node.getLastModified()); + // 通过ContentResolver更新本地数据库中对应笔记的记录,设置同步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"); + } + } finally { + // 无论查询是否成功,最终都要关闭游标,释放资源,并将游标置空 + if (c != null) { + c.close(); + c = null; + } + } + } + + // 获取同步账户名称的方法,通过Google Tasks客户端获取同步账户对象,并返回其名称 + public String getSyncAccount() { + return GTaskClient.getInstance().getSyncAccount().name; + } + + // 取消同步的方法,将表示同步是否取消的标志(mCancelled)设置为true,其他相关操作可以根据该标志来判断是否停止执行 + public void cancelSync() { + mCancelled = true; + } +} +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae diff --git a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskSyncService.java b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskSyncService.java index 25969d2..189946b 100644 --- a/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskSyncService.java +++ b/src/Notes-master/src/net/micode/notes/gtask/remote/GTaskSyncService.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.os.Bundle; import android.os.IBinder; +<<<<<<< HEAD /** * GTaskSyncService 是一个用于处理 Google 任务同步的服务类。 * 该类扩展自 Android 的 Service 类,负责执行同步操作。 @@ -60,25 +61,78 @@ public class GTaskSyncService extends Service { */ private void startSync() { // 如果没有正在进行的同步任务,则创建并启动新的任务 +======= +// GTaskSyncService类继承自Service,用于管理与Google Tasks的同步相关操作,包括启动同步、取消同步以及广播同步状态等功能 +public class GTaskSyncService extends Service { + + // 用于在Intent中传递同步操作类型的字符串常量,作为键来标识具体的操作类型信息 + public final static String ACTION_STRING_NAME = "sync_action_type"; + + // 表示启动同步操作的整数值常量,用于在根据操作类型进行判断时识别启动同步的意图 + public final static int ACTION_START_SYNC = 0; + + // 表示取消同步操作的整数值常量,用于在根据操作类型进行判断时识别取消同步的意图 + public final static int ACTION_CANCEL_SYNC = 1; + + // 表示无效操作类型的整数值常量,当获取到的操作类型不匹配已知的有效类型时使用该值来标识 + public final static int ACTION_INVALID = 2; + + // 用于广播同步服务相关状态信息的Intent的动作名称,其他组件可以通过注册监听该动作来接收同步服务的广播消息 + public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; + + // 在广播Intent中用于传递是否正在同步状态的键名,对应的值为布尔类型,表示同步操作是否正在进行 + public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; + + // 在广播Intent中用于传递同步进度消息的键名,对应的值为字符串类型,可用于展示同步操作的进度相关文本信息 + public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; + + // 静态变量,用于存储正在执行的同步任务对象(GTaskASyncTask类型),方便在不同方法中对同步任务进行控制和判断状态 + private static GTaskASyncTask mSyncTask = null; + + // 静态变量,用于存储同步进度相关的消息字符串,可通过广播传递给其他关注同步进度的组件 + private static String mSyncProgress = ""; + + // 启动同步的私有方法,用于创建并执行同步任务 + private void startSync() { + // 如果当前没有正在执行的同步任务(mSyncTask为null) +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae if (mSyncTask == null) { + // 创建一个新的GTaskASyncTask对象,传入当前服务上下文(this)以及一个完成监听器(OnCompleteListener) mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { + // 当同步任务完成时会调用的回调方法 public void onComplete() { +<<<<<<< HEAD // 同步任务完成后,重置任务状态并发送广播 +======= + // 将正在执行的同步任务对象置为null,表示同步已结束 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae mSyncTask = null; + // 发送一个空消息的广播(可能用于通知其他组件同步已完成等状态变化) sendBroadcast(""); + // 停止当前服务(因为同步任务已完成,服务的主要工作完成,可以停止自身运行) stopSelf(); } }); +<<<<<<< HEAD // 发送初始广播,表示同步开始 sendBroadcast(""); // 执行异步任务 +======= + // 发送一个空消息的广播(可能在创建任务后就先通知其他组件同步即将开始等情况) + sendBroadcast(""); + // 执行同步任务,开始真正的同步操作流程 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae mSyncTask.execute(); } } +<<<<<<< HEAD /** * 取消当前同步任务的方法 */ +======= + // 取消同步的私有方法,用于取消正在执行的同步任务(如果存在的话) +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae private void cancelSync() { // 如果有正在进行的同步任务,则取消该任务 if (mSyncTask != null) { @@ -86,35 +140,52 @@ public class GTaskSyncService extends Service { } } + // 服务创建时调用的方法,在这里将正在执行的同步任务对象初始化为null,确保服务启动时处于初始状态 @Override public void onCreate() { // 服务创建时,初始化同步任务为空 mSyncTask = null; } + // 当服务通过startService()方法启动时会调用此方法,用于处理传入的Intent并根据不同的操作意图执行相应操作 @Override public int onStartCommand(Intent intent, int flags, int startId) { +<<<<<<< HEAD // 从 Intent 中获取数据 +======= + // 从传入的Intent中获取附加的Bundle数据(通常用于传递额外的参数信息) +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae Bundle bundle = intent.getExtras(); + // 如果获取到的Bundle不为null且包含表示操作类型的键(ACTION_STRING_NAME) if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { + // 根据操作类型的值进行不同的操作分支判断 switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { + // 如果操作类型是启动同步(ACTION_START_SYNC) case ACTION_START_SYNC: // 开始同步操作 startSync(); break; + // 如果操作类型是取消同步(ACTION_CANCEL_SYNC) case ACTION_CANCEL_SYNC: // 取消同步操作 cancelSync(); break; + // 其他未知或不处理的操作类型,直接跳过不做操作 default: break; } +<<<<<<< HEAD // 返回服务的启动模式 +======= + // 返回START_STICKY表示服务在被系统意外终止后会尝试重新创建并保持启动状态(常用于需要持续运行的服务场景) +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae return START_STICKY; } + // 如果不符合上述条件或者操作类型无效等情况,调用父类的onStartCommand方法进行默认处理 return super.onStartCommand(intent, flags, startId); } + // 当系统内存不足时会调用此方法,在这里用于取消正在执行的同步任务(如果存在的话),释放内存资源 @Override public void onLowMemory() { // 当系统内存不足时,取消同步任务 @@ -123,15 +194,23 @@ public class GTaskSyncService extends Service { } } +<<<<<<< HEAD +======= + // 用于服务绑定操作的方法,这里返回null表示不支持绑定操作(如果需要支持绑定,需要返回一个有效的IBinder对象) +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae @Override public IBinder onBind(Intent intent) { // 返回 null 表示不提供绑定服务的功能 return null; } +<<<<<<< HEAD /** * 发送广播以更新同步状态和进度消息 */ +======= + // 发送广播的公共方法,用于向其他组件广播同步服务的相关状态信息,包括同步进度消息、是否正在同步等 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public void sendBroadcast(String msg) { mSyncProgress = msg; // 创建 Intent 用于发送广播 @@ -144,11 +223,17 @@ public class GTaskSyncService extends Service { sendBroadcast(intent); } +<<<<<<< HEAD /** * 启动同步操作的静态方法 */ public static void startSync(Activity activity) { // 设置活动上下文 +======= + // 静态公共方法,用于在外部(比如Activity中)启动同步服务进行同步操作,需要传入对应的Activity作为上下文来启动服务 + public static void startSync(Activity activity) { + // 设置GTaskManager的Activity上下文(可能用于获取相关授权等操作,与同步操作相关联) +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae GTaskManager.getInstance().setActivityContext(activity); Intent intent = new Intent(activity, GTaskSyncService.class); // 添加启动同步的指令 @@ -157,9 +242,13 @@ public class GTaskSyncService extends Service { activity.startService(intent); } +<<<<<<< HEAD /** * 取消同步操作的静态方法 */ +======= + // 静态公共方法,用于在外部(比如其他组件中)取消正在进行的同步操作,需要传入上下文(Context)来启动服务发送取消同步的意图 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public static void cancelSync(Context context) { Intent intent = new Intent(context, GTaskSyncService.class); // 添加取消同步的指令 @@ -168,17 +257,25 @@ public class GTaskSyncService extends Service { context.startService(intent); } +<<<<<<< HEAD /** * 判断当前是否正在同步的静态方法 */ +======= + // 静态公共方法,用于判断当前是否正在进行同步操作,通过检查正在执行的同步任务对象是否为null来返回相应的布尔值结果 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public static boolean isSyncing() { return mSyncTask != null; } +<<<<<<< HEAD /** * 获取当前同步进度的字符串信息 */ +======= + // 静态公共方法,用于获取当前的同步进度相关的字符串消息,返回存储同步进度消息的静态变量的值 +>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae public static String getProgressString() { return mSyncProgress; } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/model/Note.java b/src/Notes-master/src/net/micode/notes/model/Note.java index 6706cf6..6ca854c 100644 --- a/src/Notes-master/src/net/micode/notes/model/Note.java +++ b/src/Notes-master/src/net/micode/notes/model/Note.java @@ -33,7 +33,7 @@ import net.micode.notes.data.Notes.TextNote; import java.util.ArrayList; - +// 代表一个笔记,包含笔记的基本信息和笔记数据 public class Note { private ContentValues mNoteDiffValues; private NoteData mNoteData; @@ -70,36 +70,44 @@ public class Note { mNoteData = new NoteData(); } + // 设置笔记的基本信息 public void setNoteValue(String key, String value) { mNoteDiffValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + // 设置文本笔记的数据 public void setTextData(String key, String value) { mNoteData.setTextData(key, value); } + // 设置文本笔记的数据ID public void setTextDataId(long id) { mNoteData.setTextDataId(id); } + // 获取文本笔记的数据ID public long getTextDataId() { return mNoteData.mTextDataId; } + // 设置通话笔记的数据ID public void setCallDataId(long id) { mNoteData.setCallDataId(id); } + // 设置通话笔记的数据 public void setCallData(String key, String value) { mNoteData.setCallData(key, value); } + // 判断笔记是否被本地修改过 public boolean isLocalModified() { return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); } + // 同步笔记到数据库 public boolean syncNote(Context context, long noteId) { if (noteId <= 0) { throw new IllegalArgumentException("Wrong note id:" + noteId); @@ -130,6 +138,7 @@ public class Note { return true; } + // 笔记数据类,包含文本数据和通话数据 private class NoteData { private long mTextDataId; @@ -148,10 +157,12 @@ public class Note { mCallDataId = 0; } + // 判断笔记数据是否被本地修改过 boolean isLocalModified() { return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; } + // 设置文本数据ID void setTextDataId(long id) { if(id <= 0) { throw new IllegalArgumentException("Text data id should larger than 0"); @@ -159,6 +170,7 @@ public class Note { mTextDataId = id; } + // 设置通话数据ID void setCallDataId(long id) { if (id <= 0) { throw new IllegalArgumentException("Call data id should larger than 0"); @@ -166,18 +178,21 @@ public class Note { mCallDataId = id; } + // 设置通话数据 void setCallData(String key, String value) { mCallDataValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + // 设置文本数据 void setTextData(String key, String value) { mTextDataValues.put(key, value); mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); } + // 将笔记数据推送到内容解析器 Uri pushIntoContentResolver(Context context, long noteId) { /** * Check for safety @@ -250,4 +265,4 @@ public class Note { return null; } } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/model/WorkingNote.java b/src/Notes-master/src/net/micode/notes/model/WorkingNote.java index be081e4..f77e6e6 100644 --- a/src/Notes-master/src/net/micode/notes/model/WorkingNote.java +++ b/src/Notes-master/src/net/micode/notes/model/WorkingNote.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + package net.micode.notes.model; - + import android.appwidget.AppWidgetManager; import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.text.TextUtils; import android.util.Log; - + import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.CallNote; import net.micode.notes.data.Notes.DataColumns; @@ -30,8 +30,8 @@ import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.TextNote; import net.micode.notes.tool.ResourceParser.NoteBgResources; - - + +// 表示正在编辑的笔记类 public class WorkingNote { // Note for the working note private Note mNote; @@ -41,27 +41,28 @@ public class WorkingNote { private String mContent; // Note mode private int mMode; - + private long mAlertDate; - + private long mModifiedDate; - + private int mBgColorId; - + private int mWidgetId; - + private int mWidgetType; - + private long mFolderId; - + private Context mContext; - + private static final String TAG = "WorkingNote"; - + private boolean mIsDeleted; - + private NoteSettingChangedListener mNoteSettingStatusListener; - + + // 数据库查询笔记数据的列 public static final String[] DATA_PROJECTION = new String[] { DataColumns.ID, DataColumns.CONTENT, @@ -71,7 +72,8 @@ public class WorkingNote { DataColumns.DATA3, DataColumns.DATA4, }; - + + // 数据库查询笔记信息的列 public static final String[] NOTE_PROJECTION = new String[] { NoteColumns.PARENT_ID, NoteColumns.ALERTED_DATE, @@ -80,28 +82,28 @@ public class WorkingNote { NoteColumns.WIDGET_TYPE, NoteColumns.MODIFIED_DATE }; - + private static final int DATA_ID_COLUMN = 0; - + private static final int DATA_CONTENT_COLUMN = 1; - + private static final int DATA_MIME_TYPE_COLUMN = 2; - + private static final int DATA_MODE_COLUMN = 3; - + private static final int NOTE_PARENT_ID_COLUMN = 0; - + private static final int NOTE_ALERTED_DATE_COLUMN = 1; - + private static final int NOTE_BG_COLOR_ID_COLUMN = 2; - + private static final int NOTE_WIDGET_ID_COLUMN = 3; - + private static final int NOTE_WIDGET_TYPE_COLUMN = 4; - + private static final int NOTE_MODIFIED_DATE_COLUMN = 5; - - // New note construct + + // 新笔记构造方法 private WorkingNote(Context context, long folderId) { mContext = context; mAlertDate = 0; @@ -113,8 +115,8 @@ public class WorkingNote { mMode = 0; mWidgetType = Notes.TYPE_WIDGET_INVALIDE; } - - // Existing note construct + + // 已有笔记构造方法 private WorkingNote(Context context, long noteId, long folderId) { mContext = context; mNoteId = noteId; @@ -123,12 +125,13 @@ public class WorkingNote { mNote = new Note(); loadNote(); } - + + // 从数据库加载笔记信息 private void loadNote() { Cursor cursor = mContext.getContentResolver().query( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, null, null); - + if (cursor != null) { if (cursor.moveToFirst()) { mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); @@ -145,13 +148,14 @@ public class WorkingNote { } loadNoteData(); } - + + // 从数据库加载笔记数据 private void loadNoteData() { Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { String.valueOf(mNoteId) }, null); - + if (cursor != null) { if (cursor.moveToFirst()) { do { @@ -173,7 +177,8 @@ public class WorkingNote { throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId); } } - + + // 创建一个空笔记 public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, int widgetType, int defaultBgColorId) { WorkingNote note = new WorkingNote(context, folderId); @@ -182,11 +187,13 @@ public class WorkingNote { note.setWidgetType(widgetType); return note; } - + + // 从数据库加载笔记 public static WorkingNote load(Context context, long id) { return new WorkingNote(context, id, 0); } - + + // 保存笔记到数据库 public synchronized boolean saveNote() { if (isWorthSaving()) { if (!existInDatabase()) { @@ -195,9 +202,9 @@ public class WorkingNote { return false; } } - + mNote.syncNote(mContext, mNoteId); - + /** * Update widget content if there exist any widget of this note */ @@ -211,11 +218,13 @@ public class WorkingNote { return false; } } - + + // 判断笔记是否存在于数据库中 public boolean existInDatabase() { return mNoteId > 0; } - + + // 判断笔记是否值得保存 private boolean isWorthSaving() { if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) || (existInDatabase() && !mNote.isLocalModified())) { @@ -224,11 +233,13 @@ public class WorkingNote { return true; } } - + + // 设置笔记信息改变监听器 public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { mNoteSettingStatusListener = l; } - + + // 设置闹钟提醒日期 public void setAlertDate(long date, boolean set) { if (date != mAlertDate) { mAlertDate = date; @@ -238,7 +249,8 @@ public class WorkingNote { mNoteSettingStatusListener.onClockAlertChanged(date, set); } } - + + // 标记笔记是否已删除 public void markDeleted(boolean mark) { mIsDeleted = mark; if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID @@ -246,7 +258,8 @@ public class WorkingNote { mNoteSettingStatusListener.onWidgetChanged(); } } - + + // 设置笔记背景颜色ID public void setBgColorId(int id) { if (id != mBgColorId) { mBgColorId = id; @@ -256,7 +269,8 @@ public class WorkingNote { mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); } } - + + // 设置笔记的检查列表模式 public void setCheckListMode(int mode) { if (mMode != mode) { if (mNoteSettingStatusListener != null) { @@ -266,98 +280,115 @@ public class WorkingNote { mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); } } - + + // 设置笔记的窗口小部件类型 public void setWidgetType(int type) { if (type != mWidgetType) { mWidgetType = type; mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); } } - + + // 设置笔记的窗口小部件ID public void setWidgetId(int id) { if (id != mWidgetId) { mWidgetId = id; mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); } } - + + // 设置工作文本内容 public void setWorkingText(String text) { if (!TextUtils.equals(mContent, text)) { mContent = text; mNote.setTextData(DataColumns.CONTENT, mContent); } } - + + // 将笔记转换为通话笔记 public void convertToCallNote(String phoneNumber, long callDate) { mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); } - + + // 检查笔记是否有闹钟提醒 public boolean hasClockAlert() { return (mAlertDate > 0 ? true : false); } - + + // 获取笔记内容 public String getContent() { return mContent; } - + + // 获取闹钟提醒日期 public long getAlertDate() { return mAlertDate; } - + + // 获取笔记修改日期 public long getModifiedDate() { return mModifiedDate; } - + + // 获取笔记背景颜色资源ID public int getBgColorResId() { return NoteBgResources.getNoteBgResource(mBgColorId); } - + + // 获取笔记背景颜色ID public int getBgColorId() { return mBgColorId; } - + + // 获取笔记标题背景颜色资源ID public int getTitleBgResId() { return NoteBgResources.getNoteTitleBgResource(mBgColorId); } - + + // 获取检查列表模式 public int getCheckListMode() { return mMode; } - + + // 获取笔记ID public long getNoteId() { return mNoteId; } - + + // 获取笔记所在的文件夹ID public long getFolderId() { return mFolderId; } - + + // 获取窗口小部件ID public int getWidgetId() { return mWidgetId; } - + + // 获取窗口小部件类型 public int getWidgetType() { return mWidgetType; } - + + // 笔记信息改变监听器接口 public interface NoteSettingChangedListener { /** * Called when the background color of current note has just changed */ void onBackgroundColorChanged(); - + /** * Called when user set clock */ void onClockAlertChanged(long date, boolean set); - + /** * Call when user create note from widget */ void onWidgetChanged(); - + /** * Call when switch between check list mode and normal mode * @param oldMode is previous mode before change @@ -365,4 +396,4 @@ public class WorkingNote { */ void onCheckListModeChanged(int oldMode, int newMode); } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/tool/BackupUtils.java b/src/Notes-master/src/net/micode/notes/tool/BackupUtils.java index 39f6ec4..c6a1095 100644 --- a/src/Notes-master/src/net/micode/notes/tool/BackupUtils.java +++ b/src/Notes-master/src/net/micode/notes/tool/BackupUtils.java @@ -13,78 +13,84 @@ * 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"; - // Singleton stuff + // 单例实例 private static BackupUtils sInstance; - + + // 获取BackupUtils的单例实例 public static synchronized BackupUtils getInstance(Context context) { if (sInstance == null) { sInstance = new BackupUtils(context); } return sInstance; } - + /** - * Following states are signs to represents backup or restore - * status + * 以下状态码表示备份或恢复的状态 */ - // Currently, the sdcard is not mounted + // SD卡未挂载 public static final int STATE_SD_CARD_UNMOUONTED = 0; - // The backup file not exist + // 备份文件不存在 public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; - // The data is not well formated, may be changed by other programs + // 数据损坏或格式不正确 public static final int STATE_DATA_DESTROIED = 2; - // Some run-time exception which causes restore or backup fails + // 系统错误导致备份或恢复失败 public static final int STATE_SYSTEM_ERROR = 3; - // Backup or restore success + // 备份或恢复成功 public static final int STATE_SUCCESS = 4; - + private TextExport mTextExport; - + + // 构造函数,初始化TextExport实例 private BackupUtils(Context context) { mTextExport = new TextExport(context); } - + + // 检查外部存储是否可用 private static boolean externalStorageAvailable() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } - + + // 导出笔记数据为文本文件 public int exportToText() { return mTextExport.exportToText(); } - + + // 获取导出的文本文件名 public String getExportedTextFileName() { return mTextExport.mFileName; } - + + // 获取导出的文本文件目录 public String getExportedTextFileDir() { return mTextExport.mFileDirectory; } - + + // 文本导出类,用于将笔记数据导出为文本格式 private static class TextExport { private static final String[] NOTE_PROJECTION = { NoteColumns.ID, @@ -92,13 +98,13 @@ public class BackupUtils { NoteColumns.SNIPPET, NoteColumns.TYPE }; - + 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, @@ -107,53 +113,53 @@ public class BackupUtils { DataColumns.DATA3, DataColumns.DATA4, }; - + 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; - + + // 构造函数,初始化导出格式和上下文 public TextExport(Context context) { TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); mContext = context; mFileName = ""; mFileDirectory = ""; } - + + // 根据ID获取导出格式字符串 private String getFormat(int id) { return TEXT_FORMAT[id]; } - - /** - * Export the folder identified by folder id to text - */ + + // 导出指定文件夹下的笔记到文本文件 private void exportFolderToText(String folderId, PrintStream ps) { - // Query notes belong to this folder + // 查询属于该文件夹的笔记 Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { folderId }, null); - + if (notesCursor != null) { if (notesCursor.moveToFirst()) { do { - // Print note's last modified date + // 打印笔记的最后修改日期 ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( mContext.getString(R.string.format_datetime_mdhm), notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // Query data belong to this note + // 查询属于该笔记的数据 String noteId = notesCursor.getString(NOTE_COLUMN_ID); exportNoteToText(noteId, ps); } while (notesCursor.moveToNext()); @@ -161,35 +167,33 @@ public class BackupUtils { notesCursor.close(); } } - - /** - * Export note identified by id to a print stream - */ + + // 导出指定笔记到文本输出流 private void exportNoteToText(String noteId, PrintStream ps) { Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { noteId }, null); - + if (dataCursor != null) { if (dataCursor.moveToFirst()) { do { String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); if (DataConstants.CALL_NOTE.equals(mimeType)) { - // Print phone number + // 打印电话号码 String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); String location = dataCursor.getString(DATA_COLUMN_CONTENT); - + if (!TextUtils.isEmpty(phoneNumber)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), phoneNumber)); } - // Print call date + // 打印通话日期 ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat .format(mContext.getString(R.string.format_datetime_mdhm), callDate))); - // Print call attachment location + // 打印通话附件位置 if (!TextUtils.isEmpty(location)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), location)); @@ -205,7 +209,7 @@ public class BackupUtils { } dataCursor.close(); } - // print a line separator between note + // 打印笔记之间的分隔符 try { ps.write(new byte[] { Character.LINE_SEPARATOR, Character.LETTER_NUMBER @@ -214,33 +218,31 @@ public class BackupUtils { Log.e(TAG, e.toString()); } } - - /** - * Note will be exported as text which is user readable - */ + + // 将笔记数据导出为用户可读的文本文件 public int exportToText() { if (!externalStorageAvailable()) { Log.d(TAG, "Media was not mounted"); return STATE_SD_CARD_UNMOUONTED; } - + PrintStream ps = getExportToTextPrintStream(); if (ps == null) { Log.e(TAG, "get print stream error"); return STATE_SYSTEM_ERROR; } - // First export folder and its notes + // 首先导出文件夹及其笔记 Cursor folderCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); - + if (folderCursor != null) { if (folderCursor.moveToFirst()) { do { - // Print folder's name + // 打印文件夹名称 String folderName = ""; if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { folderName = mContext.getString(R.string.call_record_folder_name); @@ -256,21 +258,21 @@ public class BackupUtils { } folderCursor.close(); } - - // Export notes in root's folder + + // 导出根文件夹下的笔记 Cursor noteCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + "=0", null, null); - + if (noteCursor != null) { if (noteCursor.moveToFirst()) { do { ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( mContext.getString(R.string.format_datetime_mdhm), noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // Query data belong to this note + // 查询属于该笔记的数据 String noteId = noteCursor.getString(NOTE_COLUMN_ID); exportNoteToText(noteId, ps); } while (noteCursor.moveToNext()); @@ -278,13 +280,11 @@ public class BackupUtils { noteCursor.close(); } ps.close(); - + return STATE_SUCCESS; } - - /** - * Get a print stream pointed to the file {@generateExportedTextFile} - */ + + // 获取指向导出文本文件的PrintStream private PrintStream getExportToTextPrintStream() { File file = generateFileMountedOnSDcard(mContext, R.string.file_path, R.string.file_name_txt_format); @@ -308,10 +308,8 @@ public class BackupUtils { return ps; } } - - /** - * Generate the text file to store imported data - */ + + // 生成用于存储导入数据的文本文件 private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { StringBuilder sb = new StringBuilder(); sb.append(Environment.getExternalStorageDirectory()); @@ -322,7 +320,7 @@ public class BackupUtils { DateFormat.format(context.getString(R.string.format_date_ymd), System.currentTimeMillis()))); File file = new File(sb.toString()); - + try { if (!filedir.exists()) { filedir.mkdir(); @@ -336,9 +334,7 @@ public class BackupUtils { } catch (IOException e) { e.printStackTrace(); } - + return null; } -} - - +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/tool/DataUtils.java b/src/Notes-master/src/net/micode/notes/tool/DataUtils.java index 2a14982..7de4569 100644 --- a/src/Notes-master/src/net/micode/notes/tool/DataUtils.java +++ b/src/Notes-master/src/net/micode/notes/tool/DataUtils.java @@ -34,9 +34,11 @@ import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; import java.util.ArrayList; import java.util.HashSet; - +// 数据工具类,提供批量删除笔记、移动笔记到文件夹等操作 public class DataUtils { public static final String TAG = "DataUtils"; + + // 批量删除笔记 public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { if (ids == null) { Log.d(TAG, "the ids is null"); @@ -72,6 +74,7 @@ public class DataUtils { return false; } + // 将笔记移动到指定文件夹 public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { ContentValues values = new ContentValues(); values.put(NoteColumns.PARENT_ID, desFolderId); @@ -80,6 +83,7 @@ public class DataUtils { resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); } + // 批量将笔记移动到指定文件夹 public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids, long folderId) { if (ids == null) { @@ -112,7 +116,7 @@ public class DataUtils { } /** - * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} + * 获取用户文件夹(非系统文件夹)的数量 */ public static int getUserFolderCount(ContentResolver resolver) { Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, @@ -136,6 +140,7 @@ public class DataUtils { return count; } + // 检查指定类型的笔记是否可见 public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, @@ -153,6 +158,7 @@ public class DataUtils { return exist; } + // 检查笔记是否存在于数据库中 public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, null, null, null); @@ -167,6 +173,7 @@ public class DataUtils { return exist; } + // 检查数据是否存在于数据库中 public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null, null, null, null); @@ -181,6 +188,7 @@ public class DataUtils { return exist; } + // 检查指定名称的用户文件夹是否存在 public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + @@ -197,6 +205,7 @@ public class DataUtils { return exist; } + // 获取指定文件夹下的笔记小部件信息 public static HashSet getFolderNoteWidget(ContentResolver resolver, long folderId) { Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, @@ -224,6 +233,7 @@ public class DataUtils { return set; } + // 根据笔记ID获取通话号码 public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, new String [] { CallNote.PHONE_NUMBER }, @@ -243,6 +253,7 @@ public class DataUtils { return ""; } + // 根据通话号码和通话日期获取笔记ID public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, new String [] { CallNote.NOTE_ID }, @@ -264,6 +275,7 @@ public class DataUtils { return 0; } + // 根据笔记ID获取笔记摘要 public static String getSnippetById(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, new String [] { NoteColumns.SNIPPET }, @@ -282,6 +294,7 @@ public class DataUtils { throw new IllegalArgumentException("Note is not found with id: " + noteId); } + // 格式化笔记摘要 public static String getFormattedSnippet(String snippet) { if (snippet != null) { snippet = snippet.trim(); @@ -292,4 +305,4 @@ public class DataUtils { } return snippet; } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/tool/GTaskStringUtils.java b/src/Notes-master/src/net/micode/notes/tool/GTaskStringUtils.java index 666b729..8903dd7 100644 --- a/src/Notes-master/src/net/micode/notes/tool/GTaskStringUtils.java +++ b/src/Notes-master/src/net/micode/notes/tool/GTaskStringUtils.java @@ -13,101 +13,102 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + package net.micode.notes.tool; - + +// 此类用于定义和存储GTask相关的JSON字段名常量 public class GTaskStringUtils { - + public final static String GTASK_JSON_ACTION_ID = "action_id"; - + public final static String GTASK_JSON_ACTION_LIST = "action_list"; - + public final static String GTASK_JSON_ACTION_TYPE = "action_type"; - + public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; - + public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; - + public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; - + public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; - + public final static String GTASK_JSON_CREATOR_ID = "creator_id"; - + public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; - + public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; - + public final static String GTASK_JSON_COMPLETED = "completed"; - + public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; - + public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; - + public final static String GTASK_JSON_DELETED = "deleted"; - + public final static String GTASK_JSON_DEST_LIST = "dest_list"; - + public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; - + public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; - + public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; - + public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; - + public final static String GTASK_JSON_GET_DELETED = "get_deleted"; - + public final static String GTASK_JSON_ID = "id"; - + public final static String GTASK_JSON_INDEX = "index"; - + public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; - + public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; - + public final static String GTASK_JSON_LIST_ID = "list_id"; - + public final static String GTASK_JSON_LISTS = "lists"; - + public final static String GTASK_JSON_NAME = "name"; - + public final static String GTASK_JSON_NEW_ID = "new_id"; - + public final static String GTASK_JSON_NOTES = "notes"; - + public final static String GTASK_JSON_PARENT_ID = "parent_id"; - + public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; - + public final static String GTASK_JSON_RESULTS = "results"; - + public final static String GTASK_JSON_SOURCE_LIST = "source_list"; - + public final static String GTASK_JSON_TASKS = "tasks"; - + public final static String GTASK_JSON_TYPE = "type"; - + public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; - + public final static String GTASK_JSON_TYPE_TASK = "TASK"; - + public final static String GTASK_JSON_USER = "user"; - + public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; - + public final static String FOLDER_DEFAULT = "Default"; - + public final static String FOLDER_CALL_NOTE = "Call_Note"; - + public final static String FOLDER_META = "METADATA"; - + public final static String META_HEAD_GTASK_ID = "meta_gid"; - + public final static String META_HEAD_NOTE = "meta_note"; - + public final static String META_HEAD_DATA = "meta_data"; - + public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; - -} + +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/tool/ResourceParser.java b/src/Notes-master/src/net/micode/notes/tool/ResourceParser.java index 1ad3ad6..b813795 100644 --- a/src/Notes-master/src/net/micode/notes/tool/ResourceParser.java +++ b/src/Notes-master/src/net/micode/notes/tool/ResourceParser.java @@ -13,32 +13,37 @@ * 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; - + public class ResourceParser { - + + // 定义笔记背景颜色的常量 public static final int YELLOW = 0; public static final int BLUE = 1; public static final int WHITE = 2; public static final int GREEN = 3; public static final int RED = 4; - + + // 默认笔记背景颜色 public static final int 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; - + + // 用于获取编辑笔记时的背景资源 public static class NoteBgResources { private final static int [] BG_EDIT_RESOURCES = new int [] { R.drawable.edit_yellow, @@ -47,7 +52,7 @@ public class ResourceParser { R.drawable.edit_green, R.drawable.edit_red }; - + private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { R.drawable.edit_title_yellow, R.drawable.edit_title_blue, @@ -55,16 +60,19 @@ public class ResourceParser { 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 public static int getDefaultBgId(Context context) { if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { @@ -73,7 +81,8 @@ public class ResourceParser { return BG_DEFAULT_COLOR; } } - + + // 用于获取笔记列表项的背景资源 public static class NoteItemBgResources { private final static int [] BG_FIRST_RESOURCES = new int [] { R.drawable.list_yellow_up, @@ -82,15 +91,15 @@ public class ResourceParser { R.drawable.list_green_up, R.drawable.list_red_up }; - + 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 + R.drawable.list_red_middle, }; - + private final static int [] BG_LAST_RESOURCES = new int [] { R.drawable.list_yellow_down, R.drawable.list_blue_down, @@ -98,7 +107,7 @@ public class ResourceParser { R.drawable.list_green_down, R.drawable.list_red_down, }; - + private final static int [] BG_SINGLE_RESOURCES = new int [] { R.drawable.list_yellow_single, R.drawable.list_blue_single, @@ -106,28 +115,34 @@ public class ResourceParser { 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]; } - + + // 获取文件夹背景资源 public static int getFolderBgRes() { return R.drawable.list_folder; } } - + + // 用于获取小部件的背景资源 public static class WidgetBgResources { private final static int [] BG_2X_RESOURCES = new int [] { R.drawable.widget_2x_yellow, @@ -136,11 +151,12 @@ public class ResourceParser { R.drawable.widget_2x_green, R.drawable.widget_2x_red, }; - + + // 根据ID获取2x小部件的背景资源 public static int getWidget2xBgResource(int id) { return BG_2X_RESOURCES[id]; } - + private final static int [] BG_4X_RESOURCES = new int [] { R.drawable.widget_4x_yellow, R.drawable.widget_4x_blue, @@ -148,12 +164,14 @@ public class ResourceParser { R.drawable.widget_4x_green, R.drawable.widget_4x_red }; - + + // 根据ID获取4x小部件的背景资源 public static int getWidget4xBgResource(int id) { return BG_4X_RESOURCES[id]; } } - + + // 用于获取文本外观资源 public static class TextAppearanceResources { private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { R.style.TextAppearanceNormal, @@ -161,7 +179,8 @@ public class ResourceParser { R.style.TextAppearanceLarge, R.style.TextAppearanceSuper }; - + + // 根据ID获取文本外观资源 public static int getTexAppearanceResource(int id) { /** * HACKME: Fix bug of store the resource id in shared preference. @@ -173,9 +192,10 @@ public class ResourceParser { } return TEXTAPPEARANCE_RESOURCES[id]; } - + + // 获取文本外观资源的数量 public static int getResourcesSize() { return TEXTAPPEARANCE_RESOURCES.length; } } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java b/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java index 85723be..5f40071 100644 --- a/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java @@ -39,21 +39,29 @@ import net.micode.notes.tool.DataUtils; import java.io.IOException; - +/** + * 闹钟提醒活动类,继承自Activity,实现了OnClickListener和OnDismissListener接口。 + * 该活动用于在闹钟响起时显示提醒对话框,并播放默认的闹钟声音。 + */ public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { - private long mNoteId; - private String mSnippet; - private static final int SNIPPET_PREW_MAX_LEN = 60; - MediaPlayer mPlayer; - + private long mNoteId; // 保存笔记的ID + private String mSnippet; // 保存笔记的摘要 + private static final int SNIPPET_PREW_MAX_LEN = 60; // 笔记摘要的最大长度 + MediaPlayer mPlayer; // 用于播放闹钟声音的MediaPlayer实例 + + /** + * 创建活动时调用的方法。 + * @param savedInstanceState 保存的实例状态数据包 + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); + requestWindowFeature(Window.FEATURE_NO_TITLE); // 请求无标题窗口 - final Window win = getWindow(); - win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + final Window win = getWindow(); // 获取当前窗口 + win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); // 设置窗口在锁屏时显示 + // 如果屏幕未点亮,则添加更多标志以保持屏幕点亮并允许在锁屏时操作 if (!isScreenOn()) { win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON @@ -61,98 +69,123 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); } - Intent intent = getIntent(); + Intent intent = getIntent(); // 获取启动该活动的Intent try { - mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); - mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); + mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); // 从Intent中提取笔记ID + mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); // 根据笔记ID获取摘要 + // 如果摘要长度超过最大限制,则截取前SNIPPET_PREW_MAX_LEN个字符并添加省略号 mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) : mSnippet; } catch (IllegalArgumentException e) { - e.printStackTrace(); - return; + e.printStackTrace(); // 打印异常信息 + return; // 如果发生异常,则结束该方法 } - mPlayer = new MediaPlayer(); + mPlayer = new MediaPlayer(); // 创建MediaPlayer实例 + // 检查数据库中是否存在指定ID的笔记 if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { - showActionDialog(); - playAlarmSound(); + showActionDialog(); // 显示操作对话框 + playAlarmSound(); // 播放闹钟声音 } else { - finish(); + finish(); // 如果笔记不存在,则结束该活动 } } + /** + * 检查屏幕是否点亮。 + * @return 如果屏幕点亮则返回true,否则返回false + */ private boolean isScreenOn() { - PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - return pm.isScreenOn(); + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); // 获取电源管理服务 + return pm.isScreenOn(); // 返回屏幕是否点亮的状态 } + /** + * 播放闹钟声音的方法。 + */ private void playAlarmSound() { + // 获取实际的默认闹钟铃声URI Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + // 获取当前设置的静音模式影响的音频流类型 int silentModeStreams = Settings.System.getInt(getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + // 如果静音模式影响了闹钟音频流,则设置MediaPlayer的音频流类型为silentModeStreams + // 否则,设置为AudioManager.STREAM_ALARM if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { mPlayer.setAudioStreamType(silentModeStreams); } else { mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); } try { - mPlayer.setDataSource(this, url); - mPlayer.prepare(); - mPlayer.setLooping(true); - mPlayer.start(); + mPlayer.setDataSource(this, url); // 设置MediaPlayer的数据源为闹钟铃声URI + mPlayer.prepare(); // 准备MediaPlayer + mPlayer.setLooping(true); // 设置MediaPlayer循环播放 + mPlayer.start(); // 开始播放闹钟声音 } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + e.printStackTrace(); // 打印异常信息 } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + e.printStackTrace(); // 打印异常信息 } catch (IllegalStateException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + e.printStackTrace(); // 打印异常信息 } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + e.printStackTrace(); // 打印异常信息 } } + /** + * 显示操作对话框的方法。 + */ private void showActionDialog() { - AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle(R.string.app_name); - dialog.setMessage(mSnippet); - dialog.setPositiveButton(R.string.notealert_ok, this); + AlertDialog.Builder dialog = new AlertDialog.Builder(this); // 创建AlertDialog.Builder实例 + dialog.setTitle(R.string.app_name); // 设置对话框标题为应用名称 + dialog.setMessage(mSnippet); // 设置对话框消息为笔记摘要 + dialog.setPositiveButton(R.string.notealert_ok, this); // 设置确定按钮及其监听器 + // 如果屏幕已点亮,则添加进入按钮及其监听器 if (isScreenOn()) { dialog.setNegativeButton(R.string.notealert_enter, this); } - dialog.show().setOnDismissListener(this); + dialog.show().setOnDismissListener(this); // 显示对话框并设置对话框消失监听器 } + /** + * 实现OnClickListener接口的方法,处理对话框按钮点击事件。 + * @param dialog 触发点击事件的对话框 + * @param which 点击的按钮的ID + */ public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_NEGATIVE: - Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_UID, mNoteId); - startActivity(intent); + Intent intent = new Intent(this, NoteEditActivity.class); // 创建Intent准备跳转到笔记编辑活动 + intent.setAction(Intent.ACTION_VIEW); // 设置Intent动作类型为ACTION_VIEW + intent.putExtra(Intent.EXTRA_UID, mNoteId); // 添加笔记ID作为附加信息 + startActivity(intent); // 启动笔记编辑活动 break; default: break; } } + /** + * 实现OnDismissListener接口的方法,处理对话框消失事件。 + * @param dialog 消失的对话框 + */ public void onDismiss(DialogInterface dialog) { - stopAlarmSound(); - finish(); + stopAlarmSound(); // 停止播放闹钟声音 + finish(); // 结束该活动 } + /** + * 停止并释放MediaPlayer的方法。 + */ private void stopAlarmSound() { if (mPlayer != null) { - mPlayer.stop(); - mPlayer.release(); - mPlayer = null; + mPlayer.stop(); // 停止MediaPlayer播放 + mPlayer.release(); // 释放MediaPlayer资源 + mPlayer = null; // 将mPlayer置为null } } } diff --git a/src/Notes-master/src/net/micode/notes/ui/AlarmInitReceiver.java b/src/Notes-master/src/net/micode/notes/ui/AlarmInitReceiver.java index f221202..f8bb98a 100644 --- a/src/Notes-master/src/net/micode/notes/ui/AlarmInitReceiver.java +++ b/src/Notes-master/src/net/micode/notes/ui/AlarmInitReceiver.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + package net.micode.notes.ui; - + import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -23,21 +23,24 @@ import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.database.Cursor; - + import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; - - + +// 该广播接收器用于初始化闹钟,查询即将提醒的笔记并设置闹钟 public class AlarmInitReceiver extends BroadcastReceiver { - + + // 查询笔记数据库时使用的列投影 private static final String [] PROJECTION = new String [] { NoteColumns.ID, NoteColumns.ALERTED_DATE }; - + + // 列索引常量,用于从查询结果中提取数据 private static final int COLUMN_ID = 0; private static final int COLUMN_ALERTED_DATE = 1; - + + // 接收广播时执行的方法,查询数据库并设置闹钟 @Override public void onReceive(Context context, Intent intent) { long currentDate = System.currentTimeMillis(); @@ -46,7 +49,7 @@ public class AlarmInitReceiver extends BroadcastReceiver { NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, new String[] { String.valueOf(currentDate) }, null); - + if (c != null) { if (c.moveToFirst()) { do { @@ -62,4 +65,4 @@ public class AlarmInitReceiver extends BroadcastReceiver { c.close(); } } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java b/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java index 54e503b..3d2b949 100644 --- a/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java +++ b/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java @@ -13,18 +13,23 @@ * 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; - + +// 一个继承自BroadcastReceiver的类,用于接收闹钟触发的广播 public class AlarmReceiver extends BroadcastReceiver { + // 当接收到广播时,该方法会被调用 @Override public void onReceive(Context context, Intent intent) { + // 设置Intent的目标Activity为AlarmAlertActivity intent.setClass(context, AlarmAlertActivity.class); + // 添加标志以允许启动新的Activity intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // 启动AlarmAlertActivity context.startActivity(intent); } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java b/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java index 496b0cd..9601ff7 100644 --- a/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java +++ b/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java @@ -1,485 +1,745 @@ -/* - * 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 { - - private static final boolean DEFAULT_ENABLE_STATE = true; - - private static final int HOURS_IN_HALF_DAY = 12; - private static final int HOURS_IN_ALL_DAY = 24; - private static final int DAYS_IN_ALL_WEEK = 7; - private static final int DATE_SPINNER_MIN_VAL = 0; - private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; - private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; - private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; - private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; - private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; - private static final int MINUT_SPINNER_MIN_VAL = 0; - private static final int MINUT_SPINNER_MAX_VAL = 59; - private static final int AMPM_SPINNER_MIN_VAL = 0; - private static final int AMPM_SPINNER_MAX_VAL = 1; - - private final NumberPicker mDateSpinner; - private final NumberPicker mHourSpinner; - private final NumberPicker mMinuteSpinner; - private final NumberPicker mAmPmSpinner; - private Calendar mDate; - - private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; - - private boolean mIsAm; - - private boolean mIs24HourView; - - 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) { - if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { - cal.setTimeInMillis(mDate.getTimeInMillis()); - cal.add(Calendar.DAY_OF_YEAR, 1); - isDateChanged = true; - } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { - cal.setTimeInMillis(mDate.getTimeInMillis()); - cal.add(Calendar.DAY_OF_YEAR, -1); - isDateChanged = true; - } - if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || - oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { - mIsAm = !mIsAm; - updateAmPmControl(); - } - } else { - if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { - cal.setTimeInMillis(mDate.getTimeInMillis()); - cal.add(Calendar.DAY_OF_YEAR, 1); - isDateChanged = true; - } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { - cal.setTimeInMillis(mDate.getTimeInMillis()); - cal.add(Calendar.DAY_OF_YEAR, -1); - isDateChanged = true; - } - } - int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); - mDate.set(Calendar.HOUR_OF_DAY, newHour); - onDateTimeChanged(); - if (isDateChanged) { - setCurrentYear(cal.get(Calendar.YEAR)); - setCurrentMonth(cal.get(Calendar.MONTH)); - setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); - } - } - }; - - private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { - @Override - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - int minValue = mMinuteSpinner.getMinValue(); - int maxValue = mMinuteSpinner.getMaxValue(); - int offset = 0; - 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(); - if (newHour >= HOURS_IN_HALF_DAY) { - mIsAm = false; - updateAmPmControl(); - } else { - mIsAm = true; - updateAmPmControl(); - } - } - mDate.set(Calendar.MINUTE, newVal); - onDateTimeChanged(); - } - }; - - private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { - @Override - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - mIsAm = !mIsAm; - if (mIsAm) { - mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); - } else { - mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); - } - updateAmPmControl(); - 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)); - } - - public DateTimePicker(Context context, long date, boolean is24HourView) { - super(context); - mDate = Calendar.getInstance(); - mInitialising = true; - mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; - inflate(context, R.layout.datetime_picker, this); - - mDateSpinner = (NumberPicker) findViewById(R.id.date); - mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); - mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); - mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); - - mHourSpinner = (NumberPicker) findViewById(R.id.hour); - mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); - mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); - mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); - mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); - mMinuteSpinner.setOnLongPressUpdateInterval(100); - mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); - - String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); - mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); - mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); - mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); - mAmPmSpinner.setDisplayedValues(stringsForAmPm); - mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); - - // update controls to initial state - updateDateControl(); - updateHourControl(); - updateAmPmControl(); - - set24HourView(is24HourView); - - // set to current time - setCurrentDate(date); - - setEnabled(isEnabled()); - - // set the content descriptions - 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; - } - - /** - * Get the current date in millis - * - * @return the current date in millis - */ - public long getCurrentDateInTimeMillis() { - return mDate.getTimeInMillis(); - } - - /** - * Set the current date - * - * @param date The current date in millis - */ - public void setCurrentDate(long date) { - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(date); - setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), - cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); - } - - /** - * Set the current date - * - * @param year The current year - * @param month The current month - * @param dayOfMonth The current dayOfMonth - * @param hourOfDay The current hourOfDay - * @param minute The current minute - */ - public void setCurrentDate(int year, int month, - int dayOfMonth, int hourOfDay, int minute) { - setCurrentYear(year); - setCurrentMonth(month); - setCurrentDay(dayOfMonth); - setCurrentHour(hourOfDay); - setCurrentMinute(minute); - } - - /** - * Get current year - * - * @return The current year - */ - public int getCurrentYear() { - return mDate.get(Calendar.YEAR); - } - - /** - * Set current year - * - * @param year The current year - */ - public void setCurrentYear(int year) { - if (!mInitialising && year == getCurrentYear()) { - return; - } - mDate.set(Calendar.YEAR, year); - updateDateControl(); - onDateTimeChanged(); - } - - /** - * Get current month in the year - * - * @return The current month in the year - */ - public int getCurrentMonth() { - return mDate.get(Calendar.MONTH); - } - - /** - * Set current month in the year - * - * @param month The month in the year - */ - public void setCurrentMonth(int month) { - if (!mInitialising && month == getCurrentMonth()) { - return; - } - mDate.set(Calendar.MONTH, month); - updateDateControl(); - onDateTimeChanged(); - } - - /** - * Get current day of the month - * - * @return The day of the month - */ - public int getCurrentDay() { - return mDate.get(Calendar.DAY_OF_MONTH); - } - - /** - * Set current day of the month - * - * @param dayOfMonth The day of the month - */ - public void setCurrentDay(int dayOfMonth) { - if (!mInitialising && dayOfMonth == getCurrentDay()) { - return; - } - mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); - updateDateControl(); - onDateTimeChanged(); - } - - /** - * Get current hour in 24 hour mode, in the range (0~23) - * @return The current hour in 24 hour mode - */ - public int getCurrentHourOfDay() { - return mDate.get(Calendar.HOUR_OF_DAY); - } - - private int getCurrentHour() { - if (mIs24HourView){ - return getCurrentHourOfDay(); - } else { - int hour = getCurrentHourOfDay(); - if (hour > HOURS_IN_HALF_DAY) { - return hour - HOURS_IN_HALF_DAY; - } else { - return hour == 0 ? HOURS_IN_HALF_DAY : hour; - } - } - } - - /** - * Set current hour in 24 hour mode, in the range (0~23) - * - * @param hourOfDay - */ - public void setCurrentHour(int hourOfDay) { - if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { - return; - } - mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); - if (!mIs24HourView) { - if (hourOfDay >= HOURS_IN_HALF_DAY) { - mIsAm = false; - if (hourOfDay > HOURS_IN_HALF_DAY) { - hourOfDay -= HOURS_IN_HALF_DAY; - } - } else { - mIsAm = true; - if (hourOfDay == 0) { - hourOfDay = HOURS_IN_HALF_DAY; - } - } - updateAmPmControl(); - } - mHourSpinner.setValue(hourOfDay); - onDateTimeChanged(); - } - - /** - * Get currentMinute - * - * @return The Current Minute - */ - public int getCurrentMinute() { - return mDate.get(Calendar.MINUTE); - } - - /** - * Set current minute - */ - public void setCurrentMinute(int minute) { - if (!mInitialising && minute == getCurrentMinute()) { - return; - } - mMinuteSpinner.setValue(minute); - mDate.set(Calendar.MINUTE, minute); - onDateTimeChanged(); - } - - /** - * @return true if this is in 24 hour view else false. - */ - public boolean is24HourView () { - return mIs24HourView; - } - - /** - * Set whether in 24 hour or AM/PM mode. - * - * @param is24HourView True for 24 hour mode. False for AM/PM mode. - */ - public void set24HourView(boolean is24HourView) { - if (mIs24HourView == is24HourView) { - return; - } - mIs24HourView = is24HourView; - mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); - int hour = getCurrentHourOfDay(); - updateHourControl(); - setCurrentHour(hour); - updateAmPmControl(); - } - - 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() { - if (mIs24HourView) { - mAmPmSpinner.setVisibility(View.GONE); - } else { - int index = mIsAm ? Calendar.AM : Calendar.PM; - mAmPmSpinner.setValue(index); - mAmPmSpinner.setVisibility(View.VISIBLE); - } - } - - private void updateHourControl() { - if (mIs24HourView) { - mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); - mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); - } else { - mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); - mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); - } - } - - /** - * Set the callback that indicates the 'Set' button has been pressed. - * @param callback the callback, if null will do nothing - */ - public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { - mOnDateTimeChangedListener = callback; - } - - private void onDateTimeChanged() { - if (mOnDateTimeChangedListener != null) { - mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), - getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); - } - } -} +/* + * 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; + +// DateTimePicker类继承自FrameLayout,用于显示日期和时间选择控件 +public class DateTimePicker extends FrameLayout { + + // 默认控件是否可用 + private static final boolean DEFAULT_ENABLE_STATE = true; + + // 半天中的小时数、全天中的小时数、一周中的天数等常量 + private static final int HOURS_IN_HALF_DAY = 12; + private static final int HOURS_IN_ALL_DAY = 24; + private static final int DAYS_IN_ALL_WEEK = 7; + private static final int DATE_SPINNER_MIN_VAL = 0; + private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; + private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; + private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; + private static final int MINUT_SPINNER_MIN_VAL = 0; + private static final int MINUT_SPINNER_MAX_VAL = 59; + private static final int AMPM_SPINNER_MIN_VAL = 0; + private static final int AMPM_SPINNER_MAX_VAL = 1; + + // 日期、小时、分钟、AM/PM选择器控件 + private final NumberPicker mDateSpinner; + private final NumberPicker mHourSpinner; + private final NumberPicker mMinuteSpinner; + private final NumberPicker mAmPmSpinner; + // 当前日期时间 + private Calendar mDate; + + // 日期显示值数组 + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + + // 当前是否为AM + private boolean mIsAm; + + // 是否为24小时制视图 + private boolean mIs24HourView; + + // 控件是否可用 + 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) { + if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || + oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + } else { + if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + } + int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); + mDate.set(Calendar.HOUR_OF_DAY, newHour); + onDateTimeChanged(); + if (isDateChanged) { + setCurrentYear(cal.get(Calendar.YEAR)); + setCurrentMonth(cal.get(Calendar.MONTH)); + setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); + } + } + }; + + // 分钟选择器值改变监听器 + private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + int minValue = mMinuteSpinner.getMinValue(); + int maxValue = mMinuteSpinner.getMaxValue(); + int offset = 0; + 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(); + if (newHour >= HOURS_IN_HALF_DAY) { + mIsAm = false; + updateAmPmControl(); + } else { + mIsAm = true; + updateAmPmControl(); + } + } + 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; + if (mIsAm) { + mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); + } else { + mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); + } + updateAmPmControl(); + 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; + 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); + + String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); + 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(); + + set24HourView(is24HourView); + + // 设置到当前时间 + 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; + } + + + +``` +/* + * 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; + +/** + * DateTimePicker 类用于显示和选择日期和时间。 + */ +public class DateTimePicker extends FrameLayout { + + private static final boolean DEFAULT_ENABLE_STATE = true; + + private static final int HOURS_IN_HALF_DAY = 12; + private static final int HOURS_IN_ALL_DAY = 24; + private static final int DAYS_IN_ALL_WEEK = 7; + private static final int DATE_SPINNER_MIN_VAL = 0; + private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; + private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; + private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; + private static final int MINUT_SPINNER_MIN_VAL = 0; + private static final int MINUT_SPINNER_MAX_VAL = 59; + private static final int AMPM_SPINNER_MIN_VAL = 0; + private static final int AMPM_SPINNER_MAX_VAL = 1; + + private final NumberPicker mDateSpinner; + private final NumberPicker mHourSpinner; + private final NumberPicker mMinuteSpinner; + private final NumberPicker mAmPmSpinner; + private Calendar mDate; + + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + + private boolean mIsAm; + + private boolean mIs24HourView; + + 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) { + if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || + oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + } else { + if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + } + int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); + mDate.set(Calendar.HOUR_OF_DAY, newHour); + onDateTimeChanged(); + if (isDateChanged) { + setCurrentYear(cal.get(Calendar.YEAR)); + setCurrentMonth(cal.get(Calendar.MONTH)); + setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); + } + } + }; + + private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + int minValue = mMinuteSpinner.getMinValue(); + int maxValue = mMinuteSpinner.getMaxValue(); + int offset = 0; + 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(); + if (newHour >= HOURS_IN_HALF_DAY) { + mIsAm = false; + updateAmPmControl(); + } else { + mIsAm = true; + updateAmPmControl(); + } + } + mDate.set(Calendar.MINUTE, newVal); + onDateTimeChanged(); + } + }; + + private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mIsAm = !mIsAm; + if (mIsAm) { + mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); + } else { + mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); + } + updateAmPmControl(); + onDateTimeChanged(); + } + }; + + /** + * OnDateTimeChangedListener 接口用于监听日期和时间的变化。 + */ + public interface OnDateTimeChangedListener { + void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute); + } + + /** + * 构造函数,使用当前时间初始化 DateTimePicker。 + */ + public DateTimePicker(Context context) { + this(context, System.currentTimeMillis()); + } + + /** + * 构造函数,使用指定时间初始化 DateTimePicker。 + */ + public DateTimePicker(Context context, long date) { + this(context, date, DateFormat.is24HourFormat(context)); + } + + /** + * 构造函数,使用指定时间和24小时制设置初始化 DateTimePicker。 + */ + public DateTimePicker(Context context, long date, boolean is24HourView) { + super(context); + mDate = Calendar.getInstance(); + mInitialising = true; + mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + inflate(context, R.layout.datetime_picker, this); + + mDateSpinner = (NumberPicker) findViewById(R.id.date); + mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); + mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); + mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + + mHourSpinner = (NumberPicker) findViewById(R.id.hour); + mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); + mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); + mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); + mMinuteSpinner.setOnLongPressUpdateInterval(100); + mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); + + String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); + 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(); + + set24HourView(is24HourView); + + // 设置为当前时间 + 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; + } + + /** + * 获取当前日期的毫秒数。 + */ + public long getCurrentDateInTimeMillis() { + return mDate.getTimeInMillis(); + } + + /** + * 设置当前日期。 + */ + 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)); + } + + /** + * 设置当前日期。 + */ + public void setCurrentDate(int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + setCurrentYear(year); + setCurrentMonth(month); + setCurrentDay(dayOfMonth); + setCurrentHour(hourOfDay); + setCurrentMinute(minute); + } + + /** + * 获取当前年份。 + */ + public int getCurrentYear() { + return mDate.get(Calendar.YEAR); + } + + /** + * 设置当前年份。 + */ + public void setCurrentYear(int year) { + if (!mInitialising && year == getCurrentYear()) { + return; + } + mDate.set(Calendar.YEAR, year); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * 获取当前月份。 + */ + public int getCurrentMonth() { + return mDate.get(Calendar.MONTH); + } + + /** + * 设置当前月份。 + */ + public void setCurrentMonth(int month) { + if (!mInitialising && month == getCurrentMonth()) { + return; + } + mDate.set(Calendar.MONTH, month); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * 获取当前日期。 + */ + public int getCurrentDay() { + return mDate.get(Calendar.DAY_OF_MONTH); + } + + /** + * 设置当前日期。 + */ + public void setCurrentDay(int dayOfMonth) { + if (!mInitialising && dayOfMonth == getCurrentDay()) { + return; + } + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * 获取当前小时(24小时制)。 + */ + public int getCurrentHourOfDay() { + return mDate.get(Calendar.HOUR_OF_DAY); + } + + private int getCurrentHour() { + if (mIs24HourView){ + return getCurrentHourOfDay(); + } else { + int hour = getCurrentHourOfDay(); + if (hour > HOURS_IN_HALF_DAY) { + return hour - HOURS_IN_HALF_DAY; + } else { + return hour == 0 ? HOURS_IN_HALF_DAY : hour; + } + } + } + + /** + * 设置当前小时(24小时制)。 + */ + public void setCurrentHour(int hourOfDay) { + if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { + return; + } + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + if (!mIs24HourView) { + if (hourOfDay >= HOURS_IN_HALF_DAY) { + mIsAm = false; + if (hourOfDay > HOURS_IN_HALF_DAY) { + hourOfDay -= HOURS_IN_HALF_DAY; + } + } else { + mIsAm = true; + if (hourOfDay == 0) { + hourOfDay = HOURS_IN_HALF_DAY; + } + } + updateAmPmControl(); + } + mHourSpinner.setValue(hourOfDay); + onDateTimeChanged(); + } + + /** + * 获取当前分钟。 + */ + public int getCurrentMinute() { + return mDate.get(Calendar.MINUTE); + } + + /** + * 设置当前分钟。 + */ + public void setCurrentMinute(int minute) { + if (!mInitialising && minute == getCurrentMinute()) { + return; + } + mMinuteSpinner.setValue(minute); + mDate.set(Calendar.MINUTE, minute); + onDateTimeChanged(); + } + + /** + * 判断是否为24小时制。 + */ + public boolean is24HourView () { + return mIs24HourView; + } + + /** + * 设置是否为24小时制。 + */ + public void set24HourView(boolean is24HourView) { + if (mIs24HourView == is24HourView) { + return; + } + mIs24HourView = is24HourView; + mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); + int hour = getCurrentHourOfDay(); + updateHourControl(); + setCurrentHour(hour); + updateAmPmControl(); + } + + /** + * 更新日期控件的显示。 + */ + private void updateDateControl() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); + mDateSpinner.setDisplayedValues(null); + for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { + cal.add(Calendar.DAY_OF_YEAR, 1); + mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); + } + mDateSpinner.setDisplayedValues(mDateDisplayValues); + mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); + mDateSpinner.invalidate(); + } + + /** + * 更新AM/PM控件的显示。 + */ + private void updateAmPmControl() { + if (mIs24HourView) { + mAmPmSpinner.setVisibility(View.GONE); + } else { + int index = mIsAm ? Calendar.AM : Calendar.PM; + mAmPmSpinner.setValue(index); + mAmPmSpinner.setVisibility(View.VISIBLE); + } + } + + /** + * 更新小时控件的显示。 + */ + private void updateHourControl() { + if (mIs24HourView) { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); + } else { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); + } + } + + /** + * 设置日期时间变化的监听器。 + */ + public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { + mOnDateTimeChangedListener = callback; + } + + /** + * 当日期或时间发生变化时调用此方法。 + */ + private void onDateTimeChanged() { + if (mOnDateTimeChangedListener != null) { + mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), + getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); + } + } +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java b/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java index 2c47ba4..e3c2eb4 100644 --- a/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java +++ b/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java @@ -13,33 +13,40 @@ * 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; - + +// 自定义的日期时间选择对话框,继承自AlertDialog并实现了OnClickListener接口 public class DateTimePickerDialog extends AlertDialog implements OnClickListener { - + + // 存储当前选择的日期时间 private Calendar mDate = Calendar.getInstance(); + // 是否使用24小时制 private boolean mIs24HourView; + // 回调接口,当日期时间设置完成后调用 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); @@ -63,15 +70,18 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener set24HourView(DateFormat.is24HourFormat(this.getContext())); updateTitle(mDate.getTimeInMillis()); } - + + // 设置是否使用24小时制显示时间 public void set24HourView(boolean is24HourView) { mIs24HourView = is24HourView; } - + + // 设置日期时间选择完成后的回调监听器 public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { mOnDateTimeSetListener = callBack; } - + + // 更新对话框的标题以显示当前选择的日期时间 private void updateTitle(long date) { int flag = DateUtils.FORMAT_SHOW_YEAR | @@ -80,11 +90,12 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); } - + + // 处理用户点击对话框按钮的事件,如果是确认按钮则调用回调监听器 public void onClick(DialogInterface arg0, int arg1) { if (mOnDateTimeSetListener != null) { mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); } } - + } \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java b/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java index 613dc74..eb009a2 100644 --- a/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java +++ b/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java @@ -13,9 +13,9 @@ * 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; @@ -24,14 +24,16 @@ 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; - + private Button mButton; // 用于触发下拉菜单的按钮 + private PopupMenu mPopupMenu; // 弹出菜单对象 + private Menu mMenu; // 菜单对象 + + // 构造函数,初始化下拉菜单,设置按钮背景,并将菜单资源加载到弹出菜单中 public DropdownMenu(Context context, Button button, int menuId) { mButton = button; mButton.setBackgroundResource(R.drawable.dropdown_icon); @@ -44,18 +46,21 @@ public class DropdownMenu { } }); } - + + // 设置下拉菜单项点击监听器 public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { if (mPopupMenu != null) { mPopupMenu.setOnMenuItemClickListener(listener); } } - + + // 根据菜单项的ID查找菜单项 public MenuItem findItem(int id) { return mMenu.findItem(id); } - + + // 设置按钮的标题 public void setTitle(CharSequence title) { mButton.setText(title); } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java b/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java index 96b77da..1c6a78a 100644 --- a/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java +++ b/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java @@ -28,26 +28,31 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; - +// 自定义的 CursorAdapter 用于显示文件夹列表 public class FoldersListAdapter extends CursorAdapter { + // 定义查询文件夹时需要的列 public static final String [] PROJECTION = { NoteColumns.ID, NoteColumns.SNIPPET }; + // 列索引常量 public static final int ID_COLUMN = 0; public static final int NAME_COLUMN = 1; + // 构造函数,初始化 FoldersListAdapter 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) { @@ -57,24 +62,28 @@ public class FoldersListAdapter extends CursorAdapter { } } + // 根据位置获取文件夹名称 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 { private TextView mName; + // 构造函数,初始化 FolderListItem 视图 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); } } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java index 96a9ff8..3e45406 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java @@ -13,9 +13,9 @@ * 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.AlarmManager; import android.app.AlertDialog; @@ -51,7 +51,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; - + import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.TextNote; @@ -64,26 +64,28 @@ import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; import net.micode.notes.widget.NoteWidgetProvider_2x; import net.micode.notes.widget.NoteWidgetProvider_4x; - + import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; - - + +// 笔记编辑界面的Activity类 public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { + // 用于存储笔记头部视图的ViewHolder类 private class HeadViewHolder { public TextView tvModified; - + public ImageView ivAlertIcon; - + public TextView tvAlertDate; - + public ImageView ibSetBgColor; } - + + // 背景色选择按钮和颜色ID的映射 private static final Map sBgSelectorBtnsMap = new HashMap(); static { sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); @@ -92,7 +94,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); } - + + // 背景色选择后的显示标记和颜色ID的映射 private static final Map sBgSelectorSelectionMap = new HashMap(); static { sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); @@ -101,7 +104,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } - + + // 字体大小选择按钮和字体大小ID的映射 private static final Map sFontSizeBtnsMap = new HashMap(); static { sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); @@ -109,7 +113,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); } - + + // 字体大小选择后的显示标记和字体大小ID的映射 private static final Map sFontSelectorSelectionMap = new HashMap(); static { sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); @@ -117,54 +122,52 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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"; - + 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; - + 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; - + + // 初始化Activity视图 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.note_edit); - + if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } initResources(); } - - /** - * Current activity may be killed when the memory is low. Once it is killed, for another time - * user load this activity, we should restore the former state - */ + + // 当Activity被系统杀死后,恢复其状态 @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); @@ -178,25 +181,19 @@ public class NoteEditActivity extends Activity implements OnClickListener, Log.d(TAG, "Restoring from killed activity"); } } - + + // 初始化Activity状态,根据Intent决定加载笔记或创建新笔记 private boolean initActivityState(Intent intent) { - /** - * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, - * then jump to the NotesListActivity - */ mWorkingNote = null; if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); mUserQuery = ""; - - /** - * Starting from the searched result - */ + if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { 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); startActivity(jump); @@ -215,7 +212,6 @@ public class NoteEditActivity extends Activity implements OnClickListener, WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); } else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { - // New note long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); @@ -223,8 +219,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, Notes.TYPE_WIDGET_INVALIDE); int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, ResourceParser.getDefaultBgId(this)); - - // Parse call-record note + String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); if (callDate != 0 && phoneNumber != null) { @@ -249,7 +244,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId); } - + getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); @@ -261,13 +256,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, mWorkingNote.setOnSettingStatusChangedListener(this); return true; } - + + // Activity恢复时初始化笔记显示 @Override protected void onResume() { super.onResume(); initNoteScreen(); } - + + // 初始化笔记显示界面,包括背景颜色、字体大小、修改日期等 private void initNoteScreen() { mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); @@ -282,19 +279,16 @@ public class NoteEditActivity extends Activity implements OnClickListener, } 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: Add the menu for setting alert. Currently disable it because the DateTimePicker - * is not ready - */ + showAlertHeader(); } - + + // 显示或隐藏提醒头部信息 private void showAlertHeader() { if (mWorkingNote.hasClockAlert()) { long time = System.currentTimeMillis(); @@ -311,28 +305,26 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); }; } - + + // 处理新的Intent,可能需要重新加载笔记或创建新笔记 @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); initActivityState(intent); } - + + // 保存Activity状态,在系统需要恢复Activity时使用 @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - /** - * For new note without note id, we should firstly save it to - * generate a id. If the editing note is not worth saving, there - * is no id which is equivalent to create new note - */ if (!mWorkingNote.existInDatabase()) { saveNote(); } outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); } - + + // 处理触摸事件,用于隐藏颜色和字体大小选择面板 @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mNoteBgColorSelector.getVisibility() == View.VISIBLE @@ -340,7 +332,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteBgColorSelector.setVisibility(View.GONE); return true; } - + if (mFontSizeSelector.getVisibility() == View.VISIBLE && !inRangeOfView(mFontSizeSelector, ev)) { mFontSizeSelector.setVisibility(View.GONE); @@ -348,7 +340,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, } return super.dispatchTouchEvent(ev); } - + + // 判断触摸事件是否发生在指定视图内 private boolean inRangeOfView(View view, MotionEvent ev) { int []location = new int[2]; view.getLocationOnScreen(location); @@ -362,7 +355,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, } return true; } - + + // 初始化视图资源 private void initResources() { mHeadViewPanel = findViewById(R.id.note_title); mNoteHeaderHolder = new HeadViewHolder(); @@ -378,7 +372,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, ImageView iv = (ImageView) findViewById(id); iv.setOnClickListener(this); } - + mFontSizeSelector = findViewById(R.id.font_size_selector); for (int id : sFontSizeBtnsMap.keySet()) { View view = findViewById(id); @@ -386,17 +380,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, }; mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); - /** - * 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(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; } mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); } - + + // 暂停Activity时保存笔记并清理设置状态 @Override protected void onPause() { super.onPause(); @@ -405,7 +395,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, } clearSettingState(); } - + + // 更新桌面小部件显示 private void updateWidget() { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { @@ -416,21 +407,22 @@ public class NoteEditActivity extends Activity implements OnClickListener, Log.e(TAG, "Unspported widget type"); return; } - + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { mWorkingNote.getWidgetId() }); - + sendBroadcast(intent); setResult(RESULT_OK, intent); } - + + // 处理视图点击事件,包括颜色和字体大小选择 public void onClick(View v) { int id = v.getId(); if (id == R.id.btn_set_bg_color) { mNoteBgColorSelector.setVisibility(View.VISIBLE); findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - - View.VISIBLE); + View.VISIBLE); } else if (sBgSelectorBtnsMap.containsKey(id)) { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.GONE); @@ -451,17 +443,19 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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); @@ -472,14 +466,16 @@ public class NoteEditActivity extends Activity implements OnClickListener, } return 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()) { @@ -504,7 +500,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, } return true; } - + + // 处理选项菜单项点击事件 @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -552,7 +549,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, } return true; } - + + // 设置提醒时间 private void setReminder() { DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); d.setOnDateTimeSetListener(new OnDateTimeSetListener() { @@ -562,30 +560,27 @@ public class NoteEditActivity extends Activity implements OnClickListener, }); d.show(); } - - /** - * Share note to apps that support {@link Intent#ACTION_SEND} action - * and {@text/plain} type - */ + + // 分享笔记内容到支持ACTION_SEND的其他应用 private void sendTo(Context context, String info) { Intent intent = new Intent(Intent.ACTION_SEND); intent.putExtra(Intent.EXTRA_TEXT, info); intent.setType("text/plain"); context.startActivity(intent); } - + + // 创建新笔记,跳转到新笔记编辑界面 private void createNewNote() { - // Firstly, save current editing notes saveNote(); - - // For safety, start a new NoteEditActivity + finish(); Intent intent = new Intent(this, NoteEditActivity.class); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); startActivity(intent); } - + + // 删除当前笔记 private void deleteCurrentNote() { if (mWorkingNote.existInDatabase()) { HashSet ids = new HashSet(); @@ -607,16 +602,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, } mWorkingNote.markDeleted(true); } - + + // 判断是否处于同步模式 private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } - + + // 处理时钟提醒变化事件,设置或取消提醒 public void onClockAlertChanged(long date, boolean set) { - /** - * User could set clock to an unsaved note, so before setting the - * alert clock, we should save the note first - */ if (!mWorkingNote.existInDatabase()) { saveNote(); } @@ -632,31 +625,28 @@ public class NoteEditActivity extends Activity implements OnClickListener, alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); } } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ Log.e(TAG, "Clock alert setting error"); showToast(R.string.error_note_empty_for_clock); } } - + + // 处理小部件变化事件,更新小部件显示 public void onWidgetChanged() { updateWidget(); } - + + // 处理EditText删除事件,调整列表项索引 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)) .setIndex(i - 1); } - + mEditTextList.removeViewAt(index); NoteEditText edit = null; if(index == 0) { @@ -671,15 +661,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, edit.requestFocus(); edit.setSelection(length); } - + + // 处理EditText输入事件,添加新列表项 public void onEditTextEnter(int index, String text) { - /** - * Should not happen, check for debug - */ 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); @@ -690,7 +678,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, .setIndex(i); } } - + + // 切换到列表模式,根据笔记内容生成列表项 private void switchToListMode(String text) { mEditTextList.removeAllViews(); String[] items = text.split("\n"); @@ -703,11 +692,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, } 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)) { @@ -724,7 +714,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, } 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); @@ -739,7 +730,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } }); - + if (item.startsWith(TAG_CHECKED)) { cb.setChecked(true); edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); @@ -749,13 +740,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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; } - + + // 处理EditText文本变化事件,显示或隐藏复选框 public void onTextChange(int index, boolean hasText) { if (index >= mEditTextList.getChildCount()) { Log.e(TAG, "Wrong index, should not happen"); @@ -767,7 +759,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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()); @@ -781,7 +774,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteEditor.setVisibility(View.VISIBLE); } } - + + // 获取正在编辑的文本内容,根据列表模式添加标签 private boolean getWorkingText() { boolean hasChecked = false; if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { @@ -804,33 +798,23 @@ public class NoteEditActivity extends Activity implements OnClickListener, } return hasChecked; } - + + // 保存笔记内容到数据库 private boolean saveNote() { getWorkingText(); boolean saved = mWorkingNote.saveNote(); if (saved) { - /** - * There are two modes from List view to edit view, open one note, - * create/edit a node. Opening node requires to the original - * position in the list when back from edit view, while creating a - * new node requires to the top of the list. This code - * {@link #RESULT_OK} is used to identify the create/edit state - */ setResult(RESULT_OK); } return saved; } - + + // 将笔记快捷方式添加到桌面 private void sendToDesktop() { - /** - * Before send message to home, we should make sure that current - * editing note is exists in databases. So, for new note, firstly - * save it - */ if (!mWorkingNote.existInDatabase()) { saveNote(); } - + if (mWorkingNote.getNoteId() > 0) { Intent sender = new Intent(); Intent shortcutIntent = new Intent(this, NoteEditActivity.class); @@ -846,28 +830,26 @@ public class NoteEditActivity extends Activity implements OnClickListener, showToast(R.string.info_note_enter_desktop); sendBroadcast(sender); } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ 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; } - + + // 显示短Toast消息 private void showToast(int resId) { showToast(resId, Toast.LENGTH_SHORT); } - + + // 显示指定持续时间的Toast消息 private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).show(); } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java b/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java index 2afe2a8..d350c57 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java @@ -13,9 +13,9 @@ * 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.graphics.Rect; import android.text.Layout; @@ -31,28 +31,29 @@ import android.view.MenuItem; import android.view.MenuItem.OnMenuItemClickListener; import android.view.MotionEvent; import android.widget.EditText; - + import net.micode.notes.R; - + import java.util.HashMap; import java.util.Map; - + +// 自定义的EditText,用于笔记应用中,支持删除、添加文本事件监听 public class NoteEditText extends EditText { private static final String TAG = "NoteEditText"; private int mIndex; private int mSelectionStartBeforeDelete; - + private static final String SCHEME_TEL = "tel:" ; private static final String SCHEME_HTTP = "http:" ; private static final String SCHEME_EMAIL = "mailto:" ; - + private static final Map sSchemaActionResMap = new HashMap(); static { sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); } - + /** * Call by the {@link NoteEditActivity} to delete or add edit text */ @@ -62,65 +63,69 @@ public class NoteEditText extends EditText { * and the text is null */ void onEditTextDelete(int index, String text); - + /** * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} * happen */ void onEditTextEnter(int index, String text); - + /** * Hide or show item option when text change */ void onTextChange(int index, boolean hasText); } - + private OnTextViewChangeListener mOnTextViewChangeListener; - + public NoteEditText(Context context) { super(context, null); mIndex = 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(); int y = (int) event.getY(); x -= getTotalPaddingLeft(); y -= getTotalPaddingTop(); x += getScrollX(); y += getScrollY(); - + Layout layout = getLayout(); int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); Selection.setSelection(getText(), off); break; } - + return super.onTouchEvent(event); } - + + // 处理按键按下事件,记录删除操作前的光标位置 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { @@ -137,7 +142,8 @@ public class NoteEditText extends EditText { } return super.onKeyDown(keyCode, event); } - + + // 处理按键弹起事件,根据按键类型执行相应操作 @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch(keyCode) { @@ -166,7 +172,8 @@ public class NoteEditText extends EditText { } return super.onKeyUp(keyCode, event); } - + + // 当EditText焦点发生变化时调用,通知监听器文本是否有内容 @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { if (mOnTextViewChangeListener != null) { @@ -178,16 +185,17 @@ public class NoteEditText extends EditText { } super.onFocusChanged(focused, direction, previouslyFocusedRect); } - + + // 创建上下文菜单,处理URL点击事件 @Override protected void onCreateContextMenu(ContextMenu menu) { if (getText() instanceof Spanned) { int selStart = getSelectionStart(); int selEnd = getSelectionEnd(); - + int min = Math.min(selStart, selEnd); int max = Math.max(selStart, selEnd); - + final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); if (urls.length == 1) { int defaultResId = 0; @@ -197,11 +205,11 @@ public class NoteEditText extends EditText { break; } } - + if (defaultResId == 0) { defaultResId = R.string.note_link_other; } - + menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( new OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { @@ -214,4 +222,4 @@ public class NoteEditText extends EditText { } super.onCreateContextMenu(menu); } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java b/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java index 0f5a878..632fe29 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java @@ -13,19 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + package net.micode.notes.ui; - + import android.content.Context; import android.database.Cursor; import android.text.TextUtils; - + import net.micode.notes.data.Contact; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.tool.DataUtils; - - + +// 该类用于从数据库游标中提取笔记项数据,并处理与笔记位置相关的逻辑 public class NoteItemData { static final String [] PROJECTION = new String [] { NoteColumns.ID, @@ -41,7 +41,8 @@ public class NoteItemData { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, }; - + + // 定义了游标中各个列的索引位置 private static final int ID_COLUMN = 0; private static final int ALERTED_DATE_COLUMN = 1; private static final int BG_COLOR_ID_COLUMN = 2; @@ -54,7 +55,8 @@ public class NoteItemData { private static final int TYPE_COLUMN = 9; private static final int WIDGET_ID_COLUMN = 10; private static final int WIDGET_TYPE_COLUMN = 11; - + + // 笔记项的各种属性 private long mId; private long mAlertDate; private int mBgColorId; @@ -69,13 +71,15 @@ public class NoteItemData { private int mWidgetType; private String mName; private String mPhoneNumber; - + + // 笔记项在列表中的位置信息 private boolean mIsLastItem; private boolean mIsFirstItem; private boolean mIsOnlyOneItem; private boolean mIsOneNoteFollowingFolder; private boolean mIsMultiNotesFollowingFolder; - + + // 构造函数,从游标中提取笔记项数据 public NoteItemData(Context context, Cursor cursor) { mId = cursor.getLong(ID_COLUMN); mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); @@ -91,7 +95,7 @@ public class NoteItemData { mType = cursor.getInt(TYPE_COLUMN); mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); - + mPhoneNumber = ""; if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); @@ -102,20 +106,21 @@ public class NoteItemData { } } } - + if (mName == null) { mName = ""; } checkPostion(cursor); } - + + // 检查笔记项在列表中的位置信息 private void checkPostion(Cursor cursor) { mIsLastItem = cursor.isLast() ? true : false; mIsFirstItem = cursor.isFirst() ? true : false; mIsOnlyOneItem = (cursor.getCount() == 1); mIsMultiNotesFollowingFolder = false; mIsOneNoteFollowingFolder = false; - + if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { int position = cursor.getPosition(); if (cursor.moveToPrevious()) { @@ -133,92 +138,114 @@ public class NoteItemData { } } } - + + // 判断该笔记项是否是单个笔记跟在一个文件夹后 public boolean isOneFollowingFolder() { return mIsOneNoteFollowingFolder; } - + + // 判断该笔记项是否是多个笔记跟在一个文件夹后 public boolean isMultiFollowingFolder() { return mIsMultiNotesFollowingFolder; } - + + // 判断该笔记项是否是列表中的最后一个项 public boolean isLast() { return mIsLastItem; } - + + // 获取与该笔记项关联的呼叫记录的联系人名称 public String getCallName() { return mName; } - + + // 判断该笔记项是否是列表中的第一个项 public boolean isFirst() { return mIsFirstItem; } - + + // 判断该笔记项是否是列表中唯一的项 public boolean isSingle() { return mIsOnlyOneItem; } - + + // 获取笔记项的ID public long getId() { return mId; } - + + // 获取笔记项的提醒日期 public long getAlertDate() { return mAlertDate; } - + + // 获取笔记项的创建日期 public long getCreatedDate() { return mCreatedDate; } - + + // 判断该笔记项是否有附件 public boolean hasAttachment() { return mHasAttachment; } - + + // 获取笔记项的修改日期 public long getModifiedDate() { return mModifiedDate; } - + + // 获取笔记项的背景颜色ID public int getBgColorId() { return mBgColorId; } - + + // 获取笔记项的父ID public long getParentId() { return mParentId; } - + + // 获取笔记项包含的笔记数量 public int getNotesCount() { return mNotesCount; } - + + // 获取笔记项所在的文件夹ID public long getFolderId () { return mParentId; } - + + // 获取笔记项的类型 public int getType() { return mType; } - + + // 获取笔记项的小部件类型 public int getWidgetType() { return mWidgetType; } - + + // 获取笔记项的小部件ID public int getWidgetId() { return mWidgetId; } - + + // 获取笔记项的摘要 public String getSnippet() { return mSnippet; } - + + // 判断该笔记项是否有提醒 public boolean hasAlert() { return (mAlertDate > 0); } - + + // 判断该笔记项是否是呼叫记录类型 public boolean isCallRecord() { return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); } - + + // 静态方法,从游标中获取笔记项的类型 public static int getNoteType(Cursor cursor) { return cursor.getInt(TYPE_COLUMN); } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java index e843aec..5893567 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java @@ -126,12 +126,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt 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; @@ -951,4 +951,4 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } return false; } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java b/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java index 51c9cb9..fc77697 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java @@ -13,48 +13,52 @@ * 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.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.CursorAdapter; - + import net.micode.notes.data.Notes; - + import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; - - + +// 自定义的CursorAdapter,用于显示笔记列表 public class NotesListAdapter extends CursorAdapter { private static final String TAG = "NotesListAdapter"; private Context mContext; private HashMap mSelectedIndex; private int mNotesCount; private boolean mChoiceMode; - + + // 用于存储小部件属性的内部类 public static class AppWidgetAttribute { public int widgetId; public int widgetType; }; - + + // 构造函数,初始化上下文和选择索引 public NotesListAdapter(Context context) { super(context, null); mSelectedIndex = new HashMap(); mContext = context; mNotesCount = 0; } - + + // 创建新的视图项 @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new NotesListItem(context); } - + + // 绑定数据到视图项 @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof NotesListItem) { @@ -63,21 +67,25 @@ public class NotesListAdapter extends CursorAdapter { 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++) { @@ -88,7 +96,8 @@ public class NotesListAdapter extends CursorAdapter { } } } - + + // 获取所有选中的笔记ID集合 public HashSet getSelectedItemIds() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -101,10 +110,11 @@ public class NotesListAdapter extends CursorAdapter { } } } - + return itemSet; } - + + // 获取所有选中的小部件属性集合 public HashSet getSelectedWidget() { HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { @@ -127,7 +137,8 @@ public class NotesListAdapter extends CursorAdapter { } return itemSet; } - + + // 获取选中的笔记数量 public int getSelectedCount() { Collection values = mSelectedIndex.values(); if (null == values) { @@ -142,31 +153,36 @@ public class NotesListAdapter extends CursorAdapter { } return count; } - + + // 检查是否所有笔记都被选中 public boolean isAllSelected() { int checkedCount = getSelectedCount(); return (checkedCount != 0 && checkedCount == mNotesCount); } - + + // 检查指定位置的项是否被选中 public boolean isSelectedItem(final int position) { if (null == mSelectedIndex.get(position)) { return false; } return mSelectedIndex.get(position); } - + + // 当数据内容发生变化时,更新笔记数量 @Override protected void onContentChanged() { super.onContentChanged(); calcNotesCount(); } - + + // 更改Cursor时,更新笔记数量 @Override public void changeCursor(Cursor cursor) { super.changeCursor(cursor); calcNotesCount(); } - + + // 计算笔记数量 private void calcNotesCount() { mNotesCount = 0; for (int i = 0; i < getCount(); i++) { @@ -181,4 +197,4 @@ public class NotesListAdapter extends CursorAdapter { } } } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java b/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java index 1221e80..1546bbe 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java @@ -13,9 +13,9 @@ * 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.text.format.DateUtils; import android.view.View; @@ -23,13 +23,13 @@ import android.widget.CheckBox; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; - + import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser.NoteItemBgResources; - - + +// NotesListItem 类继承自 LinearLayout,用于表示笔记列表中的一个项 public class NotesListItem extends LinearLayout { private ImageView mAlert; private TextView mTitle; @@ -37,7 +37,8 @@ public class NotesListItem extends LinearLayout { private TextView mCallName; private NoteItemData mItemData; private CheckBox mCheckBox; - + + // 构造函数,初始化 NotesListItem 的视图组件 public NotesListItem(Context context) { super(context); inflate(context, R.layout.note_item, this); @@ -47,7 +48,8 @@ public class NotesListItem extends LinearLayout { mCallName = (TextView) findViewById(R.id.tv_name); mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); } - + + // 绑定数据到 NotesListItem 的视图组件,并设置选择模式和选中状态 public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { if (choiceMode && data.getType() == Notes.TYPE_NOTE) { mCheckBox.setVisibility(View.VISIBLE); @@ -55,7 +57,7 @@ public class NotesListItem extends LinearLayout { } else { mCheckBox.setVisibility(View.GONE); } - + mItemData = data; if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mCallName.setVisibility(View.GONE); @@ -78,7 +80,7 @@ public class NotesListItem extends LinearLayout { } 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, @@ -95,10 +97,11 @@ public class NotesListItem extends LinearLayout { } } mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); - + setBackground(data); } - + + // 根据数据设置 NotesListItem 的背景资源 private void setBackground(NoteItemData data) { int id = data.getBgColorId(); if (data.getType() == Notes.TYPE_NOTE) { @@ -115,8 +118,9 @@ public class NotesListItem extends LinearLayout { setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } } - + + // 获取绑定到此 NotesListItem 的数据 public NoteItemData getItemData() { return mItemData; } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java b/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java index 07c5f7e..48125be 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + package net.micode.notes.ui; - + import android.accounts.Account; import android.accounts.AccountManager; import android.app.ActionBar; @@ -41,59 +41,60 @@ import android.view.View; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; - + import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.gtask.remote.GTaskSyncService; - - + +// 设置界面活动类,继承自PreferenceActivity public class NotesPreferenceActivity extends PreferenceActivity { public static final String PREFERENCE_NAME = "notes_preferences"; - + public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; - + public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; - + public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; - + private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; - + private static final String AUTHORITIES_FILTER_KEY = "authorities"; - + private PreferenceCategory mAccountCategory; - + private GTaskReceiver mReceiver; - + private Account[] mOriAccounts; - + private boolean mHasAddedAccount; - + + // 创建活动时初始化界面 @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - - /* using the app icon for navigation */ + + /* 使用应用图标进行导航 */ getActionBar().setDisplayHomeAsUpEnabled(true); - + addPreferencesFromResource(R.xml.preferences); mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); mReceiver = new GTaskReceiver(); IntentFilter filter = new IntentFilter(); filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); registerReceiver(mReceiver, filter); - + mOriAccounts = null; View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); getListView().addHeaderView(header, null, true); } - + + // 恢复活动时刷新界面 @Override protected void onResume() { super.onResume(); - - // need to set sync account automatically if user has added a new - // account + + // 如果用户添加了新账户,自动设置同步账户 if (mHasAddedAccount) { Account[] accounts = getGoogleAccounts(); if (mOriAccounts != null && accounts.length > mOriAccounts.length) { @@ -112,10 +113,11 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } } - + refreshUI(); } - + + // 销毁活动时注销广播接收器 @Override protected void onDestroy() { if (mReceiver != null) { @@ -123,10 +125,11 @@ public class NotesPreferenceActivity extends PreferenceActivity { } super.onDestroy(); } - + + // 加载账户偏好设置 private void loadAccountPreference() { mAccountCategory.removeAll(); - + Preference accountPref = new Preference(this); final String defaultAccount = getSyncAccountName(this); accountPref.setTitle(getString(R.string.preferences_account_title)); @@ -135,11 +138,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { public boolean onPreferenceClick(Preference preference) { if (!GTaskSyncService.isSyncing()) { if (TextUtils.isEmpty(defaultAccount)) { - // the first time to set account + // 第一次设置账户 showSelectAccountAlertDialog(); } else { - // if the account has already been set, we need to promp - // user about the risk + // 如果账户已经设置,提示用户切换账户的风险 showChangeAccountConfirmAlertDialog(); } } else { @@ -150,15 +152,16 @@ public class NotesPreferenceActivity extends PreferenceActivity { 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() { @@ -175,8 +178,8 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); - - // set last sync time + + // 设置上次同步时间 if (GTaskSyncService.isSyncing()) { lastSyncTimeView.setText(GTaskSyncService.getProgressString()); lastSyncTimeView.setVisibility(View.VISIBLE); @@ -192,30 +195,32 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } } - + + // 刷新用户界面 private void refreshUI() { loadAccountPreference(); loadSyncButton(); } - + + // 显示选择账户的对话框 private void showSelectAccountAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - + View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); - + dialogBuilder.setCustomTitle(titleView); dialogBuilder.setPositiveButton(null, null); - + Account[] accounts = getGoogleAccounts(); String defAccount = getSyncAccountName(this); - + mOriAccounts = accounts; mHasAddedAccount = false; - + if (accounts.length > 0) { CharSequence[] items = new CharSequence[accounts.length]; final CharSequence[] itemMapping = items; @@ -236,10 +241,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { } }); } - + 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) { @@ -253,10 +258,11 @@ public class NotesPreferenceActivity extends PreferenceActivity { } }); } - + + // 显示更改账户确认对话框 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, @@ -264,7 +270,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { 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), @@ -282,12 +288,14 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); dialogBuilder.show(); } - + + // 获取Google账户列表 private Account[] getGoogleAccounts() { AccountManager accountManager = AccountManager.get(this); return accountManager.getAccountsByType("com.google"); } - + + // 设置同步账户 private void setSyncAccount(String account) { if (!getSyncAccountName(this).equals(account)) { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); @@ -298,11 +306,11 @@ public class NotesPreferenceActivity extends PreferenceActivity { editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); } editor.commit(); - - // clean up last sync time + + // 清除上次同步时间 setLastSyncTime(this, 0); - - // clean up local gtask related info + + // 清除本地Gtask相关信息 new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); @@ -311,13 +319,14 @@ public class NotesPreferenceActivity extends PreferenceActivity { 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(); @@ -328,8 +337,8 @@ public class NotesPreferenceActivity extends PreferenceActivity { editor.remove(PREFERENCE_LAST_SYNC_TIME); } editor.commit(); - - // clean up local gtask related info + + // 清除本地Gtask相关信息 new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); @@ -339,13 +348,15 @@ public class NotesPreferenceActivity extends PreferenceActivity { } }).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); @@ -353,15 +364,17 @@ public class NotesPreferenceActivity extends PreferenceActivity { 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); } - + + // 广播接收器,用于接收同步状态更新 private class GTaskReceiver extends BroadcastReceiver { - + @Override public void onReceive(Context context, Intent intent) { refreshUI(); @@ -370,10 +383,11 @@ public class NotesPreferenceActivity extends PreferenceActivity { syncStatus.setText(intent .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); } - + } } - + + // 选项菜单项点击事件处理 public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: @@ -385,4 +399,4 @@ public class NotesPreferenceActivity extends PreferenceActivity { return false; } } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider.java b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider.java index ec6f819..0fbd347 100644 --- a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider.java +++ b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + package net.micode.notes.widget; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; @@ -24,27 +24,32 @@ import android.content.Intent; import android.database.Cursor; import android.util.Log; import android.widget.RemoteViews; - + import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.tool.ResourceParser; import net.micode.notes.ui.NoteEditActivity; import net.micode.notes.ui.NotesListActivity; - + +// 提供笔记小部件功能的抽象类,继承自AppWidgetProvider public abstract class NoteWidgetProvider extends AppWidgetProvider { + // 查询笔记时使用的投影列 public static final String [] PROJECTION = new String [] { NoteColumns.ID, NoteColumns.BG_COLOR_ID, NoteColumns.SNIPPET }; - + + // 投影列对应的索引 public static final int COLUMN_ID = 0; public static final int COLUMN_BG_COLOR_ID = 1; public static final int COLUMN_SNIPPET = 2; - + + // 日志标签 private static final String TAG = "NoteWidgetProvider"; - + + // 当小部件被删除时调用,更新数据库中的小部件ID为无效值 @Override public void onDeleted(Context context, int[] appWidgetIds) { ContentValues values = new ContentValues(); @@ -56,7 +61,8 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { new String[] { String.valueOf(appWidgetIds[i])}); } } - + + // 根据小部件ID获取笔记信息 private Cursor getNoteWidgetInfo(Context context, int widgetId) { return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, @@ -64,11 +70,13 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, null); } - + + // 更新小部件视图 protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { update(context, appWidgetManager, appWidgetIds, false); } - + + // 更新小部件视图,支持隐私模式 private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, boolean privacyMode) { for (int i = 0; i < appWidgetIds.length; i++) { @@ -79,7 +87,7 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); - + Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); if (c != null && c.moveToFirst()) { if (c.getCount() > 1) { @@ -95,11 +103,11 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { snippet = context.getResources().getString(R.string.widget_havenot_content); intent.setAction(Intent.ACTION_INSERT_OR_EDIT); } - + if (c != null) { c.close(); } - + RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); @@ -117,16 +125,19 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, PendingIntent.FLAG_UPDATE_CURRENT); } - + rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); appWidgetManager.updateAppWidget(appWidgetIds[i], rv); } } } - + + // 获取背景资源ID的方法,由子类实现 protected abstract int getBgResourceId(int bgId); - + + // 获取布局ID的方法,由子类实现 protected abstract int getLayoutId(); - + + // 获取小部件类型的ID,由子类实现 protected abstract int getWidgetType(); -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_2x.java b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_2x.java index adcb2f7..ffc1401 100644 --- a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_2x.java +++ b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_2x.java @@ -13,35 +13,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + package net.micode.notes.widget; - + import android.appwidget.AppWidgetManager; import android.content.Context; - + import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; - - + +// 2x2 小部件提供者类,继承自 NoteWidgetProvider public class NoteWidgetProvider_2x extends NoteWidgetProvider { + // 更新小部件时调用的方法 @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } - + + // 返回 2x2 小部件的布局 ID @Override protected int getLayoutId() { return R.layout.widget_2x; } - + + // 根据背景 ID 返回对应的 2x2 小部件背景资源 ID @Override protected int getBgResourceId(int bgId) { return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); } - + + // 返回 2x2 小部件的类型 ID @Override protected int getWidgetType() { return Notes.TYPE_WIDGET_2X; } -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_4x.java b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_4x.java index c12a02e..d1b1240 100644 --- a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_4x.java +++ b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_4x.java @@ -1,19 +1,3 @@ -/* - * 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.appwidget.AppWidgetManager; @@ -23,24 +7,28 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; - +// 定义一个4x4小部件的提供者类,继承自NoteWidgetProvider public class NoteWidgetProvider_4x extends NoteWidgetProvider { + // 覆盖父类的onUpdate方法,用于更新小部件的视图 @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } + // 返回4x4小部件的布局资源ID protected int getLayoutId() { return R.layout.widget_4x; } + // 根据背景ID返回4x4小部件的背景资源ID @Override protected int getBgResourceId(int bgId) { return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); } + // 返回小部件的类型,这里是4x4类型 @Override protected int getWidgetType() { return Notes.TYPE_WIDGET_4X; } -} +} \ No newline at end of file