diff --git a/src/notes/data/NotesDatabaseHelper.java b/src/notes/data/NotesDatabaseHelper.java index f64297b..c6daf1a 100644 --- a/src/notes/data/NotesDatabaseHelper.java +++ b/src/notes/data/NotesDatabaseHelper.java @@ -28,8 +28,16 @@ import net.micode.notes.data.Notes.NoteColumns; /** - * 数据库帮助类,负责数据库的创建、升级和管理 - * 单例模式实现,提供了创建表、管理触发器和系统文件夹的功能 + * 数据库帮助类 (Data Layer - Database Helper) + *

+ * 核心职责: + * 1. 负责 SQLite 数据库的创建与版本升级 (onCreate, onUpgrade)。 + * 2. 定义并维护数据库表结构(便签表、数据表)及索引。 + * 3. 通过 SQL 触发器实现复杂的业务规则,如文件夹计数同步、数据关联删除、内容片段(SNIPPET)自动更新等。 + * 4. 初始化系统文件夹(根目录、通话记录、临时文件夹、回收站)。 + *

+ * 设计模式:采用单例模式,确保全局仅有一个数据库连接实例,避免多线程下的资源竞争和数据不一致。 + * 架构位置:位于数据持久化层,是 {@link NotesProvider} 的底层依赖,为整个应用提供结构化数据存储。 */ public class NotesDatabaseHelper extends SQLiteOpenHelper { /** @@ -38,12 +46,16 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME = "note.db"; /** - * 数据库版本号 + * 数据库版本号,版本迭代历史详见 {@link #onUpgrade} 方法 */ private static final int DB_VERSION = 4; /** * 数据库表名定义接口 + *

+ * 定义两个核心表: + * 1. {@link #NOTE}:便签元数据表,存储文件夹、标题、时间、类型等信息。 + * 2. {@link #DATA}:便签内容数据表,支持多种 MIME 类型(文本、图片、提醒等)。 */ public interface TABLE { /** @@ -63,10 +75,21 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { private static final String TAG = "NotesDatabaseHelper"; /** - * 单例实例 + * 单例实例,通过 {@link #getInstance(Context)} 获取 */ private static NotesDatabaseHelper mInstance; + /** + * 创建便签表 (note) 的 SQL 语句。 + *

+ * 核心字段说明: + * - {@link NoteColumns#PARENT_ID}: 父文件夹 ID,用于构建树形结构。 + * - {@link NoteColumns#SNIPPET}: 内容摘要,由触发器从 data 表自动同步。 + * - {@link NoteColumns#TYPE}: 区分普通笔记、文件夹、系统文件夹。 + * - {@link NoteColumns#BG_COLOR_ID}: 背景色ID。【2025 AI分类】此字段现用于存储 AI 智能分类结果,实现颜色聚类排序。 + * - {@link NoteColumns#GTASK_ID}: Google 任务同步 ID,用于云端同步。 + * - {@link NoteColumns#VERSION}: 数据版本号,用于乐观锁或同步冲突解决。 + */ private static final String CREATE_NOTE_TABLE_SQL = "CREATE TABLE " + TABLE.NOTE + "(" + NoteColumns.ID + " INTEGER PRIMARY KEY," + @@ -88,6 +111,16 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + ")"; + /** + * 创建数据表 (data) 的 SQL 语句。 + *

+ * 设计模式:采用 EAV (Entity-Attribute-Value) 变体。 + * 核心字段说明: + * - {@link DataColumns#MIME_TYPE}: 数据类型(如文本、图片、提醒),决定 CONTENT 字段的语义。 + * - {@link DataColumns#NOTE_ID}: 外键,关联到 note 表的 ID。 + * - {@link DataColumns#CONTENT}: 实际数据内容。 + * - DATA1~DATA5: 通用扩展字段,存储整数或文本,具体含义由 MIME_TYPE 决定。 + */ private static final String CREATE_DATA_TABLE_SQL = "CREATE TABLE " + TABLE.DATA + "(" + DataColumns.ID + " INTEGER PRIMARY KEY," + @@ -103,12 +136,17 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + ")"; + /** + * 在 data 表的 NOTE_ID 列上创建索引的 SQL 语句。 + *

+ * 性能优化:显著加速根据便签 ID 查询其所有数据项(如内容、提醒)的速度。 + */ private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = "CREATE INDEX IF NOT EXISTS note_id_index ON " + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; /** - * Increase folder's note count when move note to the folder + * 触发器:当更新便签的父文件夹时(移动便签),增加新文件夹的笔记计数。 */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER increase_folder_count_on_update "+ @@ -120,7 +158,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Decrease folder's note count when move note from folder + * 触发器:当更新便签的父文件夹时(移动便签),减少旧文件夹的笔记计数。 + *

+ * 防御性编程:确保计数不小于 0。 */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_update " + @@ -133,7 +173,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Increase folder's note count when insert new note to the folder + * 触发器:当插入新便签时,增加其父文件夹的笔记计数。 */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = "CREATE TRIGGER increase_folder_count_on_insert " + @@ -145,7 +185,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Decrease folder's note count when delete note from the folder + * 触发器:当删除便签时,减少其父文件夹的笔记计数。 + *

+ * 防御性编程:确保计数不小于 0。 */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_delete " + @@ -158,7 +200,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Update note's content when insert data with type {@link DataConstants#NOTE} + * 触发器:当在 data 表中插入类型为 {@link DataConstants#NOTE}(文本内容)的数据时, + * 自动更新 note 表中对应便签的 {@link NoteColumns#SNIPPET} 字段。 + *

+ * 业务规则:实现内容与摘要的实时同步,确保列表页能立即显示最新内容。 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = "CREATE TRIGGER update_note_content_on_insert " + @@ -171,7 +216,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Update note's content when data with {@link DataConstants#NOTE} type has changed + * 触发器:当更新 data 表中类型为 {@link DataConstants#NOTE} 的数据时,自动更新对应便签的摘要。 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = "CREATE TRIGGER update_note_content_on_update " + @@ -184,7 +229,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Update note's content when data with {@link DataConstants#NOTE} type has deleted + * 触发器:当删除 data 表中类型为 {@link DataConstants#NOTE} 的数据时,将对应便签的摘要清空。 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = "CREATE TRIGGER update_note_content_on_delete " + @@ -197,7 +242,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Delete datas belong to note which has been deleted + * 级联删除触发器:当删除 note 表中的一条记录(一个便签/文件夹)时, + * 自动删除 data 表中所有关联的 ({@link DataColumns#NOTE_ID}) 数据记录。 + *

+ * 数据一致性:确保没有孤儿数据残留,是数据库层面实现的外键级联删除(尽管未使用 FOREIGN KEY 约束)。 */ private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = "CREATE TRIGGER delete_data_on_delete " + @@ -208,7 +256,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Delete notes belong to folder which has been deleted + * 级联删除触发器:当删除一个文件夹(TYPE 为文件夹)时, + * 自动删除其下的所有子便签(根据 PARENT_ID 匹配)。 + *

+ * 注意:此触发器会与 {@link #NOTE_DELETE_DATA_ON_DELETE_TRIGGER} 形成连锁反应,最终清理所有相关数据。 */ private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = "CREATE TRIGGER folder_delete_notes_on_delete " + @@ -219,7 +270,10 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Move notes belong to folder which has been moved to trash folder + * 业务规则触发器:当将一个文件夹移动到回收站(其 PARENT_ID 被更新为 {@link Notes#ID_TRASH_FOLER})时, + * 自动将其下的所有子便签的 PARENT_ID 也更新为回收站 ID。 + *

+ * 设计意图:实现“移动文件夹到回收站即移动其全部内容”的用户预期,避免文件夹进入回收站后内容外露。 */ private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = "CREATE TRIGGER folder_move_notes_on_trash " + @@ -232,16 +286,23 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * 私有构造函数,防止外部实例化 - * @param context 上下文 + * 私有构造函数,强制通过 {@link #getInstance(Context)} 获取单例。 + * + * @param context 应用上下文,用于数据库创建和路径定位。 */ public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } /** - * 创建笔记表 - * @param db 数据库实例 + * 创建便签表 (note) 及其关联的触发器与系统数据。 + *

+ * 执行步骤: + * 1. 执行建表 SQL。 + * 2. 重建所有业务触发器。 + * 3. 插入四个必需的系统文件夹记录。 + * + * @param db 可写的数据库实例。 */ public void createNoteTable(SQLiteDatabase db) { // 执行创建表SQL @@ -254,8 +315,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } /** - * 重新创建笔记表相关触发器 - * @param db 数据库实例 + * 重建 note 表的所有 SQL 触发器。 + *

+ * 方法逻辑:先删除可能存在的旧触发器,再创建新触发器。 + * 此方法用于保障表结构升级后,触发器逻辑与新版表结构保持一致。 + * + * @param db 可写的数据库实例。 */ private void reCreateNoteTableTriggers(SQLiteDatabase db) { // 删除旧触发器(如果存在) @@ -278,8 +343,15 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } /** - * 创建系统文件夹 - * @param db 数据库实例 + * 初始化插入系统文件夹记录。 + *

+ * 系统文件夹是应用运行的基础,包括: + * 1. 通话记录文件夹 ({@link Notes#ID_CALL_RECORD_FOLDER}) + * 2. 根文件夹 ({@link Notes#ID_ROOT_FOLDER}):用户看到的默认文件夹。 + * 3. 临时文件夹 ({@link Notes#ID_TEMPARAY_FOLDER}):用于暂存正在移动的便签。 + * 4. 回收站 ({@link Notes#ID_TRASH_FOLER}):存储已删除的便签。 + * + * @param db 可写的数据库实例。 */ private void createSystemFolder(SQLiteDatabase db) { ContentValues values = new ContentValues(); @@ -317,8 +389,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } /** - * 创建数据表 - * @param db 数据库实例 + * 创建数据表 (data) 及其关联的触发器与索引。 + * + * @param db 可写的数据库实例。 */ public void createDataTable(SQLiteDatabase db) { // 执行创建表SQL @@ -331,8 +404,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } /** - * 重新创建数据表相关触发器 - * @param db 数据库实例 + * 重建 data 表的所有 SQL 触发器。 + *

+ * 这些触发器主要负责维护 note 表的 SNIPPET 字段与 data 表 CONTENT 字段的一致性。 + * + * @param db 可写的数据库实例。 */ private void reCreateDataTableTriggers(SQLiteDatabase db) { db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); @@ -345,9 +421,12 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } /** - * 获取单例实例 - * @param context 上下文 - * @return 数据库帮助类实例 + * 获取数据库帮助类的全局单例实例。 + *

+ * 线程安全:通过 synchronized 方法确保多线程环境下仅创建一个实例。 + * + * @param context 应用上下文。 + * @return NotesDatabaseHelper 的单例实例。 */ static synchronized NotesDatabaseHelper getInstance(Context context) { if (mInstance == null) { @@ -357,8 +436,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } /** - * 创建数据库 - * @param db 数据库实例 + * 数据库首次创建时回调。 + *

+ * 执行顺序:先创建 note 表(含系统文件夹),再创建 data 表。 + * + * @param db 数据库实例。 */ @Override public void onCreate(SQLiteDatabase db) { @@ -369,38 +451,48 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } /** - * 升级数据库 - * @param db 数据库实例 - * @param oldVersion 旧版本号 - * @param newVersion 新版本号 + * 数据库版本升级时回调。 + *

+ * 升级策略:采用递进式升级,每个版本号对应一个升级方法。 + * 注意处理跨版本升级(如从v1直接升到v4)的路径。 + * + * @param db 可写的数据库实例。 + * @param oldVersion 当前设备上的旧数据库版本。 + * @param newVersion 目标新版本({@link #DB_VERSION})。 + * @throws IllegalStateException 如果升级路径无法处理,将抛出此异常。 */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { boolean reCreateTriggers = false; boolean skipV2 = false; + // 1. 从版本1升级 if (oldVersion == 1) { upgradeToV2(db); skipV2 = true; // this upgrade including the upgrade from v2 to v3 oldVersion++; } + // 2. 从版本2升级(如果未跳过) if (oldVersion == 2 && !skipV2) { upgradeToV3(db); reCreateTriggers = true; oldVersion++; } + // 3. 从版本3升级 if (oldVersion == 3) { upgradeToV4(db); oldVersion++; } + // 4. 如果表结构在v3升级中发生较大变更,需要重建触发器以确保兼容性 if (reCreateTriggers) { reCreateNoteTableTriggers(db); reCreateDataTableTriggers(db); } + // 5. 最终版本校验:确保所有升级步骤已执行完毕 if (oldVersion != newVersion) { throw new IllegalStateException("Upgrade notes database to version " + newVersion + "fails"); @@ -408,8 +500,13 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } /** - * 升级到版本2 - * @param db 数据库实例 + * 升级数据库到版本 2。 + *

+ * 升级方式:激进式重构。直接删除旧表,然后调用 {@link #onCreate} 重建全新表结构。 + * 适用场景:早期版本表结构不稳定或存在重大设计缺陷时使用。 + * 副作用:所有用户数据将被清空! + * + * @param db 可写的数据库实例。 */ private void upgradeToV2(SQLiteDatabase db) { db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); @@ -419,8 +516,15 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } /** - * 升级到版本3 - * @param db 数据库实例 + * 升级数据库到版本 3。 + *

+ * 升级方式:渐进式迁移 (ALTER TABLE)。 + * 主要变更: + * 1. 移除过时的修改日期触发器(其逻辑已整合到表定义默认值中)。 + * 2. 增加 Google 任务同步 ID 字段 ({@link NoteColumns#GTASK_ID})。 + * 3. 新增“回收站”系统文件夹。 + * + * @param db 可写的数据库实例。 */ private void upgradeToV3(SQLiteDatabase db) { // drop unused triggers @@ -438,11 +542,17 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { } /** - * 升级到版本4 - * @param db 数据库实例 + * 升级数据库到版本 4。 + *

+ * 升级方式:渐进式迁移。 + * 主要变更:增加数据版本字段 ({@link NoteColumns#VERSION})。 + *

+ * 设计意图:为未来可能的数据同步冲突解决(乐观锁)或更精细的增量同步提供基础。 + * + * @param db 可写的数据库实例。 */ 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/src/notes/gtask/remote/GTaskClient.java b/src/notes/gtask/remote/GTaskClient.java index a10ffe1..71b180c 100644 --- a/src/notes/gtask/remote/GTaskClient.java +++ b/src/notes/gtask/remote/GTaskClient.java @@ -324,28 +324,21 @@ public class GTaskClient { Log.d(TAG, "encoding: " + contentEncoding); } - InputStream input = entity.getContent(); - if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { - input = new GZIPInputStream(entity.getContent()); - } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { - Inflater inflater = new Inflater(true); - input = new InflaterInputStream(entity.getContent(), inflater); - } - - try { - InputStreamReader isr = new InputStreamReader(input); - BufferedReader br = new BufferedReader(isr); + try (InputStream input = entity.getContent(); + InputStream finalInput = (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) + ? new GZIPInputStream(input) + : (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) + ? new InflaterInputStream(input, new Inflater(true)) + : input; + InputStreamReader isr = new InputStreamReader(finalInput); + 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); + String buff; + while ((buff = br.readLine()) != null) { + sb.append(buff); } - } finally { - input.close(); + return sb.toString(); } } diff --git a/src/notes/tool/BackupUtils.java b/src/notes/tool/BackupUtils.java index b869743..e853810 100644 --- a/src/notes/tool/BackupUtils.java +++ b/src/notes/tool/BackupUtils.java @@ -236,67 +236,69 @@ public class BackupUtils { return STATE_SD_CARD_UNMOUONTED; } - // 获取输出流 - PrintStream ps = getExportToTextPrintStream(); - if (ps == null) { - Log.e(TAG, "get print stream error"); - return STATE_SYSTEM_ERROR; - } - - // 第一部分:导出文件夹及其中的笔记 - // 查询所有文件夹(排除回收站,但包含通话记录文件夹) - Cursor folderCursor = mContext.getContentResolver().query( - Notes.CONTENT_NOTE_URI, - NOTE_PROJECTION, - "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " - + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " - + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); - - if (folderCursor != null) { - if (folderCursor.moveToFirst()) { - do { - // 输出文件夹名称 - String folderName = ""; - if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { - folderName = mContext.getString(R.string.call_record_folder_name); - } else { - folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); - } - if (!TextUtils.isEmpty(folderName)) { - ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); - } - // 导出该文件夹下的所有笔记 - String folderId = folderCursor.getString(NOTE_COLUMN_ID); - exportFolderToText(folderId, ps); - } while (folderCursor.moveToNext()); + // 使用try-with-resources自动关闭输出流 + try (PrintStream ps = getExportToTextPrintStream()) { + if (ps == null) { + Log.e(TAG, "get print stream error"); + return STATE_SYSTEM_ERROR; } - folderCursor.close(); - } - // 第二部分:导出根目录下的笔记(不属于任何文件夹的笔记) - Cursor noteCursor = mContext.getContentResolver().query( - Notes.CONTENT_NOTE_URI, - NOTE_PROJECTION, - NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID - + "=0", null, null); + // 第一部分:导出文件夹及其中的笔记 + // 查询所有文件夹(排除回收站,但包含通话记录文件夹) + Cursor folderCursor = mContext.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, + "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " + + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " + + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); + + if (folderCursor != null) { + if (folderCursor.moveToFirst()) { + do { + // 输出文件夹名称 + String folderName = ""; + if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { + folderName = mContext.getString(R.string.call_record_folder_name); + } else { + folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); + } + if (!TextUtils.isEmpty(folderName)) { + ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); + } + // 导出该文件夹下的所有笔记 + String folderId = folderCursor.getString(NOTE_COLUMN_ID); + exportFolderToText(folderId, ps); + } while (folderCursor.moveToNext()); + } + folderCursor.close(); + } - if (noteCursor != null) { - if (noteCursor.moveToFirst()) { - do { - ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( - mContext.getString(R.string.format_datetime_mdhm), - noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // 导出该笔记的内容 - String noteId = noteCursor.getString(NOTE_COLUMN_ID); - exportNoteToText(noteId, ps); - } while (noteCursor.moveToNext()); + // 第二部分:导出根目录下的笔记(不属于任何文件夹的笔记) + Cursor noteCursor = mContext.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, + NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + + "=0", null, null); + + if (noteCursor != null) { + if (noteCursor.moveToFirst()) { + do { + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( + mContext.getString(R.string.format_datetime_mdhm), + noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); + // 导出该笔记的内容 + String noteId = noteCursor.getString(NOTE_COLUMN_ID); + exportNoteToText(noteId, ps); + } while (noteCursor.moveToNext()); + } + noteCursor.close(); } - noteCursor.close(); - } - // 关闭输出流 - ps.close(); - return STATE_SUCCESS; + return STATE_SUCCESS; + } catch (Exception e) { + Log.e(TAG, "Export text failed: " + e.getMessage()); + return STATE_SYSTEM_ERROR; + } } /** diff --git a/src/notes/tool/DataUtils.java b/src/notes/tool/DataUtils.java index b4cdb3d..a0aa76f 100644 --- a/src/notes/tool/DataUtils.java +++ b/src/notes/tool/DataUtils.java @@ -153,15 +153,10 @@ public class DataUtils { int count = 0; if(cursor != null) { - if(cursor.moveToFirst()) { - try { - count = cursor.getInt(0); // 获取计数结果 - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, "get folder count failed:" + e.toString()); - } finally { - cursor.close(); - } + if(cursor.moveToFirst() && cursor.getColumnCount() > 0) { + count = cursor.getInt(0); // 获取计数结果 } + cursor.close(); } return count; } @@ -181,14 +176,14 @@ public class DataUtils { new String [] {String.valueOf(type)}, null); - boolean exist = false; + boolean exists = false; if (cursor != null) { if (cursor.getCount() > 0) { // 如果查询结果数量大于0,则表示存在 - exist = true; + exists = true; } cursor.close(); } - return exist; + return exists; } /** @@ -202,14 +197,14 @@ public class DataUtils { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, null, null, null); - boolean exist = false; + boolean exists = false; if (cursor != null) { if (cursor.getCount() > 0) { - exist = true; + exists = true; } cursor.close(); } - return exist; + return exists; } /** @@ -223,14 +218,14 @@ public class DataUtils { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null, null, null, null); - boolean exist = false; + boolean exists = false; if (cursor != null) { if (cursor.getCount() > 0) { - exist = true; + exists = true; } cursor.close(); } - return exist; + return exists; } /** @@ -246,14 +241,14 @@ public class DataUtils { " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + " AND " + NoteColumns.SNIPPET + "=?", new String[] { name }, null); - boolean exist = false; + boolean exists = false; if(cursor != null) { if(cursor.getCount() > 0) { - exist = true; + exists = true; } cursor.close(); } - return exist; + return exists; } /** @@ -275,13 +270,12 @@ public class DataUtils { if (c.moveToFirst()) { set = new HashSet(); do { - try { - AppWidgetAttribute widget = new AppWidgetAttribute(); + AppWidgetAttribute widget = new AppWidgetAttribute(); + // 确保列索引有效 + if (c.getColumnCount() > 1) { widget.widgetId = c.getInt(0); // 小部件ID widget.widgetType = c.getInt(1); // 小部件类型 set.add(widget); - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, e.toString()); } } while (c.moveToNext()); } @@ -306,9 +300,9 @@ public class DataUtils { if (cursor != null && cursor.moveToFirst()) { try { - return cursor.getString(0); // 返回电话号码 - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, "Get call number fails " + e.toString()); + if (cursor.getColumnCount() > 0) { + return cursor.getString(0); // 返回电话号码 + } } finally { cursor.close(); } @@ -328,17 +322,13 @@ public class DataUtils { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, new String [] { CallNote.NOTE_ID }, CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL(" - + CallNote.PHONE_NUMBER + ",?)", + + CallNote.PHONE_NUMBER + ",?)" , new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber }, null); if (cursor != null) { - if (cursor.moveToFirst()) { - try { - return cursor.getLong(0); // 返回笔记ID - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, "Get call note id fails " + e.toString()); - } + if (cursor.moveToFirst() && cursor.getColumnCount() > 0) { + return cursor.getLong(0); // 返回笔记ID } cursor.close(); } diff --git a/src/notes/tool/ResourceParser.java b/src/notes/tool/ResourceParser.java index f86f4a8..3cbb6c0 100644 --- a/src/notes/tool/ResourceParser.java +++ b/src/notes/tool/ResourceParser.java @@ -22,6 +22,8 @@ import android.preference.PreferenceManager; import net.micode.notes.R; import net.micode.notes.ui.NotesPreferenceActivity; +import java.security.SecureRandom; + public class ResourceParser { // 笔记背景颜色常量定义 public static final int YELLOW = 0; // 黄色背景 @@ -92,8 +94,9 @@ public class ResourceParser { // 检查用户是否启用了随机背景颜色功能 if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { - // 随机选择一个背景颜色 - return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length); + // 使用密码学安全的随机数生成器随机选择一个背景颜色 + SecureRandom secureRandom = new SecureRandom(); + return secureRandom.nextInt(NoteBgResources.BG_EDIT_RESOURCES.length); } else { // 使用默认的背景颜色(黄色) return BG_DEFAULT_COLOR; diff --git a/src/notes/ui/NotesListActivity.java b/src/notes/ui/NotesListActivity.java index 6bbb470..4662f54 100644 --- a/src/notes/ui/NotesListActivity.java +++ b/src/notes/ui/NotesListActivity.java @@ -177,13 +177,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt // 检查是否已添加过介绍笔记 if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { StringBuilder sb = new StringBuilder(); - InputStream in = null; - try { + try (InputStream in = getResources().openRawResource(R.raw.introduction); + InputStreamReader isr = new InputStreamReader(in); + BufferedReader br = new BufferedReader(isr)) { // 从raw资源读取介绍文件 - 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) { @@ -196,14 +194,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } catch (IOException e) { e.printStackTrace(); return; - } finally { - if(in != null) { - try { - in.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } } // 创建空白笔记并设置介绍文本 diff --git a/src/notes/ui/NotesPreferenceActivity.java b/src/notes/ui/NotesPreferenceActivity.java index 7cd21a9..f63373c 100644 --- a/src/notes/ui/NotesPreferenceActivity.java +++ b/src/notes/ui/NotesPreferenceActivity.java @@ -118,15 +118,15 @@ public class NotesPreferenceActivity extends PreferenceActivity { // 检查是否有新账户添加 if (mOriAccounts != null && accounts.length > mOriAccounts.length) { for (Account accountNew : accounts) { - boolean found = false; + boolean isFound = false; for (Account accountOld : mOriAccounts) { if (TextUtils.equals(accountOld.name, accountNew.name)) { - found = true; + isFound = true; break; } } // 发现新账户,自动设置为同步账户 - if (!found) { + if (!isFound) { setSyncAccount(accountNew.name); break; }