From 3453fe47e3fae4fcc6dff61552f5a1551104ed7d Mon Sep 17 00:00:00 2001 From: pwiz98tyo <1257800485@qq.com> Date: Thu, 12 Jun 2025 17:04:48 +0800 Subject: [PATCH] Update BackupUtils.java --- java/net/micode/notes/tool/BackupUtils.java | 422 ++++++++++++-------- 1 file changed, 245 insertions(+), 177 deletions(-) diff --git a/java/net/micode/notes/tool/BackupUtils.java b/java/net/micode/notes/tool/BackupUtils.java index 39f6ec4..19b4d21 100644 --- a/java/net/micode/notes/tool/BackupUtils.java +++ b/java/net/micode/notes/tool/BackupUtils.java @@ -18,6 +18,7 @@ package net.micode.notes.tool; import android.content.Context; import android.database.Cursor; +import android.os.Build; import android.os.Environment; import android.text.TextUtils; import android.text.format.DateFormat; @@ -29,12 +30,14 @@ import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.NoteColumns; +import java.io.BufferedWriter; import java.io.File; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.io.PrintStream; - +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; public class BackupUtils { private static final String TAG = "BackupUtils"; @@ -62,6 +65,8 @@ public class BackupUtils { public static final int STATE_SYSTEM_ERROR = 3; // Backup or restore success public static final int STATE_SUCCESS = 4; + // Permission denied + public static final int STATE_PERMISSION_DENIED = 5; private TextExport mTextExport; @@ -90,14 +95,15 @@ public class BackupUtils { NoteColumns.ID, NoteColumns.MODIFIED_DATE, NoteColumns.SNIPPET, - NoteColumns.TYPE + NoteColumns.TYPE, + NoteColumns.PARENT_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 int NOTE_COLUMN_TYPE = 3; + private static final int NOTE_COLUMN_PARENT_ID = 4; private static final String[] DATA_PROJECTION = { DataColumns.CONTENT, @@ -109,11 +115,8 @@ public class BackupUtils { }; 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; - private static final int DATA_COLUMN_PHONE_NUMBER = 4; private final String [] TEXT_FORMAT; @@ -124,12 +127,17 @@ public class BackupUtils { private Context mContext; private String mFileName; private String mFileDirectory; + private BufferedWriter mWriter; + private int mNotesCount; + private int mFoldersCount; public TextExport(Context context) { TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); mContext = context; mFileName = ""; mFileDirectory = ""; + mNotesCount = 0; + mFoldersCount = 0; } private String getFormat(int id) { @@ -137,208 +145,268 @@ public class BackupUtils { } /** - * Export the folder identified by folder id to text + * Export notes to text file */ - private void exportFolderToText(String folderId, PrintStream ps) { - // Query notes belong to this folder - Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, - NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { - folderId - }, null); + public int exportToText() { + if (!externalStorageAvailable()) { + Log.d(TAG, "Media was not mounted"); + return STATE_SD_CARD_UNMOUONTED; + } - if (notesCursor != null) { - if (notesCursor.moveToFirst()) { - do { - // Print note's last modified date - ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( - mContext.getString(R.string.format_datetime_mdhm), - notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // Query data belong to this note - String noteId = notesCursor.getString(NOTE_COLUMN_ID); - exportNoteToText(noteId, ps); - } while (notesCursor.moveToNext()); - } - notesCursor.close(); + 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 STATE_SYSTEM_ERROR; + } + + mFileName = file.getName(); + mFileDirectory = mContext.getString(R.string.file_path); + + // 使用try-with-resources确保资源释放 + try (BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8))) { + mWriter = writer; + + // 导出文件夹和笔记 + exportFoldersAndNotes(); + + // 写入导出统计信息 + writeExportSummary(); + + Log.d(TAG, "Export successful. Exported " + mFoldersCount + " folders and " + mNotesCount + " notes."); + return STATE_SUCCESS; + } catch (IOException e) { + Log.e(TAG, "Error during export: " + e.getMessage(), e); + return STATE_SYSTEM_ERROR; } } - + /** - * 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); - - if (dataCursor != null) { - if (dataCursor.moveToFirst()) { + private void writeExportSummary() throws IOException { + mWriter.write("\n"); + mWriter.write("==============================\n"); + mWriter.write(mContext.getString(R.string.export_summary) + "\n"); + mWriter.write(mContext.getString(R.string.export_date) + ": " + + DateFormat.format(mContext.getString(R.string.format_datetime_ymdhm), System.currentTimeMillis()) + "\n"); + mWriter.write(mContext.getString(R.string.folder_count) + ": " + mFoldersCount + "\n"); + mWriter.write(mContext.getString(R.string.note_count) + ": " + mNotesCount + "\n"); + mWriter.write("==============================\n"); + } + + /** + * 导出所有文件夹和笔记 + */ + private void exportFoldersAndNotes() throws IOException { + // 首先导出文件夹和其中的笔记 + exportFolders(); + + // 导出根文件夹中的笔记 + exportRootNotes(); + } + + /** + * 导出所有文件夹 + */ + private void exportFolders() throws IOException { + // 查询所有文件夹 + String selection = "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " + + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " + + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER; + + try (Cursor folderCursor = mContext.getContentResolver().query( + Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, + selection, null, null)) { + + if (folderCursor != null && folderCursor.moveToFirst()) { do { - String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); - if (DataConstants.CALL_NOTE.equals(mimeType)) { - // Print phone number - 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)); - } - // 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 - 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()); + mFoldersCount++; + exportFolder(folderCursor); + } while (folderCursor.moveToNext()); } - dataCursor.close(); - } - // print a line separator between note - 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; + private void exportFolder(Cursor folderCursor) throws IOException { + String folderId = folderCursor.getString(NOTE_COLUMN_ID); + 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); } - - PrintStream ps = getExportToTextPrintStream(); - if (ps == null) { - Log.e(TAG, "get print stream error"); - return STATE_SYSTEM_ERROR; + + if (!TextUtils.isEmpty(folderName)) { + mWriter.write(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); + mWriter.newLine(); } - // First export folder and its notes - 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()) { + + // 导出文件夹中的笔记 + exportNotesInFolder(folderId); + + // 添加文件夹分隔线 + mWriter.write("\n"); + } + + /** + * 导出指定文件夹中的所有笔记 + */ + private void exportNotesInFolder(String folderId) throws IOException { + try (Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, + NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { folderId }, null)) { + + if (notesCursor != null && notesCursor.moveToFirst()) { do { - // Print folder's name - 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()); + mNotesCount++; + exportNote(notesCursor); + } while (notesCursor.moveToNext()); } - folderCursor.close(); } - - // Export notes in root's folder - Cursor noteCursor = mContext.getContentResolver().query( + } + + /** + * 导出根文件夹中的笔记 + */ + private void exportRootNotes() throws IOException { + try (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()) { + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID + "=0", + null, null)) { + + if (noteCursor != null && 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 - String noteId = noteCursor.getString(NOTE_COLUMN_ID); - exportNoteToText(noteId, ps); + mNotesCount++; + exportNote(noteCursor); } while (noteCursor.moveToNext()); } - noteCursor.close(); } - ps.close(); - - return STATE_SUCCESS; } - + /** - * Get a print stream pointed to the file {@generateExportedTextFile} + * 导出单个笔记 */ - 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; + private void exportNote(Cursor noteCursor) throws IOException { + // 打印笔记的最后修改日期 + mWriter.write(String.format(getFormat(FORMAT_NOTE_DATE), + DateFormat.format(mContext.getString(R.string.format_datetime_mdhm), + noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); + mWriter.newLine(); + + // 查询并导出笔记的数据 + String noteId = noteCursor.getString(NOTE_COLUMN_ID); + exportNoteData(noteId); + + // 添加笔记分隔线 + mWriter.write("\n"); + } + + /** + * 导出笔记的所有数据 + */ + private void exportNoteData(String noteId) throws IOException { + try (Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, + DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { noteId }, null)) { + + if (dataCursor != null && dataCursor.moveToFirst()) { + do { + String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); + if (DataConstants.CALL_NOTE.equals(mimeType)) { + exportCallNote(dataCursor); + } else if (DataConstants.NOTE.equals(mimeType)) { + exportTextNote(dataCursor); + } + } while (dataCursor.moveToNext()); + } } - 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; + } + + /** + * 导出通话记录笔记 + */ + private void exportCallNote(Cursor dataCursor) throws IOException { + // 打印电话号码 + 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)) { + mWriter.write(String.format(getFormat(FORMAT_NOTE_CONTENT), phoneNumber)); + mWriter.newLine(); + } + + // 打印通话日期 + mWriter.write(String.format(getFormat(FORMAT_NOTE_CONTENT), + DateFormat.format(mContext.getString(R.string.format_datetime_mdhm), callDate))); + mWriter.newLine(); + + // 打印通话附件位置 + if (!TextUtils.isEmpty(location)) { + mWriter.write(String.format(getFormat(FORMAT_NOTE_CONTENT), location)); + mWriter.newLine(); + } + } + + /** + * 导出文本笔记 + */ + private void exportTextNote(Cursor dataCursor) throws IOException { + String content = dataCursor.getString(DATA_COLUMN_CONTENT); + if (!TextUtils.isEmpty(content)) { + mWriter.write(String.format(getFormat(FORMAT_NOTE_CONTENT), content)); + mWriter.newLine(); } - return ps; } - } - - /** - * Generate the text file to store imported data - */ - 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(); + /** + * Generate the text file to store imported data + */ + private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { + StringBuilder sb = new StringBuilder(); + + // 适配Android 10+的存储访问 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + sb.append(context.getExternalFilesDir(null)); + } else { + sb.append(Environment.getExternalStorageDirectory()); } - if (!file.exists()) { - file.createNewFile(); + + sb.append(context.getString(filePathResId)); + File filedir = new File(sb.toString()); + + // 确保目录存在 + if (!filedir.exists() && !filedir.mkdirs()) { + Log.e(TAG, "Failed to create directory: " + filedir.getAbsolutePath()); + return null; } - return file; - } catch (SecurityException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } + + // 构建文件名 + sb.append(context.getString( + fileNameFormatResId, + DateFormat.format(context.getString(R.string.format_date_ymd), + System.currentTimeMillis()))); + File file = new File(sb.toString()); - return null; + try { + if (!file.exists() && !file.createNewFile()) { + Log.e(TAG, "Failed to create file: " + file.getAbsolutePath()); + return null; + } + return file; + } catch (SecurityException | IOException e) { + Log.e(TAG, "Error creating file: " + e.getMessage(), e); + return null; + } + } } } +