package net.micode.notes.data; // 导入SearchManager类,用于管理搜索功能 import android.app.SearchManager; // 导入ContentProvider类,用于实现数据的访问接口 import android.content.ContentProvider; // 导入ContentUris类,提供了一些用于处理URI的方法 import android.content.ContentUris; // 导入ContentValues类,用于存储键值对,通常用于插入或更新数据库记录 import android.content.ContentValues; // 导入Intent类,用于启动Activity、Service或发送广播 import android.content.Intent; // 导入UriMatcher类,用于匹配URI,以便根据不同的URI执行不同的操作 import android.content.UriMatcher; // 导入Cursor类,用于从数据库查询结果集中获取数据 import android.database.Cursor; // 导入SQLiteDatabase类,用于访问SQLite数据库 import android.database.sqlite.SQLiteDatabase; // 导入Uri类,用于表示统一资源标识符 import android.net.Uri; // 导入TextUtils类,提供了一些文本处理的工具方法 import android.text.TextUtils; // 导入Log类,用于日志输出 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; public class NotesProvider extends ContentProvider { //NotesProvider的主要功能是作为一个内容提供者,为其他应用程序或组件提供对“Notes”数据的访问。 //它允许其他应用程序查询、插入、更新或删除标签数据。 //通过URI匹配,NotesProvider能够区分对哪种数据类型的请求(例如,单独的标签、标签的数据、文件夹操作等),并执行相应的操作。 //用于匹配不同URI的UriMatcher对象,通常用于解析传入的URI,并确定应该执行哪种操作。 private static final UriMatcher mMatcher; //NotesDatabaseHelper实类,用来操作SQLite数据库,负责创建、更新和查询数据库。 private NotesDatabaseHelper mHelper; //标签,输出日志时用来表示是该类发出的消息 private static final String TAG = "NotesProvider"; //6个URI的匹配码,用于区分不同的URI类型 private static final int URI_NOTE = 1; private static final int URI_NOTE_ITEM = 2; private static final int URI_DATA = 3; private static final int URI_DATA_ITEM = 4; private static final int URI_SEARCH = 5; private static final int URI_SEARCH_SUGGEST = 6; //进一步定义了URI匹配规则和搜索查询的投影 //功能概述: //初始化了一个UriMatcher对象mMatcher,并添加了一系列的URI匹配规则。 //解读: static { //创建了一个UriMatcher实例,并设置默认匹配码为NO_MATCH,表示如果没有任何URI匹配,则返回这个码。 mMatcher = new UriMatcher(UriMatcher.NO_MATCH); //添加规则,当URI的authority为Notes.AUTHORITY,路径为note时,返回匹配码URI_NOTE。 mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); //添加规则,当URI的authority为Notes.AUTHORITY,路径为note/后跟一个数字(#代表数字)时,返回匹配码URI_NOTE_ITEM。 mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); //和上面两句同理,但用于匹配数据相关的URI mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); //用于匹配搜索相关的URI mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); //这两行用于匹配搜索建议相关的URI mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); } /** * x'0A' represents the '\n' character in sqlite. For title and content in the search result, * we will trim '\n' and white space in order to show more information. */ //功能概述: //一个 SQL 查询的投影部分,用于定义查询返回的结果集中应该包含哪些列。 //解读:(每行对应) //返回笔记的 ID。 //笔记的 ID 也被重命名为 SUGGEST_COLUMN_INTENT_EXTRA_DATA,这通常用于 Android 的搜索建议中,作为传递给相关 Intent 的额外数据。 //对 SNIPPET 列的处理:首先使用 REPLACE 函数将 x'0A'(即换行符 \n)替换为空字符串,然后使用 TRIM 函数删除前后的空白字符,处理后的结果分别重命名为 SUGGEST_COLUMN_TEXT_1 //对 SNIPPET 列的处理:首先使用 REPLACE 函数将 x'0A'(即换行符 \n)替换为空字符串,然后使用 TRIM 函数删除前后的空白字符,处理后的结果分别重命名为 SUGGEST_COLUMN_TEXT_2 //返回一个用于搜索建议图标的资源 ID,并命名为 SUGGEST_COLUMN_ICON_1。 //返回一个固定的 Intent 动作 ACTION_VIEW,并命名为 SUGGEST_COLUMN_INTENT_ACTION。 //返回一个内容类型,并命名为 SUGGEST_COLUMN_INTENT_DATA。 private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," //返回笔记的 ID + 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 查询语句,用于从 TABLE.NOTE 表中检索信息 //解读: // 使用上面定义的投影来选择数据。 // 并指定从哪个表中选择数据。 //WHERE子句包含三个条件: // ①搜索 SNIPPET 列中包含特定模式的行(? 是一个占位符,实际查询时会用具体的值替换)。 // ②父ID不为回收站的ID:排除那些父 ID 为回收站的行。 // ③只选择类型为note(标签)的行。 private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION + " FROM " + TABLE.NOTE + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; //重写onCreate方法: //getContext() 方法被调用以获取当前组件的上下文(Context),以便 NotesDatabaseHelper 能够访问应用程序的资源和其他功能 //mHelper用于存储从 NotesDatabaseHelper.getInstance 方法返回的实例。这样,该实例就可以在整个组件的其他方法中被访问和使用。 @Override public boolean onCreate() { mHelper = NotesDatabaseHelper.getInstance(getContext()); return true; } //功能:查询数据 @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { //初始化变量: //Cursor对象 c,用来存储查询结果 //使用 NotesDatabaseHelper 的实例 mHelper来获取一个可读的数据库实例 //定义一个字符串id,用来存储从URI中解析出的ID Cursor c = null; SQLiteDatabase db = mHelper.getReadableDatabase(); String id = null; //根据匹配不同的URI来进行不同的查询 switch (mMatcher.match(uri)) { // URI_NOTE:查询整个 NOTE 表。 // URI_NOTE_ITEM:查询 NOTE 表中的特定项。ID 从 URI 的路径段中获取,并添加到查询条件中。 // URI_DATA:查询整个 DATA 表。 // URI_DATA_ITEM:查询 DATA 表中的特定项。ID 的获取和处理方式与 URI_NOTE_ITEM 相同。 case URI_NOTE: c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder); 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; //URI_SEARCH 和 URI_SEARCH_SUGGEST:处理搜索查询。 // 代码首先检查是否提供了不应与搜索查询一起使用的参数(如 sortOrder, selection, selectionArgs, 或 projection)。 // 如果提供了这些参数,则抛出一个 IllegalArgumentException。 // 根据 URI 类型,从 URI 的路径段或查询参数中获取搜索字符串 searchString。 // 如果 searchString 为空或无效,则返回 null,表示没有搜索结果。 case URI_SEARCH: case URI_SEARCH_SUGGEST: if (sortOrder != null || projection != null) { 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"); } if (TextUtils.isEmpty(searchString)) { return null; } //字符串格式化:格式化后的字符串就会是 "%s%",即包含s是任何文本 //然后执行原始SQL查询 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()); } break; //未知URI处理: default: throw new IllegalArgumentException("Unknown URI " + uri); } //如果查询结果不为空(即 Cursor 对象 c 不是 null),则为其设置一个通知 URI。 //这意味着当与这个 URI 关联的数据发生变化时,任何注册了监听这个 URI 的 ContentObserver 都会被通知。 if (c != null) { c.setNotificationUri(getContext().getContentResolver(), uri); } return c; } //功能:插入数据 //参数:Uri 用来标识要插入数据的表,ContentValues对象包含要插入的键值对 @Override public Uri insert(Uri uri, ContentValues values) { //获取数据库 //三个长整型变量,分别用来存储数据项ID、便签ID 和插入行的ID SQLiteDatabase db = mHelper.getWritableDatabase(); long dataId = 0, noteId = 0, insertedId = 0; //对于 URI_NOTE,将values插入到 TABLE.NOTE 表中,并返回插入行的 ID。 //对于 URI_DATA,首先检查values是否包含 DataColumns.NOTE_ID,如果包含,则获取其值。如果不包含,记录一条日志信息。然后,将 values 插入到 TABLE.DATA 表中,并返回插入行的 ID。 //如果 uri 不是已知的 URI 类型,则抛出一个 IllegalArgumentException。 switch (mMatcher.match(uri)) { case URI_NOTE: insertedId = noteId = db.insert(TABLE.NOTE, null, values); 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; default: throw new IllegalArgumentException("Unknown URI " + uri); } //功能:通知变化 //如果noteId 或 dataId 大于 0(即成功插入了数据),则使用 ContentResolver 的 notifyChange 方法通知监听这些 URI 的观察者,告知数据已经改变。 //ContentUris.withAppendedId 方法用于在基本 URI 后面追加一个 ID,形成完整的 URI。 // Notify the note uri if (noteId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); } // Notify the data uri if (dataId > 0) { getContext().getContentResolver().notifyChange( ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); } //返回包含新插入数据项ID 的 Uri。允许调用者知道新插入的数据项的位置 return ContentUris.withAppendedId(uri, insertedId); } //功能:删除数据项 //参数:uri:标识要删除数据的表或数据项。 selection:一个可选的 WHERE 子句,用于指定删除条件。 selectionArgs:一个可选的字符串数组,用于替换 selection 中的占位符 @Override public int delete(Uri uri, String selection, String[] selectionArgs) { //count:记录被删除的行数。 //id:用于存储从 URI 中解析出的数据项 ID。 //db:可写的数据库对象,用于执行删除操作。 //deleteData:一个布尔值,用于标记是否删除了 DATA 表中的数据。 int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean deleteData = false; switch (mMatcher.match(uri)) { //URI_NOTE: 修改 selection 语句:确保只删除 ID 大于 0 的笔记。然后执行删除操作并返回被删除的行数。 //URI_NOTE_ITEM: 从 URI 中解析出 ID。检查 ID 是否小于等于 0,如果是,则不执行删除操作;否则执行删除操作并返回被删除的行数 //URI_DATA: 执行删除操作并返回被删除的行数。设置 deleteData 为 true,表示删除了 DATA 表中的数据。 //URI_DATA_ITEM: 先从 URI 中解析出 ID,然后执行删除操作并返回被删除的行数,并设置 deleteData 为 true,表示删除了 DATA 表中的数据。 case URI_NOTE: selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; count = db.delete(TABLE.NOTE, selection, selectionArgs); 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; } count = db.delete(TABLE.NOTE, NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); break; case URI_DATA: count = db.delete(TABLE.DATA, selection, selectionArgs); deleteData = true; break; case URI_DATA_ITEM: id = uri.getPathSegments().get(1); count = db.delete(TABLE.DATA, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); deleteData = true; break; default: throw new IllegalArgumentException("Unknown URI " + uri); } //如果 count 大于 0,说明有数据被删除。 //如果 deleteData 为 true,则通知监听 Notes.CONTENT_NOTE_URI 的观察者,数据已改变。 //通知监听传入 uri 的观察者数据已改变。 if (count > 0) { if (deleteData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); } getContext().getContentResolver().notifyChange(uri, null); } return count; } //功能:更新数据库的数据 //参数:uri:标识要更新数据的表或数据项。 values:一个包含新值的键值对集合。 // selection:一个可选的 WHERE 子句,用于指定更新条件。 selectionArgs:一个可选的字符串数组,用于替换 selection 中的占位符。 @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { //count:记录被更新的行数。 //id:用于存储从 URI 中解析出的数据项 ID。 //db:可写的 SQLite 数据库对象,用于执行更新操作。 //updateData:用于标记是否更新了 data 表中的数据。 int count = 0; String id = null; SQLiteDatabase db = mHelper.getWritableDatabase(); boolean updateData = false; switch (mMatcher.match(uri)) { //URI_NOTE:调用 increaseNoteVersion 方法(用于增加便签版本),然后在note表执行更新操作并返回被更新的行数。 //URI_NOTE_ITEM:从 URI 中解析出 ID,并调用 increaseNoteVersion 方法,传入解析出的 ID,最后在note表执行更新操作并返回被更新的行数。 //URI_DATA:在data表执行更新操作并返回被更新的行数。设置 updateData 为 true,表示更新了 DATA 表中的数据。 //URI_DATA_ITEM:从 URI 中解析出 ID。执行更新操作并返回被更新的行数。置 updateData 为 true,表示更新了 DATA 表中的数据。 case URI_NOTE: increaseNoteVersion(-1, selection, selectionArgs); count = db.update(TABLE.NOTE, values, selection, selectionArgs); 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); break; case URI_DATA: count = db.update(TABLE.DATA, values, selection, selectionArgs); updateData = true; break; case URI_DATA_ITEM: id = uri.getPathSegments().get(1); count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); updateData = true; break; default: throw new IllegalArgumentException("Unknown URI " + uri); } //如果 count 大于 0,说明有数据被更新。 //如果 updateData 为 true,则通知监听 Notes.CONTENT_NOTE_URI 的观察者数据已改变。 //通知监听传入 uri 的观察者数据已改变。 if (count > 0) { if (updateData) { getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); } getContext().getContentResolver().notifyChange(uri, null); } return count; } //解析传入的条件语句:一个 SQL WHERE 子句的一部分 private String parseSelection(String selection) { return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); } //更新note表的version列,将其值增加 1。 private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); sql.append(TABLE.NOTE); sql.append(" SET "); sql.append(NoteColumns.VERSION); sql.append("=" + NoteColumns.VERSION + "+1 "); 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); } sql.append(selectString); } mHelper.getWritableDatabase().execSQL(sql.toString()); } @Override public String getType(Uri uri) { // TODO Auto-generated method stub return null; } }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 { // 数据库帮助类,用于管理名为 note.db 的 SQLite 数据库。 // 它继承自 SQLiteOpenHelper 类,这是 Android提供的一个方便的工具类,用于管理数据库的创建和版本更新. // 数据库的基本信息;数据库名称和版本信息(在创建实例对象时会用到) private static final String DB_NAME = "note.db"; private static final int DB_VERSION = 4; //内部接口:个人理解为两个表名,一个note,一个data public interface TABLE { public static final String NOTE = "note"; public static final String DATA = "data"; } //一个标签,方便日志输出时识别出信息来自哪里 private static final String TAG = "NotesDatabaseHelper"; //静态所有变量,提供一个全局访问点来获取数据库辅助类的唯一实例,使得在应用的任何地方都可以方便地使用它 private static NotesDatabaseHelper mInstance; /* 以下都是一些SQL语句,辅助我们来对数据库进行操作 */ //创建note表的语句,这里的NoteColumns就是我们刚刚在Notes中定义的一个接口,里面定义了一系列静态的数据库表中的列名 private static final String CREATE_NOTE_TABLE_SQL = "CREATE TABLE " + TABLE.NOTE + "(" + NoteColumns.ID + " INTEGER PRIMARY KEY," + NoteColumns.PARENT_ID + " 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," + 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 ''," + 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 { // 数据库帮助类,用于管理名为 note.db 的 SQLite 数据库。 // 它继承自 SQLiteOpenHelper 类,这是 Android提供的一个方便的工具类,用于管理数据库的创建和版本更新. // 数据库的基本信息;数据库名称和版本信息(在创建实例对象时会用到) private static final String DB_NAME = "note.db"; private static final int DB_VERSION = 4; //内部接口:个人理解为两个表名,一个note,一个data public interface TABLE { public static final String NOTE = "note"; public static final String DATA = "data"; } //一个标签,方便日志输出时识别出信息来自哪里 private static final String TAG = "NotesDatabaseHelper"; //静态所有变量,提供一个全局访问点来获取数据库辅助类的唯一实例,使得在应用的任何地方都可以方便地使用它 private static NotesDatabaseHelper mInstance; /* 以下都是一些SQL语句,辅助我们来对数据库进行操作 */ //创建note表的语句,这里的NoteColumns就是我们刚刚在Notes中定义的一个接口,里面定义了一系列静态的数据库表中的列名 private static final String CREATE_NOTE_TABLE_SQL = "CREATE TABLE " + TABLE.NOTE + "(" + NoteColumns.ID + " INTEGER PRIMARY KEY," + NoteColumns.PARENT_ID + " 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," + 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 ''," + NoteColupackage 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 { // 用于处理联系人信息 // 实现了从联系人数据库中获取指定电话号码对应的联系人姓名的功能 //sContactCache:用于缓存电话号码和对应的联系人姓名 //TAG:用于日志输出的标识 private static HashMap sContactCache; private static final String TAG = "Contact"; //SQL查询条件( WHERE 后面的语句),用于从联系人数据库中筛选出与给定电话号码匹配的联系人。 private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + " AND " + Data.RAW_CONTACT_ID + " IN " + "(SELECT raw_contact_id " + " FROM phone_lookup" + " WHERE min_match = '+')"; //功能简介:用于从Android设备的联系人数据库中获取与给定电话号码对应的联系人姓名。 //参数:Context对象:用于访问系统服务和应用资源 phoneNumber:需要查询的联系人电话号码 public static String getContact(Context context, String phoneNumber) { // 没映射表就建表,有就查缓存中有没有这个联系人 if(sContactCache == null) { sContactCache = new HashMap(); } if(sContactCache.containsKey(phoneNumber)) { return sContactCache.get(phoneNumber); } //缓存没有,就查询数据库 //构造一个SQL查询条件:CALLER_ID_SELECTION中的"+"被替换为电话号码的最小匹配值 //然后执行查询语句 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); //判断查询结果: //查询结果不为空,且能够移动到第一条记录: // 那么就尝试从Cursor中获取联系人姓名,并将其存入缓存sContactCache。然后返回联系人姓名。 // 异常情况:如果在获取字符串时发生数组越界异常,则记录一个错误日志并返回null。 // 最后都要确保关闭Cursor对象,以避免内存泄漏。 //如果查询结果为空或者没有记录可以移动到(即没有找到匹配的联系人): // 则记录一条调试日志并返回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; } } } package net.micode.notes.gtask.data; // 定义了 `Task` 类所在的包。 import android.database.Cursor; import android.text.TextUtils; import android.util.Log; // 导入 Android SDK 中必要的类: // - `Cursor`:用于访问数据库中的数据。 // - `TextUtils`:用于文本处理的工具类。 // - `Log`:用于记录日志消息。 import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.gtask.exception.ActionFailureException; import net.micode.notes.tool.GTaskStringUtils; // 导入与笔记和任务处理相关的自定义类和常量,包括异常处理和工具方法。 import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; // 导入与 JSON 相关的类,用于处理 JSON 数据结构。 ## Task 类定义 public class Task extends Node { // 声明 `Task` 类,该类是 `Node` 的子类。这表示 `Task` 继承了 `Node` 的属性和方法。 ``` ## 类变量 private static final String TAG = Task.class.getSimpleName(); // - 一个常量字符串,用于记录日志,表示 `Task` 类的名称。 private boolean mCompleted; private String mNotes; private JSONObject mMetaInfo; private Task mPriorSibling; private TaskList mParent; // 声明实例变量: // - `mCompleted`:布尔值,跟踪任务是否已完成。 // - `mNotes`:字符串,用于保存与任务关联的笔记。 // - `mMetaInfo`:`JSONObject`,存储有关任务的元数据。 // - `mPriorSibling`:引用同级中的前一个 `Task`。 // - `mParent`:引用父级 `TaskList`。 ## 构造函数 public Task() { // `Task` 类的构造函数,用于初始化新的任务对象。 super(); // 调用父类 `Node` 的构造函数,确保父类的属性也被初始化。 mCompleted = false; mNotes = null; mPriorSibling = null; mParent = null; mMetaInfo = null; // 初始化实例变量: // - 将 `mCompleted` 设置为 `false`,表示任务未完成。 // - `mNotes`、`mPriorSibling`、`mParent` 和 `mMetaInfo` 初始化为 `null`。 ## 方法:getCreateAction public JSONObject getCreateAction(int actionId) { // 定义一个方法,用于生成一个 JSON 对象,描述创建任务的操作。 JSONObject js = new JSONObject(); // 创建一个新的 JSON 对象,用于存储任务的创建信息。 try { // 开始一个 `try` 块,以处理可能出现的 `JSONException`。 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); // 向 JSON 对象中添加操作类型,表示这是一个创建操作。 js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); // 设置操作 ID,以便跟踪此操作。 js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); // 将当前任务在父任务列表中的索引添加到 JSON 对象中。 JSONObject entity = new JSONObject(); // 创建一个新的 JSON 对象,用于存储与任务实体相关的信息。 entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_TASK); // 将任务名称、创建者 ID 和实体类型添加到实体 JSON 对象中。 if (getNotes() != null) { entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); } // 如果任务有笔记,则将其添加到实体 JSON 对象中。 js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 将实体 JSON 对象添加到主 JSON package net.micode.notes.gtask.data; 定义了 Task 类所在的包。 import android.database.Cursor; import android.text.TextUtils; import android.util.Log; 导入 Android SDK 中必要的类: Cursor:用于访问数据库中的数据。 TextUtils:用于文本处理的工具类。 Log:用于记录日志消息。 import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.gtask.exception.ActionFailureException; import net.micode.notes.tool.GTaskStringUtils; 导入与笔记和任务处理相关的自定义类和常量,包括异常处理和工具方法。 import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; 导入与 JSON 相关的类,用于处理 JSON 数据结构 private static final String TAG = Task.class.getSimpleName(); 一个常量字符串,用于记录日志,表示 Task 类的名称。 private boolean mCompleted; private String mNotes; private JSONObject mMetaInfo; private Task mPriorSibling; private TaskList mParent; 声明实例变量: mCompleted:布尔值,跟踪任务是否已完成。 mNotes:字符串,用于保存与任务关联的笔记。 mMetaInfo:JSONObject,存储有关任务的元数据。 mPriorSibling:引用同级中的前一个 Task。 mParent:引用父级 TaskList。 mCompleted = false; mNotes = null; mPriorSibling = null; mParent = null; mMetaInfo = null; 初始化实例变量: 将 mCompleted 设置为 false,表示任务未完成。 mNotes、mPriorSibling、mParent 和 mMetaInfo 初始化为 null。 try { 开始一个 try 块,以处理可能出现的 JSONException。 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); 向 JSON 对象中添加操作类型,表示这是一个创建操作 js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); 设置操作 ID,以便跟踪此操作。 js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); 将当前任务在父任务列表中的索引添加到 JSON 对象中 JSONObject entity = new JSONObject(); 创建一个新的 JSON 对象,用于存储与任务实体相关的信息。 entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_TASK); 将任务名称、创建者 ID 和实体类型添加到实体 JSON 对象中。 if (getNotes() != null) { entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); } 如果任务有笔记,则将其添加到实体 JSON 对象中。 js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); 将实体 JSON 对象添加到主 JSON 对象中。 js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); 添加父任务列表的 ID。 js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, GTaskStringUtils.GTASK_JSON_TYPE_GROUP); 设置目标父级类型为组。 js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); 添加任务列表的 ID。 if (mPriorSibling != null) { js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); } 如果有前一个兄弟任务,将其 ID 添加到 JSON 对象中。 } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("fail to generate task-create jsonobject"); } 捕获 JSON 相关异常,记录错误并抛出一个 ActionFailureException。 return js; 返回构建的 JSON 对象,表示创建任务的操作。 public JSONObject getUpdateAction(int actionId) { 定义一个方法,用于生成一个 JSON 对象,描述更新任务的操作。 JSONObject js = new JSONObject(); 创建新的 JSON 对象,用于存储任务的更新信息。 try { 开始一个 try 块以处理可能的异常。 js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); 向 JSON 对象添加操作类型,表示这是一个更新操作。 js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); 设置操作 ID。 js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); 添加任务的 ID。 JSONObject entity = new JSONObject(); 创建一个新的 JSON 对象,存储任务的实体更新信息。 entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); 将任务名称添加到实体 JSON 对象中。 if (getNotes() != null) { entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); } 如果有笔记,将其添加到实体 JSON 对象中。 entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); 将任务的删除状态添加到实体 JSON 对象中。 js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); 将实体 JSON 对象添加到主 JSON 对象中。 } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("fail to generate task-update jsonobject"); } 捕获 JSON 相关异常,记录错误并抛出一个 ActionFailureException。 return js; 返回构建的 JSON 对象,表示更新任务的操作。 方法:setContentByRemoteJSON public void setContentByRemoteJSON(JSONObject js) { 定义一个方法,用于通过远程 JSON 对象设置任务内容。 if (js != null) { 检查传入的 JSON 对象是否为 null。 try { 开始一个 try 块以处理异常。 if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); } 如果 JSON 对象中有任务 ID,则设置任务的 ID。 if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); } 如果有最后修改时间,则设置任务的最后修改时间。 if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); } 如果 JSON 对象中有名称,设置任务名称。 if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); } 如果有笔记,则设置任务笔记。 if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); } 如果有删除状态,则设置该状态。 if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); } 如果有完成状态,则设置该状态。 } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); throw new ActionFailureException("fail to get task content from jsonobject"); } 捕获 JSON 相关异常,记录错误并抛出一个 ActionFailureException。 方法:setContentByLocalJSON public void setContentByLocalJSON(JSONObject js) { 定义一个方法,用于通过本地 JSON 对象设置任务内容。 if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) || !js.has(GTaskStringUtils.META_HEAD_DATA)) { Log.w(TAG, "setContentByLocalJSON: nothing is available"); } 如果 JSON 对象为 null 或缺少 "note" 和 "data",记录警告。 try { JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); 从 JSON 对象中提取 "note" 和 "data"。 if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { Log.e(TAG, "invalid type"); return; } 检查笔记类型是否有效,如果无效则记录错误并返回。 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { setName(data.getString(DataColumns.CONTENT)); break; } } 遍历数据数组,获取 MIME 类型为 "note" 的数据,设置任务名称。 } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); } 捕获 JSON 相关异常,记录错误信息。 方法:getLocalJSONFromContent public JSONObject getLocalJSONFromContent() { 定义一个方法,用于生成描述任务内容的本地 JSON 对象。 String name = getName(); 获取任务名称。 try { if (mMetaInfo == null) { 开始一个 try 块,并检查元信息是否为 null。 if (name == null) { Log.w(TAG, "the note seems to be an empty one"); return null; } 如果任务名称为空,记录警告并返回 null。 JSONObject js = new JSONObject(); JSONObject note = new JSONObject(); JSONArray dataArray = new JSONArray(); JSONObject data = new JSONObject(); data.put(DataColumns.CONTENT, name); dataArray.put(data); js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); js.put(GTaskStringUtils.META_HEAD_NOTE, note); return js; 创建一个新的 JSON 对象,构建笔记信息并返回。 } else { JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); 否则,从元信息中提取笔记和数据数组。 for (int i = 0; i < dataArray.length(); i++) { JSONObject data = dataArray.getJSONObject(i); if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { data.put(DataColumns.CONTENT, getName()); break; } } 遍历数据数组,找到 MIME 类型为 "note" 的数据,并更新其内容为任务名称。 note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); return mMetaInfo; 设置笔记类型并返回元信息的 JSON 对象。 } catch (JSONException e) { Log.e(TAG, e.toString()); e.printStackTrace(); return null; } 捕获 JSON 相关异常,记录错误并返回 public abstract class NoteWidgetProvider extends AppWidgetProvider { 声明一个名为 NoteWidgetProvider 的抽象类。它继承自 AppWidgetProvider,用于实现 Android 小部件功能的基础类。由于是抽象类,意味着它可以包含未实现的方法,这些方法将在子类中定义。 public static final String [] PROJECTION = new String [] { NoteColumns.ID, NoteColumns.BG_COLOR_ID, NoteColumns.SNIPPET }; 定义一个字符串数组 PROJECTION,包含 SQL 查询时需要的列名。这些列来自 NoteColumns 类,包括笔记的 ID、背景颜色 ID 和内容片段。 public static final int COLUMN_ID = 0; public static final int COLUMN_BG_COLOR_ID = 1; public static final int COLUMN_SNIPPET = 2; 定义常量,用于表示查询结果中列的索引。COLUMN_ID对应第一列(ID),COLUMN_BG_COLOR_ID对应第二列(背景颜色),COLUMN_SNIPPET对应第三列(内容片段)。 private static final String TAG = "NoteWidgetProvider"; 定义一个用于日志记录的常量 TAG,其值为类名 "NoteWidgetProvider"。在日志中使用此标签时可以明确指出日志来自该类。 @Override public void onDeleted(Context context, int[] appWidgetIds) { 重写 AppWidgetProvider 类的 onDeleted 方法。该方法在小部件被删除时调用,提供上下文信息和小部件 ID 数组。 ContentValues values = new ContentValues(); 创建一个 ContentValues 对象 values,用于存储要更新的数据库字段及其值。 values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); 向 values 对象中添加一对键值对,设置 WIDGET_ID 字段的值为无效小部件 ID。这表示当前小部件的 ID 已无效,将在数据库中进行相应更新。 for (int i = 0; i < appWidgetIds.length; i++) { 使用 for 循环遍历 appWidgetIds 数组中的每个小部件 ID。 context.getContentResolver().update(Notes.CONTENT_NOTE_URI, values, NoteColumns.WIDGET_ID + "=?", new String[] { String.valueOf(appWidgetIds[i])}); 使用 ContentResolver 更新 Notes.CONTENT_NOTE_URI(笔记内容 URI)的记录,将小部件 ID 更新为无效值。查询条件是 WIDGET_ID 等于当前 ID。 } } 结束 for 循环和 onDeleted 方法的定义。 private Cursor getNoteWidgetInfo(Context context, int widgetId) { 定义一个私有方法 getNoteWidgetInfo,接收上下文和小部件 ID 作为参数。该方法返回与指定小部件 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); } 使用 ContentResolver 查询 Notes.CONTENT_NOTE_URI,返回与指定 widgetId 相关的笔记信息。PROJECTION 指定需要的列,查询条件是 WIDGET_ID 匹配传入的小部件 ID,并且 PARENT_ID 不等于回收站 ID。 protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { 定义一个受保护的方法 update,用于更新指定的小部件。该方法接受上下文、小部件管理器和小部件 ID 数组作为参数。 update(context, appWidgetManager, appWidgetIds, false); } 调用私有方法 update 的另一个重载版本,将隐私模式参数设置为 false,以进行更新操作。 private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, boolean privacyMode) { 定义一个私有方法,重载 update 方法,接收上下文、小部件管理器、小部件 ID 数组和隐私模式作为参数。 for (int i = 0; i < appWidgetIds.length; i++) { 使用 for 循环遍历小部件 ID 数组。 if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { 检查当前小部件 ID 是否有效(不等于无效小部件 ID)。 int bgId = ResourceParser.getDefaultBgId(context); package net.micode.notes.data; // 导入SearchManager类,用于管理搜索功能 import android.app.SearchManager; // 导入ContentProvider类,用于实现数据的访问接口 import android.content.ContentProvider; // 导入ContentUris类,提供了一些用于处理URI的方法 import android.content.ContentUris; // 导入ContentValues类,用于存储键值对,通常用于插入或更新数据库记录 import android.content.ContentValues; // 导入Intent类,用于启动Activity、Service或发送广播 import android.content.Intent; // 导入UriMatcher类,用于匹配URI,以便根据不同的URI执行不同的操作 import android.content.UriMatcher; // 导入Cursor类,用于从数据库查询结果集中获取数据 import android.database.Cursor; // 导入SQLiteDatabase类,用于访问SQLite数据库 import android.database.sqlite.SQLiteDatabase; // 导入Uri类,用于表示统一资源标识符 import android.net.Uri; // 导入TextUtils类,提供了一些文本处理的工具方法 import android.text.TextUtils; // 导入Log类,用于日志输出 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; public class NotesProvider extends ContentProvider { //NotesProvider的主要功能是作为一个内容提供者,为其他应用程序或组件提供对“Notes”数据的访问。 //它允许其他应用程序查询、插入、更新或删除标签数据。 //通过URI匹配,NotesProvider能够区分对哪种数据类型的请求(例如,单独的标签、标签的数据、文件夹操作等),并执行相应的操作。 //用于匹配不同URI的UriMatcher对象,通常用于解析传入的URI,并确定应该执行哪种操作。 private static final UriMatcher mMatcher; //NotesDatabaseHelper实类,用来操作SQLite数据库,负责创建、更新和查询数据库。 private NotesDatabaseHelper mHelper; //标签,输出日志时用来表示是该类发出的消息 private static final String TAG = "NotesProvider"; //6个URI的匹配码,用于区分不同的URI类型 private static final int URI_NOTE = 1; private static final int URI_NOTE_ITEM = 2; private static final int URI_DATA = 3; private static final int URI_DATA_ITEM = 4; private static final int URI_SEARCH = 5; private static final int URI_SEARCH_SUGGEST = 6; //进一步定义了URI匹配规则和搜索查询的投影 //功能概述: //初始化了一个UriMatcher对象mMatcher,并添加了一系列的URI匹配规则。 //解读: static { //创建了一个UriMatcher实例,并设置默认匹配码为NO_MATCH,表示如果没有任何URI匹配,则返回这个码。 mMatcher = new UriMatcher(UriMatcher.NO_MATCH); //添加规则,当URI的authority为Notes.AUTHORITY,路径为note时,返回匹配码URI_NOTE。 mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); //添加规则,当URI的authority为Notes.AUTHORITY,路径为note/后跟一个数字(#代表数字)时,返回匹配码URI_NOTE_ITEM。 mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); //和上面两句同理,但用于匹配数据相关的URI mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); //用于匹配搜索相关的URI mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); //这两行用于匹配搜索建议相关的URI mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); }