diff --git a/java%2Fnet%2Fmicode%2Fnotes%2Ftool/DataUtils.java b/java%2Fnet%2Fmicode%2Fnotes%2Ftool/DataUtils.java new file mode 100644 index 0000000..ef9414b --- /dev/null +++ b/java%2Fnet%2Fmicode%2Fnotes%2Ftool/DataUtils.java @@ -0,0 +1,431 @@ +/* + * 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.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.OperationApplicationException; +import android.database.Cursor; +import android.os.RemoteException; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.CallNote; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +/** + * 数据工具类 - 提供批量数据库操作和查询功能 + * 功能包括:批量删除/移动笔记、文件夹计数、存在性检查、小部件查询等 + */ +public class DataUtils { + private static final String TAG = "DataUtils"; + + /** + * 批量删除笔记 + * + * @param resolver 内容解析器 + * @param ids 要删除的笔记ID集合 + * @return 删除成功返回true,否则返回false + */ + public static boolean batchDeleteNotes(ContentResolver resolver, Set ids) { + // 参数有效性检查 + if (resolver == null) { + Log.w(TAG, "ContentResolver is null"); + return false; + } + + if (ids == null || ids.isEmpty()) { + Log.d(TAG, "No notes to delete"); + return true; + } + + ArrayList operations = new ArrayList<>(ids.size()); + + for (long id : ids) { + // 防止删除系统根文件夹 + if (id == Notes.ID_ROOT_FOLDER) { + Log.w(TAG, "Skipping system root folder deletion"); + continue; + } + + // 添加删除操作 + operations.add(ContentProviderOperation + .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)) + .build()); + } + + if (operations.isEmpty()) { + Log.d(TAG, "No valid operations to execute"); + return true; + } + + try { + ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operations); + return results != null && results.length > 0; + } catch (RemoteException | OperationApplicationException e) { + Log.e(TAG, "Batch delete failed", e); + } + return false; + } + + /** + * 移动笔记到新文件夹 + * + * @param resolver 内容解析器 + * @param id 笔记ID + * @param srcFolderId 源文件夹ID + * @param desFolderId 目标文件夹ID + */ + public static void moveNoteToFolder(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { + if (resolver == null) { + Log.w(TAG, "ContentResolver is null"); + return; + } + + 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); + } + + /** + * 批量移动笔记到文件夹 + * + * @param resolver 内容解析器 + * @param ids 笔记ID集合 + * @param folderId 目标文件夹ID + * @return 移动成功返回true,否则返回false + */ + public static boolean batchMoveToFolder(ContentResolver resolver, Set ids, long folderId) { + if (resolver == null) { + Log.w(TAG, "ContentResolver is null"); + return false; + } + + if (ids == null || ids.isEmpty()) { + Log.d(TAG, "No notes to move"); + return true; + } + + ArrayList operations = new ArrayList<>(ids.size()); + + for (long id : ids) { + operations.add(ContentProviderOperation + .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)) + .withValue(NoteColumns.PARENT_ID, folderId) + .withValue(NoteColumns.LOCAL_MODIFIED, 1) + .build()); + } + + try { + ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operations); + return results != null && results.length > 0; + } catch (RemoteException | OperationApplicationException e) { + Log.e(TAG, "Batch move failed", e); + } + return false; + } + + /** + * 获取用户创建的文件夹数量(排除系统文件夹) + * + * @param resolver 内容解析器 + * @return 用户文件夹数量 + */ + public static int getUserFolderCount(ContentResolver resolver) { + if (resolver == null) { + Log.w(TAG, "ContentResolver is null"); + return 0; + } + + String[] projection = {NoteColumns.ID}; + String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?"; + String[] selectionArgs = { + String.valueOf(Notes.TYPE_FOLDER), + String.valueOf(Notes.ID_TRASH_FOLDER) // 修正拼写错误:FOLER -> FOLDER + }; + + try (Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, + projection, selection, selectionArgs, null)) { + return cursor != null ? cursor.getCount() : 0; + } + } + + /** + * 检查指定类型笔记是否可见(不在回收站中) + * + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @param type 笔记类型 + * @return 笔记存在且可见返回true + */ + public static boolean isNoteVisible(ContentResolver resolver, long noteId, int type) { + if (resolver == null) return false; + + String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?"; + String[] selectionArgs = { + String.valueOf(type), + String.valueOf(Notes.ID_TRASH_FOLDER) + }; + + try (Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), + null, selection, selectionArgs, null)) { + return cursor != null && cursor.getCount() > 0; + } + } + + /** + * 检查笔记是否存在 + * + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @return 笔记存在返回true + */ + public static boolean doesNoteExist(ContentResolver resolver, long noteId) { + if (resolver == null) return false; + + try (Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), + null, null, null, null)) { + return cursor != null && cursor.getCount() > 0; + } + } + + /** + * 检查笔记数据是否存在 + * + * @param resolver 内容解析器 + * @param dataId 数据ID + * @return 数据存在返回true + */ + public static boolean doesDataExist(ContentResolver resolver, long dataId) { + if (resolver == null) return false; + + try (Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), + null, null, null, null)) { + return cursor != null && cursor.getCount() > 0; + } + } + + /** + * 检查文件夹名称是否已存在(可见文件夹) + * + * @param resolver 内容解析器 + * @param name 文件夹名称 + * @return 名称已存在返回true + */ + public static boolean isFolderNameExist(ContentResolver resolver, String name) { + if (resolver == null || TextUtils.isEmpty(name)) return false; + + String selection = NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLDER + + " AND " + NoteColumns.SNIPPET + "=?"; + String[] selectionArgs = { name }; + + try (Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, + null, selection, selectionArgs, null)) { + return cursor != null && cursor.getCount() > 0; + } + } + + /** + * 获取文件夹关联的小部件属性 + * + * @param resolver 内容解析器 + * @param folderId 文件夹ID + * @return 小部件属性集合 + */ + public static Set getFolderWidgets(ContentResolver resolver, long folderId) { + Set widgets = new HashSet<>(); + if (resolver == null) return widgets; + + String[] projection = { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }; + String selection = NoteColumns.PARENT_ID + "=?"; + String[] selectionArgs = { String.valueOf(folderId) }; + + try (Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, + projection, selection, selectionArgs, null)) { + + if (cursor != null && cursor.moveToFirst()) { + do { + try { + AppWidgetAttribute widget = new AppWidgetAttribute(); + widget.widgetId = cursor.getInt(0); + widget.widgetType = cursor.getInt(1); + widgets.add(widget); + } catch (Exception e) { + Log.e(TAG, "Error reading widget attributes", e); + } + } while (cursor.moveToNext()); + } + } + return widgets; + } + + /** + * 通过笔记ID获取电话号码(通话记录笔记) + * + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @return 电话号码,查询失败返回空字符串 + */ + public static String getCallNumber(ContentResolver resolver, long noteId) { + if (resolver == null) return ""; + + String[] projection = { CallNote.PHONE_NUMBER }; + String selection = CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?"; + String[] selectionArgs = { + String.valueOf(noteId), + CallNote.CONTENT_ITEM_TYPE + }; + + try (Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, + projection, selection, selectionArgs, null)) { + + if (cursor != null && cursor.moveToFirst()) { + return cursor.getString(0); + } + } catch (Exception e) { + Log.e(TAG, "Error getting call number", e); + } + return ""; + } + + /** + * 通过电话号码和呼叫日期查找笔记ID + * + * @param resolver 内容解析器 + * @param phoneNumber 电话号码 + * @param callDate 通话日期 + * @return 笔记ID,未找到返回0 + */ + public static long findNoteByCallDetails(ContentResolver resolver, String phoneNumber, long callDate) { + if (resolver == null || TextUtils.isEmpty(phoneNumber)) return 0; + + String[] projection = { CallNote.NOTE_ID }; + String selection = CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND " + + "PHONE_NUMBERS_EQUAL(" + CallNote.PHONE_NUMBER + ", ?)"; + String[] selectionArgs = { + String.valueOf(callDate), + CallNote.CONTENT_ITEM_TYPE, + phoneNumber + }; + + try (Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, + projection, selection, selectionArgs, null)) { + + if (cursor != null && cursor.moveToFirst()) { + return cursor.getLong(0); + } + } catch (Exception e) { + Log.e(TAG, "Error finding note by call details", e); + } + return 0; + } + + /** + * 获取笔记摘要 + * + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @return 笔记摘要 + * @throws IllegalArgumentException 笔记不存在时抛出异常 + */ + public static String getNoteSnippet(ContentResolver resolver, long noteId) { + if (resolver == null) throw new IllegalArgumentException("Resolver is null"); + + String[] projection = { NoteColumns.SNIPPET }; + String selection = NoteColumns.ID + "=?"; + String[] selectionArgs = { String.valueOf(noteId) }; + + try (Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, + projection, selection, selectionArgs, null)) { + + if (cursor != null && cursor.moveToFirst()) { + return cursor.getString(0); + } + throw new IllegalArgumentException("Note not found with id: " + noteId); + } + } + + /** + * 格式化笔记摘要(移除换行符) + * + * @param snippet 原始摘要 + * @return 格式化后的摘要 + */ + public static String formatSnippet(String snippet) { + if (snippet == null) return null; + + String formatted = snippet.trim(); + int newlineIndex = formatted.indexOf('\n'); + return (newlineIndex != -1) ? formatted.substring(0, newlineIndex) : formatted; + } + + /* ----------------- 新增功能 ----------------- */ + + /** + * 安全删除笔记(防止系统文件夹被删除) + * + * @param resolver 内容解析器 + * @param noteId 笔记ID + * @return 删除成功返回true + */ + public static boolean safeDeleteNote(ContentResolver resolver, long noteId) { + Set ids = new HashSet<>(1); + ids.add(noteId); + return batchDeleteNotes(resolver, ids); + } + + /** + * 批量更新笔记的本地修改标志 + * + * @param resolver 内容解析器 + * @param ids 笔记ID集合 + * @param modified 修改标志值 (1-已修改, 0-未修改) + * @return 更新成功返回true + */ + public static boolean batchMarkModified(ContentResolver resolver, Set ids, int modified) { + if (resolver == null) return false; + + ArrayList operations = new ArrayList<>(ids.size()); + + for (long id : ids) { + operations.add(ContentProviderOperation + .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)) + .withValue(NoteColumns.LOCAL_MODIFIED, modified) + .build()); + } + + try { + resolver.applyBatch(Notes.AUTHORITY, operations); + return true; + } catch (Exception e) { + Log.e(TAG, "Batch mark modified failed", e); + } + return false; + } +} \ No newline at end of file