pull/2/head
刘骐瑞 4 months ago
parent 449f7d41e1
commit 1b9d763751

@ -0,0 +1,440 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.ui.NotesPreferenceActivity;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
// GTaskClient 类用于与 Google Tasks 服务进行交互,包括登录、创建任务、创建任务列表等操作
public class GTaskClient {
// 日志标签,用于在日志中标识该类的信息
private static final String TAG = GTaskClient.class.getSimpleName();
// Google Tasks 的基础 URL
private static final String GTASK_URL = "https://mail.google.com/tasks/";
// 用于获取数据的 URL
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
// 用于提交数据的 URL
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
// 单例模式的实例
private static GTaskClient mInstance = null;
// HTTP 客户端,用于发送 HTTP 请求
private DefaultHttpClient mHttpClient;
// 当前使用的获取数据的 URL
private String mGetUrl;
// 当前使用的提交数据的 URL
private String mPostUrl;
// 客户端版本号
private long mClientVersion;
// 登录状态标识
private boolean mLoggedin;
// 上次登录时间
private long mLastLoginTime;
// 操作 ID用于标识每个操作
private int mActionId;
// 当前使用的 Google 账户
private Account mAccount;
// 待更新的操作数组
private JSONArray mUpdateArray;
// 私有构造函数,确保单例模式
private GTaskClient() {
// 初始化 HTTP 客户端为 null
mHttpClient = null;
// 初始化获取数据的 URL
mGetUrl = GTASK_GET_URL;
// 初始化提交数据的 URL
mPostUrl = GTASK_POST_URL;
// 初始化客户端版本号为 -1
mClientVersion = -1;
// 初始化登录状态为未登录
mLoggedin = false;
// 初始化上次登录时间为 0
mLastLoginTime = 0;
// 初始化操作 ID 为 1
mActionId = 1;
// 初始化账户为 null
mAccount = null;
// 初始化待更新操作数组为 null
mUpdateArray = null;
}
// 获取单例实例的静态方法
public static synchronized GTaskClient getInstance() {
// 如果实例为空,则创建一个新的实例
if (mInstance == null) {
mInstance = new GTaskClient();
}
// 返回实例
return mInstance;
}
// 登录 Google Tasks 服务的方法
public boolean login(Activity activity) {
// 假设 cookie 有效期为 5 分钟,超过该时间需要重新登录
final long interval = 1000 * 60 * 5;
// 如果距离上次登录超过 5 分钟,将登录状态设置为未登录
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false;
}
// 如果已登录,但账户发生切换,需要重新登录
if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
mLoggedin = false;
}
// 如果已经登录,打印日志并返回 true
if (mLoggedin) {
Log.d(TAG, "already logged in");
return true;
}
// 更新上次登录时间
mLastLoginTime = System.currentTimeMillis();
// 登录 Google 账户并获取认证令牌
String authToken = loginGoogleAccount(activity, false);
// 如果认证令牌为空,登录失败,打印错误日志并返回 false
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// 如果账户不是 gmail.com 或 googlemail.com 结尾,使用自定义域名登录
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
// 构建自定义域名的 URL
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
url.append(suffix + "/");
mGetUrl = url.toString() + "ig";
mPostUrl = url.toString() + "r/ig";
// 尝试使用自定义域名登录 Google Tasks
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true;
}
}
// 如果使用自定义域名登录失败,尝试使用 Google 官方 URL 登录
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
// 如果登录失败,打印错误日志并返回 false
if (!tryToLoginGtask(activity, authToken)) {
return false;
}
}
// 登录成功,设置登录状态为已登录并返回 true
mLoggedin = true;
return true;
}
// 登录 Google 账户并获取认证令牌的方法
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
// 获取账户管理器
AccountManager accountManager = AccountManager.get(activity);
// 获取所有 Google 账户
Account[] accounts = accountManager.getAccountsByType("com.google");
// 如果没有可用的 Google 账户,打印错误日志并返回 null
if (accounts.length == 0) {
Log.e(TAG, "there is no available google account");
return null;
}
// 获取设置中同步的账户名
String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
Account account = null;
// 查找与设置中账户名相同的账户
for (Account a : accounts) {
if (a.name.equals(accountName)) {
account = a;
break;
}
}
// 如果找到账户,将其赋值给 mAccount
if (account != null) {
mAccount = account;
} else {
// 如果未找到账户,打印错误日志并返回 null
Log.e(TAG, "unable to get an account with the same name in the settings");
return null;
}
// 获取认证令牌
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
try {
// 获取认证令牌的结果
Bundle authTokenBundle = accountManagerFuture.getResult();
// 从结果中获取认证令牌
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
// 如果需要使令牌失效,调用 invalidateAuthToken 方法并重新获取令牌
if (invalidateToken) {
accountManager.invalidateAuthToken("com.google", authToken);
loginGoogleAccount(activity, false);
}
} catch (Exception e) {
// 如果获取认证令牌失败,打印错误日志并将 authToken 设置为 null
Log.e(TAG, "get auth token failed");
authToken = null;
}
// 返回认证令牌
return authToken;
}
// 尝试登录 Google Tasks 服务的方法
private boolean tryToLoginGtask(Activity activity, String authToken) {
// 首先尝试使用当前认证令牌登录
if (!loginGtask(authToken)) {
// 如果登录失败,可能是认证令牌过期,使令牌失效并重新获取
authToken = loginGoogleAccount(activity, true);
// 如果重新获取令牌失败,打印错误日志并返回 false
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// 再次尝试使用新的认证令牌登录
if (!loginGtask(authToken)) {
// 如果仍然登录失败,打印错误日志并返回 false
Log.e(TAG, "login gtask failed");
return false;
}
}
// 登录成功,返回 true
return true;
}
// 使用认证令牌登录 Google Tasks 服务的方法
private boolean loginGtask(String authToken) {
// 设置连接超时时间为 10 秒
int timeoutConnection = 10000;
// 设置套接字超时时间为 15 秒
int timeoutSocket = 15000;
// 创建 HTTP 参数对象
HttpParams httpParameters = new BasicHttpParams();
// 设置连接超时时间
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
// 设置套接字超时时间
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
// 创建 HTTP 客户端
mHttpClient = new DefaultHttpClient(httpParameters);
// 创建 cookie 存储对象
BasicCookieStore localBasicCookieStore = new BasicCookieStore();
// 设置 HTTP 客户端的 cookie 存储
mHttpClient.setCookieStore(localBasicCookieStore);
// 设置不使用 Expect: 100-continue 机制
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// 构建登录 URL
try {
String loginUrl = mGetUrl + "?auth=" + authToken;
// 创建 HTTP GET 请求
HttpGet httpGet = new HttpGet(loginUrl);
HttpResponse response = null;
// 执行 HTTP GET 请求
response = mHttpClient.execute(httpGet);
// 获取 cookie
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false;
// 检查是否存在包含 "GTL" 的 cookie
for (Cookie cookie : cookies) {
if (cookie.getName().contains("GTL")) {
hasAuthCookie = true;
}
}
// 如果没有找到认证 cookie打印警告日志
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie");
}
// 获取响应内容
String resString = getResponseContent(response.getEntity());
// 定义 JSON 字符串的开始和结束标记
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
// 查找 JSON 字符串的开始和结束位置
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
String jsString = null;
// 如果找到开始和结束位置,提取 JSON 字符串
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
// 将 JSON 字符串转换为 JSONObject
JSONObject js = new JSONObject(jsString);
// 获取客户端版本号
mClientVersion = js.getLong("v");
} catch (JSONException e) {
// 如果处理 JSON 数据时发生异常,打印错误日志并返回 false
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
} catch (Exception e) {
// 如果执行 HTTP 请求时发生异常,打印错误日志并返回 false
Log.e(TAG, "httpget gtask_url failed");
return false;
}
// 登录成功,返回 true
return true;
}
// 获取操作 ID 的方法,每次调用操作 ID 加 1
private int getActionId() {
return mActionId++;
}
// 创建 HTTP POST 请求的方法
private HttpPost createHttpPost() {
// 创建 HTTP POST 请求对象
HttpPost httpPost = new HttpPost(mPostUrl);
// 设置请求头的 Content-Type
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
// 设置请求头的 AT 字段
httpPost.setHeader("AT", "1");
// 返回 HTTP POST 请求对象
return httpPost;
}
// 获取 HTTP 响应内容的方法
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
// 获取响应内容的编码
if (entity.getContentEncoding() != null) {
contentEncoding = entity.getContentEncoding().getValue();
Log.d(TAG, "encoding: " + contentEncoding);
}
// 获取响应内容的输入流
InputStream input = entity.getContent();
// 如果响应内容使用 gzip 压缩,使用 GZIPInputStream 解压
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
input = new GZIPInputStream(entity.getContent());
} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
// 如果响应内容使用 deflate 压缩,使用 InflaterInputStream 解压
Inflater inflater = new Inflater(true);
input = new InflaterInputStream(entity.getContent(), inflater);
}
try {
// 创建输入流读取器
InputStreamReader isr = new InputStreamReader(input);
// 创建缓冲读取器
BufferedReader br = new BufferedReader(isr);
// 创建字符串构建器
StringBuilder sb = new StringBuilder();
while (true) {
// 读取一行内容
String buff = br.readLine();
// 如果读取到文件末尾,返回字符串构建器的内容
if (buff == null) {
return sb.toString();
}
// 将读取的内容追加到字符串构建器中
sb = sb.append(buff);
}
} finally {
// 关闭输入流
input.close();
}
}
// 发送 POST 请求的方法
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
// 如果未登录,抛出异常并打印错误日志
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
// 创建 HTTP POST 请求
HttpPost httpPost = createHttpPost();
try {
// 创建参数列表
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
// 将 JSON 对象转换为字符串并添加到参数列表中
list.add(new BasicNameValuePair("r", js.toString()));
// 创建 URL 编码的表单实体
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
// 设置请求实体
httpPost.setEntity(entity);
// 执行 HTTP POST 请求
HttpResponse response = mHttpClient.execute(httpPost);
// 获取响应内容
String jsString = getResponseContent(response.getEntity());
// 将响应内容转换为 JSONObject 并返回
return new JSONObject(jsString);
} catch (ClientProtocolException e) {
// 如果发生客户端协议异常,抛出网络失败异常并打印错误日志
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
} catch (IOException e) {
// 如果发生 IO 异常,抛出网络失败异常并打印错误日志
Log.e(TAG,

@ -0,0 +1,439 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.data.MetaData;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.SqlNote;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
/**
* GTaskManager Google
* 使
*/
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 获取的任务列表,键为任务列表的全局 ID
private HashMap<String, TaskList> mGTaskListHashMap;
// 存储从 Google 获取的所有任务节点,键为任务的全局 ID
private HashMap<String, Node> mGTaskHashMap;
// 存储元数据,键为相关任务的全局 ID
private HashMap<String, MetaData> mMetaHashMap;
// 元数据任务列表
private TaskList mMetaList;
// 存储本地已删除的笔记 ID
private HashSet<Long> mLocalDeleteIdMap;
// 用于将 Google 任务 ID 映射到本地笔记 ID
private HashMap<String, Long> mGidToNid;
// 用于将本地笔记 ID 映射到 Google 任务 ID
private HashMap<Long, String> mNidToGid;
/**
*
*/
private GTaskManager() {
// 初始化同步状态
mSyncing = false;
mCancelled = false;
// 初始化存储数据的集合
mGTaskListHashMap = new HashMap<String, TaskList>();
mGTaskHashMap = new HashMap<String, Node>();
mMetaHashMap = new HashMap<String, MetaData>();
mMetaList = null;
mLocalDeleteIdMap = new HashSet<Long>();
mGidToNid = new HashMap<String, Long>();
mNidToGid = new HashMap<Long, String>();
}
/**
* GTaskManager
* @return GTaskManager
*/
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
// 如果实例不存在,则创建一个新的实例
mInstance = new GTaskManager();
}
return mInstance;
}
/**
* Activity
* @param activity Activity
*/
public synchronized void setActivityContext(Activity activity) {
mActivity = activity;
}
/**
* Google
* @param context
* @param asyncTask
* @return
*/
public int sync(Context context, GTaskASyncTask asyncTask) {
if (mSyncing) {
// 如果同步正在进行,记录日志并返回同步进行中状态码
Log.d(TAG, "Sync is in progress");
return STATE_SYNC_IN_PROGRESS;
}
// 设置上下文和内容解析器
mContext = context;
mContentResolver = mContext.getContentResolver();
// 标记同步开始
mSyncing = true;
mCancelled = false;
// 清空存储数据的集合
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
try {
// 获取 GTaskClient 实例
GTaskClient client = GTaskClient.getInstance();
// 重置更新数组
client.resetUpdateArray();
// 登录 Google 任务
if (!mCancelled) {
if (!client.login(mActivity)) {
// 登录失败,抛出网络异常
throw new NetworkFailureException("login google task failed");
}
}
// 从 Google 获取任务列表
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList();
// 执行内容同步工作
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent();
} catch (NetworkFailureException e) {
// 捕获网络异常,记录日志并返回网络错误状态码
Log.e(TAG, e.toString());
return STATE_NETWORK_ERROR;
} catch (ActionFailureException e) {
// 捕获操作失败异常,记录日志并返回内部错误状态码
Log.e(TAG, e.toString());
return STATE_INTERNAL_ERROR;
} catch (Exception e) {
// 捕获其他异常,记录日志并返回内部错误状态码
Log.e(TAG, e.toString());
e.printStackTrace();
return STATE_INTERNAL_ERROR;
} finally {
// 清空存储数据的集合
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
// 标记同步结束
mSyncing = false;
}
// 根据取消状态返回同步结果状态码
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
/**
* Google
* @throws NetworkFailureException
*/
private void initGTaskList() throws NetworkFailureException {
if (mCancelled)
return;
// 获取 GTaskClient 实例
GTaskClient client = GTaskClient.getInstance();
try {
// 从 Google 获取任务列表的 JSON 数组
JSONArray jsTaskLists = client.getTaskLists();
// 初始化元数据列表
mMetaList = null;
for (int i = 0; i < jsTaskLists.length(); i++) {
// 获取每个任务列表的 JSON 对象
JSONObject object = jsTaskLists.getJSONObject(i);
// 获取任务列表的全局 ID
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();
// 根据远程 JSON 数据设置任务列表内容
mMetaList.setContentByRemoteJSON(object);
// 加载元数据
JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) {
// 获取每个元数据的 JSON 对象
object = (JSONObject) jsMetas.getJSONObject(j);
MetaData metaData = new MetaData();
// 根据远程 JSON 数据设置元数据内容
metaData.setContentByRemoteJSON(object);
if (metaData.isWorthSaving()) {
// 如果元数据值得保存,添加到元数据列表
mMetaList.addChildTask(metaData);
if (metaData.getGid() != null) {
// 将元数据添加到元数据哈希表
mMetaHashMap.put(metaData.getRelatedGid(), metaData);
}
}
}
}
}
// 如果元数据列表不存在,创建一个新的元数据列表
if (mMetaList == null) {
mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META);
GTaskClient.getInstance().createTaskList(mMetaList);
}
// 初始化任务列表
for (int i = 0; i < jsTaskLists.length(); i++) {
// 获取每个任务列表的 JSON 对象
JSONObject object = jsTaskLists.getJSONObject(i);
// 获取任务列表的全局 ID
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();
// 根据远程 JSON 数据设置任务列表内容
tasklist.setContentByRemoteJSON(object);
// 将任务列表添加到任务列表哈希表
mGTaskListHashMap.put(gid, tasklist);
// 将任务列表添加到任务哈希表
mGTaskHashMap.put(gid, tasklist);
// 加载任务
JSONArray jsTasks = client.getTaskList(gid);
for (int j = 0; j < jsTasks.length(); j++) {
// 获取每个任务的 JSON 对象
object = (JSONObject) jsTasks.getJSONObject(j);
// 获取任务的全局 ID
gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
Task task = new Task();
// 根据远程 JSON 数据设置任务内容
task.setContentByRemoteJSON(object);
if (task.isWorthSaving()) {
// 如果任务值得保存,设置元信息并添加到任务列表
task.setMetaInfo(mMetaHashMap.get(gid));
tasklist.addChildTask(task);
// 将任务添加到任务哈希表
mGTaskHashMap.put(gid, task);
}
}
}
}
} catch (JSONException e) {
// 捕获 JSON 解析异常,记录日志并抛出操作失败异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("initGTaskList: handing JSONObject failed");
}
}
/**
*
* @throws NetworkFailureException
*/
private void syncContent() throws NetworkFailureException {
int syncType;
Cursor c = null;
String gid;
Node node;
// 清空本地已删除笔记 ID 集合
mLocalDeleteIdMap.clear();
if (mCancelled) {
return;
}
// 处理本地已删除的笔记
try {
// 查询本地已删除的笔记
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id=?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, null);
if (c != null) {
while (c.moveToNext()) {
// 获取笔记的全局 ID
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);
}
// 将本地已删除的笔记 ID 添加到集合中
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();
// 处理数据库中存在的笔记
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()) {
// 获取笔记的全局 ID
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
// 从任务哈希表中获取对应的任务节点
node = mGTaskHashMap.get(gid);
if (node != null) {
// 如果节点存在,从任务哈希表中移除该节点
mGTaskHashMap.remove(gid);
// 建立全局 ID 到本地笔记 ID 的映射
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
// 建立本地笔记 ID 到全局 ID 的映射
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
// 获取同步类型
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 如果全局 ID 为空,说明是本地新增的笔记
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// 否则,说明是远程已删除的笔记
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
// 执行同步操作
doContentSync(syncType, node, c);
}
} else {
// 查询失败,记录警告日志
Log.w(TAG, "failed to query existing note in database");
}
} finally {
if (c != null) {
// 关闭游标
c.close();
c = null;
}
}
// 处理剩余的任务节点
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Node> entry = iter.next();
node = entry.getValue();
// 执行添加本地任务的同步操作
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
// 检查是否被取消
if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
// 批量删除本地已删除的笔记失败,抛出操作失败异常
throw new ActionFailureException("failed to batch-delete local deleted notes");
}
}
// 检查是否被取消
if (!mCancelled) {
// 提交更新
GTaskClient.getInstance().commitUpdate();

@ -0,0 +1,211 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import android.view.Window;
import android.view.WindowManager;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import java.io.IOException;
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
private long mNoteId;
private String mSnippet;
private static final int SNIPPET_PREW_MAX_LEN = 60;
MediaPlayer mPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
// 调用父类的onCreate方法初始化Activity
super.onCreate(savedInstanceState);
// 请求无标题窗口特性
requestWindowFeature(Window.FEATURE_NO_TITLE);
// 获取当前Activity的Window对象
final Window win = getWindow();
// 设置窗口标志,使窗口在锁屏时仍然显示
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
// 检查屏幕是否处于关闭状态
if (!isScreenOn()) {
// 如果屏幕关闭,则添加多个标志以保持屏幕常亮、解锁屏幕并允许在屏幕上显示内容
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
}
// 获取当前Activity的Intent对象
Intent intent = getIntent();
try {
// 从Intent的数据中获取Note的ID
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
// 根据Note的ID从内容解析器中获取对应的摘要信息
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
// 如果摘要信息长度超过预设的最大长度,则截取并添加省略号
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
: mSnippet;
} catch (IllegalArgumentException e) {
// 捕获并打印非法参数异常
e.printStackTrace();
// 发生异常时,直接返回,不继续执行后续代码
return;
}
// 创建一个新的MediaPlayer对象
mPlayer = new MediaPlayer();
// 检查Note是否在Note数据库中可见
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
// 如果可见,则显示操作对话框并播放警报声音
showActionDialog();
playAlarmSound();
} else {
// 如果不可见则结束当前Activity
finish();
}
}
// 定义一个私有方法isScreenOn用于检查设备屏幕是否处于开启状态
private boolean isScreenOn() {
// 获取PowerManager系统服务用于管理电源相关的功能
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
// 调用PowerManager的isScreenOn方法返回屏幕是否开启的布尔值
return pm.isScreenOn();
}
// 定义一个私有方法用于播放闹钟声音
private void playAlarmSound() {
// 获取系统默认的闹钟铃声URI
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
// 获取当前系统设置中静音模式影响的音频流类型
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
// 检查当前静音模式是否影响闹钟音频流
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
// 如果影响则设置MediaPlayer的音频流类型为静音模式影响的类型
mPlayer.setAudioStreamType(silentModeStreams);
} else {
// 如果不影响则设置MediaPlayer的音频流类型为闹钟类型
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
}
try {
// 设置MediaPlayer的数据源为闹钟铃声的URI
mPlayer.setDataSource(this, url);
// 准备MediaPlayer
mPlayer.prepare();
// 设置MediaPlayer为循环播放
mPlayer.setLooping(true);
mPlayer.start();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 定义一个私有方法showActionDialog用于显示操作对话框
private void showActionDialog() {
// 创建一个AlertDialog.Builder对象用于构建对话框
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
// 设置对话框的标题,使用资源文件中的字符串
dialog.setTitle(R.string.app_name);
// 设置对话框的消息内容使用成员变量mSnippet的值
dialog.setMessage(mSnippet);
// 设置对话框的确定按钮并指定点击事件监听器为当前对象this
dialog.setPositiveButton(R.string.notealert_ok, this);
// 检查屏幕是否处于开启状态
if (isScreenOn()) {
// 如果屏幕开启则设置对话框的取消按钮并指定点击事件监听器为当前对象this
dialog.setNegativeButton(R.string.notealert_enter, this);
}
// 显示对话框并设置对话框关闭时的监听器为当前对象this
dialog.show().setOnDismissListener(this);
}
// 定义一个方法,当对话框中的按钮被点击时调用
public void onClick(DialogInterface dialog, int which) {
// 使用switch语句根据点击的按钮类型进行不同的处理
switch (which) {
// 如果点击的是对话框的否定按钮(通常是“取消”或“否”)
case DialogInterface.BUTTON_NEGATIVE:
// 创建一个新的Intent对象用于启动NoteEditActivity
Intent intent = new Intent(this, NoteEditActivity.class);
// 设置Intent的动作类型为查看ACTION_VIEW
intent.setAction(Intent.ACTION_VIEW);
// 将笔记的ID作为额外数据添加到Intent中键为Intent.EXTRA_UID
intent.putExtra(Intent.EXTRA_UID, mNoteId);
// 启动NoteEditActivity传递Intent对象
startActivity(intent);
break;
// 默认情况,不做任何处理
default:
break;
}
}
// 定义一个方法,当对话框被取消或关闭时调用
public void onDismiss(DialogInterface dialog) {
// 当对话框被取消或关闭时,执行以下操作
// 调用stopAlarmSound方法停止闹钟声音
stopAlarmSound();
// 调用finish方法结束当前Activity
finish();
}
// 定义一个私有方法用于停止闹钟声音
private void stopAlarmSound() {
// 检查mPlayer对象是否不为空以避免空指针异常
if (mPlayer != null) {
// 调用mPlayer的stop方法停止播放声音
mPlayer.stop();
// 调用mPlayer的release方法释放资源
mPlayer.release();
// 将mPlayer对象设置为null表示不再持有该对象
mPlayer = null;
}
}
}

@ -0,0 +1,79 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
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, // 笔记的ID
NoteColumns.ALERTED_DATE // 笔记的提醒日期
};
// 定义列的索引
private static final int COLUMN_ID = 0; // ID列的索引
private static final int COLUMN_ALERTED_DATE = 1; // 提醒日期列的索引
@Override
public void onReceive(Context context, Intent intent) {
// 获取当前时间
long currentDate = System.currentTimeMillis();
// 查询提醒日期大于当前时间且类型为笔记的记录
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
new String[] { String.valueOf(currentDate) },
null);
if (c != null) {
// 如果查询结果非空,遍历结果集
if (c.moveToFirst()) {
do {
// 获取提醒日期
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
// 创建Intent指定接收器为AlarmReceiver
Intent sender = new Intent(context, AlarmReceiver.class);
// 设置Intent的数据包含笔记的ID
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
// 创建PendingIntent用于延迟发送广播
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 获取AlarmManager服务
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
// 设置闹钟,在提醒日期触发广播
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
// 关闭Cursor
c.close();
}
}
}

@ -0,0 +1,35 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
// 定义一个名为AlarmReceiver的类继承自BroadcastReceiver用于接收广播消息
public class AlarmReceiver extends BroadcastReceiver {
// 重写onReceive方法该方法会在接收到广播时被调用
@Override
public void onReceive(Context context, Intent intent) {
// 将Intent的类设置为AlarmAlertActivity即指定要启动的Activity
intent.setClass(context, AlarmAlertActivity.class);
// 为Intent添加FLAG_ACTIVITY_NEW_TASK标志表示启动一个新的任务来承载该Activity
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 使用上下文context启动Activity传入修改后的Intent
context.startActivity(intent);
}
}

@ -0,0 +1,655 @@
/*
* 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;
// 24小时制小时选择器的最小值
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
// 24小时制小时选择器的最大值
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
// 12小时制小时选择器的最小值
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
// 12小时制小时选择器的最大值
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
// 分钟选择器的最小值
private static final int MINUT_SPINNER_MIN_VAL = 0;
// 分钟选择器的最大值
private static final int MINUT_SPINNER_MAX_VAL = 59;
// AM/PM选择器的最小值
private static final int AMPM_SPINNER_MIN_VAL = 0;
// AM/PM选择器的最大值
private static final int AMPM_SPINNER_MAX_VAL = 1;
// 日期选择器
private final NumberPicker mDateSpinner;
// 小时选择器
private final NumberPicker mHourSpinner;
// 分钟选择器
private final NumberPicker mMinuteSpinner;
// AM/PM选择器
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);
}
// 构造函数
// 构造函数用于创建DateTimePicker对象
// 参数context上下文对象用于获取应用环境和资源
public DateTimePicker(Context context) {
// 调用另一个构造函数,传入当前系统时间作为默认值
// System.currentTimeMillis():获取当前系统时间的毫秒值
this(context, System.currentTimeMillis());
}
// 构造函数用于创建DateTimePicker对象
// 参数context上下文对象用于获取系统资源和配置
// 参数date初始日期时间以毫秒为单位的长整型数值
public DateTimePicker(Context context, long date) {
// 调用另一个构造函数传入当前上下文、初始日期时间和是否使用24小时制
// DateFormat.is24HourFormat(context)方法用于判断当前系统是否使用24小时制
this(context, date, DateFormat.is24HourFormat(context));
}
// 构造函数用于初始化DateTimePicker对象
public DateTimePicker(Context context, long date, boolean is24HourView) {
// 调用父类的构造函数,传入上下文
super(context);
// 初始化Calendar对象用于存储日期和时间
mDate = Calendar.getInstance();
// 设置初始化标志为true表示正在初始化
mInitialising = true;
// 获取当前小时,判断是否为上午或下午
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
// 加载布局文件datetime_picker.xml并将其设置为当前视图
inflate(context, R.layout.datetime_picker, this);
// 获取日期选择器NumberPicker并设置其最小值和最大值
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
// 设置日期选择器的值变化监听器
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);
// 获取小时选择器NumberPicker并设置其值变化监听器
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
// 获取分钟选择器NumberPicker并设置其最小值和最大值
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();
// 获取上午/下午选择器NumberPicker并设置其最小值、最大值和显示的值
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;
}
// 调用父类的setEnabled方法设置组件的基本启用状态
super.setEnabled(enabled);
// 设置日期选择器的启用状态
mDateSpinner.setEnabled(enabled);
// 设置分钟选择器的启用状态
mMinuteSpinner.setEnabled(enabled);
// 设置小时选择器的启用状态
mHourSpinner.setEnabled(enabled);
// 设置上午/下午选择器的启用状态
mAmPmSpinner.setEnabled(enabled);
// 更新当前组件的启用状态标志
mIsEnabled = enabled;
}
// 重写父类或接口中的isEnabled方法
@Override
public boolean isEnabled() {
// 返回成员变量mIsEnabled的值表示当前对象是否启用
return mIsEnabled;
}
/**
* Get the current date in millis
*
* @return the current date in millis
*/
// 定义一个公共方法,返回当前日期的时间戳(以毫秒为单位)
public long getCurrentDateInTimeMillis() {
// 调用mDate对象的getTimeInMillis方法获取当前日期的时间戳
return mDate.getTimeInMillis();
}
/**
* Set the current date
*
* @param date The current date in millis
*/
// 定义一个方法用于设置当前日期和时间
public void setCurrentDate(long date) {
// 获取一个Calendar实例
Calendar cal = Calendar.getInstance();
// 将传入的日期以毫秒为单位设置到Calendar对象中
cal.setTimeInMillis(date);
// 调用另一个重载的setCurrentDate方法传入年、月、日、小时和分钟
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() {
// 调用mDate对象的get方法传入Calendar.YEAR常量获取当前年份
return mDate.get(Calendar.YEAR);
}
/**
* Set current year
*
* @param year The current year
*/
// 设置当前年份的方法
public void setCurrentYear(int year) {
// 检查是否正在初始化以及年份是否与当前年份相同
// 如果正在初始化或者年份未改变,则直接返回,不进行后续操作
if (!mInitialising && year == getCurrentYear()) {
return;
}
// 使用Calendar对象设置日期的年份
mDate.set(Calendar.YEAR, year);
// 更新日期控件,以反映新的年份
updateDateControl();
// 调用方法处理日期时间变化可能触发相关事件或更新UI
onDateTimeChanged();
}
/**
* Get current month in the year
*
* @return The current month in the year
*/
// 定义一个公共方法,用于获取当前月份
public int getCurrentMonth() {
// 调用mDate对象的get方法传入Calendar.MONTH常量获取当前日期中的月份
// Calendar.MONTH的值是从0开始的即0代表一月1代表二月依此类推
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;
}
// 使用Calendar对象设置日期的月份
mDate.set(Calendar.MONTH, month);
// 更新日期控件,以反映新的月份
updateDateControl();
// 调用onDateTimeChanged方法通知日期时间已更改
onDateTimeChanged();
}
/**
* Get current day of the month
*
* @return The day of the month
*/
// 定义一个公共方法,用于获取当前日期的天数
public int getCurrentDay() {
// 调用Calendar实例mDate的get方法传入Calendar.DAY_OF_MONTH常量
// 该常量表示获取当前日期中的天数
// 返回当前日期的天数
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Set current day of the month
*
* @param dayOfMonth The day of the month
*/
// 设置当前日期的天数
public void setCurrentDay(int dayOfMonth) {
// 检查是否正在初始化以及传入的天数是否与当前天数相同
if (!mInitialising && dayOfMonth == getCurrentDay()) {
// 如果相同,则直接返回,不做任何操作
return;
}
// 使用Calendar对象设置日期的天数
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
*/
// 定义一个公共方法用于获取当前的小时数24小时制
public int getCurrentHourOfDay() {
// 调用mDate对象的get方法传入Calendar.HOUR_OF_DAY常量
// 该常量表示获取当前时间的小时数24小时制
// 返回获取到的小时数
return mDate.get(Calendar.HOUR_OF_DAY);
}
// 获取当前小时数的私有方法
private int getCurrentHour() {
// 检查是否为24小时制视图
if (mIs24HourView){
// 如果是24小时制直接返回当前的小时数
return getCurrentHourOfDay();
} else {
// 如果不是24小时制先获取当前的小时数
int hour = getCurrentHourOfDay();
// 检查当前小时数是否超过半天的小时数12小时
if (hour > HOURS_IN_HALF_DAY) {
// 如果超过12小时返回减去半天小时数的结果转换为下午的小时数
return hour - HOURS_IN_HALF_DAY;
} else {
// 如果当前小时数为0即午夜则返回半天小时数12
// 否则,直接返回当前小时数(上午的小时数)
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
*
* @param hourOfDay
*/
// 设置当前小时的方法
public void setCurrentHour(int hourOfDay) {
// 检查是否正在初始化且小时数与当前小时相同,如果是则直接返回
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
// 使用Calendar对象设置小时数
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
// 检查是否为24小时制
if (!mIs24HourView) {
// 如果小时数大于等于半天的小时数12小时则设置为下午
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;
// 如果小时数大于半天的小时数,则减去半天的小时数
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
// 否则设置为上午
mIsAm = true;
// 如果小时数为0则设置为半天的小时数
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;
}
}
// 更新上午/下午控件
updateAmPmControl();
}
// 设置小时选择器的值
mHourSpinner.setValue(hourOfDay);
// 触发时间变化事件
onDateTimeChanged();
}
/**
* Get currentMinute
*
* @return The Current Minute
*/
// 定义一个公共方法,用于获取当前分钟数
public int getCurrentMinute() {
// 调用mDate对象的get方法传入Calendar.MINUTE常量获取当前时间的分钟数
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.
*/
// 定义一个公共的方法用于判断是否为24小时制视图
public boolean is24HourView () {
// 返回成员变量mIs24HourView的值该变量用于存储是否为24小时制的状态
return mIs24HourView;
}
/**
* Set whether in 24 hour or AM/PM mode.
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
*/
// 设置24小时制视图
public void set24HourView(boolean is24HourView) {
// 如果当前24小时制视图状态与传入的参数相同则直接返回不做任何操作
if (mIs24HourView == is24HourView) {
return;
}
// 更新24小时制视图状态
mIs24HourView = is24HourView;
// 根据是否为24小时制视图设置上午/下午选择器的可见性
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
// 获取当前的小时数
int hour = getCurrentHourOfDay();
// 更新小时控制部分
updateHourControl();
// 设置当前小时数
setCurrentHour(hour);
// 更新上午/下午控制部分
updateAmPmControl();
}
// 更新日期控件的方法
private void updateDateControl() {
// 获取当前时间的Calendar实例
Calendar cal = Calendar.getInstance();
// 将Calendar实例的时间设置为mDate的时间
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() {
// 检查是否为24小时制视图
if (mIs24HourView) {
// 如果是24小时制视图则隐藏上午/下午选择器
mAmPmSpinner.setVisibility(View.GONE);
} else {
// 如果不是24小时制视图则根据当前是上午还是下午设置选择器的值
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
// 显示上午/下午选择器
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}
// 定义一个私有方法updateHourControl用于更新小时选择器的范围
private void updateHourControl() {
// 检查是否为24小时制视图
if (mIs24HourView) {
// 如果是24小时制视图设置小时选择器的最小值为HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
// 设置小时选择器的最大值为HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
// 如果不是24小时制视图设置小时选择器的最小值为HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
// 设置小时选择器的最大值为HOUR_SPINNER_MAX_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
mOnDateTimeChangedListener = callback;
}
// 定义一个私有方法,用于处理日期时间变化事件
private void onDateTimeChanged() {
// 检查是否设置了日期时间变化监听器
if (mOnDateTimeChangedListener != null) {
// 如果监听器不为空则调用监听器的onDateTimeChanged方法
// 传递当前对象以及当前的年、月、日、小时和分钟
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}

@ -0,0 +1,124 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import java.util.Calendar;
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
// 定义一个日历对象,用于存储日期和时间
private Calendar mDate = Calendar.getInstance();
// 是否使用24小时制
private boolean mIs24HourView;
// 日期时间设置监听器
private OnDateTimeSetListener mOnDateTimeSetListener;
// 日期时间选择器
private DateTimePicker mDateTimePicker;
// 定义一个接口,用于监听日期时间设置事件
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
}
// 构造函数,初始化日期时间选择器
// 构造函数用于创建DateTimePickerDialog对象
public DateTimePickerDialog(Context context, long date) {
// 调用父类的构造函数,传入上下文
super(context);
// 创建DateTimePicker对象用于选择日期和时间
mDateTimePicker = new DateTimePicker(context);
// 将DateTimePicker对象设置为对话框的视图
setView(mDateTimePicker);
// 设置日期时间变化监听器,当日期或时间发生变化时触发
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
// 监听器回调方法,当日期或时间发生变化时调用
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
// 更新日历对象的日期和时间
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
// 更新对话框标题
updateTitle(mDate.getTimeInMillis());
}
});
// 设置初始日期和时间
mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0);
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
// 设置对话框的确定和取消按钮
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
// 设置是否使用24小时制
set24HourView(DateFormat.is24HourFormat(this.getContext()));
// 更新对话框标题
updateTitle(mDate.getTimeInMillis());
}
// 设置是否使用24小时制
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
// 设置日期时间设置监听器
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
// 更新对话框标题
// 定义一个私有方法updateTitle用于更新标题接收一个长整型参数date表示日期时间
private void updateTitle(long date) {
// 定义一个整型变量flag用于存储日期时间的格式标志
int flag =
// 使用位或运算符将FORMAT_SHOW_YEAR标志添加到flag中表示显示年份
DateUtils.FORMAT_SHOW_YEAR |
// 使用位或运算符将FORMAT_SHOW_DATE标志添加到flag中表示显示日期
DateUtils.FORMAT_SHOW_DATE |
// 使用位或运算符将FORMAT_SHOW_TIME标志添加到flag中表示显示时间
DateUtils.FORMAT_SHOW_TIME;
// 使用位或运算符将FORMAT_24HOUR标志添加到flag中
// 如果mIs24HourView为true则添加FORMAT_24HOUR标志表示使用24小时制
// 如果mIs24HourView为false则同样添加FORMAT_24HOUR标志这里可能是一个逻辑错误应该根据mIs24HourView的值决定是否添加该标志
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
// 调用DateUtils的formatDateTime方法将日期时间格式化为指定格式并设置为当前上下文的标题
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
// 处理对话框按钮点击事件
// 定义一个方法,当对话框被点击时触发
public void onClick(DialogInterface arg0, int arg1) {
// 检查mOnDateTimeSetListener是否不为空确保有监听器注册
if (mOnDateTimeSetListener != null) {
// 调用监听器的OnDateTimeSet方法传递当前对象和日期的毫秒值
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

@ -0,0 +1,78 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import net.micode.notes.R;
public class DropdownMenu {
// 声明一个Button对象用于显示下拉菜单的按钮
private Button mButton;
// 声明一个PopupMenu对象用于创建和管理下拉菜单
private PopupMenu mPopupMenu;
// 声明一个Menu对象用于获取下拉菜单的菜单项
private Menu mMenu;
// 构造函数初始化DropdownMenu对象
public DropdownMenu(Context context, Button button, int menuId) {
// 将传入的Button对象赋值给mButton
mButton = button;
// 设置按钮的背景资源为下拉图标
mButton.setBackgroundResource(R.drawable.dropdown_icon);
// 创建一个PopupMenu对象传入上下文和按钮对象
mPopupMenu = new PopupMenu(context, mButton);
// 获取PopupMenu的Menu对象
mMenu = mPopupMenu.getMenu();
// 使用PopupMenu的MenuInflater对象加载菜单布局文件
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
// 设置按钮的点击监听器
mButton.setOnClickListener(new OnClickListener() {
// 点击按钮时显示PopupMenu
public void onClick(View v) {
mPopupMenu.show();
}
});
}
// 设置下拉菜单项的点击监听器
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
// 如果PopupMenu对象不为空则设置菜单项点击监听器
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
// 根据菜单项ID查找菜单项
public MenuItem findItem(int id) {
// 返回找到的菜单项
return mMenu.findItem(id);
}
// 设置按钮的标题
public void setTitle(CharSequence title) {
// 将传入的标题设置给按钮
mButton.setText(title);
}
}

@ -0,0 +1,106 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
public class FoldersListAdapter extends CursorAdapter {
// 定义查询的列
public static final String [] PROJECTION = {
NoteColumns.ID, // 笔记ID
NoteColumns.SNIPPET // 笔记摘要
};
// 定义列的索引
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1; // ID列的索引
// 定义一个名为 FoldersListAdapter 的构造函数接收两个参数Context 类型的 context 和 Cursor 类型的 c
public FoldersListAdapter(Context context, Cursor c) {
// 调用父类(可能是 ArrayAdapter 或其他适配器类)的构造函数,传递 context 和 c 参数
super(context, c);
// TODO Auto-generated constructor stub
}
// 重写方法,用于创建一个新的视图实例
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
// 使用传入的上下文对象创建一个新的FolderListItem视图实例
// FolderListItem是一个自定义的视图类用于展示文件夹列表中的每一项
return new FolderListItem(context);
}
@Override
// 重写父类或接口中的方法
public void bindView(View view, Context context, Cursor cursor) {
// 绑定视图的方法,用于将数据绑定到视图上
if (view instanceof FolderListItem) {
// 检查传入的视图是否是FolderListItem的实例
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
// 如果当前游标所在的行的ID是根文件夹的ID
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
// 则获取字符串资源中的"menu_move_parent_folder"作为文件夹名称
// 否则从游标中获取NAME_COLUMN列的值作为文件夹名称
((FolderListItem) view).bind(folderName);
// 将文件夹名称绑定到FolderListItem视图上
}
}
// 定义一个方法,用于获取指定位置的文件夹名称
public String getFolderName(Context context, int position) {
// 通过位置获取对应的Cursor对象
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);
}
// 定义一个内部类FolderListItem继承自LinearLayout用于表示文件夹列表中的一个条目
private class FolderListItem extends LinearLayout {
// 声明一个TextView成员变量mName用于显示文件夹名称
private TextView mName;
// 构造方法接收一个Context对象作为参数
public FolderListItem(Context context) {
// 调用父类LinearLayout的构造方法传入context
super(context);
// 调用inflate方法将布局文件R.layout.folder_list_item填充到当前LinearLayout中
inflate(context, R.layout.folder_list_item, this);
// 通过findViewById方法找到布局文件中的TextView控件并赋值给mName
mName = (TextView) findViewById(R.id.tv_folder_name);
}
// 定义一个bind方法接收一个String类型的name参数用于绑定文件夹名称到TextView上
public void bind(String name) {
// 调用TextView的setText方法将传入的name设置为TextView的文本内容
mName.setText(name);
}
}
}
Loading…
Cancel
Save