From c3d40a4ed905a3458cccfcec7e2df03525033334 Mon Sep 17 00:00:00 2001 From: MikkoAyaka <3401286177@qq.com> Date: Mon, 12 Jun 2023 09:42:53 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- other/05_冉琦_贡献度.xls | Bin 0 -> 26624 bytes other/06_冉琦_代码标注.txt | 2266 +++++++++++++++++++++++ other/07_冉琦_实践总结报告.docx | Bin 0 -> 18084 bytes 3 files changed, 2266 insertions(+) create mode 100644 other/05_冉琦_贡献度.xls create mode 100644 other/06_冉琦_代码标注.txt create mode 100644 other/07_冉琦_实践总结报告.docx diff --git a/other/05_冉琦_贡献度.xls b/other/05_冉琦_贡献度.xls new file mode 100644 index 0000000000000000000000000000000000000000..cb21dd51f33e2b9c5a320bdba0fb4a6a805ca0a4 GIT binary patch literal 26624 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 literal 0 HcmV?d00001 diff --git a/other/06_冉琦_代码标注.txt b/other/06_冉琦_代码标注.txt new file mode 100644 index 0000000..aaf038e --- /dev/null +++ b/other/06_冉琦_代码标注.txt @@ -0,0 +1,2266 @@ +/** + * 日期选择器,是 [FrameLayout] 的子类 + */ +public class DateTimePicker extends FrameLayout { + + private static final boolean DEFAULT_ENABLE_STATE = true; + + /** + * 常量声明,包含一些数值范围等 + * 例如小时可选范围为0~23小时 + * 分钟可选范围为0~59分钟 + */ + private static final int HOURS_IN_HALF_DAY = 12; + private static final int HOURS_IN_ALL_DAY = 24; + private static final int DAYS_IN_ALL_WEEK = 7; + private static final int DATE_SPINNER_MIN_VAL = 0; + private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; + private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; + private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; + private static final int MINUT_SPINNER_MIN_VAL = 0; + private static final int MINUT_SPINNER_MAX_VAL = 59; + private static final int AMPM_SPINNER_MIN_VAL = 0; + private static final int AMPM_SPINNER_MAX_VAL = 1; + + /** + * 天数选择器 + */ + private final NumberPicker mDateSpinner; + /** + * 时间选择器 + */ + private final NumberPicker mHourSpinner; + /** + * 分钟选择器 + */ + private final NumberPicker mMinuteSpinner; + /** + * AM / PM 早晚选择器 + */ + private final NumberPicker mAmPmSpinner; + /** + * 日期实例对象 + */ + private Calendar mDate; + /** + * 日期显示的数值 + */ + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + /** + * 是否是早晨 + */ + private boolean mIsAm; + /** + * 是否是24小时制 + */ + private boolean mIs24HourView; + /** + * 是否启用 + */ + private boolean mIsEnabled = DEFAULT_ENABLE_STATE; + /** + * 是否正在初始化中 + */ + private boolean mInitialising; + /** + * 日期变更监听器 + */ + private OnDateTimeChangedListener mOnDateTimeChangedListener; + /** + * 数值改变监听器 + * + * 在数值更改时同步修改 mDate 的数值 + * 并刷新选择器的数值 + * 触发时间改变监听器 + */ + private NumberPicker.OnValueChangeListener mOnDateChangedListener = + new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); + updateDateControl(); + onDateTimeChanged(); + } + }; + /** + * 数值改变监听器 + * + * 在数值改变时传入选择器对象和操作前与操作后的数值 + * 对数值进行具体的分析 + */ + private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener(){ + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + boolean isDateChanged = false; + Calendar cal = Calendar.getInstance(); + if (!mIs24HourView) { + if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || + oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + } else { + if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + } + int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); + mDate.set(Calendar.HOUR_OF_DAY, newHour); + onDateTimeChanged(); + if (isDateChanged) { + setCurrentYear(cal.get(Calendar.YEAR)); + setCurrentMonth(cal.get(Calendar.MONTH)); + setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); + } + } + }; + /** + * 分钟改变监听器 + */ + private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + int minValue = mMinuteSpinner.getMinValue(); + int maxValue = mMinuteSpinner.getMaxValue(); + int offset = 0; + if (oldVal == maxValue && newVal == minValue) { + offset += 1; + } else if (oldVal == minValue && newVal == maxValue) { + offset -= 1; + } + if (offset != 0) { + mDate.add(Calendar.HOUR_OF_DAY, offset); + mHourSpinner.setValue(getCurrentHour()); + updateDateControl(); + int newHour = getCurrentHourOfDay(); + if (newHour >= HOURS_IN_HALF_DAY) { + mIsAm = false; + updateAmPmControl(); + } else { + mIsAm = true; + updateAmPmControl(); + } + } + mDate.set(Calendar.MINUTE, newVal); + onDateTimeChanged(); + } + }; + /** + * 早晚变更的监听器 + * 在选择AM / PM的时候通知监听器触发方法 + */ + private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mIsAm = !mIsAm; + if (mIsAm) { + mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); + } else { + mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); + } + updateAmPmControl(); + onDateTimeChanged(); + } + }; +/** + * 日期选择器 界面 + * 将用户交互与日期选择器数据类对接 + * 并保存了一些基础信息,如当前日期、是否为24小时格式等 + * 也注册了相应的事件监听器 + */ +public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + + private Calendar mDate = Calendar.getInstance(); + private boolean mIs24HourView; + private OnDateTimeSetListener mOnDateTimeSetListener; + private DateTimePicker mDateTimePicker; + + // 为什么要在类里面声明公开接口还在其他类中使用?这样做并不规范 + public interface OnDateTimeSetListener { + void OnDateTimeSet(AlertDialog dialog, long date); + } + // 构造方法 + public DateTimePickerDialog(Context context, long date) { + super(context); +// 设置为 24 小时的日期显示格式 + public void set24HourView(boolean is24HourView) { + mIs24HourView = is24HourView; + } + // 初始化 OnDateTimeSetListener 监听器 + public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { + mOnDateTimeSetListener = callBack; + } + // 更新弹出窗口标题 + private void updateTitle(long date) { + int flag = + DateUtils.FORMAT_SHOW_YEAR | + DateUtils.FORMAT_SHOW_DATE | + DateUtils.FORMAT_SHOW_TIME; + flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; + setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); + } + @Override + public void onClick(DialogInterface arg0, int arg1) { + if (mOnDateTimeSetListener != null) { + // onClick 委托至 OnDateTimeSetListener + mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); + } + } +/** + * 闹钟传回信息接收者 + * 在设置闹钟之后该活动类会接受来自闹钟系统传回的消息并进行相关处理 + */ +public class AlarmInitReceiver extends BroadcastReceiver { + + // 数据格式封包 + private static final String[] PROJECTION = new String[]{ + NoteColumns.ID, + NoteColumns.ALERTED_DATE + }; + + private static final int COLUMN_ID = 0; + private static final int COLUMN_ALERTED_DATE = 1; + + // 接收到闹钟应用的广播后执行该方法 + @Override + public void onReceive(Context context, Intent intent) { + +/** + * 闹钟传回的初始化信息接收者 + * 在设置闹钟之后该活动类会接受来自闹钟系统传回的消息并进行相关处理 + */ +public class AlarmInitReceiver extends BroadcastReceiver { + + // 数据格式封包 + private static final String[] PROJECTION = new String[]{ + NoteColumns.ID, + NoteColumns.ALERTED_DATE + }; + + private static final int COLUMN_ID = 0; + private static final int COLUMN_ALERTED_DATE = 1; + + // 接收到闹钟应用的广播后执行该方法 + @Override + public void onReceive(Context context, Intent intent) { + long currentDate = System.currentTimeMillis(); + Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, + PROJECTION, + NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, + new String[]{String.valueOf(currentDate)}, + null); + + if (c != null) { + if (c.moveToFirst()) { + do { + long alertDate = c.getLong(COLUMN_ALERTED_DATE); + Intent sender = new Intent(context, AlarmReceiver.class); + sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + AlarmManager alermManager = (AlarmManager) context + .getSystemService(Context.ALARM_SERVICE); + alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); + } while (c.moveToNext()); + } + c.close(); + } + } +} +/** + * 闹钟广播消息的接收者 + * 根据消息内容发起 Intent 调用 + */ +public class AlarmReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + intent.setClass(context, AlarmAlertActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } +} + +/** + * 下拉菜单的数据类 + * 并封装了一个下拉菜单内容选定事件监听器 + */ +public class DropdownMenu { + // 菜单按钮 + private Button mButton; + // 弹出菜单 + private PopupMenu mPopupMenu; + // 菜单 + private Menu mMenu; + + public DropdownMenu(Context context, Button button, int menuId) { + mButton = button; + mButton.setBackgroundResource(R.drawable.dropdown_icon); + mPopupMenu = new PopupMenu(context, mButton); + mMenu = mPopupMenu.getMenu(); + mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + mButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mPopupMenu.show(); + } + }); + } + // 绑定监听器 + public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { + if (mPopupMenu != null) { + mPopupMenu.setOnMenuItemClickListener(listener); + } + } + // 根据id获取MenuItem + public MenuItem findItem(int id) { + return mMenu.findItem(id); + } + // 设置菜单按钮的文本 + public void setTitle(CharSequence title) { + mButton.setText(title); + } +} +/** + * 文件列表适配器 + */ +public class FoldersListAdapter extends CursorAdapter { + public static final String[] PROJECTION = { + NoteColumns.ID, + NoteColumns.SNIPPET + }; + + public static final int ID_COLUMN = 0; + public static final int NAME_COLUMN = 1; + + public FoldersListAdapter(Context context, Cursor c) { + super(context, c); + // TODO Auto-generated constructor stub + } + + /** + * 创建新视图 + * @param context 上下文 + * @param cursor 数据库指针 + * @param parent 父视图 + * @return View 视图对象 + */ + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return new FolderListItem(context); + } + + /** + * 视图绑定方法 + * @param view 视图 + * @param context 上下文 + * @param cursor 数据库指针 + */ + @Override + public void bindView(View view, Context context, Cursor cursor) { + if (view instanceof FolderListItem) { + String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context + .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); + ((FolderListItem) view).bind(folderName); + } + } + /** + * 获取文件夹名称 + */ + public String getFolderName(Context context, int position) { + Cursor cursor = (Cursor) getItem(position); + return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context + .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); + } + + /** + * 文件列表中的子项 + */ + private class FolderListItem extends LinearLayout { + // 保存文件名 + private TextView mName; + + public FolderListItem(Context context) { + super(context); + inflate(context, R.layout.folder_list_item, this); + mName = (TextView) findViewById(R.id.tv_folder_name); + } + // 设置文件名 + public void bind(String name) { + mName.setText(name); + } + } + +} +/** + * 笔记编辑控件 + * 扩展了默认控件的一些基础功能 + */ +public class NoteEditText extends EditText { + private static final String TAG = "NoteEditText"; + private int mIndex; + private int mSelectionStartBeforeDelete; + + private static final String SCHEME_TEL = "tel:"; + private static final String SCHEME_HTTP = "http:"; + private static final String SCHEME_EMAIL = "mailto:"; + + private static final Map sSchemaActionResMap = new HashMap<>(); + // 静态初始化 + static { + sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); + sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); + sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); + } + + /** + * Call by the {@link NoteEditActivity} to delete or add edit text + */ + public interface OnTextViewChangeListener { + /** + * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens + * and the text is null + */ + void onEditTextDelete(int index, String text); + + /** + * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} + * happen + */ + void onEditTextEnter(int index, String text); + + /** + * Hide or show item option when text change + */ + void onTextChange(int index, boolean hasText); + } + /** + * 文本视图改变事件监听器 + */ + private OnTextViewChangeListener mOnTextViewChangeListener; + /** + * 构造器 + */ + public NoteEditText(Context context) { + super(context, null); + mIndex = 0; + } + /** + * 设置索引值 + */ + public void setIndex(int index) { + mIndex = index; + } + /** + * 绑定 文本视图改变事件监听器 + */ + public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { + mOnTextViewChangeListener = listener; + } + /** + * 构造器 + */ + public NoteEditText(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.editTextStyle); + } + + public NoteEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + // TODO Auto-generated constructor stub + } + + /** + * 点击事件 + */ + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + + int x = (int) event.getX(); + int y = (int) event.getY(); + x -= getTotalPaddingLeft(); + y -= getTotalPaddingTop(); + x += getScrollX(); + y += getScrollY(); + + Layout layout = getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + Selection.setSelection(getText(), off); + break; + } + + return super.onTouchEvent(event); + } + + /** + * 按下键 + * @param keyCode 按键对应编码 + * @param event 键盘事件 + * @return boolean 是否成功处理 + */ + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + if (mOnTextViewChangeListener != null) { + return false; + } + break; + case KeyEvent.KEYCODE_DEL: + mSelectionStartBeforeDelete = getSelectionStart(); + break; + default: + break; + } + return super.onKeyDown(keyCode, event); + } + + /** + * 松开键 + * @param keyCode 键对应编码 + * @param event 键盘事件 + * @return 是否处理成功 + */ + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_DEL: + if (mOnTextViewChangeListener != null) { + if (0 == mSelectionStartBeforeDelete && mIndex != 0) { + mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); + return true; + } + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + break; + case KeyEvent.KEYCODE_ENTER: + if (mOnTextViewChangeListener != null) { + int selectionStart = getSelectionStart(); + String text = getText().subSequence(selectionStart, length()).toString(); + setText(getText().subSequence(0, selectionStart)); + mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + break; + default: + break; + } + return super.onKeyUp(keyCode, event); + } + + /** + * 焦点事件改变 + * @param focused 改变的结果是对焦还是失焦 + * @param direction 方向 + * @param previouslyFocusedRect 上一个聚焦的区域 + */ + @Override + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + if (mOnTextViewChangeListener != null) { + if (!focused && TextUtils.isEmpty(getText())) { + mOnTextViewChangeListener.onTextChange(mIndex, false); + } else { + mOnTextViewChangeListener.onTextChange(mIndex, true); + } + } + super.onFocusChanged(focused, direction, previouslyFocusedRect); + } + + /** + * 菜单内容初始化 + * @param menu 菜单 + */ + @Override + protected void onCreateContextMenu(ContextMenu menu) { + if (getText() instanceof Spanned) { + int selStart = getSelectionStart(); + int selEnd = getSelectionEnd(); + + int min = Math.min(selStart, selEnd); + int max = Math.max(selStart, selEnd); + + final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); + if (urls.length == 1) { + int defaultResId = 0; + for (String schema : sSchemaActionResMap.keySet()) { + if (urls[0].getURL().indexOf(schema) >= 0) { + defaultResId = sSchemaActionResMap.get(schema); + break; + } + } + + if (defaultResId == 0) { + defaultResId = R.string.note_link_other; + } + + menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( + new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // goto a new intent + urls[0].onClick(NoteEditText.this); + return true; + } + }); + } + } + super.onCreateContextMenu(menu); + } +} +/** + * 便签列表子项 + * 存储许多便签有关的数据 + */ +public class NoteItemData { + public static final String[] PROJECTION = new String[]{ + NoteColumns.ID, + NoteColumns.ALERTED_DATE, + NoteColumns.BG_COLOR_ID, + NoteColumns.CREATED_DATE, + NoteColumns.HAS_ATTACHMENT, + NoteColumns.MODIFIED_DATE, + NoteColumns.NOTES_COUNT, + NoteColumns.PARENT_ID, + NoteColumns.SNIPPET, + NoteColumns.TYPE, + NoteColumns.WIDGET_ID, + NoteColumns.WIDGET_TYPE, + }; + +/** + * 便签列表适配器 + */ +public class NotesListAdapter extends CursorAdapter { + private static final String TAG = "NotesListAdapter"; + private Context mContext; + private HashMap mSelectedIndex; + private int mNotesCount; + private boolean mChoiceMode; + + /** + * APP页面属性类 + */ + public static class AppWidgetAttribute { + public int widgetId; + public int widgetType; + } + + ; + + /** + * 构造器 + * @param context 便签列表上下文环境 + */ + public NotesListAdapter(Context context) { + super(context, null); + mSelectedIndex = new HashMap<>(); + mContext = context; + mNotesCount = 0; + } + + /** + * 新建便签列表视图的方法 + * @param context 上下文环境 + * @param cursor 数据库指针 + * @param parent 视图父类 + * @return + */ + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return new NotesListItem(context); + } + + /** + * 视图绑定 + * @param view 视图 + * @param context 上下文 + * @param cursor 数据库指针 + */ + @Override + public void bindView(View view, Context context, Cursor cursor) { + if (view instanceof NotesListItem) { + NoteItemData itemData = new NoteItemData(context, cursor); + ((NotesListItem) view).bind(context, itemData, mChoiceMode, + isSelectedItem(cursor.getPosition())); + } + } + + /** + * 设置子项选中状态 + * @param position 子项的索引 + * @param checked 是否选中 + */ + public void setCheckedItem(final int position, final boolean checked) { + mSelectedIndex.put(position, checked); + notifyDataSetChanged(); + } + + /** + * 是否在决策模式 + * @return 结果 + */ + public boolean isInChoiceMode() { + return mChoiceMode; + } + + /** + * 设置决策模式是否启用 + * @param mode 是否启用 + */ + public void setChoiceMode(boolean mode) { + mSelectedIndex.clear(); + mChoiceMode = mode; + } + + /** + * 选择全部便签 + * @param checked 选中/取消选择 + */ + public void selectAll(boolean checked) { + Cursor cursor = getCursor(); + for (int i = 0; i < getCount(); i++) { + if (cursor.moveToPosition(i)) { + if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { + setCheckedItem(i, checked); + } + } + } + } + + /** + * 获取当前选定的所有标签项的Id + * @return + */ + public HashSet getSelectedItemIds() { + HashSet itemSet = new HashSet(); + for (Integer position : mSelectedIndex.keySet()) { + if (mSelectedIndex.get(position) == true) { + Long id = getItemId(position); + if (id == Notes.ID_ROOT_FOLDER) { + Log.d(TAG, "Wrong item id, should not happen"); + } else { + itemSet.add(id); + } + } + } + + return itemSet; + } + + /** + * 获取选中的界面 + */ + public HashSet getSelectedWidget() { + HashSet itemSet = new HashSet(); + for (Integer position : mSelectedIndex.keySet()) { + if (mSelectedIndex.get(position) == true) { + Cursor c = (Cursor) getItem(position); + if (c != null) { + AppWidgetAttribute widget = new AppWidgetAttribute(); + NoteItemData item = new NoteItemData(mContext, c); + widget.widgetId = item.getWidgetId(); + widget.widgetType = item.getWidgetType(); + itemSet.add(widget); + /** + * Don't close cursor here, only the adapter could close it + */ + } else { + Log.e(TAG, "Invalid cursor"); + return null; + } + } + } + return itemSet; + } + + /** + * 获取当前选中的数量 + * @return 数量 + */ + public int getSelectedCount() { + Collection values = mSelectedIndex.values(); + if (null == values) { + return 0; + } + Iterator iter = values.iterator(); + int count = 0; + while (iter.hasNext()) { + if (true == iter.next()) { + count++; + } + } + return count; + } + + /** + * 标签是否全部选中 + * @return 是否全选 + */ + public boolean isAllSelected() { + int checkedCount = getSelectedCount(); + return (checkedCount != 0 && checkedCount == mNotesCount); + } + + /** + * 是否选择了指定索引的便签项 + * @param position + * @return + */ + public boolean isSelectedItem(final int position) { + if (null == mSelectedIndex.get(position)) { + return false; + } + return mSelectedIndex.get(position); + } + + /** + * 在内容改变时调用该方法重新计算便签数量 + */ + @Override + protected void onContentChanged() { + super.onContentChanged(); + calcNotesCount(); + } + + /** + * 修改数据库指针然后重新计算标签项 + * @param cursor 新的指针 + */ + @Override + public void changeCursor(Cursor cursor) { + super.changeCursor(cursor); + calcNotesCount(); + } + + /** + * 计算便签数量 + */ + private void calcNotesCount() { + mNotesCount = 0; + for (int i = 0; i < getCount(); i++) { + Cursor c = (Cursor) getItem(i); + if (c != null) { + if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { + mNotesCount++; + } + } else { + Log.e(TAG, "Invalid cursor"); + return; + } + } + } +} +/** + * 便签项子类 + * 存储便签列表中便签的状态 + */ +public class NotesListItem extends LinearLayout { + private ImageView mAlert; + private TextView mTitle; + private TextView mTime; + private TextView mCallName; + private NoteItemData mItemData; + private CheckBox mCheckBox; +/** + * 便签项子类 + * 存储便签列表中便签的状态 + */ +public class NotesListItem extends LinearLayout { + private ImageView mAlert; + private TextView mTitle; + private TextView mTime; + private TextView mCallName; + private NoteItemData mItemData; + private CheckBox mCheckBox; + +/** + * 闹钟应用活动类 + */ +public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + private long mNoteId; + private String mSnippet; + private static final int SNIPPET_PREW_MAX_LEN = 60; + MediaPlayer mPlayer; + + /** + * onCreate 生命周期方法 + * @param savedInstanceState 之前保存的实例状态 + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + + final Window win = getWindow(); + win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + + if (!isScreenOn()) { + win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); + } + + Intent intent = getIntent(); + + try { + mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); + mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, + SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) + : mSnippet; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return; + } + + mPlayer = new MediaPlayer(); + if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { + showActionDialog(); + playAlarmSound(); + } else { + finish(); + } + } + + /** + * 屏幕是否开着 + * @return 状态 + */ + private boolean isScreenOn() { + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + return pm.isScreenOn(); + } + + /** + * 播放闹钟提示音 + */ + private void playAlarmSound() { + Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + + int silentModeStreams = Settings.System.getInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + + if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { + mPlayer.setAudioStreamType(silentModeStreams); + } else { + mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + } + try { + mPlayer.setDataSource(this, url); + mPlayer.prepare(); + mPlayer.setLooping(true); + mPlayer.start(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalStateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * 显示操作弹窗 + */ + private void showActionDialog() { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setTitle(R.string.app_name); + dialog.setMessage(mSnippet); + dialog.setPositiveButton(R.string.notealert_ok, this); + if (isScreenOn()) { + dialog.setNegativeButton(R.string.notealert_enter, this); + } + dialog.show().setOnDismissListener(this); + } + + /** + * 在点击时调用该时间 + * @param dialog 弹窗接口 + * @param which 接口类型 + */ + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_NEGATIVE: + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, mNoteId); + startActivity(intent); + break; + default: + break; + } + } + + /** + * 在活动停止调用,停止播放闹钟声音 + * @param dialog + */ + public void onDismiss(DialogInterface dialog) { + stopAlarmSound(); + finish(); + } + + /** + * 停止播放闹钟声音 + */ + private void stopAlarmSound() { + if (mPlayer != null) { + mPlayer.stop(); + mPlayer.release(); + mPlayer = null; + } + } +} +/** + * 为了适应不同尺寸的屏幕,拓展了四倍大小的桌面挂件 + */ +public class NoteWidgetProvider_4x extends NoteWidgetProvider { + /** + * 在控件状态更新时调用该方法 + * @param context 上下文 + * @param appWidgetManager APP组件管理器实例 + * @param appWidgetIds 组件ID列表 + */ + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + super.update(context, appWidgetManager, appWidgetIds); + } + + /** + * 获取当前布局ID + * @return 布局ID + */ + protected int getLayoutId() { + return R.layout.widget_4x; + } + + /** + * 获取背景资源ID + * @param bgId 背景ID + * @return 背景资源ID + */ + @Override + protected int getBgResourceId(int bgId) { + return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); + } + + /** + * 获取控件类别 + * @return 控件类别 + */ + @Override + protected int getWidgetType() { + return Notes.TYPE_WIDGET_4X; + } +} + +/** + * 为了适应不同尺寸的屏幕,拓展了两倍大小的桌面挂件 + */ + +public class NoteWidgetProvider_2x extends NoteWidgetProvider { + /** + * 在组件状态更新时调用该方法 + * @param context 上下文环境 + * @param appWidgetManager 组件管理器实例 + * @param appWidgetIds 组件ID列表 + */ + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + super.update(context, appWidgetManager, appWidgetIds); + } + + /** + * 获取布局ID + * @return 布局ID + */ + @Override + protected int getLayoutId() { + return R.layout.widget_2x; + } + + /** + * 获取背景资源ID + * @param bgId 背景ID + * @return 背景资源ID + */ + @Override + protected int getBgResourceId(int bgId) { + return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); + } + + /** + * 获取组件类型 + * @return 组件类型 + */ + @Override + protected int getWidgetType() { + return Notes.TYPE_WIDGET_2X; + } +} +public abstract class NoteWidgetProvider extends AppWidgetProvider { + /** + * 声明数据投影模板 + */ + public static final String[] PROJECTION = new String[]{ + NoteColumns.ID, + NoteColumns.BG_COLOR_ID, + NoteColumns.SNIPPET + }; + /** + * 元素ID + */ + public static final int COLUMN_ID = 0; + /** + * 元素背景ID + */ + public static final int COLUMN_BG_COLOR_ID = 1; + /** + * 元素片段ID + */ + public static final int COLUMN_SNIPPET = 2; + + /** + * 标签名 + */ + private static final String TAG = "NoteWidgetProvider"; + + /** + * 删除的钩子方法 + * @param context 上下文 + * @param appWidgetIds 组件ID + */ + @Override + public void onDeleted(Context context, int[] appWidgetIds) { + ContentValues values = new ContentValues(); + values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + for (int i = 0; i < appWidgetIds.length; i++) { + context.getContentResolver().update(Notes.CONTENT_NOTE_URI, + values, + NoteColumns.WIDGET_ID + "=?", + new String[]{String.valueOf(appWidgetIds[i])}); + } + } + + /** + * 获取便签组件信息 + * @param context 上下文 + * @param widgetId 便签ID + * @return 指针 + */ + private Cursor getNoteWidgetInfo(Context context, int widgetId) { + return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, + PROJECTION, + NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", + new String[]{String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER)}, + null); + } + + /** + * 组件更新方法 + * @param context 上下文 + * @param appWidgetManager 组件管理器 + * @param appWidgetIds 组件ID列表 + */ + protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + update(context, appWidgetManager, appWidgetIds, false); + } + + /** + * 组件更新方法(重载) + * @param context + * @param appWidgetManager + * @param appWidgetIds + * @param privacyMode + */ + private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, + boolean privacyMode) { + for (int i = 0; i < appWidgetIds.length; i++) { + if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { + int bgId = ResourceParser.getDefaultBgId(context); + String snippet = ""; + Intent intent = new Intent(context, NoteEditActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); + + Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); + if (c != null && c.moveToFirst()) { + if (c.getCount() > 1) { + Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); + c.close(); + return; + } + snippet = c.getString(COLUMN_SNIPPET); + bgId = c.getInt(COLUMN_BG_COLOR_ID); + intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); + intent.setAction(Intent.ACTION_VIEW); + } else { + snippet = context.getResources().getString(R.string.widget_havenot_content); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + } + + if (c != null) { + c.close(); + } + + RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); + rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); + intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); + /** + * Generate the pending intent to start host for the widget + */ + PendingIntent pendingIntent = null; + if (privacyMode) { + rv.setTextViewText(R.id.widget_text, + context.getString(R.string.widget_under_visit_mode)); + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( + context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); + } else { + rv.setTextViewText(R.id.widget_text, snippet); + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } + + rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); + appWidgetManager.updateAppWidget(appWidgetIds[i], rv); + } + } + } + + /** + * 获取背景元素ID + * @param bgId 背景ID + * @return 背景资源ID + */ + protected abstract int getBgResourceId(int bgId); + + /** + * 获取布局ID + * @return 布局ID + */ + protected abstract int getLayoutId(); + + /** + * 获取组件类型 + * @return 组件类型ID + */ + protected abstract int getWidgetType(); +} +/** + * 闹钟应用活动类 + */ +public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + /** + * 便签ID + */ + private long mNoteId; + /** + * 片段 + */ + private String mSnippet; + /** + * 片段前缀最长长度 + */ + private static final int SNIPPET_PREW_MAX_LEN = 60; + MediaPlayer mPlayer; + + /** + * onCreate 生命周期方法 + * @param savedInstanceState 之前保存的实例状态 + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); + + final Window win = getWindow(); + win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + + if (!isScreenOn()) { + win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); + } + + //定义意图对象,调用系统应用 + Intent intent = getIntent(); + + try { + // 便签ID + mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + // 片段名称 + mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); + mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, + SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) + : mSnippet; + } catch (IllegalArgumentException e) { + // 异常处理 + e.printStackTrace(); + return; + } + + mPlayer = new MediaPlayer(); + if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { + // 显示弹出窗口 + showActionDialog(); + // 播放提示音 + playAlarmSound(); + } else { + finish(); + } + } + + /** + * 屏幕是否开着 + * @return 状态 + */ + private boolean isScreenOn() { + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + return pm.isScreenOn(); + } + + /** + * 播放闹钟提示音 + */ + private void playAlarmSound() { + Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + + int silentModeStreams = Settings.System.getInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + + if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { + mPlayer.setAudioStreamType(silentModeStreams); + } else { + mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + } + try { + mPlayer.setDataSource(this, url); + mPlayer.prepare(); + mPlayer.setLooping(true); + mPlayer.start(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalStateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /** + * 显示操作弹窗 + */ + private void showActionDialog() { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + // 设置标题 + dialog.setTitle(R.string.app_name); + // 设置消息 + dialog.setMessage(mSnippet); + // 设置按钮 + dialog.setPositiveButton(R.string.notealert_ok, this); + // 判断屏幕状态是否正常 + if (isScreenOn()) { + // 设置导航按钮 + dialog.setNegativeButton(R.string.notealert_enter, this); + } + // 展示弹窗并绑定监听器 + dialog.show().setOnDismissListener(this); + } + + /** + * 在点击时调用该时间 + * @param dialog 弹窗接口 + * @param which 接口类型 + */ + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_NEGATIVE: + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, mNoteId); + startActivity(intent); + break; + default: + break; + } + } + + /** + * 在活动停止调用,停止播放闹钟声音 + * @param dialog + */ + public void onDismiss(DialogInterface dialog) { + stopAlarmSound(); + finish(); + } + + /** + * 停止播放闹钟声音 + */ + private void stopAlarmSound() { + if (mPlayer != null) { + mPlayer.stop(); + mPlayer.release(); + mPlayer = null; + } + } +} +/** + * 便签编辑活动,继承自 Activity + * 实现了 OnClickListener,NoteSettingChangedListener,OnTextViewChangeListener 接口 + */ +public class NoteEditActivity extends Activity implements OnClickListener, + NoteSettingChangedListener, OnTextViewChangeListener { + // 定义私有视图持有者,保存视图元素基础信息 + private class HeadViewHolder { + public TextView tvModified; + + public ImageView ivAlertIcon; + + public TextView tvAlertDate; + + public ImageView ibSetBgColor; + } + // 背景选择器map + 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); + } + // 背景选择器Map + 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); + } + // 字体选择Map + 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); + } + // 字体选择器Map + 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; + // 面板编辑视图 + private View mNoteEditorPanel; + + private WorkingNote mWorkingNote; + // 共享引用 + private SharedPreferences mSharedPrefs; + // 字体尺寸 + private int mFontSizeId; + + private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; + + private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; + // 标签选择FLAG + public static final String TAG_CHECKED = String.valueOf('\u221A'); + // 标签取消选择FLAG + public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); + // 线性布局 + private LinearLayout mEditTextList; + // 用户查询指令 + private String mUserQuery; + private Pattern mPattern; + + /** + * 生命周期方法 在Activity OnCreate 阶段调用 + * @param savedInstanceState 先前保存的实例状态 + */ + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.setContentView(R.layout.note_edit); + + if (savedInstanceState == null && !initActivityState(getIntent())) { + finish(); + return; + } + initResources(); + } + + /** + * Current activity may be killed when the memory is low. Once it is killed, for another time + * user load this activity, we should restore the former state + */ + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + 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"); + } + } + + /** + * 初始化活动状态 + * @param intent 发起活动的意图对象 + */ + private boolean initActivityState(Intent intent) { + /** + * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, + * then jump to the NotesListActivity + */ + mWorkingNote = null; + if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { + long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); + mUserQuery = ""; + + /** + * Starting from the searched result + */ + if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { + noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); + mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); + } + + 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 { + 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())) { + // New note + 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)); + + // Parse call-record note + 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; + } + mWorkingNote.setOnSettingStatusChangedListener(this); + return true; + } + + /** + * 刷新便签屏幕状态 + */ + @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: Add the menu for setting alert. Currently disable it because the DateTimePicker + * is not ready + */ + 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); + } + ; + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + initActivityState(intent); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + /** + * For new note without note id, we should firstly save it to + * generate a id. If the editing note is not worth saving, there + * is no id which is equivalent to create new note + */ + 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 + && !inRangeOfView(mFontSizeSelector, ev)) { + mFontSizeSelector.setVisibility(View.GONE); + return true; + } + return super.dispatchTouchEvent(ev); + } + + private boolean inRangeOfView(View view, MotionEvent ev) { + int[] location = new int[2]; + view.getLocationOnScreen(location); + int x = location[0]; + int y = location[1]; + if (ev.getX() < x + || ev.getX() > (x + view.getWidth()) + || ev.getY() < y + || ev.getY() > (y + view.getHeight())) { + return false; + } + return true; + } + + private void initResources() { + mHeadViewPanel = findViewById(R.id.note_title); + mNoteHeaderHolder = new HeadViewHolder(); + mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); + mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); + mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); + mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); + mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); + mNoteEditor = (EditText) findViewById(R.id.note_edit_view); + mNoteEditorPanel = findViewById(R.id.sv_note_edit); + mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); + for (int id : sBgSelectorBtnsMap.keySet()) { + ImageView iv = (ImageView) findViewById(id); + iv.setOnClickListener(this); + } + + mFontSizeSelector = findViewById(R.id.font_size_selector); + for (int id : sFontSizeBtnsMap.keySet()) { + View view = findViewById(id); + view.setOnClickListener(this); + } + ; + mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); + /** + * HACKME: Fix bug of store the resource id in shared preference. + * The id may larger than the length of resources, in this case, + * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} + */ + if (mFontSizeId >= TextAppearanceResources.getResourcesSize()) { + mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; + } + mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); + } + + @Override + protected void onPause() { + super.onPause(); + if (saveNote()) { + Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); + } + clearSettingState(); + } + + /** + * 更新便签小配件的类型以及功能 + */ + private void updateWidget() { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { + intent.setClass(this, NoteWidgetProvider_2x.class); + } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { + intent.setClass(this, NoteWidgetProvider_4x.class); + } else { + Log.e(TAG, "Unspported widget type"); + return; + } + + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[]{ + mWorkingNote.getWidgetId() + }); + + sendBroadcast(intent); + setResult(RESULT_OK, intent); + } + // 点击时触发 + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.btn_set_bg_color) { + mNoteBgColorSelector.setVisibility(View.VISIBLE); + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + -View.VISIBLE); + } else if (sBgSelectorBtnsMap.containsKey(id)) { + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + View.GONE); + mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); + mNoteBgColorSelector.setVisibility(View.GONE); + } else if (sFontSizeBtnsMap.containsKey(id)) { + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); + mFontSizeId = sFontSizeBtnsMap.get(id); + mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + getWorkingText(); + switchToListMode(mWorkingNote.getContent()); + } else { + mNoteEditor.setTextAppearance(this, + TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); + } + mFontSizeSelector.setVisibility(View.GONE); + } + } + // 按下返回键 + @Override + public void onBackPressed() { + if (clearSettingState()) { + return; + } + + saveNote(); + super.onBackPressed(); + } + // 清理设置状态 + private boolean clearSettingState() { + if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { + mNoteBgColorSelector.setVisibility(View.GONE); + return true; + } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { + mFontSizeSelector.setVisibility(View.GONE); + return true; + } + return false; + } + // 背景颜色改变时触发 + public void onBackgroundColorChanged() { + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( + View.VISIBLE); + mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); + } + // 在准备渲染设置菜单时触发 + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + if (isFinishing()) { + return true; + } + clearSettingState(); + menu.clear(); + if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { + getMenuInflater().inflate(R.menu.call_note_edit, menu); + } else { + getMenuInflater().inflate(R.menu.note_edit, menu); + } + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); + } else { + menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); + } + if (mWorkingNote.hasClockAlert()) { + menu.findItem(R.id.menu_alert).setVisible(false); + } else { + menu.findItem(R.id.menu_delete_remind).setVisible(false); + } + return true; + } + // 在设置改变时触发 + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_new_note: + createNewNote(); + break; + case R.id.menu_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_note)); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + deleteCurrentNote(); + finish(); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + break; + case R.id.menu_font_size: + mFontSizeSelector.setVisibility(View.VISIBLE); + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); + break; + case R.id.menu_list_mode: + mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? + TextNote.MODE_CHECK_LIST : 0); + break; + case R.id.menu_share: + getWorkingText(); + sendTo(this, mWorkingNote.getContent()); + break; + case R.id.menu_send_to_desktop: + sendToDesktop(); + break; + case R.id.menu_alert: + setReminder(); + break; + case R.id.menu_delete_remind: + mWorkingNote.setAlertDate(0, false); + break; + default: + break; + } + return true; + } + // 设置提醒着 + private void setReminder() { + DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); + d.setOnDateTimeSetListener(new OnDateTimeSetListener() { + public void OnDateTimeSet(AlertDialog dialog, long date) { + mWorkingNote.setAlertDate(date, true); + } + }); + d.show(); + } + + /** + * Share note to apps that support {@link Intent#ACTION_SEND} action + * and {@text/plain} type + */ + private void sendTo(Context context, String info) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_TEXT, info); + intent.setType("text/plain"); + context.startActivity(intent); + } + // 创建新的便签 + private void createNewNote() { + // Firstly, save current editing notes + saveNote(); + + // For safety, start a new NoteEditActivity + finish(); + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); + startActivity(intent); + } + // 删除当前便签 + private void deleteCurrentNote() { + if (mWorkingNote.existInDatabase()) { + HashSet ids = new HashSet(); + long id = mWorkingNote.getNoteId(); + if (id != Notes.ID_ROOT_FOLDER) { + ids.add(id); + } else { + Log.d(TAG, "Wrong note id, should not happen"); + } + if (!isSyncMode()) { + if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { + Log.e(TAG, "Delete Note error"); + } + } else { + if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { + Log.e(TAG, "Move notes to trash folder error, should not happens"); + } + } + } + mWorkingNote.markDeleted(true); + } + // 是否为同步模式 + private boolean isSyncMode() { + return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + } + // 在闹钟改变时候触发 + public void onClockAlertChanged(long date, boolean set) { + /** + * User could set clock to an unsaved note, so before setting the + * alert clock, we should save the note first + */ + if (!mWorkingNote.existInDatabase()) { + saveNote(); + } + if (mWorkingNote.getNoteId() > 0) { + Intent intent = new Intent(this, AlarmReceiver.class); + intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); + AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); + showAlertHeader(); + if (!set) { + alarmManager.cancel(pendingIntent); + } else { + alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); + } + } else { + /** + * There is the condition that user has input nothing (the note is + * not worthy saving), we have no note id, remind the user that he + * should input something + */ + Log.e(TAG, "Clock alert setting error"); + showToast(R.string.error_note_empty_for_clock); + } + } + // 在组件改变时触发 + public void onWidgetChanged() { + updateWidget(); + } + // 在编辑文本删除时触发 + public void onEditTextDelete(int index, String text) { + int childCount = mEditTextList.getChildCount(); + if (childCount == 1) { + return; + } + + for (int i = index + 1; i < childCount; i++) { + ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) + .setIndex(i - 1); + } + + mEditTextList.removeViewAt(index); + NoteEditText edit = null; + if (index == 0) { + edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( + R.id.et_edit_text); + } else { + edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById( + R.id.et_edit_text); + } + int length = edit.length(); + edit.append(text); + edit.requestFocus(); + edit.setSelection(length); + } + // 在编辑文本按下Enter时触发 + public void onEditTextEnter(int index, String text) { + /** + * Should not happen, check for debug + */ + if (index > mEditTextList.getChildCount()) { + Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); + } + + View view = getListItem(text, index); + mEditTextList.addView(view, index); + NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + edit.requestFocus(); + edit.setSelection(0); + for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { + ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) + .setIndex(i); + } + } + // 切换到列表模式 + private void switchToListMode(String text) { + mEditTextList.removeAllViews(); + String[] items = text.split("\n"); + int index = 0; + for (String item : items) { + if (!TextUtils.isEmpty(item)) { + mEditTextList.addView(getListItem(item, index)); + index++; + } + } + mEditTextList.addView(getListItem("", index)); + mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); + + mNoteEditor.setVisibility(View.GONE); + mEditTextList.setVisibility(View.VISIBLE); + } + // 获取查询结果 + private Spannable getHighlightQueryResult(String fullText, String userQuery) { + SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); + if (!TextUtils.isEmpty(userQuery)) { + mPattern = Pattern.compile(userQuery); + Matcher m = mPattern.matcher(fullText); + int start = 0; + while (m.find(start)) { + spannable.setSpan( + new BackgroundColorSpan(this.getResources().getColor( + R.color.user_query_highlight)), m.start(), m.end(), + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + start = m.end(); + } + } + return spannable; + } + // 获取列表项目 + private View getListItem(String item, int index) { + View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); + final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); + CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); + cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + } else { + edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); + } + } + }); + + if (item.startsWith(TAG_CHECKED)) { + cb.setChecked(true); + edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); + item = item.substring(TAG_CHECKED.length(), item.length()).trim(); + } else if (item.startsWith(TAG_UNCHECKED)) { + cb.setChecked(false); + edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); + item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); + } + + edit.setOnTextViewChangeListener(this); + edit.setIndex(index); + edit.setText(getHighlightQueryResult(item, mUserQuery)); + return view; + } + // 在文本内容改变时触发 + public void onTextChange(int index, boolean hasText) { + if (index >= mEditTextList.getChildCount()) { + Log.e(TAG, "Wrong index, should not happen"); + return; + } + if (hasText) { + mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); + } else { + mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); + } + } + // 在选择列表模型改变时触发 + public void onCheckListModeChanged(int oldMode, int newMode) { + if (newMode == TextNote.MODE_CHECK_LIST) { + switchToListMode(mNoteEditor.getText().toString()); + } else { + if (!getWorkingText()) { + mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", + "")); + } + mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + mEditTextList.setVisibility(View.GONE); + mNoteEditor.setVisibility(View.VISIBLE); + } + } + // 获取工作区文本 + private boolean getWorkingText() { + boolean hasChecked = false; + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < mEditTextList.getChildCount(); i++) { + View view = mEditTextList.getChildAt(i); + NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); + if (!TextUtils.isEmpty(edit.getText())) { + if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { + sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); + hasChecked = true; + } else { + sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); + } + } + } + mWorkingNote.setWorkingText(sb.toString()); + } else { + mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); + } + return hasChecked; + } + // 保存便签 + private boolean saveNote() { + getWorkingText(); + boolean saved = mWorkingNote.saveNote(); + if (saved) { + /** + * There are two modes from List view to edit view, open one note, + * create/edit a node. Opening node requires to the original + * position in the list when back from edit view, while creating a + * new node requires to the top of the list. This code + * {@link #RESULT_OK} is used to identify the create/edit state + */ + setResult(RESULT_OK); + } + return saved; + } + // 发送到桌面 + private void sendToDesktop() { + /** + * Before send message to home, we should make sure that current + * editing note is exists in databases. So, for new note, firstly + * save it + */ + if (!mWorkingNote.existInDatabase()) { + saveNote(); + } + + if (mWorkingNote.getNoteId() > 0) { + Intent sender = new Intent(); + Intent shortcutIntent = new Intent(this, NoteEditActivity.class); + shortcutIntent.setAction(Intent.ACTION_VIEW); + shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); + sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, + makeShortcutIconTitle(mWorkingNote.getContent())); + sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, + Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); + sender.putExtra("duplicate", true); + sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + showToast(R.string.info_note_enter_desktop); + sendBroadcast(sender); + } else { + /** + * There is the condition that user has input nothing (the note is + * not worthy saving), we have no note id, remind the user that he + * should input something + */ + Log.e(TAG, "Send to desktop error"); + showToast(R.string.error_note_empty_for_send_to_desktop); + } + } + // 制作标题缩略图标 + private String makeShortcutIconTitle(String content) { + content = content.replace(TAG_CHECKED, ""); + content = content.replace(TAG_UNCHECKED, ""); + return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, + SHORTCUT_ICON_TITLE_MAX_LEN) : content; + } + // 显示吐司提示窗 + private void showToast(int resId) { + showToast(resId, Toast.LENGTH_SHORT); + } + // 显示吐司提示窗 + private void showToast(int resId, int duration) { + Toast.makeText(this, resId, duration).show(); + } +} + diff --git a/other/07_冉琦_实践总结报告.docx b/other/07_冉琦_实践总结报告.docx new file mode 100644 index 0000000000000000000000000000000000000000..c2a4a995dfe87f8664ffb018c204bb3895c14256 GIT binary patch literal 18084 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 literal 0 HcmV?d00001