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.
git/java/net/micode/notes/tool/DataUtils.java

448 lines
18 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.
*/
// DataUtils.java - 小米便签数据工具类
// 主要功能:提供便签数据的批量操作、查询、验证等工具方法
package net.micode.notes.tool;
// ======================= 导入区域 =======================
// Android数据操作相关类
import android.content.ContentProviderOperation; // 内容提供者批量操作
import android.content.ContentProviderResult; // 批量操作结果
import android.content.ContentResolver; // 内容解析器用于访问ContentProvider
import android.content.ContentUris; // URI工具用于构建带ID的URI
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; // Notes主类
import net.micode.notes.data.Notes.CallNote; // 通话记录相关常量
import net.micode.notes.data.Notes.NoteColumns; // 便签表列定义
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; // 小部件属性类
// Java集合类
import java.util.ArrayList; // 动态数组
import java.util.HashSet; // 哈希集合用于存储唯一ID
// ======================= 数据工具主类 =======================
/**
* DataUtils - 便签数据操作工具类
* 包含对便签数据的批量删除、移动、查询、验证等常用操作
* 所有方法都是静态工具方法,无需实例化即可使用
*/
public class DataUtils {
// TAG - 日志标签用于Logcat日志筛选
public static final String TAG = "DataUtils";
/**
* 批量删除便签
* 使用ContentProvider批量操作提高删除效率
* @param resolver 内容解析器用于访问ContentProvider
* @param ids 要删除的便签ID集合HashSet保证ID唯一性
* @return true: 删除成功; false: 删除失败
*/
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> 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; // 空集合视为成功
}
// 创建批量操作列表
ArrayList<ContentProviderOperation> operationList =
new ArrayList<ContentProviderOperation>();
// 遍历ID集合为每个便签创建删除操作
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;
}
/**
* 移动单个便签到其他文件夹
* 更新便签的父文件夹ID并标记为本地已修改
* @param resolver 内容解析器
* @param id 要移动的便签ID
* @param srcFolderId 源文件夹ID
* @param desFolderId 目标文件夹ID
*/
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
// 创建更新数据
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, desFolderId); // 新父文件夹ID
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); // 原父文件夹ID用于同步
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, HashSet<Long> ids,
long folderId) {
// 参数验证
if (ids == null) {
Log.d(TAG, "the ids is null");
return true; // 空集合视为成功
}
// 创建批量操作列表
ArrayList<ContentProviderOperation> operationList =
new ArrayList<ContentProviderOperation>();
// 为每个便签创建更新操作
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId); // 更新父文件夹ID
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); // 标记为本地已修改
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;
}
/**
* 获取用户文件夹数量(排除系统文件夹)
* 统计所有用户创建的文件夹,不包括系统预置文件夹
* @param resolver 内容解析器
* @return 用户文件夹数量
*/
public static int getUserFolderCount(ContentResolver resolver) {
// 查询条件:类型为文件夹
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" }, // 只查询数量
NoteColumns.TYPE + "=?", // 查询条件
new String[] {
String.valueOf(Notes.TYPE_FOLDER) // 参数1文件夹类型
},
null);
int count = 0;
if(cursor != null) {
if(cursor.moveToFirst()) {
try {
count = cursor.getInt(0); // 获取第一列COUNT(*)结果)
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
cursor.close(); // 确保游标关闭
}
}
}
return count;
}
/**
* 检查便签在数据库中是否可见
* 可见条件:指定类型 且 不在回收站中
* @param resolver 内容解析器
* @param noteId 便签ID
* @param type 便签类型Notes.TYPE_NOTE 或 Notes.TYPE_FOLDER
* @return true: 可见; false: 不可见(在回收站中或类型不匹配)
*/
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
// 查询指定便签
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, // 查询所有列
NoteColumns.TYPE + "=?",
new String [] {String.valueOf(type)}, // 类型参数
null);
boolean exist = false;
if (cursor != null) {
// 只要查询到记录就表示可见
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
/**
* 检查便签是否存在于数据库中
* 不关心是否在回收站只要存在就返回true
* @param resolver 内容解析器
* @param noteId 便签ID
* @return true: 存在; false: 不存在
*/
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
// 查询指定便签,无过滤条件
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
/**
* 检查数据项是否存在于数据表中
* 数据表存储便签的具体内容
* @param resolver 内容解析器
* @param dataId 数据项ID
* @return true: 存在; false: 不存在
*/
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
/**
* 检查可见文件夹名称是否已存在
* 用于创建文件夹时的重名检查
* @param resolver 内容解析器
* @param name 要检查的文件夹名称
* @return true: 已存在; false: 不存在
*/
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
// 查询条件:文件夹类型 且 名称匹配
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.SNIPPET + "=?", // SNIPPET字段存储文件夹名称
new String[] { name }, null);
boolean exist = false;
if(cursor != null) {
if(cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
/**
* 获取文件夹下所有便签的小部件信息
* 用于桌面小部件管理
* @param resolver 内容解析器
* @param folderId 文件夹ID
* @return 小部件属性集合null表示无小部件
*/
public static HashSet<AppWidgetAttribute> 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<AppWidgetAttribute> set = null;
if (c != null) {
if (c.moveToFirst()) {
set = new HashSet<AppWidgetAttribute>(); // 创建集合
do {
try {
// 创建小部件属性对象
AppWidgetAttribute widget = new AppWidgetAttribute();
widget.widgetId = c.getInt(0); // 小部件ID
widget.widgetType = c.getInt(1); // 小部件类型
set.add(widget); // 添加到集合
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, e.toString()); // 字段索引异常
}
} while (c.moveToNext()); // 遍历所有便签
}
c.close();
}
return set;
}
/**
* 根据便签ID获取通话记录的电话号码
* 用于通话记录类型便签
* @param resolver 内容解析器
* @param noteId 便签ID
* @return 电话号码,空字符串表示未找到
*/
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
// 查询条件便签ID匹配 且 类型为通话记录
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), // 参数1便签ID
CallNote.CONTENT_ITEM_TYPE // 参数2通话记录类型
},
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(); // finally块确保游标关闭
}
}
return ""; // 未找到返回空字符串
}
/**
* 根据电话号码和通话时间查找便签ID
* 用于避免创建重复的通话记录
* @param resolver 内容解析器
* @param phoneNumber 电话号码
* @param callDate 通话时间戳
* @return 便签ID0表示未找到
*/
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
// 查询条件:通话时间匹配 且 类型为通话记录 且 电话号码匹配
// PHONE_NUMBERS_EQUAL是自定义函数用于比较电话号码可能处理格式差异
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID }, // 只查询便签ID
CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL("
+ CallNote.PHONE_NUMBER + ",?)",
new String [] {
String.valueOf(callDate), // 参数1通话时间
CallNote.CONTENT_ITEM_TYPE, // 参数2通话记录类型
phoneNumber // 参数3电话号码
},
null);
if (cursor != null) {
if (cursor.moveToFirst()) {
try {
return cursor.getLong(0); // 返回便签ID
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call note id fails " + e.toString());
}
}
cursor.close();
}
return 0; // 未找到返回0
}
/**
* 根据便签ID获取便签摘要
* 摘要通常显示在列表视图中
* @param resolver 内容解析器
* @param noteId 便签ID
* @return 便签摘要
* @throws IllegalArgumentException 如果便签不存在
*/
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);
if (cursor != null) {
String snippet = "";
if (cursor.moveToFirst()) {
snippet = cursor.getString(0); // 获取摘要
}
cursor.close();
return snippet;
}
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
/**
* 格式化便签摘要
* 处理摘要显示:去除首尾空格,只显示第一行
* @param snippet 原始摘要
* @return 格式化后的摘要
*/
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);
}
}
return snippet;
}
}