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.

377 lines
14 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.widget;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
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;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
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 {
// 数据库查询字段
private static final String[] PROJECTION = new String[] {
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET,
NoteColumns.WIDGET_ID // 添加字段用于数据一致性检查
};
// 查询字段索引
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) {
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);
}
}
// ========================== 核心功能方法 ==========================
/**
* 获取与指定小部件关联的笔记信息
*
* @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,
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) {
if (context == null || appWidgetManager == null || appWidgetIds == null) {
Log.w(TAG, "无效的更新请求参数");
return;
}
for (int widgetId : appWidgetIds) {
// 跳过无效小部件ID
if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
continue;
}
// 初始化默认值
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());
// 查询数据库获取笔记信息
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 {
// 未找到关联笔记
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);
}
// 创建小部件视图
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
}
}