From 37aee3a2828bb8bc152548adceee6772a159a87e Mon Sep 17 00:00:00 2001 From: xuningshuo <2105361900@qq.com> Date: Tue, 27 Jan 2026 01:17:07 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/.vs/ProjectSettings.json | 3 - src/.vs/VSWorkspaceState.json | 7 - src/.vs/slnx.sqlite | Bin 90112 -> 0 bytes ...9182b2f8-8181-4fb6-b243-d6cc690c6e79.vsidx | Bin 176920 -> 0 bytes src/.vs/src/v17/.wsuo | Bin 20992 -> 0 bytes src/.vs/src/v17/DocumentLayout.backup.json | 37 - src/.vs/src/v17/DocumentLayout.json | 23 - src/data/Contact.java | 83 -- src/data/Notes.java | 317 ----- src/data/NotesDatabaseHelper.java | 396 ------ src/data/NotesProvider.java | 364 ------ src/gtask/data/MetaData.java | 83 -- src/gtask/data/Node.java | 102 -- src/gtask/data/SqlData.java | 205 ---- src/gtask/data/SqlNote.java | 540 --------- src/gtask/data/Task.java | 378 ------ src/gtask/data/TaskList.java | 384 ------ .../exception/ActionFailureException.java | 33 - .../exception/NetworkFailureException.java | 33 - src/gtask/remote/GTaskASyncTask.java | 146 --- src/gtask/remote/GTaskClient.java | 688 ----------- src/gtask/remote/GTaskManager.java | 948 --------------- src/gtask/remote/GTaskSyncService.java | 158 --- src/model/Note.java | 261 ---- src/model/WorkingNote.java | 406 ------- src/tool/BackupUtils.java | 346 ------ src/tool/DataUtils.java | 295 ----- src/tool/GTaskStringUtils.java | 113 -- src/tool/ResourceParser.java | 261 ---- src/ui/AlarmAlertActivity.java | 208 ---- src/ui/AlarmInitReceiver.java | 72 -- src/ui/AlarmReceiver.java | 35 - src/ui/DateTimePicker.java | 508 -------- src/ui/DateTimePickerDialog.java | 103 -- src/ui/DropdownMenu.java | 65 - src/ui/FoldersListAdapter.java | 86 -- src/ui/NoteEditActivity.java | 1068 ----------------- src/ui/NoteEditText.java | 278 ----- src/ui/NoteItemData.java | 214 ---- src/ui/NotesListActivity.java | 1002 ---------------- src/ui/NotesListAdapter.java | 257 ---- src/ui/NotesListItem.java | 116 -- src/ui/NotesPreferenceActivity.java | 516 -------- src/widget/NoteWidgetProvider.java | 132 -- src/widget/NoteWidgetProvider_2x.java | 47 - src/widget/NoteWidgetProvider_4x.java | 46 - 46 files changed, 11363 deletions(-) delete mode 100644 src/.vs/ProjectSettings.json delete mode 100644 src/.vs/VSWorkspaceState.json delete mode 100644 src/.vs/slnx.sqlite delete mode 100644 src/.vs/src/FileContentIndex/9182b2f8-8181-4fb6-b243-d6cc690c6e79.vsidx delete mode 100644 src/.vs/src/v17/.wsuo delete mode 100644 src/.vs/src/v17/DocumentLayout.backup.json delete mode 100644 src/.vs/src/v17/DocumentLayout.json delete mode 100644 src/data/Contact.java delete mode 100644 src/data/Notes.java delete mode 100644 src/data/NotesDatabaseHelper.java delete mode 100644 src/data/NotesProvider.java delete mode 100644 src/gtask/data/MetaData.java delete mode 100644 src/gtask/data/Node.java delete mode 100644 src/gtask/data/SqlData.java delete mode 100644 src/gtask/data/SqlNote.java delete mode 100644 src/gtask/data/Task.java delete mode 100644 src/gtask/data/TaskList.java delete mode 100644 src/gtask/exception/ActionFailureException.java delete mode 100644 src/gtask/exception/NetworkFailureException.java delete mode 100644 src/gtask/remote/GTaskASyncTask.java delete mode 100644 src/gtask/remote/GTaskClient.java delete mode 100644 src/gtask/remote/GTaskManager.java delete mode 100644 src/gtask/remote/GTaskSyncService.java delete mode 100644 src/model/Note.java delete mode 100644 src/model/WorkingNote.java delete mode 100644 src/tool/BackupUtils.java delete mode 100644 src/tool/DataUtils.java delete mode 100644 src/tool/GTaskStringUtils.java delete mode 100644 src/tool/ResourceParser.java delete mode 100644 src/ui/AlarmAlertActivity.java delete mode 100644 src/ui/AlarmInitReceiver.java delete mode 100644 src/ui/AlarmReceiver.java delete mode 100644 src/ui/DateTimePicker.java delete mode 100644 src/ui/DateTimePickerDialog.java delete mode 100644 src/ui/DropdownMenu.java delete mode 100644 src/ui/FoldersListAdapter.java delete mode 100644 src/ui/NoteEditActivity.java delete mode 100644 src/ui/NoteEditText.java delete mode 100644 src/ui/NoteItemData.java delete mode 100644 src/ui/NotesListActivity.java delete mode 100644 src/ui/NotesListAdapter.java delete mode 100644 src/ui/NotesListItem.java delete mode 100644 src/ui/NotesPreferenceActivity.java delete mode 100644 src/widget/NoteWidgetProvider.java delete mode 100644 src/widget/NoteWidgetProvider_2x.java delete mode 100644 src/widget/NoteWidgetProvider_4x.java diff --git a/src/.vs/ProjectSettings.json b/src/.vs/ProjectSettings.json deleted file mode 100644 index f8b4888..0000000 --- a/src/.vs/ProjectSettings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "CurrentProjectSetting": null -} \ No newline at end of file diff --git a/src/.vs/VSWorkspaceState.json b/src/.vs/VSWorkspaceState.json deleted file mode 100644 index e4b2f07..0000000 --- a/src/.vs/VSWorkspaceState.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "ExpandedNodes": [ - "", - "\\data" - ], - "PreviewInSolutionExplorer": false -} \ No newline at end of file diff --git a/src/.vs/slnx.sqlite b/src/.vs/slnx.sqlite deleted file mode 100644 index f51b2c755807d9acb9d03ce66bd0bca8a6604d62..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90112 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 diff --git a/src/.vs/src/FileContentIndex/9182b2f8-8181-4fb6-b243-d6cc690c6e79.vsidx b/src/.vs/src/FileContentIndex/9182b2f8-8181-4fb6-b243-d6cc690c6e79.vsidx deleted file mode 100644 index 7302d8c2680af91edc182b64d7704d4890a64941..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 176920 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 Nu78L7xBtlB{|mwQ=N|w7 diff --git a/src/.vs/src/v17/.wsuo b/src/.vs/src/v17/.wsuo deleted file mode 100644 index 1daee3a3baa912160af69746f92644f384b68d27..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20992 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 sContactCache;//存储联系人信息。电话——姓名 - private static final String TAG = "Contact"; - //这个常量通常用于在日志输出或调试信息中标识所属类别或模块,帮助开发人员在查看日志时快速定位到相关信息的来源。 - - - //这段代码的作用是构建一个复杂的查询条件,用于在查询联系人信息时精确匹配电话号码,并确保所查询到的数据符合特定条件。 - //CALLER_ID_SELECTION 的含义是:选择电话号码字段为特定号码,MIME 类型为电话号码类型,且原始联系人 ID 符合特定条件(通过子查询获取)的联系人数据。 - 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); - } - - //使用 CALLER_ID_SELECTION 中定义的查询条件,并将其中的 + 替换为经过处理后的最小匹配号码 - String selection = CALLER_ID_SELECTION.replace("+", - PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); - //PhoneNumberUtils.toCallerIDMinMatch(phoneNumber) 是一个 Android 平台提供的用于处理电话号码的工具方法之一。 - // 它通常用于将输入的电话号码转换为适合作为 CALLER_ID_SELECTION 筛选条件中的最小匹配号码。 - - //这段代码使用 Android 的内容解析器(Content Resolver)来执行查询操作,并返回一个 Cursor 对象,用于获取符合查询条件的联系人信息。 - Cursor cursor = context.getContentResolver().query( - Data.CONTENT_URI, - new String [] { Phone.DISPLAY_NAME }, - selection, - new String[] { phoneNumber }, - null); - - if (cursor != null && cursor.moveToFirst()) { //如果查询返回的 cursor 不为空且能够移动到第一个结果位置(即有匹配的联系人信息) - try { - String name = cursor.getString(0);//获取第一个字段的值,即联系人的显示名称 - sContactCache.put(phoneNumber, name);//将该信息存储在 sContactCache 中,以电话号码作为键,联系人名称作为值。 - return name; - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, " Cursor get string error " + e.toString()); //记录错误日志 - return null; - } finally { //无论是否发生异常,都会在最终执行块中关闭查询结果的 cursor - cursor.close(); - } - } else { //如果查询结果为空或未找到匹配的联系人信息,则记录调试日志表示未找到匹配的联系人信息,并返回 null。 - Log.d(TAG, "No contact matched with number:" + phoneNumber); - return null; - } - } -} diff --git a/src/data/Notes.java b/src/data/Notes.java deleted file mode 100644 index 5d6225d..0000000 --- a/src/data/Notes.java +++ /dev/null @@ -1,317 +0,0 @@ - -/* - * 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;//系统类型 - - //这段注释解释了一些系统文件夹的标识符,以及它们对应的含义 - // Notes#ID_ROOT_FOLDER: 默认文件夹的标识符。 - // Notes#ID_TEMPARAY_FOLDER: 用于存储不属于任何文件夹的临时笔记的标识符。 - //Notes#ID_CALL_RECORD_FOLDER: 用于存储通话记录的文件夹的标识符。 - /** - * Following IDs are system folders' identifiers - * {@link Notes#ID_ROOT_FOLDER } is default folder - * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder - * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records - */ - - //这些标识符被赋予了特定的整数数值,用于在代码中唯一地标识不同的系统文件夹。 - // 通过使用这些常量,可以方便地在代码中引用这些特殊文件夹的标识符,而无需硬编码数值。 - public static final int ID_ROOT_FOLDER = 0;//默认文件夹的标识符 - public static final int ID_TEMPARAY_FOLDER = -1;//用于存储不属于任何文件夹的临时笔记的标识符 - public static final int ID_CALL_RECORD_FOLDER = -2;//用于存储通话记录的文件夹的标识符 - public static final int ID_TRASH_FOLER = -3;//用于存储回收站文件夹的标识符 - -// Intent是Android应用程序组件之间进行通信的一种重要方式 -// 这段代码定义了一些静态常量,用于表示在 Intent 中传递时所需的额外参数的键值对。这些静态常量的含义如下: -// INTENT_EXTRA_ALERT_DATE: 表示提醒日期的额外参数键值,在 Intent 中使用时对应的键名为 "net.micode.notes.alert_date"。 -// INTENT_EXTRA_BACKGROUND_ID: 表示背景颜色 ID 的额外参数键值,在 Intent 中使用时对应的键名为 "net.micode.notes.background_color_id"。 -// INTENT_EXTRA_WIDGET_ID: 表示小部件 ID 的额外参数键值,在 Intent 中使用时对应的键名为 "net.micode.notes.widget_id"。 -// INTENT_EXTRA_WIDGET_TYPE: 表示小部件类型的额外参数键值,在 Intent 中使用时对应的键名为 "net.micode.notes.widget_type"。 -// INTENT_EXTRA_FOLDER_ID: 表示文件夹 ID 的额外参数键值,在 Intent 中使用时对应的键名为 "net.micode.notes.folder_id"。 -// INTENT_EXTRA_CALL_DATE: 表示通话日期的额外参数键值,在 Intent 中使用时对应的键名为 "net.micode.notes.call_date"。 -// 通过定义这些静态常量,可以避免在代码中硬编码这些字符串,提高了代码的可读性和可维护性。 -// 这些常量的命名也很清晰地表达了它们所代表的含义,让开发人员能够准确地理解它们在 Intent 中所承载的数据。 - - public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; - public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; - public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; - public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; - public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; - public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; - - //这段代码定义了一些静态常量,用于表示小部件的类型 - public static final int TYPE_WIDGET_INVALIDE = -1; //无效的小部件类型 - public static final int TYPE_WIDGET_2X = 0; //2x2小部件 - public static final int TYPE_WIDGET_4X = 1; //4x4小部件 - - - //MIME(Multipurpose Internet Mail Extensions)类型是一种标准化的方法,用于表示各种数据的类型和格式 - //在 Android 开发中,MIME 类型也经常用于指示 Intent 的数据类型,以便系统能够选择合适的应用程序来处理特定类型的数据。 - //定义了一个静态内部类 DataConstants,将数据类型的常量集中定义在一个内部类中,有助于组织和管理相关的常量,并使得代码结构更清晰。 - public static class DataConstants { - public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;//表示笔记的 MIME 类型 - public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;//表示通话记录的 MIME 类型 - } - - //URI(统一资源标识符)是用于标识互联网上资源的字符串文本,它提供了一种统一的方式来定位资源。 - /** - * Uri to query all notes and folders - */ - //表示用于查询所有笔记和文件夹的 URI - public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); - - /** - * Uri to query data - */ - //表示用于查询数据的 URI - public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); - - - //定义了一个接口 NoteColumns,其中包含了一系列常量,用于表示笔记或文件夹的各种属性和信息。 - // 这些常量提供了对笔记或文件夹数据结构中不同字段的访问,使得代码可以更清晰地操作和管理这些数据。 - 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"; - } - - - //定义了另一个接口 DataColumns,其中包含了一系列常量,用于表示数据的各种属性和信息。 - // 这些常量提供了对数据结构中不同字段的访问,使得代码可以更清晰地操作和管理这些数据。 - 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"; - } - - - //定义了一个静态内部类 TextNote,它实现了接口 DataColumns。 - // 这个内部类包含了一些常量和字段,用于表示文本笔记的相关信息。 - 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; - //用于指示文本笔记是否处于待办事项列表模式的模式。值为 1 表示待办事项列表模式,值为 0 表示普通模式 - - 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");//用于表示文本笔记的内容 URI - } - - //静态内部类 CallNote,它也实现了接口 DataColumns。这个内部类包含了一些常量和字段,用于表示通话记录的相关信息。 - 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");//用于表示通话记录的内容 URI - } -} diff --git a/src/data/NotesDatabaseHelper.java b/src/data/NotesDatabaseHelper.java deleted file mode 100644 index 313baf4..0000000 --- a/src/data/NotesDatabaseHelper.java +++ /dev/null @@ -1,396 +0,0 @@ -/* - * 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"; - //表示数据库的名称为 "note.db"。这个常量用于指定应用程序中使用的数据库的名称。 - - private static final int DB_VERSION = 4; - //表示数据库的版本号为 4。 - - public interface TABLE { //接口 - public static final String NOTE = "note"; - //表示数据库中的一个表名为 "note"。这个常量用于指定应用程序中使用的表名。 - - public static final String DATA = "data"; - //表示数据库中的一个表名为 "data"。这个常量也用于指定应用程序中使用的表名。 - } - - private static final String TAG = "NotesDatabaseHelper"; //日志标签 - - private static NotesDatabaseHelper mInstance;//类静态变量 - - //创建一个名为"NOTE"的数据库表的SQL语句 - private static final String CREATE_NOTE_TABLE_SQL = - "CREATE TABLE " + TABLE.NOTE + "(" + - NoteColumns.ID + " INTEGER PRIMARY KEY," + //ID,主键 - NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + //父ID - NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + //提醒日期 - NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + //背景颜色ID - 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," +//小部件ID - NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," +//小部件类型 - NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," +//同步ID - NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +//本地修改 - NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +//原始父ID - NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +//文本类型 - NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +//版本 - ")"; - - //创建一个名为"DATA"的数据库表的SQL语句 - private static final String CREATE_DATA_TABLE_SQL = - "CREATE TABLE " + TABLE.DATA + "(" + - DataColumns.ID + " INTEGER PRIMARY KEY," +//ID,主键 - DataColumns.MIME_TYPE + " TEXT NOT NULL," +//MIME类型 - DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," +//笔记ID - 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 ''" + - ")"; - - //在"DATA"的数据库表上创建一个名为"note_id_index"的索引的SQL语句。 - // 该索引是基于"NOTE_ID"字段的,旨在提高根据笔记ID进行查询时的检索效率。 - 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 - */ - //创建一个触发器,在"NOTE"表中的"parent_id"字段更新之后执行,目的是在更新父文件夹ID后自动增加相应文件夹的笔记计数(notes_count)。 - 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 - */ - //创建一个触发器,在"NOTE"表中的"parent_id"字段更新之后执行,目的是在更新父文件夹ID后自动减少相应文件夹的笔记计数(notes_count)。 - 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 - */ - //在"NOTE"表中插入新记录之后执行,目的是在新笔记被添加到特定文件夹时自动增加该文件夹的笔记计数(notes_count)。 - 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 - */ - //在删除笔记时自动减少相应父文件夹的笔记计数(notes_count)。 - 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} - */ - //向"DATA"表中插入记录之后执行,当插入数据的MIME类型为"NOTE"时会更新"NOTE"表中相应笔记的摘要(snippet)字段。 - 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 - */ - //在更新"DATA"表中的记录之后执行,当更新数据的MIME类型为"NOTE"时,会更新"NOTE"表中相应笔记的摘要(snippet)字段。 - 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 - */ - //在删除"DATA"表中的记录之后执行,当被删除数据的MIME类型为"NOTE"时,会将对应笔记的摘要(snippet)字段置空。 - 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 - */ - //在删除"NOTE"表中的记录之后执行,会同时删除"DATA"表中与被删除笔记相关联的记录。 - 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 - */ - //触发器在删除"NOTE"表中的记录之后执行,会同时删除"NOTE"表中与被删除文件夹相关联的所有笔记记录。 - 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 - */ - //更新"NOTE"表中的记录之后执行,当将笔记移动到垃圾箱(Trash Folder)时,会将相关笔记的父级ID(Parent ID)更新为垃圾箱的ID。 - 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); - }//调用了父类 SQLiteOpenHelper 的构造函数 - - //数据库中创建笔记表 - 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(); -// 创建通话记录文件夹:向 Note 表插入一条记录,指定 ID 为 Notes.ID_CALL_RECORD_FOLDER,类型为系统类型。 -// 创建根文件夹:向 Note 表插入一条记录,指定 ID 为 Notes.ID_ROOT_FOLDER,类型为系统类型。 -// 创建临时文件夹:向 Note 表插入一条记录,指定 ID 为 Notes.ID_TEMPARAY_FOLDER,类型为系统类型。 -// 创建垃圾箱文件夹:向 Note 表插入一条记录,指定 ID 为 Notes.ID_TRASH_FOLER,类型为系统类型。 - - /** - * 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); - } - - //在数据库中创建DATA表 - public void createDataTable(SQLiteDatabase db) { - db.execSQL(CREATE_DATA_TABLE_SQL);//创建数据表 - reCreateDataTableTriggers(db);//重新创建与DATA表相关的触发器 - db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);//索引 - Log.d(TAG, "data table has been created");//日志 - } - - //重新创建与DATA表相关的触发器 - 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); - } - - //用于获取 NotesDatabaseHelper 的单例实例 - 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"); - } - } - - //将数据库升级到版本 2 - 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); - } - - //将数据库升级到版本 3 - 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); - } - - //将数据库升级到版本 4 - private void upgradeToV4(SQLiteDatabase db) { - db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION - + " INTEGER NOT NULL DEFAULT 0"); - } -} diff --git a/src/data/NotesProvider.java b/src/data/NotesProvider.java deleted file mode 100644 index 54b757d..0000000 --- a/src/data/NotesProvider.java +++ /dev/null @@ -1,364 +0,0 @@ -/* - * 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; -//为存储和获取数据提供接口。可以在不同的应用程序之间共享数据 -//ContentProvider提供的方法 -//query:查询 -//insert:插入 -//update:更新 -//delete:删除 -//getType:得到数据类型 -public class NotesProvider extends ContentProvider { - // 用于将URI与相应代码匹配的UriMatcher - private static final UriMatcher mMatcher; - // 数据库助手,用于管理数据库操作 - private NotesDatabaseHelper mHelper; - - // 用于记录日志的标签 - private static final String TAG = "NotesProvider"; - - // 用于定义不同URI模式的常量 - 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; - - // 静态初始化器用于填充UriMatcher - static { - // 创建UriMatcher时,调用UriMatcher(UriMatcher.NO_MATCH)表示不匹配任何路径的返回码 - mMatcher = new UriMatcher(UriMatcher.NO_MATCH); - // 把需要匹配Uri路径全部给注册上 - 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. - */ - // 声明 NOTES_SEARCH_PROJECTION - 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; - // 声明NOTES_SNIPPET_SEARCH_QUERY - 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 - // Context只有在onCreate()中才被初始化 - // 对mHelper进行实例化 - public boolean onCreate() { - mHelper = NotesDatabaseHelper.getInstance(getContext()); - return true; - } - - @Override - // 查询uri在数据库中对应的位置 - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - Cursor c = null; - // 获取可读数据库 - SQLiteDatabase db = mHelper.getReadableDatabase(); - String id = null; - // 匹配查找uri - 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); - // 根据提供的ID查询特定的笔记项 - c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id - + parseSelection(selection), selectionArgs, null, null, sortOrder); - break; - case URI_DATA: - // 在数据库中查询 "data" 表 - c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, - sortOrder); - break; - case URI_DATA_ITEM: - id = uri.getPathSegments().get(1); - // 根据提供的ID查询特定的数据项 - 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) { - // getPathSegments()方法得到一个String的List, - // 在uri.getPathSegments().get(1)为第2个元素 - 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 - // 插入一个uri - 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: - // 如果 URI 匹配通用的 note URI, - // 修改选择条件以确保仅删除 ID 大于 0 的笔记 - insertedId = noteId = db.insert(TABLE.NOTE, null, values); - break; - // 如果存在,查找NOTE_ID - 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 - // notifyChange获得一个ContextResolver对象并且更新里面的内容 - 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); - } - - // 返回插入的uri的路径 - return ContentUris.withAppendedId(uri, insertedId); - } - - @Override - // 删除一个uri - public int delete(Uri uri, String selection, String[] selectionArgs) { - //Uri代表要操作的数据,Android上可用的每种资源 -包括 图像、视频片段、音频资源等都可以用Uri来表示。 - 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: - // 如果 URI 匹配特定的 note 项, - // 从 URI 路径段中提取 ID - id = uri.getPathSegments().get(1); - /** - * ID that smaller than 0 is system folder which is not allowed to - * trash - */ - long noteId = Long.valueOf(id); - // 如果笔记 ID 小于或等于 0(系统文件夹),则退出 switch 语句 - if (noteId <= 0) { - break; - } - // 否则,删除指定 ID 的笔记并应用选择条件 - 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: - // 如果 URI 匹配特定的 data 项,从 URI 路径段中提取 ID - id = uri.getPathSegments().get(1); - // 删除指定 ID 的数据并应用选择条件 - count = db.delete(TABLE.DATA, - DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); - deleteData = true; - break; - default: - // 如果 URI 不匹配任何已知模式,则抛出 IllegalArgumentException - throw new IllegalArgumentException("Unknown URI " + uri); - } - // 如果删除了任何行,则通知已注册的 ContentObservers - if (count > 0) { - if (deleteData) { - getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); - } - getContext().getContentResolver().notifyChange(uri, null); - } - // 返回删除的行数 - return count; - } - - @Override - // 更新一个uri - 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); - // 更新 "note" 表中的数据 - count = db.update(TABLE.NOTE, values, selection, selectionArgs); - break; - case URI_NOTE_ITEM: - id = uri.getPathSegments().get(1); - // 减少特定笔记项的版本号 - increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); - // 更新 "note" 表中特定ID的数据 - count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id - + parseSelection(selection), selectionArgs); - break; - case URI_DATA: - // 更新 "data" 表中的数据 - count = db.update(TABLE.DATA, values, selection, selectionArgs); - updateData = true; - break; - case URI_DATA_ITEM: - id = uri.getPathSegments().get(1); - // 更新 "data" 表中特定ID的数据 - 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); - } - // 通知URI的观察者数据发生变化 - getContext().getContentResolver().notifyChange(uri, null); - } - return count; - } - - // 将字符串解析成规定格式 - private String parseSelection(String selection) { - return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); - } - - //增加一个noteVersion - 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); - } - - // execSQL()方法可以执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句 - mHelper.getWritableDatabase().execSQL(sql.toString()); - } - - @Override - public String getType(Uri uri) { - // TODO Auto-generated method stub - return null; - } - -} diff --git a/src/gtask/data/MetaData.java b/src/gtask/data/MetaData.java deleted file mode 100644 index 80ea1ec..0000000 --- a/src/gtask/data/MetaData.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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; - -//MetaData类继承自Task类,专门用于处理Google任务相关的元数据 -public class MetaData extends Task { - private final static String TAG = MetaData.class.getSimpleName();// 日志输出时使用的TAG标识 - - private String mRelatedGid = null;// 存储与本元数据相关的GTask全局ID - // 设置元数据方法,将gid添加到metaInfo中,并将整个metaInfo以字符串形式保存到notes,同时设置名称 - - 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");// 添加gid失败时的日志输出 - } - setNotes(metaInfo.toString()); - setName(GTaskStringUtils.META_NOTE_NAME); - } - // 获取与本元数据相关的GTask全局ID - public String getRelatedGid() { - return mRelatedGid; - } - // 重写父类方法,判断当前元数据是否值得保存(根据notes是否为空) - @Override - public boolean isWorthSaving() { - return getNotes() != null; - } - // 从远程JSON数据设置内容并提取gid信息 - @Override - public void setContentByRemoteJSON(JSONObject js) { - super.setContentByRemoteJSON(js);// 调用父类的setContentByRemoteJSON方法 - if (getNotes() != null) { // 检查notes字段是否为空 - try { - JSONObject metaInfo = new JSONObject(getNotes().trim());// 将notes字段转换为JSONObject对象 - mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);// 从JSONObject中获取特定字段并赋值给mRelatedGid - } catch (JSONException e) { - Log.w(TAG, "failed to get related gid");// 记录日志,提示无法获取相关的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/gtask/data/Node.java b/src/gtask/data/Node.java deleted file mode 100644 index 10b9fba..0000000 --- a/src/gtask/data/Node.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * 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; // 节点ID - - 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); // 抽象方法,根据远程JSON数据设置节点内容 - - public abstract void setContentByLocalJSON(JSONObject js); // 抽象方法,根据本地JSON数据设置节点内容 - - public abstract JSONObject getLocalJSONFromContent();// 抽象方法,从节点内容获取本地JSON数据 - - public abstract int getSyncAction(Cursor c); // 抽象方法,根据游标获取同步操作类型 - - public void setGid(String gid) { - this.mGid = gid; - }// 设置节点ID - - 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; - }// 获取节点ID - - public String getName() { - return this.mName; - }// 获取节点名称 - - public long getLastModified() { - return this.mLastModified; - }// 获取最后修改时间 - - public boolean getDeleted() { - return this.mDeleted; - } // 获取是否已删除 -} diff --git a/src/gtask/data/SqlData.java b/src/gtask/data/SqlData.java deleted file mode 100644 index f6fde3b..0000000 --- a/src/gtask/data/SqlData.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * 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; - -//SqlData 类是一个数据对象,用于封装数据表中的一行数据。SqlData 类提供了一些方法,用于从 Cursor 对象中加载数据、从 JSONObject 对象中加载数据、将数据提交到数据库等操作。 -public class SqlData { - private static final String TAG = SqlData.class.getSimpleName(); - - private static final int INVALID_ID = -99999; - //通常情况下,将一个特殊数值(比如负数)作为无效 ID 的标识,这样在程序中可以通过判断是否等于 INVALID_ID 来表示某个 ID 是否有效。 - - //定义了一个名为 PROJECTION_DATA 的公共静态常量数组 - 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;//数据表ID列索引 - - public static final int DATA_MIME_TYPE_COLUMN = 1;//数据表MIME_TYPE列索引 - - public static final int DATA_CONTENT_COLUMN = 2;//数据表CONTENT列索引 - - public static final int DATA_CONTENT_DATA_1_COLUMN = 3;//数据表DATA1列索引 - - public static final int DATA_CONTENT_DATA_3_COLUMN = 4;//数据表DATA3列索引 - - private ContentResolver mContentResolver;//访问内容提供者的接口,可以进行数据库操作 - - private boolean mIsCreate;//标识当前数据是否创建 - - private long mDataId;//数据ID - - private String mDataMimeType;//数据MIME类型 - - private String mDataContent;//数据内容 - - private long mDataContentData1;//数据DATA1 - - private String mDataContentData3;//数据DATA3 - - private ContentValues mDiffDataValues; - //ContentValues 是一个键值对存储容器,通常用于封装一组待插入或更新到数据库的数据。 - //mDiffDataValues 主要用来存储数据对象与数据库中实际数据的差异。通过比较 mDiffDataValues 中的数据与数据库中对应的数据, - // 可以确定数据对象是否需要进行更新操作,并且可以方便地获取到数据的变化部分。 - public SqlData(Context context) {//构造函数 - mContentResolver = context.getContentResolver(); - mIsCreate = true;//新创建的数据 - mDataId = INVALID_ID;//数据ID,初始值为无效ID - mDataMimeType = DataConstants.NOTE;//数据MIME类型 - mDataContent = ""; - mDataContentData1 = 0; - mDataContentData3 = ""; - mDiffDataValues = new ContentValues(); - } - - public SqlData(Context context, Cursor c) {//构造函数2 - mContentResolver = context.getContentResolver(); - mIsCreate = false;//不是新创建的数据 - loadFromCursor(c); - mDiffDataValues = new ContentValues(); - } - //用于从传入的 Cursor 对象中加载数据到当前 SqlData 对象中 - 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); - } - //用于将传入的 JSONObject 对象中的数据加载到当前 SqlData 对象中 - public void setContent(JSONObject js) throws JSONException { - long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;//如果JSONObject对象中包含ID,则获取ID,否则ID为无效ID - if (mIsCreate || mDataId != dataId) {//如果是新创建的数据或者数据ID不等于传入的数据ID - mDiffDataValues.put(DataColumns.ID, dataId);//将数据ID放入mDiffDataValues中 - } - mDataId = dataId;//更新数据ID - - 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; - } - //用于获取对象的内容并返回一个 JSON 对象 - 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) 分支中: -// -// 首先检查条件,如果数据ID无效且包含ID字段,则将ID字段从差异数据值中移除。 -// 将指定的笔记ID(noteId)放入差异数据值中。 -// 调用内容解析器(ContentResolver)的insert()方法向数据库中插入数据,并获取返回的URI。 -// 解析URI以获取新插入数据的ID,并将其赋值给 mDataId 变量。 -// 如果出现异常(NumberFormatException),则记录错误日志并抛出 ActionFailureException 异常。 - -// 在 else 分支中: -// -// 如果差异数据值的大小大于0,则继续执行更新操作。 -// 根据是否需要验证版本,调用内容解析器的update()方法更新数据库中的数据。 -// 如果更新结果为0,则记录警告日志,表示可能在同步期间用户更新了笔记。 - 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();//清空mDiffDataValues - mIsCreate = false;//数据已经创建 - } - //返回数据ID - public long getId() { - return mDataId; - } -} diff --git a/src/gtask/data/SqlNote.java b/src/gtask/data/SqlNote.java deleted file mode 100644 index e3f613f..0000000 --- a/src/gtask/data/SqlNote.java +++ /dev/null @@ -1,540 +0,0 @@ - -/* - * 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; - -//Sql类的作用是用于处理笔记相关的数据库操作,其中包含了对笔记的增删改查以及与笔记相关的数据操作。 -//具体来说,这个类实现了以下功能: -// -// 提供了构造函数,可以根据上下文对象初始化一个 SqlNote 对象,并设置默认的属性。 -// 可以根据传入的 Cursor 对象或笔记 ID 加载数据到当前 SqlNote 对象中。 -// 可以根据传入的 JSON 对象设置笔记对象的内容。 -// 可以获取当前对象的内容并返回一个 JSON 对象。 -// 提供了设置父ID、Google任务ID、同步ID等属性的方法。 -// 包含了提交笔记对象更改的方法,可以将笔记对象的数据插入到数据库中或更新数据库中已有的笔记数据。 -// 提供了获取数据ID、父ID、摘要等属性的方法。 -// 包含了加载数据内容、设置内容、提交更改等私有方法,用于具体的数据处理和操作。 -public class SqlNote { - private static final String TAG = SqlNote.class.getSimpleName();//日志 - - private static final int INVALID_ID = -99999;//无效ID标识 - - //包含了一组用于查询的字段名称,可以用于在数据库查询中指定需要返回的列 - 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;//数据ID - - private long mAlertDate;//提醒日期 - - private int mBgColorId;//背景颜色ID - - private long mCreatedDate;//创建日期 - - private int mHasAttachment;//是否有附件 - - private long mModifiedDate;//修改日期 - - private long mParentId;//父ID - - private String mSnippet;//摘要 - - private int mType;//类型 - - private int mWidgetId;//小部件ID - - private int mWidgetType;//小部件类型 - - private long mOriginParent;//原始父ID - - private long mVersion;//版本 - - private ContentValues mDiffNoteValues;//ContentValues 是一个键值对存储容器,通常用于封装一组待插入或更新到数据库的数据 - - 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(); - } - - //构造函数2,根据Cursor对象初始化数据 - 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(); - } - - //构造函数3,根据ID初始化数据 - 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(); - - } - - //根据ID加载数据,利用loadFromCursor(Cursor c)方法加载到当前 SqlNote 对象中 - private void loadFromCursor(long id) { - Cursor c = null; - try { //通过mContentResolver查询获取Cursor对象 - 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 { //无论是否发生异常,都会执行finally块中的代码 - if (c != null) - c.close(); - } - } - - //从传入的 Cursor 对象中加载数据到当前 SqlNote 对象中 - 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); - } - - //从数据库中加载指定笔记ID相关的数据,并将这些数据转化为SqlData对象并存储在mDataList中供后续使用 - private void loadDataContent() { - Cursor c = null; - mDataList.clear(); - try { - //通过内容解析器(ContentResolver)执行查询操作,从笔记内容的URI(Notes.CONTENT_DATA_URI)中获取与当前笔记ID(mId)相关联的数据 - 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) { //如果Cursor对象中没有数据 - Log.w(TAG, "it seems that the note has not data"); - return; - } - while (c.moveToNext()) { //遍历Cursor对象 - SqlData data = new SqlData(mContext, c); //对于每一行数据,创建一个SqlData对象 - mDataList.add(data); - } - } else { - Log.w(TAG, "loadDataContent: cursor = null"); - } - } finally { - if (c != null) - c.close(); - } - } - - //将传入的 JSONObject 对象中的数据加载到当前 SqlNote 对象中 - 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 //如果JSONObject对象中包含摘要,则获取摘要,否则摘要为空字符串 - .getString(NoteColumns.SNIPPET) : ""; - if (mIsCreate || !mSnippet.equals(snippet)) { //如果是新创建的数据或者摘要不等于传入的摘要 - mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); //将摘要放入mDiffNoteValues中 - } - 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; - } - - //获取对象的内容并返回一个 JSON 对象 - 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(); - //将数据列表中的数据加载到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; - } - - //设置父ID - public void setParentId(long id) { - mParentId = id; - mDiffNoteValues.put(NoteColumns.PARENT_ID, id); - } - //设置Google任务ID - public void setGtaskId(String gid) { - mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); - } - //设置同步ID - public void setSyncId(long syncId) { - mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); - } - //设置本地修改标识 - public void resetLocalModified() { - mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); - } - //返回数据ID - public long getId() { - return mId; - } - //返回父ID - 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) { - //如果数据未创建,下面创建新笔记 - //首先检查条件,如果数据ID无效且mDiffNoteValues中包含ID字段,则将ID字段从差异数据值中移除。 - if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) { - mDiffNoteValues.remove(NoteColumns.ID); - } - //通过内容解析器(ContentResolver)的insert方法将mDiffNoteValues中的数据插入到Notes.CONTENT_NOTE_URI所代表的位置,并获取返回的URI。 - Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); - try { - //解析URI以获取新创建数据的ID,并将其赋值给 mId 变量。 - 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) { //检查ID是否有效 - Log.e(TAG, "No such note"); - throw new IllegalStateException("Try to update note with invalid id"); - } - if (mDiffNoteValues.size() > 0) { //如果差异数据值的大小大于0,表示有数据需要更新 - mVersion ++; - int result = 0; - if (!validateVersion) { - result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?)", new String[] { - String.valueOf(mId) - }); - } else { - //根据笔记的ID(mId)将mDiffNoteValues中的数据更新到指定的Notes.CONTENT_NOTE_URI所代表的位置, - result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", - new String[] { - String.valueOf(mId), String.valueOf(mVersion) - }); -// Notes.CONTENT_NOTE_URI:表示要操作的数据的URI,可能是笔记数据的地址。 -// mDiffNoteValues:包含了需要更新的字段和对应数值的键值对。 -// ("(" + NoteColumns.ID + "=?)"):这是用于指定更新条件的部分,其中NoteColumns.ID是笔记的ID字段,"=?"表示该位置需要传入一个值。 -// new String[] {String.valueOf(mId)}:这是要替换"=?"的具体数值,即要更新的笔记的ID。 - - - } - 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); //根据ID加载数据,将数据加载到当前 SqlNote 对象中 - if (mType == Notes.TYPE_NOTE) - loadDataContent(); //从数据库中加载指定笔记ID相关的数据,并将这些数据转化为SqlData对象并存储在mDataList中供后续使用 - - mDiffNoteValues.clear();//清空待提交的数据 - mIsCreate = false; - } -} diff --git a/src/gtask/data/Task.java b/src/gtask/data/Task.java deleted file mode 100644 index 747263e..0000000 --- a/src/gtask/data/Task.java +++ /dev/null @@ -1,378 +0,0 @@ - -/* - * 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();//调用父类Node的构造方法 - mCompleted = false; - mNotes = null; - mPriorSibling = null; - mParent = null; - mMetaInfo = null; - } - - //获取创建任务的json对象 - public JSONObject getCreateAction(int actionId) { - JSONObject js = new JSONObject();//创建json对象 - try { - //设置各种属性 - // action_type - //将键值对放入json对象中 - 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) { //如果任务的备注不为空 - //将备注信息放入json对象中 - 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; - } - - //生成更新任务的json对象 - public JSONObject getUpdateAction(int actionId) { - JSONObject js = new JSONObject(); - - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - - // action_id - js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // id - js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - - // entity_delta - JSONObject entity = new JSONObject(); - entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); - 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; - } - - //根据远程传入的json对象设置任务的内容 - public void setContentByRemoteJSON(JSONObject js) { - if (js != null) { - try { - // id - if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { - setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); - } - - // last_modified - if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { - setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); - } - - // name - if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { - setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); - } - - // 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"); - } - } - } - - //根据本地的json对象设置任务的内容 - public void setContentByLocalJSON(JSONObject js) { - //若传入的json对象为空或者没包含特定键 - 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++) { -// 获取数组中索引为 i 的元素,该元素是一个 JSON 对象。 -// 检查该对象中的 MIME_TYPE 字段是否等于 DataConstants.NOTE。 -// 如果上述条件成立,则将该对象中的 CONTENT 字段的值设置为本地对象的 name 属性。 - 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(); - } - } - - //根据当前对象的状态生成本地的json对象 - 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 { - //将notes字符串的值转换为json对象 - mMetaInfo = new JSONObject(metaData.getNotes()); - } catch (JSONException e) { - Log.w(TAG, e.toString()); - mMetaInfo = null; - } - } - } - - //根据传入Cursor对象和当前对象的mMetainfo属性判断当前对象的同步状态 - public int getSyncAction(Cursor c) { -// 首先尝试获取当前对象的mMetaInfo中的note信息,并进行一些基本的验证。 -// 如果note信息为空,则输出警告日志并返回SYNC_ACTION_UPDATE_REMOTE,表示需要更新远程数据。 -// 如果note信息中没有ID字段,则输出警告日志并返回SYNC_ACTION_UPDATE_LOCAL,表示需要更新本地数据。 -// 验证note的ID与传入Cursor的ID是否匹配,如果不匹配则返回SYNC_ACTION_UPDATE_LOCAL。 -// 检查本地是否有更新,如果没有则比较最后修改时间,如果远程数据有更新则返回SYNC_ACTION_UPDATE_LOCAL,否则返回SYNC_ACTION_NONE。 -// 如果本地有更新,则验证gtask id,如果不匹配则返回SYNC_ACTION_ERROR。 -// 最后根据本地修改时间和最后同步时间的比较,确定具体的同步操作类型,返回相应的同步操作常量。 -// 在处理过程中,如果出现异常则会捕获该异常并打印错误日志,最终返回SYNC_ACTION_ERROR。 - 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() { -// 首先检查当前对象的mMetaInfo属性是否不为null。 -// 如果mMetaInfo属性为null,则继续检查当前对象的name属性是否不为null且去除首尾空格后长度大于0。 -// 接着检查当前对象的notes属性是否不为null且去除首尾空格后长度大于0。 -// 如果上述条件有一个成立,则返回true,否则返回false。 - 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/gtask/data/TaskList.java b/src/gtask/data/TaskList.java deleted file mode 100644 index f42d389..0000000 --- a/src/gtask/data/TaskList.java +++ /dev/null @@ -1,384 +0,0 @@ -/* - * 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; - //ArrayList 类型的数据成员,用来存储属于该任务列表的所有子任务实例。 - - - //构造函数 - public TaskList() { - super(); - mChildren = new ArrayList(); - mIndex = 1; - } - - //生成一个表示创建任务列表操作的JSON对象,以便在网络传输或者持久化存储中使用。 - public JSONObject getCreateAction(int actionId) { - JSONObject js = new JSONObject(); - - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - - // action_id - js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // index - js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); - - // entity_delta - JSONObject entity = new JSONObject(); - entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); - entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); - entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, - GTaskStringUtils.GTASK_JSON_TYPE_GROUP); - js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to generate tasklist-create jsonobject"); - } - - return js; - } - - //该Java方法用于构建并返回一个表示更新动作的JSONObject对象,该动作与给定的actionId相关联。 - // 这个JSON对象遵循预定义的结构,其中键值由GTaskStringUtils类提供。 - 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; - } - - //setContentByRemoteJSON的作用是从传入的远程JSON对象JSONObject中提取并设置相关的属性值。 - 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"); - } - } - } - - //接受一个 JSONObject 参数 js,该方法用于从给定的 JSON 对象中解析并设置本地内容 - public void setContentByLocalJSON(JSONObject js) { - if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { //如果传入的JSON对象为空或者不包含指定的键值对 - Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); - } - - try { - //获取名为 META_HEAD_NOTE 的键所对应的 JSON 对象,并将其赋值给 folder 变量。 - JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - //检查 folder 对象中键为 NoteColumns.TYPE 所对应的值是否等于 Notes.TYPE_FOLDER,即判断是否为文件夹类型的笔记。 - //如果是文件夹类型的笔记,则获取文件夹的名称,并设置为当前任务列表的名称。 - if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { - String name = folder.getString(NoteColumns.SNIPPET); - setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); - } - //如果不是文件夹类型的笔记,则检查是否为系统类型的笔记。 - //如果是系统类型的笔记,则根据笔记的ID来设置当前任务列表的名称。 - else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { - if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) - setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); - else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER) - setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_CALL_NOTE); - else - Log.e(TAG, "invalid system folder"); - } else { - Log.e(TAG, "error type"); - } - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - } - } - //用于将当前任务列表的内容转换为本地的 JSON 对象。 - public JSONObject getLocalJSONFromContent() { - try { - JSONObject js = new JSONObject(); - JSONObject folder = new JSONObject(); - - String folderName = getName();//获取当前任务列表的名称,并将其赋值给 folderName 变量。 - //检查任务列表的名称是否以 GTaskStringUtils.MIUI_FOLDER_PREFFIX 开头。 - //如果是,则截取名称中 GTaskStringUtils.MIUI_FOLDER_PREFFIX 后面的部分作为文件夹名称。 - if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) - folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(), - folderName.length()); - folder.put(NoteColumns.SNIPPET, folderName);//将文件夹名称放入 folder 对象中的 NoteColumns.SNIPPET 键下。 - //检查文件夹名称是否等于默认文件夹名称或通话笔记文件夹名称。 - //如果是,则将文件夹类型设置为系统类型 (Notes.TYPE_SYSTEM)。 - if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) - || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) - folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - //如果文件夹名称不是默认文件夹名称或通话笔记文件夹名称,则将文件夹类型设置为普通文件夹类型 (Notes.TYPE_FOLDER)。 - else - folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - //将存储了文件夹信息的 folder 对象放入主 JSON 对象 js 中的 GTaskStringUtils.META_HEAD_NOTE 键下。 - js.put(GTaskStringUtils.META_HEAD_NOTE, folder); - - return js; - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - return null; - } - } - //用于确定与同步相关的操作。它接收一个 Cursor 对象作为参数,该 Cursor 对象包含了任务列表的数据。 - public int getSyncAction(Cursor c) { - try { - //检查是否存在本地修改。 - //如果没有本地修改: - //如果任务的最后修改时间与本地最后修改时间相同,则表示没有任何更新,返回 SYNC_ACTION_NONE。 - //如果任务的最后修改时间与本地最后修改时间不同,则表示需要将远程更新应用到本地,返回 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 { ... } - //如果存在本地修改: - //验证任务的 GTasks ID 是否与当前任务的 GTasks ID 匹配,如果不匹配则记录错误并返回 SYNC_ACTION_ERROR。 - //如果任务的最后修改时间与本地最后修改时间相同,则表示只有本地修改,需要将本地修改应用到远程,返回 SYNC_ACTION_UPDATE_REMOTE。 - //如果任务的最后修改时间与本地最后修改时间不同,目前代码中注释为针对文件夹冲突的情况,仍然将本地修改应用到远程,返回 SYNC_ACTION_UPDATE_REMOTE。 - 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;//用于标记是否添加成功 - //检查传入的子任务对象 task 不为空,并且当前任务的子任务列表 mChildren 中不包含该子任务。 - if (task != null && !mChildren.contains(task)) { - //如果满足上述条件,将子任务 task 添加到当前任务的子任务列表 mChildren 中,并将返回值赋给 ret,表示添加操作是否成功。 - ret = mChildren.add(task); - if (ret) { - //如果添加操作成功: - //设置子任务的前一个兄弟任务(prior sibling),如果当前任务的子任务列表为空,则前一个兄弟任务为 null,否则为列表中最后一个子任务。 - //设置子任务的父任务为当前任务。 - // 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) { - //首先检查传入的索引 index 是否有效,即确保它在合理的范围内。如果索引小于 0 或大于子任务列表的大小 - if (index < 0 || index > mChildren.size()) { - Log.e(TAG, "add child task: invalid index"); - return false; - } - //使用 indexOf() 方法检查子任务列表中是否已经存在传入的子任务 task,并将其索引存储在变量 pos 中。 - int pos = mChildren.indexOf(task); - if (task != null && pos == -1) { - //将传入的子任务 task 添加到子任务列表 mChildren 的指定索引 index 处。 - mChildren.add(index, task); - - // update the task list - //初始化变量 preTask 和 afterTask,用于存储新添加的子任务的前一个兄弟任务和后一个兄弟任务。 - Task preTask = null; - Task afterTask = null; - if (index != 0) - //如果新添加的子任务不是列表中的第一个任务,则将其前一个兄弟任务设置为索引为 index - 1 的任务。 - preTask = mChildren.get(index - 1); - if (index != mChildren.size() - 1) - //如果新添加的子任务不是列表中的最后一个任务,则将其后一个兄弟任务设置为索引为 index + 1 的任务。 - afterTask = mChildren.get(index + 1); - //将新添加的子任务的前一个兄弟任务设置为 preTask。 - task.setPriorSibling(preTask); - if (afterTask != null) - //如果存在后一个兄弟任务,则将其前一个兄弟任务设置为新添加的子任务 task。 - 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);//将移除的子任务的前一个兄弟任务设置为 null。 - task.setParent(null);//将移除的子任务的父任务设置为 null。 - - // update the task list - if (index != mChildren.size()) { //如果移除的子任务不是列表中的最后一个任务,则执行以下操作: - //将下一个任务的前一个兄弟任务设置为移除任务的前一个兄弟任务,即更新列表中移除任务后的任务的前一个兄弟关系。 - mChildren.get(index).setPriorSibling( - index == 0 ? null : mChildren.get(index - 1)); - } - } - } - return ret; - } - //移动子任务到指定索引位置的方法 - public boolean moveChildTask(Task task, int index) { - - if (index < 0 || index >= mChildren.size()) { - Log.e(TAG, "move child task: invalid index"); - return false; - } - - int pos = mChildren.indexOf(task); - if (pos == -1) { - Log.e(TAG, "move child task: the task should in the list"); - return false; - } - - if (pos == index) - return true; - return (removeChildTask(task) && addChildTask(task, index)); - } - - //根据子任务的 gid在子任务列表中查找特定子任务的方法 - public Task findChildTaskByGid(String gid) { - for (int i = 0; i < mChildren.size(); i++) { - Task t = mChildren.get(i); - if (t.getGid().equals(gid)) { - return t; - } - } - return null; - } - //获取特定子任务在子任务列表中的索引位置 - public int getChildTaskIndex(Task task) { - return mChildren.indexOf(task); - } - //通过索引位置获取子任务 - public Task getChildTaskByIndex(int index) { - if (index < 0 || index >= mChildren.size()) { - Log.e(TAG, "getTaskByIndex: invalid index"); - return null; - } - return mChildren.get(index); - } - //根据子任务的 gid 查找特定子任务 - public Task getChilTaskByGid(String gid) { - for (Task task : mChildren) { - if (task.getGid().equals(gid)) - return task; - } - return null; - } - //获取子任务列表 - public ArrayList getChildTaskList() { - return this.mChildren; - } - //设置索引 - public void setIndex(int index) { - this.mIndex = index; - } - //获取索引 - public int getIndex() { - return this.mIndex; - } -} diff --git a/src/gtask/exception/ActionFailureException.java b/src/gtask/exception/ActionFailureException.java deleted file mode 100644 index 84d5d87..0000000 --- a/src/gtask/exception/ActionFailureException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 {//ActionFailureException 类是小米便签应用中定义的一个运行时异常类, - private static final long serialVersionUID = 4425249765923293627L;//这是一个固定的序列化版本号,用于确保在序列化和反序列化过程中,类的兼容性和一致性。 - - public ActionFailureException() { - super(); - } - - public ActionFailureException(String paramString) {//创建一个携带失败原因信息的 ActionFailureException 异常实例。 - super(paramString); - } - - public ActionFailureException(String paramString, Throwable paramThrowable) {//同时还接收一个 Throwable 对象(paramThrowable),该对象通常是指引发当前异常的实际底层异常。 - super(paramString, paramThrowable); - } -} diff --git a/src/gtask/exception/NetworkFailureException.java b/src/gtask/exception/NetworkFailureException.java deleted file mode 100644 index ccf242d..0000000 --- a/src/gtask/exception/NetworkFailureException.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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; //异常版本ID - - public NetworkFailureException() { //重写并调用超类方法, - super(); //当网络操作失败且没有特定的错误信息需要传达时,可以使用这个构造函数抛出异常。 - } - - public NetworkFailureException(String paramString) { - super(paramString); //网络发生异常,需要传递相关排除异常的详细信息,有助于了解和解决异常 - } - - public NetworkFailureException(String paramString, Throwable paramThrowable) { - super(paramString, paramThrowable); //获得易于理解的错误描述,还能跟踪到原始的异常堆栈信息,有利于问题排查 - } -} diff --git a/src/gtask/remote/GTaskASyncTask.java b/src/gtask/remote/GTaskASyncTask.java deleted file mode 100644 index 782eebb..0000000 --- a/src/gtask/remote/GTaskASyncTask.java +++ /dev/null @@ -1,146 +0,0 @@ - -/* - * 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; - -//定义一个异步任务类GTaskASyncTask,用于在后台线程执行Google Tasks同步操作 -public class GTaskASyncTask extends AsyncTask { - - private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;// 定义一个静态常量,用于通知的唯一ID - // 定义一个接口,用于在同步完成时回调 - public interface OnCompleteListener { - void onComplete(); - } - - private Context mContext;// 定义成员变量,存储上下文环境 - - private NotificationManager mNotifiManager;// 定义NotificationManager实例,用于管理通知 - - private GTaskManager mTaskManager; // 定义GTaskManager实例,负责与Google Tasks服务交互 - - private OnCompleteListener mOnCompleteListener;// 定义监听器,当同步完成时触发onComplete方法 - - // 构造函数,初始化相关组件和变量 - public GTaskASyncTask(Context context, OnCompleteListener listener) { - mContext = context;// 设置应用程序上下文 - mOnCompleteListener = listener;// 设置完成监听器 - mNotifiManager = (NotificationManager)mContext - .getSystemService(Context.NOTIFICATION_SERVICE);// 获取系统通知管理器 - mTaskManager = GTaskManager.getInstance();// 获取GTaskManager单例 - } - // 取消正在进行的同步任务 - 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; - // pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, - // NotesPreferenceActivity.class), 0); - // } else { - // pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, - // NotesListActivity.class), 0); - // } - // notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, - // pendingIntent); - // mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); - // } - - // 创建并显示通知的方法 - private void showNotification(int tickerId, String content) { - PendingIntent pendingIntent;// 根据不同状态创建不同的PendingIntent - if (tickerId != R.string.ticker_success) { - pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, - NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE);// 同步过程中的通知跳转至偏好设置界面 - } - else { - pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, - NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE);// 同步成功后的通知跳转至笔记列表界面 - } - Notification.Builder builder = new Notification.Builder(mContext) - .setAutoCancel(true) - .setContentTitle(mContext.getString(R.string.app_name)) - .setContentText(content) - .setContentIntent(pendingIntent) - .setWhen(System.currentTimeMillis()) - .setOngoing(true); - Notification notification = builder.getNotification(); - mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);// 显示通知 - } - @Override// 在后台线程执行同步逻辑 - protected Integer doInBackground(Void... unused) { - publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity - .getSyncAccountName(mContext)));// 发布登录账号的同步进度 - - return mTaskManager.sync(mContext, this);// 执行实际的同步操作,并返回同步结果状态码 - } - - @Override// 当后台线程发布进度更新时,在主线程中处理更新 - protected void onProgressUpdate(String... progress) { - showNotification(R.string.ticker_syncing, progress[0]);// 显示同步进度通知 - if (mContext instanceof GTaskSyncService) { - ((GTaskSyncService)mContext).sendBroadcast(progress[0]);// 如果当前上下文是GTaskSyncService,则发送广播更新 - } - } - - @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) { // 若存在监听器,则在新线程中触发onComplete方法 - new Thread(new Runnable(){ - - public void run() { - mOnCompleteListener.onComplete(); - } - }).start(); - } - } -} diff --git a/src/gtask/remote/GTaskClient.java b/src/gtask/remote/GTaskClient.java deleted file mode 100644 index 152fe8d..0000000 --- a/src/gtask/remote/GTaskClient.java +++ /dev/null @@ -1,688 +0,0 @@ -/* - * 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 { - - // 定义日志TAG,方便调试输出 - private static final String TAG = GTaskClient.class.getSimpleName(); - // 定义Google Tasks服务的基本URL - private static final String GTASK_URL = "https://mail.google.com/tasks/"; - // 定义获取任务信息的GET请求URL - private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; - // 定义提交任务更新等POST请求URL - private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; - // 单例模式,确保全局只有一个GTaskClient实例 - private static GTaskClient mInstance = null; - // HTTP客户端实例,用于发起网络请求 - private DefaultHttpClient mHttpClient; - // 存储GET请求的实际URL - private String mGetUrl; - // 存储POST请求的实际URL - private String mPostUrl; - // 客户端版本号 - private long mClientVersion; - // 登录状态标志位,标识是否已登录 - private boolean mLoggedin; - // 最后登录时间戳 - private long mLastLoginTime; - // 行动(操作)ID,用于跟踪每个请求的唯一性 - private int mActionId; - // Google账户对象,当前正在使用的同步账户 - private Account mAccount; - // 用于缓存待更新的任务数据的JSONArray - 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; - } - - // 静态工厂方法,获取GTaskClient的单例 - public static synchronized GTaskClient getInstance() { - // 如果实例尚未创建,则创建并返回一个新的GTaskClient实例 - if (mInstance == null) { - mInstance = new GTaskClient(); - } - return mInstance; - } - // 登录方法,尝试登录Google账户并验证GTask服务访问权限 - public boolean login(Activity activity) { - // we suppose that the cookie would expire after 5 minutes - // then we need to re-login - // 判断上次登录时间是否超过5分钟,若超过则需要重新登录 - 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; - } - // 如果已登录则直接返回true - if (mLoggedin) { - Log.d(TAG, "already logged in"); - return true; - } - // 更新最后登录时间 - mLastLoginTime = System.currentTimeMillis(); - // 获取Google账户授权令牌 - String authToken = loginGoogleAccount(activity, false); - if (authToken == null) { - Log.e(TAG, "login google account failed"); - return false; - } - - // 对于非Gmail或Googlemail域的账户,尝试使用自定义域名登录GTask服务 - // 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; - } - } - - // 若未登录成功,则尝试使用官方URL登录GTask服务 - // 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; - } - - // 辅助方法:从Google账户获取授权令牌 - private String loginGoogleAccount(Activity activity, boolean invalidateToken) { - String authToken; - // 获取AccountManager实例,用于管理账户认证 - AccountManager accountManager = AccountManager.get(activity); - // 获取所有类型为"com.google"的账户 - Account[] accounts = accountManager.getAccountsByType("com.google"); - // 检查是否存在可用的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; - } - - // 这个方法尝试登录GTask服务。首先使用提供的authToken进行登录,如果失败,则尝试刷新并重新登录。 - // 登录成功后返回true,否则返回false。 - private boolean tryToLoginGtask(Activity activity, String authToken) { - // 使用给定的authToken尝试登录GTask - if (!loginGtask(authToken)) { - // 如果登录失败(可能是authToken过期),则尝试通过Google账号刷新并获取新的authToken - // maybe the auth token is out of date, now let's invalidate the - // token and try again - authToken = loginGoogleAccount(activity, true); - // 如果刷新并获取新的authToken失败 - if (authToken == null) { - Log.e(TAG, "login google account failed"); - return false;// 返回登录失败 - } - - // 使用新的authToken再次尝试登录GTask - if (!loginGtask(authToken)) { - Log.e(TAG, "login gtask failed");// 记录错误日志 - return false;// 返回登录失败 - } - } - // 登录成功 - return true; - } - - // 这个私有方法用于执行实际的GTask登录过程,根据提供的authToken发送HTTP请求,并检查响应中的认证Cookie是否存在。 - private boolean loginGtask(String authToken) { - // 设置HTTP连接和读取超时时间 - int timeoutConnection = 10000; - int timeoutSocket = 15000; - HttpParams httpParameters = new BasicHttpParams(); - HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); - HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); - mHttpClient = new DefaultHttpClient(httpParameters); // 创建一个HTTP客户端实例 - // 创建并设置Cookie存储对象 - BasicCookieStore localBasicCookieStore = new BasicCookieStore(); - mHttpClient.setCookieStore(localBasicCookieStore); - // 设置HTTP协议参数 - HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); - - // login gtask - try { - // 构建登录URL并发起GET请求 - String loginUrl = mGetUrl + "?auth=" + authToken; - HttpGet httpGet = new HttpGet(loginUrl); - // 执行HTTP请求并获取响应 - HttpResponse response = null; - response = mHttpClient.execute(httpGet); - - // 检查响应中是否包含认证Cookie - // get the cookie now - List cookies = mHttpClient.getCookieStore().getCookies(); - boolean hasAuthCookie = false; - for (Cookie cookie : cookies) { - if (cookie.getName().contains("GTL")) { - hasAuthCookie = true;// 如果找到名为"GTL"的Cookie,则表示存在认证Cookie - } - } - if (!hasAuthCookie) { - Log.w(TAG, "it seems that there is no auth cookie");// 若未发现认证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());// 记录JSON解析异常的日志 - e.printStackTrace(); - return false;// JSON解析出错,返回登录失败 - } - catch (Exception e) {// 捕获所有其他可能的异常 - // simply catch all exceptions - Log.e(TAG, "httpget gtask_url failed");// 记录HTTP GET请求失败的日志 - return false;// HTTP请求出错,返回登录失败 - } - // 如果执行到此处,说明登录过程顺利完成 - return true; - } - // 这是一个辅助方法,用于生成并返回唯一的动作ID。 - private int getActionId() { - return mActionId++;// 自增并返回mActionId的当前值 - } - - // 这个私有方法用于创建并配置一个HttpPost对象,指定目标URL和相关HTTP头部信息。 - private HttpPost createHttpPost() { - // 创建一个新的HttpPost对象指向预定义的POST请求URL - HttpPost httpPost = new HttpPost(mPostUrl); - // 设置HTTP头部Content-Type字段,表明发送的数据格式为application/x-www-form-urlencoded,并采用UTF-8编码 - httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); - // 添加自定义HTTP头部信息,这里设定了一个名为"AT"的字段,其值为"1" - httpPost.setHeader("AT", "1"); - // 返回已配置好的HttpPost对象 - return httpPost; - } - // 这个私有方法用于从HttpEntity中获取并解析响应内容,支持gzip和deflate压缩格式。 - private String getResponseContent(HttpEntity entity) throws IOException { - // 获取实体的Content-Encoding属性,即数据的压缩编码方式 - String contentEncoding = null; - if (entity.getContentEncoding() != null) { - contentEncoding = entity.getContentEncoding().getValue(); - Log.d(TAG, "encoding: " + contentEncoding);// 输出编码方式至日志 - } - // 获取实体内容的输入流 - InputStream input = entity.getContent(); - // 根据Content-Encoding选择合适的解压方式 - if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { - // 如果数据被gzip压缩,使用GZIPInputStream解压 - input = new GZIPInputStream(entity.getContent()); - } - else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { - // 如果数据被deflate压缩,使用InflaterInputStream解压,注意这里的Inflater构造函数传入true启用ZLIB RFC1950格式 - Inflater inflater = new Inflater(true); - input = new InflaterInputStream(entity.getContent(), inflater); - } - // 使用Reader和BufferedReader逐行读取并拼接响应内容 - try { - InputStreamReader isr = new InputStreamReader(input);// 使用UTF-8编码创建StreamReader - BufferedReader br = new BufferedReader(isr); - StringBuilder sb = new StringBuilder(); - - // 循环读取每一行内容直到结束 - while (true) { - String buff = br.readLine(); - if (buff == null) { - // 当读取到最后一行(null)时,返回累积的字符串内容 - return sb.toString(); - } - sb = sb.append(buff);// 将读取到的一行追加到StringBuilder中 - } - } - finally { - // 在所有操作结束后确保关闭输入流 - input.close(); - } - } - // 这个私有方法用于执行一个POST请求并将JSON对象作为请求体发送。若发生网络错误或无法将响应转换为JSON对象,将抛出异常 - private JSONObject postRequest(JSONObject js) throws NetworkFailureException { - // 验证用户是否已登录,未登录则抛出自定义异常 - if (!mLoggedin) { - Log.e(TAG, "please login first"); - throw new ActionFailureException("not logged in"); - } - // 创建并初始化HttpPost对象 - HttpPost httpPost = createHttpPost(); - try { - // 创建一个包含请求参数的链表,参数名为'r',值为传入的JSON对象的字符串形式 - LinkedList list = new LinkedList(); - list.add(new BasicNameValuePair("r", js.toString())); - // 创建一个UrlEncodedFormEntity对象,编码格式为UTF-8,用于封装请求参数 - UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); - httpPost.setEntity(entity); - - // execute the post - // 执行POST请求 - HttpResponse response = mHttpClient.execute(httpPost); - // 从响应中获取内容并转换为JSON对象 - 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) { - // 记录并抛出JSON解析错误异常 - 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 { - // 创建一个用于发送的JSON对象 - JSONObject jsPost = new JSONObject(); - JSONArray actionList = new JSONArray();// 创建一个JSONArray存放动作列表 - - // action_list - // 将新任务的动作信息添加到action_list中 - actionList.put(task.getCreateAction(getActionId())); - jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - - // 添加客户端版本信息到JSON对象 - // client_version - jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - - // 调用postRequest方法执行POST请求 - // post - JSONObject jsResponse = postRequest(jsPost); - // 从响应的JSON对象中提取结果数组的第一个元素 - JSONObject jsResult = (JSONObject)jsResponse.getJSONArray( - GTaskStringUtils.GTASK_JSON_RESULTS).get(0); - // 从响应结果中获取新创建的任务ID,并将其设置到task对象中 - task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); - - } - catch (JSONException e) { - // 记录并抛出JSON处理异常 - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("create task: handing jsonobject failed"); - } - } - - // 此公共方法用于创建一个新的任务列表,将任务列表的创建动作添加到请求体中并提交至服务器 - public void createTaskList(TaskList tasklist) throws NetworkFailureException { - // 在更新数据库前进行必要的提交操作 - commitUpdate(); - try { - // 创建用于发送请求的JSON对象 - JSONObject jsPost = new JSONObject(); - JSONArray actionList = new JSONArray();// 创建一个JSONArray来存储动作列表 - - - // 将新建任务列表的动作信息加入到动作列表中 - // action_list - actionList.put(tasklist.getCreateAction(getActionId())); - jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);// 将动作列表放入JSON对象 - // 添加客户端版本信息到JSON对象 - // client version - jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - - // 发送POST请求 - // post - JSONObject jsResponse = postRequest(jsPost); - // 从响应的JSON对象中获取结果数组的第一个元素 - JSONObject jsResult = (JSONObject)jsResponse.getJSONArray( - GTaskStringUtils.GTASK_JSON_RESULTS).get(0); - // 从响应结果中取出新创建的任务列表ID,并设置到tasklist对象中 - tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); - - } - catch (JSONException e) { - // 处理JSON异常并抛出操作失败异常 - 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();// 创建用于发送请求的JSON对象 - - // action_list - jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);// 将待提交的动作列表放入JSON对象 - - // client_version - jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 添加客户端版本信息到JSON对象 - - postRequest(jsPost);// 发送POST请求 - mUpdateArray = null; // 清空已提交的更新数组 - } - catch (JSONException e) {// 处理JSON异常并抛出操作失败异常 - 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) {// 如果节点非空 - // 避免一次性更新过多导致错误,限制最多允许10个更新项 - // too many update items may result in an error - // set max to 10 items - if (mUpdateArray != null && mUpdateArray.length() > 10) { - commitUpdate();// 超过10个则先提交现有的更新 - } - - if (mUpdateArray == null) - mUpdateArray = new JSONArray(); // 如果待更新数组为空,则创建一个新的JSONArray - mUpdateArray.put(node.getUpdateAction(getActionId()));// 将节点的更新动作添加到待更新数组中 - } - } - - public void moveTask(Task task, TaskList preParent, TaskList curParent)// 此方法用于移动一个任务到另一个任务列表中,包括在同一任务列表内调整位置和跨任务列表移动 - throws NetworkFailureException{ - commitUpdate();// 在移动任务之前先提交任何待更新的操作 - try {// 创建用于发送请求的JSON对象 - 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());// 动作ID - action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid());// 目标任务ID - if (preParent == curParent && task.getPriorSibling() != null) {// 如果当前任务列表与原父任务列表相同,并且该任务不是第一个子任务,则添加前置兄弟任务ID - // 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());// 添加原父任务列表ID - action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid());// 添加目标父任务列表ID - - if (preParent != curParent) {// 如果任务要从一个任务列表移动到另一个任务列表,则添加目标任务列表ID - // 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); - // 发送POST请求 - 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 {// 创建用于发送请求的JSON对象 - 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);// 发送POST请求 - 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 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); - } - // 将解析出的JSON字符串转换为JSONObject,并从中提取任务列表数据 - 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"); - } - } - - // 此方法用于获取指定任务列表ID下的所有任务 - public JSONArray getTaskList(String listGid) throws NetworkFailureException { - // 在获取任务列表前先提交任何待更新的操作 - commitUpdate(); - try {// 创建用于发送请求的JSON对象及动作列表 - 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());// 设置动作ID - action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid);// 设置要获取的任务列表ID - action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false);// 不获取已删除的任务 - actionList.put(action); - jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 将动作列表放入JSON请求体 - - // client_version - // 添加客户端版本信息到JSON请求体 - jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - - // 发送POST请求并获取响应 - JSONObject jsResponse = postRequest(jsPost); - // 从响应的JSON对象中提取任务列表(tasks)并返回 - 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;// 将待更新动作数组设为null,表示清空所有待更新项 - } -} diff --git a/src/gtask/remote/GTaskManager.java b/src/gtask/remote/GTaskManager.java deleted file mode 100644 index 07196fd..0000000 --- a/src/gtask/remote/GTaskManager.java +++ /dev/null @@ -1,948 +0,0 @@ -/* - * 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; - - -//GTaskManager类,用于管理小米便签与Google Tasks之间的同步逻辑 -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; // Google任务列表哈希映射 - - private HashMap mGTaskHashMap; // Google任务哈希映射 - - private HashMap mMetaHashMap; // 元数据哈希映射 - - private TaskList mMetaList; // 元数据列表 - - private HashSet mLocalDeleteIdMap; // 本地删除ID集合 - - private HashMap mGidToNid; // Google ID到本地ID的映射 - - private HashMap mNidToGid; // 本地ID到Google ID的映射 - - // 私有构造函数,初始化各变量 - 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) { - // 用于获取授权令牌 - 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(); - - // 登录Google任务 - if (!mCancelled) { - if (!client.login(mActivity)) { - throw new NetworkFailureException("login google task failed"); - } - } - - // 从Google获取任务列表 - asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); - initGTaskList(); - - // 执行内容同步工作 - 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 实例 - GTaskClient client = GTaskClient.getInstance(); - try { - // 获取任务列表的 JSON 数组 - JSONArray jsTaskLists = client.getTaskLists(); - - // 初始化元数据列表 - 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); - - // 加载元数据 - 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) { - // 将元数据与相关的元数据 ID 映射存储 - mMetaHashMap.put(metaData.getRelatedGid(), metaData); - } - } - } - } - } - - // 如果元数据列表为空,则创建一个新的元数据列表 - if (mMetaList == null) { - mMetaList = new TaskList(); - mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META); - GTaskClient.getInstance().createTaskList(mMetaList); - } - - // 初始化任务列表 - 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); - - // 如果任务列表以 MIUI_FOLDER_PREFIX 开头且不是元数据文件夹 - 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); - - // 加载任务 - 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; // 游标对象初始化为null - String gid; // Google Task ID - Node node; // 节点对象 - - mLocalDeleteIdMap.clear(); // 清空本地删除 ID 映射表 - - if (mCancelled) { // 如果同步已取消,则直接返回 - return; - } - - // 处理本地已删除的便签 - 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)); // 将本地删除 ID 添加到映射表中 - } - } else { - Log.w(TAG, "failed to query trash folder"); - } - } finally { - if (c != null) { - c.close(); // 关闭游标 - c = null; - } - } - - // 先同步文件夹 - syncFolder(); - - // 处理数据库中存在的便签 - 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) { - // 本地添加 - syncType = Node.SYNC_ACTION_ADD_REMOTE; - } else { - // 远程删除 - 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; - } - } - - // 处理剩余的项 - 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,所以我们需要逐个检查 - // 清除本地删除表 - if (!mCancelled) { - if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { - throw new ActionFailureException("failed to batch-delete local deleted notes"); - } - } - - // 刷新本地同步 ID - if (!mCancelled) { - GTaskClient.getInstance().commitUpdate(); - refreshLocalSyncId(); - } - } - - - private void syncFolder() throws NetworkFailureException { - Cursor c = null; // 定义游标对象 - String gid; // Google 任务 ID - Node node; // 节点对象 - int syncType; // 同步类型 - - if (mCancelled) { // 如果已取消同步,则直接返回 - return; - } - - // 对根文件夹进行处理 - 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); - // 对系统文件夹,仅在必要时更新远程名称 - 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; - } - } - - // 对通话便签文件夹进行处理 - 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 - // 对系统文件夹,仅在必要时更新远程名称 - 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; - } - } - - // 对本地存在的文件夹进行处理 - 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; - } - } - - // 遍历远程任务列表映射(mGTaskListHashMap),处理每个映射项 - Iterator> iter = mGTaskListHashMap.entrySet().iterator(); - while (iter.hasNext()) { - // 获取当前映射项 - Map.Entry entry = iter.next(); - // 提取映射项的键(Google 任务 ID)与值(TaskList 对象) - gid = entry.getKey(); - node = entry.getValue(); - // 检查本地任务哈希表(mGTaskHashMap)中是否存在该 Google 任务 ID - if (mGTaskHashMap.containsKey(gid)) { - // 若存在,从本地任务哈希表中移除该 ID 对应的任务 - mGTaskHashMap.remove(gid); - // 执行内容同步操作,将远程任务添加到本地 - doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); - } - } - // 如果同步过程未被取消 - if (!mCancelled) { - // 提交更新至 Google 任务服务端 - 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: - // 获取待删除任务的元数据(MetaData) - meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); - - // 如果元数据存在,调用 Google 任务客户端删除该任务 - if (meta != null) { - GTaskClient.getInstance().deleteNode(meta); - } - - // 将待删除的本地任务 ID 添加到本地删除 ID 集合 - mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); - break; - case Node.SYNC_ACTION_DEL_REMOTE: - // 获取待删除任务的元数据(MetaData) - meta = mMetaHashMap.get(node.getGid()); - - // 如果元数据存在,调用 Google 任务客户端删除该任务 - if (meta != null) { - GTaskClient.getInstance().deleteNode(meta); - } - - // 调用 Google 任务客户端删除指定任务 - 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: - // 处理冲突:目前仅使用本地更新简单处理 - 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; - - // 处理不同类型的节点(TaskList 或 Task) - if (node instanceof TaskList) { - // 节点为 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 { - // 节点为 Task 类型 - sqlNote = new SqlNote(mContext); - - // 获取节点的本地 JSON 表示,并处理 ID 冲突 - 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)) { - // 当前 ID 已存在,需创建新 ID - 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)) { - // 数据 ID 已存在,需创建新 ID - data.remove(DataColumns.ID); - } - } - } - } - } catch (JSONException e) { - Log.w(TAG, e.toString()); - e.printStackTrace(); - } - sqlNote.setContent(js); - - // 设置父任务 ID - 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()); - } - - // 创建本地节点 - sqlNote.setGtaskId(node.getGid()); - sqlNote.commit(false); - - // 更新 GID-NID 映射 - mGidToNid.put(node.getGid(), sqlNote.getId()); - mNidToGid.put(sqlNote.getId(), node.getGid()); - - // 更新远程元数据 - updateRemoteMeta(node.getGid(), sqlNote); - } - - private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { - if (mCancelled) { - // 如果同步过程已被取消,直接返回,不再执行后续操作 - return; - } - - SqlNote sqlNote; - - // 1. 创建并初始化 SqlNote 对象,用于更新本地节点 - // 使用传入的上下文 mContext 和游标 c 初始化 SqlNote 实例 - sqlNote = new SqlNote(mContext, c); - - // 2. 设置 SqlNote 的内容为当前节点的本地 JSON 表示 - // 调用 Node 对象的 getLocalJSONFromContent() 方法获取其本地 JSON 表示,并设置到 SqlNote 对象中 - sqlNote.setContent(node.getLocalJSONFromContent()); - - // 3. 获取待更新节点的父任务 ID - // 根据节点类型(Task 或其他类型)区分处理: - // - 如果节点是 Task 类型,通过 mGidToNid 映射表获取其父任务的 Google ID,再转换为本地 ID - // - 否则,将其父任务 ID 默认设置为根文件夹 ID(Notes.ID_ROOT_FOLDER) - Long parentId; - if (node instanceof Task) { - parentId = mGidToNid.get(((Task) node).getParent().getGid()); - } else { - parentId = new Long(Notes.ID_ROOT_FOLDER); - } - - // 4. 检查父任务 ID 是否有效 - // 如果父任务 ID 为 null,说明无法找到对应的本地父任务,记录错误信息并抛出异常 - if (parentId == null) { - Log.e(TAG, "cannot find task's parent id locally"); - throw new ActionFailureException("cannot update local node"); - } - - // 5. 设置 SqlNote 的父任务 ID,并提交更新 - // 将计算得到的父任务 ID 设置到 SqlNote 对象中,然后调用 commit(true) 方法提交更新至本地数据库 - sqlNote.setParentId(parentId.longValue()); - sqlNote.commit(true); - - // 6. 更新远程元数据 - // 调用 updateRemoteMeta() 方法,同步更新远程元数据以保持与本地数据一致 - updateRemoteMeta(node.getGid(), sqlNote); - } - - private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { - if (mCancelled) { - // 如果同步过程已被取消,直接返回,不再执行后续操作 - return; - } - - SqlNote sqlNote = new SqlNote(mContext, c); - Node n; - - // 1. 根据 SqlNote 类型(任务或文件夹),执行相应的远程添加操作 - - // 如果 SqlNote 是任务类型 - if (sqlNote.isNoteType()) { - // 创建 Task 对象并设置内容 - Task task = new Task(); - task.setContentByLocalJSON(sqlNote.getContent()); - - // 获取父任务列表的 Google ID - String parentGid = mNidToGid.get(sqlNote.getParentId()); - - // 检查父任务列表 ID 是否有效 - if (parentGid == null) { - Log.e(TAG, "cannot find task's parent tasklist"); - throw new ActionFailureException("cannot add remote task"); - } - - // 在对应的父任务列表中添加子任务 - mGTaskListHashMap.get(parentGid).addChildTask(task); - - // 调用 Google 任务客户端创建远程任务 - GTaskClient.getInstance().createTask(task); - - // 将 Task 对象赋值给 Node 变量 n - n = (Node) task; - - // 更新远程元数据 - updateRemoteMeta(task.getGid(), sqlNote); - } else { - // 如果 SqlNote 是文件夹类型 - - // 初始化 TaskList 变量 tasklist,用于存储已匹配或新建的远程文件夹 - TaskList tasklist = null; - - // 构建文件夹名称 - 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; - } - } - - // 如果未找到匹配的文件夹,创建新的 TaskList 对象并添加到远程 - if (tasklist == null) { - tasklist = new TaskList(); - tasklist.setContentByLocalJSON(sqlNote.getContent()); - GTaskClient.getInstance().createTaskList(tasklist); - mGTaskListHashMap.put(tasklist.getGid(), tasklist); - } - - // 将 TaskList 对象赋值给 Node 变量 n - n = (Node) tasklist; - } - - // 2. 更新本地 SqlNote 记录 - - // 设置 SqlNote 的 Google ID - sqlNote.setGtaskId(n.getGid()); - - // 提交更新(false:不触发本地更新) - sqlNote.commit(false); - - // 清除本地修改标志 - sqlNote.resetLocalModified(); - - // 提交更新(true:触发本地更新) - sqlNote.commit(true); - - // 3. 更新 GID-NID 映射表 - - // 将新添加的远程节点的 Google ID 和本地 ID 添加到映射表 - 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); - - // 1. 更新远程节点 - - // 根据 SqlNote 设置 Node 对象的内容 - node.setContentByLocalJSON(sqlNote.getContent()); - - // 调用 Google 任务客户端更新远程节点 - GTaskClient.getInstance().addUpdateNode(node); - - // 更新远程元数据 - updateRemoteMeta(node.getGid(), sqlNote); - - // 2. 若为任务类型,检查是否需要移动任务 - - if (sqlNote.isNoteType()) { - Task task = (Task) node; - TaskList preParentList = task.getParent(); - - // 获取当前父任务列表的 Google ID - String curParentGid = mNidToGid.get(sqlNote.getParentId()); - - // 检查当前父任务列表 ID 是否有效 - 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); - } - } - - // 3. 清除本地修改标志并提交更新 - - // 清除 SqlNote 的本地修改标志 - sqlNote.resetLocalModified(); - - // 提交更新(true:触发本地更新) - sqlNote.commit(true); - } - - private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { - if (sqlNote != null && sqlNote.isNoteType()) { - // 1. 获取或创建 MetaData 对象 - - MetaData metaData = mMetaHashMap.get(gid); - - if (metaData != null) { - // 若已存在 MetaData,更新其内容并添加到更新队列 - metaData.setMeta(gid, sqlNote.getContent()); - GTaskClient.getInstance().addUpdateNode(metaData); - } else { - // 若不存在 MetaData,创建新的 MetaData 对象,设置内容,添加到元数据列表、映射表和更新队列 - 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; - } - - // 1. 获取最新的 Google 任务列表 - - // 清空本地缓存的 Google 任务哈希表、Google 任务列表哈希表和元数据哈希表 - mGTaskHashMap.clear(); - mGTaskListHashMap.clear(); - mMetaHashMap.clear(); - - // 初始化 Google 任务列表 - initGTaskList(); - - // 2. 查询本地便签数据,更新同步 ID - - 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) { - // 遍历查询结果,更新每个便签的同步 ID - while (c.moveToNext()) { - // 获取便签的 Google ID - String gid = c.getString(SqlNote.GTASK_ID_COLUMN); - - // 查找对应的 Google 任务 - Node node = mGTaskHashMap.get(gid); - - if (node != null) { - // 移除已处理的 Google 任务 - mGTaskHashMap.remove(gid); - - // 构建用于更新同步 ID 的 ContentValues - ContentValues values = new ContentValues(); - values.put(NoteColumns.SYNC_ID, node.getLastModified()); - - // 更新便签的同步 ID - mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, - c.getLong(SqlNote.ID_COLUMN)), values, null, null); - } else { - // 记录错误信息并抛出异常,表示本地便签缺少 Google ID - 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() { - // 返回 Google 任务客户端使用的同步账户名 - return GTaskClient.getInstance().getSyncAccount().name; - } - - public void cancelSync() { - // 标记同步过程已取消 - mCancelled = true; - } -} \ No newline at end of file diff --git a/src/gtask/remote/GTaskSyncService.java b/src/gtask/remote/GTaskSyncService.java deleted file mode 100644 index 4f89a49..0000000 --- a/src/gtask/remote/GTaskSyncService.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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; - -//定义一个服务类GTaskSyncService,用于实现Google Tasks的同步服务 -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) { - // 创建新的GTaskASyncTask实例 - mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { - @Override - public void onComplete() { - // 当同步完成时,取消引用并停止服务 - mSyncTask = null; - sendBroadcast(""); // 发送广播通知同步已完成 - stopSelf(); // 停止当前服务 - } - }); - - // 发送初始广播通知开始同步 - sendBroadcast(""); - - // 执行异步同步任务 - mSyncTask.execute(); - } - } - - // 取消同步任务的方法 - private void cancelSync() { - // 如果当前有同步任务正在进行 - if (mSyncTask != null) { - // 取消同步任务 - mSyncTask.cancelSync(); - } - } - - // 服务创建时重置同步任务 - @Override - public void onCreate() { - mSyncTask = null; - } - - // 服务启动时接收并处理Intent - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Bundle bundle = intent.getExtras(); - - // 如果Intent携带了ACTION_STRING_NAME键的数据 - 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; - } - - // 返回START_STICKY使得服务在被系统杀死后自动重启 - return START_STICKY; - } - - // 若Intent不满足条件,调用父类方法 - return super.onStartCommand(intent, flags, startId); - } - - // 当系统内存不足时,尝试取消同步任务 - @Override - public void onLowMemory() { - if (mSyncTask != null) { - mSyncTask.cancelSync(); - } - } - - // 绑定服务时返回null,表明此服务不支持绑定 - @Override - 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(ACTION_STRING_NAME, ACTION_START_SYNC); - activity.startService(intent); // 由Activity启动服务并开始同步 - } - - // 静态方法,取消同步服务 - public static void cancelSync(Context context) { - Intent intent = new Intent(context, GTaskSyncService.class); - intent.putExtra(ACTION_STRING_NAME, ACTION_CANCEL_SYNC); - context.startService(intent); // 由Context启动服务并取消同步 - } - - // 静态方法,检查是否正在进行同步 - public static boolean isSyncing() { - return mSyncTask != null; - } - - // 静态方法,获取当前同步进度的字符串描述 - public static String getProgressString() { - return mSyncProgress; - } -} diff --git a/src/model/Note.java b/src/model/Note.java deleted file mode 100644 index 2ae2969..0000000 --- a/src/model/Note.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * 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;//用于添加和获取Uri后面的ID -import android.content.ContentValues;//一种用来存储基本数据类型数据的存储机制 -import android.content.Context;//需要用该类来弄清楚调用者的实例 -import android.content.OperationApplicationException;//操作应用程序容错 -import android.net.Uri;//表示待操作的数据 -import android.os.RemoteException;//远程容错 -import android.util.Log;//输出日志,比如说出错、警告等 - -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); - //ContentResolver()主要是实现外部应用对ContentProvider中的数据 - //进行添加、删除、修改和查询操作 - long noteId = 0; - try { - noteId = Long.valueOf(uri.getPathSegments().get(1)); - } - catch (NumberFormatException e) { - Log.e(TAG, "Get note id error :" + e.toString()); - noteId = 0; - }//try-catch异常处理 - if (noteId == -1) { - throw new IllegalStateException("Wrong note id:" + noteId); - } - return noteId; - } - - public Note() { - mNoteDiffValues = new ContentValues(); - mNoteData = new NoteData(); - }//定义两个变量用来存储便签的数据,一个是存储便签属性、一个是存储便签内容 - - public void setNoteValue(String key, String value) { - mNoteDiffValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); - }//设置数据库表格的标签属性数据 - - public void setTextData(String key, String value) { - mNoteData.setTextData(key, value); - }//设置数据库表格的标签文本内容的数据 - - public void setTextDataId(long id) { - mNoteData.setTextDataId(id); - }//设置文本数据的ID - - public long getTextDataId() { - return mNoteData.mTextDataId; - }//得到文本数据的ID - - public void setCallDataId(long id) { - mNoteData.setCallDataId(id); - }//设置电话号码数据的ID - - public void setCallData(String key, String value) { - mNoteData.setCallData(key, value); - }//得到电话号码数据的ID - - public boolean isLocalModified() { - return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); - }//判断是否是本地修改 - - public boolean syncNote(Context context, long noteId) { - if (noteId <= 0) { - throw new IllegalArgumentException("Wrong note id:" + noteId); - } - - if (!isLocalModified()) { - return true; - } - - /** - * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and - * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the - * note data info - */ - if (context.getContentResolver().update( - ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, - null) == 0) { - Log.e(TAG, "Update note error, should not happen"); - // Do not return, fall through - } - mNoteDiffValues.clear(); - - if (mNoteData.isLocalModified() - && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { - return false; - } - - return true; - }//判断数据是否同步 - - private class NoteData {//定义一个基本的便签内容的数据类,主要包含文本数据和电话号码数据 - private long mTextDataId; - - private ContentValues mTextDataValues;//文本数据 - - private long mCallDataId; - - private ContentValues mCallDataValues;//电话号码数据 - - private static final String TAG = "NoteData"; - - public NoteData() { - mTextDataValues = new ContentValues(); - mCallDataValues = new ContentValues(); - mTextDataId = 0; - mCallDataId = 0; - } - //下面是上述几个函数的具体实现 - boolean isLocalModified() { - return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; - } - - void setTextDataId(long id) { - if (id <= 0) { - throw new IllegalArgumentException("Text data id should larger than 0"); - } - mTextDataId = id; - } - - void setCallDataId(long id) { - if (id <= 0) { - throw new IllegalArgumentException("Call data id should larger than 0"); - } - mCallDataId = id; - } - - void setCallData(String key, String value) { - mCallDataValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); - } - - void setTextData(String key, String value) { - mTextDataValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); - } - //下面函数的作用是将新的数据通过Uri的操作存储到数据库 - Uri pushIntoContentResolver(Context context, long noteId) { - /** - * Check for safety - */ - if (noteId <= 0) { - throw new IllegalArgumentException("Wrong note id:" + noteId); - }//判断数据是否合法 - - ArrayList 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(); - }//把文本数据存入DataColumns - - if (mCallDataValues.size() > 0) { - mCallDataValues.put(DataColumns.NOTE_ID, noteId); - if (mCallDataId == 0) { - mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); - Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, - mCallDataValues); - try { - setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); - } - catch (NumberFormatException e) { - Log.e(TAG, "Insert new call data fail with noteId" + noteId); - mCallDataValues.clear(); - return null; - } - } - else { - builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mCallDataId)); - builder.withValues(mCallDataValues); - operationList.add(builder.build()); - } - mCallDataValues.clear(); - }//把电话号码数据存入DataColumns - - if (operationList.size() > 0) { - try { - ContentProviderResult[] results = context.getContentResolver().applyBatch( - Notes.AUTHORITY, operationList); - return (results == null || results.length == 0 || results[0] == null) ? null - : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); - } - catch (RemoteException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - return null; - } - catch (OperationApplicationException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - return null; - } - }//存储过程中的异常处理 - return null; - } - } -} diff --git a/src/model/WorkingNote.java b/src/model/WorkingNote.java deleted file mode 100644 index 3d1b1f8..0000000 --- a/src/model/WorkingNote.java +++ /dev/null @@ -1,406 +0,0 @@ -/* - * 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; - - // 声明 DATA_PROJECTION字符串数组 - public static final String[] DATA_PROJECTION = new String[] { - DataColumns.ID, - DataColumns.CONTENT, - DataColumns.MIME_TYPE, - DataColumns.DATA1, - DataColumns.DATA2, - DataColumns.DATA3, - DataColumns.DATA4, - }; - - // 声明 NOTE_PROJECTION字符串数组 - 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 - public 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; - } - - // WorkingNote的构造函数 - // Existing note construct - private WorkingNote(Context context, long noteId, long folderId) { - mContext = context; - mNoteId = noteId; - mFolderId = folderId; - mIsDeleted = false; - mNote = new Note(); - loadNote(); - } - - // 加载Note - // 通过数据库调用query函数找到第一个条目 - 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(); - } - - // 加载NoteData - 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); - } - } - - // 创建空的Note - // 传参:context,文件夹id,widget,背景颜色 - 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); - } - - // 保存Note - 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; - } - } - - - // 设置mNoteSettingStatusListener - public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { - mNoteSettingStatusListener = l; - } - - // 设置AlertDate - // 若 mAlertDate与data不同,则更改mAlertDate并设定NoteValue - 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(); - // 调用mNoteSettingStatusListener的 onWidgetChanged方法 - } - } - - // 设定背景颜色 - public void setBgColorId(int id) { - if (id != mBgColorId) { //设定条件 id != mBgColorId - mBgColorId = id; - if (mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onBackgroundColorChanged(); - } - mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); - } - } - - // 设定检查列表模式 - // 参数:mode - public void setCheckListMode(int mode) { - if (mMode != mode) { //设定条件 mMode != mode - if (mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); - } - mMode = mode; - mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); - } - } - - - // 设定WidgetType - // 参数:type - public void setWidgetType(int type) { - if (type != mWidgetType) {//设定条件 type != mWidgetType - mWidgetType = type; - mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); - // 调用Note的setNoteValue方法更改WidgetType - } - } - - // 设定WidgetId - // 参数:id - public void setWidgetId(int id) { - if (id != mWidgetId) {//设定条件 id != mWidgetId - mWidgetId = id; - mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); - // 调用Note的setNoteValue方法更改WidgetId - } - } - - // 设定WorkingTex - // 参数:更改的text - public void setWorkingText(String text) { - if (!TextUtils.equals(mContent, text)) {//设定条件 mContent, text内容不同 - mContent = text; - mNote.setTextData(DataColumns.CONTENT, mContent); - // 调用Note的setTextData方法更改WorkingText - } - } - - // 转变mNote的CallData及CallNote信息 - // 参数:String phoneNumber, long callDate - 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); - } - - // 获取Content - public String getContent() { - return mContent; - } - - // 获取AlertDate - public long getAlertDate() { - return mAlertDate; - } - - // 获取ModifiedDate - public long getModifiedDate() { - return mModifiedDate; - } - - // 获取背景颜色来源id - public int getBgColorResId() { - return NoteBgResources.getNoteBgResource(mBgColorId); - } - - // 获取背景颜色id - public int getBgColorId() { - return mBgColorId; - } - - // 获取标题背景颜色id - public int getTitleBgResId() { - return NoteBgResources.getNoteTitleBgResource(mBgColorId); - } - - // 获取CheckListMode - public int getCheckListMode() { - return mMode; - } - - // 获取便签id - public long getNoteId() { - return mNoteId; - } - - // 获取文件夹id - public long getFolderId() { - return mFolderId; - } - - // 获取WidgetId - public int getWidgetId() { - return mWidgetId; - } - - // 获取WidgetType - public int getWidgetType() { - return mWidgetType; - } - - // 创建接口 NoteSettingChangedListener,便签更新监视 - // 为NoteEditActivity提供接口 - // 提供函数有 - 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/tool/BackupUtils.java b/src/tool/BackupUtils.java deleted file mode 100644 index e87f7c7..0000000 --- a/src/tool/BackupUtils.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * 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;//Android提供的一个上下文对象,可以用于访问应用程序的资源和执行一些操作。 -import android.database.Cursor;//一个用于查询数据库结果集的接口,可以用于获取查询结果中的各个数据项。 -import android.os.Environment;//提供了访问系统环境的方法,例如获取外部存储路径。 -import android.text.TextUtils;//个包含各种字符串处理方法的工具类,例如判断字符串是否为空。 -import android.text.format.DateFormat;//提供了各种日期和时间格式化的方法。 -import android.util.Log;//Android提供的日志工具类,用于打印调试信息。 - -import net.micode.notes.R;//应用程序的资源类,用于引用应用程序中的各种资源,如布局文件、图标、字符串等。 -import net.micode.notes.data.Notes;//一个自定义的Notes数据类,用于访问和操作备忘录的数据。 -import net.micode.notes.data.Notes.DataColumns;//Notes数据类中定义的备忘录数据列常量。 -import net.micode.notes.data.Notes.DataConstants;//Notes数据类中定义的备忘录数据常量。 -import net.micode.notes.data.Notes.NoteColumns;//Notes数据类中定义的备忘录笔记列常量。 - -//用于文件操作,如创建文件、写入内容等。 -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 SD卡没有被装到手机里 - 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 通过查询parent,id是文件夹id的note来选出制定ID文件夹下的Note - 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里面保存有这份note的日期 - 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 将文件导出到text - 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) {//利用光标来扫面内容,区别为callnote和note两种,靠ps.printline输出 - 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() {//总函数,调用上面的exportFolder和exportNote - 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);//将ps输出流输出到特定文件,目的是导出到文件,而不是直接输出 - } 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());//filedir应该就是用来存储路径信息 - 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(); - } - //try catch 异常处理 - return null; - } -} - - diff --git a/src/tool/DataUtils.java b/src/tool/DataUtils.java deleted file mode 100644 index 1a2266e..0000000 --- a/src/tool/DataUtils.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * 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, //光标指向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(); //最终关闭,可能存在问题,cursor可能无法正常释放,从而导致程序崩溃 - } - } - } - return count; - } - //可视化操作数据库,搭建ui界面方便用户操作本地数据库 - 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); //得到部件id,0是请求代码 - widget.widgetType = c.getInt(1); //得到部件种类,1是请求代码 - set.add(widget); //将部件加入到哈希设置中 - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, e.toString()); //检测异常,加入到错误日志文件中去 - } - } while (c.moveToNext()); - } - c.close(); - } - return set; - } - //得到电话号码笔记id - 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; - } - //得到片段id - 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); //通过请求代码0,请求获取字符串,并传递给snippet - } - 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/tool/GTaskStringUtils.java b/src/tool/GTaskStringUtils.java deleted file mode 100644 index 666b729..0000000 --- a/src/tool/GTaskStringUtils.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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/tool/ResourceParser.java b/src/tool/ResourceParser.java deleted file mode 100644 index 874faf5..0000000 --- a/src/tool/ResourceParser.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * 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 - }; - - /** - * 根据id获取笔记背景资源 - * @param id 颜色id - * @return 背景资源id - */ - public static int getNoteBgResource(int id) { - return BG_EDIT_RESOURCES[id]; - } - - /** - * 根据id获取笔记标题背景资源 - * @param id 颜色id - * @return 标题背景资源id - */ - public static int getNoteTitleBgResource(int id) { - return BG_EDIT_TITLE_RESOURCES[id]; - } - } - - /** - * 获取默认笔记背景id - * @param context 上下文对象,用于访问SharedPreferences - * @return 笔记背景资源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 - }; - - /** - * 获取第一个笔记项的背景资源 - * @param id 颜色id - * @return 背景资源id - */ - public static int getNoteBgFirstRes(int id) { - return BG_FIRST_RESOURCES[id]; - } - - /** - * 获取最后一个笔记项的背景资源 - * @param id 颜色id - * @return 背景资源id - */ - public static int getNoteBgLastRes(int id) { - return BG_LAST_RESOURCES[id]; - } - - /** - * 获取单个笔记项的背景资源 - * @param id 颜色id - * @return 背景资源id - */ - public static int getNoteBgSingleRes(int id) { - return BG_SINGLE_RESOURCES[id]; - } - - /** - * 获取普通笔记项的背景资源 - * @param id 颜色id - * @return 背景资源id - */ - public static int getNoteBgNormalRes(int id) { - return BG_NORMAL_RESOURCES[id]; - } - - /** - * 获取文件夹背景资源 - * @return 文件夹背景资源id - */ - public static int getFolderBgRes() { - return R.drawable.list_folder; - } - } - - /** - * 处理小部件背景资源的静态内部类 - */ - public static class WidgetBgResources { - // 2x小部件背景资源数组 - 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, - }; - - /** - * 根据id获取2x小部件背景资源 - * @param id 颜色id - * @return 背景资源id - */ - public static int getWidget2xBgResource(int id) { - return BG_2X_RESOURCES[id]; - } - - // 4x小部件背景资源数组 - 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 - }; - - /** - * 根据id获取4x小部件背景资源 - * @param id 颜色id - * @return 背景资源id - */ - 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 - }; - - /** - * 根据id获取文本外观资源 - * @param id 外观id - * @return 文本外观资源id - */ - public static int getTexAppearanceResource(int id) { - // 若id超出资源数组范围,则返回默认字体大小 - if (id >= TEXTAPPEARANCE_RESOURCES.length) { - return BG_DEFAULT_FONT_SIZE; - } - return TEXTAPPEARANCE_RESOURCES[id]; - } - - /** - * 获取文本外观资源的数量 - * @return 资源数量 - */ - public static int getResourcesSize() { - return TEXTAPPEARANCE_RESOURCES.length; - } - } -} diff --git a/src/ui/AlarmAlertActivity.java b/src/ui/AlarmAlertActivity.java deleted file mode 100644 index 49f58a8..0000000 --- a/src/ui/AlarmAlertActivity.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * 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; //表示引入 Android 应用程序中的 Activity 类,用于创建用户界面 -import android.app.AlertDialog; //表示引入 Android 应用程序中的 AlertDialog 类,用于创建对话框。 -import android.content.Context; //表示引入 Android 应用程序中的 Context 类,用于访问应用程序的资源和执行操作。 -import android.content.DialogInterface; //表示引入 Android 应用程序中的 DialogInterface 类,用于处理对话框事件 -import android.content.DialogInterface.OnClickListener; //表示引入 Android 应用程序中的 OnClickListener 接口,用于处理对话框按钮点击事件。 -import android.content.DialogInterface.OnDismissListener; //表示引入 Android 应用程序中的 OnDismissListener 接口,用于处理对话框消失事件 -import android.content.Intent; //表示引入 Android 应用程序中的 Intent 类,用于在应用程序组件之间进行通信。 -import android.media.AudioManager; //表示引入 Android 应用程序中的 AudioManager 类,用于控制音频管理器。 -import android.media.MediaPlayer; //表示引入 Android 应用程序中的 MediaPlayer 类,用于实现音频播放功能。 -import android.media.RingtoneManager; //表示引入 Android 应用程序中的 RingtoneManager 类,用于管理铃声。 -import android.net.Uri; //表示引入 Android 应用程序中的 Uri 类,用于表示统一资源标识符。 -import android.os.Bundle; //表示引入 Android 应用程序中的 Bundle 类,用于传递数据。 -import android.os.PowerManager; //表示引入 Android 应用程序中的 PowerManager 类,用于管理设备电源。 -import android.provider.Settings; //表示引入 Android 应用程序中的 Settings 类,用于访问设备设置。 -import android.view.Window; //表示引入 Android 应用程序中的 Window 类,用于管理窗口。 -import android.view.WindowManager; //表示引入 Android 应用程序中的 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; //文本在数据库存储中的ID号 - private String mSnippet; //闹钟提示时出现的文本片段 - private static final int SNIPPET_PREW_MAX_LEN = 60; //表示文本片段的最大长度。 - MediaPlayer mPlayer; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - //Bundle类型的数据与Map类型的数据相似,都是以key-value的形式存储数据的 - //onsaveInstanceState方法是用来保存Activity的状态的 - //能从onCreate的参数savedInsanceState中获得状态数据 - 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); - //根据ID从数据库中获取标签的内容; - //getContentResolver()是实现数据共享,实例存储。 - 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; - } - /* - try - { - // 代码区 - } - catch(Exception e) - { - // 异常处理 - } - 代码区如果有错误,就会返回所写异常的处理。*/ - 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); - //调用系统的铃声管理URI,得到闹钟提示音 - 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); - //方法:setDataSource(Context context, Uri uri) - //解释:无返回值,设置多媒体数据来源【根据 Uri】 - mPlayer.prepare(); - //准备同步 - mPlayer.setLooping(true); - //设置是否循环播放 - mPlayer.start(); - //开始播放 - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - //e.printStackTrace()函数功能是抛出异常, 还将显示出更深的调用信息 - //System.out.println(e),这个方法打印出异常,并且输出在哪里出现的异常 - } 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); - //AlertDialog的构造方法全部是Protected的 - //所以不能直接通过new一个AlertDialog来创建出一个AlertDialog。 - //要创建一个AlertDialog,就要用到AlertDialog.Builder中的create()方法 - //如这里的dialog就是新建了一个AlertDialog - dialog.setTitle(R.string.app_name); - //为对话框设置标题 - dialog.setMessage(mSnippet); - //为对话框设置内容 - dialog.setPositiveButton(R.string.notealert_ok, this); - //给对话框添加"Yes"按钮 - if (isScreenOn()) { - dialog.setNegativeButton(R.string.notealert_enter, this); - }//对话框添加"No"按钮 - dialog.show().setOnDismissListener(this); - } - - public void onClick(DialogInterface dialog, int which) { - switch (which) { - //用which来选择click后下一步的操作 - case DialogInterface.BUTTON_NEGATIVE: - //这是取消操作 - Intent intent = new Intent(this, NoteEditActivity.class); - //实现两个类间的数据传输 - intent.setAction(Intent.ACTION_VIEW); - //设置动作属性 - intent.putExtra(Intent.EXTRA_UID, mNoteId); - //实现key-value对 - //EXTRA_UID为key;mNoteId为键 - startActivity(intent); - //开始动作 - break; - default: - //这是确定操作 - break; - } - } - - public void onDismiss(DialogInterface dialog) { - //忽略 - stopAlarmSound(); - //停止闹钟声音 - finish(); - //完成该动作 - } - - private void stopAlarmSound() { - if (mPlayer != null) { - mPlayer.stop(); - //停止播放 - mPlayer.release(); - //释放MediaPlayer对象 - mPlayer = null; - } - } -} diff --git a/src/ui/AlarmInitReceiver.java b/src/ui/AlarmInitReceiver.java deleted file mode 100644 index bbeae00..0000000 --- a/src/ui/AlarmInitReceiver.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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 - }; - //对数据库的操作,调用标签ID和闹钟时间 - 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(); - //System.currentTimeMillis()产生一个当前的毫秒 - //这个毫秒其实就是自1970年1月1日0时起的毫秒数 - Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, - PROJECTION, - NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, - new String[] { String.valueOf(currentDate) }, - //将long变量currentDate转化为字符串 - null); - //Cursor在这里的作用是通过查找数据库中的标签内容,找到和当前系统时间相等的标签 - - 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(); - } - //然而通过网上查找资料发现,对于闹钟机制的启动,通常需要上面的几个步骤 - //如新建Intent、PendingIntent以及AlarmManager等 - //这里就是根据数据库里的闹钟时间创建一个闹钟机制 - } -} diff --git a/src/ui/AlarmReceiver.java b/src/ui/AlarmReceiver.java deleted file mode 100644 index 814fec2..0000000 --- a/src/ui/AlarmReceiver.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * 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); - //启动AlarmAlertActivity - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - //activity要存在于activity的栈中,而非activity的途径启动activity时必然不存在一个activity的栈 - //所以要新起一个栈装入启动的activity - context.startActivity(intent); - } -} -//这是实现alarm这个功能最接近用户层的包,基于上面的两个包, -//作用还需要深究但是对于setClass和addFlags的 diff --git a/src/ui/DateTimePicker.java b/src/ui/DateTimePicker.java deleted file mode 100644 index f917268..0000000 --- a/src/ui/DateTimePicker.java +++ /dev/null @@ -1,508 +0,0 @@ -/* - * 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;//将当前的 Java 文件放置在 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 { - //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; - //NumberPicker是数字选择器 - //这里定义的四个变量全部是在设置闹钟时需要选择的变量(如日期、时、分、上午或者下午) - private Calendar mDate; - //定义了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(); - } - };//OnValueChangeListener,这是时间改变监听器,这里主要是对日期的监听 - //将现在日期的值传递给mDate;updateDateControl是同步操作 - - private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { - //这里是对 小时(Hour) 的监听 - @Override - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - boolean isDateChanged = false; - Calendar cal = Calendar.getInstance(); - //声明一个Calendar的变量cal,便于后续的操作 - 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; - //这里是对于12小时制时,晚上11点和12点交替时对日期的更改 - } 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; - } - //这里是对于12小时制时,凌晨11点和12点交替时对日期的更改 - 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(); - }//这里是对于12小时制时,中午11点和12点交替时对AM和PM的更改 - } else { - if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { - cal.setTimeInMillis(mDate.getTimeInMillis()); - cal.add(Calendar.DAY_OF_YEAR, 1); - isDateChanged = true; - //这里是对于24小时制时,晚上11点和12点交替时对日期的更改 - } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { - cal.setTimeInMillis(mDate.getTimeInMillis()); - cal.add(Calendar.DAY_OF_YEAR, -1); - isDateChanged = true; - } - } //这里是对于12小时制时,凌晨11点和12点交替时对日期的更改 - int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); - //通过数字选择器对newHour的赋值 - mDate.set(Calendar.HOUR_OF_DAY, newHour); - //通过set函数将新的Hour值传给mDate - 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 - //这里是对 分钟(Minute)改变的监听 - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - int minValue = mMinuteSpinner.getMinValue(); - int maxValue = mMinuteSpinner.getMaxValue(); - int offset = 0; - //设置offset,作为小时改变的一个记录数据 - if (oldVal == maxValue && newVal == minValue) { - offset += 1; - } else if (oldVal == minValue && newVal == maxValue) { - offset -= 1; - } - //如果原值为59,新值为0,则offset加1 - //如果原值为0,新值为59,则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() { - //对AM和PM的监听 - @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)); - }//上面函数的得到的是一个天文数字(1970至今的秒数),需要DateFormat将其变得有意义 - - 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); - //如果当前Activity里用到别的layout,比如对话框layout - //还要设置这个layout上的其他组件的内容,就必须用inflate()方法先将对话框的layout找出来 - //然后再用findViewById()找到它上面的其它组件 - 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; - } - //存在疑问!!!!!!!!!!!!!setEnabled的作用 - //下面的代码通过原程序的注释已经比较清晰,另外可以通过函数名来判断 - //下面的各函数主要是对上面代码引用到的各函数功能的实现 - @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)); - }//实现函数功能——设置当前的时间,参数是date - - /** - * 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 - */ - //下面是得到year、month、day等值 - 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/ui/DateTimePickerDialog.java b/src/ui/DateTimePickerDialog.java deleted file mode 100644 index 0bf2f00..0000000 --- a/src/ui/DateTimePickerDialog.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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(); - //创建一个Calendar类型的变量 mDate,方便时间的操作 - private boolean mIs24HourView; - private OnDateTimeSetListener mOnDateTimeSetListener; - //声明一个时间日期滚动选择控件 mOnDateTimeSetListener - private DateTimePicker mDateTimePicker; - //DateTimePicker控件,控件一般用于让用户可以从日期列表中选择单个值。 - //运行时,单击控件边上的下拉箭头,会显示为两个部分:一个下拉列表,一个用于选择日期的 - - 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); - //将秒数设置为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)); - }//android开发中常见日期管理工具类(API)——DateUtils:按照上下午显示时间 - - public void onClick(DialogInterface arg0, int arg1) { - if (mOnDateTimeSetListener != null) { - mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); - } - }//第一个参数arg0是接收到点击事件的对话框 - //第二个参数arg1是该对话框上的按钮 - -} \ No newline at end of file diff --git a/src/ui/DropdownMenu.java b/src/ui/DropdownMenu.java deleted file mode 100644 index 3e7386c..0000000 --- a/src/ui/DropdownMenu.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import android.content.Context; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.PopupMenu; -import android.widget.PopupMenu.OnMenuItemClickListener; - -import net.micode.notes.R; - -public class DropdownMenu { - private Button mButton; - private PopupMenu mPopupMenu; - //声明一个下拉菜单 - private Menu mMenu; - - public DropdownMenu(Context context, Button button, int menuId) { - mButton = button; - mButton.setBackgroundResource(R.drawable.dropdown_icon); - //设置这个view的背景 - mPopupMenu = new PopupMenu(context, mButton); - mMenu = mPopupMenu.getMenu(); - mPopupMenu.getMenuInflater().inflate(menuId, mMenu); - //MenuInflater是用来实例化Menu目录下的Menu布局文件 - //根据ID来确认menu的内容选项 - mButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - mPopupMenu.show(); - } - }); - } - - public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { - if (mPopupMenu != null) { - mPopupMenu.setOnMenuItemClickListener(listener); - }//设置菜单的监听 - } - - public MenuItem findItem(int id) { - return mMenu.findItem(id); - }//对于菜单选项的初始化,根据索引搜索菜单需要的选项 - - public void setTitle(CharSequence title) { - mButton.setText(title); - }//布局文件,设置标题 -} diff --git a/src/ui/FoldersListAdapter.java b/src/ui/FoldersListAdapter.java deleted file mode 100644 index fa714df..0000000 --- a/src/ui/FoldersListAdapter.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 { - //CursorAdapter是Cursor和ListView的接口 - //FoldersListAdapter继承了CursorAdapter的类 - //主要作用是便签数据库和用户的交互 - //这里就是用folder(文件夹)的形式展现给用户 - public static final String [] PROJECTION = { - NoteColumns.ID, - NoteColumns.SNIPPET - };//调用数据库中便签的ID和片段 - - 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) { - //ViewGroup是容器 - 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); - }//根据数据库中标签的ID得到标签的各项内容 - - 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/ui/NoteEditActivity.java b/src/ui/NoteEditActivity.java deleted file mode 100644 index 6dc52b0..0000000 --- a/src/ui/NoteEditActivity.java +++ /dev/null @@ -1,1068 +0,0 @@ -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.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; -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; - } - //使用Map实现数据存储 - 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); - //put函数是将指定值和指定键相连 - } - - 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); - //put函数是将指定值和指定键相连 - } - - 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); - //put函数是将指定值和指定键相连 - } - - 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); - //put函数是将指定值和指定键相连 - } - - private static final String TAG = "NoteEditActivity"; - - private HeadViewHolder mNoteHeaderHolder; - - private View mHeadViewPanel; - //私有化一个界面操作mHeadViewPanel,对表头的操作 - private View mNoteBgColorSelector; - //私有化一个界面操作mNoteBgColorSelector,对背景颜色的操作 - private View mFontSizeSelector; - //私有化一个界面操作mFontSizeSelector,对标签字体的操作 - private NoteEditText mNoteEditor; - //声明编辑控件,对文本操作 - private View mNoteEditorPanel; - //私有化一个界面操作mNoteEditorPanel,文本编辑的控制板 - //private WorkingNote mWorkingNote; - public WorkingNote mWorkingNote; - //对模板WorkingNote的初始化 - private SharedPreferences mSharedPrefs; - //私有化SharedPreferences的数据存储方式 - //它的本质是基于XML文件存储key-value键值对数据 - 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 = ""; - //如果用户实例化标签时,系统并未给出标签ID - /** - * Starting from the searched result - */ - //根据键值查找ID - if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { - noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); - mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); - } - //如果ID在数据库中未找到 - if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { - Intent jump = new Intent(this, NotesListActivity.class); - startActivity(jump); - //程序将跳转到上面声明的intent——jump - showToast(R.string.error_note_not_exist); - finish(); - return false; - } - //ID在数据库中找到 - else { - mWorkingNote = WorkingNote.load(this, noteId); - if (mWorkingNote == null) { - Log.e(TAG, "load note failed with note id" + noteId); - //打印出红色的错误信息 - finish(); - return false; - } - } - //setSoftInputMode——软键盘输入模式 - getWindow().setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN - | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); - } else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { - // intent.getAction() - // 大多用于broadcast发送广播时给机制(intent)设置一个action,就是一个字符串 - // 用户可以通过receive(接受)intent,通过 getAction得到的字符串,来决定做什么 - 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)); - // intent.getInt(Long、String)Extra是对各变量的语法分析 - // 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); - }//创建一个新的WorkingNote - - getWindow().setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN - | 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 - //MotionEvent是对屏幕触控的传递机制 - 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())) - //如果触控的位置超出了给定的范围,返回false - { - 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 = (NoteEditText) 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); - mNoteEditor.setOnTextViewChangeListener(this); - } - - @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); - // MenuInflater是用来实例化Menu目录下的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()) { - //根据菜单的id来编剧相关项目 - 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)); - // 设置标签的标题为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(); - } - }); - //添加“YES”按钮 - builder.setNegativeButton(android.R.string.cancel, null); - //添加“NO”的按钮 - builder.show(); - //显示对话框 - break; - case R.id.menu_font_size: - //字体大小的编辑 - mFontSizeSelector.setVisibility(View.VISIBLE); - // 将字体选择器置为可见 - findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); - // 通过id找到相应的大小 - 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()); - // 用sendto函数将运行文本发送到遍历的本文内 - 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链接选项 - intent.putExtra(Intent.EXTRA_TEXT, info); - //将需要传递的便签信息放入text文件中 - intent.setType("text/plain"); - //编辑连接器的类型 - context.startActivity(intent); - //在acti中进行链接 - } - - /* - * 函数功能:创建一个新的便签 - * 函数实现:如下注释 - */ - 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()); - //将运行便签的id添加到INTENT_EXTRA_FOLDER_ID标记中 - startActivity(intent); - //开始activity并链接 - } - - /* - * 函数功能:删除当前便签 - * 函数实现:如下注释 - */ - private void deleteCurrentNote() { - if (mWorkingNote.existInDatabase()) { - //假如当前运行的便签内存有数据 - HashSet ids = new HashSet(); - long id = mWorkingNote.getNoteId(); - if (id != Notes.ID_ROOT_FOLDER) { - ids.add(id); - //如果不是头文件夹建立一个hash表把便签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); - //将这些标签的删除标记置为true - } - - /* - * 函数功能:判断是否为同步模式 - * 函数实现:直接看NotesPreferenceActivity中同步名称是否为空 - */ - 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())); - //若有有运行的便签就是建立一个链接器将标签id都存在uri中 - 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); - } - } - - /* - * 函数功能:Widget发生改变的所触发的事件 - */ - public void onWidgetChanged() { - updateWidget();//更新Widget - } - - /* - * 函数功能: 删除编辑文本框所触发的事件 - * 函数实现:如下注释 - */ - 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); - //通过id把编辑框存在便签编辑框中 - } - - 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); - } - //通过id把编辑框存在空的NoteEditText中 - int length = edit.length(); - edit.append(text); - edit.requestFocus();//请求优先完成该此 编辑 - edit.setSelection(length);//定位到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); - //建立一个状态机检查Pattern并进行匹配 - 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; - //初始化check标记 - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - // 若模式为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; - //扩展字符串为已打钩并把标记置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(); - //运行 getWorkingText()之后保存 - 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 - */ - //如英文注释所说链接RESULT_OK是为了识别保存的2种情况,一是创建后保存,二是修改后保存 - 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"); - //设置sneder的行为是发送 - 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; - //直接设置为content中的内容并返回,有勾选和未勾选2种 - } - - /* - * 函数功能:显示提示的视图 - * 函数实现:根据下标显示对应的提示 - */ - private void showToast(int resId) { - showToast(resId, Toast.LENGTH_SHORT); - } - - /* - * 函数功能:持续显示提示的视图 - * 函数实现:根据下标和持续的时间(duration)编辑提示视图并显示 - */ - private void showToast(int resId, int duration) { - Toast.makeText(this, resId, duration).show(); - } -} \ No newline at end of file diff --git a/src/ui/NoteEditText.java b/src/ui/NoteEditText.java deleted file mode 100644 index 890ebec..0000000 --- a/src/ui/NoteEditText.java +++ /dev/null @@ -1,278 +0,0 @@ -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; - -//继承edittext,设置便签设置文本框 -public class NoteEditText extends androidx.appcompat.widget.AppCompatEditText { - 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:" ; - - ///建立一个字符和整数的hash表,用于链接电话,网站,还有邮箱 - 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 - */ - //在NoteEditActivity中删除或添加文本的操作,可以看做是一个文本是否被变的标记,英文注释已说明的很清楚 - 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; - - //根据context设置文本 - public NoteEditText(Context context) { - super(context, null);//用super引用父类变量 - mIndex = 0; - } - - //设置当前光标 - public void setIndex(int index) { - mIndex = index; - } - - //初始化文本修改标记 - public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { - mOnTextViewChangeListener = listener; - } - - //AttributeSet 百度了一下是自定义空控件属性,用于维护便签动态变化的属性 - //初始化便签 - public NoteEditText(Context context, AttributeSet attrs) { - super(context, attrs, android.R.attr.editTextStyle); - } - - // 根据defstyle自动初始化 - public NoteEditText(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - // TODO Auto-generated construct or stub - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - if (isInEditMode()) { - break; - } - int x = (int) event.getX(); - int y = (int) event.getY(); - x -= getTotalPaddingLeft(); - y -= getTotalPaddingTop(); - x += getScrollX(); - y += getScrollY(); - - Layout layout = getLayout(); - if (layout != null) { - 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) { - //根据按键的 Unicode 编码值来处理 - case KeyEvent.KEYCODE_ENTER: - //“进入”按键 - // 检查是否是输入法的确认操作,如果是则不拦截 - if (mOnTextViewChangeListener != null) { - if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) == 0 && !isInEditMode()) { - // 允许事件正常传递给父类 - return super.onKeyDown(keyCode, event); - } - } - 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) { - //根据按键的 Unicode 编码值来处理,有删除和进入2种操作 - case KeyEvent.KEYCODE_DEL: - if (mOnTextViewChangeListener != null) { - //若是被修改过 - if (0 == mSelectionStartBeforeDelete && mIndex != 0) { - //若之前有被修改并且文档不为空 - mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); - //利用上文OnTextViewChangeListener对KEYCODE_DEL按键情况的删除函数进行删除 - return true; - } - } else { - Log.d(TAG, "OnTextViewChangeListener was not seted"); - //其他情况报错,文档的改动监听器并没有建立 - } - break; - case KeyEvent.KEYCODE_ENTER: - //同上也是分为监听器是否建立2种情况 - if (mOnTextViewChangeListener != null) { - // 检查是否是输入法的确认操作,如果是则不拦截 - // 同时检查是否有文本输入,避免拦截中文输入法选择候选词的操作 - if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) == 0 && !isInEditMode() && event.getAction() == KeyEvent.ACTION_UP) { - // 只有当没有输入文本时才执行换行操作 - if (TextUtils.isEmpty(getText())) { - int selectionStart = getSelectionStart(); - //获取当前位置 - String text = getText().subSequence(selectionStart, length()).toString(); - //获取当前文本 - setText(getText().subSequence(0, selectionStart)); - //根据获取的文本设置当前文本 - mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); - //当{@link KeyEvent#KEYCODE_ENTER}添加新文本 - return true; - } - } - } else { - Log.d(TAG, "OnTextViewChangeListener was not seted"); - //其他情况报错,文档的改动监听器并没有建立 - } - break; - default: - break; - } - //继续执行父类的其他按键弹起的事件 - return super.onKeyUp(keyCode, event); - } - - @Override - /* - * 函数功能:当焦点发生变化时,会自动调用该方法来处理焦点改变的事件 - * 实现方式:如下注释 - * 参数:focused表示触发该事件的View是否获得了焦点,当该控件获得焦点时,Focused等于true,否则等于false。 - direction表示焦点移动的方向,用数值表示 - Rect:表示在触发事件的View的坐标系中,前一个获得焦点的矩形区域,即表示焦点是从哪里来的。如果不可用则为null - */ - protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { - if (mOnTextViewChangeListener != null) { - //若监听器已经建立 - if (!focused && TextUtils.isEmpty(getText())) { - //获取到焦点并且文本不为空 - mOnTextViewChangeListener.onTextChange(mIndex, false); - //mOnTextViewChangeListener子函数,置false隐藏事件选项 - } else { - mOnTextViewChangeListener.onTextChange(mIndex, true); - //mOnTextViewChangeListener子函数,置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); - //设置url的信息的范围值 - if (urls.length == 1) { - int defaultResId = 0; - for(String schema: sSchemaActionResMap.keySet()) { - //获取计划表中所有的key值 - if(urls[0].getURL().indexOf(schema) >= 0) { - //若url可以添加则在添加后将defaultResId置为key所映射的值 - defaultResId = sSchemaActionResMap.get(schema); - break; - } - } - - if (defaultResId == 0) { - //defaultResId == 0则说明url并没有添加任何东西,所以置为连接其他SchemaActionResMap的值 - 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); - } -} \ No newline at end of file diff --git a/src/ui/NoteItemData.java b/src/ui/NoteItemData.java deleted file mode 100644 index 9dde13c..0000000 --- a/src/ui/NoteItemData.java +++ /dev/null @@ -1,214 +0,0 @@ -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; - //初始化NoteItemData,主要利用光标cursor获取的东西 - public NoteItemData(Context context, Cursor cursor) { - //getxxx为转换格式 - 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)) {//mphonenumber里有符合字符串,则用contart功能连接 - mName = Contact.getContact(context, mPhoneNumber); - if (mName == null) { - mName = mPhoneNumber; - } - } - } - - if (mName == null) { - mName = ""; - } - checkPostion(cursor); - } - ///根据鼠标的位置设置标记,和位置 - private void checkPostion(Cursor cursor) { - //初始化几个标记,cursor具体功能笔记中已提到,不一一叙述 - mIsLastItem = cursor.isLast() ? true : false; - mIsFirstItem = cursor.isFirst() ? true : false; - mIsOnlyOneItem = (cursor.getCount() == 1); - //初始化“多重子文件”“单一子文件”2个标记 - mIsMultiNotesFollowingFolder = false; - mIsOneNoteFollowingFolder = false; - - //主要是设置上诉2标记 - if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {//若是note格式并且不是第一个元素 - int position = cursor.getPosition(); - if (cursor.moveToPrevious()) {//获取光标位置后看上一行 - if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER - || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {//若光标满足系统或note格式 - if (cursor.getCount() > (position + 1)) { - mIsMultiNotesFollowingFolder = true;//若是数据行数大于但前位置+1则设置成正确 - } else { - mIsOneNoteFollowingFolder = true;//否则单一文件夹标记为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); - } - - //若数据父id为保存至文件夹模式的id且满足电话号码单元不为空,则isCallRecord为true - public boolean isCallRecord() { - return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); - } - - public static int getNoteType(Cursor cursor) { - return cursor.getInt(TYPE_COLUMN); - } -} \ No newline at end of file diff --git a/src/ui/NotesListActivity.java b/src/ui/NotesListActivity.java deleted file mode 100644 index 0813597..0000000 --- a/src/ui/NotesListActivity.java +++ /dev/null @@ -1,1002 +0,0 @@ -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; -//主界面,一进入就是这个界面 -/** - * @author k - * - */ -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"; //单行超过80个字符 - - 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(final Bundle savedInstanceState) { //需要是final类型 根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变:设计或效率。 - // final类不能被继承,没有子类,final类中的方法默认是final的。 - //final方法不能被子类的方法覆盖,但可以被继承。 - //final成员变量表示常量,只能被赋值一次,赋值后值不再改变。 - //final不能用于修饰构造方法。 - super.onCreate(savedInstanceState); // 调用父类的onCreate函数 - setContentView(R.layout.note_list); - initResources(); - - /** - * Insert an introduction when user firstly use this application - */ - setAppInfoFromRawRes(); - } - - @Override - // 返回一些子模块完成的数据交给主Activity处理 - 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); - // 调用 Activity 的onActivityResult() - } - } - - private void setAppInfoFromRawRes() { - // Android平台给我们提供了一个SharedPreferences类,它是一个轻量级的存储类,特别适合用于保存软件配置参数。 - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); - if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { - StringBuilder sb = new StringBuilder(); - InputStream in = null; - try { - // 把资源文件放到应用程序的/raw/raw下,那么就可以在应用中使用getResources获取资源后, - // 以openRawResource方法(不带后缀的资源文件名)打开这个文件。 - 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 - 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()) { - // 更新保存note的信息 - 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; - - // findViewById 是安卓编程的定位函数,主要是引用.R文件里的引用名 - mNotesListView = (ListView) findViewById(R.id.notes_list); // 绑定XML中的ListView,作为Item的容器 - 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);// 在activity中要获取该按钮 - 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(); - } - - // 继承自ListView.MultiChoiceModeListener 和 OnMenuItemClickListener - 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(final 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锛圲nit: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 - - } - }); - } - - /* (non-Javadoc) - * @see android.app.Activity#onBackPressed() - * 按返回键时根据情况更改类中的数据 - */ - @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; - } - } - - /** - * @param appWidgetId - * @param appWidgetType - * 根据不同类型的widget更新插件,通过intent传送数据 - */ - 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); - } - - /* (non-Javadoc) - * @see android.app.Activity#onContextItemSelected(android.view.MenuItem) - * 针对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; - } - - /* (non-Javadoc) - * @see android.app.Activity#onSearchRequested() - * 直接调用startSearch函数 - */ - @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(); - } - - /** - * @return - * 功能:判断是否正在同步 - */ - private boolean isSyncMode() { - return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; - } - - /** - * 功能:跳转到PreferenceActivity界面 - */ - private void startPreferenceActivity() { - Activity from = getParent() != null ? getParent() : this; - Intent intent = new Intent(from, NotesPreferenceActivity.class); - from.startActivityIfNeeded(intent, -1); - } - - /** - * @author k - * 函数功能:实现对便签列表项的点击事件(短按) - */ - 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"); - } - - /* (non-Javadoc) - * @see android.widget.AdapterView.OnItemLongClickListener#onItemLongClick(android.widget.AdapterView, android.view.View, int, long) - * 长按某一项时进行的操作 - * 如果长按的是便签,则通过ActionMode菜单实现;如果长按的是文件夹,则通过ContextMenu菜单实现; - * 具体ActionMOde菜单和ContextMenu菜单的详细见精度笔记 - */ - 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; - } -} \ No newline at end of file diff --git a/src/ui/NotesListAdapter.java b/src/ui/NotesListAdapter.java deleted file mode 100644 index b165769..0000000 --- a/src/ui/NotesListAdapter.java +++ /dev/null @@ -1,257 +0,0 @@ -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; - - -/* - * 功能:直译为便签表连接器,继承了CursorAdapter,它为cursor和ListView提供了连接的桥梁。 - * 所以NotesListAdapter实现的是鼠标和编辑便签链接的桥梁 - */ -public class NotesListAdapter extends CursorAdapter { - private static final String TAG = "NotesListAdapter"; - private Context mContext; - private HashMap mSelectedIndex; - private int mNotesCount; //便签数 - private boolean mChoiceMode; //选择模式标记 - - /* - * 桌面widget的属性,包括编号和类型 - */ - public static class AppWidgetAttribute { - public int widgetId; - public int widgetType; - }; - - /* - * 函数功能:初始化便签链接器 - * 函数实现:根据传进来的内容设置相关变量 - */ - public NotesListAdapter(Context context) { - super(context, null); //父类对象置空 - mSelectedIndex = new HashMap(); //新建选项下标的hash表 - mContext = context; - mNotesCount = 0; - } - - @Override - /* - * 函数功能:新建一个视图来存储光标所指向的数据 - * 函数实现:使用兄弟类NotesListItem新建一个项目选项 - */ - 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) { - //若view是NotesListItem的一个实例 - NoteItemData itemData = new NoteItemData(context, cursor); - ((NotesListItem) view).bind(context, itemData, mChoiceMode, - isSelectedItem(cursor.getPosition())); - //则新建一个项目选项并且用bind跟将view和鼠标,内容,便签数据捆绑在一起 - } - } - - /* - * 函数功能:设置勾选框 - * 函数实现:如下注释 - */ - public void setCheckedItem(final int position, final boolean checked) { - mSelectedIndex.put(position, checked); - //根据定位和是否勾选设置下标 - notifyDataSetChanged(); - //在修改后刷新activity - } - - /* - * 函数功能:判断单选按钮是否勾选 - */ - public boolean isInChoiceMode() { - return mChoiceMode; - } - - /* - * 函数功能:设置单项选项框 - * 函数实现:重置下标并且根据参数mode设置选项 - */ - 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(); - //建立hash表 - 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); - } - //则将id该下标假如选项集合中 - - } - } - - return itemSet; - } - - /* - * 函数功能:建立桌面Widget的选项表 - * 函数实现:如下注释 - */ - public HashSet getSelectedWidget() { - HashSet itemSet = new HashSet(); - for (Integer position : mSelectedIndex.keySet()) { - if (mSelectedIndex.get(position) == true) { - Cursor c = (Cursor) getItem(position); - //以上4句和getSelectedItemIds一样,不再重复 - if (c != null) { - //光标位置可用的话就建立新的Widget属性并编辑下标和类型,最后添加到选项集中 - 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()) { - //若value值为真计数+1 - 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 - /* - * 函数功能:在activity内容发生局部变动的时候回调该函数计算便签的数量 - * 函数实现:如下注释 - */ - protected void onContentChanged() { - super.onContentChanged(); - //执行基类函数 - calcNotesCount(); - } - - @Override - /* - * 函数功能:在activity光标发生局部变动的时候回调该函数计算便签的数量 - */ - 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++; - //若该位置不为空并且文本类型为便签就+1 - } - } else { - Log.e(TAG, "Invalid cursor"); - return; - } - //否则报错 - } - } -} \ No newline at end of file diff --git a/src/ui/NotesListItem.java b/src/ui/NotesListItem.java deleted file mode 100644 index fa8b3d2..0000000 --- a/src/ui/NotesListItem.java +++ /dev/null @@ -1,116 +0,0 @@ -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); //super()它的主要作用是调整调用父类构造函数的顺序 - inflate(context, R.layout.note_item, this);//Inflate可用于将一个xml中定义的布局控件找出来,这里的xml是r。layout - //findViewById用于从contentView中查找指定ID的View,转换出来的形式根据需要而定; - 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); - } - ///根据data的属性对各个控件的属性的控制,主要是可见性Visibility,内容setText,格式setTextAppearance - 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; - ///设置控件属性,一共三种情况,由data的id和父id是否与保存到文件夹的id一致来决定 - if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { - mCallName.setVisibility(View.GONE); - mAlert.setVisibility(View.VISIBLE); - //设置该textview的style - mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); - //settext为设置内容 - 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); - ///设置title格式 - 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); - } - } - } - ///设置内容,获取相关时间,从data里编辑的日期中获取 - mTime. setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); - - setBackground(data); - } - //根据data的文件属性来设置背景 - private void setBackground(NoteItemData data) { - int id = data.getBgColorId(); - //,若是note型文件,则4种情况,对于4种不同情况的背景来源 - 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 { - //若不是note直接调用文件夹的背景来源 - setBackgroundResource(NoteItemBgResources.getFolderBgRes()); - } - } - public NoteItemData getItemData() { - return mItemData; - } -} \ No newline at end of file diff --git a/src/ui/NotesPreferenceActivity.java b/src/ui/NotesPreferenceActivity.java deleted file mode 100644 index 3a1a3de..0000000 --- a/src/ui/NotesPreferenceActivity.java +++ /dev/null @@ -1,516 +0,0 @@ -package net.micode.notes.ui; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.annotation.SuppressLint; -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; - -/* - *该类功能:NotesPreferenceActivity,在小米便签中主要实现的是对背景颜色和字体大小的数据储存。 - * 继承了PreferenceActivity主要功能为对系统信息和配置进行自动保存的Activity - */ -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; - //账户的hash标记 - - @SuppressLint("UnspecifiedRegisterReceiverFlag") - @Override - /* - *函数功能:创建一个activity,在函数里要完成所有的正常静态设置 - *参数:Bundle icicle:存放了 activity 当前的状态 - *函数实现:如下注释 - */ - protected void onCreate(Bundle icicle) { - //先执行父类的创建函数 - super.onCreate(icicle); - - /* using the app icon for navigation */ - getActionBar().setDisplayHomeAsUpEnabled(true); - //给左上角图标的左边加上一个返回的图标 - - addPreferencesFromResource(R.xml.preferences); - //添加xml来源并显示 xml - 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); - //获取listvivew,ListView的作用:用于列出所有选择 - getListView().addHeaderView(header, null, true); - //在listview组件上方添加其他组件 - } - - @Override - /* - * 函数功能:activity交互功能的实现,用于接受用户的输入 - * 函数实现:如下注释 - */ - protected void onResume() { - //先执行父类 的交互实现 - super.onResume(); - - // need to set sync account automatically if user has added a new - // account - if (mHasAddedAccount) { - //若用户新加了账户则自动设置同步账户 - Account[] accounts = getGoogleAccounts(); - //获取google同步账户 - 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 - /* - * 函数功能:销毁一个activity - * 函数实现:如下注释 - */ - 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中显示不能修改 - 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); - //设置对话框的自定义标题,建立一个YES的按钮 - 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; - //将新加账户的hash置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() { - //设置对话框要显示的一个list,用于显示几个命令时,即change,remove,cancel - public void onClick(DialogInterface dialog, int which) { - //按键功能,由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(); - //提交修改的数据 - - - 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(); - //将toast的文本信息置为“设置账户成功”并显示出来 - } - } - /* - * 函数功能:删除同步账户 - * 函数实现:如下注释: - */ - 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); - } - - /* - * 函数功能:接受同步信息 - * 函数实现:继承BroadcastReceiver - */ - private class GTaskReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - refreshUI(); - if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { - //获取随广播而来的Intent中的同步服务的数据 - TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); - syncStatus.setText(intent - .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); - //通过获取的数据在设置系统的状态 - } - - } - } - - /* - * 函数功能:处理菜单的选项 - * 函数实现:如下注释 - * 参数:MenuItem菜单选项 - */ - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - //根据选项的id选择,这里只有一个主页 - case android.R.id.home: - Intent intent = new Intent(this, NotesListActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - //在主页情况下在创建连接组件intent,发出清空的信号并开始一个相应的activity - default: - return false; - } - } -} - \ No newline at end of file diff --git a/src/widget/NoteWidgetProvider.java b/src/widget/NoteWidgetProvider.java deleted file mode 100644 index 4bae085..0000000 --- a/src/widget/NoteWidgetProvider.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * 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; //隶属于widget这个包中 -import android.app.PendingIntent; //在Android.app包中的,用于提供暂缓的服务,当某种条件达到时,就可以执行某些活动 -import android.appwidget.AppWidgetManager; //继承object类,更新appwidget状态,和获取安装appwidget提供信息,以及其它相关状态信息。 -import android.appwidget.AppWidgetProvider; //本质是广播作用,用来对widget进行更新 -import android.content.ContentValues; //此类用于存储ContentResolver 可以处理的一组值,是一些基本类型的值 -import android.content.Context; //可以理解为一个接口(抽象类),它提供了有关应用程序的全局信息。从它继承或者从它的直接或间接子类继承,那么继承的子类就可以拿到应用程序的全局信息。 -import android.content.Intent; //联系各个Activity的关键对象。告诉android该做什么 -import android.database.Cursor; //是数据库指针,用于查询并操作数据 -import android.util.Log; //更新删改等的日志文件 -import android.widget.RemoteViews; //它表示的是一个View结构,可以在其他进程中显示,小部件的旋转效果则是通过不断地更新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; //引入ui包中的编辑笔记类 -import net.micode.notes.ui.NotesListActivity; //引入ui包中的列表活动类 - -public abstract class NoteWidgetProvider extends AppWidgetProvider { //继承AppWidgetProvider的抽象类NoteWidgetProvider,可以给子类提供接口 - public static final String [] PROJECTION = new String [] { //最终静态字符串数组 - NoteColumns.ID, //笔记框ID号 - NoteColumns.BG_COLOR_ID, //笔记框颜色ID - NoteColumns.SNIPPET //一小段笔记框 - }; - - public static final int COLUMN_ID = 0; //最终静态笔记框ID号 - public static final int COLUMN_BG_COLOR_ID = 1; //最终静态笔记框颜色ID - public static final int COLUMN_SNIPPET = 2; //最终静态一小段笔记框 - - private static final String TAG = "NoteWidgetProvider"; //最终静态 TAG - - @Override //重写 - public void onDeleted(Context context, int[] appWidgetIds) { //方法,删除部件 - ContentValues values = new ContentValues(); //和Bundle类一样,键值对 - values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); //通过put来存放数据。大多是基础数据类型 - for (int i = 0; i < appWidgetIds.length; i++) { - context.getContentResolver().update(Notes.CONTENT_NOTE_URI, //根据长度批量修改数据,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, //查找数据,并返回含有数据信息的cursor对象。 - PROJECTION, //参数为 uri 查询表名,查询列名,属性,属性域值,排序 - 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); //意图,从context到NoteEditActivity类的意图 - 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); //隐式intent,设置意图,参数为向用户展示意图。 - } else { - snippet = context.getResources().getString(R.string.widget_havenot_content); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); //隐式intent,设置联系人 - } - - if (c != null) { //关闭 - c.close(); - } - - RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); //布局文件layout的远程视图 - rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); //设置图像资源,从布局文件layout中找到ID - 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( //开始意图启动Activity - context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); //上下文,请求码,跳转对象, - } else { //FLAG_UPDATE_CURRENT当前描述的PendingIntent如果已经存在,那么它们都会被更新,即它们的Intent中的Extras会被替换成最新的 - rv.setTextViewText(R.id.widget_text, snippet); - pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, //先前的意图 - PendingIntent.FLAG_UPDATE_CURRENT); - } //FLAG_UPDATE_CURRENT当前描述的PendingIntent如果已经存在,那么它们都会被更新,即它们的Intent中的Extras会被替换成最新的 - - rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); //启动点击待定意图 - appWidgetManager.updateAppWidget(appWidgetIds[i], rv); //更新部件 - } - } - } - - protected abstract int getBgResourceId(int bgId); //抽象类获取Bg资源ID - - protected abstract int getLayoutId(); //抽象方法获取布局ID - - protected abstract int getWidgetType(); //获取部件的种类 -} diff --git a/src/widget/NoteWidgetProvider_2x.java b/src/widget/NoteWidgetProvider_2x.java deleted file mode 100644 index 2ddd97f..0000000 --- a/src/widget/NoteWidgetProvider_2x.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 //获取xml中的二倍部件id - protected int getLayoutId() { - return R.layout.widget_2x; - } - - @Override //获取资源id - protected int getBgResourceId(int bgId) { - return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); - } - - @Override //获取二倍部件种类 - protected int getWidgetType() { - return Notes.TYPE_WIDGET_2X; - } -} diff --git a/src/widget/NoteWidgetProvider_4x.java b/src/widget/NoteWidgetProvider_4x.java deleted file mode 100644 index 8156006..0000000 --- a/src/widget/NoteWidgetProvider_4x.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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); - } - //获取四倍部件id - protected int getLayoutId() { - return R.layout.widget_4x; - } - - @Override //获取xml资源id - protected int getBgResourceId(int bgId) { - return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); - } - - @Override //获取四倍部件种类 - protected int getWidgetType() { - return Notes.TYPE_WIDGET_4X; - } -}