From a1dbb2962550414bce2948e267bd5b8dc3af9dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=A0=BC?= <3107280015@qq.com> Date: Thu, 15 May 2025 11:28:00 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Notesmaster/app/.idea/.gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Notesmaster/app/.idea/.gitignore diff --git a/Notesmaster/app/.idea/.gitignore b/Notesmaster/app/.idea/.gitignore new file mode 100644 index 0000000..35410ca --- /dev/null +++ b/Notesmaster/app/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml From 384d4ddd9b1a7275740cbd2a726b48197568a318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=A0=BC?= <3107280015@qq.com> Date: Thu, 29 May 2025 09:03:38 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=9B=BE=E7=89=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Notesmaster/.gitignore | 18 +- Notesmaster/app/src/main/AndroidManifest.xml | 103 +-- .../java/net/micode/notes/data/Contact.java | 38 +- .../java/net/micode/notes/data/Notes.java | 57 +- .../notes/data/NotesDatabaseHelper.java | 128 +--- .../net/micode/notes/data/NotesProvider.java | 119 +--- .../net/micode/notes/gtask/data/MetaData.java | 83 ++- .../net/micode/notes/gtask/data/Node.java | 107 ++- .../net/micode/notes/gtask/data/SqlData.java | 113 ++-- .../net/micode/notes/gtask/data/SqlNote.java | 190 +++--- .../net/micode/notes/gtask/data/Task.java | 158 +++-- .../net/micode/notes/gtask/data/TaskList.java | 168 +++-- .../exception/ActionFailureException.java | 27 +- .../exception/NetworkFailureException.java | 30 +- .../notes/gtask/remote/GTaskASyncTask.java | 86 ++- .../notes/gtask/remote/GTaskClient.java | 353 +++++----- .../notes/gtask/remote/GTaskManager.java | 363 +++++----- .../notes/gtask/remote/GTaskSyncService.java | 69 +- .../net/micode/notes/model/WorkingNote.java | 50 +- .../net/micode/notes/tool/BackupUtils.java | 54 +- .../java/net/micode/notes/tool/DataUtils.java | 137 +++- .../micode/notes/tool/GTaskStringUtils.java | 5 +- .../net/micode/notes/tool/ResourceParser.java | 27 +- .../micode/notes/ui/AlarmAlertActivity.java | 105 +-- .../micode/notes/ui/AlarmInitReceiver.java | 21 +- .../net/micode/notes/ui/AlarmReceiver.java | 19 +- .../net/micode/notes/ui/DateTimePicker.java | 76 ++- .../micode/notes/ui/DateTimePickerDialog.java | 47 +- .../net/micode/notes/ui/DropdownMenu.java | 24 +- .../micode/notes/ui/FoldersListAdapter.java | 18 +- .../net/micode/notes/ui/NoteEditActivity.java | 580 ++++++++++------ .../net/micode/notes/ui/NoteEditText.java | 95 +-- .../net/micode/notes/ui/NoteItemData.java | 46 +- .../micode/notes/ui/NotesListActivity.java | 618 ++++++++++++------ .../net/micode/notes/ui/NotesListAdapter.java | 155 ++++- .../net/micode/notes/ui/NotesListItem.java | 62 +- .../notes/ui/NotesPreferenceActivity.java | 148 ++--- .../notes/widget/NoteWidgetProvider.java | 8 +- .../res/drawable-hdpi/edit_title_green.9.png | Bin 5627 -> 2610 bytes .../app/src/main/res/layout/activity_main.xml | 29 +- .../app/src/main/res/layout/note_edit.xml | 28 +- .../app/src/main/res/layout/note_item.xml | 11 +- .../app/src/main/res/layout/note_list.xml | 16 +- .../src/main/res/values-zh-rCN/strings.xml | 10 +- .../src/main/res/values-zh-rTW/strings.xml | 8 + .../app/src/main/res/values/strings.xml | 18 +- .../app/src/main/res/values/styles.xml | 4 +- Notesmaster/gradle.properties | 24 +- .../gradle/wrapper/gradle-wrapper.properties | 7 +- 49 files changed, 2577 insertions(+), 2083 deletions(-) diff --git a/Notesmaster/.gitignore b/Notesmaster/.gitignore index aa724b7..2a08bc8 100644 --- a/Notesmaster/.gitignore +++ b/Notesmaster/.gitignore @@ -1,15 +1,5 @@ -*.iml -.gradle +/.gradle/ +/.idea/ +/app/build/ +/gradle/wrapper/gradle-wrapper.jar /local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -/build -/captures -.externalNativeBuild -.cxx -local.properties diff --git a/Notesmaster/app/src/main/AndroidManifest.xml b/Notesmaster/app/src/main/AndroidManifest.xml index e061ed5..40fe148 100644 --- a/Notesmaster/app/src/main/AndroidManifest.xml +++ b/Notesmaster/app/src/main/AndroidManifest.xml @@ -1,8 +1,28 @@ + + + + xmlns:tools="http://schemas.android.com/tools" + package="net.micode.notes" + android:versionCode="1" + android:versionName="0.1" > - + + @@ -12,26 +32,36 @@ + + + + + + + + + + + + + - + android:requestLegacyExternalStorage="true" + > + > @@ -40,13 +70,15 @@ + android:theme="@style/Theme.AppCompat.Light" + > - + @@ -70,16 +102,15 @@ android:resource="@xml/searchable" /> - + android:label="@string/app_widget2x2" > @@ -91,9 +122,9 @@ android:resource="@xml/widget_2x_info" /> + android:label="@string/app_widget4x4" > @@ -106,8 +137,7 @@ android:resource="@xml/widget_4x_info" /> - + @@ -120,9 +150,8 @@ + android:theme="@android:style/Theme.Black" > - - + + + + - - - - - - - - - - - - - \ No newline at end of file + diff --git a/Notesmaster/app/src/main/java/net/micode/notes/data/Contact.java b/Notesmaster/app/src/main/java/net/micode/notes/data/Contact.java index a59ae4e..7fd50b9 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/data/Contact.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/data/Contact.java @@ -1,4 +1,19 @@ -//Contact类用于处理联系人信信,实现了从联系人数据库中获取指定电话号码对应的联系人姓名的功能 +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.micode.notes.data; import android.content.Context; @@ -11,15 +26,9 @@ import android.util.Log; import java.util.HashMap; public class Contact { -// 用于处理联系人信息 -// 实现了从联系人数据库中获取指定电话号码对应的联系人姓名的功能 - - //sContactCache:用于缓存电话号码和对应的联系人姓名 - //TAG:用于日志输出的标识 private static HashMap sContactCache; private static final String TAG = "Contact"; - //SQL查询条件( WHERE 后面的语句),用于从联系人数据库中筛选出与给定电话号码匹配的联系人。 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 " @@ -27,10 +36,7 @@ public class Contact { + " FROM phone_lookup" + " WHERE min_match = '+')"; - //功能简介:用于从Android设备的联系人数据库中获取与给定电话号码对应的联系人姓名。 - //参数:Context对象:用于访问系统服务和应用资源 phoneNumber:需要查询的联系人电话号码 public static String getContact(Context context, String phoneNumber) { - // 没映射表就建表,有就查缓存中有没有这个联系人 if(sContactCache == null) { sContactCache = new HashMap(); } @@ -39,9 +45,6 @@ public class Contact { return sContactCache.get(phoneNumber); } - //缓存没有,就查询数据库 - //构造一个SQL查询条件:CALLER_ID_SELECTION中的"+"被替换为电话号码的最小匹配值 - //然后执行查询语句 String selection = CALLER_ID_SELECTION.replace("+", PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); Cursor cursor = context.getContentResolver().query( @@ -51,13 +54,6 @@ public class Contact { new String[] { phoneNumber }, null); - //判断查询结果: - //查询结果不为空,且能够移动到第一条记录: - // 那么就尝试从Cursor中获取联系人姓名,并将其存入缓存sContactCache。然后返回联系人姓名。 - // 异常情况:如果在获取字符串时发生数组越界异常,则记录一个错误日志并返回null。 - // 最后都要确保关闭Cursor对象,以避免内存泄漏。 - //如果查询结果为空或者没有记录可以移动到(即没有找到匹配的联系人): - // 则记录一条调试日志并返回null if (cursor != null && cursor.moveToFirst()) { try { String name = cursor.getString(0); @@ -74,4 +70,4 @@ public class Contact { return null; } } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/data/Notes.java b/Notesmaster/app/src/main/java/net/micode/notes/data/Notes.java index 8ba743e..2e59131 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/data/Notes.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/data/Notes.java @@ -13,18 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -//Notes类是最底层的数据类,它定义了一堆常量,用来表示标签和文件的各种属性,以及Intent的额外数据(如布局、小组件ID)。同时,它还通过接口以及其实现类定义了数据库表的列名(两个表:note表和data表)。 package net.micode.notes.data; import android.net.Uri; public class Notes { -// 用于表示笔记应用中的各种类型、标识符以及Intent的额外数据 public static final String AUTHORITY = "micode_notes"; public static final String TAG = "Notes"; - - //对NoteColumns.TYPE的值进行设置时使用: - //即不同种类:笔记、文件夹和系统文件夹 public static final int TYPE_NOTE = 0; public static final int TYPE_FOLDER = 1; public static final int TYPE_SYSTEM = 2; @@ -35,18 +30,14 @@ public class Notes { * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records */ - //以下id是系统文件夹的标识符(即系统文件夹的分类) - //ID_ROOT_FOLDER:默认文件夹 - //ID_TEMPARAY_FOLDER:不属于文件夹的笔记 - //ID_CALL_RECORD_FOLDER:用于存储通话记录,以便返回 - //ID_TRASH_FOLER:垃圾回收站 public static final int ID_ROOT_FOLDER = 0; public static final int ID_TEMPARAY_FOLDER = -1; public static final int ID_CALL_RECORD_FOLDER = -2; - public static final int ID_TRASH_FOLER = -3; + public static int ID_TRASH_FOLER = 1;//-3; + + public static final String LOCKED = "locked"; + public static final String UNLOCKED = "unlocked"; - // 额外的数据键,个人理解为就是定义一些布局的ID - // 这部分就是用于设置UI界面的一些布局或小组件的id,给它定义成常量了。 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"; @@ -54,16 +45,17 @@ public class Notes { public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; - // 数据常量:里面定义了两种类型:文本便签和通话记录 public static final int TYPE_WIDGET_INVALIDE = -1; public static final int TYPE_WIDGET_2X = 0; public static final int TYPE_WIDGET_4X = 1; - //内容提供者是一种Android组件,它允许应用程序共享和存储数据。这里定义了一个URI来查询数据 public static class DataConstants { public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; } + public static void setID_TRASH_FOLER(int id){ + ID_TRASH_FOLER = id; + } /** * Uri to query all notes and folders @@ -76,11 +68,6 @@ public class Notes { public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); public interface NoteColumns { - // 这个接口定义了一系列静态的、最终的字符串常量,这些常量代表数据库表中的列名。 - // 作用:用于后面创建数据库的表头 - // 总的属性有:ID、父级ID、创建日期、修改日期、提醒日期、文件(标签)名(摘要?)、小部件ID、小部件类型、背景颜色ID、附件、文件中的标签数量、 - // 文件(标签)类型、最后一个同步ID、本地修改标签、移动前的ID、谷歌任务ID、代码版本信息。 - /** * The unique ID for a row *

Type: INTEGER (long)

@@ -184,6 +171,8 @@ public class Notes { *

Type : INTEGER (long)

*/ public static final String VERSION = "version"; + + public static final String LOCKED = "lock_type"; } public interface DataColumns { @@ -191,87 +180,75 @@ public class Notes { * The unique ID for a row *

Type: INTEGER (long)

*/ - - // DataColumns的接口,这个接口包含了一系列静态常量,这些常量代表了数据库表中用于存储数据的列名。 - // 每个常量都有相应的注释,说明该列的作用和数据类型。 public static final String ID = "_id"; /** * The MIME type of the item represented by this row. *

Type: Text

*/ - //MIME类型是一种标准,用于标识文档、文件或字节流的性质和格式。在数据库中,这个字段可以用来识别不同类型的数据,例如文本、图片、音频或视频等。 public static final String MIME_TYPE = "mime_type"; /** * The reference id to note that this data belongs to *

Type: INTEGER (long)

*/ - //归属的Note的ID 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 + * Generic data column, the meaning is specific, used for * integer data type *

Type: INTEGER

*/ - // 以下5个是通用数据列,它们的具体意义取决于MIME类型(由MIME_TYPE字段指定)。 - // 不同的MIME类型可能需要存储不同类型的数据,这五个字段提供了灵活性,允许根据MIME类型来存储相应的数据。 public static final String DATA1 = "data1"; /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * Generic data column, the meaning is 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 + * Generic data column, the meaning is 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 + * Generic data column, the meaning is 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 + * Generic data column, the meaning is specific, used for * TEXT data type *

Type: TEXT

*/ public static final String DATA5 = "data5"; } - //以下是文本便签的定义 public static final class TextNote implements DataColumns { /** * Mode to indicate the text in check list mode or not @@ -301,10 +278,10 @@ public class Notes { */ public static final String PHONE_NUMBER = DATA3; - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";// 定义了MIME类型,用于标识文本标签的目录 + 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";// 定义了MIME类型,用于标识文本标签的目录 + 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");//文本标签内容提供者(Content Provider)的URI,用于访问文本标签数据 + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); } } diff --git a/Notesmaster/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/Notesmaster/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index c723213..7fb3222 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java @@ -28,28 +28,20 @@ import net.micode.notes.data.Notes.NoteColumns; public class NotesDatabaseHelper extends SQLiteOpenHelper { - // 数据库帮助类,用于管理名为 note.db 的 SQLite 数据库。 -// 它继承自 SQLiteOpenHelper 类,这是 Android提供的一个方便的工具类,用于管理数据库的创建和版本更新. - // 数据库的基本信息;数据库名称和版本信息(在创建实例对象时会用到) - private static final String DB_NAME = "note.db"; + private static final String DB_NAME = "note4.db"; private static final int DB_VERSION = 4; - //内部接口:个人理解为两个表名,一个note,一个data public interface TABLE { public static final String NOTE = "note"; public static final String DATA = "data"; } - //一个标签,方便日志输出时识别出信息来自哪里 private static final String TAG = "NotesDatabaseHelper"; - //静态所有变量,提供一个全局访问点来获取数据库辅助类的唯一实例,使得在应用的任何地方都可以方便地使用它 private static NotesDatabaseHelper mInstance; - /* 以下都是一些SQL语句,辅助我们来对数据库进行操作 */ - //创建note表的语句,这里的NoteColumns就是我们刚刚在Notes中定义的一个接口,里面定义了一系列静态的数据库表中的列名 private static final String CREATE_NOTE_TABLE_SQL = "CREATE TABLE " + TABLE.NOTE + "(" + NoteColumns.ID + " INTEGER PRIMARY KEY," + @@ -67,11 +59,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.LOCKED + " TEXT NOT NULL DEFAULT '"+Notes.UNLOCKED+"'," + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + ")"; - //同上,创建data表的语句,这里的DataColumns就是我们刚刚在Notes中定义的一个接口,里面定义了一系列静态的数据库表中的列名 private static final String CREATE_DATA_TABLE_SQL = "CREATE TABLE " + TABLE.DATA + "(" + DataColumns.ID + " INTEGER PRIMARY KEY," + @@ -87,31 +79,13 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + ")"; - // 功能简介: - // 创建一个以note的ID为索引 - // 解读: - // 用于在TABLE.DATA表上创建一个名为note_id_index的索引。 - // 这个索引是基于DataColumns.NOTE_ID列的。IF NOT EXISTS确保了如果索引已经存在,那么就不会尝试重新创建它,避免了可能的错误。 - // 索引通常用于提高查询性能,特别是在对某个字段进行频繁查询时。 private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = "CREATE INDEX IF NOT EXISTS note_id_index ON " + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; - /* 以下是一些对便签增删改定义的触发器 */ - /* 总结 - * 这些触发器都是用来维护NOTE表和与之相关联的DATA表之间数据一致性的。 - * 当在NOTE表中发生删除或更新操作时,这些触发器会自动执行相应的数据清理或更新操作,确保数据库中的数据保持正确和一致。 - * 特别是在处理文件夹和回收站等逻辑时,这些触发器起到了非常重要的作用,可以自动管理数据的移动和删除。*/ /** * Increase folder's note count when move note to the folder */ - // 功能简介: - // 添加触发器:增加文件夹的便签个数记录(因为我们会移动便签进入文件夹,这时候文件夹的计数要进行更新) - // 解读: - // 定义了一个SQL触发器increase_folder_count_on_update。 - // 触发器是一种特殊的存储过程,它会在指定表上的指定事件(如INSERT、UPDATE、DELETE)发生时自动执行。 - // 这个触发器会在TABLE.NOTE表的NoteColumns.PARENT_ID字段更新后执行。 - // 触发器的逻辑是:当某个笔记的PARENT_ID(即父文件夹ID)被更新时,它会找到对应的文件夹(通过新的PARENT_ID),并将该文件夹的NOTES_COUNT(即笔记数)增加1。 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 + @@ -124,8 +98,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * Decrease folder's note count when move note from folder */ - // 功能简介:(触发器和上面的 “增加文件夹的便签个数记录” 同理,就不细节解读了) - // 添加触发器:减少文件夹的便签个数记录(因为我们会移动便签移出文件夹,这时候文件夹的计数要进行更新) private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_update " + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + @@ -139,8 +111,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * Increase folder's note count when insert new note to the folder */ - // 功能简介:(触发器原理和上面的 “增加文件夹的便签个数记录” 同理,就不细节解读了) - // 添加触发器:当我们在文件夹插入便签时,增加文件夹的便签个数记录 private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = "CREATE TRIGGER increase_folder_count_on_insert " + " AFTER INSERT ON " + TABLE.NOTE + @@ -153,8 +123,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * Decrease folder's note count when delete note from the folder */ - // 功能简介:(触发器原理和上面的 “增加文件夹的便签个数记录” 同理,就不细节解读了) - // 添加触发器:当我们在文件夹删除便签时,减少文件夹的便签个数记录 private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_delete " + " AFTER DELETE ON " + TABLE.NOTE + @@ -168,11 +136,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * Update note's content when insert data with type {@link DataConstants#NOTE} */ - // 功能简介: - // 添加触发器:当向DATA表中插入类型为NOTE(便签)的数据时,更新note表对应的笔记内容。 - // 解读: - // 在DATA表上进行INSERT操作后,如果新插入的数据的MIME_TYPE为NOTE,则触发此操作。 - // 它会更新NOTE表,将与新插入数据相关联的标签的SNIPPET(摘要)字段设置为新插入数据的CONTENT字段的值 private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = "CREATE TRIGGER update_note_content_on_insert " + " AFTER INSERT ON " + TABLE.DATA + @@ -186,11 +149,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * Update note's content when data with {@link DataConstants#NOTE} type has changed */ - // 功能简介: - // 添加触发器:当DATA表中,类型为NOTE(便签)的数据更改时,更新note表对应的笔记内容。 - // 解读: - // 在DATA表上进行UPDATE操作后,如果更新前的数据的MIME_TYPE为NOTE,则触发此操作。 - // 它会更新NOTE表,将与更新后的数据相关联的笔记的SNIPPET字段设置为新数据的CONTENT字段的值 private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = "CREATE TRIGGER update_note_content_on_update " + " AFTER UPDATE ON " + TABLE.DATA + @@ -204,11 +162,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * Update note's content when data with {@link DataConstants#NOTE} type has deleted */ - // 功能简介: - // 添加触发器:当DATA表中,类型为NOTE(便签)的数据删除时,更新note表对应的笔记内容(置空)。 - // 解读: - // 在DATA表上进行DELETE操作后,如果删除的数据的MIME_TYPE为NOTE,则触发此操作。 - // 它会更新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 + @@ -222,11 +175,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * Delete datas belong to note which has been deleted */ - // 功能简介: - // 添加触发器:当从NOTE表中删除笔记时,删除与该笔记相关联的数据(就是删除data表中为该note的数据) - // 解读: - // 在NOTE表上进行DELETE操作后,此触发器被激活。 - // 它会从DATA表中删除所有与已删除的笔记(由old.ID表示)相关联的数据行(通过比较DATA表中的NOTE_ID字段与已删除笔记的ID来实现) private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = "CREATE TRIGGER delete_data_on_delete " + " AFTER DELETE ON " + TABLE.NOTE + @@ -238,11 +186,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * Delete notes belong to folder which has been deleted */ - // 功能简介: - // 添加触发器:当从NOTE表中删除一个文件夹时,删除该文件夹下的所有笔记。 - // 解读: - // 在NOTE表上进行DELETE操作后,如果删除的是一个文件夹(由old.ID表示) - // 触发器会删除所有以该文件夹为父级(PARENT_ID)的笔记(通过比较NOTE表中的PARENT_ID字段与已删除文件夹的ID来实现) private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = "CREATE TRIGGER folder_delete_notes_on_delete " + " AFTER DELETE ON " + TABLE.NOTE + @@ -254,11 +197,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * Move notes belong to folder which has been moved to trash folder */ - // 功能简介: - // 添加触发器:当某个文件夹被移动到回收站时,移动该文件夹下的所有笔记到回收站 - // 解读: - // 在NOTE表上进行UPDATE操作后,如果某个文件夹的新PARENT_ID字段值等于回收站的ID(Notes.ID_TRASH_FOLER) - // 触发器会更新所有以该文件夹为父级(PARENT_ID)的笔记,将它们也移动到回收站。 private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = "CREATE TRIGGER folder_move_notes_on_trash " + " AFTER UPDATE ON " + TABLE.NOTE + @@ -269,12 +207,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " END"; - // 构造器 public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } - // 创建note(标签)表 public void createNoteTable(SQLiteDatabase db) { db.execSQL(CREATE_NOTE_TABLE_SQL); reCreateNoteTableTriggers(db); @@ -282,9 +218,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { Log.d(TAG, "note table has been created"); } - // 重新创建或更新与笔记表相关的触发器。 - // 首先,使用DROP TRIGGER IF EXISTS语句删除已存在的触发器。确保在重新创建触发器之前,不存在同名的触发器。 - // 然后,使用db.execSQL()方法执行预定义的SQL语句,这些语句用于创建新的触发器。 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"); @@ -303,17 +236,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); } - /* 以下部分是操作SQLite数据库部分 */ - // 功能简介: - // 创建通话记录文件夹、默认文件夹、临时文件夹和回收站,并插入相关数据 - // 具体解读: - // ContentValues是一个用于存储键值对的类,常用于SQLite数据库的插入操作 - // values.put方法可以向ContentValues对象中添加数据。 - // NoteColumns.ID是存储文件夹ID的列名,Notes.ID_CALL_RECORD_FOLDER是通话记录文件夹的ID。 - // NoteColumns.TYPE是存储文件夹类型的列名,Notes.TYPE_SYSTEM表示这是一个系统文件夹。 - // 使用db.insert方法将values中的数据插入到TABLE.NOTE(即标签表)中。 - // 每次插入新数据前,都使用values.clear()方法清除ContentValues对象中的旧数据,确保不会重复插入旧数据。 - // 然后分别创建默认文件夹、临时文件夹和回收站,并以同样的方法插入数据。 private void createSystemFolder(SQLiteDatabase db) { ContentValues values = new ContentValues(); @@ -327,7 +249,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * root folder which is default folder */ - // 创建默认文件夹:重复上述步骤,但这次是为根文件夹插入数据。 values.clear(); values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); @@ -336,7 +257,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * 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); @@ -345,21 +265,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** * 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(数据)表 - //解读: - //这个方法用于创建数据表,以及与之相关的触发器。 - //创建数据表:使用db.execSQL方法执行预定义的SQL语句CREATE_DATA_TABLE_SQL,用于创建数据表。 - //重新创建数据表触发器:调用reCreateDataTableTriggers方法,用于删除并重新创建与数据表相关的触发器。 - //创建索引:使用db.execSQL方法执行CREATE_DATA_NOTE_ID_INDEX_SQL语句,为数据表创建索引。 - //记录日志:使用Log.d方法记录一条调试级别的日志,表示数据表已经创建。 public void createDataTable(SQLiteDatabase db) { db.execSQL(CREATE_DATA_TABLE_SQL); reCreateDataTableTriggers(db); @@ -367,10 +278,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { Log.d(TAG, "data table has been created"); } - //和上面的note表的reCreate...同理 - //重新创建或更新与笔记表相关的触发器。 - //首先,使用DROP TRIGGER IF EXISTS语句删除已存在的触发器。确保在重新创建触发器之前,不存在同名的触发器。 - //然后,使用db.execSQL()方法执行预定义的SQL语句,这些语句用于创建新的触发器。 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"); @@ -381,11 +288,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); } - //解读: - //synchronized关键字确保在多线程环境下,只有一个线程能够进入这个方法,防止了同时创建多个实例的情况 - //getInstance(Context context)方法使用了单例模式来确保整个应用程序中只有一个NotesDatabaseHelper实例。 - //它首先检查mInstance(类的静态成员变量,没有在代码片段中显示)是否为null。 - //如果是null,则创建一个新的NotesDatabaseHelper实例,并将其赋值给mInstance。最后返回mInstance。 static synchronized NotesDatabaseHelper getInstance(Context context) { if (mInstance == null) { mInstance = new NotesDatabaseHelper(context); @@ -393,19 +295,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { return mInstance; } - //功能简介: - //当数据库首次创建时,onCreate方法会被调用。 - //这里重写onCreate方法,它调用了上述createNoteTable(db)和createDataTable(db)两个方法 - //这样首次创建数据库时就多出了两张表。 @Override public void onCreate(SQLiteDatabase db) { createNoteTable(db); createDataTable(db); } - //功能简介: - //当数据库需要升级时(即数据库的版本号改变),onUpgrade方法会被调用。 - //该方法会根据当前的oldVersion和新的newVersion来执行相应的升级操作 @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { boolean reCreateTriggers = false; @@ -433,17 +328,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { reCreateDataTableTriggers(db); } - if (oldVersion != newVersion) { //数据库升级失败,抛出一个异常,表示数据库升级失败 + if (oldVersion != newVersion) { throw new IllegalStateException("Upgrade notes database to version " + newVersion + "fails"); } } - //功能简介: - // 将数据库从版本1升级到版本2。 - //解读: - // 首先,它删除了已经存在的NOTE和DATA表(如果存在的话)。DROP TABLE IF EXISTS语句确保了即使这些表不存在,也不会抛出错误。 - // 然后,它调用了createNoteTable(db)和createDataTable(db)方法来重新创建这两个表。这意味着在升级到版本2时,这两个表的内容会被完全清除,并重新创建新的空表。 private void upgradeToV2(SQLiteDatabase db) { db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); @@ -451,12 +341,6 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { createDataTable(db); } - //功能简介: - // 将数据库从版本2(或可能是跳过版本2的某个状态)升级到版本3。 - //解读: - // 首先,删除了三个不再使用的触发器(如果存在的话)。触发器是数据库中的一种对象,可以在插入、更新或删除记录时自动执行某些操作。 - // 然后,使用ALTER TABLE语句修改表结构,向NOTE表中添加了一个名为GTASK_ID的新列,并设置默认值为空字符串。 - // 最后,向NOTE表中插入了一条新的系统文件夹记录,表示一个名为“trash folder”的系统文件夹。这可能是用于存储已删除笔记的回收站功能。 private void upgradeToV3(SQLiteDatabase db) { // drop unused triggers db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); @@ -472,12 +356,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.insert(TABLE.NOTE, null, values); } - //功能简介: - // 这个方法负责将数据库从版本3升级到版本4。 - //解读: - // 它向NOTE表中添加了一个名为VERSION的新列,并设置了默认值为0。这个新列用于记录标签版本信息。 private void upgradeToV4(SQLiteDatabase db) { db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/data/NotesProvider.java b/Notesmaster/app/src/main/java/net/micode/notes/data/NotesProvider.java index 3be3da2..71a1d47 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/data/NotesProvider.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/data/NotesProvider.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -//NotesProvider的主要功能是作为一个内容提供者,为其他应用程序或组件提供对“Notes”数据的访问。它允许其他应用程序查询、插入、更新或删除标签数据。 + package net.micode.notes.data; @@ -36,24 +36,12 @@ import net.micode.notes.data.NotesDatabaseHelper.TABLE; public class NotesProvider extends ContentProvider { -// Android 应用程序中的一部分:内容提供者(ContentProvider)。 -// 内容提供者是 Android 四大组件之一,它允许应用程序之间共享数据。 - - //概述: - //NotesProvider的主要功能是作为一个内容提供者,为其他应用程序或组件提供对“Notes”数据的访问。 - //它允许其他应用程序查询、插入、更新或删除标签数据。 - //通过URI匹配,NotesProvider能够区分对哪种数据类型的请求(例如,单独的标签、标签的数据、文件夹操作等),并执行相应的操作。 - - //用于匹配不同URI的UriMatcher对象,通常用于解析传入的URI,并确定应该执行哪种操作。 private static final UriMatcher mMatcher; - //NotesDatabaseHelper实类,用来操作SQLite数据库,负责创建、更新和查询数据库。 private NotesDatabaseHelper mHelper; - //标签,输出日志时用来表示是该类发出的消息 private static final String TAG = "NotesProvider"; - //6个URI的匹配码,用于区分不同的URI类型 private static final int URI_NOTE = 1; private static final int URI_NOTE_ITEM = 2; private static final int URI_DATA = 3; @@ -62,23 +50,13 @@ public class NotesProvider extends ContentProvider { private static final int URI_SEARCH = 5; private static final int URI_SEARCH_SUGGEST = 6; - //进一步定义了URI匹配规则和搜索查询的投影 - //功能概述: - //初始化了一个UriMatcher对象mMatcher,并添加了一系列的URI匹配规则。 - //解读: static { - //创建了一个UriMatcher实例,并设置默认匹配码为NO_MATCH,表示如果没有任何URI匹配,则返回这个码。 mMatcher = new UriMatcher(UriMatcher.NO_MATCH); - //添加规则,当URI的authority为Notes.AUTHORITY,路径为note时,返回匹配码URI_NOTE。 mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); - //添加规则,当URI的authority为Notes.AUTHORITY,路径为note/后跟一个数字(#代表数字)时,返回匹配码URI_NOTE_ITEM。 mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); - //和上面两句同理,但用于匹配数据相关的URI mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); - //用于匹配搜索相关的URI mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); - //这两行用于匹配搜索建议相关的URI mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); } @@ -87,17 +65,7 @@ public class NotesProvider extends ContentProvider { * 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. */ - //功能概述: - //一个 SQL 查询的投影部分,用于定义查询返回的结果集中应该包含哪些列。 - //解读:(每行对应) - //返回笔记的 ID。 - //笔记的 ID 也被重命名为 SUGGEST_COLUMN_INTENT_EXTRA_DATA,这通常用于 Android 的搜索建议中,作为传递给相关 Intent 的额外数据。 - //对 SNIPPET 列的处理:首先使用 REPLACE 函数将 x'0A'(即换行符 \n)替换为空字符串,然后使用 TRIM 函数删除前后的空白字符,处理后的结果分别重命名为 SUGGEST_COLUMN_TEXT_1 - //对 SNIPPET 列的处理:首先使用 REPLACE 函数将 x'0A'(即换行符 \n)替换为空字符串,然后使用 TRIM 函数删除前后的空白字符,处理后的结果分别重命名为 SUGGEST_COLUMN_TEXT_2 - //返回一个用于搜索建议图标的资源 ID,并命名为 SUGGEST_COLUMN_ICON_1。 - //返回一个固定的 Intent 动作 ACTION_VIEW,并命名为 SUGGEST_COLUMN_INTENT_ACTION。 - //返回一个内容类型,并命名为 SUGGEST_COLUMN_INTENT_DATA。 - private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," //返回笔记的 ID + 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 + "," @@ -105,48 +73,25 @@ public class NotesProvider extends ContentProvider { + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; - //功能概述: - //完整的 SQL 查询语句,用于从 TABLE.NOTE 表中检索信息 - //解读: - // 使用上面定义的投影来选择数据。 - // 并指定从哪个表中选择数据。 - //WHERE子句包含三个条件: - // ①搜索 SNIPPET 列中包含特定模式的行(? 是一个占位符,实际查询时会用具体的值替换)。 - // ②父ID不为回收站的ID:排除那些父 ID 为回收站的行。 - // ③只选择类型为note(标签)的行。 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; - //重写onCreate方法: - //getContext() 方法被调用以获取当前组件的上下文(Context),以便 NotesDatabaseHelper 能够访问应用程序的资源和其他功能 - //mHelper用于存储从 NotesDatabaseHelper.getInstance 方法返回的实例。这样,该实例就可以在整个组件的其他方法中被访问和使用。 @Override public boolean onCreate() { mHelper = NotesDatabaseHelper.getInstance(getContext()); return true; } - //功能:查询数据 @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - //初始化变量: - //Cursor对象 c,用来存储查询结果 - //使用 NotesDatabaseHelper 的实例 mHelper来获取一个可读的数据库实例 - //定义一个字符串id,用来存储从URI中解析出的ID Cursor c = null; SQLiteDatabase db = mHelper.getReadableDatabase(); String id = null; - - //根据匹配不同的URI来进行不同的查询 switch (mMatcher.match(uri)) { - // URI_NOTE:查询整个 NOTE 表。 - // URI_NOTE_ITEM:查询 NOTE 表中的特定项。ID 从 URI 的路径段中获取,并添加到查询条件中。 - // URI_DATA:查询整个 DATA 表。 - // URI_DATA_ITEM:查询 DATA 表中的特定项。ID 的获取和处理方式与 URI_NOTE_ITEM 相同。 case URI_NOTE: c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder); @@ -165,12 +110,6 @@ public class NotesProvider extends ContentProvider { c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder); break; - - //URI_SEARCH 和 URI_SEARCH_SUGGEST:处理搜索查询。 - // 代码首先检查是否提供了不应与搜索查询一起使用的参数(如 sortOrder, selection, selectionArgs, 或 projection)。 - // 如果提供了这些参数,则抛出一个 IllegalArgumentException。 - // 根据 URI 类型,从 URI 的路径段或查询参数中获取搜索字符串 searchString。 - // 如果 searchString 为空或无效,则返回 null,表示没有搜索结果。 case URI_SEARCH: case URI_SEARCH_SUGGEST: if (sortOrder != null || projection != null) { @@ -191,8 +130,6 @@ public class NotesProvider extends ContentProvider { return null; } - //字符串格式化:格式化后的字符串就会是 "%s%",即包含s是任何文本 - //然后执行原始SQL查询 try { searchString = String.format("%%%s%%", searchString); c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, @@ -201,31 +138,19 @@ public class NotesProvider extends ContentProvider { Log.e(TAG, "got exception: " + ex.toString()); } break; - - //未知URI处理: default: throw new IllegalArgumentException("Unknown URI " + uri); } - //如果查询结果不为空(即 Cursor 对象 c 不是 null),则为其设置一个通知 URI。 - //这意味着当与这个 URI 关联的数据发生变化时,任何注册了监听这个 URI 的 ContentObserver 都会被通知。 if (c != null) { c.setNotificationUri(getContext().getContentResolver(), uri); } return c; } - //功能:插入数据 - //参数:Uri 用来标识要插入数据的表,ContentValues对象包含要插入的键值对 @Override public Uri insert(Uri uri, ContentValues values) { - //获取数据库 - //三个长整型变量,分别用来存储数据项ID、便签ID 和插入行的ID SQLiteDatabase db = mHelper.getWritableDatabase(); long dataId = 0, noteId = 0, insertedId = 0; - - //对于 URI_NOTE,将values插入到 TABLE.NOTE 表中,并返回插入行的 ID。 - //对于 URI_DATA,首先检查values是否包含 DataColumns.NOTE_ID,如果包含,则获取其值。如果不包含,记录一条日志信息。然后,将 values 插入到 TABLE.DATA 表中,并返回插入行的 ID。 - //如果 uri 不是已知的 URI 类型,则抛出一个 IllegalArgumentException。 switch (mMatcher.match(uri)) { case URI_NOTE: insertedId = noteId = db.insert(TABLE.NOTE, null, values); @@ -241,10 +166,6 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } - - //功能:通知变化 - //如果noteId 或 dataId 大于 0(即成功插入了数据),则使用 ContentResolver 的 notifyChange 方法通知监听这些 URI 的观察者,告知数据已经改变。 - //ContentUris.withAppendedId 方法用于在基本 URI 后面追加一个 ID,形成完整的 URI。 // Notify the note uri if (noteId > 0) { getContext().getContentResolver().notifyChange( @@ -257,28 +178,16 @@ public class NotesProvider extends ContentProvider { ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); } - //返回包含新插入数据项ID 的 Uri。允许调用者知道新插入的数据项的位置 return ContentUris.withAppendedId(uri, insertedId); } - //功能:删除数据项 - //参数:uri:标识要删除数据的表或数据项。 selection:一个可选的 WHERE 子句,用于指定删除条件。 selectionArgs:一个可选的字符串数组,用于替换 selection 中的占位符 @Override public int delete(Uri uri, String selection, String[] selectionArgs) { - //count:记录被删除的行数。 - //id:用于存储从 URI 中解析出的数据项 ID。 - //db:可写的数据库对象,用于执行删除操作。 - //deleteData:一个布尔值,用于标记是否删除了 DATA 表中的数据。 int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean deleteData = false; - switch (mMatcher.match(uri)) { - //URI_NOTE: 修改 selection 语句:确保只删除 ID 大于 0 的笔记。然后执行删除操作并返回被删除的行数。 - //URI_NOTE_ITEM: 从 URI 中解析出 ID。检查 ID 是否小于等于 0,如果是,则不执行删除操作;否则执行删除操作并返回被删除的行数 - //URI_DATA: 执行删除操作并返回被删除的行数。设置 deleteData 为 true,表示删除了 DATA 表中的数据。 - //URI_DATA_ITEM: 先从 URI 中解析出 ID,然后执行删除操作并返回被删除的行数,并设置 deleteData 为 true,表示删除了 DATA 表中的数据。 case URI_NOTE: selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; count = db.delete(TABLE.NOTE, selection, selectionArgs); @@ -309,39 +218,22 @@ public class NotesProvider extends ContentProvider { default: throw new IllegalArgumentException("Unknown URI " + uri); } - - //如果 count 大于 0,说明有数据被删除。 - //如果 deleteData 为 true,则通知监听 Notes.CONTENT_NOTE_URI 的观察者,数据已改变。 - //通知监听传入 uri 的观察者数据已改变。 if (count > 0) { if (deleteData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); } getContext().getContentResolver().notifyChange(uri, null); } - return count; } - //功能:更新数据库的数据 - //参数:uri:标识要更新数据的表或数据项。 values:一个包含新值的键值对集合。 - // selection:一个可选的 WHERE 子句,用于指定更新条件。 selectionArgs:一个可选的字符串数组,用于替换 selection 中的占位符。 @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - //count:记录被更新的行数。 - //id:用于存储从 URI 中解析出的数据项 ID。 - //db:可写的 SQLite 数据库对象,用于执行更新操作。 - //updateData:用于标记是否更新了 data 表中的数据。 int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean updateData = false; - switch (mMatcher.match(uri)) { - //URI_NOTE:调用 increaseNoteVersion 方法(用于增加便签版本),然后在note表执行更新操作并返回被更新的行数。 - //URI_NOTE_ITEM:从 URI 中解析出 ID,并调用 increaseNoteVersion 方法,传入解析出的 ID,最后在note表执行更新操作并返回被更新的行数。 - //URI_DATA:在data表执行更新操作并返回被更新的行数。设置 updateData 为 true,表示更新了 DATA 表中的数据。 - //URI_DATA_ITEM:从 URI 中解析出 ID。执行更新操作并返回被更新的行数。置 updateData 为 true,表示更新了 DATA 表中的数据。 case URI_NOTE: increaseNoteVersion(-1, selection, selectionArgs); count = db.update(TABLE.NOTE, values, selection, selectionArgs); @@ -366,9 +258,6 @@ public class NotesProvider extends ContentProvider { throw new IllegalArgumentException("Unknown URI " + uri); } - //如果 count 大于 0,说明有数据被更新。 - //如果 updateData 为 true,则通知监听 Notes.CONTENT_NOTE_URI 的观察者数据已改变。 - //通知监听传入 uri 的观察者数据已改变。 if (count > 0) { if (updateData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); @@ -378,12 +267,10 @@ public class NotesProvider extends ContentProvider { return count; } - //解析传入的条件语句:一个 SQL WHERE 子句的一部分 private String parseSelection(String selection) { return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); } - //更新note表的version列,将其值增加 1。 private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); @@ -415,4 +302,4 @@ public class NotesProvider extends ContentProvider { return null; } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/MetaData.java b/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/MetaData.java index ec41adc..919a5d4 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/MetaData.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/MetaData.java @@ -24,103 +24,100 @@ import net.micode.notes.tool.GTaskStringUtils; import org.json.JSONException; import org.json.JSONObject; - +/** + * MetaData类继承自Task类,用于管理任务的元数据。 + * 该类提供了设置和获取与任务相关的GID的方法,以及处理远程JSON内容的方法。 + */ public class MetaData extends Task { - /* - * 功能描述:得到类的简写名称存入字符串TAG中 - * 实现过程:调用getSimpleName ()函数 - */ + // 日志标签,用于调试输出 private final static String TAG = MetaData.class.getSimpleName(); + // 相关的GID private String mRelatedGid = null; - /* - * 功能描述:设置数据,即生成元数据库 - * 实现过程:调用JSONObject库函数put (),Task类中的setNotes ()和setName ()函数 - * 参数注解: + /** + * 设置元数据 + * @param gid 任务的GID + * @param metaInfo 元数据的JSON对象 */ public void setMeta(String gid, JSONObject metaInfo) { try { + // 将GID添加到元数据JSON对象中 metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); - /* - * 将这对键值放入metaInfo这个jsonobject对象中 - */ } catch (JSONException e) { + // 输出错误日志 Log.e(TAG, "failed to put related gid"); - /* - * 输出错误信息 - */ } + // 将元数据转换为字符串并设置为任务的备注 setNotes(metaInfo.toString()); + // 设置任务名称 setName(GTaskStringUtils.META_NOTE_NAME); } - /* - * 功能描述:获取相关联的Gid + /** + * 获取相关的GID + * @return 相关的GID */ public String getRelatedGid() { return mRelatedGid; } + /** + * 判断任务是否值得保存 + * @return 如果任务的备注不为空,则返回true;否则返回false + */ @Override public boolean isWorthSaving() { return getNotes() != null; } - /* - * 功能描述:使用远程json数据对象设置元数据内容 - * 实现过程:调用父类Task中的setContentByRemoteJSON ()函数,并 - * 参数注解: + /** + * 通过远程JSON内容设置任务内容 + * @param js 远程JSON对象 */ @Override public void setContentByRemoteJSON(JSONObject js) { super.setContentByRemoteJSON(js); if (getNotes() != null) { try { + // 解析备注中的元数据JSON对象 JSONObject metaInfo = new JSONObject(getNotes().trim()); + // 获取元数据中的GID mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); } catch (JSONException e) { + // 输出警告日志 Log.w(TAG, "failed to get related gid"); mRelatedGid = null; } } } - /* - * 功能描述:使用本地json数据对象设置元数据内容,一般不会用到,若用到,则抛出异常 + + /** + * 禁止通过本地JSON内容设置任务内容 + * @param js 本地JSON对象 */ @Override public void setContentByLocalJSON(JSONObject js) { - // this function should not be called + // 此方法不应被调用 throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); - - /* - * 传递非法参数异常 - */ - } - /* - * 功能描述:从元数据内容中获取本地json对象,一般不会用到,若用到,则抛出异常 + /** + * 禁止从任务内容中获取本地JSON对象 + * @return 本地JSON对象 */ @Override public JSONObject getLocalJSONFromContent() { throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); - - /* - * 传递非法参数异常 - */ - } - /* - * 功能描述:获取同步动作状态,一般不会用到,若用到,则抛出异常 + + /** + * 禁止获取同步动作 + * @param c 游标对象 + * @return 同步动作 */ @Override public int getSyncAction(Cursor c) { throw new IllegalAccessError("MetaData:getSyncAction should not be called"); - /* - * 传递非法参数异常 - */ - } - } diff --git a/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Node.java b/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Node.java index 9bc7f75..fdee51f 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Node.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Node.java @@ -21,37 +21,37 @@ import android.database.Cursor; import org.json.JSONObject; /** - * 应该是同步操作的基础数据类型,定义了相关指示同步操作的常量 - * 关键字:abstract + * Node类是一个抽象类,用于表示任务节点。 + * 该类定义了任务节点的基本属性和操作,包括同步动作的常量、任务节点的GID、名称、最后修改时间、删除状态等。 + * 具体的任务节点需要继承该类并实现抽象方法。 */ 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;// 同步出现错误 - + // 同步动作的常量 + 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; + + // 任务节点的GID private String mGid; + // 任务节点的名称 private String mName; - private long mLastModified;//记录最后一次修改时间 + // 任务节点的最后修改时间 + private long mLastModified; - private boolean mDeleted;//表征是否被删除 + // 任务节点的删除状态 + private boolean mDeleted; + /** + * Node类的构造函数,初始化任务节点的属性 + */ public Node() { mGid = null; mName = ""; @@ -59,48 +59,107 @@ public abstract class Node { mDeleted = false; } + /** + * 获取创建操作的JSON对象 + * @param actionId 操作ID + * @return 创建操作的JSON对象 + */ public abstract JSONObject getCreateAction(int actionId); + /** + * 获取更新操作的JSON对象 + * @param actionId 操作ID + * @return 更新操作的JSON对象 + */ public abstract JSONObject getUpdateAction(int actionId); + /** + * 通过远程JSON对象设置任务节点的内容 + * @param js 远程JSON对象 + */ public abstract void setContentByRemoteJSON(JSONObject js); + /** + * 通过本地JSON对象设置任务节点的内容 + * @param js 本地JSON对象 + */ public abstract void setContentByLocalJSON(JSONObject js); + /** + * 从任务节点的内容中获取本地JSON对象 + * @return 本地JSON对象 + */ public abstract JSONObject getLocalJSONFromContent(); + /** + * 获取同步动作 + * @param c 游标对象 + * @return 同步动作 + */ public abstract int getSyncAction(Cursor c); + /** + * 设置任务节点的GID + * @param gid 任务节点的GID + */ public void setGid(String gid) { this.mGid = gid; } + /** + * 设置任务节点的名称 + * @param name 任务节点的名称 + */ public void setName(String name) { this.mName = name; } + /** + * 设置任务节点的最后修改时间 + * @param lastModified 任务节点的最后修改时间 + */ public void setLastModified(long lastModified) { this.mLastModified = lastModified; } + /** + * 设置任务节点的删除状态 + * @param deleted 任务节点的删除状态 + */ public void setDeleted(boolean deleted) { this.mDeleted = deleted; } + /** + * 获取任务节点的GID + * @return 任务节点的GID + */ public String getGid() { return this.mGid; } + /** + * 获取任务节点的名称 + * @return 任务节点的名称 + */ public String getName() { return this.mName; } + /** + * 获取任务节点的最后修改时间 + * @return 任务节点的最后修改时间 + */ public long getLastModified() { return this.mLastModified; } + /** + * 获取任务节点的删除状态 + * @return 任务节点的删除状态 + */ public boolean getDeleted() { return this.mDeleted; } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlData.java b/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlData.java index 94ee5f0..85e4d5b 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlData.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlData.java @@ -14,14 +14,8 @@ * limitations under the License. */ -/* - * Description:用于支持小米便签最底层的数据库相关操作,和sqldata的关系上是父集关系,即note是data的子父集。 - * 和SqlData相比,SqlNote算是真正意义上的数据了。 - */ - package net.micode.notes.gtask.data; - import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; @@ -40,62 +34,50 @@ import net.micode.notes.gtask.exception.ActionFailureException; import org.json.JSONException; import org.json.JSONObject; - +/** + * SqlData类用于处理数据库中的笔记数据。 + * 该类提供了从数据库加载数据、设置数据内容、获取数据内容以及提交数据的功能。 + */ public class SqlData { - /* - * 功能描述:得到类的简写名称存入字符串TAG中 - * 实现过程:调用getSimpleName ()函数 - */ + // 日志标签,用于调试输出 private static final String TAG = SqlData.class.getSimpleName(); - private static final int INVALID_ID = -99999;//为mDataId置初始值-99999 - - /** - * 来自Notes类中定义的DataColumn中的一些常量 - */ + // 无效的ID常量 + private static final int INVALID_ID = -99999; - // 集合了interface DataColumns中所有SF常量 + // 数据表的字段投影 public static final String[] PROJECTION_DATA = new String[] { DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, DataColumns.DATA3 }; - /** - * 以下五个变量作为sql表中5列的编号 - */ + // 数据表字段的列索引 public static final int DATA_ID_COLUMN = 0; - public static final int DATA_MIME_TYPE_COLUMN = 1; - public static final int DATA_CONTENT_COLUMN = 2; - public static final int DATA_CONTENT_DATA_1_COLUMN = 3; - public static final int DATA_CONTENT_DATA_3_COLUMN = 4; + // 内容解析器,用于访问内容提供者的数据 private ContentResolver mContentResolver; - //判断是否直接用Content生成,是为true,否则为false + // 标识数据是否为新创建的 private boolean mIsCreate; + // 数据的各个字段 private long mDataId; - private String mDataMimeType; - private String mDataContent; - private long mDataContentData1; - private String mDataContentData3; + // 存储数据变更的内容值 private ContentValues mDiffDataValues; - /* - * 功能描述:构造函数,用于初始化数据 - * 参数注解:mContentResolver用于获取ContentProvider提供的数据 - * 参数注解: mIsCreate表征当前数据是用哪种方式创建(两种构造函数的参数不同) - * 参数注解: - */ + /** + * SqlData类的构造函数,用于新创建的数据 + * @param context 应用上下文 + */ public SqlData(Context context) { mContentResolver = context.getContentResolver(); mIsCreate = true; @@ -106,11 +88,11 @@ public class SqlData { mDataContentData3 = ""; mDiffDataValues = new ContentValues(); } - /* - * 功能描述:构造函数,初始化数据 - * 参数注解:mContentResolver用于获取ContentProvider提供的数据 - * 参数注解: mIsCreate表征当前数据是用哪种方式创建(两种构造函数的参数不同) - * 参数注解: + + /** + * SqlData类的构造函数,用于从数据库加载的数据 + * @param context 应用上下文 + * @param c 游标对象,指向加载的数据 */ public SqlData(Context context, Cursor c) { mContentResolver = context.getContentResolver(); @@ -118,9 +100,10 @@ public class SqlData { loadFromCursor(c); mDiffDataValues = new ContentValues(); } - /* - * 功能描述:从光标处加载数据 - * 从当前的光标处将五列的数据加载到该类的对象 + + /** + * 从游标加载数据 + * @param c 游标对象,指向加载的数据 */ private void loadFromCursor(Cursor c) { mDataId = c.getLong(DATA_ID_COLUMN); @@ -129,12 +112,13 @@ public class SqlData { mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); } - /* - * 功能描述:设置用于共享的数据,并提供异常抛出与处理机制 - * 参数注解: + + /** + * 设置数据内容 + * @param js JSON对象,包含数据内容 + * @throws JSONException 如果JSON解析错误 */ public void setContent(JSONObject js) throws JSONException { - //如果传入的JSONObject对象中有DataColumns.ID这一项,则设置,否则设为INVALID_ID long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; if (mIsCreate || mDataId != dataId) { mDiffDataValues.put(DataColumns.ID, dataId); @@ -167,17 +151,16 @@ public class SqlData { mDataContentData3 = dataContentData3; } - /* - * 功能描述:获取共享的数据内容,并提供异常抛出与处理机制 - * 参数注解: + /** + * 获取数据内容 + * @return JSON对象,包含数据内容 + * @throws JSONException 如果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对象。并将相关数据放入其中,并返回。 JSONObject js = new JSONObject(); js.put(DataColumns.ID, mDataId); js.put(DataColumns.MIME_TYPE, mDataMimeType); @@ -186,20 +169,27 @@ public class SqlData { js.put(DataColumns.DATA3, mDataContentData3); return js; } - /* - * 功能描述:commit函数用于把当前造作所做的修改保存到数据库 - * 参数注解: + + /** + * 提交数据到数据库 + * @param noteId 笔记ID + * @param validateVersion 是否验证版本 + * @param version 版本号 */ public void commit(long noteId, boolean validateVersion, long version) { if (mIsCreate) { + // 如果是新创建的数据且ID无效,则移除ID字段 if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { mDiffDataValues.remove(DataColumns.ID); } + // 将笔记ID添加到内容值中 mDiffDataValues.put(DataColumns.NOTE_ID, noteId); + // 插入新数据 Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); try { + // 从插入结果的URI中获取数据ID mDataId = Long.valueOf(uri.getPathSegments().get(1)); } catch (NumberFormatException e) { Log.e(TAG, "Get note id error :" + e.toString()); @@ -209,11 +199,13 @@ public class SqlData { 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, + Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, " ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE + " WHERE " + NoteColumns.VERSION + "=?)", new String[] { String.valueOf(noteId), String.valueOf(version) @@ -225,13 +217,14 @@ public class SqlData { } } + // 清空变更内容值并重置创建标识 mDiffDataValues.clear(); mIsCreate = false; } - /* - * 功能描述:获取当前id - * 实现过程: - * 参数注解: + + /** + * 获取数据ID + * @return 数据ID */ public long getId() { return mDataId; diff --git a/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java b/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java index 89a551d..6e6039f 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/SqlNote.java @@ -37,21 +37,18 @@ import org.json.JSONObject; import java.util.ArrayList; -/* - * Description:用于支持小米便签最底层的数据库相关操作,和sqldata的关系上是父集关系,即note是data的子父集。 - * 和SqlData相比,SqlNote算是真正意义上的数据了。 +/** + * SqlNote类用于处理数据库中的笔记数据。 + * 该类提供了从数据库加载笔记、设置笔记内容、获取笔记内容以及提交笔记的功能。 */ - - public class SqlNote { - /* - * 功能描述:得到类的简写名称存入字符串TAG中 - * 实现过程:调用getSimpleName ()函数 - */ + // 日志标签,用于调试输出 private static final String TAG = SqlNote.class.getSimpleName(); + // 无效的ID常量 private static final int INVALID_ID = -99999; - // 集合了interface NoteColumns中所有SF常量(17个) + + // 笔记表的字段投影 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, @@ -61,84 +58,59 @@ public class SqlNote { NoteColumns.VERSION }; - //以下设置17个列的编号 + // 笔记表字段的列索引 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; - //一下定义了17个内部的变量,其中12个可以由content中获得,5个需要初始化为0或者new + // 应用上下文 private Context mContext; + // 内容解析器,用于访问内容提供者的数据 private ContentResolver mContentResolver; + // 标识笔记是否为新创建的 private boolean mIsCreate; + // 笔记的各个字段 private long mId; - private long mAlertDate; - private int mBgColorId; - private long mCreatedDate; - private int mHasAttachment; - private long mModifiedDate; - private long mParentId; - private String mSnippet; - private int mType; - private int mWidgetId; - private int mWidgetType; - private long mOriginParent; - private long mVersion; + // 存储笔记变更的内容值 private ContentValues mDiffNoteValues; + // 笔记的数据列表 private ArrayList mDataList; - /* - * 功能描述:构造函数 - * 参数注解: mIsCreate用于标示构造方式 - * 参数注解: + /** + * SqlNote类的构造函数,用于新创建的笔记 + * @param context 应用上下文 */ - //构造函数只有context,对所有的变量进行初始化 public SqlNote(Context context) { mContext = context; mContentResolver = context.getContentResolver(); @@ -146,9 +118,9 @@ public class SqlNote { mId = INVALID_ID; mAlertDate = 0; mBgColorId = ResourceParser.getDefaultBgId(context); - mCreatedDate = System.currentTimeMillis();//调用系统函数获得创建时间 + mCreatedDate = System.currentTimeMillis(); mHasAttachment = 0; - mModifiedDate = System.currentTimeMillis();//最后一次修改时间初始化为创建时间 + mModifiedDate = System.currentTimeMillis(); mParentId = 0; mSnippet = ""; mType = Notes.TYPE_NOTE; @@ -160,30 +132,26 @@ public class SqlNote { mDataList = new ArrayList(); } - - /* - * 功能描述:构造函数 - * 参数注解: mIsCreate用于标示构造方式 - * 参数注解: + /** + * SqlNote类的构造函数,用于从数据库加载的笔记 + * @param context 应用上下文 + * @param c 游标对象,指向加载的笔记 */ - //构造函数有context和一个数据库的cursor,多数变量通过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(); } - - /* - * 功能描述:构造函数 - * 参数注解: mIsCreate用于标示构造方式 - * 参数注解: + /** + * SqlNote类的构造函数,通过笔记ID从数据库加载笔记 + * @param context 应用上下文 + * @param id 笔记ID */ public SqlNote(Context context, long id) { mContext = context; @@ -194,11 +162,11 @@ public class SqlNote { if (mType == Notes.TYPE_NOTE) loadDataContent(); mDiffNoteValues = new ContentValues(); - } - /* - * 功能描述:通过id从光标处加载数据 + /** + * 通过笔记ID从数据库加载笔记 + * @param id 笔记ID */ private void loadFromCursor(long id) { Cursor c = null; @@ -206,11 +174,10 @@ public class SqlNote { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)", new String[] { String.valueOf(id) - }, null);//通过id获得对应的ContentResolver中的cursor + }, null); if (c != null) { c.moveToNext(); - loadFromCursor(c);//然后加载数据进行初始化,这样函数 - //SqlNote(Context context, long id)与SqlNote(Context context, long id)的实现方式基本相同 + loadFromCursor(c); } else { Log.w(TAG, "loadFromCursor: cursor = null"); } @@ -220,11 +187,11 @@ public class SqlNote { } } - /* - * 功能描述:通过游标从光标处加载数据 + /** + * 从游标加载笔记数据 + * @param c 游标对象,指向加载的笔记 */ private void loadFromCursor(Cursor c) { - //直接从一条记录中的获得以下变量的初始值 mId = c.getLong(ID_COLUMN); mAlertDate = c.getLong(ALERTED_DATE_COLUMN); mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); @@ -239,9 +206,8 @@ public class SqlNote { mVersion = c.getLong(VERSION_COLUMN); } - /* - * 功能描述:通过content机制获取共享数据并加载到数据库当前游标处 - * 参数注解: + /** + * 从数据库加载笔记的数据内容 */ private void loadDataContent() { Cursor c = null; @@ -269,9 +235,10 @@ public class SqlNote { } } - /* - * 功能描述:设置通过content机制用于共享的数据信息 - * 参数注解: + /** + * 设置笔记内容 + * @param js JSON对象,包含笔记内容 + * @return 设置成功返回true,否则返回false */ public boolean setContent(JSONObject js) { try { @@ -279,7 +246,7 @@ public class SqlNote { if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { Log.w(TAG, "cannot set system folder"); } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { - // for folder we can only update the snnipet and type + // 对于文件夹,我们只能更新片段和类型 String snippet = note.has(NoteColumns.SNIPPET) ? note .getString(NoteColumns.SNIPPET) : ""; if (mIsCreate || !mSnippet.equals(snippet)) { @@ -314,6 +281,7 @@ public class SqlNote { 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) { @@ -405,9 +373,9 @@ public class SqlNote { return true; } - /* - * 功能描述:获取content机制提供的数据并加载到note中 - * 参数注解: + /** + * 获取笔记内容 + * @return JSON对象,包含笔记内容 */ public JSONObject getContent() { try { @@ -419,7 +387,7 @@ public class SqlNote { } JSONObject note = new JSONObject(); - if (mType == Notes.TYPE_NOTE) {//类型为note时 + if (mType == Notes.TYPE_NOTE) { note.put(NoteColumns.ID, mId); note.put(NoteColumns.ALERTED_DATE, mAlertDate); note.put(NoteColumns.BG_COLOR_ID, mBgColorId); @@ -442,7 +410,7 @@ public class SqlNote { } } js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); - } else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) {//类型为文件夹或者 + } 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); @@ -457,75 +425,73 @@ public class SqlNote { return null; } - /* - * 功能描述:给当前id设置父id - * 参数注解: + /** + * 设置笔记的父ID + * @param id 父ID */ public void setParentId(long id) { mParentId = id; mDiffNoteValues.put(NoteColumns.PARENT_ID, id); } - /* - * 功能描述:给当前id设置Gtaskid - * 参数注解: - * Made By CuiCan + /** + * 设置GTask ID + * @param gid GTask ID */ public void setGtaskId(String gid) { mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); } - /* - * 功能描述:给当前id设置同步id - * 参数注解: + /** + * 设置同步ID + * @param syncId 同步ID */ public void setSyncId(long syncId) { mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); } - /* - * 功能描述:初始化本地修改,即撤销所有当前修改 - * 参数注解: + /** + * 重置本地修改标志 */ public void resetLocalModified() { mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); } - /* - * 功能描述:获得当前id - * 参数注解: + /** + * 获取笔记ID + * @return 笔记ID */ public long getId() { return mId; } - /* - * 功能描述:获得当前id的父id - * 参数注解: + /** + * 获取笔记的父ID + * @return 父ID */ public long getParentId() { return mParentId; } - /* - * 功能描述:获取小片段即用于显示的部分便签内容 - * 参数注解: + /** + * 获取笔记的片段 + * @return 片段 */ public String getSnippet() { return mSnippet; } - /* - * 功能描述:判断是否为便签类型 - * 参数注解: + /** + * 判断笔记是否为普通笔记类型 + * @return 如果笔记类型为普通笔记,返回true;否则返回false */ public boolean isNoteType() { return mType == Notes.TYPE_NOTE; } - /* - * 功能描述:commit函数用于把当前造作所做的修改保存到数据库 - * 参数注解: + /** + * 提交笔记数据到数据库 + * @param validateVersion 是否验证版本 */ public void commit(boolean validateVersion) { if (mIsCreate) { @@ -545,7 +511,7 @@ public class SqlNote { } if (mType == Notes.TYPE_NOTE) { - for (SqlData sqlData : mDataList) {//直接使用sqldata中的实现 + for (SqlData sqlData : mDataList) { sqlData.commit(mId, false, -1); } } @@ -557,7 +523,7 @@ public class SqlNote { if (mDiffNoteValues.size() > 0) { mVersion ++; int result = 0; - if (!validateVersion) {//构造字符串 + if (!validateVersion) { result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" + NoteColumns.ID + "=?)", new String[] { String.valueOf(mId) @@ -581,7 +547,7 @@ public class SqlNote { } } - // refresh local info + // 刷新本地信息 loadFromCursor(mId); if (mType == Notes.TYPE_NOTE) loadDataContent(); @@ -589,4 +555,4 @@ public class SqlNote { mDiffNoteValues.clear(); mIsCreate = false; } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Task.java b/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Task.java index 3ce2832..fcd310d 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Task.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/Task.java @@ -31,44 +31,61 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; - +/** + * Task类用于表示一个任务节点,继承自Node类。 + * 该类提供了任务节点的创建、更新、内容设置与获取等功能。 + */ public class Task extends Node { + // 日志标签,用于调试输出 private static final String TAG = Task.class.getSimpleName(); - private boolean mCompleted;//是否完成 + // 标识任务是否已完成 + private boolean mCompleted; + // 任务的备注 private String mNotes; - private JSONObject mMetaInfo;//将在实例中存储数据的类型 + // 任务的元信息 + private JSONObject mMetaInfo; - private Task mPriorSibling;//对应的优先兄弟Task的指针(待完善) + // 任务的前一个兄弟节点 + private Task mPriorSibling; - private TaskList mParent;//所在的任务列表的指针 + // 任务的父节点 + private TaskList mParent; + /** + * Task类的构造函数,初始化任务节点的属性 + */ public Task() { super(); mCompleted = false; mNotes = null; - mPriorSibling = null;//TaskList中当前Task前面的Task的指针 - mParent = null;//当前Task所在的TaskList + mPriorSibling = null; + mParent = null; mMetaInfo = null; } + /** + * 获取任务的创建操作JSON对象 + * @param actionId 操作ID + * @return 创建操作的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 + // 设置操作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"); @@ -79,21 +96,17 @@ public class Task extends Node { } js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - // parent_id - if (mParent!= null) { - js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); - } + // 设置父节点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 - if (mParent!= null) { - js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); - } + // 设置任务列表ID + js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); - // prior_sibling_id + // 设置前一个兄弟节点ID if (mPriorSibling != null) { js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); } @@ -107,21 +120,26 @@ public class Task extends Node { return js; } + /** + * 获取任务的更新操作JSON对象 + * @param actionId 操作ID + * @return 更新操作的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 + // 设置操作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // id + // 设置任务ID js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - // entity_delta + // 设置任务的实体变化 JSONObject entity = new JSONObject(); entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); if (getNotes() != null) { @@ -139,35 +157,39 @@ public class Task extends Node { return js; } + /** + * 通过远程JSON对象设置任务的内容 + * @param js 远程JSON对象 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { - // id + // 设置任务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)); } @@ -179,7 +201,11 @@ public class Task extends Node { } } - public void setContentByLocalJSON(JSONObject js) { //��metadata����ʵʩ + /** + * 通过本地JSON对象设置任务的内容 + * @param js 本地JSON对象 + */ + public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) || !js.has(GTaskStringUtils.META_HEAD_DATA)) { Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); @@ -208,11 +234,15 @@ public class Task extends Node { } } + /** + * 从任务的内容中获取本地JSON对象 + * @return 本地JSON对象 + */ public JSONObject getLocalJSONFromContent() { String name = getName(); try { if (mMetaInfo == null) { - // new task created from web + // 从web新创建的任务 if (name == null) { Log.w(TAG, "the note seems to be an empty one"); return null; @@ -229,7 +259,7 @@ public class Task extends Node { 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); @@ -251,6 +281,10 @@ public class Task extends Node { } } + /** + * 设置任务的元信息 + * @param metaData 元信息对象 + */ public void setMetaInfo(MetaData metaData) { if (metaData != null && metaData.getNotes() != null) { try { @@ -262,6 +296,11 @@ public class Task extends Node { } } + /** + * 获取任务的同步操作类型 + * @param c 游标对象,指向数据库中的笔记 + * @return 同步操作类型 + */ public int getSyncAction(Cursor c) { try { JSONObject noteInfo = null; @@ -279,31 +318,32 @@ public class Task extends Node { return SYNC_ACTION_UPDATE_LOCAL; } - // validate the note id now + // 验证笔记ID 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 + // 验证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; } } @@ -315,41 +355,77 @@ public class Task extends Node { return SYNC_ACTION_ERROR; } + /** + * 判断任务是否值得保存 + * @return 如果任务值得保存返回true,否则返回false + */ public boolean isWorthSaving() { return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) || (getNotes() != null && getNotes().trim().length() > 0); } + /** + * 设置任务完成状态 + * @param completed 任务是否完成 + */ public void setCompleted(boolean completed) { this.mCompleted = completed; } + /** + * 设置任务的备注 + * @param notes 任务的备注 + */ public void setNotes(String notes) { this.mNotes = notes; } + /** + * 设置任务的前一个兄弟节点 + * @param priorSibling 前一个兄弟节点 + */ public void setPriorSibling(Task priorSibling) { this.mPriorSibling = priorSibling; } + /** + * 设置任务的父节点 + * @param parent 父节点 + */ public void setParent(TaskList parent) { this.mParent = parent; } + /** + * 获取任务的完成状态 + * @return 任务是否完成 + */ public boolean getCompleted() { return this.mCompleted; } + /** + * 获取任务的备注 + * @return 任务的备注 + */ public String getNotes() { return this.mNotes; } + /** + * 获取任务的前一个兄弟节点 + * @return 前一个兄弟节点 + */ public Task getPriorSibling() { return this.mPriorSibling; } + /** + * 获取任务的父节点 + * @return 父节点 + */ public TaskList getParent() { return this.mParent; } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/TaskList.java b/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/TaskList.java index a27f57f..88baf10 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/TaskList.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/gtask/data/TaskList.java @@ -29,40 +29,50 @@ import org.json.JSONObject; import java.util.ArrayList; - +/** + * TaskList类用于表示一个任务列表节点,继承自Node类。 + * 该类提供了任务列表的创建、更新、内容设置与获取、子任务管理等功能。 + */ public class TaskList extends Node { - private static final String TAG = TaskList.class.getSimpleName();//tag标记 + // 日志标签,用于调试输出 + private static final String TAG = TaskList.class.getSimpleName(); - private int mIndex;//当前TaskList的指针 + // 任务列表的索引 + private int mIndex; - private ArrayList mChildren;//类中主要的保存数据的单元,用来实现一个以Task为元素的ArrayList + // 子任务列表 + private ArrayList mChildren; + /** + * TaskList类的构造函数,初始化任务列表的属性 + */ public TaskList() { super(); mChildren = new ArrayList(); mIndex = 1; } - /* (non-Javadoc) - * @see net.micode.notes.gtask.data.Node#getCreateAction(int) - * 生成并返回一个包含了一定数据的JSONObject实体 + /** + * 获取任务列表的创建操作JSON对象 + * @param actionId 操作ID + * @return 创建操作的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 + // 设置操作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // index + // 设置任务列表的索引 js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); - // entity_delta - JSONObject entity = new JSONObject();//entity实体 + // 设置任务列表的实体变化 + 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, @@ -78,25 +88,26 @@ public class TaskList extends Node { return js; } - /* (non-Javadoc) - * @see net.micode.notes.gtask.data.Node#getUpdateAction(int) - * 生成并返回一个包含了一定数据的JSONObject实体 + /** + * 获取任务列表的更新操作JSON对象 + * @param actionId 操作ID + * @return 更新操作的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 + // 设置操作ID js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - // id + // 设置任务列表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()); @@ -111,20 +122,24 @@ public class TaskList extends Node { return js; } + /** + * 通过远程JSON对象设置任务列表的内容 + * @param js 远程JSON对象 + */ public void setContentByRemoteJSON(JSONObject js) { if (js != null) { try { - // id + // 设置任务列表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)); } @@ -137,6 +152,10 @@ public class TaskList extends Node { } } + /** + * 通过本地JSON对象设置任务列表的内容 + * @param js 本地JSON对象 + */ public void setContentByLocalJSON(JSONObject js) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); @@ -165,6 +184,10 @@ public class TaskList extends Node { } } + /** + * 从任务列表的内容中获取本地JSON对象 + * @return 本地JSON对象 + */ public JSONObject getLocalJSONFromContent() { try { JSONObject js = new JSONObject(); @@ -191,28 +214,33 @@ public class TaskList extends Node { } } + /** + * 获取任务列表的同步操作类型 + * @param c 游标对象,指向数据库中的任务列表 + * @return 同步操作类型 + */ public int getSyncAction(Cursor c) { try { if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update + // 本地没有更新 if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side + // 双方都没有更新 return SYNC_ACTION_NONE; } else { - // apply remote to local + // 应用远程更新到本地 return SYNC_ACTION_UPDATE_LOCAL; } } else { - // validate gtask id + // 验证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; } } @@ -225,39 +253,37 @@ public class TaskList extends Node { } /** - * @return - * 功能:获得TaskList的大小,即mChildren的大小 + * 获取子任务的数量 + * @return 子任务的数量 */ public int getChildTaskCount() { return mChildren.size(); } /** - * @param task - * @return 返回值为是否成功添加任务。 - * 功能:在当前任务表末尾添加新的任务。 + * 添加子任务到任务列表 + * @param task 子任务 + * @return 添加成功返回true,否则返回false */ public boolean addChildTask(Task task) { boolean ret = false; if (task != null && !mChildren.contains(task)) { ret = mChildren.add(task); if (ret) { - // need to set prior sibling and parent + // 设置前一个兄弟节点和父节点 task.setPriorSibling(mChildren.isEmpty() ? null : mChildren .get(mChildren.size() - 1)); task.setParent(this); - //注意:每一次ArrayList的变化都要紧跟相关Task中PriorSibling的更改 - //,接下来几个函数都有相关操作 } } return ret; } /** - * @param task - * @param index - * @return - * 功能:在当前任务表的指定位置添加新的任务。 + * 在指定索引位置添加子任务到任务列表 + * @param task 子任务 + * @param index 索引位置 + * @return 添加成功返回true,否则返回false */ public boolean addChildTask(Task task, int index) { if (index < 0 || index > mChildren.size()) { @@ -269,7 +295,7 @@ public class TaskList extends Node { if (task != null && pos == -1) { mChildren.add(index, task); - // update the task list + // 更新任务列表 Task preTask = null; Task afterTask = null; if (index != 0) @@ -286,9 +312,9 @@ public class TaskList extends Node { } /** - * @param task - * @return 返回删除是否成功 - * 功能:删除TaskList中的一个Task + * 从任务列表中移除子任务 + * @param task 子任务 + * @return 移除成功返回true,否则返回false */ public boolean removeChildTask(Task task) { boolean ret = false; @@ -297,11 +323,11 @@ public class TaskList extends Node { ret = mChildren.remove(task); if (ret) { - // reset prior sibling and parent + // 重置前一个兄弟节点和父节点 task.setPriorSibling(null); task.setParent(null); - // update the task list + // 更新任务列表 if (index != mChildren.size()) { mChildren.get(index).setPriorSibling( index == 0 ? null : mChildren.get(index - 1)); @@ -312,13 +338,12 @@ public class TaskList extends Node { } /** - * @param task - * @param index - * @return - * 功能:将当前TaskList中含有的某个Task移到index位置 + * 移动子任务到任务列表的指定位置 + * @param task 子任务 + * @param index 目标索引位置 + * @return 移动成功返回true,否则返回false */ public boolean moveChildTask(Task task, int index) { - if (index < 0 || index >= mChildren.size()) { Log.e(TAG, "move child task: invalid index"); return false; @@ -333,13 +358,12 @@ public class TaskList extends Node { if (pos == index) return true; return (removeChildTask(task) && addChildTask(task, index)); - //利用已实现好的功能完成当下功能; } /** - * @param gid - * @return返回寻找结果 - * 功能:按gid寻找Task + * 根据GID查找子任务 + * @param gid 子任务的GID + * @return 子任务对象 */ public Task findChildTaskByGid(String gid) { for (int i = 0; i < mChildren.size(); i++) { @@ -352,18 +376,18 @@ public class TaskList extends Node { } /** - * @param task - * @return - * 功能:返回指定Task的index + * 获取子任务在任务列表中的索引位置 + * @param task 子任务 + * @return 索引位置 */ public int getChildTaskIndex(Task task) { return mChildren.indexOf(task); } /** - * @param index - * @return - * 功能:返回指定index的Task + * 根据索引位置获取子任务 + * @param index 索引位置 + * @return 子任务对象 */ public Task getChildTaskByIndex(int index) { if (index < 0 || index >= mChildren.size()) { @@ -374,27 +398,39 @@ public class TaskList extends Node { } /** - * @param gid - * @return - * 功能:返回指定gid的Task + * 根据GID获取子任务 + * @param gid 子任务的GID + * @return 子任务对象 */ public Task getChilTaskByGid(String gid) { - for (Task task : mChildren) {//一种常见的ArrayList的遍历方法(四种,见精读笔记) + for (Task task : mChildren) { if (task.getGid().equals(gid)) return task; } return null; } + /** + * 获取子任务列表 + * @return 子任务列表 + */ public ArrayList getChildTaskList() { return this.mChildren; } + /** + * 设置任务列表的索引 + * @param index 索引 + */ public void setIndex(int index) { this.mIndex = index; } + /** + * 获取任务列表的索引 + * @return 索引 + */ public int getIndex() { return this.mIndex; } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java b/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java index 01e2d99..a4153ea 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/ActionFailureException.java @@ -16,30 +16,33 @@ package net.micode.notes.gtask.exception; -/* - * Description:支持小米便签运行过程中的运行异常处理。 +/** + * ActionFailureException类用于表示操作失败的异常。 + * 该类继承自RuntimeException,并提供了多种构造方法来创建异常对象。 */ - public class ActionFailureException extends RuntimeException { private static final long serialVersionUID = 4425249765923293627L; - /* - * serialVersionUID相当于java类的身份证。主要用于版本控制。 - * serialVersionUID作用是序列化时保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。 - */ + /** + * 默认构造方法,创建一个ActionFailureException对象。 + */ public ActionFailureException() { super(); } - /* - * 在JAVA类中使用super来引用父类的成分,用this来引用当前对象. - * 如果一个类从另外一个类继承,我们new这个子类的实例对象的时候,这个子类对象里面会有一个父类对象。 - * 怎么去引用里面的父类对象呢?使用super来引用 - * 也就是说,此处super()以及super (paramString)可认为是Exception ()和Exception (paramString) + + /** + * 带有异常信息的构造方法,创建一个ActionFailureException对象。 + * @param paramString 异常信息 */ public ActionFailureException(String paramString) { super(paramString); } + /** + * 带有异常信息和原因的构造方法,创建一个ActionFailureException对象。 + * @param paramString 异常信息 + * @param paramThrowable 异常原因 + */ public ActionFailureException(String paramString, Throwable paramThrowable) { super(paramString, paramThrowable); } diff --git a/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java b/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java index 2332b5a..6a3cab9 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/gtask/exception/NetworkFailureException.java @@ -14,34 +14,36 @@ * limitations under the License. */ -/* - * Description:支持小米便签运行过程中的网络异常处理。 - */ - package net.micode.notes.gtask.exception; +/** + * NetworkFailureException类用于表示网络操作失败的异常。 + * 该类继承自Exception,并提供了多种构造方法来创建异常对象。 + */ public class NetworkFailureException extends Exception { private static final long serialVersionUID = 2107610287180234136L; - /* - * serialVersionUID相当于java类的身份证。主要用于版本控制。 - * serialVersionUID作用是序列化时保持版本的兼容性,即在版本升级时反序列化仍保持对象的唯一性。 - */ + /** + * 默认构造方法,创建一个NetworkFailureException对象。 + */ public NetworkFailureException() { super(); } - /* - * 在JAVA类中使用super来引用父类的成分,用this来引用当前对象. - * 如果一个类从另外一个类继承,我们new这个子类的实例对象的时候,这个子类对象里面会有一个父类对象。 - * 怎么去引用里面的父类对象呢?使用super来引用 - * 也就是说,此处super()以及super (paramString)可认为是Exception ()和Exception (paramString) + /** + * 带有异常信息的构造方法,创建一个NetworkFailureException对象。 + * @param paramString 异常信息 */ public NetworkFailureException(String paramString) { super(paramString); } + /** + * 带有异常信息和原因的构造方法,创建一个NetworkFailureException对象。 + * @param paramString 异常信息 + * @param paramThrowable 异常原因 + */ public NetworkFailureException(String paramString, Throwable paramThrowable) { super(paramString, paramThrowable); } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java b/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java index 64edd56..f52dd34 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskASyncTask.java @@ -1,4 +1,3 @@ - /* * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) * @@ -28,31 +27,39 @@ import net.micode.notes.R; import net.micode.notes.ui.NotesListActivity; import net.micode.notes.ui.NotesPreferenceActivity; - -/*异步操作类,实现GTask的异步操作过程 - * 主要方法: - * private void showNotification(int tickerId, String content) 向用户提示当前同步的状态,是一个用于交互的方法 - * protected Integer doInBackground(Void... unused) 此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间 - * protected void onProgressUpdate(String... progress) 可以使用进度条增加用户体验度。 此方法在主线程执行,用于显示任务执行的进度。 - * protected void onPostExecute(Integer result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI +/** + * GTaskASyncTask类用于异步同步任务管理。 + * 该类继承自AsyncTask,并提供了任务的后台执行、进度更新、任务完成的功能。 */ public class GTaskASyncTask extends AsyncTask { - + // 同步通知的ID private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; + /** + * 任务完成监听器接口 + */ public interface OnCompleteListener { void onComplete(); } + // 上下文对象 private Context mContext; + // 通知管理器 private NotificationManager mNotifiManager; + // GTask任务管理器 private GTaskManager mTaskManager; + // 任务完成监听器 private OnCompleteListener mOnCompleteListener; + /** + * 构造函数,初始化GTaskASyncTask对象 + * @param context 上下文对象 + * @param listener 任务完成监听器 + */ public GTaskASyncTask(Context context, OnCompleteListener listener) { mContext = context; mOnCompleteListener = listener; @@ -61,56 +68,80 @@ public class GTaskASyncTask extends AsyncTask { mTaskManager = GTaskManager.getInstance(); } + /** + * 取消同步任务 + */ public void cancelSync() { mTaskManager.cancelSync(); } - public void publishProgess(String message) { // 发布进度单位,系统将会调用onProgressUpdate()方法更新这些值 + /** + * 发布进度信息 + * @param message 进度信息 + */ + public void publishProgess(String message) { publishProgress(new String[] { message }); } + /** + * 显示通知 + * @param tickerId 通知栏提示ID + * @param content 通知内容 + */ 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; //一个描述了想要启动一个Activity、Broadcast或是Service的意图 + notification.defaults = Notification.DEFAULT_LIGHTS; + notification.flags = Notification.FLAG_AUTO_CANCEL; + PendingIntent pendingIntent; if (tickerId != R.string.ticker_success) { pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, - NotesPreferenceActivity.class), 0); //如果同步不成功,那么从系统取得一个用于启动一个NotesPreferenceActivity的PendingIntent对象 + NotesPreferenceActivity.class), 0); } else { pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, - NotesListActivity.class), 0); //如果同步成功,那么从系统取得一个用于启动一个NotesListActivity的PendingIntent对象 + NotesListActivity.class), 0); } - notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, - pendingIntent); - mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);//通过NotificationManager对象的notify()方法来执行一个notification的消息 + //notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, pendingIntent); + mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); } + /** + * 后台执行任务 + * @param unused 不使用的参数 + * @return 同步状态 + */ @Override protected Integer doInBackground(Void... unused) { publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity - .getSyncAccountName(mContext))); //利用getString,将把 NotesPreferenceActivity.getSyncAccountName(mContext))的字符串内容传进sync_progress_login中 - return mTaskManager.sync(mContext, this); //进行后台同步具体操作 + .getSyncAccountName(mContext))); + return mTaskManager.sync(mContext, this); } + /** + * 更新任务进度 + * @param progress 进度信息 + */ @Override protected void onProgressUpdate(String... progress) { showNotification(R.string.ticker_syncing, progress[0]); - if (mContext instanceof GTaskSyncService) { //instanceof 判断mContext是否是GTaskSyncService的实例 + if (mContext instanceof GTaskSyncService) { ((GTaskSyncService) mContext).sendBroadcast(progress[0]); } } + /** + * 任务执行完成后的操作 + * @param result 同步结果状态 + */ @Override - protected void onPostExecute(Integer result) { //用于在执行完后台任务后更新UI,显示结果 + 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()); //设置最新同步的时间 + 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) { @@ -118,14 +149,13 @@ public class GTaskASyncTask extends AsyncTask { } else if (result == GTaskManager.STATE_SYNC_CANCELLED) { showNotification(R.string.ticker_cancel, mContext .getString(R.string.error_sync_cancelled)); - } //几种不同情况下的结果显示 + } if (mOnCompleteListener != null) { - new Thread(new Runnable() { //这里好像是方法内的一个线程,但是并不太懂什么意思 - - public void run() { //完成后的操作,使用onComplete()将所有值都重新初始化,相当于完成一次操作 + new Thread(new Runnable() { + public void run() { mOnCompleteListener.onComplete(); } }).start(); } } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java b/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java index 8bdf6d3..adfe51d 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskClient.java @@ -32,68 +32,79 @@ 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.apache.hc.client5.http.ClientProtocolException; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.cookie.Cookie; +import org.apache.hc.client5.http.cookie.CookieStore; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.apache.hc.core5.util.Timeout; 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.nio.charset.Charset; import java.util.LinkedList; import java.util.List; -import java.util.zip.GZIPInputStream; -import java.util.zip.Inflater; -import java.util.zip.InflaterInputStream; - -/* - * 主要功能:实现GTASK的登录操作,进行GTASK任务的创建,创建任务列表,从网络上获取任务和任务列表的内容 - * 主要使用类或技术:accountManager JSONObject HttpParams authToken Gid +/** + * GTaskClient类用于与Google Tasks服务进行交互。 + * 该类实现了登录、获取任务列表、创建任务和任务列表、提交更新、移动任务和删除节点等功能。 */ public class GTaskClient { + // 日志标签,用于调试输出 private static final String TAG = GTaskClient.class.getSimpleName(); - private static final String GTASK_URL = "https://mail.google.com/tasks/"; //这个是指定的URL + // Google Tasks服务的URL + private static final String GTASK_URL = "https://mail.google.com/tasks/"; + // 获取任务的URL private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; + // 提交任务的URL private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; + // 单例实例 private static GTaskClient mInstance = null; - private DefaultHttpClient mHttpClient; + // HttpClient对象 + private CloseableHttpClient mHttpClient; + // 获取任务的URL private String mGetUrl; + // 提交任务的URL private String mPostUrl; + // 客户端版本 private long mClientVersion; + // 是否已登录 private boolean mLoggedin; + // 最后登录时间 private long mLastLoginTime; + // 操作ID private int mActionId; + // 账号对象 private Account mAccount; + // 更新数组 private JSONArray mUpdateArray; + /** + * 私有构造函数,初始化GTaskClient对象 + */ private GTaskClient() { mHttpClient = null; mGetUrl = GTASK_GET_URL; @@ -106,9 +117,9 @@ public class GTaskClient { mUpdateArray = null; } - /*用来获取的实例化对象 - * 使用 getInstance() - * 返回mInstance这个实例化对象 + /** + * 获取GTaskClient的单例实例 + * @return GTaskClient实例 */ public static synchronized GTaskClient getInstance() { if (mInstance == null) { @@ -117,58 +128,53 @@ public class GTaskClient { return mInstance; } - /*用来实现登录操作的函数,传入的参数是一个Activity - * 设置登录操作限制时间,如果超时则需要重新登录 - * 有两种登录方式,使用用户自己的URL登录或者使用谷歌官方的URL登录 - * 返回true或者false,即最后是否登陆成功 + /** + * 登录Google Tasks服务 + * @param activity 活动对象 + * @return 登录成功返回true,否则返回false */ public boolean login(Activity activity) { - // we suppose that the cookie would expire after 5 minutes - // then we need to re-login - //判断距离最后一次登录操作是否超过5分钟 + // 假设cookie会在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; } - //如果没超过时间,则不需要重新登录 if (mLoggedin) { Log.d(TAG, "already logged in"); return true; } - mLastLoginTime = System.currentTimeMillis();//更新最后登录时间,改为系统当前的时间 - String authToken = loginGoogleAccount(activity, false);//判断是否登录到谷歌账户 + mLastLoginTime = System.currentTimeMillis(); + String authToken = loginGoogleAccount(activity, false); if (authToken == null) { Log.e(TAG, "login google account failed"); return false; } - // login with custom domain if necessary - //尝试使用用户自己的域名登录 - if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() //将用户账号名改为统一格式(小写)后判断是否为一个谷歌账号地址 + // 必要时使用自定义域登录 + 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"; //设置用户对应的getUrl - mPostUrl = url.toString() + "r/ig"; //设置用户对应的postUrl + mGetUrl = url + "ig"; + mPostUrl = url + "r/ig"; if (tryToLoginGtask(activity, authToken)) { mLoggedin = true; } } - // try to login with google official url - //如果用户账户无法登录,则使用谷歌官方的URI进行登录 + // 尝试使用Google官方URL登录 if (!mLoggedin) { mGetUrl = GTASK_GET_URL; mPostUrl = GTASK_POST_URL; @@ -181,15 +187,16 @@ public class GTaskClient { return true; } - /*具体实现登录谷歌账户的方法 - * 使用令牌机制 - * 使用AccountManager来管理注册账号 - * 返回值是账号的令牌 + /** + * 登录Google账号 + * @param activity 活动对象 + * @param invalidateToken 是否使令牌失效 + * @return 登录成功返回授权令牌,否则返回null */ private String loginGoogleAccount(Activity activity, boolean invalidateToken) { - String authToken; //令牌,是登录操作保证安全性的一个方法 - AccountManager accountManager = AccountManager.get(activity);//AccountManager这个类给用户提供了集中注册账号的接口 - Account[] accounts = accountManager.getAccountsByType("com.google");//获取全部以com.google结尾的account + String authToken; + AccountManager accountManager = AccountManager.get(activity); + Account[] accounts = accountManager.getAccountsByType("com.google"); if (accounts.length == 0) { Log.e(TAG, "there is no available google account"); @@ -198,7 +205,6 @@ public class GTaskClient { String accountName = NotesPreferenceActivity.getSyncAccountName(activity); Account account = null; - //遍历获得的accounts信息,寻找已经记录过的账户信息 for (Account a : accounts) { if (a.name.equals(accountName)) { account = a; @@ -212,14 +218,12 @@ public class GTaskClient { 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); - //如果是invalidateToken,那么需要调用invalidateAuthToken(String, String)方法废除这个无效token if (invalidateToken) { accountManager.invalidateAuthToken("com.google", authToken); loginGoogleAccount(activity, false); @@ -232,12 +236,15 @@ public class GTaskClient { return authToken; } - //尝试登陆Gtask,这只是一个预先判断令牌是否是有效以及是否能登上GTask的方法,而不是具体实现登陆的方法 + /** + * 尝试登录Google Tasks服务 + * @param activity 活动对象 + * @param authToken 授权令牌 + * @return 登录成功返回true,否则返回false + */ private boolean tryToLoginGtask(Activity activity, String authToken) { if (!loginGtask(authToken)) { - // maybe the auth token is out of authTokedate, now let's invalidate the - // token and try again - //删除过一个无效的authToken,申请一个新的后再次尝试登陆 + // 可能授权令牌已过期,现在让我们使令牌失效并再次尝试 authToken = loginGoogleAccount(activity, true); if (authToken == null) { Log.e(TAG, "login google account failed"); @@ -252,28 +259,38 @@ public class GTaskClient { return true; } - //实现登录GTask的具体操作 + /** + * 登录Google Tasks服务 + * @param authToken 授权令牌 + * @return 登录成功返回true,否则返回false + */ private boolean loginGtask(String authToken) { + // 连接超时的毫秒数 int timeoutConnection = 10000; - int timeoutSocket = 15000; //socket是一种通信连接实现数据的交换的端口 - HttpParams httpParameters = new BasicHttpParams(); //实例化一个新的HTTP参数类 - HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);//设置连接超时时间 - HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);//设置设置端口超时时间 - mHttpClient = new DefaultHttpClient(httpParameters); - BasicCookieStore localBasicCookieStore = new BasicCookieStore(); //设置本地cookie - mHttpClient.setCookieStore(localBasicCookieStore); - HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); - - // login gtask + // 响应超时的毫秒数 + int timeoutResponse = 15000; + // 实例HttpClient对象 + mHttpClient= HttpClients.createDefault(); + // 设置请求配置 + RequestConfig requestConfig=RequestConfig.custom() + .setConnectionRequestTimeout(Timeout.ofMilliseconds(timeoutConnection)) + .setResponseTimeout(Timeout.ofMilliseconds(timeoutResponse)) + .build(); + + // 登录GTask服务 try { - String loginUrl = mGetUrl + "?auth=" + authToken; //设置登录的url - HttpGet httpGet = new HttpGet(loginUrl); //通过登录的uri实例化网页上资源的查找 - HttpResponse response = null; - response = mHttpClient.execute(httpGet); - - // get the cookie now - //获取CookieStore里存放的cookie,看如果存有“GTL(不知道什么意思)”,则说明有验证成功的有效的cookie - List cookies = mHttpClient.getCookieStore().getCookies(); + String loginUrl = mGetUrl + "?auth=" + authToken; + // 创建Get请求 + HttpGet httpGet=new HttpGet(loginUrl); + httpGet.setConfig(requestConfig); + // 创建Cookie存储器 + CookieStore cookieStore=new BasicCookieStore(); + HttpClientContext httpClientContext=HttpClientContext.create(); + httpClientContext.setCookieStore(cookieStore); + String resString = mHttpClient.execute(httpGet, httpClientContext,new BasicHttpClientResponseHandler()); + + // 获取cookie + List cookies = cookieStore.getCookies(); boolean hasAuthCookie = false; for (Cookie cookie : cookies) { if (cookie.getName().contains("GTL")) { @@ -284,9 +301,7 @@ public class GTaskClient { Log.w(TAG, "it seems that there is no auth cookie"); } - // get the client version - //获取client的内容,具体操作是在返回的Content中截取从_setup(开始到)}中间的字符串内容,也就是gtask_url的内容 - String resString = getResponseContent(response.getEntity()); + // 获取客户端版本 String jsBegin = "_setup("; String jsEnd = ")}"; int begin = resString.indexOf(jsBegin); @@ -302,7 +317,7 @@ public class GTaskClient { e.printStackTrace(); return false; } catch (Exception e) { - // simply catch all exceptions + // 简单捕获所有异常 Log.e(TAG, "httpget gtask_url failed"); return false; } @@ -310,13 +325,17 @@ public class GTaskClient { return true; } + /** + * 获取操作ID + * @return 操作ID + */ private int getActionId() { return mActionId++; } - /*实例化创建一个用于向网络传输数据的对象 - * 使用HttpPost类 - * 返回一个httpPost实例化对象,但里面还没有内容 + /** + * 创建HttpPost对象 + * @return HttpPost对象 */ private HttpPost createHttpPost() { HttpPost httpPost = new HttpPost(mPostUrl); @@ -325,66 +344,27 @@ public class GTaskClient { return httpPost; } - /*通过URL获取响应后返回的数据,也就是网络上的数据和资源 - * 使用getContentEncoding()获取网络上的资源和数据 - * 返回值就是获取到的资源 - */ - private String getResponseContent(HttpEntity entity) throws IOException { - String contentEncoding = null; - if (entity.getContentEncoding() != null) {//通过URL得到HttpEntity对象,如果不为空则使用getContent()方法创建一个流将数据从网络都过来 - contentEncoding = entity.getContentEncoding().getValue(); - Log.d(TAG, "encoding: " + contentEncoding); - } - - InputStream input = entity.getContent(); - if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {//GZIP是使用DEFLATE进行压缩数据的另一个压缩库 - input = new GZIPInputStream(entity.getContent()); - } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {//DEFLATE是一个无专利的压缩算法,它可以实现无损数据压缩 - Inflater inflater = new Inflater(true); - input = new InflaterInputStream(entity.getContent(), inflater); - } - - try { - InputStreamReader isr = new InputStreamReader(input); - BufferedReader br = new BufferedReader(isr);//是一个包装类,它可以包装字符流,将字符流放入缓存里,先把字符读到缓存里,到缓存满了时候,再读入内存,是为了提供读的效率而设计的 - StringBuilder sb = new StringBuilder(); - - while (true) { - String buff = br.readLine(); - if (buff == null) { - return sb.toString(); - } - sb = sb.append(buff); - } - } finally { - input.close(); - } - } - - /*通过JSON发送请求 - * 请求的具体内容在json的实例化对象js中然后传入 - * 利用UrlEncodedFormEntity entity和httpPost.setEntity(entity)方法把js中的内容放置到httpPost中 - * 执行请求后使用getResponseContent方法得到返回的数据和资源 - * 将资源再次放入json后返回 + /** + * 发送POST请求 + * @param js JSON对象 + * @return 响应的JSON对象 + * @throws NetworkFailureException 网络故障异常 */ private JSONObject postRequest(JSONObject js) throws NetworkFailureException { - if (!mLoggedin) {//未登录 + if (!mLoggedin) { Log.e(TAG, "please login first"); throw new ActionFailureException("not logged in"); } - //实例化一个httpPost的对象用来向服务器传输数据,在这里就是发送请求,而请求的内容在js里 HttpPost httpPost = createHttpPost(); try { - LinkedList list = new LinkedList(); + LinkedList list = new LinkedList<>(); list.add(new BasicNameValuePair("r", js.toString())); - UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); //UrlEncodedFormEntity()的形式比较单一,是普通的键值对 + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, Charset.forName("utf8")); httpPost.setEntity(entity); - // execute the post - //执行这个请求 - HttpResponse response = mHttpClient.execute(httpPost); - String jsString = getResponseContent(response.getEntity()); + // 执行POST请求 + String jsString = mHttpClient.execute(httpPost,new BasicHttpClientResponseHandler()); return new JSONObject(jsString); } catch (ClientProtocolException e) { @@ -406,11 +386,10 @@ public class GTaskClient { } } - /*创建单个任务 - * 传入参数是一个.gtask.data.Task包里Task类的对象 - * 利用json获取Task里的内容,并且创建相应的jsPost - * 利用postRequest得到任务的返回信息 - * 使用task.setGid设置task的new_ID + /** + * 创建任务 + * @param task 任务对象 + * @throws NetworkFailureException 网络故障异常 */ public void createTask(Task task) throws NetworkFailureException { commitUpdate(); @@ -438,8 +417,10 @@ public class GTaskClient { } } - /* - * 创建一个任务列表,与createTask几乎一样,区别就是最后设置的是tasklist的gid + /** + * 创建任务列表 + * @param tasklist 任务列表对象 + * @throws NetworkFailureException 网络故障异常 */ public void createTaskList(TaskList tasklist) throws NetworkFailureException { commitUpdate(); @@ -451,7 +432,7 @@ public class GTaskClient { actionList.put(tasklist.getCreateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - // client version + // client_version jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // post @@ -467,10 +448,9 @@ public class GTaskClient { } } - /* - * 同步更新操作 - * 使用JSONObject进行数据存储,使用jsPost.put,Put的信息包括UpdateArray和ClientVersion - * 使用postRequest发送这个jspost,进行处理 + /** + * 提交更新 + * @throws NetworkFailureException 网络故障异常 */ public void commitUpdate() throws NetworkFailureException { if (mUpdateArray != null) { @@ -493,14 +473,14 @@ public class GTaskClient { } } - /* - * 添加更新的事项 - * 调用commitUpdate()实现 + /** + * 添加更新节点 + * @param node 节点对象 + * @throws NetworkFailureException 网络故障异常 */ public void addUpdateNode(Node node) throws NetworkFailureException { if (node != null) { - // too many update items may result in an error - // set max to 10 items + // 太多更新项可能会导致错误,最大设置为10项 if (mUpdateArray != null && mUpdateArray.length() > 10) { commitUpdate(); } @@ -511,11 +491,12 @@ public class GTaskClient { } } - /* - * 移动task,比如讲task移动到不同的task列表中去 - * 通过getGid获取task所属列表的gid - * 通过JSONObject.put(String name, Object value)函数设置移动后的task的相关属性值,从而达到移动的目的 - * 最后还是通过postRequest进行更新后的发送 + /** + * 移动任务 + * @param task 任务对象 + * @param preParent 之前的父任务列表 + * @param curParent 当前的父任务列表 + * @throws NetworkFailureException 网络故障异常 */ public void moveTask(Task task, TaskList preParent, TaskList curParent) throws NetworkFailureException { @@ -531,19 +512,16 @@ public class GTaskClient { action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); if (preParent == curParent && task.getPriorSibling() != null) { - // put prioring_sibing_id only if moving within the tasklist and - // it is not the first one - //设置优先级ID,只有当移动是发生在文件中 + // 仅在任务列表内移动且不是第一个时放置prioring_sibling_id action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); } - action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); //设置移动前所属列表 - action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); //设置当前所属列表 + action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); + action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); if (preParent != curParent) { - // put the dest_list only if moving between tasklists + // 仅在任务列表之间移动时放置dest_list action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); } actionList.put(action); - //最后将ACTION_LIST加入到jsPost中 jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // client_version @@ -558,10 +536,10 @@ public class GTaskClient { } } - /* - * 删除操作节点 - * 还是利用JSON - * 删除过后使用postRequest发送删除后的结果 + /** + * 删除节点 + * @param node 节点对象 + * @throws NetworkFailureException 网络故障异常 */ public void deleteNode(Node node) throws NetworkFailureException { commitUpdate(); @@ -571,7 +549,7 @@ public class GTaskClient { // action_list node.setDeleted(true); - actionList.put(node.getUpdateAction(getActionId())); //这里会获取到删除操作的ID,加入到actionLiast中 + actionList.put(node.getUpdateAction(getActionId())); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // client_version @@ -586,10 +564,10 @@ public class GTaskClient { } } - /* + /** * 获取任务列表 - * 首先通过GetURI使用getResponseContent从网上获取数据 - * 然后筛选出"_setup("到)}的部分,并且从中获取GTASK_JSON_LISTS的内容返回 + * @return 任务列表的JSON数组 + * @throws NetworkFailureException 网络故障异常 */ public JSONArray getTaskLists() throws NetworkFailureException { if (!mLoggedin) { @@ -599,12 +577,9 @@ public class GTaskClient { try { HttpGet httpGet = new HttpGet(mGetUrl); - HttpResponse response = null; - response = mHttpClient.execute(httpGet); - // get the task list - //筛选工作,把筛选出的字符串放入jsString - String resString = getResponseContent(response.getEntity()); + // 获取任务列表 + String resString = mHttpClient.execute(httpGet,new BasicHttpClientResponseHandler()); String jsBegin = "_setup("; String jsEnd = ")}"; int begin = resString.indexOf(jsBegin); @@ -614,7 +589,6 @@ public class GTaskClient { jsString = resString.substring(begin + jsBegin.length(), end); } JSONObject js = new JSONObject(jsString); - //获取GTASK_JSON_LISTS return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); } catch (ClientProtocolException e) { Log.e(TAG, e.toString()); @@ -631,8 +605,11 @@ public class GTaskClient { } } - /* - * 通过传入的TASKList的gid,从网络上获取相应属于这个任务列表的任务 + /** + * 获取指定任务列表的任务 + * @param listGid 任务列表的GID + * @return 任务的JSON数组 + * @throws NetworkFailureException 网络故障异常 */ public JSONArray getTaskList(String listGid) throws NetworkFailureException { commitUpdate(); @@ -645,7 +622,7 @@ public class GTaskClient { action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); - action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); //这里设置为传入的listGid + action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); actionList.put(action); jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); @@ -662,12 +639,18 @@ public class GTaskClient { } } + /** + * 获取同步账号 + * @return 同步账号 + */ public Account getSyncAccount() { return mAccount; } - //重置更新的内容 + /** + * 重置更新数组 + */ public void resetUpdateArray() { mUpdateArray = null; } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java b/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java index 4f8c491..c78ca22 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java @@ -47,48 +47,56 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; - +/** + * GTaskManager类负责管理与Google Task的同步操作。 + * 它实现了同步任务列表和任务、处理元数据、管理同步状态和处理网络或操作失败的情况。 + */ 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; + + public static final int STATE_SUCCESS = 0; // 同步成功 + public static final int STATE_NETWORK_ERROR = 1; // 网络错误 + public static final int STATE_INTERNAL_ERROR = 2; // 内部错误 + public static final int STATE_SYNC_IN_PROGRESS = 3; // 同步进行中 + public static final int STATE_SYNC_CANCELLED = 4; // 同步取消 + + private static GTaskManager mInstance = null; // 单例实例 private Activity mActivity; private Context mContext; private ContentResolver mContentResolver; - private boolean mSyncing; - private boolean mCancelled; - private HashMap mGTaskListHashMap; - private HashMap mGTaskHashMap; - private HashMap mMetaHashMap; - private TaskList mMetaList; - private HashSet mLocalDeleteIdMap; - private HashMap mGidToNid; - private HashMap mNidToGid; - - private GTaskManager() { //对象初始化函数 - mSyncing = false; //正在同步,flase代表未执行 - mCancelled = false; //全局标识,flase代表可以执行 - mGTaskListHashMap = new HashMap(); //<>代表Java的泛型,就是创建一个用类型作为参数的类。 + + private boolean mSyncing; // 同步状态标志 + private boolean mCancelled; // 取消同步标志 + + private HashMap mGTaskListHashMap; // 任务列表映射 + private HashMap mGTaskHashMap; // 任务节点映射 + private HashMap mMetaHashMap; // 元数据映射 + + private TaskList mMetaList; // 元数据列表 + private HashSet mLocalDeleteIdMap; // 本地删除的ID集合 + + private HashMap mGidToNid; // GID到NID的映射 + private HashMap mNidToGid; // NID到GID的映射 + + private GTaskManager() { + mSyncing = false; + mCancelled = false; + mGTaskListHashMap = new HashMap(); mGTaskHashMap = new HashMap(); mMetaHashMap = new HashMap(); mMetaList = null; mLocalDeleteIdMap = new HashSet(); - mGidToNid = new HashMap(); //GoogleID to NodeID?? - mNidToGid = new HashMap(); //NodeID to GoogleID???通过hashmap散列表建立映射 + mGidToNid = new HashMap(); + mNidToGid = new HashMap(); } /** - * 包含关键字synchronized,语言级同步,指明该函数可能运行在多线程的环境下。 - * 功能:类初始化函数 - * @author TTS - * @return GtaskManger + * 获取GTaskManager的单例实例。 + * + * @return 单例实例 */ - public static synchronized GTaskManager getInstance() { //可能运行在多线程环境下,使用语言级同步--synchronized + public static synchronized GTaskManager getInstance() { if (mInstance == null) { mInstance = new GTaskManager(); } @@ -96,26 +104,24 @@ public class GTaskManager { } /** - * 包含关键字synchronized,语言级同步,指明该函数可能运行在多线程的环境下。 - * @author TTS - * @param activity + * 设置Activity上下文,用于获取AuthToken。 + * + * @param activity Activity实例 */ public synchronized void setActivityContext(Activity activity) { - // used for getting auth token mActivity = activity; } /** - * 核心函数 - * 功能:实现了本地同步操作和远端同步操作 - * @author TTS - * @param context-----获取上下文 - * @param asyncTask-------用于同步的异步操作类 - * @return int + * 执行同步操作。 + * + * @param context 上下文 + * @param asyncTask 异步任务 + * @return 同步状态 */ - public int sync(Context context, GTaskASyncTask asyncTask) { //核心函数 + public int sync(Context context, GTaskASyncTask asyncTask) { if (mSyncing) { - Log.d(TAG, "Sync is in progress"); //创建日志文件(调试信息),debug + Log.d(TAG, "Sync is in progress"); return STATE_SYNC_IN_PROGRESS; } mContext = context; @@ -130,27 +136,27 @@ public class GTaskManager { mNidToGid.clear(); try { - GTaskClient client = GTaskClient.getInstance(); //getInstance即为创建一个实例,client--客户机 - client.resetUpdateArray(); //JSONArray类型,reset即置为NULL + GTaskClient client = GTaskClient.getInstance(); + client.resetUpdateArray(); - // login google task + // 登录Google Task if (!mCancelled) { if (!client.login(mActivity)) { throw new NetworkFailureException("login google task failed"); } } - // get the task list from google + // 从Google获取任务列表 asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); - initGTaskList(); //获取Google上的JSONtasklist转为本地TaskList + initGTaskList(); - // do content sync work + // 执行内容同步工作 asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); syncContent(); - } catch (NetworkFailureException e) { //分为两种异常,此类异常为网络异常 - Log.e(TAG, e.toString()); //创建日志文件(调试信息),error + } catch (NetworkFailureException e) { + Log.e(TAG, e.toString()); return STATE_NETWORK_ERROR; - } catch (ActionFailureException e) { //此类异常为操作异常 + } catch (ActionFailureException e) { Log.e(TAG, e.toString()); return STATE_INTERNAL_ERROR; } catch (Exception e) { @@ -171,40 +177,35 @@ public class GTaskManager { } /** - *功能:初始化GtaskList,获取Google上的JSONtasklist转为本地TaskList。 - *获得的数据存储在mMetaList,mGTaskListHashMap,mGTaskHashMap - *@author TTS - *@exception NetworkFailureException - *@return void + * 初始化GTask任务列表。 + * + * @throws NetworkFailureException 网络故障异常 */ private void initGTaskList() throws NetworkFailureException { if (mCancelled) return; - GTaskClient client = GTaskClient.getInstance(); //getInstance即为创建一个实例,client应指远端客户机 + GTaskClient client = GTaskClient.getInstance(); try { - //Json对象是Name Value对(即子元素)的无序集合,相当于一个Map对象。JsonObject类是bantouyan-json库对Json对象的抽象,提供操纵Json对象的各种方法。 - //其格式为{"key1":value1,"key2",value2....};key 必须是字符串。 - //因为ajax请求不刷新页面,但配合js可以实现局部刷新,因此json常常被用来作为异步请求的返回对象使用。 - JSONArray jsTaskLists = client.getTaskLists(); //原注释为get task list------lists??? + JSONArray jsTaskLists = client.getTaskLists(); - // init meta list first - mMetaList = null; //TaskList类型 + // 初始化元数据列表 + mMetaList = null; for (int i = 0; i < jsTaskLists.length(); i++) { - JSONObject object = jsTaskLists.getJSONObject(i); //JSONObject与JSONArray一个为对象,一个为数组。此处取出单个JASONObject + 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(); //MetaList意为元表,Tasklist类型,此处为初始化 - mMetaList.setContentByRemoteJSON(object); //将JSON中部分数据复制到自己定义的对象中相对应的数据:name->mname... + mMetaList = new TaskList(); + mMetaList.setContentByRemoteJSON(object); - // load meta data - JSONArray jsMetas = client.getTaskList(gid); //原注释为get action_list------list??? + // 加载元数据 + JSONArray jsMetas = client.getTaskList(gid); for (int j = 0; j < jsMetas.length(); j++) { - object = (JSONObject) jsMetas.getJSONObject(j); - MetaData metaData = new MetaData(); //继承自Node + object = jsMetas.getJSONObject(j); + MetaData metaData = new MetaData(); metaData.setContentByRemoteJSON(object); - if (metaData.isWorthSaving()) { //if not worth to save,metadata将不加入mMetaList + if (metaData.isWorthSaving()) { mMetaList.addChildTask(metaData); if (metaData.getGid() != null) { mMetaHashMap.put(metaData.getRelatedGid(), metaData); @@ -214,32 +215,30 @@ public class GTaskManager { } } - // create meta list if not existed + // 如果元数据列表不存在则创建 if (mMetaList == null) { mMetaList = new TaskList(); - mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_META); + mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META); GTaskClient.getInstance().createTaskList(mMetaList); } - // init task list + // 初始化任务列表 for (int i = 0; i < jsTaskLists.length(); i++) { JSONObject object = jsTaskLists.getJSONObject(i); - String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); //通过getString函数传入本地某个标志数据的名称,获取其在远端的名称。 + String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); - if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) - && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_META)) { - TaskList tasklist = new TaskList(); //继承自Node + 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); //为什么加两遍??? + mGTaskHashMap.put(gid, tasklist); - // load tasks + // 加载任务 JSONArray jsTasks = client.getTaskList(gid); for (int j = 0; j < jsTasks.length(); j++) { - object = (JSONObject) jsTasks.getJSONObject(j); + object = jsTasks.getJSONObject(j); gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); Task task = new Task(); task.setContentByRemoteJSON(object); @@ -259,23 +258,23 @@ public class GTaskManager { } /** - * 功能:本地内容同步操作 - * @throws NetworkFailureException - * @return 无返回值 + * 执行内容同步。 + * + * @throws NetworkFailureException 网络故障异常 */ - private void syncContent() throws NetworkFailureException { //本地内容同步操作 + private void syncContent() throws NetworkFailureException { int syncType; - Cursor c = null; //数据库指针 - String gid; //GoogleID?? - Node node; //Node包含Sync_Action的不同类型 + Cursor c = null; + String gid; + Node node; - mLocalDeleteIdMap.clear(); //HashSet类型 + mLocalDeleteIdMap.clear(); if (mCancelled) { return; } - // for local deleted note + // 处理本地删除的便签 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type<>? AND parent_id=?)", new String[] { @@ -302,10 +301,10 @@ public class GTaskManager { } } - // sync folder first + // 同步文件夹 syncFolder(); - // for note existing in database + // 处理数据库中现有的便签 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type=? AND parent_id<>?)", new String[] { @@ -317,15 +316,13 @@ public class GTaskManager { node = mGTaskHashMap.get(gid); if (node != null) { mGTaskHashMap.remove(gid); - mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); //通过hashmap建立联系 - mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); //通过hashmap建立联系 + 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; } } @@ -334,7 +331,6 @@ public class GTaskManager { } else { Log.w(TAG, "failed to query existing note in database"); } - } finally { if (c != null) { c.close(); @@ -342,35 +338,30 @@ public class GTaskManager { } } - // go through remaining items - Iterator> iter = mGTaskHashMap.entrySet().iterator(); //Iterator迭代器 + // 处理剩余项 + Iterator> iter = mGTaskHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); node = entry.getValue(); doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); } - // mCancelled can be set by another thread, so we neet to check one by //thread----线程 - // one - // clear local delete table if (!mCancelled) { if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { throw new ActionFailureException("failed to batch-delete local deleted notes"); } } - // refresh local sync id if (!mCancelled) { GTaskClient.getInstance().commitUpdate(); refreshLocalSyncId(); } - } /** - * 功能: - * @author TTS - * @throws NetworkFailureException + * 同步文件夹。 + * + * @throws NetworkFailureException 网络故障异常 */ private void syncFolder() throws NetworkFailureException { Cursor c = null; @@ -382,7 +373,7 @@ public class GTaskManager { return; } - // for root folder + // 处理根文件夹 try { c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); @@ -394,9 +385,7 @@ public class GTaskManager { mGTaskHashMap.remove(gid); mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); - // for system folder, only update remote name if necessary - if (!node.getName().equals( - GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) + 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); @@ -411,7 +400,7 @@ public class GTaskManager { } } - // for call-note folder + // 处理通话记录文件夹 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", new String[] { @@ -425,11 +414,7 @@ public class GTaskManager { mGTaskHashMap.remove(gid); mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); - // for system folder, only update remote name if - // necessary - if (!node.getName().equals( - GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_CALL_NOTE)) + 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); @@ -445,7 +430,7 @@ public class GTaskManager { } } - // for local existing folders + // 处理本地现有文件夹 try { c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(type=? AND parent_id<>?)", new String[] { @@ -462,10 +447,8 @@ public class GTaskManager { 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; } } @@ -481,7 +464,7 @@ public class GTaskManager { } } - // for remote add folders + // 处理远程添加文件夹 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -498,12 +481,12 @@ public class GTaskManager { } /** - * 功能:syncType分类,addLocalNode,addRemoteNode,deleteNode,updateLocalNode,updateRemoteNode - * @author TTS - * @param syncType - * @param node - * @param c - * @throws NetworkFailureException + * 执行内容同步操作。 + * + * @param syncType 同步类型 + * @param node 节点 + * @param c 游标 + * @throws NetworkFailureException 网络故障异常 */ private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { @@ -539,8 +522,6 @@ public class GTaskManager { updateRemoteNode(node, c); break; case Node.SYNC_ACTION_UPDATE_CONFLICT: - // merging both modifications maybe a good idea - // right now just use local update simply updateRemoteNode(node, c); break; case Node.SYNC_ACTION_NONE: @@ -552,10 +533,10 @@ public class GTaskManager { } /** - * 功能:本地增加Node - * @author TTS - * @param node - * @throws NetworkFailureException + * 添加本地节点。 + * + * @param node 节点 + * @throws NetworkFailureException 网络故障异常 */ private void addLocalNode(Node node) throws NetworkFailureException { if (mCancelled) { @@ -564,11 +545,9 @@ public class GTaskManager { SqlNote sqlNote; if (node instanceof TaskList) { - if (node.getName().equals( - GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { + 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)) { + } 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); @@ -584,7 +563,6 @@ public class GTaskManager { if (note.has(NoteColumns.ID)) { long id = note.getLong(NoteColumns.ID); if (DataUtils.existInNoteDatabase(mContentResolver, id)) { - // the id is not available, have to create a new one note.remove(NoteColumns.ID); } } @@ -597,13 +575,10 @@ public class GTaskManager { if (data.has(DataColumns.ID)) { long dataId = data.getLong(DataColumns.ID); if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { - // the data id is not available, have to create - // a new one data.remove(DataColumns.ID); } } } - } } catch (JSONException e) { Log.w(TAG, e.toString()); @@ -619,26 +594,21 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); } - // create the local node sqlNote.setGtaskId(node.getGid()); sqlNote.commit(false); - // update gid-nid mapping mGidToNid.put(node.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), node.getGid()); - // update meta updateRemoteMeta(node.getGid(), sqlNote); } /** - * 功能:update本地node - * @author TTS - * @param node - * ----同步操作的基础数据类型 - * @param c - * ----Cursor - * @throws NetworkFailureException + * 更新本地节点。 + * + * @param node 节点 + * @param c 游标 + * @throws NetworkFailureException 网络故障异常 */ private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { @@ -646,7 +616,6 @@ public class GTaskManager { } SqlNote sqlNote; - // update the note locally sqlNote = new SqlNote(mContext, c); sqlNote.setContent(node.getLocalJSONFromContent()); @@ -659,50 +628,42 @@ public class GTaskManager { sqlNote.setParentId(parentId.longValue()); sqlNote.commit(true); - // update meta info updateRemoteMeta(node.getGid(), sqlNote); } /** - * 功能:远程增加Node - * 需要updateRemoteMeta - * @author TTS - * @param node - * ----同步操作的基础数据类型 - * @param c - * --Cursor - * @throws NetworkFailureException + * 添加远程节点。 + * + * @param node 节点 + * @param c 游标 + * @throws NetworkFailureException 网络故障异常 */ private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; } - SqlNote sqlNote = new SqlNote(mContext, c); //从本地mContext中获取内容 + SqlNote sqlNote = new SqlNote(mContext, c); Node n; - // update remotely if (sqlNote.isNoteType()) { Task task = new Task(); task.setContentByLocalJSON(sqlNote.getContent()); String parentGid = mNidToGid.get(sqlNote.getParentId()); if (parentGid == null) { - Log.e(TAG, "cannot find task's parent tasklist"); //调试信息 + Log.e(TAG, "cannot find task's parent tasklist"); throw new ActionFailureException("cannot add remote task"); } - mGTaskListHashMap.get(parentGid).addChildTask(task); //在本地生成的GTaskList中增加子结点 + mGTaskListHashMap.get(parentGid).addChildTask(task); - //登录远程服务器,创建Task GTaskClient.getInstance().createTask(task); n = (Node) task; - // add meta updateRemoteMeta(task.getGid(), sqlNote); } else { TaskList tasklist = null; - // we need to skip folder if it has already existed String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) folderName += GTaskStringUtils.FOLDER_DEFAULT; @@ -711,7 +672,6 @@ public class GTaskManager { else folderName += sqlNote.getSnippet(); - //iterator迭代器,通过统一的接口迭代所有的map元素 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -727,7 +687,6 @@ public class GTaskManager { } } - // no match we can add now if (tasklist == null) { tasklist = new TaskList(); tasklist.setContentByLocalJSON(sqlNote.getContent()); @@ -737,25 +696,21 @@ public class GTaskManager { n = (Node) tasklist; } - // update local note sqlNote.setGtaskId(n.getGid()); sqlNote.commit(false); sqlNote.resetLocalModified(); sqlNote.commit(true); - // gid-id mapping //创建id间的映射 mGidToNid.put(n.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), n.getGid()); } /** - * 功能:更新远端的Node,包含meta更新(updateRemoteMeta) - * @author TTS - * @param node - * ----同步操作的基础数据类型 - * @param c - * --Cursor - * @throws NetworkFailureException + * 更新远程节点。 + * + * @param node 节点 + * @param c 游标 + * @throws NetworkFailureException 网络故障异常 */ private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { @@ -764,50 +719,39 @@ public class GTaskManager { SqlNote sqlNote = new SqlNote(mContext, c); - // update remotely node.setContentByLocalJSON(sqlNote.getContent()); - GTaskClient.getInstance().addUpdateNode(node); //GTaskClient用途为从本地登陆远端服务器 + GTaskClient.getInstance().addUpdateNode(node); - // update meta updateRemoteMeta(node.getGid(), sqlNote); - // move task if necessary if (sqlNote.isNoteType()) { Task task = (Task) node; TaskList preParentList = task.getParent(); - //preParentList为通过node获取的父节点列表 String curParentGid = mNidToGid.get(sqlNote.getParentId()); - //curParentGid为通过光标在数据库中找到sqlNote的mParentId,再通过mNidToGid由long类型转为String类型的Gid - if (curParentGid == null) { Log.e(TAG, "cannot find task's parent tasklist"); throw new ActionFailureException("cannot update remote task"); } TaskList curParentList = mGTaskListHashMap.get(curParentGid); - //通过HashMap找到对应Gid的TaskList - if (preParentList != curParentList) { //????????????? + if (preParentList != curParentList) { preParentList.removeChildTask(task); curParentList.addChildTask(task); GTaskClient.getInstance().moveTask(task, preParentList, curParentList); } } - // clear local modified flag sqlNote.resetLocalModified(); - //commit到本地数据库 sqlNote.commit(true); } /** - * 功能:升级远程meta。 meta---元数据----计算机文件系统管理数据---管理数据的数据。 - * @author TTS - * @param gid - * ---GoogleID为String类型 - * @param sqlNote - * ---同步前的数据库操作,故使用类SqlNote - * @throws NetworkFailureException + * 更新远程元数据。 + * + * @param gid GID + * @param sqlNote SqlNote实例 + * @throws NetworkFailureException 网络故障异常 */ private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { if (sqlNote != null && sqlNote.isNoteType()) { @@ -826,17 +770,15 @@ public class GTaskManager { } /** - * 功能:刷新本地,给sync的ID对应上最后更改过的对象 - * @author TTS - * @return void - * @throws NetworkFailureException + * 刷新本地同步ID。 + * + * @throws NetworkFailureException 网络故障异常 */ private void refreshLocalSyncId() throws NetworkFailureException { if (mCancelled) { return; } - // get the latest gtask list //获取最近的(最晚的)gtask list mGTaskHashMap.clear(); mGTaskListHashMap.clear(); mMetaHashMap.clear(); @@ -847,16 +789,16 @@ public class GTaskManager { 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"); //query语句:五个参数,NoteColumns.TYPE + " DESC"-----为按类型递减顺序返回查询结果。new String[] {String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)}------为选择参数。"(type<>? AND parent_id<>?)"-------指明返回行过滤器。SqlNote.PROJECTION_NOTE--------应返回的数据列的名字。Notes.CONTENT_NOTE_URI--------contentProvider包含所有数据集所对应的uri + }, NoteColumns.TYPE + " DESC"); if (c != null) { while (c.moveToNext()) { String gid = c.getString(SqlNote.GTASK_ID_COLUMN); Node node = mGTaskHashMap.get(gid); if (node != null) { mGTaskHashMap.remove(gid); - ContentValues values = new ContentValues(); //在ContentValues中创建键值对。准备通过contentResolver写入数据 + ContentValues values = new ContentValues(); values.put(NoteColumns.SYNC_ID, node.getLastModified()); - mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, //进行批量更改,选择参数为NULL,应该可以用insert替换,参数分别为表名和需要更新的value对象。 + mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(SqlNote.ID_COLUMN)), values, null, null); } else { Log.e(TAG, "something is missed"); @@ -876,19 +818,18 @@ public class GTaskManager { } /** - * 功能:获取同步账号,mAccount.name - * @author TTS - * @return String + * 获取同步账户。 + * + * @return 同步账户名称 */ public String getSyncAccount() { return GTaskClient.getInstance().getSyncAccount().name; } /** - * 功能:取消同步,置mCancelled为true - * @author TTS + * 取消同步操作。 */ public void cancelSync() { mCancelled = true; } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java b/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java index 950b06e..f943200 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/gtask/remote/GTaskSyncService.java @@ -23,22 +23,10 @@ import android.content.Intent; import android.os.Bundle; import android.os.IBinder; -/* - * Service是在一段不定的时间运行在后台,不和用户交互的应用组件 - * 主要方法: - * private void startSync() 启动一个同步工作 - * private void cancelSync() 取消同步 - * public void onCreate() - * public int onStartCommand(Intent intent, int flags, int startId) service生命周期的组成部分,相当于重启service(比如在被暂停之后),而不是创建一个新的service - * public void onLowMemory() 在没有内存的情况下如果存在service则结束掉这的service - * public IBinder onBind() - * public void sendBroadcast(String msg) 发送同步的相关通知 - * public static void startSync(Activity activity) - * public static void cancelSync(Context context) - * public static boolean isSyncing() 判读是否在进行同步 - * public static String getProgressString() 获取当前进度的信息 +/** + * GTaskSyncService负责在后台执行GTask的同步操作。 + * 它使用GTaskASyncTask来执行实际的同步工作,并在同步过程中发送广播通知。 */ - public class GTaskSyncService extends Service { public final static String ACTION_STRING_NAME = "sync_action_type"; @@ -58,7 +46,10 @@ public class GTaskSyncService extends Service { private static String mSyncProgress = ""; - //开始一个同步的工作 + /** + * 开始同步任务。 + * 如果当前没有同步任务在进行,则创建一个新的GTaskASyncTask并执行。 + */ private void startSync() { if (mSyncTask == null) { mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { @@ -69,11 +60,14 @@ public class GTaskSyncService extends Service { } }); sendBroadcast(""); - mSyncTask.execute(); //这个函数让任务是以单线程队列方式或线程池队列方式运行 + mSyncTask.execute(); } } - + /** + * 取消当前的同步任务。 + * 如果当前有同步任务在进行,则取消它。 + */ private void cancelSync() { if (mSyncTask != null) { mSyncTask.cancelSync(); @@ -81,7 +75,7 @@ public class GTaskSyncService extends Service { } @Override - public void onCreate() { //初始化一个service + public void onCreate() { mSyncTask = null; } @@ -90,7 +84,6 @@ public class GTaskSyncService extends Service { Bundle bundle = intent.getExtras(); if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { - //两种情况,开始同步或者取消同步 case ACTION_START_SYNC: startSync(); break; @@ -100,7 +93,7 @@ public class GTaskSyncService extends Service { default: break; } - return START_STICKY; //等待新的intent来是这个service继续运行 + return START_STICKY; } return super.onStartCommand(intent, flags, startId); } @@ -112,36 +105,56 @@ public class GTaskSyncService extends Service { } } - public IBinder onBind(Intent intent) { //不知道干吗用的 + public IBinder onBind(Intent intent) { return null; } + /** + * 发送广播通知同步状态。 + * @param msg 同步进度信息 + */ public void sendBroadcast(String msg) { mSyncProgress = msg; - Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); //创建一个新的Intent - intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); //附加INTENT中的相应参数的值 + 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); //发送这个通知 + sendBroadcast(intent); } - public static void startSync(Activity activity) {//执行一个service,service的内容里的同步动作就是开始同步 + /** + * 静态方法,启动同步服务。 + * @param activity 调用该方法的Activity + */ + public static void startSync(Activity activity) { GTaskManager.getInstance().setActivityContext(activity); Intent intent = new Intent(activity, GTaskSyncService.class); intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); activity.startService(intent); } - public static void cancelSync(Context context) {//执行一个service,service的内容里的同步动作就是取消同步 + /** + * 静态方法,取消同步服务。 + * @param context 调用该方法的Context + */ + public static void cancelSync(Context context) { Intent intent = new Intent(context, GTaskSyncService.class); intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); context.startService(intent); } + /** + * 检查是否有同步任务在进行。 + * @return 如果有同步任务在进行,返回true;否则返回false。 + */ public static boolean isSyncing() { return mSyncTask != null; } + /** + * 获取同步进度信息。 + * @return 同步进度信息字符串 + */ public static String getProgressString() { return mSyncProgress; } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/model/WorkingNote.java b/Notesmaster/app/src/main/java/net/micode/notes/model/WorkingNote.java index be081e4..3560ace 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/model/WorkingNote.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/model/WorkingNote.java @@ -38,7 +38,7 @@ public class WorkingNote { // Note Id private long mNoteId; // Note content - private String mContent; + public String mContent; // Note mode private int mMode; @@ -60,6 +60,8 @@ public class WorkingNote { private boolean mIsDeleted; + private boolean mIsLocked; + private NoteSettingChangedListener mNoteSettingStatusListener; public static final String[] DATA_PROJECTION = new String[] { @@ -78,7 +80,8 @@ public class WorkingNote { NoteColumns.BG_COLOR_ID, NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, - NoteColumns.MODIFIED_DATE + NoteColumns.MODIFIED_DATE, + NoteColumns.LOCKED, }; private static final int DATA_ID_COLUMN = 0; @@ -101,6 +104,11 @@ public class WorkingNote { private static final int NOTE_MODIFIED_DATE_COLUMN = 5; + private static final int NOTE_LOCKED_COLUMN = 6; + + private static final String LOCKED = "locked"; + private static final String UNLOCKED = "unlocked"; + // New note construct private WorkingNote(Context context, long folderId) { mContext = context; @@ -137,6 +145,8 @@ public class WorkingNote { mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); + + mIsLocked = !cursor.getString(NOTE_LOCKED_COLUMN).equals(UNLOCKED); } cursor.close(); } else { @@ -149,7 +159,7 @@ public class WorkingNote { private void loadNoteData() { Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { - String.valueOf(mNoteId) + String.valueOf(mNoteId) }, null); if (cursor != null) { @@ -175,7 +185,7 @@ public class WorkingNote { } public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, - int widgetType, int defaultBgColorId) { + int widgetType, int defaultBgColorId) { WorkingNote note = new WorkingNote(context, folderId); note.setBgColorId(defaultBgColorId); note.setWidgetId(widgetId); @@ -217,12 +227,8 @@ public class WorkingNote { } private boolean isWorthSaving() { - if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) - || (existInDatabase() && !mNote.isLocalModified())) { - return false; - } else { - return true; - } + return !mIsDeleted && (existInDatabase() || !TextUtils.isEmpty(mContent)) + && (!existInDatabase() || mNote.isLocalModified()); } public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { @@ -243,7 +249,7 @@ public class WorkingNote { mIsDeleted = mark; if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onWidgetChanged(); + mNoteSettingStatusListener.onWidgetChanged(); } } @@ -257,6 +263,17 @@ public class WorkingNote { } } + public void setLock(boolean lock) { + if (lock != mIsLocked) { + mIsLocked = lock; + if(lock){ + mNote.setNoteValue(NoteColumns.LOCKED, LOCKED); + }else { + mNote.setNoteValue(NoteColumns.LOCKED, UNLOCKED); + } + } + } + public void setCheckListMode(int mode) { if (mMode != mode) { if (mNoteSettingStatusListener != null) { @@ -295,7 +312,7 @@ public class WorkingNote { } public boolean hasClockAlert() { - return (mAlertDate > 0 ? true : false); + return mAlertDate > 0; } public String getContent() { @@ -342,6 +359,14 @@ public class WorkingNote { return mWidgetType; } + public boolean getIsLocked(){ + return mIsLocked; + } + + public void setIsLocked(boolean locked){ + mIsLocked = locked; + } + public interface NoteSettingChangedListener { /** * Called when the background color of current note has just changed @@ -365,4 +390,5 @@ public class WorkingNote { */ void onCheckListModeChanged(int oldMode, int newMode); } + } diff --git a/Notesmaster/app/src/main/java/net/micode/notes/tool/BackupUtils.java b/Notesmaster/app/src/main/java/net/micode/notes/tool/BackupUtils.java index 7b3aa61..c26472a 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/tool/BackupUtils.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/tool/BackupUtils.java @@ -39,14 +39,10 @@ import java.io.PrintStream; public class BackupUtils { private static final String TAG = "BackupUtils"; // Singleton stuff - private static BackupUtils sInstance; //类里面为什么可以定义自身类的对象? + private static BackupUtils sInstance; public static synchronized BackupUtils getInstance(Context context) { - //ynchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A) - //运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行。 - //它包括两种用法:synchronized 方法和 synchronized 块。 if (sInstance == null) { - //如果当前备份不存在,则新声明一个 sInstance = new BackupUtils(context); } return sInstance; @@ -56,24 +52,24 @@ public class BackupUtils { * Following states are signs to represents backup or restore * status */ - // Currently, the sdcard is not mounted SD卡没有被装入手机 + // Currently, the sdcard is not mounted public static final int STATE_SD_CARD_UNMOUONTED = 0; - // The backup file not exist 备份文件夹不存在 + // 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 数据已被破坏,可能被修改 + // 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 超时异常 + // Some run-time exception which causes restore or backup fails public static final int STATE_SYSTEM_ERROR = 3; - // Backup or restore success 成功存储 + // Backup or restore success public static final int STATE_SUCCESS = 4; private TextExport mTextExport; - private BackupUtils(Context context) { //初始化函数 + private BackupUtils(Context context) { mTextExport = new TextExport(context); } - private static boolean externalStorageAvailable() { //外部存储功能是否可用 + private static boolean externalStorageAvailable() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } @@ -132,11 +128,11 @@ public class BackupUtils { public TextExport(Context context) { TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); mContext = context; - mFileName = ""; //为什么为空? + mFileName = ""; mFileDirectory = ""; } - private String getFormat(int id) { //获取文本的组成部分 + private String getFormat(int id) { return TEXT_FORMAT[id]; } @@ -144,7 +140,7 @@ public class BackupUtils { * 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 + // Query notes belong to this folder Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { folderId @@ -153,13 +149,13 @@ public class BackupUtils { if (notesCursor != null) { if (notesCursor.moveToFirst()) { do { - // Print note's last modified date ps里面保存有这份note的日期 + // Print note's last modified date ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( mContext.getString(R.string.format_datetime_mdhm), notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); // Query data belong to this note String noteId = notesCursor.getString(NOTE_COLUMN_ID); - exportNoteToText(noteId, ps); //将文件导出到text + exportNoteToText(noteId, ps); } while (notesCursor.moveToNext()); } notesCursor.close(); @@ -175,7 +171,7 @@ public class BackupUtils { noteId }, null); - if (dataCursor != null) { //利用光标来扫描内容,区别为callnote和note两种,靠ps.printline输出 + if (dataCursor != null) { if (dataCursor.moveToFirst()) { do { String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); @@ -185,7 +181,7 @@ public class BackupUtils { long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); String location = dataCursor.getString(DATA_COLUMN_CONTENT); - if (!TextUtils.isEmpty(phoneNumber)) { //判断是否为空字符 + if (!TextUtils.isEmpty(phoneNumber)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), phoneNumber)); } @@ -222,7 +218,7 @@ public class BackupUtils { /** * Note will be exported as text which is user readable */ - public int exportToText() { //总函数,调用上面的exportFolder和exportNote + public int exportToText() { if (!externalStorageAvailable()) { Log.d(TAG, "Media was not mounted"); return STATE_SD_CARD_UNMOUONTED; @@ -233,7 +229,7 @@ public class BackupUtils { Log.e(TAG, "get print stream error"); return STATE_SYSTEM_ERROR; } - // First export folder and its notes 导出文件夹,就是导出里面包含的便签 + // First export folder and its notes Cursor folderCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, @@ -261,7 +257,7 @@ public class BackupUtils { folderCursor.close(); } - // Export notes in root's folder 将根目录里的便签导出(由于不属于任何文件夹,因此无法通过文件夹导出来实现这一部分便签的导出) + // Export notes in root's folder Cursor noteCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, @@ -301,7 +297,7 @@ public class BackupUtils { PrintStream ps = null; try { FileOutputStream fos = new FileOutputStream(file); - ps = new PrintStream(fos); //将ps输出流输出到特定的文件,目的就是导出到文件,而不是直接输出 + ps = new PrintStream(fos); } catch (FileNotFoundException e) { e.printStackTrace(); return null; @@ -318,16 +314,16 @@ public class BackupUtils { */ private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { StringBuilder sb = new StringBuilder(); - sb.append(Environment.getExternalStorageDirectory()); //外部(SD卡)的存储路径 - sb.append(context.getString(filePathResId)); //文件的存储路径 - File filedir = new File(sb.toString()); //filedir应该就是用来存储路径信息 + sb.append(Environment.getExternalStorageDirectory()); + sb.append(context.getString(filePathResId)); + File filedir = new File(sb.toString()); sb.append(context.getString( fileNameFormatResId, DateFormat.format(context.getString(R.string.format_date_ymd), System.currentTimeMillis()))); File file = new File(sb.toString()); - try { //如果这些文件不存在,则新建 + try { if (!filedir.exists()) { filedir.mkdir(); } @@ -340,7 +336,9 @@ public class BackupUtils { } catch (IOException e) { e.printStackTrace(); } -// try catch 异常处理 + return null; } } + + diff --git a/Notesmaster/app/src/main/java/net/micode/notes/tool/DataUtils.java b/Notesmaster/app/src/main/java/net/micode/notes/tool/DataUtils.java index 191d589..81b6bcc 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/tool/DataUtils.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/tool/DataUtils.java @@ -34,9 +34,10 @@ 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) { //直接删除多个笔记 + public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { if (ids == null) { Log.d(TAG, "the ids is null"); return true; @@ -46,27 +47,24 @@ public class DataUtils { return true; } - ArrayList operationList = new ArrayList(); //提供一个任务列表 + 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)); //用newDelete实现删除功能 - operationList.add(builder.build()); // + .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); + operationList.add(builder.build()); } try { - ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);//主机名(或叫Authority)用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。 - //数据库事务,数据库事务是由一组数据库操作序列组成,事务作为一个整体被执行 + 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) { + } catch (RemoteException | OperationApplicationException e) { Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); } return false; @@ -77,7 +75,7 @@ public class DataUtils { 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); //对需要移动的便签进行数据更新,然后用update实现 + resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); } public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids, @@ -90,14 +88,14 @@ public class DataUtils { ArrayList operationList = new ArrayList(); for (long id : ids) { ContentProviderOperation.Builder builder = ContentProviderOperation - .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); //通过withAppendedId方法,为该Uri加上ID + .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); builder.withValue(NoteColumns.PARENT_ID, folderId); builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); operationList.add(builder.build()); - }//将ids里包含的每一列的数据逐次加入到operationList中,等待最后的批量处理 + } try { - ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); //applyBatch一次性处理一个操作列表 + 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; @@ -119,7 +117,7 @@ public class DataUtils { new String[] { "COUNT(*)" }, NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, - null); //筛选条件:源文件不为trash folder + null); int count = 0; if(cursor != null) { @@ -137,15 +135,15 @@ public class DataUtils { } public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { - Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), //通过withAppendedId方法,为该Uri加上ID + 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); //查询条件:type符合,且不属于垃圾文件夹 + null); boolean exist = false; if (cursor != null) { - if (cursor.getCount() > 0) {//用getcount函数判断cursor是否为空 + if (cursor.getCount() > 0) { exist = true; } cursor.close(); @@ -187,7 +185,22 @@ public class DataUtils { " 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 boolean checkFolderId(ContentResolver resolver, long id) { + Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.ID + "=?", + new String[] { String.valueOf(id) }, null); boolean exist = false; if(cursor != null) { if(cursor.getCount() > 0) { @@ -203,7 +216,7 @@ public class DataUtils { new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, NoteColumns.PARENT_ID + "=?", new String[] { String.valueOf(folderId) }, - null); //查询条件:父ID是传入的folderId; + null); HashSet set = null; if (c != null) { @@ -212,13 +225,13 @@ public class DataUtils { do { try { AppWidgetAttribute widget = new AppWidgetAttribute(); - widget.widgetId = c.getInt(0); //0对应的NoteColumns.WIDGET_ID - widget.widgetType = c.getInt(1); //1对应的NoteColumns.WIDGET_TYPE + widget.widgetId = c.getInt(0); + widget.widgetType = c.getInt(1); set.add(widget); } catch (IndexOutOfBoundsException e) { Log.e(TAG, e.toString()); } - } while (c.moveToNext()); //查询下一条 + } while (c.moveToNext()); } c.close(); } @@ -251,12 +264,11 @@ public class DataUtils { + CallNote.PHONE_NUMBER + ",?)", new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber }, null); - //通过数据库操作,查询条件是(callDate和phoneNumber匹配传入参数的值) if (cursor != null) { if (cursor.moveToFirst()) { try { - return cursor.getLong(0); //0对应的CallNote.NOTE_ID + return cursor.getLong(0); } catch (IndexOutOfBoundsException e) { Log.e(TAG, "Get call note id fails " + e.toString()); } @@ -271,7 +283,7 @@ public class DataUtils { new String [] { NoteColumns.SNIPPET }, NoteColumns.ID + "=?", new String [] { String.valueOf(noteId)}, - null);//查询条件:noteId + null); if (cursor != null) { String snippet = ""; @@ -283,7 +295,75 @@ public class DataUtils { } throw new IllegalArgumentException("Note is not found with id: " + noteId); } - public static String getFormattedSnippet(String snippet) { //对字符串进行格式处理,将字符串两头的空格去掉,同时将换行符去掉 + + public static long getTrashIdByName(ContentResolver resolver, String name) { + Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, + new String [] { NoteColumns.ID }, + NoteColumns.SNIPPET + "=? AND "+NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER, + new String [] { name }, + null); + if (cursor != null) { + long id = 0; + if (cursor.moveToFirst()) { + id = cursor.getLong(0); + } + cursor.close(); + return id; + } + throw new IllegalArgumentException("getTrashIdByName is not found"); + } + + public static long getParentIdbyId(ContentResolver resolver,long id){ + Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, + new String [] { NoteColumns.PARENT_ID }, + NoteColumns.ID + "=? AND "+NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER, + new String [] { String.valueOf(id) }, + null); + if (cursor != null) { + long pid = 0; + if (cursor.moveToFirst()) { + pid = cursor.getLong(0); + } + cursor.close(); + return pid; + } + throw new IllegalArgumentException("getParentIdbyId is not found"); + } + + public static HashSet getHasLockedByFolderId(ContentResolver resolver,long fid){ + HashSet sset = new HashSet<>(); + Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, + new String [] { NoteColumns.LOCKED }, + NoteColumns.PARENT_ID + "=? AND "+NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, + new String [] { String.valueOf(fid) }, + null); + if (cursor != null) { + if (cursor.moveToFirst()) { + do{ + sset.add(cursor.getString(0)); + } while (cursor.moveToNext()); + } + cursor.close(); + } + + Cursor cursor2 = resolver.query(Notes.CONTENT_NOTE_URI, + new String [] { NoteColumns.ID }, + NoteColumns.PARENT_ID + "=? AND "+NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER, + new String [] { String.valueOf(fid) }, + null); + if (cursor2 != null) { + if(cursor2.moveToFirst()){ + do{ + HashSet tmp = getHasLockedByFolderId(resolver,cursor2.getLong(0)); + sset.addAll(tmp); + } while (cursor2.moveToNext()); + } + cursor2.close(); + } + return sset; + } + + public static String getFormattedSnippet(String snippet) { if (snippet != null) { snippet = snippet.trim(); int index = snippet.indexOf('\n'); @@ -293,5 +373,4 @@ public class DataUtils { } return snippet; } - -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/tool/GTaskStringUtils.java b/Notesmaster/app/src/main/java/net/micode/notes/tool/GTaskStringUtils.java index a659211..666b729 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/tool/GTaskStringUtils.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/tool/GTaskStringUtils.java @@ -15,10 +15,7 @@ */ package net.micode.notes.tool; -//简介:定义了很多的静态字符串,目的就是为了提供jsonObject中相应字符串的"key"。把这些静态的定义单独写到了一个类里面,这是非常好的编程规范 - -//这个类就是定义了一堆static string,实际就是为jsonObject提供Key,把这些定义全部写到一个类里,方便查看管理,是一个非常好的编程习惯 public class GTaskStringUtils { public final static String GTASK_JSON_ACTION_ID = "action_id"; @@ -113,4 +110,4 @@ public class GTaskStringUtils { public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/tool/ResourceParser.java b/Notesmaster/app/src/main/java/net/micode/notes/tool/ResourceParser.java index 7578b68..2d574c3 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/tool/ResourceParser.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/tool/ResourceParser.java @@ -22,21 +22,6 @@ import android.preference.PreferenceManager; import net.micode.notes.R; import net.micode.notes.ui.NotesPreferenceActivity; -/*简介:字面意义是资源分析器,实际上就是获取资源并且在程序中使用,比如颜色图片等 - * 实现方法:主要利用R.java这个类,其中包括 - * R.id 组件资源引用 - * R.drawable 图片资源 (被使用) - * R.layout 布局资源 - * R.menu 菜单资源 - * R.String 文字资源 - * R.style 主题资源 (被使用) - * 在按顺序设置好相应的id后,就可以编写简单的getXXX函数获取需要的资源 - * - * 特殊的变量 : - * @BG_DEFAULT_COLOR 默认背景颜色(黄) - * BG_DEFAULT_FONT_SIZE 默认文本大小(中) - */ - public class ResourceParser { public static final int YELLOW = 0; @@ -45,7 +30,7 @@ public class ResourceParser { public static final int GREEN = 3; public static final int RED = 4; - public static final int BG_DEFAULT_COLOR = YELLOW; + public static final int BG_DEFAULT_COLOR = WHITE; public static final int TEXT_SMALL = 0; public static final int TEXT_MEDIUM = 1; @@ -79,7 +64,7 @@ public class ResourceParser { return BG_EDIT_TITLE_RESOURCES[id]; } } - //直接获取默认的背景颜色。看不太懂,这个PREFERENCE_SET_BG_COLOR_KEY是个final string,也就是说getBoolean肯定执行else,为什么要这么写 + public static int getDefaultBgId(Context context) { if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { @@ -140,6 +125,7 @@ public class ResourceParser { public static int getFolderBgRes() { return R.drawable.list_folder; +// return R.drawable.listtest; } } @@ -153,7 +139,7 @@ public class ResourceParser { }; public static int getWidget2xBgResource(int id) { - return BG_2X_RESOURCES[id]; + return BG_2X_RESOURCES[2]; } private final static int [] BG_4X_RESOURCES = new int [] { @@ -165,7 +151,7 @@ public class ResourceParser { }; public static int getWidget4xBgResource(int id) { - return BG_4X_RESOURCES[id]; + return BG_4X_RESOURCES[2]; } } @@ -177,7 +163,6 @@ public class ResourceParser { R.style.TextAppearanceSuper }; - //这里有一个容错的函数,防止输入的id大于资源总量,若如此,则自动返回默认的设置结果 public static int getTexAppearanceResource(int id) { /** * HACKME: Fix bug of store the resource id in shared preference. @@ -194,4 +179,4 @@ public class ResourceParser { return TEXTAPPEARANCE_RESOURCES.length; } } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java index fc6a3ee..b71e250 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmAlertActivity.java @@ -1,3 +1,19 @@ +/* + * 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; @@ -23,9 +39,10 @@ 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 long mNoteId;//文本在数据库存储中的ID号 + private String mSnippet;//闹钟提示时出现的文本片段 private static final int SNIPPET_PREW_MAX_LEN = 60; MediaPlayer mPlayer; @@ -37,7 +54,6 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD //能从onCreate的参数savedInsanceState中获得状态数据 requestWindowFeature(Window.FEATURE_NO_TITLE); //界面显示——无标题 - final Window win = getWindow(); win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); @@ -49,12 +65,13 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD | 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)); + mNoteId = Long.parseLong(intent.getData().getPathSegments().get(1)); mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); //根据ID从数据库中获取标签的内容; //getContentResolver()是实现数据共享,实例存储。 @@ -66,25 +83,20 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD e.printStackTrace(); return; } - /* - try + /*try { - // 代码区 + //代码区 } - catch(Exception e) - { - // 异常处理 + catch(Exception e){ + //异常处理 } - 代码区如果有错误,就会返回所写异常的处理。*/ + 代码区如果有错误,就会返回所写异常的处理。*/ mPlayer = new MediaPlayer(); if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { - showActionDialog(); - //弹出对话框 - playAlarmSound(); - //闹钟提示音激发 + showActionDialog();//弹出对话框 + playAlarmSound();//闹钟提示音激发 } else { - finish(); - //完成闹钟动作 + finish();//完成闹钟动作 } } @@ -119,8 +131,8 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); - //e.printStackTrace()函数功能是抛出异常, 还将显示出更深的调用信息 - //System.out.println(e),这个方法打印出异常,并且输出在哪里出现的异常 + //e.printStackTrace()函数功能是抛出异常, 还将显示出更深的调用信息; + //System.out.println(e),这个方法打印出异常,并且输出在哪里出现的异常; } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -135,58 +147,49 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD 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"按钮 + //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"按钮 + }//对话框添加"No"按钮; dialog.show().setOnDismissListener(this); } public void onClick(DialogInterface dialog, int which) { - switch (which) { - //用which来选择click后下一步的操作 + 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为键 + //实现key-value对; + //EXTRA_UID为key;mNoteId为键; startActivity(intent); - //开始动作 + //开始动作; break; default: - //这是确定操作 + //这是确定操作; break; } } - public void onDismiss(DialogInterface dialog) { - //忽略 - stopAlarmSound(); - //停止闹钟声音 - finish(); - //完成该动作 + public void onDismiss(DialogInterface dialog) {//忽略 + stopAlarmSound();//停止闹钟声音 + finish();//完成该动作 } private void stopAlarmSound() { if (mPlayer != null) { - mPlayer.stop(); - //停止播放 - mPlayer.release(); - //释放MediaPlayer对象 + mPlayer.stop();//停止播放 + mPlayer.release();//释放MediaPlayer对象 mPlayer = null; } } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java index 4163ccd..bec850f 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmInitReceiver.java @@ -18,22 +18,22 @@ public class AlarmInitReceiver extends BroadcastReceiver { NoteColumns.ID, NoteColumns.ALERTED_DATE }; - //对数据库的操作,调用标签ID和闹钟时间 + //对数据库的操作,调用标签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时起的毫秒数 + //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转化为字符串 + //将long变量currentDate转化为字符串; null); - //Cursor在这里的作用是通过查找数据库中的标签内容,找到和当前系统时间相等的标签 + //Cursor在这里的作用是通过查找数据库中的标签内容,找到和当前系统时间相等的标签; if (c != null) { if (c.moveToFirst()) { @@ -41,7 +41,8 @@ public class AlarmInitReceiver extends BroadcastReceiver { 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); + + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, PendingIntent.FLAG_IMMUTABLE); AlarmManager alermManager = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); @@ -49,8 +50,8 @@ public class AlarmInitReceiver extends BroadcastReceiver { } c.close(); } - //然而通过网上查找资料发现,对于闹钟机制的启动,通常需要上面的几个步骤 - //如新建Intent、PendingIntent以及AlarmManager等 - //这里就是根据数据库里的闹钟时间创建一个闹钟机制 + //然而通过网上查找资料发现,对于闹钟机制的启动,通常需要上面的几个步骤; + //如新建Intent、PendingIntent以及AlarmManager等; + //这里就是根据数据库里的闹钟时间创建一个闹钟机制; } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java index 6e22aff..cacce64 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/AlarmReceiver.java @@ -1,3 +1,19 @@ +/* + * 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; @@ -15,6 +31,3 @@ public class AlarmReceiver extends BroadcastReceiver { context.startActivity(intent); } } -//这是实现alarm这个功能最接近用户层的包,基于上面的两个包, -//作用还需要深究但是对于setClass和addFlags的 - \ No newline at end of file diff --git a/Notesmaster/app/src/main/java/net/micode/notes/ui/DateTimePicker.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/DateTimePicker.java index 8d74062..02227df 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/DateTimePicker.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/DateTimePicker.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.micode.notes.ui; import java.text.DateFormatSymbols; @@ -13,10 +29,11 @@ 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; @@ -30,15 +47,17 @@ public class DateTimePicker extends FrameLayout { 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; - //初始化控件 + + //NumberPicker是数字选择器 + //这里定义的四个变量全部是在设置闹钟时需要选择的变量(如日期、时、分、上午或者下午) private final NumberPicker mDateSpinner; private final NumberPicker mHourSpinner; private final NumberPicker mMinuteSpinner; private final NumberPicker mAmPmSpinner; - //NumberPicker是数字选择器 - //这里定义的四个变量全部是在设置闹钟时需要选择的变量(如日期、时、分、上午或者下午) - private Calendar mDate; + //定义了Calendar类型的变量mDate,用于操作时间 + private Calendar mDate; + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; private boolean mIsAm; @@ -53,21 +72,22 @@ public class DateTimePicker extends FrameLayout { private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { @Override + //OnValueChangeListener,这是时间改变监听器,这里主要是对日期的监听 public void onValueChange(NumberPicker picker, int oldVal, int newVal) { mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); + //将现在日期的值传递给mDate;updateDateControl是同步操作 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,便于后续的操作 + 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()); @@ -79,7 +99,7 @@ public class DateTimePicker extends FrameLayout { cal.add(Calendar.DAY_OF_YEAR, -1); isDateChanged = true; } - //这里是对于12小时制时,凌晨11点和12点交替时对日期的更改 + //这里是对于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; @@ -96,11 +116,10 @@ public class DateTimePicker extends FrameLayout { 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的赋值 + } //这里是对于24小时制时,晚上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 + //通过数字选择器对newHour的赋值 onDateTimeChanged(); if (isDateChanged) { setCurrentYear(cal.get(Calendar.YEAR)); @@ -117,7 +136,7 @@ public class DateTimePicker extends FrameLayout { int minValue = mMinuteSpinner.getMinValue(); int maxValue = mMinuteSpinner.getMaxValue(); int offset = 0; - //设置offset,作为小时改变的一个记录数据 + //这里是对 分钟(Minute)改变的监听 if (oldVal == maxValue && newVal == minValue) { offset += 1; } else if (oldVal == minValue && newVal == maxValue) { @@ -144,8 +163,8 @@ public class DateTimePicker extends FrameLayout { }; private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { - //对AM和PM的监听 @Override + //对AM和PM的监听 public void onValueChange(NumberPicker picker, int oldVal, int newVal) { mIsAm = !mIsAm; if (mIsAm) { @@ -165,15 +184,14 @@ public class DateTimePicker extends FrameLayout { 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); - //获取系统时间 + super(context); //获取系统时间 mDate = Calendar.getInstance(); mInitialising = true; mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; @@ -188,7 +206,7 @@ public class DateTimePicker extends FrameLayout { mHourSpinner = (NumberPicker) findViewById(R.id.hour); mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); - mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); + mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); mMinuteSpinner.setOnLongPressUpdateInterval(100); @@ -229,9 +247,7 @@ public class DateTimePicker extends FrameLayout { mAmPmSpinner.setEnabled(enabled); mIsEnabled = enabled; } - //存在疑问!!!!!!!!!!!!!setEnabled的作用 - //下面的代码通过原程序的注释已经比较清晰,另外可以通过函数名来判断 - //下面的各函数主要是对上面代码引用到的各函数功能的实现 + @Override public boolean isEnabled() { return mIsEnabled; @@ -242,6 +258,7 @@ public class DateTimePicker extends FrameLayout { * * @return the current date in millis */ + public long getCurrentDateInTimeMillis() { return mDate.getTimeInMillis(); }//实现函数——得到当前的秒数 @@ -281,7 +298,6 @@ public class DateTimePicker extends FrameLayout { * * @return The current year */ - //下面是得到year、month、day等值 public int getCurrentYear() { return mDate.get(Calendar.YEAR); } @@ -461,8 +477,8 @@ public class DateTimePicker extends FrameLayout { int index = mIsAm ? Calendar.AM : Calendar.PM; mAmPmSpinner.setValue(index); mAmPmSpinner.setVisibility(View.VISIBLE); - }// 对于上下午操作的算法 - } + } + }// 对于星期几的算法 private void updateHourControl() { if (mIs24HourView) { @@ -471,8 +487,8 @@ public class DateTimePicker extends FrameLayout { } 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. @@ -488,4 +504,4 @@ public class DateTimePicker extends FrameLayout { getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); } } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java index d6ad95e..cf41fd5 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/DateTimePickerDialog.java @@ -1,3 +1,19 @@ +/* + * 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; @@ -15,11 +31,10 @@ import android.text.format.DateUtils; public class DateTimePickerDialog extends AlertDialog implements OnClickListener { - private Calendar mDate = Calendar.getInstance(); - //创建一个Calendar类型的变量 mDate,方便时间的操作 + private Calendar mDate = Calendar.getInstance(); //创建一个Calendar类型的变量 mDate,方便时间的操作 + private boolean mIs24HourView; - private OnDateTimeSetListener mOnDateTimeSetListener; - //声明一个时间日期滚动选择控件 mOnDateTimeSetListener + private OnDateTimeSetListener mOnDateTimeSetListener; //声明一个时间日期滚动选择控件 mOnDateTimeSetListener private DateTimePicker mDateTimePicker; //DateTimePicker控件,控件一般用于让用户可以从日期列表中选择单个值。 //运行时,单击控件边上的下拉箭头,会显示为两个部分:一个下拉列表,一个用于选择日期的 @@ -29,10 +44,10 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener } public DateTimePickerDialog(Context context, long date) { - //对该界面对话框的实例化 super(context); - //对数据库的操作 + //对该界面对话框的实例化 mDateTimePicker = new DateTimePicker(context); + //对该界面对话框的实例化 setView(mDateTimePicker); //添加一个子视图 mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { @@ -47,16 +62,12 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener updateTitle(mDate.getTimeInMillis()); } }); - mDate.setTimeInMillis(date); - //得到系统时间 - mDate.set(Calendar.SECOND, 0); - //将秒数设置为0 + 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())); - //时间标准化打印 + setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); //设置按钮 + set24HourView(DateFormat.is24HourFormat(this.getContext())); //时间标准化打印 updateTitle(mDate.getTimeInMillis()); } @@ -66,7 +77,7 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { mOnDateTimeSetListener = callBack; - }//将时间日期滚动选择控件实例化 + } //时间标准化打印 private void updateTitle(long date) { int flag = @@ -75,13 +86,11 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener DateUtils.FORMAT_SHOW_TIME; flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); - }//android开发中常见日期管理工具类(API)——DateUtils:按照上下午显示时间 + } //android开发中常见日期管理工具类(API)——DateUtils:按照上下午显示时间 public void onClick(DialogInterface arg0, int arg1) { if (mOnDateTimeSetListener != null) { mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); } - }//第一个参数arg0是接收到点击事件的对话框 - //第二个参数arg1是该对话框上的按钮 - + }//第一个参数arg0是接收到点击事件的对话框 第二个参数arg1是该对话框上的按钮 } \ No newline at end of file diff --git a/Notesmaster/app/src/main/java/net/micode/notes/ui/DropdownMenu.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/DropdownMenu.java index c831a9b..f2cc0e0 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/DropdownMenu.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/DropdownMenu.java @@ -1,3 +1,19 @@ +/* + * 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; @@ -13,14 +29,12 @@ import net.micode.notes.R; public class DropdownMenu { private Button mButton; - private PopupMenu mPopupMenu; - //声明一个下拉菜单 + private PopupMenu mPopupMenu; //声明一个下拉菜单 private Menu mMenu; public DropdownMenu(Context context, Button button, int menuId) { mButton = button; - mButton.setBackgroundResource(R.drawable.dropdown_icon); - //设置这个view的背景 + mButton.setBackgroundResource(R.drawable.dropdown_icon); //设置这个view的背景 mPopupMenu = new PopupMenu(context, mButton); mMenu = mPopupMenu.getMenu(); mPopupMenu.getMenuInflater().inflate(menuId, mMenu); @@ -46,4 +60,4 @@ public class DropdownMenu { public void setTitle(CharSequence title) { mButton.setText(title); }//布局文件,设置标题 -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java index b6867fb..dfb5f8f 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/FoldersListAdapter.java @@ -1,3 +1,19 @@ +/* + * 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; @@ -68,4 +84,4 @@ public class FoldersListAdapter extends CursorAdapter { } } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java index c33c34a..dcfa201 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -1,24 +1,51 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.micode.notes.ui; -import android.app.Activity; +import android.Manifest; import android.app.AlarmManager; import android.app.AlertDialog; import android.app.PendingIntent; import android.app.SearchManager; import android.appwidget.AppWidgetManager; +import android.content.ContentResolver; import android.content.ContentUris; +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.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.graphics.Paint; +import android.net.Uri; import android.os.Bundle; +import android.os.Environment; import android.preference.PreferenceManager; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.text.Editable; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.BackgroundColorSpan; +import android.text.style.ImageSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -31,11 +58,18 @@ import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; +import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import androidx.activity.result.ActivityResult; +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.appcompat.app.AppCompatActivity; + import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.TextNote; @@ -49,6 +83,7 @@ import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; import net.micode.notes.widget.NoteWidgetProvider_2x; import net.micode.notes.widget.NoteWidgetProvider_4x; +import java.io.FileNotFoundException; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -56,7 +91,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -public class NoteEditActivity extends Activity implements OnClickListener, +public class NoteEditActivity extends AppCompatActivity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { //该类主要是针对标签的编辑 //继承了系统内部许多和监听有关的类 @@ -68,6 +103,10 @@ public class NoteEditActivity extends Activity implements OnClickListener, public TextView tvAlertDate; public ImageView ibSetBgColor; + + public ImageView ivLocked; + + public ImageView ivUnlocked; } //使用Map实现数据存储 private static final Map sBgSelectorBtnsMap = new HashMap(); @@ -112,24 +151,23 @@ public class NoteEditActivity extends Activity implements OnClickListener, private HeadViewHolder mNoteHeaderHolder; - private View mHeadViewPanel; - //私有化一个界面操作mHeadViewPanel,对表头的操作 - private View mNoteBgColorSelector; - //私有化一个界面操作mNoteBgColorSelector,对背景颜色的操作 - private View mFontSizeSelector; - //私有化一个界面操作mFontSizeSelector,对标签字体的操作 - private EditText mNoteEditor; - //声明编辑控件,对文本操作 + private View mHeadViewPanel; //私有化一个界面操作mHeadViewPanel,对表头的操作 + + private View mNoteBgColorSelector; //私有化一个界面操作mHeadViewPanel,对表头的操作 + private View mFontSizeSelector;//私有化一个界面操作mFontSizeSelector,对标签字体的操作 + + private EditText mNoteEditor; //声明编辑控件,对文本操作 + private View mNoteEditorPanel; //私有化一个界面操作mNoteEditorPanel,文本编辑的控制板 //private WorkingNote mWorkingNote; - public WorkingNote mWorkingNote; - //对模板WorkingNote的初始化 + private WorkingNote mWorkingNote; //对模板WorkingNote的初始化 + private SharedPreferences mSharedPrefs; //私有化SharedPreferences的数据存储方式 //它的本质是基于XML文件存储key-value键值对数据 - private int mFontSizeId; - //用于操作字体的大小 + private int mFontSizeId; //用于操作字体的大小 + private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; @@ -137,16 +175,20 @@ public class NoteEditActivity extends Activity implements OnClickListener, public static final String TAG_CHECKED = String.valueOf('\u221A'); public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); - private LinearLayout mEditTextList; - //线性布局 + private LinearLayout mEditTextList; //线性布局 private String mUserQuery; private Pattern mPattern; + private ImageButton mImageButton; // 添加图片按钮 + + private ActivityResultLauncher requestPermissionLauncher; //获取存储权限 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.note_edit); //对数据库的访问操作 +// setContentView(R.layout.activity_main); + if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; @@ -185,7 +227,6 @@ public class NoteEditActivity extends Activity implements OnClickListener, /** * 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); @@ -241,12 +282,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, finish(); return false; } - //将电话号码与手机的号码簿相关 - } else { + } //将电话号码与手机的号码簿相关 + else { mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId); mWorkingNote.convertToCallNote(phoneNumber, callDate); - // } } else { mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, @@ -298,6 +338,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, * is not ready */ showAlertHeader(); + //将有图片路径的位置转换为图片 + requestPermissionLauncher.launch(Manifest.permission.READ_MEDIA_IMAGES); } //设置闹钟的显示 private void showAlertHeader() { @@ -305,8 +347,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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)); @@ -317,7 +358,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, } else { mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); - }; + } + if(mWorkingNote.getIsLocked()){ + mNoteHeaderHolder.ivUnlocked.setVisibility(View.GONE); + mNoteHeaderHolder.ivLocked.setVisibility(View.VISIBLE); + }else{ + mNoteHeaderHolder.ivUnlocked.setVisibility(View.VISIBLE); + mNoteHeaderHolder.ivLocked.setVisibility(View.GONE); + } + } @Override @@ -368,9 +417,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, if (ev.getX() < x || ev.getX() > (x + view.getWidth()) || ev.getY() < y - || ev.getY() > (y + view.getHeight())) - //如果触控的位置超出了给定的范围,返回false - { + || ev.getY() > (y + view.getHeight())) { + //如果触控的位置超出了给定的范围,返回false return false; } return true; @@ -384,6 +432,10 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); + + mNoteHeaderHolder.ivLocked = (ImageView) findViewById(R.id.tv_locked); + mNoteHeaderHolder.ivUnlocked = (ImageView) findViewById(R.id.tv_unlocked); + mNoteEditor = (EditText) findViewById(R.id.note_edit_view); mNoteEditorPanel = findViewById(R.id.sv_note_edit); mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); @@ -408,6 +460,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; } mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); + + // 获取读取存储权限 + requestPermissionLauncher=getrequestDataLauncher(); + // 初始化添加图片按钮 + mImageButton = (ImageButton) findViewById(R.id.add_img_btn); + ActivityResultLauncher intentActivityResultLauncher=getActivityResultLauncher(); + mImageButton.setOnClickListener(view -> onAddImage(intentActivityResultLauncher)); } @Override @@ -443,7 +502,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, if (id == R.id.btn_set_bg_color) { mNoteBgColorSelector.setVisibility(View.VISIBLE); findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - - View.VISIBLE); + View.VISIBLE); } else if (sBgSelectorBtnsMap.containsKey(id)) { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.GONE); @@ -463,7 +522,21 @@ public class NoteEditActivity extends Activity implements OnClickListener, } mFontSizeSelector.setVisibility(View.GONE); } - }//************************存在问题 + } + + public void onChangeLock(View v){ + int id = v.getId(); + switch (id){ + case R.id.tv_locked: + mWorkingNote.setLock(false); + break; + case R.id.tv_unlocked: + + mWorkingNote.setLock(true); + break; + } + showAlertHeader(); + } @Override public void onBackPressed() { @@ -493,9 +566,34 @@ public class NoteEditActivity extends Activity implements OnClickListener, mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } +// @Override +// public boolean onCreateOptionsMenu(Menu menu){ +// if (isFinishing()) { +// return true; +// } +// clearSettingState(); +// menu.clear(); +// if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { +// getMenuInflater().inflate(R.menu.call_note_edit, menu); +// } else { +// getMenuInflater().inflate(R.menu.note_edit, menu); +// } +// if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { +// menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); +// } else { +// menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); +// } +// if (mWorkingNote.hasClockAlert()) { +// menu.findItem(R.id.menu_alert).setVisible(false); +// } else { +// menu.findItem(R.id.menu_delete_remind).setVisible(false); +// } +// return true; +// } + @Override - //对选择菜单的准备 public boolean onPrepareOptionsMenu(Menu menu) { +// getMenuInflater().inflate(R.menu.call_note_edit, menu); if (isFinishing()) { return true; } @@ -503,7 +601,6 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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); } @@ -521,71 +618,45 @@ public class NoteEditActivity extends Activity implements OnClickListener, } @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: @@ -594,170 +665,112 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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() { + saveNote(); 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"); - } +// 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; + return true; } - /* - * 函数功能:设置提醒时间 - * 函数实现:如下注释 - */ 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); + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); 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 + updateWidget(); } - /* - * 函数功能: 删除编辑文本框所触发的事件 - * 函数实现:如下注释 - */ public void onEditTextDelete(int index, String text) { int childCount = mEditTextList.getChildCount(); if (childCount == 1) { return; } - //没有编辑框的话直接返回 + for (int i = index + 1; i < childCount; i++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) .setIndex(i - 1); - //通过id把编辑框存在便签编辑框中 } mEditTextList.removeViewAt(index); - //删除特定位置的视图 NoteEditText edit = null; if(index == 0) { edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( @@ -766,101 +779,69 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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位置处的条目 + edit.requestFocus(); + edit.setSelection(length); } - /* - * 函数功能:进入编辑文本框所触发的事件 - * 函数实现:如下注释 - */ public void onEditTextEnter(int index, String text) { /** * Should not happen, check for debug */ if(index > mEditTextList.getChildCount()) { Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); - //越界把偶偶 } View view = getListItem(text, index); mEditTextList.addView(view, index); - //建立一个新的视图并添加到编辑文本框内 NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - edit.requestFocus();//请求优先操作 - edit.setSelection(0);//定位到起始位置 + 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) { @@ -871,15 +852,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } }); - //建立一个打钩框并设置监听器 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(); @@ -888,94 +866,61 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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, @@ -984,16 +929,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, * 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 @@ -1002,16 +942,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, */ 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, @@ -1019,12 +955,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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 @@ -1032,8 +965,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, * should input something */ Log.e(TAG, "Send to desktop error"); - showToast(R.string.error_note_empty_for_send_to_desktop); - //空便签直接报错 + showToast(R.string.error_note_empty_for_send_to_desktop); //空便签直接报错 } } @@ -1064,4 +996,234 @@ public class NoteEditActivity extends Activity implements OnClickListener, private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).show(); } -} \ No newline at end of file + + /** + * @author: ClearDewy + * @date: 2023/3/9 11:20 + * @param: [android.content.Context, android.net.Uri] + * @return: java.lang.String + * @description:根据Url解析文件路径 + **/ + public static String getPathFromUri(final Context context, final Uri uri) { + if (uri == null) { + return null; + } + // 判斷是否為Android 4.4之後的版本 + if (DocumentsContract.isDocumentUri(context, uri)) { + // 如果是Android 4.4之後的版本,而且屬於文件URI + final String authority = uri.getAuthority(); + // 判斷Authority是否為本地端檔案所使用的 + if ("com.android.externalstorage.documents".equals(authority)) { + // 外部儲存空間 + final String docId = DocumentsContract.getDocumentId(uri); + final String[] divide = docId.split(":"); + final String type = divide[0]; + if ("primary".equals(type)) { + String path = Environment.getExternalStorageDirectory().getAbsolutePath().concat("/").concat(divide[1]); + return path; + } else { + String path = "/storage/".concat(type).concat("/").concat(divide[1]); + return path; + } + } else if ("com.android.providers.downloads.documents".equals(authority)) { + // 下載目錄 + final String docId = DocumentsContract.getDocumentId(uri); + if (docId.startsWith("raw:")) { + final String path = docId.replaceFirst("raw:", ""); + return path; + } + final Uri downloadUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.parseLong(docId)); + String path = queryAbsolutePath(context, downloadUri); + return path; + } else if ("com.android.providers.media.documents".equals(authority)) { + // 圖片、影音檔案 + final String docId = DocumentsContract.getDocumentId(uri); + final String[] divide = docId.split(":"); + final String type = divide[0]; + Uri mediaUri = null; + if ("image".equals(type)) { + mediaUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + mediaUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } else { + return null; + } + mediaUri = ContentUris.withAppendedId(mediaUri, Long.parseLong(divide[1])); + String path = queryAbsolutePath(context, mediaUri); + return path; + } + } else { + // 如果是一般的URI + final String scheme = uri.getScheme(); + String path = null; + if ("content".equals(scheme)) { + // 內容URI + path = queryAbsolutePath(context, uri); + } else if ("file".equals(scheme)) { + // 檔案URI + path = uri.getPath(); + } + return path; + } + return null; + } + + public static String queryAbsolutePath(final Context context, final Uri uri) { + final String[] projection = {MediaStore.MediaColumns.DATA}; + Cursor cursor = null; + try { + cursor = context.getContentResolver().query(uri, projection, null, null, null); + if (cursor != null && cursor.moveToFirst()) { + final int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); + return cursor.getString(index); + } + } catch (final Exception ex) { + ex.printStackTrace(); + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + private ActivityResultLauncher getActivityResultLauncher(){ + return registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), new ActivityResultCallback(){ + // 重写回调方法 + @Override + public void onActivityResult(ActivityResult result) { + if (result.getResultCode() != RESULT_OK) return; + ContentResolver resolver = getContentResolver(); + Uri originalUri = result.getData().getData(); //1.获得图片的真实路径 + Bitmap bitmap = null; + try { + bitmap = BitmapFactory.decodeStream(resolver.openInputStream(originalUri));//2.解码图片 + } catch (FileNotFoundException e) { + Log.d(TAG, "onActivityResult: get file_exception"); + e.printStackTrace(); + } + + if (bitmap != null) { + //3.根据Bitmap对象创建ImageSpan对象 + Log.d(TAG, "onActivityResult: bitmap is not null"); + ImageSpan imageSpan = new ImageSpan(NoteEditActivity.this, bitmap); + String path = getPathFromUri(NoteEditActivity.this, originalUri); + //4.使用[local][/local]将path括起来,用于之后方便识别图片路径在note中的位置 + String img_fragment = "" + path + ""; + //创建一个SpannableString对象,以便插入用ImageSpan对象封装的图像 + SpannableString spannableString = new SpannableString(img_fragment); + spannableString.setSpan(imageSpan, 0, img_fragment.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + //5.将选择的图片追加到EditText中光标所在位置 + NoteEditText e = (NoteEditText) findViewById(R.id.note_edit_view); + int index = e.getSelectionStart(); //获取光标所在位置 + Log.d(TAG, "Index是: " + index); + Editable edit_text = e.getEditableText(); + edit_text.insert(index, spannableString); //将图片插入到光标所在位置 + + mWorkingNote.mContent = e.getText().toString(); + //6.把改动提交到数据库中,两个数据库表都要改的 + ContentResolver contentResolver = getContentResolver(); + ContentValues contentValues = new ContentValues(); + final long id = mWorkingNote.getNoteId(); + contentValues.put("snippet", mWorkingNote.mContent); + contentResolver.update(Uri.parse("content://micode_notes/note"), contentValues, "_id=?", new String[]{"" + id}); + ContentValues contentValues1 = new ContentValues(); + contentValues1.put("content", mWorkingNote.mContent); + contentResolver.update(Uri.parse("content://micode_notes/data"), contentValues1, "mime_type=? and note_id=?", new String[]{"vnd.android.cursor.item/text_note", "" + id}); + + } + } + }); + } + /** + * @author: ClearDewy + * @date: 2023/3/10 12:43 + * @param: [] + * @return: androidx.activity.result.ActivityResultLauncher + * @description:当API>=33时,申请读取存储授权 + **/ + private ActivityResultLauncher getrequestDataLauncher(){ + return registerForActivityResult(new ActivityResultContracts.RequestPermission(), result -> { + if (result){ + Log.d(TAG,"用户已授权"); + convertToImage(); + }else{ + Log.d(TAG,"用户取消授权"); + } + }); + } + + //路径字符串格式 转换为 图片image格式 + private void convertToImage() { + NoteEditText noteEditText = (NoteEditText) findViewById(R.id.note_edit_view); //获取当前的edit + Editable editable = noteEditText.getText();//1.获取text + String noteText = editable.toString(); //2.将note内容转换为字符串 + int length = editable.length(); //内容的长度 + //3.截取img片段 [local]+uri+[local],提取uri + for(int i = 0; i < length; i++) { + for(int j = i; j < length; j++) { + String img_fragment = noteText.substring(i, j+1); //img_fragment:关于图片路径的片段 + if(img_fragment.length() > 15 && img_fragment.endsWith("[/local]") && img_fragment.startsWith("[local]")){ + int limit = 7; //[local]为7个字符 + //[local][/local]共15个字符,剩下的为真正的path长度 + int len = img_fragment.length()-15; + //从[local]之后的len个字符就是path + String path = img_fragment.substring(limit,limit+len);//获取到了图片路径 + + + } + } + } + + for (int i = 0; i < length; i++) { + if (noteText.startsWith("", i)){ + for(int j=i+5;j< length;j++){ + if (noteText.substring(i,j+1).endsWith("")){ + Bitmap bitmap = null; + String path=noteText.substring(i+5,j-5); + Log.d(TAG, "图片的路径是:"+path); + try { + bitmap = BitmapFactory.decodeFile(path);//将图片路径解码为图片格式 + } catch (Exception e) { + e.printStackTrace(); + } + if(bitmap!=null){ //若图片存在 + Log.d(TAG, "图片不为null"); + ImageSpan imageSpan = new ImageSpan(NoteEditActivity.this, bitmap); + //4.创建一个SpannableString对象,以便插入用ImageSpan对象封装的图像 + String ss = "" + path + ""; + SpannableString spannableString = new SpannableString(ss); + //5.将指定的标记对象附加到文本的开始...结束范围 + spannableString.setSpan(imageSpan, 0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + Log.d(TAG, "Create spannable string success!"); + Editable edit_text = noteEditText.getEditableText(); + edit_text.delete(i,j-i+1); //6.删掉图片路径的文字 + edit_text.insert(i, spannableString); //7.在路径的起始位置插入图片 + + i=j;break; + } + + } + } + } + } + + + + } + + + + // 添加图片按钮点击时触发 + private void onAddImage(ActivityResultLauncher intentActivityResultLauncher){ + Intent imgIntent=new Intent(Intent.ACTION_GET_CONTENT); + //Category属性用于指定当前动作(Action)被执行的环境. + //CATEGORY_OPENABLE; 用来指示一个ACTION_GET_CONTENT的intent + imgIntent.addCategory(Intent.CATEGORY_OPENABLE); + imgIntent.setType("image/*"); + intentActivityResultLauncher.launch(imgIntent); + } + + +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditText.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditText.java index 883cdb7..2afe2a8 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditText.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteEditText.java @@ -1,3 +1,19 @@ +/* + * 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; @@ -21,7 +37,6 @@ import net.micode.notes.R; import java.util.HashMap; import java.util.Map; -//继承edittext,设置便签设置文本框 public class NoteEditText extends EditText { private static final String TAG = "NoteEditText"; private int mIndex; @@ -31,7 +46,6 @@ public class NoteEditText extends EditText { 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); @@ -42,20 +56,17 @@ public class NoteEditText extends EditText { /** * 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); /** @@ -66,43 +77,33 @@ public class NoteEditText extends EditText { private OnTextViewChangeListener mOnTextViewChangeListener; - //根据context设置文本 public NoteEditText(Context context) { - super(context, null);//用super引用父类变量 + super(context, null); mIndex = 0; } - //设置当前光标 public void setIndex(int index) { mIndex = index; } - //初始化文本修改标记 public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { mOnTextViewChangeListener = listener; } - //AttributeSet 百度了一下是自定义空控件属性,用于维护便签动态变化的属性 - //初始化便签 - public NoteEditText(Context context, AttributeSet attrs) { + 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 + // TODO Auto-generated constructor stub } @Override - //view里的函数,处理手机屏幕的所有事件 - /*参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息, - 例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。*/ public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { - //重写了需要处理屏幕被按下的事件 case MotionEvent.ACTION_DOWN: - //跟新当前坐标值 + int x = (int) event.getX(); int y = (int) event.getY(); x -= getTotalPaddingLeft(); @@ -110,12 +111,9 @@ public class NoteEditText extends EditText { x += getScrollX(); y += getScrollY(); - //用布局控件layout根据x,y的新值设置新的位置 Layout layout = getLayout(); int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); - - //更新光标新的位置 Selection.setSelection(getText(), off); break; } @@ -124,147 +122,96 @@ public class NoteEditText extends EditText { } @Override - /* - * 函数功能:处理用户按下一个键盘按键时会触发 的事件 - * 实现过程:如下注释 - */ public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { - //根据按键的 Unicode 编码值来处理 case KeyEvent.KEYCODE_ENTER: - //“进入”按键 if (mOnTextViewChangeListener != null) { return false; } break; case KeyEvent.KEYCODE_DEL: - //“删除”按键 mSelectionStartBeforeDelete = getSelectionStart(); break; default: break; } - //继续执行父类的其他点击事件 return super.onKeyDown(keyCode, event); } @Override - /* - * 函数功能:处理用户松开一个键盘按键时会触发 的事件 - * 实现方式:如下注释 - */ public boolean onKeyUp(int keyCode, KeyEvent event) { switch(keyCode) { - //根据按键的 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) { int selectionStart = getSelectionStart(); - //获取当前位置 String text = getText().subSequence(selectionStart, length()).toString(); - //获取当前文本 setText(getText().subSequence(0, selectionStart)); - //根据获取的文本设置当前文本 mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); - //当{@link KeyEvent#KEYCODE_ENTER}添加新文本 } 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/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemData.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemData.java index 9dde13c..d1cf2d2 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemData.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/NoteItemData.java @@ -1,3 +1,19 @@ +/* + * 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; @@ -24,7 +40,9 @@ public class NoteItemData { NoteColumns.TYPE, NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, + NoteColumns.LOCKED }; + //常量标记和数据就不一一标记了,意义翻译基本就知道 private static final int ID_COLUMN = 0; private static final int ALERTED_DATE_COLUMN = 1; @@ -38,6 +56,10 @@ public class NoteItemData { private static final int TYPE_COLUMN = 9; private static final int WIDGET_ID_COLUMN = 10; private static final int WIDGET_TYPE_COLUMN = 11; + private static final int LOCKED_COLUMN = 12; + + private static final String LOCKED = "locked"; + private static final String UNLOCKED = "unlocked"; private long mId; private long mAlertDate; @@ -60,8 +82,9 @@ public class NoteItemData { private boolean mIsOneNoteFollowingFolder; private boolean mIsMultiNotesFollowingFolder; //初始化NoteItemData,主要利用光标cursor获取的东西 - public NoteItemData(Context context, Cursor cursor) { - //getxxx为转换格式 + private boolean mIsLocked; + + 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); @@ -77,8 +100,9 @@ public class NoteItemData { mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); - //初始化电话号码的信息 - mPhoneNumber = ""; + mIsLocked = !cursor.getString(LOCKED_COLUMN).equals(UNLOCKED); + + mPhoneNumber = "";//getxxx为转换格式 if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); if (!TextUtils.isEmpty(mPhoneNumber)) {//mphonenumber里有符合字符串,则用contart功能连接 @@ -94,7 +118,8 @@ public class NoteItemData { } checkPostion(cursor); } - ///根据鼠标的位置设置标记,和位置 + //根据鼠标的位置设置标记,和位置 + private void checkPostion(Cursor cursor) { //初始化几个标记,cursor具体功能笔记中已提到,不一一叙述 mIsLastItem = cursor.isLast() ? true : false; @@ -122,7 +147,7 @@ public class NoteItemData { } } } - ///以下都是获取标记没什么好说的,不过倒数第二个需要说明下,很具体看下面 + public boolean isOneFollowingFolder() { return mIsOneNoteFollowingFolder; } @@ -211,4 +236,11 @@ public class NoteItemData { public static int getNoteType(Cursor cursor) { return cursor.getInt(TYPE_COLUMN); } -} \ No newline at end of file + + public boolean getIsLocked(){ + return mIsLocked; + } + public void setIsLocked(boolean islocked){ + mIsLocked = islocked; + } +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java index 3a1639d..093a0d1 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListActivity.java @@ -1,8 +1,26 @@ +/* + * 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.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; +import android.app.KeyguardManager; import android.appwidget.AppWidgetManager; import android.content.AsyncQueryHandler; import android.content.ContentResolver; @@ -12,9 +30,13 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; +import android.hardware.fingerprint.FingerprintManager; import android.os.AsyncTask; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; @@ -44,29 +66,27 @@ import android.widget.PopupMenu; import android.widget.TextView; import android.widget.Toast; +import androidx.appcompat.app.AppCompatActivity; + 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.tool.FingerprintDialogFragment; 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.security.KeyStore; import java.util.HashSet; -//主界面,一进入就是这个界面 -/** - * @author k - * - */ -public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { //没有用特定的标签加注释。。。感觉没有什么用 + +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; + +public class NotesListActivity extends AppCompatActivity implements OnClickListener, OnItemLongClickListener { private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; private static final int FOLDER_LIST_QUERY_TOKEN = 1; @@ -77,7 +97,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt private static final int MENU_FOLDER_CHANGE_NAME = 2; - private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; //单行超过80个字符 + private static final int MENU_FOLDER_MOVE_OUT = 3; + + private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; private enum ListEditState { NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER @@ -123,16 +145,25 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt private final static int REQUEST_CODE_OPEN_NODE = 102; private final static int REQUEST_CODE_NEW_NODE = 103; + private static final String DEFAULT_KEY_NAME = "default_key"; + private static final int OPEN_NOTE = 0; + private static final int DELETE_NOTE = 1; + private static final int MOVE_NOTE = 2; + private static final int NONE_ACTION = -1; + KeyStore keyStore; + private NoteItemData mNoteDataItem; + private int mAuthenticatedType; + private long mAuthenticated_FOLDER; + @Override - // 创建类 - protected void onCreate(final Bundle savedInstanceState) { //需要是final类型 根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变:设计或效率。 - // final类不能被继承,没有子类,final类中的方法默认是final的。 - //final方法不能被子类的方法覆盖,但可以被继承。 - //final成员变量表示常量,只能被赋值一次,赋值后值不再改变。 - //final不能用于修饰构造方法。 - super.onCreate(savedInstanceState); // 调用父类的onCreate函数 + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); setContentView(R.layout.note_list); initResources(); + if (supportFingerprint()) { + initKey(); + } + /** * Insert an introduction when user firstly use this application @@ -141,67 +172,68 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } @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; + if(!DataUtils.checkFolderId(mContentResolver,Long.valueOf(Notes.ID_TRASH_FOLER)) && !DataUtils.checkVisibleFolderName(mContentResolver,this.getString(R.string.trash_name))){ + ContentValues values = new ContentValues(); + values.put(NoteColumns.SNIPPET, this.getString(R.string.trash_name)); + values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); +// values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); + mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); } +// StringBuilder sb = new StringBuilder(); +// InputStream in = null; +// try { +// in = getResources().openRawResource(R.raw.introduction); +// if (in != null) { +// InputStreamReader isr = new InputStreamReader(in); +// BufferedReader br = new BufferedReader(isr); +// char [] buf = new char[1024]; +// int len = 0; +// while ((len = br.read(buf)) > 0) { +// sb.append(buf, 0, len); +// } +// } else { +// Log.e(TAG, "Read introduction file error"); +// return; +// } +// } catch (IOException e) { +// e.printStackTrace(); +// return; +// } finally { +// if(in != null) { +// try { +// in.close(); +// } catch (IOException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// } +// } +// +// WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, +// AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, +// ResourceParser.RED); +// note.setWorkingText(sb.toString()); +// if (note.saveNote()) { +// sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); +// } else { +// Log.e(TAG, "Save introduction note error"); +// return; +// } } + long id = DataUtils.getTrashIdByName(mContentResolver,this.getString(R.string.trash_name)); + Notes.setID_TRASH_FOLER((int)id); } @Override @@ -210,21 +242,18 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt startAsyncNotesListQuery(); } - // 初始化资源 private void initResources() { - mContentResolver = this.getContentResolver(); // 获取应用程序的数据,得到类似数据表的东西 + 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 = (ListView) findViewById(R.id.notes_list); mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), null, false); mNotesListView.setOnItemClickListener(new OnListItemClickListener()); mNotesListView.setOnItemLongClickListener(this); mNotesListAdapter = new NotesListAdapter(this); mNotesListView.setAdapter(mNotesListAdapter); - mAddNewNote = (Button) findViewById(R.id.btn_new_note);// 在activity中要获取该按钮 + mAddNewNote = (Button) findViewById(R.id.btn_new_note); mAddNewNote.setOnClickListener(this); mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); mDispatch = false; @@ -235,7 +264,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mModeCallBack = new ModeCallback(); } - // 继承自ListView.MultiChoiceModeListener 和 OnMenuItemClickListener private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { private DropdownMenu mDropDownMenu; private ActionMode mActionMode; @@ -264,7 +292,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt (Button) customView.findViewById(R.id.selection_menu), R.menu.note_list_dropdown); mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ - public boolean onMenuItemClick(final MenuItem item) { + public boolean onMenuItemClick(MenuItem item) { mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); updateMenu(); return true; @@ -274,12 +302,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt 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); // 更改标题 + mDropDownMenu.setTitle(format); MenuItem item = mDropDownMenu.findItem(R.id.action_select_all); if (item != null) { if (mNotesListAdapter.isAllSelected()) { @@ -372,7 +399,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt /** * 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锛� + * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel) * and the line top of the button. The coordinate based on left of the "New * Note" button. The 94 represents maximum height of the transparent part. * Notice that, if the background of the button changes, the formula should @@ -447,6 +474,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + private void showFolderListMenu(Cursor cursor) { AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(R.string.menu_title_select_folder); @@ -454,14 +482,27 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt 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(); + if(adapter.getItemId(which)==Notes.ID_TRASH_FOLER){ + Toast.makeText( + NotesListActivity.this, + R.string.forbidden_move_to_trash, + Toast.LENGTH_SHORT).show(); + }else{ + if(mNotesListAdapter.getHasLocked() && false){ + //not ready + initCipher(); + mAuthenticatedType = MOVE_NOTE; + }else { + 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(); } }); @@ -475,11 +516,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); } - private void batchDelete() { + private void reallyBatchDelete(){ new AsyncTask>() { protected HashSet doInBackground(Void... unused) { HashSet widgets = mNotesListAdapter.getSelectedWidget(); - if (!isSyncMode()) { + HashSet folers = mNotesListAdapter.getSelectedFolderIds(); + if (folers.contains(new Long( Notes.ID_TRASH_FOLER))) { // if not synced, delete notes directly if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter .getSelectedItemIds())) { @@ -512,17 +554,22 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }.execute(); } - private void deleteFolder(long folderId) { - if (folderId == Notes.ID_ROOT_FOLDER) { - Log.e(TAG, "Wrong folder id, should not happen " + folderId); - return; + private void batchDelete() { + if(ifHaveLocked()){ + initCipher(); + mAuthenticatedType = DELETE_NOTE; + }else { + reallyBatchDelete(); } + } + private void reallyDeleteFolder(long folderId){ HashSet ids = new HashSet(); + ids.add(folderId); HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver, folderId); - if (!isSyncMode()) { + if (DataUtils.getParentIdbyId(mContentResolver,folderId)==Notes.ID_TRASH_FOLER) { // if not synced, delete folder directly DataUtils.batchDeleteNotes(mContentResolver, ids); } else { @@ -539,38 +586,120 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } - 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; + private void deleteFolder(long folderId) { + if (folderId == Notes.ID_ROOT_FOLDER) { + Log.e(TAG, "Wrong folder id, should not happen " + folderId); + return; } - if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { - mTitleBar.setText(R.string.call_record_folder_name); - } else { - mTitleBar.setText(data.getSnippet()); + if(folderId == Notes.ID_TRASH_FOLER){ + Toast.makeText( + NotesListActivity.this, + R.string.forbidden_delete_trash, + Toast.LENGTH_SHORT).show(); + return; + } + + if(DataUtils.getHasLockedByFolderId(mContentResolver,folderId).contains(Notes.LOCKED)){ + initCipher(); + mAuthenticatedType = MOVE_NOTE; + mAuthenticated_FOLDER = folderId; + }else { + reallyDeleteFolder(folderId); } - mTitleBar.setVisibility(View.VISIBLE); } - public void onClick(View v) { - switch (v.getId()) { - case R.id.btn_new_note: - createNewNote(); + private boolean ifHaveLocked(){ + return mNotesListAdapter.getHasLocked(); + } + + + private void openNode(NoteItemData data) { + if(!data.getIsLocked()){ + 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); + }else{ + this.openLockedNote(); + mNoteDataItem = data; + mAuthenticatedType = OPEN_NOTE; + } + } + + private void openLockedNote(){ + initCipher(); + } + + public void onAuthenticated() { + switch (mAuthenticatedType){ + case OPEN_NOTE: + switch (mNoteDataItem.getType()){ + case Notes.TYPE_FOLDER: + mCurrentFolderId = mNoteDataItem.getId(); + startAsyncNotesListQuery(); + if (mNoteDataItem.getId() == Notes.ID_CALL_RECORD_FOLDER) { + mState = ListEditState.CALL_RECORD_FOLDER; + mAddNewNote.setVisibility(View.GONE); + } else { + mState = ListEditState.SUB_FOLDER; + } + if (mNoteDataItem.getId() == Notes.ID_CALL_RECORD_FOLDER) { + mTitleBar.setText(R.string.call_record_folder_name); + } else { + mTitleBar.setText(mNoteDataItem.getSnippet()); + } + mTitleBar.setVisibility(View.VISIBLE); + break; + case Notes.TYPE_NOTE: + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, mNoteDataItem.getId()); + this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); + break; + } break; - default: + case DELETE_NOTE: + reallyBatchDelete(); + break; + case MOVE_NOTE: + reallyDeleteFolder(mAuthenticated_FOLDER); break; } + mNoteDataItem = null; + mAuthenticated_FOLDER = -1; + mAuthenticatedType = NONE_ACTION; + } + + public void onStopAuthenticated() { + mNoteDataItem = null; + } + + private void openFolder(NoteItemData data) { + if(!data.getIsLocked()){ + 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); + }else{ + this.openLockedNote(); + mNoteDataItem = data; + mAuthenticatedType = OPEN_NOTE; + } + } + + public void onClick(View v) { + createNewNote(); } private void showSoftInput() { @@ -592,8 +721,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt showSoftInput(); if (!create) { if (mFocusNoteDataItem != null) { - etName.setText(mFocusNoteDataItem.getSnippet()); - builder.setTitle(getString(R.string.menu_folder_change_name)); + if(mFocusNoteDataItem.getId()==(long)Notes.ID_TRASH_FOLER){ + Toast.makeText(this, R.string.forbidden_change_trash_name, Toast.LENGTH_SHORT).show(); + return; + }else{ + 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; @@ -670,38 +804,30 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }); } - /* (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; - } + 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) { @@ -721,16 +847,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt 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); + if(mFocusNoteDataItem.getParentId()==Notes.ID_TRASH_FOLER){ + menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); + menu.add(0, MENU_FOLDER_MOVE_OUT, 0, R.string.menu_folder_move_out); + menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); + }else { + 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); + } } } }; @@ -743,41 +872,62 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt 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; + if(mFocusNoteDataItem.getParentId()==Notes.ID_TRASH_FOLER){ + switch (item.getItemId()) { + case MENU_FOLDER_MOVE_OUT: + HashSet ids = new HashSet(); + ids.add(mFocusNoteDataItem.getId()); + DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_ROOT_FOLDER); + 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; + default: + break; + } + }else { + 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; } @@ -827,7 +977,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt break; } case R.id.menu_new_note: { - createNewNote(); +// createNewNote(); break; } case R.id.menu_search: @@ -839,19 +989,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt 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() { @@ -894,27 +1037,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }.execute(); } - /** - * @return - * 功能:判断是否正在同步 - */ private boolean isSyncMode() { - return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + return true; } - /** - * 功能:跳转到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) { @@ -956,11 +1088,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } - /** - * 查询目标文件 - */ private void startQueryDestinationFolders() { - String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; + String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>? AND "+ NoteColumns.ID + "<>?"; selection = (mState == ListEditState.NOTE_LIST) ? selection: "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; @@ -972,17 +1101,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER), - String.valueOf(mCurrentFolderId) + String.valueOf(mCurrentFolderId), + String.valueOf(Notes.ID_TRASH_FOLER) }, 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(); @@ -999,4 +1123,68 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } return false; } -} \ No newline at end of file + + + + public boolean supportFingerprint() { + if (Build.VERSION.SDK_INT < 23) { + Toast.makeText(this, "您的系统版本过低,不支持指纹功能", Toast.LENGTH_SHORT).show(); + return false; + } else { + KeyguardManager keyguardManager = getSystemService(KeyguardManager.class); + FingerprintManager fingerprintManager = getSystemService(FingerprintManager.class); + if (!fingerprintManager.isHardwareDetected()) { + Toast.makeText(this, "您的手机不支持指纹功能", Toast.LENGTH_SHORT).show(); + return false; + } else if (!keyguardManager.isKeyguardSecure()) { + Toast.makeText(this, "您还未设置锁屏,请先设置锁屏并添加一个指纹", Toast.LENGTH_SHORT).show(); + return false; + } else if (!fingerprintManager.hasEnrolledFingerprints()) { + Toast.makeText(this, "您至少需要在系统设置中添加一个指纹", Toast.LENGTH_SHORT).show(); + return false; + } + } + return true; + } + + @TargetApi(23) + private void initKey() { + try { + keyStore = KeyStore.getInstance("AndroidKeyStore"); + keyStore.load(null); + KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); + KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(DEFAULT_KEY_NAME, + KeyProperties.PURPOSE_ENCRYPT | + KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_CBC) + .setUserAuthenticationRequired(true) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7); + keyGenerator.init(builder.build()); + keyGenerator.generateKey(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @TargetApi(23) + private void initCipher() { + try { + SecretKey key = (SecretKey) keyStore.getKey(DEFAULT_KEY_NAME, null); + Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + + KeyProperties.BLOCK_MODE_CBC + "/" + + KeyProperties.ENCRYPTION_PADDING_PKCS7); + cipher.init(Cipher.ENCRYPT_MODE, key); + showFingerPrintDialog(cipher); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void showFingerPrintDialog(Cipher cipher) { + FingerprintDialogFragment fragment = new FingerprintDialogFragment(); + fragment.setCipher(cipher); + fragment.show(getFragmentManager(), "fingerprint"); + } + + +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java index 51c9cb9..1135239 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListAdapter.java @@ -30,54 +30,87 @@ 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; + 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(); + 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) { + if (view instanceof NotesListItem) {//若view是NotesListItem的一个实例 NoteItemData itemData = new NoteItemData(context, cursor); ((NotesListItem) view).bind(context, itemData, mChoiceMode, - isSelectedItem(cursor.getPosition())); + isSelectedItem(cursor.getPosition()));//则新建一个项目选项并且用bind跟将view和鼠标,内容,便签数据捆绑在一起 } } + /* + * 函数功能:设置勾选框 + * 函数实现:如下注释 + */ public void setCheckedItem(final int position, final boolean checked) { - mSelectedIndex.put(position, checked); - notifyDataSetChanged(); + 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++) { @@ -86,31 +119,60 @@ public class NotesListAdapter extends CursorAdapter { setCheckedItem(i, checked); } } - } + }//遍历所有光标可用的位置在判断为便签类型之后勾选单项框 } + /* + * 函数功能:建立选择项的下标列表 + * 函数实现:如下注释 + */ public HashSet getSelectedItemIds() { - HashSet itemSet = new HashSet(); - for (Integer position : mSelectedIndex.keySet()) { - if (mSelectedIndex.get(position) == true) { + 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) { + if (id == Notes.ID_ROOT_FOLDER) {//原文件不需要添加 Log.d(TAG, "Wrong item id, should not happen"); } else { itemSet.add(id); - } + }//则将id该下标假如选项集合中 } } return itemSet; } - public HashSet getSelectedWidget() { - HashSet itemSet = new HashSet(); + + public HashSet getSelectedFolderIds() { + HashSet itemSet = new HashSet(); for (Integer position : mSelectedIndex.keySet()) { if (mSelectedIndex.get(position) == true) { Cursor c = (Cursor) getItem(position); if (c != null) { + NoteItemData item = new NoteItemData(mContext, c); + itemSet.add(item.getFolderId()); + /** + * Don't close cursor here, only the adapter could close it + */ + } else { + Log.e(TAG, "Invalid cursor"); + return null; + } + } + } + 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(); @@ -128,26 +190,57 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + public boolean getHasLocked(){ + HashSet itemSet = new HashSet(); + for (Integer position : mSelectedIndex.keySet()) { + if (mSelectedIndex.get(position) == true) { + Cursor c = (Cursor) getItem(position); + if (c != null) { + NoteItemData item = new NoteItemData(mContext, c); + if(item.getIsLocked()){ + return true; + } + } else { + Log.e(TAG, "Invalid cursor"); + return true; + } + } + } + return false; + } + + /* + * 函数功能:获取选项个数 + * 函数实现:如下注释 + */ public int getSelectedCount() { - Collection values = mSelectedIndex.values(); + Collection values = mSelectedIndex.values();//首先获取选项下标的值 if (null == values) { return 0; } - Iterator iter = values.iterator(); + Iterator iter = values.iterator();//初始化叠加器 int count = 0; while (iter.hasNext()) { - if (true == iter.next()) { + if (true == iter.next()) {//若value值为真计数+1 count++; } } return count; } + /* + * 函数功能:判断是否全部选中 + * 函数实现:如下注释 + */ public boolean isAllSelected() { int checkedCount = getSelectedCount(); - return (checkedCount != 0 && checkedCount == mNotesCount); + return (checkedCount != 0 && checkedCount == mNotesCount);//获取选项数看是否等于便签的个数 } + /* + * 函数功能:判断是否为选项表 + * 函数实现:通过传递的下标来确定 + */ public boolean isSelectedItem(final int position) { if (null == mSelectedIndex.get(position)) { return false; @@ -156,29 +249,41 @@ public class NotesListAdapter extends CursorAdapter { } @Override + /* + * 函数功能:在activity内容发生局部变动的时候回调该函数计算便签的数量 + * 函数实现:如下注释 + */ protected void onContentChanged() { - super.onContentChanged(); + super.onContentChanged();//执行基类函数 calcNotesCount(); } @Override + /* + * 函数功能:在activity光标发生局部变动的时候回调该函数计算便签的数量 + */ public void changeCursor(Cursor cursor) { - super.changeCursor(cursor); + super.changeCursor(cursor);//执行基类函数 calcNotesCount(); } + /* + * 函数功能:计算便签数量 + * + */ private void calcNotesCount() { mNotesCount = 0; - for (int i = 0; i < getCount(); i++) { + for (int i = 0; i < getCount(); i++) {//获取总数同时遍历 Cursor c = (Cursor) getItem(i); if (c != null) { if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { - mNotesCount++; + mNotesCount++; //若该位置不为空并且文本类型为便签就+1 } } else { Log.e(TAG, "Invalid cursor"); return; } + //否则报错 } } } diff --git a/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListItem.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListItem.java index fa8b3d2..85d3481 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListItem.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesListItem.java @@ -1,3 +1,19 @@ +/* + * 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; @@ -13,19 +29,19 @@ 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; //打钩框 + private TextView mTitle;//标题 + private TextView mTime;//时间 + private TextView mCallName; + private NoteItemData mItemData;//标签数据 + private CheckBox mCheckBox;//打钩框 + /*初始化基本信息*/ public NotesListItem(Context context) { - super(context); //super()它的主要作用是调整调用父类构造函数的顺序 + 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); @@ -34,17 +50,18 @@ public class NotesListItem extends LinearLayout { mCallName = (TextView) findViewById(R.id.tv_name); mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); } - ///根据data的属性对各个控件的属性的控制,主要是可见性Visibility,内容setText,格式setTextAppearance + + //根据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); ///格子打钩 + mCheckBox.setVisibility(View.VISIBLE);//设置可见行为可见 + mCheckBox.setChecked(checked);//格子打钩 } else { mCheckBox.setVisibility(View.GONE); } mItemData = data; - ///设置控件属性,一共三种情况,由data的id和父id是否与保存到文件夹的id一致来决定 + //设置控件属性,一共三种情况,由data的id和父id是否与保存到文件夹的id一致来决定 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mCallName.setVisibility(View.GONE); mAlert.setVisibility(View.VISIBLE); @@ -59,7 +76,7 @@ public class NotesListItem extends LinearLayout { 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); @@ -69,7 +86,8 @@ public class NotesListItem extends LinearLayout { } else { mCallName.setVisibility(View.GONE); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); - ///设置title格式 + //设置title格式 + if (data.getType() == Notes.TYPE_FOLDER) { mTitle.setText(data.getSnippet() + context.getString(R.string.format_folder_files_count, @@ -78,24 +96,29 @@ public class NotesListItem extends LinearLayout { } else { mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); if (data.hasAlert()) { - mAlert.setImageResource(R.drawable.clock);///设置图片来源 + mAlert.setImageResource(R.drawable.clock);//设置图片来源 mAlert.setVisibility(View.VISIBLE); } else { mAlert.setVisibility(View.GONE); } } } - ///设置内容,获取相关时间,从data里编辑的日期中获取 - mTime. setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + //设置内容,获取相关时间,从data里编辑的日期中获取 + mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); setBackground(data); } + //根据data的文件属性来设置背景 private void setBackground(NoteItemData data) { int id = data.getBgColorId(); - //,若是note型文件,则4种情况,对于4种不同情况的背景来源 + //若是note型文件,则4种情况,对于4种不同情况的背景来源 +// setBackgroundResource(R.color.user_query_highlight); +// setBackgroundColor(0xFF888888); +// setX(500); +// setWeightSum((float)1); +// setw if (data.getType() == Notes.TYPE_NOTE) { - //单个数据并且只有一个子文件夹 if (data.isSingle() || data.isOneFollowingFolder()) { setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); } else if (data.isLast()) {//是最后一个数据 @@ -110,7 +133,8 @@ public class NotesListItem extends LinearLayout { setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } } + public NoteItemData getItemData() { return mItemData; } -} \ No newline at end of file +} diff --git a/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java b/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java index 785c3a3..5520104 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/ui/NotesPreferenceActivity.java @@ -1,3 +1,19 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package net.micode.notes.ui; import android.accounts.Account; @@ -56,36 +72,28 @@ public class NotesPreferenceActivity extends PreferenceActivity { //账户 private boolean mHasAddedAccount; //账户的hash标记 - @Override /* *函数功能:创建一个activity,在函数里要完成所有的正常静态设置 *参数:Bundle icicle:存放了 activity 当前的状态 *函数实现:如下注释 */ - protected void onCreate(Bundle icicle) { - //先执行父类的创建函数 + 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); - //根据同步账户关键码来初始化分组 + 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); - //初始化同步组件 + registerReceiver(mReceiver, filter);//初始化同步组件 mOriAccounts = null; - View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); - //获取listvivew,ListView的作用:用于列出所有选择 - getListView().addHeaderView(header, null, true); - //在listview组件上方添加其他组件 + View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);//获取listvivew,ListView的作用:用于列出所有选择 + getListView().addHeaderView(header, null, true);//在listview组件上方添加其他组件 } @Override @@ -93,18 +101,14 @@ public class NotesPreferenceActivity extends PreferenceActivity { * 函数功能:activity交互功能的实现,用于接受用户的输入 * 函数实现:如下注释 */ - protected void onResume() { - //先执行父类 的交互实现 + 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) { - //若原账户不为空且当前账户有增加 + if (mHasAddedAccount) {//若用户新加了账户则自动设置同步账户 + Account[] accounts = getGoogleAccounts();//获取google同步账户 + if (mOriAccounts != null && accounts.length > mOriAccounts.length) {//若原账户不为空且当前账户有增加 for (Account accountNew : accounts) { boolean found = false; for (Account accountOld : mOriAccounts) { @@ -115,8 +119,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } if (!found) { - setSyncAccount(accountNew.name); - //若是没有找到旧的账户,那么同步账号中就只添加新账户 + setSyncAccount(accountNew.name);//若是没有找到旧的账户,那么同步账号中就只添加新账户 break; } } @@ -134,8 +137,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { */ protected void onDestroy() { if (mReceiver != null) { - unregisterReceiver(mReceiver); - //注销接收器 + unregisterReceiver(mReceiver);//注销接收器 } super.onDestroy(); //执行父类的销毁动作 @@ -148,15 +150,12 @@ public class NotesPreferenceActivity extends PreferenceActivity { private void loadAccountPreference() { mAccountCategory.removeAll(); //销毁所有的分组 - Preference accountPref = new Preference(this); - //建立首选项 + 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.setSummary(getString(R.string.preferences_account_summary));//设置首选项的大标题和小标题 accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - public boolean onPreferenceClick(Preference preference) { - //建立监听器 + public boolean onPreferenceClick(Preference preference) {//建立监听器 if (!GTaskSyncService.isSyncing()) { if (TextUtils.isEmpty(defaultAccount)) { // the first time to set account @@ -177,7 +176,6 @@ public class NotesPreferenceActivity extends PreferenceActivity { return true; } }); - //根据新建首选项编辑新的账户分组 mAccountCategory.addPreference(accountPref); } @@ -190,10 +188,10 @@ public class NotesPreferenceActivity extends PreferenceActivity { 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()) { - //若是在同步状态下 + + // 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) { @@ -206,22 +204,18 @@ public class NotesPreferenceActivity extends PreferenceActivity { 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()) { - //若是在同步的情况下 + if (GTaskSyncService.isSyncing()) {//若是在同步的情况下 lastSyncTimeView.setText(GTaskSyncService.getProgressString()); - lastSyncTimeView.setVisibility(View.VISIBLE); - // 根据当前同步服务器设置时间显示框的文本以及可见性 - } else { - //若是非同步情况 + lastSyncTimeView.setVisibility(View.VISIBLE);// 根据当前同步服务器设置时间显示框的文本以及可见性 + } else {//若是非同步情况 long lastSyncTime = getLastSyncTime(this); if (lastSyncTime != 0) { lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time, @@ -235,6 +229,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } } + /* *函数功能:刷新标签界面 *函数实现:调用上文设置账号和设置按键两个函数来实现 @@ -249,26 +244,24 @@ public class NotesPreferenceActivity extends PreferenceActivity { * 函数实现:如下注释 */ private void showSelectAccountAlertDialog() { - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - //创建一个新的对话框 + 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)); - //设置标题以及子标题的内容 + subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));//设置标题以及子标题的内容 + dialogBuilder.setCustomTitle(titleView); - dialogBuilder.setPositiveButton(null, null); - //设置对话框的自定义标题,建立一个YES的按钮 + dialogBuilder.setPositiveButton(null, null);//设置对话框的自定义标题,建立一个YES的按钮 + Account[] accounts = getGoogleAccounts(); - String defAccount = getSyncAccountName(this); - //获取同步账户信息 + String defAccount = getSyncAccountName(this);//获取同步账户信息 + mOriAccounts = accounts; mHasAddedAccount = false; - if (accounts.length > 0) { - //若账户不为空 + if (accounts.length > 0) {//若账户不为空 CharSequence[] items = new CharSequence[accounts.length]; final CharSequence[] itemMapping = items; int checkedItem = -1; @@ -280,8 +273,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { } items[index++] = account.name; } - dialogBuilder.setSingleChoiceItems(items, checkedItem, - //在对话框建立一个单选的复选框 + dialogBuilder.setSingleChoiceItems(items, checkedItem,//在对话框建立一个单选的复选框 new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { setSyncAccount(itemMapping[which].toString()); @@ -290,27 +282,21 @@ public class NotesPreferenceActivity extends PreferenceActivity { refreshUI(); } //设置点击后执行的事件,包括检录新同步账户和刷新标签界面 - }); - //建立对话框网络版的监听器 + });//建立对话框网络版的监听器 } View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); - dialogBuilder.setView(addAccountView); - //给新加账户对话框设置自定义样式 + dialogBuilder.setView(addAccountView);//给新加账户对话框设置自定义样式 - final AlertDialog dialog = dialogBuilder.show(); - //显示对话框 + 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"); - //建立网络建立组件 + 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); - //跳回上一个选项 + startActivityForResult(intent, -1);//跳回上一个选项 dialog.dismiss(); } }); @@ -371,8 +357,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { * 函数实现:如下注释: */ private void setSyncAccount(String account) { - if (!getSyncAccountName(this).equals(account)) { - //假如该账号不在同步账号列表中 + if (!getSyncAccountName(this).equals(account)) {//假如该账号不在同步账号列表中 SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); SharedPreferences.Editor editor = settings.edit(); //编辑共享的首选项 @@ -381,12 +366,11 @@ public class NotesPreferenceActivity extends PreferenceActivity { } else { editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); } - //将该账号加入到首选项中 - + //编辑共享的首选项 editor.commit(); //提交修改的数据 - + // clean up last sync time setLastSyncTime(this, 0); //将最后同步时间清零 @@ -407,15 +391,14 @@ public class NotesPreferenceActivity extends PreferenceActivity { //将toast的文本信息置为“设置账户成功”并显示出来 } } + /* * 函数功能:删除同步账户 * 函数实现:如下注释: */ private void removeSyncAccount() { SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); - //设置共享首选项 - + SharedPreferences.Editor editor = settings.edit();//设置共享首选项 if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); //假如当前首选项中有账户就删除 @@ -456,12 +439,11 @@ public class NotesPreferenceActivity extends PreferenceActivity { public static void setLastSyncTime(Context context, long time) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); - // 从共享首选项中找到相关账户并获取其编辑器 + SharedPreferences.Editor editor = settings.edit();// 从共享首选项中找到相关账户并获取其编辑器 editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); - editor.commit(); - //编辑最终同步时间并提交更新 + editor.commit();//编辑最终同步时间并提交更新 } + /* * 函数功能:获取最终同步时间 * 函数实现:通过共享的首选项里的信息直接获取 @@ -498,14 +480,12 @@ public class NotesPreferenceActivity extends PreferenceActivity { * 参数:MenuItem菜单选项 */ public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - //根据选项的id选择,这里只有一个主页 + 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 + return true;//在主页情况下在创建连接组件intent,发出清空的信号并开始一个相应的activity default: return false; } diff --git a/Notesmaster/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider.java b/Notesmaster/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider.java index ec6f819..2bdb5c2 100644 --- a/Notesmaster/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider.java +++ b/Notesmaster/app/src/main/java/net/micode/notes/widget/NoteWidgetProvider.java @@ -34,9 +34,9 @@ import net.micode.notes.ui.NotesListActivity; public abstract class NoteWidgetProvider extends AppWidgetProvider { public static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.BG_COLOR_ID, - NoteColumns.SNIPPET + NoteColumns.ID, + NoteColumns.BG_COLOR_ID, + NoteColumns.SNIPPET }; public static final int COLUMN_ID = 0; @@ -70,7 +70,7 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { } private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, - boolean privacyMode) { + boolean privacyMode) { for (int i = 0; i < appWidgetIds.length; i++) { if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { int bgId = ResourceParser.getDefaultBgId(context); diff --git a/Notesmaster/app/src/main/res/drawable-hdpi/edit_title_green.9.png b/Notesmaster/app/src/main/res/drawable-hdpi/edit_title_green.9.png index 08d864470f5f087d740bb9c83b8e4aac505bc97c..f0046a6e88eeb75cca792107619acaa8e1397d5a 100644 GIT binary patch delta 2602 zcmV+_3f1-dE3y=jBYz3`Nkl7i+{a)e(~thtKq@r`TqOy{NnrZ)&2qh;(vO*k87y0)#VQyTU|bk z_YcNbc)x!*y~h~lKIE9+>(V{mO};MI|NHR~{O=|lhuE(DKQdMq_uqNB|9bOc_SoN00Bq>OHjH;Fb0RqV-5~10uTWVntK|yF%Q>s_w4s0q zItcpj@a^LU`pY3B4WBV@9+aN-UL8hjvH$@(0)I|61EjCgd9+q%Z&z}pRJWf2ut1-z z?=TV@hThDuuGTH+Fl0b5C4-!kQj0DWHl+FLOdUAM3h7jq{VYNk+fN(l&E0SmT8oIy zs{3cYhZ8cXf~vGz^X|!WP#&`l*tM)22*ykT=4L^{-=={+8}4ZWcQAppVKdGkngd^| z1%Lk)D|U#n1RL*rAnq^@+Q7M%!Phu#b*2{+(`K@##Q`Ydwn;S5D!g8}H?tl%CqVLD z0N~sh^OYn_^ReMrMqW$b+q+#JchZ=h^|Tq7!5JX*P3N4~3J&j?8Ota@ZX?}6P}wq* zrE9D&B9yt)U1NGVJ^0YVs6yWzh!$14sDJOM^3Imtmov`uoB*PlQL(4ZodUvqZ#pJT zflL{n8b)b)|JjHghmj9vyJX<8u2lmo{i!eybuU>t#bM8Lg5^G+&yfM05@j2{LGBez zJ5_ZbDc)%Z?1vq8fhbZ1D+ir*&&{3CbrE)SEDiP}5Imdll7$GI7vl6F=32!kqkq24 ztPDA2S9*G|h3jl@=T+DuqO#cKgbTLBs(W*nTJO`7R(vRDaYc0Q7?yx{Jf|1$*&d2xr4k$wPCu?SF?kBdio4{5|%JoP9p`86fT%sOExsz4w?vyU@(K zyAp#4;FMtR9-uuJVo?}mq_Id0m(J}}P9%?>g*js`NM}Pf90cpAWz{tRJ9VzcHr_Uu z#kAA-d?7;u?DS;R$y#N@;Fw0p1mj_YSqb3G4oMMs6H{$Xwy_(!5N#J3#DBcmj%ZDb zjhj_K!2lUdz{-Sycpb*2J#`SL*sRhP0*P$Ye9hH-7i;;l4;6hYL_FAeXU>M4O213P zVU*erHgX>4Mj`D{FJUJEifwc&$|jx*XaoaamRx6gIS8~FaJmeh1xP)H{sNHa7W2xC z1LOOUf&;NcJcqsp*~^*;*?%R8fnm0}@a(azLQ|&@1I2zZhj|GY`fIx(i-*jXW^Kca zdaZwFx@$P-EPCQFiO}=`Xtz8A>T=<@SqN}SYgB(Q>EK)g9OHA;`z-?yNS&J%2hxz_kX|i7xObieMbc7s%(KVh%78P2v)3Dyf;f^-R#90q#hE>6dx2W zGcl;Qn;7?T2kHP>TOBlhjl+=~)V6BFT3ClCYrles0_sizkk}HVorq3Gjp-S-fCV2f z-7>kh%L6}b$zC_Gi4=mRX0(b>{n;`RgO-La|3YyPvR&HebAS9YqD`!e;ML+PBMN5L zr|&g+RQhbpVEvAO*-!Dy1Ww~iKRyk!g-Oe1zZg~k9Z-?tU=zej~NxxXM^?CsvM!J0xr1nE|Ze2NUNAXM>4Mb1717|?6k6?ln zKF7wB_SOg8!(WhWpwg?95!|*Fq5;KR_iO`|oV3$XUX5M*F>q(ZvdNm+iWn0x>(ltC z?&2{Jr++S^^nIir4ETqvm{)%`V`k7n=zT~a?p;Ivx=Ps)>|9cr#Z~_R*?w-BWn4Pz z)|PE!OmPG=lR?Xj2}r=nR<%wS89tx+%#zF}8MUr!nhyqu1x|GtsD z%#e%Pb~^K}AIOYSLgqq(3Roo4PuRWkS4*B$Pz&F_&AOE|02jDilC|(tz)ezVhpy`PG&c<6dg!41_`#7_jGZ4 z{P)TA@!9E(@yX))`0Mfe{EM+Cr`LJ^bb9w}d1L(TxUJNt93og;ibIXNoOZ{VMezYzz&171PkOSnz?vH$=8 M07*qoM6N<$g6gRh$^ZZW literal 5627 zcmVKLZ*U+5Lu!Sk^o_Z5E4Meg@_7P6crJiNL9pw)e1;Xm069{HJUZAPk55R%$-RIA z6-eL&AQ0xu!e<4=008gy@A0LT~suv4>S3ILP<0Bm`DLLvaF4FK%)Nj?Pt*r}7;7Xa9z9H|HZjR63e zC`Tj$K)V27Re@400>HumpsYY5E(E}?0f1SyGDiY{y#)Yvj#!WnKwtoXnL;eg03bL5 z07D)V%>y7z1E4U{zu>7~aD})?0RX_umCct+(lZpemCzb@^6=o|A>zVpu|i=NDG+7} zl4`aK{0#b-!z=TL9Wt0BGO&T{GJWpjryhdijfaIQ&2!o}p04JRKYg3k&Tf zVxhe-O!X z{f;To;xw^bEES6JSc$k$B2CA6xl)ltA<32E66t?3@gJ7`36pmX0IY^jz)rRYwaaY4 ze(nJRiw;=Qb^t(r^DT@T3y}a2XEZW-_W%Hszxj_qD**t_m!#tW0KDiJT&R>6OvVTR z07RgHDzHHZ48atvzz&?j9lXF70$~P3Knx_nJP<+#`N z#-MZ2bTkiLfR>_b(HgWKJ%F~Nr_oF3b#wrIijHG|(J>BYjM-sajE6;FiC7vY#};Gd zST$CUHDeuEH+B^pz@B062qXfFfD`NpUW5?BY=V%GM_5c)L#QR}BeW8_2v-S%gfYS= zB9o|3v?Y2H`NVi)In3rTB8+ej^> zQ=~r95NVuDChL%G$=>7$vVg20myx%S50Foi`^m%Pw-h?Xh~i8Mq9jtJloCocWk2Nv zrJpiFnV_ms&8eQ$2&#xWpIS+6pmtC%Q-`S&GF4Q#^mhymh7E(qNMa}%YZ-ePrx>>xFPTiH1=E+A$W$=bG8>s^ zm=Bn5Rah$aDtr}@$`X}2l~$F0mFKEdRdZE8)p@E5RI61Ft6o-prbbn>P~)iy)E2AN zsU20jsWz_8Qg>31P|s0cqrPALg8E|(vWA65poU1JRAaZs8I2(p#xiB`SVGovRs-uS zYnV-9TeA7=Om+qP8+I>yOjAR1s%ETak!GFdam@h^# z)@rS0t$wXH+Irf)+G6c;?H29p+V6F6oj{!|o%K3xI`?%6x;DB|x`n#ibhIR?(H}Q3Gzd138Ei2)WAMz7W9Vy`X}HnwgyEn!VS)>mv$8&{hQn>w4zwy3R}t;BYlZQm5)6pty=DfLrs+A-|>>;~;Q z_F?uV_HFjh9n2gO9o9Q^JA86v({H5aB!kjoO6 zc9$1ZZKsN-Zl8L~mE{`ly3)1N^`o1+o7}D0ZPeY&J;i;i`%NyJ8_8Y6J?}yE@b_5a zam?eLr<8@mESk|3$_SkmS{wQ>%qC18))9_|&j{ZT zes8AvOzF(F2#DZEY>2oYX&IRp`F#{ADl)1r>QS^)ba8a|EY_^#S^HO&t^Rgqwv=MZThqqEWH8 zxJo>d=ABlR_Bh=;eM9Tw|Ih34~oTE|= zX_mAr*D$vzw@+p(E0Yc6dFE}(8oqt`+R{gE3x4zjX+Sb3_cYE^= zgB=w+-tUy`ytONMS8KgRef4hA?t0j zufM;t32jm~jUGrkaOInTZ`zyfns>EuS}G30LFK_G-==(f<51|K&cocp&EJ`SxAh3? zNO>#LI=^+SEu(FqJ)ynt=!~PC9bO$rzPJB=?=j6w@a-(u02P7 zaQ)#(uUl{HW%tYNS3ItC^iAtK(eKlL`f9+{bJzISE?u8_z3;~C8@FyI-5j_jy7l;W z_U#vU3hqqYU3!mrul&B+{ptt$59)uk{;_4iZQ%G|z+lhASr6|H35TBkl>gI*;nGLU zN7W-nBaM%pA0HbH8olyl&XeJ%vZoWz%6?Y=dFykl=imL}`%BMQ{Mhgd`HRoLu6e2R za__6DuR6yg#~-}Tc|Gx_{H@O0eebyMy5GmWADJlpK>kqk(fVV@r_fLLKIeS?{4e)} z^ZO;zpECde03c&XQcVB=dL;k=fP(-4`Tqa_faw4Lbua(`>RI+y?e7jKeZ#YO-C z3n)oMK~#9!v|78)rAZAe+x;)gbs;7~LPA0U5hEee$#{sBcmQHTBqRbJfVUtcg@h22 zMcOBT1Zh|7Wth{p3G8zB+1-%+rP2Iz=DW1rc2&9hNM_>8_z{uJO!-ot{Y8uZ#Q*UF z;PK1I{qu7>79LPFs8%9G1nGvgz7ErGeyaG&ufMT=_WLhf^y~cVTaW$b zk2^ki<@f+1z;*`!m}=!D6Pbwwpd!eu?5Qdsg3N?KkQtM`Oh8R*F_Fj&m>KqCL(KqW z1>gZwMPwo}pi=iJ-oab;oj<9({Nii&(>EfL$_O6&@w+$v&sUH9H!`y_nF$eryH7oc zRHiFi0uil1A|L=nFk2sySj)N!Dq!vMuw@VdJOb_xRlyENq86VS$P9#hfH(24SMHDB zKGpQ6`@J818t?zj<3pr6T#s(jR}N+lC!3g;sGp88YhB8@QYDS>oQGS?o+U~m8D)hs&k(f6uM1qoEm zX=RShhThfQS-5XV*EGukQ?uO@0L&D&tOGHi%SL-gAp-PHRKsUq<6UBjtoB0S_7GaEQW@IFj=pp(-$*U3sCf$V@I1Bu!- zAmQ=fcTT1=5T4Tj**DKa8{$+I8F@%Vw0EbjXApC5mxcFpBfOxDJ%iJ1+Q~4}+7STZ zL=u*4-J^3?Lqud<*g!rwU9|=jF?anU;qLYSSH^X&zXKMiR+&U22eFvJv9`U-vP!SA zjEg`O0G8=IoQ>E}+zau~!(3-dO`l-S)Zp&5h{0?rsOuj-W4qUSL?)2!yP30qVEZ24 z=-X=1?lCvk!**{=VwnMuAqi2zP5jy!<@UPoru#}jc9Wr*j<}m(55_d+=$F7Yjmr<={#M77v1mHuzv-eoDX^*|9+5ttvXtq14V zfxHIyzCWW^-WjLrIsz$Rwy^hZy>NkU@6=j0C17TVmbz%{H7hAPtlQ7tdt;eFS{4j5 z>xCXkY?WfKh>Gk9pEDp@xLKH6S$|op%c^QofYHov-aM*gs7ZinL_(c5b&Kj#Nk!K^ zgDG6U8j%VuBZs;pa&!nOUPky}#Cl3j7TK_7t7}n5qMmYzE~(S_#lSg1@H%NUvI+Wz zJTCT9M41WSyEQUtIA_)yiRm$QDamQ&G%Q4>UkbA^nw&LiW+zE`#1@H~s*mWQO#O|A zL$hj6a~^nM}ESZ-q zC3CT~#B>G&;(6(krsi%c4jGv_@WyGz!w=bYgNj@@eX6dufn!cYAiUtrw@;H`L>2Xh z56qD)w6iGvJISiFMhU7t1M?{zl`}0x#1Pr`gCy(nN`@@*>SVcCi?vFPI})Z}m=Rbn zYkU$FX)2GgA*ea{o*EbwwXr#pR#iqH@}-$*C0LJl z_sTLW*lhGUca8ThTP){i$uND8J#NL*F0UyIwVL~+r${JxT|~5VR06%x0Q{1z|rM(N7 zbfdRf**G7O)1VyB?isQcAEz|0L3>(~2FuXxWjIn#Q^_6Q!aKN_?WRcLT<7lXm>xTV z4pg>q+S`Y`Xh=Ty!7+-SYFgW7WroiDR?J6+V^&$aV-o72YfjGPjQ0Gfc7kbH4DynWBq(lm#$h;a&(j!&1cc}m z<4(nsOw9>Y?{UkIF_#A_wXUa|63&0nM4axmoM0 zGfrKUcOw$oW@^Tn0o80&7#Cxz*m34ckvvAb=^4EkuI0L z(LG_kgR8o=>lwFb@tPPKzNpYh40=uF5jpc7E)$-EKsJnV3}cf9XvBa11dH6Dee8i_ zk<)-YRv0a*9M3pE)1+|*12zUJJdS>u37i_F7eLE`Th6{I^pOi{^FlF!ine^EG*zN; zWjZ3E=rfQw%I^3c&lUl9BQs`pJhP#6x$wnN<&YJ;EgM_+B(MQ4^Dw9gUfy~+<&i;O z!MnjKIv(IeCgcl5KVJ};kifIPpt5V^P$@G}MQ1AKbD8yUAFrCt3&fNcB<^u5p9u!% z{O>jy%@*a|*bGWF*Ma6W-;L>W3;VXu$g^a|lDG@^)6B{@QB%al(E4H<(S!RR1+N4qHP$79yvfi#Jjy&zdQMCgSYELxYqCZdbP+w4zQb*H7 zcb)_^Rg!1mo9N2$khp5&Yax6)IyGfa!l`DEE>*SNJz;c>iScDZ}Z zKzS&*YTc}`8rKMwV~qPnDA#a$T}Sg$QZ8~kWs^cxCxbqaG5;kaa*o#Ynhob1PjV<3 z39R)}PLk+M@bOeiI@CMoj5B?=algI$r+4D9M}xCm=3YIl2>_az0zpk7-PskubrE!| zg(?+4gK1~aS|jr)@8#tM5k5`MBW5z+S_WHHd<*D>Cf!HxkJsIZg$$_KkO2tMJuX8Ye==7X*>#&~p z3BedU6!!hqhw-UVO*vni=n3fliR)-$B=86=9weRV+|XaWxQBo-_}WT1YLA~`P$b7h zf1XGNfvTg&mnQzxX(jFR{CmD2FgtY!47008k58sQ{^&I#Kk4sV)buchjGmJKdTpcV z(77lI3pVx=#$E`{A)SOl`cDvXI*a<23bUe1}Oy^3o>%_wO + > - +