Compare commits
	
		
			1 Commits 
		
	
	
		
			main
			...
			liyunlong_
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 675656a739 | 7 months ago | 
| @ -0,0 +1,287 @@ | |||||||
|  | /* | ||||||
|  |  * 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.data; | ||||||
|  | 
 | ||||||
|  | import android.net.Uri; | ||||||
|  | public class Notes { | ||||||
|  |     // 定义Notes类的常量
 | ||||||
|  |     public static final String AUTHORITY = "micode_notes"; | ||||||
|  |     public static final String TAG = "Notes"; | ||||||
|  |     public static final int TYPE_NOTE     = 0; | ||||||
|  |     public static final int TYPE_FOLDER   = 1; | ||||||
|  |     public static final int TYPE_SYSTEM   = 2; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Following IDs are system folders' identifiers | ||||||
|  |      * {@link Notes#ID_ROOT_FOLDER } is default folder | ||||||
|  |      * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder | ||||||
|  |      * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records | ||||||
|  |      */ | ||||||
|  |     public static final int ID_ROOT_FOLDER = 0; | ||||||
|  |     public static final int ID_TEMPARAY_FOLDER = -1; | ||||||
|  |     public static final int ID_CALL_RECORD_FOLDER = -2; | ||||||
|  |     public static final int ID_TRASH_FOLER = -3; | ||||||
|  | 
 | ||||||
|  |     // 定义Intent的额外参数
 | ||||||
|  |     public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; | ||||||
|  |     public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; | ||||||
|  |     public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; | ||||||
|  |     public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; | ||||||
|  |     public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; | ||||||
|  |     public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; | ||||||
|  | 
 | ||||||
|  |     // 定义Widget的类型
 | ||||||
|  |     public static final int TYPE_WIDGET_INVALIDE      = -1; | ||||||
|  |     public static final int TYPE_WIDGET_2X            = 0; | ||||||
|  |     public static final int TYPE_WIDGET_4X            = 1; | ||||||
|  | 
 | ||||||
|  |     // 定义DataConstants类
 | ||||||
|  |     public static class DataConstants { | ||||||
|  |         public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; | ||||||
|  |         public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Uri to query all notes and folders | ||||||
|  |      */ | ||||||
|  |     public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Uri to query data | ||||||
|  |      */ | ||||||
|  |     public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); | ||||||
|  | 
 | ||||||
|  |     // 定义NoteColumns接口
 | ||||||
|  |     public interface NoteColumns { | ||||||
|  |         /** | ||||||
|  |          * The unique ID for a row | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String ID = "_id"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * The parent's id for note or folder | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String PARENT_ID = "parent_id"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Created data for note or folder | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String CREATED_DATE = "created_date"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Latest modified date | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String MODIFIED_DATE = "modified_date"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Alert date | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String ALERTED_DATE = "alert_date"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Folder's name or text content of note | ||||||
|  |          * <P> Type: TEXT </P> | ||||||
|  |          */ | ||||||
|  |         public static final String SNIPPET = "snippet"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Note's widget id | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String WIDGET_ID = "widget_id"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Note's widget type | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String WIDGET_TYPE = "widget_type"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Note's background color's id | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String BG_COLOR_ID = "bg_color_id"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * For text note, it doesn't has attachment, for multi-media | ||||||
|  |          * note, it has at least one attachment | ||||||
|  |          * <P> Type: INTEGER </P> | ||||||
|  |          */ | ||||||
|  |         public static final String HAS_ATTACHMENT = "has_attachment"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Folder's count of notes | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String NOTES_COUNT = "notes_count"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * The file type: folder or note | ||||||
|  |          * <P> Type: INTEGER </P> | ||||||
|  |          */ | ||||||
|  |         public static final String TYPE = "type"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * The last sync id | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String SYNC_ID = "sync_id"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Sign to indicate local modified or not | ||||||
|  |          * <P> Type: INTEGER </P> | ||||||
|  |          */ | ||||||
|  |         public static final String LOCAL_MODIFIED = "local_modified"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Original parent id before moving into temporary folder | ||||||
|  |          * <P> Type : INTEGER </P> | ||||||
|  |          */ | ||||||
|  |         public static final String ORIGIN_PARENT_ID = "origin_parent_id"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * The gtask id | ||||||
|  |          * <P> Type : TEXT </P> | ||||||
|  |          */ | ||||||
|  |         public static final String GTASK_ID = "gtask_id"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * The version code | ||||||
|  |          * <P> Type : INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String VERSION = "version"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 定义DataColumns接口
 | ||||||
|  |     public interface DataColumns { | ||||||
|  |         /** | ||||||
|  |          * The unique ID for a row | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String ID = "_id"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * The MIME type of the item represented by this row. | ||||||
|  |          * <P> Type: Text </P> | ||||||
|  |          */ | ||||||
|  |         public static final String MIME_TYPE = "mime_type"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * The reference id to note that this data belongs to | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String NOTE_ID = "note_id"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Created data for note or folder | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String CREATED_DATE = "created_date"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Latest modified date | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String MODIFIED_DATE = "modified_date"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Data's content | ||||||
|  |          * <P> Type: TEXT </P> | ||||||
|  |          */ | ||||||
|  |         public static final String CONTENT = "content"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Generic data column, the meaning is {@link #MIMETYPE} specific, used for | ||||||
|  |          * integer data type | ||||||
|  |          * <P> Type: INTEGER </P> | ||||||
|  |          */ | ||||||
|  |         public static final String DATA1 = "data1"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Generic data column, the meaning is {@link #MIMETYPE} specific, used for | ||||||
|  |          * integer data type | ||||||
|  |          * <P> Type: INTEGER </P> | ||||||
|  |          */ | ||||||
|  |         public static final String DATA2 = "data2"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Generic data column, the meaning is {@link #MIMETYPE} specific, used for | ||||||
|  |          * TEXT data type | ||||||
|  |          * <P> Type: TEXT </P> | ||||||
|  |          */ | ||||||
|  |         public static final String DATA3 = "data3"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Generic data column, the meaning is {@link #MIMETYPE} specific, used for | ||||||
|  |          * TEXT data type | ||||||
|  |          * <P> Type: TEXT </P> | ||||||
|  |          */ | ||||||
|  |         public static final String DATA4 = "data4"; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Generic data column, the meaning is {@link #MIMETYPE} specific, used for | ||||||
|  |          * TEXT data type | ||||||
|  |          * <P> Type: TEXT </P> | ||||||
|  |          */ | ||||||
|  |         public static final String DATA5 = "data5"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 定义TextNote类
 | ||||||
|  |     public static final class TextNote implements DataColumns { | ||||||
|  |         /** | ||||||
|  |          * Mode to indicate the text in check list mode or not | ||||||
|  |          * <P> Type: Integer 1:check list mode 0: normal mode </P> | ||||||
|  |          */ | ||||||
|  |         public static final String MODE = DATA1; | ||||||
|  | 
 | ||||||
|  |         public static final int MODE_CHECK_LIST = 1; | ||||||
|  | 
 | ||||||
|  |         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; | ||||||
|  | 
 | ||||||
|  |         public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; | ||||||
|  | 
 | ||||||
|  |         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 定义CallNote类
 | ||||||
|  |     public static final class CallNote implements DataColumns { | ||||||
|  |         /** | ||||||
|  |          * Call date for this record | ||||||
|  |          * <P> Type: INTEGER (long) </P> | ||||||
|  |          */ | ||||||
|  |         public static final String CALL_DATE = DATA1; | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Phone number for this record | ||||||
|  |          * <P> Type: TEXT </P> | ||||||
|  |          */ | ||||||
|  |         public static final String PHONE_NUMBER = DATA3; | ||||||
|  | 
 | ||||||
|  |         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; | ||||||
|  | 
 | ||||||
|  |         public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; | ||||||
|  | 
 | ||||||
|  |         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,365 @@ | |||||||
|  | /* | ||||||
|  |  * 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.data; | ||||||
|  | 
 | ||||||
|  | import android.database.Cursor; | ||||||
|  | import android.util.Log; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.data.Notes.NoteColumns; | ||||||
|  | import net.micode.notes.gtask.exception.ActionFailureException; | ||||||
|  | import net.micode.notes.tool.GTaskStringUtils; | ||||||
|  | 
 | ||||||
|  | import org.json.JSONException; | ||||||
|  | import org.json.JSONObject; | ||||||
|  | 
 | ||||||
|  | import java.util.ArrayList; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class TaskList extends Node { | ||||||
|  |     // 定义一个常量,用于打印日志
 | ||||||
|  |     private static final String TAG = TaskList.class.getSimpleName(); | ||||||
|  | 
 | ||||||
|  |     // 定义一个变量,用于存储任务列表的索引
 | ||||||
|  |     private int mIndex; | ||||||
|  | 
 | ||||||
|  |     // 定义一个变量,用于存储任务列表的子任务列表
 | ||||||
|  |     private ArrayList<Task> mChildren; | ||||||
|  | 
 | ||||||
|  |     // 构造函数,初始化任务列表的子任务列表和索引
 | ||||||
|  |     public TaskList() { | ||||||
|  |         super(); | ||||||
|  |         mChildren = new ArrayList<Task>(); | ||||||
|  |         mIndex = 1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 获取创建任务的JSON对象
 | ||||||
|  |     public JSONObject getCreateAction(int actionId) { | ||||||
|  |         JSONObject js = new JSONObject(); | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             // action_type
 | ||||||
|  |             js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, | ||||||
|  |                     GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); | ||||||
|  | 
 | ||||||
|  |             // action_id
 | ||||||
|  |             js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); | ||||||
|  | 
 | ||||||
|  |             // index
 | ||||||
|  |             js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); | ||||||
|  | 
 | ||||||
|  |             // entity_delta
 | ||||||
|  |             JSONObject entity = new JSONObject(); | ||||||
|  |             entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); | ||||||
|  |             entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); | ||||||
|  |             entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, | ||||||
|  |                     GTaskStringUtils.GTASK_JSON_TYPE_GROUP); | ||||||
|  |             js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); | ||||||
|  | 
 | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new ActionFailureException("fail to generate tasklist-create jsonobject"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return js; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 获取更新任务的JSON对象
 | ||||||
|  |     public JSONObject getUpdateAction(int actionId) { | ||||||
|  |         JSONObject js = new JSONObject(); | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             // action_type
 | ||||||
|  |             js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, | ||||||
|  |                     GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); | ||||||
|  | 
 | ||||||
|  |             // action_id
 | ||||||
|  |             js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); | ||||||
|  | 
 | ||||||
|  |             // id
 | ||||||
|  |             js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); | ||||||
|  | 
 | ||||||
|  |             // entity_delta
 | ||||||
|  |             JSONObject entity = new JSONObject(); | ||||||
|  |             entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); | ||||||
|  |             entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); | ||||||
|  |             js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); | ||||||
|  | 
 | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new ActionFailureException("fail to generate tasklist-update jsonobject"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return js; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 根据远程JSON设置任务列表的内容
 | ||||||
|  |     public void setContentByRemoteJSON(JSONObject js) { | ||||||
|  |         if (js != null) { | ||||||
|  |             try { | ||||||
|  |                 // id
 | ||||||
|  |                 if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { | ||||||
|  |                     setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // last_modified
 | ||||||
|  |                 if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { | ||||||
|  |                     setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 // name
 | ||||||
|  |                 if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { | ||||||
|  |                     setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |             } catch (JSONException e) { | ||||||
|  |                 Log.e(TAG, e.toString()); | ||||||
|  |                 e.printStackTrace(); | ||||||
|  |                 throw new ActionFailureException("fail to get tasklist content from jsonobject"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 根据本地JSON设置任务列表的内容
 | ||||||
|  |     public void setContentByLocalJSON(JSONObject js) { | ||||||
|  |         if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { | ||||||
|  |             Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); | ||||||
|  | 
 | ||||||
|  |             if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { | ||||||
|  |                 String name = folder.getString(NoteColumns.SNIPPET); | ||||||
|  |                 setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); | ||||||
|  |             } else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { | ||||||
|  |                 if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) | ||||||
|  |                     setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); | ||||||
|  |                 else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER) | ||||||
|  |                     setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX | ||||||
|  |                             + GTaskStringUtils.FOLDER_CALL_NOTE); | ||||||
|  |                 else | ||||||
|  |                     Log.e(TAG, "invalid system folder"); | ||||||
|  |             } else { | ||||||
|  |                 Log.e(TAG, "error type"); | ||||||
|  |             } | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 获取本地的JSON对象
 | ||||||
|  |     public JSONObject getLocalJSONFromContent() { | ||||||
|  |         try { | ||||||
|  |             JSONObject js = new JSONObject(); | ||||||
|  |             JSONObject folder = new JSONObject(); | ||||||
|  | 
 | ||||||
|  |             String folderName = getName(); | ||||||
|  |             if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) | ||||||
|  |                 folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(), | ||||||
|  |                         folderName.length()); | ||||||
|  |             folder.put(NoteColumns.SNIPPET, folderName); | ||||||
|  |             if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) | ||||||
|  |                     || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) | ||||||
|  |                 folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); | ||||||
|  |             else | ||||||
|  |                 folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); | ||||||
|  | 
 | ||||||
|  |             js.put(GTaskStringUtils.META_HEAD_NOTE, folder); | ||||||
|  | 
 | ||||||
|  |             return js; | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 获取同步动作
 | ||||||
|  |     public int getSyncAction(Cursor c) { | ||||||
|  |         try { | ||||||
|  |             if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { | ||||||
|  |                 // there is no local update
 | ||||||
|  |                 if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { | ||||||
|  |                     // no update both side
 | ||||||
|  |                     return SYNC_ACTION_NONE; | ||||||
|  |                 } else { | ||||||
|  |                     // apply remote to local
 | ||||||
|  |                     return SYNC_ACTION_UPDATE_LOCAL; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 // validate gtask id
 | ||||||
|  |                 if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { | ||||||
|  |                     Log.e(TAG, "gtask id doesn't match"); | ||||||
|  |                     return SYNC_ACTION_ERROR; | ||||||
|  |                 } | ||||||
|  |                 if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { | ||||||
|  |                     // local modification only
 | ||||||
|  |                     return SYNC_ACTION_UPDATE_REMOTE; | ||||||
|  |                 } else { | ||||||
|  |                     // for folder conflicts, just apply local modification
 | ||||||
|  |                     return SYNC_ACTION_UPDATE_REMOTE; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return SYNC_ACTION_ERROR; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 获取子任务数量
 | ||||||
|  |     public int getChildTaskCount() { | ||||||
|  |         return mChildren.size(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 添加子任务
 | ||||||
|  |     public boolean addChildTask(Task task) { | ||||||
|  |         boolean ret = false; | ||||||
|  |         if (task != null && !mChildren.contains(task)) { | ||||||
|  |             ret = mChildren.add(task); | ||||||
|  |             if (ret) { | ||||||
|  |                 // need to set prior sibling and parent
 | ||||||
|  |                 task.setPriorSibling(mChildren.isEmpty() ? null : mChildren | ||||||
|  |                         .get(mChildren.size() - 1)); | ||||||
|  |                 task.setParent(this); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 在指定位置添加子任务
 | ||||||
|  |     public boolean addChildTask(Task task, int index) { | ||||||
|  |         if (index < 0 || index > mChildren.size()) { | ||||||
|  |             Log.e(TAG, "add child task: invalid index"); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         int pos = mChildren.indexOf(task); | ||||||
|  |         if (task != null && pos == -1) { | ||||||
|  |             mChildren.add(index, task); | ||||||
|  | 
 | ||||||
|  |             // update the task list
 | ||||||
|  |             Task preTask = null; | ||||||
|  |             Task afterTask = null; | ||||||
|  |             if (index != 0) | ||||||
|  |                 preTask = mChildren.get(index - 1); | ||||||
|  |             if (index != mChildren.size() - 1) | ||||||
|  |                 afterTask = mChildren.get(index + 1); | ||||||
|  | 
 | ||||||
|  |             task.setPriorSibling(preTask); | ||||||
|  |             if (afterTask != null) | ||||||
|  |                 afterTask.setPriorSibling(task); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 移除子任务
 | ||||||
|  |     public boolean removeChildTask(Task task) { | ||||||
|  |         boolean ret = false; | ||||||
|  |         int index = mChildren.indexOf(task); | ||||||
|  |         if (index != -1) { | ||||||
|  |             ret = mChildren.remove(task); | ||||||
|  | 
 | ||||||
|  |             if (ret) { | ||||||
|  |                 // reset prior sibling and parent
 | ||||||
|  |                 task.setPriorSibling(null); | ||||||
|  |                 task.setParent(null); | ||||||
|  | 
 | ||||||
|  |                 // update the task list
 | ||||||
|  |                 if (index != mChildren.size()) { | ||||||
|  |                     mChildren.get(index).setPriorSibling( | ||||||
|  |                             index == 0 ? null : mChildren.get(index - 1)); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return ret; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 移动子任务
 | ||||||
|  |     public boolean moveChildTask(Task task, int index) { | ||||||
|  | 
 | ||||||
|  |         if (index < 0 || index >= mChildren.size()) { | ||||||
|  |             Log.e(TAG, "move child task: invalid index"); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         int pos = mChildren.indexOf(task); | ||||||
|  |         if (pos == -1) { | ||||||
|  |             Log.e(TAG, "move child task: the task should in the list"); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (pos == index) | ||||||
|  |             return true; | ||||||
|  |         return (removeChildTask(task) && addChildTask(task, index)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 根据GID查找子任务
 | ||||||
|  |     public Task findChildTaskByGid(String gid) { | ||||||
|  |         for (int i = 0; i < mChildren.size(); i++) { | ||||||
|  |             Task t = mChildren.get(i); | ||||||
|  |             if (t.getGid().equals(gid)) { | ||||||
|  |                 return t; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 获取子任务索引
 | ||||||
|  |     public int getChildTaskIndex(Task task) { | ||||||
|  |         return mChildren.indexOf(task); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 根据索引获取子任务
 | ||||||
|  |     public Task getChildTaskByIndex(int index) { | ||||||
|  |         if (index < 0 || index >= mChildren.size()) { | ||||||
|  |             Log.e(TAG, "getTaskByIndex: invalid index"); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |         return mChildren.get(index); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 根据GID获取子任务
 | ||||||
|  |     public Task getChilTaskByGid(String gid) { | ||||||
|  |         for (Task task : mChildren) { | ||||||
|  |             if (task.getGid().equals(gid)) | ||||||
|  |                 return task; | ||||||
|  |         } | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 获取子任务列表
 | ||||||
|  |     public ArrayList<Task> getChildTaskList() { | ||||||
|  |         return this.mChildren; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 设置索引
 | ||||||
|  |     public void setIndex(int index) { | ||||||
|  |         this.mIndex = index; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // 获取索引
 | ||||||
|  |     public int getIndex() { | ||||||
|  |         return this.mIndex; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,33 @@ | |||||||
|  | /* | ||||||
|  |  * 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.exception; | ||||||
|  | 
 | ||||||
|  | public class ActionFailureException extends RuntimeException { | ||||||
|  |     private static final long serialVersionUID = 4425249765923293627L; | ||||||
|  | 
 | ||||||
|  |     public ActionFailureException() { | ||||||
|  |         super(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public ActionFailureException(String paramString) { | ||||||
|  |         super(paramString); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public ActionFailureException(String paramString, Throwable paramThrowable) { | ||||||
|  |         super(paramString, paramThrowable); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,33 @@ | |||||||
|  | /* | ||||||
|  |  * 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.exception; | ||||||
|  | 
 | ||||||
|  | public class NetworkFailureException extends Exception { | ||||||
|  |     private static final long serialVersionUID = 2107610287180234136L; | ||||||
|  | 
 | ||||||
|  |     public NetworkFailureException() { | ||||||
|  |         super(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public NetworkFailureException(String paramString) { | ||||||
|  |         super(paramString); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public NetworkFailureException(String paramString, Throwable paramThrowable) { | ||||||
|  |         super(paramString, paramThrowable); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,123 @@ | |||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  * 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) { | ||||||
|  |         Notification notification = new Notification(R.drawable.notification, mContext | ||||||
|  |                 .getString(tickerId), System.currentTimeMillis()); | ||||||
|  |         notification.defaults = Notification.DEFAULT_LIGHTS; | ||||||
|  |         notification.flags = Notification.FLAG_AUTO_CANCEL; | ||||||
|  |         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.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, | ||||||
|  |                 pendingIntent); | ||||||
|  |         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; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // 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
 | ||||||
|  |         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) { | ||||||
|  |         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; | ||||||
|  |             HttpGet httpGet = new HttpGet(loginUrl); | ||||||
|  |             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); | ||||||
|  |     } | ||||||
|  | } | ||||||
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								
											
												
													File diff suppressed because it is too large
													Load Diff
												
											
										
									
								| @ -0,0 +1,184 @@ | |||||||
|  | /* | ||||||
|  |  * 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.database.Cursor; | ||||||
|  | import android.util.Log; | ||||||
|  | import android.view.View; | ||||||
|  | import android.view.ViewGroup; | ||||||
|  | import android.widget.CursorAdapter; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | 
 | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.Iterator; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class NotesListAdapter extends CursorAdapter { | ||||||
|  |     private static final String TAG = "NotesListAdapter"; | ||||||
|  |     private Context mContext; | ||||||
|  |     private HashMap<Integer, Boolean> mSelectedIndex; | ||||||
|  |     private int mNotesCount; | ||||||
|  |     private boolean mChoiceMode; | ||||||
|  | 
 | ||||||
|  |     public static class AppWidgetAttribute { | ||||||
|  |         public int widgetId; | ||||||
|  |         public int widgetType; | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     public NotesListAdapter(Context context) { | ||||||
|  |         super(context, null); | ||||||
|  |         mSelectedIndex = new HashMap<Integer, Boolean>(); | ||||||
|  |         mContext = context; | ||||||
|  |         mNotesCount = 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public View newView(Context context, Cursor cursor, ViewGroup parent) { | ||||||
|  |         return new NotesListItem(context); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void bindView(View view, Context context, Cursor cursor) { | ||||||
|  |         if (view instanceof NotesListItem) { | ||||||
|  |             NoteItemData itemData = new NoteItemData(context, cursor); | ||||||
|  |             ((NotesListItem) view).bind(context, itemData, mChoiceMode, | ||||||
|  |                     isSelectedItem(cursor.getPosition())); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setCheckedItem(final int position, final boolean checked) { | ||||||
|  |         mSelectedIndex.put(position, checked); | ||||||
|  |         notifyDataSetChanged(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isInChoiceMode() { | ||||||
|  |         return mChoiceMode; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setChoiceMode(boolean mode) { | ||||||
|  |         mSelectedIndex.clear(); | ||||||
|  |         mChoiceMode = mode; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void selectAll(boolean checked) { | ||||||
|  |         Cursor cursor = getCursor(); | ||||||
|  |         for (int i = 0; i < getCount(); i++) { | ||||||
|  |             if (cursor.moveToPosition(i)) { | ||||||
|  |                 if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { | ||||||
|  |                     setCheckedItem(i, checked); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public HashSet<Long> getSelectedItemIds() { | ||||||
|  |         HashSet<Long> itemSet = new HashSet<Long>(); | ||||||
|  |         for (Integer position : mSelectedIndex.keySet()) { | ||||||
|  |             if (mSelectedIndex.get(position) == true) { | ||||||
|  |                 Long id = getItemId(position); | ||||||
|  |                 if (id == Notes.ID_ROOT_FOLDER) { | ||||||
|  |                     Log.d(TAG, "Wrong item id, should not happen"); | ||||||
|  |                 } else { | ||||||
|  |                     itemSet.add(id); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return itemSet; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public HashSet<AppWidgetAttribute> getSelectedWidget() { | ||||||
|  |         HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>(); | ||||||
|  |         for (Integer position : mSelectedIndex.keySet()) { | ||||||
|  |             if (mSelectedIndex.get(position) == true) { | ||||||
|  |                 Cursor c = (Cursor) getItem(position); | ||||||
|  |                 if (c != null) { | ||||||
|  |                     AppWidgetAttribute widget = new AppWidgetAttribute(); | ||||||
|  |                     NoteItemData item = new NoteItemData(mContext, c); | ||||||
|  |                     widget.widgetId = item.getWidgetId(); | ||||||
|  |                     widget.widgetType = item.getWidgetType(); | ||||||
|  |                     itemSet.add(widget); | ||||||
|  |                     /** | ||||||
|  |                      * Don't close cursor here, only the adapter could close it | ||||||
|  |                      */ | ||||||
|  |                 } else { | ||||||
|  |                     Log.e(TAG, "Invalid cursor"); | ||||||
|  |                     return null; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return itemSet; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getSelectedCount() { | ||||||
|  |         Collection<Boolean> values = mSelectedIndex.values(); | ||||||
|  |         if (null == values) { | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  |         Iterator<Boolean> iter = values.iterator(); | ||||||
|  |         int count = 0; | ||||||
|  |         while (iter.hasNext()) { | ||||||
|  |             if (true == iter.next()) { | ||||||
|  |                 count++; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return count; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isAllSelected() { | ||||||
|  |         int checkedCount = getSelectedCount(); | ||||||
|  |         return (checkedCount != 0 && checkedCount == mNotesCount); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isSelectedItem(final int position) { | ||||||
|  |         if (null == mSelectedIndex.get(position)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         return mSelectedIndex.get(position); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onContentChanged() { | ||||||
|  |         super.onContentChanged(); | ||||||
|  |         calcNotesCount(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void changeCursor(Cursor cursor) { | ||||||
|  |         super.changeCursor(cursor); | ||||||
|  |         calcNotesCount(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void calcNotesCount() { | ||||||
|  |         mNotesCount = 0; | ||||||
|  |         for (int i = 0; i < getCount(); i++) { | ||||||
|  |             Cursor c = (Cursor) getItem(i); | ||||||
|  |             if (c != null) { | ||||||
|  |                 if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { | ||||||
|  |                     mNotesCount++; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Log.e(TAG, "Invalid cursor"); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,122 @@ | |||||||
|  | /* | ||||||
|  |  * 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.text.format.DateUtils; | ||||||
|  | import android.view.View; | ||||||
|  | import android.widget.CheckBox; | ||||||
|  | import android.widget.ImageView; | ||||||
|  | import android.widget.LinearLayout; | ||||||
|  | import android.widget.TextView; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.tool.DataUtils; | ||||||
|  | import net.micode.notes.tool.ResourceParser.NoteItemBgResources; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class NotesListItem extends LinearLayout { | ||||||
|  |     private ImageView mAlert; | ||||||
|  |     private TextView mTitle; | ||||||
|  |     private TextView mTime; | ||||||
|  |     private TextView mCallName; | ||||||
|  |     private NoteItemData mItemData; | ||||||
|  |     private CheckBox mCheckBox; | ||||||
|  | 
 | ||||||
|  |     public NotesListItem(Context context) { | ||||||
|  |         super(context); | ||||||
|  |         inflate(context, R.layout.note_item, this); | ||||||
|  |         mAlert = (ImageView) findViewById(R.id.iv_alert_icon); | ||||||
|  |         mTitle = (TextView) findViewById(R.id.tv_title); | ||||||
|  |         mTime = (TextView) findViewById(R.id.tv_time); | ||||||
|  |         mCallName = (TextView) findViewById(R.id.tv_name); | ||||||
|  |         mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { | ||||||
|  |         if (choiceMode && data.getType() == Notes.TYPE_NOTE) { | ||||||
|  |             mCheckBox.setVisibility(View.VISIBLE); | ||||||
|  |             mCheckBox.setChecked(checked); | ||||||
|  |         } else { | ||||||
|  |             mCheckBox.setVisibility(View.GONE); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         mItemData = data; | ||||||
|  |         if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { | ||||||
|  |             mCallName.setVisibility(View.GONE); | ||||||
|  |             mAlert.setVisibility(View.VISIBLE); | ||||||
|  |             mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); | ||||||
|  |             mTitle.setText(context.getString(R.string.call_record_folder_name) | ||||||
|  |                     + context.getString(R.string.format_folder_files_count, data.getNotesCount())); | ||||||
|  |             mAlert.setImageResource(R.drawable.call_record); | ||||||
|  |         } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { | ||||||
|  |             mCallName.setVisibility(View.VISIBLE); | ||||||
|  |             mCallName.setText(data.getCallName()); | ||||||
|  |             mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); | ||||||
|  |             mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); | ||||||
|  |             if (data.hasAlert()) { | ||||||
|  |                 mAlert.setImageResource(R.drawable.clock); | ||||||
|  |                 mAlert.setVisibility(View.VISIBLE); | ||||||
|  |             } else { | ||||||
|  |                 mAlert.setVisibility(View.GONE); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             mCallName.setVisibility(View.GONE); | ||||||
|  |             mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); | ||||||
|  | 
 | ||||||
|  |             if (data.getType() == Notes.TYPE_FOLDER) { | ||||||
|  |                 mTitle.setText(data.getSnippet() | ||||||
|  |                         + context.getString(R.string.format_folder_files_count, | ||||||
|  |                                 data.getNotesCount())); | ||||||
|  |                 mAlert.setVisibility(View.GONE); | ||||||
|  |             } else { | ||||||
|  |                 mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); | ||||||
|  |                 if (data.hasAlert()) { | ||||||
|  |                     mAlert.setImageResource(R.drawable.clock); | ||||||
|  |                     mAlert.setVisibility(View.VISIBLE); | ||||||
|  |                 } else { | ||||||
|  |                     mAlert.setVisibility(View.GONE); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); | ||||||
|  | 
 | ||||||
|  |         setBackground(data); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void setBackground(NoteItemData data) { | ||||||
|  |         int id = data.getBgColorId(); | ||||||
|  |         if (data.getType() == Notes.TYPE_NOTE) { | ||||||
|  |             if (data.isSingle() || data.isOneFollowingFolder()) { | ||||||
|  |                 setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); | ||||||
|  |             } else if (data.isLast()) { | ||||||
|  |                 setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); | ||||||
|  |             } else if (data.isFirst() || data.isMultiFollowingFolder()) { | ||||||
|  |                 setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); | ||||||
|  |             } else { | ||||||
|  |                 setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             setBackgroundResource(NoteItemBgResources.getFolderBgRes()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public NoteItemData getItemData() { | ||||||
|  |         return mItemData; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,388 @@ | |||||||
|  | /* | ||||||
|  |  * 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.accounts.Account; | ||||||
|  | import android.accounts.AccountManager; | ||||||
|  | import android.app.ActionBar; | ||||||
|  | import android.app.AlertDialog; | ||||||
|  | import android.content.BroadcastReceiver; | ||||||
|  | import android.content.ContentValues; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.DialogInterface; | ||||||
|  | import android.content.Intent; | ||||||
|  | import android.content.IntentFilter; | ||||||
|  | import android.content.SharedPreferences; | ||||||
|  | import android.os.Bundle; | ||||||
|  | import android.preference.Preference; | ||||||
|  | import android.preference.Preference.OnPreferenceClickListener; | ||||||
|  | import android.preference.PreferenceActivity; | ||||||
|  | import android.preference.PreferenceCategory; | ||||||
|  | import android.text.TextUtils; | ||||||
|  | import android.text.format.DateFormat; | ||||||
|  | import android.view.LayoutInflater; | ||||||
|  | import android.view.Menu; | ||||||
|  | import android.view.MenuItem; | ||||||
|  | import android.view.View; | ||||||
|  | import android.widget.Button; | ||||||
|  | import android.widget.TextView; | ||||||
|  | import android.widget.Toast; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.data.Notes.NoteColumns; | ||||||
|  | import net.micode.notes.gtask.remote.GTaskSyncService; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class NotesPreferenceActivity extends PreferenceActivity { | ||||||
|  |     public static final String PREFERENCE_NAME = "notes_preferences"; | ||||||
|  | 
 | ||||||
|  |     public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; | ||||||
|  | 
 | ||||||
|  |     public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; | ||||||
|  | 
 | ||||||
|  |     public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; | ||||||
|  | 
 | ||||||
|  |     private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; | ||||||
|  | 
 | ||||||
|  |     private static final String AUTHORITIES_FILTER_KEY = "authorities"; | ||||||
|  | 
 | ||||||
|  |     private PreferenceCategory mAccountCategory; | ||||||
|  | 
 | ||||||
|  |     private GTaskReceiver mReceiver; | ||||||
|  | 
 | ||||||
|  |     private Account[] mOriAccounts; | ||||||
|  | 
 | ||||||
|  |     private boolean mHasAddedAccount; | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onCreate(Bundle icicle) { | ||||||
|  |         super.onCreate(icicle); | ||||||
|  | 
 | ||||||
|  |         /* using the app icon for navigation */ | ||||||
|  |         getActionBar().setDisplayHomeAsUpEnabled(true); | ||||||
|  | 
 | ||||||
|  |         addPreferencesFromResource(R.xml.preferences); | ||||||
|  |         mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); | ||||||
|  |         mReceiver = new GTaskReceiver(); | ||||||
|  |         IntentFilter filter = new IntentFilter(); | ||||||
|  |         filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); | ||||||
|  |         registerReceiver(mReceiver, filter); | ||||||
|  | 
 | ||||||
|  |         mOriAccounts = null; | ||||||
|  |         View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); | ||||||
|  |         getListView().addHeaderView(header, null, true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onResume() { | ||||||
|  |         super.onResume(); | ||||||
|  | 
 | ||||||
|  |         // need to set sync account automatically if user has added a new
 | ||||||
|  |         // account
 | ||||||
|  |         if (mHasAddedAccount) { | ||||||
|  |             Account[] accounts = getGoogleAccounts(); | ||||||
|  |             if (mOriAccounts != null && accounts.length > mOriAccounts.length) { | ||||||
|  |                 for (Account accountNew : accounts) { | ||||||
|  |                     boolean found = false; | ||||||
|  |                     for (Account accountOld : mOriAccounts) { | ||||||
|  |                         if (TextUtils.equals(accountOld.name, accountNew.name)) { | ||||||
|  |                             found = true; | ||||||
|  |                             break; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     if (!found) { | ||||||
|  |                         setSyncAccount(accountNew.name); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         refreshUI(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onDestroy() { | ||||||
|  |         if (mReceiver != null) { | ||||||
|  |             unregisterReceiver(mReceiver); | ||||||
|  |         } | ||||||
|  |         super.onDestroy(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void loadAccountPreference() { | ||||||
|  |         mAccountCategory.removeAll(); | ||||||
|  | 
 | ||||||
|  |         Preference accountPref = new Preference(this); | ||||||
|  |         final String defaultAccount = getSyncAccountName(this); | ||||||
|  |         accountPref.setTitle(getString(R.string.preferences_account_title)); | ||||||
|  |         accountPref.setSummary(getString(R.string.preferences_account_summary)); | ||||||
|  |         accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { | ||||||
|  |             public boolean onPreferenceClick(Preference preference) { | ||||||
|  |                 if (!GTaskSyncService.isSyncing()) { | ||||||
|  |                     if (TextUtils.isEmpty(defaultAccount)) { | ||||||
|  |                         // the first time to set account
 | ||||||
|  |                         showSelectAccountAlertDialog(); | ||||||
|  |                     } else { | ||||||
|  |                         // if the account has already been set, we need to promp
 | ||||||
|  |                         // user about the risk
 | ||||||
|  |                         showChangeAccountConfirmAlertDialog(); | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     Toast.makeText(NotesPreferenceActivity.this, | ||||||
|  |                             R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) | ||||||
|  |                             .show(); | ||||||
|  |                 } | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         mAccountCategory.addPreference(accountPref); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void loadSyncButton() { | ||||||
|  |         Button syncButton = (Button) findViewById(R.id.preference_sync_button); | ||||||
|  |         TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); | ||||||
|  | 
 | ||||||
|  |         // set button state
 | ||||||
|  |         if (GTaskSyncService.isSyncing()) { | ||||||
|  |             syncButton.setText(getString(R.string.preferences_button_sync_cancel)); | ||||||
|  |             syncButton.setOnClickListener(new View.OnClickListener() { | ||||||
|  |                 public void onClick(View v) { | ||||||
|  |                     GTaskSyncService.cancelSync(NotesPreferenceActivity.this); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } else { | ||||||
|  |             syncButton.setText(getString(R.string.preferences_button_sync_immediately)); | ||||||
|  |             syncButton.setOnClickListener(new View.OnClickListener() { | ||||||
|  |                 public void onClick(View v) { | ||||||
|  |                     GTaskSyncService.startSync(NotesPreferenceActivity.this); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |         syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); | ||||||
|  | 
 | ||||||
|  |         // set last sync time
 | ||||||
|  |         if (GTaskSyncService.isSyncing()) { | ||||||
|  |             lastSyncTimeView.setText(GTaskSyncService.getProgressString()); | ||||||
|  |             lastSyncTimeView.setVisibility(View.VISIBLE); | ||||||
|  |         } else { | ||||||
|  |             long lastSyncTime = getLastSyncTime(this); | ||||||
|  |             if (lastSyncTime != 0) { | ||||||
|  |                 lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time, | ||||||
|  |                         DateFormat.format(getString(R.string.preferences_last_sync_time_format), | ||||||
|  |                                 lastSyncTime))); | ||||||
|  |                 lastSyncTimeView.setVisibility(View.VISIBLE); | ||||||
|  |             } else { | ||||||
|  |                 lastSyncTimeView.setVisibility(View.GONE); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void refreshUI() { | ||||||
|  |         loadAccountPreference(); | ||||||
|  |         loadSyncButton(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void showSelectAccountAlertDialog() { | ||||||
|  |         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); | ||||||
|  | 
 | ||||||
|  |         View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); | ||||||
|  |         TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); | ||||||
|  |         titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); | ||||||
|  |         TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); | ||||||
|  |         subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); | ||||||
|  | 
 | ||||||
|  |         dialogBuilder.setCustomTitle(titleView); | ||||||
|  |         dialogBuilder.setPositiveButton(null, null); | ||||||
|  | 
 | ||||||
|  |         Account[] accounts = getGoogleAccounts(); | ||||||
|  |         String defAccount = getSyncAccountName(this); | ||||||
|  | 
 | ||||||
|  |         mOriAccounts = accounts; | ||||||
|  |         mHasAddedAccount = false; | ||||||
|  | 
 | ||||||
|  |         if (accounts.length > 0) { | ||||||
|  |             CharSequence[] items = new CharSequence[accounts.length]; | ||||||
|  |             final CharSequence[] itemMapping = items; | ||||||
|  |             int checkedItem = -1; | ||||||
|  |             int index = 0; | ||||||
|  |             for (Account account : accounts) { | ||||||
|  |                 if (TextUtils.equals(account.name, defAccount)) { | ||||||
|  |                     checkedItem = index; | ||||||
|  |                 } | ||||||
|  |                 items[index++] = account.name; | ||||||
|  |             } | ||||||
|  |             dialogBuilder.setSingleChoiceItems(items, checkedItem, | ||||||
|  |                     new DialogInterface.OnClickListener() { | ||||||
|  |                         public void onClick(DialogInterface dialog, int which) { | ||||||
|  |                             setSyncAccount(itemMapping[which].toString()); | ||||||
|  |                             dialog.dismiss(); | ||||||
|  |                             refreshUI(); | ||||||
|  |                         } | ||||||
|  |                     }); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); | ||||||
|  |         dialogBuilder.setView(addAccountView); | ||||||
|  | 
 | ||||||
|  |         final AlertDialog dialog = dialogBuilder.show(); | ||||||
|  |         addAccountView.setOnClickListener(new View.OnClickListener() { | ||||||
|  |             public void onClick(View v) { | ||||||
|  |                 mHasAddedAccount = true; | ||||||
|  |                 Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); | ||||||
|  |                 intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { | ||||||
|  |                     "gmail-ls" | ||||||
|  |                 }); | ||||||
|  |                 startActivityForResult(intent, -1); | ||||||
|  |                 dialog.dismiss(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void showChangeAccountConfirmAlertDialog() { | ||||||
|  |         AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); | ||||||
|  | 
 | ||||||
|  |         View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); | ||||||
|  |         TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); | ||||||
|  |         titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, | ||||||
|  |                 getSyncAccountName(this))); | ||||||
|  |         TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); | ||||||
|  |         subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); | ||||||
|  |         dialogBuilder.setCustomTitle(titleView); | ||||||
|  | 
 | ||||||
|  |         CharSequence[] menuItemArray = new CharSequence[] { | ||||||
|  |                 getString(R.string.preferences_menu_change_account), | ||||||
|  |                 getString(R.string.preferences_menu_remove_account), | ||||||
|  |                 getString(R.string.preferences_menu_cancel) | ||||||
|  |         }; | ||||||
|  |         dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { | ||||||
|  |             public void onClick(DialogInterface dialog, int which) { | ||||||
|  |                 if (which == 0) { | ||||||
|  |                     showSelectAccountAlertDialog(); | ||||||
|  |                 } else if (which == 1) { | ||||||
|  |                     removeSyncAccount(); | ||||||
|  |                     refreshUI(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         dialogBuilder.show(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Account[] getGoogleAccounts() { | ||||||
|  |         AccountManager accountManager = AccountManager.get(this); | ||||||
|  |         return accountManager.getAccountsByType("com.google"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void setSyncAccount(String account) { | ||||||
|  |         if (!getSyncAccountName(this).equals(account)) { | ||||||
|  |             SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); | ||||||
|  |             SharedPreferences.Editor editor = settings.edit(); | ||||||
|  |             if (account != null) { | ||||||
|  |                 editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); | ||||||
|  |             } else { | ||||||
|  |                 editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); | ||||||
|  |             } | ||||||
|  |             editor.commit(); | ||||||
|  | 
 | ||||||
|  |             // clean up last sync time
 | ||||||
|  |             setLastSyncTime(this, 0); | ||||||
|  | 
 | ||||||
|  |             // clean up local gtask related info
 | ||||||
|  |             new Thread(new Runnable() { | ||||||
|  |                 public void run() { | ||||||
|  |                     ContentValues values = new ContentValues(); | ||||||
|  |                     values.put(NoteColumns.GTASK_ID, ""); | ||||||
|  |                     values.put(NoteColumns.SYNC_ID, 0); | ||||||
|  |                     getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); | ||||||
|  |                 } | ||||||
|  |             }).start(); | ||||||
|  | 
 | ||||||
|  |             Toast.makeText(NotesPreferenceActivity.this, | ||||||
|  |                     getString(R.string.preferences_toast_success_set_accout, account), | ||||||
|  |                     Toast.LENGTH_SHORT).show(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void removeSyncAccount() { | ||||||
|  |         SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); | ||||||
|  |         SharedPreferences.Editor editor = settings.edit(); | ||||||
|  |         if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { | ||||||
|  |             editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); | ||||||
|  |         } | ||||||
|  |         if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) { | ||||||
|  |             editor.remove(PREFERENCE_LAST_SYNC_TIME); | ||||||
|  |         } | ||||||
|  |         editor.commit(); | ||||||
|  | 
 | ||||||
|  |         // clean up local gtask related info
 | ||||||
|  |         new Thread(new Runnable() { | ||||||
|  |             public void run() { | ||||||
|  |                 ContentValues values = new ContentValues(); | ||||||
|  |                 values.put(NoteColumns.GTASK_ID, ""); | ||||||
|  |                 values.put(NoteColumns.SYNC_ID, 0); | ||||||
|  |                 getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); | ||||||
|  |             } | ||||||
|  |         }).start(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static String getSyncAccountName(Context context) { | ||||||
|  |         SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, | ||||||
|  |                 Context.MODE_PRIVATE); | ||||||
|  |         return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static void setLastSyncTime(Context context, long time) { | ||||||
|  |         SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, | ||||||
|  |                 Context.MODE_PRIVATE); | ||||||
|  |         SharedPreferences.Editor editor = settings.edit(); | ||||||
|  |         editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); | ||||||
|  |         editor.commit(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static long getLastSyncTime(Context context) { | ||||||
|  |         SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, | ||||||
|  |                 Context.MODE_PRIVATE); | ||||||
|  |         return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private class GTaskReceiver extends BroadcastReceiver { | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public void onReceive(Context context, Intent intent) { | ||||||
|  |             refreshUI(); | ||||||
|  |             if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { | ||||||
|  |                 TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); | ||||||
|  |                 syncStatus.setText(intent | ||||||
|  |                         .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean onOptionsItemSelected(MenuItem item) { | ||||||
|  |         switch (item.getItemId()) { | ||||||
|  |             case android.R.id.home: | ||||||
|  |                 Intent intent = new Intent(this, NotesListActivity.class); | ||||||
|  |                 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); | ||||||
|  |                 startActivity(intent); | ||||||
|  |                 return true; | ||||||
|  |             default: | ||||||
|  |                 return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
					Loading…
					
					
				
		Reference in new issue