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.

431 lines
15 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.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<Long> 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<ContentProviderOperation> 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<Long> 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<ContentProviderOperation> 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<AppWidgetAttribute> getFolderWidgets(ContentResolver resolver, long folderId) {
Set<AppWidgetAttribute> 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<Long> 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<Long> ids, int modified) {
if (resolver == null) return false;
ArrayList<ContentProviderOperation> 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;
}
}