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

1027 lines
45 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.
*/
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;
// 定义菜单项
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;
// 定义一个偏好设置键,用于检查用户是否首次使用应用
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;
private ListView mNotesListView;
private Button mAddNewNote;
private boolean mDispatch;
private int mOriginY, mDispatchY;
private TextView mTitleBar;
private long mCurrentFolderId;
private ContentResolver mContentResolver;
private ModeCallback mModeCallBack;
private static final String TAG = "NotesListActivity";
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
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)";
// 定义请求码
private final static int REQUEST_CODE_OPEN_NODE = 102;
private final static int REQUEST_CODE_NEW_NODE = 103;
// 当Activity被创建时调用
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_list); // 设置布局
initResources(); // 初始化资源
// 插入一个引导说明,当用户首次使用此应用时
setAppInfoFromRawRes();
}
// 处理从其他Activity返回的结果
@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实例
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;
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(); // 打印堆栈跟踪
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());
// 保存笔记并将引导说明已读标志设置为true
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(); // 开始异步查询笔记列表
}
// 初始化资源的方法
private void initResources() {
// 获取内容解析器,用于访问内容提供者提供的数据
mContentResolver = this.getContentResolver();
// 创建后台查询处理器,用于在后台线程中执行查询
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
// 设置当前文件夹ID为根文件夹ID
mCurrentFolderId = Notes.ID_ROOT_FOLDER;
// 获取列表视图,用于显示笔记列表
mNotesListView = (ListView) findViewById(R.id.notes_list);
// 向列表视图添加底部视图,通常用于显示加载更多或添加按钮等
mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null),
null, false);
// 设置列表项的点击监听器
mNotesListView.setOnItemClickListener(new OnListItemClickListener());
// 设置列表项的长按监听器,通常用于触发选择模式
mNotesListView.setOnItemLongClickListener(this);
// 创建并设置笔记列表的适配器
mNotesListAdapter = new NotesListAdapter(this);
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;
// 创建操作模式时的回调
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);
// 禁用列表视图的长按,因为已经在操作模式中
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);
}
}
}
}
// 这是一个回调接口的实现用于处理ActionMode生命周期中的事件
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// 准备ActionMode时调用这里只是简单地返回false表示没有特殊准备
return false;
}
// 当ActionMode中的菜单项被点击时调用
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// 这里只是简单地返回false表示没有处理点击事件
return false;
}
// 当ActionMode被销毁时调用
public void onDestroyActionMode(ActionMode mode) {
// 取消列表适配器的选择模式
mNotesListAdapter.setChoiceMode(false);
// 允许列表视图长按
mNotesListView.setLongClickable(true);
// 使添加新笔记的按钮可见
mAddNewNote.setVisibility(View.VISIBLE);
}
// 结束当前ActionMode
public void finishActionMode() {
mActionMode.finish();
}
// 当列表项的选择状态改变时调用
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
// 更新列表适配器中对应位置项的选中状态
mNotesListAdapter.setCheckedItem(position, checked);
// 更新ActionMode的菜单项可能根据选中的项数显示或隐藏某些选项
updateMenu();
}
// 当ActionMode中的菜单项被点击时调用这里似乎是另一个版本的onActionItemClicked可能是特定于某个实现
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;
}
// 一个内部类,用于处理“添加新笔记”按钮的触摸事件
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();
// 如果处于子文件夹状态,需要减去标题栏的高度
if (mState == ListEditState.SUB_FOLDER) {
eventY -= mTitleBar.getHeight();
start -= mTitleBar.getHeight();
}
// 这是一个特殊的处理,用于处理“添加新笔记”按钮透明部分的触摸事件
// 将触摸事件转发给列表视图,如果触摸位置满足特定条件
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;
}
};
// 开始一个异步查询以获取笔记列表
private void startAsyncNotesListQuery() {
// 根据当前文件夹ID决定使用哪个SQL选择语句
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
// 使用BackgroundQueryHandler启动异步查询
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");
}
// 一个内部类继承自AsyncQueryHandler用于处理异步查询
private final class BackgroundQueryHandler extends AsyncQueryHandler {
// 构造函数传入ContentResolver以访问内容提供者
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
// 当查询完成时调用此方法
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
// 根据查询的token判断是哪一个查询完成了
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:
// 未知的token不做处理
return;
}
}
}
// 显示一个包含文件夹列表的对话框,允许用户选择一个文件夹
private void showFolderListMenu(Cursor cursor) {
// 创建AlertDialog.Builder
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() {
public void onClick(DialogInterface dialog, int which) {
// 将选中的笔记移动到用户选择的文件夹中
DataUtils.batchMoveToFolder(mContentResolver,
mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which));
// 显示Toast提示移动成功
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();
}
// 创建一个新的笔记
private void createNewNote() {
// 创建一个Intent以启动NoteEditActivity
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
// 将当前文件夹ID作为额外数据传递给NoteEditActivity
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId);
// 启动NoteEditActivity并请求结果
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
}
// 定义一个方法用于批量删除笔记
private void batchDelete() {
// 使用AsyncTask在后台线程中执行批量删除操作以避免阻塞UI线程
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
// 在后台线程中执行的操作
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;
}
// 在后台操作完成后,在主线程上执行的操作
@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(); // 执行AsyncTask
}
// 定义一个方法用于删除文件夹
private void deleteFolder(long folderId) {
// 检查是否尝试删除根文件夹,这是不允许的
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); // 更新小部件
}
}
}
}
// 定义一个方法用于打开笔记节点(即笔记详情页面)
private void openNode(NoteItemData data) {
// 创建一个Intent用于跳转到NoteEditActivity
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW); // 设置Action为查看
intent.putExtra(Intent.EXTRA_UID, data.getId()); // 将笔记ID作为额外数据传递给新Activity
// 启动新Activity并请求结果
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
// 定义一个方法,用于打开指定的文件夹
private void openFolder(NoteItemData data) {
// 更新当前文件夹ID
mCurrentFolderId = data.getId();
// 开始异步查询笔记列表
startAsyncNotesListQuery();
// 如果打开的是通话记录文件夹
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
// 设置状态为通话记录文件夹
mState = ListEditState.CALL_RECORD_FOLDER;
// 隐藏添加新笔记的按钮
mAddNewNote.setVisibility(View.GONE);
} else {
// 否则,设置为子文件夹状态
mState = ListEditState.SUB_FOLDER;
}
// 根据文件夹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);
}
// 定义一个点击事件处理方法
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);
}
}
// 隐藏软键盘
private void hideSoftInput(View view) {
// 获取输入法管理器
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// 隐藏指定视图窗口上的软键盘
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
// 显示创建或修改文件夹的对话框
private void showCreateOrModifyFolderDialog(final boolean create) {
// 初始化对话框构建器
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() {
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();
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) {
// TODO Auto-generated method stub
}
// 当用户按下返回键时的处理逻辑
@Override
public void onBackPressed() {
// 根据当前的状态mState执行不同的操作
switch (mState) {
case SUB_FOLDER: // 如果是子文件夹状态
mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 回到根文件夹
mState = ListEditState.NOTE_LIST; // 切换到笔记列表状态
startAsyncNotesListQuery(); // 开始异步查询笔记列表
mTitleBar.setVisibility(View.GONE); // 隐藏标题栏
break;
case CALL_RECORD_FOLDER: // 如果是通话记录文件夹状态
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;
}
}
// 更新指定ID和类型的小部件
private void updateWidget(int appWidgetId, int appWidgetType) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); // 创建更新小部件的Intent
if (appWidgetType == Notes.TYPE_WIDGET_2X) { // 如果是2x大小的小部件
intent.setClass(this, NoteWidgetProvider_2x.class); // 设置目标为2x小部件的Provider
} else if (appWidgetType == Notes.TYPE_WIDGET_4X) { // 如果是4x大小的小部件
intent.setClass(this, NoteWidgetProvider_4x.class); // 设置目标为4x小部件的Provider
} else {
Log.e(TAG, "Unsupported widget type"); // 不支持的类型,记录错误日志
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { appWidgetId }); // 添加小部件ID到Intent
sendBroadcast(intent); // 发送广播以更新小部件
setResult(RESULT_OK, intent); // 设置结果
}
// 为文件夹列表视图创建上下文菜单的监听器
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); // 添加更改文件夹名称选项
}
}
};
// 当上下文菜单关闭时的处理逻辑
@Override
public void onContextMenuClosed(Menu menu) {
if (mNotesListView != null) {
mNotesListView.setOnCreateContextMenuListener(null); // 清除列表视图的上下文菜单监听器
}
super.onContextMenuClosed(menu); // 调用父类的处理逻辑
}
// 当用户选择上下文菜单项时的处理逻辑
@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);
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; // 表示事件已处理
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear();
if (mState == ListEditState.NOTE_LIST) {
getMenuInflater().inflate(R.menu.note_list, menu);
// set sync or sync_cancel
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;
}
// 重写onOptionsItemSelected方法用于处理菜单项的点击事件
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// 根据点击的菜单项ID执行不同的操作
switch (item.getItemId()) {
case R.id.menu_new_folder: {
// 显示创建或修改文件夹的对话框传入true表示是创建新文件夹
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();
}
break;
}
case R.id.menu_setting: {
// 跳转到设置界面
startPreferenceActivity();
break;
}
case R.id.menu_new_note: {
// 创建新笔记
createNewNote();
break;
}
case R.id.menu_search:
// 请求搜索
onSearchRequested();
break;
default:
break;
}
return true; // 表示事件已处理
}
// 重写onSearchRequested方法用于处理搜索请求
@Override
public boolean onSearchRequested() {
// 发起搜索请求,不指定搜索初始查询、不调用搜索建议接口、不使用应用提供的额外数据
startSearch(null, false, null /* appData */, false);
return true; // 表示事件已处理
}
// 导出笔记到文本文件的私有方法
private void exportNoteToText() {
// 获取BackupUtils实例
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
// 使用AsyncTask在后台线程执行导出操作
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) {
// SD卡未挂载
showSdCardUnmountedDialog();
} else if (result == BackupUtils.STATE_SUCCESS) {
// 导出成功
showExportSuccessDialog(backup.getExportedTextFileName(), backup.getExportedTextFileDir());
} else if (result == BackupUtils.STATE_SYSTEM_ERROR) {
// 系统错误
showExportFailedDialog();
}
}
// 省略了对话框的创建和显示代码以showSdCardUnmountedDialog()、showExportSuccessDialog()、showExportFailedDialog()表示
}.execute();
}
// 判断当前是否处于同步模式的私有方法
private boolean isSyncMode() {
// 获取同步账户名,如果非空则处于同步模式
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
// 跳转到设置界面的私有方法
private void startPreferenceActivity() {
// 获取启动Intent的Activity如果当前Activity有父Activity则使用父Activity否则使用当前Activity
Activity from = getParent() != null ? getParent() : this;
// 创建跳转到设置界面的Intent
Intent intent = new Intent(from, NotesPreferenceActivity.class);
// 根据需要启动设置界面Activity
from.startActivityIfNeeded(intent, -1);
}
// 内部类OnListItemClickListener实现了OnItemClickListener接口用于处理列表项的点击事件
private class OnListItemClickListener implements OnItemClickListener {
// 当列表项被点击时调用的方法
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;
}
}
}
// 省略了openFolder、openNode等方法的实现细节
}
// 定义一个私有方法,用于启动查询目标文件夹的操作
private void startQueryDestinationFolders() {
// 定义一个SQL查询条件字符串用于筛选特定类型的笔记排除特定父ID和ID的笔记
String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?";
// 根据当前的状态mState如果是笔记列表状态则使用上述筛选条件
// 如果不是则添加一个额外的条件允许查询根文件夹ID_ROOT_FOLDER
selection = (mState == ListEditState.NOTE_LIST) ? selection:
"(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")";
// 使用后台查询处理器mBackgroundQueryHandler开始执行查询。
// 查询的目标是笔记内容URINotes.CONTENT_NOTE_URI
// 投影列FoldersListAdapter.PROJECTION指定了查询返回的列
// selection参数是上述定义的SQL查询条件
// selectionArgs是查询条件的参数值数组包括文件夹类型、垃圾桶文件夹ID和当前文件夹ID
// orderBy参数指定了结果按修改日期降序排列。
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), // 注意这里可能是一个拼写错误应该是ID_TRASH_FOLDER
String.valueOf(mCurrentFolderId)
},
NoteColumns.MODIFIED_DATE + " DESC");
}
// 定义一个公开方法,处理列表项的长按事件
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
// 如果被长按的视图是NotesListItem的实例
if (view instanceof NotesListItem) {
// 获取被长按的列表项的数据
mFocusNoteDataItem = ((NotesListItem) view).getItemData();
// 如果该项是笔记类型Notes.TYPE_NOTE并且当前不在选择模式下
if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) {
// 尝试启动动作模式ActionMode用于选择或操作多个笔记
if (mNotesListView.startActionMode(mModeCallBack) != null) {
// 如果动作模式启动成功,则更新选中状态,并给出触觉反馈
mModeCallBack.onItemCheckedStateChanged(null, position, id, true);
mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
} else {
// 如果启动动作模式失败,则记录错误日志
Log.e(TAG, "startActionMode fails");
}
}
// 如果该项是文件夹类型Notes.TYPE_FOLDER
else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) {
// 设置上下文菜单创建监听器,用于文件夹的上下文菜单
mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener);
}
}
// 返回false表示事件没有被消费即没有阻止后续的事件处理
return false;
}