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,实现了OnClickListener、NoteSettingChangedListener、OnTextViewChangeListener接口,用于编辑笔记相关的操作和界面交互 public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { // 内部类,用于方便地持有笔记头部视图相关的控件引用 private class HeadViewHolder { public TextView tvModified; // 用于显示笔记修改时间的TextView public ImageView ivAlertIcon; // 用于显示提醒图标(比如闹钟图标等)的ImageView public TextView tvAlertDate; // 用于显示提醒时间相关文本的TextView public ImageView ibSetBgColor; // 用于设置背景颜色的按钮ImageView } // 以下是一些静态的映射关系,用于将界面上的按钮ID与对应的颜色资源ID等进行关联 // 将背景颜色选择按钮的ID映射到对应的颜色资源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(比如选中黄色背景时对应的选中标识视图的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(如大字体、小字体等对应的资源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; // 字体大小选择器的视图(包含不同字体大小选项的布局) private EditText mNoteEditor; // 用于编辑笔记内容的EditText控件 private View mNoteEditorPanel; // 包含笔记编辑区域的整体视图(可能用于设置背景等相关属性) private WorkingNote mWorkingNote; // 代表正在编辑的笔记对象,包含笔记的各种属性和操作方法 private SharedPreferences mSharedPrefs; // 用于获取和保存应用的共享偏好设置 private int mFontSizeId; // 当前选中的字体大小对应的资源ID 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; // 用于显示笔记内容为列表模式时的列表布局(LinearLayout) private String mUserQuery; // 用户输入的查询字符串(可能用于搜索笔记内容等场景) private Pattern mPattern; // 用于对用户查询内容进行正则匹配的模式对象 // Activity创建时调用的方法 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 设置该Activity对应的布局文件 this.setContentView(R.layout.note_edit); // 如果savedInstanceState为null(即Activity首次创建,不是从内存恢复的情况),并且初始化Activity状态失败,则直接结束该Activity if (savedInstanceState == null &&!initActivityState(getIntent())) { finish(); return; } // 初始化相关资源,如查找视图等操作 initResources(); } /** * 当Activity因内存不足被系统销毁后,再次恢复时调用该方法,用于从保存的状态中恢复Activity的相关信息 */ @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // 如果保存的状态不为null且包含特定的额外数据(通过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的状态,比如确定是查看已有笔记还是新建笔记等情况,并加载相应的笔记数据 private boolean initActivityState(Intent intent) { /** * 如果用户指定了Intent.ACTION_VIEW动作,但没有提供笔记的ID,那么跳转到NotesListActivity(可能是笔记列表页面) */ mWorkingNote = null; if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); mUserQuery = ""; /** * 如果Intent中包含搜索相关的额外数据(从搜索结果进入该Activity的情况),则获取对应的笔记ID和用户查询字符串 */ if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); } // 检查指定的笔记ID对应的笔记在数据库中是否可见(是否存在且满足可见条件),如果不存在则跳转到笔记列表页面并提示错误信息 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 { // 从数据库加载指定ID的笔记对象,如果加载失败则记录错误日志并结束该Activity mWorkingNote = WorkingNote.load(this, noteId); if (mWorkingNote == null) { Log.e(TAG, "load note failed with note id" + noteId); finish(); return false; } } // 设置软键盘的显示模式,初始为隐藏且根据布局调整大小 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())) { // 新建笔记的情况 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)); // 解析可能的通话记录笔记相关信息(如果有) 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); finish(); return false; } } else { mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId); mWorkingNote.convertToCallNote(phoneNumber, callDate); } } else { mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId); } // 设置软键盘的显示模式,初始为可见且根据布局调整大小 getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); } else { Log.e(TAG, "Intent not specified action, should not support"); finish(); return false; } // 设置笔记对象的设置状态改变监听器为当前Activity(实现了NoteSettingChangedListener接口) mWorkingNote.setOnSettingStatusChangedListener(this); return true; } // Activity恢复可见时(如从暂停状态恢复)调用的方法,用于初始化笔记编辑界面的相关显示内容 @Override protected void onResume() { super.onResume(); initNoteScreen(); } // 初始化笔记编辑界面的显示内容,包括设置字体样式、根据笔记模式显示不同内容、设置头部和编辑区域的背景等 private void initNoteScreen() { mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { switchToListMode(mWorkingNote.getContent()); } else { mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); mNoteEditor.setSelection(mNoteEditor.getText().length()); } for (Integer id : sBgSelectorSelectionMap.keySet()) { findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); } mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR)); /** * TODO: 添加用于设置提醒的菜单选项。目前禁用该功能,因为DateTimePicker还未准备好 */ showAlertHeader(); } // 根据笔记是否设置了提醒时间,显示或隐藏提醒相关的视图(如提醒时间文本和图标)并设置相应的文本内容 private void showAlertHeader() { if (mWorkingNote.hasClockAlert()) { long time = System.currentTimeMillis(); if (time > mWorkingNote.getAlertDate()) { mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); } else { mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); } mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); } else { mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); }; } // 当Activity接收到新的Intent时调用该方法(比如通过启动模式设置可以接收新的Intent情况),重新初始化Activity状态 @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); initActivityState(intent); } // 在Activity可能被销毁前(如系统内存不足、用户切换应用等情况)保存当前Activity的相关状态信息(比如正在编辑的笔记的ID等) @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); /** * 对于没有笔记ID的新笔记(可能还未保存),先保存笔记以生成一个ID。如果正在编辑的笔记不值得保存(可能内容为空等情况),没有ID就相当于新建笔记的情况 */ if (!mWorkingNote.existInDatabase()) { saveNote(); } outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); } // 用于分发触摸事件,当背景颜色选择器或字体大小选择器可见时,判断触摸事件是否在其区域外,如果在区域外则隐藏相应的选择器视图 @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (mNoteBgColorSelector.getVisibility() == View.VISIBLE &&!inRangeOfView(mNoteBgColorSelector, ev)) { mNoteBgColorSelector.setVisibility(View.GONE); return true; } if (mFontSizeSelector.getVisibility() == View.VISIBLE