|
|
|
|
@ -0,0 +1,962 @@
|
|
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2025, Modern Notes Project
|
|
|
|
|
*
|
|
|
|
|
* 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.data;
|
|
|
|
|
|
|
|
|
|
import android.content.ContentResolver;
|
|
|
|
|
import android.content.ContentUris;
|
|
|
|
|
import android.content.ContentValues;
|
|
|
|
|
import android.content.Context;
|
|
|
|
|
import android.database.Cursor;
|
|
|
|
|
import android.net.Uri;
|
|
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
|
|
import net.micode.notes.data.Notes;
|
|
|
|
|
import net.micode.notes.data.Notes.CallNote;
|
|
|
|
|
import net.micode.notes.data.Notes.DataColumns;
|
|
|
|
|
import net.micode.notes.data.Notes.NoteColumns;
|
|
|
|
|
import net.micode.notes.data.Notes.TextNote;
|
|
|
|
|
import net.micode.notes.model.Note;
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.concurrent.ExecutorService;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 笔记数据仓库
|
|
|
|
|
* <p>
|
|
|
|
|
* 负责数据访问逻辑,统一管理Content Provider和缓存
|
|
|
|
|
* 提供笔记的增删改查、搜索、统计等功能
|
|
|
|
|
* </p>
|
|
|
|
|
* <p>
|
|
|
|
|
* 使用Executor进行后台线程数据访问,避免阻塞UI线程
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @see Note
|
|
|
|
|
* @see Notes
|
|
|
|
|
*/
|
|
|
|
|
public class NotesRepository {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 笔记信息类
|
|
|
|
|
* <p>
|
|
|
|
|
* 存储从数据库查询的笔记基本信息
|
|
|
|
|
* </p>
|
|
|
|
|
*/
|
|
|
|
|
public static class NoteInfo {
|
|
|
|
|
public long id;
|
|
|
|
|
public String title;
|
|
|
|
|
public String snippet;
|
|
|
|
|
public long parentId;
|
|
|
|
|
public long createdDate;
|
|
|
|
|
public long modifiedDate;
|
|
|
|
|
public int type;
|
|
|
|
|
public int localModified;
|
|
|
|
|
public int bgColorId;
|
|
|
|
|
public boolean isPinned; // 新增置顶字段
|
|
|
|
|
|
|
|
|
|
public NoteInfo() {}
|
|
|
|
|
|
|
|
|
|
public long getId() {
|
|
|
|
|
return id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public long getParentId() {
|
|
|
|
|
return parentId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String getNoteDataValue() {
|
|
|
|
|
return snippet;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
private static final String TAG = "NotesRepository";
|
|
|
|
|
|
|
|
|
|
private final ContentResolver contentResolver;
|
|
|
|
|
private final ExecutorService executor;
|
|
|
|
|
|
|
|
|
|
// 选择条件常量
|
|
|
|
|
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + " = ?";
|
|
|
|
|
private static final String ROOT_FOLDER_SELECTION = "(" +
|
|
|
|
|
NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " +
|
|
|
|
|
NoteColumns.PARENT_ID + "=?) OR (" +
|
|
|
|
|
NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " +
|
|
|
|
|
NoteColumns.NOTES_COUNT + ">0)";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 数据访问回调接口
|
|
|
|
|
* <p>
|
|
|
|
|
* 统一的数据访问结果回调机制
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param <T> 返回数据类型
|
|
|
|
|
*/
|
|
|
|
|
public interface Callback<T> {
|
|
|
|
|
/**
|
|
|
|
|
* 成功回调
|
|
|
|
|
*
|
|
|
|
|
* @param result 返回的结果数据
|
|
|
|
|
*/
|
|
|
|
|
void onSuccess(T result);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 失败回调
|
|
|
|
|
*
|
|
|
|
|
* @param error 异常对象
|
|
|
|
|
*/
|
|
|
|
|
void onError(Exception error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 从 Cursor 创建 NoteInfo 对象
|
|
|
|
|
*
|
|
|
|
|
* @param cursor 数据库游标
|
|
|
|
|
* @return NoteInfo 对象
|
|
|
|
|
*/
|
|
|
|
|
private NoteInfo noteFromCursor(Cursor cursor) {
|
|
|
|
|
NoteInfo noteInfo = new NoteInfo();
|
|
|
|
|
noteInfo.id = cursor.getLong(cursor.getColumnIndexOrThrow(NoteColumns.ID));
|
|
|
|
|
noteInfo.title = cursor.getString(cursor.getColumnIndexOrThrow(NoteColumns.SNIPPET));
|
|
|
|
|
noteInfo.snippet = cursor.getString(cursor.getColumnIndexOrThrow(NoteColumns.SNIPPET));
|
|
|
|
|
noteInfo.parentId = cursor.getLong(cursor.getColumnIndexOrThrow(NoteColumns.PARENT_ID));
|
|
|
|
|
noteInfo.createdDate = cursor.getLong(cursor.getColumnIndexOrThrow(NoteColumns.CREATED_DATE));
|
|
|
|
|
noteInfo.modifiedDate = cursor.getLong(cursor.getColumnIndexOrThrow(NoteColumns.MODIFIED_DATE));
|
|
|
|
|
noteInfo.type = cursor.getInt(cursor.getColumnIndexOrThrow(NoteColumns.TYPE));
|
|
|
|
|
noteInfo.localModified = cursor.getInt(cursor.getColumnIndexOrThrow(NoteColumns.LOCAL_MODIFIED));
|
|
|
|
|
|
|
|
|
|
int bgColorIdIndex = cursor.getColumnIndex(NoteColumns.BG_COLOR_ID);
|
|
|
|
|
if (bgColorIdIndex != -1 && !cursor.isNull(bgColorIdIndex)) {
|
|
|
|
|
noteInfo.bgColorId = cursor.getInt(bgColorIdIndex);
|
|
|
|
|
} else {
|
|
|
|
|
noteInfo.bgColorId = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int topIndex = cursor.getColumnIndex(NoteColumns.TOP);
|
|
|
|
|
if (topIndex != -1) {
|
|
|
|
|
noteInfo.isPinned = cursor.getInt(topIndex) > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return noteInfo;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构造函数
|
|
|
|
|
* <p>
|
|
|
|
|
* 初始化ContentResolver和线程池
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param contentResolver Content解析器
|
|
|
|
|
*/
|
|
|
|
|
public NotesRepository(ContentResolver contentResolver) {
|
|
|
|
|
this.contentResolver = contentResolver;
|
|
|
|
|
// 使用单线程Executor确保数据访问的顺序性
|
|
|
|
|
this.executor = java.util.concurrent.Executors.newSingleThreadExecutor();
|
|
|
|
|
Log.d(TAG, "NotesRepository initialized");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取指定文件夹的笔记列表
|
|
|
|
|
* <p>
|
|
|
|
|
* 支持根文件夹(显示所有笔记)和子文件夹两种模式
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param folderId 文件夹ID,{@link Notes#ID_ROOT_FOLDER} 表示根文件夹
|
|
|
|
|
* @param callback 回调接口
|
|
|
|
|
*/
|
|
|
|
|
public void getNotes(long folderId, Callback<List<NoteInfo>> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
List<NoteInfo> notes = queryNotes(folderId);
|
|
|
|
|
callback.onSuccess(notes);
|
|
|
|
|
Log.d(TAG, "Successfully loaded notes for folder: " + folderId);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to load notes for folder: " + folderId, e);
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 查询笔记列表(内部方法)
|
|
|
|
|
* <p>
|
|
|
|
|
* 同时返回文件夹和便签,文件夹显示在便签之前
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param folderId 文件夹ID
|
|
|
|
|
* @return 笔记列表(包含文件夹和便签)
|
|
|
|
|
*/
|
|
|
|
|
private List<NoteInfo> queryNotes(long folderId) {
|
|
|
|
|
List<NoteInfo> notes = new ArrayList<>();
|
|
|
|
|
List<NoteInfo> folders = new ArrayList<>();
|
|
|
|
|
List<NoteInfo> normalNotes = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
String selection;
|
|
|
|
|
String[] selectionArgs;
|
|
|
|
|
|
|
|
|
|
if (folderId == Notes.ID_ROOT_FOLDER) {
|
|
|
|
|
// 根文件夹:显示所有文件夹和便签
|
|
|
|
|
selection = "(" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?) OR (" +
|
|
|
|
|
NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0)";
|
|
|
|
|
selectionArgs = new String[]{String.valueOf(Notes.ID_ROOT_FOLDER)};
|
|
|
|
|
} else {
|
|
|
|
|
// 子文件夹:显示该文件夹下的文件夹和便签
|
|
|
|
|
selection = NoteColumns.PARENT_ID + "=? AND " + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM;
|
|
|
|
|
selectionArgs = new String[]{String.valueOf(folderId)};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Cursor cursor = contentResolver.query(
|
|
|
|
|
Notes.CONTENT_NOTE_URI,
|
|
|
|
|
null,
|
|
|
|
|
selection,
|
|
|
|
|
selectionArgs,
|
|
|
|
|
NoteColumns.MODIFIED_DATE + " DESC"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (cursor != null) {
|
|
|
|
|
try {
|
|
|
|
|
while (cursor.moveToNext()) {
|
|
|
|
|
NoteInfo note = noteFromCursor(cursor);
|
|
|
|
|
if (note.type == Notes.TYPE_FOLDER) {
|
|
|
|
|
// 文件夹单独收集
|
|
|
|
|
folders.add(note);
|
|
|
|
|
} else if (note.type == Notes.TYPE_NOTE) {
|
|
|
|
|
// 便签收集
|
|
|
|
|
normalNotes.add(note);
|
|
|
|
|
} else if (note.type == Notes.TYPE_SYSTEM && note.id == Notes.ID_CALL_RECORD_FOLDER) {
|
|
|
|
|
// 通话记录文件夹
|
|
|
|
|
folders.add(note);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Log.d(TAG, "Query returned " + folders.size() + " folders and " + normalNotes.size() + " notes");
|
|
|
|
|
} finally {
|
|
|
|
|
cursor.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 文件夹按修改时间倒序排列
|
|
|
|
|
folders.sort((a, b) -> Long.compare(b.modifiedDate, a.modifiedDate));
|
|
|
|
|
// 便签按修改时间倒序排列
|
|
|
|
|
normalNotes.sort((a, b) -> {
|
|
|
|
|
// 首先按置顶状态排序(置顶在前)
|
|
|
|
|
if (a.isPinned != b.isPinned) {
|
|
|
|
|
return a.isPinned ? -1 : 1;
|
|
|
|
|
}
|
|
|
|
|
// 其次按修改时间倒序排列
|
|
|
|
|
return Long.compare(b.modifiedDate, a.modifiedDate);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 合并:文件夹在前,便签在后
|
|
|
|
|
notes.addAll(folders);
|
|
|
|
|
notes.addAll(normalNotes);
|
|
|
|
|
|
|
|
|
|
return notes;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 查询单个文件夹信息
|
|
|
|
|
*
|
|
|
|
|
* @param folderId 文件夹ID
|
|
|
|
|
* @return 文件夹信息,如果不存在返回null
|
|
|
|
|
*/
|
|
|
|
|
public NoteInfo getFolderInfo(long folderId) {
|
|
|
|
|
if (folderId == Notes.ID_ROOT_FOLDER) {
|
|
|
|
|
NoteInfo root = new NoteInfo();
|
|
|
|
|
root.id = Notes.ID_ROOT_FOLDER;
|
|
|
|
|
root.title = "我的便签";
|
|
|
|
|
root.snippet = "我的便签";
|
|
|
|
|
root.type = Notes.TYPE_FOLDER;
|
|
|
|
|
return root;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String selection = NoteColumns.ID + "=?";
|
|
|
|
|
String[] selectionArgs = new String[]{String.valueOf(folderId)};
|
|
|
|
|
|
|
|
|
|
Cursor cursor = contentResolver.query(
|
|
|
|
|
Notes.CONTENT_NOTE_URI,
|
|
|
|
|
null,
|
|
|
|
|
selection,
|
|
|
|
|
selectionArgs,
|
|
|
|
|
null
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (cursor != null) {
|
|
|
|
|
try {
|
|
|
|
|
if (cursor.moveToFirst()) {
|
|
|
|
|
return noteFromCursor(cursor);
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
cursor.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 查询文件夹的父文件夹ID(异步版本)
|
|
|
|
|
*
|
|
|
|
|
* @param folderId 文件夹ID
|
|
|
|
|
* @param callback 回调接口,返回父文件夹ID
|
|
|
|
|
*/
|
|
|
|
|
public void getParentFolderId(long folderId, Callback<Long> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
long parentId = getParentFolderId(folderId);
|
|
|
|
|
callback.onSuccess(parentId);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 查询文件夹的父文件夹ID
|
|
|
|
|
*
|
|
|
|
|
* @param folderId 文件夹ID
|
|
|
|
|
* @return 父文件夹ID,如果不存在返回根文件夹ID
|
|
|
|
|
*/
|
|
|
|
|
public long getParentFolderId(long folderId) {
|
|
|
|
|
if (folderId == Notes.ID_ROOT_FOLDER || folderId == Notes.ID_CALL_RECORD_FOLDER) {
|
|
|
|
|
return Notes.ID_ROOT_FOLDER;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NoteInfo folder = getFolderInfo(folderId);
|
|
|
|
|
if (folder != null) {
|
|
|
|
|
return folder.parentId;
|
|
|
|
|
}
|
|
|
|
|
return Notes.ID_ROOT_FOLDER;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取文件夹路径(从根到当前)
|
|
|
|
|
*
|
|
|
|
|
* @param folderId 当前文件夹ID
|
|
|
|
|
* @return 文件夹路径列表(从根到当前)
|
|
|
|
|
*/
|
|
|
|
|
public List<NoteInfo> getFolderPath(long folderId) {
|
|
|
|
|
List<NoteInfo> path = new ArrayList<>();
|
|
|
|
|
long currentId = folderId;
|
|
|
|
|
|
|
|
|
|
while (currentId != Notes.ID_ROOT_FOLDER) {
|
|
|
|
|
NoteInfo folder = getFolderInfo(currentId);
|
|
|
|
|
if (folder == null) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
path.add(0, folder); // 添加到列表头部
|
|
|
|
|
currentId = folder.parentId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 添加根文件夹
|
|
|
|
|
NoteInfo root = new NoteInfo();
|
|
|
|
|
root.id = Notes.ID_ROOT_FOLDER;
|
|
|
|
|
root.title = "我的便签";
|
|
|
|
|
root.snippet = "我的便签";
|
|
|
|
|
root.type = Notes.TYPE_FOLDER;
|
|
|
|
|
path.add(0, root);
|
|
|
|
|
|
|
|
|
|
return path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取文件夹路径(异步版本)
|
|
|
|
|
*
|
|
|
|
|
* @param folderId 当前文件夹ID
|
|
|
|
|
* @param callback 回调接口,返回文件夹路径列表
|
|
|
|
|
*/
|
|
|
|
|
public void getFolderPath(long folderId, Callback<List<NoteInfo>> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
List<NoteInfo> path = getFolderPath(folderId);
|
|
|
|
|
callback.onSuccess(path);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建新文件夹
|
|
|
|
|
*
|
|
|
|
|
* @param parentId 父文件夹ID
|
|
|
|
|
* @param name 文件夹名称
|
|
|
|
|
* @param callback 回调接口,返回新文件夹的ID
|
|
|
|
|
*/
|
|
|
|
|
public void createFolder(long parentId, String name, Callback<Long> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
|
long currentTime = System.currentTimeMillis();
|
|
|
|
|
|
|
|
|
|
values.put(NoteColumns.PARENT_ID, parentId);
|
|
|
|
|
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
|
|
|
|
|
values.put(NoteColumns.SNIPPET, name);
|
|
|
|
|
values.put(NoteColumns.CREATED_DATE, currentTime);
|
|
|
|
|
values.put(NoteColumns.MODIFIED_DATE, currentTime);
|
|
|
|
|
values.put(NoteColumns.LOCAL_MODIFIED, 1);
|
|
|
|
|
values.put(NoteColumns.NOTES_COUNT, 0);
|
|
|
|
|
|
|
|
|
|
Uri uri = contentResolver.insert(Notes.CONTENT_NOTE_URI, values);
|
|
|
|
|
|
|
|
|
|
Long folderId = 0L;
|
|
|
|
|
if (uri != null) {
|
|
|
|
|
try {
|
|
|
|
|
folderId = ContentUris.parseId(uri);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to parse folder ID from URI", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
callback.onSuccess(folderId);
|
|
|
|
|
Log.d(TAG, "Successfully created folder: " + name + " with ID: " + folderId);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to create folder: " + name, e);
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建新笔记
|
|
|
|
|
* <p>
|
|
|
|
|
* 在指定文件夹下创建一个空笔记
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param folderId 父文件夹ID
|
|
|
|
|
* @param callback 回调接口,返回新笔记的ID
|
|
|
|
|
*/
|
|
|
|
|
public void createNote(long folderId, Callback<Long> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
|
long currentTime = System.currentTimeMillis();
|
|
|
|
|
|
|
|
|
|
values.put(NoteColumns.PARENT_ID, folderId);
|
|
|
|
|
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
|
|
|
|
|
values.put(NoteColumns.CREATED_DATE, currentTime);
|
|
|
|
|
values.put(NoteColumns.MODIFIED_DATE, currentTime);
|
|
|
|
|
values.put(NoteColumns.LOCAL_MODIFIED, 1);
|
|
|
|
|
values.put(NoteColumns.SNIPPET, "");
|
|
|
|
|
|
|
|
|
|
Uri uri = contentResolver.insert(Notes.CONTENT_NOTE_URI, values);
|
|
|
|
|
|
|
|
|
|
Long noteId = 0L;
|
|
|
|
|
if (uri != null) {
|
|
|
|
|
try {
|
|
|
|
|
noteId = ContentUris.parseId(uri);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to parse note ID from URI", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (noteId > 0) {
|
|
|
|
|
callback.onSuccess(noteId);
|
|
|
|
|
Log.d(TAG, "Successfully created note with ID: " + noteId);
|
|
|
|
|
} else {
|
|
|
|
|
callback.onError(new IllegalStateException("Failed to create note, invalid ID returned"));
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to create note", e);
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 更新笔记内容
|
|
|
|
|
* <p>
|
|
|
|
|
* 更新笔记的标题和内容,自动更新修改时间和本地修改标志
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param noteId 笔记ID
|
|
|
|
|
* @param content 笔记内容
|
|
|
|
|
* @param callback 回调接口,返回影响的行数
|
|
|
|
|
*/
|
|
|
|
|
public void updateNote(long noteId, String content, Callback<Integer> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
|
long currentTime = System.currentTimeMillis();
|
|
|
|
|
|
|
|
|
|
values.put(NoteColumns.SNIPPET, extractSnippet(content));
|
|
|
|
|
values.put(NoteColumns.MODIFIED_DATE, currentTime);
|
|
|
|
|
values.put(NoteColumns.LOCAL_MODIFIED, 1);
|
|
|
|
|
|
|
|
|
|
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
|
|
|
|
|
int rows = contentResolver.update(uri, values, null, null);
|
|
|
|
|
|
|
|
|
|
if (rows > 0) {
|
|
|
|
|
// 查询现有的文本数据记录
|
|
|
|
|
Cursor cursor = contentResolver.query(
|
|
|
|
|
Notes.CONTENT_DATA_URI,
|
|
|
|
|
new String[]{DataColumns.ID},
|
|
|
|
|
DataColumns.NOTE_ID + " = ? AND " + DataColumns.MIME_TYPE + " = ?",
|
|
|
|
|
new String[]{String.valueOf(noteId), TextNote.CONTENT_ITEM_TYPE},
|
|
|
|
|
null
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
long dataId = 0;
|
|
|
|
|
if (cursor != null) {
|
|
|
|
|
try {
|
|
|
|
|
if (cursor.moveToFirst()) {
|
|
|
|
|
dataId = cursor.getLong(cursor.getColumnIndexOrThrow(DataColumns.ID));
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
cursor.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 更新或插入文本数据
|
|
|
|
|
ContentValues dataValues = new ContentValues();
|
|
|
|
|
dataValues.put(DataColumns.CONTENT, content);
|
|
|
|
|
|
|
|
|
|
if (dataId > 0) {
|
|
|
|
|
// 更新现有记录
|
|
|
|
|
Uri dataUri = ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId);
|
|
|
|
|
int dataRows = contentResolver.update(dataUri, dataValues, null, null);
|
|
|
|
|
if (dataRows > 0) {
|
|
|
|
|
callback.onSuccess(rows);
|
|
|
|
|
Log.d(TAG, "Successfully updated note: " + noteId);
|
|
|
|
|
} else {
|
|
|
|
|
callback.onError(new RuntimeException("Failed to update note data"));
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 插入新记录
|
|
|
|
|
dataValues.put(DataColumns.NOTE_ID, noteId);
|
|
|
|
|
dataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
|
|
|
|
|
Uri dataUri = contentResolver.insert(Notes.CONTENT_DATA_URI, dataValues);
|
|
|
|
|
if (dataUri != null) {
|
|
|
|
|
callback.onSuccess(rows);
|
|
|
|
|
Log.d(TAG, "Successfully updated note: " + noteId);
|
|
|
|
|
} else {
|
|
|
|
|
callback.onError(new RuntimeException("Failed to insert note data"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
callback.onError(new RuntimeException("No note found with ID: " + noteId));
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to update note: " + noteId, e);
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 删除笔记
|
|
|
|
|
* <p>
|
|
|
|
|
* 将笔记移动到回收站文件夹
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param noteId 笔记ID
|
|
|
|
|
* @param callback 回调接口,返回影响的行数
|
|
|
|
|
*/
|
|
|
|
|
public void deleteNote(long noteId, Callback<Integer> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
|
values.put(NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER);
|
|
|
|
|
values.put(NoteColumns.LOCAL_MODIFIED, 1);
|
|
|
|
|
|
|
|
|
|
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
|
|
|
|
|
int rows = contentResolver.update(uri, values, null, null);
|
|
|
|
|
|
|
|
|
|
if (rows > 0) {
|
|
|
|
|
callback.onSuccess(rows);
|
|
|
|
|
Log.d(TAG, "Successfully moved note to trash: " + noteId);
|
|
|
|
|
} else {
|
|
|
|
|
callback.onError(new RuntimeException("No note found with ID: " + noteId));
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to delete note: " + noteId, e);
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 批量删除笔记(逻辑删除,移动到回收站)
|
|
|
|
|
*
|
|
|
|
|
* @param noteIds 笔记ID列表
|
|
|
|
|
* @param callback 回调接口,返回影响的行数
|
|
|
|
|
*/
|
|
|
|
|
public void deleteNotes(List<Long> noteIds, Callback<Integer> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
if (noteIds == null || noteIds.isEmpty()) {
|
|
|
|
|
callback.onError(new IllegalArgumentException("Note IDs list is empty"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int totalRows = 0;
|
|
|
|
|
for (Long noteId : noteIds) {
|
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
|
values.put(NoteColumns.PARENT_ID, Notes.ID_TRASH_FOLER);
|
|
|
|
|
values.put(NoteColumns.LOCAL_MODIFIED, 1);
|
|
|
|
|
|
|
|
|
|
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
|
|
|
|
|
int rows = contentResolver.update(uri, values, null, null);
|
|
|
|
|
totalRows += rows;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (totalRows > 0) {
|
|
|
|
|
callback.onSuccess(totalRows);
|
|
|
|
|
Log.d(TAG, "Successfully moved " + totalRows + " notes to trash");
|
|
|
|
|
} else {
|
|
|
|
|
callback.onError(new RuntimeException("No notes were deleted"));
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to batch delete notes", e);
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 恢复笔记(从回收站移回根目录)
|
|
|
|
|
*
|
|
|
|
|
* @param noteIds 笔记ID列表
|
|
|
|
|
* @param callback 回调接口
|
|
|
|
|
*/
|
|
|
|
|
public void restoreNotes(List<Long> noteIds, Callback<Integer> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
if (noteIds == null || noteIds.isEmpty()) {
|
|
|
|
|
callback.onError(new IllegalArgumentException("Note IDs list is empty"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int totalRows = 0;
|
|
|
|
|
// 恢复到根目录
|
|
|
|
|
long targetFolderId = Notes.ID_ROOT_FOLDER;
|
|
|
|
|
|
|
|
|
|
for (Long noteId : noteIds) {
|
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
|
values.put(NoteColumns.PARENT_ID, targetFolderId);
|
|
|
|
|
values.put(NoteColumns.LOCAL_MODIFIED, 1);
|
|
|
|
|
|
|
|
|
|
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
|
|
|
|
|
int rows = contentResolver.update(uri, values, null, null);
|
|
|
|
|
totalRows += rows;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (totalRows > 0) {
|
|
|
|
|
callback.onSuccess(totalRows);
|
|
|
|
|
Log.d(TAG, "Successfully restored " + totalRows + " notes");
|
|
|
|
|
} else {
|
|
|
|
|
callback.onError(new RuntimeException("No notes were restored"));
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to restore notes", e);
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 永久删除笔记(物理删除)
|
|
|
|
|
*
|
|
|
|
|
* @param noteIds 笔记ID列表
|
|
|
|
|
* @param callback 回调接口
|
|
|
|
|
*/
|
|
|
|
|
public void deleteNotesForever(List<Long> noteIds, Callback<Integer> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
if (noteIds == null || noteIds.isEmpty()) {
|
|
|
|
|
callback.onError(new IllegalArgumentException("Note IDs list is empty"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int totalRows = 0;
|
|
|
|
|
for (Long noteId : noteIds) {
|
|
|
|
|
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
|
|
|
|
|
int rows = contentResolver.delete(uri, null, null);
|
|
|
|
|
totalRows += rows;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (totalRows > 0) {
|
|
|
|
|
callback.onSuccess(totalRows);
|
|
|
|
|
Log.d(TAG, "Successfully permanently deleted " + totalRows + " notes");
|
|
|
|
|
} else {
|
|
|
|
|
callback.onError(new RuntimeException("No notes were deleted"));
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to delete notes forever", e);
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 搜索笔记
|
|
|
|
|
* <p>
|
|
|
|
|
* 根据关键字在标题和内容中搜索笔记
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param keyword 搜索关键字
|
|
|
|
|
* @param callback 回调接口
|
|
|
|
|
*/
|
|
|
|
|
public void searchNotes(String keyword, Callback<List<NoteInfo>> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
if (keyword == null || keyword.trim().isEmpty()) {
|
|
|
|
|
callback.onSuccess(new ArrayList<>());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String selection = "(" + NoteColumns.TYPE + " = ?) AND (" +
|
|
|
|
|
NoteColumns.SNIPPET + " LIKE ? OR " +
|
|
|
|
|
NoteColumns.ID + " IN (SELECT " + DataColumns.NOTE_ID +
|
|
|
|
|
" FROM data WHERE " + DataColumns.CONTENT + " LIKE ?))";
|
|
|
|
|
|
|
|
|
|
String[] selectionArgs = new String[]{
|
|
|
|
|
String.valueOf(Notes.TYPE_NOTE),
|
|
|
|
|
"%" + keyword + "%",
|
|
|
|
|
"%" + keyword + "%"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Cursor cursor = contentResolver.query(
|
|
|
|
|
Notes.CONTENT_NOTE_URI,
|
|
|
|
|
null,
|
|
|
|
|
selection,
|
|
|
|
|
selectionArgs,
|
|
|
|
|
NoteColumns.MODIFIED_DATE + " DESC"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
List<NoteInfo> notes = new ArrayList<>();
|
|
|
|
|
if (cursor != null) {
|
|
|
|
|
try {
|
|
|
|
|
while (cursor.moveToNext()) {
|
|
|
|
|
notes.add(noteFromCursor(cursor));
|
|
|
|
|
}
|
|
|
|
|
Log.d(TAG, "Search returned " + cursor.getCount() + " results for: " + keyword);
|
|
|
|
|
} finally {
|
|
|
|
|
cursor.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
callback.onSuccess(notes);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to search notes: " + keyword, e);
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取笔记统计信息
|
|
|
|
|
* <p>
|
|
|
|
|
* 统计指定文件夹下的笔记数量
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param folderId 文件夹ID
|
|
|
|
|
* @param callback 回调接口
|
|
|
|
|
*/
|
|
|
|
|
public void countNotes(long folderId, Callback<Integer> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
String selection;
|
|
|
|
|
String[] selectionArgs;
|
|
|
|
|
|
|
|
|
|
if (folderId == Notes.ID_ROOT_FOLDER) {
|
|
|
|
|
selection = NoteColumns.TYPE + " != ?";
|
|
|
|
|
selectionArgs = new String[]{String.valueOf(Notes.TYPE_FOLDER)};
|
|
|
|
|
} else {
|
|
|
|
|
selection = NoteColumns.PARENT_ID + " = ?";
|
|
|
|
|
selectionArgs = new String[]{String.valueOf(folderId)};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Cursor cursor = contentResolver.query(
|
|
|
|
|
Notes.CONTENT_NOTE_URI,
|
|
|
|
|
new String[]{"COUNT(*) AS count"},
|
|
|
|
|
selection,
|
|
|
|
|
selectionArgs,
|
|
|
|
|
null
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
int count = 0;
|
|
|
|
|
if (cursor != null) {
|
|
|
|
|
try {
|
|
|
|
|
if (cursor.moveToFirst()) {
|
|
|
|
|
count = cursor.getInt(0);
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
cursor.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
callback.onSuccess(count);
|
|
|
|
|
Log.d(TAG, "Counted " + count + " notes in folder: " + folderId);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to count notes in folder: " + folderId, e);
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取文件夹列表
|
|
|
|
|
* <p>
|
|
|
|
|
* 查询所有文件夹类型的笔记
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param callback 回调接口
|
|
|
|
|
*/
|
|
|
|
|
public void getFolders(Callback<List<NoteInfo>> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
String selection = NoteColumns.TYPE + " = ?";
|
|
|
|
|
String[] selectionArgs = new String[]{
|
|
|
|
|
String.valueOf(Notes.TYPE_FOLDER)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Cursor cursor = contentResolver.query(
|
|
|
|
|
Notes.CONTENT_NOTE_URI,
|
|
|
|
|
null,
|
|
|
|
|
selection,
|
|
|
|
|
selectionArgs,
|
|
|
|
|
NoteColumns.MODIFIED_DATE + " DESC"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
List<NoteInfo> folders = new ArrayList<>();
|
|
|
|
|
if (cursor != null) {
|
|
|
|
|
try {
|
|
|
|
|
while (cursor.moveToNext()) {
|
|
|
|
|
folders.add(noteFromCursor(cursor));
|
|
|
|
|
}
|
|
|
|
|
Log.d(TAG, "Found " + cursor.getCount() + " folders");
|
|
|
|
|
} finally {
|
|
|
|
|
cursor.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
callback.onSuccess(folders);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to load folders", e);
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 批量移动笔记到指定文件夹
|
|
|
|
|
* <p>
|
|
|
|
|
* 将笔记从当前文件夹移动到目标文件夹
|
|
|
|
|
* </p>
|
|
|
|
|
*
|
|
|
|
|
* @param noteIds 要移动的笔记ID列表
|
|
|
|
|
* @param targetFolderId 目标文件夹ID
|
|
|
|
|
* @param callback 回调接口
|
|
|
|
|
*/
|
|
|
|
|
public void moveNotes(List<Long> noteIds, long targetFolderId, Callback<Integer> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
if (noteIds == null || noteIds.isEmpty()) {
|
|
|
|
|
callback.onError(new IllegalArgumentException("Note IDs list is empty"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int totalRows = 0;
|
|
|
|
|
for (Long noteId : noteIds) {
|
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
|
values.put(NoteColumns.PARENT_ID, targetFolderId);
|
|
|
|
|
values.put(NoteColumns.LOCAL_MODIFIED, 1);
|
|
|
|
|
|
|
|
|
|
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
|
|
|
|
|
int rows = contentResolver.update(uri, values, null, null);
|
|
|
|
|
totalRows += rows;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (totalRows > 0) {
|
|
|
|
|
callback.onSuccess(totalRows);
|
|
|
|
|
Log.d(TAG, "Successfully moved " + totalRows + " notes to folder: " + targetFolderId);
|
|
|
|
|
} else {
|
|
|
|
|
callback.onError(new RuntimeException("No notes were moved"));
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to move notes", e);
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 批量更新笔记置顶状态
|
|
|
|
|
*
|
|
|
|
|
* @param noteIds 笔记ID列表
|
|
|
|
|
* @param isPinned 是否置顶
|
|
|
|
|
* @param callback 回调接口
|
|
|
|
|
*/
|
|
|
|
|
public void batchTogglePin(List<Long> noteIds, boolean isPinned, Callback<Integer> callback) {
|
|
|
|
|
executor.execute(() -> {
|
|
|
|
|
try {
|
|
|
|
|
if (noteIds == null || noteIds.isEmpty()) {
|
|
|
|
|
callback.onError(new IllegalArgumentException("Note IDs list is empty"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int totalRows = 0;
|
|
|
|
|
ContentValues values = new ContentValues();
|
|
|
|
|
values.put(NoteColumns.TOP, isPinned ? 1 : 0);
|
|
|
|
|
values.put(NoteColumns.LOCAL_MODIFIED, 1);
|
|
|
|
|
|
|
|
|
|
for (Long noteId : noteIds) {
|
|
|
|
|
Uri uri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
|
|
|
|
|
int rows = contentResolver.update(uri, values, null, null);
|
|
|
|
|
totalRows += rows;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (totalRows > 0) {
|
|
|
|
|
callback.onSuccess(totalRows);
|
|
|
|
|
Log.d(TAG, "Successfully updated pin state for " + totalRows + " notes");
|
|
|
|
|
} else {
|
|
|
|
|
callback.onError(new RuntimeException("No notes were updated"));
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
Log.e(TAG, "Failed to update pin state", e);
|
|
|
|
|
callback.onError(e);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 从内容中提取摘要
|
|
|
|
|
*
|
|
|
|
|
* @param content 笔记内容
|
|
|
|
|
* @return 摘要文本(最多100个字符)
|
|
|
|
|
*/
|
|
|
|
|
private String extractSnippet(String content) {
|
|
|
|
|
if (content == null || content.isEmpty()) {
|
|
|
|
|
return "";
|
|
|
|
|
}
|
|
|
|
|
int maxLength = 100;
|
|
|
|
|
return content.length() > maxLength
|
|
|
|
|
? content.substring(0, maxLength)
|
|
|
|
|
: content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 关闭Executor
|
|
|
|
|
* <p>
|
|
|
|
|
* 在不再需要数据访问时调用,释放线程池资源
|
|
|
|
|
* </p>
|
|
|
|
|
*/
|
|
|
|
|
public void shutdown() {
|
|
|
|
|
if (executor != null && !executor.isShutdown()) {
|
|
|
|
|
executor.shutdown();
|
|
|
|
|
Log.d(TAG, "Executor shutdown");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|