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

1565 lines
67 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;
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;
/**
* NotesListActivity 类是一个 Activity用于显示笔记列表。
* 它处理笔记列表的显示、查询、添加、删除和移动等操作。
*/
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
// 文件夹笔记列表查询的令牌
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;
// 文件夹查看菜单项的 ID
private static final int MENU_FOLDER_VIEW = 1;
// 文件夹重命名菜单项的 ID
private static final int MENU_FOLDER_CHANGE_NAME = 2;
// 用于存储是否添加过介绍的偏好设置键
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
// 列表编辑状态的枚举,包括笔记列表、子文件夹和通话记录文件夹
private enum ListEditState {
NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER
};
// 当前列表编辑状态
private ListEditState mState;
// 用于在后台执行查询操作的处理程序
private BackgroundQueryHandler mBackgroundQueryHandler;
// 笔记列表的适配器
private NotesListAdapter mNotesListAdapter;
// 显示笔记列表的 ListView
private ListView mNotesListView;
// 添加新笔记的按钮
private Button mAddNewNote;
// 是否将触摸事件分发到 ListView 的标志
private boolean mDispatch;
// 触摸事件的起始 Y 坐标
private int mOriginY;
// 触摸事件分发的 Y 坐标
private int mDispatchY;
// 标题栏的 TextView
private TextView mTitleBar;
// 当前文件夹的 ID
private long mCurrentFolderId;
// 内容解析器,用于与 ContentProvider 交互
private ContentResolver mContentResolver;
// 上下文操作模式的回调
private ModeCallback mModeCallBack;
// 日志标签
private static final String TAG = "NotesListActivity";
// 笔记列表滚动速率
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
// 当前聚焦的笔记数据项
private NoteItemData mFocusNoteDataItem;
// 普通查询的选择条件
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)";
// 打开节点的请求码
private final static int REQUEST_CODE_OPEN_NODE = 102;
// 创建新节点的请求码
private final static int REQUEST_CODE_NEW_NODE = 103;
/**
* Activity 创建时调用的方法。
* 初始化布局和资源,并设置应用信息。
*
* @param savedInstanceState 如果 Activity 是重新创建的,则包含之前保存的状态。
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置布局
setContentView(R.layout.note_list);
// 初始化资源
initResources();
/**
* 当用户首次使用此应用程序时,插入一条介绍信息。
*/
setAppInfoFromRawRes();
}
/**
* 处理 Activity 返回结果的方法。
* 如果返回结果为 RESULT_OK 且请求码为打开节点或创建新节点,则更新笔记列表适配器的游标。
*
* @param requestCode 请求码
* @param resultCode 返回结果码
* @param data 返回的意图数据
*/
@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);
}
}
/**
* 从原始资源中设置应用信息。
* 如果用户是首次使用应用程序,则从原始资源中读取介绍信息并保存为笔记。
*/
private void setAppInfoFromRawRes() {
// 获取默认的共享偏好设置
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
// 检查是否已经添加过介绍信息
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {
StringBuilder sb = new StringBuilder();
InputStream in = null;
try {
// 打开原始资源文件
in = getResources().openRawResource(R.raw.introduction);
if (in != null) {
// 创建输入流读取器
InputStreamReader isr = new InputStreamReader(in);
// 创建缓冲读取器
BufferedReader br = new BufferedReader(isr);
char[] buf = new char[1024];
int len = 0;
// 读取文件内容
while ((len = br.read(buf)) > 0) {
sb.append(buf, 0, len);
}
} else {
// 记录读取介绍文件错误的日志
Log.e(TAG, "Read introduction file error");
return;
}
} catch (IOException e) {
// 打印异常堆栈信息
e.printStackTrace();
return;
} finally {
if (in != null) {
try {
// 关闭输入流
in.close();
} catch (IOException e) {
// 打印异常堆栈信息
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();
} else {
// 记录保存介绍笔记错误的日志
Log.e(TAG, "Save introduction note error");
return;
}
}
}
/**
* Activity 开始时调用的方法。
* 启动异步笔记列表查询。
*/
@Override
protected void onStart() {
super.onStart();
// 启动异步笔记列表查询
startAsyncNotesListQuery();
}
/**
* 初始化资源的方法。
* 初始化 ContentResolver、查询处理程序、ListView、适配器、按钮等。
*/
private void initResources() {
// 获取 ContentResolver
mContentResolver = this.getContentResolver();
// 创建背景查询处理程序
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
// 设置当前文件夹 ID 为根文件夹 ID
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
// 获取笔记列表的 ListView
mNotesListView = (ListView) findViewById(R.id.notes_list);
// 为 ListView 添加页脚视图
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null),
null, false);
// 设置 ListView 的项点击监听器
mNotesListView.setOnItemClickListener(new OnListItemClickListener());
// 设置 ListView 的项长按监听器
mNotesListView.setOnItemLongClickListener(this);
// 创建笔记列表适配器
mNotesListAdapter = new NotesListAdapter(this);
// 设置 ListView 的适配器
mNotesListView.setAdapter(mNotesListAdapter);
// 获取添加新笔记的按钮
mAddNewNote = (Button) findViewById(R.id.btn_new_note);
// 设置按钮的点击监听器
mAddNewNote.setOnClickListener(this);
// 设置按钮的触摸监听器
mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener());
// 初始化分发标志和坐标
mDispatch = false;
mDispatchY = 0;
mOriginY = 0;
// 获取标题栏的 TextView
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);
// 设置初始列表编辑状态为笔记列表
mState = ListEditState.NOTE_LIST;
// 创建上下文操作模式的回调
mModeCallBack = new ModeCallback();
}
/**
* 上下文操作模式的回调类。
* 处理上下文操作模式的创建、准备、菜单项点击、销毁等事件。
*/
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
// 下拉菜单
private DropdownMenu mDropDownMenu;
// 上下文操作模式
private ActionMode mActionMode;
// 移动菜单项
private MenuItem mMoveMenu;
/**
* 创建上下文操作模式时调用的方法。
* 初始化菜单、设置菜单项监听器、更新视图状态。
*
* @param mode 上下文操作模式
* @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;
// 设置适配器的选择模式为多选
mNotesListAdapter.setChoiceMode(true);
// 设置 ListView 不可长按
mNotesListView.setLongClickable(false);
// 隐藏添加新笔记的按钮
mAddNewNote.setVisibility(View.GONE);
// 加载自定义视图
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;
}
/**
* 更新菜单的方法。
* 根据选择的项数更新下拉菜单的标题和全选菜单项的状态。
*/
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);
}
}
}
/**
* 准备上下文操作模式时调用的方法。
* 目前未实现具体逻辑。
*
* @param mode 上下文操作模式
* @param menu 上下文操作模式的菜单
* @return 是否准备成功
*/
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub
return false;
}
/**
* 上下文操作模式的菜单项点击时调用的方法。
* 目前未实现具体逻辑。
*
* @param mode 上下文操作模式
* @param item 被点击的菜单项
* @return 是否处理成功
*/
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false;
}
/**
* 销毁上下文操作模式时调用的方法。
* 恢复视图状态。
*
* @param mode 上下文操作模式
*/
public void onDestroyActionMode(ActionMode mode) {
// 设置适配器的选择模式为单选
mNotesListAdapter.setChoiceMode(false);
// 设置 ListView 可长按
mNotesListView.setLongClickable(true);
// 显示添加新笔记的按钮
mAddNewNote.setVisibility(View.VISIBLE);
}
/**
* 结束上下文操作模式的方法。
*/
/**
* 结束上下文操作模式的方法
*/
public void finishActionMode() {
// 调用 ActionMode 的 finish 方法来结束当前的上下文操作模式
mActionMode.finish();
}
/**
* 当列表项的选中状态发生改变时调用此方法
*
* @param mode 上下文操作模式
* @param position 列表项的位置
* @param id 列表项的 ID
* @param checked 列表项是否被选中
*/
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
// 调用 NotesListAdapter 的 setCheckedItem 方法,设置指定位置的列表项的选中状态
mNotesListAdapter.setCheckedItem(position, checked);
// 调用 updateMenu 方法,更新菜单的显示状态
updateMenu();
}
/**
* 处理菜单项点击事件的方法
*
* @param item 被点击的菜单项
* @return 如果事件被处理返回 true否则返回 false
*/
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;
}
// 根据菜单项的 ID 进行不同的操作
switch (item.getItemId()) {
case R.id.delete:
// 创建一个 AlertDialog 构建器
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()));
// 设置对话框的确定按钮,点击确定时调用 batchDelete 方法
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;
}
}
/**
* 处理新建笔记按钮触摸事件的监听器类
*/
private class NewNoteOnTouchListener implements OnTouchListener {
/**
* 处理触摸事件的方法
*
* @param v 触发触摸事件的视图
* @param event 触摸事件对象
* @return 如果事件被处理返回 true否则返回 false
*/
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;
// 计算触摸事件的 Y 坐标
int eventY = start + (int) event.getY();
/**
* 如果当前处于子文件夹列表编辑状态,减去标题栏的高度
*/
if (mState == ListEditState.SUB_FOLDER) {
eventY -= mTitleBar.getHeight();
start -= mTitleBar.getHeight();
}
/**
* 注意当点击“New Note”按钮的透明部分时将事件分发到按钮后面的列表视图。
* “New Note”按钮的透明部分可以用公式 y = -0.12x + 94单位像素和按钮顶部的线来表示。
* 坐标基于“New Note”按钮的左侧。94 表示透明部分的最大高度。
* 注意,如果按钮的背景发生变化,公式也应该相应改变。这是为了满足 UI 设计师的强烈要求,不太理想。
*/
if (event.getY() < (event.getX() * (-0.12) + 94)) {
// 获取列表视图的最后一个非页脚子视图
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- mNotesListView.getFooterViewsCount());
// 检查视图是否存在,并且视图的底部位置大于按钮顶部位置,顶部位置小于按钮顶部位置加上 94
if (view != null && view.getBottom() > start
&& (view.getTop() < (start + 94))) {
// 记录触摸事件的起始 Y 坐标
mOriginY = (int) event.getY();
// 记录要分发的 Y 坐标
mDispatchY = eventY;
// 修改触摸事件的位置
event.setLocation(event.getX(), mDispatchY);
// 设置分发标志为 true
mDispatch = true;
// 将触摸事件分发给列表视图
return mNotesListView.dispatchTouchEvent(event);
}
}
break;
}
case MotionEvent.ACTION_MOVE: {
// 如果分发标志为 true
if (mDispatch) {
// 更新要分发的 Y 坐标
mDispatchY += (int) event.getY() - mOriginY;
// 修改触摸事件的位置
event.setLocation(event.getX(), mDispatchY);
// 将触摸事件分发给列表视图
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
default: {
// 如果分发标志为 true
if (mDispatch) {
// 修改触摸事件的位置
event.setLocation(event.getX(), mDispatchY);
// 设置分发标志为 false
mDispatch = false;
// 将触摸事件分发给列表视图
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
}
return false;
}
};
/**
* 启动异步查询笔记列表的方法
*/
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[] {
// 将当前文件夹 ID 转换为字符串作为查询参数
String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}
/**
* 后台查询处理程序类,继承自 AsyncQueryHandler
*/
private final class BackgroundQueryHandler extends AsyncQueryHandler {
/**
* 构造方法,初始化 ContentResolver
*
* @param contentResolver 内容解析器
*/
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
/**
* 当查询完成时调用的方法
*
* @param token 查询的令牌,用于区分不同的查询
* @param cookie 传递的额外数据
* @param cursor 查询结果的游标
*/
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
// 根据查询令牌进行不同的处理
switch (token) {
case FOLDER_NOTE_LIST_QUERY_TOKEN:
// 将查询结果的游标传递给 NotesListAdapter
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;
}
}
}
/**
* 显示文件夹列表菜单的方法
*
* @param cursor 包含文件夹信息的游标
*/
private void showFolderListMenu(Cursor cursor) {
// 创建一个 AlertDialog 构建器
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
// 设置对话框的标题
builder.setTitle(R.string.menu_title_select_folder);
// 创建一个 FoldersListAdapter用于显示文件夹列表
final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor);
// 设置对话框的适配器和点击监听器
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
/**
* 当点击文件夹列表中的某一项时调用的方法
*
* @param dialog 对话框对象
* @param which 被点击项的位置
*/
public void onClick(DialogInterface dialog, int which) {
// 调用 DataUtils 的 batchMoveToFolder 方法,将选中的笔记移动到指定的文件夹
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();
}
/**
* 创建新笔记的方法。
* 该方法会创建一个新的意图,启动 NoteEditActivity 来创建新笔记,并传递当前文件夹的 ID。
*/
private void createNewNote() {
// 创建一个意图,用于启动 NoteEditActivity
Intent intent = new Intent(this, NoteEditActivity.class);
// 设置意图的动作,表明是插入或编辑操作
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
// 向意图中添加额外数据,传递当前文件夹的 ID
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId);
// 启动 NoteEditActivity 并等待返回结果
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
}
/**
* 批量删除选中笔记的方法。
* 该方法使用异步任务在后台执行删除或移动到回收站的操作,并在操作完成后更新相关小部件。
*/
private void batchDelete() {
// 创建一个异步任务,用于在后台执行批量删除或移动到回收站的操作
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
/**
* 在后台线程中执行的方法,负责批量删除或移动选中的笔记,并返回相关小部件的集合。
* @param unused 未使用的参数
* @return 包含选中笔记关联小部件属性的集合
*/
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
// 获取选中笔记关联的小部件集合
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
// 检查是否处于同步模式
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;
}
/**
* 在主线程中执行的方法,在后台任务完成后调用,用于更新相关小部件并结束操作模式。
* @param widgets 包含选中笔记关联小部件属性的集合
*/
@Override
protected void onPostExecute(HashSet<AppWidgetAttribute> widgets) {
// 检查小部件集合是否为空
if (widgets != null) {
// 遍历小部件集合
for (AppWidgetAttribute widget : widgets) {
// 检查小部件 ID 和类型是否有效
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
// 更新有效的小部件
updateWidget(widget.widgetId, widget.widgetType);
}
}
}
// 结束当前的操作模式
mModeCallBack.finishActionMode();
}
}.execute();
}
/**
* 删除指定文件夹的方法。
* 该方法会根据同步模式的不同,直接删除文件夹或将其移动到回收站,并更新相关小部件。
* @param folderId 要删除的文件夹的 ID
*/
private void deleteFolder(long folderId) {
// 检查文件夹 ID 是否为根文件夹 ID
if (folderId == Notes.ID_ROOT_FOLDER) {
// 记录错误日志,因为根文件夹不能被删除
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
return;
}
// 创建一个包含要删除文件夹 ID 的集合
HashSet<Long> ids = new HashSet<Long>();
ids.add(folderId);
// 获取指定文件夹中笔记关联的小部件集合
HashSet<AppWidgetAttribute> widgets = DataUtils.getFolderNoteWidget(mContentResolver,
folderId);
// 检查是否处于同步模式
if (!isSyncMode()) {
// 如果不是同步模式,直接删除文件夹
DataUtils.batchDeleteNotes(mContentResolver, ids);
} else {
// 如果处于同步模式,将文件夹移动到回收站
DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER);
}
// 检查小部件集合是否为空
if (widgets != null) {
// 遍历小部件集合
for (AppWidgetAttribute widget : widgets) {
// 检查小部件 ID 和类型是否有效
if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) {
// 更新有效的小部件
updateWidget(widget.widgetId, widget.widgetType);
}
}
}
}
/**
* 打开指定笔记的方法。
* 该方法会创建一个意图,启动 NoteEditActivity 来查看指定笔记。
* @param data 要打开的笔记的数据对象
*/
private void openNode(NoteItemData data) {
// 创建一个意图,用于启动 NoteEditActivity
Intent intent = new Intent(this, NoteEditActivity.class);
// 设置意图的动作,表明是查看操作
intent.setAction(Intent.ACTION_VIEW);
// 向意图中添加额外数据,传递笔记的 ID
intent.putExtra(Intent.EXTRA_UID, data.getId());
// 启动 NoteEditActivity 并等待返回结果
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
/**
* 打开指定文件夹的方法。
* 该方法会更新当前文件夹 ID重新查询该文件夹下的笔记列表并更新界面状态。
* @param data 要打开的文件夹的数据对象
*/
private void openFolder(NoteItemData data) {
// 更新当前文件夹 ID
mCurrentFolderId = data.getId();
// 启动异步查询,获取当前文件夹下的笔记列表
startAsyncNotesListQuery();
// 检查文件夹 ID 是否为通话记录文件夹 ID
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
// 如果是通话记录文件夹,设置列表编辑状态为通话记录文件夹状态
mState = ListEditState.CALL_RECORD_FOLDER;
// 隐藏新建笔记按钮
mAddNewNote.setVisibility(View.GONE);
} else {
// 如果不是通话记录文件夹,设置列表编辑状态为子文件夹状态
mState = ListEditState.SUB_FOLDER;
}
// 检查文件夹 ID 是否为通话记录文件夹 ID
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);
}
/**
* 处理视图点击事件的方法。
* 根据点击的视图 ID 执行相应的操作。
* @param v 被点击的视图
*/
public void onClick(View v) {
// 根据点击的视图 ID 进行不同的操作
switch (v.getId()) {
case R.id.btn_new_note:
// 如果点击的是新建笔记按钮,调用创建新笔记的方法
createNewNote();
break;
default:
break;
}
}
/**
* 显示软键盘的方法。
* 该方法会强制显示软键盘。
*/
private void showSoftInput() {
// 获取输入法管理器
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// 检查输入法管理器是否为空
if (inputMethodManager != null) {
// 强制显示软键盘
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
}
}
/**
* 隐藏软键盘的方法。
* 该方法会隐藏指定视图关联的软键盘。
* @param view 关联软键盘的视图
*/
private void hideSoftInput(View view) {
// 获取输入法管理器
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// 隐藏指定视图关联的软键盘
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
/**
* 显示创建或修改文件夹对话框的方法。
* 根据传入的参数,显示创建文件夹或修改文件夹名称的对话框。
* @param create 是否为创建文件夹操作true 表示创建false 表示修改
*/
private void showCreateOrModifyFolderDialog(final boolean create) {
// 创建一个 AlertDialog 构建器
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
// 加载对话框的布局视图
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() {
/**
* 取消按钮点击事件处理方法。
* 点击取消按钮时,隐藏软键盘。
* @param dialog 对话框对象
* @param which 按钮的索引
*/
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() {
/**
* 确定按钮点击事件处理方法。
* 点击确定按钮时,检查文件夹名称是否已存在,根据操作类型进行创建或修改文件夹的操作。
* @param v 被点击的视图
*/
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 对象,用于存储要更新的字段和值
ContentValues values = new ContentValues();
// 设置文件夹的摘要信息
values.put(NoteColumns.SNIPPET, name);
// 设置文件夹的类型
values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
// 设置文件夹的本地修改标志
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 更新指定 ID 的文件夹信息
mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID
+ "=?", new String[] {
// 将当前文件夹的 ID 转换为字符串作为查询参数
String.valueOf(mFocusNoteDataItem.getId())
});
}
} else if (!TextUtils.isEmpty(name)) {
// 如果是创建操作,检查文件夹名称是否为空
// 创建一个 ContentValues 对象,用于存储要插入的字段和值
ContentValues values = new 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() {
/**
* 在文本变化之前调用的方法。
* @param s 变化前的文本
* @param start 变化的起始位置
* @param count 变化的字符数
* @param after 变化后的字符数
*/
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// 留空,不需要处理
}
/**
* 在文本变化时调用的方法。
* 检查输入框中的文本是否为空,并相应地启用或禁用确定按钮。
* @param s 变化后的文本
* @param start 变化的起始位置
* @param before 变化前的字符数
* @param count 变化的字符数
*/
public void onTextChanged(CharSequence s, int start, int before, int count) {
// 检查输入框中的文本是否为空
if (TextUtils.isEmpty(etName.getText())) {
// 如果为空,禁用确定按钮
positive.setEnabled(false);
} else {
// 如果不为空,启用确定按钮
positive.setEnabled(true);
}
}
/**
* 在文本变化之后调用的方法。
* @param s 变化后的可编辑文本
*/
public void afterTextChanged(Editable s) {
// 留空,不需要处理
}
});
}
/**
* 处理返回键按下事件的方法。
* 根据当前列表编辑状态,执行相应的操作,如返回上一级文件夹或退出应用。
*/
@Override
public void onBackPressed() {
// 根据当前列表编辑状态进行不同的操作
switch (mState) {
case SUB_FOLDER:
// 如果当前处于子文件夹状态,将当前文件夹 ID 设置为根文件夹 ID
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
// 将列表编辑状态设置为笔记列表状态
mState = ListEditState.NOTE_LIST;
// 启动异步查询,获取根文件夹下的笔记列表
startAsyncNotesListQuery();
// 隐藏标题栏
mTitleBar.setVisibility(View.GONE);
break;
case CALL_RECORD_FOLDER:
// 如果当前处于通话记录文件夹状态,将当前文件夹 ID 设置为根文件夹 ID
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
// 将列表编辑状态设置为笔记列表状态
mState = ListEditState.NOTE_LIST;
// 显示新建笔记按钮
mAddNewNote.setVisibility(View.VISIBLE);
// 隐藏标题栏
mTitleBar.setVisibility(View.GONE);
// 启动异步查询,获取根文件夹下的笔记列表
startAsyncNotesListQuery();
break;
case NOTE_LIST:
// 如果当前处于笔记列表状态,调用父类的返回键处理方法
super.onBackPressed();
break;
default:
break;
}
}
/**
* 更新指定小部件的方法。
* 该方法会发送一个广播,通知相应的小部件提供者更新小部件。
* @param appWidgetId 要更新的小部件的 ID
* @param appWidgetType 要更新的小部件的类型
*/
/**
* 更新小部件的方法
* @param appWidgetId 小部件的ID
* @param appWidgetType 小部件的类型
*/
private void updateWidget(int appWidgetId, int appWidgetType) {
// 创建一个用于更新小部件的意图
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
// 根据小部件类型设置意图的目标类
if (appWidgetType == Notes.TYPE_WIDGET_2X) {
// 如果是2x类型的小部件设置目标类为NoteWidgetProvider_2x
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (appWidgetType == Notes.TYPE_WIDGET_4X) {
// 如果是4x类型的小部件设置目标类为NoteWidgetProvider_4x
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
// 如果是不支持的小部件类型,记录错误日志并返回
Log.e(TAG, "Unspported widget type");
return;
}
// 将需要更新的小部件ID放入意图的额外数据中
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
appWidgetId
});
// 发送广播以更新小部件
sendBroadcast(intent);
// 设置当前活动的结果为成功,并携带更新小部件的意图
setResult(RESULT_OK, intent);
}
/**
* 文件夹上下文菜单创建监听器
*/
private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {
/**
* 创建上下文菜单时调用的方法
* @param menu 上下文菜单对象
* @param v 触发上下文菜单的视图
* @param menuInfo 上下文菜单信息
*/
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);
}
}
};
/**
* 上下文菜单关闭时调用的方法
* @param menu 关闭的上下文菜单
*/
@Override
public void onContextMenuClosed(Menu menu) {
// 如果笔记列表视图不为空
if (mNotesListView != null) {
// 移除笔记列表视图的上下文菜单创建监听器
mNotesListView.setOnCreateContextMenuListener(null);
}
// 调用父类的上下文菜单关闭方法
super.onContextMenuClosed(menu);
}
/**
* 上下文菜单项被选中时调用的方法
* @param item 被选中的菜单项
* @return 是否处理了该菜单项的选中事件
*/
@Override
public boolean onContextItemSelected(MenuItem item) {
// 如果当前聚焦的笔记数据项为空记录错误日志并返回false
if (mFocusNoteDataItem == null) {
Log.e(TAG, "The long click data item is null");
return false;
}
// 根据菜单项的ID进行不同的处理
switch (item.getItemId()) {
case MENU_FOLDER_VIEW:
// 查看文件夹调用openFolder方法打开聚焦的笔记数据项对应的文件夹
openFolder(mFocusNoteDataItem);
break;
case MENU_FOLDER_DELETE:
// 删除文件夹,弹出确认删除的对话框
AlertDialog.Builder builder = new AlertDialog.Builder(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_folder));
// 设置对话框的确定按钮点击确定时调用deleteFolder方法删除文件夹
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方法显示创建或修改文件夹的对话框
showCreateOrModifyFolderDialog(false);
break;
default:
break;
}
// 返回true表示处理了该菜单项的选中事件
return true;
}
/**
* 准备选项菜单时调用的方法
* @param menu 选项菜单对象
* @return 是否处理了选项菜单的准备事件
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
// 清空菜单中的所有菜单项
menu.clear();
// 根据当前的列表编辑状态加载不同的菜单布局
if (mState == ListEditState.NOTE_LIST) {
// 如果是笔记列表状态,加载笔记列表的菜单布局
getMenuInflater().inflate(R.menu.note_list, menu);
// 设置同步菜单项的标题根据GTaskSyncService是否正在同步来显示不同的标题
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);
}
// 返回true表示处理了选项菜单的准备事件
return true;
}
/**
* 选项菜单项被选中时调用的方法
* @param item 被选中的菜单项
* @return 是否处理了该菜单项的选中事件
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// 根据菜单项的ID进行不同的处理
switch (item.getItemId()) {
case R.id.menu_new_folder: {
// 创建新文件夹调用showCreateOrModifyFolderDialog方法显示创建或修改文件夹的对话框
showCreateOrModifyFolderDialog(true);
break;
}
case R.id.menu_export_text: {
// 导出笔记为文本调用exportNoteToText方法进行导出操作
exportNoteToText();
break;
}
case R.id.menu_sync: {
// 同步操作,如果是同步模式
if (isSyncMode()) {
// 根据菜单项的标题判断是开始同步还是取消同步
if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) {
// 如果标题是同步调用GTaskSyncService的startSync方法开始同步
GTaskSyncService.startSync(this);
} else {
// 如果标题是取消同步调用GTaskSyncService的cancelSync方法取消同步
GTaskSyncService.cancelSync(this);
}
} else {
// 如果不是同步模式调用startPreferenceActivity方法启动设置活动
startPreferenceActivity();
}
break;
}
case R.id.menu_setting: {
// 打开设置调用startPreferenceActivity方法启动设置活动
startPreferenceActivity();
break;
}
case R.id.menu_new_note: {
// 创建新笔记调用createNewNote方法创建新笔记
createNewNote();
break;
}
case R.id.menu_search:
// 搜索操作调用onSearchRequested方法触发搜索请求
onSearchRequested();
break;
default:
break;
}
// 返回true表示处理了该菜单项的选中事件
return true;
}
/**
* 触发搜索请求时调用的方法
* @return 是否处理了搜索请求
*/
@Override
public boolean onSearchRequested() {
// 启动搜索,传入搜索关键字为空,不使用全局搜索,不携带应用数据,不使用渐进式搜索
startSearch(null, false, null /* appData */, false);
// 返回true表示处理了搜索请求
return true;
}
/**
* 将笔记导出为文本文件的方法
*/
private void exportNoteToText() {
// 获取 BackupUtils 类的单例实例,传入当前 Activity 的上下文
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
// 创建一个异步任务,避免在主线程执行耗时的导出操作
new AsyncTask<Void, Void, Integer>() {
/**
* 此方法在后台线程执行,用于执行实际的导出操作
* @param unused 未使用的参数
* @return 导出操作的结果状态码
*/
@Override
protected Integer doInBackground(Void... unused) {
// 调用 BackupUtils 实例的 exportToText 方法进行导出操作,并返回结果状态码
return backup.exportToText();
}
/**
* 当后台任务执行完成后,此方法在主线程执行,用于处理导出结果
* @param result 导出操作的结果状态码
*/
@Override
protected void onPostExecute(Integer result) {
// 根据不同的结果状态码,显示不同的提示对话框
if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) {
// 如果 SD 卡未挂载,创建一个警告对话框提示用户
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
// 设置对话框标题为导出失败
builder.setTitle(NotesListActivity.this
.getString(R.string.failed_sdcard_export));
// 设置对话框消息为 SD 卡未挂载的错误信息
builder.setMessage(NotesListActivity.this
.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(NotesListActivity.this
.getString(R.string.success_sdcard_export));
// 设置对话框消息,显示导出文件的名称和所在目录
builder.setMessage(NotesListActivity.this.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(NotesListActivity.this
.getString(R.string.failed_sdcard_export));
// 设置对话框消息为 SD 卡导出错误信息
builder.setMessage(NotesListActivity.this
.getString(R.string.error_sdcard_export));
// 设置对话框的确认按钮,点击后对话框关闭
builder.setPositiveButton(android.R.string.ok, null);
// 显示对话框
builder.show();
}
}
}.execute(); // 执行异步任务
}
/**
* 判断是否处于同步模式的方法
* @return 如果有有效的同步账户名,则返回 true否则返回 false
*/
private boolean isSyncMode() {
// 从 NotesPreferenceActivity 中获取同步账户名,去除首尾空格后检查长度是否大于 0
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
/**
* 启动设置偏好活动的方法
*/
private void startPreferenceActivity() {
// 获取当前 Activity 的父 Activity如果没有父 Activity 则使用当前 Activity
Activity from = getParent() != null ? getParent() : this;
// 创建一个意图,用于启动 NotesPreferenceActivity
Intent intent = new Intent(from, NotesPreferenceActivity.class);
// 启动 NotesPreferenceActivity如果需要的话
from.startActivityIfNeeded(intent, -1);
}
/**
* 列表项点击监听器类,实现 OnItemClickListener 接口
*/
private class OnListItemClickListener implements OnItemClickListener {
/**
* 当列表项被点击时调用的方法
* @param parent 列表项所在的适配器视图
* @param view 被点击的视图
* @param position 被点击的列表项的位置
* @param id 被点击的列表项的 ID
*/
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 检查被点击的视图是否为 NotesListItem 类型
if (view instanceof 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;
}
}
}
}
/**
* 开始查询目标文件夹的方法
*/
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");
}
/**
* 列表项长按监听器方法
* @param parent 列表项所在的适配器视图
* @param view 被长按的视图
* @param position 被长按的列表项的位置
* @param id 被长按的列表项的 ID
* @return 是否处理了长按事件
*/
/**
* 处理列表项长按事件的方法
*
* @param parent 触发长按事件的 AdapterView
* @param view 被长按的视图
* @param position 被长按的列表项在适配器中的位置
* @param id 被长按的列表项的 ID
* @return 表示是否已处理该长按事件,这里返回 false 表示未完全处理,可能会触发其他默认行为
*/
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
// 检查被长按的视图是否为 NotesListItem 类型
if (view instanceof NotesListItem) {
// 获取被长按的列表项的数据
mFocusNoteDataItem = ((NotesListItem) view).getItemData();
// 检查被长按的列表项类型是否为笔记类型,并且当前列表不处于选择模式
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
// 尝试启动 ActionMode用于处理多选操作
if (mNotesListView.startActionMode(mModeCallBack) != null) {
// 如果成功启动 ActionMode则调用 mModeCallBack 的 onItemCheckedStateChanged 方法
// 将该列表项标记为已选中
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) {
// 如果被长按的列表项类型是文件夹类型
// 设置列表视图的上下文菜单创建监听器为 mFolderOnCreateContextMenuListener
// 以便在长按文件夹时显示上下文菜单
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
}
}
// 返回 false 表示该长按事件未完全处理,可能会触发其他默认行为
return false;
}
}