From 5039cd6b336a12aeeff61251f5e27ec647a38236 Mon Sep 17 00:00:00 2001 From: pwiz98tyo <1257800485@qq.com> Date: Thu, 12 Jun 2025 17:07:56 +0800 Subject: [PATCH] Update DataUtils.java --- java/net/micode/notes/tool/DataUtils.java | 577 +++++++++++++--------- 1 file changed, 346 insertions(+), 231 deletions(-) diff --git a/java/net/micode/notes/tool/DataUtils.java b/java/net/micode/notes/tool/DataUtils.java index 2a14982..b0a7b5c 100644 --- a/java/net/micode/notes/tool/DataUtils.java +++ b/java/net/micode/notes/tool/DataUtils.java @@ -16,280 +16,395 @@ package net.micode.notes.tool; -import android.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.OperationApplicationException; +import android.content.Context; import android.database.Cursor; -import android.os.RemoteException; +import android.os.Build; +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.CallNote; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.HashSet; +import java.util.List; +public class BackupUtils { + private static final String TAG = "BackupUtils"; + // Singleton stuff + private static BackupUtils sInstance; -public class DataUtils { - public static final String TAG = "DataUtils"; - public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { - if (ids == null) { - Log.d(TAG, "the ids is null"); - return true; - } - if (ids.size() == 0) { - Log.d(TAG, "no id is in the hashset"); - return true; + public static synchronized BackupUtils getInstance(Context context) { + if (sInstance == null) { + sInstance = new BackupUtils(context); } + return sInstance; + } - ArrayList operationList = new ArrayList(); - for (long id : ids) { - if(id == Notes.ID_ROOT_FOLDER) { - Log.e(TAG, "Don't delete system folder root"); - continue; - } - ContentProviderOperation.Builder builder = ContentProviderOperation - .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); - operationList.add(builder.build()); - } - try { - ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); - if (results == null || results.length == 0 || results[0] == null) { - Log.d(TAG, "delete notes failed, ids:" + ids.toString()); - return false; - } - return true; - } catch (RemoteException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - } catch (OperationApplicationException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - } - return false; + /** + * Following states are signs to represents backup or restore + * status + */ + // Currently, the sdcard is not mounted + 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; + // Permission denied + public static final int STATE_PERMISSION_DENIED = 5; + + private TextExport mTextExport; + + private BackupUtils(Context context) { + mTextExport = new TextExport(context); } - public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { - ContentValues values = new ContentValues(); - values.put(NoteColumns.PARENT_ID, desFolderId); - values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); - values.put(NoteColumns.LOCAL_MODIFIED, 1); - resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); + private static boolean externalStorageAvailable() { + return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } - public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids, - long folderId) { - if (ids == null) { - Log.d(TAG, "the ids is null"); - return true; - } + public int exportToText() { + return mTextExport.exportToText(); + } - ArrayList operationList = new ArrayList(); - for (long id : ids) { - ContentProviderOperation.Builder builder = ContentProviderOperation - .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); - builder.withValue(NoteColumns.PARENT_ID, folderId); - builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); - operationList.add(builder.build()); - } + public String getExportedTextFileName() { + return mTextExport.mFileName; + } - try { - ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); - if (results == null || results.length == 0 || results[0] == null) { - Log.d(TAG, "delete notes failed, ids:" + ids.toString()); - return false; - } - return true; - } catch (RemoteException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - } catch (OperationApplicationException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - } - return false; + public String getExportedTextFileDir() { + return mTextExport.mFileDirectory; } - /** - * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} - */ - public static int getUserFolderCount(ContentResolver resolver) { - Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, - new String[] { "COUNT(*)" }, - NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", - new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, - null); + private static class TextExport { + private static final String[] NOTE_PROJECTION = { + NoteColumns.ID, + NoteColumns.MODIFIED_DATE, + NoteColumns.SNIPPET, + NoteColumns.TYPE, + NoteColumns.PARENT_ID + }; - int count = 0; - if(cursor != null) { - if(cursor.moveToFirst()) { - try { - count = cursor.getInt(0); - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, "get folder count failed:" + e.toString()); - } finally { - cursor.close(); - } - } - } - return count; - } + 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; - public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { - Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), - null, - NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, - new String [] {String.valueOf(type)}, - null); + private static final String[] DATA_PROJECTION = { + DataColumns.CONTENT, + DataColumns.MIME_TYPE, + DataColumns.DATA1, + DataColumns.DATA2, + DataColumns.DATA3, + DataColumns.DATA4, + }; - boolean exist = false; - if (cursor != null) { - if (cursor.getCount() > 0) { - exist = true; - } - cursor.close(); - } - return exist; - } + 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; - public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { - Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), - null, null, null, null); + 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; - boolean exist = false; - if (cursor != null) { - if (cursor.getCount() > 0) { - exist = true; - } - cursor.close(); + 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; } - return exist; - } - public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { - Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), - null, null, null, null); + private String getFormat(int id) { + return TEXT_FORMAT[id]; + } - boolean exist = false; - if (cursor != null) { - if (cursor.getCount() > 0) { - exist = true; + /** + * Export notes to text file + */ + public int exportToText() { + if (!externalStorageAvailable()) { + Log.d(TAG, "Media was not mounted"); + return STATE_SD_CARD_UNMOUONTED; } - cursor.close(); - } - return exist; - } - public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { - Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, - NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + - " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + - " AND " + NoteColumns.SNIPPET + "=?", - new String[] { name }, null); - boolean exist = false; - if(cursor != null) { - if(cursor.getCount() > 0) { - exist = true; + 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; } - cursor.close(); } - return exist; - } - - public static HashSet getFolderNoteWidget(ContentResolver resolver, long folderId) { - Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, - new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, - NoteColumns.PARENT_ID + "=?", - new String[] { String.valueOf(folderId) }, - null); - - HashSet set = null; - if (c != null) { - if (c.moveToFirst()) { - set = new HashSet(); - do { - try { - AppWidgetAttribute widget = new AppWidgetAttribute(); - widget.widgetId = c.getInt(0); - widget.widgetType = c.getInt(1); - set.add(widget); - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, e.toString()); - } - } while (c.moveToNext()); + + /** + * 写入导出摘要信息 + */ + 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 { + mFoldersCount++; + exportFolder(folderCursor); + } while (folderCursor.moveToNext()); + } } - c.close(); } - return set; - } - - public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { - Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, - new String [] { CallNote.PHONE_NUMBER }, - CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?", - new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE }, - null); - - if (cursor != null && cursor.moveToFirst()) { - try { - return cursor.getString(0); - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, "Get call number fails " + e.toString()); - } finally { - cursor.close(); + + /** + * 导出单个文件夹及其包含的笔记 + */ + 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); + } + + if (!TextUtils.isEmpty(folderName)) { + mWriter.write(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); + mWriter.newLine(); } + + // 导出文件夹中的笔记 + exportNotesInFolder(folderId); + + // 添加文件夹分隔线 + mWriter.write("\n"); } - return ""; - } - - public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { - Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, - new String [] { CallNote.NOTE_ID }, - CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL(" - + CallNote.PHONE_NUMBER + ",?)", - new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber }, - null); - - if (cursor != null) { - if (cursor.moveToFirst()) { - try { - return cursor.getLong(0); - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, "Get call note id fails " + e.toString()); + + /** + * 导出指定文件夹中的所有笔记 + */ + 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 { + mNotesCount++; + exportNote(notesCursor); + } while (notesCursor.moveToNext()); } } - cursor.close(); } - return 0; - } - - public static String getSnippetById(ContentResolver resolver, long noteId) { - Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, - new String [] { NoteColumns.SNIPPET }, - NoteColumns.ID + "=?", - new String [] { String.valueOf(noteId)}, - null); + + /** + * 导出根文件夹中的笔记 + */ + 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 && noteCursor.moveToFirst()) { + do { + mNotesCount++; + exportNote(noteCursor); + } while (noteCursor.moveToNext()); + } + } + } + + /** + * 导出单个笔记 + */ + 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()); + } + } + } + + /** + * 导出通话记录笔记 + */ + 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 (cursor != null) { - String snippet = ""; - if (cursor.moveToFirst()) { - snippet = cursor.getString(0); + 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(); } - cursor.close(); - return snippet; } - throw new IllegalArgumentException("Note is not found with id: " + noteId); - } + + /** + * 导出文本笔记 + */ + 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(); + } + } + + /** + * 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()); + } + + 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; + } + + // 构建文件名 + sb.append(context.getString( + fileNameFormatResId, + DateFormat.format(context.getString(R.string.format_date_ymd), + System.currentTimeMillis()))); + File file = new File(sb.toString()); - public static String getFormattedSnippet(String snippet) { - if (snippet != null) { - snippet = snippet.trim(); - int index = snippet.indexOf('\n'); - if (index != -1) { - snippet = snippet.substring(0, index); + 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; } } - return snippet; } } +