/* * 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; public class GTaskClient {//GTaskClient类:实现GTask的登录以及创建GTask任务和任务列表,从网络上获取任务内容 private static final String TAG = GTaskClient.class.getSimpleName();//Google邮箱指定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;//后续使用的参数以及变量 private DefaultHttpClient mHttpClient; private String mGetUrl; //构造函数:初始化各属性 private String mPostUrl; private long mClientVersion; private boolean mLoggedin; private long mLastLoginTime; private int mActionId; private Account mAccount; private JSONArray mUpdateArray; private GTaskClient() {//初始化客户端,如果没有就new一个,否则就用当前的。 mHttpClient = null; mGetUrl = GTASK_GET_URL; mPostUrl = GTASK_POST_URL; mClientVersion = -1; mLoggedin = false; mLastLoginTime = 0; mActionId = 1; mAccount = null; mUpdateArray = null; } public static synchronized GTaskClient getInstance() {//获取实例,如果当前没有示例,则新建一个登陆的gtask,如果有直接返回 if (mInstance == null) { mInstance = new GTaskClient(); } return mInstance; } public boolean login(Activity activity) {//login:用于实现登录的方法,以activity作为参数 // we suppose that the cookie would expire after 5 minutes // then we need to re-login final long interval = 1000 * 60 * 5; if (mLastLoginTime + interval < System.currentTimeMillis()) { mLoggedin = false; } // need to re-login after account switch if (mLoggedin//在登陆成功的情况下检测到用户名和密码不匹配时登录失败 && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity .getSyncAccountName(activity))) { mLoggedin = false; } if (mLoggedin) {//代码块,如果登录的时候符合上面的要求则让其显示已登录登陆 Log.d(TAG, "already logged in"); return true; } mLastLoginTime = System.currentTimeMillis(); String authToken = loginGoogleAccount(activity, false); if (authToken == null) {//登录失败的情况 Log.e(TAG, "login google account failed"); return false; } // login with custom domain if necessary 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;//返回@第一次出现的位置并把位置+1后记录在index里 String suffix = mAccount.name.substring(index); url.append(suffix + "/"); mGetUrl = url.toString() + "ig"; mPostUrl = url.toString() + "r/ig"; if (tryToLoginGtask(activity, authToken)) {//成功登入 mLoggedin = true; } } // try to login with google official url if (!mLoggedin) {//代码块: 若前面的尝试失败,则尝试使用官方的域名登陆 mGetUrl = GTASK_GET_URL; mPostUrl = GTASK_POST_URL; if (!tryToLoginGtask(activity, authToken)) {//第二次登录失败,返回false return false; } } mLoggedin = true; return true; } private String loginGoogleAccount(Activity activity, boolean invalidateToken) {//登录谷歌账号的主函数,在登陆成功后获取认证令牌 String authToken; AccountManager accountManager = AccountManager.get(activity); Account[] accounts = accountManager.getAccountsByType("com.google"); if (accounts.length == 0) {//如果没有这样的账号,输出日志信息“无有效的google账户” 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; } // 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); } } catch (Exception e) { Log.e(TAG, "get auth token failed"); authToken = null; } return authToken; } private boolean tryToLoginGtask(Activity activity, String authToken) {//方法:用于判断令牌对于登陆gtask账号是否有效 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; } if (!loginGtask(authToken)) {//代码块: 重新获取的令牌再次失效 Log.e(TAG, "login gtask failed"); return false; } } return true; } private boolean loginGtask(String authToken) {//.loginGtask:实现登录Gtask的方法 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); // login gtask try {//登录Gtask String loginUrl = mGetUrl + "?auth=" + authToken; HttpGet httpGet = new HttpGet(loginUrl); HttpResponse response = null; response = mHttpClient.execute(httpGet); // get the cookie now List cookies = mHttpClient.getCookieStore().getCookies(); boolean hasAuthCookie = false; for (Cookie cookie : cookies) { if (cookie.getName().contains("GTL")) {//验证cookie信息,这里通过GTL标志来验证 hasAuthCookie = true; } } 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 = ")}"; 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");//验证cookie信息,这里通过GTL标志来验证 } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); return false; } catch (Exception e) { // simply catch all exceptions Log.e(TAG, "httpget gtask_url failed"); return false; } return true; } private int getActionId() { return mActionId++; }//获取动作的id号码 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; } private String getResponseContent(HttpEntity entity) throws IOException {//getResponseContent:获取服务器响应的数据,主要通过方法getContentEncoding来获取网上资源,返回这些资源 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(); } } 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 list = new LinkedList(); list.add(new BasicNameValuePair("r", js.toString())); UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); httpPost.setEntity(entity); // execute the post 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"); } } public void createTask(Task task) throws NetworkFailureException {//创建单个任务,通过json获取TASK中的内容并创建对应的jsPost,通过postRequest方法获取任务的返回信息,使用setGid方法设置task的new_id 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); 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"); } } public void createTaskList(TaskList tasklist) throws NetworkFailureException {//创建一个任务列表 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); 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"); } } 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; } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("commit update: handing jsonobject failed"); } } } public void addUpdateNode(Node node) throws NetworkFailureException {//添加更新节点,主要利用commitUpdate if (node != null) { // too many update items may result in an error // set max to 10 items if (mUpdateArray != null && mUpdateArray.length() > 10) { commitUpdate(); } if (mUpdateArray == null) mUpdateArray = new JSONArray(); mUpdateArray.put(node.getUpdateAction(getActionId())); } } public void moveTask(Task task, TaskList preParent, TaskList curParent)//移动一个任务,通过getGid获取task所属的Id,还是通过JSONObject和postRequest实现 throws NetworkFailureException { 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()); 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()); if (preParent != curParent) {//当移动发生在不同的任务列表之间,设置为dest_list // 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) { Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("move task: handing jsonobject failed"); } } public void deleteNode(Node node) throws NetworkFailureException {//删除节点,过程类似移动 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; } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("delete node: handing jsonobject failed"); } } public JSONArray getTaskLists() throws NetworkFailureException {//获取任务列表,首先通过getURI在网上获取数据,在截取所需部分内容返回 if (!mLoggedin) { Log.e(TAG, "please login first"); throw new ActionFailureException("not logged in"); } try { HttpGet httpGet = new HttpGet(mGetUrl); HttpResponse response = null; response = mHttpClient.execute(httpGet); // get the task list String resString = getResponseContent(response.getEntity()); String jsBegin = "_setup("; String jsEnd = ")}"; 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); return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); } catch (ClientProtocolException e) { Log.e(TAG, e.toString()); e.printStackTrace(); throw new NetworkFailureException("gettasklists: httpget failed"); } catch (IOException e) { Log.e(TAG, e.toString()); e.printStackTrace(); throw new NetworkFailureException("gettasklists: httpget failed"); } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("get task lists: handing jasonobject failed"); } } public JSONArray getTaskList(String listGid) throws NetworkFailureException {//方法:对于已经获取的任务列表,可以通过其id来获取到 commitUpdate(); try { JSONObject jsPost = new JSONObject(); JSONArray actionList = new JSONArray(); JSONObject action = new JSONObject(); // action_list 通过action.pu()t对JSONObject对象action添加元素,通过jsPost.put()对jsPost添加相关元素,然后通过postRequest()提交更新后的请求并返回一个JSONObject的对象,最后使用jsResponse.getJSONArray( )获取jsResponse中的JSONArray值并作为函数返回值 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); 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(); throw new ActionFailureException("get task list: handing jsonobject failed"); } } public Account getSyncAccount() { return mAccount; }//获得同步账户 public void resetUpdateArray() { mUpdateArray = null; }//重置更新内容 }