|
|
@ -47,23 +47,28 @@ public class BackupUtils {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return sInstance;
|
|
|
|
return sInstance;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* `TAG` 是一个 String 常量,用于标识日志输出的 tag。
|
|
|
|
|
|
|
|
`sInstance` 是一个静态变量,用于存储单例实例。
|
|
|
|
|
|
|
|
`getInstance()` 是一个静态方法,通过传入一个 `Context` 参数获取 `BackupUtils` 的单例实例。
|
|
|
|
|
|
|
|
这里使用了双重检查锁定来确保线程安全。
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Following states are signs to represents backup or restore
|
|
|
|
* Following states are signs to represents backup or restore(以下是表示备份或恢复的符号)
|
|
|
|
* status
|
|
|
|
* status
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
// Currently, the sdcard is not mounted
|
|
|
|
// Currently, the sdcard is not mounted
|
|
|
|
public static final int STATE_SD_CARD_UNMOUONTED = 0;
|
|
|
|
public static final int STATE_SD_CARD_UNMOUONTED = 0;
|
|
|
|
// The backup file not exist
|
|
|
|
// The backup file not exist(备份文件不存在)
|
|
|
|
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
|
|
|
|
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
|
|
|
|
// The data is not well formated, may be changed by other programs
|
|
|
|
// The data is not well formated, may be changed by other programs(数据格式不正确可能被其他程序更改)
|
|
|
|
public static final int STATE_DATA_DESTROIED = 2;
|
|
|
|
public static final int STATE_DATA_DESTROIED = 2;
|
|
|
|
// Some run-time exception which causes restore or backup fails
|
|
|
|
// Some run-time exception which causes restore or backup fails(导致备份或还原失败的默写运行异常)
|
|
|
|
public static final int STATE_SYSTEM_ERROR = 3;
|
|
|
|
public static final int STATE_SYSTEM_ERROR = 3;
|
|
|
|
// Backup or restore success
|
|
|
|
// Backup or restore success(备份或还原成功)
|
|
|
|
public static final int STATE_SUCCESS = 4;
|
|
|
|
public static final int STATE_SUCCESS = 4;
|
|
|
|
|
|
|
|
|
|
|
|
private TextExport mTextExport;
|
|
|
|
private TextExport mTextExport;//实例化对象
|
|
|
|
|
|
|
|
|
|
|
|
private BackupUtils(Context context) {
|
|
|
|
private BackupUtils(Context context) {
|
|
|
|
mTextExport = new TextExport(context);
|
|
|
|
mTextExport = new TextExport(context);
|
|
|
@ -75,15 +80,15 @@ public class BackupUtils {
|
|
|
|
|
|
|
|
|
|
|
|
public int exportToText() {
|
|
|
|
public int exportToText() {
|
|
|
|
return mTextExport.exportToText();
|
|
|
|
return mTextExport.exportToText();
|
|
|
|
}
|
|
|
|
}//这是一个 public 方法,用于将数据导出为文本,并返回一个整数值。
|
|
|
|
|
|
|
|
|
|
|
|
public String getExportedTextFileName() {
|
|
|
|
public String getExportedTextFileName() {
|
|
|
|
return mTextExport.mFileName;
|
|
|
|
return mTextExport.mFileName;
|
|
|
|
}
|
|
|
|
}//这是一个 public 方法,用于获取导出的文本文件名。
|
|
|
|
|
|
|
|
|
|
|
|
public String getExportedTextFileDir() {
|
|
|
|
public String getExportedTextFileDir() {
|
|
|
|
return mTextExport.mFileDirectory;
|
|
|
|
return mTextExport.mFileDirectory;
|
|
|
|
}
|
|
|
|
}//这是一个 public 方法,用于获取导出的文本文件所在的目录。
|
|
|
|
|
|
|
|
|
|
|
|
private static class TextExport {
|
|
|
|
private static class TextExport {
|
|
|
|
private static final String[] NOTE_PROJECTION = {
|
|
|
|
private static final String[] NOTE_PROJECTION = {
|
|
|
@ -91,7 +96,8 @@ public class BackupUtils {
|
|
|
|
NoteColumns.MODIFIED_DATE,
|
|
|
|
NoteColumns.MODIFIED_DATE,
|
|
|
|
NoteColumns.SNIPPET,
|
|
|
|
NoteColumns.SNIPPET,
|
|
|
|
NoteColumns.TYPE
|
|
|
|
NoteColumns.TYPE
|
|
|
|
};
|
|
|
|
};//这是一个私有的静态内部类,用于完成数据导出的操作。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static final int NOTE_COLUMN_ID = 0;
|
|
|
|
private static final int NOTE_COLUMN_ID = 0;
|
|
|
|
|
|
|
|
|
|
|
@ -115,32 +121,37 @@ public class BackupUtils {
|
|
|
|
private static final int DATA_COLUMN_CALL_DATE = 2;
|
|
|
|
private static final int DATA_COLUMN_CALL_DATE = 2;
|
|
|
|
|
|
|
|
|
|
|
|
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
|
|
|
|
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
|
|
|
|
|
|
|
|
// 定义了多个常量,用于操作笔记和数据的列索引
|
|
|
|
|
|
|
|
|
|
|
|
private final String [] TEXT_FORMAT;
|
|
|
|
private final String [] TEXT_FORMAT;
|
|
|
|
private static final int FORMAT_FOLDER_NAME = 0;
|
|
|
|
private static final int FORMAT_FOLDER_NAME = 0;
|
|
|
|
private static final int FORMAT_NOTE_DATE = 1;
|
|
|
|
private static final int FORMAT_NOTE_DATE = 1;
|
|
|
|
private static final int FORMAT_NOTE_CONTENT = 2;
|
|
|
|
private static final int FORMAT_NOTE_CONTENT = 2;
|
|
|
|
|
|
|
|
// 定义了一个字符串数组 TEXT_FORMAT,其中存储了导出笔记时的文件格式
|
|
|
|
|
|
|
|
|
|
|
|
private Context mContext;
|
|
|
|
private Context mContext;
|
|
|
|
private String mFileName;
|
|
|
|
private String mFileName;
|
|
|
|
private String mFileDirectory;
|
|
|
|
private String mFileDirectory;
|
|
|
|
|
|
|
|
// 定义了 Context mContext、String mFileName、String mFileDirectory 三个变量
|
|
|
|
|
|
|
|
// mContext 存储了当前上下文,mFileName 存储了文件名,mFileDirectory 存储了文件夹路径
|
|
|
|
|
|
|
|
|
|
|
|
public TextExport(Context context) {
|
|
|
|
public TextExport(Context context) {
|
|
|
|
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
|
|
|
|
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
|
|
|
|
mContext = context;
|
|
|
|
mContext = context;
|
|
|
|
mFileName = "";
|
|
|
|
mFileName = "";
|
|
|
|
mFileDirectory = "";
|
|
|
|
mFileDirectory = "";// 构造函数,初始化了 TEXT_FORMAT、mContext、mFileName 和 mFileDirectory
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private String getFormat(int id) {
|
|
|
|
private String getFormat(int id) {
|
|
|
|
return TEXT_FORMAT[id];
|
|
|
|
return TEXT_FORMAT[id];
|
|
|
|
}
|
|
|
|
}// getFormat 方法返回 TEXT_FORMAT 数组中指定 id 的字符串
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Export the folder identified by folder id to text
|
|
|
|
* Export the folder identified by folder id to text
|
|
|
|
|
|
|
|
* (exportFolderToText 方法用于将指定文件夹下的笔记导出到文本中)
|
|
|
|
*/
|
|
|
|
*/
|
|
|
|
private void exportFolderToText(String folderId, PrintStream ps) {
|
|
|
|
private void exportFolderToText(String folderId, PrintStream ps) {
|
|
|
|
// Query notes belong to this folder
|
|
|
|
// Query notes belong to this folder// 查询属于该文件夹的笔记
|
|
|
|
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
|
|
|
|
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
|
|
|
|
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
|
|
|
|
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
|
|
|
|
folderId
|
|
|
|
folderId
|
|
|
@ -149,11 +160,11 @@ public class BackupUtils {
|
|
|
|
if (notesCursor != null) {
|
|
|
|
if (notesCursor != null) {
|
|
|
|
if (notesCursor.moveToFirst()) {
|
|
|
|
if (notesCursor.moveToFirst()) {
|
|
|
|
do {
|
|
|
|
do {
|
|
|
|
// Print note's last modified date
|
|
|
|
// Print note's last modified date // 输出笔记的最后修改日期
|
|
|
|
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
|
|
|
|
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
|
|
|
|
mContext.getString(R.string.format_datetime_mdhm),
|
|
|
|
mContext.getString(R.string.format_datetime_mdhm),
|
|
|
|
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
|
|
|
|
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
|
|
|
|
// Query data belong to this note
|
|
|
|
// Query data belong to this note // 查询属于该笔记的数据
|
|
|
|
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
|
|
|
|
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
|
|
|
|
exportNoteToText(noteId, ps);
|
|
|
|
exportNoteToText(noteId, ps);
|
|
|
|
} while (notesCursor.moveToNext());
|
|
|
|
} while (notesCursor.moveToNext());
|
|
|
@ -169,14 +180,14 @@ public class BackupUtils {
|
|
|
|
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
|
|
|
|
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
|
|
|
|
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
|
|
|
|
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
|
|
|
|
noteId
|
|
|
|
noteId
|
|
|
|
}, null);
|
|
|
|
}, null);// exportNoteToText 方法用于将指定笔记的数据导出到文本中
|
|
|
|
|
|
|
|
|
|
|
|
if (dataCursor != null) {
|
|
|
|
if (dataCursor != null) {
|
|
|
|
if (dataCursor.moveToFirst()) {
|
|
|
|
if (dataCursor.moveToFirst()) {
|
|
|
|
do {
|
|
|
|
do {
|
|
|
|
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
|
|
|
|
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
|
|
|
|
if (DataConstants.CALL_NOTE.equals(mimeType)) {
|
|
|
|
if (DataConstants.CALL_NOTE.equals(mimeType)) {
|
|
|
|
// Print phone number
|
|
|
|
// Print phone number // 输出电话号码
|
|
|
|
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
|
|
|
|
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
|
|
|
|
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
|
|
|
|
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
|
|
|
|
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
|
|
|
|
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
|
|
|
@ -185,11 +196,11 @@ public class BackupUtils {
|
|
|
|
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
|
|
|
|
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
|
|
|
|
phoneNumber));
|
|
|
|
phoneNumber));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Print call date
|
|
|
|
// Print call date// 输出通话日期
|
|
|
|
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
|
|
|
|
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
|
|
|
|
.format(mContext.getString(R.string.format_datetime_mdhm),
|
|
|
|
.format(mContext.getString(R.string.format_datetime_mdhm),
|
|
|
|
callDate)));
|
|
|
|
callDate)));
|
|
|
|
// Print call attachment location
|
|
|
|
// Print call attachment location // 输出通话附件位置
|
|
|
|
if (!TextUtils.isEmpty(location)) {
|
|
|
|
if (!TextUtils.isEmpty(location)) {
|
|
|
|
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
|
|
|
|
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
|
|
|
|
location));
|
|
|
|
location));
|
|
|
@ -205,7 +216,7 @@ public class BackupUtils {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
dataCursor.close();
|
|
|
|
dataCursor.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// print a line separator between note
|
|
|
|
// print a line separator between note// 在导出每个笔记后输出一个分隔符
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
ps.write(new byte[] {
|
|
|
|
ps.write(new byte[] {
|
|
|
|
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
|
|
|
|
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
|
|
|
@ -222,14 +233,20 @@ public class BackupUtils {
|
|
|
|
if (!externalStorageAvailable()) {
|
|
|
|
if (!externalStorageAvailable()) {
|
|
|
|
Log.d(TAG, "Media was not mounted");
|
|
|
|
Log.d(TAG, "Media was not mounted");
|
|
|
|
return STATE_SD_CARD_UNMOUONTED;
|
|
|
|
return STATE_SD_CARD_UNMOUONTED;
|
|
|
|
}
|
|
|
|
}/*这段代码的作用是向指定的PrintStream对象中写入一个分隔符。它使用了Java中的byte数组,其中包含了两个特殊的字符:Character.LINE_SEPARATOR和Character.LETTER_NUMBER。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Character.LINE_SEPARATOR表示平台的行分隔符,其值因平台而异。例如,在Windows中,它的值是"\r\n",在Unix/Linux中,它的值是"\n"。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Character.LETTER_NUMBER是一个没有实际意义的字符,它只是被用来作为分隔符的一部分。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
当这个byte数组被写入PrintStream对象时,它会在文本中插入一个分隔符,以便在文本中区分不同的笔记。如果写入过程中发生IOException,那么会在Logcat中输出相应的错误信息。*/
|
|
|
|
|
|
|
|
|
|
|
|
PrintStream ps = getExportToTextPrintStream();
|
|
|
|
PrintStream ps = getExportToTextPrintStream();
|
|
|
|
if (ps == null) {
|
|
|
|
if (ps == null) {
|
|
|
|
Log.e(TAG, "get print stream error");
|
|
|
|
Log.e(TAG, "get print stream error");
|
|
|
|
return STATE_SYSTEM_ERROR;
|
|
|
|
return STATE_SYSTEM_ERROR;
|
|
|
|
}
|
|
|
|
}//这段代码中的 getExportToTextPrintStream() 是一个自定义方法,它返回一个 PrintStream 对象,该对象用于将笔记数据导出到文本文件中
|
|
|
|
// First export folder and its notes
|
|
|
|
// First export folder and its notes//“首先导出文件夹及其包含的笔记”
|
|
|
|
Cursor folderCursor = mContext.getContentResolver().query(
|
|
|
|
Cursor folderCursor = mContext.getContentResolver().query(
|
|
|
|
Notes.CONTENT_NOTE_URI,
|
|
|
|
Notes.CONTENT_NOTE_URI,
|
|
|
|
NOTE_PROJECTION,
|
|
|
|
NOTE_PROJECTION,
|
|
|
@ -255,7 +272,11 @@ public class BackupUtils {
|
|
|
|
} while (folderCursor.moveToNext());
|
|
|
|
} while (folderCursor.moveToNext());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
folderCursor.close();
|
|
|
|
folderCursor.close();
|
|
|
|
}
|
|
|
|
}/*这段代码的作用是导出所有文件夹及其包含的笔记。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
首先,通过调用 ContentResolver 的 query() 方法查询所有的文件夹和 Call Record 文件夹,同时排除回收站中的笔记,将结果保存在 Cursor 对象 folderCursor 中。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
接着,通过遍历 folderCursor 中的所有记录,获取每个文件夹的名称和ID,并将其写入到输出流 ps 中。如果当前文件夹是 Call Record 文件夹,则使用字符串资源文件中的值作为文件夹的名称*/
|
|
|
|
|
|
|
|
|
|
|
|
// Export notes in root's folder
|
|
|
|
// Export notes in root's folder
|
|
|
|
Cursor noteCursor = mContext.getContentResolver().query(
|
|
|
|
Cursor noteCursor = mContext.getContentResolver().query(
|
|
|
@ -280,7 +301,13 @@ public class BackupUtils {
|
|
|
|
ps.close();
|
|
|
|
ps.close();
|
|
|
|
|
|
|
|
|
|
|
|
return STATE_SUCCESS;
|
|
|
|
return STATE_SUCCESS;
|
|
|
|
}
|
|
|
|
}/*这段代码的作用是导出根文件夹中的所有笔记。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
首先,通过调用 ContentResolver 的 query() 方法查询根文件夹中的所有笔记,并将结果保存在 Cursor 对象 noteCursor 中。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
接着,通过遍历 noteCursor 中的所有记录,获取每个笔记的修改日期,并将其写入到输出流 ps 中。然后,调用 exportNoteToText() 方法,将当前笔记的内容导出到输出流 ps 中。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
最后,关闭输出流 ps,并返回 STATE_SUCCESS 表示导出笔记数据成功。如果 noteCursor 为空,则不会做任何处理,直接关闭输出流并返回成功状态。*/
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Get a print stream pointed to the file {@generateExportedTextFile}
|
|
|
|
* Get a print stream pointed to the file {@generateExportedTextFile}
|
|
|
@ -307,7 +334,15 @@ public class BackupUtils {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ps;
|
|
|
|
return ps;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}/*这段代码的作用是创建一个输出流 PrintStream 对象,用于将笔记数据导出到文本文件中。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
首先,通过调用 generateFileMountedOnSDcard() 方法获取导出文件的路径和名称,并将结果保存在 File 对象 file 中。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
接着,检查 file 是否为 null。如果是,则在Logcat中输出 "create file to exported failed" 的错误信息,并返回 null。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
然后,获取 file 的名称和路径,并创建一个 FileOutputStream 对象 fos,将其作为参数传递给 PrintStream 构造函数,创建一个 PrintStream 对象 ps。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
最后,返回 ps 对象,如果在创建 PrintStream 对象时出现 FileNotFoundException 或 NullPointerException 异常,则返回 null*/
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Generate the text file to store imported data
|
|
|
|
* Generate the text file to store imported data
|
|
|
@ -339,6 +374,14 @@ public class BackupUtils {
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}/*这段代码的作用是生成一个文件对象,并返回该对象的引用。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
首先,获取外部存储设备的根目录,并将其与 filePathResId 参数所指定的路径拼接成一个完整的路径,并将其保存在 StringBuilder 对象 sb 中。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
接着,创建一个 File 对象 filedir,用于表示存储导出文件的目录。如果该目录不存在,则调用 mkdir() 方法创建该目录。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
然后,使用 fileNameFormatResId 参数所指定的文件名格式,将当前日期和时间添加到 sb 中,形成完整的文件路径。最后,创建一个 File 对象 file,用于表示导出的文件。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
最后,检查 file 和 filedir 是否存在。如果它们都存在,则直接返回 file 对象的引用。否则,通过捕捉 SecurityException 和 IOException 异常,输出异常信息,并返回 null。*/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|