You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
xiaomi/GTaskClient.java

458 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net)
*
* 遵循 Apache 许可证 2.0 版(“许可证”);
* 除非遵守许可证,否则不得使用此文件。
* 你可以在以下网址获取许可证副本:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 除非适用法律要求或书面同意,
* 根据许可证分发的软件按“原样”分发,
* 不附带任何明示或暗示的保证或条件。
* 请参阅许可证,了解具体的权限和限制。
*/
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;
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 任务GTask服务器进行交互包括登录、创建任务、获取任务列表等操作
public class GTaskClient {
// 用于日志记录的标签,使用类的简单名称
private static final String TAG = GTaskClient.class.getSimpleName();
// GTask 服务的基本 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";
// GTaskClient 的单例实例
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;
// 当前登录的账户
private Account mAccount;
// 存储待提交的更新操作的 JSON 数组
private JSONArray mUpdateArray;
// 私有构造函数,用于初始化 GTaskClient 的成员变量
private GTaskClient() {
mHttpClient = null;
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
mClientVersion = -1;
mLoggedin = false;
mLastLoginTime = 0;
mActionId = 1;
mAccount = null;
mUpdateArray = null;
}
// 获取 GTaskClient 单例实例的方法
public static synchronized GTaskClient getInstance() {
if (mInstance == null) {
mInstance = new GTaskClient();
}
return mInstance;
}
// 登录方法,用于登录 Google 账户并获取 GTask 服务的授权
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);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// 如果是自定义域名账户,进行特殊处理
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
// 构建自定义域名的 URL
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
url.append(suffix + "/");
mGetUrl = url.toString() + "ig";
mPostUrl = url.toString() + "r/ig";
// 尝试使用自定义域名 URL 登录 GTask
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true;
}
}
// 如果使用自定义域名未登录成功,尝试使用 Google 官方 URL 登录
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
if (!tryToLoginGtask(activity, authToken)) {
return false;
}
}
mLoggedin = true;
return true;
}
// 登录 Google 账户并获取认证令牌的方法
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;
}
}
// 如果未找到匹配的账户,记录错误并返回 null
if (account!= null) {
mAccount = account;
} else {
Log.e(TAG, "unable to get an account with the same name in the settings");
return null;
}
// 获取认证令牌
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
try {
// 获取包含认证令牌的 Bundle
Bundle authTokenBundle = accountManagerFuture.getResult();
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
// 如果需要使令牌失效,进行相应操作并重新登录
if (invalidateToken) {
accountManager.invalidateAuthToken("com.google", authToken);
loginGoogleAccount(activity, false);
}
} catch (Exception e) {
Log.e(TAG, "get auth token failed");
authToken = null;
}
return authToken;
}
// 尝试使用认证令牌登录 GTask 的方法
private boolean tryToLoginGtask(Activity activity, String authToken) {
// 先尝试使用当前认证令牌登录 GTask
if (!loginGtask(authToken)) {
// 如果登录失败,使当前认证令牌失效并重新获取
authToken = loginGoogleAccount(activity, true);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// 再次尝试使用新的认证令牌登录 GTask
if (!loginGtask(authToken)) {
Log.e(TAG, "login gtask failed");
return false;
}
}
return true;
}
// 使用认证令牌登录 GTask 的具体方法
private boolean loginGtask(String authToken) {
// 设置连接超时时间和套接字超时时间
int timeoutConnection = 10000;
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());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
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) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
} catch (Exception e) {
// 捕获所有异常
Log.e(TAG, "httpget gtask_url failed");
return false;
}
return true;
}
// 获取并递增操作 ID 的方法
private int getActionId() {
return mActionId++;
}
// 创建一个用于提交任务操作的 HttpPost 对象的方法
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl);
// 设置请求头的内容类型
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
// 设置请求头的 AT 字段
httpPost.setHeader("AT", "1");
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解压输入流
if (contentEncoding!= null && contentEncoding.equalsIgnoreCase("gzip")) {
input = new GZIPInputStream(entity.getContent());
}
// 如果内容编码是 deflate解压输入流
else if (contentEncoding!= null && contentEncoding.equalsIgnoreCase("deflate")) {
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();
}
}
// 向 GTask 服务器发送 POST 请求的方法
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
// 如果未登录,抛出异常
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
// 创建 HttpPost 对象
HttpPost httpPost = createHttpPost();
try {
// 创建一个包含请求参数的链表
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
// 添加请求参数
list.add(new BasicNameValuePair("r", js.toString()));
// 创建 URL 编码的表单实体
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
// 设置 HttpPost 的实体
httpPost.setEntity(entity);
// 执行 POST 请求
HttpResponse response = mHttpClient.execute(httpPost);
// 获取响应内容并转换为 JSONObject
String jsString = getResponseContent(response.getEntity());
return new JSONObject(jsString);
} catch (ClientProtocolException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
} catch (IOException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("unable to convert response content to jsonobject");
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("error occurs when posting request");
}
}
// 创建任务的方法
public void createTask(Task task) throws NetworkFailureException {
// 提交之前的更新操作
commitUpdate();
try {
// 创建一个用于 POST 请求的 JSONObject
JSONObject jsPost = new JSONObject();
// 创建一个包含操作的 JSON 数组
JSONArray actionList = new JSONArray();
// 添加创建任务的操作到 JSON 数组
actionList.put(task.getCreateAction(getActionId()));
// 将操作数组添加到请求的 JSONObject 中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 添加客户端版本号到请求的 JSONObject 中
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送 POST 请求并获取响应
JSONObject jsResponse = postRequest(jsPost);
// 从响应中获取结果
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
// 设置任务的 GID
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create task: handing jsonobject failed");
}
}
// 创建任务列表的方法
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
// 提交之前的更新