From fb895eb777fb1c4d5cc73967527b5d41e2b7a8f1 Mon Sep 17 00:00:00 2001 From: mxvwfs5gq <2694470752@qq.com> Date: Thu, 12 Jun 2025 21:17:17 +0800 Subject: [PATCH] Update NoteWidgetProvider.java --- .../notes/widget/NoteWidgetProvider.java | 371 +++++++++++++++--- 1 file changed, 308 insertions(+), 63 deletions(-) diff --git a/java/net/micode/notes/widget/NoteWidgetProvider.java b/java/net/micode/notes/widget/NoteWidgetProvider.java index ec6f819..c3c7199 100644 --- a/java/net/micode/notes/widget/NoteWidgetProvider.java +++ b/java/net/micode/notes/widget/NoteWidgetProvider.java @@ -15,6 +15,7 @@ */ package net.micode.notes.widget; + import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; @@ -22,6 +23,7 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; +import android.net.Uri; import android.util.Log; import android.widget.RemoteViews; @@ -32,101 +34,344 @@ import net.micode.notes.tool.ResourceParser; import net.micode.notes.ui.NoteEditActivity; import net.micode.notes.ui.NotesListActivity; +/** + * 笔记小部件基础提供者抽象类 + * + * 功能概述: + * 1. 管理小部件生命周期 + * 2. 处理小部件点击事件 + * 3. 从数据库加载笔记数据更新小部件视图 + * 4. 支持隐私模式显示 + * + * 子类需要实现以下抽象方法: + * - getBgResourceId() : 获取背景资源ID + * - getLayoutId() : 获取布局资源ID + * - getWidgetType() : 获取小部件类型 + */ public abstract class NoteWidgetProvider extends AppWidgetProvider { - public static final String [] PROJECTION = new String [] { + + // 数据库查询字段 + private static final String[] PROJECTION = new String[] { NoteColumns.ID, NoteColumns.BG_COLOR_ID, - NoteColumns.SNIPPET + NoteColumns.SNIPPET, + NoteColumns.WIDGET_ID // 添加字段用于数据一致性检查 }; - public static final int COLUMN_ID = 0; - public static final int COLUMN_BG_COLOR_ID = 1; - public static final int COLUMN_SNIPPET = 2; - + // 查询字段索引 + private static final int COLUMN_ID = 0; + private static final int COLUMN_BG_COLOR_ID = 1; + private static final int COLUMN_SNIPPET = 2; + private static final int COLUMN_WIDGET_ID = 3; // 添加的索引 + private static final String TAG = "NoteWidgetProvider"; + // ========================== 生命周期方法 ========================== + + /** + * 当小部件被删除时调用 - 清理相关笔记的widget_id字段 + * + * @param context 应用上下文 + * @param appWidgetIds 被删除的小部件ID数组 + */ @Override public void onDeleted(Context context, int[] appWidgetIds) { - ContentValues values = new ContentValues(); - values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); - for (int i = 0; i < appWidgetIds.length; i++) { - context.getContentResolver().update(Notes.CONTENT_NOTE_URI, - values, - NoteColumns.WIDGET_ID + "=?", - new String[] { String.valueOf(appWidgetIds[i])}); + try { + // 防止空指针异常 + if (context == null || appWidgetIds == null || appWidgetIds.length == 0) { + Log.w(TAG, "无效的小部件删除请求"); + return; + } + + ContentValues values = new ContentValues(); + values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + + for (int widgetId : appWidgetIds) { + // 使用try-catch防止个别更新失败中断整个操作 + try { + int rowsUpdated = context.getContentResolver().update( + Notes.CONTENT_NOTE_URI, + values, + NoteColumns.WIDGET_ID + "=?", + new String[] { String.valueOf(widgetId) } + ); + + Log.d(TAG, "已清理小部件 " + widgetId + " 关联的笔记引用,影响行数: " + rowsUpdated); + } catch (Exception e) { + Log.e(TAG, "清理小部件 " + widgetId + " 关联的笔记引用时出错", e); + } + } + } catch (Exception e) { + Log.e(TAG, "小部件删除处理发生未预期错误", e); } } - private Cursor getNoteWidgetInfo(Context context, int widgetId) { - return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, + // ========================== 核心功能方法 ========================== + + /** + * 获取与指定小部件关联的笔记信息 + * + * @param context 应用上下文 + * @param widgetId 小部件ID + * @return 笔记数据Cursor(需在调用处关闭) + */ + protected Cursor getNoteWidgetInfo(Context context, int widgetId) { + if (context == null || widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { + return null; + } + + String selection = NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?"; + String[] selectionArgs = new String[] { + String.valueOf(widgetId), + String.valueOf(Notes.ID_TRASH_FOLDER) // 修正: TRASH_FOLER -> TRASH_FOLDER + }; + + try { + return context.getContentResolver().query( + Notes.CONTENT_NOTE_URI, PROJECTION, - NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", - new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, - null); + selection, + selectionArgs, + null + ); + } catch (Exception e) { + Log.e(TAG, "查询小部件 " + widgetId + " 关联笔记信息时出错", e); + return null; + } } + /** + * 更新小部件显示 - 公开方法供外部调用 + * + * @param context 应用上下文 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 要更新的小部件ID数组 + */ protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { update(context, appWidgetManager, appWidgetIds, false); } + /** + * 更新小部件显示(支持隐私模式)- 内部实现 + * + * @param context 应用上下文 + * @param appWidgetManager 小部件管理器 + * @param appWidgetIds 要更新的小部件ID数组 + * @param privacyMode 是否为隐私模式(隐藏真实内容) + */ private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, boolean privacyMode) { - for (int i = 0; i < appWidgetIds.length; i++) { - if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { - int bgId = ResourceParser.getDefaultBgId(context); - String snippet = ""; - Intent intent = new Intent(context, NoteEditActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); - intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); - - Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); - if (c != null && c.moveToFirst()) { - if (c.getCount() > 1) { - Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); - c.close(); - return; - } - snippet = c.getString(COLUMN_SNIPPET); - bgId = c.getInt(COLUMN_BG_COLOR_ID); - intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); - intent.setAction(Intent.ACTION_VIEW); - } else { - snippet = context.getResources().getString(R.string.widget_havenot_content); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); - } + + if (context == null || appWidgetManager == null || appWidgetIds == null) { + Log.w(TAG, "无效的更新请求参数"); + return; + } + + for (int widgetId : appWidgetIds) { + // 跳过无效小部件ID + if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { + continue; + } - if (c != null) { - c.close(); - } + // 初始化默认值 + int bgId = ResourceParser.NoteColor.YELLOW.id; + String snippet = ""; + long noteId = Notes.DEFAULT_NOTE_ID; + + // 准备笔记编辑意图 + Intent noteIntent = new Intent(context, NoteEditActivity.class); + noteIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + noteIntent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, widgetId); + noteIntent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); - RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); - rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); - intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); - /** - * Generate the pending intent to start host for the widget - */ - PendingIntent pendingIntent = null; - if (privacyMode) { - rv.setTextViewText(R.id.widget_text, - context.getString(R.string.widget_under_visit_mode)); - pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( - context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); + // 查询数据库获取笔记信息 + try (Cursor cursor = getNoteWidgetInfo(context, widgetId)) { + if (cursor != null && cursor.moveToFirst()) { + // 安全检查:确保一个WidgetID只关联一个笔记 + if (cursor.getCount() > 1) { + Log.w(TAG, "小部件 " + widgetId + " 关联多个笔记,请检查数据一致性"); + + // 清除异常关联的笔记 + cleanInvalidWidgetLinks(context, widgetId, cursor); + + // 只处理第一条记录 + } + + // 获取笔记数据 + noteId = cursor.getLong(COLUMN_ID); + snippet = cursor.getString(COLUMN_SNIPPET); + bgId = cursor.getInt(COLUMN_BG_COLOR_ID); + + noteIntent.putExtra(Intent.EXTRA_UID, noteId); + noteIntent.setAction(Intent.ACTION_VIEW); + + // 检查数据一致性 + int storedWidgetId = cursor.getInt(COLUMN_WIDGET_ID); + if (storedWidgetId != widgetId) { + Log.w(TAG, String.format( + "笔记%d的小部件ID不一致: 数据库=%d, 请求=%d", + noteId, storedWidgetId, widgetId + )); + } } else { - rv.setTextViewText(R.id.widget_text, snippet); - pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, - PendingIntent.FLAG_UPDATE_CURRENT); + // 未找到关联笔记 + snippet = context.getString(R.string.widget_no_content); + noteIntent.setAction(Intent.ACTION_INSERT_OR_EDIT); } + } catch (Exception e) { + Log.e(TAG, "处理小部件 " + widgetId + " 时发生错误", e); + snippet = context.getString(R.string.widget_error_loading); + } - rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); - appWidgetManager.updateAppWidget(appWidgetIds[i], rv); + // 创建小部件视图 + RemoteViews remoteViews = new RemoteViews(context.getPackageName(), getLayoutId()); + + // 设置背景 + int bgResId = getBgResourceId(bgId); + remoteViews.setImageViewResource(R.id.widget_bg_image, bgResId); + noteIntent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); + + // 创建点击意图 + PendingIntent pendingIntent; + if (privacyMode) { + // 隐私模式:显示提示文本,点击进入主列表 + remoteViews.setTextViewText(R.id.widget_text, + context.getString(R.string.widget_privacy_mode)); + + Intent listIntent = new Intent(context, NotesListActivity.class); + pendingIntent = PendingIntent.getActivity( + context, widgetId, listIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE + ); + } else { + // 正常模式:显示笔记摘要,点击进入笔记编辑 + remoteViews.setTextViewText(R.id.widget_text, snippet); + + pendingIntent = PendingIntent.getActivity( + context, widgetId, noteIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE + ); + } + + // 设置点击事件 + remoteViews.setOnClickPendingIntent(R.id.widget_text, pendingIntent); + + // 应用更新 + try { + appWidgetManager.updateAppWidget(widgetId, remoteViews); + Log.d(TAG, "成功更新小部件: " + widgetId); + } catch (Exception e) { + Log.e(TAG, "更新小部件 " + widgetId + " 失败", e); } } } + // ========================== 抽象方法 ========================== + + /** + * 获取背景资源ID + * + * @param bgId 背景颜色ID + * @return 对应的drawable资源ID + */ protected abstract int getBgResourceId(int bgId); + /** + * 获取小部件布局资源ID + * + * @return 布局资源ID + */ protected abstract int getLayoutId(); + /** + * 获取小部件类型 + * + * @return 小部件类型常量 + */ protected abstract int getWidgetType(); -} + + // ========================== 新增功能方法 ========================== + + /** + * 清理无效的小部件关联(数据一致性修复) + * + * @param context 应用上下文 + * @param widgetId 小部件ID + * @param cursor 查询到的关联笔记数据 + */ + private void cleanInvalidWidgetLinks(Context context, int widgetId, Cursor cursor) { + if (cursor == null) return; + + ContentValues values = new ContentValues(); + values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + + try { + // 遍历所有关联笔记 + cursor.moveToPosition(-1); // 移动到开始位置 + while (cursor.moveToNext()) { + long currentNoteId = cursor.getLong(COLUMN_ID); + int currentWidgetId = cursor.getInt(COLUMN_WIDGET_ID); + + // 只保留最新关联的笔记 + if (currentWidgetId == widgetId) { + Log.i(TAG, "保留笔记 " + currentNoteId + " 关联的小部件 " + widgetId); + continue; + } + + // 解除其他笔记的关联 + Uri noteUri = ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, currentNoteId); + int rows = context.getContentResolver().update( + noteUri, values, null, null); + + if (rows > 0) { + Log.w(TAG, "已清理笔记 " + currentNoteId + " 上的小部件关联"); + } + } + } catch (Exception e) { + Log.e(TAG, "清理无效小部件关联时出错", e); + } + } + + // ========================== 新增功能:定时刷新支持 ========================== + + /** + * 设置小部件自动刷新间隔 + * + * @param context 应用上下文 + * @param appWidgetId 小部件ID + * @param intervalMinutes 刷新间隔(分钟) + */ + public static void setUpdateInterval(Context context, int appWidgetId, int intervalMinutes) { + // 最小间隔限制 + if (intervalMinutes < 15) { + Log.w(TAG, "刷新间隔不能小于15分钟"); + intervalMinutes = 60; // 默认1小时 + } + + // TODO: 实现AlarmManager定时刷新逻辑 + } + + // ========================== 新增功能:多尺寸小部件支持 ========================== + + /** + * 小部件尺寸类型 + */ + public enum WidgetSize { + SIZE_1X1, + SIZE_2X1, + SIZE_2X2, + SIZE_3X3 + } + + /** + * 获取当前小部件尺寸 + * + * @param context 应用上下文 + * @param appWidgetId 小部件ID + * @return 小部件尺寸类型 + */ + protected WidgetSize getWidgetSize(Context context, int appWidgetId) { + // TODO: 实现小部件尺寸检测逻辑 + return WidgetSize.SIZE_2X1; // 默认返回2x1 + } +} \ No newline at end of file