Merge branch 'main' of https://bdgit.educoder.net/p87v4biok/git-test into SU
	
		
	
				
					
				
			# Conflicts: # 苏韬文.txtSU
						commit
						d3f43c2d3d
					
				| @ -0,0 +1,73 @@ | ||||
| /* | ||||
|  * 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.content.Context; | ||||
| import android.database.Cursor; | ||||
| import android.provider.ContactsContract.CommonDataKinds.Phone; | ||||
| import android.provider.ContactsContract.Data; | ||||
| import android.telephony.PhoneNumberUtils; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import java.util.HashMap; | ||||
| 
 | ||||
| public class Contact { | ||||
|     private static HashMap<String, String> sContactCache; | ||||
|     private static final String TAG = "Contact"; | ||||
| 
 | ||||
|     private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER | ||||
|     + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" | ||||
|     + " AND " + Data.RAW_CONTACT_ID + " IN " | ||||
|             + "(SELECT raw_contact_id " | ||||
|             + " FROM phone_lookup" | ||||
|             + " WHERE min_match = '+')"; | ||||
| 
 | ||||
|     public static String getContact(Context context, String phoneNumber) { | ||||
|         if(sContactCache == null) { | ||||
|             sContactCache = new HashMap<String, String>(); | ||||
|         } | ||||
| 
 | ||||
|         if(sContactCache.containsKey(phoneNumber)) { | ||||
|             return sContactCache.get(phoneNumber); | ||||
|         } | ||||
| 
 | ||||
|         String selection = CALLER_ID_SELECTION.replace("+", | ||||
|                 PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); | ||||
|         Cursor cursor = context.getContentResolver().query( | ||||
|                 Data.CONTENT_URI, | ||||
|                 new String [] { Phone.DISPLAY_NAME }, | ||||
|                 selection, | ||||
|                 new String[] { phoneNumber }, | ||||
|                 null); | ||||
| 
 | ||||
|         if (cursor != null && cursor.moveToFirst()) { | ||||
|             try { | ||||
|                 String name = cursor.getString(0); | ||||
|                 sContactCache.put(phoneNumber, name); | ||||
|                 return name; | ||||
|             } catch (IndexOutOfBoundsException e) { | ||||
|                 Log.e(TAG, " Cursor get string error " + e.toString()); | ||||
|                 return null; | ||||
|             } finally { | ||||
|                 cursor.close(); | ||||
|             } | ||||
|         } else { | ||||
|             Log.d(TAG, "No contact matched with number:" + phoneNumber); | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,279 @@ | ||||
| /* | ||||
|  * 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 { | ||||
|     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; | ||||
| 
 | ||||
|     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"; | ||||
| 
 | ||||
|     public static final int TYPE_WIDGET_INVALIDE      = -1; | ||||
|     public static final int TYPE_WIDGET_2X            = 0; | ||||
|     public static final int TYPE_WIDGET_4X            = 1; | ||||
| 
 | ||||
|     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"); | ||||
| 
 | ||||
|     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"; | ||||
|     } | ||||
| 
 | ||||
|     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"; | ||||
|     } | ||||
| 
 | ||||
|     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"); | ||||
|     } | ||||
| 
 | ||||
|     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,362 @@ | ||||
| /* | ||||
|  * 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.content.ContentValues; | ||||
| import android.content.Context; | ||||
| import android.database.sqlite.SQLiteDatabase; | ||||
| import android.database.sqlite.SQLiteOpenHelper; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import net.micode.notes.data.Notes.DataColumns; | ||||
| import net.micode.notes.data.Notes.DataConstants; | ||||
| import net.micode.notes.data.Notes.NoteColumns; | ||||
| 
 | ||||
| 
 | ||||
| public class NotesDatabaseHelper extends SQLiteOpenHelper { | ||||
|     private static final String DB_NAME = "note.db"; | ||||
| 
 | ||||
|     private static final int DB_VERSION = 4; | ||||
| 
 | ||||
|     public interface TABLE { | ||||
|         public static final String NOTE = "note"; | ||||
| 
 | ||||
|         public static final String DATA = "data"; | ||||
|     } | ||||
| 
 | ||||
|     private static final String TAG = "NotesDatabaseHelper"; | ||||
| 
 | ||||
|     private static NotesDatabaseHelper mInstance; | ||||
| 
 | ||||
|     private static final String CREATE_NOTE_TABLE_SQL = | ||||
|         "CREATE TABLE " + TABLE.NOTE + "(" + | ||||
|             NoteColumns.ID + " INTEGER PRIMARY KEY," + | ||||
|             NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + | ||||
|             NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + | ||||
|             NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + | ||||
|             NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + | ||||
|             NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + | ||||
|             NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + | ||||
|             NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + | ||||
|             NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + | ||||
|             NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + | ||||
|             NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + | ||||
|             NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + | ||||
|             NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + | ||||
|             NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + | ||||
|             NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + | ||||
|             NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + | ||||
|             NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + | ||||
|         ")"; | ||||
| 
 | ||||
|     private static final String CREATE_DATA_TABLE_SQL = | ||||
|         "CREATE TABLE " + TABLE.DATA + "(" + | ||||
|             DataColumns.ID + " INTEGER PRIMARY KEY," + | ||||
|             DataColumns.MIME_TYPE + " TEXT NOT NULL," + | ||||
|             DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + | ||||
|             NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + | ||||
|             NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + | ||||
|             DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + | ||||
|             DataColumns.DATA1 + " INTEGER," + | ||||
|             DataColumns.DATA2 + " INTEGER," + | ||||
|             DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + | ||||
|             DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + | ||||
|             DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + | ||||
|         ")"; | ||||
| 
 | ||||
|     private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = | ||||
|         "CREATE INDEX IF NOT EXISTS note_id_index ON " + | ||||
|         TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; | ||||
| 
 | ||||
|     /** | ||||
|      * Increase folder's note count when move note to the folder | ||||
|      */ | ||||
|     private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = | ||||
|         "CREATE TRIGGER increase_folder_count_on_update "+ | ||||
|         " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + | ||||
|         " BEGIN " + | ||||
|         "  UPDATE " + TABLE.NOTE + | ||||
|         "   SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + | ||||
|         "  WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + | ||||
|         " END"; | ||||
| 
 | ||||
|     /** | ||||
|      * Decrease folder's note count when move note from folder | ||||
|      */ | ||||
|     private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = | ||||
|         "CREATE TRIGGER decrease_folder_count_on_update " + | ||||
|         " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + | ||||
|         " BEGIN " + | ||||
|         "  UPDATE " + TABLE.NOTE + | ||||
|         "   SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + | ||||
|         "  WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + | ||||
|         "  AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + | ||||
|         " END"; | ||||
| 
 | ||||
|     /** | ||||
|      * Increase folder's note count when insert new note to the folder | ||||
|      */ | ||||
|     private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = | ||||
|         "CREATE TRIGGER increase_folder_count_on_insert " + | ||||
|         " AFTER INSERT ON " + TABLE.NOTE + | ||||
|         " BEGIN " + | ||||
|         "  UPDATE " + TABLE.NOTE + | ||||
|         "   SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + | ||||
|         "  WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + | ||||
|         " END"; | ||||
| 
 | ||||
|     /** | ||||
|      * Decrease folder's note count when delete note from the folder | ||||
|      */ | ||||
|     private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = | ||||
|         "CREATE TRIGGER decrease_folder_count_on_delete " + | ||||
|         " AFTER DELETE ON " + TABLE.NOTE + | ||||
|         " BEGIN " + | ||||
|         "  UPDATE " + TABLE.NOTE + | ||||
|         "   SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + | ||||
|         "  WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + | ||||
|         "  AND " + NoteColumns.NOTES_COUNT + ">0;" + | ||||
|         " END"; | ||||
| 
 | ||||
|     /** | ||||
|      * Update note's content when insert data with type {@link DataConstants#NOTE} | ||||
|      */ | ||||
|     private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = | ||||
|         "CREATE TRIGGER update_note_content_on_insert " + | ||||
|         " AFTER INSERT ON " + TABLE.DATA + | ||||
|         " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + | ||||
|         " BEGIN" + | ||||
|         "  UPDATE " + TABLE.NOTE + | ||||
|         "   SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + | ||||
|         "  WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + | ||||
|         " END"; | ||||
| 
 | ||||
|     /** | ||||
|      * Update note's content when data with {@link DataConstants#NOTE} type has changed | ||||
|      */ | ||||
|     private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = | ||||
|         "CREATE TRIGGER update_note_content_on_update " + | ||||
|         " AFTER UPDATE ON " + TABLE.DATA + | ||||
|         " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + | ||||
|         " BEGIN" + | ||||
|         "  UPDATE " + TABLE.NOTE + | ||||
|         "   SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + | ||||
|         "  WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + | ||||
|         " END"; | ||||
| 
 | ||||
|     /** | ||||
|      * Update note's content when data with {@link DataConstants#NOTE} type has deleted | ||||
|      */ | ||||
|     private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = | ||||
|         "CREATE TRIGGER update_note_content_on_delete " + | ||||
|         " AFTER delete ON " + TABLE.DATA + | ||||
|         " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + | ||||
|         " BEGIN" + | ||||
|         "  UPDATE " + TABLE.NOTE + | ||||
|         "   SET " + NoteColumns.SNIPPET + "=''" + | ||||
|         "  WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + | ||||
|         " END"; | ||||
| 
 | ||||
|     /** | ||||
|      * Delete datas belong to note which has been deleted | ||||
|      */ | ||||
|     private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = | ||||
|         "CREATE TRIGGER delete_data_on_delete " + | ||||
|         " AFTER DELETE ON " + TABLE.NOTE + | ||||
|         " BEGIN" + | ||||
|         "  DELETE FROM " + TABLE.DATA + | ||||
|         "   WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + | ||||
|         " END"; | ||||
| 
 | ||||
|     /** | ||||
|      * Delete notes belong to folder which has been deleted | ||||
|      */ | ||||
|     private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = | ||||
|         "CREATE TRIGGER folder_delete_notes_on_delete " + | ||||
|         " AFTER DELETE ON " + TABLE.NOTE + | ||||
|         " BEGIN" + | ||||
|         "  DELETE FROM " + TABLE.NOTE + | ||||
|         "   WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + | ||||
|         " END"; | ||||
| 
 | ||||
|     /** | ||||
|      * Move notes belong to folder which has been moved to trash folder | ||||
|      */ | ||||
|     private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = | ||||
|         "CREATE TRIGGER folder_move_notes_on_trash " + | ||||
|         " AFTER UPDATE ON " + TABLE.NOTE + | ||||
|         " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + | ||||
|         " BEGIN" + | ||||
|         "  UPDATE " + TABLE.NOTE + | ||||
|         "   SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + | ||||
|         "  WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + | ||||
|         " END"; | ||||
| 
 | ||||
|     public NotesDatabaseHelper(Context context) { | ||||
|         super(context, DB_NAME, null, DB_VERSION); | ||||
|     } | ||||
| 
 | ||||
|     public void createNoteTable(SQLiteDatabase db) { | ||||
|         db.execSQL(CREATE_NOTE_TABLE_SQL); | ||||
|         reCreateNoteTableTriggers(db); | ||||
|         createSystemFolder(db); | ||||
|         Log.d(TAG, "note table has been created"); | ||||
|     } | ||||
| 
 | ||||
|     private void reCreateNoteTableTriggers(SQLiteDatabase db) { | ||||
|         db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); | ||||
|         db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); | ||||
|         db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); | ||||
|         db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete"); | ||||
|         db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); | ||||
|         db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); | ||||
|         db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); | ||||
| 
 | ||||
|         db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); | ||||
|         db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); | ||||
|         db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); | ||||
|         db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); | ||||
|         db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); | ||||
|         db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); | ||||
|         db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); | ||||
|     } | ||||
| 
 | ||||
|     private void createSystemFolder(SQLiteDatabase db) { | ||||
|         ContentValues values = new ContentValues(); | ||||
| 
 | ||||
|         /** | ||||
|          * call record foler for call notes | ||||
|          */ | ||||
|         values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); | ||||
|         values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); | ||||
|         db.insert(TABLE.NOTE, null, values); | ||||
| 
 | ||||
|         /** | ||||
|          * root folder which is default folder | ||||
|          */ | ||||
|         values.clear(); | ||||
|         values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); | ||||
|         values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); | ||||
|         db.insert(TABLE.NOTE, null, values); | ||||
| 
 | ||||
|         /** | ||||
|          * temporary folder which is used for moving note | ||||
|          */ | ||||
|         values.clear(); | ||||
|         values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); | ||||
|         values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); | ||||
|         db.insert(TABLE.NOTE, null, values); | ||||
| 
 | ||||
|         /** | ||||
|          * create trash folder | ||||
|          */ | ||||
|         values.clear(); | ||||
|         values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); | ||||
|         values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); | ||||
|         db.insert(TABLE.NOTE, null, values); | ||||
|     } | ||||
| 
 | ||||
|     public void createDataTable(SQLiteDatabase db) { | ||||
|         db.execSQL(CREATE_DATA_TABLE_SQL); | ||||
|         reCreateDataTableTriggers(db); | ||||
|         db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); | ||||
|         Log.d(TAG, "data table has been created"); | ||||
|     } | ||||
| 
 | ||||
|     private void reCreateDataTableTriggers(SQLiteDatabase db) { | ||||
|         db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); | ||||
|         db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); | ||||
|         db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete"); | ||||
| 
 | ||||
|         db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); | ||||
|         db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); | ||||
|         db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); | ||||
|     } | ||||
| 
 | ||||
|     static synchronized NotesDatabaseHelper getInstance(Context context) { | ||||
|         if (mInstance == null) { | ||||
|             mInstance = new NotesDatabaseHelper(context); | ||||
|         } | ||||
|         return mInstance; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onCreate(SQLiteDatabase db) { | ||||
|         createNoteTable(db); | ||||
|         createDataTable(db); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { | ||||
|         boolean reCreateTriggers = false; | ||||
|         boolean skipV2 = false; | ||||
| 
 | ||||
|         if (oldVersion == 1) { | ||||
|             upgradeToV2(db); | ||||
|             skipV2 = true; // this upgrade including the upgrade from v2 to v3
 | ||||
|             oldVersion++; | ||||
|         } | ||||
| 
 | ||||
|         if (oldVersion == 2 && !skipV2) { | ||||
|             upgradeToV3(db); | ||||
|             reCreateTriggers = true; | ||||
|             oldVersion++; | ||||
|         } | ||||
| 
 | ||||
|         if (oldVersion == 3) { | ||||
|             upgradeToV4(db); | ||||
|             oldVersion++; | ||||
|         } | ||||
| 
 | ||||
|         if (reCreateTriggers) { | ||||
|             reCreateNoteTableTriggers(db); | ||||
|             reCreateDataTableTriggers(db); | ||||
|         } | ||||
| 
 | ||||
|         if (oldVersion != newVersion) { | ||||
|             throw new IllegalStateException("Upgrade notes database to version " + newVersion | ||||
|                     + "fails"); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void upgradeToV2(SQLiteDatabase db) { | ||||
|         db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); | ||||
|         db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); | ||||
|         createNoteTable(db); | ||||
|         createDataTable(db); | ||||
|     } | ||||
| 
 | ||||
|     private void upgradeToV3(SQLiteDatabase db) { | ||||
|         // drop unused triggers
 | ||||
|         db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); | ||||
|         db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); | ||||
|         db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); | ||||
|         // add a column for gtask id
 | ||||
|         db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID | ||||
|                 + " TEXT NOT NULL DEFAULT ''"); | ||||
|         // add a trash system folder
 | ||||
|         ContentValues values = new ContentValues(); | ||||
|         values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); | ||||
|         values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); | ||||
|         db.insert(TABLE.NOTE, null, values); | ||||
|     } | ||||
| 
 | ||||
|     private void upgradeToV4(SQLiteDatabase db) { | ||||
|         db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION | ||||
|                 + " INTEGER NOT NULL DEFAULT 0"); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,305 @@ | ||||
| /* | ||||
|  * 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.app.SearchManager; | ||||
| import android.content.ContentProvider; | ||||
| import android.content.ContentUris; | ||||
| import android.content.ContentValues; | ||||
| import android.content.Intent; | ||||
| import android.content.UriMatcher; | ||||
| import android.database.Cursor; | ||||
| import android.database.sqlite.SQLiteDatabase; | ||||
| import android.net.Uri; | ||||
| import android.text.TextUtils; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import net.micode.notes.R; | ||||
| import net.micode.notes.data.Notes.DataColumns; | ||||
| import net.micode.notes.data.Notes.NoteColumns; | ||||
| import net.micode.notes.data.NotesDatabaseHelper.TABLE; | ||||
| 
 | ||||
| 
 | ||||
| public class NotesProvider extends ContentProvider { | ||||
|     private static final UriMatcher mMatcher; | ||||
| 
 | ||||
|     private NotesDatabaseHelper mHelper; | ||||
| 
 | ||||
|     private static final String TAG = "NotesProvider"; | ||||
| 
 | ||||
|     private static final int URI_NOTE            = 1; | ||||
|     private static final int URI_NOTE_ITEM       = 2; | ||||
|     private static final int URI_DATA            = 3; | ||||
|     private static final int URI_DATA_ITEM       = 4; | ||||
| 
 | ||||
|     private static final int URI_SEARCH          = 5; | ||||
|     private static final int URI_SEARCH_SUGGEST  = 6; | ||||
| 
 | ||||
|     static { | ||||
|         mMatcher = new UriMatcher(UriMatcher.NO_MATCH); | ||||
|         mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); | ||||
|         mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); | ||||
|         mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); | ||||
|         mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); | ||||
|         mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); | ||||
|         mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); | ||||
|         mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * x'0A' represents the '\n' character in sqlite. For title and content in the search result, | ||||
|      * we will trim '\n' and white space in order to show more information. | ||||
|      */ | ||||
|     private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," | ||||
|         + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," | ||||
|         + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," | ||||
|         + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," | ||||
|         + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," | ||||
|         + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," | ||||
|         + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; | ||||
| 
 | ||||
|     private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION | ||||
|         + " FROM " + TABLE.NOTE | ||||
|         + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" | ||||
|         + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER | ||||
|         + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean onCreate() { | ||||
|         mHelper = NotesDatabaseHelper.getInstance(getContext()); | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, | ||||
|             String sortOrder) { | ||||
|         Cursor c = null; | ||||
|         SQLiteDatabase db = mHelper.getReadableDatabase(); | ||||
|         String id = null; | ||||
|         switch (mMatcher.match(uri)) { | ||||
|             case URI_NOTE: | ||||
|                 c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, | ||||
|                         sortOrder); | ||||
|                 break; | ||||
|             case URI_NOTE_ITEM: | ||||
|                 id = uri.getPathSegments().get(1); | ||||
|                 c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id | ||||
|                         + parseSelection(selection), selectionArgs, null, null, sortOrder); | ||||
|                 break; | ||||
|             case URI_DATA: | ||||
|                 c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, | ||||
|                         sortOrder); | ||||
|                 break; | ||||
|             case URI_DATA_ITEM: | ||||
|                 id = uri.getPathSegments().get(1); | ||||
|                 c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id | ||||
|                         + parseSelection(selection), selectionArgs, null, null, sortOrder); | ||||
|                 break; | ||||
|             case URI_SEARCH: | ||||
|             case URI_SEARCH_SUGGEST: | ||||
|                 if (sortOrder != null || projection != null) { | ||||
|                     throw new IllegalArgumentException( | ||||
|                             "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); | ||||
|                 } | ||||
| 
 | ||||
|                 String searchString = null; | ||||
|                 if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { | ||||
|                     if (uri.getPathSegments().size() > 1) { | ||||
|                         searchString = uri.getPathSegments().get(1); | ||||
|                     } | ||||
|                 } else { | ||||
|                     searchString = uri.getQueryParameter("pattern"); | ||||
|                 } | ||||
| 
 | ||||
|                 if (TextUtils.isEmpty(searchString)) { | ||||
|                     return null; | ||||
|                 } | ||||
| 
 | ||||
|                 try { | ||||
|                     searchString = String.format("%%%s%%", searchString); | ||||
|                     c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, | ||||
|                             new String[] { searchString }); | ||||
|                 } catch (IllegalStateException ex) { | ||||
|                     Log.e(TAG, "got exception: " + ex.toString()); | ||||
|                 } | ||||
|                 break; | ||||
|             default: | ||||
|                 throw new IllegalArgumentException("Unknown URI " + uri); | ||||
|         } | ||||
|         if (c != null) { | ||||
|             c.setNotificationUri(getContext().getContentResolver(), uri); | ||||
|         } | ||||
|         return c; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public Uri insert(Uri uri, ContentValues values) { | ||||
|         SQLiteDatabase db = mHelper.getWritableDatabase(); | ||||
|         long dataId = 0, noteId = 0, insertedId = 0; | ||||
|         switch (mMatcher.match(uri)) { | ||||
|             case URI_NOTE: | ||||
|                 insertedId = noteId = db.insert(TABLE.NOTE, null, values); | ||||
|                 break; | ||||
|             case URI_DATA: | ||||
|                 if (values.containsKey(DataColumns.NOTE_ID)) { | ||||
|                     noteId = values.getAsLong(DataColumns.NOTE_ID); | ||||
|                 } else { | ||||
|                     Log.d(TAG, "Wrong data format without note id:" + values.toString()); | ||||
|                 } | ||||
|                 insertedId = dataId = db.insert(TABLE.DATA, null, values); | ||||
|                 break; | ||||
|             default: | ||||
|                 throw new IllegalArgumentException("Unknown URI " + uri); | ||||
|         } | ||||
|         // Notify the note uri
 | ||||
|         if (noteId > 0) { | ||||
|             getContext().getContentResolver().notifyChange( | ||||
|                     ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); | ||||
|         } | ||||
| 
 | ||||
|         // Notify the data uri
 | ||||
|         if (dataId > 0) { | ||||
|             getContext().getContentResolver().notifyChange( | ||||
|                     ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); | ||||
|         } | ||||
| 
 | ||||
|         return ContentUris.withAppendedId(uri, insertedId); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int delete(Uri uri, String selection, String[] selectionArgs) { | ||||
|         int count = 0; | ||||
|         String id = null; | ||||
|         SQLiteDatabase db = mHelper.getWritableDatabase(); | ||||
|         boolean deleteData = false; | ||||
|         switch (mMatcher.match(uri)) { | ||||
|             case URI_NOTE: | ||||
|                 selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; | ||||
|                 count = db.delete(TABLE.NOTE, selection, selectionArgs); | ||||
|                 break; | ||||
|             case URI_NOTE_ITEM: | ||||
|                 id = uri.getPathSegments().get(1); | ||||
|                 /** | ||||
|                  * ID that smaller than 0 is system folder which is not allowed to | ||||
|                  * trash | ||||
|                  */ | ||||
|                 long noteId = Long.valueOf(id); | ||||
|                 if (noteId <= 0) { | ||||
|                     break; | ||||
|                 } | ||||
|                 count = db.delete(TABLE.NOTE, | ||||
|                         NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); | ||||
|                 break; | ||||
|             case URI_DATA: | ||||
|                 count = db.delete(TABLE.DATA, selection, selectionArgs); | ||||
|                 deleteData = true; | ||||
|                 break; | ||||
|             case URI_DATA_ITEM: | ||||
|                 id = uri.getPathSegments().get(1); | ||||
|                 count = db.delete(TABLE.DATA, | ||||
|                         DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); | ||||
|                 deleteData = true; | ||||
|                 break; | ||||
|             default: | ||||
|                 throw new IllegalArgumentException("Unknown URI " + uri); | ||||
|         } | ||||
|         if (count > 0) { | ||||
|             if (deleteData) { | ||||
|                 getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); | ||||
|             } | ||||
|             getContext().getContentResolver().notifyChange(uri, null); | ||||
|         } | ||||
|         return count; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { | ||||
|         int count = 0; | ||||
|         String id = null; | ||||
|         SQLiteDatabase db = mHelper.getWritableDatabase(); | ||||
|         boolean updateData = false; | ||||
|         switch (mMatcher.match(uri)) { | ||||
|             case URI_NOTE: | ||||
|                 increaseNoteVersion(-1, selection, selectionArgs); | ||||
|                 count = db.update(TABLE.NOTE, values, selection, selectionArgs); | ||||
|                 break; | ||||
|             case URI_NOTE_ITEM: | ||||
|                 id = uri.getPathSegments().get(1); | ||||
|                 increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); | ||||
|                 count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id | ||||
|                         + parseSelection(selection), selectionArgs); | ||||
|                 break; | ||||
|             case URI_DATA: | ||||
|                 count = db.update(TABLE.DATA, values, selection, selectionArgs); | ||||
|                 updateData = true; | ||||
|                 break; | ||||
|             case URI_DATA_ITEM: | ||||
|                 id = uri.getPathSegments().get(1); | ||||
|                 count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id | ||||
|                         + parseSelection(selection), selectionArgs); | ||||
|                 updateData = true; | ||||
|                 break; | ||||
|             default: | ||||
|                 throw new IllegalArgumentException("Unknown URI " + uri); | ||||
|         } | ||||
| 
 | ||||
|         if (count > 0) { | ||||
|             if (updateData) { | ||||
|                 getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); | ||||
|             } | ||||
|             getContext().getContentResolver().notifyChange(uri, null); | ||||
|         } | ||||
|         return count; | ||||
|     } | ||||
| 
 | ||||
|     private String parseSelection(String selection) { | ||||
|         return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); | ||||
|     } | ||||
| 
 | ||||
|     private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { | ||||
|         StringBuilder sql = new StringBuilder(120); | ||||
|         sql.append("UPDATE "); | ||||
|         sql.append(TABLE.NOTE); | ||||
|         sql.append(" SET "); | ||||
|         sql.append(NoteColumns.VERSION); | ||||
|         sql.append("=" + NoteColumns.VERSION + "+1 "); | ||||
| 
 | ||||
|         if (id > 0 || !TextUtils.isEmpty(selection)) { | ||||
|             sql.append(" WHERE "); | ||||
|         } | ||||
|         if (id > 0) { | ||||
|             sql.append(NoteColumns.ID + "=" + String.valueOf(id)); | ||||
|         } | ||||
|         if (!TextUtils.isEmpty(selection)) { | ||||
|             String selectString = id > 0 ? parseSelection(selection) : selection; | ||||
|             for (String args : selectionArgs) { | ||||
|                 selectString = selectString.replaceFirst("\\?", args); | ||||
|             } | ||||
|             sql.append(selectString); | ||||
|         } | ||||
| 
 | ||||
|         mHelper.getWritableDatabase().execSQL(sql.toString()); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String getType(Uri uri) { | ||||
|         // TODO Auto-generated method stub
 | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,82 @@ | ||||
| /* | ||||
|  * 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.tool.GTaskStringUtils; | ||||
| 
 | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| 
 | ||||
| public class MetaData extends Task { | ||||
|     private final static String TAG = MetaData.class.getSimpleName(); | ||||
| 
 | ||||
|     private String mRelatedGid = null; | ||||
| 
 | ||||
|     public void setMeta(String gid, JSONObject metaInfo) { | ||||
|         try { | ||||
|             metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); | ||||
|         } catch (JSONException e) { | ||||
|             Log.e(TAG, "failed to put related gid"); | ||||
|         } | ||||
|         setNotes(metaInfo.toString()); | ||||
|         setName(GTaskStringUtils.META_NOTE_NAME); | ||||
|     } | ||||
| 
 | ||||
|     public String getRelatedGid() { | ||||
|         return mRelatedGid; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean isWorthSaving() { | ||||
|         return getNotes() != null; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setContentByRemoteJSON(JSONObject js) { | ||||
|         super.setContentByRemoteJSON(js); | ||||
|         if (getNotes() != null) { | ||||
|             try { | ||||
|                 JSONObject metaInfo = new JSONObject(getNotes().trim()); | ||||
|                 mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); | ||||
|             } catch (JSONException e) { | ||||
|                 Log.w(TAG, "failed to get related gid"); | ||||
|                 mRelatedGid = null; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void setContentByLocalJSON(JSONObject js) { | ||||
|         // this function should not be called
 | ||||
|         throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public JSONObject getLocalJSONFromContent() { | ||||
|         throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public int getSyncAction(Cursor c) { | ||||
|         throw new IllegalAccessError("MetaData:getSyncAction should not be called"); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,101 @@ | ||||
| /* | ||||
|  * 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 org.json.JSONObject; | ||||
| 
 | ||||
| public abstract class Node { | ||||
|     public static final int SYNC_ACTION_NONE = 0; | ||||
| 
 | ||||
|     public static final int SYNC_ACTION_ADD_REMOTE = 1; | ||||
| 
 | ||||
|     public static final int SYNC_ACTION_ADD_LOCAL = 2; | ||||
| 
 | ||||
|     public static final int SYNC_ACTION_DEL_REMOTE = 3; | ||||
| 
 | ||||
|     public static final int SYNC_ACTION_DEL_LOCAL = 4; | ||||
| 
 | ||||
|     public static final int SYNC_ACTION_UPDATE_REMOTE = 5; | ||||
| 
 | ||||
|     public static final int SYNC_ACTION_UPDATE_LOCAL = 6; | ||||
| 
 | ||||
|     public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; | ||||
| 
 | ||||
|     public static final int SYNC_ACTION_ERROR = 8; | ||||
| 
 | ||||
|     private String mGid; | ||||
| 
 | ||||
|     private String mName; | ||||
| 
 | ||||
|     private long mLastModified; | ||||
| 
 | ||||
|     private boolean mDeleted; | ||||
| 
 | ||||
|     public Node() { | ||||
|         mGid = null; | ||||
|         mName = ""; | ||||
|         mLastModified = 0; | ||||
|         mDeleted = false; | ||||
|     } | ||||
| 
 | ||||
|     public abstract JSONObject getCreateAction(int actionId); | ||||
| 
 | ||||
|     public abstract JSONObject getUpdateAction(int actionId); | ||||
| 
 | ||||
|     public abstract void setContentByRemoteJSON(JSONObject js); | ||||
| 
 | ||||
|     public abstract void setContentByLocalJSON(JSONObject js); | ||||
| 
 | ||||
|     public abstract JSONObject getLocalJSONFromContent(); | ||||
| 
 | ||||
|     public abstract int getSyncAction(Cursor c); | ||||
| 
 | ||||
|     public void setGid(String gid) { | ||||
|         this.mGid = gid; | ||||
|     } | ||||
| 
 | ||||
|     public void setName(String name) { | ||||
|         this.mName = name; | ||||
|     } | ||||
| 
 | ||||
|     public void setLastModified(long lastModified) { | ||||
|         this.mLastModified = lastModified; | ||||
|     } | ||||
| 
 | ||||
|     public void setDeleted(boolean deleted) { | ||||
|         this.mDeleted = deleted; | ||||
|     } | ||||
| 
 | ||||
|     public String getGid() { | ||||
|         return this.mGid; | ||||
|     } | ||||
| 
 | ||||
|     public String getName() { | ||||
|         return this.mName; | ||||
|     } | ||||
| 
 | ||||
|     public long getLastModified() { | ||||
|         return this.mLastModified; | ||||
|     } | ||||
| 
 | ||||
|     public boolean getDeleted() { | ||||
|         return this.mDeleted; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,189 @@ | ||||
| /* | ||||
|  * 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.content.ContentResolver; | ||||
| import android.content.ContentUris; | ||||
| import android.content.ContentValues; | ||||
| import android.content.Context; | ||||
| import android.database.Cursor; | ||||
| import android.net.Uri; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import net.micode.notes.data.Notes; | ||||
| 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.NotesDatabaseHelper.TABLE; | ||||
| import net.micode.notes.gtask.exception.ActionFailureException; | ||||
| 
 | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| 
 | ||||
| public class SqlData { | ||||
|     private static final String TAG = SqlData.class.getSimpleName(); | ||||
| 
 | ||||
|     private static final int INVALID_ID = -99999; | ||||
| 
 | ||||
|     public static final String[] PROJECTION_DATA = new String[] { | ||||
|             DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, | ||||
|             DataColumns.DATA3 | ||||
|     }; | ||||
| 
 | ||||
|     public static final int DATA_ID_COLUMN = 0; | ||||
| 
 | ||||
|     public static final int DATA_MIME_TYPE_COLUMN = 1; | ||||
| 
 | ||||
|     public static final int DATA_CONTENT_COLUMN = 2; | ||||
| 
 | ||||
|     public static final int DATA_CONTENT_DATA_1_COLUMN = 3; | ||||
| 
 | ||||
|     public static final int DATA_CONTENT_DATA_3_COLUMN = 4; | ||||
| 
 | ||||
|     private ContentResolver mContentResolver; | ||||
| 
 | ||||
|     private boolean mIsCreate; | ||||
| 
 | ||||
|     private long mDataId; | ||||
| 
 | ||||
|     private String mDataMimeType; | ||||
| 
 | ||||
|     private String mDataContent; | ||||
| 
 | ||||
|     private long mDataContentData1; | ||||
| 
 | ||||
|     private String mDataContentData3; | ||||
| 
 | ||||
|     private ContentValues mDiffDataValues; | ||||
| 
 | ||||
|     public SqlData(Context context) { | ||||
|         mContentResolver = context.getContentResolver(); | ||||
|         mIsCreate = true; | ||||
|         mDataId = INVALID_ID; | ||||
|         mDataMimeType = DataConstants.NOTE; | ||||
|         mDataContent = ""; | ||||
|         mDataContentData1 = 0; | ||||
|         mDataContentData3 = ""; | ||||
|         mDiffDataValues = new ContentValues(); | ||||
|     } | ||||
| 
 | ||||
|     public SqlData(Context context, Cursor c) { | ||||
|         mContentResolver = context.getContentResolver(); | ||||
|         mIsCreate = false; | ||||
|         loadFromCursor(c); | ||||
|         mDiffDataValues = new ContentValues(); | ||||
|     } | ||||
| 
 | ||||
|     private void loadFromCursor(Cursor c) { | ||||
|         mDataId = c.getLong(DATA_ID_COLUMN); | ||||
|         mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); | ||||
|         mDataContent = c.getString(DATA_CONTENT_COLUMN); | ||||
|         mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); | ||||
|         mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); | ||||
|     } | ||||
| 
 | ||||
|     public void setContent(JSONObject js) throws JSONException { | ||||
|         long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; | ||||
|         if (mIsCreate || mDataId != dataId) { | ||||
|             mDiffDataValues.put(DataColumns.ID, dataId); | ||||
|         } | ||||
|         mDataId = dataId; | ||||
| 
 | ||||
|         String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE) | ||||
|                 : DataConstants.NOTE; | ||||
|         if (mIsCreate || !mDataMimeType.equals(dataMimeType)) { | ||||
|             mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); | ||||
|         } | ||||
|         mDataMimeType = dataMimeType; | ||||
| 
 | ||||
|         String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : ""; | ||||
|         if (mIsCreate || !mDataContent.equals(dataContent)) { | ||||
|             mDiffDataValues.put(DataColumns.CONTENT, dataContent); | ||||
|         } | ||||
|         mDataContent = dataContent; | ||||
| 
 | ||||
|         long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0; | ||||
|         if (mIsCreate || mDataContentData1 != dataContentData1) { | ||||
|             mDiffDataValues.put(DataColumns.DATA1, dataContentData1); | ||||
|         } | ||||
|         mDataContentData1 = dataContentData1; | ||||
| 
 | ||||
|         String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : ""; | ||||
|         if (mIsCreate || !mDataContentData3.equals(dataContentData3)) { | ||||
|             mDiffDataValues.put(DataColumns.DATA3, dataContentData3); | ||||
|         } | ||||
|         mDataContentData3 = dataContentData3; | ||||
|     } | ||||
| 
 | ||||
|     public JSONObject getContent() throws JSONException { | ||||
|         if (mIsCreate) { | ||||
|             Log.e(TAG, "it seems that we haven't created this in database yet"); | ||||
|             return null; | ||||
|         } | ||||
|         JSONObject js = new JSONObject(); | ||||
|         js.put(DataColumns.ID, mDataId); | ||||
|         js.put(DataColumns.MIME_TYPE, mDataMimeType); | ||||
|         js.put(DataColumns.CONTENT, mDataContent); | ||||
|         js.put(DataColumns.DATA1, mDataContentData1); | ||||
|         js.put(DataColumns.DATA3, mDataContentData3); | ||||
|         return js; | ||||
|     } | ||||
| 
 | ||||
|     public void commit(long noteId, boolean validateVersion, long version) { | ||||
| 
 | ||||
|         if (mIsCreate) { | ||||
|             if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { | ||||
|                 mDiffDataValues.remove(DataColumns.ID); | ||||
|             } | ||||
| 
 | ||||
|             mDiffDataValues.put(DataColumns.NOTE_ID, noteId); | ||||
|             Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); | ||||
|             try { | ||||
|                 mDataId = Long.valueOf(uri.getPathSegments().get(1)); | ||||
|             } catch (NumberFormatException e) { | ||||
|                 Log.e(TAG, "Get note id error :" + e.toString()); | ||||
|                 throw new ActionFailureException("create note failed"); | ||||
|             } | ||||
|         } else { | ||||
|             if (mDiffDataValues.size() > 0) { | ||||
|                 int result = 0; | ||||
|                 if (!validateVersion) { | ||||
|                     result = mContentResolver.update(ContentUris.withAppendedId( | ||||
|                             Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); | ||||
|                 } else { | ||||
|                     result = mContentResolver.update(ContentUris.withAppendedId( | ||||
|                             Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, | ||||
|                             " ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE | ||||
|                                     + " WHERE " + NoteColumns.VERSION + "=?)", new String[] { | ||||
|                                     String.valueOf(noteId), String.valueOf(version) | ||||
|                             }); | ||||
|                 } | ||||
|                 if (result == 0) { | ||||
|                     Log.w(TAG, "there is no update. maybe user updates note when syncing"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         mDiffDataValues.clear(); | ||||
|         mIsCreate = false; | ||||
|     } | ||||
| 
 | ||||
|     public long getId() { | ||||
|         return mDataId; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,505 @@ | ||||
| /* | ||||
|  * 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.appwidget.AppWidgetManager; | ||||
| import android.content.ContentResolver; | ||||
| import android.content.ContentValues; | ||||
| import android.content.Context; | ||||
| import android.database.Cursor; | ||||
| import android.net.Uri; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| 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.exception.ActionFailureException; | ||||
| import net.micode.notes.tool.GTaskStringUtils; | ||||
| import net.micode.notes.tool.ResourceParser; | ||||
| 
 | ||||
| import org.json.JSONArray; | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| 
 | ||||
| 
 | ||||
| public class SqlNote { | ||||
|     private static final String TAG = SqlNote.class.getSimpleName(); | ||||
| 
 | ||||
|     private static final int INVALID_ID = -99999; | ||||
| 
 | ||||
|     public static final String[] PROJECTION_NOTE = new String[] { | ||||
|             NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID, | ||||
|             NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE, | ||||
|             NoteColumns.NOTES_COUNT, NoteColumns.PARENT_ID, NoteColumns.SNIPPET, NoteColumns.TYPE, | ||||
|             NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, NoteColumns.SYNC_ID, | ||||
|             NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID, | ||||
|             NoteColumns.VERSION | ||||
|     }; | ||||
| 
 | ||||
|     public static final int ID_COLUMN = 0; | ||||
| 
 | ||||
|     public static final int ALERTED_DATE_COLUMN = 1; | ||||
| 
 | ||||
|     public static final int BG_COLOR_ID_COLUMN = 2; | ||||
| 
 | ||||
|     public static final int CREATED_DATE_COLUMN = 3; | ||||
| 
 | ||||
|     public static final int HAS_ATTACHMENT_COLUMN = 4; | ||||
| 
 | ||||
|     public static final int MODIFIED_DATE_COLUMN = 5; | ||||
| 
 | ||||
|     public static final int NOTES_COUNT_COLUMN = 6; | ||||
| 
 | ||||
|     public static final int PARENT_ID_COLUMN = 7; | ||||
| 
 | ||||
|     public static final int SNIPPET_COLUMN = 8; | ||||
| 
 | ||||
|     public static final int TYPE_COLUMN = 9; | ||||
| 
 | ||||
|     public static final int WIDGET_ID_COLUMN = 10; | ||||
| 
 | ||||
|     public static final int WIDGET_TYPE_COLUMN = 11; | ||||
| 
 | ||||
|     public static final int SYNC_ID_COLUMN = 12; | ||||
| 
 | ||||
|     public static final int LOCAL_MODIFIED_COLUMN = 13; | ||||
| 
 | ||||
|     public static final int ORIGIN_PARENT_ID_COLUMN = 14; | ||||
| 
 | ||||
|     public static final int GTASK_ID_COLUMN = 15; | ||||
| 
 | ||||
|     public static final int VERSION_COLUMN = 16; | ||||
| 
 | ||||
|     private Context mContext; | ||||
| 
 | ||||
|     private ContentResolver mContentResolver; | ||||
| 
 | ||||
|     private boolean mIsCreate; | ||||
| 
 | ||||
|     private long mId; | ||||
| 
 | ||||
|     private long mAlertDate; | ||||
| 
 | ||||
|     private int mBgColorId; | ||||
| 
 | ||||
|     private long mCreatedDate; | ||||
| 
 | ||||
|     private int mHasAttachment; | ||||
| 
 | ||||
|     private long mModifiedDate; | ||||
| 
 | ||||
|     private long mParentId; | ||||
| 
 | ||||
|     private String mSnippet; | ||||
| 
 | ||||
|     private int mType; | ||||
| 
 | ||||
|     private int mWidgetId; | ||||
| 
 | ||||
|     private int mWidgetType; | ||||
| 
 | ||||
|     private long mOriginParent; | ||||
| 
 | ||||
|     private long mVersion; | ||||
| 
 | ||||
|     private ContentValues mDiffNoteValues; | ||||
| 
 | ||||
|     private ArrayList<SqlData> mDataList; | ||||
| 
 | ||||
|     public SqlNote(Context context) { | ||||
|         mContext = context; | ||||
|         mContentResolver = context.getContentResolver(); | ||||
|         mIsCreate = true; | ||||
|         mId = INVALID_ID; | ||||
|         mAlertDate = 0; | ||||
|         mBgColorId = ResourceParser.getDefaultBgId(context); | ||||
|         mCreatedDate = System.currentTimeMillis(); | ||||
|         mHasAttachment = 0; | ||||
|         mModifiedDate = System.currentTimeMillis(); | ||||
|         mParentId = 0; | ||||
|         mSnippet = ""; | ||||
|         mType = Notes.TYPE_NOTE; | ||||
|         mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; | ||||
|         mWidgetType = Notes.TYPE_WIDGET_INVALIDE; | ||||
|         mOriginParent = 0; | ||||
|         mVersion = 0; | ||||
|         mDiffNoteValues = new ContentValues(); | ||||
|         mDataList = new ArrayList<SqlData>(); | ||||
|     } | ||||
| 
 | ||||
|     public SqlNote(Context context, Cursor c) { | ||||
|         mContext = context; | ||||
|         mContentResolver = context.getContentResolver(); | ||||
|         mIsCreate = false; | ||||
|         loadFromCursor(c); | ||||
|         mDataList = new ArrayList<SqlData>(); | ||||
|         if (mType == Notes.TYPE_NOTE) | ||||
|             loadDataContent(); | ||||
|         mDiffNoteValues = new ContentValues(); | ||||
|     } | ||||
| 
 | ||||
|     public SqlNote(Context context, long id) { | ||||
|         mContext = context; | ||||
|         mContentResolver = context.getContentResolver(); | ||||
|         mIsCreate = false; | ||||
|         loadFromCursor(id); | ||||
|         mDataList = new ArrayList<SqlData>(); | ||||
|         if (mType == Notes.TYPE_NOTE) | ||||
|             loadDataContent(); | ||||
|         mDiffNoteValues = new ContentValues(); | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     private void loadFromCursor(long id) { | ||||
|         Cursor c = null; | ||||
|         try { | ||||
|             c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)", | ||||
|                     new String[] { | ||||
|                         String.valueOf(id) | ||||
|                     }, null); | ||||
|             if (c != null) { | ||||
|                 c.moveToNext(); | ||||
|                 loadFromCursor(c); | ||||
|             } else { | ||||
|                 Log.w(TAG, "loadFromCursor: cursor = null"); | ||||
|             } | ||||
|         } finally { | ||||
|             if (c != null) | ||||
|                 c.close(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void loadFromCursor(Cursor c) { | ||||
|         mId = c.getLong(ID_COLUMN); | ||||
|         mAlertDate = c.getLong(ALERTED_DATE_COLUMN); | ||||
|         mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); | ||||
|         mCreatedDate = c.getLong(CREATED_DATE_COLUMN); | ||||
|         mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN); | ||||
|         mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN); | ||||
|         mParentId = c.getLong(PARENT_ID_COLUMN); | ||||
|         mSnippet = c.getString(SNIPPET_COLUMN); | ||||
|         mType = c.getInt(TYPE_COLUMN); | ||||
|         mWidgetId = c.getInt(WIDGET_ID_COLUMN); | ||||
|         mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); | ||||
|         mVersion = c.getLong(VERSION_COLUMN); | ||||
|     } | ||||
| 
 | ||||
|     private void loadDataContent() { | ||||
|         Cursor c = null; | ||||
|         mDataList.clear(); | ||||
|         try { | ||||
|             c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA, | ||||
|                     "(note_id=?)", new String[] { | ||||
|                         String.valueOf(mId) | ||||
|                     }, null); | ||||
|             if (c != null) { | ||||
|                 if (c.getCount() == 0) { | ||||
|                     Log.w(TAG, "it seems that the note has not data"); | ||||
|                     return; | ||||
|                 } | ||||
|                 while (c.moveToNext()) { | ||||
|                     SqlData data = new SqlData(mContext, c); | ||||
|                     mDataList.add(data); | ||||
|                 } | ||||
|             } else { | ||||
|                 Log.w(TAG, "loadDataContent: cursor = null"); | ||||
|             } | ||||
|         } finally { | ||||
|             if (c != null) | ||||
|                 c.close(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public boolean setContent(JSONObject js) { | ||||
|         try { | ||||
|             JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); | ||||
|             if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { | ||||
|                 Log.w(TAG, "cannot set system folder"); | ||||
|             } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { | ||||
|                 // for folder we can only update the snnipet and type
 | ||||
|                 String snippet = note.has(NoteColumns.SNIPPET) ? note | ||||
|                         .getString(NoteColumns.SNIPPET) : ""; | ||||
|                 if (mIsCreate || !mSnippet.equals(snippet)) { | ||||
|                     mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); | ||||
|                 } | ||||
|                 mSnippet = snippet; | ||||
| 
 | ||||
|                 int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) | ||||
|                         : Notes.TYPE_NOTE; | ||||
|                 if (mIsCreate || mType != type) { | ||||
|                     mDiffNoteValues.put(NoteColumns.TYPE, type); | ||||
|                 } | ||||
|                 mType = type; | ||||
|             } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { | ||||
|                 JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); | ||||
|                 long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID; | ||||
|                 if (mIsCreate || mId != id) { | ||||
|                     mDiffNoteValues.put(NoteColumns.ID, id); | ||||
|                 } | ||||
|                 mId = id; | ||||
| 
 | ||||
|                 long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note | ||||
|                         .getLong(NoteColumns.ALERTED_DATE) : 0; | ||||
|                 if (mIsCreate || mAlertDate != alertDate) { | ||||
|                     mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate); | ||||
|                 } | ||||
|                 mAlertDate = alertDate; | ||||
| 
 | ||||
|                 int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note | ||||
|                         .getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext); | ||||
|                 if (mIsCreate || mBgColorId != bgColorId) { | ||||
|                     mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId); | ||||
|                 } | ||||
|                 mBgColorId = bgColorId; | ||||
| 
 | ||||
|                 long createDate = note.has(NoteColumns.CREATED_DATE) ? note | ||||
|                         .getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis(); | ||||
|                 if (mIsCreate || mCreatedDate != createDate) { | ||||
|                     mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate); | ||||
|                 } | ||||
|                 mCreatedDate = createDate; | ||||
| 
 | ||||
|                 int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note | ||||
|                         .getInt(NoteColumns.HAS_ATTACHMENT) : 0; | ||||
|                 if (mIsCreate || mHasAttachment != hasAttachment) { | ||||
|                     mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment); | ||||
|                 } | ||||
|                 mHasAttachment = hasAttachment; | ||||
| 
 | ||||
|                 long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note | ||||
|                         .getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis(); | ||||
|                 if (mIsCreate || mModifiedDate != modifiedDate) { | ||||
|                     mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate); | ||||
|                 } | ||||
|                 mModifiedDate = modifiedDate; | ||||
| 
 | ||||
|                 long parentId = note.has(NoteColumns.PARENT_ID) ? note | ||||
|                         .getLong(NoteColumns.PARENT_ID) : 0; | ||||
|                 if (mIsCreate || mParentId != parentId) { | ||||
|                     mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId); | ||||
|                 } | ||||
|                 mParentId = parentId; | ||||
| 
 | ||||
|                 String snippet = note.has(NoteColumns.SNIPPET) ? note | ||||
|                         .getString(NoteColumns.SNIPPET) : ""; | ||||
|                 if (mIsCreate || !mSnippet.equals(snippet)) { | ||||
|                     mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); | ||||
|                 } | ||||
|                 mSnippet = snippet; | ||||
| 
 | ||||
|                 int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) | ||||
|                         : Notes.TYPE_NOTE; | ||||
|                 if (mIsCreate || mType != type) { | ||||
|                     mDiffNoteValues.put(NoteColumns.TYPE, type); | ||||
|                 } | ||||
|                 mType = type; | ||||
| 
 | ||||
|                 int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID) | ||||
|                         : AppWidgetManager.INVALID_APPWIDGET_ID; | ||||
|                 if (mIsCreate || mWidgetId != widgetId) { | ||||
|                     mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId); | ||||
|                 } | ||||
|                 mWidgetId = widgetId; | ||||
| 
 | ||||
|                 int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note | ||||
|                         .getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE; | ||||
|                 if (mIsCreate || mWidgetType != widgetType) { | ||||
|                     mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType); | ||||
|                 } | ||||
|                 mWidgetType = widgetType; | ||||
| 
 | ||||
|                 long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note | ||||
|                         .getLong(NoteColumns.ORIGIN_PARENT_ID) : 0; | ||||
|                 if (mIsCreate || mOriginParent != originParent) { | ||||
|                     mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent); | ||||
|                 } | ||||
|                 mOriginParent = originParent; | ||||
| 
 | ||||
|                 for (int i = 0; i < dataArray.length(); i++) { | ||||
|                     JSONObject data = dataArray.getJSONObject(i); | ||||
|                     SqlData sqlData = null; | ||||
|                     if (data.has(DataColumns.ID)) { | ||||
|                         long dataId = data.getLong(DataColumns.ID); | ||||
|                         for (SqlData temp : mDataList) { | ||||
|                             if (dataId == temp.getId()) { | ||||
|                                 sqlData = temp; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     if (sqlData == null) { | ||||
|                         sqlData = new SqlData(mContext); | ||||
|                         mDataList.add(sqlData); | ||||
|                     } | ||||
| 
 | ||||
|                     sqlData.setContent(data); | ||||
|                 } | ||||
|             } | ||||
|         } catch (JSONException e) { | ||||
|             Log.e(TAG, e.toString()); | ||||
|             e.printStackTrace(); | ||||
|             return false; | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     public JSONObject getContent() { | ||||
|         try { | ||||
|             JSONObject js = new JSONObject(); | ||||
| 
 | ||||
|             if (mIsCreate) { | ||||
|                 Log.e(TAG, "it seems that we haven't created this in database yet"); | ||||
|                 return null; | ||||
|             } | ||||
| 
 | ||||
|             JSONObject note = new JSONObject(); | ||||
|             if (mType == Notes.TYPE_NOTE) { | ||||
|                 note.put(NoteColumns.ID, mId); | ||||
|                 note.put(NoteColumns.ALERTED_DATE, mAlertDate); | ||||
|                 note.put(NoteColumns.BG_COLOR_ID, mBgColorId); | ||||
|                 note.put(NoteColumns.CREATED_DATE, mCreatedDate); | ||||
|                 note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment); | ||||
|                 note.put(NoteColumns.MODIFIED_DATE, mModifiedDate); | ||||
|                 note.put(NoteColumns.PARENT_ID, mParentId); | ||||
|                 note.put(NoteColumns.SNIPPET, mSnippet); | ||||
|                 note.put(NoteColumns.TYPE, mType); | ||||
|                 note.put(NoteColumns.WIDGET_ID, mWidgetId); | ||||
|                 note.put(NoteColumns.WIDGET_TYPE, mWidgetType); | ||||
|                 note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent); | ||||
|                 js.put(GTaskStringUtils.META_HEAD_NOTE, note); | ||||
| 
 | ||||
|                 JSONArray dataArray = new JSONArray(); | ||||
|                 for (SqlData sqlData : mDataList) { | ||||
|                     JSONObject data = sqlData.getContent(); | ||||
|                     if (data != null) { | ||||
|                         dataArray.put(data); | ||||
|                     } | ||||
|                 } | ||||
|                 js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); | ||||
|             } else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { | ||||
|                 note.put(NoteColumns.ID, mId); | ||||
|                 note.put(NoteColumns.TYPE, mType); | ||||
|                 note.put(NoteColumns.SNIPPET, mSnippet); | ||||
|                 js.put(GTaskStringUtils.META_HEAD_NOTE, note); | ||||
|             } | ||||
| 
 | ||||
|             return js; | ||||
|         } catch (JSONException e) { | ||||
|             Log.e(TAG, e.toString()); | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public void setParentId(long id) { | ||||
|         mParentId = id; | ||||
|         mDiffNoteValues.put(NoteColumns.PARENT_ID, id); | ||||
|     } | ||||
| 
 | ||||
|     public void setGtaskId(String gid) { | ||||
|         mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); | ||||
|     } | ||||
| 
 | ||||
|     public void setSyncId(long syncId) { | ||||
|         mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); | ||||
|     } | ||||
| 
 | ||||
|     public void resetLocalModified() { | ||||
|         mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); | ||||
|     } | ||||
| 
 | ||||
|     public long getId() { | ||||
|         return mId; | ||||
|     } | ||||
| 
 | ||||
|     public long getParentId() { | ||||
|         return mParentId; | ||||
|     } | ||||
| 
 | ||||
|     public String getSnippet() { | ||||
|         return mSnippet; | ||||
|     } | ||||
| 
 | ||||
|     public boolean isNoteType() { | ||||
|         return mType == Notes.TYPE_NOTE; | ||||
|     } | ||||
| 
 | ||||
|     public void commit(boolean validateVersion) { | ||||
|         if (mIsCreate) { | ||||
|             if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) { | ||||
|                 mDiffNoteValues.remove(NoteColumns.ID); | ||||
|             } | ||||
| 
 | ||||
|             Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); | ||||
|             try { | ||||
|                 mId = Long.valueOf(uri.getPathSegments().get(1)); | ||||
|             } catch (NumberFormatException e) { | ||||
|                 Log.e(TAG, "Get note id error :" + e.toString()); | ||||
|                 throw new ActionFailureException("create note failed"); | ||||
|             } | ||||
|             if (mId == 0) { | ||||
|                 throw new IllegalStateException("Create thread id failed"); | ||||
|             } | ||||
| 
 | ||||
|             if (mType == Notes.TYPE_NOTE) { | ||||
|                 for (SqlData sqlData : mDataList) { | ||||
|                     sqlData.commit(mId, false, -1); | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) { | ||||
|                 Log.e(TAG, "No such note"); | ||||
|                 throw new IllegalStateException("Try to update note with invalid id"); | ||||
|             } | ||||
|             if (mDiffNoteValues.size() > 0) { | ||||
|                 mVersion ++; | ||||
|                 int result = 0; | ||||
|                 if (!validateVersion) { | ||||
|                     result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" | ||||
|                             + NoteColumns.ID + "=?)", new String[] { | ||||
|                         String.valueOf(mId) | ||||
|                     }); | ||||
|                 } else { | ||||
|                     result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" | ||||
|                             + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", | ||||
|                             new String[] { | ||||
|                                     String.valueOf(mId), String.valueOf(mVersion) | ||||
|                             }); | ||||
|                 } | ||||
|                 if (result == 0) { | ||||
|                     Log.w(TAG, "there is no update. maybe user updates note when syncing"); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (mType == Notes.TYPE_NOTE) { | ||||
|                 for (SqlData sqlData : mDataList) { | ||||
|                     sqlData.commit(mId, validateVersion, mVersion); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // refresh local info
 | ||||
|         loadFromCursor(mId); | ||||
|         if (mType == Notes.TYPE_NOTE) | ||||
|             loadDataContent(); | ||||
| 
 | ||||
|         mDiffNoteValues.clear(); | ||||
|         mIsCreate = false; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,351 @@ | ||||
| /* | ||||
|  * 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.text.TextUtils; | ||||
| import android.util.Log; | ||||
| 
 | ||||
| import net.micode.notes.data.Notes; | ||||
| 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.gtask.exception.ActionFailureException; | ||||
| import net.micode.notes.tool.GTaskStringUtils; | ||||
| 
 | ||||
| import org.json.JSONArray; | ||||
| import org.json.JSONException; | ||||
| import org.json.JSONObject; | ||||
| 
 | ||||
| 
 | ||||
| public class Task extends Node { | ||||
|     private static final String TAG = Task.class.getSimpleName(); | ||||
| 
 | ||||
|     private boolean mCompleted; | ||||
| 
 | ||||
|     private String mNotes; | ||||
| 
 | ||||
|     private JSONObject mMetaInfo; | ||||
| 
 | ||||
|     private Task mPriorSibling; | ||||
| 
 | ||||
|     private TaskList mParent; | ||||
| 
 | ||||
|     public Task() { | ||||
|         super(); | ||||
|         mCompleted = false; | ||||
|         mNotes = null; | ||||
|         mPriorSibling = null; | ||||
|         mParent = null; | ||||
|         mMetaInfo = null; | ||||
|     } | ||||
| 
 | ||||
|     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, mParent.getChildTaskIndex(this)); | ||||
| 
 | ||||
|             // 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_TASK); | ||||
|             if (getNotes() != null) { | ||||
|                 entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); | ||||
|             } | ||||
|             js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); | ||||
| 
 | ||||
|             // parent_id
 | ||||
|             js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); | ||||
| 
 | ||||
|             // dest_parent_type
 | ||||
|             js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, | ||||
|                     GTaskStringUtils.GTASK_JSON_TYPE_GROUP); | ||||
| 
 | ||||
|             // list_id
 | ||||
|             js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); | ||||
| 
 | ||||
|             // prior_sibling_id
 | ||||
|             if (mPriorSibling != null) { | ||||
|                 js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); | ||||
|             } | ||||
| 
 | ||||
|         } catch (JSONException e) { | ||||
|             Log.e(TAG, e.toString()); | ||||
|             e.printStackTrace(); | ||||
|             throw new ActionFailureException("fail to generate task-create jsonobject"); | ||||
|         } | ||||
| 
 | ||||
|         return js; | ||||
|     } | ||||
| 
 | ||||
|     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()); | ||||
|             if (getNotes() != null) { | ||||
|                 entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); | ||||
|             } | ||||
|             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 task-update jsonobject"); | ||||
|         } | ||||
| 
 | ||||
|         return js; | ||||
|     } | ||||
| 
 | ||||
|     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)); | ||||
|                 } | ||||
| 
 | ||||
|                 // notes
 | ||||
|                 if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { | ||||
|                     setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); | ||||
|                 } | ||||
| 
 | ||||
|                 // deleted
 | ||||
|                 if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { | ||||
|                     setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); | ||||
|                 } | ||||
| 
 | ||||
|                 // completed
 | ||||
|                 if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { | ||||
|                     setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); | ||||
|                 } | ||||
|             } catch (JSONException e) { | ||||
|                 Log.e(TAG, e.toString()); | ||||
|                 e.printStackTrace(); | ||||
|                 throw new ActionFailureException("fail to get task content from jsonobject"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void setContentByLocalJSON(JSONObject js) { | ||||
|         if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) | ||||
|                 || !js.has(GTaskStringUtils.META_HEAD_DATA)) { | ||||
|             Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); | ||||
|             JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); | ||||
| 
 | ||||
|             if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { | ||||
|                 Log.e(TAG, "invalid type"); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             for (int i = 0; i < dataArray.length(); i++) { | ||||
|                 JSONObject data = dataArray.getJSONObject(i); | ||||
|                 if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { | ||||
|                     setName(data.getString(DataColumns.CONTENT)); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|         } catch (JSONException e) { | ||||
|             Log.e(TAG, e.toString()); | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public JSONObject getLocalJSONFromContent() { | ||||
|         String name = getName(); | ||||
|         try { | ||||
|             if (mMetaInfo == null) { | ||||
|                 // new task created from web
 | ||||
|                 if (name == null) { | ||||
|                     Log.w(TAG, "the note seems to be an empty one"); | ||||
|                     return null; | ||||
|                 } | ||||
| 
 | ||||
|                 JSONObject js = new JSONObject(); | ||||
|                 JSONObject note = new JSONObject(); | ||||
|                 JSONArray dataArray = new JSONArray(); | ||||
|                 JSONObject data = new JSONObject(); | ||||
|                 data.put(DataColumns.CONTENT, name); | ||||
|                 dataArray.put(data); | ||||
|                 js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); | ||||
|                 note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); | ||||
|                 js.put(GTaskStringUtils.META_HEAD_NOTE, note); | ||||
|                 return js; | ||||
|             } else { | ||||
|                 // synced task
 | ||||
|                 JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); | ||||
|                 JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); | ||||
| 
 | ||||
|                 for (int i = 0; i < dataArray.length(); i++) { | ||||
|                     JSONObject data = dataArray.getJSONObject(i); | ||||
|                     if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { | ||||
|                         data.put(DataColumns.CONTENT, getName()); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); | ||||
|                 return mMetaInfo; | ||||
|             } | ||||
|         } catch (JSONException e) { | ||||
|             Log.e(TAG, e.toString()); | ||||
|             e.printStackTrace(); | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void setMetaInfo(MetaData metaData) { | ||||
|         if (metaData != null && metaData.getNotes() != null) { | ||||
|             try { | ||||
|                 mMetaInfo = new JSONObject(metaData.getNotes()); | ||||
|             } catch (JSONException e) { | ||||
|                 Log.w(TAG, e.toString()); | ||||
|                 mMetaInfo = null; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public int getSyncAction(Cursor c) { | ||||
|         try { | ||||
|             JSONObject noteInfo = null; | ||||
|             if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) { | ||||
|                 noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); | ||||
|             } | ||||
| 
 | ||||
|             if (noteInfo == null) { | ||||
|                 Log.w(TAG, "it seems that note meta has been deleted"); | ||||
|                 return SYNC_ACTION_UPDATE_REMOTE; | ||||
|             } | ||||
| 
 | ||||
|             if (!noteInfo.has(NoteColumns.ID)) { | ||||
|                 Log.w(TAG, "remote note id seems to be deleted"); | ||||
|                 return SYNC_ACTION_UPDATE_LOCAL; | ||||
|             } | ||||
| 
 | ||||
|             // validate the note id now
 | ||||
|             if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { | ||||
|                 Log.w(TAG, "note id doesn't match"); | ||||
|                 return SYNC_ACTION_UPDATE_LOCAL; | ||||
|             } | ||||
| 
 | ||||
|             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 { | ||||
|                     return SYNC_ACTION_UPDATE_CONFLICT; | ||||
|                 } | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             Log.e(TAG, e.toString()); | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
| 
 | ||||
|         return SYNC_ACTION_ERROR; | ||||
|     } | ||||
| 
 | ||||
|     public boolean isWorthSaving() { | ||||
|         return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) | ||||
|                 || (getNotes() != null && getNotes().trim().length() > 0); | ||||
|     } | ||||
| 
 | ||||
|     public void setCompleted(boolean completed) { | ||||
|         this.mCompleted = completed; | ||||
|     } | ||||
| 
 | ||||
|     public void setNotes(String notes) { | ||||
|         this.mNotes = notes; | ||||
|     } | ||||
| 
 | ||||
|     public void setPriorSibling(Task priorSibling) { | ||||
|         this.mPriorSibling = priorSibling; | ||||
|     } | ||||
| 
 | ||||
|     public void setParent(TaskList parent) { | ||||
|         this.mParent = parent; | ||||
|     } | ||||
| 
 | ||||
|     public boolean getCompleted() { | ||||
|         return this.mCompleted; | ||||
|     } | ||||
| 
 | ||||
|     public String getNotes() { | ||||
|         return this.mNotes; | ||||
|     } | ||||
| 
 | ||||
|     public Task getPriorSibling() { | ||||
|         return this.mPriorSibling; | ||||
|     } | ||||
| 
 | ||||
|     public TaskList getParent() { | ||||
|         return this.mParent; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,343 @@ | ||||
| /* | ||||
|  * 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; | ||||
|     } | ||||
| 
 | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
|     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; | ||||
|     } | ||||
| 
 | ||||
|     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"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     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(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     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)); | ||||
|     } | ||||
| 
 | ||||
|     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); | ||||
|     } | ||||
| 
 | ||||
|     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,232 @@ | ||||
| package net.micode.notes.model; | ||||
|   | ||||
| import android.content.ContentProviderOperation;//批量的更新、插入、删除数据。
 | ||||
| import android.content.ContentProviderResult;//操作的结果
 | ||||
| import android.content.ContentUris;//用于添加和获取Uri后面的ID
 | ||||
| import android.content.ContentValues;//一种用来存储基本数据类型数据的存储机制
 | ||||
| import android.content.Context;//需要用该类来弄清楚调用者的实例
 | ||||
| import android.content.OperationApplicationException;//操作应用程序容错
 | ||||
| import android.net.Uri;//表示待操作的数据
 | ||||
| import android.os.RemoteException;//远程容错
 | ||||
| import android.util.Log;//输出日志,比如说出错、警告等
 | ||||
|   | ||||
| public class Note { | ||||
|  //   private ContentValues mNoteDiffValues;
 | ||||
|     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); | ||||
|         //ContentResolver()主要是实现外部应用对ContentProvider中的数据
 | ||||
|         //进行添加、删除、修改和查询操作
 | ||||
|         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; | ||||
|         }//try-catch异常处理
 | ||||
|         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); | ||||
|     }//设置文本数据的ID
 | ||||
|   | ||||
|     public long getTextDataId() { | ||||
|         return mNoteData.mTextDataId; | ||||
|     }//得到文本数据的ID
 | ||||
|   | ||||
|     public void setCallDataId(long id) { | ||||
|         mNoteData.setCallDataId(id); | ||||
|     }//设置电话号码数据的ID
 | ||||
|   | ||||
|     public void setCallData(String key, String value) { | ||||
|         mNoteData.setCallData(key, value); | ||||
|     }//得到电话号码数据的ID
 | ||||
|   | ||||
|     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的操作存储到数据库
 | ||||
|         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(); | ||||
|             }//把文本数据存入DataColumns
 | ||||
|   | ||||
|             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(); | ||||
|             }//把电话号码数据存入DataColumns
 | ||||
|   | ||||
|             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,344 @@ | ||||
| /* | ||||
|  * 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.tool; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.database.Cursor; | ||||
| import android.os.Environment; | ||||
| import android.text.TextUtils; | ||||
| import android.text.format.DateFormat; | ||||
| 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.DataConstants; | ||||
| import net.micode.notes.data.Notes.NoteColumns; | ||||
| 
 | ||||
| import java.io.File; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.FileOutputStream; | ||||
| import java.io.IOException; | ||||
| import java.io.PrintStream; | ||||
| 
 | ||||
| 
 | ||||
| public class BackupUtils { | ||||
|     private static final String TAG = "BackupUtils"; | ||||
|     // Singleton stuff
 | ||||
|     private static BackupUtils sInstance; | ||||
| 
 | ||||
|     public static synchronized BackupUtils getInstance(Context context) { | ||||
|         if (sInstance == null) { | ||||
|             sInstance = new BackupUtils(context); | ||||
|         } | ||||
|         return sInstance; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Following states are signs to represents backup or restore | ||||
|      * status | ||||
|      */ | ||||
|     // Currently, the sdcard is not mounted
 | ||||
|     public static final int STATE_SD_CARD_UNMOUONTED           = 0; | ||||
|     // The backup file not exist
 | ||||
|     public static final int STATE_BACKUP_FILE_NOT_EXIST        = 1; | ||||
|     // The data is not well formated, may be changed by other programs
 | ||||
|     public static final int STATE_DATA_DESTROIED               = 2; | ||||
|     // Some run-time exception which causes restore or backup fails
 | ||||
|     public static final int STATE_SYSTEM_ERROR                 = 3; | ||||
|     // Backup or restore success
 | ||||
|     public static final int STATE_SUCCESS                      = 4; | ||||
| 
 | ||||
|     private TextExport mTextExport; | ||||
| 
 | ||||
|     private BackupUtils(Context context) { | ||||
|         mTextExport = new TextExport(context); | ||||
|     } | ||||
| 
 | ||||
|     private static boolean externalStorageAvailable() { | ||||
|         return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); | ||||
|     } | ||||
| 
 | ||||
|     public int exportToText() { | ||||
|         return mTextExport.exportToText(); | ||||
|     } | ||||
| 
 | ||||
|     public String getExportedTextFileName() { | ||||
|         return mTextExport.mFileName; | ||||
|     } | ||||
| 
 | ||||
|     public String getExportedTextFileDir() { | ||||
|         return mTextExport.mFileDirectory; | ||||
|     } | ||||
| 
 | ||||
|     private static class TextExport { | ||||
|         private static final String[] NOTE_PROJECTION = { | ||||
|                 NoteColumns.ID, | ||||
|                 NoteColumns.MODIFIED_DATE, | ||||
|                 NoteColumns.SNIPPET, | ||||
|                 NoteColumns.TYPE | ||||
|         }; | ||||
| 
 | ||||
|         private static final int NOTE_COLUMN_ID = 0; | ||||
| 
 | ||||
|         private static final int NOTE_COLUMN_MODIFIED_DATE = 1; | ||||
| 
 | ||||
|         private static final int NOTE_COLUMN_SNIPPET = 2; | ||||
| 
 | ||||
|         private static final String[] DATA_PROJECTION = { | ||||
|                 DataColumns.CONTENT, | ||||
|                 DataColumns.MIME_TYPE, | ||||
|                 DataColumns.DATA1, | ||||
|                 DataColumns.DATA2, | ||||
|                 DataColumns.DATA3, | ||||
|                 DataColumns.DATA4, | ||||
|         }; | ||||
| 
 | ||||
|         private static final int DATA_COLUMN_CONTENT = 0; | ||||
| 
 | ||||
|         private static final int DATA_COLUMN_MIME_TYPE = 1; | ||||
| 
 | ||||
|         private static final int DATA_COLUMN_CALL_DATE = 2; | ||||
| 
 | ||||
|         private static final int DATA_COLUMN_PHONE_NUMBER = 4; | ||||
| 
 | ||||
|         private final String [] TEXT_FORMAT; | ||||
|         private static final int FORMAT_FOLDER_NAME          = 0; | ||||
|         private static final int FORMAT_NOTE_DATE            = 1; | ||||
|         private static final int FORMAT_NOTE_CONTENT         = 2; | ||||
| 
 | ||||
|         private Context mContext; | ||||
|         private String mFileName; | ||||
|         private String mFileDirectory; | ||||
| 
 | ||||
|         public TextExport(Context context) { | ||||
|             TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); | ||||
|             mContext = context; | ||||
|             mFileName = ""; | ||||
|             mFileDirectory = ""; | ||||
|         } | ||||
| 
 | ||||
|         private String getFormat(int id) { | ||||
|             return TEXT_FORMAT[id]; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * Export the folder identified by folder id to text | ||||
|          */ | ||||
|         private void exportFolderToText(String folderId, PrintStream ps) { | ||||
|             // Query notes belong to this folder
 | ||||
|             Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, | ||||
|                     NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { | ||||
|                         folderId | ||||
|                     }, null); | ||||
| 
 | ||||
|             if (notesCursor != null) { | ||||
|                 if (notesCursor.moveToFirst()) { | ||||
|                     do { | ||||
|                         // Print note's last modified date
 | ||||
|                         ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( | ||||
|                                 mContext.getString(R.string.format_datetime_mdhm), | ||||
|                                 notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); | ||||
|                         // Query data belong to this note
 | ||||
|                         String noteId = notesCursor.getString(NOTE_COLUMN_ID); | ||||
|                         exportNoteToText(noteId, ps); | ||||
|                     } while (notesCursor.moveToNext()); | ||||
|                 } | ||||
|                 notesCursor.close(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * Export note identified by id to a print stream | ||||
|          */ | ||||
|         private void exportNoteToText(String noteId, PrintStream ps) { | ||||
|             Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, | ||||
|                     DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { | ||||
|                         noteId | ||||
|                     }, null); | ||||
| 
 | ||||
|             if (dataCursor != null) { | ||||
|                 if (dataCursor.moveToFirst()) { | ||||
|                     do { | ||||
|                         String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); | ||||
|                         if (DataConstants.CALL_NOTE.equals(mimeType)) { | ||||
|                             // Print phone number
 | ||||
|                             String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); | ||||
|                             long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); | ||||
|                             String location = dataCursor.getString(DATA_COLUMN_CONTENT); | ||||
| 
 | ||||
|                             if (!TextUtils.isEmpty(phoneNumber)) { | ||||
|                                 ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), | ||||
|                                         phoneNumber)); | ||||
|                             } | ||||
|                             // Print call date
 | ||||
|                             ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat | ||||
|                                     .format(mContext.getString(R.string.format_datetime_mdhm), | ||||
|                                             callDate))); | ||||
|                             // Print call attachment location
 | ||||
|                             if (!TextUtils.isEmpty(location)) { | ||||
|                                 ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), | ||||
|                                         location)); | ||||
|                             } | ||||
|                         } else if (DataConstants.NOTE.equals(mimeType)) { | ||||
|                             String content = dataCursor.getString(DATA_COLUMN_CONTENT); | ||||
|                             if (!TextUtils.isEmpty(content)) { | ||||
|                                 ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), | ||||
|                                         content)); | ||||
|                             } | ||||
|                         } | ||||
|                     } while (dataCursor.moveToNext()); | ||||
|                 } | ||||
|                 dataCursor.close(); | ||||
|             } | ||||
|             // print a line separator between note
 | ||||
|             try { | ||||
|                 ps.write(new byte[] { | ||||
|                         Character.LINE_SEPARATOR, Character.LETTER_NUMBER | ||||
|                 }); | ||||
|             } catch (IOException e) { | ||||
|                 Log.e(TAG, e.toString()); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * Note will be exported as text which is user readable | ||||
|          */ | ||||
|         public int exportToText() { | ||||
|             if (!externalStorageAvailable()) { | ||||
|                 Log.d(TAG, "Media was not mounted"); | ||||
|                 return STATE_SD_CARD_UNMOUONTED; | ||||
|             } | ||||
| 
 | ||||
|             PrintStream ps = getExportToTextPrintStream(); | ||||
|             if (ps == null) { | ||||
|                 Log.e(TAG, "get print stream error"); | ||||
|                 return STATE_SYSTEM_ERROR; | ||||
|             } | ||||
|             // First export folder and its notes
 | ||||
|             Cursor folderCursor = mContext.getContentResolver().query( | ||||
|                     Notes.CONTENT_NOTE_URI, | ||||
|                     NOTE_PROJECTION, | ||||
|                     "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " | ||||
|                             + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " | ||||
|                             + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); | ||||
| 
 | ||||
|             if (folderCursor != null) { | ||||
|                 if (folderCursor.moveToFirst()) { | ||||
|                     do { | ||||
|                         // Print folder's name
 | ||||
|                         String folderName = ""; | ||||
|                         if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { | ||||
|                             folderName = mContext.getString(R.string.call_record_folder_name); | ||||
|                         } else { | ||||
|                             folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); | ||||
|                         } | ||||
|                         if (!TextUtils.isEmpty(folderName)) { | ||||
|                             ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); | ||||
|                         } | ||||
|                         String folderId = folderCursor.getString(NOTE_COLUMN_ID); | ||||
|                         exportFolderToText(folderId, ps); | ||||
|                     } while (folderCursor.moveToNext()); | ||||
|                 } | ||||
|                 folderCursor.close(); | ||||
|             } | ||||
| 
 | ||||
|             // Export notes in root's folder
 | ||||
|             Cursor noteCursor = mContext.getContentResolver().query( | ||||
|                     Notes.CONTENT_NOTE_URI, | ||||
|                     NOTE_PROJECTION, | ||||
|                     NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID | ||||
|                             + "=0", null, null); | ||||
| 
 | ||||
|             if (noteCursor != null) { | ||||
|                 if (noteCursor.moveToFirst()) { | ||||
|                     do { | ||||
|                         ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( | ||||
|                                 mContext.getString(R.string.format_datetime_mdhm), | ||||
|                                 noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); | ||||
|                         // Query data belong to this note
 | ||||
|                         String noteId = noteCursor.getString(NOTE_COLUMN_ID); | ||||
|                         exportNoteToText(noteId, ps); | ||||
|                     } while (noteCursor.moveToNext()); | ||||
|                 } | ||||
|                 noteCursor.close(); | ||||
|             } | ||||
|             ps.close(); | ||||
| 
 | ||||
|             return STATE_SUCCESS; | ||||
|         } | ||||
| 
 | ||||
|         /** | ||||
|          * Get a print stream pointed to the file {@generateExportedTextFile} | ||||
|          */ | ||||
|         private PrintStream getExportToTextPrintStream() { | ||||
|             File file = generateFileMountedOnSDcard(mContext, R.string.file_path, | ||||
|                     R.string.file_name_txt_format); | ||||
|             if (file == null) { | ||||
|                 Log.e(TAG, "create file to exported failed"); | ||||
|                 return null; | ||||
|             } | ||||
|             mFileName = file.getName(); | ||||
|             mFileDirectory = mContext.getString(R.string.file_path); | ||||
|             PrintStream ps = null; | ||||
|             try { | ||||
|                 FileOutputStream fos = new FileOutputStream(file); | ||||
|                 ps = new PrintStream(fos); | ||||
|             } catch (FileNotFoundException e) { | ||||
|                 e.printStackTrace(); | ||||
|                 return null; | ||||
|             } catch (NullPointerException e) { | ||||
|                 e.printStackTrace(); | ||||
|                 return null; | ||||
|             } | ||||
|             return ps; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Generate the text file to store imported data | ||||
|      */ | ||||
|     private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { | ||||
|         StringBuilder sb = new StringBuilder(); | ||||
|         sb.append(Environment.getExternalStorageDirectory()); | ||||
|         sb.append(context.getString(filePathResId)); | ||||
|         File filedir = new File(sb.toString()); | ||||
|         sb.append(context.getString( | ||||
|                 fileNameFormatResId, | ||||
|                 DateFormat.format(context.getString(R.string.format_date_ymd), | ||||
|                         System.currentTimeMillis()))); | ||||
|         File file = new File(sb.toString()); | ||||
| 
 | ||||
|         try { | ||||
|             if (!filedir.exists()) { | ||||
|                 filedir.mkdir(); | ||||
|             } | ||||
|             if (!file.exists()) { | ||||
|                 file.createNewFile(); | ||||
|             } | ||||
|             return file; | ||||
|         } catch (SecurityException e) { | ||||
|             e.printStackTrace(); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| @ -0,0 +1,295 @@ | ||||
| /* | ||||
|  * 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.tool; | ||||
| 
 | ||||
| import android.content.ContentProviderOperation; | ||||
| import android.content.ContentProviderResult; | ||||
| import android.content.ContentResolver; | ||||
| import android.content.ContentUris; | ||||
| import android.content.ContentValues; | ||||
| import android.content.OperationApplicationException; | ||||
| import android.database.Cursor; | ||||
| 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.NoteColumns; | ||||
| import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; | ||||
| 
 | ||||
| import java.util.ArrayList; | ||||
| import java.util.HashSet; | ||||
| 
 | ||||
| 
 | ||||
| public class DataUtils { | ||||
|     public static final String TAG = "DataUtils"; | ||||
|     public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) { | ||||
|         if (ids == null) { | ||||
|             Log.d(TAG, "the ids is null"); | ||||
|             return true; | ||||
|         } | ||||
|         if (ids.size() == 0) { | ||||
|             Log.d(TAG, "no id is in the hashset"); | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); | ||||
|         for (long id : ids) { | ||||
|             if(id == Notes.ID_ROOT_FOLDER) { | ||||
|                 Log.e(TAG, "Don't delete system folder root"); | ||||
|                 continue; | ||||
|             } | ||||
|             ContentProviderOperation.Builder builder = ContentProviderOperation | ||||
|                     .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); | ||||
|             operationList.add(builder.build()); | ||||
|         } | ||||
|         try { | ||||
|             ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); | ||||
|             if (results == null || results.length == 0 || results[0] == null) { | ||||
|                 Log.d(TAG, "delete notes failed, ids:" + ids.toString()); | ||||
|                 return false; | ||||
|             } | ||||
|             return true; | ||||
|         } catch (RemoteException e) { | ||||
|             Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); | ||||
|         } catch (OperationApplicationException e) { | ||||
|             Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { | ||||
|         ContentValues values = new ContentValues(); | ||||
|         values.put(NoteColumns.PARENT_ID, desFolderId); | ||||
|         values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); | ||||
|         values.put(NoteColumns.LOCAL_MODIFIED, 1); | ||||
|         resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); | ||||
|     } | ||||
| 
 | ||||
|     public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids, | ||||
|             long folderId) { | ||||
|         if (ids == null) { | ||||
|             Log.d(TAG, "the ids is null"); | ||||
|             return true; | ||||
|         } | ||||
| 
 | ||||
|         ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); | ||||
|         for (long id : ids) { | ||||
|             ContentProviderOperation.Builder builder = ContentProviderOperation | ||||
|                     .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); | ||||
|             builder.withValue(NoteColumns.PARENT_ID, folderId); | ||||
|             builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); | ||||
|             operationList.add(builder.build()); | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); | ||||
|             if (results == null || results.length == 0 || results[0] == null) { | ||||
|                 Log.d(TAG, "delete notes failed, ids:" + ids.toString()); | ||||
|                 return false; | ||||
|             } | ||||
|             return true; | ||||
|         } catch (RemoteException e) { | ||||
|             Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); | ||||
|         } catch (OperationApplicationException e) { | ||||
|             Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} | ||||
|      */ | ||||
|     public static int getUserFolderCount(ContentResolver resolver) { | ||||
|         Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, | ||||
|                 new String[] { "COUNT(*)" }, | ||||
|                 NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", | ||||
|                 new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, | ||||
|                 null); | ||||
| 
 | ||||
|         int count = 0; | ||||
|         if(cursor != null) { | ||||
|             if(cursor.moveToFirst()) { | ||||
|                 try { | ||||
|                     count = cursor.getInt(0); | ||||
|                 } catch (IndexOutOfBoundsException e) { | ||||
|                     Log.e(TAG, "get folder count failed:" + e.toString()); | ||||
|                 } finally { | ||||
|                     cursor.close(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return count; | ||||
|     } | ||||
| 
 | ||||
|     public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { | ||||
|         Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), | ||||
|                 null, | ||||
|                 NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, | ||||
|                 new String [] {String.valueOf(type)}, | ||||
|                 null); | ||||
| 
 | ||||
|         boolean exist = false; | ||||
|         if (cursor != null) { | ||||
|             if (cursor.getCount() > 0) { | ||||
|                 exist = true; | ||||
|             } | ||||
|             cursor.close(); | ||||
|         } | ||||
|         return exist; | ||||
|     } | ||||
| 
 | ||||
|     public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { | ||||
|         Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), | ||||
|                 null, null, null, null); | ||||
| 
 | ||||
|         boolean exist = false; | ||||
|         if (cursor != null) { | ||||
|             if (cursor.getCount() > 0) { | ||||
|                 exist = true; | ||||
|             } | ||||
|             cursor.close(); | ||||
|         } | ||||
|         return exist; | ||||
|     } | ||||
| 
 | ||||
|     public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { | ||||
|         Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), | ||||
|                 null, null, null, null); | ||||
| 
 | ||||
|         boolean exist = false; | ||||
|         if (cursor != null) { | ||||
|             if (cursor.getCount() > 0) { | ||||
|                 exist = true; | ||||
|             } | ||||
|             cursor.close(); | ||||
|         } | ||||
|         return exist; | ||||
|     } | ||||
| 
 | ||||
|     public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { | ||||
|         Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, | ||||
|                 NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + | ||||
|                 " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + | ||||
|                 " AND " + NoteColumns.SNIPPET + "=?", | ||||
|                 new String[] { name }, null); | ||||
|         boolean exist = false; | ||||
|         if(cursor != null) { | ||||
|             if(cursor.getCount() > 0) { | ||||
|                 exist = true; | ||||
|             } | ||||
|             cursor.close(); | ||||
|         } | ||||
|         return exist; | ||||
|     } | ||||
| 
 | ||||
|     public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) { | ||||
|         Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, | ||||
|                 new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, | ||||
|                 NoteColumns.PARENT_ID + "=?", | ||||
|                 new String[] { String.valueOf(folderId) }, | ||||
|                 null); | ||||
| 
 | ||||
|         HashSet<AppWidgetAttribute> set = null; | ||||
|         if (c != null) { | ||||
|             if (c.moveToFirst()) { | ||||
|                 set = new HashSet<AppWidgetAttribute>(); | ||||
|                 do { | ||||
|                     try { | ||||
|                         AppWidgetAttribute widget = new AppWidgetAttribute(); | ||||
|                         widget.widgetId = c.getInt(0); | ||||
|                         widget.widgetType = c.getInt(1); | ||||
|                         set.add(widget); | ||||
|                     } catch (IndexOutOfBoundsException e) { | ||||
|                         Log.e(TAG, e.toString()); | ||||
|                     } | ||||
|                 } while (c.moveToNext()); | ||||
|             } | ||||
|             c.close(); | ||||
|         } | ||||
|         return set; | ||||
|     } | ||||
| 
 | ||||
|     public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { | ||||
|         Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, | ||||
|                 new String [] { CallNote.PHONE_NUMBER }, | ||||
|                 CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?", | ||||
|                 new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE }, | ||||
|                 null); | ||||
| 
 | ||||
|         if (cursor != null && cursor.moveToFirst()) { | ||||
|             try { | ||||
|                 return cursor.getString(0); | ||||
|             } catch (IndexOutOfBoundsException e) { | ||||
|                 Log.e(TAG, "Get call number fails " + e.toString()); | ||||
|             } finally { | ||||
|                 cursor.close(); | ||||
|             } | ||||
|         } | ||||
|         return ""; | ||||
|     } | ||||
| 
 | ||||
|     public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { | ||||
|         Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, | ||||
|                 new String [] { CallNote.NOTE_ID }, | ||||
|                 CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL(" | ||||
|                 + CallNote.PHONE_NUMBER + ",?)", | ||||
|                 new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber }, | ||||
|                 null); | ||||
| 
 | ||||
|         if (cursor != null) { | ||||
|             if (cursor.moveToFirst()) { | ||||
|                 try { | ||||
|                     return cursor.getLong(0); | ||||
|                 } catch (IndexOutOfBoundsException e) { | ||||
|                     Log.e(TAG, "Get call note id fails " + e.toString()); | ||||
|                 } | ||||
|             } | ||||
|             cursor.close(); | ||||
|         } | ||||
|         return 0; | ||||
|     } | ||||
| 
 | ||||
|     public static String getSnippetById(ContentResolver resolver, long noteId) { | ||||
|         Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, | ||||
|                 new String [] { NoteColumns.SNIPPET }, | ||||
|                 NoteColumns.ID + "=?", | ||||
|                 new String [] { String.valueOf(noteId)}, | ||||
|                 null); | ||||
| 
 | ||||
|         if (cursor != null) { | ||||
|             String snippet = ""; | ||||
|             if (cursor.moveToFirst()) { | ||||
|                 snippet = cursor.getString(0); | ||||
|             } | ||||
|             cursor.close(); | ||||
|             return snippet; | ||||
|         } | ||||
|         throw new IllegalArgumentException("Note is not found with id: " + noteId); | ||||
|     } | ||||
| 
 | ||||
|     public static String getFormattedSnippet(String snippet) { | ||||
|         if (snippet != null) { | ||||
|             snippet = snippet.trim(); | ||||
|             int index = snippet.indexOf('\n'); | ||||
|             if (index != -1) { | ||||
|                 snippet = snippet.substring(0, index); | ||||
|             } | ||||
|         } | ||||
|         return snippet; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,113 @@ | ||||
| /* | ||||
|  * 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.tool; | ||||
| 
 | ||||
| public class GTaskStringUtils { | ||||
| 
 | ||||
|     public final static String GTASK_JSON_ACTION_ID = "action_id"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_ACTION_LIST = "action_list"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_ACTION_TYPE = "action_type"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_CREATOR_ID = "creator_id"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_COMPLETED = "completed"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_DELETED = "deleted"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_DEST_LIST = "dest_list"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_GET_DELETED = "get_deleted"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_ID = "id"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_INDEX = "index"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_LIST_ID = "list_id"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_LISTS = "lists"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_NAME = "name"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_NEW_ID = "new_id"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_NOTES = "notes"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_PARENT_ID = "parent_id"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_RESULTS = "results"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_SOURCE_LIST = "source_list"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_TASKS = "tasks"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_TYPE = "type"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_TYPE_TASK = "TASK"; | ||||
| 
 | ||||
|     public final static String GTASK_JSON_USER = "user"; | ||||
| 
 | ||||
|     public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; | ||||
| 
 | ||||
|     public final static String FOLDER_DEFAULT = "Default"; | ||||
| 
 | ||||
|     public final static String FOLDER_CALL_NOTE = "Call_Note"; | ||||
| 
 | ||||
|     public final static String FOLDER_META = "METADATA"; | ||||
| 
 | ||||
|     public final static String META_HEAD_GTASK_ID = "meta_gid"; | ||||
| 
 | ||||
|     public final static String META_HEAD_NOTE = "meta_note"; | ||||
| 
 | ||||
|     public final static String META_HEAD_DATA = "meta_data"; | ||||
| 
 | ||||
|     public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; | ||||
| 
 | ||||
| } | ||||
| @ -0,0 +1,181 @@ | ||||
| /* | ||||
|  * 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.tool; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.preference.PreferenceManager; | ||||
| 
 | ||||
| import net.micode.notes.R; | ||||
| import net.micode.notes.ui.NotesPreferenceActivity; | ||||
| 
 | ||||
| public class ResourceParser { | ||||
| 
 | ||||
|     public static final int YELLOW           = 0; | ||||
|     public static final int BLUE             = 1; | ||||
|     public static final int WHITE            = 2; | ||||
|     public static final int GREEN            = 3; | ||||
|     public static final int RED              = 4; | ||||
| 
 | ||||
|     public static final int BG_DEFAULT_COLOR = YELLOW; | ||||
| 
 | ||||
|     public static final int TEXT_SMALL       = 0; | ||||
|     public static final int TEXT_MEDIUM      = 1; | ||||
|     public static final int TEXT_LARGE       = 2; | ||||
|     public static final int TEXT_SUPER       = 3; | ||||
| 
 | ||||
|     public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; | ||||
| 
 | ||||
|     public static class NoteBgResources { | ||||
|         private final static int [] BG_EDIT_RESOURCES = new int [] { | ||||
|             R.drawable.edit_yellow, | ||||
|             R.drawable.edit_blue, | ||||
|             R.drawable.edit_white, | ||||
|             R.drawable.edit_green, | ||||
|             R.drawable.edit_red | ||||
|         }; | ||||
| 
 | ||||
|         private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { | ||||
|             R.drawable.edit_title_yellow, | ||||
|             R.drawable.edit_title_blue, | ||||
|             R.drawable.edit_title_white, | ||||
|             R.drawable.edit_title_green, | ||||
|             R.drawable.edit_title_red | ||||
|         }; | ||||
| 
 | ||||
|         public static int getNoteBgResource(int id) { | ||||
|             return BG_EDIT_RESOURCES[id]; | ||||
|         } | ||||
| 
 | ||||
|         public static int getNoteTitleBgResource(int id) { | ||||
|             return BG_EDIT_TITLE_RESOURCES[id]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static int getDefaultBgId(Context context) { | ||||
|         if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( | ||||
|                 NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { | ||||
|             return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length); | ||||
|         } else { | ||||
|             return BG_DEFAULT_COLOR; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static class NoteItemBgResources { | ||||
|         private final static int [] BG_FIRST_RESOURCES = new int [] { | ||||
|             R.drawable.list_yellow_up, | ||||
|             R.drawable.list_blue_up, | ||||
|             R.drawable.list_white_up, | ||||
|             R.drawable.list_green_up, | ||||
|             R.drawable.list_red_up | ||||
|         }; | ||||
| 
 | ||||
|         private final static int [] BG_NORMAL_RESOURCES = new int [] { | ||||
|             R.drawable.list_yellow_middle, | ||||
|             R.drawable.list_blue_middle, | ||||
|             R.drawable.list_white_middle, | ||||
|             R.drawable.list_green_middle, | ||||
|             R.drawable.list_red_middle | ||||
|         }; | ||||
| 
 | ||||
|         private final static int [] BG_LAST_RESOURCES = new int [] { | ||||
|             R.drawable.list_yellow_down, | ||||
|             R.drawable.list_blue_down, | ||||
|             R.drawable.list_white_down, | ||||
|             R.drawable.list_green_down, | ||||
|             R.drawable.list_red_down, | ||||
|         }; | ||||
| 
 | ||||
|         private final static int [] BG_SINGLE_RESOURCES = new int [] { | ||||
|             R.drawable.list_yellow_single, | ||||
|             R.drawable.list_blue_single, | ||||
|             R.drawable.list_white_single, | ||||
|             R.drawable.list_green_single, | ||||
|             R.drawable.list_red_single | ||||
|         }; | ||||
| 
 | ||||
|         public static int getNoteBgFirstRes(int id) { | ||||
|             return BG_FIRST_RESOURCES[id]; | ||||
|         } | ||||
| 
 | ||||
|         public static int getNoteBgLastRes(int id) { | ||||
|             return BG_LAST_RESOURCES[id]; | ||||
|         } | ||||
| 
 | ||||
|         public static int getNoteBgSingleRes(int id) { | ||||
|             return BG_SINGLE_RESOURCES[id]; | ||||
|         } | ||||
| 
 | ||||
|         public static int getNoteBgNormalRes(int id) { | ||||
|             return BG_NORMAL_RESOURCES[id]; | ||||
|         } | ||||
| 
 | ||||
|         public static int getFolderBgRes() { | ||||
|             return R.drawable.list_folder; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static class WidgetBgResources { | ||||
|         private final static int [] BG_2X_RESOURCES = new int [] { | ||||
|             R.drawable.widget_2x_yellow, | ||||
|             R.drawable.widget_2x_blue, | ||||
|             R.drawable.widget_2x_white, | ||||
|             R.drawable.widget_2x_green, | ||||
|             R.drawable.widget_2x_red, | ||||
|         }; | ||||
| 
 | ||||
|         public static int getWidget2xBgResource(int id) { | ||||
|             return BG_2X_RESOURCES[id]; | ||||
|         } | ||||
| 
 | ||||
|         private final static int [] BG_4X_RESOURCES = new int [] { | ||||
|             R.drawable.widget_4x_yellow, | ||||
|             R.drawable.widget_4x_blue, | ||||
|             R.drawable.widget_4x_white, | ||||
|             R.drawable.widget_4x_green, | ||||
|             R.drawable.widget_4x_red | ||||
|         }; | ||||
| 
 | ||||
|         public static int getWidget4xBgResource(int id) { | ||||
|             return BG_4X_RESOURCES[id]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public static class TextAppearanceResources { | ||||
|         private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { | ||||
|             R.style.TextAppearanceNormal, | ||||
|             R.style.TextAppearanceMedium, | ||||
|             R.style.TextAppearanceLarge, | ||||
|             R.style.TextAppearanceSuper | ||||
|         }; | ||||
| 
 | ||||
|         public static int getTexAppearanceResource(int id) { | ||||
|             /** | ||||
|              * HACKME: Fix bug of store the resource id in shared preference. | ||||
|              * The id may larger than the length of resources, in this case, | ||||
|              * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} | ||||
|              */ | ||||
|             if (id >= TEXTAPPEARANCE_RESOURCES.length) { | ||||
|                 return BG_DEFAULT_FONT_SIZE; | ||||
|             } | ||||
|             return TEXTAPPEARANCE_RESOURCES[id]; | ||||
|         } | ||||
| 
 | ||||
|         public static int getResourcesSize() { | ||||
|             return TEXTAPPEARANCE_RESOURCES.length; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,165 @@ | ||||
| /* | ||||
|  * 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.app.Activity; | ||||
| import android.app.AlertDialog; | ||||
| import android.content.Context; | ||||
| import android.content.DialogInterface; | ||||
| import android.content.DialogInterface.OnClickListener; | ||||
| import android.content.DialogInterface.OnDismissListener; | ||||
| import android.content.Intent; | ||||
| import android.media.AudioManager; | ||||
| import android.media.MediaPlayer; | ||||
| import android.media.RingtoneManager; | ||||
| import android.net.Uri; | ||||
| import android.os.Bundle; | ||||
| import android.os.PowerManager; | ||||
| import android.provider.Settings; | ||||
| import android.view.Window; | ||||
| import android.view.WindowManager; | ||||
| 
 | ||||
| import net.micode.notes.R; | ||||
| import net.micode.notes.data.Notes; | ||||
| import net.micode.notes.tool.DataUtils; | ||||
| 
 | ||||
| import java.io.IOException; | ||||
| 
 | ||||
| 
 | ||||
| // AlarmAlertActivity 类,用于显示闹钟提醒界面
 | ||||
| public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { | ||||
|     private long mNoteId;  // 笔记的唯一标识符
 | ||||
|     private String mSnippet;  // 笔记的片段内容
 | ||||
|     private static final int SNIPPET_PREW_MAX_LEN = 60;  // 笔记片段的最大长度
 | ||||
|     MediaPlayer mPlayer;  // 播放器对象
 | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onCreate(Bundle savedInstanceState) { | ||||
|         super.onCreate(savedInstanceState); | ||||
|         requestWindowFeature(Window.FEATURE_NO_TITLE); | ||||
| 
 | ||||
|         final Window win = getWindow(); | ||||
|         win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); | ||||
| 
 | ||||
|         if (!isScreenOn()) { | ||||
|             win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | ||||
|                     | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON | ||||
|                     | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON | ||||
|                     | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); | ||||
|         } | ||||
| 
 | ||||
|         Intent intent = getIntent(); | ||||
| 
 | ||||
|         try { | ||||
|             // 从意图中获取笔记的唯一标识符,并根据标识符获取笔记片段内容
 | ||||
|             mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); | ||||
|             mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); | ||||
|             // 如果笔记片段内容超过最大长度,截取并添加附加信息
 | ||||
|             mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, | ||||
|                     SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) | ||||
|                     : mSnippet; | ||||
|         } catch (IllegalArgumentException e) { | ||||
|             e.printStackTrace(); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         mPlayer = new MediaPlayer(); | ||||
|         if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { | ||||
|             // 如果笔记在数据库中可见,显示操作对话框并播放闹钟声音
 | ||||
|             showActionDialog(); | ||||
|             playAlarmSound(); | ||||
|         } else { | ||||
|             finish(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private boolean isScreenOn() { | ||||
|         PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); | ||||
|         return pm.isScreenOn(); | ||||
|     } | ||||
| 
 | ||||
|     private void playAlarmSound() { | ||||
|         // 获取闹钟铃声的 URI
 | ||||
|         Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); | ||||
| 
 | ||||
|         int silentModeStreams = Settings.System.getInt(getContentResolver(), | ||||
|                 Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); | ||||
| 
 | ||||
|         // 根据静音模式设置播放器的音频流类型
 | ||||
|         if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { | ||||
|             mPlayer.setAudioStreamType(silentModeStreams); | ||||
|         } else { | ||||
|             mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); | ||||
|         } | ||||
|         try { | ||||
|             // 设置播放器的数据源,准备播放并循环播放
 | ||||
|             mPlayer.setDataSource(this, url); | ||||
|             mPlayer.prepare(); | ||||
|             mPlayer.setLooping(true); | ||||
|             mPlayer.start(); | ||||
|         } catch (IllegalArgumentException e) { | ||||
|             e.printStackTrace(); | ||||
|         } catch (SecurityException e) { | ||||
|             e.printStackTrace(); | ||||
|         } catch (IllegalStateException e) { | ||||
|             e.printStackTrace(); | ||||
|         } catch (IOException e) { | ||||
|             e.printStackTrace(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void showActionDialog() { | ||||
|         // 显示操作对话框,包括笔记片段内容和按钮
 | ||||
|         AlertDialog.Builder dialog = new AlertDialog.Builder(this); | ||||
|         dialog.setTitle(R.string.app_name); | ||||
|         dialog.setMessage(mSnippet); | ||||
|         dialog.setPositiveButton(R.string.notealert_ok, this); | ||||
|         if (isScreenOn()) { | ||||
|             dialog.setNegativeButton(R.string.notealert_enter, this); | ||||
|         } | ||||
|         dialog.show().setOnDismissListener(this); | ||||
|     } | ||||
| 
 | ||||
|     public void onClick(DialogInterface dialog, int which) { | ||||
|         switch (which) { | ||||
|             case DialogInterface.BUTTON_NEGATIVE: | ||||
|                 // 点击“进入”按钮,打开 NoteEditActivity 并显示当前笔记
 | ||||
|                 Intent intent = new Intent(this, NoteEditActivity.class); | ||||
|                 intent.setAction(Intent.ACTION_VIEW); | ||||
|                 intent.putExtra(Intent.EXTRA_UID, mNoteId); | ||||
|                 startActivity(intent); | ||||
|                 break; | ||||
|             default: | ||||
|                 break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public void onDismiss(DialogInterface dialog) { | ||||
|         // 对话框消失时停止闹钟声音并关闭界面
 | ||||
|         stopAlarmSound(); | ||||
|         finish(); | ||||
|     } | ||||
| 
 | ||||
|     private void stopAlarmSound() { | ||||
|         // 停止并释放播放器对象
 | ||||
|         if (mPlayer != null) { | ||||
|             mPlayer.stop(); | ||||
|             mPlayer.release(); | ||||
|             mPlayer = null; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,62 @@ | ||||
| /* | ||||
|  * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *        http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package net.micode.notes.ui; | ||||
| 
 | ||||
| import android.content.Context; | ||||
| import android.view.Menu; | ||||
| import android.view.MenuItem; | ||||
| import android.view.View; | ||||
| import android.view.View.OnClickListener; | ||||
| import android.widget.Button; | ||||
| import android.widget.PopupMenu; | ||||
| import android.widget.PopupMenu.OnMenuItemClickListener; | ||||
| 
 | ||||
| import net.micode.notes.R; | ||||
| 
 | ||||
| public class DropdownMenu { | ||||
|     private Button mButton; | ||||
|     private PopupMenu mPopupMenu; | ||||
|     private Menu mMenu; | ||||
| 
 | ||||
|     public DropdownMenu(Context context, Button button, int menuId) { | ||||
|         mButton = button; // 保存传入的按钮引用
 | ||||
|         mButton.setBackgroundResource(R.drawable.dropdown_icon); // 设置按钮背景图像
 | ||||
|         mPopupMenu = new PopupMenu(context, mButton); // 创建弹出菜单与按钮关联
 | ||||
|         mMenu = mPopupMenu.getMenu(); // 获取弹出菜单的菜单对象
 | ||||
|         mPopupMenu.getMenuInflater().inflate(menuId, mMenu); // 填充菜单项从指定的menuId
 | ||||
|         mButton.setOnClickListener(new OnClickListener() { | ||||
|             public void onClick(View v) { | ||||
|                 mPopupMenu.show(); // 当按钮被点击时,显示弹出菜单
 | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|      | ||||
|     public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { | ||||
|         if (mPopupMenu != null) { | ||||
|             mPopupMenu.setOnMenuItemClickListener(listener); // 设置弹出菜单的菜单项点击监听器
 | ||||
|         } | ||||
|     } | ||||
|      | ||||
|     public MenuItem findItem(int id) { | ||||
|         return mMenu.findItem(id); // 查找特定ID的菜单项
 | ||||
|     } | ||||
|      | ||||
|     public void setTitle(CharSequence title) { | ||||
|         mButton.setText(title); // 设置按钮的文本标题
 | ||||
|     } | ||||
|      | ||||
| } | ||||
											
												
													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; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,52 @@ | ||||
| /* | ||||
|  * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) | ||||
|  * | ||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * | ||||
|  *        http://www.apache.org/licenses/LICENSE-2.0
 | ||||
|  * | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
| 
 | ||||
| package net.micode.notes.widget; | ||||
| 
 | ||||
| import android.appwidget.AppWidgetManager; | ||||
| import android.content.Context; | ||||
| 
 | ||||
| import net.micode.notes.R; | ||||
| import net.micode.notes.data.Notes; | ||||
| import net.micode.notes.tool.ResourceParser; | ||||
| 
 | ||||
| // NoteWidgetProvider_2x 类是 NoteWidgetProvider 的子类,用于特定类型的小部件
 | ||||
| public class NoteWidgetProvider_2x extends NoteWidgetProvider { | ||||
| 
 | ||||
|     @Override | ||||
|     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { | ||||
|         // 调用父类的 update 方法以执行小部件更新操作
 | ||||
|         super.update(context, appWidgetManager, appWidgetIds); | ||||
|     } | ||||
| 
 | ||||
|     // 获取小部件的布局资源 ID
 | ||||
|     @Override | ||||
|     protected int getLayoutId() { | ||||
|         return R.layout.widget_2x; | ||||
|     } | ||||
| 
 | ||||
|     // 获取与小部件类型相关的背景资源 ID
 | ||||
|     @Override | ||||
|     protected int getBgResourceId(int bgId) { | ||||
|         return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); | ||||
|     } | ||||
| 
 | ||||
|     // 获取小部件的类型,此处返回 Notes.TYPE_WIDGET_2X
 | ||||
|     @Override | ||||
|     protected int getWidgetType() { | ||||
|         return Notes.TYPE_WIDGET_2X; | ||||
|     } | ||||
| } | ||||
					Loading…
					
					
				
		Reference in new issue