diff --git a/doc/小米便签泛读、标注和维护报告.docx b/doc/小米便签泛读、标注和维护报告.docx index 58a4fd9..709de28 100644 Binary files a/doc/小米便签泛读、标注和维护报告.docx and b/doc/小米便签泛读、标注和维护报告.docx differ diff --git a/src/tool/BackupUtils.java b/src/tool/BackupUtils.java index 39f6ec4..3e0222c 100644 --- a/src/tool/BackupUtils.java +++ b/src/tool/BackupUtils.java @@ -35,12 +35,32 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; - +/** + * 备份工具类 + * + * 负责将便签数据导出为可读的文本文件,支持以下功能: + * 1. 导出所有便签数据到SD卡上的文本文件 + * 2. 按文件夹组织导出内容 + * 3. 支持普通便签和通话记录便签的格式转换 + * 4. 提供详细的导出状态反馈 + * + * 设计说明: + * - 使用单例模式确保全局只有一个备份工具实例 + * - 内部类TextExport处理具体的文本导出逻辑 + * - 支持状态码反馈,便于UI层展示导出状态 + */ public class BackupUtils { - private static final String TAG = "BackupUtils"; + private static final String TAG = "BackupUtils"; // 日志标签 // Singleton stuff - private static BackupUtils sInstance; + private static BackupUtils sInstance; // 单例实例 + /** + * 获取BackupUtils单例实例 + * 使用双重检查锁定确保线程安全 + * + * @param context 应用上下文 + * @return BackupUtils单例 + */ public static synchronized BackupUtils getInstance(Context context) { if (sInstance == null) { sInstance = new BackupUtils(context); @@ -49,82 +69,117 @@ public class BackupUtils { } /** - * Following states are signs to represents backup or restore - * status + * 导出和恢复操作的状态码定义 */ - // Currently, the sdcard is not mounted + // 当前SD卡未挂载 public static final int STATE_SD_CARD_UNMOUONTED = 0; - // The backup file not exist + // 备份文件不存在 public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; - // The data is not well formated, may be changed by other programs + // 数据格式被破坏,可能被其他程序修改 public static final int STATE_DATA_DESTROIED = 2; - // Some run-time exception which causes restore or backup fails + // 运行时异常导致备份或恢复失败 public static final int STATE_SYSTEM_ERROR = 3; - // Backup or restore success + // 备份或恢复成功 public static final int STATE_SUCCESS = 4; - private TextExport mTextExport; + private TextExport mTextExport; // 文本导出器实例 + /** + * 私有构造函数 + * + * @param context 应用上下文 + */ private BackupUtils(Context context) { mTextExport = new TextExport(context); } + /** + * 检查外部存储是否可用 + * + * @return true表示SD卡已挂载且可写,false表示不可用 + */ private static boolean externalStorageAvailable() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } + /** + * 导出便签到文本文件 + * + * @return 导出操作的状态码 + */ public int exportToText() { return mTextExport.exportToText(); } + /** + * 获取导出的文本文件名 + * + * @return 导出的文本文件名 + */ public String getExportedTextFileName() { return mTextExport.mFileName; } + /** + * 获取导出的文本文件目录 + * + * @return 导出的文本文件目录 + */ public String getExportedTextFileDir() { return mTextExport.mFileDirectory; } + /** + * 文本导出内部类 + * + * 负责具体的文本导出逻辑,包括: + * 1. 遍历数据库中的便签和文件夹 + * 2. 格式化输出为可读文本 + * 3. 生成输出文件 + */ private static class TextExport { + // 便签表的查询投影列 private static final String[] NOTE_PROJECTION = { - NoteColumns.ID, - NoteColumns.MODIFIED_DATE, - NoteColumns.SNIPPET, - NoteColumns.TYPE + NoteColumns.ID, // 0: 便签ID + NoteColumns.MODIFIED_DATE, // 1: 修改日期 + NoteColumns.SNIPPET, // 2: 便签片段(用于文件夹名) + NoteColumns.TYPE // 3: 便签类型 }; - private static final int NOTE_COLUMN_ID = 0; - - private static final int NOTE_COLUMN_MODIFIED_DATE = 1; - - private static final int NOTE_COLUMN_SNIPPET = 2; + private static final int NOTE_COLUMN_ID = 0; // 便签ID列索引 + private static final int NOTE_COLUMN_MODIFIED_DATE = 1; // 修改日期列索引 + private static final int NOTE_COLUMN_SNIPPET = 2; // 便签片段列索引 + // 数据表的查询投影列 private static final String[] DATA_PROJECTION = { - DataColumns.CONTENT, - DataColumns.MIME_TYPE, - DataColumns.DATA1, - DataColumns.DATA2, - DataColumns.DATA3, - DataColumns.DATA4, + DataColumns.CONTENT, // 0: 数据内容 + DataColumns.MIME_TYPE, // 1: MIME类型 + DataColumns.DATA1, // 2: 数据1(通话记录的通话日期) + DataColumns.DATA2, // 3: 数据2 + DataColumns.DATA3, // 4: 数据3 + DataColumns.DATA4, // 5: 数据4(通话记录的电话号码) }; - private static final int DATA_COLUMN_CONTENT = 0; - - private static final int DATA_COLUMN_MIME_TYPE = 1; - - private static final int DATA_COLUMN_CALL_DATE = 2; - - private static final int DATA_COLUMN_PHONE_NUMBER = 4; + private static final int DATA_COLUMN_CONTENT = 0; // 内容列索引 + private static final int DATA_COLUMN_MIME_TYPE = 1; // MIME类型列索引 + private static final int DATA_COLUMN_CALL_DATE = 2; // 通话日期列索引 + private static final int DATA_COLUMN_PHONE_NUMBER = 4; // 电话号码列索引 + // 文本格式化字符串数组,从资源文件中加载 private final String [] TEXT_FORMAT; - private static final int FORMAT_FOLDER_NAME = 0; - private static final int FORMAT_NOTE_DATE = 1; - private static final int FORMAT_NOTE_CONTENT = 2; + private static final int FORMAT_FOLDER_NAME = 0; // 文件夹名称格式索引 + private static final int FORMAT_NOTE_DATE = 1; // 便签日期格式索引 + private static final int FORMAT_NOTE_CONTENT = 2; // 便签内容格式索引 - private Context mContext; - private String mFileName; - private String mFileDirectory; + private Context mContext; // 应用上下文 + private String mFileName; // 导出的文件名 + private String mFileDirectory; // 导出的文件目录 + /** + * 构造函数 + * + * @param context 应用上下文 + */ public TextExport(Context context) { TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); mContext = context; @@ -132,28 +187,37 @@ public class BackupUtils { mFileDirectory = ""; } + /** + * 获取指定格式的字符串 + * + * @param id 格式ID(FORMAT_FOLDER_NAME, FORMAT_NOTE_DATE, FORMAT_NOTE_CONTENT) + * @return 格式字符串 + */ private String getFormat(int id) { return TEXT_FORMAT[id]; } /** - * Export the folder identified by folder id to text + * 导出指定文件夹及其下的所有便签到文本流 + * + * @param folderId 文件夹ID + * @param ps 输出打印流 */ private void exportFolderToText(String folderId, PrintStream ps) { - // Query notes belong to this folder + // 查询属于此文件夹的所有便签 Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { - folderId + folderId }, null); if (notesCursor != null) { if (notesCursor.moveToFirst()) { do { - // Print note's last modified date + // 打印便签的最后修改日期 ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( mContext.getString(R.string.format_datetime_mdhm), notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // Query data belong to this note + // 查询属于此便签的数据 String noteId = notesCursor.getString(NOTE_COLUMN_ID); exportNoteToText(noteId, ps); } while (notesCursor.moveToNext()); @@ -163,12 +227,15 @@ public class BackupUtils { } /** - * Export note identified by id to a print stream + * 导出指定便签到文本流 + * + * @param noteId 便签ID + * @param ps 输出打印流 */ private void exportNoteToText(String noteId, PrintStream ps) { Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { - noteId + noteId }, null); if (dataCursor != null) { @@ -176,25 +243,27 @@ public class BackupUtils { do { String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); if (DataConstants.CALL_NOTE.equals(mimeType)) { - // Print phone number + // 处理通话记录便签 String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); String location = dataCursor.getString(DATA_COLUMN_CONTENT); + // 打印电话号码(如果存在) if (!TextUtils.isEmpty(phoneNumber)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), phoneNumber)); } - // Print call date + // 打印通话日期 ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat .format(mContext.getString(R.string.format_datetime_mdhm), callDate))); - // Print call attachment location + // 打印通话附件位置(如果存在) if (!TextUtils.isEmpty(location)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), location)); } } else if (DataConstants.NOTE.equals(mimeType)) { + // 处理普通文本便签 String content = dataCursor.getString(DATA_COLUMN_CONTENT); if (!TextUtils.isEmpty(content)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), @@ -205,7 +274,7 @@ public class BackupUtils { } dataCursor.close(); } - // print a line separator between note + // 在便签之间打印一个行分隔符 try { ps.write(new byte[] { Character.LINE_SEPARATOR, Character.LETTER_NUMBER @@ -216,20 +285,26 @@ public class BackupUtils { } /** - * Note will be exported as text which is user readable + * 将便签导出为用户可读的文本文件 + * + * @return 导出操作的状态码 */ public int exportToText() { + // 检查SD卡是否可用 if (!externalStorageAvailable()) { Log.d(TAG, "Media was not mounted"); return STATE_SD_CARD_UNMOUONTED; } + // 获取输出打印流 PrintStream ps = getExportToTextPrintStream(); if (ps == null) { Log.e(TAG, "get print stream error"); return STATE_SYSTEM_ERROR; } - // First export folder and its notes + + // 首先导出文件夹及其下的便签 + // 查询所有文件夹(不包括回收站)和通话记录文件夹 Cursor folderCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, @@ -240,7 +315,7 @@ public class BackupUtils { if (folderCursor != null) { if (folderCursor.moveToFirst()) { do { - // Print folder's name + // 打印文件夹名称 String folderName = ""; if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { folderName = mContext.getString(R.string.call_record_folder_name); @@ -257,7 +332,7 @@ public class BackupUtils { folderCursor.close(); } - // Export notes in root's folder + // 导出根文件夹下的便签(不属于任何自定义文件夹的便签) Cursor noteCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, @@ -270,30 +345,33 @@ public class BackupUtils { ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( mContext.getString(R.string.format_datetime_mdhm), noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // Query data belong to this note + // 查询属于此便签的数据 String noteId = noteCursor.getString(NOTE_COLUMN_ID); exportNoteToText(noteId, ps); } while (noteCursor.moveToNext()); } noteCursor.close(); } - ps.close(); + ps.close(); // 关闭输出流 - return STATE_SUCCESS; + return STATE_SUCCESS; // 导出成功 } /** - * Get a print stream pointed to the file {@generateExportedTextFile} + * 获取指向导出文本文件的打印流 + * + * @return 打印流,失败时返回null */ private PrintStream getExportToTextPrintStream() { + // 生成SD卡上的输出文件 File file = generateFileMountedOnSDcard(mContext, R.string.file_path, R.string.file_name_txt_format); if (file == null) { Log.e(TAG, "create file to exported failed"); return null; } - mFileName = file.getName(); - mFileDirectory = mContext.getString(R.string.file_path); + mFileName = file.getName(); // 保存文件名 + mFileDirectory = mContext.getString(R.string.file_path); // 保存文件目录 PrintStream ps = null; try { FileOutputStream fos = new FileOutputStream(file); @@ -310,23 +388,33 @@ public class BackupUtils { } /** - * Generate the text file to store imported data + * 在SD卡上生成用于存储导出数据的文本文件 + * + * @param context 应用上下文 + * @param filePathResId 文件路径资源ID + * @param fileNameFormatResId 文件名格式资源ID + * @return 生成的File对象,失败时返回null */ private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { StringBuilder sb = new StringBuilder(); - sb.append(Environment.getExternalStorageDirectory()); - sb.append(context.getString(filePathResId)); - File filedir = new File(sb.toString()); + // 构建完整文件路径 + sb.append(Environment.getExternalStorageDirectory()); // SD卡根目录 + 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()); + File file = new File(sb.toString()); // 完整文件对象 try { + // 创建目录(如果不存在) if (!filedir.exists()) { filedir.mkdir(); } + // 创建文件(如果不存在) if (!file.exists()) { file.createNewFile(); } @@ -337,8 +425,6 @@ public class BackupUtils { e.printStackTrace(); } - return null; + return null; // 创建失败 } -} - - +} \ No newline at end of file diff --git a/src/tool/DataUtils.java b/src/tool/DataUtils.java index 2a14982..7483c6e 100644 --- a/src/tool/DataUtils.java +++ b/src/tool/DataUtils.java @@ -34,24 +34,50 @@ import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; import java.util.ArrayList; import java.util.HashSet; - +/** + * 数据操作工具类 + * + * 提供便签数据的批量操作、查询和验证功能,包括: + * 1. 批量删除便签 + * 2. 批量移动便签到指定文件夹 + * 3. 查询文件夹和便签信息 + * 4. 验证数据存在性和唯一性 + * + * 设计说明: + * - 所有方法均为静态方法,无需实例化 + * - 使用批量操作提高数据库操作效率 + * - 提供详细的错误日志输出 + */ public class DataUtils { - public static final String TAG = "DataUtils"; + public static final String TAG = "DataUtils"; // 日志标签 + + /** + * 批量删除便签 + * + * 使用批量操作(applyBatch)高效删除多个便签,支持以下特性: + * 1. 自动跳过系统文件夹(如根文件夹)的保护 + * 2. 支持空集合和空参数的检查 + * 3. 使用事务保证数据一致性 + * + * @param resolver 内容解析器,用于访问ContentProvider + * @param ids 要删除的便签ID集合,为null或空集合时直接返回成功 + * @return true表示删除成功,false表示删除失败 + */ public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { if (ids == null) { Log.d(TAG, "the ids is null"); - return true; + return true; // 空参数视为成功,无需操作 } if (ids.size() == 0) { Log.d(TAG, "no id is in the hashset"); - return true; + return true; // 空集合视为成功,无需操作 } ArrayList operationList = new ArrayList(); for (long id : ids) { if(id == Notes.ID_ROOT_FOLDER) { Log.e(TAG, "Don't delete system folder root"); - continue; + continue; // 跳过系统根文件夹,防止误删系统数据 } ContentProviderOperation.Builder builder = ContentProviderOperation .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); @@ -61,38 +87,61 @@ public class DataUtils { 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 false; // 批量操作返回结果异常 } - return true; + return true; // 删除成功 } catch (RemoteException e) { Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); } catch (OperationApplicationException e) { Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); } - return false; + return false; // 异常情况 } + /** + * 移动单个便签到指定文件夹 + * + * 更新便签的父文件夹ID,并记录原始父文件夹ID以便撤销操作 + * + * @param resolver 内容解析器 + * @param id 要移动的便签ID + * @param srcFolderId 原始文件夹ID + * @param desFolderId 目标文件夹ID + */ public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { ContentValues values = new ContentValues(); - values.put(NoteColumns.PARENT_ID, desFolderId); - values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); - values.put(NoteColumns.LOCAL_MODIFIED, 1); + 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); } + /** + * 批量移动便签到指定文件夹 + * + * 使用批量操作高效移动多个便签,支持以下特性: + * 1. 支持空集合的检查 + * 2. 使用事务保证数据一致性 + * 3. 自动标记为本地修改,触发同步 + * + * @param resolver 内容解析器 + * @param ids 要移动的便签ID集合 + * @param folderId 目标文件夹ID + * @return true表示移动成功,false表示移动失败 + */ public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids, - long folderId) { + long folderId) { if (ids == null) { Log.d(TAG, "the ids is null"); - return true; + return true; // 空参数视为成功 } ArrayList operationList = new ArrayList(); for (long id : ids) { ContentProviderOperation.Builder builder = ContentProviderOperation .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); - builder.withValue(NoteColumns.PARENT_ID, folderId); - builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); + builder.withValue(NoteColumns.PARENT_ID, folderId); // 设置新父文件夹 + builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); // 标记为本地修改 operationList.add(builder.build()); } @@ -112,7 +161,12 @@ public class DataUtils { } /** - * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} + * 获取用户文件夹数量(排除系统文件夹) + * + * 统计用户创建的自定义文件夹数量,不包括系统文件夹和回收站 + * + * @param resolver 内容解析器 + * @return 用户文件夹数量,查询失败时返回0 */ public static int getUserFolderCount(ContentResolver resolver) { Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, @@ -136,6 +190,16 @@ public class DataUtils { return count; } + /** + * 检查指定类型便签在数据库中是否可见(不在回收站中) + * + * 用于验证便签是否存在于数据库中且未被放入回收站 + * + * @param resolver 内容解析器 + * @param noteId 要检查的便签ID + * @param type 便签类型(Notes.TYPE_NOTE或Notes.TYPE_FOLDER) + * @return true表示便签存在且不在回收站中,false表示不存在或在回收站中 + */ public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, @@ -153,6 +217,15 @@ public class DataUtils { return exist; } + /** + * 检查便签是否存在于数据库中 + * + * 不检查便签是否在回收站中,只验证ID是否存在 + * + * @param resolver 内容解析器 + * @param noteId 要检查的便签ID + * @return true表示便签ID存在,false表示不存在 + */ public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null, null, null, null); @@ -167,6 +240,15 @@ public class DataUtils { return exist; } + /** + * 检查数据记录是否存在于数据表中 + * + * 用于验证数据ID是否可用,避免ID冲突 + * + * @param resolver 内容解析器 + * @param dataId 要检查的数据记录ID + * @return true表示数据ID存在,false表示不存在 + */ public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null, null, null, null); @@ -181,11 +263,20 @@ public class DataUtils { return exist; } + /** + * 检查指定文件夹名称是否已存在(在可见文件夹中) + * + * 用于创建新文件夹时的名称重复性检查,不检查回收站中的文件夹 + * + * @param resolver 内容解析器 + * @param name 要检查的文件夹名称 + * @return true表示同名文件夹已存在,false表示不存在 + */ public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + - " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + - " AND " + NoteColumns.SNIPPET + "=?", + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.SNIPPET + "=?", new String[] { name }, null); boolean exist = false; if(cursor != null) { @@ -197,6 +288,15 @@ public class DataUtils { return exist; } + /** + * 获取指定文件夹下所有便签关联的小部件属性 + * + * 用于文件夹删除或移动时,更新关联的小部件显示 + * + * @param resolver 内容解析器 + * @param folderId 文件夹ID + * @return 小部件属性集合,没有关联小部件时返回null + */ public static HashSet getFolderNoteWidget(ContentResolver resolver, long folderId) { Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, @@ -224,6 +324,15 @@ public class DataUtils { return set; } + /** + * 根据便签ID获取关联的通话记录电话号码 + * + * 用于通话记录便签的显示和查询 + * + * @param resolver 内容解析器 + * @param noteId 便签ID + * @return 电话号码字符串,未找到时返回空字符串 + */ public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, new String [] { CallNote.PHONE_NUMBER }, @@ -240,14 +349,25 @@ public class DataUtils { cursor.close(); } } - return ""; + return ""; // 未找到通话记录或查询失败 } + /** + * 根据电话号码和通话时间获取便签ID + * + * 用于检查是否存在相同通话记录,避免重复创建 + * 使用自定义SQL函数PHONE_NUMBERS_EQUAL进行电话号码匹配 + * + * @param resolver 内容解析器 + * @param phoneNumber 电话号码 + * @param callDate 通话时间戳 + * @return 便签ID,未找到时返回0 + */ public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, new String [] { CallNote.NOTE_ID }, CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL(" - + CallNote.PHONE_NUMBER + ",?)", + + CallNote.PHONE_NUMBER + ",?)", new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber }, null); @@ -261,9 +381,19 @@ public class DataUtils { } cursor.close(); } - return 0; + return 0; // 未找到匹配的通话记录 } + /** + * 根据便签ID获取便签片段(snippet) + * + * 便签片段通常用于列表显示,是便签内容的摘要 + * + * @param resolver 内容解析器 + * @param noteId 便签ID + * @return 便签片段字符串 + * @throws IllegalArgumentException 当便签ID不存在时抛出 + */ public static String getSnippetById(ContentResolver resolver, long noteId) { Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, new String [] { NoteColumns.SNIPPET }, @@ -282,14 +412,24 @@ public class DataUtils { throw new IllegalArgumentException("Note is not found with id: " + noteId); } + /** + * 格式化便签片段 + * + * 处理便签片段,使其适合在UI中显示: + * 1. 去除首尾空格 + * 2. 截取到第一个换行符处(如果存在) + * + * @param snippet 原始便签片段 + * @return 格式化后的便签片段 + */ public static String getFormattedSnippet(String snippet) { if (snippet != null) { - snippet = snippet.trim(); + snippet = snippet.trim(); // 去除首尾空格 int index = snippet.indexOf('\n'); if (index != -1) { - snippet = snippet.substring(0, index); + snippet = snippet.substring(0, index); // 截取到第一个换行符 } } return snippet; } -} +} \ No newline at end of file diff --git a/src/tool/GTaskStringUtils.java b/src/tool/GTaskStringUtils.java index 666b729..680e064 100644 --- a/src/tool/GTaskStringUtils.java +++ b/src/tool/GTaskStringUtils.java @@ -16,98 +16,106 @@ package net.micode.notes.tool; +/** + * Google Tasks API字符串常量工具类 + * + * 定义与Google Tasks API交互时使用的所有JSON键名、操作类型和特殊标识符 + * 此类主要用于: + * 1. 提供与Google Tasks API通信时的标准化键名 + * 2. 定义MIUI便签的特殊文件夹和元数据标识 + * 3. 统一管理API相关字符串,避免硬编码 + */ public class GTaskStringUtils { - public final static String GTASK_JSON_ACTION_ID = "action_id"; - - public final static String GTASK_JSON_ACTION_LIST = "action_list"; - - public final static String GTASK_JSON_ACTION_TYPE = "action_type"; - - public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; - - public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; - - public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; - - public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; - - public final static String GTASK_JSON_CREATOR_ID = "creator_id"; - - public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; - - public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; - - public final static String GTASK_JSON_COMPLETED = "completed"; - - public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; - - public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; - - public final static String GTASK_JSON_DELETED = "deleted"; - - public final static String GTASK_JSON_DEST_LIST = "dest_list"; - - public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; - - public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; - - public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; - - public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; - - public final static String GTASK_JSON_GET_DELETED = "get_deleted"; - - public final static String GTASK_JSON_ID = "id"; - - public final static String GTASK_JSON_INDEX = "index"; - - public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; - - public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; - - public final static String GTASK_JSON_LIST_ID = "list_id"; - - public final static String GTASK_JSON_LISTS = "lists"; - - public final static String GTASK_JSON_NAME = "name"; - - public final static String GTASK_JSON_NEW_ID = "new_id"; - - public final static String GTASK_JSON_NOTES = "notes"; - - public final static String GTASK_JSON_PARENT_ID = "parent_id"; - - public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; - - public final static String GTASK_JSON_RESULTS = "results"; - - public final static String GTASK_JSON_SOURCE_LIST = "source_list"; - - public final static String GTASK_JSON_TASKS = "tasks"; - - public final static String GTASK_JSON_TYPE = "type"; - - public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; - - public final static String GTASK_JSON_TYPE_TASK = "TASK"; - - public final static String GTASK_JSON_USER = "user"; + public final static String GTASK_JSON_ACTION_ID = "action_id"; // 操作ID标识 + public final static String GTASK_JSON_ACTION_LIST = "action_list"; // 操作列表标识 + public final static String GTASK_JSON_ACTION_TYPE = "action_type"; // 操作类型标识 + public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; // 创建操作类型 + public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; // 获取所有操作类型 + public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; // 移动操作类型 + public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; // 更新操作类型 + + public final static String GTASK_JSON_CREATOR_ID = "creator_id"; // 创建者ID标识 + public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; // 子实体标识 + public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; // 客户端版本标识 + public final static String GTASK_JSON_COMPLETED = "completed"; // 完成状态标识 + public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; // 当前列表ID标识 + public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; // 默认列表ID标识 + public final static String GTASK_JSON_DELETED = "deleted"; // 删除状态标识 + public final static String GTASK_JSON_DEST_LIST = "dest_list"; // 目标列表标识 + public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; // 目标父级标识 + public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; // 目标父级类型标识 + public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; // 实体增量标识 + public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; // 实体类型标识 + public final static String GTASK_JSON_GET_DELETED = "get_deleted"; // 获取已删除项标识 + public final static String GTASK_JSON_ID = "id"; // ID标识 + public final static String GTASK_JSON_INDEX = "index"; // 索引标识 + public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; // 最后修改时间标识 + + public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; // 最新同步点标识 + public final static String GTASK_JSON_LIST_ID = "list_id"; // 列表ID标识 + public final static String GTASK_JSON_LISTS = "lists"; // 列表集合标识 + public final static String GTASK_JSON_NAME = "name"; // 名称标识 + public final static String GTASK_JSON_NEW_ID = "new_id"; // 新ID标识 + public final static String GTASK_JSON_NOTES = "notes"; // 备注标识 + public final static String GTASK_JSON_PARENT_ID = "parent_id"; // 父级ID标识 + public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; // 前一个兄弟ID标识 + public final static String GTASK_JSON_RESULTS = "results"; // 结果集合标识 + public final static String GTASK_JSON_SOURCE_LIST = "source_list"; // 源列表标识 + public final static String GTASK_JSON_TASKS = "tasks"; // 任务集合标识 + public final static String GTASK_JSON_TYPE = "type"; // 类型标识 + public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; // 组类型标识 + public final static String GTASK_JSON_TYPE_TASK = "TASK"; // 任务类型标识 + public final static String GTASK_JSON_USER = "user"; // 用户标识 + + + /** + * MIUI便签文件夹前缀 + * 用于标识由MIUI便签创建的文件夹,避免与其他Google Tasks应用冲突 + */ public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; + /** + * 默认文件夹名称 + * 用于标识根目录文件夹在Google Tasks中的名称 + */ public final static String FOLDER_DEFAULT = "Default"; + /** + * 通话记录文件夹名称 + * 用于标识通话记录便签文件夹在Google Tasks中的名称 + */ public final static String FOLDER_CALL_NOTE = "Call_Note"; + /** + * 元数据文件夹名称 + * 用于存储便签的元数据信息,如图片、附件等 + */ public final static String FOLDER_META = "METADATA"; + + /** + * 元数据头:Google Tasks ID + * 用于在元数据中存储便签对应的Google Tasks ID + */ public final static String META_HEAD_GTASK_ID = "meta_gid"; + /** + * 元数据头:便签信息 + * 用于在元数据中存储便签的基本信息 + */ public final static String META_HEAD_NOTE = "meta_note"; + /** + * 元数据头:数据信息 + * 用于在元数据中存储便签的具体数据内容 + */ public final static String META_HEAD_DATA = "meta_data"; + /** + * 元数据便签名称 + * 用于标识元数据便签,提醒用户不要修改或删除此便签 + */ public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; - -} +} \ No newline at end of file diff --git a/src/tool/ResourceParser.java b/src/tool/ResourceParser.java index 1ad3ad6..635b263 100644 --- a/src/tool/ResourceParser.java +++ b/src/tool/ResourceParser.java @@ -22,38 +22,47 @@ import android.preference.PreferenceManager; import net.micode.notes.R; import net.micode.notes.ui.NotesPreferenceActivity; +/** + * ResourceParser - 资源解析工具类 + * 管理和解析便签应用的UI资源,包括背景颜色、字体大小等 + */ public class ResourceParser { + // 背景颜色ID常量 public static final int YELLOW = 0; public static final int BLUE = 1; public static final int WHITE = 2; public static final int GREEN = 3; public static final int RED = 4; - public static final int BG_DEFAULT_COLOR = YELLOW; + // 字体大小ID常量 public static final int TEXT_SMALL = 0; public static final int TEXT_MEDIUM = 1; public static final int TEXT_LARGE = 2; public static final int TEXT_SUPER = 3; - public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; + /** + * NoteBgResources - 便签背景资源管理 + */ public static class NoteBgResources { + // 编辑界面便签背景资源数组 private final static int [] BG_EDIT_RESOURCES = new int [] { - R.drawable.edit_yellow, - R.drawable.edit_blue, - R.drawable.edit_white, - R.drawable.edit_green, - R.drawable.edit_red + R.drawable.edit_yellow, + R.drawable.edit_blue, + R.drawable.edit_white, + R.drawable.edit_green, + R.drawable.edit_red }; + // 编辑界面便签标题背景资源数组 private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { - R.drawable.edit_title_yellow, - R.drawable.edit_title_blue, - R.drawable.edit_title_white, - R.drawable.edit_title_green, - R.drawable.edit_title_red + R.drawable.edit_title_yellow, + R.drawable.edit_title_blue, + R.drawable.edit_title_white, + R.drawable.edit_title_green, + R.drawable.edit_title_red }; public static int getNoteBgResource(int id) { @@ -65,6 +74,10 @@ public class ResourceParser { } } + /** + * 获取默认背景颜色ID + * 根据用户偏好设置:启用随机背景色则随机返回,否则返回默认颜色 + */ public static int getDefaultBgId(Context context) { if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { @@ -74,37 +87,40 @@ public class ResourceParser { } } + /** + * NoteItemBgResources - 便签列表项背景资源管理 + */ public static class NoteItemBgResources { private final static int [] BG_FIRST_RESOURCES = new int [] { - R.drawable.list_yellow_up, - R.drawable.list_blue_up, - R.drawable.list_white_up, - R.drawable.list_green_up, - R.drawable.list_red_up + R.drawable.list_yellow_up, + R.drawable.list_blue_up, + R.drawable.list_white_up, + R.drawable.list_green_up, + R.drawable.list_red_up }; private final static int [] BG_NORMAL_RESOURCES = new int [] { - R.drawable.list_yellow_middle, - R.drawable.list_blue_middle, - R.drawable.list_white_middle, - R.drawable.list_green_middle, - R.drawable.list_red_middle + R.drawable.list_yellow_middle, + R.drawable.list_blue_middle, + R.drawable.list_white_middle, + R.drawable.list_green_middle, + R.drawable.list_red_middle }; private final static int [] BG_LAST_RESOURCES = new int [] { - R.drawable.list_yellow_down, - R.drawable.list_blue_down, - R.drawable.list_white_down, - R.drawable.list_green_down, - R.drawable.list_red_down, + R.drawable.list_yellow_down, + R.drawable.list_blue_down, + R.drawable.list_white_down, + R.drawable.list_green_down, + R.drawable.list_red_down, }; private final static int [] BG_SINGLE_RESOURCES = new int [] { - R.drawable.list_yellow_single, - R.drawable.list_blue_single, - R.drawable.list_white_single, - R.drawable.list_green_single, - R.drawable.list_red_single + R.drawable.list_yellow_single, + R.drawable.list_blue_single, + R.drawable.list_white_single, + R.drawable.list_green_single, + R.drawable.list_red_single }; public static int getNoteBgFirstRes(int id) { @@ -128,13 +144,16 @@ public class ResourceParser { } } + /** + * WidgetBgResources - 小部件背景资源管理 + */ public static class WidgetBgResources { private final static int [] BG_2X_RESOURCES = new int [] { - R.drawable.widget_2x_yellow, - R.drawable.widget_2x_blue, - R.drawable.widget_2x_white, - R.drawable.widget_2x_green, - R.drawable.widget_2x_red, + R.drawable.widget_2x_yellow, + R.drawable.widget_2x_blue, + R.drawable.widget_2x_white, + R.drawable.widget_2x_green, + R.drawable.widget_2x_red, }; public static int getWidget2xBgResource(int id) { @@ -142,11 +161,11 @@ public class ResourceParser { } private final static int [] BG_4X_RESOURCES = new int [] { - R.drawable.widget_4x_yellow, - R.drawable.widget_4x_blue, - R.drawable.widget_4x_white, - R.drawable.widget_4x_green, - R.drawable.widget_4x_red + R.drawable.widget_4x_yellow, + R.drawable.widget_4x_blue, + R.drawable.widget_4x_white, + R.drawable.widget_4x_green, + R.drawable.widget_4x_red }; public static int getWidget4xBgResource(int id) { @@ -154,20 +173,22 @@ public class ResourceParser { } } + /** + * TextAppearanceResources - 文本外观资源管理 + */ public static class TextAppearanceResources { private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { - R.style.TextAppearanceNormal, - R.style.TextAppearanceMedium, - R.style.TextAppearanceLarge, - R.style.TextAppearanceSuper + R.style.TextAppearanceNormal, + R.style.TextAppearanceMedium, + R.style.TextAppearanceLarge, + R.style.TextAppearanceSuper }; + /** + * 根据字体大小ID获取文本外观样式资源 + * 安全机制:ID超出范围时返回默认字体大小 + */ public static int getTexAppearanceResource(int id) { - /** - * HACKME: Fix bug of store the resource id in shared preference. - * The id may larger than the length of resources, in this case, - * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} - */ if (id >= TEXTAPPEARANCE_RESOURCES.length) { return BG_DEFAULT_FONT_SIZE; } @@ -178,4 +199,4 @@ public class ResourceParser { return TEXTAPPEARANCE_RESOURCES.length; } } -} +} \ No newline at end of file diff --git a/src/ui/NotesListItem.java b/src/ui/NotesListItem.java index 1221e80..18a6a54 100644 --- a/src/ui/NotesListItem.java +++ b/src/ui/NotesListItem.java @@ -1,17 +1,15 @@ /* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * 版权所有 (c) 2010-2011,The MiCode 开源社区 (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 + * 根据 Apache 许可证 2.0 版本("许可证")授权; + * 除非遵守许可证,否则不得使用此文件。 + * 您可以在以下网址获取许可证副本: * * 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; @@ -29,18 +27,28 @@ import net.micode.notes.data.Notes; import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser.NoteItemBgResources; - +/** + * 笔记列表项视图类 + * 继承自LinearLayout,表示笔记列表中的单个项 + * 负责显示笔记的标题、时间、提醒图标等信息 + */ public class NotesListItem extends LinearLayout { - private ImageView mAlert; - private TextView mTitle; - private TextView mTime; - private TextView mCallName; - private NoteItemData mItemData; - private CheckBox mCheckBox; + private ImageView mAlert; // 提醒图标 + private TextView mTitle; // 笔记标题 + private TextView mTime; // 笔记修改时间 + private TextView mCallName; // 通话记录名称(用于通话记录类型的笔记) + private NoteItemData mItemData; // 笔记数据对象 + private CheckBox mCheckBox; // 复选框(用于选择模式) + /** + * 构造函数 + * @param context 上下文环境 + */ public NotesListItem(Context context) { super(context); + // 从布局文件note_item.xml中加载视图 inflate(context, R.layout.note_item, this); + // 初始化各个视图组件 mAlert = (ImageView) findViewById(R.id.iv_alert_icon); mTitle = (TextView) findViewById(R.id.tv_title); mTime = (TextView) findViewById(R.id.tv_time); @@ -48,75 +56,112 @@ public class NotesListItem extends LinearLayout { mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); } + /** + * 绑定数据到视图 + * 根据笔记类型和状态设置不同的显示方式 + * @param context 上下文环境 + * @param data 笔记数据对象 + * @param choiceMode 是否处于选择模式 + * @param checked 是否被选中 + */ 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); + mCheckBox.setVisibility(View.GONE); // 其他情况隐藏复选框 } - mItemData = data; + mItemData = data; // 保存数据引用 + + // 根据笔记类型和父文件夹ID设置不同的显示方式 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { - mCallName.setVisibility(View.GONE); - mAlert.setVisibility(View.VISIBLE); + // 通话记录文件夹 + mCallName.setVisibility(View.GONE); // 隐藏通话记录名称 + mAlert.setVisibility(View.VISIBLE); // 显示提醒图标 mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); + // 设置文件夹标题,显示通话记录文件夹名称和笔记数量 mTitle.setText(context.getString(R.string.call_record_folder_name) + context.getString(R.string.format_folder_files_count, data.getNotesCount())); - mAlert.setImageResource(R.drawable.call_record); + mAlert.setImageResource(R.drawable.call_record); // 设置通话记录图标 } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { - mCallName.setVisibility(View.VISIBLE); - mCallName.setText(data.getCallName()); + // 通话记录文件夹中的笔记 + mCallName.setVisibility(View.VISIBLE); // 显示通话记录名称 + mCallName.setText(data.getCallName()); // 设置通话记录名称 mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); - mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + 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); } } else { - mCallName.setVisibility(View.GONE); + // 普通笔记或文件夹 + mCallName.setVisibility(View.GONE); // 隐藏通话记录名称 mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); if (data.getType() == Notes.TYPE_FOLDER) { - mTitle.setText(data.getSnippet() + // 文件夹类型 + mTitle.setText(data.getSnippet() // 设置文件夹名称 + context.getString(R.string.format_folder_files_count, - data.getNotesCount())); - mAlert.setVisibility(View.GONE); + data.getNotesCount())); // 添加笔记数量 + mAlert.setVisibility(View.GONE); // 文件夹不显示提醒图标 } else { - mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + // 普通笔记类型 + 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); } } } + // 设置相对时间(如"5分钟前"、"昨天"等格式) mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + // 设置背景 setBackground(data); } + /** + * 根据笔记数据设置背景 + * 根据笔记的位置(第一个、最后一个、中间等)和颜色ID设置不同的背景 + * @param data 笔记数据对象 + */ private void setBackground(NoteItemData data) { - int id = data.getBgColorId(); + int id = data.getBgColorId(); // 获取背景颜色ID + if (data.getType() == Notes.TYPE_NOTE) { + // 普通笔记类型 if (data.isSingle() || data.isOneFollowingFolder()) { + // 单个笔记或紧接文件夹后的单个笔记:使用单一样式背景 setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); } else if (data.isLast()) { + // 最后一个笔记:使用最后一项样式背景 setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); } else if (data.isFirst() || data.isMultiFollowingFolder()) { + // 第一个笔记或多个笔记在文件夹后:使用第一项样式背景 setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); } else { + // 中间笔记:使用普通样式背景 setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); } } else { + // 文件夹类型:使用文件夹背景 setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } } + /** + * 获取笔记数据对象 + * @return 当前笔记的数据对象 + */ public NoteItemData getItemData() { return mItemData; } -} +} \ No newline at end of file diff --git a/src/ui/NotesPreferenceActivity.java b/src/ui/NotesPreferenceActivity.java index 07c5f7e..aaafbd3 100644 --- a/src/ui/NotesPreferenceActivity.java +++ b/src/ui/NotesPreferenceActivity.java @@ -1,17 +1,15 @@ /* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * 版权所有 (c) 2010-2011,The MiCode 开源社区 (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 + * 根据 Apache 许可证 2.0 版本("许可证")授权; + * 除非遵守许可证,否则不得使用此文件。 + * 您可以在以下网址获取许可证副本: * * 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; @@ -47,342 +45,388 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.gtask.remote.GTaskSyncService; - +/** + * 笔记应用设置页面Activity + * 继承自PreferenceActivity,用于管理应用的偏好设置 + * 主要功能:同步账户设置、同步状态显示、同步操作等 + */ public class NotesPreferenceActivity extends PreferenceActivity { - public static final String PREFERENCE_NAME = "notes_preferences"; - - public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; - - public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; - - public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; - - private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; - - private static final String AUTHORITIES_FILTER_KEY = "authorities"; - - private PreferenceCategory mAccountCategory; - - private GTaskReceiver mReceiver; - - private Account[] mOriAccounts; - - private boolean mHasAddedAccount; + public static final String PREFERENCE_NAME = "notes_preferences"; // 偏好设置文件名 + public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; // 同步账户名称键 + public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; // 上次同步时间键 + public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; // 背景颜色设置键 + private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; // 同步账户类别键 + private static final String AUTHORITIES_FILTER_KEY = "authorities"; // 账户权限过滤器键 + + private PreferenceCategory mAccountCategory; // 账户设置分类 + private GTaskReceiver mReceiver; // 同步服务广播接收器 + private Account[] mOriAccounts; // 原始账户列表 + private boolean mHasAddedAccount; // 是否添加了新账户的标志 @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - /* using the app icon for navigation */ - getActionBar().setDisplayHomeAsUpEnabled(true); + /* 使用应用图标进行导航 */ + getActionBar().setDisplayHomeAsUpEnabled(true); // 显示返回主页按钮 - addPreferencesFromResource(R.xml.preferences); - mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); - mReceiver = new GTaskReceiver(); - IntentFilter filter = new IntentFilter(); - filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); - registerReceiver(mReceiver, filter); + addPreferencesFromResource(R.xml.preferences); // 从XML资源加载偏好设置 + mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); // 查找账户设置分类 + mReceiver = new GTaskReceiver(); // 创建广播接收器 + IntentFilter filter = new IntentFilter(); // 创建Intent过滤器 + filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); // 添加同步服务广播动作 + registerReceiver(mReceiver, filter); // 注册广播接收器 - mOriAccounts = null; - View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); - getListView().addHeaderView(header, null, true); + mOriAccounts = null; // 初始化原始账户列表 + View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); // 加载设置页面头部布局 + getListView().addHeaderView(header, null, true); // 将头部添加到列表视图 } @Override protected void onResume() { super.onResume(); - // need to set sync account automatically if user has added a new - // account + // 如果用户添加了新账户,需要自动设置同步账户 if (mHasAddedAccount) { - Account[] accounts = getGoogleAccounts(); + Account[] accounts = getGoogleAccounts(); // 获取当前所有Google账户 if (mOriAccounts != null && accounts.length > mOriAccounts.length) { + // 检查是否有新账户被添加 for (Account accountNew : accounts) { boolean found = false; for (Account accountOld : mOriAccounts) { if (TextUtils.equals(accountOld.name, accountNew.name)) { - found = true; + found = true; // 账户已存在 break; } } if (!found) { - setSyncAccount(accountNew.name); + setSyncAccount(accountNew.name); // 设置新账户为同步账户 break; } } } } - refreshUI(); + refreshUI(); // 刷新UI } @Override protected void onDestroy() { if (mReceiver != null) { - unregisterReceiver(mReceiver); + unregisterReceiver(mReceiver); // 取消注册广播接收器 } super.onDestroy(); } + /** + * 加载账户设置项 + */ private void loadAccountPreference() { - mAccountCategory.removeAll(); + mAccountCategory.removeAll(); // 清除所有现有设置项 - Preference accountPref = new Preference(this); - final String defaultAccount = getSyncAccountName(this); - accountPref.setTitle(getString(R.string.preferences_account_title)); - accountPref.setSummary(getString(R.string.preferences_account_summary)); + Preference accountPref = new Preference(this); // 创建账户设置项 + final String defaultAccount = getSyncAccountName(this); // 获取当前同步账户名称 + accountPref.setTitle(getString(R.string.preferences_account_title)); // 设置标题 + accountPref.setSummary(getString(R.string.preferences_account_summary)); // 设置摘要 accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { - if (!GTaskSyncService.isSyncing()) { + if (!GTaskSyncService.isSyncing()) { // 检查是否正在同步 if (TextUtils.isEmpty(defaultAccount)) { - // the first time to set account - showSelectAccountAlertDialog(); + // 首次设置账户 + showSelectAccountAlertDialog(); // 显示选择账户对话框 } else { - // if the account has already been set, we need to promp - // user about the risk - showChangeAccountConfirmAlertDialog(); + // 如果账户已经设置,需要提示用户风险 + showChangeAccountConfirmAlertDialog(); // 显示更改账户确认对话框 } } else { Toast.makeText(NotesPreferenceActivity.this, - R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) - .show(); + R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) + .show(); // 显示无法更改账户的提示 } return true; } }); - mAccountCategory.addPreference(accountPref); + mAccountCategory.addPreference(accountPref); // 将设置项添加到分类 } + /** + * 加载同步按钮 + */ private void loadSyncButton() { - Button syncButton = (Button) findViewById(R.id.preference_sync_button); - TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); + Button syncButton = (Button) findViewById(R.id.preference_sync_button); // 同步按钮 + TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); // 上次同步时间显示 - // set button state + // 设置按钮状态 if (GTaskSyncService.isSyncing()) { - syncButton.setText(getString(R.string.preferences_button_sync_cancel)); + syncButton.setText(getString(R.string.preferences_button_sync_cancel)); // 同步中显示取消 syncButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - GTaskSyncService.cancelSync(NotesPreferenceActivity.this); + GTaskSyncService.cancelSync(NotesPreferenceActivity.this); // 取消同步 } }); } else { - syncButton.setText(getString(R.string.preferences_button_sync_immediately)); + syncButton.setText(getString(R.string.preferences_button_sync_immediately)); // 未同步显示立即同步 syncButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { - GTaskSyncService.startSync(NotesPreferenceActivity.this); + GTaskSyncService.startSync(NotesPreferenceActivity.this); // 开始同步 } }); } - syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); + syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); // 有同步账户时启用按钮 - // set last sync time + // 设置上次同步时间 if (GTaskSyncService.isSyncing()) { - lastSyncTimeView.setText(GTaskSyncService.getProgressString()); + lastSyncTimeView.setText(GTaskSyncService.getProgressString()); // 显示同步进度 lastSyncTimeView.setVisibility(View.VISIBLE); } else { - long lastSyncTime = getLastSyncTime(this); + long lastSyncTime = getLastSyncTime(this); // 获取上次同步时间 if (lastSyncTime != 0) { lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time, DateFormat.format(getString(R.string.preferences_last_sync_time_format), - lastSyncTime))); + lastSyncTime))); // 格式化显示上次同步时间 lastSyncTimeView.setVisibility(View.VISIBLE); } else { - lastSyncTimeView.setVisibility(View.GONE); + lastSyncTimeView.setVisibility(View.GONE); // 从未同步过则隐藏 } } } + /** + * 刷新UI + */ private void refreshUI() { - loadAccountPreference(); - loadSyncButton(); + loadAccountPreference(); // 重新加载账户设置项 + loadSyncButton(); // 重新加载同步按钮 } + /** + * 显示选择账户对话框 + */ private void showSelectAccountAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); - TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); - titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); - TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); - subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); + View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); // 加载对话框标题布局 + TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); // 标题文本 + titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); // 设置标题 + TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); // 副标题文本 + subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); // 设置副标题 - dialogBuilder.setCustomTitle(titleView); - dialogBuilder.setPositiveButton(null, null); + dialogBuilder.setCustomTitle(titleView); // 设置自定义标题 + dialogBuilder.setPositiveButton(null, null); // 不设置确定按钮 - Account[] accounts = getGoogleAccounts(); - String defAccount = getSyncAccountName(this); + Account[] accounts = getGoogleAccounts(); // 获取所有Google账户 + String defAccount = getSyncAccountName(this); // 获取当前同步账户 - mOriAccounts = accounts; - mHasAddedAccount = false; + mOriAccounts = accounts; // 保存原始账户列表 + mHasAddedAccount = false; // 重置添加账户标志 if (accounts.length > 0) { - CharSequence[] items = new CharSequence[accounts.length]; - final CharSequence[] itemMapping = items; - int checkedItem = -1; + CharSequence[] items = new CharSequence[accounts.length]; // 创建选项数组 + final CharSequence[] itemMapping = items; // 映射选项 + int checkedItem = -1; // 默认未选中 int index = 0; for (Account account : accounts) { if (TextUtils.equals(account.name, defAccount)) { - checkedItem = index; + checkedItem = index; // 设置当前同步账户为选中状态 } - items[index++] = account.name; + items[index++] = account.name; // 添加账户名称到选项 } dialogBuilder.setSingleChoiceItems(items, checkedItem, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - setSyncAccount(itemMapping[which].toString()); - dialog.dismiss(); - refreshUI(); + setSyncAccount(itemMapping[which].toString()); // 设置选中的账户为同步账户 + dialog.dismiss(); // 关闭对话框 + refreshUI(); // 刷新UI } }); } - View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); - dialogBuilder.setView(addAccountView); + View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); // 加载添加账户视图 + 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; - Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); + mHasAddedAccount = true; // 标记已添加账户 + Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); // 跳转到添加账户设置 intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { - "gmail-ls" + "gmail-ls" // 设置账户权限过滤器 }); - startActivityForResult(intent, -1); - dialog.dismiss(); + startActivityForResult(intent, -1); // 启动添加账户Activity + dialog.dismiss(); // 关闭对话框 } }); } + /** + * 显示更改账户确认对话框 + */ private void showChangeAccountConfirmAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); - TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); + View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); // 加载对话框标题布局 + TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); // 标题文本 titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, - getSyncAccountName(this))); - TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); - subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); - dialogBuilder.setCustomTitle(titleView); + getSyncAccountName(this))); // 设置标题,包含当前账户名 + TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); // 副标题文本 + subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); // 设置警告消息 + + dialogBuilder.setCustomTitle(titleView); // 设置自定义标题 CharSequence[] menuItemArray = new CharSequence[] { - getString(R.string.preferences_menu_change_account), - getString(R.string.preferences_menu_remove_account), - getString(R.string.preferences_menu_cancel) + getString(R.string.preferences_menu_change_account), // 更改账户 + getString(R.string.preferences_menu_remove_account), // 移除账户 + getString(R.string.preferences_menu_cancel) // 取消 }; dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { if (which == 0) { - showSelectAccountAlertDialog(); + showSelectAccountAlertDialog(); // 显示选择账户对话框 } else if (which == 1) { - removeSyncAccount(); - refreshUI(); + removeSyncAccount(); // 移除同步账户 + refreshUI(); // 刷新UI } + // 选项2为取消,不执行任何操作 } }); - dialogBuilder.show(); + dialogBuilder.show(); // 显示对话框 } + /** + * 获取所有Google账户 + * @return Google账户数组 + */ private Account[] getGoogleAccounts() { - AccountManager accountManager = AccountManager.get(this); - return accountManager.getAccountsByType("com.google"); + AccountManager accountManager = AccountManager.get(this); // 获取账户管理器 + return accountManager.getAccountsByType("com.google"); // 返回Google类型账户 } + /** + * 设置同步账户 + * @param account 账户名称 + */ private void setSyncAccount(String account) { - if (!getSyncAccountName(this).equals(account)) { - SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); + if (!getSyncAccountName(this).equals(account)) { // 检查账户是否已更改 + SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); // 获取偏好设置 + SharedPreferences.Editor editor = settings.edit(); // 获取编辑器 if (account != null) { - editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); + editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); // 保存账户名称 } else { - editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); + editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); // 清空账户名称 } - editor.commit(); + editor.commit(); // 提交更改 - // clean up last sync time + // 清除上次同步时间 setLastSyncTime(this, 0); - // clean up local gtask related info + // 清除本地GTask相关信息 new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); - values.put(NoteColumns.GTASK_ID, ""); - values.put(NoteColumns.SYNC_ID, 0); - getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); + values.put(NoteColumns.GTASK_ID, ""); // 清空GTask ID + values.put(NoteColumns.SYNC_ID, 0); // 清空同步ID + getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); // 更新数据库 } }).start(); Toast.makeText(NotesPreferenceActivity.this, getString(R.string.preferences_toast_success_set_accout, account), - Toast.LENGTH_SHORT).show(); + Toast.LENGTH_SHORT).show(); // 显示设置成功提示 } } + /** + * 移除同步账户 + */ private void removeSyncAccount() { - SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); + SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); // 获取偏好设置 + SharedPreferences.Editor editor = settings.edit(); // 获取编辑器 if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { - editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); + editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); // 移除同步账户名称 } if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) { - editor.remove(PREFERENCE_LAST_SYNC_TIME); + editor.remove(PREFERENCE_LAST_SYNC_TIME); // 移除上次同步时间 } - editor.commit(); + editor.commit(); // 提交更改 - // clean up local gtask related info + // 清除本地GTask相关信息 new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); - values.put(NoteColumns.GTASK_ID, ""); - values.put(NoteColumns.SYNC_ID, 0); - getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); + values.put(NoteColumns.GTASK_ID, ""); // 清空GTask ID + values.put(NoteColumns.SYNC_ID, 0); // 清空同步ID + getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); // 更新数据库 } }).start(); } + /** + * 获取同步账户名称 + * @param context 上下文 + * @return 同步账户名称,如果没有则为空字符串 + */ public static String getSyncAccountName(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, - Context.MODE_PRIVATE); - return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); + Context.MODE_PRIVATE); // 获取偏好设置 + return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); // 返回账户名称 } + /** + * 设置上次同步时间 + * @param context 上下文 + * @param time 同步时间戳 + */ public static void setLastSyncTime(Context context, long time) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, - Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); - editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); - editor.commit(); + Context.MODE_PRIVATE); // 获取偏好设置 + SharedPreferences.Editor editor = settings.edit(); // 获取编辑器 + editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); // 保存同步时间 + editor.commit(); // 提交更改 } + /** + * 获取上次同步时间 + * @param context 上下文 + * @return 上次同步时间,如果没有则为0 + */ public static long getLastSyncTime(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, - Context.MODE_PRIVATE); - return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); + Context.MODE_PRIVATE); // 获取偏好设置 + return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); // 返回同步时间 } + /** + * GTask同步服务广播接收器 + * 用于接收同步状态更新并刷新UI + */ private class GTaskReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - refreshUI(); + refreshUI(); // 刷新UI if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { - TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); + TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); // 同步状态文本 syncStatus.setText(intent - .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); + .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); // 更新进度消息 } - } } + /** + * 处理选项菜单项选择 + * @param item 被选中的菜单项 + * @return 是否处理了该菜单项 + */ public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case android.R.id.home: - Intent intent = new Intent(this, NotesListActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + case android.R.id.home: // 返回主页按钮 + Intent intent = new Intent(this, NotesListActivity.class); // 跳转到笔记列表Activity + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // 清除Activity栈 startActivity(intent); return true; default: return false; } } -} +} \ No newline at end of file diff --git a/src/widget/NoteWidgetProvider.java b/src/widget/NoteWidgetProvider.java index ec6f819..e6b5a9b 100644 --- a/src/widget/NoteWidgetProvider.java +++ b/src/widget/NoteWidgetProvider.java @@ -1,20 +1,19 @@ /* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * 版权所有 (c) 2010-2011,The MiCode 开源社区 (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 + * 根据 Apache 许可证 2.0 版本("许可证")授权; + * 除非遵守许可证,否则不得使用此文件。 + * 您可以在以下网址获取许可证副本: * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 除非适用法律要求或书面同意,否则按"原样"分发软件, + * 没有任何明示或暗示的担保或条件。 + * 有关许可证下权限和限制的具体语言,请参阅许可证。 */ package net.micode.notes.widget; + import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; @@ -32,101 +31,166 @@ import net.micode.notes.tool.ResourceParser; import net.micode.notes.ui.NoteEditActivity; import net.micode.notes.ui.NotesListActivity; +/** + * 笔记小部件提供者抽象基类 + * 继承自AppWidgetProvider,用于实现笔记应用的小部件功能 + * 处理小部件的创建、更新、删除等生命周期事件 + */ public abstract class NoteWidgetProvider extends AppWidgetProvider { + // 查询笔记信息时使用的投影(需要获取的字段) public static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.BG_COLOR_ID, - NoteColumns.SNIPPET + NoteColumns.ID, // 笔记ID + NoteColumns.BG_COLOR_ID, // 背景颜色ID + NoteColumns.SNIPPET // 笔记内容片段 }; - public static final int COLUMN_ID = 0; - public static final int COLUMN_BG_COLOR_ID = 1; - public static final int COLUMN_SNIPPET = 2; + // 投影字段的索引常量 + public static final int COLUMN_ID = 0; // ID字段索引 + public static final int COLUMN_BG_COLOR_ID = 1; // 背景颜色ID字段索引 + public static final int COLUMN_SNIPPET = 2; // 内容片段字段索引 - private static final String TAG = "NoteWidgetProvider"; + private static final String TAG = "NoteWidgetProvider"; // 日志标签 + /** + * 当小部件被删除时调用 + * 清理数据库中对应的小部件ID + * @param context 上下文 + * @param appWidgetIds 被删除的小部件ID数组 + */ @Override public void onDeleted(Context context, int[] appWidgetIds) { ContentValues values = new ContentValues(); + // 将小部件ID设置为无效值 values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + // 遍历所有被删除的小部件ID for (int i = 0; i < appWidgetIds.length; i++) { + // 更新数据库,清除对应的小部件ID context.getContentResolver().update(Notes.CONTENT_NOTE_URI, values, - NoteColumns.WIDGET_ID + "=?", - new String[] { String.valueOf(appWidgetIds[i])}); + NoteColumns.WIDGET_ID + "=?", // 条件:小部件ID等于被删除的小部件ID + new String[] { String.valueOf(appWidgetIds[i])}); // 参数值 } } + /** + * 获取指定小部件ID对应的笔记信息 + * @param context 上下文 + * @param widgetId 小部件ID + * @return 包含笔记信息的Cursor对象 + */ private Cursor getNoteWidgetInfo(Context context, int widgetId) { + // 查询数据库:获取指定小部件ID且不在回收站中的笔记信息 return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, - NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", - new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, - null); + NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", // 条件 + new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, // 参数值 + null); // 排序方式 } + /** + * 更新小部件(公共方法) + * @param context 上下文 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 需要更新的小部件ID数组 + */ protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - update(context, appWidgetManager, appWidgetIds, false); + update(context, appWidgetManager, appWidgetIds, false); // 默认非隐私模式 } + /** + * 更新小部件的私有方法 + * @param context 上下文 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 需要更新的小部件ID数组 + * @param privacyMode 是否为隐私模式(隐私模式下显示占位文本) + */ private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, - boolean privacyMode) { + boolean privacyMode) { + // 遍历所有需要更新的小部件 for (int i = 0; i < appWidgetIds.length; i++) { + // 检查小部件ID是否有效 if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { - int bgId = ResourceParser.getDefaultBgId(context); - String snippet = ""; - Intent intent = new Intent(context, NoteEditActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + int bgId = ResourceParser.getDefaultBgId(context); // 默认背景颜色ID + String snippet = ""; // 笔记内容片段 + Intent intent = new Intent(context, NoteEditActivity.class); // 创建编辑笔记的Intent + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); // 设置为单例模式 + // 传递小部件ID和类型信息 intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); + // 查询小部件对应的笔记信息 Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); if (c != null && c.moveToFirst()) { + // 检查是否有多条笔记对应同一个widgetId(不应该发生) if (c.getCount() > 1) { Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); c.close(); return; } + // 获取笔记内容片段和背景颜色ID snippet = c.getString(COLUMN_SNIPPET); bgId = c.getInt(COLUMN_BG_COLOR_ID); + // 传递笔记ID,用于查看已有笔记 intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); - intent.setAction(Intent.ACTION_VIEW); + intent.setAction(Intent.ACTION_VIEW); // 设置动作为查看 } else { + // 如果没有找到对应的笔记,显示默认文本并设置为新建/编辑动作 snippet = context.getResources().getString(R.string.widget_havenot_content); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 设置动作为插入或编辑 } if (c != null) { - c.close(); + c.close(); // 关闭Cursor } + // 创建RemoteViews对象 RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); + // 设置背景图片 rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); + // 传递背景颜色ID intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); + /** - * Generate the pending intent to start host for the widget + * 生成启动宿主Activity的PendingIntent */ PendingIntent pendingIntent = null; if (privacyMode) { + // 隐私模式:显示占位文本,点击跳转到笔记列表 rv.setTextViewText(R.id.widget_text, context.getString(R.string.widget_under_visit_mode)); pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); } else { + // 正常模式:显示笔记内容,点击跳转到笔记编辑页面 rv.setTextViewText(R.id.widget_text, snippet); pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, PendingIntent.FLAG_UPDATE_CURRENT); } + // 设置点击事件 rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); + // 更新小部件 appWidgetManager.updateAppWidget(appWidgetIds[i], rv); } } } + /** + * 抽象方法:根据背景颜色ID获取对应的背景资源ID + * @param bgId 背景颜色ID + * @return 背景资源ID + */ protected abstract int getBgResourceId(int bgId); + /** + * 抽象方法:获取小部件的布局ID + * @return 布局资源ID + */ protected abstract int getLayoutId(); + /** + * 抽象方法:获取小部件类型 + * @return 小部件类型标识 + */ protected abstract int getWidgetType(); -} +} \ No newline at end of file diff --git a/src/widget/NoteWidgetProvider_2x.java b/src/widget/NoteWidgetProvider_2x.java index adcb2f7..7c278a5 100644 --- a/src/widget/NoteWidgetProvider_2x.java +++ b/src/widget/NoteWidgetProvider_2x.java @@ -1,17 +1,15 @@ /* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * 版权所有 (c) 2010-2011,The MiCode 开源社区 (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 + * 根据 Apache 许可证 2.0 版本("许可证")授权; + * 除非遵守许可证,否则不得使用此文件。 + * 您可以在以下网址获取许可证副本: * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 除非适用法律要求或书面同意,否则按"原样"分发软件, + * 没有任何明示或暗示的担保或条件。 + * 有关许可证下权限和限制的具体语言,请参阅许可证。 */ package net.micode.notes.widget; @@ -23,25 +21,51 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; - +/** + * 2x大小笔记小部件提供者 + * 继承自NoteWidgetProvider,专门处理2x大小的小部件 + * 实现抽象父类的具体方法,提供2x小部件的特定配置 + */ public class NoteWidgetProvider_2x extends NoteWidgetProvider { + + /** + * 当小部件需要更新时调用 + * 调用父类的update方法更新小部件 + * @param context 上下文 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 需要更新的小部件ID数组 + */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - super.update(context, appWidgetManager, appWidgetIds); + super.update(context, appWidgetManager, appWidgetIds); // 调用父类更新方法 } + /** + * 获取2x小部件的布局ID + * @return 2x小部件的布局资源ID + */ @Override protected int getLayoutId() { - return R.layout.widget_2x; + return R.layout.widget_2x; // 返回2x小部件的布局文件 } + /** + * 根据背景颜色ID获取对应的背景资源ID + * 专门为2x小部件提供背景资源 + * @param bgId 背景颜色ID + * @return 2x小部件的背景资源ID + */ @Override protected int getBgResourceId(int bgId) { - return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); + return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); // 获取2x小部件的背景资源 } + /** + * 获取小部件类型标识 + * @return 2x小部件类型常量 + */ @Override protected int getWidgetType() { - return Notes.TYPE_WIDGET_2X; + return Notes.TYPE_WIDGET_2X; // 返回2x小部件的类型标识 } -} +} \ No newline at end of file diff --git a/src/widget/NoteWidgetProvider_4x.java b/src/widget/NoteWidgetProvider_4x.java index c12a02e..3e79e74 100644 --- a/src/widget/NoteWidgetProvider_4x.java +++ b/src/widget/NoteWidgetProvider_4x.java @@ -1,17 +1,15 @@ /* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * 版权所有 (c) 2010-2011,The MiCode 开源社区 (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 + * 根据 Apache 许可证 2.0 版本("许可证")授权; + * 除非遵守许可证,否则不得使用此文件。 + * 您可以在以下网址获取许可证副本: * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * 除非适用法律要求或书面同意,否则按"原样"分发软件, + * 没有任何明示或暗示的担保或条件。 + * 有关许可证下权限和限制的具体语言,请参阅许可证。 */ package net.micode.notes.widget; @@ -23,24 +21,52 @@ import net.micode.notes.R; import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; - +/** + * 4x大小笔记小部件提供者 + * 继承自NoteWidgetProvider,专门处理4x大小的小部件 + * 实现抽象父类的具体方法,提供4x小部件的特定配置 + * 与NoteWidgetProvider_2x类类似,但针对4x尺寸的布局和资源 + */ public class NoteWidgetProvider_4x extends NoteWidgetProvider { + + /** + * 当小部件需要更新时调用 + * 调用父类的update方法更新小部件 + * @param context 上下文环境 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 需要更新的小部件ID数组 + */ @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - super.update(context, appWidgetManager, appWidgetIds); + super.update(context, appWidgetManager, appWidgetIds); // 调用父类更新方法 } + /** + * 获取4x小部件的布局ID + * 返回4x小部件特定的布局文件资源ID + * @return 4x小部件的布局资源ID + */ protected int getLayoutId() { - return R.layout.widget_4x; + return R.layout.widget_4x; // 返回4x小部件的布局文件 } + /** + * 根据背景颜色ID获取对应的背景资源ID + * 专门为4x小部件提供背景资源 + * @param bgId 背景颜色ID + * @return 4x小部件的背景资源ID + */ @Override protected int getBgResourceId(int bgId) { - return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); + return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); // 获取4x小部件的背景资源 } + /** + * 获取小部件类型标识 + * @return 4x小部件类型常量 + */ @Override protected int getWidgetType() { - return Notes.TYPE_WIDGET_4X; + return Notes.TYPE_WIDGET_4X; // 返回4x小部件的类型标识 } -} +} \ No newline at end of file