You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Harry/BackupUtils.java

403 lines
16 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* 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";
// 单例实例
private static BackupUtils sInstance;
/**
* 获取BackupUtils的单例实例
* @param context 上下文对象
* @return BackupUtils实例
*/
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
return sInstance;
}
/**
* 以下状态码用于表示备份或还原操作的状态
*/
// 当前SD卡未挂载
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// 备份文件不存在
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;
/**
* 私有构造函数,初始化文本导出器
* @param context 上下文对象
*/
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
/**
* 检查外部存储是否可用(已挂载)
* @return 如果外部存储已挂载则返回true否则返回false
*/
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
/**
* 将所有笔记导出为文本格式
* @return 导出状态码
*/
public int exportToText() {
return mTextExport.exportToText();
}
/**
* 获取导出的文本文件名
* @return 导出的文件名
*/
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
/**
* 获取导出的文本文件目录
* @return 导出的文件目录路径
*/
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
/**
* 文本导出内部类 - 负责将笔记数据导出为可读的文本格式
*/
private static class TextExport {
// 笔记查询投影字段ID、修改日期、摘要、类型
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET,
NoteColumns.TYPE
};
private static final int NOTE_COLUMN_ID = 0; // ID列索引
private static final int NOTE_COLUMN_MODIFIED_DATE = 1; // 修改日期列索引
private static final int NOTE_COLUMN_SNIPPET = 2; // 摘要列索引
// 数据查询投影字段内容、MIME类型、DATA1-DATA4
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
private static final int DATA_COLUMN_CONTENT = 0; // 内容列索引
private static final int DATA_COLUMN_MIME_TYPE = 1; // MIME类型列索引
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; // 笔记内容格式索引
private Context mContext; // 上下文对象
private String mFileName; // 导出的文件名
private String mFileDirectory; // 导出的文件目录
/**
* 构造函数,初始化文本导出器
* @param context 上下文对象
*/
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
/**
* 获取指定索引的格式化模板
* @param id 格式索引
* @return 格式化字符串模板
*/
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
/**
* 将指定文件夹及其中的笔记导出为文本格式
* @param folderId 文件夹ID
* @param ps 打印流对象,用于写入文本
*/
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);
if (notesCursor != null) {
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))));
// 查询属于此笔记的数据
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
/**
* 将指定ID的笔记导出为文本并写入打印流
* @param noteId 笔记ID
* @param ps 打印流对象,用于写入文本
*/
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);
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
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));
}
} 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());
}
dataCursor.close();
}
// 在笔记之间打印分隔符
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
/**
* 将笔记导出为用户可读的文本格式
* 包括文件夹笔记和根目录下的笔记
* @return 导出状态码
*/
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;
}
// 首先导出文件夹及其包含的笔记
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_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));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close();
}
// 导出根目录下的笔记
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=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))));
// 查询属于此笔记的数据
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
noteCursor.close();
}
ps.close();
return STATE_SUCCESS;
}
/**
* 获取指向导出文件的打印流
* 文件位置由{@link #generateFileMountedOnSDcard}方法生成
* @return 打印流对象如果创建失败则返回null
*/
private PrintStream getExportToTextPrintStream() {
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
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);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();
return null;
}
return ps;
}
}
/**
* 生成用于存储导出数据的文本文件
* 文件位于SD卡的指定目录下文件名包含日期信息
* @param context 上下文对象
* @param filePathResId 文件路径的资源ID
* @param fileNameFormatResId 文件名格式的资源ID
* @return 生成的文件对象如果创建失败则返回null
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
// 构建文件目录路径
StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory());
sb.append(context.getString(filePathResId));
File filedir = new File(sb.toString());
// 构建完整文件路径(包含文件名和日期)
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
File file = new File(sb.toString());
try {
// 如果目录不存在,则创建目录
if (!filedir.exists()) {
filedir.mkdir();
}
// 如果文件不存在,则创建新文件
if (!file.exists()) {
file.createNewFile();
}
return file;
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}