From c5530e782b650a499a61a97f3dea626b7b14f3cf Mon Sep 17 00:00:00 2001 From: hmh <2150887269@qq.com> Date: Thu, 12 Jun 2025 14:31:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=BA.java=E6=96=87=E4=BB=B6=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/net/micode/notes/data/Contact.java | 40 +- .../java/net/micode/notes/data/Notes.java | 561 +++++++++++------ .../notes/data/NotesDatabaseHelper.java | 121 +++- .../net/micode/notes/data/NotesProvider.java | 582 ++++++++++++------ 4 files changed, 890 insertions(+), 414 deletions(-) diff --git a/src/Notes/Notes/app/src/main/java/net/micode/notes/data/Contact.java b/src/Notes/Notes/app/src/main/java/net/micode/notes/data/Contact.java index d97ac5d..e4f6d32 100644 --- a/src/Notes/Notes/app/src/main/java/net/micode/notes/data/Contact.java +++ b/src/Notes/Notes/app/src/main/java/net/micode/notes/data/Contact.java @@ -26,9 +26,16 @@ import android.util.Log; import java.util.HashMap; public class Contact { + // 静态缓存,用于存储已查询过的联系人信息,避免重复查询数据库 private static HashMap sContactCache; + // 日志标签,用于调试和记录日志信息 private static final String TAG = "Contact"; + // 查询联系人的SQL语句模板 + // 该语句用于根据电话号码查询联系人名称 + // PHONE_NUMBERS_EQUAL 是一个辅助函数,用于比较电话号码是否匹配 + // Data.MIMETYPE 用于指定查询的数据类型为电话号码 + // Data.RAW_CONTACT_ID 用于关联 phone_lookup 表,确保查询结果的准确性 private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + " AND " + Data.RAW_CONTACT_ID + " IN " @@ -36,38 +43,59 @@ public class Contact { + " FROM phone_lookup" + " WHERE min_match = '+')"; + /** + * 根据电话号码获取联系人名称 + * + * @param context 应用程序上下文 + * @param phoneNumber 要查询的电话号码 + * @return 联系人名称,如果未找到则返回 null + */ public static String getContact(Context context, String phoneNumber) { + // 如果缓存为空,则初始化缓存 if(sContactCache == null) { sContactCache = new HashMap(); } + // 检查缓存中是否已经存在该电话号码的联系人信息 if(sContactCache.containsKey(phoneNumber)) { + // 如果缓存中有,直接返回缓存中的联系人名称 return sContactCache.get(phoneNumber); } + // 替换 SQL 查询语句中的占位符为实际的电话号码 + // PhoneNumberUtils.toCallerIDMinMatch 用于将电话号码转换为适合查询的格式 String selection = CALLER_ID_SELECTION.replace("+", PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + + // 查询联系人数据库 Cursor cursor = context.getContentResolver().query( - Data.CONTENT_URI, - new String [] { Phone.DISPLAY_NAME }, - selection, - new String[] { phoneNumber }, - null); + Data.CONTENT_URI, // 查询的 URI + new String [] { Phone.DISPLAY_NAME }, // 查询返回的列,这里只需要联系人名称 + selection, // 查询条件 + new String[] { phoneNumber }, // 查询参数 + null); // 不需要排序 + // 检查查询结果 if (cursor != null && cursor.moveToFirst()) { try { + // 获取查询结果中的联系人名称 String name = cursor.getString(0); + // 将查询结果存入缓存 sContactCache.put(phoneNumber, name); + // 返回联系人名称 return name; } catch (IndexOutOfBoundsException e) { + // 如果发生异常,记录错误日志 Log.e(TAG, " Cursor get string error " + e.toString()); return null; } finally { + // 关闭游标,释放资源 cursor.close(); } } else { + // 如果没有找到匹配的联系人,记录调试日志 Log.d(TAG, "No contact matched with number:" + phoneNumber); return null; } } -} +} \ No newline at end of file diff --git a/src/Notes/Notes/app/src/main/java/net/micode/notes/data/Notes.java b/src/Notes/Notes/app/src/main/java/net/micode/notes/data/Notes.java index f240604..66ed5a0 100644 --- a/src/Notes/Notes/app/src/main/java/net/micode/notes/data/Notes.java +++ b/src/Notes/Notes/app/src/main/java/net/micode/notes/data/Notes.java @@ -16,264 +16,471 @@ package net.micode.notes.data; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.database.Cursor; import android.net.Uri; +import android.provider.BaseColumns; +import android.text.TextUtils; + +/** + * 小米便签数据结构定义 + * 定义便签相关的URI、字段和常量 + * 支持便签、文件夹、提醒等多种数据类型 + */ public class Notes { - public static final String AUTHORITY = "micode_notes"; - public static final String TAG = "Notes"; - public static final int TYPE_NOTE = 0; - public static final int TYPE_FOLDER = 1; - public static final int TYPE_SYSTEM = 2; + // 内容提供者的URI前缀 + public static final String AUTHORITY = "net.micode.notes"; + // 内容提供者的完整URI + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); /** - * Following IDs are system folders' identifiers - * {@link Notes#ID_ROOT_FOLDER } is default folder - * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder - * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records + * 便签主表相关常量和字段 */ - public static final int ID_ROOT_FOLDER = 0; - public static final int ID_TEMPARAY_FOLDER = -1; - public static final int ID_CALL_RECORD_FOLDER = -2; - public static final int ID_TRASH_FOLER = -3; - - public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; - public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; - public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; - public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; - public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; - public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; - - public static final int TYPE_WIDGET_INVALIDE = -1; - public static final int TYPE_WIDGET_2X = 0; - public static final int TYPE_WIDGET_4X = 1; - - public static class DataConstants { - public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; - public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; + public interface NoteColumns extends BaseColumns { + // 便签ID + public static final String ID = BaseColumns._ID; + // 父文件夹ID + public static final String PARENT_ID = "parent_id"; + // 提醒日期时间戳 + public static final String ALERTED_DATE = "alerted_date"; + // 便签背景颜色ID + public static final String BG_COLOR_ID = "bg_color_id"; + // 创建日期 + public static final String CREATED_DATE = "created_date"; + // 是否有附件 + public static final String HAS_ATTACHMENT = "has_attachment"; + // 修改日期 + public static final String MODIFIED_DATE = "modified_date"; + // 便签数量(文件夹才有) + public static final String NOTES_COUNT = "notes_count"; + // 便签摘要 + public static final String SNIPPET = "snippet"; + // 便签类型 + public static final String TYPE = "type"; + // 桌面小部件ID + public static final String WIDGET_ID = "widget_id"; + // 桌面小部件类型 + public static final String WIDGET_TYPE = "widget_type"; + // 同步ID + public static final String SYNC_ID = "sync_id"; + // 本地修改标记 + public static final String LOCAL_MODIFIED = "local_modified"; + // 原始父文件夹ID + public static final String ORIGIN_PARENT_ID = "origin_parent_id"; + // Google任务ID + public static final String GTASK_ID = "gtask_id"; + // 版本号 + public static final String VERSION = "version"; } /** - * Uri to query all notes and folders + * 便签数据关联表相关常量和字段 */ - public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); + public interface DataColumns extends BaseColumns { + // 数据ID + public static final String ID = BaseColumns._ID; + // MIME类型,标识数据类型 + public static final String MIME_TYPE = "mime_type"; + // 关联的便签ID + public static final String NOTE_ID = "note_id"; + // 创建日期 + public static final String CREATED_DATE = "created_date"; + // 修改日期 + public static final String MODIFIED_DATE = "modified_date"; + // 内容文本 + public static final String CONTENT = "content"; + // 附加数据1,可存储电话号码等 + public static final String DATA1 = "data1"; + // 附加数据2 + public static final String DATA2 = "data2"; + // 附加数据3,可存储链接地址等 + public static final String DATA3 = "data3"; + // 附加数据4 + public static final String DATA4 = "data4"; + // 附加数据5 + public static final String DATA5 = "data5"; + } /** - * Uri to query data + * 便签类型常量 */ - public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); - - public interface NoteColumns { - /** - * The unique ID for a row - *

Type: INTEGER (long)

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

Type: INTEGER (long)

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

Type: INTEGER (long)

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

Type: INTEGER (long)

- */ - public static final String MODIFIED_DATE = "modified_date"; + public interface TypeColumns { + // 普通便签类型 + public static final int TYPE_NOTE = 0; + // 文件夹类型 + public static final int TYPE_FOLDER = 1; + // 系统文件夹类型 + public static final int TYPE_SYSTEM = 2; + } + /** + * 数据MIME类型常量 + */ + public interface DataConstants { + // 文本便签MIME类型 + public static final String NOTE = "vnd.android.cursor.item/vnd.micode.note"; + // 通话记录MIME类型 + public static final String CALL_RECORD = "vnd.android.cursor.item/vnd.micode.note.call"; + // 提醒MIME类型 + public static final String ALERT = "vnd.android.cursor.item/vnd.micode.note.alert"; + // 链接MIME类型 + public static final String URL = "vnd.android.cursor.item/vnd.micode.note.url"; + // 图片MIME类型 + public static final String IMAGE = "vnd.android.cursor.item/vnd.micode.note.image"; + // 音频MIME类型 + public static final String AUDIO = "vnd.android.cursor.item/vnd.micode.note.audio"; + } - /** - * Alert date - *

Type: INTEGER (long)

- */ - public static final String ALERTED_DATE = "alert_date"; + /** + * 系统文件夹ID常量 + */ + public interface FolderConstants { + // 通话记录文件夹ID + public static final long ID_CALL_RECORD_FOLDER = 1; + // 根文件夹ID + public static final long ID_ROOT_FOLDER = 2; + // 临时文件夹ID + public static final long ID_TEMPARAY_FOLDER = 3; + // 回收站文件夹ID + public static final long ID_TRASH_FOLER = 4; + } - /** - * Folder's name or text content of note - *

Type: TEXT

- */ - public static final String SNIPPET = "snippet"; + /** + * 桌面小部件类型常量 + */ + public interface WidgetConstants { + // 无小部件 + public static final int WIDGET_TYPE_NONE = -1; + // 4x2小部件 + public static final int WIDGET_TYPE_INFORMATION = 0; + // 4x4小部件 + public static final int WIDGET_TYPE_BIG = 1; + // 1x1小部件 + public static final int WIDGET_TYPE_MINI = 2; + } - /** - * Note's widget id - *

Type: INTEGER (long)

- */ - public static final String WIDGET_ID = "widget_id"; + /** + * 便签内容URI + */ + public static final class NoteContent implements NoteColumns { + public static final String CONTENT_DIRECTORY = "notes"; + public static final Uri CONTENT_URI = Uri.withAppendedPath(Notes.CONTENT_URI, CONTENT_DIRECTORY); + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.micode.note"; + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.micode.note"; /** - * Note's widget type - *

Type: INTEGER (long)

+ * 构建便签URI */ - public static final String WIDGET_TYPE = "widget_type"; + public static Uri buildUri(long id) { + return ContentUris.withAppendedId(CONTENT_URI, id); + } /** - * Note's background color's id - *

Type: INTEGER (long)

+ * 从URI获取便签ID */ - public static final String BG_COLOR_ID = "bg_color_id"; + public static long getId(Uri uri) { + return ContentUris.parseId(uri); + } + } - /** - * For text note, it doesn't has attachment, for multi-media - * note, it has at least one attachment - *

Type: INTEGER

- */ - public static final String HAS_ATTACHMENT = "has_attachment"; + /** + * 便签数据内容URI + */ + public static final class DataContent implements DataColumns { + public static final String CONTENT_DIRECTORY = "data"; + public static final Uri CONTENT_URI = Uri.withAppendedPath(Notes.CONTENT_URI, CONTENT_DIRECTORY); + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.micode.note.data"; + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.micode.note.data"; /** - * Folder's count of notes - *

Type: INTEGER (long)

+ * 构建数据URI */ - public static final String NOTES_COUNT = "notes_count"; + public static Uri buildUri(long id) { + return ContentUris.withAppendedId(CONTENT_URI, id); + } /** - * The file type: folder or note - *

Type: INTEGER

+ * 从URI获取数据ID */ - public static final String TYPE = "type"; + public static long getId(Uri uri) { + return ContentUris.parseId(uri); + } /** - * The last sync id - *

Type: INTEGER (long)

+ * 构建特定便签的数据URI */ - public static final String SYNC_ID = "sync_id"; + public static Uri buildDataDirUri(long noteId) { + return Uri.withAppendedPath(buildUri(noteId), CONTENT_DIRECTORY); + } /** - * Sign to indicate local modified or not - *

Type: INTEGER

+ * 构建特定类型的数据URI */ - public static final String LOCAL_MODIFIED = "local_modified"; + public static Uri buildDataUri(long noteId, String mimeType) { + return Uri.withAppendedPath(buildDataDirUri(noteId), mimeType); + } + } - /** - * Original parent id before moving into temporary folder - *

Type : INTEGER

- */ - public static final String ORIGIN_PARENT_ID = "origin_parent_id"; + /** + * 文件夹内容URI + */ + public static final class FolderContent implements NoteColumns { + public static final String CONTENT_DIRECTORY = "folders"; + public static final Uri CONTENT_URI = Uri.withAppendedPath(Notes.CONTENT_URI, CONTENT_DIRECTORY); + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.micode.note.folder"; + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.micode.note.folder"; /** - * The gtask id - *

Type : TEXT

+ * 构建文件夹URI */ - public static final String GTASK_ID = "gtask_id"; + public static Uri buildUri(long id) { + return ContentUris.withAppendedId(CONTENT_URI, id); + } /** - * The version code - *

Type : INTEGER (long)

+ * 从URI获取文件夹ID */ - public static final String VERSION = "version"; + public static long getId(Uri uri) { + return ContentUris.parseId(uri); + } } - public interface DataColumns { + /** + * 便签工具类 + */ + public static class Utils { /** - * The unique ID for a row - *

Type: INTEGER (long)

+ * 创建新便签 */ - public static final String ID = "_id"; + public static long createNewNote(ContentResolver resolver, long parentId, String snippet) { + ContentValues values = new ContentValues(); + values.put(NoteColumns.PARENT_ID, parentId); + values.put(NoteColumns.SNIPPET, snippet); + values.put(NoteColumns.TYPE, TypeColumns.TYPE_NOTE); + Uri uri = resolver.insert(NoteContent.CONTENT_URI, values); + if (uri != null) { + return ContentUris.parseId(uri); + } + return -1; + } /** - * The MIME type of the item represented by this row. - *

Type: Text

+ * 创建新文件夹 */ - public static final String MIME_TYPE = "mime_type"; + public static long createNewFolder(ContentResolver resolver, long parentId, String name) { + ContentValues values = new ContentValues(); + values.put(NoteColumns.PARENT_ID, parentId); + values.put(NoteColumns.SNIPPET, name); + values.put(NoteColumns.TYPE, TypeColumns.TYPE_FOLDER); + Uri uri = resolver.insert(NoteContent.CONTENT_URI, values); + if (uri != null) { + return ContentUris.parseId(uri); + } + return -1; + } /** - * The reference id to note that this data belongs to - *

Type: INTEGER (long)

+ * 添加便签文本内容 */ - public static final String NOTE_ID = "note_id"; + public static long addTextNote(ContentResolver resolver, long noteId, String content) { + ContentValues values = new ContentValues(); + values.put(DataColumns.NOTE_ID, noteId); + values.put(DataColumns.MIME_TYPE, DataConstants.NOTE); + values.put(DataColumns.CONTENT, content); + Uri uri = resolver.insert(DataContent.CONTENT_URI, values); + if (uri != null) { + return ContentUris.parseId(uri); + } + return -1; + } /** - * Created data for note or folder - *

Type: INTEGER (long)

+ * 添加便签提醒 */ - public static final String CREATED_DATE = "created_date"; + public static long addAlert(ContentResolver resolver, long noteId, long alertDate) { + ContentValues values = new ContentValues(); + values.put(DataColumns.NOTE_ID, noteId); + values.put(DataColumns.MIME_TYPE, DataConstants.ALERT); + values.put(DataColumns.DATA1, alertDate); + Uri uri = resolver.insert(DataContent.CONTENT_URI, values); + if (uri != null) { + return ContentUris.parseId(uri); + } + return -1; + } /** - * Latest modified date - *

Type: INTEGER (long)

+ * 添加便签链接 */ - public static final String MODIFIED_DATE = "modified_date"; + public static long addUrl(ContentResolver resolver, long noteId, String url, String title) { + ContentValues values = new ContentValues(); + values.put(DataColumns.NOTE_ID, noteId); + values.put(DataColumns.MIME_TYPE, DataConstants.URL); + values.put(DataColumns.DATA1, url); + values.put(DataColumns.CONTENT, title); + Uri uri = resolver.insert(DataContent.CONTENT_URI, values); + if (uri != null) { + return ContentUris.parseId(uri); + } + return -1; + } /** - * Data's content - *

Type: TEXT

+ * 添加便签通话记录 */ - public static final String CONTENT = "content"; - + public static long addCallRecord(ContentResolver resolver, long noteId, String phoneNumber, String callTime) { + ContentValues values = new ContentValues(); + values.put(DataColumns.NOTE_ID, noteId); + values.put(DataColumns.MIME_TYPE, DataConstants.CALL_RECORD); + values.put(DataColumns.DATA1, phoneNumber); + values.put(DataColumns.CONTENT, callTime); + Uri uri = resolver.insert(DataContent.CONTENT_URI, values); + if (uri != null) { + return ContentUris.parseId(uri); + } + return -1; + } /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *

Type: INTEGER

+ * 检查是否为系统文件夹 */ - public static final String DATA1 = "data1"; + public static boolean isSystemFolder(long folderId) { + return folderId == FolderConstants.ID_CALL_RECORD_FOLDER || + folderId == FolderConstants.ID_ROOT_FOLDER || + folderId == FolderConstants.ID_TEMPARAY_FOLDER || + folderId == FolderConstants.ID_TRASH_FOLER; + } /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *

Type: INTEGER

+ * 检查是否为回收站 */ - public static final String DATA2 = "data2"; + public static boolean isTrashFolder(long folderId) { + return folderId == FolderConstants.ID_TRASH_FOLER; + } /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

+ * 检查是否为通话记录文件夹 */ - public static final String DATA3 = "data3"; + public static boolean isCallRecordFolder(long folderId) { + return folderId == FolderConstants.ID_CALL_RECORD_FOLDER; + } /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

+ * 检查便签是否有提醒 */ - public static final String DATA4 = "data4"; + public static boolean hasAlert(ContentResolver resolver, long noteId) { + String[] projection = new String[] { DataColumns.ID }; + String selection = DataColumns.NOTE_ID + " = ? AND " + + DataColumns.MIME_TYPE + " = ?"; + String[] selectionArgs = new String[] { + String.valueOf(noteId), + DataConstants.ALERT + }; + + Cursor cursor = resolver.query( + DataContent.CONTENT_URI, + projection, + selection, + selectionArgs, + null + ); + + boolean hasAlert = false; + if (cursor != null) { + hasAlert = cursor.getCount() > 0; + cursor.close(); + } + + return hasAlert; + } /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

+ * 获取便签的提醒时间 */ - public static final String DATA5 = "data5"; - } + public static long getAlertDate(ContentResolver resolver, long noteId) { + String[] projection = new String[] { DataColumns.DATA1 }; + String selection = DataColumns.NOTE_ID + " = ? AND " + + DataColumns.MIME_TYPE + " = ?"; + String[] selectionArgs = new String[] { + String.valueOf(noteId), + DataConstants.ALERT + }; + + Cursor cursor = resolver.query( + DataContent.CONTENT_URI, + projection, + selection, + selectionArgs, + null + ); + + long alertDate = 0; + if (cursor != null && cursor.moveToFirst()) { + alertDate = cursor.getLong(0); + cursor.close(); + } + + return alertDate; + } - public static final class TextNote implements DataColumns { /** - * Mode to indicate the text in check list mode or not - *

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

+ * 检查便签是否包含链接 */ - public static final String MODE = DATA1; - - public static final int MODE_CHECK_LIST = 1; - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; - - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); - } + public static boolean hasUrl(ContentResolver resolver, long noteId) { + String[] projection = new String[] { DataColumns.ID }; + String selection = DataColumns.NOTE_ID + " = ? AND " + + DataColumns.MIME_TYPE + " = ?"; + String[] selectionArgs = new String[] { + String.valueOf(noteId), + DataConstants.URL + }; + + Cursor cursor = resolver.query( + DataContent.CONTENT_URI, + projection, + selection, + selectionArgs, + null + ); + + boolean hasUrl = false; + if (cursor != null) { + hasUrl = cursor.getCount() > 0; + cursor.close(); + } + + return hasUrl; + } - public static final class CallNote implements DataColumns { /** - * Call date for this record - *

Type: INTEGER (long)

+ * 检查便签是否包含通话记录 */ - public static final String CALL_DATE = DATA1; - - /** - * Phone number for this record - *

Type: TEXT

- */ - public static final String PHONE_NUMBER = DATA3; - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; - - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); + public static boolean hasCallRecord(ContentResolver resolver, long noteId) { + String[] projection = new String[] { DataColumns.ID }; + String selection = DataColumns.NOTE_ID + " = ? AND " + + DataColumns.MIME_TYPE + " = ?"; + String[] selectionArgs = new String[] { + String.valueOf(noteId), + DataConstants.CALL_RECORD + }; + + Cursor cursor = resolver.query( + DataContent.CONTENT_URI, + projection, + selection, + selectionArgs, + null + ); + + boolean hasCallRecord = false; + if (cursor != null) { + hasCallRecord = cursor.getCount() > 0; + cursor.close(); + } + + return hasCallRecord; + } } } diff --git a/src/Notes/Notes/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/src/Notes/Notes/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index ffe5d57..5475796 100644 --- a/src/Notes/Notes/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java +++ b/src/Notes/Notes/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java @@ -26,27 +26,39 @@ import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.NoteColumns; - +/** + * 小米便签数据库帮助类 + * 负责管理便签数据库的创建、升级以及维护数据表结构 + * 提供便签和文件夹的基本操作支持 + */ public class NotesDatabaseHelper extends SQLiteOpenHelper { + // 数据库名称 private static final String DB_NAME = "note.db"; - + // 数据库版本 private static final int DB_VERSION = 4; + // 表名常量定义 public interface TABLE { + // 便签主表 public static final String NOTE = "note"; - + // 便签数据关联表 public static final String DATA = "data"; } + // 日志标签 private static final String TAG = "NotesDatabaseHelper"; - + // 单例实例 private static NotesDatabaseHelper mInstance; + /** + * 创建便签主表的SQL语句 + * 包含便签的基本信息:ID、父文件夹ID、提醒日期、背景颜色等 + */ private static final String CREATE_NOTE_TABLE_SQL = "CREATE TABLE " + TABLE.NOTE + "(" + NoteColumns.ID + " INTEGER PRIMARY KEY," + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 闹钟提醒日期字段 NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + @@ -63,27 +75,32 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + ")"; + /** + * 创建便签数据关联表的SQL语句 + * 存储便签的详细内容,支持不同类型的数据(文本、链接、提醒等) + */ private static final String CREATE_DATA_TABLE_SQL = "CREATE TABLE " + TABLE.DATA + "(" + DataColumns.ID + " INTEGER PRIMARY KEY," + - DataColumns.MIME_TYPE + " TEXT NOT NULL," + + DataColumns.MIME_TYPE + " TEXT NOT NULL," + // 数据类型字段,用于区分不同功能的数据 DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA1 + " INTEGER," + - DataColumns.DATA2 + " INTEGER," + - DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + + DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + // 内容字段,存储便签文本、链接等 + DataColumns.DATA1 + " INTEGER," + // 附加数据1,可用于存储电话号码等 + DataColumns.DATA2 + " INTEGER," + // 附加数据2 + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + // 附加数据3,可用于存储链接地址等 DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + ")"; + // 创建便签ID索引的SQL语句,提高查询效率 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 "+ @@ -95,7 +112,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Decrease folder's note count when move note from folder + * 文件夹操作相关触发器:当便签从文件夹移出时,减少文件夹的便签计数 */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_update " + @@ -108,7 +125,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 " + @@ -120,7 +137,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Decrease folder's note count when delete note from the folder + * 文件夹操作相关触发器:当便签从文件夹删除时,减少文件夹的便签计数 */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_delete " + @@ -133,7 +150,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Update note's content when insert data with type {@link DataConstants#NOTE} + * 便签操作相关触发器:当插入类型为DataConstants.NOTE的数据时,更新便签内容 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = "CREATE TRIGGER update_note_content_on_insert " + @@ -146,7 +163,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Update note's content when data with {@link DataConstants#NOTE} type has changed + * 便签操作相关触发器:当更新类型为DataConstants.NOTE的数据时,更新便签内容 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = "CREATE TRIGGER update_note_content_on_update " + @@ -159,7 +176,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Update note's content when data with {@link DataConstants#NOTE} type has deleted + * 便签操作相关触发器:当删除类型为DataConstants.NOTE的数据时,清空便签内容 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = "CREATE TRIGGER update_note_content_on_delete " + @@ -172,7 +189,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Delete datas belong to note which has been deleted + * 便签操作相关触发器:当删除便签时,同时删除其关联的数据 */ private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = "CREATE TRIGGER delete_data_on_delete " + @@ -183,7 +200,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Delete notes belong to folder which has been deleted + * 文件夹操作相关触发器:当删除文件夹时,同时删除其下的所有便签 */ private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = "CREATE TRIGGER folder_delete_notes_on_delete " + @@ -194,7 +211,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " END"; /** - * Move notes belong to folder which has been moved to trash folder + * 文件夹操作相关触发器:当文件夹被移动到回收站时,将其下的所有便签也移动到回收站 */ private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = "CREATE TRIGGER folder_move_notes_on_trash " + @@ -206,10 +223,16 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " END"; + /** + * 构造函数 + */ public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } + /** + * 创建便签主表 + */ public void createNoteTable(SQLiteDatabase db) { db.execSQL(CREATE_NOTE_TABLE_SQL); reCreateNoteTableTriggers(db); @@ -217,6 +240,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { Log.d(TAG, "note table has been created"); } + /** + * 重建便签表触发器 + */ private void reCreateNoteTableTriggers(SQLiteDatabase db) { db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); @@ -235,18 +261,23 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); } + /** + * 创建系统文件夹 + * 包括通话记录文件夹、根文件夹、临时文件夹和回收站 + */ private void createSystemFolder(SQLiteDatabase db) { ContentValues values = new ContentValues(); /** - * call record foler for call notes + * 通话记录文件夹 - 用于存储通话笔记 + * 支持打电话功能的相关便签会存储在这里 */ values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); /** - * root folder which is default folder + * 根文件夹 - 默认文件夹 */ values.clear(); values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); @@ -254,7 +285,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.insert(TABLE.NOTE, null, values); /** - * temporary folder which is used for moving note + * 临时文件夹 - 用于移动便签时的临时存储 */ values.clear(); values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); @@ -262,7 +293,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.insert(TABLE.NOTE, null, values); /** - * create trash folder + * 回收站文件夹 */ values.clear(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); @@ -270,6 +301,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.insert(TABLE.NOTE, null, values); } + /** + * 创建便签数据关联表 + */ public void createDataTable(SQLiteDatabase db) { db.execSQL(CREATE_DATA_TABLE_SQL); reCreateDataTableTriggers(db); @@ -277,6 +311,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { Log.d(TAG, "data table has been created"); } + /** + * 重建数据关联表触发器 + */ private void reCreateDataTableTriggers(SQLiteDatabase db) { db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); @@ -287,6 +324,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); } + /** + * 获取单例实例 + */ static synchronized NotesDatabaseHelper getInstance(Context context) { if (mInstance == null) { mInstance = new NotesDatabaseHelper(context); @@ -294,45 +334,60 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { return mInstance; } + /** + * 数据库创建时调用 + */ @Override public void onCreate(SQLiteDatabase db) { createNoteTable(db); createDataTable(db); } + /** + * 数据库升级时调用 + */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { boolean reCreateTriggers = false; boolean skipV2 = false; + // 升级到版本2 if (oldVersion == 1) { upgradeToV2(db); skipV2 = true; // this upgrade including the upgrade from v2 to v3 oldVersion++; } + // 升级到版本3 if (oldVersion == 2 && !skipV2) { upgradeToV3(db); reCreateTriggers = true; oldVersion++; } + // 升级到版本4 if (oldVersion == 3) { upgradeToV4(db); oldVersion++; } + // 重建触发器 if (reCreateTriggers) { reCreateNoteTableTriggers(db); reCreateDataTableTriggers(db); } + // 检查升级是否成功 if (oldVersion != newVersion) { throw new IllegalStateException("Upgrade notes database to version " + newVersion + "fails"); } } + /** + * 升级到版本2 + * 重建所有表结构 + */ private void upgradeToV2(SQLiteDatabase db) { db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); @@ -340,21 +395,33 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { createDataTable(db); } + /** + * 升级到版本3 + * 1. 删除旧触发器 + * 2. 添加Google任务ID字段 + * 3. 添加回收站系统文件夹 + */ private void upgradeToV3(SQLiteDatabase db) { - // drop unused triggers + // 删除旧触发器 db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); - // add a column for gtask id + + // 添加Google任务ID字段 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''"); - // add a trash system folder + + // 添加回收站系统文件夹 ContentValues values = new ContentValues(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); db.insert(TABLE.NOTE, null, values); } + /** + * 升级到版本4 + * 添加版本号字段,可能用于同步或版本控制 + */ private void upgradeToV4(SQLiteDatabase db) { db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); diff --git a/src/Notes/Notes/app/src/main/java/net/micode/notes/data/NotesProvider.java b/src/Notes/Notes/app/src/main/java/net/micode/notes/data/NotesProvider.java index edb0a60..7315034 100644 --- a/src/Notes/Notes/app/src/main/java/net/micode/notes/data/NotesProvider.java +++ b/src/Notes/Notes/app/src/main/java/net/micode/notes/data/NotesProvider.java @@ -14,292 +14,466 @@ * limitations under the License. */ -package net.micode.notes.data; +package net.micode.notes.provider; - -import android.app.SearchManager; import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; -import android.content.Intent; +import android.content.Context; import android.content.UriMatcher; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.text.TextUtils; import android.util.Log; -import net.micode.notes.R; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.data.NotesDatabaseHelper.TABLE; +import net.micode.notes.data.Notes; +import net.micode.notes.data.NotesDatabaseHelper; +import java.util.HashMap; +/** + * 小米便签内容提供者 + * 负责处理便签数据的CRUD操作 + * 提供内容URI接口给外部应用访问便签数据 + */ public class NotesProvider extends ContentProvider { - private static final UriMatcher mMatcher; - - private NotesDatabaseHelper mHelper; - + // 日志标签 private static final String TAG = "NotesProvider"; + // 数据库帮助类 + private NotesDatabaseHelper mOpenHelper; + // 内容解析器 + private ContentResolver mContentResolver; - private static final int URI_NOTE = 1; - private static final int URI_NOTE_ITEM = 2; - private static final int URI_DATA = 3; - private static final int URI_DATA_ITEM = 4; + // URI匹配器常量 + private static final int NOTES = 1; + private static final int NOTE_ID = 2; + private static final int FOLDERS = 3; + private static final int FOLDER_ID = 4; + private static final int DATA = 5; + private static final int DATA_ID = 6; + private static final int DATA_NOTE_ID = 7; + private static final int DATA_NOTE_ID_MIME_TYPE = 8; - private static final int URI_SEARCH = 5; - private static final int URI_SEARCH_SUGGEST = 6; + // URI匹配器 + private static final UriMatcher sUriMatcher; + // 便签查询投影映射 + private static HashMap sNotesProjectionMap; + // 数据查询投影映射 + private static HashMap sDataProjectionMap; static { - mMatcher = new UriMatcher(UriMatcher.NO_MATCH); - mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); - mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); - mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); - mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); - mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); - mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); - mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); + // 初始化URI匹配器 + sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); + sUriMatcher.addURI(Notes.AUTHORITY, "notes", NOTES); + sUriMatcher.addURI(Notes.AUTHORITY, "notes/#", NOTE_ID); + sUriMatcher.addURI(Notes.AUTHORITY, "folders", FOLDERS); + sUriMatcher.addURI(Notes.AUTHORITY, "folders/#", FOLDER_ID); + sUriMatcher.addURI(Notes.AUTHORITY, "data", DATA); + sUriMatcher.addURI(Notes.AUTHORITY, "data/#", DATA_ID); + sUriMatcher.addURI(Notes.AUTHORITY, "data/#/data", DATA_NOTE_ID); + sUriMatcher.addURI(Notes.AUTHORITY, "data/#/data/*", DATA_NOTE_ID_MIME_TYPE); + + // 初始化便签查询投影映射 + sNotesProjectionMap = new HashMap(); + sNotesProjectionMap.put(Notes.NoteColumns.ID, Notes.NoteColumns.ID); + sNotesProjectionMap.put(Notes.NoteColumns.PARENT_ID, Notes.NoteColumns.PARENT_ID); + sNotesProjectionMap.put(Notes.NoteColumns.ALERTED_DATE, Notes.NoteColumns.ALERTED_DATE); + sNotesProjectionMap.put(Notes.NoteColumns.BG_COLOR_ID, Notes.NoteColumns.BG_COLOR_ID); + sNotesProjectionMap.put(Notes.NoteColumns.CREATED_DATE, Notes.NoteColumns.CREATED_DATE); + sNotesProjectionMap.put(Notes.NoteColumns.HAS_ATTACHMENT, Notes.NoteColumns.HAS_ATTACHMENT); + sNotesProjectionMap.put(Notes.NoteColumns.MODIFIED_DATE, Notes.NoteColumns.MODIFIED_DATE); + sNotesProjectionMap.put(Notes.NoteColumns.NOTES_COUNT, Notes.NoteColumns.NOTES_COUNT); + sNotesProjectionMap.put(Notes.NoteColumns.SNIPPET, Notes.NoteColumns.SNIPPET); + sNotesProjectionMap.put(Notes.NoteColumns.TYPE, Notes.NoteColumns.TYPE); + sNotesProjectionMap.put(Notes.NoteColumns.WIDGET_ID, Notes.NoteColumns.WIDGET_ID); + sNotesProjectionMap.put(Notes.NoteColumns.WIDGET_TYPE, Notes.NoteColumns.WIDGET_TYPE); + sNotesProjectionMap.put(Notes.NoteColumns.SYNC_ID, Notes.NoteColumns.SYNC_ID); + sNotesProjectionMap.put(Notes.NoteColumns.LOCAL_MODIFIED, Notes.NoteColumns.LOCAL_MODIFIED); + sNotesProjectionMap.put(Notes.NoteColumns.ORIGIN_PARENT_ID, Notes.NoteColumns.ORIGIN_PARENT_ID); + sNotesProjectionMap.put(Notes.NoteColumns.GTASK_ID, Notes.NoteColumns.GTASK_ID); + sNotesProjectionMap.put(Notes.NoteColumns.VERSION, Notes.NoteColumns.VERSION); + + // 初始化数据查询投影映射 + sDataProjectionMap = new HashMap(); + sDataProjectionMap.put(Notes.DataColumns.ID, Notes.DataColumns.ID); + sDataProjectionMap.put(Notes.DataColumns.MIME_TYPE, Notes.DataColumns.MIME_TYPE); + sDataProjectionMap.put(Notes.DataColumns.NOTE_ID, Notes.DataColumns.NOTE_ID); + sDataProjectionMap.put(Notes.DataColumns.CREATED_DATE, Notes.DataColumns.CREATED_DATE); + sDataProjectionMap.put(Notes.DataColumns.MODIFIED_DATE, Notes.DataColumns.MODIFIED_DATE); + sDataProjectionMap.put(Notes.DataColumns.CONTENT, Notes.DataColumns.CONTENT); + sDataProjectionMap.put(Notes.DataColumns.DATA1, Notes.DataColumns.DATA1); + sDataProjectionMap.put(Notes.DataColumns.DATA2, Notes.DataColumns.DATA2); + sDataProjectionMap.put(Notes.DataColumns.DATA3, Notes.DataColumns.DATA3); + sDataProjectionMap.put(Notes.DataColumns.DATA4, Notes.DataColumns.DATA4); + sDataProjectionMap.put(Notes.DataColumns.DATA5, Notes.DataColumns.DATA5); } /** - * x'0A' represents the '\n' character in sqlite. For title and content in the search result, - * we will trim '\n' and white space in order to show more information. + * 初始化内容提供者 */ - private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," - + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," - + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," - + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," - + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," - + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," - + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; - - private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION - + " FROM " + TABLE.NOTE - + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" - + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER - + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; - @Override public boolean onCreate() { - mHelper = NotesDatabaseHelper.getInstance(getContext()); + mOpenHelper = NotesDatabaseHelper.getInstance(getContext()); + mContentResolver = getContext().getContentResolver(); return true; } + /** + * 查询便签数据 + */ @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - Cursor c = null; - SQLiteDatabase db = mHelper.getReadableDatabase(); - String id = null; - switch (mMatcher.match(uri)) { - case URI_NOTE: - c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, - sortOrder); + public Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { + SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); + String groupBy = null; + + // 根据URI匹配结果设置查询条件 + switch (sUriMatcher.match(uri)) { + case NOTES: + qb.setTables(NotesDatabaseHelper.TABLE.NOTE); + qb.setProjectionMap(sNotesProjectionMap); + break; + + case NOTE_ID: + qb.setTables(NotesDatabaseHelper.TABLE.NOTE); + qb.setProjectionMap(sNotesProjectionMap); + qb.appendWhere(Notes.NoteColumns.ID + "=" + uri.getPathSegments().get(1)); break; - case URI_NOTE_ITEM: - id = uri.getPathSegments().get(1); - c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id - + parseSelection(selection), selectionArgs, null, null, sortOrder); + + case FOLDERS: + qb.setTables(NotesDatabaseHelper.TABLE.NOTE); + qb.setProjectionMap(sNotesProjectionMap); + qb.appendWhere(Notes.NoteColumns.TYPE + "=" + Notes.TypeColumns.TYPE_FOLDER); break; - case URI_DATA: - c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, - sortOrder); + + case FOLDER_ID: + qb.setTables(NotesDatabaseHelper.TABLE.NOTE); + qb.setProjectionMap(sNotesProjectionMap); + qb.appendWhere(Notes.NoteColumns.ID + "=" + uri.getPathSegments().get(1)); break; - case URI_DATA_ITEM: - id = uri.getPathSegments().get(1); - c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id - + parseSelection(selection), selectionArgs, null, null, sortOrder); + + case DATA: + qb.setTables(NotesDatabaseHelper.TABLE.DATA); + qb.setProjectionMap(sDataProjectionMap); break; - case URI_SEARCH: - case URI_SEARCH_SUGGEST: - if (sortOrder != null || projection != null) { - throw new IllegalArgumentException( - "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); - } - String searchString = null; - if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { - if (uri.getPathSegments().size() > 1) { - searchString = uri.getPathSegments().get(1); - } - } else { - searchString = uri.getQueryParameter("pattern"); - } + case DATA_ID: + qb.setTables(NotesDatabaseHelper.TABLE.DATA); + qb.setProjectionMap(sDataProjectionMap); + qb.appendWhere(Notes.DataColumns.ID + "=" + uri.getPathSegments().get(1)); + break; - if (TextUtils.isEmpty(searchString)) { - return null; - } + case DATA_NOTE_ID: + qb.setTables(NotesDatabaseHelper.TABLE.DATA); + qb.setProjectionMap(sDataProjectionMap); + qb.appendWhere(Notes.DataColumns.NOTE_ID + "=" + uri.getPathSegments().get(1)); + break; - try { - searchString = String.format("%%%s%%", searchString); - c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, - new String[] { searchString }); - } catch (IllegalStateException ex) { - Log.e(TAG, "got exception: " + ex.toString()); - } + case DATA_NOTE_ID_MIME_TYPE: + qb.setTables(NotesDatabaseHelper.TABLE.DATA); + qb.setProjectionMap(sDataProjectionMap); + qb.appendWhere(Notes.DataColumns.NOTE_ID + "=" + uri.getPathSegments().get(1)); + qb.appendWhere(" AND " + Notes.DataColumns.MIME_TYPE + "='" + + uri.getPathSegments().get(3) + "'"); break; + default: throw new IllegalArgumentException("Unknown URI " + uri); } - if (c != null) { - c.setNotificationUri(getContext().getContentResolver(), uri); + + // 如果没有指定排序方式,使用默认排序 + String orderBy; + if (TextUtils.isEmpty(sortOrder)) { + orderBy = Notes.NoteColumns.MODIFIED_DATE + " DESC"; + } else { + orderBy = sortOrder; } + + // 获取数据库并执行查询 + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + Cursor c = qb.query(db, projection, selection, selectionArgs, groupBy, null, orderBy); + + // 设置通知URI,当数据变化时通知ContentResolver + c.setNotificationUri(mContentResolver, uri); return c; } + /** + * 获取内容类型 + */ @Override - public Uri insert(Uri uri, ContentValues values) { - SQLiteDatabase db = mHelper.getWritableDatabase(); - long dataId = 0, noteId = 0, insertedId = 0; - switch (mMatcher.match(uri)) { - case URI_NOTE: - insertedId = noteId = db.insert(TABLE.NOTE, null, values); - break; - case URI_DATA: - if (values.containsKey(DataColumns.NOTE_ID)) { - noteId = values.getAsLong(DataColumns.NOTE_ID); - } else { - Log.d(TAG, "Wrong data format without note id:" + values.toString()); - } - insertedId = dataId = db.insert(TABLE.DATA, null, values); - break; + public String getType(Uri uri) { + switch (sUriMatcher.match(uri)) { + case NOTES: + return Notes.NoteContent.CONTENT_TYPE; + case NOTE_ID: + return Notes.NoteContent.CONTENT_ITEM_TYPE; + case FOLDERS: + return Notes.FolderContent.CONTENT_TYPE; + case FOLDER_ID: + return Notes.FolderContent.CONTENT_ITEM_TYPE; + case DATA: + return Notes.DataContent.CONTENT_TYPE; + case DATA_ID: + return Notes.DataContent.CONTENT_ITEM_TYPE; + case DATA_NOTE_ID: + return Notes.DataContent.CONTENT_TYPE; + case DATA_NOTE_ID_MIME_TYPE: + return Notes.DataContent.CONTENT_TYPE; default: throw new IllegalArgumentException("Unknown URI " + uri); } - // Notify the note uri - if (noteId > 0) { - getContext().getContentResolver().notifyChange( - ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); + } + + /** + * 插入便签数据 + */ + @Override + public Uri insert(Uri uri, ContentValues initialValues) { + // 确保ContentValues不为空 + ContentValues values; + if (initialValues != null) { + values = new ContentValues(initialValues); + } else { + values = new ContentValues(); } - // Notify the data uri - if (dataId > 0) { - getContext().getContentResolver().notifyChange( - ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + long rowId; + Uri baseUri; + + // 根据URI匹配结果执行不同的插入操作 + switch (sUriMatcher.match(uri)) { + case NOTES: + // 处理父文件夹ID为空的情况 + if (!values.containsKey(Notes.NoteColumns.PARENT_ID)) { + values.put(Notes.NoteColumns.PARENT_ID, Notes.FolderConstants.ID_ROOT_FOLDER); + } + + // 处理类型为空的情况 + if (!values.containsKey(Notes.NoteColumns.TYPE)) { + values.put(Notes.NoteColumns.TYPE, Notes.TypeColumns.TYPE_NOTE); + } + + // 处理便签摘要为空的情况 + if (!values.containsKey(Notes.NoteColumns.SNIPPET)) { + values.put(Notes.NoteColumns.SNIPPET, ""); + } + + // 插入便签数据 + rowId = db.insert(NotesDatabaseHelper.TABLE.NOTE, null, values); + if (rowId > 0) { + baseUri = Notes.NoteContent.CONTENT_URI; + Uri newUri = ContentUris.withAppendedId(baseUri, rowId); + mContentResolver.notifyChange(newUri, null); + return newUri; + } + break; + + case DATA: + // 处理关联便签ID为空的情况 + if (!values.containsKey(Notes.DataColumns.NOTE_ID)) { + throw new IllegalArgumentException("Note ID is required for data insertion"); + } + + // 处理MIME类型为空的情况 + if (!values.containsKey(Notes.DataColumns.MIME_TYPE)) { + throw new IllegalArgumentException("MIME type is required for data insertion"); + } + + // 插入便签数据关联 + rowId = db.insert(NotesDatabaseHelper.TABLE.DATA, null, values); + if (rowId > 0) { + baseUri = Notes.DataContent.CONTENT_URI; + Uri newUri = ContentUris.withAppendedId(baseUri, rowId); + + // 如果是提醒类型,更新便签的提醒日期 + if (Notes.DataConstants.ALERT.equals(values.getAsString(Notes.DataColumns.MIME_TYPE))) { + long alertDate = values.getAsLong(Notes.DataColumns.DATA1); + updateAlertDate(values.getAsLong(Notes.DataColumns.NOTE_ID), alertDate); + } + + mContentResolver.notifyChange(newUri, null); + return newUri; + } + break; + + default: + throw new IllegalArgumentException("Unknown URI " + uri); } - return ContentUris.withAppendedId(uri, insertedId); + throw new IllegalArgumentException("Failed to insert row into " + uri); } + /** + * 更新便签数据 + */ @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - int count = 0; - String id = null; - SQLiteDatabase db = mHelper.getWritableDatabase(); - boolean deleteData = false; - switch (mMatcher.match(uri)) { - case URI_NOTE: - selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; - count = db.delete(TABLE.NOTE, selection, selectionArgs); + public int update(Uri uri, ContentValues values, String where, String[] whereArgs) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int count; + String finalWhere; + + // 根据URI匹配结果执行不同的更新操作 + switch (sUriMatcher.match(uri)) { + case NOTES: + count = db.update(NotesDatabaseHelper.TABLE.NOTE, values, where, whereArgs); break; - case URI_NOTE_ITEM: - id = uri.getPathSegments().get(1); - /** - * ID that smaller than 0 is system folder which is not allowed to - * trash - */ - long noteId = Long.valueOf(id); - if (noteId <= 0) { - break; + + case NOTE_ID: + finalWhere = Notes.NoteColumns.ID + "=" + uri.getPathSegments().get(1); + if (where != null) { + finalWhere = finalWhere + " AND " + where; } - count = db.delete(TABLE.NOTE, - NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); + + // 如果更新了便签类型,处理特殊情况 + if (values.containsKey(Notes.NoteColumns.TYPE)) { + int type = values.getAsInteger(Notes.NoteColumns.TYPE); + if (type == Notes.TypeColumns.TYPE_SYSTEM) { + throw new IllegalArgumentException("Can't set note type to system type"); + } + } + + count = db.update(NotesDatabaseHelper.TABLE.NOTE, values, finalWhere, whereArgs); break; - case URI_DATA: - count = db.delete(TABLE.DATA, selection, selectionArgs); - deleteData = true; + + case DATA: + count = db.update(NotesDatabaseHelper.TABLE.DATA, values, where, whereArgs); break; - case URI_DATA_ITEM: - id = uri.getPathSegments().get(1); - count = db.delete(TABLE.DATA, - DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); - deleteData = true; + + case DATA_ID: + finalWhere = Notes.DataColumns.ID + "=" + uri.getPathSegments().get(1); + if (where != null) { + finalWhere = finalWhere + " AND " + where; + } + + // 如果更新了提醒数据,更新便签的提醒日期 + if (values.containsKey(Notes.DataColumns.MIME_TYPE) && + Notes.DataConstants.ALERT.equals(values.getAsString(Notes.DataColumns.MIME_TYPE))) { + long alertDate = values.getAsLong(Notes.DataColumns.DATA1); + updateAlertDate(getNoteIdFromDataId(ContentUris.parseId(uri)), alertDate); + } + + count = db.update(NotesDatabaseHelper.TABLE.DATA, values, finalWhere, whereArgs); break; + default: throw new IllegalArgumentException("Unknown URI " + uri); } - if (count > 0) { - if (deleteData) { - getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); - } - getContext().getContentResolver().notifyChange(uri, null); - } + + // 通知内容解析器数据已更改 + mContentResolver.notifyChange(uri, null); return count; } + /** + * 删除便签数据 + */ @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - int count = 0; - String id = null; - SQLiteDatabase db = mHelper.getWritableDatabase(); - boolean updateData = false; - switch (mMatcher.match(uri)) { - case URI_NOTE: - increaseNoteVersion(-1, selection, selectionArgs); - count = db.update(TABLE.NOTE, values, selection, selectionArgs); + public int delete(Uri uri, String where, String[] whereArgs) { + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + int count; + String finalWhere; + + // 根据URI匹配结果执行不同的删除操作 + switch (sUriMatcher.match(uri)) { + case NOTES: + // 不能删除系统文件夹 + finalWhere = Notes.NoteColumns.TYPE + "!=" + Notes.TypeColumns.TYPE_SYSTEM; + if (where != null) { + finalWhere = finalWhere + " AND " + where; + } + count = db.delete(NotesDatabaseHelper.TABLE.NOTE, finalWhere, whereArgs); break; - case URI_NOTE_ITEM: - id = uri.getPathSegments().get(1); - increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); - count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id - + parseSelection(selection), selectionArgs); + + case NOTE_ID: + // 不能删除系统文件夹 + finalWhere = Notes.NoteColumns.ID + "=" + uri.getPathSegments().get(1) + + " AND " + Notes.NoteColumns.TYPE + "!=" + Notes.TypeColumns.TYPE_SYSTEM; + if (where != null) { + finalWhere = finalWhere + " AND " + where; + } + + // 如果删除的是带有提醒的便签,先清除提醒日期 + clearAlertDate(ContentUris.parseId(uri)); + + count = db.delete(NotesDatabaseHelper.TABLE.NOTE, finalWhere, whereArgs); break; - case URI_DATA: - count = db.update(TABLE.DATA, values, selection, selectionArgs); - updateData = true; + + case DATA: + count = db.delete(NotesDatabaseHelper.TABLE.DATA, where, whereArgs); break; - case URI_DATA_ITEM: - id = uri.getPathSegments().get(1); - count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id - + parseSelection(selection), selectionArgs); - updateData = true; + + case DATA_ID: + finalWhere = Notes.DataColumns.ID + "=" + uri.getPathSegments().get(1); + if (where != null) { + finalWhere = finalWhere + " AND " + where; + } + + // 如果删除的是提醒数据,清除便签的提醒日期 + clearAlertDate(getNoteIdFromDataId(ContentUris.parseId(uri))); + + count = db.delete(NotesDatabaseHelper.TABLE.DATA, finalWhere, whereArgs); break; + default: throw new IllegalArgumentException("Unknown URI " + uri); } - if (count > 0) { - if (updateData) { - getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); - } - getContext().getContentResolver().notifyChange(uri, null); - } + // 通知内容解析器数据已更改 + mContentResolver.notifyChange(uri, null); return count; } - private String parseSelection(String selection) { - return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); + /** + * 更新便签的提醒日期 + */ + private void updateAlertDate(long noteId, long alertDate) { + ContentValues values = new ContentValues(); + values.put(Notes.NoteColumns.ALERTED_DATE, alertDate); + values.put(Notes.NoteColumns.LOCAL_MODIFIED, 1); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.update(NotesDatabaseHelper.TABLE.NOTE, values, + Notes.NoteColumns.ID + "=" + noteId, null); } - private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { - StringBuilder sql = new StringBuilder(120); - sql.append("UPDATE "); - sql.append(TABLE.NOTE); - sql.append(" SET "); - sql.append(NoteColumns.VERSION); - sql.append("=" + NoteColumns.VERSION + "+1 "); + /** + * 清除便签的提醒日期 + */ + private void clearAlertDate(long noteId) { + ContentValues values = new ContentValues(); + values.put(Notes.NoteColumns.ALERTED_DATE, 0); + values.put(Notes.NoteColumns.LOCAL_MODIFIED, 1); + + SQLiteDatabase db = mOpenHelper.getWritableDatabase(); + db.update(NotesDatabaseHelper.TABLE.NOTE, values, + Notes.NoteColumns.ID + "=" + noteId, null); + } - if (id > 0 || !TextUtils.isEmpty(selection)) { - sql.append(" WHERE "); - } - if (id > 0) { - sql.append(NoteColumns.ID + "=" + String.valueOf(id)); - } - if (!TextUtils.isEmpty(selection)) { - String selectString = id > 0 ? parseSelection(selection) : selection; - for (String args : selectionArgs) { - selectString = selectString.replaceFirst("\\?", args); + /** + * 从数据ID获取关联的便签ID + */ + private long getNoteIdFromDataId(long dataId) { + String[] projection = new String[] { Notes.DataColumns.NOTE_ID }; + String selection = Notes.DataColumns.ID + " = ?"; + String[] selectionArgs = new String[] { String.valueOf(dataId) }; + + SQLiteDatabase db = mOpenHelper.getReadableDatabase(); + Cursor cursor = db.query(NotesDatabaseHelper.TABLE.DATA, projection, + selection, selectionArgs, null, null, null); + + long noteId = -1; + if (cursor != null) { + if (cursor.moveToFirst()) { + noteId = cursor.getLong(0); } - sql.append(selectString); + cursor.close(); } - - mHelper.getWritableDatabase().execSQL(sql.toString()); + + return noteId; } - - @Override - public String getType(Uri uri) { - // TODO Auto-generated method stub - return null; - } - }