From 3a30d87e4ddf7b3ed0f09c3c1f90ef06eaafe199 Mon Sep 17 00:00:00 2001 From: zhangqing <1770666340@qq.com> Date: Tue, 23 Dec 2025 20:15:47 +0800 Subject: [PATCH] 1 --- .../notes/gtask/remote/GTaskClient.java | 371 +++++++++++++----- 1 file changed, 272 insertions(+), 99 deletions(-) diff --git a/src/小米标签代码/src/net/micode/notes/gtask/remote/GTaskClient.java b/src/小米标签代码/src/net/micode/notes/gtask/remote/GTaskClient.java index c67dfdf..34cfbc4 100644 --- a/src/小米标签代码/src/net/micode/notes/gtask/remote/GTaskClient.java +++ b/src/小米标签代码/src/net/micode/notes/gtask/remote/GTaskClient.java @@ -60,48 +60,65 @@ import java.util.zip.GZIPInputStream; import java.util.zip.Inflater; import java.util.zip.InflaterInputStream; - +/** + * GTaskClient 类 + * Google Tasks API客户端,负责与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/"; - + // Google Tasks获取数据的URL private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; - + // Google Tasks提交数据的URL private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; + // 单例实例 private static GTaskClient mInstance = null; + // 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() { mHttpClient = null; - mGetUrl = GTASK_GET_URL; - mPostUrl = GTASK_POST_URL; - mClientVersion = -1; - mLoggedin = false; - mLastLoginTime = 0; - mActionId = 1; - mAccount = null; - mUpdateArray = null; + mGetUrl = GTASK_GET_URL; // 默认获取URL + mPostUrl = GTASK_POST_URL; // 默认提交URL + mClientVersion = -1; // 未获取版本号 + mLoggedin = false; // 未登录状态 + mLastLoginTime = 0; // 最后登录时间为0 + mActionId = 1; // 动作ID从1开始 + mAccount = null; // 未设置账户 + mUpdateArray = null; // 无待提交更新 } + /** + * 获取GTaskClient单例实例 + * + * @return GTaskClient单例实例 + */ public static synchronized GTaskClient getInstance() { if (mInstance == null) { mInstance = new GTaskClient(); @@ -109,153 +126,198 @@ public class GTaskClient { return mInstance; } + /** + * 登录到Google Tasks + * 处理Google账户认证和Tasks服务登录 + * + * @param activity 活动上下文,用于账户管理器交互 + * @return 登录是否成功 + */ public boolean login(Activity activity) { - // we suppose that the cookie would expire after 5 minutes - // then we need to re-login - final long interval = 1000 * 60 * 5; + // 假设Cookie在5分钟后过期,需要重新登录 + final long interval = 1000 * 60 * 5; // 5分钟 if (mLastLoginTime + interval < System.currentTimeMillis()) { - mLoggedin = false; + mLoggedin = false; // 超过5分钟,标记为未登录 } - // need to re-login after account switch + // 切换账户后需要重新登录 if (mLoggedin && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity .getSyncAccountName(activity))) { - mLoggedin = false; + mLoggedin = false; // 账户不匹配,标记为未登录 } + // 如果已经登录,直接返回成功 if (mLoggedin) { Log.d(TAG, "already logged in"); return true; } + // 记录当前时间为最后登录时间 mLastLoginTime = System.currentTimeMillis(); + + // 获取Google账户认证令牌 String authToken = loginGoogleAccount(activity, false); if (authToken == null) { Log.e(TAG, "login google account failed"); - return false; + return false; // 获取认证令牌失败 } - // login with custom domain if necessary + // 如果账户不是gmail.com或googlemail.com,使用自定义域名登录 if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() .endsWith("googlemail.com"))) { StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); int index = mAccount.name.indexOf('@') + 1; String suffix = mAccount.name.substring(index); url.append(suffix + "/"); - mGetUrl = url.toString() + "ig"; - mPostUrl = url.toString() + "r/ig"; + mGetUrl = url.toString() + "ig"; // 设置自定义获取URL + mPostUrl = url.toString() + "r/ig"; // 设置自定义提交URL + // 尝试使用自定义域名登录 if (tryToLoginGtask(activity, authToken)) { - mLoggedin = true; + mLoggedin = true; // 登录成功 } } - // try to login with google official url + // 如果自定义域名登录失败或不是自定义域名,尝试使用官方URL登录 if (!mLoggedin) { - mGetUrl = GTASK_GET_URL; - mPostUrl = GTASK_POST_URL; + mGetUrl = GTASK_GET_URL; // 恢复默认获取URL + mPostUrl = GTASK_POST_URL; // 恢复默认提交URL if (!tryToLoginGtask(activity, authToken)) { - return false; + return false; // 登录失败 } } - mLoggedin = true; - return true; + mLoggedin = true; // 标记为已登录 + return true; // 登录成功 } + /** + * 登录Google账户并获取认证令牌 + * + * @param activity 活动上下文 + * @param invalidateToken 是否使旧令牌失效 + * @return 认证令牌,失败返回null + */ private String loginGoogleAccount(Activity activity, boolean invalidateToken) { String authToken; AccountManager accountManager = AccountManager.get(activity); + + // 获取所有Google账户 Account[] accounts = accountManager.getAccountsByType("com.google"); if (accounts.length == 0) { Log.e(TAG, "there is no available google account"); - return null; + return null; // 没有可用的Google账户 } + // 从设置中获取同步账户名称 String accountName = NotesPreferenceActivity.getSyncAccountName(activity); Account account = null; + + // 查找与设置中名称匹配的账户 for (Account a : accounts) { if (a.name.equals(accountName)) { account = a; break; } } + if (account != null) { - mAccount = account; + mAccount = account; // 设置当前账户 } else { Log.e(TAG, "unable to get an account with the same name in the settings"); - return null; + return null; // 找不到匹配的账户 } - // get the token now + // 获取认证令牌 AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, "goanna_mobile", null, activity, null, null); try { Bundle authTokenBundle = accountManagerFuture.getResult(); authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); + + // 如果需要使令牌失效 if (invalidateToken) { accountManager.invalidateAuthToken("com.google", authToken); - loginGoogleAccount(activity, false); + // 重新获取新令牌 + return loginGoogleAccount(activity, false); } } catch (Exception e) { Log.e(TAG, "get auth token failed"); - authToken = null; + authToken = null; // 获取令牌失败 } return authToken; } + /** + * 尝试登录Google Tasks服务 + * + * @param activity 活动上下文 + * @param authToken 认证令牌 + * @return 登录是否成功 + */ private boolean tryToLoginGtask(Activity activity, String authToken) { + // 第一次尝试登录 if (!loginGtask(authToken)) { - // maybe the auth token is out of date, now let's invalidate the - // token and try again + // 令牌可能已过期,使令牌失效并重新获取 authToken = loginGoogleAccount(activity, true); if (authToken == null) { Log.e(TAG, "login google account failed"); - return false; + return false; // 重新获取令牌失败 } + // 使用新令牌再次尝试登录 if (!loginGtask(authToken)) { Log.e(TAG, "login gtask failed"); - return false; + return false; // 再次登录失败 } } - return true; + return true; // 登录成功 } + /** + * 实际执行Google Tasks登录 + * 发送HTTP请求获取Cookie和客户端版本 + * + * @param authToken 认证令牌 + * @return 登录是否成功 + */ private boolean loginGtask(String authToken) { - int timeoutConnection = 10000; - int timeoutSocket = 15000; + // 设置HTTP连接参数 + int timeoutConnection = 10000; // 连接超时10秒 + int timeoutSocket = 15000; // 套接字超时15秒 HttpParams httpParameters = new BasicHttpParams(); HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); + + // 创建HTTP客户端 mHttpClient = new DefaultHttpClient(httpParameters); BasicCookieStore localBasicCookieStore = new BasicCookieStore(); - mHttpClient.setCookieStore(localBasicCookieStore); - HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); + mHttpClient.setCookieStore(localBasicCookieStore); // 设置Cookie存储 + HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); // 禁用Expect-Continue - // login gtask + // 登录Google Tasks try { String loginUrl = mGetUrl + "?auth=" + authToken; HttpGet httpGet = new HttpGet(loginUrl); HttpResponse response = null; - response = mHttpClient.execute(httpGet); + response = mHttpClient.execute(httpGet); // 执行GET请求 - // get the cookie now + // 检查是否获取到认证Cookie List cookies = mHttpClient.getCookieStore().getCookies(); boolean hasAuthCookie = false; for (Cookie cookie : cookies) { if (cookie.getName().contains("GTL")) { - hasAuthCookie = true; + hasAuthCookie = true; // 找到认证Cookie } } if (!hasAuthCookie) { Log.w(TAG, "it seems that there is no auth cookie"); } - // get the client version + // 从响应中解析客户端版本号 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -266,31 +328,53 @@ public class GTaskClient { jsString = resString.substring(begin + jsBegin.length(), end); } JSONObject js = new JSONObject(jsString); - mClientVersion = js.getLong("v"); + mClientVersion = js.getLong("v"); // 获取客户端版本号 + } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); - return false; + return false; // JSON解析失败 } catch (Exception e) { - // simply catch all exceptions + // 捕获所有异常 Log.e(TAG, "httpget gtask_url failed"); - return false; + return false; // HTTP请求失败 } - return true; + return true; // 登录成功 } + /** + * 获取下一个动作ID + * 每次调用递增,确保每个请求有唯一ID + * + * @return 动作ID + */ private int getActionId() { return mActionId++; } + /** + * 创建HTTP POST请求对象 + * 设置必要的请求头 + * + * @return 配置好的HttpPost对象 + */ private HttpPost createHttpPost() { HttpPost httpPost = new HttpPost(mPostUrl); + // 设置请求头 httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); - httpPost.setHeader("AT", "1"); + httpPost.setHeader("AT", "1"); // 认证令牌头 return httpPost; } + /** + * 从HTTP响应实体获取内容 + * 支持GZIP和Deflate压缩格式 + * + * @param entity HTTP响应实体 + * @return 响应内容字符串 + * @throws IOException 读取响应内容时发生错误 + */ private String getResponseContent(HttpEntity entity) throws IOException { String contentEncoding = null; if (entity.getContentEncoding() != null) { @@ -299,14 +383,16 @@ public class GTaskClient { } InputStream input = entity.getContent(); + // 根据压缩格式创建相应的输入流 if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { - input = new GZIPInputStream(entity.getContent()); + input = new GZIPInputStream(entity.getContent()); // GZIP解压 } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { Inflater inflater = new Inflater(true); - input = new InflaterInputStream(entity.getContent(), inflater); + input = new InflaterInputStream(entity.getContent(), inflater); // Deflate解压 } try { + // 读取响应内容 InputStreamReader isr = new InputStreamReader(input); BufferedReader br = new BufferedReader(isr); StringBuilder sb = new StringBuilder(); @@ -314,16 +400,24 @@ public class GTaskClient { while (true) { String buff = br.readLine(); if (buff == null) { - return sb.toString(); + return sb.toString(); // 读取完成 } sb = sb.append(buff); } } finally { - input.close(); + input.close(); // 确保关闭输入流 } } + /** + * 发送POST请求到Google Tasks服务器 + * + * @param js 要发送的JSON对象 + * @return 服务器响应的JSON对象 + * @throws NetworkFailureException 网络请求失败 + */ private JSONObject postRequest(JSONObject js) throws NetworkFailureException { + // 检查登录状态 if (!mLoggedin) { Log.e(TAG, "please login first"); throw new ActionFailureException("not logged in"); @@ -331,15 +425,16 @@ public class GTaskClient { HttpPost httpPost = createHttpPost(); try { + // 准备请求参数 LinkedList list = new LinkedList(); - list.add(new BasicNameValuePair("r", js.toString())); + list.add(new BasicNameValuePair("r", js.toString())); // JSON数据作为参数 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); httpPost.setEntity(entity); - // execute the post + // 执行POST请求 HttpResponse response = mHttpClient.execute(httpPost); String jsString = getResponseContent(response.getEntity()); - return new JSONObject(jsString); + return new JSONObject(jsString); // 解析响应为JSON对象 } catch (ClientProtocolException e) { Log.e(TAG, e.toString()); @@ -360,21 +455,28 @@ public class GTaskClient { } } + /** + * 在Google Tasks中创建新任务 + * + * @param task 要创建的任务对象 + * @throws NetworkFailureException 网络请求失败 + */ public void createTask(Task task) throws NetworkFailureException { - commitUpdate(); + commitUpdate(); // 先提交所有待更新 try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list + // 构建动作列表 actionList.put(task.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 添加客户端版本 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - // post + // 发送请求 JSONObject jsResponse = postRequest(jsPost); + // 从响应中提取新任务的ID JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( GTaskStringUtils.GTASK_JSON_RESULTS).get(0); task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); @@ -386,21 +488,28 @@ public class GTaskClient { } } + /** + * 在Google Tasks中创建新任务列表 + * + * @param tasklist 要创建的任务列表对象 + * @throws NetworkFailureException 网络请求失败 + */ public void createTaskList(TaskList tasklist) throws NetworkFailureException { - commitUpdate(); + commitUpdate(); // 先提交所有待更新 try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list + // 构建动作列表 actionList.put(tasklist.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client version + // 添加客户端版本 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - // post + // 发送请求 JSONObject jsResponse = postRequest(jsPost); + // 从响应中提取新任务列表的ID JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( GTaskStringUtils.GTASK_JSON_RESULTS).get(0); tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); @@ -412,19 +521,27 @@ public class GTaskClient { } } + /** + * 提交所有待更新的操作 + * 批量提交更新,提高效率 + * + * @throws NetworkFailureException 网络请求失败 + */ public void commitUpdate() throws NetworkFailureException { if (mUpdateArray != null) { try { JSONObject jsPost = new JSONObject(); - // action_list + // 添加动作列表 jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); - // client_version + // 添加客户端版本 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + // 发送请求 postRequest(jsPost); - mUpdateArray = null; + mUpdateArray = null; // 清空更新数组 + } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); @@ -433,50 +550,70 @@ public class GTaskClient { } } + /** + * 添加节点更新到待提交数组 + * 当更新项过多时自动提交 + * + * @param node 要更新的节点 + * @throws NetworkFailureException 网络请求失败 + */ public void addUpdateNode(Node node) throws NetworkFailureException { if (node != null) { - // too many update items may result in an error - // set max to 10 items + // 更新项过多时自动提交(最多10项) if (mUpdateArray != null && mUpdateArray.length() > 10) { commitUpdate(); } + // 初始化更新数组(如果为空) if (mUpdateArray == null) mUpdateArray = new JSONArray(); + // 添加更新动作 mUpdateArray.put(node.getUpdateAction(getActionId())); } } + /** + * 移动任务到新的任务列表 + * + * @param task 要移动的任务 + * @param preParent 原始父任务列表 + * @param curParent 目标父任务列表 + * @throws NetworkFailureException 网络请求失败 + */ public void moveTask(Task task, TaskList preParent, TaskList curParent) throws NetworkFailureException { - commitUpdate(); + commitUpdate(); // 先提交所有待更新 try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); - // action_list + // 构建移动动作 action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); + + // 在同一任务列表内移动时,设置前兄弟节点ID if (preParent == curParent && task.getPriorSibling() != null) { - // put prioring_sibing_id only if moving within the tasklist and - // it is not the first one action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); } + action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); + + // 在不同任务列表间移动时,设置目标列表ID if (preParent != curParent) { - // put the dest_list only if moving between tasklists action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); } + actionList.put(action); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 添加客户端版本 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + // 发送请求 postRequest(jsPost); } catch (JSONException e) { @@ -486,22 +623,31 @@ public class GTaskClient { } } + /** + * 删除节点(任务或任务列表) + * + * @param node 要删除的节点 + * @throws NetworkFailureException 网络请求失败 + */ public void deleteNode(Node node) throws NetworkFailureException { - commitUpdate(); + commitUpdate(); // 先提交所有待更新 try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); - // action_list + // 设置节点为删除状态 node.setDeleted(true); + // 构建更新动作 actionList.put(node.getUpdateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 添加客户端版本 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + // 发送请求 postRequest(jsPost); - mUpdateArray = null; + mUpdateArray = null; // 清空更新数组 + } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); @@ -509,7 +655,14 @@ public class GTaskClient { } } + /** + * 获取所有任务列表 + * + * @return 任务列表的JSON数组 + * @throws NetworkFailureException 网络请求失败 + */ public JSONArray getTaskLists() throws NetworkFailureException { + // 检查登录状态 if (!mLoggedin) { Log.e(TAG, "please login first"); throw new ActionFailureException("not logged in"); @@ -518,9 +671,9 @@ public class GTaskClient { try { HttpGet httpGet = new HttpGet(mGetUrl); HttpResponse response = null; - response = mHttpClient.execute(httpGet); + response = mHttpClient.execute(httpGet); // 执行GET请求 - // get the task list + // 解析响应内容 String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; @@ -531,7 +684,9 @@ public class GTaskClient { jsString = resString.substring(begin + jsBegin.length(), end); } JSONObject js = new JSONObject(jsString); + // 提取任务列表数组 return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); + } catch (ClientProtocolException e) { Log.e(TAG, e.toString()); e.printStackTrace(); @@ -547,27 +702,36 @@ public class GTaskClient { } } + /** + * 获取指定任务列表的所有任务 + * + * @param listGid 任务列表的Google ID + * @return 任务的JSON数组 + * @throws NetworkFailureException 网络请求失败 + */ public JSONArray getTaskList(String listGid) throws NetworkFailureException { - commitUpdate(); + commitUpdate(); // 先提交所有待更新 try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); - // action_list + // 构建获取所有任务的动作 action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); - action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); + action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); // 不获取已删除的任务 actionList.put(action); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client_version + // 添加客户端版本 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); + // 发送请求并获取响应 JSONObject jsResponse = postRequest(jsPost); return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); + } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); @@ -575,11 +739,20 @@ public class GTaskClient { } } + /** + * 获取当前同步的账户 + * + * @return 当前同步的Google账户 + */ public Account getSyncAccount() { return mAccount; } + /** + * 重置更新数组 + * 清空所有待提交的更新 + */ public void resetUpdateArray() { mUpdateArray = null; } -} +} \ No newline at end of file