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