parent
							
								
									5c5c91d57d
								
							
						
					
					
						commit
						0255294e0e
					
				| @ -0,0 +1,129 @@ | |||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  * 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.Notification; | ||||||
|  | import android.app.NotificationManager; | ||||||
|  | import android.app.PendingIntent; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.Intent; | ||||||
|  | import android.os.AsyncTask; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | import net.micode.notes.ui.NotesListActivity; | ||||||
|  | import net.micode.notes.ui.NotesPreferenceActivity; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class GTaskASyncTask extends AsyncTask<Void, String, Integer> { | ||||||
|  | 
 | ||||||
|  |     private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; | ||||||
|  | 
 | ||||||
|  |     public interface OnCompleteListener { | ||||||
|  |         void onComplete(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Context mContext; | ||||||
|  | 
 | ||||||
|  |     private NotificationManager mNotifiManager; | ||||||
|  | 
 | ||||||
|  |     private GTaskManager mTaskManager; | ||||||
|  | 
 | ||||||
|  |     private OnCompleteListener mOnCompleteListener; | ||||||
|  | 
 | ||||||
|  |     public GTaskASyncTask(Context context, OnCompleteListener listener) { | ||||||
|  |         mContext = context; | ||||||
|  |         mOnCompleteListener = listener; | ||||||
|  |         mNotifiManager = (NotificationManager) mContext | ||||||
|  |                 .getSystemService(Context.NOTIFICATION_SERVICE); | ||||||
|  |         mTaskManager = GTaskManager.getInstance(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void cancelSync() { | ||||||
|  |         mTaskManager.cancelSync(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void publishProgess(String message) {     //发布进度
 | ||||||
|  |         publishProgress(new String[] { | ||||||
|  |             message | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void showNotification(int tickerId, String content) {       //提示当前同步状态
 | ||||||
|  |         PendingIntent pendingIntent; | ||||||
|  |         if (tickerId != R.string.ticker_success) { | ||||||
|  |             pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, | ||||||
|  |                     NotesPreferenceActivity.class), 0); | ||||||
|  | 
 | ||||||
|  |         } else { | ||||||
|  |             pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, | ||||||
|  |                     NotesListActivity.class), 0); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         Notification.Builder builder = new Notification.Builder(mContext) | ||||||
|  |                 .setAutoCancel(true) | ||||||
|  |                 .setContentTitle(mContext.getString(R.string.app_name)) | ||||||
|  |                 .setContentText(content) | ||||||
|  |                 .setContentIntent(pendingIntent) | ||||||
|  |                 .setWhen(System.currentTimeMillis()) | ||||||
|  |                 .setOngoing(true); | ||||||
|  |         Notification notification=builder.getNotification(); | ||||||
|  |         mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected Integer doInBackground(Void... unused) {          //该任务于后台执行
 | ||||||
|  |         publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity | ||||||
|  |                 .getSyncAccountName(mContext))); | ||||||
|  |         return mTaskManager.sync(mContext, this); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onProgressUpdate(String... progress) {         //显示进度条
 | ||||||
|  |         showNotification(R.string.ticker_syncing, progress[0]); | ||||||
|  |         if (mContext instanceof GTaskSyncService) { | ||||||
|  |             ((GTaskSyncService) mContext).sendBroadcast(progress[0]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onPostExecute(Integer result) { | ||||||
|  |         if (result == GTaskManager.STATE_SUCCESS) { | ||||||
|  |             showNotification(R.string.ticker_success, mContext.getString( | ||||||
|  |                     R.string.success_sync_account, mTaskManager.getSyncAccount())); | ||||||
|  |             NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); | ||||||
|  |         } else if (result == GTaskManager.STATE_NETWORK_ERROR) { | ||||||
|  |             showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); | ||||||
|  |         } else if (result == GTaskManager.STATE_INTERNAL_ERROR) { | ||||||
|  |             showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); | ||||||
|  |         } else if (result == GTaskManager.STATE_SYNC_CANCELLED) { | ||||||
|  |             showNotification(R.string.ticker_cancel, mContext | ||||||
|  |                     .getString(R.string.error_sync_cancelled)); | ||||||
|  |         } | ||||||
|  |         if (mOnCompleteListener != null) { | ||||||
|  |             new Thread(new Runnable() { | ||||||
|  | 
 | ||||||
|  |                 public void run() { | ||||||
|  |                     mOnCompleteListener.onComplete(); | ||||||
|  |                 } | ||||||
|  |             }).start(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,585 @@ | |||||||
|  | /* | ||||||
|  |  * 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 { | ||||||
|  |     private static final String TAG = GTaskClient.class.getSimpleName(); | ||||||
|  | 
 | ||||||
|  |     private static final String GTASK_URL = "https://mail.google.com/tasks/"; | ||||||
|  | 
 | ||||||
|  |     private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; | ||||||
|  | 
 | ||||||
|  |     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() { | ||||||
|  |         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() { | ||||||
|  |         if (mInstance == null) { | ||||||
|  |             mInstance = new GTaskClient(); | ||||||
|  |         } | ||||||
|  |         return mInstance;     //返回实例化对象,若未定义则创建对象
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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; | ||||||
|  |         if (mLastLoginTime + interval < System.currentTimeMillis()) { | ||||||
|  |             mLoggedin = false;     //超过5分钟则重新登录
 | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // 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; | ||||||
|  |             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   官方URL登录
 | ||||||
|  |         if (!mLoggedin) { | ||||||
|  |             mGetUrl = GTASK_GET_URL; | ||||||
|  |             mPostUrl = GTASK_POST_URL; | ||||||
|  |             if (!tryToLoginGtask(activity, authToken)) { | ||||||
|  |                 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) { | ||||||
|  |             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<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; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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) {     //登录具体操作
 | ||||||
|  |         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 { | ||||||
|  |             String loginUrl = mGetUrl + "?auth=" + authToken;    //设置登录URL
 | ||||||
|  |             HttpGet httpGet = new HttpGet(loginUrl);    //实例化登录URL
 | ||||||
|  |             HttpResponse response = null; | ||||||
|  |             response = mHttpClient.execute(httpGet); | ||||||
|  | 
 | ||||||
|  |             // get the cookie now
 | ||||||
|  |             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"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // get the client version
 | ||||||
|  |             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) { | ||||||
|  |             // simply catch all exceptions
 | ||||||
|  |             Log.e(TAG, "httpget gtask_url failed"); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private int getActionId() { | ||||||
|  |         return mActionId++; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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 { | ||||||
|  |         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<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>(); | ||||||
|  |             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 { | ||||||
|  |         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 { | ||||||
|  |         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) | ||||||
|  |             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) { | ||||||
|  |                 // 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 { | ||||||
|  |         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 = ")}</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); | ||||||
|  |             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 { | ||||||
|  |         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); | ||||||
|  |             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; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,800 @@ | |||||||
|  | /* | ||||||
|  |  * 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; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 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; | ||||||
|  | 
 | ||||||
|  |     private Activity mActivity; | ||||||
|  | 
 | ||||||
|  |     private Context mContext; | ||||||
|  | 
 | ||||||
|  |     private ContentResolver mContentResolver; | ||||||
|  | 
 | ||||||
|  |     private boolean mSyncing; | ||||||
|  | 
 | ||||||
|  |     private boolean mCancelled; | ||||||
|  | 
 | ||||||
|  |     private HashMap<String, TaskList> mGTaskListHashMap; | ||||||
|  | 
 | ||||||
|  |     private HashMap<String, Node> mGTaskHashMap; | ||||||
|  | 
 | ||||||
|  |     private HashMap<String, MetaData> mMetaHashMap; | ||||||
|  | 
 | ||||||
|  |     private TaskList mMetaList; | ||||||
|  | 
 | ||||||
|  |     private HashSet<Long> mLocalDeleteIdMap; | ||||||
|  | 
 | ||||||
|  |     private HashMap<String, Long> mGidToNid; | ||||||
|  | 
 | ||||||
|  |     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>(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static synchronized GTaskManager getInstance() { | ||||||
|  |         if (mInstance == null) { | ||||||
|  |             mInstance = new GTaskManager(); | ||||||
|  |         } | ||||||
|  |         return mInstance; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public synchronized void setActivityContext(Activity activity) { | ||||||
|  |         // used for getting authtoken
 | ||||||
|  |         mActivity = activity; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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 client = GTaskClient.getInstance(); | ||||||
|  |             client.resetUpdateArray(); | ||||||
|  | 
 | ||||||
|  |             // login google task
 | ||||||
|  |             if (!mCancelled) { | ||||||
|  |                 if (!client.login(mActivity)) { | ||||||
|  |                     throw new NetworkFailureException("login google task failed"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // get the task list from google
 | ||||||
|  |             asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); | ||||||
|  |             initGTaskList(); | ||||||
|  | 
 | ||||||
|  |             // do content sync work
 | ||||||
|  |             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; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void initGTaskList() throws NetworkFailureException { | ||||||
|  |         if (mCancelled) | ||||||
|  |             return; | ||||||
|  |         GTaskClient client = GTaskClient.getInstance(); | ||||||
|  |         try { | ||||||
|  |             JSONArray jsTaskLists = client.getTaskLists(); | ||||||
|  | 
 | ||||||
|  |             // init meta list first
 | ||||||
|  |             mMetaList = null; | ||||||
|  |             for (int i = 0; i < jsTaskLists.length(); i++) { | ||||||
|  |                 JSONObject object = jsTaskLists.getJSONObject(i); | ||||||
|  |                 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(); | ||||||
|  |                     mMetaList.setContentByRemoteJSON(object); | ||||||
|  | 
 | ||||||
|  |                     // load meta data
 | ||||||
|  |                     JSONArray jsMetas = client.getTaskList(gid); | ||||||
|  |                     for (int j = 0; j < jsMetas.length(); j++) { | ||||||
|  |                         object = (JSONObject) jsMetas.getJSONObject(j); | ||||||
|  |                         MetaData metaData = new MetaData(); | ||||||
|  |                         metaData.setContentByRemoteJSON(object); | ||||||
|  |                         if (metaData.isWorthSaving()) { | ||||||
|  |                             mMetaList.addChildTask(metaData); | ||||||
|  |                             if (metaData.getGid() != null) { | ||||||
|  |                                 mMetaHashMap.put(metaData.getRelatedGid(), metaData); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // create meta list if not existed
 | ||||||
|  |             if (mMetaList == null) { | ||||||
|  |                 mMetaList = new TaskList(); | ||||||
|  |                 mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX | ||||||
|  |                         + GTaskStringUtils.FOLDER_META); | ||||||
|  |                 GTaskClient.getInstance().createTaskList(mMetaList); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // init task list
 | ||||||
|  |             for (int i = 0; i < jsTaskLists.length(); i++) { | ||||||
|  |                 JSONObject object = jsTaskLists.getJSONObject(i); | ||||||
|  |                 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(); | ||||||
|  |                     tasklist.setContentByRemoteJSON(object); | ||||||
|  |                     mGTaskListHashMap.put(gid, tasklist); | ||||||
|  |                     mGTaskHashMap.put(gid, tasklist); | ||||||
|  | 
 | ||||||
|  |                     // load tasks
 | ||||||
|  |                     JSONArray jsTasks = client.getTaskList(gid); | ||||||
|  |                     for (int j = 0; j < jsTasks.length(); j++) { | ||||||
|  |                         object = (JSONObject) jsTasks.getJSONObject(j); | ||||||
|  |                         gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); | ||||||
|  |                         Task task = new Task(); | ||||||
|  |                         task.setContentByRemoteJSON(object); | ||||||
|  |                         if (task.isWorthSaving()) { | ||||||
|  |                             task.setMetaInfo(mMetaHashMap.get(gid)); | ||||||
|  |                             tasklist.addChildTask(task); | ||||||
|  |                             mGTaskHashMap.put(gid, task); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new ActionFailureException("initGTaskList: handing JSONObject failed"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void syncContent() throws NetworkFailureException { | ||||||
|  |         int syncType; | ||||||
|  |         Cursor c = null; | ||||||
|  |         String gid; | ||||||
|  |         Node node; | ||||||
|  | 
 | ||||||
|  |         mLocalDeleteIdMap.clear(); | ||||||
|  | 
 | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // for local deleted note
 | ||||||
|  |         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()) { | ||||||
|  |                     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); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Log.w(TAG, "failed to query trash folder"); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             if (c != null) { | ||||||
|  |                 c.close(); | ||||||
|  |                 c = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // sync folder first
 | ||||||
|  |         syncFolder(); | ||||||
|  | 
 | ||||||
|  |         // for note existing in database
 | ||||||
|  |         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()) { | ||||||
|  |                     gid = c.getString(SqlNote.GTASK_ID_COLUMN); | ||||||
|  |                     node = mGTaskHashMap.get(gid); | ||||||
|  |                     if (node != null) { | ||||||
|  |                         mGTaskHashMap.remove(gid); | ||||||
|  |                         mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); | ||||||
|  |                         mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); | ||||||
|  |                         syncType = node.getSyncAction(c); | ||||||
|  |                     } else { | ||||||
|  |                         if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { | ||||||
|  |                             // local add
 | ||||||
|  |                             syncType = Node.SYNC_ACTION_ADD_REMOTE; | ||||||
|  |                         } else { | ||||||
|  |                             // remote delete
 | ||||||
|  |                             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; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // go through remaining items
 | ||||||
|  |         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); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // mCancelled can be set by another thread, so we neet to check one by
 | ||||||
|  |         // one
 | ||||||
|  |         // clear local delete table
 | ||||||
|  |         if (!mCancelled) { | ||||||
|  |             if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { | ||||||
|  |                 throw new ActionFailureException("failed to batch-delete local deleted notes"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // refresh local sync id
 | ||||||
|  |         if (!mCancelled) { | ||||||
|  |             GTaskClient.getInstance().commitUpdate(); | ||||||
|  |             refreshLocalSyncId(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void syncFolder() throws NetworkFailureException { | ||||||
|  |         Cursor c = null; | ||||||
|  |         String gid; | ||||||
|  |         Node node; | ||||||
|  |         int syncType; | ||||||
|  | 
 | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // for root folder
 | ||||||
|  |         try { | ||||||
|  |             c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, | ||||||
|  |                     Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); | ||||||
|  |             if (c != null) { | ||||||
|  |                 c.moveToNext(); | ||||||
|  |                 gid = c.getString(SqlNote.GTASK_ID_COLUMN); | ||||||
|  |                 node = mGTaskHashMap.get(gid); | ||||||
|  |                 if (node != null) { | ||||||
|  |                     mGTaskHashMap.remove(gid); | ||||||
|  |                     mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); | ||||||
|  |                     mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); | ||||||
|  |                     // for system folder, only update remote name if necessary
 | ||||||
|  |                     if (!node.getName().equals( | ||||||
|  |                             GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) | ||||||
|  |                         doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); | ||||||
|  |                 } else { | ||||||
|  |                     doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Log.w(TAG, "failed to query root folder"); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             if (c != null) { | ||||||
|  |                 c.close(); | ||||||
|  |                 c = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // for call-note folder
 | ||||||
|  |         try { | ||||||
|  |             c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", | ||||||
|  |                     new String[] { | ||||||
|  |                         String.valueOf(Notes.ID_CALL_RECORD_FOLDER) | ||||||
|  |                     }, null); | ||||||
|  |             if (c != null) { | ||||||
|  |                 if (c.moveToNext()) { | ||||||
|  |                     gid = c.getString(SqlNote.GTASK_ID_COLUMN); | ||||||
|  |                     node = mGTaskHashMap.get(gid); | ||||||
|  |                     if (node != null) { | ||||||
|  |                         mGTaskHashMap.remove(gid); | ||||||
|  |                         mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); | ||||||
|  |                         mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); | ||||||
|  |                         // for system folder, only update remote name if
 | ||||||
|  |                         // necessary
 | ||||||
|  |                         if (!node.getName().equals( | ||||||
|  |                                 GTaskStringUtils.MIUI_FOLDER_PREFFIX | ||||||
|  |                                         + GTaskStringUtils.FOLDER_CALL_NOTE)) | ||||||
|  |                             doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); | ||||||
|  |                     } else { | ||||||
|  |                         doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Log.w(TAG, "failed to query call note folder"); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             if (c != null) { | ||||||
|  |                 c.close(); | ||||||
|  |                 c = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // for local existing folders
 | ||||||
|  |         try { | ||||||
|  |             c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, | ||||||
|  |                     "(type=? AND parent_id<>?)", new String[] { | ||||||
|  |                             String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) | ||||||
|  |                     }, NoteColumns.TYPE + " DESC"); | ||||||
|  |             if (c != null) { | ||||||
|  |                 while (c.moveToNext()) { | ||||||
|  |                     gid = c.getString(SqlNote.GTASK_ID_COLUMN); | ||||||
|  |                     node = mGTaskHashMap.get(gid); | ||||||
|  |                     if (node != null) { | ||||||
|  |                         mGTaskHashMap.remove(gid); | ||||||
|  |                         mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); | ||||||
|  |                         mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); | ||||||
|  |                         syncType = node.getSyncAction(c); | ||||||
|  |                     } else { | ||||||
|  |                         if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { | ||||||
|  |                             // local add
 | ||||||
|  |                             syncType = Node.SYNC_ACTION_ADD_REMOTE; | ||||||
|  |                         } else { | ||||||
|  |                             // remote delete
 | ||||||
|  |                             syncType = Node.SYNC_ACTION_DEL_LOCAL; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     doContentSync(syncType, node, c); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Log.w(TAG, "failed to query existing folder"); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             if (c != null) { | ||||||
|  |                 c.close(); | ||||||
|  |                 c = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // for remote add folders
 | ||||||
|  |         Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator(); | ||||||
|  |         while (iter.hasNext()) { | ||||||
|  |             Map.Entry<String, TaskList> entry = iter.next(); | ||||||
|  |             gid = entry.getKey(); | ||||||
|  |             node = entry.getValue(); | ||||||
|  |             if (mGTaskHashMap.containsKey(gid)) { | ||||||
|  |                 mGTaskHashMap.remove(gid); | ||||||
|  |                 doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!mCancelled) | ||||||
|  |             GTaskClient.getInstance().commitUpdate(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         MetaData meta; | ||||||
|  |         switch (syncType) { | ||||||
|  |             case Node.SYNC_ACTION_ADD_LOCAL: | ||||||
|  |                 addLocalNode(node); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_ADD_REMOTE: | ||||||
|  |                 addRemoteNode(node, c); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_DEL_LOCAL: | ||||||
|  |                 meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); | ||||||
|  |                 if (meta != null) { | ||||||
|  |                     GTaskClient.getInstance().deleteNode(meta); | ||||||
|  |                 } | ||||||
|  |                 mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_DEL_REMOTE: | ||||||
|  |                 meta = mMetaHashMap.get(node.getGid()); | ||||||
|  |                 if (meta != null) { | ||||||
|  |                     GTaskClient.getInstance().deleteNode(meta); | ||||||
|  |                 } | ||||||
|  |                 GTaskClient.getInstance().deleteNode(node); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_UPDATE_LOCAL: | ||||||
|  |                 updateLocalNode(node, c); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_UPDATE_REMOTE: | ||||||
|  |                 updateRemoteNode(node, c); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_UPDATE_CONFLICT: | ||||||
|  |                 // merging both modifications maybe a good idea
 | ||||||
|  |                 // right now just use local update simply
 | ||||||
|  |                 updateRemoteNode(node, c); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_NONE: | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_ERROR: | ||||||
|  |             default: | ||||||
|  |                 throw new ActionFailureException("unkown sync action type"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void addLocalNode(Node node) throws NetworkFailureException { | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         SqlNote sqlNote; | ||||||
|  |         if (node instanceof TaskList) { | ||||||
|  |             if (node.getName().equals( | ||||||
|  |                     GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { | ||||||
|  |                 sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); | ||||||
|  |             } else if (node.getName().equals( | ||||||
|  |                     GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { | ||||||
|  |                 sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); | ||||||
|  |             } else { | ||||||
|  |                 sqlNote = new SqlNote(mContext); | ||||||
|  |                 sqlNote.setContent(node.getLocalJSONFromContent()); | ||||||
|  |                 sqlNote.setParentId(Notes.ID_ROOT_FOLDER); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             sqlNote = new SqlNote(mContext); | ||||||
|  |             JSONObject js = node.getLocalJSONFromContent(); | ||||||
|  |             try { | ||||||
|  |                 if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { | ||||||
|  |                     JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); | ||||||
|  |                     if (note.has(NoteColumns.ID)) { | ||||||
|  |                         long id = note.getLong(NoteColumns.ID); | ||||||
|  |                         if (DataUtils.existInNoteDatabase(mContentResolver, id)) { | ||||||
|  |                             // the id is not available, have to create a new one
 | ||||||
|  |                             note.remove(NoteColumns.ID); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if (js.has(GTaskStringUtils.META_HEAD_DATA)) { | ||||||
|  |                     JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); | ||||||
|  |                     for (int i = 0; i < dataArray.length(); i++) { | ||||||
|  |                         JSONObject data = dataArray.getJSONObject(i); | ||||||
|  |                         if (data.has(DataColumns.ID)) { | ||||||
|  |                             long dataId = data.getLong(DataColumns.ID); | ||||||
|  |                             if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { | ||||||
|  |                                 // the data id is not available, have to create
 | ||||||
|  |                                 // a new one
 | ||||||
|  |                                 data.remove(DataColumns.ID); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                 } | ||||||
|  |             } catch (JSONException e) { | ||||||
|  |                 Log.w(TAG, e.toString()); | ||||||
|  |                 e.printStackTrace(); | ||||||
|  |             } | ||||||
|  |             sqlNote.setContent(js); | ||||||
|  | 
 | ||||||
|  |             Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); | ||||||
|  |             if (parentId == null) { | ||||||
|  |                 Log.e(TAG, "cannot find task's parent id locally"); | ||||||
|  |                 throw new ActionFailureException("cannot add local node"); | ||||||
|  |             } | ||||||
|  |             sqlNote.setParentId(parentId.longValue()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // create the local node
 | ||||||
|  |         sqlNote.setGtaskId(node.getGid()); | ||||||
|  |         sqlNote.commit(false); | ||||||
|  | 
 | ||||||
|  |         // update gid-nid mapping
 | ||||||
|  |         mGidToNid.put(node.getGid(), sqlNote.getId()); | ||||||
|  |         mNidToGid.put(sqlNote.getId(), node.getGid()); | ||||||
|  | 
 | ||||||
|  |         // update meta
 | ||||||
|  |         updateRemoteMeta(node.getGid(), sqlNote); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         SqlNote sqlNote; | ||||||
|  |         // update the note locally
 | ||||||
|  |         sqlNote = new SqlNote(mContext, c); | ||||||
|  |         sqlNote.setContent(node.getLocalJSONFromContent()); | ||||||
|  | 
 | ||||||
|  |         Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) | ||||||
|  |                 : new Long(Notes.ID_ROOT_FOLDER); | ||||||
|  |         if (parentId == null) { | ||||||
|  |             Log.e(TAG, "cannot find task's parent id locally"); | ||||||
|  |             throw new ActionFailureException("cannot update local node"); | ||||||
|  |         } | ||||||
|  |         sqlNote.setParentId(parentId.longValue()); | ||||||
|  |         sqlNote.commit(true); | ||||||
|  | 
 | ||||||
|  |         // update meta info
 | ||||||
|  |         updateRemoteMeta(node.getGid(), sqlNote); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         SqlNote sqlNote = new SqlNote(mContext, c); | ||||||
|  |         Node n; | ||||||
|  | 
 | ||||||
|  |         // update remotely
 | ||||||
|  |         if (sqlNote.isNoteType()) { | ||||||
|  |             Task task = new Task(); | ||||||
|  |             task.setContentByLocalJSON(sqlNote.getContent()); | ||||||
|  | 
 | ||||||
|  |             String parentGid = mNidToGid.get(sqlNote.getParentId()); | ||||||
|  |             if (parentGid == null) { | ||||||
|  |                 Log.e(TAG, "cannot find task's parent tasklist"); | ||||||
|  |                 throw new ActionFailureException("cannot add remote task"); | ||||||
|  |             } | ||||||
|  |             mGTaskListHashMap.get(parentGid).addChildTask(task); | ||||||
|  | 
 | ||||||
|  |             GTaskClient.getInstance().createTask(task); | ||||||
|  |             n = (Node) task; | ||||||
|  | 
 | ||||||
|  |             // add meta
 | ||||||
|  |             updateRemoteMeta(task.getGid(), sqlNote); | ||||||
|  |         } else { | ||||||
|  |             TaskList tasklist = null; | ||||||
|  | 
 | ||||||
|  |             // we need to skip folder if it has already existed
 | ||||||
|  |             String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; | ||||||
|  |             if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) | ||||||
|  |                 folderName += GTaskStringUtils.FOLDER_DEFAULT; | ||||||
|  |             else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER) | ||||||
|  |                 folderName += GTaskStringUtils.FOLDER_CALL_NOTE; | ||||||
|  |             else | ||||||
|  |                 folderName += sqlNote.getSnippet(); | ||||||
|  | 
 | ||||||
|  |             Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator(); | ||||||
|  |             while (iter.hasNext()) { | ||||||
|  |                 Map.Entry<String, TaskList> entry = iter.next(); | ||||||
|  |                 String gid = entry.getKey(); | ||||||
|  |                 TaskList list = entry.getValue(); | ||||||
|  | 
 | ||||||
|  |                 if (list.getName().equals(folderName)) { | ||||||
|  |                     tasklist = list; | ||||||
|  |                     if (mGTaskHashMap.containsKey(gid)) { | ||||||
|  |                         mGTaskHashMap.remove(gid); | ||||||
|  |                     } | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // no match we can add now
 | ||||||
|  |             if (tasklist == null) { | ||||||
|  |                 tasklist = new TaskList(); | ||||||
|  |                 tasklist.setContentByLocalJSON(sqlNote.getContent()); | ||||||
|  |                 GTaskClient.getInstance().createTaskList(tasklist); | ||||||
|  |                 mGTaskListHashMap.put(tasklist.getGid(), tasklist); | ||||||
|  |             } | ||||||
|  |             n = (Node) tasklist; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // update local note
 | ||||||
|  |         sqlNote.setGtaskId(n.getGid()); | ||||||
|  |         sqlNote.commit(false); | ||||||
|  |         sqlNote.resetLocalModified(); | ||||||
|  |         sqlNote.commit(true); | ||||||
|  | 
 | ||||||
|  |         // gid-id mapping
 | ||||||
|  |         mGidToNid.put(n.getGid(), sqlNote.getId()); | ||||||
|  |         mNidToGid.put(sqlNote.getId(), n.getGid()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         SqlNote sqlNote = new SqlNote(mContext, c); | ||||||
|  | 
 | ||||||
|  |         // update remotely
 | ||||||
|  |         node.setContentByLocalJSON(sqlNote.getContent()); | ||||||
|  |         GTaskClient.getInstance().addUpdateNode(node); | ||||||
|  | 
 | ||||||
|  |         // update meta
 | ||||||
|  |         updateRemoteMeta(node.getGid(), sqlNote); | ||||||
|  | 
 | ||||||
|  |         // move task if necessary
 | ||||||
|  |         if (sqlNote.isNoteType()) { | ||||||
|  |             Task task = (Task) node; | ||||||
|  |             TaskList preParentList = task.getParent(); | ||||||
|  | 
 | ||||||
|  |             String curParentGid = mNidToGid.get(sqlNote.getParentId()); | ||||||
|  |             if (curParentGid == null) { | ||||||
|  |                 Log.e(TAG, "cannot find task's parent tasklist"); | ||||||
|  |                 throw new ActionFailureException("cannot update remote task"); | ||||||
|  |             } | ||||||
|  |             TaskList curParentList = mGTaskListHashMap.get(curParentGid); | ||||||
|  | 
 | ||||||
|  |             if (preParentList != curParentList) { | ||||||
|  |                 preParentList.removeChildTask(task); | ||||||
|  |                 curParentList.addChildTask(task); | ||||||
|  |                 GTaskClient.getInstance().moveTask(task, preParentList, curParentList); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // clear local modified flag
 | ||||||
|  |         sqlNote.resetLocalModified(); | ||||||
|  |         sqlNote.commit(true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { | ||||||
|  |         if (sqlNote != null && sqlNote.isNoteType()) { | ||||||
|  |             MetaData metaData = mMetaHashMap.get(gid); | ||||||
|  |             if (metaData != null) { | ||||||
|  |                 metaData.setMeta(gid, sqlNote.getContent()); | ||||||
|  |                 GTaskClient.getInstance().addUpdateNode(metaData); | ||||||
|  |             } else { | ||||||
|  |                 metaData = new MetaData(); | ||||||
|  |                 metaData.setMeta(gid, sqlNote.getContent()); | ||||||
|  |                 mMetaList.addChildTask(metaData); | ||||||
|  |                 mMetaHashMap.put(gid, metaData); | ||||||
|  |                 GTaskClient.getInstance().createTask(metaData); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void refreshLocalSyncId() throws NetworkFailureException { | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // get the latest gtask list
 | ||||||
|  |         mGTaskHashMap.clear(); | ||||||
|  |         mGTaskListHashMap.clear(); | ||||||
|  |         mMetaHashMap.clear(); | ||||||
|  |         initGTaskList(); | ||||||
|  | 
 | ||||||
|  |         Cursor c = null; | ||||||
|  |         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) | ||||||
|  |                     }, NoteColumns.TYPE + " DESC"); | ||||||
|  |             if (c != null) { | ||||||
|  |                 while (c.moveToNext()) { | ||||||
|  |                     String gid = c.getString(SqlNote.GTASK_ID_COLUMN); | ||||||
|  |                     Node node = mGTaskHashMap.get(gid); | ||||||
|  |                     if (node != null) { | ||||||
|  |                         mGTaskHashMap.remove(gid); | ||||||
|  |                         ContentValues values = new ContentValues(); | ||||||
|  |                         values.put(NoteColumns.SYNC_ID, node.getLastModified()); | ||||||
|  |                         mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, | ||||||
|  |                                 c.getLong(SqlNote.ID_COLUMN)), values, null, null); | ||||||
|  |                     } else { | ||||||
|  |                         Log.e(TAG, "something is missed"); | ||||||
|  |                         throw new ActionFailureException( | ||||||
|  |                                 "some local items don't have gid after sync"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Log.w(TAG, "failed to query local note to refresh sync id"); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             if (c != null) { | ||||||
|  |                 c.close(); | ||||||
|  |                 c = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public String getSyncAccount() { | ||||||
|  |         return GTaskClient.getInstance().getSyncAccount().name; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void cancelSync() { | ||||||
|  |         mCancelled = true; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,128 @@ | |||||||
|  | /* | ||||||
|  |  * 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.app.Service; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.Intent; | ||||||
|  | import android.os.Bundle; | ||||||
|  | import android.os.IBinder; | ||||||
|  | 
 | ||||||
|  | public class GTaskSyncService extends Service { | ||||||
|  |     public final static String ACTION_STRING_NAME = "sync_action_type"; | ||||||
|  | 
 | ||||||
|  |     public final static int ACTION_START_SYNC = 0; | ||||||
|  | 
 | ||||||
|  |     public final static int ACTION_CANCEL_SYNC = 1; | ||||||
|  | 
 | ||||||
|  |     public final static int ACTION_INVALID = 2; | ||||||
|  | 
 | ||||||
|  |     public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; | ||||||
|  | 
 | ||||||
|  |     public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; | ||||||
|  | 
 | ||||||
|  |     public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; | ||||||
|  | 
 | ||||||
|  |     private static GTaskASyncTask mSyncTask = null; | ||||||
|  | 
 | ||||||
|  |     private static String mSyncProgress = ""; | ||||||
|  | 
 | ||||||
|  |     private void startSync() { | ||||||
|  |         if (mSyncTask == null) { | ||||||
|  |             mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { | ||||||
|  |                 public void onComplete() { | ||||||
|  |                     mSyncTask = null; | ||||||
|  |                     sendBroadcast(""); | ||||||
|  |                     stopSelf(); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |             sendBroadcast(""); | ||||||
|  |             mSyncTask.execute(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void cancelSync() { | ||||||
|  |         if (mSyncTask != null) { | ||||||
|  |             mSyncTask.cancelSync(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void onCreate() { | ||||||
|  |         mSyncTask = null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public int onStartCommand(Intent intent, int flags, int startId) { | ||||||
|  |         Bundle bundle = intent.getExtras(); | ||||||
|  |         if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { | ||||||
|  |             switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { | ||||||
|  |                 case ACTION_START_SYNC: | ||||||
|  |                     startSync(); | ||||||
|  |                     break; | ||||||
|  |                 case ACTION_CANCEL_SYNC: | ||||||
|  |                     cancelSync(); | ||||||
|  |                     break; | ||||||
|  |                 default: | ||||||
|  |                     break; | ||||||
|  |             } | ||||||
|  |             return START_STICKY; | ||||||
|  |         } | ||||||
|  |         return super.onStartCommand(intent, flags, startId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void onLowMemory() { | ||||||
|  |         if (mSyncTask != null) { | ||||||
|  |             mSyncTask.cancelSync(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public IBinder onBind(Intent intent) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void sendBroadcast(String msg) { | ||||||
|  |         mSyncProgress = msg; | ||||||
|  |         Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); | ||||||
|  |         intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); | ||||||
|  |         intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); | ||||||
|  |         sendBroadcast(intent); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static void startSync(Activity activity) { | ||||||
|  |         GTaskManager.getInstance().setActivityContext(activity); | ||||||
|  |         Intent intent = new Intent(activity, GTaskSyncService.class); | ||||||
|  |         intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); | ||||||
|  |         activity.startService(intent); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static void cancelSync(Context context) { | ||||||
|  |         Intent intent = new Intent(context, GTaskSyncService.class); | ||||||
|  |         intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); | ||||||
|  |         context.startService(intent); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static boolean isSyncing() { | ||||||
|  |         return mSyncTask != null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static String getProgressString() { | ||||||
|  |         return mSyncProgress; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,253 @@ | |||||||
|  | /* | ||||||
|  |  * 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.model; | ||||||
|  | import android.content.ContentProviderOperation; | ||||||
|  | import android.content.ContentProviderResult; | ||||||
|  | import android.content.ContentUris; | ||||||
|  | import android.content.ContentValues; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.OperationApplicationException; | ||||||
|  | import android.net.Uri; | ||||||
|  | import android.os.RemoteException; | ||||||
|  | import android.util.Log; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.data.Notes.CallNote; | ||||||
|  | import net.micode.notes.data.Notes.DataColumns; | ||||||
|  | import net.micode.notes.data.Notes.NoteColumns; | ||||||
|  | import net.micode.notes.data.Notes.TextNote; | ||||||
|  | 
 | ||||||
|  | import java.util.ArrayList; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class Note { | ||||||
|  |     private ContentValues mNoteDiffValues; | ||||||
|  |     private NoteData mNoteData; | ||||||
|  |     private static final String TAG = "Note"; | ||||||
|  |     /** | ||||||
|  |      * Create a new note id for adding a new note to databases | ||||||
|  |      */ | ||||||
|  |     public static synchronized long getNewNoteId(Context context, long folderId) { | ||||||
|  |         // Create a new note in the database
 | ||||||
|  |         ContentValues values = new ContentValues(); | ||||||
|  |         long createdTime = System.currentTimeMillis(); | ||||||
|  |         values.put(NoteColumns.CREATED_DATE, createdTime); | ||||||
|  |         values.put(NoteColumns.MODIFIED_DATE, createdTime); | ||||||
|  |         values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); | ||||||
|  |         values.put(NoteColumns.LOCAL_MODIFIED, 1); | ||||||
|  |         values.put(NoteColumns.PARENT_ID, folderId); | ||||||
|  |         Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); | ||||||
|  | 
 | ||||||
|  |         long noteId = 0; | ||||||
|  |         try { | ||||||
|  |             noteId = Long.valueOf(uri.getPathSegments().get(1)); | ||||||
|  |         } catch (NumberFormatException e) { | ||||||
|  |             Log.e(TAG, "Get note id error :" + e.toString()); | ||||||
|  |             noteId = 0; | ||||||
|  |         } | ||||||
|  |         if (noteId == -1) { | ||||||
|  |             throw new IllegalStateException("Wrong note id:" + noteId); | ||||||
|  |         } | ||||||
|  |         return noteId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public Note() { | ||||||
|  |         mNoteDiffValues = new ContentValues(); | ||||||
|  |         mNoteData = new NoteData(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setNoteValue(String key, String value) { | ||||||
|  |         mNoteDiffValues.put(key, value); | ||||||
|  |         mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); | ||||||
|  |         mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setTextData(String key, String value) { | ||||||
|  |         mNoteData.setTextData(key, value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setTextDataId(long id) { | ||||||
|  |         mNoteData.setTextDataId(id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getTextDataId() { | ||||||
|  |         return mNoteData.mTextDataId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setCallDataId(long id) { | ||||||
|  |         mNoteData.setCallDataId(id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setCallData(String key, String value) { | ||||||
|  |         mNoteData.setCallData(key, value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isLocalModified() { | ||||||
|  |         return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean syncNote(Context context, long noteId) { | ||||||
|  |         if (noteId <= 0) { | ||||||
|  |             throw new IllegalArgumentException("Wrong note id:" + noteId); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!isLocalModified()) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and | ||||||
|  |          * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the | ||||||
|  |          * note data info | ||||||
|  |          */ | ||||||
|  |         if (context.getContentResolver().update( | ||||||
|  |                 ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, | ||||||
|  |                 null) == 0) { | ||||||
|  |             Log.e(TAG, "Update note error, should not happen"); | ||||||
|  |             // Do not return, fall through
 | ||||||
|  |         } | ||||||
|  |         mNoteDiffValues.clear(); | ||||||
|  | 
 | ||||||
|  |         if (mNoteData.isLocalModified() | ||||||
|  |                 && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private class NoteData { | ||||||
|  |         private long mTextDataId; | ||||||
|  | 
 | ||||||
|  |         private ContentValues mTextDataValues; | ||||||
|  | 
 | ||||||
|  |         private long mCallDataId; | ||||||
|  | 
 | ||||||
|  |         private ContentValues mCallDataValues; | ||||||
|  | 
 | ||||||
|  |         private static final String TAG = "NoteData"; | ||||||
|  | 
 | ||||||
|  |         public NoteData() { | ||||||
|  |             mTextDataValues = new ContentValues(); | ||||||
|  |             mCallDataValues = new ContentValues(); | ||||||
|  |             mTextDataId = 0; | ||||||
|  |             mCallDataId = 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         boolean isLocalModified() { | ||||||
|  |             return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void setTextDataId(long id) { | ||||||
|  |             if(id <= 0) { | ||||||
|  |                 throw new IllegalArgumentException("Text data id should larger than 0"); | ||||||
|  |             } | ||||||
|  |             mTextDataId = id; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void setCallDataId(long id) { | ||||||
|  |             if (id <= 0) { | ||||||
|  |                 throw new IllegalArgumentException("Call data id should larger than 0"); | ||||||
|  |             } | ||||||
|  |             mCallDataId = id; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void setCallData(String key, String value) { | ||||||
|  |             mCallDataValues.put(key, value); | ||||||
|  |             mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); | ||||||
|  |             mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void setTextData(String key, String value) { | ||||||
|  |             mTextDataValues.put(key, value); | ||||||
|  |             mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); | ||||||
|  |             mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Uri pushIntoContentResolver(Context context, long noteId) { | ||||||
|  |             /** | ||||||
|  |              * Check for safety | ||||||
|  |              */ | ||||||
|  |             if (noteId <= 0) { | ||||||
|  |                 throw new IllegalArgumentException("Wrong note id:" + noteId); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); | ||||||
|  |             ContentProviderOperation.Builder builder = null; | ||||||
|  | 
 | ||||||
|  |             if(mTextDataValues.size() > 0) { | ||||||
|  |                 mTextDataValues.put(DataColumns.NOTE_ID, noteId); | ||||||
|  |                 if (mTextDataId == 0) { | ||||||
|  |                     mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); | ||||||
|  |                     Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, | ||||||
|  |                             mTextDataValues); | ||||||
|  |                     try { | ||||||
|  |                         setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); | ||||||
|  |                     } catch (NumberFormatException e) { | ||||||
|  |                         Log.e(TAG, "Insert new text data fail with noteId" + noteId); | ||||||
|  |                         mTextDataValues.clear(); | ||||||
|  |                         return null; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( | ||||||
|  |                             Notes.CONTENT_DATA_URI, mTextDataId)); | ||||||
|  |                     builder.withValues(mTextDataValues); | ||||||
|  |                     operationList.add(builder.build()); | ||||||
|  |                 } | ||||||
|  |                 mTextDataValues.clear(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if(mCallDataValues.size() > 0) { | ||||||
|  |                 mCallDataValues.put(DataColumns.NOTE_ID, noteId); | ||||||
|  |                 if (mCallDataId == 0) { | ||||||
|  |                     mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); | ||||||
|  |                     Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, | ||||||
|  |                             mCallDataValues); | ||||||
|  |                     try { | ||||||
|  |                         setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); | ||||||
|  |                     } catch (NumberFormatException e) { | ||||||
|  |                         Log.e(TAG, "Insert new call data fail with noteId" + noteId); | ||||||
|  |                         mCallDataValues.clear(); | ||||||
|  |                         return null; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( | ||||||
|  |                             Notes.CONTENT_DATA_URI, mCallDataId)); | ||||||
|  |                     builder.withValues(mCallDataValues); | ||||||
|  |                     operationList.add(builder.build()); | ||||||
|  |                 } | ||||||
|  |                 mCallDataValues.clear(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (operationList.size() > 0) { | ||||||
|  |                 try { | ||||||
|  |                     ContentProviderResult[] results = context.getContentResolver().applyBatch( | ||||||
|  |                             Notes.AUTHORITY, operationList); | ||||||
|  |                     return (results == null || results.length == 0 || results[0] == null) ? null | ||||||
|  |                             : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); | ||||||
|  |                 } catch (RemoteException e) { | ||||||
|  |                     Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); | ||||||
|  |                     return null; | ||||||
|  |                 } catch (OperationApplicationException e) { | ||||||
|  |                     Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); | ||||||
|  |                     return null; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,368 @@ | |||||||
|  | /* | ||||||
|  |  * 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.model; | ||||||
|  | 
 | ||||||
|  | import android.appwidget.AppWidgetManager; | ||||||
|  | import android.content.ContentUris; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.database.Cursor; | ||||||
|  | import android.text.TextUtils; | ||||||
|  | import android.util.Log; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.data.Notes.CallNote; | ||||||
|  | import net.micode.notes.data.Notes.DataColumns; | ||||||
|  | import net.micode.notes.data.Notes.DataConstants; | ||||||
|  | import net.micode.notes.data.Notes.NoteColumns; | ||||||
|  | import net.micode.notes.data.Notes.TextNote; | ||||||
|  | import net.micode.notes.tool.ResourceParser.NoteBgResources; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class WorkingNote { | ||||||
|  |     // Note for the working note
 | ||||||
|  |     private Note mNote; | ||||||
|  |     // Note Id
 | ||||||
|  |     private long mNoteId; | ||||||
|  |     // Note content
 | ||||||
|  |     private String mContent; | ||||||
|  |     // Note mode
 | ||||||
|  |     private int mMode; | ||||||
|  | 
 | ||||||
|  |     private long mAlertDate; | ||||||
|  | 
 | ||||||
|  |     private long mModifiedDate; | ||||||
|  | 
 | ||||||
|  |     private int mBgColorId; | ||||||
|  | 
 | ||||||
|  |     private int mWidgetId; | ||||||
|  | 
 | ||||||
|  |     private int mWidgetType; | ||||||
|  | 
 | ||||||
|  |     private long mFolderId; | ||||||
|  | 
 | ||||||
|  |     private Context mContext; | ||||||
|  | 
 | ||||||
|  |     private static final String TAG = "WorkingNote"; | ||||||
|  | 
 | ||||||
|  |     private boolean mIsDeleted; | ||||||
|  | 
 | ||||||
|  |     private NoteSettingChangedListener mNoteSettingStatusListener; | ||||||
|  | 
 | ||||||
|  |     public static final String[] DATA_PROJECTION = new String[] { | ||||||
|  |             DataColumns.ID, | ||||||
|  |             DataColumns.CONTENT, | ||||||
|  |             DataColumns.MIME_TYPE, | ||||||
|  |             DataColumns.DATA1, | ||||||
|  |             DataColumns.DATA2, | ||||||
|  |             DataColumns.DATA3, | ||||||
|  |             DataColumns.DATA4, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     public static final String[] NOTE_PROJECTION = new String[] { | ||||||
|  |             NoteColumns.PARENT_ID, | ||||||
|  |             NoteColumns.ALERTED_DATE, | ||||||
|  |             NoteColumns.BG_COLOR_ID, | ||||||
|  |             NoteColumns.WIDGET_ID, | ||||||
|  |             NoteColumns.WIDGET_TYPE, | ||||||
|  |             NoteColumns.MODIFIED_DATE | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     private static final int DATA_ID_COLUMN = 0; | ||||||
|  | 
 | ||||||
|  |     private static final int DATA_CONTENT_COLUMN = 1; | ||||||
|  | 
 | ||||||
|  |     private static final int DATA_MIME_TYPE_COLUMN = 2; | ||||||
|  | 
 | ||||||
|  |     private static final int DATA_MODE_COLUMN = 3; | ||||||
|  | 
 | ||||||
|  |     private static final int NOTE_PARENT_ID_COLUMN = 0; | ||||||
|  | 
 | ||||||
|  |     private static final int NOTE_ALERTED_DATE_COLUMN = 1; | ||||||
|  | 
 | ||||||
|  |     private static final int NOTE_BG_COLOR_ID_COLUMN = 2; | ||||||
|  | 
 | ||||||
|  |     private static final int NOTE_WIDGET_ID_COLUMN = 3; | ||||||
|  | 
 | ||||||
|  |     private static final int NOTE_WIDGET_TYPE_COLUMN = 4; | ||||||
|  | 
 | ||||||
|  |     private static final int NOTE_MODIFIED_DATE_COLUMN = 5; | ||||||
|  | 
 | ||||||
|  |     // New note construct
 | ||||||
|  |     private WorkingNote(Context context, long folderId) { | ||||||
|  |         mContext = context; | ||||||
|  |         mAlertDate = 0; | ||||||
|  |         mModifiedDate = System.currentTimeMillis(); | ||||||
|  |         mFolderId = folderId; | ||||||
|  |         mNote = new Note(); | ||||||
|  |         mNoteId = 0; | ||||||
|  |         mIsDeleted = false; | ||||||
|  |         mMode = 0; | ||||||
|  |         mWidgetType = Notes.TYPE_WIDGET_INVALIDE; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Existing note construct
 | ||||||
|  |     private WorkingNote(Context context, long noteId, long folderId) { | ||||||
|  |         mContext = context; | ||||||
|  |         mNoteId = noteId; | ||||||
|  |         mFolderId = folderId; | ||||||
|  |         mIsDeleted = false; | ||||||
|  |         mNote = new Note(); | ||||||
|  |         loadNote(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void loadNote() { | ||||||
|  |         Cursor cursor = mContext.getContentResolver().query( | ||||||
|  |                 ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, | ||||||
|  |                 null, null); | ||||||
|  | 
 | ||||||
|  |         if (cursor != null) { | ||||||
|  |             if (cursor.moveToFirst()) { | ||||||
|  |                 mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); | ||||||
|  |                 mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); | ||||||
|  |                 mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); | ||||||
|  |                 mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); | ||||||
|  |                 mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); | ||||||
|  |                 mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); | ||||||
|  |             } | ||||||
|  |             cursor.close(); | ||||||
|  |         } else { | ||||||
|  |             Log.e(TAG, "No note with id:" + mNoteId); | ||||||
|  |             throw new IllegalArgumentException("Unable to find note with id " + mNoteId); | ||||||
|  |         } | ||||||
|  |         loadNoteData(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void loadNoteData() { | ||||||
|  |         Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, | ||||||
|  |                 DataColumns.NOTE_ID + "=?", new String[] { | ||||||
|  |                     String.valueOf(mNoteId) | ||||||
|  |                 }, null); | ||||||
|  | 
 | ||||||
|  |         if (cursor != null) { | ||||||
|  |             if (cursor.moveToFirst()) { | ||||||
|  |                 do { | ||||||
|  |                     String type = cursor.getString(DATA_MIME_TYPE_COLUMN); | ||||||
|  |                     if (DataConstants.NOTE.equals(type)) { | ||||||
|  |                         mContent = cursor.getString(DATA_CONTENT_COLUMN); | ||||||
|  |                         mMode = cursor.getInt(DATA_MODE_COLUMN); | ||||||
|  |                         mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); | ||||||
|  |                     } else if (DataConstants.CALL_NOTE.equals(type)) { | ||||||
|  |                         mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); | ||||||
|  |                     } else { | ||||||
|  |                         Log.d(TAG, "Wrong note type with type:" + type); | ||||||
|  |                     } | ||||||
|  |                 } while (cursor.moveToNext()); | ||||||
|  |             } | ||||||
|  |             cursor.close(); | ||||||
|  |         } else { | ||||||
|  |             Log.e(TAG, "No data with id:" + mNoteId); | ||||||
|  |             throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, | ||||||
|  |             int widgetType, int defaultBgColorId) { | ||||||
|  |         WorkingNote note = new WorkingNote(context, folderId); | ||||||
|  |         note.setBgColorId(defaultBgColorId); | ||||||
|  |         note.setWidgetId(widgetId); | ||||||
|  |         note.setWidgetType(widgetType); | ||||||
|  |         return note; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static WorkingNote load(Context context, long id) { | ||||||
|  |         return new WorkingNote(context, id, 0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public synchronized boolean saveNote() { | ||||||
|  |         if (isWorthSaving()) { | ||||||
|  |             if (!existInDatabase()) { | ||||||
|  |                 if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { | ||||||
|  |                     Log.e(TAG, "Create new note fail with id:" + mNoteId); | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             mNote.syncNote(mContext, mNoteId); | ||||||
|  | 
 | ||||||
|  |             /** | ||||||
|  |              * Update widget content if there exist any widget of this note | ||||||
|  |              */ | ||||||
|  |             if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID | ||||||
|  |                     && mWidgetType != Notes.TYPE_WIDGET_INVALIDE | ||||||
|  |                     && mNoteSettingStatusListener != null) { | ||||||
|  |                 mNoteSettingStatusListener.onWidgetChanged(); | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         } else { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean existInDatabase() { | ||||||
|  |         return mNoteId > 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private boolean isWorthSaving() { | ||||||
|  |         if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) | ||||||
|  |                 || (existInDatabase() && !mNote.isLocalModified())) { | ||||||
|  |             return false; | ||||||
|  |         } else { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { | ||||||
|  |         mNoteSettingStatusListener = l; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setAlertDate(long date, boolean set) { | ||||||
|  |         if (date != mAlertDate) { | ||||||
|  |             mAlertDate = date; | ||||||
|  |             mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); | ||||||
|  |         } | ||||||
|  |         if (mNoteSettingStatusListener != null) { | ||||||
|  |             mNoteSettingStatusListener.onClockAlertChanged(date, set); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void markDeleted(boolean mark) { | ||||||
|  |         mIsDeleted = mark; | ||||||
|  |         if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID | ||||||
|  |                 && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { | ||||||
|  |                 mNoteSettingStatusListener.onWidgetChanged(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setBgColorId(int id) { | ||||||
|  |         if (id != mBgColorId) { | ||||||
|  |             mBgColorId = id; | ||||||
|  |             if (mNoteSettingStatusListener != null) { | ||||||
|  |                 mNoteSettingStatusListener.onBackgroundColorChanged(); | ||||||
|  |             } | ||||||
|  |             mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setCheckListMode(int mode) { | ||||||
|  |         if (mMode != mode) { | ||||||
|  |             if (mNoteSettingStatusListener != null) { | ||||||
|  |                 mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); | ||||||
|  |             } | ||||||
|  |             mMode = mode; | ||||||
|  |             mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setWidgetType(int type) { | ||||||
|  |         if (type != mWidgetType) { | ||||||
|  |             mWidgetType = type; | ||||||
|  |             mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setWidgetId(int id) { | ||||||
|  |         if (id != mWidgetId) { | ||||||
|  |             mWidgetId = id; | ||||||
|  |             mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setWorkingText(String text) { | ||||||
|  |         if (!TextUtils.equals(mContent, text)) { | ||||||
|  |             mContent = text; | ||||||
|  |             mNote.setTextData(DataColumns.CONTENT, mContent); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void convertToCallNote(String phoneNumber, long callDate) { | ||||||
|  |         mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); | ||||||
|  |         mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); | ||||||
|  |         mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean hasClockAlert() { | ||||||
|  |         return (mAlertDate > 0 ? true : false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public String getContent() { | ||||||
|  |         return mContent; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getAlertDate() { | ||||||
|  |         return mAlertDate; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getModifiedDate() { | ||||||
|  |         return mModifiedDate; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getBgColorResId() { | ||||||
|  |         return NoteBgResources.getNoteBgResource(mBgColorId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getBgColorId() { | ||||||
|  |         return mBgColorId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getTitleBgResId() { | ||||||
|  |         return NoteBgResources.getNoteTitleBgResource(mBgColorId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getCheckListMode() { | ||||||
|  |         return mMode; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getNoteId() { | ||||||
|  |         return mNoteId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getFolderId() { | ||||||
|  |         return mFolderId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getWidgetId() { | ||||||
|  |         return mWidgetId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getWidgetType() { | ||||||
|  |         return mWidgetType; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public interface NoteSettingChangedListener { | ||||||
|  |         /** | ||||||
|  |          * Called when the background color of current note has just changed | ||||||
|  |          */ | ||||||
|  |         void onBackgroundColorChanged(); | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Called when user set clock | ||||||
|  |          */ | ||||||
|  |         void onClockAlertChanged(long date, boolean set); | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Call when user create note from widget | ||||||
|  |          */ | ||||||
|  |         void onWidgetChanged(); | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Call when switch between check list mode and normal mode | ||||||
|  |          * @param oldMode is previous mode before change | ||||||
|  |          * @param newMode is new mode | ||||||
|  |          */ | ||||||
|  |         void onCheckListModeChanged(int oldMode, int newMode); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,65 @@ | |||||||
|  | /* | ||||||
|  |  * 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 { | ||||||
|  |     private Button mButton; | ||||||
|  |     private PopupMenu mPopupMenu; | ||||||
|  |     //声明一个下拉菜单
 | ||||||
|  |     private Menu mMenu; | ||||||
|  | 
 | ||||||
|  |     public DropdownMenu(Context context, Button button, int menuId) { | ||||||
|  |         mButton = button; | ||||||
|  |         mButton.setBackgroundResource(R.drawable.dropdown_icon); | ||||||
|  |         //设置这个view的背景
 | ||||||
|  |         mPopupMenu = new PopupMenu(context, mButton); | ||||||
|  |         mMenu = mPopupMenu.getMenu(); | ||||||
|  |         mPopupMenu.getMenuInflater().inflate(menuId, mMenu); | ||||||
|  |         //MenuInflater是用来实例化Menu目录下的Menu布局文件
 | ||||||
|  |         //根据ID来确认menu的内容选项
 | ||||||
|  |         mButton.setOnClickListener(new OnClickListener() { | ||||||
|  |             public void onClick(View v) { | ||||||
|  |                 mPopupMenu.show(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { | ||||||
|  |         if (mPopupMenu != null) { | ||||||
|  |             mPopupMenu.setOnMenuItemClickListener(listener); | ||||||
|  |         }//设置菜单的监听
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public MenuItem findItem(int id) { | ||||||
|  |         return mMenu.findItem(id); | ||||||
|  |     }//对于菜单选项的初始化,根据索引搜索菜单需要的选项
 | ||||||
|  | 
 | ||||||
|  |     public void setTitle(CharSequence title) { | ||||||
|  |         mButton.setText(title); | ||||||
|  |     }//布局文件,设置标题
 | ||||||
|  | } | ||||||
| @ -0,0 +1,132 @@ | |||||||
|  | /* | ||||||
|  |  * 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.widget; | ||||||
|  | import android.app.PendingIntent; | ||||||
|  | import android.appwidget.AppWidgetManager; | ||||||
|  | import android.appwidget.AppWidgetProvider; | ||||||
|  | import android.content.ContentValues; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.Intent; | ||||||
|  | import android.database.Cursor; | ||||||
|  | import android.util.Log; | ||||||
|  | import android.widget.RemoteViews; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.data.Notes.NoteColumns; | ||||||
|  | import net.micode.notes.tool.ResourceParser; | ||||||
|  | import net.micode.notes.ui.NoteEditActivity; | ||||||
|  | import net.micode.notes.ui.NotesListActivity; | ||||||
|  | 
 | ||||||
|  | public abstract class NoteWidgetProvider extends AppWidgetProvider { | ||||||
|  |     public static final String [] PROJECTION = new String [] { | ||||||
|  |         NoteColumns.ID, | ||||||
|  |         NoteColumns.BG_COLOR_ID, | ||||||
|  |         NoteColumns.SNIPPET | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     public static final int COLUMN_ID           = 0; | ||||||
|  |     public static final int COLUMN_BG_COLOR_ID  = 1; | ||||||
|  |     public static final int COLUMN_SNIPPET      = 2; | ||||||
|  | 
 | ||||||
|  |     private static final String TAG = "NoteWidgetProvider"; | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void onDeleted(Context context, int[] appWidgetIds) { | ||||||
|  |         ContentValues values = new ContentValues(); | ||||||
|  |         values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); | ||||||
|  |         for (int i = 0; i < appWidgetIds.length; i++) { | ||||||
|  |             context.getContentResolver().update(Notes.CONTENT_NOTE_URI, | ||||||
|  |                     values, | ||||||
|  |                     NoteColumns.WIDGET_ID + "=?", | ||||||
|  |                     new String[] { String.valueOf(appWidgetIds[i])}); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Cursor getNoteWidgetInfo(Context context, int widgetId) { | ||||||
|  |         return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, | ||||||
|  |                 PROJECTION, | ||||||
|  |                 NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", | ||||||
|  |                 new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, | ||||||
|  |                 null); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { | ||||||
|  |         update(context, appWidgetManager, appWidgetIds, false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, | ||||||
|  |             boolean privacyMode) { | ||||||
|  |         for (int i = 0; i < appWidgetIds.length; i++) { | ||||||
|  |             if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { | ||||||
|  |                 int bgId = ResourceParser.getDefaultBgId(context); | ||||||
|  |                 String snippet = ""; | ||||||
|  |                 Intent intent = new Intent(context, NoteEditActivity.class); | ||||||
|  |                 intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); | ||||||
|  |                 intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); | ||||||
|  |                 intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); | ||||||
|  | 
 | ||||||
|  |                 Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); | ||||||
|  |                 if (c != null && c.moveToFirst()) { | ||||||
|  |                     if (c.getCount() > 1) { | ||||||
|  |                         Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); | ||||||
|  |                         c.close(); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                     snippet = c.getString(COLUMN_SNIPPET); | ||||||
|  |                     bgId = c.getInt(COLUMN_BG_COLOR_ID); | ||||||
|  |                     intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); | ||||||
|  |                     intent.setAction(Intent.ACTION_VIEW); | ||||||
|  |                 } else { | ||||||
|  |                     snippet = context.getResources().getString(R.string.widget_havenot_content); | ||||||
|  |                     intent.setAction(Intent.ACTION_INSERT_OR_EDIT); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if (c != null) { | ||||||
|  |                     c.close(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); | ||||||
|  |                 rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); | ||||||
|  |                 intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); | ||||||
|  |                 /** | ||||||
|  |                  * Generate the pending intent to start host for the widget | ||||||
|  |                  */ | ||||||
|  |                 PendingIntent pendingIntent = null; | ||||||
|  |                 if (privacyMode) { | ||||||
|  |                     rv.setTextViewText(R.id.widget_text, | ||||||
|  |                             context.getString(R.string.widget_under_visit_mode)); | ||||||
|  |                     pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( | ||||||
|  |                             context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); | ||||||
|  |                 } else { | ||||||
|  |                     rv.setTextViewText(R.id.widget_text, snippet); | ||||||
|  |                     pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, | ||||||
|  |                             PendingIntent.FLAG_UPDATE_CURRENT); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); | ||||||
|  |                 appWidgetManager.updateAppWidget(appWidgetIds[i], rv); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected abstract int getBgResourceId(int bgId); | ||||||
|  | 
 | ||||||
|  |     protected abstract int getLayoutId(); | ||||||
|  | 
 | ||||||
|  |     protected abstract int getWidgetType(); | ||||||
|  | } | ||||||
| @ -0,0 +1,47 @@ | |||||||
|  | /* | ||||||
|  |  * 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.widget; | ||||||
|  | 
 | ||||||
|  | import android.appwidget.AppWidgetManager; | ||||||
|  | import android.content.Context; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.tool.ResourceParser; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class NoteWidgetProvider_2x extends NoteWidgetProvider { | ||||||
|  |     @Override | ||||||
|  |     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { | ||||||
|  |         super.update(context, appWidgetManager, appWidgetIds); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected int getLayoutId() { | ||||||
|  |         return R.layout.widget_2x; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected int getBgResourceId(int bgId) { | ||||||
|  |         return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected int getWidgetType() { | ||||||
|  |         return Notes.TYPE_WIDGET_2X; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,46 @@ | |||||||
|  | /* | ||||||
|  |  * 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.widget; | ||||||
|  | 
 | ||||||
|  | import android.appwidget.AppWidgetManager; | ||||||
|  | import android.content.Context; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.tool.ResourceParser; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class NoteWidgetProvider_4x extends NoteWidgetProvider { | ||||||
|  |     @Override | ||||||
|  |     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { | ||||||
|  |         super.update(context, appWidgetManager, appWidgetIds); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected int getLayoutId() { | ||||||
|  |         return R.layout.widget_4x; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected int getBgResourceId(int bgId) { | ||||||
|  |         return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected int getWidgetType() { | ||||||
|  |         return Notes.TYPE_WIDGET_4X; | ||||||
|  |     } | ||||||
|  | } | ||||||
					Loading…
					
					
				
		Reference in new issue