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/widget/NoteWidgetProvider.java

257 lines
11 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.
*/
// NoteWidgetProvider.java - 便签小部件提供者抽象基类
// 主要功能:为便签小部件提供基础实现,包括数据加载、视图更新、点击处理
package net.micode.notes.widget;
// ======================= 导入区域 =======================
// Android小部件
import android.app.PendingIntent; // 待定意图
import android.appwidget.AppWidgetManager; // 小部件管理器
import android.appwidget.AppWidgetProvider; // 小部件提供者基类
// Android数据
import android.content.ContentValues; // 内容值
import android.content.Context; // 上下文
import android.content.Intent; // 意图
import android.database.Cursor; // 数据库查询结果游标
import android.util.Log; // 日志工具
// Android小部件视图
import android.widget.RemoteViews; // 远程视图
// 应用内部资源
import net.micode.notes.R; // 资源文件R类
// 应用数据模型
import net.micode.notes.data.Notes; // Notes主类
import net.micode.notes.data.Notes.NoteColumns; // 便签表列定义
// 应用资源解析
import net.micode.notes.tool.ResourceParser; // 资源解析器
// 应用界面
import net.micode.notes.ui.NoteEditActivity; // 便签编辑Activity
import net.micode.notes.ui.NotesListActivity; // 便签列表Activity
// ======================= 便签小部件提供者抽象基类 =======================
/**
* NoteWidgetProvider - 便签小部件提供者抽象基类
* 继承自AppWidgetProvider为便签小部件提供通用基础功能
* 抽象类具体小部件类型2x、4x需要实现抽象方法
* 核心功能:
* 1. 小部件数据加载和显示
* 2. 小部件点击事件处理
* 3. 小部件删除时数据清理
* 4. 隐私模式支持
*/
public abstract class NoteWidgetProvider extends AppWidgetProvider {
// ======================= 数据库查询配置 =======================
/**
* 查询投影数组 - 指定从数据库查询哪些字段
* 包含小部件显示所需的最小字段集
*/
public static final String [] PROJECTION = new String [] {
NoteColumns.ID, // 0 - 便签ID
NoteColumns.BG_COLOR_ID, // 1 - 背景颜色ID
NoteColumns.SNIPPET // 2 - 便签摘要
};
// ======================= 字段索引常量 =======================
public static final int COLUMN_ID = 0; // ID字段索引
public static final int COLUMN_BG_COLOR_ID = 1; // 背景颜色字段索引
public static final int COLUMN_SNIPPET = 2; // 摘要字段索引
// ======================= 常量定义 =======================
private static final String TAG = "NoteWidgetProvider"; // 日志标签
// ======================= 小部件生命周期方法 =======================
/**
* 小部件删除回调
* 当用户从小部件列表中删除小部件时调用
* 功能清理数据库中与该小部件关联的便签的小部件ID
*
* @param context 上下文
* @param appWidgetIds 被删除的小部件ID数组
*/
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
// 创建内容值,用于更新数据库
ContentValues values = new ContentValues();
// 将小部件ID设置为无效值
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
// 遍历所有被删除的小部件
for (int i = 0; i < appWidgetIds.length; i++) {
// 更新数据库清理关联的小部件ID
context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
values,
NoteColumns.WIDGET_ID + "=?", // 条件小部件ID匹配
new String[] { String.valueOf(appWidgetIds[i])});
}
}
// ======================= 小部件信息查询 =======================
/**
* 获取便签小部件信息
* 根据小部件ID查询关联的便签信息
* 注意:排除回收站中的便签
*
* @param context 上下文
* @param widgetId 小部件ID
* @return 便签信息游标包含ID、背景颜色、摘要
*/
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
// 条件小部件ID匹配 且 不在回收站中
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null);
}
// ======================= 小部件更新方法 =======================
/**
* 更新小部件(公开方法)
* 默认使用非隐私模式更新
*
* @param context 上下文
* @param appWidgetManager 小部件管理器
* @param appWidgetIds 要更新的小部件ID数组
*/
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false); // 默认非隐私模式
}
/**
* 更新小部件(私有方法)
* 实际执行小部件更新逻辑
* 更新流程:
* 1. 查询便签信息
* 2. 构建远程视图
* 3. 设置点击意图
* 4. 更新小部件
*
* @param context 上下文
* @param appWidgetManager 小部件管理器
* @param appWidgetIds 要更新的小部件ID数组
* @param privacyMode 是否处于隐私模式
* true: 隐私模式,显示"浏览中"文本
* false: 正常模式,显示便签内容
*/
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)); // 便签ID
intent.setAction(Intent.ACTION_VIEW); // 查看模式
} else {
// 无关联便签:显示默认文本
snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 创建/编辑模式
}
// 关闭游标
if (c != null) {
c.close();
}
// 创建远程视图
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
// 设置背景图片
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
/**
* 生成待定意图用于启动小部件对应的Activity
*/
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);
} else {
// 正常模式:显示便签内容,点击编辑便签
rv.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
// 设置点击事件
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
// 更新小部件
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
}
// ======================= 抽象方法 =======================
/**
* 获取背景资源ID
* 将背景颜色ID映射到实际的Drawable资源ID
* 抽象方法,由具体子类实现
*
* @param bgId 背景颜色ID
* @return Drawable资源ID
*/
protected abstract int getBgResourceId(int bgId);
/**
* 获取布局ID
* 返回小部件使用的布局资源ID
* 抽象方法,由具体子类实现
*
* @return 布局资源ID
*/
protected abstract int getLayoutId();
/**
* 获取小部件类型
* 返回小部件的类型常量
* 抽象方法,由具体子类实现
*
* @return 小部件类型TYPE_WIDGET_2X 或 TYPE_WIDGET_4X
*/
protected abstract int getWidgetType();
}