From a0ec9b231d2b86465d1509b23a5b651282d8c60e Mon Sep 17 00:00:00 2001 From: mayue <3355825524@qq.com> Date: Mon, 16 Jun 2025 00:17:23 +0800 Subject: [PATCH] 111 --- .gitignore | 9 + .gradle/8.11.1/checksums/checksums.lock | Bin 0 -> 17 bytes .gradle/8.11.1/checksums/md5-checksums.bin | Bin 0 -> 21797 bytes .gradle/8.11.1/checksums/sha1-checksums.bin | Bin 0 -> 26489 bytes .../executionHistory/executionHistory.bin | Bin 0 -> 1443400 bytes .../executionHistory/executionHistory.lock | Bin 0 -> 17 bytes .gradle/8.11.1/fileChanges/last-build.bin | Bin 0 -> 1 bytes .gradle/8.11.1/fileHashes/fileHashes.bin | Bin 0 -> 73915 bytes .gradle/8.11.1/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .../8.11.1/fileHashes/resourceHashesCache.bin | Bin 0 -> 18939 bytes .gradle/8.11.1/gc.properties | 0 .../buildOutputCleanup.lock | Bin 0 -> 17 bytes .gradle/buildOutputCleanup/cache.properties | 2 + .gradle/buildOutputCleanup/outputFiles.bin | Bin 0 -> 24257 bytes .gradle/config.properties | 2 + .gradle/file-system.probe | Bin 0 -> 8 bytes .gradle/vcs-1/gc.properties | 0 .idea/.gitignore | 3 + .idea/AndroidProjectSystem.xml | 6 + .idea/compiler.xml | 6 + .idea/deploymentTargetSelector.xml | 13 + .idea/gradle.xml | 19 + .idea/migrations.xml | 10 + .idea/misc.xml | 10 + .idea/runConfigurations.xml | 17 + .idea/vcs.xml | 6 + AndroidManifest.xml | 150 +++ NOTICE | 190 ++++ README | 23 + app/.gitignore | 1 + app/build.gradle.kts | 43 + app/proguard-rules.pro | 21 + .../notes_master/ExampleInstrumentedTest.java | 26 + app/src/main/AndroidManifest.xml | 156 +++ .../example/notes_master/MainActivity.java | 24 + .../res/drawable/ic_launcher_background.xml | 170 ++++ .../res/drawable/ic_launcher_foreground.xml | 30 + app/src/main/res/layout/activity_main.xml | 19 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 + app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes app/src/main/res/values-night/themes.xml | 7 + app/src/main/res/values/colors.xml | 5 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/themes.xml | 9 + app/src/main/res/xml/backup_rules.xml | 13 + .../main/res/xml/data_extraction_rules.xml | 19 + .../example/notes_master/ExampleUnitTest.java | 17 + build.gradle.kts | 4 + gmh_gitpracticel | 1 + gradle.properties | 21 + gradle/libs.versions.toml | 22 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 185 ++++ gradlew.bat | 89 ++ local.properties | 10 + res/color/primary_text_dark.xml | 22 + res/color/secondary_text_dark.xml | 20 + res/drawable-hdpi/bg_btn_set_color.png | Bin 0 -> 3588 bytes res/drawable-hdpi/bg_color_btn_mask.png | Bin 0 -> 245 bytes res/drawable-hdpi/call_record.png | Bin 0 -> 3533 bytes res/drawable-hdpi/clock.png | Bin 0 -> 3958 bytes res/drawable-hdpi/delete.png | Bin 0 -> 3490 bytes res/drawable-hdpi/dropdown_icon.9.png | Bin 0 -> 443 bytes res/drawable-hdpi/edit_blue.9.png | Bin 0 -> 3524 bytes res/drawable-hdpi/edit_green.9.png | Bin 0 -> 3565 bytes res/drawable-hdpi/edit_red.9.png | Bin 0 -> 3458 bytes res/drawable-hdpi/edit_title_blue.9.png | Bin 0 -> 5100 bytes res/drawable-hdpi/edit_title_green.9.png | Bin 0 -> 5627 bytes res/drawable-hdpi/edit_title_red.9.png | Bin 0 -> 5061 bytes res/drawable-hdpi/edit_title_white.9.png | Bin 0 -> 3866 bytes res/drawable-hdpi/edit_title_yellow.9.png | Bin 0 -> 6000 bytes res/drawable-hdpi/edit_white.9.png | Bin 0 -> 3441 bytes res/drawable-hdpi/edit_yellow.9.png | Bin 0 -> 3634 bytes res/drawable-hdpi/font_large.png | Bin 0 -> 3161 bytes res/drawable-hdpi/font_normal.png | Bin 0 -> 3097 bytes res/drawable-hdpi/font_size_selector_bg.9.png | Bin 0 -> 4101 bytes res/drawable-hdpi/font_small.png | Bin 0 -> 3099 bytes res/drawable-hdpi/font_super.png | Bin 0 -> 3188 bytes res/drawable-hdpi/icon_app.png | Bin 0 -> 6887 bytes res/drawable-hdpi/list_background.png | Bin 0 -> 567323 bytes res/drawable-hdpi/list_blue_down.9.png | Bin 0 -> 4361 bytes res/drawable-hdpi/list_blue_middle.9.png | Bin 0 -> 3099 bytes res/drawable-hdpi/list_blue_single.9.png | Bin 0 -> 4618 bytes res/drawable-hdpi/list_blue_up.9.png | Bin 0 -> 3301 bytes res/drawable-hdpi/list_folder.9.png | Bin 0 -> 5608 bytes res/drawable-hdpi/list_footer_bg.9.png | Bin 0 -> 2837 bytes res/drawable-hdpi/list_green_down.9.png | Bin 0 -> 4488 bytes res/drawable-hdpi/list_green_middle.9.png | Bin 0 -> 3179 bytes res/drawable-hdpi/list_green_single.9.png | Bin 0 -> 4728 bytes res/drawable-hdpi/list_green_up.9.png | Bin 0 -> 3351 bytes res/drawable-hdpi/list_red_down.9.png | Bin 0 -> 4307 bytes res/drawable-hdpi/list_red_middle.9.png | Bin 0 -> 3075 bytes res/drawable-hdpi/list_red_single.9.png | Bin 0 -> 4565 bytes res/drawable-hdpi/list_red_up.9.png | Bin 0 -> 3277 bytes res/drawable-hdpi/list_white_down.9.png | Bin 0 -> 4305 bytes res/drawable-hdpi/list_white_middle.9.png | Bin 0 -> 3042 bytes res/drawable-hdpi/list_white_single.9.png | Bin 0 -> 4546 bytes res/drawable-hdpi/list_white_up.9.png | Bin 0 -> 3220 bytes res/drawable-hdpi/list_yellow_down.9.png | Bin 0 -> 4420 bytes res/drawable-hdpi/list_yellow_middle.9.png | Bin 0 -> 3150 bytes res/drawable-hdpi/list_yellow_single.9.png | Bin 0 -> 4670 bytes res/drawable-hdpi/list_yellow_up.9.png | Bin 0 -> 3335 bytes res/drawable-hdpi/menu_delete.png | Bin 0 -> 3426 bytes res/drawable-hdpi/menu_move.png | Bin 0 -> 3294 bytes res/drawable-hdpi/new_note_normal.png | Bin 0 -> 89221 bytes res/drawable-hdpi/new_note_pressed.png | Bin 0 -> 90126 bytes .../note_edit_color_selector_panel.png | Bin 0 -> 7030 bytes res/drawable-hdpi/notification.png | Bin 0 -> 3107 bytes res/drawable-hdpi/search_result.png | Bin 0 -> 3030 bytes res/drawable-hdpi/selected.png | Bin 0 -> 4959 bytes res/drawable-hdpi/title_alert.png | Bin 0 -> 3696 bytes res/drawable-hdpi/title_bar_bg.9.png | Bin 0 -> 5970 bytes res/drawable-hdpi/widget_2x_blue.png | Bin 0 -> 1946 bytes res/drawable-hdpi/widget_2x_green.png | Bin 0 -> 1954 bytes res/drawable-hdpi/widget_2x_red.png | Bin 0 -> 1892 bytes res/drawable-hdpi/widget_2x_white.png | Bin 0 -> 1901 bytes res/drawable-hdpi/widget_2x_yellow.png | Bin 0 -> 1978 bytes res/drawable-hdpi/widget_4x_blue.png | Bin 0 -> 4165 bytes res/drawable-hdpi/widget_4x_green.png | Bin 0 -> 4192 bytes res/drawable-hdpi/widget_4x_red.png | Bin 0 -> 4223 bytes res/drawable-hdpi/widget_4x_white.png | Bin 0 -> 4106 bytes res/drawable-hdpi/widget_4x_yellow.png | Bin 0 -> 4199 bytes res/drawable/new_note.xml | 23 + res/layout/account_dialog_title.xml | 43 + res/layout/add_account_text.xml | 32 + res/layout/datetime_picker.xml | 56 + res/layout/dialog_edit_text.xml | 23 + res/layout/folder_list_item.xml | 29 + res/layout/note_edit.xml | 400 ++++++++ res/layout/note_edit_list_item.xml | 39 + res/layout/note_item.xml | 78 ++ res/layout/note_list.xml | 58 ++ res/layout/note_list_dropdown_menu.xml | 32 + res/layout/note_list_footer.xml | 24 + res/layout/settings_header.xml | 41 + res/layout/widget_2x.xml | 37 + res/layout/widget_4x.xml | 39 + res/menu/call_note_edit.xml | 48 + res/menu/call_record_folder.xml | 23 + res/menu/note_edit.xml | 52 + res/menu/note_list.xml | 39 + res/menu/note_list_dropdown.xml | 20 + res/menu/note_list_options.xml | 31 + res/menu/sub_folder.xml | 24 + res/raw-zh-rCN/introduction | 7 + res/raw/introduction | 1 + res/values-zh-rCN/arrays.xml | 23 + res/values-zh-rCN/strings.xml | 126 +++ res/values-zh-rTW/arrays.xml | 23 + res/values-zh-rTW/strings.xml | 127 +++ res/values/arrays.xml | 31 + res/values/colors.xml | 20 + res/values/dimens.xml | 24 + res/values/strings.xml | 135 +++ res/values/styles.xml | 69 ++ res/xml/preferences.xml | 30 + res/xml/searchable.xml | 27 + res/xml/widget_2x_info.xml | 23 + res/xml/widget_4x_info.xml | 23 + settings.gradle.kts | 23 + src/net/micode/notes/data/Contact.java | 73 ++ src/net/micode/notes/data/Notes.java | 279 +++++ .../notes/data/NotesDatabaseHelper.java | 362 +++++++ src/net/micode/notes/data/NotesProvider.java | 305 ++++++ src/net/micode/notes/gtask/data/MetaData.java | 82 ++ src/net/micode/notes/gtask/data/Node.java | 101 ++ src/net/micode/notes/gtask/data/SqlData.java | 189 ++++ src/net/micode/notes/gtask/data/SqlNote.java | 505 +++++++++ src/net/micode/notes/gtask/data/Task.java | 351 +++++++ src/net/micode/notes/gtask/data/TaskList.java | 343 +++++++ .../exception/ActionFailureException.java | 33 + .../exception/NetworkFailureException.java | 33 + .../notes/gtask/remote/GTaskASyncTask.java | 123 +++ .../notes/gtask/remote/GTaskClient.java | 585 +++++++++++ .../notes/gtask/remote/GTaskManager.java | 800 +++++++++++++++ .../notes/gtask/remote/GTaskSyncService.java | 128 +++ src/net/micode/notes/model/Note.java | 253 +++++ src/net/micode/notes/model/WorkingNote.java | 368 +++++++ src/net/micode/notes/tool/BackupUtils.java | 344 +++++++ src/net/micode/notes/tool/DataUtils.java | 295 ++++++ .../micode/notes/tool/GTaskStringUtils.java | 113 +++ src/net/micode/notes/tool/ResourceParser.java | 181 ++++ .../micode/notes/ui/AlarmAlertActivity.java | 158 +++ .../micode/notes/ui/AlarmInitReceiver.java | 65 ++ src/net/micode/notes/ui/AlarmReceiver.java | 30 + src/net/micode/notes/ui/DateTimePicker.java | 485 +++++++++ .../micode/notes/ui/DateTimePickerDialog.java | 90 ++ src/net/micode/notes/ui/DropdownMenu.java | 61 ++ .../micode/notes/ui/FoldersListAdapter.java | 80 ++ src/net/micode/notes/ui/NoteEditActivity.java | 873 ++++++++++++++++ src/net/micode/notes/ui/NoteEditText.java | 217 ++++ src/net/micode/notes/ui/NoteItemData.java | 224 ++++ .../micode/notes/ui/NotesListActivity.java | 954 ++++++++++++++++++ src/net/micode/notes/ui/NotesListAdapter.java | 184 ++++ src/net/micode/notes/ui/NotesListItem.java | 122 +++ .../notes/ui/NotesPreferenceActivity.java | 388 +++++++ .../notes/widget/NoteWidgetProvider.java | 132 +++ .../notes/widget/NoteWidgetProvider_2x.java | 47 + .../notes/widget/NoteWidgetProvider_4x.java | 46 + 210 files changed, 13326 insertions(+) create mode 100644 .gitignore create mode 100644 .gradle/8.11.1/checksums/checksums.lock create mode 100644 .gradle/8.11.1/checksums/md5-checksums.bin create mode 100644 .gradle/8.11.1/checksums/sha1-checksums.bin create mode 100644 .gradle/8.11.1/executionHistory/executionHistory.bin create mode 100644 .gradle/8.11.1/executionHistory/executionHistory.lock create mode 100644 .gradle/8.11.1/fileChanges/last-build.bin create mode 100644 .gradle/8.11.1/fileHashes/fileHashes.bin create mode 100644 .gradle/8.11.1/fileHashes/fileHashes.lock create mode 100644 .gradle/8.11.1/fileHashes/resourceHashesCache.bin create mode 100644 .gradle/8.11.1/gc.properties create mode 100644 .gradle/buildOutputCleanup/buildOutputCleanup.lock create mode 100644 .gradle/buildOutputCleanup/cache.properties create mode 100644 .gradle/buildOutputCleanup/outputFiles.bin create mode 100644 .gradle/config.properties create mode 100644 .gradle/file-system.probe create mode 100644 .gradle/vcs-1/gc.properties create mode 100644 .idea/.gitignore create mode 100644 .idea/AndroidProjectSystem.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/deploymentTargetSelector.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/migrations.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/runConfigurations.xml create mode 100644 .idea/vcs.xml create mode 100644 AndroidManifest.xml create mode 100644 NOTICE create mode 100644 README create mode 100644 app/.gitignore create mode 100644 app/build.gradle.kts create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/example/notes_master/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/com/example/notes_master/MainActivity.java create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/values-night/themes.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/main/res/xml/backup_rules.xml create mode 100644 app/src/main/res/xml/data_extraction_rules.xml create mode 100644 app/src/test/java/com/example/notes_master/ExampleUnitTest.java create mode 100644 build.gradle.kts create mode 160000 gmh_gitpracticel create mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 local.properties create mode 100644 res/color/primary_text_dark.xml create mode 100644 res/color/secondary_text_dark.xml create mode 100644 res/drawable-hdpi/bg_btn_set_color.png create mode 100644 res/drawable-hdpi/bg_color_btn_mask.png create mode 100644 res/drawable-hdpi/call_record.png create mode 100644 res/drawable-hdpi/clock.png create mode 100644 res/drawable-hdpi/delete.png create mode 100644 res/drawable-hdpi/dropdown_icon.9.png create mode 100644 res/drawable-hdpi/edit_blue.9.png create mode 100644 res/drawable-hdpi/edit_green.9.png create mode 100644 res/drawable-hdpi/edit_red.9.png create mode 100644 res/drawable-hdpi/edit_title_blue.9.png create mode 100644 res/drawable-hdpi/edit_title_green.9.png create mode 100644 res/drawable-hdpi/edit_title_red.9.png create mode 100644 res/drawable-hdpi/edit_title_white.9.png create mode 100644 res/drawable-hdpi/edit_title_yellow.9.png create mode 100644 res/drawable-hdpi/edit_white.9.png create mode 100644 res/drawable-hdpi/edit_yellow.9.png create mode 100644 res/drawable-hdpi/font_large.png create mode 100644 res/drawable-hdpi/font_normal.png create mode 100644 res/drawable-hdpi/font_size_selector_bg.9.png create mode 100644 res/drawable-hdpi/font_small.png create mode 100644 res/drawable-hdpi/font_super.png create mode 100644 res/drawable-hdpi/icon_app.png create mode 100644 res/drawable-hdpi/list_background.png create mode 100644 res/drawable-hdpi/list_blue_down.9.png create mode 100644 res/drawable-hdpi/list_blue_middle.9.png create mode 100644 res/drawable-hdpi/list_blue_single.9.png create mode 100644 res/drawable-hdpi/list_blue_up.9.png create mode 100644 res/drawable-hdpi/list_folder.9.png create mode 100644 res/drawable-hdpi/list_footer_bg.9.png create mode 100644 res/drawable-hdpi/list_green_down.9.png create mode 100644 res/drawable-hdpi/list_green_middle.9.png create mode 100644 res/drawable-hdpi/list_green_single.9.png create mode 100644 res/drawable-hdpi/list_green_up.9.png create mode 100644 res/drawable-hdpi/list_red_down.9.png create mode 100644 res/drawable-hdpi/list_red_middle.9.png create mode 100644 res/drawable-hdpi/list_red_single.9.png create mode 100644 res/drawable-hdpi/list_red_up.9.png create mode 100644 res/drawable-hdpi/list_white_down.9.png create mode 100644 res/drawable-hdpi/list_white_middle.9.png create mode 100644 res/drawable-hdpi/list_white_single.9.png create mode 100644 res/drawable-hdpi/list_white_up.9.png create mode 100644 res/drawable-hdpi/list_yellow_down.9.png create mode 100644 res/drawable-hdpi/list_yellow_middle.9.png create mode 100644 res/drawable-hdpi/list_yellow_single.9.png create mode 100644 res/drawable-hdpi/list_yellow_up.9.png create mode 100644 res/drawable-hdpi/menu_delete.png create mode 100644 res/drawable-hdpi/menu_move.png create mode 100644 res/drawable-hdpi/new_note_normal.png create mode 100644 res/drawable-hdpi/new_note_pressed.png create mode 100644 res/drawable-hdpi/note_edit_color_selector_panel.png create mode 100644 res/drawable-hdpi/notification.png create mode 100644 res/drawable-hdpi/search_result.png create mode 100644 res/drawable-hdpi/selected.png create mode 100644 res/drawable-hdpi/title_alert.png create mode 100644 res/drawable-hdpi/title_bar_bg.9.png create mode 100644 res/drawable-hdpi/widget_2x_blue.png create mode 100644 res/drawable-hdpi/widget_2x_green.png create mode 100644 res/drawable-hdpi/widget_2x_red.png create mode 100644 res/drawable-hdpi/widget_2x_white.png create mode 100644 res/drawable-hdpi/widget_2x_yellow.png create mode 100644 res/drawable-hdpi/widget_4x_blue.png create mode 100644 res/drawable-hdpi/widget_4x_green.png create mode 100644 res/drawable-hdpi/widget_4x_red.png create mode 100644 res/drawable-hdpi/widget_4x_white.png create mode 100644 res/drawable-hdpi/widget_4x_yellow.png create mode 100644 res/drawable/new_note.xml create mode 100644 res/layout/account_dialog_title.xml create mode 100644 res/layout/add_account_text.xml create mode 100644 res/layout/datetime_picker.xml create mode 100644 res/layout/dialog_edit_text.xml create mode 100644 res/layout/folder_list_item.xml create mode 100644 res/layout/note_edit.xml create mode 100644 res/layout/note_edit_list_item.xml create mode 100644 res/layout/note_item.xml create mode 100644 res/layout/note_list.xml create mode 100644 res/layout/note_list_dropdown_menu.xml create mode 100644 res/layout/note_list_footer.xml create mode 100644 res/layout/settings_header.xml create mode 100644 res/layout/widget_2x.xml create mode 100644 res/layout/widget_4x.xml create mode 100644 res/menu/call_note_edit.xml create mode 100644 res/menu/call_record_folder.xml create mode 100644 res/menu/note_edit.xml create mode 100644 res/menu/note_list.xml create mode 100644 res/menu/note_list_dropdown.xml create mode 100644 res/menu/note_list_options.xml create mode 100644 res/menu/sub_folder.xml create mode 100644 res/raw-zh-rCN/introduction create mode 100644 res/raw/introduction create mode 100644 res/values-zh-rCN/arrays.xml create mode 100644 res/values-zh-rCN/strings.xml create mode 100644 res/values-zh-rTW/arrays.xml create mode 100644 res/values-zh-rTW/strings.xml create mode 100644 res/values/arrays.xml create mode 100644 res/values/colors.xml create mode 100644 res/values/dimens.xml create mode 100644 res/values/strings.xml create mode 100644 res/values/styles.xml create mode 100644 res/xml/preferences.xml create mode 100644 res/xml/searchable.xml create mode 100644 res/xml/widget_2x_info.xml create mode 100644 res/xml/widget_4x_info.xml create mode 100644 settings.gradle.kts create mode 100644 src/net/micode/notes/data/Contact.java create mode 100644 src/net/micode/notes/data/Notes.java create mode 100644 src/net/micode/notes/data/NotesDatabaseHelper.java create mode 100644 src/net/micode/notes/data/NotesProvider.java create mode 100644 src/net/micode/notes/gtask/data/MetaData.java create mode 100644 src/net/micode/notes/gtask/data/Node.java create mode 100644 src/net/micode/notes/gtask/data/SqlData.java create mode 100644 src/net/micode/notes/gtask/data/SqlNote.java create mode 100644 src/net/micode/notes/gtask/data/Task.java create mode 100644 src/net/micode/notes/gtask/data/TaskList.java create mode 100644 src/net/micode/notes/gtask/exception/ActionFailureException.java create mode 100644 src/net/micode/notes/gtask/exception/NetworkFailureException.java create mode 100644 src/net/micode/notes/gtask/remote/GTaskASyncTask.java create mode 100644 src/net/micode/notes/gtask/remote/GTaskClient.java create mode 100644 src/net/micode/notes/gtask/remote/GTaskManager.java create mode 100644 src/net/micode/notes/gtask/remote/GTaskSyncService.java create mode 100644 src/net/micode/notes/model/Note.java create mode 100644 src/net/micode/notes/model/WorkingNote.java create mode 100644 src/net/micode/notes/tool/BackupUtils.java create mode 100644 src/net/micode/notes/tool/DataUtils.java create mode 100644 src/net/micode/notes/tool/GTaskStringUtils.java create mode 100644 src/net/micode/notes/tool/ResourceParser.java create mode 100644 src/net/micode/notes/ui/AlarmAlertActivity.java create mode 100644 src/net/micode/notes/ui/AlarmInitReceiver.java create mode 100644 src/net/micode/notes/ui/AlarmReceiver.java create mode 100644 src/net/micode/notes/ui/DateTimePicker.java create mode 100644 src/net/micode/notes/ui/DateTimePickerDialog.java create mode 100644 src/net/micode/notes/ui/DropdownMenu.java create mode 100644 src/net/micode/notes/ui/FoldersListAdapter.java create mode 100644 src/net/micode/notes/ui/NoteEditActivity.java create mode 100644 src/net/micode/notes/ui/NoteEditText.java create mode 100644 src/net/micode/notes/ui/NoteItemData.java create mode 100644 src/net/micode/notes/ui/NotesListActivity.java create mode 100644 src/net/micode/notes/ui/NotesListAdapter.java create mode 100644 src/net/micode/notes/ui/NotesListItem.java create mode 100644 src/net/micode/notes/ui/NotesPreferenceActivity.java create mode 100644 src/net/micode/notes/widget/NoteWidgetProvider.java create mode 100644 src/net/micode/notes/widget/NoteWidgetProvider_2x.java create mode 100644 src/net/micode/notes/widget/NoteWidgetProvider_4x.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7df8dff --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# generated files +bin/ +gen/ + +# Local configuration file (sdk path, etc) +project.properties +.settings/ +.classpath +.project diff --git a/.gradle/8.11.1/checksums/checksums.lock b/.gradle/8.11.1/checksums/checksums.lock new file mode 100644 index 0000000000000000000000000000000000000000..c5a6e6a7501405b6320d1e8c9a0c3e10f801b1aa GIT binary patch literal 17 UcmZQBJJ`v)LATSA0Rrv-04;z7OaK4? literal 0 HcmV?d00001 diff --git a/.gradle/8.11.1/checksums/md5-checksums.bin b/.gradle/8.11.1/checksums/md5-checksums.bin new file mode 100644 index 0000000000000000000000000000000000000000..5988d2931fe98744473c4bfdd9bd5df423f0c4c1 GIT binary patch literal 21797 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 literal 0 HcmV?d00001 diff --git a/.gradle/8.11.1/checksums/sha1-checksums.bin b/.gradle/8.11.1/checksums/sha1-checksums.bin new file mode 100644 index 0000000000000000000000000000000000000000..9cb8957223c98d19d98060727e7a18f26f2fa769 GIT binary patch literal 26489 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

C%4 zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{ ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh zOBLA>$~P3r4b*NcqR;y6pwyhZ3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu z(U^&XLW^QX7F9Z*SRPpQl{B%x)_AMp^}_v~?j7 zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0 z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@ E0Of=7!TQj4N+cqN`nQhxvX7dAV-`K|Ub$-q+H-5I?Tx0g9jWxd@A|?POE8`3b8fO$T))xP* z(X?&brZw({`)WU&rdAs1iTa0x6F@PIxJ&&L|dpySV!ID|iUhjCcKz(@mE z!x@~W#3H<)4Ae(4eQJRk`Iz3<1)6^m)0b_4_TRZ+cz#eD3f8V;2r-1fE!F}W zEi0MEkTTx}8i1{`l_6vo0(Vuh0HD$I4SjZ=?^?k82R51bC)2D_{y8mi_?X^=U?2|F{Vr7s!k(AZC$O#ZMyavHhlQ7 zUR~QXuH~#o#>(b$u4?s~HLF*3IcF7023AlwAYudn0FV~|odGH^05AYPEfR)8p`i{n zwg3zPVp{+wOsxKc>)(pMupKF!Y2HoUqQ3|Yu|8lwR=?5zZuhG6J?H`bSNk_wPoM{u zSL{c@pY7+c2kck>`^q1^^gR0QB7Y?KUD{vz-uVX~;V-rW)PDcI)$_UjgVV?S?=oLR zf4}zz{#*R_{LkiJ#0RdQLNC^2Vp%JPEUvG9ra2BVZ92(p9h7Ka@!yf9(lj#}>+|u* z;^_?KWdzkM`6gqPo9;;r6&JEa)}R3X{(CWv?NvgLeOTq$cZXqf7|sPImi-7cS8DCN zGf;DVt3Am`>hH3{4-WzH43Ftx)SofNe^-#|0HdCo<+8Qs!}TZP{HH8~z5n`ExcHuT zDL1m&|DVpIy=xsLO>8k92HcmfSKhflQ0H~9=^-{#!I1g(;+44xw~=* zxvNz35vfsQE)@)Zsp*6_GjYD};Squ83<_?^SbALb{a`j<0Gn%6JY!zhp=Fg}Ga2|8 z52e1WU%^L1}15Ex0fF$e@eCT(()_P zvV?CA%#Sy08_U6VPt4EtmVQraWJX` zh=N|WQ>LgrvF~R&qOfB$!%D3cGv?;Xh_z$z7k&s4N)$WYf*k=|*jCEkO19{h_(%W4 zPuOqbCw`SeAX*R}UUsbVsgtuG?xs(#Ikx9`JZoQFz0n*7ZG@Fv@kZk`gzO$HoA9kN z8U5{-yY zvV{`&WKU2$mZeoBmiJrEdzUZAv1sRxpePdg1)F*X^Y)zp^Y*R;;z~vOv-z&)&G)JQ{m!C9cmziu1^nHA z`#`0c>@PnQ9CJKgC5NjJD8HM3|KC(g5nnCq$n0Gsu_DXk36@ql%npEye|?%RmG)

FJ$wK}0tWNB{uH;AM~i literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..948a3070fe34c611c42c0d3ad3013a0dce358be0 GIT binary patch literal 1900 zcmV-y2b1_xNk&Fw2LJ$9MM6+kP&il$0000G0001A003VA06|PpNH75a00DqwTbm-~ zullQTcXxO9ki!OCRx^i?oR|n!<8G0=kI^!JSjFi-LL*`V;ET0H2IXfU0*i>o6o6Gy zRq6Ap5(_{XLdXcL-MzlN`ugSdZY_`jXhcENAu)N_0?GhF))9R;E`!bo9p?g?SRgw_ zEXHhFG$0{qYOqhdX<(wE4N@es3VIo$%il%6xP9gjiBri+2pI6aY4 zJbgh-Ud|V%3O!IcHKQx1FQH(_*TK;1>FQWbt^$K1zNn^cczkBs=QHCYZ8b&l!UV{K z{L0$KCf_&KR^}&2Fe|L&?1I7~pBENnCtCuH3sjcx6$c zwqkNkru);ie``q+_QI;IYLD9OV0ZxkuyBz|5<$1BH|vtey$> z5oto4=l-R-Aaq`Dk0}o9N0VrkqW_#;!u{!bJLDq%0092{Ghe=F;(kn} z+sQ@1=UlX30+2nWjkL$B^b!H2^QYO@iFc0{(-~yXj2TWz?VG{v`Jg zg}WyYnwGgn>{HFaG7E~pt=)sOO}*yd(UU-D(E&x{xKEl6OcU?pl)K%#U$dn1mDF19 zSw@l8G!GNFB3c3VVK0?uyqN&utT-D5%NM4g-3@Sii9tSXKtwce~uF zS&Jn746EW^wV~8zdQ1XC28~kXu8+Yo9p!<8h&(Q({J*4DBglPdpe4M_mD8AguZFn~ ztiuO~{6Bx?SfO~_ZV(GIboeR9~hAym{{fV|VM=77MxDrbW6`ujX z<3HF(>Zr;#*uCvC*bpoSr~C$h?_%nXps@A)=l_;({Fo#6Y1+Zv`!T5HB+)#^-Ud_; zBwftPN=d8Vx)*O1Mj+0oO=mZ+NVH*ptNDC-&zZ7Hwho6UQ#l-yNvc0Cm+2$$6YUk2D2t#vdZX-u3>-Be1u9gtTBiMB^xwWQ_rgvGpZ6(C@e23c!^K=>ai-Rqu zhqT`ZQof;9Bu!AD(i^PCbYV%yha9zuoKMp`U^z;3!+&d@Hud&_iy!O-$b9ZLcSRh? z)R|826w}TU!J#X6P%@Zh=La$I6zXa#h!B;{qfug}O%z@K{EZECu6zl)7CiNi%xti0 zB{OKfAj83~iJvmpTU|&q1^?^cIMn2RQ?jeSB95l}{DrEPTW{_gmU_pqTc)h@4T>~& zluq3)GM=xa(#^VU5}@FNqpc$?#SbVsX!~RH*5p0p@w z;~v{QMX0^bFT1!cXGM8K9FP+=9~-d~#TK#ZE{4umGT=;dfvWi?rYj;^l_Zxywze`W z^Cr{55U@*BalS}K%Czii_80e0#0#Zkhlij4-~I@}`-JFJ7$5{>LnoJSs??J8kWVl6|8A}RCGAu9^rAsfCE=2}tHwl93t0C?#+jMpvr7O3`2=tr{Hg$=HlnjVG^ewm|Js0J*kfPa6*GhtB>`fN!m#9J(sU!?(OSfzY*zS(FJ<-Vb zfAIg+`U)YaXv#sY(c--|X zEB+TVyZ%Ie4L$gi#Fc++`h6%vzsS$pjz9aLt+ZL(g;n$Dzy5=m=_TV(3H8^C{r0xd zp#a%}ht55dOq?yhwYPrtp-m1xXp;4X;)NhxxUpgP%XTLmO zcjaFva^}dP3$&sfFTIR_jC=2pHh9kpI@2(6V*GQo7Ws)`j)hd+tr@P~gR*2gO@+1? zG<`_tB+LJuF|SZ9tIec;h%}}6WClT`L>HSW?E{Hp1h^+mlbf_$9zA>!ug>NALJsO{ mU%z=YwVD?}XMya)Bp;vlyE5&E_6!fzx9pwrdz474!~g(M6R?N? literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f GIT binary patch literal 3918 zcmV-U53%r4Nk&FS4*&pHMM6+kP&il$0000G0001A003VA06|PpNSy@$00HoY|G(*G z+qV7x14$dSO^Re!iqt-AAIE9iwr$(CZQJL$blA4B`>;C3fBY6Q8_YSjb2%a=fc}4E zrSzssacq<^nmW|Rs93PJni30R<8w<(bK_$LO4L?!_OxLl$}K$MUEllnMK|rg=f3;y z*?;3j|Nh>)p0JQ3A~rf(MibH2r+)3cyV1qF&;8m{w-S*y+0mM){KTK^M5}ksc`qX3 zy>rf^b>~l>SSHds8(I@hz3&PD@LmEs4&prkT=BjsBCXTMhN$_)+kvnl0bLKW5rEsj z*d#KXGDB4P&>etx0X+`R19yC=LS)j!mgs5M0L~+o-T~Jl!p!AJxnGAhV%~rhYUL4hlWhgES3Kb5oA&X z{}?3OBSS-{!v$nCIGj->(-TAG)8LR{htr41^gxsT8yqt2@DEG6Yl`Uma3Nd4;YUoW zTbkYl3CMU5ypMF3EIkYmWL|*BknM`0+Kq6CpvO(y$#j94e+q{vI{Zp8cV_6RK!`&C zob$*5Q|$IZ09dW=L!V zw@#2wviu|<#3lgGE8GEhcx+zBt`} zOwP8j9X%^f7i_bth4PiJ$LYtFJSCN$3xwDN;8mr*B;CJwBP2G0TMq0uNt7S^DO_wE zepk!Wrn#Z#03j{`c*Rf~y3o7?J}w?tEELRUR2cgxB*Y{LzA#pxHgf}q?u5idu>077 zd^=p)`nA}6e`|@`p?u}YU66PP_MA}Zqqe!c{nK&z%Jwq1N4e_q<#4g^xaz=ao;u|6 zwpRcW2Lax=ZGbx=Q*HhlJ`Ns#Y*r0*%!T?P*TTiX;rb)$CGLz=rSUum$)3Qyv{BL2 zO*=OI2|%(Yz~`pNEOnLp>+?T@glq-DujlIp?hdJeZ7ctP4_OKx|5@EOps3rr(pWzg zK4d3&oN-X2qN(d_MkfwB4I)_)!I_6nj2iA9u^pQ{;GckGLxBGrJUM2Wdda!k)Y>lq zmjws>dVQ*vW9lvEMkiN3wE-__6OWD0txS&Qn0n22cyj4Q*8(nG4!G{6OOwNvsrPIL zCl-$W9UwkEUVuLwyD%|inbOF*xMODZ4VMEVAq_zUxZ+K#Gdqf!DW$5f)?7UNOFMz! zrB~tuu=6X2FE(p^iqgxr+?ZK;=yz`e;C$#_@D9Lj-+TDVOrva>(#*PVbaHO>A)mhl z07OJWCqYC60518$!&c`eNBcBW%GnfaQ*$eazV^2_AW?j)h;J1nUjN(I9=0+!RVx~% z3@Tf!P0TE+98jA?WceK-}A1% zW!K)lyKcGqy#M~})315-A#2NXQ`?6NR#Apo=S!oF=JfpX>iR*49ec{7AN$xxpK{D$ z2d%Fz&rdfSqourN$~Y^NFIMV1CZ?J*bMx~H3k&meGtH@q9ra2vZxmA$S(#jaaj-g4 ztJmxG+DLV<*q<|sDXPp$X>E)#S}Vm&sRaO5P&goh2><}FEdZSXDqsL$06sAkh(e+v zAsBhKSRexgwg6tIy~GFJzaTxXD(}|+0eOwFDA%rn`X;MVwDHT9=4=g%OaJ9s%3b9>9EUTnnp0t;2Zpa{*>mk~hZqItE_!dQ zOtC>8`$l|mV43Jbudf0N6&&X;{=z}Zi}d1`2qmJ}i|0*GsulD3>GgQXHN)pkR6sf1 z?5ZU%&xtL}oH;YiAA)d*^Ndw2T$+Mjuzyzz@-SM`9df7LqTxLuIwC~S0092~+=qYv z@*ja;?Wt!T!{U?c*Z0YtGe)XbI&y-?B&G2$`JDM)(dIV9G`Sc#6?sI60de6kv+)Qb zUW~2|WjvJq3TA8`0+sWA3zRhY9a~ow)O~&StBkG2{*{TGiY~S8ep{V&Vo2l<6LWsu z^#p0-v*t2?3&aA1)ozu|%efSR=XnpX$lvTeRdKlvM!@|pM5p2w3u-6 zU>}t2xiYLS+{|%C65AzX+23Mtlq?BS&YdYcYsVjoiE&rT>;Necn6l^K)T^lmE`5u{ zm1i+-a-gc;Z&v-{;8r)z6NYfBUv+=_L}ef}qa9FX01)+Aaf+;xj(mL6|JUzGJR1|fnanb%?BPPIp>SCjP|8qE5qJ{=n5ZGw?81z3(k;pzH%1CtlX50{E7h)$h{qGKfzC`e2o`*IqA#tjA z`Fz&^%$b9F*N`)U-#6>a)Z`55`$Dd0cfcs0$d13^ONrdCu9xcv_=n#WQo8stcz3jP9|2EvdI-RhJM3%Q%oM&!OlShM|0 z?gz?wHZSnm45njLtsz8PVT1S&jAlbKg5kVam$p16=EK@Sj4EP0OtH zmJDmdc^v)x>56Qg_wmYHz6h)>kl_h$>0@J!ypv%APmjZTAQVLy6Fu50RGY&JAVNhx zrF_qG6`x9MkT;1SFWo$)l{M$;3qUDn9JwE}z zRl#E_bDRJFii61kPgBybIgp8dNW!Cc1b*^YYk-#oWLJvtM_v^hQx~9?8LD4VFFxBF z3MlrsSC%f9Oupn*ctPL0U1fwfX?`tRhPD{PSLFPQOmIt$mDy0SgpNVvHS+f#Do>h1Gn?LZU9(KaN>Q_=Y*_T zvtD7%_u^^+{g`0VGzg(VZrpVQ6Ub5M=tI_p7T93R8@3Zulu3|#{iNcu!oiHxZ4Rf*( zfmiN$$ru(*_Zqn=`Gq#OuHRTSwp7uH_SokR&|)RuW5yo=Z|_4?qU-JU+tpt>!B&Is z@N(=SG;bpVc;AO@zbmMM zScqq1)b-ZQIrs={oD}|?6y{$HNB1U0^LsBh8JI&3!GBZxOXI<}&5-$lgkAaYqhOTb z?2vEnZ$-kk;*M_17(upJF3%+iH*s0-r{vttXVB2OUwI1s^+G(Ft(U8gYFXC}#P&E^ z>T@C^tS`Z7{6HT4_nF~n>JlZtk5&qDBl6r|^kzQYe`wq!C)n@$c>WOPA61NDFj<<6 zGW71NMMhwAl!U-yqrq2xrSFqRCI8acw7?}3j;ynxo*-b7Co;g5r%^j=H@9({PXXBf z@r>U>>N;E)81wx`B4f%{PB~MHka_);%kBCb(d|Jy5!MqJ%2p`t&@L)4$T2j&-WHvG zv3(uyA_gwqNu(k?jQTtv3dgPKRZoH8prxe7>pQBW5L&dpumS&5Ld2?(sCpJjvc4L5 zEnh&?91WVm)ZdTj=fjJ$pPDdgAttLXuke+?KdKxu*;kTC(r!tQk6;gxj4h%FdHAt(^M3YvYj(!tOeN)+Hvj6+< zzyJRG?^lZfWuR#t!tUKP&(?%3v&Zd$R2YN>lB(Lq`OInY48%4%yTv2 zYe1{G`3)(PDEio5Y@-I5tUf`c%%OCJMtSW56g3iEg%3`$7XSJJHyA z<|7&N)5Xrlgv~%BO24eFd;Hd;uiK%D`EdK|quUeRZDqbh9l)%j%J#0lfrZumvA<_w zu&=AVvdChf6}eqh(bUz`(`Ue*p01{fBAcTgKyDYLs_I+YyJEk+rM@avU~>fB$n)HS zM7pfJydu`i%gfS<{PF94kZDv$t>06sAkheDzu40NJ$5CMW%n^Lls?8^p^QGWURbKu3ZduZQZ((s2? zzE`}<{;Zt7<$C|9R8A~DJ~@%x>TfP zF>TX8)@v|t)q4GjRt<}5s6hLHwRel7>V@&r-O|Av(yh;Q1A{E>Ir>p+%dHD|=l+lT zpr(Dg&>#Nu=!)6bCLr-ZS%|;h)Ij$+e@r8_{qO19QvDe=&1tmpY*0lcA^Cc-#{9fQ z<~$*<&P$Q<_jy#<$40PMofM7aQ}C=jphI`4kLg}Z7CIN#26D{-4v-_CA-LiE@(%{y!BzsU%gG`Q?sjLUf%qFSl0y)2#ae*+EI>s|i`d^V$Dn)qmzqRq6VJRY|{4ujsIU%#bnqU6MR&-1I_43=|5(6Jr;Jvert) zE?S|Tmn}Tv<-??sxV5@9t}3D=>YZ0JrQe$CO~|EY=Lj9RM&4svQHPQL6%pV5fPFiH zfXDx;l@~et{*{U*#c#Dvzu)|znDO7$#CRx)Z&yp-}SrD{&|(MQtfUz~n35@RLfUy=aqrhCX0M}J_r5QsK~NmRCR|Nm&L z41UdsLjWxSUlL41r^0K&nCCK>fdR-!MYjFg(z9_mF^C|#ZQw?`)f6uVzF^`bRnVY& zo}@M06J&_+>w9@jpaO4snmU;0t-(zYW1qVBHtuD!d?%?AtN7Plp><-1Y8Rqb20ZaP zTCgn*-Sri4Q8Xn>=gNaWQ57%!D35UkA@ksOlPB*Dvw}t02ENAqw|kFhn%ZyyW%+t{ zNdM!uqEM^;2}f+tECHbwLmH*!nZVrb$-az%t50Y2pg(HqhvY-^-lb}>^6l{$jOI6} zo_kBzj%8aX|6H5M0Y<)7pzz_wLkIpRm!;PzY)9+24wk2&TT{w--phDGDCOz{cN_ca zpnm7`$oDy=HX%0i-`769*0M6(e5j-?(?24%)<)&46y0e&6@HCDZAm9W6Ib#Y#BF6- z=30crHGg+RRTe%VBC>T00OV6F+gQDAK38Ne3N9bm|62tPccBJi)5{B z4zc^Db72XiBd}v$CF|yU{Z=M|DZ%-(XarYNclODlb1Kz1_EKLy(NSLCN`eUl(rBCL zT*jx@wNvze0|TSqgE(QArOZU)_?qH(sj#TwzElLs9q)(0u!_P|R%Cy_0JFQxgGV>1 zz4?_uq<8_gM0`c*Hh|;UMz~vrg1gQXp{ufg`hM_qU;U>+zmvc5blCLSq@PrEBSGR# z&8=2Z4uXN`F3p73ueD1l{s{k$WipAvSh5W7ABe?4)t;r@V?y`bNB5FvBuE|0VRTb< zM1Hn^?DSsJY+sX@T5xW=#>T9VEV|?<(=6|ge$X6Sb05!LFdjDcoq*gM(Zq=t;_)Le&jyt(&9jzR73noru`a# zN*<`KwGa^gZU3-)MSLF0aFag#f0<>E(bYTeHmtdbns#|I)-$)mJ`q9ctQ8g0=ET?| zdO}eZ*b_p>ygRTtR^5Ggdam=Zb5wmd{}np+Jn1d_=M`~P=M67jj})fH4ztb5yQqQW z^C|C&^LHAK-u+ooIK)yM)QM?t;|<{P;;{`p=BclzAN#JzL4jCwXkQB1Dy{=^KR`=~ zTrr)y7eiYBzSNs_DvO=4A6#EgGS-zY%Vi)N*Yb`U;6o}KR}dq{r9pT5wqZ@3NOE8- z9-(}D|Nc5732CSYQbL)!gPQ#RbD8BhK3dl{sUuPvei0tkvnJBxDEAYTesU8H$)g(Plra{VH(v3u^CO1~(+ zU0O7#)jaS4{NcwA+LuSm&VBcX2#Im3xg)W}ySNw%->orn1taZ&+d)}8gJTqA!u|5P z{yv?zol_3|(1(%M(EVU=cp?L`{Pi|ixk{U)*guFML3P!OSlz;zGA#T+E@8@cgQ_mv1o7RSU=Zo_82F?&&2r;WE z@wk}JHYEZ9nYUc(Vv~iTCa3u8e4q(yq<29VoNbKk|`mq%I6u)My=gPIDuUb&lzf4`MEA9^g8u z)vp8|$$HE9m_BTV?lOosIGa4jud=jIbw)O2eCMfyw2*S8?hjWw^nqws$O*M$3I1)x zR0PWFb3$ySOcGTe1dz%N0l;RPc`x%05FtT^f^j{YCP}*Q=lvp4$ZXrTZQHhO+w%wJn3c8j%+5C3UAFD&%8dBl_qi9D5g8fry}6Ev z2_Q~)5^N$!IU`BPh1O|=BxQ#*C5*}`lluC515$lxc-vNC)IgW=K|=z7o%cWFpndn= zX}f{`!VK02_kU+Q5a3m37J;c} zTzbxteE{GNf?yLt5X=Bzc-mio^Up0nunMCgp*ZJ;%MJvPM3QK)BryP(_v@ei4UvHr z6+sbCifQaOkL6-;5fL8$W($zZ_;CZp305C;~$hhRquZr-r)jjd1z z31%ZK{-(`P#|Um_Sivn@p$-vz46uqT>QG0B1w9znfS9A8PB2LaHdzA|_)yjXVR*l{ zkcu3@vEf7bxH0nkh`q?8FmoO_Ucui*>_a~P?qQrlZ9@+D7%MTpSnztpylXrt5!-k8_QPB?YL8Kx_On8WD zgT+111d(Op$^$&KLAN5+@?>f7F4~wFi(8TL8+szgVmcMDTp5l&k6~=rA{Dt}!gb^r zSWY<)M7D|Z2P0cEodj6E42PV>&>DFmQpgt)E-|#sSUU@uKed+F680H@<;-x{p|nuH4!_mn85rx>wz;0mPi2ZkL#k6;sznu?cXh!T0S>{w6 zL^gvR05NY64l*<+_L>On$rjx9!US;l;LX6@z}yi#2XHh)F@Oo+l)h%fq$v}DNmF2> zfs^_t0)3N-W<9-N?uedVv{)-J0W5mh#29QM5R5h&KuiRM=0Zvnf#lF=K#WlCgc#9c zS;qvh(P$!_a8JwyhI^ZJV2k+B6Z^64?w|1?5gyo6y{}923CRZfYVe1#?F% z7h2SUiNO3;T#JUOyovSs@@C1GtwipycA=*x5{BpIZ_#GCMuV8XK=x;qCNy{d7?wA~ zC+=vjls;ci&zW=6$H~4^K%v{p}Ab?U%C6Z4p%eC<3ExqU$XR<}LLF67A$Sr20DR_pJ3yeBa~ z^sw{V0FI5;UpwXsScYuhbqGQ`YQ25;6p6W^+tgL&;Ml;>S3CGpSZ>VrTn0m1$y$HU z&65)I!c?oREz};c=nLCliriqQX->4uivHTgd${GqeAlf*!P^B|jkU|*IdNP(&6C>4 zqOW$)Nw9nvjy^&`?E|gotDV{JmJ9Q~vuhy<`^C4XIUDt|j4o6rK^e8_(=YqC zuaR6TRVf@tUFHB079o4MBIh{M~4>WwnGgesQH*3?w(RA%hCZ*7)b!aNV=yOQ%o_Y=Lt0Sl*(9^jfRnC210Om$=y>*o|3z} zAR&vAdrB#mWoaB0fJSw9xw|Am$fzK>rx-~R#7IFSAwdu_EI|SRfB*yl0w8oX09H^q zAjl2?0I)v*odGJ40FVGaF&2qJq9Gv`>V>2r0|c`GX8h>CX8eHcOy>S0@<;M3<_6UM z7yCEpug5NZL!H_0>Hg_HasQGxR`rY&Z{geOy?N92Z z{lER^um|$*?*G63*njwc(R?NT)Bei*3jVzR>FWUDb^gKhtL4A=kE_1p-%Fo2`!8M} z(0AjuCiS;G{?*^1tB-uY%=)SRx&D)pK4u@>f6@KPe3}2j_har$>HqzH;UCR^ssFD0 z7h+VLO4o@_Yt>>AeaZKUxqyvxWCAjKB>qjQ30UA)#w z&=RmdwlT`7a8J8Yae=7*c8XL|{@%wA8uvCqfsNX^?UZsS>wX}QD{K}ad4y~iO*p%4 z_cS{u7Ek%?WV6em2(U9#d8(&JDirb^u~7wK4+xP$iiI6IlD|a&S)6o=kG;59N|>K1 zn(0mUqbG3YIY7dQd+*4~)`!S9m7H6HP6YcKHhBc#b%1L}VIisp%;TckEkcu0>lo@u995$<*Em;XNodjTiCdC%R+TX|_ZR#|1`RR|`^@Teh zl#w@8fI1FTx2Dy+{blUT{`^kY*V-AZUd?ZZqCS4gW(kY5?retkLbF=>p=59Nl|=sf zo1Pc|{{N4>5nt#627ylGF`3n>X%`w%bw-Y~zWM_{Si$dc82|=YhISal{N7OY?O`C4 zD|qb}6nLWJ`hUyL+E>-;ricg9J@ZNYP(x(Sct&OI$Y!QWr*=^VN;G3#i>^1n4e#Je zOVhbFbLpXVu*16enDM+ic;97@R~u&kh__kgP#!R`*rQEnA+_dLkNP~L`0alC|J;c; zeiK=s8;BsLE)KbG3BD&Br@(Ha@SBT&$?xX`=$;eeel=|R_dIr6-Ro?=HEjnsJ_b`1 zK6Yg^-6;^2aW!xeTK)A~3Rm|L^FCHB_I>jIju7ZGo&N_1*QHkxH2!!%@o4iZ?vntS;&zJdPe1dH#04YD93A44o-MpfD zP{rn_aq>U%RDvC2+bp;xPlsOzauIi3*Lf42`jVKKZCRuKdYhi>FDuL2l=v{$BCN#Q6796s%r-AG$Q^t(3c@ zD?w0UhYr11@feiyl9kY_@H8~|xlmO<8PfQmj1!$@WieW@VxR@Psxfe-v9WCi1+f>F4VL?0O~K7T?m4-u|pSkBpUJZZe*16_wAp zSYZ@;k`3;W3UHKUWc8QeI}0jH5Ly=cGWQPw(Kr2fm=-5L(d`lcXofy8tJY3@Tuadz zYWXR{mW7XT!RF#RVCe%}=tM*O6!AD3^(!8un~opNI%Uko7$5t@<8+?; zTxDys(MyyGsUjtSu9$+|_-t!U3fVb1dkK?l`17<+jfl=hrBHnDSV>^R1=TnQeyqbW z>ov#l%!1|S!1>8UUxIdhQq`_klcHVx0{?#>K3#$4GlXncwldt!g17TcvKq-jo_996 z>oA=tH9CqRl6Yw?Uc`am!V?lHJbizOJaVaScf1UP5e7Dbgabq=b!B~T&_F6?ooU>w%x0A zH~&MHJ=q`fCH{U<7MDXE4SD32cDZA)WJeWkllJ`UspWaS#eDe^kg^oU_A14UE9zG-a^g{xaXf$})Wik>gT zl#dkzGr(;h0JZDuFn(+k8wNq?PZ5grQ<+sM?wBGt@JnH6v0#or-5wBQWKU~(S_> zkE!tc*ZJ1Y&*p(xX84POb3cClRMd!^qJ#CAZfIepEj-<`VURS_yCz0(?*Ixcj4 z-!zV1_QZhpm=0<;*(nm+F>T=)o?ep@CK5I%g^VAA+RB25ab?7)A~z~egru=I1S|@v zH7tXV!0wmGS^qj#e+MY;C5eUjEAp$Y?LDkS^QPZ}8WN85?r$u<-Epi;yZ1|J2J`se z$D6DpH~2F=eI0B&=UFAUnJvZAmClJlK)sutJ?M>xpZiWV&0=G4MZP+x+p>EX=HbCz zxls%Mw?*u^;LbHWIWCyq+yi)`GmFn9J112CZda_u@YIP%i;srFg_paU02Ifij*7}l z&CF-(3|>*a|+vbNR`^RP=9G?ymEJ0Z~)d&c*UE$UMepZ zcITr{0WqhxkjUnM15js_gW=e3Uh|y6ZReaXHIz-=p`x5VvB&rH9y>Amv@^WmXFEw) zQXYrk3feir=a{jMQ+wDIkkFnZ$k{sJakHn*?u za%4b!00ev8NVLM1TY=cl?KB&55BY_MU-sg?c>=Dbz_W{(Z~c?HJi*XpYL)C6Bd8WH zt+v-#0&o~@t4qESi*)+eW%@VD0|o^yF)n0hME$UtXF$*Lvh}7sso{`|pn*JDIy5^Fm3s$5*zEE=?u5<=l8FJc3r%+H} zdfoNl2J0^~!-*mOL5o-x32|e0Im*E!yY7F7E5N)W3>+v_LBydlEx?4$RL5f2oYRD# zaR0wv(-p~wO0eLDl3K=%`{5+0Gd$ktO=W)gWlGZJ0`K z$_RNA=ckrfa;H0KA~dR^p�(p-{x$&=IACIfoAR!za)F-^da-t3#0Dycnp zwO~NVXwXCl;jE<}>%@xz|=8fIJAB?>+E{7)|4l${4ngA3G|=r z2Dyv;VVWSgZx9Wj>qUjleGl3Ei9K4>h!(lPS%8VOG>Xu0%6VDz^O=bjJmuP7>DeUv zrbI}MlHB^^d?{zv6d=@_ZD2lg1&G7UjnVN{1}9WkaM3H~btX0GtSzB+tZ^qRgWo4m z!GmimlG$=wgXCnr6j@m<1gAL46#T~5Bnm=2{^@>|t&`9mkEPddj zAvG~@Tv~TAm2i%VW}R-g(Z0)z-Y|szHr@rk>4MAyG*Ma*7Yh#H7(!-5>DZ@8r;_dx z{prSe<>~099F8vsYd2xff7uAS%7{S)f(|@me3t2$iy&NEc7OUEchp@9A|X;;IA>8!oX+y(BKJ$EzV* znR$z;!L$s7uy@{OT~nG#B!NRraT8(X##Ho!0r_o@gg0CA-9H^;-uE&?$2$nHv_00o z%cbuUc-tCx$Uh&EZ4Nf4Zgqv)Y6>usG3>GeQnxx_Z6+PcbX-+ysbt1hQ`K1LDpOE? zrAhIZhSN9yVIAOa22gn577tbc&i3|3V8NWy&!tw##`}9*x}gtI^h1DzZRA>UuaJG) zaZ7j)dq!O}{?#8Y7~7i6fHh4{`pL?>-18|p!S75Y#^DM>-S3)vuZG+Q7l@ek zQP~#cBpWgg#mApc_sPYjpw8odQuRokmTkzcNl`^CcKB7e&;zViV;{Y{o^Y$%7i0m# z62%#1Lq!RC?}lK>%mp}T!3Xv;L*0v*>USLm``N%>w>@fwC+#T&Tx2bN4w(20JB}oU zuSa6v^kXi0xPs?pbaOHnyiqq6By1EZY9OZ^^QA>{q-Hsd&m`pbQ%8121aWG-F5xf zlZ%;B{;C>X19|`^_?dVyCq>n+41w7|!tUS!{9rHlbhX=SZO5CQ^;!Du_E7*`GiR^Q w)2!4MKjfSAeNo!9>IaV6aUZ*?W>} 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

UmY2RIYtNPVDpE$%vda+HD#3m z&VuXJ{BK&Qe+rBa7eq}Q(bq|tn(RrJAk|ztj2(i{d>nmQnM?;HF2k&9sA6up5tmjl z7lySlzMbifH17-m-Lwa_F&e7nOH?ESi3#ckR3tsM+jsck3`oG!uMS}|eAwVXv>}qxwq?QY%QJ0}r@^;fhuUA9W z*BVl>TGo&N004@xSiwDUXUvp51sVmqO3m)=B55aPwf@0=e}cN+$-BdKxY`YrT_4)0 z_d10#i44Q*rFr8MC>*)v$EJvz``(pb{e&*6k+b zsMz%($|1+8hn8c2?P(l@;Rb&CsZeYoCI3?2!LqjbwPXW3z4G$Qfj=cT5Yb%vY0(AX oeb?AaKtwrnc|$|zzw9vfvn^aJJ!zd)XFXqqy0000001=f@-~a#s literal 0 HcmV?d00001 diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..d2c68d1 --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..c8524cd --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..accd6c9 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Notes-master + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..7c616ff --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,9 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml new file mode 100644 index 0000000..fe58f8f --- /dev/null +++ b/res/xml/preferences.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + diff --git a/res/xml/searchable.xml b/res/xml/searchable.xml new file mode 100644 index 0000000..bf74f14 --- /dev/null +++ b/res/xml/searchable.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/res/xml/widget_2x_info.xml b/res/xml/widget_2x_info.xml new file mode 100644 index 0000000..ac8b225 --- /dev/null +++ b/res/xml/widget_2x_info.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/res/xml/widget_4x_info.xml b/res/xml/widget_4x_info.xml new file mode 100644 index 0000000..cf79f9c --- /dev/null +++ b/res/xml/widget_4x_info.xml @@ -0,0 +1,23 @@ + + + + + + diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..bcbfede --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,23 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "Notes-master" +include(":app") diff --git a/src/net/micode/notes/data/Contact.java b/src/net/micode/notes/data/Contact.java new file mode 100644 index 0000000..d97ac5d --- /dev/null +++ b/src/net/micode/notes/data/Contact.java @@ -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 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(); + } + + 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; + } + } +} diff --git a/src/net/micode/notes/data/Notes.java b/src/net/micode/notes/data/Notes.java new file mode 100644 index 0000000..f240604 --- /dev/null +++ b/src/net/micode/notes/data/Notes.java @@ -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 + *

Type: INTEGER (long)

+ */ + public static final String ID = "_id"; + + /** + * The parent's id for note or folder + *

Type: INTEGER (long)

+ */ + public static final String PARENT_ID = "parent_id"; + + /** + * Created data for note or folder + *

Type: INTEGER (long)

+ */ + public static final String CREATED_DATE = "created_date"; + + /** + * Latest modified date + *

Type: INTEGER (long)

+ */ + public static final String MODIFIED_DATE = "modified_date"; + + + /** + * Alert date + *

Type: INTEGER (long)

+ */ + public static final String ALERTED_DATE = "alert_date"; + + /** + * Folder's name or text content of note + *

Type: TEXT

+ */ + public static final String SNIPPET = "snippet"; + + /** + * Note's widget id + *

Type: INTEGER (long)

+ */ + public static final String WIDGET_ID = "widget_id"; + + /** + * Note's widget type + *

Type: INTEGER (long)

+ */ + public static final String WIDGET_TYPE = "widget_type"; + + /** + * Note's background color's id + *

Type: INTEGER (long)

+ */ + 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 + *

Type: INTEGER

+ */ + public static final String HAS_ATTACHMENT = "has_attachment"; + + /** + * Folder's count of notes + *

Type: INTEGER (long)

+ */ + public static final String NOTES_COUNT = "notes_count"; + + /** + * The file type: folder or note + *

Type: INTEGER

+ */ + public static final String TYPE = "type"; + + /** + * The last sync id + *

Type: INTEGER (long)

+ */ + public static final String SYNC_ID = "sync_id"; + + /** + * Sign to indicate local modified or not + *

Type: INTEGER

+ */ + public static final String LOCAL_MODIFIED = "local_modified"; + + /** + * Original parent id before moving into temporary folder + *

Type : INTEGER

+ */ + public static final String ORIGIN_PARENT_ID = "origin_parent_id"; + + /** + * The gtask id + *

Type : TEXT

+ */ + public static final String GTASK_ID = "gtask_id"; + + /** + * The version code + *

Type : INTEGER (long)

+ */ + public static final String VERSION = "version"; + } + + public interface DataColumns { + /** + * The unique ID for a row + *

Type: INTEGER (long)

+ */ + public static final String ID = "_id"; + + /** + * The MIME type of the item represented by this row. + *

Type: Text

+ */ + public static final String MIME_TYPE = "mime_type"; + + /** + * The reference id to note that this data belongs to + *

Type: INTEGER (long)

+ */ + public static final String NOTE_ID = "note_id"; + + /** + * Created data for note or folder + *

Type: INTEGER (long)

+ */ + public static final String CREATED_DATE = "created_date"; + + /** + * Latest modified date + *

Type: INTEGER (long)

+ */ + public static final String MODIFIED_DATE = "modified_date"; + + /** + * Data's content + *

Type: TEXT

+ */ + public static final String CONTENT = "content"; + + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * integer data type + *

Type: INTEGER

+ */ + public static final String DATA1 = "data1"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * integer data type + *

Type: INTEGER

+ */ + public static final String DATA2 = "data2"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * TEXT data type + *

Type: TEXT

+ */ + public static final String DATA3 = "data3"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * TEXT data type + *

Type: TEXT

+ */ + public static final String DATA4 = "data4"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * TEXT data type + *

Type: TEXT

+ */ + public static final String DATA5 = "data5"; + } + + public static final class TextNote implements DataColumns { + /** + * Mode to indicate the text in check list mode or not + *

Type: Integer 1:check list mode 0: normal mode

+ */ + 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 + *

Type: INTEGER (long)

+ */ + public static final String CALL_DATE = DATA1; + + /** + * Phone number for this record + *

Type: TEXT

+ */ + 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"); + } +} diff --git a/src/net/micode/notes/data/NotesDatabaseHelper.java b/src/net/micode/notes/data/NotesDatabaseHelper.java new file mode 100644 index 0000000..ffe5d57 --- /dev/null +++ b/src/net/micode/notes/data/NotesDatabaseHelper.java @@ -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"); + } +} diff --git a/src/net/micode/notes/data/NotesProvider.java b/src/net/micode/notes/data/NotesProvider.java new file mode 100644 index 0000000..edb0a60 --- /dev/null +++ b/src/net/micode/notes/data/NotesProvider.java @@ -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; + } + +} diff --git a/src/net/micode/notes/gtask/data/MetaData.java b/src/net/micode/notes/gtask/data/MetaData.java new file mode 100644 index 0000000..3a2050b --- /dev/null +++ b/src/net/micode/notes/gtask/data/MetaData.java @@ -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"); + } + +} diff --git a/src/net/micode/notes/gtask/data/Node.java b/src/net/micode/notes/gtask/data/Node.java new file mode 100644 index 0000000..63950e0 --- /dev/null +++ b/src/net/micode/notes/gtask/data/Node.java @@ -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; + } + +} diff --git a/src/net/micode/notes/gtask/data/SqlData.java b/src/net/micode/notes/gtask/data/SqlData.java new file mode 100644 index 0000000..d3ec3be --- /dev/null +++ b/src/net/micode/notes/gtask/data/SqlData.java @@ -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; + } +} diff --git a/src/net/micode/notes/gtask/data/SqlNote.java b/src/net/micode/notes/gtask/data/SqlNote.java new file mode 100644 index 0000000..79a4095 --- /dev/null +++ b/src/net/micode/notes/gtask/data/SqlNote.java @@ -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 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(); + } + + public SqlNote(Context context, Cursor c) { + mContext = context; + mContentResolver = context.getContentResolver(); + mIsCreate = false; + loadFromCursor(c); + mDataList = new ArrayList(); + 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(); + 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; + } +} diff --git a/src/net/micode/notes/gtask/data/Task.java b/src/net/micode/notes/gtask/data/Task.java new file mode 100644 index 0000000..6a19454 --- /dev/null +++ b/src/net/micode/notes/gtask/data/Task.java @@ -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; + } + +} diff --git a/src/net/micode/notes/gtask/data/TaskList.java b/src/net/micode/notes/gtask/data/TaskList.java new file mode 100644 index 0000000..4ea21c5 --- /dev/null +++ b/src/net/micode/notes/gtask/data/TaskList.java @@ -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 mChildren; + + public TaskList() { + super(); + mChildren = new ArrayList(); + 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 getChildTaskList() { + return this.mChildren; + } + + public void setIndex(int index) { + this.mIndex = index; + } + + public int getIndex() { + return this.mIndex; + } +} diff --git a/src/net/micode/notes/gtask/exception/ActionFailureException.java b/src/net/micode/notes/gtask/exception/ActionFailureException.java new file mode 100644 index 0000000..15504be --- /dev/null +++ b/src/net/micode/notes/gtask/exception/ActionFailureException.java @@ -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); + } +} diff --git a/src/net/micode/notes/gtask/exception/NetworkFailureException.java b/src/net/micode/notes/gtask/exception/NetworkFailureException.java new file mode 100644 index 0000000..b08cfb1 --- /dev/null +++ b/src/net/micode/notes/gtask/exception/NetworkFailureException.java @@ -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); + } +} diff --git a/src/net/micode/notes/gtask/remote/GTaskASyncTask.java b/src/net/micode/notes/gtask/remote/GTaskASyncTask.java new file mode 100644 index 0000000..b3b61e7 --- /dev/null +++ b/src/net/micode/notes/gtask/remote/GTaskASyncTask.java @@ -0,0 +1,123 @@ + +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.remote; + +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; + +import net.micode.notes.R; +import net.micode.notes.ui.NotesListActivity; +import net.micode.notes.ui.NotesPreferenceActivity; + + +public class GTaskASyncTask extends AsyncTask { + + private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; + + public interface OnCompleteListener { + void onComplete(); + } + + private Context mContext; + + private NotificationManager mNotifiManager; + + private GTaskManager mTaskManager; + + private OnCompleteListener mOnCompleteListener; + + public GTaskASyncTask(Context context, OnCompleteListener listener) { + mContext = context; + mOnCompleteListener = listener; + mNotifiManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + mTaskManager = GTaskManager.getInstance(); + } + + public void cancelSync() { + mTaskManager.cancelSync(); + } + + public void publishProgess(String message) { + publishProgress(new String[] { + message + }); + } + + private void showNotification(int tickerId, String content) { + Notification notification = new Notification(R.drawable.notification, mContext + .getString(tickerId), System.currentTimeMillis()); + notification.defaults = Notification.DEFAULT_LIGHTS; + notification.flags = Notification.FLAG_AUTO_CANCEL; + PendingIntent pendingIntent; + if (tickerId != R.string.ticker_success) { + pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, + NotesPreferenceActivity.class), 0); + + } else { + pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, + NotesListActivity.class), 0); + } + notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, + pendingIntent); + mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); + } + + @Override + protected Integer doInBackground(Void... unused) { + publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity + .getSyncAccountName(mContext))); + return mTaskManager.sync(mContext, this); + } + + @Override + protected void onProgressUpdate(String... progress) { + showNotification(R.string.ticker_syncing, progress[0]); + if (mContext instanceof GTaskSyncService) { + ((GTaskSyncService) mContext).sendBroadcast(progress[0]); + } + } + + @Override + protected void onPostExecute(Integer result) { + if (result == GTaskManager.STATE_SUCCESS) { + showNotification(R.string.ticker_success, mContext.getString( + R.string.success_sync_account, mTaskManager.getSyncAccount())); + NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); + } else if (result == GTaskManager.STATE_NETWORK_ERROR) { + showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); + } else if (result == GTaskManager.STATE_INTERNAL_ERROR) { + showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); + } else if (result == GTaskManager.STATE_SYNC_CANCELLED) { + showNotification(R.string.ticker_cancel, mContext + .getString(R.string.error_sync_cancelled)); + } + if (mOnCompleteListener != null) { + new Thread(new Runnable() { + + public void run() { + mOnCompleteListener.onComplete(); + } + }).start(); + } + } +} diff --git a/src/net/micode/notes/gtask/remote/GTaskClient.java b/src/net/micode/notes/gtask/remote/GTaskClient.java new file mode 100644 index 0000000..c67dfdf --- /dev/null +++ b/src/net/micode/notes/gtask/remote/GTaskClient.java @@ -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 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 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 = ")}"; + 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 list = new LinkedList(); + list.add(new BasicNameValuePair("r", js.toString())); + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); + httpPost.setEntity(entity); + + // execute the post + HttpResponse response = mHttpClient.execute(httpPost); + String jsString = getResponseContent(response.getEntity()); + return new JSONObject(jsString); + + } catch (ClientProtocolException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new NetworkFailureException("postRequest failed"); + } catch (IOException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new NetworkFailureException("postRequest failed"); + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("unable to convert response content to jsonobject"); + } catch (Exception e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("error occurs when posting request"); + } + } + + public void createTask(Task task) throws NetworkFailureException { + 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 = ")}"; + 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; + } +} diff --git a/src/net/micode/notes/gtask/remote/GTaskManager.java b/src/net/micode/notes/gtask/remote/GTaskManager.java new file mode 100644 index 0000000..d2b4082 --- /dev/null +++ b/src/net/micode/notes/gtask/remote/GTaskManager.java @@ -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 mGTaskListHashMap; + + private HashMap mGTaskHashMap; + + private HashMap mMetaHashMap; + + private TaskList mMetaList; + + private HashSet mLocalDeleteIdMap; + + private HashMap mGidToNid; + + private HashMap mNidToGid; + + private GTaskManager() { + mSyncing = false; + mCancelled = false; + mGTaskListHashMap = new HashMap(); + mGTaskHashMap = new HashMap(); + mMetaHashMap = new HashMap(); + mMetaList = null; + mLocalDeleteIdMap = new HashSet(); + mGidToNid = new HashMap(); + mNidToGid = new HashMap(); + } + + 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> iter = mGTaskHashMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry 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> iter = mGTaskListHashMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry 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> iter = mGTaskListHashMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry 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; + } +} diff --git a/src/net/micode/notes/gtask/remote/GTaskSyncService.java b/src/net/micode/notes/gtask/remote/GTaskSyncService.java new file mode 100644 index 0000000..cca36f7 --- /dev/null +++ b/src/net/micode/notes/gtask/remote/GTaskSyncService.java @@ -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; + } +} diff --git a/src/net/micode/notes/model/Note.java b/src/net/micode/notes/model/Note.java new file mode 100644 index 0000000..6706cf6 --- /dev/null +++ b/src/net/micode/notes/model/Note.java @@ -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 operationList = new ArrayList(); + 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; + } + } +} diff --git a/src/net/micode/notes/model/WorkingNote.java b/src/net/micode/notes/model/WorkingNote.java new file mode 100644 index 0000000..be081e4 --- /dev/null +++ b/src/net/micode/notes/model/WorkingNote.java @@ -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); + } +} diff --git a/src/net/micode/notes/tool/BackupUtils.java b/src/net/micode/notes/tool/BackupUtils.java new file mode 100644 index 0000000..39f6ec4 --- /dev/null +++ b/src/net/micode/notes/tool/BackupUtils.java @@ -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; + } +} + + diff --git a/src/net/micode/notes/tool/DataUtils.java b/src/net/micode/notes/tool/DataUtils.java new file mode 100644 index 0000000..2a14982 --- /dev/null +++ b/src/net/micode/notes/tool/DataUtils.java @@ -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 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 operationList = new ArrayList(); + 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 ids, + long folderId) { + if (ids == null) { + Log.d(TAG, "the ids is null"); + return true; + } + + ArrayList operationList = new ArrayList(); + 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 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 set = null; + if (c != null) { + if (c.moveToFirst()) { + set = new HashSet(); + 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; + } +} diff --git a/src/net/micode/notes/tool/GTaskStringUtils.java b/src/net/micode/notes/tool/GTaskStringUtils.java new file mode 100644 index 0000000..666b729 --- /dev/null +++ b/src/net/micode/notes/tool/GTaskStringUtils.java @@ -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"; + +} diff --git a/src/net/micode/notes/tool/ResourceParser.java b/src/net/micode/notes/tool/ResourceParser.java new file mode 100644 index 0000000..1ad3ad6 --- /dev/null +++ b/src/net/micode/notes/tool/ResourceParser.java @@ -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; + } + } +} diff --git a/src/net/micode/notes/ui/AlarmAlertActivity.java b/src/net/micode/notes/ui/AlarmAlertActivity.java new file mode 100644 index 0000000..85723be --- /dev/null +++ b/src/net/micode/notes/ui/AlarmAlertActivity.java @@ -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; + } + } +} diff --git a/src/net/micode/notes/ui/AlarmInitReceiver.java b/src/net/micode/notes/ui/AlarmInitReceiver.java new file mode 100644 index 0000000..f221202 --- /dev/null +++ b/src/net/micode/notes/ui/AlarmInitReceiver.java @@ -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(); + } + } +} diff --git a/src/net/micode/notes/ui/AlarmReceiver.java b/src/net/micode/notes/ui/AlarmReceiver.java new file mode 100644 index 0000000..54e503b --- /dev/null +++ b/src/net/micode/notes/ui/AlarmReceiver.java @@ -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); + } +} diff --git a/src/net/micode/notes/ui/DateTimePicker.java b/src/net/micode/notes/ui/DateTimePicker.java new file mode 100644 index 0000000..496b0cd --- /dev/null +++ b/src/net/micode/notes/ui/DateTimePicker.java @@ -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()); + } + } +} diff --git a/src/net/micode/notes/ui/DateTimePickerDialog.java b/src/net/micode/notes/ui/DateTimePickerDialog.java new file mode 100644 index 0000000..2c47ba4 --- /dev/null +++ b/src/net/micode/notes/ui/DateTimePickerDialog.java @@ -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()); + } + } + +} \ No newline at end of file diff --git a/src/net/micode/notes/ui/DropdownMenu.java b/src/net/micode/notes/ui/DropdownMenu.java new file mode 100644 index 0000000..613dc74 --- /dev/null +++ b/src/net/micode/notes/ui/DropdownMenu.java @@ -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); + } +} diff --git a/src/net/micode/notes/ui/FoldersListAdapter.java b/src/net/micode/notes/ui/FoldersListAdapter.java new file mode 100644 index 0000000..96b77da --- /dev/null +++ b/src/net/micode/notes/ui/FoldersListAdapter.java @@ -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); + } + } + +} diff --git a/src/net/micode/notes/ui/NoteEditActivity.java b/src/net/micode/notes/ui/NoteEditActivity.java new file mode 100644 index 0000000..96a9ff8 --- /dev/null +++ b/src/net/micode/notes/ui/NoteEditActivity.java @@ -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 sBgSelectorBtnsMap = new HashMap(); + 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 sBgSelectorSelectionMap = new HashMap(); + 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 sFontSizeBtnsMap = new HashMap(); + 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 sFontSelectorSelectionMap = new HashMap(); + 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 ids = new HashSet(); + 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(); + } +} diff --git a/src/net/micode/notes/ui/NoteEditText.java b/src/net/micode/notes/ui/NoteEditText.java new file mode 100644 index 0000000..2afe2a8 --- /dev/null +++ b/src/net/micode/notes/ui/NoteEditText.java @@ -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 sSchemaActionResMap = new HashMap(); + 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); + } +} diff --git a/src/net/micode/notes/ui/NoteItemData.java b/src/net/micode/notes/ui/NoteItemData.java new file mode 100644 index 0000000..0f5a878 --- /dev/null +++ b/src/net/micode/notes/ui/NoteItemData.java @@ -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); + } +} diff --git a/src/net/micode/notes/ui/NotesListActivity.java b/src/net/micode/notes/ui/NotesListActivity.java new file mode 100644 index 0000000..e843aec --- /dev/null +++ b/src/net/micode/notes/ui/NotesListActivity.java @@ -0,0 +1,954 @@ +/* + * 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.app.Dialog; +import android.appwidget.AppWidgetManager; +import android.content.AsyncQueryHandler; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.ActionMode; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Display; +import android.view.HapticFeedbackConstants; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnCreateContextMenuListener; +import android.view.View.OnTouchListener; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.PopupMenu; +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; +import net.micode.notes.model.WorkingNote; +import net.micode.notes.tool.BackupUtils; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; +import net.micode.notes.widget.NoteWidgetProvider_2x; +import net.micode.notes.widget.NoteWidgetProvider_4x; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashSet; + +public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { + private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; + + private static final int FOLDER_LIST_QUERY_TOKEN = 1; + + private static final int MENU_FOLDER_DELETE = 0; + + private static final int MENU_FOLDER_VIEW = 1; + + private static final int MENU_FOLDER_CHANGE_NAME = 2; + + private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; + + private enum ListEditState { + NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER + }; + + private ListEditState mState; + + private BackgroundQueryHandler mBackgroundQueryHandler; + + private NotesListAdapter mNotesListAdapter; + + private ListView mNotesListView; + + private Button mAddNewNote; + + private boolean mDispatch; + + private int mOriginY; + + private int mDispatchY; + + private TextView mTitleBar; + + private long mCurrentFolderId; + + private ContentResolver mContentResolver; + + private ModeCallback mModeCallBack; + + private static final String TAG = "NotesListActivity"; + + public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; + + private NoteItemData mFocusNoteDataItem; + + private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; + + private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" + + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" + + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + + NoteColumns.NOTES_COUNT + ">0)"; + + private final static int REQUEST_CODE_OPEN_NODE = 102; + private final static int REQUEST_CODE_NEW_NODE = 103; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.note_list); + initResources(); + + /** + * Insert an introduction when user firstly use this application + */ + setAppInfoFromRawRes(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == RESULT_OK + && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { + mNotesListAdapter.changeCursor(null); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } + + private void setAppInfoFromRawRes() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { + StringBuilder sb = new StringBuilder(); + InputStream in = null; + try { + in = getResources().openRawResource(R.raw.introduction); + if (in != null) { + InputStreamReader isr = new InputStreamReader(in); + BufferedReader br = new BufferedReader(isr); + char [] buf = new char[1024]; + int len = 0; + while ((len = br.read(buf)) > 0) { + sb.append(buf, 0, len); + } + } else { + Log.e(TAG, "Read introduction file error"); + return; + } + } catch (IOException e) { + e.printStackTrace(); + return; + } finally { + if(in != null) { + try { + in.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, + AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, + ResourceParser.RED); + note.setWorkingText(sb.toString()); + if (note.saveNote()) { + sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); + } else { + Log.e(TAG, "Save introduction note error"); + return; + } + } + } + + @Override + protected void onStart() { + super.onStart(); + startAsyncNotesListQuery(); + } + + private void initResources() { + mContentResolver = this.getContentResolver(); + mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + mNotesListView = (ListView) findViewById(R.id.notes_list); + mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), + null, false); + mNotesListView.setOnItemClickListener(new OnListItemClickListener()); + mNotesListView.setOnItemLongClickListener(this); + mNotesListAdapter = new NotesListAdapter(this); + mNotesListView.setAdapter(mNotesListAdapter); + mAddNewNote = (Button) findViewById(R.id.btn_new_note); + mAddNewNote.setOnClickListener(this); + mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); + mDispatch = false; + mDispatchY = 0; + mOriginY = 0; + mTitleBar = (TextView) findViewById(R.id.tv_title_bar); + mState = ListEditState.NOTE_LIST; + mModeCallBack = new ModeCallback(); + } + + private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { + private DropdownMenu mDropDownMenu; + private ActionMode mActionMode; + private MenuItem mMoveMenu; + + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + getMenuInflater().inflate(R.menu.note_list_options, menu); + menu.findItem(R.id.delete).setOnMenuItemClickListener(this); + mMoveMenu = menu.findItem(R.id.move); + if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER + || DataUtils.getUserFolderCount(mContentResolver) == 0) { + mMoveMenu.setVisible(false); + } else { + mMoveMenu.setVisible(true); + mMoveMenu.setOnMenuItemClickListener(this); + } + mActionMode = mode; + mNotesListAdapter.setChoiceMode(true); + mNotesListView.setLongClickable(false); + mAddNewNote.setVisibility(View.GONE); + + View customView = LayoutInflater.from(NotesListActivity.this).inflate( + R.layout.note_list_dropdown_menu, null); + mode.setCustomView(customView); + mDropDownMenu = new DropdownMenu(NotesListActivity.this, + (Button) customView.findViewById(R.id.selection_menu), + R.menu.note_list_dropdown); + mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ + public boolean onMenuItemClick(MenuItem item) { + mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); + updateMenu(); + return true; + } + + }); + return true; + } + + private void updateMenu() { + int selectedCount = mNotesListAdapter.getSelectedCount(); + // Update dropdown menu + String format = getResources().getString(R.string.menu_select_title, selectedCount); + mDropDownMenu.setTitle(format); + MenuItem item = mDropDownMenu.findItem(R.id.action_select_all); + if (item != null) { + if (mNotesListAdapter.isAllSelected()) { + item.setChecked(true); + item.setTitle(R.string.menu_deselect_all); + } else { + item.setChecked(false); + item.setTitle(R.string.menu_select_all); + } + } + } + + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // TODO Auto-generated method stub + return false; + } + + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + // TODO Auto-generated method stub + return false; + } + + public void onDestroyActionMode(ActionMode mode) { + mNotesListAdapter.setChoiceMode(false); + mNotesListView.setLongClickable(true); + mAddNewNote.setVisibility(View.VISIBLE); + } + + public void finishActionMode() { + mActionMode.finish(); + } + + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, + boolean checked) { + mNotesListAdapter.setCheckedItem(position, checked); + updateMenu(); + } + + public boolean onMenuItemClick(MenuItem item) { + if (mNotesListAdapter.getSelectedCount() == 0) { + Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), + Toast.LENGTH_SHORT).show(); + return true; + } + + switch (item.getItemId()) { + case R.id.delete: + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.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_notes, + mNotesListAdapter.getSelectedCount())); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + batchDelete(); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + break; + case R.id.move: + startQueryDestinationFolders(); + break; + default: + return false; + } + return true; + } + } + + private class NewNoteOnTouchListener implements OnTouchListener { + + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: { + Display display = getWindowManager().getDefaultDisplay(); + int screenHeight = display.getHeight(); + int newNoteViewHeight = mAddNewNote.getHeight(); + int start = screenHeight - newNoteViewHeight; + int eventY = start + (int) event.getY(); + /** + * Minus TitleBar's height + */ + if (mState == ListEditState.SUB_FOLDER) { + eventY -= mTitleBar.getHeight(); + start -= mTitleBar.getHeight(); + } + /** + * HACKME:When click the transparent part of "New Note" button, dispatch + * the event to the list view behind this button. The transparent part of + * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel) + * and the line top of the button. The coordinate based on left of the "New + * Note" button. The 94 represents maximum height of the transparent part. + * Notice that, if the background of the button changes, the formula should + * also change. This is very bad, just for the UI designer's strong requirement. + */ + if (event.getY() < (event.getX() * (-0.12) + 94)) { + View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1 + - mNotesListView.getFooterViewsCount()); + if (view != null && view.getBottom() > start + && (view.getTop() < (start + 94))) { + mOriginY = (int) event.getY(); + mDispatchY = eventY; + event.setLocation(event.getX(), mDispatchY); + mDispatch = true; + return mNotesListView.dispatchTouchEvent(event); + } + } + break; + } + case MotionEvent.ACTION_MOVE: { + if (mDispatch) { + mDispatchY += (int) event.getY() - mOriginY; + event.setLocation(event.getX(), mDispatchY); + return mNotesListView.dispatchTouchEvent(event); + } + break; + } + default: { + if (mDispatch) { + event.setLocation(event.getX(), mDispatchY); + mDispatch = false; + return mNotesListView.dispatchTouchEvent(event); + } + break; + } + } + return false; + } + + }; + + private void startAsyncNotesListQuery() { + String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION + : NORMAL_SELECTION; + mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, + Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] { + String.valueOf(mCurrentFolderId) + }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); + } + + private final class BackgroundQueryHandler extends AsyncQueryHandler { + public BackgroundQueryHandler(ContentResolver contentResolver) { + super(contentResolver); + } + + @Override + protected void onQueryComplete(int token, Object cookie, Cursor cursor) { + switch (token) { + case FOLDER_NOTE_LIST_QUERY_TOKEN: + mNotesListAdapter.changeCursor(cursor); + break; + case FOLDER_LIST_QUERY_TOKEN: + if (cursor != null && cursor.getCount() > 0) { + showFolderListMenu(cursor); + } else { + Log.e(TAG, "Query folder failed"); + } + break; + default: + return; + } + } + } + + private void showFolderListMenu(Cursor cursor) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(R.string.menu_title_select_folder); + final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor); + builder.setAdapter(adapter, new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int which) { + DataUtils.batchMoveToFolder(mContentResolver, + mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which)); + Toast.makeText( + NotesListActivity.this, + getString(R.string.format_move_notes_to_folder, + mNotesListAdapter.getSelectedCount(), + adapter.getFolderName(NotesListActivity.this, which)), + Toast.LENGTH_SHORT).show(); + mModeCallBack.finishActionMode(); + } + }); + builder.show(); + } + + private void createNewNote() { + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); + this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); + } + + private void batchDelete() { + new AsyncTask>() { + protected HashSet doInBackground(Void... unused) { + HashSet widgets = mNotesListAdapter.getSelectedWidget(); + if (!isSyncMode()) { + // if not synced, delete notes directly + if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter + .getSelectedItemIds())) { + } else { + Log.e(TAG, "Delete notes error, should not happens"); + } + } else { + // in sync mode, we'll move the deleted note into the trash + // folder + if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter + .getSelectedItemIds(), Notes.ID_TRASH_FOLER)) { + Log.e(TAG, "Move notes to trash folder error, should not happens"); + } + } + return widgets; + } + + @Override + protected void onPostExecute(HashSet widgets) { + if (widgets != null) { + for (AppWidgetAttribute widget : widgets) { + if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { + updateWidget(widget.widgetId, widget.widgetType); + } + } + } + mModeCallBack.finishActionMode(); + } + }.execute(); + } + + private void deleteFolder(long folderId) { + if (folderId == Notes.ID_ROOT_FOLDER) { + Log.e(TAG, "Wrong folder id, should not happen " + folderId); + return; + } + + HashSet ids = new HashSet(); + ids.add(folderId); + HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver, + folderId); + if (!isSyncMode()) { + // if not synced, delete folder directly + DataUtils.batchDeleteNotes(mContentResolver, ids); + } else { + // in sync mode, we'll move the deleted folder into the trash folder + DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER); + } + if (widgets != null) { + for (AppWidgetAttribute widget : widgets) { + if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { + updateWidget(widget.widgetId, widget.widgetType); + } + } + } + } + + private void openNode(NoteItemData data) { + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, data.getId()); + this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); + } + + private void openFolder(NoteItemData data) { + mCurrentFolderId = data.getId(); + startAsyncNotesListQuery(); + if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + mState = ListEditState.CALL_RECORD_FOLDER; + mAddNewNote.setVisibility(View.GONE); + } else { + mState = ListEditState.SUB_FOLDER; + } + if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + mTitleBar.setText(R.string.call_record_folder_name); + } else { + mTitleBar.setText(data.getSnippet()); + } + mTitleBar.setVisibility(View.VISIBLE); + } + + public void onClick(View v) { + switch (v.getId()) { + case R.id.btn_new_note: + createNewNote(); + break; + default: + break; + } + } + + private void showSoftInput() { + InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + if (inputMethodManager != null) { + inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + } + } + + private void hideSoftInput(View view) { + InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + private void showCreateOrModifyFolderDialog(final boolean create) { + final AlertDialog.Builder builder = new AlertDialog.Builder(this); + View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); + final EditText etName = (EditText) view.findViewById(R.id.et_foler_name); + showSoftInput(); + if (!create) { + if (mFocusNoteDataItem != null) { + etName.setText(mFocusNoteDataItem.getSnippet()); + builder.setTitle(getString(R.string.menu_folder_change_name)); + } else { + Log.e(TAG, "The long click data item is null"); + return; + } + } else { + etName.setText(""); + builder.setTitle(this.getString(R.string.menu_create_folder)); + } + + builder.setPositiveButton(android.R.string.ok, null); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + hideSoftInput(etName); + } + }); + + final Dialog dialog = builder.setView(view).show(); + final Button positive = (Button)dialog.findViewById(android.R.id.button1); + positive.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + hideSoftInput(etName); + String name = etName.getText().toString(); + if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { + Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name), + Toast.LENGTH_LONG).show(); + etName.setSelection(0, etName.length()); + return; + } + if (!create) { + if (!TextUtils.isEmpty(name)) { + ContentValues values = new ContentValues(); + values.put(NoteColumns.SNIPPET, name); + values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); + values.put(NoteColumns.LOCAL_MODIFIED, 1); + mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID + + "=?", new String[] { + String.valueOf(mFocusNoteDataItem.getId()) + }); + } + } else if (!TextUtils.isEmpty(name)) { + ContentValues values = new ContentValues(); + values.put(NoteColumns.SNIPPET, name); + values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); + mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); + } + dialog.dismiss(); + } + }); + + if (TextUtils.isEmpty(etName.getText())) { + positive.setEnabled(false); + } + /** + * When the name edit text is null, disable the positive button + */ + etName.addTextChangedListener(new TextWatcher() { + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // TODO Auto-generated method stub + + } + + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (TextUtils.isEmpty(etName.getText())) { + positive.setEnabled(false); + } else { + positive.setEnabled(true); + } + } + + public void afterTextChanged(Editable s) { + // TODO Auto-generated method stub + + } + }); + } + + @Override + public void onBackPressed() { + switch (mState) { + case SUB_FOLDER: + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + mState = ListEditState.NOTE_LIST; + startAsyncNotesListQuery(); + mTitleBar.setVisibility(View.GONE); + break; + case CALL_RECORD_FOLDER: + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + mState = ListEditState.NOTE_LIST; + mAddNewNote.setVisibility(View.VISIBLE); + mTitleBar.setVisibility(View.GONE); + startAsyncNotesListQuery(); + break; + case NOTE_LIST: + super.onBackPressed(); + break; + default: + break; + } + } + + private void updateWidget(int appWidgetId, int appWidgetType) { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + if (appWidgetType == Notes.TYPE_WIDGET_2X) { + intent.setClass(this, NoteWidgetProvider_2x.class); + } else if (appWidgetType == 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[] { + appWidgetId + }); + + sendBroadcast(intent); + setResult(RESULT_OK, intent); + } + + private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { + if (mFocusNoteDataItem != null) { + menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); + menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); + menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); + menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); + } + } + }; + + @Override + public void onContextMenuClosed(Menu menu) { + if (mNotesListView != null) { + mNotesListView.setOnCreateContextMenuListener(null); + } + super.onContextMenuClosed(menu); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + if (mFocusNoteDataItem == null) { + Log.e(TAG, "The long click data item is null"); + return false; + } + switch (item.getItemId()) { + case MENU_FOLDER_VIEW: + openFolder(mFocusNoteDataItem); + break; + case MENU_FOLDER_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_folder)); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + deleteFolder(mFocusNoteDataItem.getId()); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + break; + case MENU_FOLDER_CHANGE_NAME: + showCreateOrModifyFolderDialog(false); + break; + default: + break; + } + + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + menu.clear(); + if (mState == ListEditState.NOTE_LIST) { + getMenuInflater().inflate(R.menu.note_list, menu); + // set sync or sync_cancel + menu.findItem(R.id.menu_sync).setTitle( + GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync); + } else if (mState == ListEditState.SUB_FOLDER) { + getMenuInflater().inflate(R.menu.sub_folder, menu); + } else if (mState == ListEditState.CALL_RECORD_FOLDER) { + getMenuInflater().inflate(R.menu.call_record_folder, menu); + } else { + Log.e(TAG, "Wrong state:" + mState); + } + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_new_folder: { + showCreateOrModifyFolderDialog(true); + break; + } + case R.id.menu_export_text: { + exportNoteToText(); + break; + } + case R.id.menu_sync: { + if (isSyncMode()) { + if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) { + GTaskSyncService.startSync(this); + } else { + GTaskSyncService.cancelSync(this); + } + } else { + startPreferenceActivity(); + } + break; + } + case R.id.menu_setting: { + startPreferenceActivity(); + break; + } + case R.id.menu_new_note: { + createNewNote(); + break; + } + case R.id.menu_search: + onSearchRequested(); + break; + default: + break; + } + return true; + } + + @Override + public boolean onSearchRequested() { + startSearch(null, false, null /* appData */, false); + return true; + } + + private void exportNoteToText() { + final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); + new AsyncTask() { + + @Override + protected Integer doInBackground(Void... unused) { + return backup.exportToText(); + } + + @Override + protected void onPostExecute(Integer result) { + if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(NotesListActivity.this + .getString(R.string.failed_sdcard_export)); + builder.setMessage(NotesListActivity.this + .getString(R.string.error_sdcard_unmounted)); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); + } else if (result == BackupUtils.STATE_SUCCESS) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(NotesListActivity.this + .getString(R.string.success_sdcard_export)); + builder.setMessage(NotesListActivity.this.getString( + R.string.format_exported_file_location, backup + .getExportedTextFileName(), backup.getExportedTextFileDir())); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); + } else if (result == BackupUtils.STATE_SYSTEM_ERROR) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(NotesListActivity.this + .getString(R.string.failed_sdcard_export)); + builder.setMessage(NotesListActivity.this + .getString(R.string.error_sdcard_export)); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); + } + } + + }.execute(); + } + + private boolean isSyncMode() { + return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + } + + private void startPreferenceActivity() { + Activity from = getParent() != null ? getParent() : this; + Intent intent = new Intent(from, NotesPreferenceActivity.class); + from.startActivityIfNeeded(intent, -1); + } + + private class OnListItemClickListener implements OnItemClickListener { + + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (view instanceof NotesListItem) { + NoteItemData item = ((NotesListItem) view).getItemData(); + if (mNotesListAdapter.isInChoiceMode()) { + if (item.getType() == Notes.TYPE_NOTE) { + position = position - mNotesListView.getHeaderViewsCount(); + mModeCallBack.onItemCheckedStateChanged(null, position, id, + !mNotesListAdapter.isSelectedItem(position)); + } + return; + } + + switch (mState) { + case NOTE_LIST: + if (item.getType() == Notes.TYPE_FOLDER + || item.getType() == Notes.TYPE_SYSTEM) { + openFolder(item); + } else if (item.getType() == Notes.TYPE_NOTE) { + openNode(item); + } else { + Log.e(TAG, "Wrong note type in NOTE_LIST"); + } + break; + case SUB_FOLDER: + case CALL_RECORD_FOLDER: + if (item.getType() == Notes.TYPE_NOTE) { + openNode(item); + } else { + Log.e(TAG, "Wrong note type in SUB_FOLDER"); + } + break; + default: + break; + } + } + } + + } + + private void startQueryDestinationFolders() { + String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; + selection = (mState == ListEditState.NOTE_LIST) ? selection: + "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; + + mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, + null, + Notes.CONTENT_NOTE_URI, + FoldersListAdapter.PROJECTION, + selection, + new String[] { + String.valueOf(Notes.TYPE_FOLDER), + String.valueOf(Notes.ID_TRASH_FOLER), + String.valueOf(mCurrentFolderId) + }, + NoteColumns.MODIFIED_DATE + " DESC"); + } + + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + if (view instanceof NotesListItem) { + mFocusNoteDataItem = ((NotesListItem) view).getItemData(); + if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { + if (mNotesListView.startActionMode(mModeCallBack) != null) { + mModeCallBack.onItemCheckedStateChanged(null, position, id, true); + mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } else { + Log.e(TAG, "startActionMode fails"); + } + } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { + mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); + } + } + return false; + } +} diff --git a/src/net/micode/notes/ui/NotesListAdapter.java b/src/net/micode/notes/ui/NotesListAdapter.java new file mode 100644 index 0000000..51c9cb9 --- /dev/null +++ b/src/net/micode/notes/ui/NotesListAdapter.java @@ -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 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(); + 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 getSelectedItemIds() { + HashSet itemSet = new HashSet(); + 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 getSelectedWidget() { + HashSet itemSet = new HashSet(); + 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 values = mSelectedIndex.values(); + if (null == values) { + return 0; + } + Iterator 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; + } + } + } +} diff --git a/src/net/micode/notes/ui/NotesListItem.java b/src/net/micode/notes/ui/NotesListItem.java new file mode 100644 index 0000000..1221e80 --- /dev/null +++ b/src/net/micode/notes/ui/NotesListItem.java @@ -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; + } +} diff --git a/src/net/micode/notes/ui/NotesPreferenceActivity.java b/src/net/micode/notes/ui/NotesPreferenceActivity.java new file mode 100644 index 0000000..07c5f7e --- /dev/null +++ b/src/net/micode/notes/ui/NotesPreferenceActivity.java @@ -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; + } + } +} diff --git a/src/net/micode/notes/widget/NoteWidgetProvider.java b/src/net/micode/notes/widget/NoteWidgetProvider.java new file mode 100644 index 0000000..ec6f819 --- /dev/null +++ b/src/net/micode/notes/widget/NoteWidgetProvider.java @@ -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(); +} diff --git a/src/net/micode/notes/widget/NoteWidgetProvider_2x.java b/src/net/micode/notes/widget/NoteWidgetProvider_2x.java new file mode 100644 index 0000000..adcb2f7 --- /dev/null +++ b/src/net/micode/notes/widget/NoteWidgetProvider_2x.java @@ -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; + } +} diff --git a/src/net/micode/notes/widget/NoteWidgetProvider_4x.java b/src/net/micode/notes/widget/NoteWidgetProvider_4x.java new file mode 100644 index 0000000..c12a02e --- /dev/null +++ b/src/net/micode/notes/widget/NoteWidgetProvider_4x.java @@ -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; + } +}

656JufWLoz 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 literal 0 HcmV?d00001 diff --git a/.gradle/8.11.1/fileChanges/last-build.bin b/.gradle/8.11.1/fileChanges/last-build.bin new file mode 100644 index 0000000000000000000000000000000000000000..f76dd238ade08917e6712764a16a22005a50573d GIT binary patch literal 1 IcmZPo000310RR91 literal 0 HcmV?d00001 diff --git a/.gradle/8.11.1/fileHashes/fileHashes.bin b/.gradle/8.11.1/fileHashes/fileHashes.bin new file mode 100644 index 0000000000000000000000000000000000000000..1ee7d26f65fb32c2f4bacbeb162da096c06f2624 GIT binary patch literal 73915 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 Yn+%KSAl5nNM2g4{Whng74y;!H2ZUp>$p8QV literal 0 HcmV?d00001 diff --git a/.gradle/8.11.1/fileHashes/fileHashes.lock b/.gradle/8.11.1/fileHashes/fileHashes.lock new file mode 100644 index 0000000000000000000000000000000000000000..1710d53d156ae6613b8174792f930af09e66e87e GIT binary patch literal 17 VcmZSnr?a?sn~u*01~6du1OPU!1c(3t literal 0 HcmV?d00001 diff --git a/.gradle/8.11.1/fileHashes/resourceHashesCache.bin b/.gradle/8.11.1/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000000000000000000000000000000000000..4ebff2fa9ce5b6db080760fb02b7169b10a8ff29 GIT binary patch literal 18939 zcmeI%T}V?=00;1ET9&oiEyOQego#1(tBf!ev<-35gF4p)qYt;Xpi72XGaWq?wIM{q z6e~oc1TpfZmMyo|2hvhnU~ZL2_0TLfaU^J8bFMq?JxJW1dx-ymbMN7Q|D1cz@Ab+l zDp?%i2emX3mzG#T00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##An?BksMv!faED2j zE{iVK87L}m4Z9Fonu^;KFW24T9xFuT|AD1rwp1MQb#r;e1(G`w;&&?NS0CZ~6C@v= z-qGm$Poa6Hmk}npI<{hV8HZE73Bzdm2p`gbgIm72wBrn==$hEiMr{nkOBl+%6m4U3H zM-jgNfaHwmXOgu1<{;nKkeu8pDn`LQ!xaJ$fB*y_009U<00Izz00bZa0SG_<0uX=z z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bcLZv?ipuSsDi9i8H@%xIV1 zP`kX=nWdTOdhK9C$)62VzKzMDdxj=ymGR4XuU**4&WwFGXZ~0=r4Gu2_CSrW@l@$j zy)mi_x0&}%+_sLT2^$~6+V3mYiqv%fc&TzRb>S}!*=$>3e%e+4OWV}IC1+UJh>OV% zb%rb@vHtdhRd?eXg^ep)MHj=&{+R)@``VVZi0v7qVbys+V# UYpx6o=R7mXXx(XBMpLx$8~!saJpcdz literal 0 HcmV?d00001 diff --git a/.gradle/8.11.1/gc.properties b/.gradle/8.11.1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock new file mode 100644 index 0000000000000000000000000000000000000000..c12890cedcbd20ffa82921ef8c15d2d0ae476481 GIT binary patch literal 17 VcmZRc*Xd#C^6udl1~B0G1pqcG1#JKT literal 0 HcmV?d00001 diff --git a/.gradle/buildOutputCleanup/cache.properties b/.gradle/buildOutputCleanup/cache.properties new file mode 100644 index 0000000..3ba6ef7 --- /dev/null +++ b/.gradle/buildOutputCleanup/cache.properties @@ -0,0 +1,2 @@ +#Thu May 15 08:55:58 CST 2025 +gradle.version=8.11.1 diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin new file mode 100644 index 0000000000000000000000000000000000000000..869d62a799eab26bd551434f5e6fb6052c101ae5 GIT binary patch literal 24257 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 literal 0 HcmV?d00001 diff --git a/.gradle/config.properties b/.gradle/config.properties new file mode 100644 index 0000000..c22d4fb --- /dev/null +++ b/.gradle/config.properties @@ -0,0 +1,2 @@ +#Thu Apr 17 10:09:14 CST 2025 +java.home=D\:\\Android Studio\\jbr diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe new file mode 100644 index 0000000000000000000000000000000000000000..a1fe44c6225de44f6820a86f0459d0044966923c GIT binary patch literal 8 PcmZQzV4Pm~LuwrW2#*5F literal 0 HcmV?d00001 diff --git a/.gradle/vcs-1/gc.properties b/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..c6c2ddc --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..639c779 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..74dd639 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..b90d520 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/AndroidManifest.xml b/AndroidManifest.xml new file mode 100644 index 0000000..e5c7d47 --- /dev/null +++ b/AndroidManifest.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..9a54521 --- /dev/null +++ b/NOTICE @@ -0,0 +1,190 @@ + + 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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/README b/README new file mode 100644 index 0000000..fc0d824 --- /dev/null +++ b/README @@ -0,0 +1,23 @@ +[中文] + +1. MiCode便签是小米便签的社区开源版,由MIUI团队(www.miui.com) 发起并贡献第一批代码,遵循NOTICE文件所描述的开源协议, + 今后为MiCode社区(www.micode.net) 拥有,并由社区发布和维护。 + +2. Bug反馈和跟踪,请访问Github, + https://github.com/MiCode/Notes/issues?sort=created&direction=desc&state=open + +3. 功能建议和综合讨论,请访问MiCode, + http://micode.net/forum.php?mod=forumdisplay&fid=38 + + +[English] + +1. MiCode Notes is open source edition of XM notepad, it's first initiated and sponsored by MIUI team (www.miui.com). + It's opened under license described by NOTICE file. It's owned by the MiCode community (www.micode.net). In future, + the MiCode community will release and maintain this project. + +2. Regarding issue tracking, please visit Github, + https://github.com/MiCode/Notes/issues?sort=created&direction=desc&state=open + +3. Regarding feature request and general discussion, please visit Micode forum, + http://micode.net/forum.php?mod=forumdisplay&fid=38 diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..64a0472 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,43 @@ +plugins { + alias(libs.plugins.android.application) +} + +android { + namespace = "com.example.notes_master" + compileSdk = 35 + + defaultConfig { + applicationId = "com.example.notes_master" + minSdk = 24 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +dependencies { + + implementation(libs.appcompat) + implementation(libs.material) + implementation(libs.activity) + implementation(libs.constraintlayout) + testImplementation(libs.junit) + androidTestImplementation(libs.ext.junit) + androidTestImplementation(libs.espresso.core) +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/notes_master/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/notes_master/ExampleInstrumentedTest.java new file mode 100644 index 0000000..2b9eb64 --- /dev/null +++ b/app/src/androidTest/java/com/example/notes_master/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.example.notes_master; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.example.notes_master", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e061ed5 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/notes_master/MainActivity.java b/app/src/main/java/com/example/notes_master/MainActivity.java new file mode 100644 index 0000000..f673a63 --- /dev/null +++ b/app/src/main/java/com/example/notes_master/MainActivity.java @@ -0,0 +1,24 @@ +package com.example.notes_master; + +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; + }); + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..86a5d97 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,19 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c209e78ecd372343283f4157dcfd918ec5165bb3 GIT binary patch literal 1404 zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN zXl<@<#Cme5f5yr2h%@8TWh?)bSK`O z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF! z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!jH(_Z*nzTK4 zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG z?Fe{#9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~ zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG8I5h*uf{rn48Z!_@ z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqroa($ne7EUK;#3VYkXaew%Kh^3OrMhtjYN?XEoY`tRPQsAkH-DSL^QqyN0>^ zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu z4)P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6 z>%NbT+5*@L5(I9j@06@(!{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W Kmw_-CKmY()leWbG literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..b2dfe3d1ba5cf3ee31b3ecc1ced89044a1f3b7a9 GIT binary patch literal 2898 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..4f0f1d64e58ba64d180ce43ee13bf9a17835fbca GIT binary patch literal 982 zcmV;{11bDcNk&G_0{{S5MM6+kP&il$0000G0000l001ul06|PpNU8t;00Dqo+t#w^ z^1csucXz7-Qrhzl9HuHB%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}