/* * 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; } }