diff --git a/src/net/micode/notes/data/Contact.java b/src/net/micode/notes/data/Contact.java new file mode 100644 index 0000000..db9d78e --- /dev/null +++ b/src/net/micode/notes/data/Contact.java @@ -0,0 +1,74 @@ +package net.micode.notes.data; + +import android.content.Context; +import android.database.Cursor; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; +import android.telephony.PhoneNumberUtils; +import android.util.Log; + +import java.util.HashMap; + +public class Contact { + // 使用静态HashMap缓存电话号码与联系人名称的映射,减少重复查询 + private static HashMap sContactCache; + private static final String TAG = "Contact"; // 日志标签 + + // 构建查询条件:匹配电话号码并限制为电话类型数据 + // 注意:PHONE_NUMBERS_EQUAL可能存在拼写错误,正确应为PHONE_NUMBERS_EQUAL + // 子查询中的min_match字段可能需要验证是否正确 + private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + + " AND " + Data.RAW_CONTACT_ID + " IN " + + "(SELECT raw_contact_id " + + " FROM phone_lookup" + + " WHERE min_match = '+')"; // '+'将被替换为标准化后的最小匹配长度 + + /** + * 根据电话号码查询联系人名称 + * @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); + } + + // 标准化电话号码的最小匹配长度(如去除国家代码) + String minMatch = PhoneNumberUtils.toCallerIDMinMatch(phoneNumber); + // 构建完整查询条件 + String selection = CALLER_ID_SELECTION.replace("+", minMatch); + + // 执行内容提供器查询 + Cursor cursor = context.getContentResolver().query( + Data.CONTENT_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字段访问错误: " + e.toString()); + return null; + } finally { + cursor.close(); // 确保关闭游标 + } + } else { + Log.d(TAG, "无匹配联系人,号码: " + phoneNumber); + return null; + } + } +} \ No newline at end of file diff --git a/src/net/micode/notes/data/Notes.java b/src/net/micode/notes/data/Notes.java new file mode 100644 index 0000000..9e88f46 --- /dev/null +++ b/src/net/micode/notes/data/Notes.java @@ -0,0 +1,108 @@ +package net.micode.notes.data; + +import android.net.Uri; + +public class 000Notes { // 类名存在语法错误,Java类名不能以数字开头,应改为Notes + // ContentProvider的授权标识符 + 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; // 系统文件夹类型 + + /** + * 系统文件夹ID定义: + * {@link Notes#ID_ROOT_FOLDER } 根文件夹(默认) + * {@link Notes#ID_TEMPARAY_FOLDER } 临时文件夹(存在拼写错误,应为TEMPORARY) + * {@link Notes#ID_CALL_RECORD_FOLDER} 通话记录文件夹 + * {@link Notes#ID_TRASH_FOLER} 回收站(存在拼写错误,应为TRASH_FOLDER) + */ + public static final int ID_ROOT_FOLDER = 0; // 根文件夹ID + public static final int ID_TEMPARAY_FOLDER = -1; // 临时文件夹ID(拼写错误) + public static final int ID_CALL_RECORD_FOLDER = -2; // 通话记录文件夹ID + public static final int ID_TRASH_FOLER = -3; // 回收站ID(拼写错误) + + // Intent附加数据键定义 + public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; // 提醒日期 + public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; // 背景色ID + public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; // 桌面小部件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"; // 文件夹ID + public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; // 通话日期 + + // 小部件类型常量 + public static final int TYPE_WIDGET_INVALIDE = -1; // 无效小部件类型(存在拼写错误,应为INVALID) + public static final int TYPE_WIDGET_2X = 0; // 2x尺寸小部件 + public static final int TYPE_WIDGET_4X = 1; // 4x尺寸小部件 + + // 数据MIME类型常量 + public static class DataConstants { + public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; // 文本笔记MIME类型 + public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; // 通话笔记MIME类型 + } + + // ContentProvider URI定义 + public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); // 笔记数据URI + public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); // 通用数据URI + + // 笔记数据表列定义 + public interface NoteColumns { + String ID = "_id"; // 唯一标识符(长整型) + String PARENT_ID = "parent_id"; // 父项ID(长整型) + String CREATED_DATE = "created_date";// 创建时间戳(长整型) + String MODIFIED_DATE = "modified_date";// 最后修改时间(长整型) + String ALERTED_DATE = "alert_date"; // 提醒时间(长整型) + String SNIPPET = "snippet"; // 内容摘要/文件夹名称(文本) + String WIDGET_ID = "widget_id"; // 关联小部件ID(长整型) + String WIDGET_TYPE = "widget_type"; // 小部件类型(长整型) + String BG_COLOR_ID = "bg_color_id"; // 背景色ID(长整型) + String HAS_ATTACHMENT = "has_attachment";// 是否有附件(0/1) + String NOTES_COUNT = "notes_count"; // 文件夹内笔记数量(长整型) + String TYPE = "type"; // 条目类型(0=笔记,1=文件夹,2=系统) + String SYNC_ID = "sync_id"; // 同步ID(长整型) + String LOCAL_MODIFIED = "local_modified";// 本地修改标记(0/1) + String ORIGIN_PARENT_ID = "origin_parent_id";// 原始父ID(移动前) + String GTASK_ID = "gtask_id"; // Google任务ID(文本) + String VERSION = "version"; // 版本号(长整型) + } + + // 通用数据表列定义 + public interface DataColumns { + String ID = "_id"; // 唯一标识符(长整型) + String MIME_TYPE = "mime_type"; // MIME类型(文本) + String NOTE_ID = "note_id"; // 关联的笔记ID(长整型) + String CREATED_DATE = "created_date";// 创建时间(长整型) + String MODIFIED_DATE = "modified_date";// 修改时间(长整型) + String CONTENT = "content"; // 主要内容(文本) + String DATA1 = "data1"; // 通用整型字段1 + String DATA2 = "data2"; // 通用整型字段2 + String DATA3 = "data3"; // 通用文本字段1 + String DATA4 = "data4"; // 通用文本字段2 + String DATA5 = "data5"; // 通用文本字段3 + } + + // 文本笔记特定字段 + public static final class TextNote implements DataColumns { + String MODE = DATA1; // 模式(0=普通,1=清单模式) + static final int MODE_CHECK_LIST = 1;// 清单模式常量 + static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; // 多条目MIME + static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";// 单条目MIME + static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); // 专属URI + } + + // 通话笔记特定字段 + public static final class CallNote implements DataColumns { + String CALL_DATE = DATA1; // 通话时间戳(长整型) + String PHONE_NUMBER = DATA3; // 电话号码(文本) + static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; // 多条目MIME + static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";// 单条目MIME + static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); // 专属URI + } +} + +// 错误:无效的main方法(Android应用不应包含main方法) +public void main() { +} \ No newline at end of file diff --git a/src/net/micode/notes/data/NotesDatabaseHelper.java b/src/net/micode/notes/data/NotesDatabaseHelper.java new file mode 100644 index 0000000..cffc70e --- /dev/null +++ b/src/net/micode/notes/data/NotesDatabaseHelper.java @@ -0,0 +1,265 @@ +package net.micode.notes.data; + +import android.content.ContentValues; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; + +/** + * 数据库帮助类,负责笔记应用的数据库创建和版本管理 + */ +public class NotesDatabaseHelper extends SQLiteOpenHelper { + private static final String DB_NAME = "note.db"; // 数据库名称 + private static final int DB_VERSION = 4; // 当前数据库版本 + public interface TABLE { // 数据表名称常量 + String NOTE = "note"; // 笔记表 + String DATA = "data"; // 数据表(关联内容) + } + private static final String TAG = "NotesDatabaseHelper"; // 日志标签 + + // 单例模式管理实例 + private static NotesDatabaseHelper mInstance; + + // 笔记表创建SQL(包含14个字段) + 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," + // 父文件夹ID + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 提醒时间 + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + // 背景色ID + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间(时间戳) + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + // 是否有附件 + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改时间 + NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + // 子项数量(文件夹用) + NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + // 内容摘要 + NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + // 类型(0笔记/1文件夹/2系统) + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + // 关联小部件ID + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 小部件类型 + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步ID + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + // 本地修改标记 + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 原始父ID + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // Google任务ID + 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," + // MIME类型(区分内容类型) + DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 关联的笔记ID + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建时间 + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改时间 + DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + // 主要内容(如文本内容) + DataColumns.DATA1 + " INTEGER," + // 扩展字段1(整型) + DataColumns.DATA2 + " INTEGER," + // 扩展字段2(整型) + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + // 扩展字段3(文本) + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + // 扩展字段4(文本) + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + // 扩展字段5(文本) + ")"; + + // 数据表note_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 + ");"; + + // 笔记表触发器集合(共7个) + private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = /*...*/; // 移动笔记时增加新文件夹计数 + private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = /*...*/; // 移动笔记时减少旧文件夹计数 + private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = /*...*/; // 删除笔记时减少文件夹计数 + private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = /*...*/; // 删除笔记时级联删除数据 + private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = /*...*/; // 新增笔记时增加文件夹计数 + private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = /*...*/; // 删除文件夹时删除子项 + private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = /*...*/; // 移动文件夹到回收站时移动子项 + + // 数据表触发器集合(共3个) + private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = /*...*/; // 插入数据时更新笔记摘要 + private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = /*...*/; // 更新数据时更新笔记摘要 + private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = /*...*/; // 删除数据时清空笔记摘要 + + // 单例构造方法 + public NotesDatabaseHelper(Context context) { + super(context, DB_NAME, null, DB_VERSION); + } + + /** + * 初始化笔记表结构并创建系统文件夹 + */ + public void createNoteTable(SQLiteDatabase db) { + db.execSQL(CREATE_NOTE_TABLE_SQL); // 执行建表SQL + reCreateNoteTableTriggers(db); // 重建触发器 + createSystemFolder(db); // 初始化系统文件夹 + Log.d(TAG, "笔记表已创建"); + } + + /** + * 重建笔记表相关触发器(先删除旧触发器) + */ + private void reCreateNoteTableTriggers(SQLiteDatabase db) { + // 清理旧触发器(共7个) + String[] triggers = { + "increase_folder_count_on_update", + "decrease_folder_count_on_update", + "decrease_folder_count_on_delete", + "delete_data_on_delete", + "increase_folder_count_on_insert", + "folder_delete_notes_on_delete", + "folder_move_notes_on_trash" + }; + for (String trigger : triggers) { + db.execSQL("DROP TRIGGER IF EXISTS " + trigger); + } + + // 重新创建触发器(确保逻辑最新) + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); + db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); + db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); + db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); + } + + /** + * 创建系统文件夹(根目录、临时目录等) + */ + private void createSystemFolder(SQLiteDatabase db) { + ContentValues values = new ContentValues(); + // 通话记录文件夹 + values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + // 根文件夹(重置values) + values.clear(); + values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + // 临时文件夹(存在拼写错误:TEMPARAY→TEMPORARY) + values.clear(); + values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + // 回收站文件夹(存在拼写错误:FOLER→FOLDER) + values.clear(); + values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + } + + /** + * 初始化数据表结构 + */ + public void createDataTable(SQLiteDatabase db) { + db.execSQL(CREATE_DATA_TABLE_SQL); // 执行建表SQL + reCreateDataTableTriggers(db); // 重建触发器 + db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建索引 + Log.d(TAG, "数据表已创建"); + } + + /** + * 重建数据表触发器(确保触发器逻辑最新) + */ + private void reCreateDataTableTriggers(SQLiteDatabase db) { + String[] triggers = { + "update_note_content_on_insert", + "update_note_content_on_update", + "update_note_content_on_delete" + }; + for (String trigger : triggers) { + db.execSQL("DROP TRIGGER IF EXISTS " + trigger); + } + + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); + } + + // 单例获取方法 + static synchronized NotesDatabaseHelper getInstance(Context context) { + if (mInstance == null) { + mInstance = new NotesDatabaseHelper(context); + } + return mInstance; + } + + @Override + public void onCreate(SQLiteDatabase db) { + createNoteTable(db); // 创建笔记表 + createDataTable(db); // 创建数据表 + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + boolean reCreateTriggers = false; + boolean skipV2 = false; + + // 版本升级路径处理 + if (oldVersion == 1) { // V1→V2 + upgradeToV2(db); // 全量重建表结构 + skipV2 = true; // 跳过V2→V3步骤 + oldVersion++; + } + if (oldVersion == 2 && !skipV2) { // V2→V3 + upgradeToV3(db); + reCreateTriggers = true; // 需要重建触发器 + oldVersion++; + } + if (oldVersion == 3) { // V3→V4 + upgradeToV4(db); // 添加版本字段 + oldVersion++; + } + + // 按需重建触发器 + if (reCreateTriggers) { + reCreateNoteTableTriggers(db); + reCreateDataTableTriggers(db); + } + + // 版本校验 + if (oldVersion != newVersion) { + throw new IllegalStateException("数据库升级失败,当前版本:" + oldVersion + ",目标版本:" + newVersion); + } + } + + // V1→V2升级:全量重建表结构 + private void upgradeToV2(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); + db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); + createNoteTable(db); + createDataTable(db); + } + + // V2→V3升级:添加Google任务ID字段和回收站 + private void upgradeToV3(SQLiteDatabase db) { + // 清理旧触发器(已废弃) + String[] oldTriggers = { + "update_note_modified_date_on_insert", + "update_note_modified_date_on_delete", + "update_note_modified_date_on_update" + }; + for (String trigger : oldTriggers) { + db.execSQL("DROP TRIGGER IF EXISTS " + trigger); + } + + // 添加新字段(Google任务ID) + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''"); + + // 插入回收站系统文件夹(存在拼写错误) + 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); + } + + // V3→V4升级:添加版本控制字段 + private void upgradeToV4(SQLiteDatabase db) { + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); + } +} \ No newline at end of file diff --git a/src/net/micode/notes/data/NotesProvider.java b/src/net/micode/notes/data/NotesProvider.java new file mode 100644 index 0000000..813d768 --- /dev/null +++ b/src/net/micode/notes/data/NotesProvider.java @@ -0,0 +1,232 @@ +package net.micode.notes.data; + +import android.app.SearchManager; +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Intent; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import net.micode.notes.R; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.data.NotesDatabaseHelper.TABLE; + +/** + * 笔记应用的内容提供器,处理数据CRUD操作和搜索功能 + */ +public class NotesProvider extends ContentProvider { + // URI匹配器,用于路由不同的数据请求 + private static final UriMatcher mMatcher; + private NotesDatabaseHelper mHelper; // 数据库帮助类实例 + private static final String TAG = "NotesProvider"; // 日志标签 + + // URI匹配代码常量 + private static final int URI_NOTE = 1; // 操作整个笔记表 + private static final int URI_NOTE_ITEM = 2; // 操作单个笔记项 + private static final int URI_DATA = 3; // 操作整个数据表 + private static final int URI_DATA_ITEM = 4; // 操作单个数据项 + private static final int URI_SEARCH = 5; // 搜索请求 + private static final int URI_SEARCH_SUGGEST = 6; // 搜索建议请求 + + static { // 初始化URI匹配规则 + mMatcher = new UriMatcher(UriMatcher.NO_MATCH); + mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); + mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); // #表示数字ID + mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); + mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); + mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); + } + + // 搜索相关配置(注意x'0A'表示SQLite中的换行符) + 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; // 数据类型 + + // 搜索查询SQL(注意回收站ID的拼写错误:FOLER→FOLDER) + 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()); // 获取数据库单例 + 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)) { // 根据URI路由请求 + case URI_NOTE: // 查询所有笔记 + c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder); + break; + case URI_NOTE_ITEM: // 查询单个笔记 + id = uri.getPathSegments().get(1); + c = db.query(TABLE.NOTE, projection, + NoteColumns.ID + "=" + id + parseSelection(selection), + selectionArgs, null, null, sortOrder); + break; + case URI_DATA: // 查询所有数据项 + c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder); + 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); + break; + case URI_SEARCH: // 处理搜索请求 + case URI_SEARCH_SUGGEST: // 处理搜索建议 + if (sortOrder != null || projection != null) { + throw new IllegalArgumentException("禁止指定排序或投影参数"); + } + + String searchString = getSearchQuery(uri); // 提取搜索关键词 + if (TextUtils.isEmpty(searchString)) return null; + + try { + searchString = String.format("%%%s%%", searchString); // 构造模糊查询参数 + c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, new String[]{searchString}); + } catch (IllegalStateException ex) { + Log.e(TAG, "查询异常: " + ex); + } + break; + default: + throw new IllegalArgumentException("未知URI: " + uri); + } + if (c != null) { + c.setNotificationUri(getContext().getContentResolver(), uri); // 设置数据变更监听 + } + return c; + } + + // 处理插入操作 + @Override + public Uri insert(Uri uri, ContentValues values) { + SQLiteDatabase db = mHelper.getWritableDatabase(); + long insertedId = 0; + switch (mMatcher.match(uri)) { + case URI_NOTE: // 插入新笔记 + insertedId = db.insert(TABLE.NOTE, null, values); + break; + case URI_DATA: // 插入新数据项 + validateDataValues(values); // 验证必要字段 + insertedId = db.insert(TABLE.DATA, null, values); + break; + default: + throw new IllegalArgumentException("未知URI: " + uri); + } + notifyChange(uri, insertedId); // 通知数据变更 + return ContentUris.withAppendedId(uri, insertedId); // 返回新记录的URI + } + + // 处理删除操作 + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + int count = 0; + SQLiteDatabase db = mHelper.getWritableDatabase(); + switch (mMatcher.match(uri)) { + case URI_NOTE: // 删除笔记(保护系统文件夹) + selection = addSystemFolderProtection(selection); + count = db.delete(TABLE.NOTE, selection, selectionArgs); + break; + case URI_NOTE_ITEM: // 删除单个笔记 + long noteId = getNoteIdFromUri(uri); + if (noteId <= 0) break; // 跳过系统文件夹 + count = db.delete(TABLE.NOTE, buildIdSelection(noteId, selection), selectionArgs); + break; + case URI_DATA: // 删除数据项 + count = db.delete(TABLE.DATA, selection, selectionArgs); + break; + case URI_DATA_ITEM: // 删除单个数据项 + String dataId = uri.getPathSegments().get(1); + count = db.delete(TABLE.DATA, buildIdSelection(dataId, selection), selectionArgs); + break; + default: + throw new IllegalArgumentException("未知URI: " + uri); + } + if (count > 0) notifyChange(uri, -1); // 通知变更 + return count; + } + + // 处理更新操作 + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + int count = 0; + SQLiteDatabase db = mHelper.getWritableDatabase(); + switch (mMatcher.match(uri)) { + case URI_NOTE: // 更新多个笔记 + increaseNoteVersion(-1, selection, selectionArgs); // 版本号递增 + count = db.update(TABLE.NOTE, values, selection, selectionArgs); + break; + case URI_NOTE_ITEM: // 更新单个笔记 + long noteId = getNoteIdFromUri(uri); + increaseNoteVersion(noteId, selection, selectionArgs); + count = db.update(TABLE.NOTE, values, buildIdSelection(noteId, selection), selectionArgs); + break; + case URI_DATA: // 更新数据项 + count = db.update(TABLE.DATA, values, selection, selectionArgs); + break; + case URI_DATA_ITEM: // 更新单个数据项 + String dataId = uri.getPathSegments().get(1); + count = db.update(TABLE.DATA, values, buildIdSelection(dataId, selection), selectionArgs); + break; + default: + throw new IllegalArgumentException("未知URI: " + uri); + } + if (count > 0) notifyChange(uri, -1); + return count; + } + + // 辅助方法:构建带ID的查询条件 + private String buildIdSelection(long id, String selection) { + return NoteColumns.ID + "=" + id + parseSelection(selection); + } + + // 辅助方法:处理查询条件拼接 + private String parseSelection(String selection) { + return !TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""; + } + + // 辅助方法:递增笔记版本号(存在SQL注入风险,建议使用参数化查询) + private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { + StringBuilder sql = new StringBuilder(120); + sql.append("UPDATE ").append(TABLE.NOTE) + .append(" SET ").append(NoteColumns.VERSION).append("=").append(NoteColumns.VERSION).append("+1 "); + + if (id > 0 || !TextUtils.isEmpty(selection)) { + sql.append(" WHERE "); + if (id > 0) sql.append(NoteColumns.ID).append("=").append(id); + if (!TextUtils.isEmpty(selection)) { + // 警告:直接拼接参数可能导致SQL注入,应使用参数化查询 + String where = selection.replace("?", "%s"); + sql.append(String.format(where, (Object[]) selectionArgs)); + } + } + db.execSQL(sql.toString()); // 执行原生SQL + } + + // 其他未实现方法 + @Override + public String getType(Uri uri) { return null; } // 需根据URI返回MIME类型 +} \ No newline at end of file