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.
xiaomi/ui/NotesListActivity.java

1111 lines
53 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.ui;
// 导入Android SDK提供的类和接口
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.appwidget.AppWidgetManager;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnCreateContextMenuListener;
import android.view.View.OnTouchListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.TextView;
import android.widget.Toast;
// 导入项目内部使用的类和接口
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.tool.BackupUtils;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
// 定义了查询操作的token用于识别查询操作
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
private static final int FOLDER_LIST_QUERY_TOKEN = 1;
// 定义了菜单项的ID用于识别不同的菜单操作
private static final int MENU_FOLDER_DELETE = 0;
private static final int MENU_FOLDER_VIEW = 1;
private static final int MENU_FOLDER_CHANGE_NAME = 2;
// 定义了偏好设置的key用于检查是否显示过介绍信息
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
// 定义了列表编辑状态的枚举,用于区分不同的列表编辑状态
private enum ListEditState {
NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER
};
// 声明了ListEditState类型的成员变量mState用于记录当前的列表编辑状态
private ListEditState mState;
// 声明了BackgroundQueryHandler类型的成员变量mBackgroundQueryHandler用于后台查询操作
private BackgroundQueryHandler mBackgroundQueryHandler;
// 声明了NotesListAdapter类型的成员变量mNotesListAdapter用于笔记列表的适配器
private NotesListAdapter mNotesListAdapter;
// 声明了ListView类型的成员变量mNotesListView用于显示笔记列表
private ListView mNotesListView;
// 声明了Button类型的成员变量mAddNewNote用于添加新笔记的按钮
private Button mAddNewNote;
// 声明了boolean类型的成员变量mDispatch用于标记是否分发事件
private boolean mDispatch;
// 声明了int类型的成员变量mOriginY和mDispatchY用于记录触摸事件的坐标
private int mOriginY;
private int mDispatchY;
// 声明了TextView类型的成员变量mTitleBar用于显示标题栏
private TextView mTitleBar;
// 声明了long类型的成员变量mCurrentFolderId用于记录当前文件夹的ID
private long mCurrentFolderId;
// 声明了ContentResolver类型的成员变量mContentResolver用于进行内容提供者的操作
private ContentResolver mContentResolver;
// 声明了ModeCallback类型的成员变量mModeCallBack用于多选模式的回调
private ModeCallback mModeCallBack;
// 声明了String类型的常量TAG用于日志标记
private static final String TAG = "NotesListActivity";
// 声明了int类型的常量NOTES_LISTVIEW_SCROLL_RATE用于设置列表滚动速率
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
// 声明了NoteItemData类型的成员变量mFocusNoteDataItem用于记录当前焦点的笔记项数据
private NoteItemData mFocusNoteDataItem;
// 声明了用于查询的SQL语句字符串常量
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)";
// 声明了requestCode常量用于启动其他Activity时请求码的识别
private final static int REQUEST_CODE_OPEN_NODE = 102;
private final static int REQUEST_CODE_NEW_NODE = 103;
/**
* onCreate方法Activity生命周期中的一部分当Activity第一次创建时调用。
* @param savedInstanceState 之前保存的InstanceState可以用来恢复状态。
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_list); // 设置Activity的布局文件
initResources(); // 初始化资源
/**
* 当用户第一次使用应用时,插入一个介绍信息。
*/
setAppInfoFromRawRes(); // 从raw资源中读取介绍信息并显示
}
/**
* onActivityResult方法Activity生命周期中的一部分用于处理其他Activity返回的结果。
* @param requestCode 请求码,用于识别是哪个请求返回的结果。
* @param resultCode 结果码,表示请求的处理结果。
* @param data 包含返回数据的Intent。
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK // 如果结果是成功的
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) {
mNotesListAdapter.changeCursor(null); // 更改适配器的游标
} else {
super.onActivityResult(requestCode, resultCode, data); // 其他情况调用父类的onActivityResult
}
}
/**
* setAppInfoFromRawRes方法从raw资源中读取介绍信息并显示。
*/
private void setAppInfoFromRawRes() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); // 获取默认的SharedPreferences
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { // 如果没有显示过介绍信息
StringBuilder sb = new StringBuilder(); // 创建StringBuilder用于拼接字符串
InputStream in = null; // 创建InputStream对象
try {
in = getResources().openRawResource(R.raw.introduction); // 打开raw资源文件
if (in != null) {
InputStreamReader isr = new InputStreamReader(in); // 创建InputStreamReader对象
BufferedReader br = new BufferedReader(isr); // 创建BufferedReader对象
char [] buf = new char[1024]; // 创建字符数组作为缓冲区
int len = 0; // 用于存储每次读取的长度
while ((len = br.read(buf)) > 0) { // 循环读取资源文件
sb.append(buf, 0, len); // 将读取的内容添加到StringBuilder中
}
} else {
Log.e(TAG, "Read introduction file error"); // 如果资源文件打开失败,打印错误日志
return;
}
} catch (IOException e) {
e.printStackTrace(); // 如果发生IO异常打印堆栈跟踪
return;
} finally {
if(in != null) { // 在finally块中关闭InputStream
try {
in.close(); // 关闭InputStream
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace(); // 如果关闭失败,打印堆栈跟踪
}
}
}
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, // 创建一个新的笔记
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
ResourceParser.RED);
note.setWorkingText(sb.toString()); // 设置笔记的内容
if (note.saveNote()) { // 如果笔记保存成功
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); // 更新SharedPreferences标记已经显示过介绍信息
} else {
Log.e(TAG, "Save introduction note error"); // 如果保存失败,打印错误日志
return;
}
}
}
/**
* onStart方法Activity生命周期中的一部分当Activity对用户可见时调用。
*/
@Override
protected void onStart() {
super.onStart(); // 调用父类的onStart方法
startAsyncNotesListQuery(); // 开始异步查询笔记列表
}
/**
* initResources方法用于初始化资源和UI组件。
*/
private void initResources() {
mContentResolver = this.getContentResolver(); // 获取ContentResolver对象
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); // 创建后台查询处理器
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 设置当前文件夹ID为根文件夹ID
mNotesListView = (ListView) findViewById(R.id.notes_list); // 通过ID获取ListView组件
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), null, false); // 为ListView添加底部视图
mNotesListView.setOnItemClickListener(new OnListItemClickListener()); // 设置ListView的点击事件监听器
mNotesListView.setOnItemLongClickListener(this); // 设置ListView的长按事件监听器
mNotesListAdapter = new NotesListAdapter(this); // 创建笔记列表适配器
mNotesListView.setAdapter(mNotesListAdapter); // 为ListView设置适配器
mAddNewNote = (Button) findViewById(R.id.btn_new_note); // 通过ID获取添加新笔记的按钮
mAddNewNote.setOnClickListener(this); // 设置按钮的点击事件监听器
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); // 设置按钮的触摸事件监听器
mDispatch = false; // 初始化
// 初始化变量mDispatchY和mOriginY
mDispatchY = 0;
mOriginY = 0;
// 获取标题栏TextView组件
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
// 初始化mState为NOTE_LIST状态
mState = ListEditState.NOTE_LIST;
// 初始化ModeCallback对象
mModeCallBack = new ModeCallback();
}
/**
* ModeCallback内部类实现了MultiChoiceModeListener和OnMenuItemClickListener接口
* 用于处理ListView的多选模式和菜单项点击事件。
*/
private class ModeCallback implements ListView.MultiChoiceModeListener, MenuItem.OnMenuItemClickListener {
private DropdownMenu mDropDownMenu; // 用于显示下拉菜单的对象
private ActionMode mActionMode; // 用于管理ActionMode多选模式的对象
private MenuItem mMoveMenu; // 移动菜单项
/**
* onCreateActionMode方法当ActionMode创建时调用。
* @param mode ActionMode对象
* @param menu 菜单对象
* @return 是否创建成功
*/
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
getMenuInflater().inflate(R.menu.note_list_options, menu); // 填充菜单
menu.findItem(R.id.delete).setOnMenuItemClickListener(this); // 设置删除菜单项的点击监听
mMoveMenu = menu.findItem(R.id.move); // 获取移动菜单项
// 根据条件设置移动菜单项的可见性
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
mMoveMenu.setVisible(false);
} else {
mMoveMenu.setVisible(true);
mMoveMenu.setOnMenuItemClickListener(this);
}
mActionMode = mode; // 保存ActionMode对象
mNotesListAdapter.setChoiceMode(true); // 设置适配器为多选模式
mNotesListView.setLongClickable(false); // 设置ListView不再响应长按事件
mAddNewNote.setVisibility(View.GONE); // 隐藏添加新笔记的按钮
// 创建自定义视图作为ActionMode的自定义布局
View customView = LayoutInflater.from(NotesListActivity.this).inflate(
R.layout.note_list_dropdown_menu, null);
mode.setCustomView(customView);
mDropDownMenu = new DropdownMenu(NotesListActivity.this,
(Button) customView.findViewById(R.id.selection_menu),
R.menu.note_list_dropdown);
// 设置下拉菜单项的点击监听
mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){
public boolean onMenuItemClick(MenuItem item) {
mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); // 切换全选状态
updateMenu(); // 更新菜单状态
return true;
}
});
return true; // 返回true表示成功创建ActionMode
}
/**
* 更新菜单状态的方法。
*/
private void updateMenu() {
int selectedCount = mNotesListAdapter.getSelectedCount(); // 获取选中项的数量
// 更新下拉菜单标题
String format = getResources().getString(R.string.menu_select_title, selectedCount);
mDropDownMenu.setTitle(format);
MenuItem item = mDropDownMenu.findItem(R.id.action_select_all); // 获取全选菜单项
if (item != null) {
if (mNotesListAdapter.isAllSelected()) {
item.setChecked(true); // 设置为选中状态
item.setTitle(R.string.menu_deselect_all); // 更新标题为“取消全选”
} else {
item.setChecked(false); // 设置为未选中状态
item.setTitle(R.string.menu_select_all); // 更新标题为“全选”
}
}
}
/**
* onPrepareActionMode方法当ActionMode准备时调用。
* @param mode ActionMode对象
* @param menu 菜单对象
* @return 是否需要更新ActionMode
*/
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub
return false;
}
/**
* onActionItemClicked方法当ActionMode中的Action项被点击时调用。
* @param mode ActionMode对象
* @param item 被点击的菜单项
* @return 是否处理了点击事件
*/
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false;
}
/**
* onDestroyActionMode方法当ActionMode销毁时调用。
*/
public void onDestroyActionMode(ActionMode mode) {
mNotesListAdapter.setChoiceMode(false); // 取消多选模式
mNotesListView.setLongClickable(true); // 允许ListView响应长按事件
mAddNewNote.setVisibility(View.VISIBLE); // 显示添加新笔记的按钮
}
/**
* finishActionMode方法结束当前的ActionMode。
*/
public void finishActionMode() {
mActionMode.finish(); // 结束ActionMode
}
/**
* onItemCheckedStateChanged方法当ActionMode中的一项被选中或取消选中时调用。
* @param mode ActionMode对象
* @param position 被选中或取消选中项的位置
* @param id 被选中或取消选中项的ID
* @param checked 是否被选中
*/
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
mNotesListAdapter.setCheckedItem(position, checked); // 设置项的选中状态
updateMenu(); // 更新菜单状态
}
/**
* onMenuItemClick方法当ActionMode中的菜单项被点击时调用。
* @param item 被点击的菜单项
* @return 是否处理了点击事件
*/
public boolean onMenuItemClick(MenuItem item) {
if (mNotesListAdapter.getSelectedCount() == 0) {
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
Toast.LENGTH_SHORT).show(); // 提示没有选中任何项
return true;
}
switch (item.getItemId()) {
case R.id.delete:
// 显示删除确认对话框
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_notes,
mNotesListAdapter.getSelectedCount()));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
batchDelete(); // 执行批量删除操作
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.move:
// 执行查询目标文件夹的操作
startQueryDestinationFolders();
break;
default:
return false;
}
return true;
}
}
/**
* NewNoteOnTouchListener内部类实现了OnTouchListener接口
* 用于处理“新笔记”按钮的触摸事件。
*/
private class NewNoteOnTouchListener implements OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
// 获取屏幕高度和“新笔记”按钮的高度
Display display = getWindowManager().getDefaultDisplay();
int screenHeight = display.getHeight();
int newNoteViewHeight = mAddNewNote.getHeight();
int start = screenHeight - newNoteViewHeight;
int eventY = start + (int) event.getY();
// 如果点击的是“新笔记”按钮的透明部分则将事件分发到ListView
if (event.getY() < (event.getX() * (-0.12) + 94)) {
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- mNotesListView.getFooterViewsCount());
if (view != null && view.getBottom() > start
&& (view.getTop() < (start + 94))) {
mOriginY = (int) event.getY();
mDispatchY = eventY;
event.setLocation(event.getX(), mDispatchY);
mDispatch = true;
return mNotesListView.dispatchTouchEvent(event); // 分发触摸事件
}
}
break;
}
case MotionEvent.ACTION_MOVE: {
// 在移动时,如果之前已经分发事件,则继续分发
if (mDispatch) {
mDispatchY += (int) event.getY() - mOriginY;
event.setLocation(event.getX(), mDispatchY);
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
default: {
// 在其他情况下,如果之前已经分发事件,则结束分发
if (mDispatch) {
event.setLocation(event.getX(), mDispatchY);
mDispatch = false;
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
}
return false; // 返回false表示没有处理触摸事件
}
};
/**
* startAsyncNotesListQuery方法用于启动异步查询笔记列表的操作。
*/
private void startAsyncNotesListQuery() {
// 根据当前文件夹ID选择不同的查询条件
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
// 启动后台查询,查询笔记列表
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] {
String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}
/**
* BackgroundQueryHandler内部类继承自AsyncQueryHandler
* 用于处理异步查询操作。
*/
private final class BackgroundQueryHandler extends AsyncQueryHandler {
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver); // 调用父类构造方法
}
/**
* onQueryComplete方法当查询操作完成时调用。
* @param token 查询操作的token
* @param cookie 传递给查询操作的cookie
* @param cursor 查询结果游标
*/
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case FOLDER_NOTE_LIST_QUERY_TOKEN:
mNotesListAdapter.changeCursor(cursor); // 切换适配器的游标为查询结果
break;
case FOLDER_LIST_QUERY_TOKEN:
if (cursor != null && cursor.getCount() > 0) {
showFolderListMenu(cursor); // 显示文件夹列表菜单
} else {
Log.e(TAG, "Query folder failed"); // 查询文件夹失败,打印错误日志
}
break;
default:
return;
}
}
}
/**
* showFolderListMenu方法显示文件夹列表菜单。
* @param cursor 查询到的文件夹列表游标
*/
private void showFolderListMenu(Cursor cursor) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); // 创建AlertDialog.Builder对象
builder.setTitle(R.string.menu_title_select_folder); // 设置对话框标题
final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor); // 创建文件夹列表适配器
builder.setAdapter(adapter, new DialogInterface.OnClickListener() { // 设置列表项点击事件
public void onClick(DialogInterface dialog, int which) {
DataUtils.batchMoveToFolder(mContentResolver, // 批量移动笔记到选中的文件夹
mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which));
Toast.makeText(NotesListActivity.this, // 显示移动成功的提示
getString(R.string.format_move_notes_to_folder,
mNotesListAdapter.getSelectedCount(),
adapter.getFolderName(NotesListActivity.this, which)),
Toast.LENGTH_SHORT).show();
mModeCallBack.finishActionMode(); // 结束多选模式
}
});
builder.show(); // 显示对话框
}
/**
* createNewNote方法创建新笔记。
*/
private void createNewNote() {
Intent intent = new Intent(this, NoteEditActivity.class); // 创建Intent跳转到NoteEditActivity
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 设置Intent动作
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); // 传递当前文件夹ID
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); // 启动Activity并请求结果
}
/**
* batchDelete方法批量删除笔记。
*/
private void batchDelete() {
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() { // 创建异步任务
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget(); // 获取选中的Widget
if (!isSyncMode()) { // 如果不是同步模式,则直接删除笔记
if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter
.getSelectedItemIds())) {
} else {
Log.e(TAG, "Delete notes error, should not happens"); // 删除失败,打印错误日志
}
} else { // 如果是同步模式,则将笔记移动到回收站文件夹
if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter
.getSelectedItemIds(), Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens"); // 移动失败,打印错误日志
}
}
return widgets; // 返回Widget集合
}
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
if (widgets != null) { // 如果Widget集合不为空
for (AppWidgetAttribute widget : widgets) { // 遍历Widget集合
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType); // 更新Widget
}
}
}
mModeCallBack.finishActionMode(); // 结束多选模式
}
}.execute(); // 执行异步任务
}
/**
* deleteFolder方法删除文件夹。
* @param folderId 要删除的文件夹ID
*/
private void deleteFolder(long folderId) {
if (folderId == Notes.ID_ROOT_FOLDER) { // 如果尝试删除根文件夹,则打印错误日志并返回
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
return;
}
HashSet<Long> ids = new HashSet<Long>(); // 创建ID集合
ids.add(folderId); // 添加要删除的文件夹ID
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver, // 获取文件夹中的Widget
folderId);
if (!isSyncMode()) { // 如果不是同步模式,则直接删除文件夹中的笔记
DataUtils.batchDeleteNotes(mContentResolver, ids);
} else { // 如果是同步模式,则将文件夹中的笔记移动到回收站文件夹
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
}
if (widgets != null) { // 如果Widget集合不为空
for (AppWidgetAttribute widget : widgets) { // 遍历Widget集合
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
updateWidget(widget.widgetId, widget.widgetType); // 更新Widget
}
}
}
}
/**
* openNode方法打开笔记项。
* @param data 笔记项数据
*/
private void openNode(NoteItemData data) {
Intent intent = new Intent(this, NoteEditActivity.class); // 创建Intent跳转到NoteEditActivity
intent.setAction(Intent.ACTION_VIEW); // 设置Intent动作为查看
intent.putExtra(Intent.EXTRA_UID, data.getId()); // 传递笔记项ID
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); // 启动Activity并请求结果
}
/**
* openFolder方法打开文件夹。
* @param data 文件夹数据
*/
private void openFolder(NoteItemData data) {
mCurrentFolderId = data.getId(); // 更新当前文件夹ID
startAsyncNotesListQuery(); // 重新查询笔记列表
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { // 如果打开的是通话记录文件夹
mState = ListEditState.CALL_RECORD_FOLDER; // 更新状态
mAddNewNote.setVisibility(View.GONE); // 隐藏添加新笔记按钮
} else {
mState = ListEditState.SUB_FOLDER; // 更新状态
}
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { // 如果打开的是通话记录文件夹
mTitleBar.setText(R.string.call_record_folder_name); // 设置标题栏文本
} else {
mTitleBar.setText(data.getSnippet()); // 设置标题栏文本
}
mTitleBar.setVisibility(View.VISIBLE); // 显示标题栏
}
/**
* onClick方法实现OnClickListener接口处理点击事件。
* @param v 被点击的视图
*/
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_new_note: // 如果点击的是添加新笔记按钮
createNewNote(); // 创建新笔记
break;
default:
break;
}
}
/**
* showSoftInput方法显示软键盘。
*/
private void showSoftInput() {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); // 获取InputMethodManager服务
if (inputMethodManager != null) {
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); // 显示软键盘
}
}
/**
* hideSoftInput方法隐藏软键盘。
* @param view 视图
*/
private void hideSoftInput(View view) {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); // 获取InputMethodManager服务
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); // 隐藏软键盘
}
/**
* showCreateOrModifyFolderDialog方法显示创建或修改文件夹的对话框。
* @param create 是否为创建文件夹
*/
private void showCreateOrModifyFolderDialog(final boolean create){
final AlertDialog.Builder builder = new AlertDialog.Builder(this); // 创建AlertDialog.Builder对象
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); // 加载对话框布局
final EditText etName = (EditText) view.findViewById(R.id.et_foler_name); // 获取编辑框
showSoftInput(); // 显示软键盘
// 如果不是创建文件夹,而是修改文件夹名称
if (!create) {
if (mFocusNoteDataItem != null) {
etName.setText(mFocusNoteDataItem.getSnippet()); // 设置文件夹当前名称
builder.setTitle(getString(R.string.menu_folder_change_name)); // 设置对话框标题为“重命名文件夹”
} else {
Log.e(TAG, "The long click data item is null"); // 如果数据项为空,打印错误日志
return;
}
} else {
etName.setText(""); // 清空编辑框内容
builder.setTitle(this.getString(R.string.menu_create_folder)); // 设置对话框标题为“创建文件夹”
}
builder.setPositiveButton(android.R.string.ok, null); // 设置确定按钮
builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
hideSoftInput(etName); // 隐藏软键盘
}
});
final Dialog dialog = builder.setView(view).show(); // 设置对话框布局并显示
final Button positive = (Button) dialog.findViewById(android.R.id.button1); // 获取确定按钮
positive.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
hideSoftInput(etName); // 隐藏软键盘
String name = etName.getText().toString(); // 获取编辑框内容
if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { // 检查文件夹名称是否已存在
Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name),
Toast.LENGTH_LONG).show(); // 如果存在,显示提示
etName.setSelection(0, etName.length()); // 聚焦到编辑框
return;
}
if (!create) {
if (!TextUtils.isEmpty(name)) { // 如果不为空,则更新文件夹名称
ContentValues values = new ContentValues();
values.put(NoteColumns.SNIPPET, name); // 更新文件夹名称
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 更新文件夹类型
values.put(NoteColumns.LOCAL_MODIFIED, 1); // 更新本地修改标记
mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID
+ "=?", new String[] {
String.valueOf(mFocusNoteDataItem.getId()) // 更新指定的文件夹
});
}
} else if (!TextUtils.isEmpty(name)) {
ContentValues values = new ContentValues(); // 创建ContentValues对象
values.put(NoteColumns.SNIPPET, name); // 设置文件夹名称
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 设置文件夹类型
mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); // 插入新的文件夹
}
dialog.dismiss(); // 关闭对话框
}
});
if (TextUtils.isEmpty(etName.getText())) { // 如果编辑框为空,则禁用确定按钮
positive.setEnabled(false);
}
// 监听编辑框内容变化,以启用或禁用确定按钮
etName.addTextChangedListener(new TextWatcher() {
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// 文本变化前不做操作
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (TextUtils.isEmpty(etName.getText())) {
positive.setEnabled(false); // 如果编辑框为空,则禁用确定按钮
} else {
positive.setEnabled(true); // 如果编辑框不为空,则启用确定按钮
}
}
public void afterTextChanged(Editable s) {
// 文本变化后不做操作
}
});
}
/**
* onBackPressed方法处理返回键事件。
*/
@Override
public void onBackPressed() {
switch (mState) {
case SUB_FOLDER:
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 设置当前文件夹ID为根文件夹
mState = ListEditState.NOTE_LIST; // 更新状态为笔记列表
startAsyncNotesListQuery(); // 重新查询笔记列表
mTitleBar.setVisibility(View.GONE); // 隐藏标题栏
break;
case CALL_RECORD_FOLDER:
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 设置当前文件夹ID为根文件夹
mState = ListEditState.NOTE_LIST; // 更新状态为笔记列表
mAddNewNote.setVisibility(View.VISIBLE); // 显示添加新笔记按钮
mTitleBar.setVisibility(View.GONE); // 隐藏标题栏
startAsyncNotesListQuery(); // 重新查询笔记列表
break;
case NOTE_LIST:
super.onBackPressed(); // 如果是笔记列表状态则调用父类的onBackPressed
break;
default:
break;
}
}
/**
* updateWidget方法更新AppWidget。
* @param appWidgetId AppWidget的ID
* @param appWidgetType AppWidget的类型
*/
private void updateWidget(int appWidgetId, int appWidgetType) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); // 创建更新AppWidget的Intent
if (appWidgetType == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class); // 设置目标类为NoteWidgetProvider_2x
} else if (appWidgetType == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class); // 设置目标类为NoteWidgetProvider_4x
} else {
Log.e(TAG, "Unspported widget type"); // 如果不支持的类型,打印错误日志
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { // 传递AppWidget的ID
appWidgetId
});
sendBroadcast(intent); // 发送广播更新AppWidget
setResult(RESULT_OK, intent); // 设置结果码为成功并附加Intent
}
/**
* mFolderOnCreateContextMenuListener成员变量实现了OnCreateContextMenuListener接口
* 用于创建文件夹上下文菜单。
*/
private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (mFocusNoteDataItem != null) { // 如果焦点数据项不为空
menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); // 设置菜单标题为文件夹名称
menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); // 添加查看菜单项
menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); // 添加删除菜单项
menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); // 添加重命名菜单项
}
}
};
/**
* onContextMenuClosed方法处理上下文菜单关闭事件。
* @param menu 被关闭的菜单
*/
@Override
public void onContextMenuClosed(Menu menu) {
if (mNotesListView != null) {
mNotesListView.setOnCreateContextMenuListener(null); // 清除上下文菜单监听器
}
super.onContextMenuClosed(menu); // 调用父类的onContextMenuClosed
}
/**
* onContextItemSelected方法处理上下文菜单项选择事件。
* @param item 被选择的菜单项
* @return 是否处理了菜单项
*/
@Override
public boolean onContextItemSelected(MenuItem item) {
if (mFocusNoteDataItem == null) {
Log.e(TAG, "The long click data item is null"); // 如果焦点数据项为空,打印错误日志
return false;
}
switch (item.getItemId()) {
case MENU_FOLDER_VIEW:
openFolder(mFocusNoteDataItem); // 打开文件夹
break;
case MENU_FOLDER_DELETE:
AlertDialog.Builder builder = new AlertDialog.Builder(this); // 创建AlertDialog.Builder对象
builder.setTitle(getString(R.string.alert_title_delete)); // 设置对话框标题
builder.setIcon(android.R.drawable.ic_dialog_alert); // 设置对话框图标
builder.setMessage(getString(R.string.alert_message_delete_folder)); // 设置对话框消息
builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteFolder(mFocusNoteDataItem.getId()); // 删除文件夹
}
});
builder.setNegativeButton(android.R.string.cancel, null); // 设置取消按钮
builder.show(); // 显示对话框
break;
case MENU_FOLDER_CHANGE_NAME:
showCreateOrModifyFolderDialog(false); // 显示修改文件夹对话框
break;
default:
break;
}
return true; // 返回true表示处理了菜单项
}
/**
* onPrepareOptionsMenu方法准备选项菜单。
* @param menu 菜单
* @return 是否处理了菜单
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear(); // 清除菜单中现有的所有项
// 根据当前的编辑状态设置不同的菜单项
if (mState == ListEditState.NOTE_LIST) {
getMenuInflater().inflate(R.menu.note_list, menu); // 导入笔记列表的菜单项
// 根据是否正在同步来设置同步菜单项的标题
menu.findItem(R.id.menu_sync).setTitle(
GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync);
} else if (mState == ListEditState.SUB_FOLDER) {
getMenuInflater().inflate(R.menu.sub_folder, menu); // 导入子文件夹的菜单项
} else if (mState == ListEditState.CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_record_folder, menu); // 导入通话记录文件夹的菜单项
} else {
Log.e(TAG, "Wrong state:" + mState); // 如果状态不正确,打印错误日志
}
return true; // 返回true表示菜单已被处理
}
/**
* onOptionsItemSelected方法当选项菜单中的某一项被选中时调用。
* @param item 被选中的菜单项
* @return 是否处理了菜单项返回true表示菜单项已被处理。
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_folder: {
showCreateOrModifyFolderDialog(true); // 显示创建新文件夹的对话框
break;
}
case R.id.menu_export_text: {
exportNoteToText(); // 导出笔记为文本
break;
}
case R.id.menu_sync: {
// 如果正在同步,则取消同步;否则开始同步
if (isSyncMode()) {
if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) {
GTaskSyncService.startSync(this); // 开始同步
} else {
GTaskSyncService.cancelSync(this); // 取消同步
}
} else {
startPreferenceActivity(); // 启动偏好设置Activity
}
break;
}
case R.id.menu_setting: {
startPreferenceActivity(); // 启动偏好设置Activity
break;
}
case R.id.menu_new_note: {
createNewNote(); // 创建新笔记
break;
}
case R.id.menu_search:
onSearchRequested(); // 请求搜索
break;
default:
break;
}
return true; // 返回true表示菜单项已被处理
}
/**
* onSearchRequested方法当搜索请求被发起时调用。
* @return 是否处理了搜索请求
*/
@Override
public boolean onSearchRequested() {
startSearch(null, false, null /* appData */, false); // 启动搜索
return true; // 返回true表示搜索请求已被处理
}
/**
* exportNoteToText方法将笔记导出为文本文件。
*/
private void exportNoteToText() {
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); // 获取BackupUtils实例
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... unused) {
return backup.exportToText(); // 后台任务:导出笔记为文本
}
@Override
protected void onPostExecute(Integer result) {
// 根据导出结果弹出不同的对话框
if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(getString(R.string.failed_sdcard_export));
builder.setMessage(getString(R.string.error_sdcard_unmounted));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
} else if (result == BackupUtils.STATE_SUCCESS) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(getString(R.string.success_sdcard_export));
builder.setMessage(getString(R.string.format_exported_file_location, backup.getExportedTextFileName(), backup.getExportedTextFileDir()));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
} else if (result == BackupUtils.STATE_SYSTEM_ERROR) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(getString(R.string.failed_sdcard_export));
builder.setMessage(getString(R.string.error_sdcard_export));
builder.setPositiveButton(android.R.string.ok, null);
builder.show();
}
}
}.execute(); // 执行异步任务
}
/**
* isSyncMode方法检查当前是否处于同步模式。
* @return 是否处于同步模式
*/
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; // 如果同步账号名称不为空,则处于同步模式
}
/**
* startPreferenceActivity方法启动偏好设置Activity。
*/
private void startPreferenceActivity() {
Activity from = getParent() != null ? getParent() : this; // 获取父Activity或当前Activity
Intent intent = new Intent(from, NotesPreferenceActivity.class); // 创建Intent跳转到NotesPreferenceActivity
from.startActivityIfNeeded(intent, -1); // 启动Activity
}
/**
* OnListItemClickListener内部类实现了OnItemClickListener接口用于处理ListView项的点击事件。
*/
private class OnListItemClickListener implements OnItemClickListener {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (view instanceof NotesListItem) { // 如果点击的是NotesListItem
NoteItemData item = ((NotesListItem) view).getItemData(); // 获取笔记项数据
if (mNotesListAdapter.isInChoiceMode()) { // 如果适配器处于选择模式
if (item.getType() == Notes.TYPE_NOTE) { // 如果是笔记项
position = position - mNotesListView.getHeaderViewsCount(); // 调整位置
mModeCallBack.onItemCheckedStateChanged(null, position, id, !mNotesListAdapter.isSelectedItem(position)); // 切换选中状态
}
return;
}
// 根据当前状态处理点击事件
switch (mState) {
case NOTE_LIST:
if (item.getType() == Notes.TYPE_FOLDER || item.getType() == Notes.TYPE_SYSTEM) {
openFolder(item); // 打开文件夹
} else if (item.getType() == Notes.TYPE_NOTE) {
openNode(item); // 打开笔记
} else {
Log.e(TAG, "Wrong note type in NOTE_LIST"); // 如果类型不正确,打印错误日志
}
break;
case SUB_FOLDER:
case CALL_RECORD_FOLDER:
if (item.getType() == Notes.TYPE_NOTE) {
openNode(item); // 打开笔记
} else {
Log.e(TAG, "Wrong note type in SUB_FOLDER"); // 如果类型不正确,打印错误日志
}
break;
default:
break;
}
}
}
}
/**
* startQueryDestinationFolders方法启动查询目标文件夹的操作。
*/
private void startQueryDestinationFolders() {
// 构建查询条件
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>";
selection = (mState == ListEditState.NOTE_LIST) ? selection :
"(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")";
// 启动后台查询
mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN,
null,
Notes.CONTENT_NOTE_URI,
FoldersListAdapter.PROJECTION,
selection,
new String[] {
String.valueOf(Notes.TYPE_FOLDER),
String.valueOf(Notes.ID_TRASH_FOLER),
String.valueOf(mCurrentFolderId)
},
NoteColumns.MODIFIED_DATE + " DESC");
}
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
// 检查被长按的视图是否是NotesListItem类型NotesListItem是自定义的列表项视图
if (view instanceof NotesListItem) {
// 获取被长按项的数据对象,这个对象包含了笔记的详细信息
mFocusNoteDataItem = ((NotesListItem) view).getItemData();
// 检查被长按的数据项类型是否是笔记TYPE_NOTE且适配器不处于选择模式
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
// 尝试启动ActionMode多选模式mModeCallBack是ActionMode的回调
if (mNotesListView.startActionMode(mModeCallBack) != null) {
// 如果成功启动了ActionMode更新被长按项的选中状态
mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
// 提供触觉反馈,让用户知道操作已被执行
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
} else {
// 如果启动ActionMode失败记录错误日志
Log.e(TAG, "startActionMode fails");
}
} else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
// 如果被长按的数据项类型是文件夹TYPE_FOLDER设置上下文菜单监听器
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
// 这样当用户长按文件夹项时,可以弹出上下文菜单
}
}
// 返回false表示不消费长按事件允许其他事件如弹出上下文菜单发生
return false;
}
}