/* * 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. */ // 包声明,表明该类所在的包名为net.micode.notes.ui package net.micode.notes.ui; import android.app.Activity; import android.app.AlarmManager; import android.app.AlertDialog; import android.app.PendingIntent; import android.app.SearchManager; import android.appwidget.AppWidgetManager; import android.content.ContentUris; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Paint; import android.os.Bundle; import android.preference.PreferenceManager; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.text.format.DateUtils; import android.text.style.BackgroundColorSpan; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.WindowManager; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; 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.TextNote; import net.micode.notes.model.WorkingNote; import net.micode.notes.model.WorkingNote.NoteSettingChangedListener; import net.micode.notes.tool.DataUtils; import net.micode.notes.tool.ResourceParser; import net.micode.notes.tool.ResourceParser.TextAppearanceResources; import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; import net.micode.notes.widget.NoteWidgetProvider_2x; import net.micode.notes.widget.NoteWidgetProvider_4x; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; // NoteEditActivity类继承自Activity,它是用于编辑笔记的主要Activity,实现了多个接口来处理用户交互、笔记设置变更以及文本内容变化等相关逻辑, // 提供了丰富的功能,如文本编辑、样式设置、提醒设置、分享、删除笔记等操作。 public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { // 内部类,用于持有笔记头部相关视图控件的引用,方便在外部类中对这些控件进行统一操作和管理,例如设置文本、控制显示隐藏等。 private class HeadViewHolder { public TextView tvModified; public ImageView ivAlertIcon; public TextView tvAlertDate; public ImageView ibSetBgColor; } // 用于存储背景颜色选择按钮(如黄色、红色等按钮)与对应的颜色资源ID的映射关系,方便根据按钮ID获取对应的颜色资源标识,以进行背景颜色相关的操作。 private static final Map sBgSelectorBtnsMap = new HashMap(); static { sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED); sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE); sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); } // 用于存储背景颜色选择按钮对应的选中状态指示器(如黄色按钮选中时对应的黄色选中指示器视图)与颜色资源ID的映射关系, // 便于根据颜色资源ID找到对应的选中指示器视图并控制其显示隐藏,以反馈当前选中的背景颜色。 private static final Map sBgSelectorSelectionMap = new HashMap(); static { sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select); sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } // 用于存储字体大小选择按钮(如大字体、小字体等按钮)与对应的字体大小资源ID的映射关系,方便根据按钮ID获取相应的字体大小设置,以进行字体大小相关的操作。 private static final Map sFontSizeBtnsMap = new HashMap(); static { sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL); sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); } // 用于存储字体大小选择按钮对应的选中状态指示器(如大字体按钮选中时对应的大字体选中指示器视图)与字体大小资源ID的映射关系, // 便于根据字体大小资源ID找到对应的选中指示器视图并控制其显示隐藏,以反馈当前选中的字体大小。 private static final Map sFontSelectorSelectionMap = new HashMap(); static { sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); } // 用于日志记录的标签,方便在日志输出中识别该类相关的日志信息,便于调试和查看运行情况。 private static final String TAG = "NoteEditActivity"; // 用于持有笔记头部视图相关控件的实例,通过初始化赋值后,可方便地在其他方法中操作这些控件,比如设置修改日期、提醒日期等文本显示。 private HeadViewHolder mNoteHeaderHolder; // 笔记头部视图的整体布局视图,用于设置背景等相关样式操作,例如根据笔记的相关设置改变其背景资源显示。 private View mHeadViewPanel; // 用于显示和选择笔记背景颜色的视图,包含多个背景颜色选择按钮等,通过控制其显示隐藏以及按钮点击事件来实现背景颜色的选择功能。 private View mNoteBgColorSelector; // 用于显示和选择字体大小的视图,包含多个字体大小选择按钮等,通过点击按钮进行字体大小的切换操作,并更新相关文本的显示样式。 private View mFontSizeSelector; // 用于编辑笔记文本内容的EditText控件,用户在该控件中输入、修改笔记的具体文字内容,同时也会根据其他设置(如字体大小等)更新显示样式。 private EditText mNoteEditor; // 包含笔记编辑区域(mNoteEditor)的整体布局视图,用于设置其背景等相关样式,使其与笔记的整体风格和设置保持一致。 private View mNoteEditorPanel; // 代表正在编辑的笔记对象,封装了笔记的各种属性(如文本内容、背景颜色、提醒设置等)以及相关的操作方法(如保存、加载等),是与笔记数据交互的核心对象。 private WorkingNote mWorkingNote; // 用于获取和操作应用的共享偏好设置(SharedPreferences),可以保存和读取一些用户的个性化设置信息,例如字体大小偏好设置等。 private SharedPreferences mSharedPrefs; // 用于记录当前选中的字体大小资源ID,通过读取共享偏好设置初始化,并在字体大小切换时更新,以确保文本编辑区域能正确应用对应的字体大小样式。 private int mFontSizeId; // 用于在共享偏好设置中标识字体大小偏好设置项的键名,通过该键可以准确地存取对应的字体大小设置值。 private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; // 定义快捷方式图标标题的最大长度,用于创建桌面快捷方式时,截取合适长度的笔记内容作为快捷方式的显示标题,避免标题过长显示不美观。 private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; // 定义表示选中状态的标记字符串,用于在处理列表模式下的笔记内容时,标识某一项是否被选中,例如在复选框列表形式的笔记中表示该项已选中。 public static final String TAG_CHECKED = String.valueOf('\u221A'); // 定义表示未选中状态的标记字符串,用于在处理列表模式下的笔记内容时,标识某一项是否未被选中,例如在复选框列表形式的笔记中表示该项未选中。 public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); // 用于在列表模式下展示笔记内容的线性布局,当笔记处于列表模式(如复选框列表形式)时,每个列表项会添加到该布局中进行展示,方便用户以列表形式查看和编辑笔记内容。 private LinearLayout mEditTextList; // 用于存储用户的搜索查询字符串,当从搜索结果进入笔记编辑页面时,会携带该查询内容,可用于在笔记内容中高亮显示匹配的查询部分等相关操作。 private String mUserQuery; // 用于编译用户查询字符串为正则表达式模式,方便后续在笔记内容中进行匹配查找,以实现如高亮显示查询结果等功能。 private Pattern mPattern; // Activity创建时调用的方法,用于进行一些初始化操作,如设置Activity的布局内容,根据传入的Intent初始化活动状态等,是整个Activity生命周期的起始方法。 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 设置该Activity对应的布局文件为R.layout.note_edit,该布局文件定义了笔记编辑页面的整体UI结构,包含各种视图控件的布局和样式等。 this.setContentView(R.layout.note_edit); // 如果是首次创建该Activity(savedInstanceState为null)且无法成功初始化活动状态(initActivityState方法返回false), // 则直接结束该Activity,不再进行后续操作,因为可能缺少必要的启动参数等导致无法正常展示编辑页面。 if (savedInstanceState == null &&!initActivityState(getIntent())) { finish(); return; } // 调用初始化资源的方法,用于获取布局中的各个视图控件实例,并进行一些相关的初始设置,例如设置点击监听器等。 initResources(); } /** * 当前Activity在内存不足时可能会被系统销毁。当再次被用户启动时,需要恢复之前的状态, * 该方法会在这种情况下被调用,用于从保存的状态中恢复Activity的相关数据和设置。 */ @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // 如果保存的状态不为空且包含特定的额外数据(通过Intent.EXTRA_UID标识),则尝试重新初始化活动状态, // 若初始化失败则结束该Activity,若成功则表示从之前被销毁的状态中恢复过来了,并在日志中记录恢复信息。 if (savedInstanceState!= null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); if (!initActivityState(intent)) { finish(); return; } Log.d(TAG, "Restoring from killed activity"); } } // 用于根据传入的Intent初始化Activity的状态,根据Intent的不同动作(ACTION_VIEW或ACTION_INSERT_OR_EDIT等)以及携带的额外数据, // 进行相应的操作,如加载已有笔记、创建新笔记等,并返回初始化是否成功的标志,是决定Activity能否正常展示编辑功能的关键方法。 private boolean initActivityState(Intent intent) { // 初始化为空,表示尚未确定要编辑的笔记对象,后续根据Intent的具体情况进行赋值和加载操作。 mWorkingNote = null; // 判断Intent的动作是否为查看(ACTION_VIEW),如果是查看已有笔记的情况,则进行以下操作。 if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { // 获取Intent中传递的笔记ID,如果没有传递则默认为0,后续会根据该ID来查找并加载对应的笔记数据。 long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); mUserQuery = ""; // 如果Intent中包含搜索相关的额外数据(SearchManager.EXTRA_DATA_KEY),说明是从搜索结果进入编辑页面, // 则根据传递的搜索数据获取笔记ID,并获取用户的搜索查询字符串,用于后续在笔记中展示搜索匹配情况等操作。 if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); } // 通过DataUtils工具类方法检查该笔记ID对应的笔记是否在笔记数据库中可见(是否存在且符合类型要求等), // 如果不存在,则跳转到笔记列表页面(NotesListActivity),并显示提示信息告知用户笔记不存在,然后结束当前编辑Activity。 if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { Intent jump = new Intent(this, NotesListActivity.class); startActivity(jump); showToast(R.string.error_note_not_exist); finish(); return false; } else { // 如果笔记存在,则通过WorkingNote的加载方法,根据笔记ID加载对应的笔记对象到mWorkingNote中, // 如果加载失败则记录错误日志并结束当前编辑Activity,若加载成功则继续后续的初始化设置。 mWorkingNote = WorkingNote.load(this, noteId); if (mWorkingNote == null) { Log.e(TAG, "load note failed with note id" + noteId); finish(); return false; } } // 设置软键盘的显示模式,使其初始状态为隐藏,并且当软键盘弹出时,调整Activity的布局以适应软键盘显示,避免遮挡内容。 getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); } else if (TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { // 处理创建新笔记或编辑已有笔记的情况,以下是获取创建或编辑笔记所需的各种额外数据,如文件夹ID、小部件相关信息、背景资源ID等。 long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, Notes.TYPE_WIDGET_INVALIDE); int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, ResourceParser.getDefaultBgId(this)); // 尝试解析通话记录相关的笔记内容,如果传入的通话日期(callDate)和电话号码(phoneNumber)都不为空, // 则根据这两个信息查找是否已存在对应的笔记,如果存在则加载该笔记,若不存在则创建一个空的笔记并转换为通话记录笔记格式,填充相应的通话数据。 String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); if (callDate!= 0 && phoneNumber!= null) { if (TextUtils.isEmpty(phoneNumber)) { Log.w(TAG, "The call record number is null"); } long noteId = 0; if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), phoneNumber, callDate)) > 0) { mWorkingNote = WorkingNote.load(this, noteId); if (mWorkingNote == null) { Log.e(TAG, "load call note failed with note id" + noteId);