/* * 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; // 引入Context类,它是Android应用的全局信息的接口 import android.database.Cursor; // 引入Cursor类,用于遍历数据库查询结果 import android.os.Environment; // 引入Environment类,提供有关Android设备环境的访问 import android.text.TextUtils; // 引入TextUtils类,包含一些静态方法用于处理字符串 import android.text.format.DateFormat; // 引入DateFormat类,用于格式化日期和时间 import android.util.Log; // 引入Log类,用于记录日志信息 import net.micode.notes.R; // 引入R类,它是一个包含应用资源引用的类 import net.micode.notes.data.Notes; // 引入Notes类,它可能是一个包含数据库访问逻辑的类 import net.micode.notes.data.Notes.DataColumns; // 引入DataColumns接口,定义了数据表列名 import net.micode.notes.data.Notes.DataConstants; // 引入DataConstants类,可能包含一些数据相关的常量 import net.micode.notes.data.Notes.NoteColumns; // 引入NoteColumns接口,定义了笔记表列名 import java.io.File; // 引入File类,表示文件和目录路径名的抽象表示形式 import java.io.FileNotFoundException; // 引入FileNotFoundException类,当文件未找到时抛出 import java.io.FileOutputStream; // 引入FileOutputStream类,用于写入文件数据 import java.io.IOException; // 引入IOException类,是输入输出异常的超类 import java.io.PrintStream; // 引入PrintStream类,用于表示打印流的抽象类 // BackupUtils类,用于实现备份工具的功能 public class BackupUtils { private static final String TAG = "BackupUtils"; // 定义日志标签,用于日志输出 // Singleton stuff(单例模式相关代码) private static BackupUtils sInstance; // 定义单例实例变量 // 获取BackupUtils类的实例,使用单例模式确保全局只有一个实例 public static synchronized BackupUtils getInstance(Context context) { if (sInstance == null) { sInstance = new BackupUtils(context); } return sInstance; } // 定义一些常量来表示备份或恢复的状态 public static final int STATE_SD_CARD_UNMOUONTED = 0; // SD卡未挂载 public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; // 备份文件不存在 public static final int STATE_DATA_DESTROIED = 2; // 数据被破坏 public static final int STATE_SYSTEM_ERROR = 3; // 系统错误 public static final int STATE_SUCCESS = 4; // 成功 private TextExport mTextExport; // TextExport类的实例变量,用于导出文本 // 私有构造方法,防止外部直接创建实例,配合单例模式使用 private BackupUtils(Context context) { mTextExport = new TextExport(context); } // 检查外部存储是否可用的方法 private static boolean externalStorageAvailable() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } // 导出数据到文本文件的方法 public int exportToText() { return mTextExport.exportToText(); } // 获取导出的文本文件名的方法 public String getExportedTextFileName() { return mTextExport.mFileName; } // 获取导出的文本文件目录的方法 public String getExportedTextFileDir() { return mTextExport.mFileDirectory; } // TextExport内部类,用于实现将数据导出到文本文件的功能 private static class TextExport { // 定义从Note表中查询数据的列名数组 private static final String[] NOTE_PROJECTION = { NoteColumns.ID, NoteColumns.MODIFIED_DATE, NoteColumns.SNIPPET, NoteColumns.TYPE }; // 定义Note表中各列在NOTE_PROJECTION数组中的索引 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; // 定义从Data表中查询数据的列名数组 private static final String[] DATA_PROJECTION = { DataColumns.CONTENT, DataColumns.MIME_TYPE, DataColumns.DATA1, DataColumns.DATA2, DataColumns.DATA3, DataColumns.DATA4, }; // 定义Data表中各列在DATA_PROJECTION数组中的索引 // 注意:这里有一个错误,应该是DATA_COLUMN_DATA3而不是DATA_COLUMN_CALL_DATE private static final int DATA_COLUMN_CONTENT = 0; private static final int DATA_COLUMN_MIME_TYPE = 1; private static final int DATA_COLUMN_CALL_DATE = 2; // 错误,应为DATA_COLUMN_DATA1或其他正确的索引 private static final int DATA_COLUMN_PHONE_NUMBER = 4; // 错误,索引与DATA_PROJECTION不匹配 // 从资源文件中获取文本格式的数组 private final String [] TEXT_FORMAT; // 定义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; // 导出的文本文件名 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 = ""; } // 根据索引获取TEXT_FORMAT数组中对应格式的方法 private String getFormat(int id) { return TEXT_FORMAT[id]; } /** * Export the folder identified by folder id to text */ private void exportFolderToText(String folderId, PrintStream ps) { // 使用内容解析器查询属于指定文件夹的笔记 Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { folderId }, null); // 检查返回的Cursor是否为null if (notesCursor != null) { // 移动Cursor到第一行,如果Cursor不为空且至少有一行数据 if (notesCursor.moveToFirst()) { do { // 打印笔记的最后修改日期 ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( mContext.getString(R.string.format_datetime_mdhm), notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); // 获取当前笔记的ID String noteId = notesCursor.getString(NOTE_COLUMN_ID); // 调用方法导出指定ID的笔记到文本 exportNoteToText(noteId, ps); } while (notesCursor.moveToNext()); // 循环直到所有笔记都被处理 } // 关闭Cursor notesCursor.close(); } } /** * Export note identified by id to a print stream */ private void exportNoteToText(String noteId, PrintStream ps) { // 使用内容解析器查询指定笔记的数据 Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { noteId }, null); // 检查返回的Cursor是否为null if (dataCursor != null) { // 移动Cursor到第一行,如果Cursor不为空且至少有一行数据 if (dataCursor.moveToFirst()) { do { // 获取当前数据的MIME类型 String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); // 如果MIME类型表示这是一个电话笔记 if (DataConstants.CALL_NOTE.equals(mimeType)) { // 打印电话号码 String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); String location = dataCursor.getString(DATA_COLUMN_CONTENT); // 如果电话号码不为空,则打印电话号码 if (!TextUtils.isEmpty(phoneNumber)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), phoneNumber)); } // 打印通话日期 ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat .format(mContext.getString(R.string.format_datetime_mdhm), callDate))); // 如果位置信息不为空,则打印位置信息 if (!TextUtils.isEmpty(location)) { ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), location)); } } // 如果MIME类型表示这是一个普通笔记 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)); } } } while (dataCursor.moveToNext()); // 循环直到所有笔记数据都被处理 } // 关闭Cursor dataCursor.close(); } // 在每个笔记之后打印一个行分隔符 try { ps.write(new byte[] { Character.LINE_SEPARATOR, // 实际上这里应该是只有行分隔符,后面这个字符可能是误加 // Character.LETTER_NUMBER // 这行应该是错误的,通常我们只需要行分隔符 }); } catch (IOException e) { // 捕获并打印异常 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"); return STATE_SD_CARD_UNMOUONTED; } // 获取用于导出文本的输出流 PrintStream ps = getExportToTextPrintStream(); // 如果无法获取输出流,记录错误日志并返回系统错误状态 if (ps == null) { Log.e(TAG, "get print stream error"); return STATE_SYSTEM_ERROR; } // 首先导出文件夹及其笔记 // 使用ContentResolver查询数据库中的文件夹数据 Cursor folderCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, // 查询的URI 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); // 无排序和选择参数 // 如果查询结果不为空 if (folderCursor != null) { // 移动到查询结果的第一条记录 if (folderCursor.moveToFirst()) { do { // 打印文件夹的名称 String folderName = ""; // 如果是通话记录文件夹,使用特定的字符串 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); } // 如果文件夹名称不为空,则格式化并打印 if (!TextUtils.isEmpty(folderName)) { ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); } // 获取文件夹ID,并导出该文件夹下的所有笔记 String folderId = folderCursor.getString(NOTE_COLUMN_ID); exportFolderToText(folderId, ps); } while (folderCursor.moveToNext()); // 移动到下一条记录 } // 关闭Cursor以释放资源 folderCursor.close(); } // 导出根文件夹中的笔记 // 使用ContentResolver查询数据库中的笔记数据 Cursor noteCursor = mContext.getContentResolver().query( Notes.CONTENT_NOTE_URI, // 查询的URI NOTE_PROJECTION, // 查询的列 NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID // 笔记类型且父ID为0(根文件夹) + "=0", null, null); // 无排序和选择参数 // 如果查询结果不为空 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)))); // 笔记的修改日期 // 获取笔记ID,并导出该笔记的详细数据 String noteId = noteCursor.getString(NOTE_COLUMN_ID); exportNoteToText(noteId, ps); } while (noteCursor.moveToNext()); // 移动到下一条记录 } // 关闭Cursor以释放资源 noteCursor.close(); } // 关闭输出流以释放资源 ps.close(); // 返回成功状态 return STATE_SUCCESS; } /** * Get a print stream pointed to the file defined by generateExportedTextFile. * This method is private and returns a PrintStream object or null if an error occurs. */ private PrintStream getExportToTextPrintStream() { // Call a method to generate a file on the SD card. File file = generateFileMountedOnSDcard(mContext, R.string.file_path, R.string.file_name_txt_format); // If the file generation failed (i.e., file is null), log an error and return null. if (file == null) { Log.e(TAG, "create file to exported failed"); return null; } // Store the file name and directory for later use. mFileName = file.getName(); mFileDirectory = mContext.getString(R.string.file_path); // Initialize a PrintStream object to null. PrintStream ps = null; // Try to create a FileOutputStream for the file and wrap it in a PrintStream. try { FileOutputStream fos = new FileOutputStream(file); ps = new PrintStream(fos); } catch (FileNotFoundException e) { // If the file was not found, log the error and return null. e.printStackTrace(); return null; } catch (NullPointerException e) { // If a NullPointerException occurs (e.g., due to a null file object), log the error and return null. e.printStackTrace(); return null; } // Return the PrintStream object if no errors occurred. return ps; } /** * Generate the text file to store imported data on the SD card. * This method is private and static, returning a File object or null if an error occurs. */ private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { // Create a StringBuilder to build the file path. StringBuilder sb = new StringBuilder(); // Append the external storage directory path. sb.append(Environment.getExternalStorageDirectory()); // Append the directory path from resources. sb.append(context.getString(filePathResId)); // Create a File object for the directory. File filedir = new File(sb.toString()); // Append the formatted file name to the StringBuilder. // The file name includes a date format obtained from resources. sb.append(context.getString( fileNameFormatResId, DateFormat.format(context.getString(R.string.format_date_ymd), System.currentTimeMillis()))); // Create a File object for the full file path. File file = new File(sb.toString()); try { // If the directory does not exist, create it. if (!filedir.exists()) { filedir.mkdir(); } // If the file does not exist, create it. if (!file.exists()) { file.createNewFile(); } // Return the File object if no errors occurred. return file; } catch (SecurityException e) { // If there is a security exception (e.g., no permission to write to external storage), log the error. e.printStackTrace(); } catch (IOException e) { // If an I/O error occurs, log the error. e.printStackTrace(); } // Return null if an error occurred. return null; }