diff --git a/doc/小米便签开源代码的泛读报告.docx b/doc/小米便签开源代码的泛读报告.docx new file mode 100644 index 0000000..2057d9b Binary files /dev/null and b/doc/小米便签开源代码的泛读报告.docx differ diff --git a/doc/陈静静运行截图.jpg b/doc/陈静静运行截图.jpg new file mode 100644 index 0000000..a0cf678 Binary files /dev/null and b/doc/陈静静运行截图.jpg differ diff --git a/src/zhushi/backuputilsjava.txt.txt b/src/zhushi/backuputilsjava.txt.txt new file mode 100644 index 0000000..016284f --- /dev/null +++ b/src/zhushi/backuputilsjava.txt.txt @@ -0,0 +1,490 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.tool; + +import android.content.Context; +import android.database.Cursor; +import android.os.Environment; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.util.Log; + +import net.micode.notes.R; +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 java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; + + +public class BackupUtils { + // 定义一个常量,用于打印日志 + private static final String TAG = "BackupUtils"; + // Singleton stuff + // 定义一个静态变量,用于保存BackupUtils的实例 + private static BackupUtils sInstance; + + // 获取BackupUtils实例 + public static synchronized BackupUtils getInstance(Context context) { + // 如果sInstance为空,则创建一个新的BackupUtils实例 + if (sInstance == null) { + sInstance = new BackupUtils(context); + } + // 返回sInstance实例 + return sInstance; + } + + /** + * Following states are signs to represents backup or restore + * status + */ + // Currently, the sdcard is not mounted + // 定义一个常量,表示SD卡未挂载的状态 + public static final int STATE_SD_CARD_UNMOUONTED = 0; + // The backup file not exist +// 定义一个常量,表示备份文件不存在 + public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; + // The data is not well formated, may be changed by other programs + // 定义一个常量,表示数据被销毁的状态 + public static final int STATE_DATA_DESTROIED = 2; + // Some run-time exception which causes restore or backup fails + // 定义一个常量,表示系统错误状态 + public static final int STATE_SYSTEM_ERROR = 3; + // Backup or restore success + // 定义一个常量,表示成功状态 + public static final int STATE_SUCCESS = 4; + + // 声明一个TextExport类型的变量mTextExport + private TextExport mTextExport; + +// 定义一个BackupUtils类,构造函数接收一个Context参数 + private BackupUtils(Context context) { + // 创建一个TextExport对象,传入Context参数 + mTextExport = new TextExport(context); + } + + // 检查外部存储是否可用 + private static boolean externalStorageAvailable() { + // 返回外部存储的状态是否为MEDIA_MOUNTED + return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); + } + + // 导出为文本文件 + public int exportToText() { + // 调用mTextExport对象的exportToText方法 + return mTextExport.exportToText(); + } + + // 获取导出的文本文件名 + public String getExportedTextFileName() { + // 返回mTextExport对象的mFileName属性 + return mTextExport.mFileName; + } + + // 获取导出的文本文件目录 + public String getExportedTextFileDir() { + // 返回mTextExport对象的mFileDirectory属性 + return mTextExport.mFileDirectory; + } + +// 定义一个静态内部类TextExport + private static class TextExport { + // 定义一个常量数组NOTE_PROJECTION,包含NoteColumns中的ID、MODIFIED_DATE、SNIPPET、TYPE四个字段 + private static final String[] NOTE_PROJECTION = { +// 获取NoteColumns中的ID、MODIFIED_DATE、SNIPPET字段 + // NoteColumns.ID 表示Note表的ID列 + NoteColumns.ID, +// 添加注释 + NoteColumns.MODIFIED_DATE, // 修改日期 +// 定义NoteColumns类中的SNIPPET常量 + NoteColumns.SNIPPET, + // 定义一个常量,表示NoteColumns中的TYPE列 + NoteColumns.TYPE + }; + + // 定义一个常量,表示笔记的ID列 + private static final int NOTE_COLUMN_ID = 0; + + // 定义一个常量,表示笔记的修改日期列 + private static final int NOTE_COLUMN_MODIFIED_DATE = 1; + + // 定义一个常量,表示笔记的摘要列 + private static final int NOTE_COLUMN_SNIPPET = 2; + +// 定义一个常量数组,用于存储数据投影 + private static final String[] DATA_PROJECTION = { + // 数据列 + DataColumns.CONTENT, + // 数据列的MIME类型 + DataColumns.MIME_TYPE, + // 数据列1 + DataColumns.DATA1, + DataColumns.DATA2, + DataColumns.DATA3, + DataColumns.DATA4, + }; + +// 定义一个常量,表示数据列的内容 + private static final int DATA_COLUMN_CONTENT = 0; + + // 定义一个常量,表示数据列的MIME类型 + private static final int DATA_COLUMN_MIME_TYPE = 1; + +// 定义一个常量,表示数据列的调用日期 + private static final int DATA_COLUMN_CALL_DATE = 2; + +// 定义一个常量,表示数据列中的电话号码列的索引 + private static final int DATA_COLUMN_PHONE_NUMBER = 4; + +// 声明一个字符串数组,用于存储文本格式 + private final String [] TEXT_FORMAT; +// 定义一个常量,表示文件夹名称的格式 + private static final int FORMAT_FOLDER_NAME = 0; + // 定义常量,表示日期格式 + private static final int FORMAT_NOTE_DATE = 1; + // 定义常量,表示格式化笔记内容的格式 + private static final int FORMAT_NOTE_CONTENT = 2; + +// 定义一个Context类型的成员变量 + private Context mContext; +// 声明一个字符串类型的私有变量mFileName + private String mFileName; + // 文件目录 + private String mFileDirectory; + +// 构造函数,用于创建TextExport对象 + public TextExport(Context context) { +// 获取导出笔记的格式数组 + TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); + // 保存上下文 + mContext = context; + // 初始化文件名为空字符串 + mFileName = ""; + // 初始化文件目录为空字符串 + mFileDirectory = ""; + } + +// 根据id获取格式 + private String getFormat(int id) { +// 返回TEXT_FORMAT数组中索引为id的元素 + return TEXT_FORMAT[id]; + } + + /** +// 导出由folder id标识的文件夹到文本 + * Export the folder identified by folder id to text + */ +// 将文件夹导出到文本文件 + private void exportFolderToText(String folderId, PrintStream ps) { + // Query notes belong to this folder +// 获取指定文件夹下的所有笔记 + Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, +// 定义一个常量,用于查询Note表中的数据 + NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { +// 定义一个变量folderId,用于存储文件夹的ID + folderId +// 定义一个无符号长整型变量 + }, null); + +// 判断notesCursor是否为空 + if (notesCursor != null) { +// 如果notesCursor指向的数据表中有数据 + if (notesCursor.moveToFirst()) { + do { + // Print note's last modified date +// 打印日期 + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( +// 获取字符串资源,格式为"yyyy-MM-dd HH:mm" + mContext.getString(R.string.format_datetime_mdhm), +// 获取notesCursor中NOTE_COLUMN_MODIFIED_DATE列的值,并将其转换为long类型 + notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); + // Query data belong to this note +// 从notesCursor中获取NOTE_COLUMN_ID列的值,并将其赋值给noteId变量 + String noteId = notesCursor.getString(NOTE_COLUMN_ID); +// 导出笔记到文本 + exportNoteToText(noteId, ps); +// 使用while循环遍历notesCursor中的数据 + } while (notesCursor.moveToNext()); + } +// 关闭游标 + notesCursor.close(); + } + } + + /** + * Export note identified by id to a print stream + */ +// 将笔记导出到文本文件 + private void exportNoteToText(String noteId, PrintStream ps) { + // 获取Notes表中的数据 + Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, + // 查询数据,指定投影为DATA_PROJECTION,条件为DataColumns.NOTE_ID等于noteId,不指定排序 + DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { + noteId + }, null); + + // 如果dataCursor不为空 + if (dataCursor != null) { +// 如果dataCursor可以移动到第一行 + if (dataCursor.moveToFirst()) { +// do 关键字用于开始一个循环,循环体内的代码会一直执行,直到遇到 break 关键字 + do { + // 获取数据的光标 + String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); +// 判断mimeType是否等于DataConstants.CALL_NOTE + if (DataConstants.CALL_NOTE.equals(mimeType)) { + // Print phone number +// 从dataCursor中获取电话号码 + String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); +// 获取dataCursor中DATA_COLUMN_CALL_DATE列的值,并将其赋值给callDate变量 + long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); + // 获取数据光标中指定列的字符串值 + String location = dataCursor.getString(DATA_COLUMN_CONTENT); + +// 判断phoneNumber是否为空 + if (!TextUtils.isEmpty(phoneNumber)) { +// 打印格式化后的字符串 + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + phoneNumber)); + } + // Print call date +// 打印格式化后的字符串 + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat + // 格式化日期 + .format(mContext.getString(R.string.format_datetime_mdhm), + callDate))); + // Print call attachment location +// 判断location是否为空 + if (!TextUtils.isEmpty(location)) { + // 使用String.format方法将location格式化为指定格式,并打印输出 + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + location)); + } + } else if (DataConstants.NOTE.equals(mimeType)) { + // 获取数据内容 + String content = dataCursor.getString(DATA_COLUMN_CONTENT); + // 如果内容不为空 + if (!TextUtils.isEmpty(content)) { + // 打印格式化后的内容 + ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), + content)); + } + } +// 如果dataCursor可以移动到下一个位置,则继续循环 + } while (dataCursor.moveToNext()); + } + dataCursor.close(); + } + // print a line separator between note + try { + // 向ps写入一个换行符和一个字母数字字符 + ps.write(new byte[] { + Character.LINE_SEPARATOR, Character.LETTER_NUMBER + }); + } catch (IOException e) { + // 捕获IOException异常,并打印错误日志 + Log.e(TAG, e.toString()); + } + } + + /** + * Note will be exported as text which is user readable + */ + public int exportToText() { + // 检查外部存储是否可用 + if (!externalStorageAvailable()) { + // 如果外部存储不可用,则输出日志信息 + Log.d(TAG, "Media was not mounted"); + // 返回SD卡未挂载状态 + return STATE_SD_CARD_UNMOUONTED; + } + + // 获取导出到文本的PrintStream + PrintStream ps = getExportToTextPrintStream(); + // 如果获取失败 + if (ps == null) { + // 打印错误日志 + Log.e(TAG, "get print stream error"); + // 返回系统错误状态 + return STATE_SYSTEM_ERROR; + } + // First export folder and its notes +// 查询数据库,获取文件夹和通话记录文件夹的信息 + Cursor folderCursor = mContext.getContentResolver().query( + // 定义一个常量,表示笔记的URI + Notes.CONTENT_NOTE_URI, + // 声明一个名为NOTE_PROJECTION的常量 + NOTE_PROJECTION, + "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " + + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " + + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); + +// 如果folderCursor不为空 + if (folderCursor != null) { +// 如果游标指向的数据集不为空 + if (folderCursor.moveToFirst()) { + do { + // Print folder's name + // 声明一个字符串变量folderName,用于存储文件夹名称 + String folderName = ""; +// 如果文件夹游标中的ID等于Notes中的ID_CALL_RECORD_FOLDER,则将文件夹名称设置为上下文中的call_record_folder_name字符串 + if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { + folderName = mContext.getString(R.string.call_record_folder_name); +// 如果文件夹名称为空,则从游标中获取文件夹名称 + } else { + folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); + } +// 判断folderName是否为空 + if (!TextUtils.isEmpty(folderName)) { + // 使用String.format方法将folderName格式化为指定格式,并输出到ps流中 + ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); + } + // 获取文件夹ID + String folderId = folderCursor.getString(NOTE_COLUMN_ID); + // 将文件夹导出到文本 + exportFolderToText(folderId, ps); + // 移动到下一个文件夹 + } while (folderCursor.moveToNext()); + } +// 关闭文件夹游标 + folderCursor.close(); + } + + // Export notes in root's folder +// 获取一个游标,查询Notes表中type为Notes.TYPE_NOTE且parent_id为0的数据 Cursor noteCursor = mContext.getContentResolver().query( + Cursor noteCursor = mContext.getContentResolver().query( +// 定义一个Notes类,其中包含一个CONTENT_NOTE_URI常量,用于表示Notes表的URI + Notes.CONTENT_NOTE_URI, + // 定义一个常量,用于表示投影 + NOTE_PROJECTION, + // 拼接查询条件,查询类型为Notes.TYPE_NOTE且父ID为NoteColumns.PARENT_ID的记录 + NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + + "=0", null, null); + + // 如果noteCursor不为空 + if (noteCursor != null) { +// 如果游标移动到第一行 + if (noteCursor.moveToFirst()) { + do { + // 打印笔记的修改日期 + ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( + mContext.getString(R.string.format_datetime_mdhm), + noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); + // Query data belong to this note + // 获取当前游标中的noteId + String noteId = noteCursor.getString(NOTE_COLUMN_ID); + // 将noteId导出到文本中 + exportNoteToText(noteId, ps); + // 如果游标还有下一个元素,则继续循环 + } while (noteCursor.moveToNext()); + } +// 关闭游标 + noteCursor.close(); + } +// 关闭ps + ps.close(); + +// 返回成功状态 + return STATE_SUCCESS; + } + + /** + * Get a print stream pointed to the file {@generateExportedTextFile} + */ + private PrintStream getExportToTextPrintStream() { + // 生成一个挂载在SD卡上的文件 + File file = generateFileMountedOnSDcard(mContext, R.string.file_path, + R.string.file_name_txt_format); + // 如果文件生成失败,则返回null + if (file == null) { + Log.e(TAG, "create file to exported failed"); + return null; + } + // 获取文件名 + mFileName = file.getName(); + // 获取文件路径 + mFileDirectory = mContext.getString(R.string.file_path); + PrintStream ps = null; + try { + // 创建文件输出流 + FileOutputStream fos = new FileOutputStream(file); + // 创建PrintStream对象 + ps = new PrintStream(fos); + } catch (FileNotFoundException e) { + // 文件未找到异常,打印异常信息 + e.printStackTrace(); + return null; + } catch (NullPointerException e) { + // 空指针异常,打印异常信息 + e.printStackTrace(); + return null; + } + // 返回PrintStream对象 + return ps; + } + } + + /** + * Generate the text file to store imported data + */ +// 生成一个挂载在SD卡上的文件 + private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { + // 创建一个StringBuilder对象 + StringBuilder sb = new StringBuilder(); + // 将SD卡的根目录添加到StringBuilder中 + sb.append(Environment.getExternalStorageDirectory()); + // 将文件路径字符串添加到StringBuilder中 + sb.append(context.getString(filePathResId)); + // 创建一个File对象,表示文件路径 + File filedir = new File(sb.toString()); + // 将文件名格式字符串添加到StringBuilder中 + sb.append(context.getString( + fileNameFormatResId, + // 使用当前时间格式化文件名 + DateFormat.format(context.getString(R.string.format_date_ymd), + System.currentTimeMillis()))); + // 创建一个File对象,表示文件名 + File file = new File(sb.toString()); + + try { + // 如果文件路径不存在,则创建文件夹 + if (!filedir.exists()) { + filedir.mkdir(); + } + // 如果文件不存在,则创建文件 + if (!file.exists()) { + file.createNewFile(); + } + // 返回文件对象 + return file; + } catch (SecurityException e) { + // 捕获SecurityException异常,并打印异常信息 + e.printStackTrace(); + } catch (IOException e) { + // 捕获IOException异常,并打印异常信息 + e.printStackTrace(); + } + + // 返回null + return null; + } +} \ No newline at end of file diff --git a/src/zhushi/contactjava.txt.txt b/src/zhushi/contactjava.txt.txt new file mode 100644 index 0000000..42ff46d --- /dev/null +++ b/src/zhushi/contactjava.txt.txt @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.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"; + + // 定义一个常量,用于查询联系人信息的SQL语句 + 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 = '+')"; + + // 根据传入的上下文和电话号码获取联系人信息 + public static String getContact(Context context, String phoneNumber) { + // 如果联系人缓存为空,则创建一个新的HashMap + if(sContactCache == null) { + sContactCache = new HashMap(); + } + +// 如果sContactCache中包含phoneNumber键 + if(sContactCache.containsKey(phoneNumber)) { + // 返回sContactCache中phoneNumber键对应的值 + return sContactCache.get(phoneNumber); + } + + // 将电话号码转换为Caller ID的最小匹配格式,并替换掉"+"号 + String selection = CALLER_ID_SELECTION.replace("+", + PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + // 通过ContentResolver查询Data表,获取电话号码对应的联系人姓名 + 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 get string error " + e.toString()); + // 返回null + return null; + } finally { + // 关闭游标 + cursor.close(); + } + } else { + // 没有匹配的联系人 + Log.d(TAG, "No contact matched with number:" + phoneNumber); + // 返回null + return null; + } + } +} \ No newline at end of file diff --git a/src/zhushi/notesjava.txt.txt b/src/zhushi/notesjava.txt.txt new file mode 100644 index 0000000..b512782 --- /dev/null +++ b/src/zhushi/notesjava.txt.txt @@ -0,0 +1,152 @@ +// 定义一个常量,表示原始父级ID + public static final String ORIGIN_PARENT_ID = "origin_parent_id"; + + /** + * The gtask id + *

Type : TEXT

+ */ +// 定义一个常量,用于存储任务ID + public static final String GTASK_ID = "gtask_id"; + + /** + * The version code + *

Type : INTEGER (long)

+ */ + // 定义一个常量,表示版本号 + public static final String VERSION = "version"; + } + + // 定义一个接口DataColumns + public interface DataColumns { + /** + * The unique ID for a row + *

Type: INTEGER (long)

+ */ +// 定义一个常量,表示ID + public static final String ID = "_id"; + + /** + * The MIME type of the item represented by this row. + *

Type: Text

+ */ + // 定义一个常量,表示MIME类型 + public static final String MIME_TYPE = "mime_type"; + + /** + * The reference id to note that this data belongs to + *

Type: INTEGER (long)

+ */ +// 定义一个常量,表示笔记的ID + public static final String NOTE_ID = "note_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"; + + /** + * Data's content + *

Type: TEXT

+ */ + // 定义一个常量CONTENT,值为"content" + public static final String CONTENT = "content"; + + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * integer data type + *

Type: INTEGER

+ */ + // 定义一个常量DATA1,值为"data1" +// 定义一个常量DATA1,值为"data1" + public static final String DATA1 = "data1"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * integer data type + *

Type: INTEGER

+ */ + // 定义一个常量DATA2,值为"data2" + public static final String DATA2 = "data2"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * TEXT data type + *

Type: TEXT

+ */ + // 定义一个常量DATA3,值为"data3" + public static final String DATA3 = "data3"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * TEXT data type + *

Type: TEXT

+ */ +// 定义一个常量DATA4,值为"data4" + public static final String DATA4 = "data4"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * TEXT data type + *

Type: TEXT

+ */ + // 定义一个常量DATA5,值为"data5" + public static final String DATA5 = "data5"; + } + + // 实现DataColumns接口的TextNote类 + 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"; + + // 定义一个常量,表示内容URI + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); + } + + // 定义一个名为CallNote的公共静态内部类,实现DataColumns接口 + 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"; + + // 定义一个常量,表示内容URI + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); + } +} \ No newline at end of file diff --git a/src/zhushi/notesproviderjava.txt.txt b/src/zhushi/notesproviderjava.txt.txt new file mode 100644 index 0000000..ee33f27 --- /dev/null +++ b/src/zhushi/notesproviderjava.txt.txt @@ -0,0 +1,437 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.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; + + +public class NotesProvider extends ContentProvider { + // 定义一个UriMatcher对象,用于匹配Uri + private static final UriMatcher mMatcher; + + // 定义一个NotesDatabaseHelper对象,用于操作数据库 + private NotesDatabaseHelper mHelper; + +// 定义一个常量,用于标识NotesProvider + private static final String TAG = "NotesProvider"; + +// 定义一个常量,用于标识URI_NOTE + private static final int URI_NOTE = 1; +// 定义一个常量,用于标识URI_NOTE_ITEM + private static final int URI_NOTE_ITEM = 2; +// 定义一个常量,用于标识URI_DATA + private static final int URI_DATA = 3; +// 定义一个常量,用于标识URI_DATA_ITEM + private static final int URI_DATA_ITEM = 4; + +// 定义一个常量,用于标识URI_SEARCH + private static final int URI_SEARCH = 5; +// 定义一个常量,用于标识URI_SEARCH_SUGGEST + private static final int URI_SEARCH_SUGGEST = 6; + +// 静态代码块,用于初始化UriMatcher对象 + static { + // 创建UriMatcher对象,NO_MATCH表示没有匹配的URI + mMatcher = new UriMatcher(UriMatcher.NO_MATCH); + // 添加URI匹配规则,匹配Notes.AUTHORITY下的note,匹配成功返回URI_NOTE + mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); + // 添加URI匹配规则,匹配Notes.AUTHORITY下的note/#,匹配成功返回URI_NOTE_ITEM + mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); + // 添加URI匹配规则,匹配Notes.AUTHORITY下的data,匹配成功返回URI_DATA + mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); + // 添加URI匹配规则,匹配Notes.AUTHORITY下的data/#,匹配成功返回URI_DATA_ITEM + mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); + // 添加URI匹配规则,匹配Notes.AUTHORITY下的search,匹配成功返回URI_SEARCH + mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); + // 添加URI匹配规则,匹配Notes.AUTHORITY下的search/*,匹配成功返回URI_SEARCH_SUGGEST + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); + // 添加URI匹配规则,匹配Notes.AUTHORITY下的search/*/*,匹配成功返回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. + */ +// 定义一个常量,用于搜索笔记的投影 + private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," + // 将笔记的ID作为搜索建议的额外数据 + + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," + // 将笔记的摘要作为搜索建议的文本1 + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," + // 将笔记的摘要作为搜索建议的文本2 + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," + // 将搜索结果的图标设置为drawable/search_result + + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," + // 将搜索建议的意图动作设置为Intent.ACTION_VIEW + + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + // 将搜索建议的意图数据设置为Notes.TextNote.CONTENT_TYPE + + "'" + 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 + // 笔记的父ID不等于垃圾桶文件夹 + + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; + + @Override + // 重写onCreate方法 + public boolean onCreate() { + // 获取NotesDatabaseHelper的实例 + mHelper = NotesDatabaseHelper.getInstance(getContext()); + // 返回true + return true; + } + + @Override + // 重写query方法,用于查询数据 + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + // 声明一个Cursor对象 + Cursor c = null; + // 获取可读的数据库 + SQLiteDatabase db = mHelper.getReadableDatabase(); + // 声明一个id变量 + String id = null; + // 根据传入的uri,执行不同的数据库查询操作 + switch (mMatcher.match(uri)) { + case URI_NOTE: + // 查询note表 + c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, + sortOrder); + break; + case URI_NOTE_ITEM: + // 查询note表中指定id的记录 + id = uri.getPathSegments().get(1); + c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder); + break; + case URI_DATA: + // 查询data表 + c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, + sortOrder); + break; + case URI_DATA_ITEM: + // 查询data表中指定id的记录 + id = uri.getPathSegments().get(1); +// 查询数据库中指定表的数据,返回一个Cursor对象 + c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder); + break; + // 搜索URI + case URI_SEARCH: + // 搜索建议URI + case URI_SEARCH_SUGGEST: + // 查询搜索结果 +// 如果sortOrder不为空或者projection不为空,则抛出异常 + if (sortOrder != null || projection != null) { +// 抛出IllegalArgumentException异常,异常信息为:"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query" + throw new IllegalArgumentException( + "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); + } + + // 获取搜索字符串 + String searchString = null; + // 如果匹配到URI_SEARCH_SUGGEST + if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { + // 如果URI的路径段大于1 + if (uri.getPathSegments().size() > 1) { + // 获取路径段的第二个元素作为搜索字符串 + searchString = uri.getPathSegments().get(1); + } + } else { + // 获取uri中的查询参数pattern + searchString = uri.getQueryParameter("pattern"); + } + + // 如果查询参数为空,则返回null + 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, "got exception: " + ex.toString()); + } + break; + default: + // 抛出异常,表示未知的URI + throw new IllegalArgumentException("Unknown URI " + uri); + } + // 如果结果集不为空,则设置通知URI + if (c != null) { + c.setNotificationUri(getContext().getContentResolver(), uri); + } + // 返回结果集 + return c; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + // 获取可写的数据库 + SQLiteDatabase db = mHelper.getWritableDatabase(); + // 定义变量,用于存储插入的数据ID + long dataId = 0, noteId = 0, insertedId = 0; + // 根据传入的URI进行匹配 + switch (mMatcher.match(uri)) { + // 如果匹配到URI_NOTE + case URI_NOTE: + // 在NOTE表中插入数据,并获取插入的数据ID + insertedId = noteId = db.insert(TABLE.NOTE, null, values); + break; + // 如果匹配到URI_DATA + case URI_DATA: + // 如果values中包含NOTE_ID + if (values.containsKey(DataColumns.NOTE_ID)) { + // 获取NOTE_ID + noteId = values.getAsLong(DataColumns.NOTE_ID); + } else { + // 否则,打印错误日志 + Log.d(TAG, "Wrong data format without note id:" + values.toString()); + } + // 在DATA表中插入数据,并获取插入的数据ID + insertedId = dataId = db.insert(TABLE.DATA, null, values); + break; + // 如果没有匹配到任何URI + 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); + } + + // Notify the data uri + if (dataId > 0) { + getContext().getContentResolver().notifyChange( + ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); + } + + return ContentUris.withAppendedId(uri, insertedId); + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + // 初始化删除的记录数 + int count = 0; + // 初始化ID + String id = null; + // 获取可写的数据库 + SQLiteDatabase db = mHelper.getWritableDatabase(); + // 初始化删除数据标志 + boolean deleteData = false; + // 根据URI匹配不同的删除操作 + switch (mMatcher.match(uri)) { + // 删除笔记 + case URI_NOTE: + // 添加删除条件 + selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; + // 执行删除操作 + count = db.delete(TABLE.NOTE, selection, selectionArgs); + break; + // 删除笔记项 + case URI_NOTE_ITEM: + // 获取笔记项的ID + id = uri.getPathSegments().get(1); + /** + * ID that smaller than 0 is system folder which is not allowed to + * trash + */ + // 将id转换为long类型 + long noteId = Long.valueOf(id); + // 如果noteId小于等于0,则跳出循环 + if (noteId <= 0) { + break; + } + // 删除指定id的note + // 删除指定ID的笔记 + count = db.delete(TABLE.NOTE, + NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); + break; + // 处理URI_DATA + case URI_DATA: + // 删除数据 + count = db.delete(TABLE.DATA, selection, selectionArgs); + // 标记删除数据 + deleteData = true; + break; +// 删除数据项 + case URI_DATA_ITEM: + // 获取数据项的id + id = uri.getPathSegments().get(1); + // 删除数据项 + count = db.delete(TABLE.DATA, + DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); + // 标记删除数据 + deleteData = true; + break; +// 未知URI + 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); + } +// 返回删除的条数 + return count; + } + + // 重写update方法,用于更新数据库中的数据 + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + // 定义一个计数器,用于记录更新的数据条数 + int count = 0; + // 定义一个字符串,用于存储要更新的数据的id + String id = null; + // 获取可写的数据库 + SQLiteDatabase db = mHelper.getWritableDatabase(); + // 定义一个布尔值,用于判断是否更新了数据 + boolean updateData = false; + // 根据传入的uri进行匹配,根据匹配结果执行不同的操作 + switch (mMatcher.match(uri)) { + case URI_NOTE: + // 增加note的版本号 + increaseNoteVersion(-1, selection, selectionArgs); + // 更新note表 + count = db.update(TABLE.NOTE, values, selection, selectionArgs); + break; + case URI_NOTE_ITEM: + // 获取uri中的id + id = uri.getPathSegments().get(1); + // 增加note的版本号 + increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); + // 更新note表 + count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs); + break; + case URI_DATA: + // 更新data表 + count = db.update(TABLE.DATA, values, selection, selectionArgs); + // 标记更新data + updateData = true; + break; + case URI_DATA_ITEM: + // 获取uri中的id + id = uri.getPathSegments().get(1); + // 更新data表 + count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs); + // 标记更新data + updateData = true; + break; + default: + // 抛出异常,未知uri + throw new IllegalArgumentException("Unknown URI " + uri); + } + +// 如果count大于0 + if (count > 0) { + // 如果updateData为true + if (updateData) { + // 通知ContentResolver更新Notes.CONTENT_NOTE_URI + getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); + } + // 通知ContentResolver更新uri + getContext().getContentResolver().notifyChange(uri, null); + } +// 返回count + return count; + } + +// 解析selection字符串 + private String parseSelection(String selection) { + // 如果selection不为空 + return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); + } + + private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { + // 创建一个StringBuilder对象,用于拼接SQL语句 + StringBuilder sql = new StringBuilder(120); + // 拼接UPDATE语句 + sql.append("UPDATE "); + // 拼接表名 + sql.append(TABLE.NOTE); + // 拼接SET语句 + sql.append(" SET "); + // 拼接版本号字段 + sql.append(NoteColumns.VERSION); + // 拼接版本号增加1的语句 + sql.append("=" + NoteColumns.VERSION + "+1 "); + + // 如果id大于0或者selection不为空,则拼接WHERE语句 + if (id > 0 || !TextUtils.isEmpty(selection)) { + sql.append(" WHERE "); + } + // 如果id大于0,则拼接id等于指定值的语句 + if (id > 0) { + sql.append(NoteColumns.ID + "=" + String.valueOf(id)); + } + // 判断selection是否为空 + if (!TextUtils.isEmpty(selection)) { + // 如果id大于0,则调用parseSelection方法解析selection,否则直接赋值给selectString + String selectString = id > 0 ? parseSelection(selection) : selection; + // 遍历selectionArgs数组,将每个元素替换selectString中的第一个问号 + for (String args : selectionArgs) { + selectString = selectString.replaceFirst("\\?", args); + } + // 将selectString追加到sql中 + sql.append(selectString); + } + +// 获取可写的数据库 + mHelper.getWritableDatabase().execSQL(sql.toString()); + } + + @Override + public String getType(Uri uri) { + // 获取Uri的类型 + // TODO Auto-generated method stub \ No newline at end of file