| @ -0,0 +1,156 @@ | |||||||
|  | <?xml version="1.0" encoding="utf-8"?> | ||||||
|  | <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||||
|  |     xmlns:tools="http://schemas.android.com/tools"> | ||||||
|  | 
 | ||||||
|  |     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> | ||||||
|  |     <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" /> | ||||||
|  |     <uses-permission android:name="android.permission.INTERNET" /> | ||||||
|  |     <uses-permission android:name="android.permission.READ_CONTACTS" /> | ||||||
|  |     <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> | ||||||
|  |     <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" /> | ||||||
|  |     <uses-permission android:name="android.permission.GET_ACCOUNTS" /> | ||||||
|  |     <uses-permission android:name="android.permission.USE_CREDENTIALS" /> | ||||||
|  |     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> | ||||||
|  | 
 | ||||||
|  |     <application | ||||||
|  |         android:allowBackup="true" | ||||||
|  |         android:dataExtractionRules="@xml/data_extraction_rules" | ||||||
|  |         android:fullBackupContent="@xml/backup_rules" | ||||||
|  |         android:icon="@mipmap/ic_launcher" | ||||||
|  |         android:label="@string/app_name" | ||||||
|  |         android:roundIcon="@mipmap/ic_launcher_round" | ||||||
|  |         android:supportsRtl="true" | ||||||
|  |         android:theme="@style/Theme.MyApplication" | ||||||
|  |         tools:targetApi="31"> | ||||||
|  | 
 | ||||||
|  |         <activity | ||||||
|  |             android:name=".ui.NotesListActivity" | ||||||
|  |             android:configChanges="keyboardHidden|orientation|screenSize" | ||||||
|  |             android:label="@string/app_name" | ||||||
|  |             android:launchMode="singleTop" | ||||||
|  |             android:theme="@style/NoteTheme" | ||||||
|  |             android:uiOptions="splitActionBarWhenNarrow" | ||||||
|  |             android:windowSoftInputMode="adjustPan" | ||||||
|  |             android:exported="true"> | ||||||
|  | 
 | ||||||
|  |             <intent-filter> | ||||||
|  |                 <action android:name="android.intent.action.MAIN" /> | ||||||
|  |                 <category android:name="android.intent.category.LAUNCHER" /> | ||||||
|  |             </intent-filter> | ||||||
|  |         </activity> | ||||||
|  | 
 | ||||||
|  |         <activity | ||||||
|  |             android:name=".ui.NoteEditActivity" | ||||||
|  |             android:configChanges="keyboardHidden|orientation|screenSize" | ||||||
|  |             android:launchMode="singleTop" | ||||||
|  |             android:theme="@style/NoteTheme" | ||||||
|  |             android:exported="true"> | ||||||
|  | 
 | ||||||
|  |             <intent-filter > | ||||||
|  |                 <action android:name="android.intent.action.VIEW" /> | ||||||
|  |                 <category android:name="android.intent.category.DEFAULT" /> | ||||||
|  |                 <data android:mimeType="vnd.android.cursor.item/text_note" /> | ||||||
|  |                 <data android:mimeType="vnd.android.cursor.item/call_note" /> | ||||||
|  |             </intent-filter >> | ||||||
|  | 
 | ||||||
|  |             <intent-filter > | ||||||
|  |                 <action android:name="android.intent.action.INSERT_OR_EDIT" /> | ||||||
|  |                 <category android:name="android.intent.category.DEFAULT" /> | ||||||
|  |                 <data android:mimeType="vnd.android.cursor.item/text_note" /> | ||||||
|  |                 <data android:mimeType="vnd.android.cursor.item/call_note" /> | ||||||
|  |             </intent-filter> | ||||||
|  | 
 | ||||||
|  |             <intent-filter> | ||||||
|  |                 <action android:name="android.intent.action.SEARCH" /> | ||||||
|  |                 <category android:name="android.intent.category.DEFAULT" /> | ||||||
|  |             </intent-filter> | ||||||
|  | 
 | ||||||
|  |             <meta-data | ||||||
|  |                 android:name="android.app.searchable" | ||||||
|  |                 android:resource="@xml/searchable" /> | ||||||
|  |         </activity> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         <provider | ||||||
|  |             android:name="net.micode.notes.data.NotesProvider" | ||||||
|  |             android:authorities="micode_notes" | ||||||
|  |             android:multiprocess="true" /> | ||||||
|  | 
 | ||||||
|  |         <receiver | ||||||
|  |             android:name=".widget.NoteWidgetProvider_2x" | ||||||
|  |             android:label="@string/app_widget2x2" | ||||||
|  |             android:exported="true"> | ||||||
|  |             <intent-filter> | ||||||
|  |                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> | ||||||
|  |                 <action android:name="android.appwidget.action.APPWIDGET_DELETED" /> | ||||||
|  |                 <action android:name="android.intent.action.PRIVACY_MODE_CHANGED" /> | ||||||
|  |             </intent-filter> | ||||||
|  | 
 | ||||||
|  |             <meta-data | ||||||
|  |                 android:name="android.appwidget.provider" | ||||||
|  |                 android:resource="@xml/widget_2x_info" /> | ||||||
|  |         </receiver> | ||||||
|  |         <receiver | ||||||
|  |             android:name=".widget.NoteWidgetProvider_4x" | ||||||
|  |             android:label="@string/app_widget4x4" | ||||||
|  |             android:exported="true"> | ||||||
|  | 
 | ||||||
|  |             <intent-filter> | ||||||
|  |                 <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> | ||||||
|  |                 <action android:name="android.appwidget.action.APPWIDGET_DELETED" /> | ||||||
|  |                 <action android:name="android.intent.action.PRIVACY_MODE_CHANGED" /> | ||||||
|  |             </intent-filter> | ||||||
|  | 
 | ||||||
|  |             <meta-data | ||||||
|  |                 android:name="android.appwidget.provider" | ||||||
|  |                 android:resource="@xml/widget_4x_info" /> | ||||||
|  |         </receiver> | ||||||
|  | 
 | ||||||
|  |         <receiver android:name=".ui.AlarmInitReceiver" | ||||||
|  |             android:exported="true"> | ||||||
|  |             <intent-filter> | ||||||
|  |                 <action android:name="android.intent.action.BOOT_COMPLETED" /> | ||||||
|  |             </intent-filter> | ||||||
|  |         </receiver> | ||||||
|  | 
 | ||||||
|  |         <receiver | ||||||
|  |             android:name="net.micode.notes.ui.AlarmReceiver" | ||||||
|  |             android:process=":remote" > | ||||||
|  |         </receiver> | ||||||
|  | 
 | ||||||
|  |         <activity | ||||||
|  |             android:name=".ui.AlarmAlertActivity" | ||||||
|  |             android:label="@string/app_name" | ||||||
|  |             android:launchMode="singleInstance" | ||||||
|  |             android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" > | ||||||
|  |         </activity> | ||||||
|  | 
 | ||||||
|  |         <activity | ||||||
|  |             android:name="net.micode.notes.ui.NotesPreferenceActivity" | ||||||
|  |             android:label="@string/preferences_title" | ||||||
|  |             android:launchMode="singleTop" | ||||||
|  |             android:theme="@android:style/Theme.Holo.Light" > | ||||||
|  |         </activity> | ||||||
|  | 
 | ||||||
|  |         <service | ||||||
|  |             android:name="net.micode.notes.gtask.remote.GTaskSyncService" | ||||||
|  |             android:exported="false" > | ||||||
|  |         </service> | ||||||
|  | 
 | ||||||
|  |         <meta-data | ||||||
|  |             android:name="android.app.default_searchable" | ||||||
|  |             android:value=".ui.NoteEditActivity" /> | ||||||
|  | 
 | ||||||
|  |         <!--        <activity--> | ||||||
|  |         <!--            android:name=".MainActivity"--> | ||||||
|  |         <!--            android:exported="true">--> | ||||||
|  |         <!--            <intent-filter>--> | ||||||
|  |         <!--                <action android:name="android.intent.action.MAIN" />--> | ||||||
|  | 
 | ||||||
|  |         <!--                <category android:name="android.intent.category.LAUNCHER" />--> | ||||||
|  |         <!--            </intent-filter>--> | ||||||
|  |         <!--        </activity>--> | ||||||
|  | 
 | ||||||
|  |     </application> | ||||||
|  | 
 | ||||||
|  | </manifest> | ||||||
| @ -0,0 +1,24 @@ | |||||||
|  | package net.micode.notes; | ||||||
|  | 
 | ||||||
|  | import android.os.Bundle; | ||||||
|  | 
 | ||||||
|  | import androidx.activity.EdgeToEdge; | ||||||
|  | import androidx.appcompat.app.AppCompatActivity; | ||||||
|  | import androidx.core.graphics.Insets; | ||||||
|  | import androidx.core.view.ViewCompat; | ||||||
|  | import androidx.core.view.WindowInsetsCompat; | ||||||
|  | 
 | ||||||
|  | public class MainActivity extends AppCompatActivity { | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onCreate(Bundle savedInstanceState) { | ||||||
|  |         super.onCreate(savedInstanceState); | ||||||
|  |         EdgeToEdge.enable(this); | ||||||
|  |         setContentView(R.layout.activity_main); | ||||||
|  |         ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { | ||||||
|  |             Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); | ||||||
|  |             v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); | ||||||
|  |             return insets; | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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,142 @@ | |||||||
|  | 
 | ||||||
|  | /* | ||||||
|  |  * 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);
 | ||||||
|  |     //}
 | ||||||
|  | private void showNotification(int tickerId, String content) { | ||||||
|  |     PendingIntent pendingIntent; | ||||||
|  |     if (tickerId != R.string.ticker_success) { | ||||||
|  |         pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, | ||||||
|  |                 NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE); | ||||||
|  |     } else { | ||||||
|  |         pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, | ||||||
|  |                 NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE); | ||||||
|  |     } | ||||||
|  |     Notification.Builder builder = new Notification.Builder(mContext) | ||||||
|  |             .setAutoCancel(true) | ||||||
|  |             .setContentTitle(mContext.getString(R.string.app_name)) | ||||||
|  |             .setContentText(content) | ||||||
|  |             .setContentIntent(pendingIntent) | ||||||
|  |             .setWhen(System.currentTimeMillis()) | ||||||
|  |             .setOngoing(true); | ||||||
|  |     Notification notification=builder.getNotification(); | ||||||
|  |     mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected Integer doInBackground(Void... unused) { | ||||||
|  |         publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity | ||||||
|  |                 .getSyncAccountName(mContext))); | ||||||
|  |         return mTaskManager.sync(mContext, this); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onProgressUpdate(String... progress) { | ||||||
|  |         showNotification(R.string.ticker_syncing, progress[0]); | ||||||
|  |         if (mContext instanceof GTaskSyncService) { | ||||||
|  |             ((GTaskSyncService) mContext).sendBroadcast(progress[0]); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onPostExecute(Integer result) { | ||||||
|  |         if (result == GTaskManager.STATE_SUCCESS) { | ||||||
|  |             showNotification(R.string.ticker_success, mContext.getString( | ||||||
|  |                     R.string.success_sync_account, mTaskManager.getSyncAccount())); | ||||||
|  |             NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); | ||||||
|  |         } else if (result == GTaskManager.STATE_NETWORK_ERROR) { | ||||||
|  |             showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); | ||||||
|  |         } else if (result == GTaskManager.STATE_INTERNAL_ERROR) { | ||||||
|  |             showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); | ||||||
|  |         } else if (result == GTaskManager.STATE_SYNC_CANCELLED) { | ||||||
|  |             showNotification(R.string.ticker_cancel, mContext | ||||||
|  |                     .getString(R.string.error_sync_cancelled)); | ||||||
|  |         } | ||||||
|  |         if (mOnCompleteListener != null) { | ||||||
|  |             new Thread(new Runnable() { | ||||||
|  | 
 | ||||||
|  |                 public void run() { | ||||||
|  |                     mOnCompleteListener.onComplete(); | ||||||
|  |                 } | ||||||
|  |             }).start(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,585 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *        http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package net.micode.notes.gtask.remote; | ||||||
|  | 
 | ||||||
|  | import android.accounts.Account; | ||||||
|  | import android.accounts.AccountManager; | ||||||
|  | import android.accounts.AccountManagerFuture; | ||||||
|  | import android.app.Activity; | ||||||
|  | import android.os.Bundle; | ||||||
|  | import android.text.TextUtils; | ||||||
|  | import android.util.Log; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.gtask.data.Node; | ||||||
|  | import net.micode.notes.gtask.data.Task; | ||||||
|  | import net.micode.notes.gtask.data.TaskList; | ||||||
|  | import net.micode.notes.gtask.exception.ActionFailureException; | ||||||
|  | import net.micode.notes.gtask.exception.NetworkFailureException; | ||||||
|  | import net.micode.notes.tool.GTaskStringUtils; | ||||||
|  | import net.micode.notes.ui.NotesPreferenceActivity; | ||||||
|  | 
 | ||||||
|  | import org.apache.http.HttpEntity; | ||||||
|  | import org.apache.http.HttpResponse; | ||||||
|  | import org.apache.http.client.ClientProtocolException; | ||||||
|  | import org.apache.http.client.entity.UrlEncodedFormEntity; | ||||||
|  | import org.apache.http.client.methods.HttpGet; | ||||||
|  | import org.apache.http.client.methods.HttpPost; | ||||||
|  | import org.apache.http.cookie.Cookie; | ||||||
|  | import org.apache.http.impl.client.BasicCookieStore; | ||||||
|  | import org.apache.http.impl.client.DefaultHttpClient; | ||||||
|  | import org.apache.http.message.BasicNameValuePair; | ||||||
|  | import org.apache.http.params.BasicHttpParams; | ||||||
|  | import org.apache.http.params.HttpConnectionParams; | ||||||
|  | import org.apache.http.params.HttpParams; | ||||||
|  | import org.apache.http.params.HttpProtocolParams; | ||||||
|  | import org.json.JSONArray; | ||||||
|  | import org.json.JSONException; | ||||||
|  | import org.json.JSONObject; | ||||||
|  | 
 | ||||||
|  | import java.io.BufferedReader; | ||||||
|  | import java.io.IOException; | ||||||
|  | import java.io.InputStream; | ||||||
|  | import java.io.InputStreamReader; | ||||||
|  | import java.util.LinkedList; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.zip.GZIPInputStream; | ||||||
|  | import java.util.zip.Inflater; | ||||||
|  | import java.util.zip.InflaterInputStream; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class GTaskClient { | ||||||
|  |     private static final String TAG = GTaskClient.class.getSimpleName(); | ||||||
|  | 
 | ||||||
|  |     private static final String GTASK_URL = "https://mail.google.com/tasks/"; | ||||||
|  | 
 | ||||||
|  |     private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; | ||||||
|  | 
 | ||||||
|  |     private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; | ||||||
|  | 
 | ||||||
|  |     private static GTaskClient mInstance = null; | ||||||
|  | 
 | ||||||
|  |     private DefaultHttpClient mHttpClient; | ||||||
|  | 
 | ||||||
|  |     private String mGetUrl; | ||||||
|  | 
 | ||||||
|  |     private String mPostUrl; | ||||||
|  | 
 | ||||||
|  |     private long mClientVersion; | ||||||
|  | 
 | ||||||
|  |     private boolean mLoggedin; | ||||||
|  | 
 | ||||||
|  |     private long mLastLoginTime; | ||||||
|  | 
 | ||||||
|  |     private int mActionId; | ||||||
|  | 
 | ||||||
|  |     private Account mAccount; | ||||||
|  | 
 | ||||||
|  |     private JSONArray mUpdateArray; | ||||||
|  | 
 | ||||||
|  |     private GTaskClient() { | ||||||
|  |         mHttpClient = null; | ||||||
|  |         mGetUrl = GTASK_GET_URL; | ||||||
|  |         mPostUrl = GTASK_POST_URL; | ||||||
|  |         mClientVersion = -1; | ||||||
|  |         mLoggedin = false; | ||||||
|  |         mLastLoginTime = 0; | ||||||
|  |         mActionId = 1; | ||||||
|  |         mAccount = null; | ||||||
|  |         mUpdateArray = null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static synchronized GTaskClient getInstance() { | ||||||
|  |         if (mInstance == null) { | ||||||
|  |             mInstance = new GTaskClient(); | ||||||
|  |         } | ||||||
|  |         return mInstance; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean login(Activity activity) { | ||||||
|  |         // we suppose that the cookie would expire after 5 minutes
 | ||||||
|  |         // then we need to re-login
 | ||||||
|  |         final long interval = 1000 * 60 * 5; | ||||||
|  |         if (mLastLoginTime + interval < System.currentTimeMillis()) { | ||||||
|  |             mLoggedin = false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // need to re-login after account switch
 | ||||||
|  |         if (mLoggedin | ||||||
|  |                 && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity | ||||||
|  |                         .getSyncAccountName(activity))) { | ||||||
|  |             mLoggedin = false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (mLoggedin) { | ||||||
|  |             Log.d(TAG, "already logged in"); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         mLastLoginTime = System.currentTimeMillis(); | ||||||
|  |         String authToken = loginGoogleAccount(activity, false); | ||||||
|  |         if (authToken == null) { | ||||||
|  |             Log.e(TAG, "login google account failed"); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // login with custom domain if necessary
 | ||||||
|  |         if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() | ||||||
|  |                 .endsWith("googlemail.com"))) { | ||||||
|  |             StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); | ||||||
|  |             int index = mAccount.name.indexOf('@') + 1; | ||||||
|  |             String suffix = mAccount.name.substring(index); | ||||||
|  |             url.append(suffix + "/"); | ||||||
|  |             mGetUrl = url.toString() + "ig"; | ||||||
|  |             mPostUrl = url.toString() + "r/ig"; | ||||||
|  | 
 | ||||||
|  |             if (tryToLoginGtask(activity, authToken)) { | ||||||
|  |                 mLoggedin = true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // try to login with google official url
 | ||||||
|  |         if (!mLoggedin) { | ||||||
|  |             mGetUrl = GTASK_GET_URL; | ||||||
|  |             mPostUrl = GTASK_POST_URL; | ||||||
|  |             if (!tryToLoginGtask(activity, authToken)) { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         mLoggedin = true; | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private String loginGoogleAccount(Activity activity, boolean invalidateToken) { | ||||||
|  |         String authToken; | ||||||
|  |         AccountManager accountManager = AccountManager.get(activity); | ||||||
|  |         Account[] accounts = accountManager.getAccountsByType("com.google"); | ||||||
|  | 
 | ||||||
|  |         if (accounts.length == 0) { | ||||||
|  |             Log.e(TAG, "there is no available google account"); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         String accountName = NotesPreferenceActivity.getSyncAccountName(activity); | ||||||
|  |         Account account = null; | ||||||
|  |         for (Account a : accounts) { | ||||||
|  |             if (a.name.equals(accountName)) { | ||||||
|  |                 account = a; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if (account != null) { | ||||||
|  |             mAccount = account; | ||||||
|  |         } else { | ||||||
|  |             Log.e(TAG, "unable to get an account with the same name in the settings"); | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // get the token now
 | ||||||
|  |         AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account, | ||||||
|  |                 "goanna_mobile", null, activity, null, null); | ||||||
|  |         try { | ||||||
|  |             Bundle authTokenBundle = accountManagerFuture.getResult(); | ||||||
|  |             authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); | ||||||
|  |             if (invalidateToken) { | ||||||
|  |                 accountManager.invalidateAuthToken("com.google", authToken); | ||||||
|  |                 loginGoogleAccount(activity, false); | ||||||
|  |             } | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             Log.e(TAG, "get auth token failed"); | ||||||
|  |             authToken = null; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return authToken; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private boolean tryToLoginGtask(Activity activity, String authToken) { | ||||||
|  |         if (!loginGtask(authToken)) { | ||||||
|  |             // maybe the auth token is out of date, now let's invalidate the
 | ||||||
|  |             // token and try again
 | ||||||
|  |             authToken = loginGoogleAccount(activity, true); | ||||||
|  |             if (authToken == null) { | ||||||
|  |                 Log.e(TAG, "login google account failed"); | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (!loginGtask(authToken)) { | ||||||
|  |                 Log.e(TAG, "login gtask failed"); | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private boolean loginGtask(String authToken) { | ||||||
|  |         int timeoutConnection = 10000; | ||||||
|  |         int timeoutSocket = 15000; | ||||||
|  |         HttpParams httpParameters = new BasicHttpParams(); | ||||||
|  |         HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); | ||||||
|  |         HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); | ||||||
|  |         mHttpClient = new DefaultHttpClient(httpParameters); | ||||||
|  |         BasicCookieStore localBasicCookieStore = new BasicCookieStore(); | ||||||
|  |         mHttpClient.setCookieStore(localBasicCookieStore); | ||||||
|  |         HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); | ||||||
|  | 
 | ||||||
|  |         // login gtask
 | ||||||
|  |         try { | ||||||
|  |             String loginUrl = mGetUrl + "?auth=" + authToken; | ||||||
|  |             HttpGet httpGet = new HttpGet(loginUrl); | ||||||
|  |             HttpResponse response = null; | ||||||
|  |             response = mHttpClient.execute(httpGet); | ||||||
|  | 
 | ||||||
|  |             // get the cookie now
 | ||||||
|  |             List<Cookie> cookies = mHttpClient.getCookieStore().getCookies(); | ||||||
|  |             boolean hasAuthCookie = false; | ||||||
|  |             for (Cookie cookie : cookies) { | ||||||
|  |                 if (cookie.getName().contains("GTL")) { | ||||||
|  |                     hasAuthCookie = true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if (!hasAuthCookie) { | ||||||
|  |                 Log.w(TAG, "it seems that there is no auth cookie"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // get the client version
 | ||||||
|  |             String resString = getResponseContent(response.getEntity()); | ||||||
|  |             String jsBegin = "_setup("; | ||||||
|  |             String jsEnd = ")}</script>"; | ||||||
|  |             int begin = resString.indexOf(jsBegin); | ||||||
|  |             int end = resString.lastIndexOf(jsEnd); | ||||||
|  |             String jsString = null; | ||||||
|  |             if (begin != -1 && end != -1 && begin < end) { | ||||||
|  |                 jsString = resString.substring(begin + jsBegin.length(), end); | ||||||
|  |             } | ||||||
|  |             JSONObject js = new JSONObject(jsString); | ||||||
|  |             mClientVersion = js.getLong("v"); | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             return false; | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             // simply catch all exceptions
 | ||||||
|  |             Log.e(TAG, "httpget gtask_url failed"); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private int getActionId() { | ||||||
|  |         return mActionId++; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private HttpPost createHttpPost() { | ||||||
|  |         HttpPost httpPost = new HttpPost(mPostUrl); | ||||||
|  |         httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); | ||||||
|  |         httpPost.setHeader("AT", "1"); | ||||||
|  |         return httpPost; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private String getResponseContent(HttpEntity entity) throws IOException { | ||||||
|  |         String contentEncoding = null; | ||||||
|  |         if (entity.getContentEncoding() != null) { | ||||||
|  |             contentEncoding = entity.getContentEncoding().getValue(); | ||||||
|  |             Log.d(TAG, "encoding: " + contentEncoding); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         InputStream input = entity.getContent(); | ||||||
|  |         if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { | ||||||
|  |             input = new GZIPInputStream(entity.getContent()); | ||||||
|  |         } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { | ||||||
|  |             Inflater inflater = new Inflater(true); | ||||||
|  |             input = new InflaterInputStream(entity.getContent(), inflater); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             InputStreamReader isr = new InputStreamReader(input); | ||||||
|  |             BufferedReader br = new BufferedReader(isr); | ||||||
|  |             StringBuilder sb = new StringBuilder(); | ||||||
|  | 
 | ||||||
|  |             while (true) { | ||||||
|  |                 String buff = br.readLine(); | ||||||
|  |                 if (buff == null) { | ||||||
|  |                     return sb.toString(); | ||||||
|  |                 } | ||||||
|  |                 sb = sb.append(buff); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             input.close(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private JSONObject postRequest(JSONObject js) throws NetworkFailureException { | ||||||
|  |         if (!mLoggedin) { | ||||||
|  |             Log.e(TAG, "please login first"); | ||||||
|  |             throw new ActionFailureException("not logged in"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         HttpPost httpPost = createHttpPost(); | ||||||
|  |         try { | ||||||
|  |             LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>(); | ||||||
|  |             list.add(new BasicNameValuePair("r", js.toString())); | ||||||
|  |             UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); | ||||||
|  |             httpPost.setEntity(entity); | ||||||
|  | 
 | ||||||
|  |             // execute the post
 | ||||||
|  |             HttpResponse response = mHttpClient.execute(httpPost); | ||||||
|  |             String jsString = getResponseContent(response.getEntity()); | ||||||
|  |             return new JSONObject(jsString); | ||||||
|  | 
 | ||||||
|  |         } catch (ClientProtocolException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new NetworkFailureException("postRequest failed"); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new NetworkFailureException("postRequest failed"); | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new ActionFailureException("unable to convert response content to jsonobject"); | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new ActionFailureException("error occurs when posting request"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void createTask(Task task) throws NetworkFailureException { | ||||||
|  |         commitUpdate(); | ||||||
|  |         try { | ||||||
|  |             JSONObject jsPost = new JSONObject(); | ||||||
|  |             JSONArray actionList = new JSONArray(); | ||||||
|  | 
 | ||||||
|  |             // action_list
 | ||||||
|  |             actionList.put(task.getCreateAction(getActionId())); | ||||||
|  |             jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); | ||||||
|  | 
 | ||||||
|  |             // client_version
 | ||||||
|  |             jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); | ||||||
|  | 
 | ||||||
|  |             // post
 | ||||||
|  |             JSONObject jsResponse = postRequest(jsPost); | ||||||
|  |             JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( | ||||||
|  |                     GTaskStringUtils.GTASK_JSON_RESULTS).get(0); | ||||||
|  |             task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); | ||||||
|  | 
 | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new ActionFailureException("create task: handing jsonobject failed"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void createTaskList(TaskList tasklist) throws NetworkFailureException { | ||||||
|  |         commitUpdate(); | ||||||
|  |         try { | ||||||
|  |             JSONObject jsPost = new JSONObject(); | ||||||
|  |             JSONArray actionList = new JSONArray(); | ||||||
|  | 
 | ||||||
|  |             // action_list
 | ||||||
|  |             actionList.put(tasklist.getCreateAction(getActionId())); | ||||||
|  |             jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); | ||||||
|  | 
 | ||||||
|  |             // client version
 | ||||||
|  |             jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); | ||||||
|  | 
 | ||||||
|  |             // post
 | ||||||
|  |             JSONObject jsResponse = postRequest(jsPost); | ||||||
|  |             JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( | ||||||
|  |                     GTaskStringUtils.GTASK_JSON_RESULTS).get(0); | ||||||
|  |             tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); | ||||||
|  | 
 | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new ActionFailureException("create tasklist: handing jsonobject failed"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void commitUpdate() throws NetworkFailureException { | ||||||
|  |         if (mUpdateArray != null) { | ||||||
|  |             try { | ||||||
|  |                 JSONObject jsPost = new JSONObject(); | ||||||
|  | 
 | ||||||
|  |                 // action_list
 | ||||||
|  |                 jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); | ||||||
|  | 
 | ||||||
|  |                 // client_version
 | ||||||
|  |                 jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); | ||||||
|  | 
 | ||||||
|  |                 postRequest(jsPost); | ||||||
|  |                 mUpdateArray = null; | ||||||
|  |             } catch (JSONException e) { | ||||||
|  |                 Log.e(TAG, e.toString()); | ||||||
|  |                 e.printStackTrace(); | ||||||
|  |                 throw new ActionFailureException("commit update: handing jsonobject failed"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void addUpdateNode(Node node) throws NetworkFailureException { | ||||||
|  |         if (node != null) { | ||||||
|  |             // too many update items may result in an error
 | ||||||
|  |             // set max to 10 items
 | ||||||
|  |             if (mUpdateArray != null && mUpdateArray.length() > 10) { | ||||||
|  |                 commitUpdate(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (mUpdateArray == null) | ||||||
|  |                 mUpdateArray = new JSONArray(); | ||||||
|  |             mUpdateArray.put(node.getUpdateAction(getActionId())); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void moveTask(Task task, TaskList preParent, TaskList curParent) | ||||||
|  |             throws NetworkFailureException { | ||||||
|  |         commitUpdate(); | ||||||
|  |         try { | ||||||
|  |             JSONObject jsPost = new JSONObject(); | ||||||
|  |             JSONArray actionList = new JSONArray(); | ||||||
|  |             JSONObject action = new JSONObject(); | ||||||
|  | 
 | ||||||
|  |             // action_list
 | ||||||
|  |             action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, | ||||||
|  |                     GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); | ||||||
|  |             action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); | ||||||
|  |             action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); | ||||||
|  |             if (preParent == curParent && task.getPriorSibling() != null) { | ||||||
|  |                 // put prioring_sibing_id only if moving within the tasklist and
 | ||||||
|  |                 // it is not the first one
 | ||||||
|  |                 action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); | ||||||
|  |             } | ||||||
|  |             action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); | ||||||
|  |             action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); | ||||||
|  |             if (preParent != curParent) { | ||||||
|  |                 // put the dest_list only if moving between tasklists
 | ||||||
|  |                 action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); | ||||||
|  |             } | ||||||
|  |             actionList.put(action); | ||||||
|  |             jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); | ||||||
|  | 
 | ||||||
|  |             // client_version
 | ||||||
|  |             jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); | ||||||
|  | 
 | ||||||
|  |             postRequest(jsPost); | ||||||
|  | 
 | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new ActionFailureException("move task: handing jsonobject failed"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void deleteNode(Node node) throws NetworkFailureException { | ||||||
|  |         commitUpdate(); | ||||||
|  |         try { | ||||||
|  |             JSONObject jsPost = new JSONObject(); | ||||||
|  |             JSONArray actionList = new JSONArray(); | ||||||
|  | 
 | ||||||
|  |             // action_list
 | ||||||
|  |             node.setDeleted(true); | ||||||
|  |             actionList.put(node.getUpdateAction(getActionId())); | ||||||
|  |             jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); | ||||||
|  | 
 | ||||||
|  |             // client_version
 | ||||||
|  |             jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); | ||||||
|  | 
 | ||||||
|  |             postRequest(jsPost); | ||||||
|  |             mUpdateArray = null; | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new ActionFailureException("delete node: handing jsonobject failed"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public JSONArray getTaskLists() throws NetworkFailureException { | ||||||
|  |         if (!mLoggedin) { | ||||||
|  |             Log.e(TAG, "please login first"); | ||||||
|  |             throw new ActionFailureException("not logged in"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             HttpGet httpGet = new HttpGet(mGetUrl); | ||||||
|  |             HttpResponse response = null; | ||||||
|  |             response = mHttpClient.execute(httpGet); | ||||||
|  | 
 | ||||||
|  |             // get the task list
 | ||||||
|  |             String resString = getResponseContent(response.getEntity()); | ||||||
|  |             String jsBegin = "_setup("; | ||||||
|  |             String jsEnd = ")}</script>"; | ||||||
|  |             int begin = resString.indexOf(jsBegin); | ||||||
|  |             int end = resString.lastIndexOf(jsEnd); | ||||||
|  |             String jsString = null; | ||||||
|  |             if (begin != -1 && end != -1 && begin < end) { | ||||||
|  |                 jsString = resString.substring(begin + jsBegin.length(), end); | ||||||
|  |             } | ||||||
|  |             JSONObject js = new JSONObject(jsString); | ||||||
|  |             return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); | ||||||
|  |         } catch (ClientProtocolException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new NetworkFailureException("gettasklists: httpget failed"); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new NetworkFailureException("gettasklists: httpget failed"); | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new ActionFailureException("get task lists: handing jasonobject failed"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public JSONArray getTaskList(String listGid) throws NetworkFailureException { | ||||||
|  |         commitUpdate(); | ||||||
|  |         try { | ||||||
|  |             JSONObject jsPost = new JSONObject(); | ||||||
|  |             JSONArray actionList = new JSONArray(); | ||||||
|  |             JSONObject action = new JSONObject(); | ||||||
|  | 
 | ||||||
|  |             // action_list
 | ||||||
|  |             action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, | ||||||
|  |                     GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); | ||||||
|  |             action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); | ||||||
|  |             action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); | ||||||
|  |             action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); | ||||||
|  |             actionList.put(action); | ||||||
|  |             jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); | ||||||
|  | 
 | ||||||
|  |             // client_version
 | ||||||
|  |             jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); | ||||||
|  | 
 | ||||||
|  |             JSONObject jsResponse = postRequest(jsPost); | ||||||
|  |             return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new ActionFailureException("get task list: handing jsonobject failed"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public Account getSyncAccount() { | ||||||
|  |         return mAccount; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void resetUpdateArray() { | ||||||
|  |         mUpdateArray = null; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,800 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *        http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package net.micode.notes.gtask.remote; | ||||||
|  | 
 | ||||||
|  | import android.app.Activity; | ||||||
|  | import android.content.ContentResolver; | ||||||
|  | import android.content.ContentUris; | ||||||
|  | import android.content.ContentValues; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.database.Cursor; | ||||||
|  | import android.util.Log; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.data.Notes.DataColumns; | ||||||
|  | import net.micode.notes.data.Notes.NoteColumns; | ||||||
|  | import net.micode.notes.gtask.data.MetaData; | ||||||
|  | import net.micode.notes.gtask.data.Node; | ||||||
|  | import net.micode.notes.gtask.data.SqlNote; | ||||||
|  | import net.micode.notes.gtask.data.Task; | ||||||
|  | import net.micode.notes.gtask.data.TaskList; | ||||||
|  | import net.micode.notes.gtask.exception.ActionFailureException; | ||||||
|  | import net.micode.notes.gtask.exception.NetworkFailureException; | ||||||
|  | import net.micode.notes.tool.DataUtils; | ||||||
|  | import net.micode.notes.tool.GTaskStringUtils; | ||||||
|  | 
 | ||||||
|  | import org.json.JSONArray; | ||||||
|  | import org.json.JSONException; | ||||||
|  | import org.json.JSONObject; | ||||||
|  | 
 | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.Iterator; | ||||||
|  | import java.util.Map; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class GTaskManager { | ||||||
|  |     private static final String TAG = GTaskManager.class.getSimpleName(); | ||||||
|  | 
 | ||||||
|  |     public static final int STATE_SUCCESS = 0; | ||||||
|  | 
 | ||||||
|  |     public static final int STATE_NETWORK_ERROR = 1; | ||||||
|  | 
 | ||||||
|  |     public static final int STATE_INTERNAL_ERROR = 2; | ||||||
|  | 
 | ||||||
|  |     public static final int STATE_SYNC_IN_PROGRESS = 3; | ||||||
|  | 
 | ||||||
|  |     public static final int STATE_SYNC_CANCELLED = 4; | ||||||
|  | 
 | ||||||
|  |     private static GTaskManager mInstance = null; | ||||||
|  | 
 | ||||||
|  |     private Activity mActivity; | ||||||
|  | 
 | ||||||
|  |     private Context mContext; | ||||||
|  | 
 | ||||||
|  |     private ContentResolver mContentResolver; | ||||||
|  | 
 | ||||||
|  |     private boolean mSyncing; | ||||||
|  | 
 | ||||||
|  |     private boolean mCancelled; | ||||||
|  | 
 | ||||||
|  |     private HashMap<String, TaskList> mGTaskListHashMap; | ||||||
|  | 
 | ||||||
|  |     private HashMap<String, Node> mGTaskHashMap; | ||||||
|  | 
 | ||||||
|  |     private HashMap<String, MetaData> mMetaHashMap; | ||||||
|  | 
 | ||||||
|  |     private TaskList mMetaList; | ||||||
|  | 
 | ||||||
|  |     private HashSet<Long> mLocalDeleteIdMap; | ||||||
|  | 
 | ||||||
|  |     private HashMap<String, Long> mGidToNid; | ||||||
|  | 
 | ||||||
|  |     private HashMap<Long, String> mNidToGid; | ||||||
|  | 
 | ||||||
|  |     private GTaskManager() { | ||||||
|  |         mSyncing = false; | ||||||
|  |         mCancelled = false; | ||||||
|  |         mGTaskListHashMap = new HashMap<String, TaskList>(); | ||||||
|  |         mGTaskHashMap = new HashMap<String, Node>(); | ||||||
|  |         mMetaHashMap = new HashMap<String, MetaData>(); | ||||||
|  |         mMetaList = null; | ||||||
|  |         mLocalDeleteIdMap = new HashSet<Long>(); | ||||||
|  |         mGidToNid = new HashMap<String, Long>(); | ||||||
|  |         mNidToGid = new HashMap<Long, String>(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static synchronized GTaskManager getInstance() { | ||||||
|  |         if (mInstance == null) { | ||||||
|  |             mInstance = new GTaskManager(); | ||||||
|  |         } | ||||||
|  |         return mInstance; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public synchronized void setActivityContext(Activity activity) { | ||||||
|  |         // used for getting authtoken
 | ||||||
|  |         mActivity = activity; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int sync(Context context, GTaskASyncTask asyncTask) { | ||||||
|  |         if (mSyncing) { | ||||||
|  |             Log.d(TAG, "Sync is in progress"); | ||||||
|  |             return STATE_SYNC_IN_PROGRESS; | ||||||
|  |         } | ||||||
|  |         mContext = context; | ||||||
|  |         mContentResolver = mContext.getContentResolver(); | ||||||
|  |         mSyncing = true; | ||||||
|  |         mCancelled = false; | ||||||
|  |         mGTaskListHashMap.clear(); | ||||||
|  |         mGTaskHashMap.clear(); | ||||||
|  |         mMetaHashMap.clear(); | ||||||
|  |         mLocalDeleteIdMap.clear(); | ||||||
|  |         mGidToNid.clear(); | ||||||
|  |         mNidToGid.clear(); | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             GTaskClient client = GTaskClient.getInstance(); | ||||||
|  |             client.resetUpdateArray(); | ||||||
|  | 
 | ||||||
|  |             // login google task
 | ||||||
|  |             if (!mCancelled) { | ||||||
|  |                 if (!client.login(mActivity)) { | ||||||
|  |                     throw new NetworkFailureException("login google task failed"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // get the task list from google
 | ||||||
|  |             asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); | ||||||
|  |             initGTaskList(); | ||||||
|  | 
 | ||||||
|  |             // do content sync work
 | ||||||
|  |             asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); | ||||||
|  |             syncContent(); | ||||||
|  |         } catch (NetworkFailureException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             return STATE_NETWORK_ERROR; | ||||||
|  |         } catch (ActionFailureException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             return STATE_INTERNAL_ERROR; | ||||||
|  |         } catch (Exception e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             return STATE_INTERNAL_ERROR; | ||||||
|  |         } finally { | ||||||
|  |             mGTaskListHashMap.clear(); | ||||||
|  |             mGTaskHashMap.clear(); | ||||||
|  |             mMetaHashMap.clear(); | ||||||
|  |             mLocalDeleteIdMap.clear(); | ||||||
|  |             mGidToNid.clear(); | ||||||
|  |             mNidToGid.clear(); | ||||||
|  |             mSyncing = false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void initGTaskList() throws NetworkFailureException { | ||||||
|  |         if (mCancelled) | ||||||
|  |             return; | ||||||
|  |         GTaskClient client = GTaskClient.getInstance(); | ||||||
|  |         try { | ||||||
|  |             JSONArray jsTaskLists = client.getTaskLists(); | ||||||
|  | 
 | ||||||
|  |             // init meta list first
 | ||||||
|  |             mMetaList = null; | ||||||
|  |             for (int i = 0; i < jsTaskLists.length(); i++) { | ||||||
|  |                 JSONObject object = jsTaskLists.getJSONObject(i); | ||||||
|  |                 String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); | ||||||
|  |                 String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); | ||||||
|  | 
 | ||||||
|  |                 if (name | ||||||
|  |                         .equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { | ||||||
|  |                     mMetaList = new TaskList(); | ||||||
|  |                     mMetaList.setContentByRemoteJSON(object); | ||||||
|  | 
 | ||||||
|  |                     // load meta data
 | ||||||
|  |                     JSONArray jsMetas = client.getTaskList(gid); | ||||||
|  |                     for (int j = 0; j < jsMetas.length(); j++) { | ||||||
|  |                         object = (JSONObject) jsMetas.getJSONObject(j); | ||||||
|  |                         MetaData metaData = new MetaData(); | ||||||
|  |                         metaData.setContentByRemoteJSON(object); | ||||||
|  |                         if (metaData.isWorthSaving()) { | ||||||
|  |                             mMetaList.addChildTask(metaData); | ||||||
|  |                             if (metaData.getGid() != null) { | ||||||
|  |                                 mMetaHashMap.put(metaData.getRelatedGid(), metaData); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // create meta list if not existed
 | ||||||
|  |             if (mMetaList == null) { | ||||||
|  |                 mMetaList = new TaskList(); | ||||||
|  |                 mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX | ||||||
|  |                         + GTaskStringUtils.FOLDER_META); | ||||||
|  |                 GTaskClient.getInstance().createTaskList(mMetaList); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // init task list
 | ||||||
|  |             for (int i = 0; i < jsTaskLists.length(); i++) { | ||||||
|  |                 JSONObject object = jsTaskLists.getJSONObject(i); | ||||||
|  |                 String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); | ||||||
|  |                 String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); | ||||||
|  | 
 | ||||||
|  |                 if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) | ||||||
|  |                         && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX | ||||||
|  |                                 + GTaskStringUtils.FOLDER_META)) { | ||||||
|  |                     TaskList tasklist = new TaskList(); | ||||||
|  |                     tasklist.setContentByRemoteJSON(object); | ||||||
|  |                     mGTaskListHashMap.put(gid, tasklist); | ||||||
|  |                     mGTaskHashMap.put(gid, tasklist); | ||||||
|  | 
 | ||||||
|  |                     // load tasks
 | ||||||
|  |                     JSONArray jsTasks = client.getTaskList(gid); | ||||||
|  |                     for (int j = 0; j < jsTasks.length(); j++) { | ||||||
|  |                         object = (JSONObject) jsTasks.getJSONObject(j); | ||||||
|  |                         gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); | ||||||
|  |                         Task task = new Task(); | ||||||
|  |                         task.setContentByRemoteJSON(object); | ||||||
|  |                         if (task.isWorthSaving()) { | ||||||
|  |                             task.setMetaInfo(mMetaHashMap.get(gid)); | ||||||
|  |                             tasklist.addChildTask(task); | ||||||
|  |                             mGTaskHashMap.put(gid, task); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (JSONException e) { | ||||||
|  |             Log.e(TAG, e.toString()); | ||||||
|  |             e.printStackTrace(); | ||||||
|  |             throw new ActionFailureException("initGTaskList: handing JSONObject failed"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void syncContent() throws NetworkFailureException { | ||||||
|  |         int syncType; | ||||||
|  |         Cursor c = null; | ||||||
|  |         String gid; | ||||||
|  |         Node node; | ||||||
|  | 
 | ||||||
|  |         mLocalDeleteIdMap.clear(); | ||||||
|  | 
 | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // for local deleted note
 | ||||||
|  |         try { | ||||||
|  |             c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, | ||||||
|  |                     "(type<>? AND parent_id=?)", new String[] { | ||||||
|  |                             String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) | ||||||
|  |                     }, null); | ||||||
|  |             if (c != null) { | ||||||
|  |                 while (c.moveToNext()) { | ||||||
|  |                     gid = c.getString(SqlNote.GTASK_ID_COLUMN); | ||||||
|  |                     node = mGTaskHashMap.get(gid); | ||||||
|  |                     if (node != null) { | ||||||
|  |                         mGTaskHashMap.remove(gid); | ||||||
|  |                         doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Log.w(TAG, "failed to query trash folder"); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             if (c != null) { | ||||||
|  |                 c.close(); | ||||||
|  |                 c = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // sync folder first
 | ||||||
|  |         syncFolder(); | ||||||
|  | 
 | ||||||
|  |         // for note existing in database
 | ||||||
|  |         try { | ||||||
|  |             c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, | ||||||
|  |                     "(type=? AND parent_id<>?)", new String[] { | ||||||
|  |                             String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER) | ||||||
|  |                     }, NoteColumns.TYPE + " DESC"); | ||||||
|  |             if (c != null) { | ||||||
|  |                 while (c.moveToNext()) { | ||||||
|  |                     gid = c.getString(SqlNote.GTASK_ID_COLUMN); | ||||||
|  |                     node = mGTaskHashMap.get(gid); | ||||||
|  |                     if (node != null) { | ||||||
|  |                         mGTaskHashMap.remove(gid); | ||||||
|  |                         mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); | ||||||
|  |                         mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); | ||||||
|  |                         syncType = node.getSyncAction(c); | ||||||
|  |                     } else { | ||||||
|  |                         if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { | ||||||
|  |                             // local add
 | ||||||
|  |                             syncType = Node.SYNC_ACTION_ADD_REMOTE; | ||||||
|  |                         } else { | ||||||
|  |                             // remote delete
 | ||||||
|  |                             syncType = Node.SYNC_ACTION_DEL_LOCAL; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     doContentSync(syncType, node, c); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Log.w(TAG, "failed to query existing note in database"); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } finally { | ||||||
|  |             if (c != null) { | ||||||
|  |                 c.close(); | ||||||
|  |                 c = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // go through remaining items
 | ||||||
|  |         Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator(); | ||||||
|  |         while (iter.hasNext()) { | ||||||
|  |             Map.Entry<String, Node> entry = iter.next(); | ||||||
|  |             node = entry.getValue(); | ||||||
|  |             doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // mCancelled can be set by another thread, so we neet to check one by
 | ||||||
|  |         // one
 | ||||||
|  |         // clear local delete table
 | ||||||
|  |         if (!mCancelled) { | ||||||
|  |             if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { | ||||||
|  |                 throw new ActionFailureException("failed to batch-delete local deleted notes"); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // refresh local sync id
 | ||||||
|  |         if (!mCancelled) { | ||||||
|  |             GTaskClient.getInstance().commitUpdate(); | ||||||
|  |             refreshLocalSyncId(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void syncFolder() throws NetworkFailureException { | ||||||
|  |         Cursor c = null; | ||||||
|  |         String gid; | ||||||
|  |         Node node; | ||||||
|  |         int syncType; | ||||||
|  | 
 | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // for root folder
 | ||||||
|  |         try { | ||||||
|  |             c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, | ||||||
|  |                     Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); | ||||||
|  |             if (c != null) { | ||||||
|  |                 c.moveToNext(); | ||||||
|  |                 gid = c.getString(SqlNote.GTASK_ID_COLUMN); | ||||||
|  |                 node = mGTaskHashMap.get(gid); | ||||||
|  |                 if (node != null) { | ||||||
|  |                     mGTaskHashMap.remove(gid); | ||||||
|  |                     mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); | ||||||
|  |                     mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); | ||||||
|  |                     // for system folder, only update remote name if necessary
 | ||||||
|  |                     if (!node.getName().equals( | ||||||
|  |                             GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) | ||||||
|  |                         doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); | ||||||
|  |                 } else { | ||||||
|  |                     doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Log.w(TAG, "failed to query root folder"); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             if (c != null) { | ||||||
|  |                 c.close(); | ||||||
|  |                 c = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // for call-note folder
 | ||||||
|  |         try { | ||||||
|  |             c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", | ||||||
|  |                     new String[] { | ||||||
|  |                         String.valueOf(Notes.ID_CALL_RECORD_FOLDER) | ||||||
|  |                     }, null); | ||||||
|  |             if (c != null) { | ||||||
|  |                 if (c.moveToNext()) { | ||||||
|  |                     gid = c.getString(SqlNote.GTASK_ID_COLUMN); | ||||||
|  |                     node = mGTaskHashMap.get(gid); | ||||||
|  |                     if (node != null) { | ||||||
|  |                         mGTaskHashMap.remove(gid); | ||||||
|  |                         mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); | ||||||
|  |                         mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); | ||||||
|  |                         // for system folder, only update remote name if
 | ||||||
|  |                         // necessary
 | ||||||
|  |                         if (!node.getName().equals( | ||||||
|  |                                 GTaskStringUtils.MIUI_FOLDER_PREFFIX | ||||||
|  |                                         + GTaskStringUtils.FOLDER_CALL_NOTE)) | ||||||
|  |                             doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); | ||||||
|  |                     } else { | ||||||
|  |                         doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Log.w(TAG, "failed to query call note folder"); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             if (c != null) { | ||||||
|  |                 c.close(); | ||||||
|  |                 c = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // for local existing folders
 | ||||||
|  |         try { | ||||||
|  |             c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, | ||||||
|  |                     "(type=? AND parent_id<>?)", new String[] { | ||||||
|  |                             String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) | ||||||
|  |                     }, NoteColumns.TYPE + " DESC"); | ||||||
|  |             if (c != null) { | ||||||
|  |                 while (c.moveToNext()) { | ||||||
|  |                     gid = c.getString(SqlNote.GTASK_ID_COLUMN); | ||||||
|  |                     node = mGTaskHashMap.get(gid); | ||||||
|  |                     if (node != null) { | ||||||
|  |                         mGTaskHashMap.remove(gid); | ||||||
|  |                         mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); | ||||||
|  |                         mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); | ||||||
|  |                         syncType = node.getSyncAction(c); | ||||||
|  |                     } else { | ||||||
|  |                         if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { | ||||||
|  |                             // local add
 | ||||||
|  |                             syncType = Node.SYNC_ACTION_ADD_REMOTE; | ||||||
|  |                         } else { | ||||||
|  |                             // remote delete
 | ||||||
|  |                             syncType = Node.SYNC_ACTION_DEL_LOCAL; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     doContentSync(syncType, node, c); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Log.w(TAG, "failed to query existing folder"); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             if (c != null) { | ||||||
|  |                 c.close(); | ||||||
|  |                 c = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // for remote add folders
 | ||||||
|  |         Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator(); | ||||||
|  |         while (iter.hasNext()) { | ||||||
|  |             Map.Entry<String, TaskList> entry = iter.next(); | ||||||
|  |             gid = entry.getKey(); | ||||||
|  |             node = entry.getValue(); | ||||||
|  |             if (mGTaskHashMap.containsKey(gid)) { | ||||||
|  |                 mGTaskHashMap.remove(gid); | ||||||
|  |                 doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!mCancelled) | ||||||
|  |             GTaskClient.getInstance().commitUpdate(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         MetaData meta; | ||||||
|  |         switch (syncType) { | ||||||
|  |             case Node.SYNC_ACTION_ADD_LOCAL: | ||||||
|  |                 addLocalNode(node); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_ADD_REMOTE: | ||||||
|  |                 addRemoteNode(node, c); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_DEL_LOCAL: | ||||||
|  |                 meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); | ||||||
|  |                 if (meta != null) { | ||||||
|  |                     GTaskClient.getInstance().deleteNode(meta); | ||||||
|  |                 } | ||||||
|  |                 mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_DEL_REMOTE: | ||||||
|  |                 meta = mMetaHashMap.get(node.getGid()); | ||||||
|  |                 if (meta != null) { | ||||||
|  |                     GTaskClient.getInstance().deleteNode(meta); | ||||||
|  |                 } | ||||||
|  |                 GTaskClient.getInstance().deleteNode(node); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_UPDATE_LOCAL: | ||||||
|  |                 updateLocalNode(node, c); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_UPDATE_REMOTE: | ||||||
|  |                 updateRemoteNode(node, c); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_UPDATE_CONFLICT: | ||||||
|  |                 // merging both modifications maybe a good idea
 | ||||||
|  |                 // right now just use local update simply
 | ||||||
|  |                 updateRemoteNode(node, c); | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_NONE: | ||||||
|  |                 break; | ||||||
|  |             case Node.SYNC_ACTION_ERROR: | ||||||
|  |             default: | ||||||
|  |                 throw new ActionFailureException("unkown sync action type"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void addLocalNode(Node node) throws NetworkFailureException { | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         SqlNote sqlNote; | ||||||
|  |         if (node instanceof TaskList) { | ||||||
|  |             if (node.getName().equals( | ||||||
|  |                     GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { | ||||||
|  |                 sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); | ||||||
|  |             } else if (node.getName().equals( | ||||||
|  |                     GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { | ||||||
|  |                 sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); | ||||||
|  |             } else { | ||||||
|  |                 sqlNote = new SqlNote(mContext); | ||||||
|  |                 sqlNote.setContent(node.getLocalJSONFromContent()); | ||||||
|  |                 sqlNote.setParentId(Notes.ID_ROOT_FOLDER); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             sqlNote = new SqlNote(mContext); | ||||||
|  |             JSONObject js = node.getLocalJSONFromContent(); | ||||||
|  |             try { | ||||||
|  |                 if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { | ||||||
|  |                     JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); | ||||||
|  |                     if (note.has(NoteColumns.ID)) { | ||||||
|  |                         long id = note.getLong(NoteColumns.ID); | ||||||
|  |                         if (DataUtils.existInNoteDatabase(mContentResolver, id)) { | ||||||
|  |                             // the id is not available, have to create a new one
 | ||||||
|  |                             note.remove(NoteColumns.ID); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if (js.has(GTaskStringUtils.META_HEAD_DATA)) { | ||||||
|  |                     JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); | ||||||
|  |                     for (int i = 0; i < dataArray.length(); i++) { | ||||||
|  |                         JSONObject data = dataArray.getJSONObject(i); | ||||||
|  |                         if (data.has(DataColumns.ID)) { | ||||||
|  |                             long dataId = data.getLong(DataColumns.ID); | ||||||
|  |                             if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { | ||||||
|  |                                 // the data id is not available, have to create
 | ||||||
|  |                                 // a new one
 | ||||||
|  |                                 data.remove(DataColumns.ID); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                 } | ||||||
|  |             } catch (JSONException e) { | ||||||
|  |                 Log.w(TAG, e.toString()); | ||||||
|  |                 e.printStackTrace(); | ||||||
|  |             } | ||||||
|  |             sqlNote.setContent(js); | ||||||
|  | 
 | ||||||
|  |             Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); | ||||||
|  |             if (parentId == null) { | ||||||
|  |                 Log.e(TAG, "cannot find task's parent id locally"); | ||||||
|  |                 throw new ActionFailureException("cannot add local node"); | ||||||
|  |             } | ||||||
|  |             sqlNote.setParentId(parentId.longValue()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // create the local node
 | ||||||
|  |         sqlNote.setGtaskId(node.getGid()); | ||||||
|  |         sqlNote.commit(false); | ||||||
|  | 
 | ||||||
|  |         // update gid-nid mapping
 | ||||||
|  |         mGidToNid.put(node.getGid(), sqlNote.getId()); | ||||||
|  |         mNidToGid.put(sqlNote.getId(), node.getGid()); | ||||||
|  | 
 | ||||||
|  |         // update meta
 | ||||||
|  |         updateRemoteMeta(node.getGid(), sqlNote); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         SqlNote sqlNote; | ||||||
|  |         // update the note locally
 | ||||||
|  |         sqlNote = new SqlNote(mContext, c); | ||||||
|  |         sqlNote.setContent(node.getLocalJSONFromContent()); | ||||||
|  | 
 | ||||||
|  |         Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) | ||||||
|  |                 : new Long(Notes.ID_ROOT_FOLDER); | ||||||
|  |         if (parentId == null) { | ||||||
|  |             Log.e(TAG, "cannot find task's parent id locally"); | ||||||
|  |             throw new ActionFailureException("cannot update local node"); | ||||||
|  |         } | ||||||
|  |         sqlNote.setParentId(parentId.longValue()); | ||||||
|  |         sqlNote.commit(true); | ||||||
|  | 
 | ||||||
|  |         // update meta info
 | ||||||
|  |         updateRemoteMeta(node.getGid(), sqlNote); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         SqlNote sqlNote = new SqlNote(mContext, c); | ||||||
|  |         Node n; | ||||||
|  | 
 | ||||||
|  |         // update remotely
 | ||||||
|  |         if (sqlNote.isNoteType()) { | ||||||
|  |             Task task = new Task(); | ||||||
|  |             task.setContentByLocalJSON(sqlNote.getContent()); | ||||||
|  | 
 | ||||||
|  |             String parentGid = mNidToGid.get(sqlNote.getParentId()); | ||||||
|  |             if (parentGid == null) { | ||||||
|  |                 Log.e(TAG, "cannot find task's parent tasklist"); | ||||||
|  |                 throw new ActionFailureException("cannot add remote task"); | ||||||
|  |             } | ||||||
|  |             mGTaskListHashMap.get(parentGid).addChildTask(task); | ||||||
|  | 
 | ||||||
|  |             GTaskClient.getInstance().createTask(task); | ||||||
|  |             n = (Node) task; | ||||||
|  | 
 | ||||||
|  |             // add meta
 | ||||||
|  |             updateRemoteMeta(task.getGid(), sqlNote); | ||||||
|  |         } else { | ||||||
|  |             TaskList tasklist = null; | ||||||
|  | 
 | ||||||
|  |             // we need to skip folder if it has already existed
 | ||||||
|  |             String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; | ||||||
|  |             if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) | ||||||
|  |                 folderName += GTaskStringUtils.FOLDER_DEFAULT; | ||||||
|  |             else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER) | ||||||
|  |                 folderName += GTaskStringUtils.FOLDER_CALL_NOTE; | ||||||
|  |             else | ||||||
|  |                 folderName += sqlNote.getSnippet(); | ||||||
|  | 
 | ||||||
|  |             Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator(); | ||||||
|  |             while (iter.hasNext()) { | ||||||
|  |                 Map.Entry<String, TaskList> entry = iter.next(); | ||||||
|  |                 String gid = entry.getKey(); | ||||||
|  |                 TaskList list = entry.getValue(); | ||||||
|  | 
 | ||||||
|  |                 if (list.getName().equals(folderName)) { | ||||||
|  |                     tasklist = list; | ||||||
|  |                     if (mGTaskHashMap.containsKey(gid)) { | ||||||
|  |                         mGTaskHashMap.remove(gid); | ||||||
|  |                     } | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // no match we can add now
 | ||||||
|  |             if (tasklist == null) { | ||||||
|  |                 tasklist = new TaskList(); | ||||||
|  |                 tasklist.setContentByLocalJSON(sqlNote.getContent()); | ||||||
|  |                 GTaskClient.getInstance().createTaskList(tasklist); | ||||||
|  |                 mGTaskListHashMap.put(tasklist.getGid(), tasklist); | ||||||
|  |             } | ||||||
|  |             n = (Node) tasklist; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // update local note
 | ||||||
|  |         sqlNote.setGtaskId(n.getGid()); | ||||||
|  |         sqlNote.commit(false); | ||||||
|  |         sqlNote.resetLocalModified(); | ||||||
|  |         sqlNote.commit(true); | ||||||
|  | 
 | ||||||
|  |         // gid-id mapping
 | ||||||
|  |         mGidToNid.put(n.getGid(), sqlNote.getId()); | ||||||
|  |         mNidToGid.put(sqlNote.getId(), n.getGid()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         SqlNote sqlNote = new SqlNote(mContext, c); | ||||||
|  | 
 | ||||||
|  |         // update remotely
 | ||||||
|  |         node.setContentByLocalJSON(sqlNote.getContent()); | ||||||
|  |         GTaskClient.getInstance().addUpdateNode(node); | ||||||
|  | 
 | ||||||
|  |         // update meta
 | ||||||
|  |         updateRemoteMeta(node.getGid(), sqlNote); | ||||||
|  | 
 | ||||||
|  |         // move task if necessary
 | ||||||
|  |         if (sqlNote.isNoteType()) { | ||||||
|  |             Task task = (Task) node; | ||||||
|  |             TaskList preParentList = task.getParent(); | ||||||
|  | 
 | ||||||
|  |             String curParentGid = mNidToGid.get(sqlNote.getParentId()); | ||||||
|  |             if (curParentGid == null) { | ||||||
|  |                 Log.e(TAG, "cannot find task's parent tasklist"); | ||||||
|  |                 throw new ActionFailureException("cannot update remote task"); | ||||||
|  |             } | ||||||
|  |             TaskList curParentList = mGTaskListHashMap.get(curParentGid); | ||||||
|  | 
 | ||||||
|  |             if (preParentList != curParentList) { | ||||||
|  |                 preParentList.removeChildTask(task); | ||||||
|  |                 curParentList.addChildTask(task); | ||||||
|  |                 GTaskClient.getInstance().moveTask(task, preParentList, curParentList); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // clear local modified flag
 | ||||||
|  |         sqlNote.resetLocalModified(); | ||||||
|  |         sqlNote.commit(true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { | ||||||
|  |         if (sqlNote != null && sqlNote.isNoteType()) { | ||||||
|  |             MetaData metaData = mMetaHashMap.get(gid); | ||||||
|  |             if (metaData != null) { | ||||||
|  |                 metaData.setMeta(gid, sqlNote.getContent()); | ||||||
|  |                 GTaskClient.getInstance().addUpdateNode(metaData); | ||||||
|  |             } else { | ||||||
|  |                 metaData = new MetaData(); | ||||||
|  |                 metaData.setMeta(gid, sqlNote.getContent()); | ||||||
|  |                 mMetaList.addChildTask(metaData); | ||||||
|  |                 mMetaHashMap.put(gid, metaData); | ||||||
|  |                 GTaskClient.getInstance().createTask(metaData); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void refreshLocalSyncId() throws NetworkFailureException { | ||||||
|  |         if (mCancelled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // get the latest gtask list
 | ||||||
|  |         mGTaskHashMap.clear(); | ||||||
|  |         mGTaskListHashMap.clear(); | ||||||
|  |         mMetaHashMap.clear(); | ||||||
|  |         initGTaskList(); | ||||||
|  | 
 | ||||||
|  |         Cursor c = null; | ||||||
|  |         try { | ||||||
|  |             c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, | ||||||
|  |                     "(type<>? AND parent_id<>?)", new String[] { | ||||||
|  |                             String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) | ||||||
|  |                     }, NoteColumns.TYPE + " DESC"); | ||||||
|  |             if (c != null) { | ||||||
|  |                 while (c.moveToNext()) { | ||||||
|  |                     String gid = c.getString(SqlNote.GTASK_ID_COLUMN); | ||||||
|  |                     Node node = mGTaskHashMap.get(gid); | ||||||
|  |                     if (node != null) { | ||||||
|  |                         mGTaskHashMap.remove(gid); | ||||||
|  |                         ContentValues values = new ContentValues(); | ||||||
|  |                         values.put(NoteColumns.SYNC_ID, node.getLastModified()); | ||||||
|  |                         mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, | ||||||
|  |                                 c.getLong(SqlNote.ID_COLUMN)), values, null, null); | ||||||
|  |                     } else { | ||||||
|  |                         Log.e(TAG, "something is missed"); | ||||||
|  |                         throw new ActionFailureException( | ||||||
|  |                                 "some local items don't have gid after sync"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 Log.w(TAG, "failed to query local note to refresh sync id"); | ||||||
|  |             } | ||||||
|  |         } finally { | ||||||
|  |             if (c != null) { | ||||||
|  |                 c.close(); | ||||||
|  |                 c = null; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public String getSyncAccount() { | ||||||
|  |         return GTaskClient.getInstance().getSyncAccount().name; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void cancelSync() { | ||||||
|  |         mCancelled = true; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,128 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *        http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package net.micode.notes.gtask.remote; | ||||||
|  | 
 | ||||||
|  | import android.app.Activity; | ||||||
|  | import android.app.Service; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.Intent; | ||||||
|  | import android.os.Bundle; | ||||||
|  | import android.os.IBinder; | ||||||
|  | 
 | ||||||
|  | public class GTaskSyncService extends Service { | ||||||
|  |     public final static String ACTION_STRING_NAME = "sync_action_type"; | ||||||
|  | 
 | ||||||
|  |     public final static int ACTION_START_SYNC = 0; | ||||||
|  | 
 | ||||||
|  |     public final static int ACTION_CANCEL_SYNC = 1; | ||||||
|  | 
 | ||||||
|  |     public final static int ACTION_INVALID = 2; | ||||||
|  | 
 | ||||||
|  |     public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; | ||||||
|  | 
 | ||||||
|  |     public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; | ||||||
|  | 
 | ||||||
|  |     public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; | ||||||
|  | 
 | ||||||
|  |     private static GTaskASyncTask mSyncTask = null; | ||||||
|  | 
 | ||||||
|  |     private static String mSyncProgress = ""; | ||||||
|  | 
 | ||||||
|  |     private void startSync() { | ||||||
|  |         if (mSyncTask == null) { | ||||||
|  |             mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { | ||||||
|  |                 public void onComplete() { | ||||||
|  |                     mSyncTask = null; | ||||||
|  |                     sendBroadcast(""); | ||||||
|  |                     stopSelf(); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|  |             sendBroadcast(""); | ||||||
|  |             mSyncTask.execute(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void cancelSync() { | ||||||
|  |         if (mSyncTask != null) { | ||||||
|  |             mSyncTask.cancelSync(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void onCreate() { | ||||||
|  |         mSyncTask = null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public int onStartCommand(Intent intent, int flags, int startId) { | ||||||
|  |         Bundle bundle = intent.getExtras(); | ||||||
|  |         if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { | ||||||
|  |             switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { | ||||||
|  |                 case ACTION_START_SYNC: | ||||||
|  |                     startSync(); | ||||||
|  |                     break; | ||||||
|  |                 case ACTION_CANCEL_SYNC: | ||||||
|  |                     cancelSync(); | ||||||
|  |                     break; | ||||||
|  |                 default: | ||||||
|  |                     break; | ||||||
|  |             } | ||||||
|  |             return START_STICKY; | ||||||
|  |         } | ||||||
|  |         return super.onStartCommand(intent, flags, startId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void onLowMemory() { | ||||||
|  |         if (mSyncTask != null) { | ||||||
|  |             mSyncTask.cancelSync(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public IBinder onBind(Intent intent) { | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void sendBroadcast(String msg) { | ||||||
|  |         mSyncProgress = msg; | ||||||
|  |         Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); | ||||||
|  |         intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); | ||||||
|  |         intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); | ||||||
|  |         sendBroadcast(intent); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static void startSync(Activity activity) { | ||||||
|  |         GTaskManager.getInstance().setActivityContext(activity); | ||||||
|  |         Intent intent = new Intent(activity, GTaskSyncService.class); | ||||||
|  |         intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); | ||||||
|  |         activity.startService(intent); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static void cancelSync(Context context) { | ||||||
|  |         Intent intent = new Intent(context, GTaskSyncService.class); | ||||||
|  |         intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); | ||||||
|  |         context.startService(intent); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static boolean isSyncing() { | ||||||
|  |         return mSyncTask != null; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static String getProgressString() { | ||||||
|  |         return mSyncProgress; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,253 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *        http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package net.micode.notes.model; | ||||||
|  | import android.content.ContentProviderOperation; | ||||||
|  | import android.content.ContentProviderResult; | ||||||
|  | import android.content.ContentUris; | ||||||
|  | import android.content.ContentValues; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.OperationApplicationException; | ||||||
|  | import android.net.Uri; | ||||||
|  | import android.os.RemoteException; | ||||||
|  | import android.util.Log; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.data.Notes.CallNote; | ||||||
|  | import net.micode.notes.data.Notes.DataColumns; | ||||||
|  | import net.micode.notes.data.Notes.NoteColumns; | ||||||
|  | import net.micode.notes.data.Notes.TextNote; | ||||||
|  | 
 | ||||||
|  | import java.util.ArrayList; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class Note { | ||||||
|  |     private ContentValues mNoteDiffValues; | ||||||
|  |     private NoteData mNoteData; | ||||||
|  |     private static final String TAG = "Note"; | ||||||
|  |     /** | ||||||
|  |      * Create a new note id for adding a new note to databases | ||||||
|  |      */ | ||||||
|  |     public static synchronized long getNewNoteId(Context context, long folderId) { | ||||||
|  |         // Create a new note in the database
 | ||||||
|  |         ContentValues values = new ContentValues(); | ||||||
|  |         long createdTime = System.currentTimeMillis(); | ||||||
|  |         values.put(NoteColumns.CREATED_DATE, createdTime); | ||||||
|  |         values.put(NoteColumns.MODIFIED_DATE, createdTime); | ||||||
|  |         values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); | ||||||
|  |         values.put(NoteColumns.LOCAL_MODIFIED, 1); | ||||||
|  |         values.put(NoteColumns.PARENT_ID, folderId); | ||||||
|  |         Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); | ||||||
|  | 
 | ||||||
|  |         long noteId = 0; | ||||||
|  |         try { | ||||||
|  |             noteId = Long.valueOf(uri.getPathSegments().get(1)); | ||||||
|  |         } catch (NumberFormatException e) { | ||||||
|  |             Log.e(TAG, "Get note id error :" + e.toString()); | ||||||
|  |             noteId = 0; | ||||||
|  |         } | ||||||
|  |         if (noteId == -1) { | ||||||
|  |             throw new IllegalStateException("Wrong note id:" + noteId); | ||||||
|  |         } | ||||||
|  |         return noteId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public Note() { | ||||||
|  |         mNoteDiffValues = new ContentValues(); | ||||||
|  |         mNoteData = new NoteData(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setNoteValue(String key, String value) { | ||||||
|  |         mNoteDiffValues.put(key, value); | ||||||
|  |         mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); | ||||||
|  |         mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setTextData(String key, String value) { | ||||||
|  |         mNoteData.setTextData(key, value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setTextDataId(long id) { | ||||||
|  |         mNoteData.setTextDataId(id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getTextDataId() { | ||||||
|  |         return mNoteData.mTextDataId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setCallDataId(long id) { | ||||||
|  |         mNoteData.setCallDataId(id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setCallData(String key, String value) { | ||||||
|  |         mNoteData.setCallData(key, value); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isLocalModified() { | ||||||
|  |         return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean syncNote(Context context, long noteId) { | ||||||
|  |         if (noteId <= 0) { | ||||||
|  |             throw new IllegalArgumentException("Wrong note id:" + noteId); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (!isLocalModified()) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and | ||||||
|  |          * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the | ||||||
|  |          * note data info | ||||||
|  |          */ | ||||||
|  |         if (context.getContentResolver().update( | ||||||
|  |                 ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, | ||||||
|  |                 null) == 0) { | ||||||
|  |             Log.e(TAG, "Update note error, should not happen"); | ||||||
|  |             // Do not return, fall through
 | ||||||
|  |         } | ||||||
|  |         mNoteDiffValues.clear(); | ||||||
|  | 
 | ||||||
|  |         if (mNoteData.isLocalModified() | ||||||
|  |                 && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private class NoteData { | ||||||
|  |         private long mTextDataId; | ||||||
|  | 
 | ||||||
|  |         private ContentValues mTextDataValues; | ||||||
|  | 
 | ||||||
|  |         private long mCallDataId; | ||||||
|  | 
 | ||||||
|  |         private ContentValues mCallDataValues; | ||||||
|  | 
 | ||||||
|  |         private static final String TAG = "NoteData"; | ||||||
|  | 
 | ||||||
|  |         public NoteData() { | ||||||
|  |             mTextDataValues = new ContentValues(); | ||||||
|  |             mCallDataValues = new ContentValues(); | ||||||
|  |             mTextDataId = 0; | ||||||
|  |             mCallDataId = 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         boolean isLocalModified() { | ||||||
|  |             return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void setTextDataId(long id) { | ||||||
|  |             if(id <= 0) { | ||||||
|  |                 throw new IllegalArgumentException("Text data id should larger than 0"); | ||||||
|  |             } | ||||||
|  |             mTextDataId = id; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void setCallDataId(long id) { | ||||||
|  |             if (id <= 0) { | ||||||
|  |                 throw new IllegalArgumentException("Call data id should larger than 0"); | ||||||
|  |             } | ||||||
|  |             mCallDataId = id; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void setCallData(String key, String value) { | ||||||
|  |             mCallDataValues.put(key, value); | ||||||
|  |             mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); | ||||||
|  |             mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         void setTextData(String key, String value) { | ||||||
|  |             mTextDataValues.put(key, value); | ||||||
|  |             mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); | ||||||
|  |             mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Uri pushIntoContentResolver(Context context, long noteId) { | ||||||
|  |             /** | ||||||
|  |              * Check for safety | ||||||
|  |              */ | ||||||
|  |             if (noteId <= 0) { | ||||||
|  |                 throw new IllegalArgumentException("Wrong note id:" + noteId); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>(); | ||||||
|  |             ContentProviderOperation.Builder builder = null; | ||||||
|  | 
 | ||||||
|  |             if(mTextDataValues.size() > 0) { | ||||||
|  |                 mTextDataValues.put(DataColumns.NOTE_ID, noteId); | ||||||
|  |                 if (mTextDataId == 0) { | ||||||
|  |                     mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); | ||||||
|  |                     Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, | ||||||
|  |                             mTextDataValues); | ||||||
|  |                     try { | ||||||
|  |                         setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); | ||||||
|  |                     } catch (NumberFormatException e) { | ||||||
|  |                         Log.e(TAG, "Insert new text data fail with noteId" + noteId); | ||||||
|  |                         mTextDataValues.clear(); | ||||||
|  |                         return null; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( | ||||||
|  |                             Notes.CONTENT_DATA_URI, mTextDataId)); | ||||||
|  |                     builder.withValues(mTextDataValues); | ||||||
|  |                     operationList.add(builder.build()); | ||||||
|  |                 } | ||||||
|  |                 mTextDataValues.clear(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if(mCallDataValues.size() > 0) { | ||||||
|  |                 mCallDataValues.put(DataColumns.NOTE_ID, noteId); | ||||||
|  |                 if (mCallDataId == 0) { | ||||||
|  |                     mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); | ||||||
|  |                     Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, | ||||||
|  |                             mCallDataValues); | ||||||
|  |                     try { | ||||||
|  |                         setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); | ||||||
|  |                     } catch (NumberFormatException e) { | ||||||
|  |                         Log.e(TAG, "Insert new call data fail with noteId" + noteId); | ||||||
|  |                         mCallDataValues.clear(); | ||||||
|  |                         return null; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( | ||||||
|  |                             Notes.CONTENT_DATA_URI, mCallDataId)); | ||||||
|  |                     builder.withValues(mCallDataValues); | ||||||
|  |                     operationList.add(builder.build()); | ||||||
|  |                 } | ||||||
|  |                 mCallDataValues.clear(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (operationList.size() > 0) { | ||||||
|  |                 try { | ||||||
|  |                     ContentProviderResult[] results = context.getContentResolver().applyBatch( | ||||||
|  |                             Notes.AUTHORITY, operationList); | ||||||
|  |                     return (results == null || results.length == 0 || results[0] == null) ? null | ||||||
|  |                             : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); | ||||||
|  |                 } catch (RemoteException e) { | ||||||
|  |                     Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); | ||||||
|  |                     return null; | ||||||
|  |                 } catch (OperationApplicationException e) { | ||||||
|  |                     Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); | ||||||
|  |                     return null; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             return null; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,368 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *        http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package net.micode.notes.model; | ||||||
|  | 
 | ||||||
|  | import android.appwidget.AppWidgetManager; | ||||||
|  | import android.content.ContentUris; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.database.Cursor; | ||||||
|  | import android.text.TextUtils; | ||||||
|  | import android.util.Log; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.data.Notes.CallNote; | ||||||
|  | import net.micode.notes.data.Notes.DataColumns; | ||||||
|  | import net.micode.notes.data.Notes.DataConstants; | ||||||
|  | import net.micode.notes.data.Notes.NoteColumns; | ||||||
|  | import net.micode.notes.data.Notes.TextNote; | ||||||
|  | import net.micode.notes.tool.ResourceParser.NoteBgResources; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class WorkingNote { | ||||||
|  |     // Note for the working note
 | ||||||
|  |     private Note mNote; | ||||||
|  |     // Note Id
 | ||||||
|  |     private long mNoteId; | ||||||
|  |     // Note content
 | ||||||
|  |     private String mContent; | ||||||
|  |     // Note mode
 | ||||||
|  |     private int mMode; | ||||||
|  | 
 | ||||||
|  |     private long mAlertDate; | ||||||
|  | 
 | ||||||
|  |     private long mModifiedDate; | ||||||
|  | 
 | ||||||
|  |     private int mBgColorId; | ||||||
|  | 
 | ||||||
|  |     private int mWidgetId; | ||||||
|  | 
 | ||||||
|  |     private int mWidgetType; | ||||||
|  | 
 | ||||||
|  |     private long mFolderId; | ||||||
|  | 
 | ||||||
|  |     private Context mContext; | ||||||
|  | 
 | ||||||
|  |     private static final String TAG = "WorkingNote"; | ||||||
|  | 
 | ||||||
|  |     private boolean mIsDeleted; | ||||||
|  | 
 | ||||||
|  |     private NoteSettingChangedListener mNoteSettingStatusListener; | ||||||
|  | 
 | ||||||
|  |     public static final String[] DATA_PROJECTION = new String[] { | ||||||
|  |             DataColumns.ID, | ||||||
|  |             DataColumns.CONTENT, | ||||||
|  |             DataColumns.MIME_TYPE, | ||||||
|  |             DataColumns.DATA1, | ||||||
|  |             DataColumns.DATA2, | ||||||
|  |             DataColumns.DATA3, | ||||||
|  |             DataColumns.DATA4, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     public static final String[] NOTE_PROJECTION = new String[] { | ||||||
|  |             NoteColumns.PARENT_ID, | ||||||
|  |             NoteColumns.ALERTED_DATE, | ||||||
|  |             NoteColumns.BG_COLOR_ID, | ||||||
|  |             NoteColumns.WIDGET_ID, | ||||||
|  |             NoteColumns.WIDGET_TYPE, | ||||||
|  |             NoteColumns.MODIFIED_DATE | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     private static final int DATA_ID_COLUMN = 0; | ||||||
|  | 
 | ||||||
|  |     private static final int DATA_CONTENT_COLUMN = 1; | ||||||
|  | 
 | ||||||
|  |     private static final int DATA_MIME_TYPE_COLUMN = 2; | ||||||
|  | 
 | ||||||
|  |     private static final int DATA_MODE_COLUMN = 3; | ||||||
|  | 
 | ||||||
|  |     private static final int NOTE_PARENT_ID_COLUMN = 0; | ||||||
|  | 
 | ||||||
|  |     private static final int NOTE_ALERTED_DATE_COLUMN = 1; | ||||||
|  | 
 | ||||||
|  |     private static final int NOTE_BG_COLOR_ID_COLUMN = 2; | ||||||
|  | 
 | ||||||
|  |     private static final int NOTE_WIDGET_ID_COLUMN = 3; | ||||||
|  | 
 | ||||||
|  |     private static final int NOTE_WIDGET_TYPE_COLUMN = 4; | ||||||
|  | 
 | ||||||
|  |     private static final int NOTE_MODIFIED_DATE_COLUMN = 5; | ||||||
|  | 
 | ||||||
|  |     // New note construct
 | ||||||
|  |     private WorkingNote(Context context, long folderId) { | ||||||
|  |         mContext = context; | ||||||
|  |         mAlertDate = 0; | ||||||
|  |         mModifiedDate = System.currentTimeMillis(); | ||||||
|  |         mFolderId = folderId; | ||||||
|  |         mNote = new Note(); | ||||||
|  |         mNoteId = 0; | ||||||
|  |         mIsDeleted = false; | ||||||
|  |         mMode = 0; | ||||||
|  |         mWidgetType = Notes.TYPE_WIDGET_INVALIDE; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Existing note construct
 | ||||||
|  |     private WorkingNote(Context context, long noteId, long folderId) { | ||||||
|  |         mContext = context; | ||||||
|  |         mNoteId = noteId; | ||||||
|  |         mFolderId = folderId; | ||||||
|  |         mIsDeleted = false; | ||||||
|  |         mNote = new Note(); | ||||||
|  |         loadNote(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void loadNote() { | ||||||
|  |         Cursor cursor = mContext.getContentResolver().query( | ||||||
|  |                 ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, | ||||||
|  |                 null, null); | ||||||
|  | 
 | ||||||
|  |         if (cursor != null) { | ||||||
|  |             if (cursor.moveToFirst()) { | ||||||
|  |                 mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); | ||||||
|  |                 mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); | ||||||
|  |                 mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); | ||||||
|  |                 mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); | ||||||
|  |                 mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); | ||||||
|  |                 mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); | ||||||
|  |             } | ||||||
|  |             cursor.close(); | ||||||
|  |         } else { | ||||||
|  |             Log.e(TAG, "No note with id:" + mNoteId); | ||||||
|  |             throw new IllegalArgumentException("Unable to find note with id " + mNoteId); | ||||||
|  |         } | ||||||
|  |         loadNoteData(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void loadNoteData() { | ||||||
|  |         Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, | ||||||
|  |                 DataColumns.NOTE_ID + "=?", new String[] { | ||||||
|  |                     String.valueOf(mNoteId) | ||||||
|  |                 }, null); | ||||||
|  | 
 | ||||||
|  |         if (cursor != null) { | ||||||
|  |             if (cursor.moveToFirst()) { | ||||||
|  |                 do { | ||||||
|  |                     String type = cursor.getString(DATA_MIME_TYPE_COLUMN); | ||||||
|  |                     if (DataConstants.NOTE.equals(type)) { | ||||||
|  |                         mContent = cursor.getString(DATA_CONTENT_COLUMN); | ||||||
|  |                         mMode = cursor.getInt(DATA_MODE_COLUMN); | ||||||
|  |                         mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); | ||||||
|  |                     } else if (DataConstants.CALL_NOTE.equals(type)) { | ||||||
|  |                         mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); | ||||||
|  |                     } else { | ||||||
|  |                         Log.d(TAG, "Wrong note type with type:" + type); | ||||||
|  |                     } | ||||||
|  |                 } while (cursor.moveToNext()); | ||||||
|  |             } | ||||||
|  |             cursor.close(); | ||||||
|  |         } else { | ||||||
|  |             Log.e(TAG, "No data with id:" + mNoteId); | ||||||
|  |             throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, | ||||||
|  |             int widgetType, int defaultBgColorId) { | ||||||
|  |         WorkingNote note = new WorkingNote(context, folderId); | ||||||
|  |         note.setBgColorId(defaultBgColorId); | ||||||
|  |         note.setWidgetId(widgetId); | ||||||
|  |         note.setWidgetType(widgetType); | ||||||
|  |         return note; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static WorkingNote load(Context context, long id) { | ||||||
|  |         return new WorkingNote(context, id, 0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public synchronized boolean saveNote() { | ||||||
|  |         if (isWorthSaving()) { | ||||||
|  |             if (!existInDatabase()) { | ||||||
|  |                 if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { | ||||||
|  |                     Log.e(TAG, "Create new note fail with id:" + mNoteId); | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             mNote.syncNote(mContext, mNoteId); | ||||||
|  | 
 | ||||||
|  |             /** | ||||||
|  |              * Update widget content if there exist any widget of this note | ||||||
|  |              */ | ||||||
|  |             if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID | ||||||
|  |                     && mWidgetType != Notes.TYPE_WIDGET_INVALIDE | ||||||
|  |                     && mNoteSettingStatusListener != null) { | ||||||
|  |                 mNoteSettingStatusListener.onWidgetChanged(); | ||||||
|  |             } | ||||||
|  |             return true; | ||||||
|  |         } else { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean existInDatabase() { | ||||||
|  |         return mNoteId > 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private boolean isWorthSaving() { | ||||||
|  |         if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) | ||||||
|  |                 || (existInDatabase() && !mNote.isLocalModified())) { | ||||||
|  |             return false; | ||||||
|  |         } else { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { | ||||||
|  |         mNoteSettingStatusListener = l; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setAlertDate(long date, boolean set) { | ||||||
|  |         if (date != mAlertDate) { | ||||||
|  |             mAlertDate = date; | ||||||
|  |             mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); | ||||||
|  |         } | ||||||
|  |         if (mNoteSettingStatusListener != null) { | ||||||
|  |             mNoteSettingStatusListener.onClockAlertChanged(date, set); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void markDeleted(boolean mark) { | ||||||
|  |         mIsDeleted = mark; | ||||||
|  |         if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID | ||||||
|  |                 && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { | ||||||
|  |                 mNoteSettingStatusListener.onWidgetChanged(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setBgColorId(int id) { | ||||||
|  |         if (id != mBgColorId) { | ||||||
|  |             mBgColorId = id; | ||||||
|  |             if (mNoteSettingStatusListener != null) { | ||||||
|  |                 mNoteSettingStatusListener.onBackgroundColorChanged(); | ||||||
|  |             } | ||||||
|  |             mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setCheckListMode(int mode) { | ||||||
|  |         if (mMode != mode) { | ||||||
|  |             if (mNoteSettingStatusListener != null) { | ||||||
|  |                 mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); | ||||||
|  |             } | ||||||
|  |             mMode = mode; | ||||||
|  |             mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setWidgetType(int type) { | ||||||
|  |         if (type != mWidgetType) { | ||||||
|  |             mWidgetType = type; | ||||||
|  |             mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setWidgetId(int id) { | ||||||
|  |         if (id != mWidgetId) { | ||||||
|  |             mWidgetId = id; | ||||||
|  |             mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setWorkingText(String text) { | ||||||
|  |         if (!TextUtils.equals(mContent, text)) { | ||||||
|  |             mContent = text; | ||||||
|  |             mNote.setTextData(DataColumns.CONTENT, mContent); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void convertToCallNote(String phoneNumber, long callDate) { | ||||||
|  |         mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); | ||||||
|  |         mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); | ||||||
|  |         mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean hasClockAlert() { | ||||||
|  |         return (mAlertDate > 0 ? true : false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public String getContent() { | ||||||
|  |         return mContent; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getAlertDate() { | ||||||
|  |         return mAlertDate; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getModifiedDate() { | ||||||
|  |         return mModifiedDate; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getBgColorResId() { | ||||||
|  |         return NoteBgResources.getNoteBgResource(mBgColorId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getBgColorId() { | ||||||
|  |         return mBgColorId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getTitleBgResId() { | ||||||
|  |         return NoteBgResources.getNoteTitleBgResource(mBgColorId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getCheckListMode() { | ||||||
|  |         return mMode; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getNoteId() { | ||||||
|  |         return mNoteId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getFolderId() { | ||||||
|  |         return mFolderId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getWidgetId() { | ||||||
|  |         return mWidgetId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getWidgetType() { | ||||||
|  |         return mWidgetType; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public interface NoteSettingChangedListener { | ||||||
|  |         /** | ||||||
|  |          * Called when the background color of current note has just changed | ||||||
|  |          */ | ||||||
|  |         void onBackgroundColorChanged(); | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Called when user set clock | ||||||
|  |          */ | ||||||
|  |         void onClockAlertChanged(long date, boolean set); | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Call when user create note from widget | ||||||
|  |          */ | ||||||
|  |         void onWidgetChanged(); | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Call when switch between check list mode and normal mode | ||||||
|  |          * @param oldMode is previous mode before change | ||||||
|  |          * @param newMode is new mode | ||||||
|  |          */ | ||||||
|  |         void onCheckListModeChanged(int oldMode, int newMode); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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,158 @@ | |||||||
|  | /* | ||||||
|  |  * 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; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 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 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) { | ||||||
|  |             // TODO Auto-generated catch block
 | ||||||
|  |             e.printStackTrace(); | ||||||
|  |         } catch (SecurityException e) { | ||||||
|  |             // TODO Auto-generated catch block
 | ||||||
|  |             e.printStackTrace(); | ||||||
|  |         } catch (IllegalStateException e) { | ||||||
|  |             // TODO Auto-generated catch block
 | ||||||
|  |             e.printStackTrace(); | ||||||
|  |         } catch (IOException e) { | ||||||
|  |             // TODO Auto-generated catch block
 | ||||||
|  |             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: | ||||||
|  |                 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,65 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *        http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package net.micode.notes.ui; | ||||||
|  | 
 | ||||||
|  | import android.app.AlarmManager; | ||||||
|  | import android.app.PendingIntent; | ||||||
|  | import android.content.BroadcastReceiver; | ||||||
|  | import android.content.ContentUris; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.Intent; | ||||||
|  | import android.database.Cursor; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.data.Notes.NoteColumns; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class AlarmInitReceiver extends BroadcastReceiver { | ||||||
|  | 
 | ||||||
|  |     private static final String [] PROJECTION = new String [] { | ||||||
|  |         NoteColumns.ID, | ||||||
|  |         NoteColumns.ALERTED_DATE | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     private static final int COLUMN_ID                = 0; | ||||||
|  |     private static final int COLUMN_ALERTED_DATE      = 1; | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void onReceive(Context context, Intent intent) { | ||||||
|  |         long currentDate = System.currentTimeMillis(); | ||||||
|  |         Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, | ||||||
|  |                 PROJECTION, | ||||||
|  |                 NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, | ||||||
|  |                 new String[] { String.valueOf(currentDate) }, | ||||||
|  |                 null); | ||||||
|  | 
 | ||||||
|  |         if (c != null) { | ||||||
|  |             if (c.moveToFirst()) { | ||||||
|  |                 do { | ||||||
|  |                     long alertDate = c.getLong(COLUMN_ALERTED_DATE); | ||||||
|  |                     Intent sender = new Intent(context, AlarmReceiver.class); | ||||||
|  |                     sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); | ||||||
|  |                     PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); | ||||||
|  |                     AlarmManager alermManager = (AlarmManager) context | ||||||
|  |                             .getSystemService(Context.ALARM_SERVICE); | ||||||
|  |                     alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); | ||||||
|  |                 } while (c.moveToNext()); | ||||||
|  |             } | ||||||
|  |             c.close(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,30 @@ | |||||||
|  | /* | ||||||
|  |  * 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.BroadcastReceiver; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.Intent; | ||||||
|  | 
 | ||||||
|  | public class AlarmReceiver extends BroadcastReceiver { | ||||||
|  |     @Override | ||||||
|  |     public void onReceive(Context context, Intent intent) { | ||||||
|  |         intent.setClass(context, AlarmAlertActivity.class); | ||||||
|  |         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | ||||||
|  |         context.startActivity(intent); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,485 @@ | |||||||
|  | /* | ||||||
|  |  * 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 java.text.DateFormatSymbols; | ||||||
|  | import java.util.Calendar; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | import android.content.Context; | ||||||
|  | import android.text.format.DateFormat; | ||||||
|  | import android.view.View; | ||||||
|  | import android.widget.FrameLayout; | ||||||
|  | import android.widget.NumberPicker; | ||||||
|  | 
 | ||||||
|  | public class DateTimePicker extends FrameLayout { | ||||||
|  | 
 | ||||||
|  |     private static final boolean DEFAULT_ENABLE_STATE = true; | ||||||
|  | 
 | ||||||
|  |     private static final int HOURS_IN_HALF_DAY = 12; | ||||||
|  |     private static final int HOURS_IN_ALL_DAY = 24; | ||||||
|  |     private static final int DAYS_IN_ALL_WEEK = 7; | ||||||
|  |     private static final int DATE_SPINNER_MIN_VAL = 0; | ||||||
|  |     private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; | ||||||
|  |     private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; | ||||||
|  |     private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; | ||||||
|  |     private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; | ||||||
|  |     private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; | ||||||
|  |     private static final int MINUT_SPINNER_MIN_VAL = 0; | ||||||
|  |     private static final int MINUT_SPINNER_MAX_VAL = 59; | ||||||
|  |     private static final int AMPM_SPINNER_MIN_VAL = 0; | ||||||
|  |     private static final int AMPM_SPINNER_MAX_VAL = 1; | ||||||
|  | 
 | ||||||
|  |     private final NumberPicker mDateSpinner; | ||||||
|  |     private final NumberPicker mHourSpinner; | ||||||
|  |     private final NumberPicker mMinuteSpinner; | ||||||
|  |     private final NumberPicker mAmPmSpinner; | ||||||
|  |     private Calendar mDate; | ||||||
|  | 
 | ||||||
|  |     private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; | ||||||
|  | 
 | ||||||
|  |     private boolean mIsAm; | ||||||
|  | 
 | ||||||
|  |     private boolean mIs24HourView; | ||||||
|  | 
 | ||||||
|  |     private boolean mIsEnabled = DEFAULT_ENABLE_STATE; | ||||||
|  | 
 | ||||||
|  |     private boolean mInitialising; | ||||||
|  | 
 | ||||||
|  |     private OnDateTimeChangedListener mOnDateTimeChangedListener; | ||||||
|  | 
 | ||||||
|  |     private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { | ||||||
|  |         @Override | ||||||
|  |         public void onValueChange(NumberPicker picker, int oldVal, int newVal) { | ||||||
|  |             mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); | ||||||
|  |             updateDateControl(); | ||||||
|  |             onDateTimeChanged(); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { | ||||||
|  |         @Override | ||||||
|  |         public void onValueChange(NumberPicker picker, int oldVal, int newVal) { | ||||||
|  |             boolean isDateChanged = false; | ||||||
|  |             Calendar cal = Calendar.getInstance(); | ||||||
|  |             if (!mIs24HourView) { | ||||||
|  |                 if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { | ||||||
|  |                     cal.setTimeInMillis(mDate.getTimeInMillis()); | ||||||
|  |                     cal.add(Calendar.DAY_OF_YEAR, 1); | ||||||
|  |                     isDateChanged = true; | ||||||
|  |                 } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { | ||||||
|  |                     cal.setTimeInMillis(mDate.getTimeInMillis()); | ||||||
|  |                     cal.add(Calendar.DAY_OF_YEAR, -1); | ||||||
|  |                     isDateChanged = true; | ||||||
|  |                 } | ||||||
|  |                 if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || | ||||||
|  |                         oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { | ||||||
|  |                     mIsAm = !mIsAm; | ||||||
|  |                     updateAmPmControl(); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { | ||||||
|  |                     cal.setTimeInMillis(mDate.getTimeInMillis()); | ||||||
|  |                     cal.add(Calendar.DAY_OF_YEAR, 1); | ||||||
|  |                     isDateChanged = true; | ||||||
|  |                 } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { | ||||||
|  |                     cal.setTimeInMillis(mDate.getTimeInMillis()); | ||||||
|  |                     cal.add(Calendar.DAY_OF_YEAR, -1); | ||||||
|  |                     isDateChanged = true; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); | ||||||
|  |             mDate.set(Calendar.HOUR_OF_DAY, newHour); | ||||||
|  |             onDateTimeChanged(); | ||||||
|  |             if (isDateChanged) { | ||||||
|  |                 setCurrentYear(cal.get(Calendar.YEAR)); | ||||||
|  |                 setCurrentMonth(cal.get(Calendar.MONTH)); | ||||||
|  |                 setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { | ||||||
|  |         @Override | ||||||
|  |         public void onValueChange(NumberPicker picker, int oldVal, int newVal) { | ||||||
|  |             int minValue = mMinuteSpinner.getMinValue(); | ||||||
|  |             int maxValue = mMinuteSpinner.getMaxValue(); | ||||||
|  |             int offset = 0; | ||||||
|  |             if (oldVal == maxValue && newVal == minValue) { | ||||||
|  |                 offset += 1; | ||||||
|  |             } else if (oldVal == minValue && newVal == maxValue) { | ||||||
|  |                 offset -= 1; | ||||||
|  |             } | ||||||
|  |             if (offset != 0) { | ||||||
|  |                 mDate.add(Calendar.HOUR_OF_DAY, offset); | ||||||
|  |                 mHourSpinner.setValue(getCurrentHour()); | ||||||
|  |                 updateDateControl(); | ||||||
|  |                 int newHour = getCurrentHourOfDay(); | ||||||
|  |                 if (newHour >= HOURS_IN_HALF_DAY) { | ||||||
|  |                     mIsAm = false; | ||||||
|  |                     updateAmPmControl(); | ||||||
|  |                 } else { | ||||||
|  |                     mIsAm = true; | ||||||
|  |                     updateAmPmControl(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             mDate.set(Calendar.MINUTE, newVal); | ||||||
|  |             onDateTimeChanged(); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { | ||||||
|  |         @Override | ||||||
|  |         public void onValueChange(NumberPicker picker, int oldVal, int newVal) { | ||||||
|  |             mIsAm = !mIsAm; | ||||||
|  |             if (mIsAm) { | ||||||
|  |                 mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); | ||||||
|  |             } else { | ||||||
|  |                 mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); | ||||||
|  |             } | ||||||
|  |             updateAmPmControl(); | ||||||
|  |             onDateTimeChanged(); | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     public interface OnDateTimeChangedListener { | ||||||
|  |         void onDateTimeChanged(DateTimePicker view, int year, int month, | ||||||
|  |                 int dayOfMonth, int hourOfDay, int minute); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public DateTimePicker(Context context) { | ||||||
|  |         this(context, System.currentTimeMillis()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public DateTimePicker(Context context, long date) { | ||||||
|  |         this(context, date, DateFormat.is24HourFormat(context)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public DateTimePicker(Context context, long date, boolean is24HourView) { | ||||||
|  |         super(context); | ||||||
|  |         mDate = Calendar.getInstance(); | ||||||
|  |         mInitialising = true; | ||||||
|  |         mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; | ||||||
|  |         inflate(context, R.layout.datetime_picker, this); | ||||||
|  | 
 | ||||||
|  |         mDateSpinner = (NumberPicker) findViewById(R.id.date); | ||||||
|  |         mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); | ||||||
|  |         mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); | ||||||
|  |         mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); | ||||||
|  | 
 | ||||||
|  |         mHourSpinner = (NumberPicker) findViewById(R.id.hour); | ||||||
|  |         mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); | ||||||
|  |         mMinuteSpinner =  (NumberPicker) findViewById(R.id.minute); | ||||||
|  |         mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); | ||||||
|  |         mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); | ||||||
|  |         mMinuteSpinner.setOnLongPressUpdateInterval(100); | ||||||
|  |         mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); | ||||||
|  | 
 | ||||||
|  |         String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); | ||||||
|  |         mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); | ||||||
|  |         mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); | ||||||
|  |         mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); | ||||||
|  |         mAmPmSpinner.setDisplayedValues(stringsForAmPm); | ||||||
|  |         mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); | ||||||
|  | 
 | ||||||
|  |         // update controls to initial state
 | ||||||
|  |         updateDateControl(); | ||||||
|  |         updateHourControl(); | ||||||
|  |         updateAmPmControl(); | ||||||
|  | 
 | ||||||
|  |         set24HourView(is24HourView); | ||||||
|  | 
 | ||||||
|  |         // set to current time
 | ||||||
|  |         setCurrentDate(date); | ||||||
|  | 
 | ||||||
|  |         setEnabled(isEnabled()); | ||||||
|  | 
 | ||||||
|  |         // set the content descriptions
 | ||||||
|  |         mInitialising = false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void setEnabled(boolean enabled) { | ||||||
|  |         if (mIsEnabled == enabled) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         super.setEnabled(enabled); | ||||||
|  |         mDateSpinner.setEnabled(enabled); | ||||||
|  |         mMinuteSpinner.setEnabled(enabled); | ||||||
|  |         mHourSpinner.setEnabled(enabled); | ||||||
|  |         mAmPmSpinner.setEnabled(enabled); | ||||||
|  |         mIsEnabled = enabled; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean isEnabled() { | ||||||
|  |         return mIsEnabled; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the current date in millis | ||||||
|  |      * | ||||||
|  |      * @return the current date in millis | ||||||
|  |      */ | ||||||
|  |     public long getCurrentDateInTimeMillis() { | ||||||
|  |         return mDate.getTimeInMillis(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Set the current date | ||||||
|  |      * | ||||||
|  |      * @param date The current date in millis | ||||||
|  |      */ | ||||||
|  |     public void setCurrentDate(long date) { | ||||||
|  |         Calendar cal = Calendar.getInstance(); | ||||||
|  |         cal.setTimeInMillis(date); | ||||||
|  |         setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), | ||||||
|  |                 cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Set the current date | ||||||
|  |      * | ||||||
|  |      * @param year The current year | ||||||
|  |      * @param month The current month | ||||||
|  |      * @param dayOfMonth The current dayOfMonth | ||||||
|  |      * @param hourOfDay The current hourOfDay | ||||||
|  |      * @param minute The current minute | ||||||
|  |      */ | ||||||
|  |     public void setCurrentDate(int year, int month, | ||||||
|  |             int dayOfMonth, int hourOfDay, int minute) { | ||||||
|  |         setCurrentYear(year); | ||||||
|  |         setCurrentMonth(month); | ||||||
|  |         setCurrentDay(dayOfMonth); | ||||||
|  |         setCurrentHour(hourOfDay); | ||||||
|  |         setCurrentMinute(minute); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get current year | ||||||
|  |      * | ||||||
|  |      * @return The current year | ||||||
|  |      */ | ||||||
|  |     public int getCurrentYear() { | ||||||
|  |         return mDate.get(Calendar.YEAR); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Set current year | ||||||
|  |      * | ||||||
|  |      * @param year The current year | ||||||
|  |      */ | ||||||
|  |     public void setCurrentYear(int year) { | ||||||
|  |         if (!mInitialising && year == getCurrentYear()) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         mDate.set(Calendar.YEAR, year); | ||||||
|  |         updateDateControl(); | ||||||
|  |         onDateTimeChanged(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get current month in the year | ||||||
|  |      * | ||||||
|  |      * @return The current month in the year | ||||||
|  |      */ | ||||||
|  |     public int getCurrentMonth() { | ||||||
|  |         return mDate.get(Calendar.MONTH); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Set current month in the year | ||||||
|  |      * | ||||||
|  |      * @param month The month in the year | ||||||
|  |      */ | ||||||
|  |     public void setCurrentMonth(int month) { | ||||||
|  |         if (!mInitialising && month == getCurrentMonth()) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         mDate.set(Calendar.MONTH, month); | ||||||
|  |         updateDateControl(); | ||||||
|  |         onDateTimeChanged(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get current day of the month | ||||||
|  |      * | ||||||
|  |      * @return The day of the month | ||||||
|  |      */ | ||||||
|  |     public int getCurrentDay() { | ||||||
|  |         return mDate.get(Calendar.DAY_OF_MONTH); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Set current day of the month | ||||||
|  |      * | ||||||
|  |      * @param dayOfMonth The day of the month | ||||||
|  |      */ | ||||||
|  |     public void setCurrentDay(int dayOfMonth) { | ||||||
|  |         if (!mInitialising && dayOfMonth == getCurrentDay()) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); | ||||||
|  |         updateDateControl(); | ||||||
|  |         onDateTimeChanged(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get current hour in 24 hour mode, in the range (0~23) | ||||||
|  |      * @return The current hour in 24 hour mode | ||||||
|  |      */ | ||||||
|  |     public int getCurrentHourOfDay() { | ||||||
|  |         return mDate.get(Calendar.HOUR_OF_DAY); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private int getCurrentHour() { | ||||||
|  |         if (mIs24HourView){ | ||||||
|  |             return getCurrentHourOfDay(); | ||||||
|  |         } else { | ||||||
|  |             int hour = getCurrentHourOfDay(); | ||||||
|  |             if (hour > HOURS_IN_HALF_DAY) { | ||||||
|  |                 return hour - HOURS_IN_HALF_DAY; | ||||||
|  |             } else { | ||||||
|  |                 return hour == 0 ? HOURS_IN_HALF_DAY : hour; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Set current hour in 24 hour mode, in the range (0~23) | ||||||
|  |      * | ||||||
|  |      * @param hourOfDay | ||||||
|  |      */ | ||||||
|  |     public void setCurrentHour(int hourOfDay) { | ||||||
|  |         if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); | ||||||
|  |         if (!mIs24HourView) { | ||||||
|  |             if (hourOfDay >= HOURS_IN_HALF_DAY) { | ||||||
|  |                 mIsAm = false; | ||||||
|  |                 if (hourOfDay > HOURS_IN_HALF_DAY) { | ||||||
|  |                     hourOfDay -= HOURS_IN_HALF_DAY; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 mIsAm = true; | ||||||
|  |                 if (hourOfDay == 0) { | ||||||
|  |                     hourOfDay = HOURS_IN_HALF_DAY; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             updateAmPmControl(); | ||||||
|  |         } | ||||||
|  |         mHourSpinner.setValue(hourOfDay); | ||||||
|  |         onDateTimeChanged(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get currentMinute | ||||||
|  |      * | ||||||
|  |      * @return The Current Minute | ||||||
|  |      */ | ||||||
|  |     public int getCurrentMinute() { | ||||||
|  |         return mDate.get(Calendar.MINUTE); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Set current minute | ||||||
|  |      */ | ||||||
|  |     public void setCurrentMinute(int minute) { | ||||||
|  |         if (!mInitialising && minute == getCurrentMinute()) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         mMinuteSpinner.setValue(minute); | ||||||
|  |         mDate.set(Calendar.MINUTE, minute); | ||||||
|  |         onDateTimeChanged(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * @return true if this is in 24 hour view else false. | ||||||
|  |      */ | ||||||
|  |     public boolean is24HourView () { | ||||||
|  |         return mIs24HourView; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Set whether in 24 hour or AM/PM mode. | ||||||
|  |      * | ||||||
|  |      * @param is24HourView True for 24 hour mode. False for AM/PM mode. | ||||||
|  |      */ | ||||||
|  |     public void set24HourView(boolean is24HourView) { | ||||||
|  |         if (mIs24HourView == is24HourView) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         mIs24HourView = is24HourView; | ||||||
|  |         mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); | ||||||
|  |         int hour = getCurrentHourOfDay(); | ||||||
|  |         updateHourControl(); | ||||||
|  |         setCurrentHour(hour); | ||||||
|  |         updateAmPmControl(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void updateDateControl() { | ||||||
|  |         Calendar cal = Calendar.getInstance(); | ||||||
|  |         cal.setTimeInMillis(mDate.getTimeInMillis()); | ||||||
|  |         cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); | ||||||
|  |         mDateSpinner.setDisplayedValues(null); | ||||||
|  |         for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { | ||||||
|  |             cal.add(Calendar.DAY_OF_YEAR, 1); | ||||||
|  |             mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); | ||||||
|  |         } | ||||||
|  |         mDateSpinner.setDisplayedValues(mDateDisplayValues); | ||||||
|  |         mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); | ||||||
|  |         mDateSpinner.invalidate(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void updateAmPmControl() { | ||||||
|  |         if (mIs24HourView) { | ||||||
|  |             mAmPmSpinner.setVisibility(View.GONE); | ||||||
|  |         } else { | ||||||
|  |             int index = mIsAm ? Calendar.AM : Calendar.PM; | ||||||
|  |             mAmPmSpinner.setValue(index); | ||||||
|  |             mAmPmSpinner.setVisibility(View.VISIBLE); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void updateHourControl() { | ||||||
|  |         if (mIs24HourView) { | ||||||
|  |             mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); | ||||||
|  |             mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); | ||||||
|  |         } else { | ||||||
|  |             mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); | ||||||
|  |             mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Set the callback that indicates the 'Set' button has been pressed. | ||||||
|  |      * @param callback the callback, if null will do nothing | ||||||
|  |      */ | ||||||
|  |     public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { | ||||||
|  |         mOnDateTimeChangedListener = callback; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void onDateTimeChanged() { | ||||||
|  |         if (mOnDateTimeChangedListener != null) { | ||||||
|  |             mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), | ||||||
|  |                     getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,90 @@ | |||||||
|  | /* | ||||||
|  |  * 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 java.util.Calendar; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | import net.micode.notes.ui.DateTimePicker; | ||||||
|  | import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; | ||||||
|  | 
 | ||||||
|  | import android.app.AlertDialog; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.DialogInterface; | ||||||
|  | import android.content.DialogInterface.OnClickListener; | ||||||
|  | import android.text.format.DateFormat; | ||||||
|  | import android.text.format.DateUtils; | ||||||
|  | 
 | ||||||
|  | public class DateTimePickerDialog extends AlertDialog implements OnClickListener { | ||||||
|  | 
 | ||||||
|  |     private Calendar mDate = Calendar.getInstance(); | ||||||
|  |     private boolean mIs24HourView; | ||||||
|  |     private OnDateTimeSetListener mOnDateTimeSetListener; | ||||||
|  |     private DateTimePicker mDateTimePicker; | ||||||
|  | 
 | ||||||
|  |     public interface OnDateTimeSetListener { | ||||||
|  |         void OnDateTimeSet(AlertDialog dialog, long date); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public DateTimePickerDialog(Context context, long date) { | ||||||
|  |         super(context); | ||||||
|  |         mDateTimePicker = new DateTimePicker(context); | ||||||
|  |         setView(mDateTimePicker); | ||||||
|  |         mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { | ||||||
|  |             public void onDateTimeChanged(DateTimePicker view, int year, int month, | ||||||
|  |                     int dayOfMonth, int hourOfDay, int minute) { | ||||||
|  |                 mDate.set(Calendar.YEAR, year); | ||||||
|  |                 mDate.set(Calendar.MONTH, month); | ||||||
|  |                 mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); | ||||||
|  |                 mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); | ||||||
|  |                 mDate.set(Calendar.MINUTE, minute); | ||||||
|  |                 updateTitle(mDate.getTimeInMillis()); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         mDate.setTimeInMillis(date); | ||||||
|  |         mDate.set(Calendar.SECOND, 0); | ||||||
|  |         mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); | ||||||
|  |         setButton(context.getString(R.string.datetime_dialog_ok), this); | ||||||
|  |         setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); | ||||||
|  |         set24HourView(DateFormat.is24HourFormat(this.getContext())); | ||||||
|  |         updateTitle(mDate.getTimeInMillis()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void set24HourView(boolean is24HourView) { | ||||||
|  |         mIs24HourView = is24HourView; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { | ||||||
|  |         mOnDateTimeSetListener = callBack; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void updateTitle(long date) { | ||||||
|  |         int flag = | ||||||
|  |             DateUtils.FORMAT_SHOW_YEAR | | ||||||
|  |             DateUtils.FORMAT_SHOW_DATE | | ||||||
|  |             DateUtils.FORMAT_SHOW_TIME; | ||||||
|  |         flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; | ||||||
|  |         setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void onClick(DialogInterface arg0, int arg1) { | ||||||
|  |         if (mOnDateTimeSetListener != null) { | ||||||
|  |             mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,61 @@ | |||||||
|  | /* | ||||||
|  |  * 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); | ||||||
|  |         mButton.setOnClickListener(new OnClickListener() { | ||||||
|  |             public void onClick(View v) { | ||||||
|  |                 mPopupMenu.show(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { | ||||||
|  |         if (mPopupMenu != null) { | ||||||
|  |             mPopupMenu.setOnMenuItemClickListener(listener); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public MenuItem findItem(int id) { | ||||||
|  |         return mMenu.findItem(id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setTitle(CharSequence title) { | ||||||
|  |         mButton.setText(title); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,80 @@ | |||||||
|  | /* | ||||||
|  |  * 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.view.View; | ||||||
|  | import android.view.ViewGroup; | ||||||
|  | import android.widget.CursorAdapter; | ||||||
|  | import android.widget.LinearLayout; | ||||||
|  | import android.widget.TextView; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.data.Notes.NoteColumns; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class FoldersListAdapter extends CursorAdapter { | ||||||
|  |     public static final String [] PROJECTION = { | ||||||
|  |         NoteColumns.ID, | ||||||
|  |         NoteColumns.SNIPPET | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     public static final int ID_COLUMN   = 0; | ||||||
|  |     public static final int NAME_COLUMN = 1; | ||||||
|  | 
 | ||||||
|  |     public FoldersListAdapter(Context context, Cursor c) { | ||||||
|  |         super(context, c); | ||||||
|  |         // TODO Auto-generated constructor stub
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public View newView(Context context, Cursor cursor, ViewGroup parent) { | ||||||
|  |         return new FolderListItem(context); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void bindView(View view, Context context, Cursor cursor) { | ||||||
|  |         if (view instanceof FolderListItem) { | ||||||
|  |             String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context | ||||||
|  |                     .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); | ||||||
|  |             ((FolderListItem) view).bind(folderName); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public String getFolderName(Context context, int position) { | ||||||
|  |         Cursor cursor = (Cursor) getItem(position); | ||||||
|  |         return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context | ||||||
|  |                 .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private class FolderListItem extends LinearLayout { | ||||||
|  |         private TextView mName; | ||||||
|  | 
 | ||||||
|  |         public FolderListItem(Context context) { | ||||||
|  |             super(context); | ||||||
|  |             inflate(context, R.layout.folder_list_item, this); | ||||||
|  |             mName = (TextView) findViewById(R.id.tv_folder_name); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         public void bind(String name) { | ||||||
|  |             mName.setText(name); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,873 @@ | |||||||
|  | /* | ||||||
|  |  * 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.AlarmManager; | ||||||
|  | import android.app.AlertDialog; | ||||||
|  | import android.app.PendingIntent; | ||||||
|  | import android.app.SearchManager; | ||||||
|  | import android.appwidget.AppWidgetManager; | ||||||
|  | import android.content.ContentUris; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.DialogInterface; | ||||||
|  | import android.content.Intent; | ||||||
|  | import android.content.SharedPreferences; | ||||||
|  | import android.graphics.Paint; | ||||||
|  | import android.os.Bundle; | ||||||
|  | import android.preference.PreferenceManager; | ||||||
|  | import android.text.Spannable; | ||||||
|  | import android.text.SpannableString; | ||||||
|  | import android.text.TextUtils; | ||||||
|  | import android.text.format.DateUtils; | ||||||
|  | import android.text.style.BackgroundColorSpan; | ||||||
|  | import android.util.Log; | ||||||
|  | import android.view.LayoutInflater; | ||||||
|  | import android.view.Menu; | ||||||
|  | import android.view.MenuItem; | ||||||
|  | import android.view.MotionEvent; | ||||||
|  | import android.view.View; | ||||||
|  | import android.view.View.OnClickListener; | ||||||
|  | import android.view.WindowManager; | ||||||
|  | import android.widget.CheckBox; | ||||||
|  | import android.widget.CompoundButton; | ||||||
|  | import android.widget.CompoundButton.OnCheckedChangeListener; | ||||||
|  | import android.widget.EditText; | ||||||
|  | import android.widget.ImageView; | ||||||
|  | import android.widget.LinearLayout; | ||||||
|  | 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.TextNote; | ||||||
|  | import net.micode.notes.model.WorkingNote; | ||||||
|  | import net.micode.notes.model.WorkingNote.NoteSettingChangedListener; | ||||||
|  | import net.micode.notes.tool.DataUtils; | ||||||
|  | import net.micode.notes.tool.ResourceParser; | ||||||
|  | import net.micode.notes.tool.ResourceParser.TextAppearanceResources; | ||||||
|  | import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; | ||||||
|  | import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; | ||||||
|  | import net.micode.notes.widget.NoteWidgetProvider_2x; | ||||||
|  | import net.micode.notes.widget.NoteWidgetProvider_4x; | ||||||
|  | 
 | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.regex.Matcher; | ||||||
|  | import java.util.regex.Pattern; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class NoteEditActivity extends Activity implements OnClickListener, | ||||||
|  |         NoteSettingChangedListener, OnTextViewChangeListener { | ||||||
|  |     private class HeadViewHolder { | ||||||
|  |         public TextView tvModified; | ||||||
|  | 
 | ||||||
|  |         public ImageView ivAlertIcon; | ||||||
|  | 
 | ||||||
|  |         public TextView tvAlertDate; | ||||||
|  | 
 | ||||||
|  |         public ImageView ibSetBgColor; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>(); | ||||||
|  |     static { | ||||||
|  |         sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); | ||||||
|  |         sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED); | ||||||
|  |         sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE); | ||||||
|  |         sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); | ||||||
|  |         sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>(); | ||||||
|  |     static { | ||||||
|  |         sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); | ||||||
|  |         sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select); | ||||||
|  |         sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); | ||||||
|  |         sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); | ||||||
|  |         sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>(); | ||||||
|  |     static { | ||||||
|  |         sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); | ||||||
|  |         sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL); | ||||||
|  |         sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); | ||||||
|  |         sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>(); | ||||||
|  |     static { | ||||||
|  |         sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); | ||||||
|  |         sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); | ||||||
|  |         sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select); | ||||||
|  |         sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static final String TAG = "NoteEditActivity"; | ||||||
|  | 
 | ||||||
|  |     private HeadViewHolder mNoteHeaderHolder; | ||||||
|  | 
 | ||||||
|  |     private View mHeadViewPanel; | ||||||
|  | 
 | ||||||
|  |     private View mNoteBgColorSelector; | ||||||
|  | 
 | ||||||
|  |     private View mFontSizeSelector; | ||||||
|  | 
 | ||||||
|  |     private EditText mNoteEditor; | ||||||
|  | 
 | ||||||
|  |     private View mNoteEditorPanel; | ||||||
|  | 
 | ||||||
|  |     private WorkingNote mWorkingNote; | ||||||
|  | 
 | ||||||
|  |     private SharedPreferences mSharedPrefs; | ||||||
|  |     private int mFontSizeId; | ||||||
|  | 
 | ||||||
|  |     private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; | ||||||
|  | 
 | ||||||
|  |     private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; | ||||||
|  | 
 | ||||||
|  |     public static final String TAG_CHECKED = String.valueOf('\u221A'); | ||||||
|  |     public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); | ||||||
|  | 
 | ||||||
|  |     private LinearLayout mEditTextList; | ||||||
|  | 
 | ||||||
|  |     private String mUserQuery; | ||||||
|  |     private Pattern mPattern; | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onCreate(Bundle savedInstanceState) { | ||||||
|  |         super.onCreate(savedInstanceState); | ||||||
|  |         this.setContentView(R.layout.note_edit); | ||||||
|  | 
 | ||||||
|  |         if (savedInstanceState == null && !initActivityState(getIntent())) { | ||||||
|  |             finish(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         initResources(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Current activity may be killed when the memory is low. Once it is killed, for another time | ||||||
|  |      * user load this activity, we should restore the former state | ||||||
|  |      */ | ||||||
|  |     @Override | ||||||
|  |     protected void onRestoreInstanceState(Bundle savedInstanceState) { | ||||||
|  |         super.onRestoreInstanceState(savedInstanceState); | ||||||
|  |         if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { | ||||||
|  |             Intent intent = new Intent(Intent.ACTION_VIEW); | ||||||
|  |             intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); | ||||||
|  |             if (!initActivityState(intent)) { | ||||||
|  |                 finish(); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             Log.d(TAG, "Restoring from killed activity"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private boolean initActivityState(Intent intent) { | ||||||
|  |         /** | ||||||
|  |          * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, | ||||||
|  |          * then jump to the NotesListActivity | ||||||
|  |          */ | ||||||
|  |         mWorkingNote = null; | ||||||
|  |         if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { | ||||||
|  |             long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); | ||||||
|  |             mUserQuery = ""; | ||||||
|  | 
 | ||||||
|  |             /** | ||||||
|  |              * Starting from the searched result | ||||||
|  |              */ | ||||||
|  |             if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { | ||||||
|  |                 noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); | ||||||
|  |                 mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { | ||||||
|  |                 Intent jump = new Intent(this, NotesListActivity.class); | ||||||
|  |                 startActivity(jump); | ||||||
|  |                 showToast(R.string.error_note_not_exist); | ||||||
|  |                 finish(); | ||||||
|  |                 return false; | ||||||
|  |             } else { | ||||||
|  |                 mWorkingNote = WorkingNote.load(this, noteId); | ||||||
|  |                 if (mWorkingNote == null) { | ||||||
|  |                     Log.e(TAG, "load note failed with note id" + noteId); | ||||||
|  |                     finish(); | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             getWindow().setSoftInputMode( | ||||||
|  |                     WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN | ||||||
|  |                             | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); | ||||||
|  |         } else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { | ||||||
|  |             // New note
 | ||||||
|  |             long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); | ||||||
|  |             int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, | ||||||
|  |                     AppWidgetManager.INVALID_APPWIDGET_ID); | ||||||
|  |             int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, | ||||||
|  |                     Notes.TYPE_WIDGET_INVALIDE); | ||||||
|  |             int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, | ||||||
|  |                     ResourceParser.getDefaultBgId(this)); | ||||||
|  | 
 | ||||||
|  |             // Parse call-record note
 | ||||||
|  |             String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); | ||||||
|  |             long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); | ||||||
|  |             if (callDate != 0 && phoneNumber != null) { | ||||||
|  |                 if (TextUtils.isEmpty(phoneNumber)) { | ||||||
|  |                     Log.w(TAG, "The call record number is null"); | ||||||
|  |                 } | ||||||
|  |                 long noteId = 0; | ||||||
|  |                 if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), | ||||||
|  |                         phoneNumber, callDate)) > 0) { | ||||||
|  |                     mWorkingNote = WorkingNote.load(this, noteId); | ||||||
|  |                     if (mWorkingNote == null) { | ||||||
|  |                         Log.e(TAG, "load call note failed with note id" + noteId); | ||||||
|  |                         finish(); | ||||||
|  |                         return false; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, | ||||||
|  |                             widgetType, bgResId); | ||||||
|  |                     mWorkingNote.convertToCallNote(phoneNumber, callDate); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, | ||||||
|  |                         bgResId); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             getWindow().setSoftInputMode( | ||||||
|  |                     WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | ||||||
|  |                             | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); | ||||||
|  |         } else { | ||||||
|  |             Log.e(TAG, "Intent not specified action, should not support"); | ||||||
|  |             finish(); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         mWorkingNote.setOnSettingStatusChangedListener(this); | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onResume() { | ||||||
|  |         super.onResume(); | ||||||
|  |         initNoteScreen(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void initNoteScreen() { | ||||||
|  |         mNoteEditor.setTextAppearance(this, TextAppearanceResources | ||||||
|  |                 .getTexAppearanceResource(mFontSizeId)); | ||||||
|  |         if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { | ||||||
|  |             switchToListMode(mWorkingNote.getContent()); | ||||||
|  |         } else { | ||||||
|  |             mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); | ||||||
|  |             mNoteEditor.setSelection(mNoteEditor.getText().length()); | ||||||
|  |         } | ||||||
|  |         for (Integer id : sBgSelectorSelectionMap.keySet()) { | ||||||
|  |             findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); | ||||||
|  |         } | ||||||
|  |         mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); | ||||||
|  |         mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); | ||||||
|  | 
 | ||||||
|  |         mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, | ||||||
|  |                 mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE | ||||||
|  |                         | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME | ||||||
|  |                         | DateUtils.FORMAT_SHOW_YEAR)); | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker | ||||||
|  |          * is not ready | ||||||
|  |          */ | ||||||
|  |         showAlertHeader(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void showAlertHeader() { | ||||||
|  |         if (mWorkingNote.hasClockAlert()) { | ||||||
|  |             long time = System.currentTimeMillis(); | ||||||
|  |             if (time > mWorkingNote.getAlertDate()) { | ||||||
|  |                 mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); | ||||||
|  |             } else { | ||||||
|  |                 mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( | ||||||
|  |                         mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); | ||||||
|  |             } | ||||||
|  |             mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); | ||||||
|  |             mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); | ||||||
|  |         } else { | ||||||
|  |             mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); | ||||||
|  |             mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onNewIntent(Intent intent) { | ||||||
|  |         super.onNewIntent(intent); | ||||||
|  |         initActivityState(intent); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onSaveInstanceState(Bundle outState) { | ||||||
|  |         super.onSaveInstanceState(outState); | ||||||
|  |         /** | ||||||
|  |          * For new note without note id, we should firstly save it to | ||||||
|  |          * generate a id. If the editing note is not worth saving, there | ||||||
|  |          * is no id which is equivalent to create new note | ||||||
|  |          */ | ||||||
|  |         if (!mWorkingNote.existInDatabase()) { | ||||||
|  |             saveNote(); | ||||||
|  |         } | ||||||
|  |         outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); | ||||||
|  |         Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean dispatchTouchEvent(MotionEvent ev) { | ||||||
|  |         if (mNoteBgColorSelector.getVisibility() == View.VISIBLE | ||||||
|  |                 && !inRangeOfView(mNoteBgColorSelector, ev)) { | ||||||
|  |             mNoteBgColorSelector.setVisibility(View.GONE); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (mFontSizeSelector.getVisibility() == View.VISIBLE | ||||||
|  |                 && !inRangeOfView(mFontSizeSelector, ev)) { | ||||||
|  |             mFontSizeSelector.setVisibility(View.GONE); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         return super.dispatchTouchEvent(ev); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private boolean inRangeOfView(View view, MotionEvent ev) { | ||||||
|  |         int []location = new int[2]; | ||||||
|  |         view.getLocationOnScreen(location); | ||||||
|  |         int x = location[0]; | ||||||
|  |         int y = location[1]; | ||||||
|  |         if (ev.getX() < x | ||||||
|  |                 || ev.getX() > (x + view.getWidth()) | ||||||
|  |                 || ev.getY() < y | ||||||
|  |                 || ev.getY() > (y + view.getHeight())) { | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void initResources() { | ||||||
|  |         mHeadViewPanel = findViewById(R.id.note_title); | ||||||
|  |         mNoteHeaderHolder = new HeadViewHolder(); | ||||||
|  |         mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); | ||||||
|  |         mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); | ||||||
|  |         mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); | ||||||
|  |         mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); | ||||||
|  |         mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); | ||||||
|  |         mNoteEditor = (EditText) findViewById(R.id.note_edit_view); | ||||||
|  |         mNoteEditorPanel = findViewById(R.id.sv_note_edit); | ||||||
|  |         mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); | ||||||
|  |         for (int id : sBgSelectorBtnsMap.keySet()) { | ||||||
|  |             ImageView iv = (ImageView) findViewById(id); | ||||||
|  |             iv.setOnClickListener(this); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         mFontSizeSelector = findViewById(R.id.font_size_selector); | ||||||
|  |         for (int id : sFontSizeBtnsMap.keySet()) { | ||||||
|  |             View view = findViewById(id); | ||||||
|  |             view.setOnClickListener(this); | ||||||
|  |         }; | ||||||
|  |         mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); | ||||||
|  |         mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); | ||||||
|  |         /** | ||||||
|  |          * 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(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { | ||||||
|  |             mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; | ||||||
|  |         } | ||||||
|  |         mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onPause() { | ||||||
|  |         super.onPause(); | ||||||
|  |         if(saveNote()) { | ||||||
|  |             Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); | ||||||
|  |         } | ||||||
|  |         clearSettingState(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void updateWidget() { | ||||||
|  |         Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); | ||||||
|  |         if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { | ||||||
|  |             intent.setClass(this, NoteWidgetProvider_2x.class); | ||||||
|  |         } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { | ||||||
|  |             intent.setClass(this, NoteWidgetProvider_4x.class); | ||||||
|  |         } else { | ||||||
|  |             Log.e(TAG, "Unspported widget type"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { | ||||||
|  |             mWorkingNote.getWidgetId() | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         sendBroadcast(intent); | ||||||
|  |         setResult(RESULT_OK, intent); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void onClick(View v) { | ||||||
|  |         int id = v.getId(); | ||||||
|  |         if (id == R.id.btn_set_bg_color) { | ||||||
|  |             mNoteBgColorSelector.setVisibility(View.VISIBLE); | ||||||
|  |             findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( | ||||||
|  |                     -                    View.VISIBLE); | ||||||
|  |         } else if (sBgSelectorBtnsMap.containsKey(id)) { | ||||||
|  |             findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( | ||||||
|  |                     View.GONE); | ||||||
|  |             mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); | ||||||
|  |             mNoteBgColorSelector.setVisibility(View.GONE); | ||||||
|  |         } else if (sFontSizeBtnsMap.containsKey(id)) { | ||||||
|  |             findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); | ||||||
|  |             mFontSizeId = sFontSizeBtnsMap.get(id); | ||||||
|  |             mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); | ||||||
|  |             findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); | ||||||
|  |             if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { | ||||||
|  |                 getWorkingText(); | ||||||
|  |                 switchToListMode(mWorkingNote.getContent()); | ||||||
|  |             } else { | ||||||
|  |                 mNoteEditor.setTextAppearance(this, | ||||||
|  |                         TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); | ||||||
|  |             } | ||||||
|  |             mFontSizeSelector.setVisibility(View.GONE); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void onBackPressed() { | ||||||
|  |         if(clearSettingState()) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         saveNote(); | ||||||
|  |         super.onBackPressed(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private boolean clearSettingState() { | ||||||
|  |         if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { | ||||||
|  |             mNoteBgColorSelector.setVisibility(View.GONE); | ||||||
|  |             return true; | ||||||
|  |         } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { | ||||||
|  |             mFontSizeSelector.setVisibility(View.GONE); | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void onBackgroundColorChanged() { | ||||||
|  |         findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( | ||||||
|  |                 View.VISIBLE); | ||||||
|  |         mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); | ||||||
|  |         mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean onPrepareOptionsMenu(Menu menu) { | ||||||
|  |         if (isFinishing()) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |         clearSettingState(); | ||||||
|  |         menu.clear(); | ||||||
|  |         if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { | ||||||
|  |             getMenuInflater().inflate(R.menu.call_note_edit, menu); | ||||||
|  |         } else { | ||||||
|  |             getMenuInflater().inflate(R.menu.note_edit, menu); | ||||||
|  |         } | ||||||
|  |         if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { | ||||||
|  |             menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); | ||||||
|  |         } else { | ||||||
|  |             menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); | ||||||
|  |         } | ||||||
|  |         if (mWorkingNote.hasClockAlert()) { | ||||||
|  |             menu.findItem(R.id.menu_alert).setVisible(false); | ||||||
|  |         } else { | ||||||
|  |             menu.findItem(R.id.menu_delete_remind).setVisible(false); | ||||||
|  |         } | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean onOptionsItemSelected(MenuItem item) { | ||||||
|  |         switch (item.getItemId()) { | ||||||
|  |             case R.id.menu_new_note: | ||||||
|  |                 createNewNote(); | ||||||
|  |                 break; | ||||||
|  |             case R.id.menu_delete: | ||||||
|  |                 AlertDialog.Builder builder = new AlertDialog.Builder(this); | ||||||
|  |                 builder.setTitle(getString(R.string.alert_title_delete)); | ||||||
|  |                 builder.setIcon(android.R.drawable.ic_dialog_alert); | ||||||
|  |                 builder.setMessage(getString(R.string.alert_message_delete_note)); | ||||||
|  |                 builder.setPositiveButton(android.R.string.ok, | ||||||
|  |                         new DialogInterface.OnClickListener() { | ||||||
|  |                             public void onClick(DialogInterface dialog, int which) { | ||||||
|  |                                 deleteCurrentNote(); | ||||||
|  |                                 finish(); | ||||||
|  |                             } | ||||||
|  |                         }); | ||||||
|  |                 builder.setNegativeButton(android.R.string.cancel, null); | ||||||
|  |                 builder.show(); | ||||||
|  |                 break; | ||||||
|  |             case R.id.menu_font_size: | ||||||
|  |                 mFontSizeSelector.setVisibility(View.VISIBLE); | ||||||
|  |                 findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); | ||||||
|  |                 break; | ||||||
|  |             case R.id.menu_list_mode: | ||||||
|  |                 mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? | ||||||
|  |                         TextNote.MODE_CHECK_LIST : 0); | ||||||
|  |                 break; | ||||||
|  |             case R.id.menu_share: | ||||||
|  |                 getWorkingText(); | ||||||
|  |                 sendTo(this, mWorkingNote.getContent()); | ||||||
|  |                 break; | ||||||
|  |             case R.id.menu_send_to_desktop: | ||||||
|  |                 sendToDesktop(); | ||||||
|  |                 break; | ||||||
|  |             case R.id.menu_alert: | ||||||
|  |                 setReminder(); | ||||||
|  |                 break; | ||||||
|  |             case R.id.menu_delete_remind: | ||||||
|  |                 mWorkingNote.setAlertDate(0, false); | ||||||
|  |                 break; | ||||||
|  |             default: | ||||||
|  |                 break; | ||||||
|  |         } | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void setReminder() { | ||||||
|  |         DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); | ||||||
|  |         d.setOnDateTimeSetListener(new OnDateTimeSetListener() { | ||||||
|  |             public void OnDateTimeSet(AlertDialog dialog, long date) { | ||||||
|  |                 mWorkingNote.setAlertDate(date	, true); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         d.show(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Share note to apps that support {@link Intent#ACTION_SEND} action | ||||||
|  |      * and {@text/plain} type | ||||||
|  |      */ | ||||||
|  |     private void sendTo(Context context, String info) { | ||||||
|  |         Intent intent = new Intent(Intent.ACTION_SEND); | ||||||
|  |         intent.putExtra(Intent.EXTRA_TEXT, info); | ||||||
|  |         intent.setType("text/plain"); | ||||||
|  |         context.startActivity(intent); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void createNewNote() { | ||||||
|  |         // Firstly, save current editing notes
 | ||||||
|  |         saveNote(); | ||||||
|  | 
 | ||||||
|  |         // For safety, start a new NoteEditActivity
 | ||||||
|  |         finish(); | ||||||
|  |         Intent intent = new Intent(this, NoteEditActivity.class); | ||||||
|  |         intent.setAction(Intent.ACTION_INSERT_OR_EDIT); | ||||||
|  |         intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); | ||||||
|  |         startActivity(intent); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void deleteCurrentNote() { | ||||||
|  |         if (mWorkingNote.existInDatabase()) { | ||||||
|  |             HashSet<Long> ids = new HashSet<Long>(); | ||||||
|  |             long id = mWorkingNote.getNoteId(); | ||||||
|  |             if (id != Notes.ID_ROOT_FOLDER) { | ||||||
|  |                 ids.add(id); | ||||||
|  |             } else { | ||||||
|  |                 Log.d(TAG, "Wrong note id, should not happen"); | ||||||
|  |             } | ||||||
|  |             if (!isSyncMode()) { | ||||||
|  |                 if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { | ||||||
|  |                     Log.e(TAG, "Delete Note error"); | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { | ||||||
|  |                     Log.e(TAG, "Move notes to trash folder error, should not happens"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         mWorkingNote.markDeleted(true); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private boolean isSyncMode() { | ||||||
|  |         return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void onClockAlertChanged(long date, boolean set) { | ||||||
|  |         /** | ||||||
|  |          * User could set clock to an unsaved note, so before setting the | ||||||
|  |          * alert clock, we should save the note first | ||||||
|  |          */ | ||||||
|  |         if (!mWorkingNote.existInDatabase()) { | ||||||
|  |             saveNote(); | ||||||
|  |         } | ||||||
|  |         if (mWorkingNote.getNoteId() > 0) { | ||||||
|  |             Intent intent = new Intent(this, AlarmReceiver.class); | ||||||
|  |             intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); | ||||||
|  |             PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); | ||||||
|  |             AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); | ||||||
|  |             showAlertHeader(); | ||||||
|  |             if(!set) { | ||||||
|  |                 alarmManager.cancel(pendingIntent); | ||||||
|  |             } else { | ||||||
|  |                 alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             /** | ||||||
|  |              * There is the condition that user has input nothing (the note is | ||||||
|  |              * not worthy saving), we have no note id, remind the user that he | ||||||
|  |              * should input something | ||||||
|  |              */ | ||||||
|  |             Log.e(TAG, "Clock alert setting error"); | ||||||
|  |             showToast(R.string.error_note_empty_for_clock); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void onWidgetChanged() { | ||||||
|  |         updateWidget(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void onEditTextDelete(int index, String text) { | ||||||
|  |         int childCount = mEditTextList.getChildCount(); | ||||||
|  |         if (childCount == 1) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         for (int i = index + 1; i < childCount; i++) { | ||||||
|  |             ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) | ||||||
|  |                     .setIndex(i - 1); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         mEditTextList.removeViewAt(index); | ||||||
|  |         NoteEditText edit = null; | ||||||
|  |         if(index == 0) { | ||||||
|  |             edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( | ||||||
|  |                     R.id.et_edit_text); | ||||||
|  |         } else { | ||||||
|  |             edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById( | ||||||
|  |                     R.id.et_edit_text); | ||||||
|  |         } | ||||||
|  |         int length = edit.length(); | ||||||
|  |         edit.append(text); | ||||||
|  |         edit.requestFocus(); | ||||||
|  |         edit.setSelection(length); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void onEditTextEnter(int index, String text) { | ||||||
|  |         /** | ||||||
|  |          * Should not happen, check for debug | ||||||
|  |          */ | ||||||
|  |         if(index > mEditTextList.getChildCount()) { | ||||||
|  |             Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         View view = getListItem(text, index); | ||||||
|  |         mEditTextList.addView(view, index); | ||||||
|  |         NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); | ||||||
|  |         edit.requestFocus(); | ||||||
|  |         edit.setSelection(0); | ||||||
|  |         for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { | ||||||
|  |             ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) | ||||||
|  |                     .setIndex(i); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void switchToListMode(String text) { | ||||||
|  |         mEditTextList.removeAllViews(); | ||||||
|  |         String[] items = text.split("\n"); | ||||||
|  |         int index = 0; | ||||||
|  |         for (String item : items) { | ||||||
|  |             if(!TextUtils.isEmpty(item)) { | ||||||
|  |                 mEditTextList.addView(getListItem(item, index)); | ||||||
|  |                 index++; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         mEditTextList.addView(getListItem("", index)); | ||||||
|  |         mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); | ||||||
|  | 
 | ||||||
|  |         mNoteEditor.setVisibility(View.GONE); | ||||||
|  |         mEditTextList.setVisibility(View.VISIBLE); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Spannable getHighlightQueryResult(String fullText, String userQuery) { | ||||||
|  |         SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); | ||||||
|  |         if (!TextUtils.isEmpty(userQuery)) { | ||||||
|  |             mPattern = Pattern.compile(userQuery); | ||||||
|  |             Matcher m = mPattern.matcher(fullText); | ||||||
|  |             int start = 0; | ||||||
|  |             while (m.find(start)) { | ||||||
|  |                 spannable.setSpan( | ||||||
|  |                         new BackgroundColorSpan(this.getResources().getColor( | ||||||
|  |                                 R.color.user_query_highlight)), m.start(), m.end(), | ||||||
|  |                         Spannable.SPAN_INCLUSIVE_EXCLUSIVE); | ||||||
|  |                 start = m.end(); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         return spannable; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private View getListItem(String item, int index) { | ||||||
|  |         View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); | ||||||
|  |         final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); | ||||||
|  |         edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); | ||||||
|  |         CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); | ||||||
|  |         cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { | ||||||
|  |             public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { | ||||||
|  |                 if (isChecked) { | ||||||
|  |                     edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); | ||||||
|  |                 } else { | ||||||
|  |                     edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         if (item.startsWith(TAG_CHECKED)) { | ||||||
|  |             cb.setChecked(true); | ||||||
|  |             edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); | ||||||
|  |             item = item.substring(TAG_CHECKED.length(), item.length()).trim(); | ||||||
|  |         } else if (item.startsWith(TAG_UNCHECKED)) { | ||||||
|  |             cb.setChecked(false); | ||||||
|  |             edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); | ||||||
|  |             item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         edit.setOnTextViewChangeListener(this); | ||||||
|  |         edit.setIndex(index); | ||||||
|  |         edit.setText(getHighlightQueryResult(item, mUserQuery)); | ||||||
|  |         return view; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void onTextChange(int index, boolean hasText) { | ||||||
|  |         if (index >= mEditTextList.getChildCount()) { | ||||||
|  |             Log.e(TAG, "Wrong index, should not happen"); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         if(hasText) { | ||||||
|  |             mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); | ||||||
|  |         } else { | ||||||
|  |             mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void onCheckListModeChanged(int oldMode, int newMode) { | ||||||
|  |         if (newMode == TextNote.MODE_CHECK_LIST) { | ||||||
|  |             switchToListMode(mNoteEditor.getText().toString()); | ||||||
|  |         } else { | ||||||
|  |             if (!getWorkingText()) { | ||||||
|  |                 mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", | ||||||
|  |                         "")); | ||||||
|  |             } | ||||||
|  |             mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); | ||||||
|  |             mEditTextList.setVisibility(View.GONE); | ||||||
|  |             mNoteEditor.setVisibility(View.VISIBLE); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private boolean getWorkingText() { | ||||||
|  |         boolean hasChecked = false; | ||||||
|  |         if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { | ||||||
|  |             StringBuilder sb = new StringBuilder(); | ||||||
|  |             for (int i = 0; i < mEditTextList.getChildCount(); i++) { | ||||||
|  |                 View view = mEditTextList.getChildAt(i); | ||||||
|  |                 NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); | ||||||
|  |                 if (!TextUtils.isEmpty(edit.getText())) { | ||||||
|  |                     if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { | ||||||
|  |                         sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); | ||||||
|  |                         hasChecked = true; | ||||||
|  |                     } else { | ||||||
|  |                         sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             mWorkingNote.setWorkingText(sb.toString()); | ||||||
|  |         } else { | ||||||
|  |             mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); | ||||||
|  |         } | ||||||
|  |         return hasChecked; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private boolean saveNote() { | ||||||
|  |         getWorkingText(); | ||||||
|  |         boolean saved = mWorkingNote.saveNote(); | ||||||
|  |         if (saved) { | ||||||
|  |             /** | ||||||
|  |              * There are two modes from List view to edit view, open one note, | ||||||
|  |              * create/edit a node. Opening node requires to the original | ||||||
|  |              * position in the list when back from edit view, while creating a | ||||||
|  |              * new node requires to the top of the list. This code | ||||||
|  |              * {@link #RESULT_OK} is used to identify the create/edit state | ||||||
|  |              */ | ||||||
|  |             setResult(RESULT_OK); | ||||||
|  |         } | ||||||
|  |         return saved; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void sendToDesktop() { | ||||||
|  |         /** | ||||||
|  |          * Before send message to home, we should make sure that current | ||||||
|  |          * editing note is exists in databases. So, for new note, firstly | ||||||
|  |          * save it | ||||||
|  |          */ | ||||||
|  |         if (!mWorkingNote.existInDatabase()) { | ||||||
|  |             saveNote(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (mWorkingNote.getNoteId() > 0) { | ||||||
|  |             Intent sender = new Intent(); | ||||||
|  |             Intent shortcutIntent = new Intent(this, NoteEditActivity.class); | ||||||
|  |             shortcutIntent.setAction(Intent.ACTION_VIEW); | ||||||
|  |             shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); | ||||||
|  |             sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); | ||||||
|  |             sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, | ||||||
|  |                     makeShortcutIconTitle(mWorkingNote.getContent())); | ||||||
|  |             sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, | ||||||
|  |                     Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); | ||||||
|  |             sender.putExtra("duplicate", true); | ||||||
|  |             sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); | ||||||
|  |             showToast(R.string.info_note_enter_desktop); | ||||||
|  |             sendBroadcast(sender); | ||||||
|  |         } else { | ||||||
|  |             /** | ||||||
|  |              * There is the condition that user has input nothing (the note is | ||||||
|  |              * not worthy saving), we have no note id, remind the user that he | ||||||
|  |              * should input something | ||||||
|  |              */ | ||||||
|  |             Log.e(TAG, "Send to desktop error"); | ||||||
|  |             showToast(R.string.error_note_empty_for_send_to_desktop); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private String makeShortcutIconTitle(String content) { | ||||||
|  |         content = content.replace(TAG_CHECKED, ""); | ||||||
|  |         content = content.replace(TAG_UNCHECKED, ""); | ||||||
|  |         return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, | ||||||
|  |                 SHORTCUT_ICON_TITLE_MAX_LEN) : content; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void showToast(int resId) { | ||||||
|  |         showToast(resId, Toast.LENGTH_SHORT); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void showToast(int resId, int duration) { | ||||||
|  |         Toast.makeText(this, resId, duration).show(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,217 @@ | |||||||
|  | /* | ||||||
|  |  * 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.graphics.Rect; | ||||||
|  | import android.text.Layout; | ||||||
|  | import android.text.Selection; | ||||||
|  | import android.text.Spanned; | ||||||
|  | import android.text.TextUtils; | ||||||
|  | import android.text.style.URLSpan; | ||||||
|  | import android.util.AttributeSet; | ||||||
|  | import android.util.Log; | ||||||
|  | import android.view.ContextMenu; | ||||||
|  | import android.view.KeyEvent; | ||||||
|  | import android.view.MenuItem; | ||||||
|  | import android.view.MenuItem.OnMenuItemClickListener; | ||||||
|  | import android.view.MotionEvent; | ||||||
|  | import android.widget.EditText; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | 
 | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.Map; | ||||||
|  | 
 | ||||||
|  | public class NoteEditText extends EditText { | ||||||
|  |     private static final String TAG = "NoteEditText"; | ||||||
|  |     private int mIndex; | ||||||
|  |     private int mSelectionStartBeforeDelete; | ||||||
|  | 
 | ||||||
|  |     private static final String SCHEME_TEL = "tel:" ; | ||||||
|  |     private static final String SCHEME_HTTP = "http:" ; | ||||||
|  |     private static final String SCHEME_EMAIL = "mailto:" ; | ||||||
|  | 
 | ||||||
|  |     private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>(); | ||||||
|  |     static { | ||||||
|  |         sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); | ||||||
|  |         sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); | ||||||
|  |         sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Call by the {@link NoteEditActivity} to delete or add edit text | ||||||
|  |      */ | ||||||
|  |     public interface OnTextViewChangeListener { | ||||||
|  |         /** | ||||||
|  |          * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens | ||||||
|  |          * and the text is null | ||||||
|  |          */ | ||||||
|  |         void onEditTextDelete(int index, String text); | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} | ||||||
|  |          * happen | ||||||
|  |          */ | ||||||
|  |         void onEditTextEnter(int index, String text); | ||||||
|  | 
 | ||||||
|  |         /** | ||||||
|  |          * Hide or show item option when text change | ||||||
|  |          */ | ||||||
|  |         void onTextChange(int index, boolean hasText); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private OnTextViewChangeListener mOnTextViewChangeListener; | ||||||
|  | 
 | ||||||
|  |     public NoteEditText(Context context) { | ||||||
|  |         super(context, null); | ||||||
|  |         mIndex = 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setIndex(int index) { | ||||||
|  |         mIndex = index; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { | ||||||
|  |         mOnTextViewChangeListener = listener; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public NoteEditText(Context context, AttributeSet attrs) { | ||||||
|  |         super(context, attrs, android.R.attr.editTextStyle); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public NoteEditText(Context context, AttributeSet attrs, int defStyle) { | ||||||
|  |         super(context, attrs, defStyle); | ||||||
|  |         // TODO Auto-generated constructor stub
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean onTouchEvent(MotionEvent event) { | ||||||
|  |         switch (event.getAction()) { | ||||||
|  |             case MotionEvent.ACTION_DOWN: | ||||||
|  | 
 | ||||||
|  |                 int x = (int) event.getX(); | ||||||
|  |                 int y = (int) event.getY(); | ||||||
|  |                 x -= getTotalPaddingLeft(); | ||||||
|  |                 y -= getTotalPaddingTop(); | ||||||
|  |                 x += getScrollX(); | ||||||
|  |                 y += getScrollY(); | ||||||
|  | 
 | ||||||
|  |                 Layout layout = getLayout(); | ||||||
|  |                 int line = layout.getLineForVertical(y); | ||||||
|  |                 int off = layout.getOffsetForHorizontal(line, x); | ||||||
|  |                 Selection.setSelection(getText(), off); | ||||||
|  |                 break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return super.onTouchEvent(event); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean onKeyDown(int keyCode, KeyEvent event) { | ||||||
|  |         switch (keyCode) { | ||||||
|  |             case KeyEvent.KEYCODE_ENTER: | ||||||
|  |                 if (mOnTextViewChangeListener != null) { | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|  |             case KeyEvent.KEYCODE_DEL: | ||||||
|  |                 mSelectionStartBeforeDelete = getSelectionStart(); | ||||||
|  |                 break; | ||||||
|  |             default: | ||||||
|  |                 break; | ||||||
|  |         } | ||||||
|  |         return super.onKeyDown(keyCode, event); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean onKeyUp(int keyCode, KeyEvent event) { | ||||||
|  |         switch(keyCode) { | ||||||
|  |             case KeyEvent.KEYCODE_DEL: | ||||||
|  |                 if (mOnTextViewChangeListener != null) { | ||||||
|  |                     if (0 == mSelectionStartBeforeDelete && mIndex != 0) { | ||||||
|  |                         mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); | ||||||
|  |                         return true; | ||||||
|  |                     } | ||||||
|  |                 } else { | ||||||
|  |                     Log.d(TAG, "OnTextViewChangeListener was not seted"); | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|  |             case KeyEvent.KEYCODE_ENTER: | ||||||
|  |                 if (mOnTextViewChangeListener != null) { | ||||||
|  |                     int selectionStart = getSelectionStart(); | ||||||
|  |                     String text = getText().subSequence(selectionStart, length()).toString(); | ||||||
|  |                     setText(getText().subSequence(0, selectionStart)); | ||||||
|  |                     mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); | ||||||
|  |                 } else { | ||||||
|  |                     Log.d(TAG, "OnTextViewChangeListener was not seted"); | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|  |             default: | ||||||
|  |                 break; | ||||||
|  |         } | ||||||
|  |         return super.onKeyUp(keyCode, event); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { | ||||||
|  |         if (mOnTextViewChangeListener != null) { | ||||||
|  |             if (!focused && TextUtils.isEmpty(getText())) { | ||||||
|  |                 mOnTextViewChangeListener.onTextChange(mIndex, false); | ||||||
|  |             } else { | ||||||
|  |                 mOnTextViewChangeListener.onTextChange(mIndex, true); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         super.onFocusChanged(focused, direction, previouslyFocusedRect); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected void onCreateContextMenu(ContextMenu menu) { | ||||||
|  |         if (getText() instanceof Spanned) { | ||||||
|  |             int selStart = getSelectionStart(); | ||||||
|  |             int selEnd = getSelectionEnd(); | ||||||
|  | 
 | ||||||
|  |             int min = Math.min(selStart, selEnd); | ||||||
|  |             int max = Math.max(selStart, selEnd); | ||||||
|  | 
 | ||||||
|  |             final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); | ||||||
|  |             if (urls.length == 1) { | ||||||
|  |                 int defaultResId = 0; | ||||||
|  |                 for(String schema: sSchemaActionResMap.keySet()) { | ||||||
|  |                     if(urls[0].getURL().indexOf(schema) >= 0) { | ||||||
|  |                         defaultResId = sSchemaActionResMap.get(schema); | ||||||
|  |                         break; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if (defaultResId == 0) { | ||||||
|  |                     defaultResId = R.string.note_link_other; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( | ||||||
|  |                         new OnMenuItemClickListener() { | ||||||
|  |                             public boolean onMenuItemClick(MenuItem item) { | ||||||
|  |                                 // goto a new intent
 | ||||||
|  |                                 urls[0].onClick(NoteEditText.this); | ||||||
|  |                                 return true; | ||||||
|  |                             } | ||||||
|  |                         }); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         super.onCreateContextMenu(menu); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,224 @@ | |||||||
|  | /* | ||||||
|  |  * 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.text.TextUtils; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.data.Contact; | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.data.Notes.NoteColumns; | ||||||
|  | import net.micode.notes.tool.DataUtils; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class NoteItemData { | ||||||
|  |     static final String [] PROJECTION = 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, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     private static final int ID_COLUMN                    = 0; | ||||||
|  |     private static final int ALERTED_DATE_COLUMN          = 1; | ||||||
|  |     private static final int BG_COLOR_ID_COLUMN           = 2; | ||||||
|  |     private static final int CREATED_DATE_COLUMN          = 3; | ||||||
|  |     private static final int HAS_ATTACHMENT_COLUMN        = 4; | ||||||
|  |     private static final int MODIFIED_DATE_COLUMN         = 5; | ||||||
|  |     private static final int NOTES_COUNT_COLUMN           = 6; | ||||||
|  |     private static final int PARENT_ID_COLUMN             = 7; | ||||||
|  |     private static final int SNIPPET_COLUMN               = 8; | ||||||
|  |     private static final int TYPE_COLUMN                  = 9; | ||||||
|  |     private static final int WIDGET_ID_COLUMN             = 10; | ||||||
|  |     private static final int WIDGET_TYPE_COLUMN           = 11; | ||||||
|  | 
 | ||||||
|  |     private long mId; | ||||||
|  |     private long mAlertDate; | ||||||
|  |     private int mBgColorId; | ||||||
|  |     private long mCreatedDate; | ||||||
|  |     private boolean mHasAttachment; | ||||||
|  |     private long mModifiedDate; | ||||||
|  |     private int mNotesCount; | ||||||
|  |     private long mParentId; | ||||||
|  |     private String mSnippet; | ||||||
|  |     private int mType; | ||||||
|  |     private int mWidgetId; | ||||||
|  |     private int mWidgetType; | ||||||
|  |     private String mName; | ||||||
|  |     private String mPhoneNumber; | ||||||
|  | 
 | ||||||
|  |     private boolean mIsLastItem; | ||||||
|  |     private boolean mIsFirstItem; | ||||||
|  |     private boolean mIsOnlyOneItem; | ||||||
|  |     private boolean mIsOneNoteFollowingFolder; | ||||||
|  |     private boolean mIsMultiNotesFollowingFolder; | ||||||
|  | 
 | ||||||
|  |     public NoteItemData(Context context, Cursor cursor) { | ||||||
|  |         mId = cursor.getLong(ID_COLUMN); | ||||||
|  |         mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); | ||||||
|  |         mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); | ||||||
|  |         mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); | ||||||
|  |         mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false; | ||||||
|  |         mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); | ||||||
|  |         mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); | ||||||
|  |         mParentId = cursor.getLong(PARENT_ID_COLUMN); | ||||||
|  |         mSnippet = cursor.getString(SNIPPET_COLUMN); | ||||||
|  |         mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( | ||||||
|  |                 NoteEditActivity.TAG_UNCHECKED, ""); | ||||||
|  |         mType = cursor.getInt(TYPE_COLUMN); | ||||||
|  |         mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); | ||||||
|  |         mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); | ||||||
|  | 
 | ||||||
|  |         mPhoneNumber = ""; | ||||||
|  |         if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { | ||||||
|  |             mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); | ||||||
|  |             if (!TextUtils.isEmpty(mPhoneNumber)) { | ||||||
|  |                 mName = Contact.getContact(context, mPhoneNumber); | ||||||
|  |                 if (mName == null) { | ||||||
|  |                     mName = mPhoneNumber; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (mName == null) { | ||||||
|  |             mName = ""; | ||||||
|  |         } | ||||||
|  |         checkPostion(cursor); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void checkPostion(Cursor cursor) { | ||||||
|  |         mIsLastItem = cursor.isLast() ? true : false; | ||||||
|  |         mIsFirstItem = cursor.isFirst() ? true : false; | ||||||
|  |         mIsOnlyOneItem = (cursor.getCount() == 1); | ||||||
|  |         mIsMultiNotesFollowingFolder = false; | ||||||
|  |         mIsOneNoteFollowingFolder = false; | ||||||
|  | 
 | ||||||
|  |         if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { | ||||||
|  |             int position = cursor.getPosition(); | ||||||
|  |             if (cursor.moveToPrevious()) { | ||||||
|  |                 if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER | ||||||
|  |                         || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { | ||||||
|  |                     if (cursor.getCount() > (position + 1)) { | ||||||
|  |                         mIsMultiNotesFollowingFolder = true; | ||||||
|  |                     } else { | ||||||
|  |                         mIsOneNoteFollowingFolder = true; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |                 if (!cursor.moveToNext()) { | ||||||
|  |                     throw new IllegalStateException("cursor move to previous but can't move back"); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isOneFollowingFolder() { | ||||||
|  |         return mIsOneNoteFollowingFolder; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isMultiFollowingFolder() { | ||||||
|  |         return mIsMultiNotesFollowingFolder; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isLast() { | ||||||
|  |         return mIsLastItem; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public String getCallName() { | ||||||
|  |         return mName; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isFirst() { | ||||||
|  |         return mIsFirstItem; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isSingle() { | ||||||
|  |         return mIsOnlyOneItem; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getId() { | ||||||
|  |         return mId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getAlertDate() { | ||||||
|  |         return mAlertDate; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getCreatedDate() { | ||||||
|  |         return mCreatedDate; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean hasAttachment() { | ||||||
|  |         return mHasAttachment; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getModifiedDate() { | ||||||
|  |         return mModifiedDate; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getBgColorId() { | ||||||
|  |         return mBgColorId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getParentId() { | ||||||
|  |         return mParentId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getNotesCount() { | ||||||
|  |         return mNotesCount; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public long getFolderId () { | ||||||
|  |         return mParentId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getType() { | ||||||
|  |         return mType; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getWidgetType() { | ||||||
|  |         return mWidgetType; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public int getWidgetId() { | ||||||
|  |         return mWidgetId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public String getSnippet() { | ||||||
|  |         return mSnippet; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean hasAlert() { | ||||||
|  |         return (mAlertDate > 0); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public boolean isCallRecord() { | ||||||
|  |         return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static int getNoteType(Cursor cursor) { | ||||||
|  |         return cursor.getInt(TYPE_COLUMN); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -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,132 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *        http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package net.micode.notes.widget; | ||||||
|  | import android.app.PendingIntent; | ||||||
|  | import android.appwidget.AppWidgetManager; | ||||||
|  | import android.appwidget.AppWidgetProvider; | ||||||
|  | import android.content.ContentValues; | ||||||
|  | import android.content.Context; | ||||||
|  | import android.content.Intent; | ||||||
|  | import android.database.Cursor; | ||||||
|  | import android.util.Log; | ||||||
|  | import android.widget.RemoteViews; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.data.Notes.NoteColumns; | ||||||
|  | import net.micode.notes.tool.ResourceParser; | ||||||
|  | import net.micode.notes.ui.NoteEditActivity; | ||||||
|  | import net.micode.notes.ui.NotesListActivity; | ||||||
|  | 
 | ||||||
|  | public abstract class NoteWidgetProvider extends AppWidgetProvider { | ||||||
|  |     public static final String [] PROJECTION = new String [] { | ||||||
|  |         NoteColumns.ID, | ||||||
|  |         NoteColumns.BG_COLOR_ID, | ||||||
|  |         NoteColumns.SNIPPET | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     public static final int COLUMN_ID           = 0; | ||||||
|  |     public static final int COLUMN_BG_COLOR_ID  = 1; | ||||||
|  |     public static final int COLUMN_SNIPPET      = 2; | ||||||
|  | 
 | ||||||
|  |     private static final String TAG = "NoteWidgetProvider"; | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void onDeleted(Context context, int[] appWidgetIds) { | ||||||
|  |         ContentValues values = new ContentValues(); | ||||||
|  |         values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); | ||||||
|  |         for (int i = 0; i < appWidgetIds.length; i++) { | ||||||
|  |             context.getContentResolver().update(Notes.CONTENT_NOTE_URI, | ||||||
|  |                     values, | ||||||
|  |                     NoteColumns.WIDGET_ID + "=?", | ||||||
|  |                     new String[] { String.valueOf(appWidgetIds[i])}); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private Cursor getNoteWidgetInfo(Context context, int widgetId) { | ||||||
|  |         return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, | ||||||
|  |                 PROJECTION, | ||||||
|  |                 NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", | ||||||
|  |                 new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, | ||||||
|  |                 null); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { | ||||||
|  |         update(context, appWidgetManager, appWidgetIds, false); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, | ||||||
|  |             boolean privacyMode) { | ||||||
|  |         for (int i = 0; i < appWidgetIds.length; i++) { | ||||||
|  |             if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { | ||||||
|  |                 int bgId = ResourceParser.getDefaultBgId(context); | ||||||
|  |                 String snippet = ""; | ||||||
|  |                 Intent intent = new Intent(context, NoteEditActivity.class); | ||||||
|  |                 intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); | ||||||
|  |                 intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); | ||||||
|  |                 intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); | ||||||
|  | 
 | ||||||
|  |                 Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); | ||||||
|  |                 if (c != null && c.moveToFirst()) { | ||||||
|  |                     if (c.getCount() > 1) { | ||||||
|  |                         Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); | ||||||
|  |                         c.close(); | ||||||
|  |                         return; | ||||||
|  |                     } | ||||||
|  |                     snippet = c.getString(COLUMN_SNIPPET); | ||||||
|  |                     bgId = c.getInt(COLUMN_BG_COLOR_ID); | ||||||
|  |                     intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); | ||||||
|  |                     intent.setAction(Intent.ACTION_VIEW); | ||||||
|  |                 } else { | ||||||
|  |                     snippet = context.getResources().getString(R.string.widget_havenot_content); | ||||||
|  |                     intent.setAction(Intent.ACTION_INSERT_OR_EDIT); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if (c != null) { | ||||||
|  |                     c.close(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); | ||||||
|  |                 rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); | ||||||
|  |                 intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); | ||||||
|  |                 /** | ||||||
|  |                  * Generate the pending intent to start host for the widget | ||||||
|  |                  */ | ||||||
|  |                 PendingIntent pendingIntent = null; | ||||||
|  |                 if (privacyMode) { | ||||||
|  |                     rv.setTextViewText(R.id.widget_text, | ||||||
|  |                             context.getString(R.string.widget_under_visit_mode)); | ||||||
|  |                     pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( | ||||||
|  |                             context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); | ||||||
|  |                 } else { | ||||||
|  |                     rv.setTextViewText(R.id.widget_text, snippet); | ||||||
|  |                     pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, | ||||||
|  |                             PendingIntent.FLAG_UPDATE_CURRENT); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); | ||||||
|  |                 appWidgetManager.updateAppWidget(appWidgetIds[i], rv); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected abstract int getBgResourceId(int bgId); | ||||||
|  | 
 | ||||||
|  |     protected abstract int getLayoutId(); | ||||||
|  | 
 | ||||||
|  |     protected abstract int getWidgetType(); | ||||||
|  | } | ||||||
| @ -0,0 +1,47 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *        http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package net.micode.notes.widget; | ||||||
|  | 
 | ||||||
|  | import android.appwidget.AppWidgetManager; | ||||||
|  | import android.content.Context; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.tool.ResourceParser; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class NoteWidgetProvider_2x extends NoteWidgetProvider { | ||||||
|  |     @Override | ||||||
|  |     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { | ||||||
|  |         super.update(context, appWidgetManager, appWidgetIds); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected int getLayoutId() { | ||||||
|  |         return R.layout.widget_2x; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected int getBgResourceId(int bgId) { | ||||||
|  |         return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected int getWidgetType() { | ||||||
|  |         return Notes.TYPE_WIDGET_2X; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,46 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *        http://www.apache.org/licenses/LICENSE-2.0
 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | package net.micode.notes.widget; | ||||||
|  | 
 | ||||||
|  | import android.appwidget.AppWidgetManager; | ||||||
|  | import android.content.Context; | ||||||
|  | 
 | ||||||
|  | import net.micode.notes.R; | ||||||
|  | import net.micode.notes.data.Notes; | ||||||
|  | import net.micode.notes.tool.ResourceParser; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | public class NoteWidgetProvider_4x extends NoteWidgetProvider { | ||||||
|  |     @Override | ||||||
|  |     public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { | ||||||
|  |         super.update(context, appWidgetManager, appWidgetIds); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected int getLayoutId() { | ||||||
|  |         return R.layout.widget_4x; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected int getBgResourceId(int bgId) { | ||||||
|  |         return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected int getWidgetType() { | ||||||
|  |         return Notes.TYPE_WIDGET_4X; | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -0,0 +1,22 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | 
 | ||||||
|  | <!-- 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. | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | <selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |   <item android:state_pressed="true" android:color="#88555555" /> | ||||||
|  |   <item android:state_selected="true" android:color="#ff999999" /> | ||||||
|  |   <item android:color="#ff000000" /> | ||||||
|  | </selector> | ||||||
| @ -0,0 +1,20 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||||||
|  | 
 | ||||||
|  | <!-- 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. | ||||||
|  | --> | ||||||
|  | 
 | ||||||
|  | <selector xmlns:android="http://schemas.android.com/apk/res/android"> | ||||||
|  |   <item android:color="#50000000" /> | ||||||
|  | </selector> | ||||||
| After Width: | Height: | Size: 3.5 KiB | 
| After Width: | Height: | Size: 245 B | 
| After Width: | Height: | Size: 3.5 KiB | 
| After Width: | Height: | Size: 3.9 KiB | 
| After Width: | Height: | Size: 3.4 KiB | 
| After Width: | Height: | Size: 443 B | 
| After Width: | Height: | Size: 3.4 KiB | 
| After Width: | Height: | Size: 3.5 KiB | 
| After Width: | Height: | Size: 3.4 KiB | 
| After Width: | Height: | Size: 5.0 KiB | 
| After Width: | Height: | Size: 5.5 KiB | 
| After Width: | Height: | Size: 4.9 KiB | 
| After Width: | Height: | Size: 3.8 KiB | 
| After Width: | Height: | Size: 5.9 KiB | 
| After Width: | Height: | Size: 3.4 KiB | 
| After Width: | Height: | Size: 3.5 KiB | 
| After Width: | Height: | Size: 3.1 KiB | 
| After Width: | Height: | Size: 3.0 KiB | 
| After Width: | Height: | Size: 4.0 KiB | 
| After Width: | Height: | Size: 3.0 KiB | 
| After Width: | Height: | Size: 3.1 KiB | 
| After Width: | Height: | Size: 6.7 KiB | 
| After Width: | Height: | Size: 554 KiB | 
| After Width: | Height: | Size: 4.3 KiB | 
| After Width: | Height: | Size: 3.0 KiB | 
| After Width: | Height: | Size: 4.5 KiB | 
| After Width: | Height: | Size: 3.2 KiB | 
| After Width: | Height: | Size: 5.5 KiB | 
| After Width: | Height: | Size: 2.8 KiB | 
| After Width: | Height: | Size: 4.4 KiB | 
| After Width: | Height: | Size: 3.1 KiB | 
| After Width: | Height: | Size: 4.6 KiB | 
| After Width: | Height: | Size: 3.3 KiB | 
| After Width: | Height: | Size: 4.2 KiB | 
| After Width: | Height: | Size: 3.0 KiB | 
| After Width: | Height: | Size: 4.5 KiB | 
| After Width: | Height: | Size: 3.2 KiB | 
| After Width: | Height: | Size: 4.2 KiB | 
| After Width: | Height: | Size: 3.0 KiB | 
| After Width: | Height: | Size: 4.4 KiB | 
| After Width: | Height: | Size: 3.1 KiB | 
| After Width: | Height: | Size: 4.3 KiB | 
| After Width: | Height: | Size: 3.1 KiB | 
| After Width: | Height: | Size: 4.6 KiB | 
| After Width: | Height: | Size: 3.3 KiB | 
| After Width: | Height: | Size: 3.3 KiB | 
| After Width: | Height: | Size: 3.2 KiB | 
| After Width: | Height: | Size: 87 KiB | 
| After Width: | Height: | Size: 88 KiB | 
| After Width: | Height: | Size: 6.9 KiB | 
| After Width: | Height: | Size: 3.0 KiB | 
| After Width: | Height: | Size: 3.0 KiB | 
| After Width: | Height: | Size: 4.8 KiB | 
| After Width: | Height: | Size: 3.6 KiB | 
| After Width: | Height: | Size: 5.8 KiB | 
| After Width: | Height: | Size: 1.9 KiB | 
| After Width: | Height: | Size: 1.9 KiB |