ylp_branch
ylp 7 months ago
parent 027b949267
commit a57afce80c

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 423 KiB

Binary file not shown.

File diff suppressed because it is too large Load Diff

@ -0,0 +1,436 @@
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.HttpProtocolParams;
import org.apache.http.params.HttpParams;
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进行交互实现登录、获取任务列表、创建任务、更新任务等功能
// 它是与Google Tasks API通信的客户端类封装了相关的网络请求和数据处理逻辑
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";
// 单例模式保存GTaskClient的唯一实例
private static GTaskClient mInstance = null;
// 用于发送HTTP请求的HttpClient实例
private DefaultHttpClient mHttpClient;
// 当前使用的获取任务数据的URL
private String mGetUrl;
// 当前使用的提交任务操作的URL
private String mPostUrl;
// 客户端版本号
private long mClientVersion;
// 标记是否已登录
private boolean mLoggedin;
// 上次登录的时间戳
private long mLastLoginTime;
// 操作的唯一标识符,每次操作自增
private int mActionId;
// 关联的Google账户
private Account mAccount;
// 用于批量更新任务等操作的JSON数组暂存待提交的更新数据
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;
}
// 获取GTaskClient的单例实例
public static synchronized GTaskClient getInstance() {
if (mInstance == null) {
mInstance = new GTaskClient();
}
return mInstance;
}
// 执行登录操作根据情况尝试不同的登录方式如使用自定义域名或官方URL登录
public boolean login(Activity activity) {
// 假设登录的Cookie有效期为5分钟超过该时间则需要重新登录
final long interval = 1000 * 60 * 5;
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false;
}
// 如果账户切换了,也需要重新登录
if (mLoggedin
&&!TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
mLoggedin = false;
}
if (mLoggedin) {
Log.d(TAG, "already logged in");
return true;
}
mLastLoginTime = System.currentTimeMillis();
// 尝试获取Google账户的授权令牌
String authToken = loginGoogleAccount(activity, false);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// 如果账户不是以gmail.com或googlemail.com结尾尝试使用自定义域名登录Google Tasks
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
url.append(suffix + "/");
mGetUrl = url.toString() + "ig";
mPostUrl = url.toString() + "r/ig";
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true;
}
}
// 如果使用自定义域名登录失败尝试使用Google官方的URL登录Google Tasks
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
if (!tryToLoginGtask(activity, authToken)) {
return false;
}
}
mLoggedin = true;
return true;
}
// 获取Google账户的授权令牌从账户管理器中查找指定类型com.google的账户
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
AccountManager accountManager = AccountManager.get(activity);
Account[] accounts = accountManager.getAccountsByType("com.google");
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;
}
}
if (account!= null) {
mAccount = account;
} else {
Log.e(TAG, "unable to get an account with the same name in the settings");
return null;
}
// 从账户管理器获取授权令牌
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
try {
Bundle authTokenBundle = accountManagerFuture.getResult();
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
if (invalidateToken) {
accountManager.invalidateAuthToken("com.google", authToken);
loginGoogleAccount(activity, false);
}
} catch (Exception e) {
Log.e(TAG, "get auth token failed");
authToken = null;
}
return authToken;
}
// 尝试登录Google Tasks可能会在授权令牌过期时重新获取令牌并再次尝试登录
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) {
// 若登录失败,可能是授权令牌过期,先使其无效然后重新获取并再次尝试登录
authToken = loginGoogleAccount(activity, true);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
if (!loginGtask(authToken)) {
Log.e(TAG, "login gtask failed");
return false;
}
}
return true;
}
// 实际执行登录Google Tasks的操作发送HTTP GET请求获取相关数据如客户端版本号等
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000;
int timeoutSocket = 15000;
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);
// 构造登录的URL包含授权令牌
String loginUrl = mGetUrl + "?auth=" + authToken;
HttpGet httpGet = new HttpGet(loginUrl);
HttpResponse response = null;
try {
response = mHttpClient.execute(httpGet);
// 获取登录后的Cookie检查是否包含认证相关的Cookie以GTL开头
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false;
for (Cookie cookie : cookies) {
if (cookie.getName().contains("GTL")) {
hasAuthCookie = true;
}
}
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie");
}
// 从响应中获取客户端版本号通过解析特定格式的JavaScript代码片段
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
String jsString = null;
if (begin!= -1 && end!= -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
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;
}
// 获取下一个操作的唯一标识符
private int getActionId() {
return mActionId++;
}
// 创建用于提交POST请求的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");
return httpPost;
}
// 从HTTP实体中获取响应内容根据内容编码如gzip、deflate等进行相应的解压处理
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
if (entity.getContentEncoding()!= null) {
contentEncoding = entity.getContentEncoding().getValue();
Log.d(TAG, "encoding: " + contentEncoding);
}
InputStream input = entity.getContent();
if (contentEncoding!= null && contentEncoding.equalsIgnoreCase("gzip")) {
input = new GZIPInputStream(entity.getContent());
} 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();
}
}
// 发送POST请求将JSON数据提交到Google Tasks服务器处理响应并返回解析后的JSONObject
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
HttpPost httpPost = createHttpPost();
try {
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
list.add(new BasicNameValuePair("r", js.toString()));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httpPost.setEntity(entity);
// 执行POST请求并获取响应解析响应内容为JSONObject返回
HttpResponse response = mHttpClient.execute(httpPost);
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");
}
}
// 创建一个新任务将任务相关数据构造为JSON格式并提交到服务器设置任务的全局唯一标识符gid
public void createTask(Task task) throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// 将任务的创建操作添加到操作列表中
actionList.put(task.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送POST请求并处理响应获取新任务的gid并设置到任务对象中
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
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");
}
}
// 创建一个新的任务列表类似创建任务的操作将任务列表相关数据构造为JSON格式提交到服务器并设置其gid
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// 将任务列表的创建操作添加到操作列表
actionList.put(tasklist.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送POST请求并处理响应获取新任务列表的gid并设置到任务列表对象中
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create tasklist: handing jsonobject failed");
}
}
// 提交之前暂存的更新操作通过addUpdateNode方法添加的更新数据到服务器
public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray!= null) {
try {
JSONObject jsPost = new JSONObject();
// 设置操作列表为暂存的更新数据数组
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);
// 设置客户端版本号
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
mUpdateArray = null;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("commit update: handing jsonobject failed");
}
}
}
// 添加一个节点任务或任务列表等的更新操作到待提交的更新数组中控制更新数组长度不超过10个元素
public void addUpdateNode(Node node) throws NetworkFailureException {
if (node!= null
Loading…
Cancel
Save