From 41c3551a3e5435e051dae6940a58fe47a641fc49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B7=A6=E4=B8=B9=E5=A6=AE?= <210340044@cau.edu.cn> Date: Fri, 21 Apr 2023 11:30:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=90=8D=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc_/doc.txt | 0 .../FoldersListAdapter.doc | 88 ++ .../FoldersListAdapter.java | 88 ++ .../210340024_阮钰琳/NoteEditActivity.doc | 997 ++++++++++++++++++ .../210340024_阮钰琳/NoteEditActivity.java | 997 ++++++++++++++++++ .../210340024_阮钰琳/NoteEditText.doc | 252 +++++ .../210340024_阮钰琳/NoteEditText.java | 252 +++++ .../210340024_阮钰琳/NoteItemData.doc | 229 ++++ .../210340024_阮钰琳/NoteItemData.java | 229 ++++ .../210340024_阮钰琳/NotesListActivity.doc | 997 ++++++++++++++++++ .../NotesListActivity.java | 997 ++++++++++++++++++ .../210340024_阮钰琳/NotesListAdapter.doc | 237 +++++ .../210340024_阮钰琳/NotesListAdapter.java | 237 +++++ .../AlarmAlertActivity.java | 211 ++++ .../AlarmInitReceiver.java | 72 ++ .../210340041_郑培萱/AlarmReceiver.java | 35 + .../210340041_郑培萱/DateTimePicker.java | 508 +++++++++ .../DateTimePickerDialog.java | 102 ++ .../210340041_郑培萱/DropdownMenu.java | 68 ++ .../FoldersListAdapter.java | 88 ++ .../210340041_郑培萱/NotesListItem.java | 130 +++ .../NotesPreferenceActivity.java | 515 +++++++++ doc_/标注/210340044_左丹妮/Contact.docx | 82 ++ doc_/标注/210340044_左丹妮/Contact.java | 82 ++ .../Data_Notes_NoteColumns.jpg | Bin 0 -> 44288 bytes .../210340044_左丹妮/GTaskManager.docx | 922 ++++++++++++++++ .../210340044_左丹妮/GTaskManager.java | 922 ++++++++++++++++ .../210340044_左丹妮/GTaskSyncService.docx | 153 +++ .../210340044_左丹妮/GTaskSyncService.java | 153 +++ .../Gtask_remote_GTaskMnager.jpg | Bin 0 -> 109524 bytes ...Gtask_remote_GTaskMnager_initGTaskList.jpg | Bin 0 -> 106276 bytes ...task_remote_GTaskMnager_initGTaskList2.jpg | Bin 0 -> 168867 bytes ...task_remote_GTaskMnager_initGTaskList3.jpg | Bin 0 -> 142029 bytes .../Gtask_remote_GTaskMnager_syncContent.jpg | Bin 0 -> 49458 bytes ...remote_GTaskMnager_updateRemoteNodejpg.jpg | Bin 0 -> 38262 bytes .../Gtask_remote_GTaskSyncService_onBind.jpg | Bin 0 -> 17409 bytes doc_/标注/210340044_左丹妮/Notes.docx | 286 +++++ doc_/标注/210340044_左丹妮/Notes.java | 286 +++++ .../NotesDatabaseHelper.docx | 371 +++++++ .../NotesDatabaseHelper.java | 371 +++++++ .../210340044_左丹妮/NotesProvider.docx | 338 ++++++ .../210340044_左丹妮/NotesProvider.java | 338 ++++++ 42 files changed, 11633 insertions(+) create mode 100644 doc_/doc.txt create mode 100644 doc_/标注/210340024_阮钰琳/FoldersListAdapter.doc create mode 100644 doc_/标注/210340024_阮钰琳/FoldersListAdapter.java create mode 100644 doc_/标注/210340024_阮钰琳/NoteEditActivity.doc create mode 100644 doc_/标注/210340024_阮钰琳/NoteEditActivity.java create mode 100644 doc_/标注/210340024_阮钰琳/NoteEditText.doc create mode 100644 doc_/标注/210340024_阮钰琳/NoteEditText.java create mode 100644 doc_/标注/210340024_阮钰琳/NoteItemData.doc create mode 100644 doc_/标注/210340024_阮钰琳/NoteItemData.java create mode 100644 doc_/标注/210340024_阮钰琳/NotesListActivity.doc create mode 100644 doc_/标注/210340024_阮钰琳/NotesListActivity.java create mode 100644 doc_/标注/210340024_阮钰琳/NotesListAdapter.doc create mode 100644 doc_/标注/210340024_阮钰琳/NotesListAdapter.java create mode 100644 doc_/标注/210340041_郑培萱/AlarmAlertActivity.java create mode 100644 doc_/标注/210340041_郑培萱/AlarmInitReceiver.java create mode 100644 doc_/标注/210340041_郑培萱/AlarmReceiver.java create mode 100644 doc_/标注/210340041_郑培萱/DateTimePicker.java create mode 100644 doc_/标注/210340041_郑培萱/DateTimePickerDialog.java create mode 100644 doc_/标注/210340041_郑培萱/DropdownMenu.java create mode 100644 doc_/标注/210340041_郑培萱/FoldersListAdapter.java create mode 100644 doc_/标注/210340041_郑培萱/NotesListItem.java create mode 100644 doc_/标注/210340041_郑培萱/NotesPreferenceActivity.java create mode 100644 doc_/标注/210340044_左丹妮/Contact.docx create mode 100644 doc_/标注/210340044_左丹妮/Contact.java create mode 100644 doc_/标注/210340044_左丹妮/Data_Notes_NoteColumns.jpg create mode 100644 doc_/标注/210340044_左丹妮/GTaskManager.docx create mode 100644 doc_/标注/210340044_左丹妮/GTaskManager.java create mode 100644 doc_/标注/210340044_左丹妮/GTaskSyncService.docx create mode 100644 doc_/标注/210340044_左丹妮/GTaskSyncService.java create mode 100644 doc_/标注/210340044_左丹妮/Gtask_remote_GTaskMnager.jpg create mode 100644 doc_/标注/210340044_左丹妮/Gtask_remote_GTaskMnager_initGTaskList.jpg create mode 100644 doc_/标注/210340044_左丹妮/Gtask_remote_GTaskMnager_initGTaskList2.jpg create mode 100644 doc_/标注/210340044_左丹妮/Gtask_remote_GTaskMnager_initGTaskList3.jpg create mode 100644 doc_/标注/210340044_左丹妮/Gtask_remote_GTaskMnager_syncContent.jpg create mode 100644 doc_/标注/210340044_左丹妮/Gtask_remote_GTaskMnager_updateRemoteNodejpg.jpg create mode 100644 doc_/标注/210340044_左丹妮/Gtask_remote_GTaskSyncService_onBind.jpg create mode 100644 doc_/标注/210340044_左丹妮/Notes.docx create mode 100644 doc_/标注/210340044_左丹妮/Notes.java create mode 100644 doc_/标注/210340044_左丹妮/NotesDatabaseHelper.docx create mode 100644 doc_/标注/210340044_左丹妮/NotesDatabaseHelper.java create mode 100644 doc_/标注/210340044_左丹妮/NotesProvider.docx create mode 100644 doc_/标注/210340044_左丹妮/NotesProvider.java diff --git a/doc_/doc.txt b/doc_/doc.txt new file mode 100644 index 0000000..e69de29 diff --git a/doc_/标注/210340024_阮钰琳/FoldersListAdapter.doc b/doc_/标注/210340024_阮钰琳/FoldersListAdapter.doc new file mode 100644 index 0000000..5c8023d --- /dev/null +++ b/doc_/标注/210340024_阮钰琳/FoldersListAdapter.doc @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.database.Cursor; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + + +public class FoldersListAdapter extends CursorAdapter { + //CursorAdapter是Cursor和ListView的接口 + //FoldersListAdapter继承了CursorAdapter的类 + //主要作用是便签数据库和用户的交互 + //这里就是用folder(文件夹)的形式展现给用户 + public static final String [] PROJECTION = { + NoteColumns.ID, + NoteColumns.SNIPPET + };//调用数据库中便签的ID和片段 + + + 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 + }//数据库操作 + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + //ViewGroup是容器 + return new FolderListItem(context); + }//创建一个文件夹,对于各文件夹中子标签的初始化 + + @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); + }//根据数据库中标签的ID得到标签的各项内容 + + + 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); + } + } + +} diff --git a/doc_/标注/210340024_阮钰琳/FoldersListAdapter.java b/doc_/标注/210340024_阮钰琳/FoldersListAdapter.java new file mode 100644 index 0000000..5c8023d --- /dev/null +++ b/doc_/标注/210340024_阮钰琳/FoldersListAdapter.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.database.Cursor; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + + +public class FoldersListAdapter extends CursorAdapter { + //CursorAdapter是Cursor和ListView的接口 + //FoldersListAdapter继承了CursorAdapter的类 + //主要作用是便签数据库和用户的交互 + //这里就是用folder(文件夹)的形式展现给用户 + public static final String [] PROJECTION = { + NoteColumns.ID, + NoteColumns.SNIPPET + };//调用数据库中便签的ID和片段 + + + 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 + }//数据库操作 + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + //ViewGroup是容器 + return new FolderListItem(context); + }//创建一个文件夹,对于各文件夹中子标签的初始化 + + @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); + }//根据数据库中标签的ID得到标签的各项内容 + + + 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); + } + } + +} diff --git a/doc_/标注/210340024_阮钰琳/NoteEditActivity.doc b/doc_/标注/210340024_阮钰琳/NoteEditActivity.doc new file mode 100644 index 0000000..f248322 --- /dev/null +++ b/doc_/标注/210340024_阮钰琳/NoteEditActivity.doc @@ -0,0 +1,997 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.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; + + +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); + //put函数是将指定值和指定键相连 + } + + 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); + //put函数是将指定值和指定键相连 + } + + 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); + //put函数是将指定值和指定键相连 + } + + 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); + //put函数是将指定值和指定键相连 + } + + private static final String TAG = "NoteEditActivity"; + + private HeadViewHolder mNoteHeaderHolder; + + private View mHeadViewPanel; + //私有化一个界面操作mHeadViewPanel,对表头的操作 + + private View mNoteBgColorSelector; + //私有化一个界面操作mNoteBgColorSelector,对背景颜色的操作 + private View mFontSizeSelector; + //私有化一个界面操作mFontSizeSelector,对标签字体的操作 + private EditText mNoteEditor; + //声明编辑控件,对文本操作 + private View mNoteEditorPanel; + //私有化一个界面操作mNoteEditorPanel,文本编辑的控制板 + private WorkingNote mWorkingNote; + //对模板WorkingNote的初始化 + private SharedPreferences mSharedPrefs;//私有化SharedPreferences的数据存储方式 + //它的本质是基于XML文件存储key-value键值对数据 + 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; + + @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"); + }//为防止内存不足时程序的终止,在这里有一个保存现场的函数 + } + + 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 = ""; + //如果用户实例化标签时,系统并未给出标签ID + /** + * 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); + }//如果ID在数据库中未找到 + + if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { + Intent jump = new Intent(this, NotesListActivity.class); + startActivity(jump);//程序将跳转到上面声明的intent——jump + + showToast(R.string.error_note_not_exist); + finish(); + return false; + } //ID在数据库中找到 + else { + mWorkingNote = WorkingNote.load(this, noteId); + if (mWorkingNote == null) { + Log.e(TAG, "load note failed with note id" + noteId); + //打印出红色的错误信息 + finish(); + return false; + } + } + //setSoftInputMode——软键盘输入模式 + 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())) { + //intent.getAction() + // 大多用于broadcast发送广播时给机制(intent)设置一个action,就是一个字符串 + // 用户可以通过receive(接受)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)); +// intent.getInt(Long、String)Extra是对各变量的语法分析 + + // 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); + }//创建一个新的WorkingNote + + 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 + //MotionEvent是对屏幕触控的传递机制 + 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())) + //如果触控的位置超出了给定的范围,返回false + + { + 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); + } // MenuInflater是用来实例化Menu目录下的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()) {//根据菜单的id来编剧相关项目 + 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(); + } + }); + //添加“YES”按钮 + builder.setNegativeButton(android.R.string.cancel, null);//添加“NO”的按钮 + builder.show();//显示对话框 + break; + case R.id.menu_font_size: //字体大小的编辑 + mFontSizeSelector.setVisibility(View.VISIBLE);// 将字体选择器置为可见 + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);// 通过id找到相应的大小 + + 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());// 用sendto函数将运行文本发送到遍历的本文内 + 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链接选项 + intent.putExtra(Intent.EXTRA_TEXT, info); //将需要传递的便签信息放入text文件中 + intent.setType("text/plain");//编辑连接器的类型 + context.startActivity(intent);//在acti中进行链接 + } + /* + * 函数功能:创建一个新的便签 + * 函数实现:如下注释 + */ + 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);//该活动定义为创建或编辑 + //将运行便签的id添加到INTENT_EXTRA_FOLDER_ID标记中 + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); + startActivity(intent);//开始activity并链接 + } + /* + * 函数功能:删除当前便签 + * 函数实现:如下注释 + */ + private void deleteCurrentNote() { + if (mWorkingNote.existInDatabase()) {//假如当前运行的便签内存有数据 + HashSet ids = new HashSet(); + long id = mWorkingNote.getNoteId(); + if (id != Notes.ID_ROOT_FOLDER) { + ids.add(id);//如果不是头文件夹建立一个hash表把便签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);//将这些标签的删除标记置为true + } + /* + * 函数功能:判断是否为同步模式 + * 函数实现:直接看NotesPreferenceActivity中同步名称是否为空 + */ + 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())); + //若有有运行的便签就是建立一个链接器将标签id都存在uri中 + + 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); + } + } + /* + * 函数功能:Widget发生改变的所触发的事件 + */ + public void onWidgetChanged() { + updateWidget(); + }//更新Widget + /* + * 函数功能: 删除编辑文本框所触发的事件 + * 函数实现:如下注释 + */ + 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);//通过id把编辑框存在便签编辑框中 + + } + + 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); + }//通过id把编辑框存在空的NoteEditText中 + int length = edit.length(); + edit.append(text); + edit.requestFocus();//请求优先完成该此 编辑 + edit.setSelection(length);//定位到length位置处的条目 + } + /* + * 函数功能:进入编辑文本框所触发的事件 + * 函数实现:如下注释 + */ + 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);//建立一个状态机检查Pattern并进行匹配 + 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;//初始化check标记 + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {// 若模式为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;//扩展字符串为已打钩并把标记置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");//设置sneder的行为是发送 + + 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;//直接设置为content中的内容并返回,有勾选和未勾选2种 + } + /* + * 函数功能:显示提示的视图 + * 函数实现:根据下标显示对应的提示 + */ + private void showToast(int resId) { + showToast(resId, Toast.LENGTH_SHORT); + } + /* + * 函数功能:持续显示提示的视图 + * 函数实现:根据下标和持续的时间(duration)编辑提示视图并显示 + */ + private void showToast(int resId, int duration) { + Toast.makeText(this, resId, duration).show(); + } +} diff --git a/doc_/标注/210340024_阮钰琳/NoteEditActivity.java b/doc_/标注/210340024_阮钰琳/NoteEditActivity.java new file mode 100644 index 0000000..f248322 --- /dev/null +++ b/doc_/标注/210340024_阮钰琳/NoteEditActivity.java @@ -0,0 +1,997 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.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; + + +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); + //put函数是将指定值和指定键相连 + } + + 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); + //put函数是将指定值和指定键相连 + } + + 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); + //put函数是将指定值和指定键相连 + } + + 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); + //put函数是将指定值和指定键相连 + } + + private static final String TAG = "NoteEditActivity"; + + private HeadViewHolder mNoteHeaderHolder; + + private View mHeadViewPanel; + //私有化一个界面操作mHeadViewPanel,对表头的操作 + + private View mNoteBgColorSelector; + //私有化一个界面操作mNoteBgColorSelector,对背景颜色的操作 + private View mFontSizeSelector; + //私有化一个界面操作mFontSizeSelector,对标签字体的操作 + private EditText mNoteEditor; + //声明编辑控件,对文本操作 + private View mNoteEditorPanel; + //私有化一个界面操作mNoteEditorPanel,文本编辑的控制板 + private WorkingNote mWorkingNote; + //对模板WorkingNote的初始化 + private SharedPreferences mSharedPrefs;//私有化SharedPreferences的数据存储方式 + //它的本质是基于XML文件存储key-value键值对数据 + 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; + + @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"); + }//为防止内存不足时程序的终止,在这里有一个保存现场的函数 + } + + 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 = ""; + //如果用户实例化标签时,系统并未给出标签ID + /** + * 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); + }//如果ID在数据库中未找到 + + if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { + Intent jump = new Intent(this, NotesListActivity.class); + startActivity(jump);//程序将跳转到上面声明的intent——jump + + showToast(R.string.error_note_not_exist); + finish(); + return false; + } //ID在数据库中找到 + else { + mWorkingNote = WorkingNote.load(this, noteId); + if (mWorkingNote == null) { + Log.e(TAG, "load note failed with note id" + noteId); + //打印出红色的错误信息 + finish(); + return false; + } + } + //setSoftInputMode——软键盘输入模式 + 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())) { + //intent.getAction() + // 大多用于broadcast发送广播时给机制(intent)设置一个action,就是一个字符串 + // 用户可以通过receive(接受)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)); +// intent.getInt(Long、String)Extra是对各变量的语法分析 + + // 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); + }//创建一个新的WorkingNote + + 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 + //MotionEvent是对屏幕触控的传递机制 + 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())) + //如果触控的位置超出了给定的范围,返回false + + { + 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); + } // MenuInflater是用来实例化Menu目录下的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()) {//根据菜单的id来编剧相关项目 + 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(); + } + }); + //添加“YES”按钮 + builder.setNegativeButton(android.R.string.cancel, null);//添加“NO”的按钮 + builder.show();//显示对话框 + break; + case R.id.menu_font_size: //字体大小的编辑 + mFontSizeSelector.setVisibility(View.VISIBLE);// 将字体选择器置为可见 + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);// 通过id找到相应的大小 + + 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());// 用sendto函数将运行文本发送到遍历的本文内 + 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链接选项 + intent.putExtra(Intent.EXTRA_TEXT, info); //将需要传递的便签信息放入text文件中 + intent.setType("text/plain");//编辑连接器的类型 + context.startActivity(intent);//在acti中进行链接 + } + /* + * 函数功能:创建一个新的便签 + * 函数实现:如下注释 + */ + 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);//该活动定义为创建或编辑 + //将运行便签的id添加到INTENT_EXTRA_FOLDER_ID标记中 + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); + startActivity(intent);//开始activity并链接 + } + /* + * 函数功能:删除当前便签 + * 函数实现:如下注释 + */ + private void deleteCurrentNote() { + if (mWorkingNote.existInDatabase()) {//假如当前运行的便签内存有数据 + HashSet ids = new HashSet(); + long id = mWorkingNote.getNoteId(); + if (id != Notes.ID_ROOT_FOLDER) { + ids.add(id);//如果不是头文件夹建立一个hash表把便签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);//将这些标签的删除标记置为true + } + /* + * 函数功能:判断是否为同步模式 + * 函数实现:直接看NotesPreferenceActivity中同步名称是否为空 + */ + 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())); + //若有有运行的便签就是建立一个链接器将标签id都存在uri中 + + 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); + } + } + /* + * 函数功能:Widget发生改变的所触发的事件 + */ + public void onWidgetChanged() { + updateWidget(); + }//更新Widget + /* + * 函数功能: 删除编辑文本框所触发的事件 + * 函数实现:如下注释 + */ + 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);//通过id把编辑框存在便签编辑框中 + + } + + 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); + }//通过id把编辑框存在空的NoteEditText中 + int length = edit.length(); + edit.append(text); + edit.requestFocus();//请求优先完成该此 编辑 + edit.setSelection(length);//定位到length位置处的条目 + } + /* + * 函数功能:进入编辑文本框所触发的事件 + * 函数实现:如下注释 + */ + 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);//建立一个状态机检查Pattern并进行匹配 + 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;//初始化check标记 + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {// 若模式为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;//扩展字符串为已打钩并把标记置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");//设置sneder的行为是发送 + + 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;//直接设置为content中的内容并返回,有勾选和未勾选2种 + } + /* + * 函数功能:显示提示的视图 + * 函数实现:根据下标显示对应的提示 + */ + private void showToast(int resId) { + showToast(resId, Toast.LENGTH_SHORT); + } + /* + * 函数功能:持续显示提示的视图 + * 函数实现:根据下标和持续的时间(duration)编辑提示视图并显示 + */ + private void showToast(int resId, int duration) { + Toast.makeText(this, resId, duration).show(); + } +} diff --git a/doc_/标注/210340024_阮钰琳/NoteEditText.doc b/doc_/标注/210340024_阮钰琳/NoteEditText.doc new file mode 100644 index 0000000..405a861 --- /dev/null +++ b/doc_/标注/210340024_阮钰琳/NoteEditText.doc @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.graphics.Rect; +import android.text.Layout; +import android.text.Selection; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.URLSpan; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ContextMenu; +import android.view.KeyEvent; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.MotionEvent; +import android.widget.EditText; + +import net.micode.notes.R; + +import java.util.HashMap; +import java.util.Map; +//继承edittext,设置便签设置文本框 +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:" ; + ///建立一个字符和整数的hash表,用于链接电话,网站,还有邮箱 + 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; + //根据context设置文本 + public NoteEditText(Context context) { + super(context, null);//用super引用父类变量 + mIndex = 0; + } + //设置当前光标 + public void setIndex(int index) { + mIndex = index; + } + //初始化文本修改标记 + public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { + mOnTextViewChangeListener = listener; + } + //AttributeSet 百度了一下是自定义空控件属性,用于维护便签动态变化的属性 + //初始化便签 + public NoteEditText(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.editTextStyle); + } + // 根据defstyle自动初始化 + public NoteEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + // TODO Auto-generated constructor stub + } + + @Override + //view里的函数,处理手机屏幕的所有事件 + /*参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息, + 例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。*/ + + 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根据x,y的新值设置新的位置 + Layout layout = getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + //更新光标新的位置 + Selection.setSelection(getText(), off); + break; + } + + return super.onTouchEvent(event); + } + + @Override + /* + * 函数功能:处理用户按下一个键盘按键时会触发 的事件 + * 实现过程:如下注释 + */ + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) {//根据按键的 Unicode 编码值来处理 + case KeyEvent.KEYCODE_ENTER://“进入”按键 + if (mOnTextViewChangeListener != null) { + return false; + } + break; + case KeyEvent.KEYCODE_DEL://“删除”按键 + mSelectionStartBeforeDelete = getSelectionStart(); + break; + default: + break; + }//继续执行父类的其他点击事件 + return super.onKeyDown(keyCode, event); + } + + @Override/* + * 函数功能:处理用户松开一个键盘按键时会触发 的事件 + * 实现方式:如下注释 + */ + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch(keyCode) {//根据按键的 Unicode 编码值来处理,有删除和进入2种操作 + case KeyEvent.KEYCODE_DEL: + if (mOnTextViewChangeListener != null) {//若是被修改过 + if (0 == mSelectionStartBeforeDelete && mIndex != 0) { + //若之前有被修改并且文档不为空 + mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); + //利用上文OnTextViewChangeListener对KEYCODE_DEL按键情况的删除函数 + return true; + } + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + }//其他情况报错,文档的改动监听器并没有建立 + + break; + case KeyEvent.KEYCODE_ENTER://同上也是分为监听器是否建立2种情况 + if (mOnTextViewChangeListener != null) { + int selectionStart = getSelectionStart();//获取当前位置 + String text = getText().subSequence(selectionStart, length()).toString();//获取当前文本 + + setText(getText().subSequence(0, selectionStart));//根据获取的文本设置当前文本 + + mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);//当{@link KeyEvent#KEYCODE_ENTER}添加新文本 + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } //其他情况报错,文档的改动监听器并没有建立 + + break; + default: + break; + } //继续执行父类的其他按键弹起的事件 + return super.onKeyUp(keyCode, event); + } + + @Override /* + * 函数功能:当焦点发生变化时,会自动调用该方法来处理焦点改变的事件 + * 实现方式:如下注释 + * 参数:focused表示触发该事件的View是否获得了焦点,当该控件获得焦点时,Focused等于true,否则等于false。 + direction表示焦点移动的方向,用数值表示 + Rect:表示在触发事件的View的坐标系中,前一个获得焦点的矩形区域,即表示焦点是从哪里来的。如果不可用则为null + */ + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + if (mOnTextViewChangeListener != null) {//若监听器已经建立 + + if (!focused && TextUtils.isEmpty(getText())) {//获取到焦点并且文本不为空 + mOnTextViewChangeListener.onTextChange(mIndex, false); //mOnTextViewChangeListener子函数,置false隐藏事件选项 + } else { + mOnTextViewChangeListener.onTextChange(mIndex, true);//mOnTextViewChangeListener子函数,置true显示事件选项 + + } + }//继续执行父类的其他焦点变化的事件 + super.onFocusChanged(focused, direction, previouslyFocusedRect); + } + + @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);//设置url的信息的范围值 + + if (urls.length == 1) { + int defaultResId = 0; + for(String schema: sSchemaActionResMap.keySet()) {//获取计划表中所有的key值 + if(urls[0].getURL().indexOf(schema) >= 0) {//若url可以添加则在添加后将defaultResId置为key所映射的值 + defaultResId = sSchemaActionResMap.get(schema); + break; + } + } + + if (defaultResId == 0) {//defaultResId == 0则说明url并没有添加任何东西,所以置为连接其他SchemaActionResMap的值 + + 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); + } +} diff --git a/doc_/标注/210340024_阮钰琳/NoteEditText.java b/doc_/标注/210340024_阮钰琳/NoteEditText.java new file mode 100644 index 0000000..405a861 --- /dev/null +++ b/doc_/标注/210340024_阮钰琳/NoteEditText.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.graphics.Rect; +import android.text.Layout; +import android.text.Selection; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.URLSpan; +import android.util.AttributeSet; +import android.util.Log; +import android.view.ContextMenu; +import android.view.KeyEvent; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.MotionEvent; +import android.widget.EditText; + +import net.micode.notes.R; + +import java.util.HashMap; +import java.util.Map; +//继承edittext,设置便签设置文本框 +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:" ; + ///建立一个字符和整数的hash表,用于链接电话,网站,还有邮箱 + 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; + //根据context设置文本 + public NoteEditText(Context context) { + super(context, null);//用super引用父类变量 + mIndex = 0; + } + //设置当前光标 + public void setIndex(int index) { + mIndex = index; + } + //初始化文本修改标记 + public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { + mOnTextViewChangeListener = listener; + } + //AttributeSet 百度了一下是自定义空控件属性,用于维护便签动态变化的属性 + //初始化便签 + public NoteEditText(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.editTextStyle); + } + // 根据defstyle自动初始化 + public NoteEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + // TODO Auto-generated constructor stub + } + + @Override + //view里的函数,处理手机屏幕的所有事件 + /*参数event为手机屏幕触摸事件封装类的对象,其中封装了该事件的所有信息, + 例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。*/ + + 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根据x,y的新值设置新的位置 + Layout layout = getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + //更新光标新的位置 + Selection.setSelection(getText(), off); + break; + } + + return super.onTouchEvent(event); + } + + @Override + /* + * 函数功能:处理用户按下一个键盘按键时会触发 的事件 + * 实现过程:如下注释 + */ + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) {//根据按键的 Unicode 编码值来处理 + case KeyEvent.KEYCODE_ENTER://“进入”按键 + if (mOnTextViewChangeListener != null) { + return false; + } + break; + case KeyEvent.KEYCODE_DEL://“删除”按键 + mSelectionStartBeforeDelete = getSelectionStart(); + break; + default: + break; + }//继续执行父类的其他点击事件 + return super.onKeyDown(keyCode, event); + } + + @Override/* + * 函数功能:处理用户松开一个键盘按键时会触发 的事件 + * 实现方式:如下注释 + */ + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch(keyCode) {//根据按键的 Unicode 编码值来处理,有删除和进入2种操作 + case KeyEvent.KEYCODE_DEL: + if (mOnTextViewChangeListener != null) {//若是被修改过 + if (0 == mSelectionStartBeforeDelete && mIndex != 0) { + //若之前有被修改并且文档不为空 + mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); + //利用上文OnTextViewChangeListener对KEYCODE_DEL按键情况的删除函数 + return true; + } + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + }//其他情况报错,文档的改动监听器并没有建立 + + break; + case KeyEvent.KEYCODE_ENTER://同上也是分为监听器是否建立2种情况 + if (mOnTextViewChangeListener != null) { + int selectionStart = getSelectionStart();//获取当前位置 + String text = getText().subSequence(selectionStart, length()).toString();//获取当前文本 + + setText(getText().subSequence(0, selectionStart));//根据获取的文本设置当前文本 + + mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);//当{@link KeyEvent#KEYCODE_ENTER}添加新文本 + } else { + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } //其他情况报错,文档的改动监听器并没有建立 + + break; + default: + break; + } //继续执行父类的其他按键弹起的事件 + return super.onKeyUp(keyCode, event); + } + + @Override /* + * 函数功能:当焦点发生变化时,会自动调用该方法来处理焦点改变的事件 + * 实现方式:如下注释 + * 参数:focused表示触发该事件的View是否获得了焦点,当该控件获得焦点时,Focused等于true,否则等于false。 + direction表示焦点移动的方向,用数值表示 + Rect:表示在触发事件的View的坐标系中,前一个获得焦点的矩形区域,即表示焦点是从哪里来的。如果不可用则为null + */ + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + if (mOnTextViewChangeListener != null) {//若监听器已经建立 + + if (!focused && TextUtils.isEmpty(getText())) {//获取到焦点并且文本不为空 + mOnTextViewChangeListener.onTextChange(mIndex, false); //mOnTextViewChangeListener子函数,置false隐藏事件选项 + } else { + mOnTextViewChangeListener.onTextChange(mIndex, true);//mOnTextViewChangeListener子函数,置true显示事件选项 + + } + }//继续执行父类的其他焦点变化的事件 + super.onFocusChanged(focused, direction, previouslyFocusedRect); + } + + @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);//设置url的信息的范围值 + + if (urls.length == 1) { + int defaultResId = 0; + for(String schema: sSchemaActionResMap.keySet()) {//获取计划表中所有的key值 + if(urls[0].getURL().indexOf(schema) >= 0) {//若url可以添加则在添加后将defaultResId置为key所映射的值 + defaultResId = sSchemaActionResMap.get(schema); + break; + } + } + + if (defaultResId == 0) {//defaultResId == 0则说明url并没有添加任何东西,所以置为连接其他SchemaActionResMap的值 + + 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); + } +} diff --git a/doc_/标注/210340024_阮钰琳/NoteItemData.doc b/doc_/标注/210340024_阮钰琳/NoteItemData.doc new file mode 100644 index 0000000..3375bf4 --- /dev/null +++ b/doc_/标注/210340024_阮钰琳/NoteItemData.doc @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.database.Cursor; +import android.text.TextUtils; + +import net.micode.notes.data.Contact; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.tool.DataUtils; + + +public class NoteItemData { + 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, + }; + + private static final int ID_COLUMN = 0; + private static final int ALERTED_DATE_COLUMN = 1; + private static final int BG_COLOR_ID_COLUMN = 2; + private static final int CREATED_DATE_COLUMN = 3; + private static final int HAS_ATTACHMENT_COLUMN = 4; + private static final int MODIFIED_DATE_COLUMN = 5; + private static final int NOTES_COUNT_COLUMN = 6; + private static final int PARENT_ID_COLUMN = 7; + private static final int SNIPPET_COLUMN = 8; + private static final int TYPE_COLUMN = 9; + private static final int WIDGET_ID_COLUMN = 10; + private static final int WIDGET_TYPE_COLUMN = 11; + + private long mId; + private long mAlertDate; + private int mBgColorId; + private long mCreatedDate; + private boolean mHasAttachment; + private long mModifiedDate; + private int mNotesCount; + private long mParentId; + private String mSnippet; + private int mType; + private int mWidgetId; + private int mWidgetType; + private String mName; + private String mPhoneNumber; + + private boolean mIsLastItem; + private boolean mIsFirstItem; + private boolean mIsOnlyOneItem; + private boolean mIsOneNoteFollowingFolder; + private boolean mIsMultiNotesFollowingFolder; + //初始化NoteItemData,主要利用光标cursor获取的东西 + public NoteItemData(Context context, Cursor cursor) { + mId = cursor.getLong(ID_COLUMN); + mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); + mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); + mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); + mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false; + mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); + mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); + mParentId = cursor.getLong(PARENT_ID_COLUMN); + mSnippet = cursor.getString(SNIPPET_COLUMN); + mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( + NoteEditActivity.TAG_UNCHECKED, ""); + mType = cursor.getInt(TYPE_COLUMN); + mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); + mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); +//初始化电话号码的信息 + mPhoneNumber = ""; + if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { + mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); + if (!TextUtils.isEmpty(mPhoneNumber)) {//mphonenumber里有符合字符串,则用cont + mName = Contact.getContact(context, mPhoneNumber); + if (mName == null) { + mName = mPhoneNumber; + } + } + } + + if (mName == null) { + mName = ""; + } + checkPostion(cursor); + } + ///根据鼠标的位置设置标记,和位置 + private void checkPostion(Cursor cursor) { + mIsLastItem = cursor.isLast() ? true : false; + mIsFirstItem = cursor.isFirst() ? true : false; + mIsOnlyOneItem = (cursor.getCount() == 1); + //初始化“多重子文件”“单一子文件”2个标记 + mIsMultiNotesFollowingFolder = false; + mIsOneNoteFollowingFolder = false; + + if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {//若是note格式并且不是第一个元素 + + int position = cursor.getPosition(); + if (cursor.moveToPrevious()) {//获取光标位置后看上一行 + + if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER + || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { + if (cursor.getCount() > (position + 1)) { + mIsMultiNotesFollowingFolder = true;//若是数据行数大于但前位置+1则设置成正确 + + } else { + mIsOneNoteFollowingFolder = true;//否则单一文件夹标记为true + + } + } + if (!cursor.moveToNext()) {//若不能再往下走则报错 + throw new IllegalStateException("cursor move to previous but can't move back"); + } + } + } + } + + public boolean isOneFollowingFolder() { + return mIsOneNoteFollowingFolder; + } + + public boolean isMultiFollowingFolder() { + return mIsMultiNotesFollowingFolder; + } + + public boolean isLast() { + return mIsLastItem; + } + + public String getCallName() { + return mName; + } + + public boolean isFirst() { + return mIsFirstItem; + } + + public boolean isSingle() { + return mIsOnlyOneItem; + } + + public long getId() { + return mId; + } + + public long getAlertDate() { + return mAlertDate; + } + + public long getCreatedDate() { + return mCreatedDate; + } + + public boolean hasAttachment() { + return mHasAttachment; + } + + public long getModifiedDate() { + return mModifiedDate; + } + + public int getBgColorId() { + return mBgColorId; + } + + public long getParentId() { + return mParentId; + } + + public int getNotesCount() { + return mNotesCount; + } + + public long getFolderId () { + return mParentId; + } + + public int getType() { + return mType; + } + + public int getWidgetType() { + return mWidgetType; + } + + public int getWidgetId() { + return mWidgetId; + } + + public String getSnippet() { + return mSnippet; + } + + public boolean hasAlert() { + return (mAlertDate > 0); + } + //若数据父id为保存至文件夹模式的id且满足电话号码单元不为空,则isCallRecord为true + public boolean isCallRecord() { + return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); + } + + public static int getNoteType(Cursor cursor) { + return cursor.getInt(TYPE_COLUMN); + } +} diff --git a/doc_/标注/210340024_阮钰琳/NoteItemData.java b/doc_/标注/210340024_阮钰琳/NoteItemData.java new file mode 100644 index 0000000..3375bf4 --- /dev/null +++ b/doc_/标注/210340024_阮钰琳/NoteItemData.java @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.database.Cursor; +import android.text.TextUtils; + +import net.micode.notes.data.Contact; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.tool.DataUtils; + + +public class NoteItemData { + 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, + }; + + private static final int ID_COLUMN = 0; + private static final int ALERTED_DATE_COLUMN = 1; + private static final int BG_COLOR_ID_COLUMN = 2; + private static final int CREATED_DATE_COLUMN = 3; + private static final int HAS_ATTACHMENT_COLUMN = 4; + private static final int MODIFIED_DATE_COLUMN = 5; + private static final int NOTES_COUNT_COLUMN = 6; + private static final int PARENT_ID_COLUMN = 7; + private static final int SNIPPET_COLUMN = 8; + private static final int TYPE_COLUMN = 9; + private static final int WIDGET_ID_COLUMN = 10; + private static final int WIDGET_TYPE_COLUMN = 11; + + private long mId; + private long mAlertDate; + private int mBgColorId; + private long mCreatedDate; + private boolean mHasAttachment; + private long mModifiedDate; + private int mNotesCount; + private long mParentId; + private String mSnippet; + private int mType; + private int mWidgetId; + private int mWidgetType; + private String mName; + private String mPhoneNumber; + + private boolean mIsLastItem; + private boolean mIsFirstItem; + private boolean mIsOnlyOneItem; + private boolean mIsOneNoteFollowingFolder; + private boolean mIsMultiNotesFollowingFolder; + //初始化NoteItemData,主要利用光标cursor获取的东西 + public NoteItemData(Context context, Cursor cursor) { + mId = cursor.getLong(ID_COLUMN); + mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); + mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); + mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); + mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false; + mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); + mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); + mParentId = cursor.getLong(PARENT_ID_COLUMN); + mSnippet = cursor.getString(SNIPPET_COLUMN); + mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( + NoteEditActivity.TAG_UNCHECKED, ""); + mType = cursor.getInt(TYPE_COLUMN); + mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); + mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); +//初始化电话号码的信息 + mPhoneNumber = ""; + if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { + mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); + if (!TextUtils.isEmpty(mPhoneNumber)) {//mphonenumber里有符合字符串,则用cont + mName = Contact.getContact(context, mPhoneNumber); + if (mName == null) { + mName = mPhoneNumber; + } + } + } + + if (mName == null) { + mName = ""; + } + checkPostion(cursor); + } + ///根据鼠标的位置设置标记,和位置 + private void checkPostion(Cursor cursor) { + mIsLastItem = cursor.isLast() ? true : false; + mIsFirstItem = cursor.isFirst() ? true : false; + mIsOnlyOneItem = (cursor.getCount() == 1); + //初始化“多重子文件”“单一子文件”2个标记 + mIsMultiNotesFollowingFolder = false; + mIsOneNoteFollowingFolder = false; + + if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {//若是note格式并且不是第一个元素 + + int position = cursor.getPosition(); + if (cursor.moveToPrevious()) {//获取光标位置后看上一行 + + if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER + || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { + if (cursor.getCount() > (position + 1)) { + mIsMultiNotesFollowingFolder = true;//若是数据行数大于但前位置+1则设置成正确 + + } else { + mIsOneNoteFollowingFolder = true;//否则单一文件夹标记为true + + } + } + if (!cursor.moveToNext()) {//若不能再往下走则报错 + throw new IllegalStateException("cursor move to previous but can't move back"); + } + } + } + } + + public boolean isOneFollowingFolder() { + return mIsOneNoteFollowingFolder; + } + + public boolean isMultiFollowingFolder() { + return mIsMultiNotesFollowingFolder; + } + + public boolean isLast() { + return mIsLastItem; + } + + public String getCallName() { + return mName; + } + + public boolean isFirst() { + return mIsFirstItem; + } + + public boolean isSingle() { + return mIsOnlyOneItem; + } + + public long getId() { + return mId; + } + + public long getAlertDate() { + return mAlertDate; + } + + public long getCreatedDate() { + return mCreatedDate; + } + + public boolean hasAttachment() { + return mHasAttachment; + } + + public long getModifiedDate() { + return mModifiedDate; + } + + public int getBgColorId() { + return mBgColorId; + } + + public long getParentId() { + return mParentId; + } + + public int getNotesCount() { + return mNotesCount; + } + + public long getFolderId () { + return mParentId; + } + + public int getType() { + return mType; + } + + public int getWidgetType() { + return mWidgetType; + } + + public int getWidgetId() { + return mWidgetId; + } + + public String getSnippet() { + return mSnippet; + } + + public boolean hasAlert() { + return (mAlertDate > 0); + } + //若数据父id为保存至文件夹模式的id且满足电话号码单元不为空,则isCallRecord为true + public boolean isCallRecord() { + return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); + } + + public static int getNoteType(Cursor cursor) { + return cursor.getInt(TYPE_COLUMN); + } +} diff --git a/doc_/标注/210340024_阮钰琳/NotesListActivity.doc b/doc_/标注/210340024_阮钰琳/NotesListActivity.doc new file mode 100644 index 0000000..73b5633 --- /dev/null +++ b/doc_/标注/210340024_阮钰琳/NotesListActivity.doc @@ -0,0 +1,997 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.appwidget.AppWidgetManager; +import android.content.AsyncQueryHandler; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.ActionMode; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Display; +import android.view.HapticFeedbackConstants; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnCreateContextMenuListener; +import android.view.View.OnTouchListener; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.PopupMenu; +import android.widget.TextView; +import android.widget.Toast; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.remote.GTaskSyncService; +import net.micode.notes.model.WorkingNote; +import net.micode.notes.tool.BackupUtils; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; +import net.micode.notes.widget.NoteWidgetProvider_2x; +import net.micode.notes.widget.NoteWidgetProvider_4x; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashSet; + +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; + + private int 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; + + 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; + + @Override + + protected void onCreate(Bundle savedInstanceState) { + // final类不能被继承,没有子类,final类中的方法默认是final的。 + //final方法不能被子类的方法覆盖,但可以被继承。 + //final成员变量表示常量,只能被赋值一次,赋值后值不再改变。 + //final不能用于修饰构造方法。 + + super.onCreate(savedInstanceState); + setContentView(R.layout.note_list); + initResources(); + + /** + * Insert an introduction when user firstly use this application + */ + setAppInfoFromRawRes(); + } + + @Override// 返回一些子模块完成的数据交给主Activity处理 + 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); + // 调用 Activity 的onActivityResult() + } + } + + private void setAppInfoFromRawRes() { + // Android平台给我们提供了一个SharedPreferences类,它是一个轻量级的存储类,特别适合用于保存软件配置参数。 + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { + StringBuilder sb = new StringBuilder(); + InputStream in = null; + try {// 把资源文件放到应用程序的/raw/raw下,那么就可以在应用中使用getResources获取资源后, + // 以openRawResource方法(不带后缀的资源文件名)打开这个文件。 + in = getResources().openRawResource(R.raw.introduction); + if (in != null) { + InputStreamReader isr = new InputStreamReader(in); + BufferedReader br = new BufferedReader(isr); + char [] buf = new char[1024]; + int len = 0; + while ((len = br.read(buf)) > 0) { + sb.append(buf, 0, len); + } + } else { + Log.e(TAG, "Read introduction file error"); + return; + } + } catch (IOException e) { + e.printStackTrace(); + return; + } finally { + if(in != null) { + try { + in.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + // 创建空的WorkingNote + WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, + AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, + ResourceParser.RED); + note.setWorkingText(sb.toString()); + if (note.saveNote()) {// 更新保存note的信息 + sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); + } else { + Log.e(TAG, "Save introduction note error"); + return; + } + } + } + + @Override + protected void onStart() { + super.onStart(); + startAsyncNotesListQuery(); + } + // 初始化资源 + private void initResources() { + mContentResolver = this.getContentResolver();// 获取应用程序的数据,得到类似数据表的东西 + mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + // findViewById 是安卓编程的定位函数,主要是引用.R文件里的引用名 + mNotesListView = (ListView) findViewById(R.id.notes_list);// 绑定XML中的ListView,作为Item的容器 + 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);// 在activity中要获取该按钮 + mAddNewNote.setOnClickListener(this); + mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); + mDispatch = false; + mDispatchY = 0; + mOriginY = 0; + mTitleBar = (TextView) findViewById(R.id.tv_title_bar); + mState = ListEditState.NOTE_LIST; + mModeCallBack = new ModeCallback(); + } + // 继承自ListView.MultiChoiceModeListener 和 OnMenuItemClickListener + 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(); + // Update dropdown menu + 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); + } + } + } + + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // TODO Auto-generated method stub + return false; + } + + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + // TODO Auto-generated method stub + return false; + } + + public void onDestroyActionMode(ActionMode mode) { + mNotesListAdapter.setChoiceMode(false); + mNotesListView.setLongClickable(true); + mAddNewNote.setVisibility(View.VISIBLE); + } + + public void finishActionMode() { + mActionMode.finish(); + } + + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, + boolean checked) { + mNotesListAdapter.setCheckedItem(position, checked); + updateMenu(); + } + + 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(); + /** + * Minus TitleBar's height + */ + if (mState == ListEditState.SUB_FOLDER) { + eventY -= mTitleBar.getHeight(); + start -= mTitleBar.getHeight(); + } + /** + * HACKME:When click the transparent part of "New Note" button, dispatch + * the event to the list view behind this button. The transparent part of + * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel) + * and the line top of the button. The coordinate based on left of the "New + * Note" button. The 94 represents maximum height of the transparent part. + * Notice that, if the background of the button changes, the formula should + * also change. This is very bad, just for the UI designer's strong requirement. + */ + 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() { + String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION + : NORMAL_SELECTION; + mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, + Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] { + String.valueOf(mCurrentFolderId) + }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); + } + + private final class BackgroundQueryHandler extends AsyncQueryHandler { + public BackgroundQueryHandler(ContentResolver contentResolver) { + super(contentResolver); + } + + @Override + protected void onQueryComplete(int token, Object cookie, Cursor cursor) { + 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: + return; + } + } + } + + private void showFolderListMenu(Cursor cursor) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(R.string.menu_title_select_folder); + 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.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 intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); + this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); + } + + private void batchDelete() { + new AsyncTask>() { + protected HashSet doInBackground(Void... unused) { + HashSet widgets = mNotesListAdapter.getSelectedWidget(); + if (!isSyncMode()) { + // if not synced, delete notes directly + if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter + .getSelectedItemIds())) { + } else { + Log.e(TAG, "Delete notes error, should not happens"); + } + } else { + // in sync mode, we'll move the deleted note into the trash + // folder + 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 widgets) { + if (widgets != null) { + for (AppWidgetAttribute widget : widgets) { + if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { + updateWidget(widget.widgetId, widget.widgetType); + } + } + } + mModeCallBack.finishActionMode(); + } + }.execute(); + } + + private void deleteFolder(long folderId) { + if (folderId == Notes.ID_ROOT_FOLDER) { + Log.e(TAG, "Wrong folder id, should not happen " + folderId); + return; + } + + HashSet ids = new HashSet(); + ids.add(folderId); + HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver, + folderId); + if (!isSyncMode()) { + // if not synced, delete folder directly + DataUtils.batchDeleteNotes(mContentResolver, ids); + } else { + // in sync mode, we'll move the deleted folder into the trash folder + DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER); + } + if (widgets != null) { + for (AppWidgetAttribute widget : widgets) { + if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { + updateWidget(widget.widgetId, widget.widgetType); + } + } + } + } + + private void openNode(NoteItemData data) { + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, data.getId()); + this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); + } + + private void openFolder(NoteItemData data) { + 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; + } + 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) { + 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); + } + /** + * When the name edit text is null, disable the positive button + */ + etName.addTextChangedListener(new TextWatcher() { + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // TODO Auto-generated method stub + + } + + 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 + + } + }); + } + /* (non-Javadoc) + * @see android.app.Activity#onBackPressed() + * 按返回键时根据情况更改类中的数据 + */ + @Override + public void onBackPressed() { + 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; + } + } + /** + * @param appWidgetId + * @param appWidgetType + * 根据不同类型的widget更新插件,通过intent传送数据 + */ + private void updateWidget(int appWidgetId, int appWidgetType) { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + if (appWidgetType == Notes.TYPE_WIDGET_2X) { + intent.setClass(this, NoteWidgetProvider_2x.class); + } else if (appWidgetType == 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[] { + appWidgetId + }); + + 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); + } + /* (non-Javadoc) + * @see android.app.Activity#onContextItemSelected(android.view.MenuItem) + * 针对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; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_new_folder: { + 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; + } + /* (non-Javadoc) + * @see android.app.Activity#onSearchRequested() + * 直接调用startSearch函数 + */ + @Override + public boolean onSearchRequested() { + startSearch(null, false, null /* appData */, false); + return true; + } + /** + * 函数功能:实现将便签导出到文本功能 + */ + private void exportNoteToText() { + final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); + new AsyncTask() { + + @Override + protected Integer doInBackground(Void... unused) { + return backup.exportToText(); + } + + @Override + protected void onPostExecute(Integer result) { + if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(NotesListActivity.this + .getString(R.string.failed_sdcard_export)); + builder.setMessage(NotesListActivity.this + .getString(R.string.error_sdcard_unmounted)); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); + } else if (result == BackupUtils.STATE_SUCCESS) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(NotesListActivity.this + .getString(R.string.success_sdcard_export)); + builder.setMessage(NotesListActivity.this.getString( + R.string.format_exported_file_location, backup + .getExportedTextFileName(), backup.getExportedTextFileDir())); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); + } else if (result == BackupUtils.STATE_SYSTEM_ERROR) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(NotesListActivity.this + .getString(R.string.failed_sdcard_export)); + builder.setMessage(NotesListActivity.this + .getString(R.string.error_sdcard_export)); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); + } + } + + }.execute(); + } + /** + * @return + * 功能:判断是否正在同步 + */ + private boolean isSyncMode() { + return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + } + /** + * 功能:跳转到PreferenceActivity界面 + */ + private void startPreferenceActivity() { + Activity from = getParent() != null ? getParent() : this; + Intent intent = new Intent(from, NotesPreferenceActivity.class); + from.startActivityIfNeeded(intent, -1); + } + /** + * @author k + * 函数功能:实现对便签列表项的点击事件(短按) + */ + private class OnListItemClickListener implements OnItemClickListener { + + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (view instanceof NotesListItem) { + NoteItemData item = ((NotesListItem) view).getItemData(); + if (mNotesListAdapter.isInChoiceMode()) { + if (item.getType() == Notes.TYPE_NOTE) { + position = position - mNotesListView.getHeaderViewsCount(); + mModeCallBack.onItemCheckedStateChanged(null, position, id, + !mNotesListAdapter.isSelectedItem(position)); + } + return; + } + + switch (mState) { + case NOTE_LIST: + if (item.getType() == Notes.TYPE_FOLDER + || item.getType() == Notes.TYPE_SYSTEM) { + openFolder(item); + } else if (item.getType() == Notes.TYPE_NOTE) { + openNode(item); + } else { + Log.e(TAG, "Wrong note type in NOTE_LIST"); + } + break; + case SUB_FOLDER: + case CALL_RECORD_FOLDER: + if (item.getType() == Notes.TYPE_NOTE) { + openNode(item); + } else { + Log.e(TAG, "Wrong note type in SUB_FOLDER"); + } + break; + default: + break; + } + } + } + + } + /** + * 查询目标文件 + */ + private void startQueryDestinationFolders() { + String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; + selection = (mState == ListEditState.NOTE_LIST) ? selection: + "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; + + mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, + null, + Notes.CONTENT_NOTE_URI, + FoldersListAdapter.PROJECTION, + selection, + new String[] { + String.valueOf(Notes.TYPE_FOLDER), + String.valueOf(Notes.ID_TRASH_FOLER), + String.valueOf(mCurrentFolderId) + }, + NoteColumns.MODIFIED_DATE + " DESC"); + } + /* (non-Javadoc) + * @see android.widget.AdapterView.OnItemLongClickListener#onItemLongClick(android.widget.AdapterView, android.view.View, int, long) + * 长按某一项时进行的操作 + * 如果长按的是便签,则通过ActionMode菜单实现;如果长按的是文件夹,则通过ContextMenu菜单实现; + * 具体ActionMOde菜单和ContextMenu菜单的详细见精度笔记 + */ + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + if (view instanceof NotesListItem) { + mFocusNoteDataItem = ((NotesListItem) view).getItemData(); + if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { + if (mNotesListView.startActionMode(mModeCallBack) != null) { + mModeCallBack.onItemCheckedStateChanged(null, position, id, true); + mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } else { + Log.e(TAG, "startActionMode fails"); + } + } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { + mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); + } + } + return false; + } +} diff --git a/doc_/标注/210340024_阮钰琳/NotesListActivity.java b/doc_/标注/210340024_阮钰琳/NotesListActivity.java new file mode 100644 index 0000000..73b5633 --- /dev/null +++ b/doc_/标注/210340024_阮钰琳/NotesListActivity.java @@ -0,0 +1,997 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.appwidget.AppWidgetManager; +import android.content.AsyncQueryHandler; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.ActionMode; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Display; +import android.view.HapticFeedbackConstants; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnCreateContextMenuListener; +import android.view.View.OnTouchListener; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.PopupMenu; +import android.widget.TextView; +import android.widget.Toast; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.remote.GTaskSyncService; +import net.micode.notes.model.WorkingNote; +import net.micode.notes.tool.BackupUtils; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; +import net.micode.notes.widget.NoteWidgetProvider_2x; +import net.micode.notes.widget.NoteWidgetProvider_4x; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashSet; + +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; + + private int 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; + + 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; + + @Override + + protected void onCreate(Bundle savedInstanceState) { + // final类不能被继承,没有子类,final类中的方法默认是final的。 + //final方法不能被子类的方法覆盖,但可以被继承。 + //final成员变量表示常量,只能被赋值一次,赋值后值不再改变。 + //final不能用于修饰构造方法。 + + super.onCreate(savedInstanceState); + setContentView(R.layout.note_list); + initResources(); + + /** + * Insert an introduction when user firstly use this application + */ + setAppInfoFromRawRes(); + } + + @Override// 返回一些子模块完成的数据交给主Activity处理 + 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); + // 调用 Activity 的onActivityResult() + } + } + + private void setAppInfoFromRawRes() { + // Android平台给我们提供了一个SharedPreferences类,它是一个轻量级的存储类,特别适合用于保存软件配置参数。 + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { + StringBuilder sb = new StringBuilder(); + InputStream in = null; + try {// 把资源文件放到应用程序的/raw/raw下,那么就可以在应用中使用getResources获取资源后, + // 以openRawResource方法(不带后缀的资源文件名)打开这个文件。 + in = getResources().openRawResource(R.raw.introduction); + if (in != null) { + InputStreamReader isr = new InputStreamReader(in); + BufferedReader br = new BufferedReader(isr); + char [] buf = new char[1024]; + int len = 0; + while ((len = br.read(buf)) > 0) { + sb.append(buf, 0, len); + } + } else { + Log.e(TAG, "Read introduction file error"); + return; + } + } catch (IOException e) { + e.printStackTrace(); + return; + } finally { + if(in != null) { + try { + in.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + // 创建空的WorkingNote + WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, + AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, + ResourceParser.RED); + note.setWorkingText(sb.toString()); + if (note.saveNote()) {// 更新保存note的信息 + sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); + } else { + Log.e(TAG, "Save introduction note error"); + return; + } + } + } + + @Override + protected void onStart() { + super.onStart(); + startAsyncNotesListQuery(); + } + // 初始化资源 + private void initResources() { + mContentResolver = this.getContentResolver();// 获取应用程序的数据,得到类似数据表的东西 + mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); + mCurrentFolderId = Notes.ID_ROOT_FOLDER; + // findViewById 是安卓编程的定位函数,主要是引用.R文件里的引用名 + mNotesListView = (ListView) findViewById(R.id.notes_list);// 绑定XML中的ListView,作为Item的容器 + 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);// 在activity中要获取该按钮 + mAddNewNote.setOnClickListener(this); + mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); + mDispatch = false; + mDispatchY = 0; + mOriginY = 0; + mTitleBar = (TextView) findViewById(R.id.tv_title_bar); + mState = ListEditState.NOTE_LIST; + mModeCallBack = new ModeCallback(); + } + // 继承自ListView.MultiChoiceModeListener 和 OnMenuItemClickListener + 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(); + // Update dropdown menu + 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); + } + } + } + + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // TODO Auto-generated method stub + return false; + } + + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + // TODO Auto-generated method stub + return false; + } + + public void onDestroyActionMode(ActionMode mode) { + mNotesListAdapter.setChoiceMode(false); + mNotesListView.setLongClickable(true); + mAddNewNote.setVisibility(View.VISIBLE); + } + + public void finishActionMode() { + mActionMode.finish(); + } + + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, + boolean checked) { + mNotesListAdapter.setCheckedItem(position, checked); + updateMenu(); + } + + 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(); + /** + * Minus TitleBar's height + */ + if (mState == ListEditState.SUB_FOLDER) { + eventY -= mTitleBar.getHeight(); + start -= mTitleBar.getHeight(); + } + /** + * HACKME:When click the transparent part of "New Note" button, dispatch + * the event to the list view behind this button. The transparent part of + * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel) + * and the line top of the button. The coordinate based on left of the "New + * Note" button. The 94 represents maximum height of the transparent part. + * Notice that, if the background of the button changes, the formula should + * also change. This is very bad, just for the UI designer's strong requirement. + */ + 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() { + String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION + : NORMAL_SELECTION; + mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, + Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] { + String.valueOf(mCurrentFolderId) + }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); + } + + private final class BackgroundQueryHandler extends AsyncQueryHandler { + public BackgroundQueryHandler(ContentResolver contentResolver) { + super(contentResolver); + } + + @Override + protected void onQueryComplete(int token, Object cookie, Cursor cursor) { + 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: + return; + } + } + } + + private void showFolderListMenu(Cursor cursor) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(R.string.menu_title_select_folder); + 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.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 intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); + this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); + } + + private void batchDelete() { + new AsyncTask>() { + protected HashSet doInBackground(Void... unused) { + HashSet widgets = mNotesListAdapter.getSelectedWidget(); + if (!isSyncMode()) { + // if not synced, delete notes directly + if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter + .getSelectedItemIds())) { + } else { + Log.e(TAG, "Delete notes error, should not happens"); + } + } else { + // in sync mode, we'll move the deleted note into the trash + // folder + 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 widgets) { + if (widgets != null) { + for (AppWidgetAttribute widget : widgets) { + if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { + updateWidget(widget.widgetId, widget.widgetType); + } + } + } + mModeCallBack.finishActionMode(); + } + }.execute(); + } + + private void deleteFolder(long folderId) { + if (folderId == Notes.ID_ROOT_FOLDER) { + Log.e(TAG, "Wrong folder id, should not happen " + folderId); + return; + } + + HashSet ids = new HashSet(); + ids.add(folderId); + HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver, + folderId); + if (!isSyncMode()) { + // if not synced, delete folder directly + DataUtils.batchDeleteNotes(mContentResolver, ids); + } else { + // in sync mode, we'll move the deleted folder into the trash folder + DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER); + } + if (widgets != null) { + for (AppWidgetAttribute widget : widgets) { + if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID + && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { + updateWidget(widget.widgetId, widget.widgetType); + } + } + } + } + + private void openNode(NoteItemData data) { + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, data.getId()); + this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); + } + + private void openFolder(NoteItemData data) { + 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; + } + 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) { + 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); + } + /** + * When the name edit text is null, disable the positive button + */ + etName.addTextChangedListener(new TextWatcher() { + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // TODO Auto-generated method stub + + } + + 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 + + } + }); + } + /* (non-Javadoc) + * @see android.app.Activity#onBackPressed() + * 按返回键时根据情况更改类中的数据 + */ + @Override + public void onBackPressed() { + 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; + } + } + /** + * @param appWidgetId + * @param appWidgetType + * 根据不同类型的widget更新插件,通过intent传送数据 + */ + private void updateWidget(int appWidgetId, int appWidgetType) { + Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + if (appWidgetType == Notes.TYPE_WIDGET_2X) { + intent.setClass(this, NoteWidgetProvider_2x.class); + } else if (appWidgetType == 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[] { + appWidgetId + }); + + 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); + } + /* (non-Javadoc) + * @see android.app.Activity#onContextItemSelected(android.view.MenuItem) + * 针对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; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_new_folder: { + 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; + } + /* (non-Javadoc) + * @see android.app.Activity#onSearchRequested() + * 直接调用startSearch函数 + */ + @Override + public boolean onSearchRequested() { + startSearch(null, false, null /* appData */, false); + return true; + } + /** + * 函数功能:实现将便签导出到文本功能 + */ + private void exportNoteToText() { + final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); + new AsyncTask() { + + @Override + protected Integer doInBackground(Void... unused) { + return backup.exportToText(); + } + + @Override + protected void onPostExecute(Integer result) { + if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(NotesListActivity.this + .getString(R.string.failed_sdcard_export)); + builder.setMessage(NotesListActivity.this + .getString(R.string.error_sdcard_unmounted)); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); + } else if (result == BackupUtils.STATE_SUCCESS) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(NotesListActivity.this + .getString(R.string.success_sdcard_export)); + builder.setMessage(NotesListActivity.this.getString( + R.string.format_exported_file_location, backup + .getExportedTextFileName(), backup.getExportedTextFileDir())); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); + } else if (result == BackupUtils.STATE_SYSTEM_ERROR) { + AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); + builder.setTitle(NotesListActivity.this + .getString(R.string.failed_sdcard_export)); + builder.setMessage(NotesListActivity.this + .getString(R.string.error_sdcard_export)); + builder.setPositiveButton(android.R.string.ok, null); + builder.show(); + } + } + + }.execute(); + } + /** + * @return + * 功能:判断是否正在同步 + */ + private boolean isSyncMode() { + return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; + } + /** + * 功能:跳转到PreferenceActivity界面 + */ + private void startPreferenceActivity() { + Activity from = getParent() != null ? getParent() : this; + Intent intent = new Intent(from, NotesPreferenceActivity.class); + from.startActivityIfNeeded(intent, -1); + } + /** + * @author k + * 函数功能:实现对便签列表项的点击事件(短按) + */ + private class OnListItemClickListener implements OnItemClickListener { + + public void onItemClick(AdapterView parent, View view, int position, long id) { + if (view instanceof NotesListItem) { + NoteItemData item = ((NotesListItem) view).getItemData(); + if (mNotesListAdapter.isInChoiceMode()) { + if (item.getType() == Notes.TYPE_NOTE) { + position = position - mNotesListView.getHeaderViewsCount(); + mModeCallBack.onItemCheckedStateChanged(null, position, id, + !mNotesListAdapter.isSelectedItem(position)); + } + return; + } + + switch (mState) { + case NOTE_LIST: + if (item.getType() == Notes.TYPE_FOLDER + || item.getType() == Notes.TYPE_SYSTEM) { + openFolder(item); + } else if (item.getType() == Notes.TYPE_NOTE) { + openNode(item); + } else { + Log.e(TAG, "Wrong note type in NOTE_LIST"); + } + break; + case SUB_FOLDER: + case CALL_RECORD_FOLDER: + if (item.getType() == Notes.TYPE_NOTE) { + openNode(item); + } else { + Log.e(TAG, "Wrong note type in SUB_FOLDER"); + } + break; + default: + break; + } + } + } + + } + /** + * 查询目标文件 + */ + private void startQueryDestinationFolders() { + String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; + selection = (mState == ListEditState.NOTE_LIST) ? selection: + "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; + + mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, + null, + Notes.CONTENT_NOTE_URI, + FoldersListAdapter.PROJECTION, + selection, + new String[] { + String.valueOf(Notes.TYPE_FOLDER), + String.valueOf(Notes.ID_TRASH_FOLER), + String.valueOf(mCurrentFolderId) + }, + NoteColumns.MODIFIED_DATE + " DESC"); + } + /* (non-Javadoc) + * @see android.widget.AdapterView.OnItemLongClickListener#onItemLongClick(android.widget.AdapterView, android.view.View, int, long) + * 长按某一项时进行的操作 + * 如果长按的是便签,则通过ActionMode菜单实现;如果长按的是文件夹,则通过ContextMenu菜单实现; + * 具体ActionMOde菜单和ContextMenu菜单的详细见精度笔记 + */ + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + if (view instanceof NotesListItem) { + mFocusNoteDataItem = ((NotesListItem) view).getItemData(); + if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { + if (mNotesListView.startActionMode(mModeCallBack) != null) { + mModeCallBack.onItemCheckedStateChanged(null, position, id, true); + mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + } else { + Log.e(TAG, "startActionMode fails"); + } + } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { + mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); + } + } + return false; + } +} diff --git a/doc_/标注/210340024_阮钰琳/NotesListAdapter.doc b/doc_/标注/210340024_阮钰琳/NotesListAdapter.doc new file mode 100644 index 0000000..ee0dae6 --- /dev/null +++ b/doc_/标注/210340024_阮钰琳/NotesListAdapter.doc @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.database.Cursor; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; + +import net.micode.notes.data.Notes; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; + + +/* + * 功能:直译为便签表连接器,继承了CursorAdapter,它为cursor和ListView提供了连接的桥梁。 + * 所以NotesListAdapter实现的是鼠标和编辑便签链接的桥梁 + */ + +public class NotesListAdapter extends CursorAdapter { + private static final String TAG = "NotesListAdapter"; + private Context mContext; + private HashMap mSelectedIndex; + private int mNotesCount;//便签数 + private boolean mChoiceMode;//选择模式标记 + /* + * 桌面widget的属性,包括编号和类型 + */ + public static class AppWidgetAttribute { + public int widgetId; + public int widgetType; + }; + /* + * 函数功能:初始化便签链接器 + * 函数实现:根据传进来的内容设置相关变量 + */ + public NotesListAdapter(Context context) { + super(context, null);//父类对象置空 + mSelectedIndex = new HashMap();//新建选项下标的hash表 + mContext = context; + mNotesCount = 0; + } + + @Override + /* + * 函数功能:新建一个视图来存储光标所指向的数据 + * 函数实现:使用兄弟类NotesListItem新建一个项目选项 + */ + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return new NotesListItem(context); + } + /* + * 函数功能:将已经存在的视图和鼠标指向的数据进行捆绑 + * 函数实现:如下注释 + */ + @Override + public void bindView(View view, Context context, Cursor cursor) { + if (view instanceof NotesListItem) {//若view是NotesListItem的一个实例 + NoteItemData itemData = new NoteItemData(context, cursor); + ((NotesListItem) view).bind(context, itemData, mChoiceMode, + isSelectedItem(cursor.getPosition())); + //则新建一个项目选项并且用bind跟将view和鼠标,内容,便签数据捆绑在一起 + } + } + /* + * 函数功能:设置勾选框 + * 函数实现:如下注释 + */ + public void setCheckedItem(final int position, final boolean checked) { + mSelectedIndex.put(position, checked);//根据定位和是否勾选设置下标 + notifyDataSetChanged();//在修改后刷新activity + + } + /* + * 函数功能:判断单选按钮是否勾选 + */ + public boolean isInChoiceMode() { + return mChoiceMode; + } + /* + * 函数功能:设置单项选项框 + * 函数实现:重置下标并且根据参数mode设置选项 + */ + public void setChoiceMode(boolean mode) { + mSelectedIndex.clear(); + mChoiceMode = mode; + } + /* + * 函数功能:选择全部选项 + * 函数实现:如下注释 + */ + 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); + } + } + } //遍历所有光标可用的位置在判断为便签类型之后勾选单项框 + } + /* + * 函数功能:建立选择项的下标列表 + * 函数实现:如下注释 + */ + public HashSet getSelectedItemIds() { + HashSet itemSet = new HashSet();//建立hash表 + 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); + }//则将id该下标假如选项集合中 + } + } + + return itemSet; + } + /* + * 函数功能:建立桌面Widget的选项表 + * 函数实现:如下注释 + */ + 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) {//光标位置可用的话就建立新的Widget属性并编辑下标和类型,最后添加到选项集中 + + 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; + } + /* + * 函数功能:获取选项个数 + * 函数实现:如下注释 + */ + 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()) {//若value值为真计数+1 + count++; + } + } + return count; + } + /* + * 函数功能:判断是否全部选中 + * 函数实现:如下注释 + */ + public boolean isAllSelected() { + int checkedCount = getSelectedCount(); + return (checkedCount != 0 && checkedCount == mNotesCount);//获取选项数看是否等于便签的个数 + } + /* + * 函数功能:判断是否为选项表 + * 函数实现:通过传递的下标来确定 + */ + public boolean isSelectedItem(final int position) { + if (null == mSelectedIndex.get(position)) { + return false; + } + return mSelectedIndex.get(position); + } + + @Override + protected void onContentChanged() { + super.onContentChanged(); + calcNotesCount(); + } + + @Override + /* + * 函数功能:在activity内容发生局部变动的时候回调该函数计算便签的数量 + * 函数实现:如下注释 + */ + 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++; //若该位置不为空并且文本类型为便签就+1 + } + } else { + Log.e(TAG, "Invalid cursor"); + return; + } //否则报错 + } + } +} diff --git a/doc_/标注/210340024_阮钰琳/NotesListAdapter.java b/doc_/标注/210340024_阮钰琳/NotesListAdapter.java new file mode 100644 index 0000000..ee0dae6 --- /dev/null +++ b/doc_/标注/210340024_阮钰琳/NotesListAdapter.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.database.Cursor; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; + +import net.micode.notes.data.Notes; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; + + +/* + * 功能:直译为便签表连接器,继承了CursorAdapter,它为cursor和ListView提供了连接的桥梁。 + * 所以NotesListAdapter实现的是鼠标和编辑便签链接的桥梁 + */ + +public class NotesListAdapter extends CursorAdapter { + private static final String TAG = "NotesListAdapter"; + private Context mContext; + private HashMap mSelectedIndex; + private int mNotesCount;//便签数 + private boolean mChoiceMode;//选择模式标记 + /* + * 桌面widget的属性,包括编号和类型 + */ + public static class AppWidgetAttribute { + public int widgetId; + public int widgetType; + }; + /* + * 函数功能:初始化便签链接器 + * 函数实现:根据传进来的内容设置相关变量 + */ + public NotesListAdapter(Context context) { + super(context, null);//父类对象置空 + mSelectedIndex = new HashMap();//新建选项下标的hash表 + mContext = context; + mNotesCount = 0; + } + + @Override + /* + * 函数功能:新建一个视图来存储光标所指向的数据 + * 函数实现:使用兄弟类NotesListItem新建一个项目选项 + */ + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return new NotesListItem(context); + } + /* + * 函数功能:将已经存在的视图和鼠标指向的数据进行捆绑 + * 函数实现:如下注释 + */ + @Override + public void bindView(View view, Context context, Cursor cursor) { + if (view instanceof NotesListItem) {//若view是NotesListItem的一个实例 + NoteItemData itemData = new NoteItemData(context, cursor); + ((NotesListItem) view).bind(context, itemData, mChoiceMode, + isSelectedItem(cursor.getPosition())); + //则新建一个项目选项并且用bind跟将view和鼠标,内容,便签数据捆绑在一起 + } + } + /* + * 函数功能:设置勾选框 + * 函数实现:如下注释 + */ + public void setCheckedItem(final int position, final boolean checked) { + mSelectedIndex.put(position, checked);//根据定位和是否勾选设置下标 + notifyDataSetChanged();//在修改后刷新activity + + } + /* + * 函数功能:判断单选按钮是否勾选 + */ + public boolean isInChoiceMode() { + return mChoiceMode; + } + /* + * 函数功能:设置单项选项框 + * 函数实现:重置下标并且根据参数mode设置选项 + */ + public void setChoiceMode(boolean mode) { + mSelectedIndex.clear(); + mChoiceMode = mode; + } + /* + * 函数功能:选择全部选项 + * 函数实现:如下注释 + */ + 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); + } + } + } //遍历所有光标可用的位置在判断为便签类型之后勾选单项框 + } + /* + * 函数功能:建立选择项的下标列表 + * 函数实现:如下注释 + */ + public HashSet getSelectedItemIds() { + HashSet itemSet = new HashSet();//建立hash表 + 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); + }//则将id该下标假如选项集合中 + } + } + + return itemSet; + } + /* + * 函数功能:建立桌面Widget的选项表 + * 函数实现:如下注释 + */ + 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) {//光标位置可用的话就建立新的Widget属性并编辑下标和类型,最后添加到选项集中 + + 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; + } + /* + * 函数功能:获取选项个数 + * 函数实现:如下注释 + */ + 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()) {//若value值为真计数+1 + count++; + } + } + return count; + } + /* + * 函数功能:判断是否全部选中 + * 函数实现:如下注释 + */ + public boolean isAllSelected() { + int checkedCount = getSelectedCount(); + return (checkedCount != 0 && checkedCount == mNotesCount);//获取选项数看是否等于便签的个数 + } + /* + * 函数功能:判断是否为选项表 + * 函数实现:通过传递的下标来确定 + */ + public boolean isSelectedItem(final int position) { + if (null == mSelectedIndex.get(position)) { + return false; + } + return mSelectedIndex.get(position); + } + + @Override + protected void onContentChanged() { + super.onContentChanged(); + calcNotesCount(); + } + + @Override + /* + * 函数功能:在activity内容发生局部变动的时候回调该函数计算便签的数量 + * 函数实现:如下注释 + */ + 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++; //若该位置不为空并且文本类型为便签就+1 + } + } else { + Log.e(TAG, "Invalid cursor"); + return; + } //否则报错 + } + } +} diff --git a/doc_/标注/210340041_郑培萱/AlarmAlertActivity.java b/doc_/标注/210340041_郑培萱/AlarmAlertActivity.java new file mode 100644 index 0000000..74805f1 --- /dev/null +++ b/doc_/标注/210340041_郑培萱/AlarmAlertActivity.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.Intent; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.PowerManager; +import android.provider.Settings; +import android.view.Window; +import android.view.WindowManager; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.tool.DataUtils; + +import java.io.IOException; + + +public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + private long mNoteId; + //文本在数据库存储中的ID号 + private String mSnippet; + //闹钟提示时出现的文本片段 + private static final int SNIPPET_PREW_MAX_LEN = 60; + MediaPlayer mPlayer; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //Bundle类型的数据与Map类型的数据相似,都是以key-value的形式存储数据的 + //onsaveInstanceState方法是用来保存Activity的状态的 + //能从onCreate的参数savedInsanceState中获得状态数据 + + 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); + //根据ID从数据库中获取标签的内容; + //getContentResolver()是实现数据共享,实例存储。 + 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; + } +/* + try + { + // 代码区 + } + catch(Exception e) + { + // 异常处理 + } + 代码区如果有错误,就会返回所写异常的处理。*/ + mPlayer = new MediaPlayer(); + if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { + showActionDialog(); + //弹出对话框 + playAlarmSound(); + //闹钟提示音激发 + } else { + finish(); + //完成闹钟动作 + } + } + + private boolean isScreenOn() { + //判断屏幕是否锁屏,调用系统函数判断,最后返回值是布尔类型 + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + return pm.isScreenOn(); + } + + private void playAlarmSound() { + //闹钟提示音激发 + Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + //调用系统的铃声管理URI,得到闹钟提示音 + 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); + //方法:setDataSource(Context context, Uri uri) + //解释:无返回值,设置多媒体数据来源【根据 Uri】 + mPlayer.prepare(); + //准备同步 + mPlayer.setLooping(true); + //设置是否循环播放 + mPlayer.start(); + //开始播放 + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + //e.printStackTrace()函数功能是抛出异常, 还将显示出更深的调用信息 + //System.out.println(e),这个方法打印出异常,并且输出在哪里出现的异常 + } 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); + //AlertDialog的构造方法全部是Protected的 + //所以不能直接通过new一个AlertDialog来创建出一个AlertDialog。 + //要创建一个AlertDialog,就要用到AlertDialog.Builder中的create()方法 + //如这里的dialog就是新建了一个AlertDialog + dialog.setTitle(R.string.app_name); + //为对话框设置标题 + dialog.setMessage(mSnippet); + //为对话框设置内容 + dialog.setPositiveButton(R.string.notealert_ok, this); + //给对话框添加"Yes"按钮 + if (isScreenOn()) { + dialog.setNegativeButton(R.string.notealert_enter, this); + }//对话框添加"No"按钮 + dialog.show().setOnDismissListener(this); + } + + public void onClick(DialogInterface dialog, int which) { + switch (which) { + //用which来选择click后下一步的操作 + case DialogInterface.BUTTON_NEGATIVE: + //这是取消操作 + Intent intent = new Intent(this, NoteEditActivity.class); + //实现两个类间的数据传输 + intent.setAction(Intent.ACTION_VIEW); + //设置动作属性 + intent.putExtra(Intent.EXTRA_UID, mNoteId); + //实现key-value对 + //EXTRA_UID为key;mNoteId为键 + startActivity(intent); + //开始动作 + break; + default: + //这是确定操作 + break; + } + } + + public void onDismiss(DialogInterface dialog) { + //忽略 + stopAlarmSound(); + //停止闹钟声音 + finish(); + //完成该动作 + } + + private void stopAlarmSound() { + if (mPlayer != null) { + mPlayer.stop(); + //停止播放 + mPlayer.release(); + //释放MediaPlayer对象 + mPlayer = null; + } + } +} diff --git a/doc_/标注/210340041_郑培萱/AlarmInitReceiver.java b/doc_/标注/210340041_郑培萱/AlarmInitReceiver.java new file mode 100644 index 0000000..eff564b --- /dev/null +++ b/doc_/标注/210340041_郑培萱/AlarmInitReceiver.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + + +public class AlarmInitReceiver extends BroadcastReceiver { + + private static final String [] PROJECTION = new String [] { + NoteColumns.ID, + NoteColumns.ALERTED_DATE + }; + //对数据库的操作,调用标签ID和闹钟时间 + 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(); + //System.currentTimeMillis()产生一个当前的毫秒 + //这个毫秒其实就是自1970年1月1日0时起的毫秒数 + Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, + PROJECTION, + NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, + new String[] { String.valueOf(currentDate) }, + //将long变量currentDate转化为字符串 + //Cursor在这里的作用是通过查找数据库中的标签内容,找到和当前系统时间相等的标签 + 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、PendingIntent以及AlarmManager等 + //这里就是根据数据库里的闹钟时间创建一个闹钟机制 + } +} diff --git a/doc_/标注/210340041_郑培萱/AlarmReceiver.java b/doc_/标注/210340041_郑培萱/AlarmReceiver.java new file mode 100644 index 0000000..814fec2 --- /dev/null +++ b/doc_/标注/210340041_郑培萱/AlarmReceiver.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +public class AlarmReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + intent.setClass(context, AlarmAlertActivity.class); + //启动AlarmAlertActivity + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + //activity要存在于activity的栈中,而非activity的途径启动activity时必然不存在一个activity的栈 + //所以要新起一个栈装入启动的activity + context.startActivity(intent); + } +} +//这是实现alarm这个功能最接近用户层的包,基于上面的两个包, +//作用还需要深究但是对于setClass和addFlags的 diff --git a/doc_/标注/210340041_郑培萱/DateTimePicker.java b/doc_/标注/210340041_郑培萱/DateTimePicker.java new file mode 100644 index 0000000..786dd9c --- /dev/null +++ b/doc_/标注/210340041_郑培萱/DateTimePicker.java @@ -0,0 +1,508 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import java.text.DateFormatSymbols; +import java.util.Calendar; + +import net.micode.notes.R; + + +import android.content.Context; +import android.text.format.DateFormat; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.NumberPicker; + +public class DateTimePicker extends FrameLayout { + //FrameLayout是布局模板之一 + //所有的子元素全部在屏幕的右上方 + private static final boolean DEFAULT_ENABLE_STATE = true; + + 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; + private final NumberPicker mAmPmSpinner; + //NumberPicker是数字选择器 + //这里定义的四个变量全部是在设置闹钟时需要选择的变量(如日期、时、分、上午或者下午) + private Calendar mDate; + //定义了Calendar类型的变量mDate,用于操作时间 + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + + private boolean mIsAm; + + private boolean mIs24HourView; + + private boolean mIsEnabled = DEFAULT_ENABLE_STATE; + + private boolean mInitialising; + + private OnDateTimeChangedListener mOnDateTimeChangedListener; + + 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(); + } + };//OnValueChangeListener,这是时间改变监听器,这里主要是对日期的监听 + //将现在日期的值传递给mDate;updateDateControl是同步操作 + + private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { + //这里是对 小时(Hour) 的监听 + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + boolean isDateChanged = false; + Calendar cal = Calendar.getInstance(); + //声明一个Calendar的变量cal,便于后续的操作 + 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; + //这里是对于12小时制时,晚上11点和12点交替时对日期的更改 + } 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; + } + //这里是对于12小时制时,凌晨11点和12点交替时对日期的更改 + 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(); + }//这里是对于12小时制时,中午11点和12点交替时对AM和PM的更改 + } else { + if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + //这里是对于24小时制时,晚上11点和12点交替时对日期的更改 + } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + }//这里是对于12小时制时,凌晨11点和12点交替时对日期的更改 + int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); + //通过数字选择器对newHour的赋值 + mDate.set(Calendar.HOUR_OF_DAY, newHour); + //通过set函数将新的Hour值传给mDate + 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 + //这里是对 分钟(Minute)改变的监听 + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + int minValue = mMinuteSpinner.getMinValue(); + int maxValue = mMinuteSpinner.getMaxValue(); + int offset = 0; + //设置offset,作为小时改变的一个记录数据 + if (oldVal == maxValue && newVal == minValue) { + offset += 1; + } else if (oldVal == minValue && newVal == maxValue) { + offset -= 1; + } + //如果原值为59,新值为0,则offset加1 + //如果原值为0,新值为59,则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(); + } + }; + + private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { + //对AM和PM的监听 + @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(); + } + }; + + public interface OnDateTimeChangedListener { + void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute); + } + + public DateTimePicker(Context context) { + this(context, System.currentTimeMillis()); + //通过对数据库的访问,获取当前的系统时间 + } + + public DateTimePicker(Context context, long date) { + this(context, date, DateFormat.is24HourFormat(context)); + //上面函数的得到的是一个天文数字(1970至今的秒数),需要DateFormat将其变得有意义 + } + + public DateTimePicker(Context context, long date, boolean is24HourView) { + super(context); + //获取系统时间 + mDate = Calendar.getInstance(); + mInitialising = true; + mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + inflate(context, R.layout.datetime_picker, this); + //如果当前Activity里用到别的layout,比如对话框layout + //还要设置这个layout上的其他组件的内容,就必须用inflate()方法先将对话框的layout找出来 + //然后再用findViewById()找到它上面的其它组件 + mDateSpinner = (NumberPicker) findViewById(R.id.date); + mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); + mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); + mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + + mHourSpinner = (NumberPicker) findViewById(R.id.hour); + mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); + mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); + mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); + mMinuteSpinner.setOnLongPressUpdateInterval(100); + mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); + + String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); + mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); + mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); + mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); + mAmPmSpinner.setDisplayedValues(stringsForAmPm); + mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); + + // update controls to initial state + updateDateControl(); + updateHourControl(); + updateAmPmControl(); + + set24HourView(is24HourView); + + // set to current time + setCurrentDate(date); + + setEnabled(isEnabled()); + + // set the content descriptions + mInitialising = false; + } + + @Override + public void setEnabled(boolean enabled) { + if (mIsEnabled == enabled) { + return; + } + super.setEnabled(enabled); + mDateSpinner.setEnabled(enabled); + mMinuteSpinner.setEnabled(enabled); + mHourSpinner.setEnabled(enabled); + mAmPmSpinner.setEnabled(enabled); + mIsEnabled = enabled; + } + //存在疑问!!!!!!!!!!!!!setEnabled的作用 + //下面的代码通过原程序的注释已经比较清晰,另外可以通过函数名来判断 + //下面的各函数主要是对上面代码引用到的各函数功能的实现 + @Override + public boolean isEnabled() { + return mIsEnabled; + } + + /** + * Get the current date in millis + * + * @return the current date in millis + */ + public long getCurrentDateInTimeMillis() { + return mDate.getTimeInMillis(); + } + + /** + * Set the current date + * + * @param date The current date in millis + */ + public void setCurrentDate(long date) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(date); + setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), + cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); + }//实现函数功能——设置当前的时间,参数是date + + /** + * Set the current date + * + * @param year The current year + * @param month The current month + * @param dayOfMonth The current dayOfMonth + * @param hourOfDay The current hourOfDay + * @param minute The current minute + */ + public void setCurrentDate(int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + setCurrentYear(year); + setCurrentMonth(month); + setCurrentDay(dayOfMonth); + setCurrentHour(hourOfDay); + setCurrentMinute(minute); + }//实现函数功能——设置当前的时间,参数是各详细的变量 + + /** + * Get current year + * + * @return The current year + */ + public int getCurrentYear() { + return mDate.get(Calendar.YEAR); + } + + /** + * Set current year + * + * @param year The current year + */ + public void setCurrentYear(int year) { + if (!mInitialising && year == getCurrentYear()) { + return; + } + mDate.set(Calendar.YEAR, year); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current month in the year + * + * @return The current month in the year + */ + public int getCurrentMonth() { + return mDate.get(Calendar.MONTH); + } + + /** + * Set current month in the year + * + * @param month The month in the year + */ + public void setCurrentMonth(int month) { + if (!mInitialising && month == getCurrentMonth()) { + return; + } + mDate.set(Calendar.MONTH, month); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current day of the month + * + * @return The day of the month + */ + public int getCurrentDay() { + return mDate.get(Calendar.DAY_OF_MONTH); + } + + /** + * Set current day of the month + * + * @param dayOfMonth The day of the month + */ + public void setCurrentDay(int dayOfMonth) { + if (!mInitialising && dayOfMonth == getCurrentDay()) { + return; + } + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current hour in 24 hour mode, in the range (0~23) + * @return The current hour in 24 hour mode + */ + public int getCurrentHourOfDay() { + return mDate.get(Calendar.HOUR_OF_DAY); + } + + private int getCurrentHour() { + if (mIs24HourView){ + return getCurrentHourOfDay(); + } else { + int hour = getCurrentHourOfDay(); + if (hour > HOURS_IN_HALF_DAY) { + return hour - HOURS_IN_HALF_DAY; + } else { + return hour == 0 ? HOURS_IN_HALF_DAY : hour; + } + } + } + + /** + * Set current hour in 24 hour mode, in the range (0~23) + * + * @param hourOfDay + */ + public void setCurrentHour(int hourOfDay) { + if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { + return; + } + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + if (!mIs24HourView) { + if (hourOfDay >= HOURS_IN_HALF_DAY) { + mIsAm = false; + if (hourOfDay > HOURS_IN_HALF_DAY) { + hourOfDay -= HOURS_IN_HALF_DAY; + } + } else { + mIsAm = true; + if (hourOfDay == 0) { + hourOfDay = HOURS_IN_HALF_DAY; + } + } + updateAmPmControl(); + } + mHourSpinner.setValue(hourOfDay); + onDateTimeChanged(); + } + + /** + * Get currentMinute + * + * @return The Current Minute + */ + public int getCurrentMinute() { + return mDate.get(Calendar.MINUTE); + } + + /** + * Set current minute + */ + public void setCurrentMinute(int minute) { + if (!mInitialising && minute == getCurrentMinute()) { + return; + } + mMinuteSpinner.setValue(minute); + mDate.set(Calendar.MINUTE, minute); + onDateTimeChanged(); + } + + /** + * @return true if this is in 24 hour view else false. + */ + public boolean is24HourView () { + return mIs24HourView; + } + + /** + * Set whether in 24 hour or AM/PM mode. + * + * @param is24HourView True for 24 hour mode. False for AM/PM mode. + */ + public void set24HourView(boolean is24HourView) { + if (mIs24HourView == is24HourView) { + return; + } + mIs24HourView = is24HourView; + mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); + int hour = getCurrentHourOfDay(); + updateHourControl(); + setCurrentHour(hour); + updateAmPmControl(); + } + + private void updateDateControl() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); + mDateSpinner.setDisplayedValues(null); + for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { + cal.add(Calendar.DAY_OF_YEAR, 1); + mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); + } + mDateSpinner.setDisplayedValues(mDateDisplayValues); + mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); + mDateSpinner.invalidate(); + }// 对于星期几的算法 + + private void updateAmPmControl() { + if (mIs24HourView) { + mAmPmSpinner.setVisibility(View.GONE); + } else { + int index = mIsAm ? Calendar.AM : Calendar.PM; + mAmPmSpinner.setValue(index); + mAmPmSpinner.setVisibility(View.VISIBLE); + }// 对于上下午操作的算法 + } + + private void updateHourControl() { + if (mIs24HourView) { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); + } else { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); + }// 对与小时的算法 + } + + /** + * Set the callback that indicates the 'Set' button has been pressed. + * @param callback the callback, if null will do nothing + */ + public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { + mOnDateTimeChangedListener = callback; + } + + private void onDateTimeChanged() { + if (mOnDateTimeChangedListener != null) { + mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), + getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); + } + } +} diff --git a/doc_/标注/210340041_郑培萱/DateTimePickerDialog.java b/doc_/标注/210340041_郑培萱/DateTimePickerDialog.java new file mode 100644 index 0000000..9fd7d3b --- /dev/null +++ b/doc_/标注/210340041_郑培萱/DateTimePickerDialog.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import java.util.Calendar; + +import net.micode.notes.R; +import net.micode.notes.ui.DateTimePicker; +import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.text.format.DateFormat; +import android.text.format.DateUtils; + +public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + + private Calendar mDate = Calendar.getInstance(); + //创建一个Calendar类型的变量 mDate,方便时间的操作 + private boolean mIs24HourView; + private OnDateTimeSetListener mOnDateTimeSetListener; + //声明一个时间日期滚动选择控件 mOnDateTimeSetListener + private DateTimePicker mDateTimePicker; + //DateTimePicker控件,控件一般用于让用户可以从日期列表中选择单个值。 + //运行时,单击控件边上的下拉箭头,会显示为两个部分:一个下拉列表,一个用于选择日期的 + public interface OnDateTimeSetListener { + void OnDateTimeSet(AlertDialog dialog, long date); + } + + public DateTimePickerDialog(Context context, long date) { + //对该界面对话框的实例化 + super(context); + //对数据库的操作 + mDateTimePicker = new DateTimePicker(context); + //添加一个子视图 + setView(mDateTimePicker); + mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { + public void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + mDate.set(Calendar.YEAR, year); + mDate.set(Calendar.MONTH, month); + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + mDate.set(Calendar.MINUTE, minute); + //将视图中的各选项设置为系统当前时间 + updateTitle(mDate.getTimeInMillis()); + } + }); + mDate.setTimeInMillis(date); + //得到系统时间 + mDate.set(Calendar.SECOND, 0); + //将秒数设置为0 + mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); + setButton(context.getString(R.string.datetime_dialog_ok), this); + setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); + //设置按钮 + set24HourView(DateFormat.is24HourFormat(this.getContext())); + //时间标准化打印 + updateTitle(mDate.getTimeInMillis()); + } + + public void set24HourView(boolean is24HourView) { + mIs24HourView = is24HourView; + } + + 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)); + }//android开发中常见日期管理工具类(API)——DateUtils:按照上下午显示时间 + + public void onClick(DialogInterface arg0, int arg1) { + if (mOnDateTimeSetListener != null) { + mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); + }//第一个参数arg0是接收到点击事件的对话框 + //第二个参数arg1是该对话框上的按钮 + } + +} \ No newline at end of file diff --git a/doc_/标注/210340041_郑培萱/DropdownMenu.java b/doc_/标注/210340041_郑培萱/DropdownMenu.java new file mode 100644 index 0000000..1e69f3f --- /dev/null +++ b/doc_/标注/210340041_郑培萱/DropdownMenu.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnMenuItemClickListener; + +import net.micode.notes.R; + +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); + //设置这个view的背景 + mPopupMenu = new PopupMenu(context, mButton); + mMenu = mPopupMenu.getMenu(); + mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + //MenuInflater是用来实例化Menu目录下的Menu布局文件 + //根据ID来确认menu的内容选项 + mButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mPopupMenu.show(); + } + }); + } + + public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { + if (mPopupMenu != null) { + mPopupMenu.setOnMenuItemClickListener(listener); + }//设置菜单的监听 + + } + + public MenuItem findItem(int id) { + //对于菜单选项的初始化,根据索引搜索菜单需要的选项 + return mMenu.findItem(id); + } + + public void setTitle(CharSequence title) { + //布局文件,设置标题 + mButton.setText(title); + } +} diff --git a/doc_/标注/210340041_郑培萱/FoldersListAdapter.java b/doc_/标注/210340041_郑培萱/FoldersListAdapter.java new file mode 100644 index 0000000..9afe3ec --- /dev/null +++ b/doc_/标注/210340041_郑培萱/FoldersListAdapter.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.database.Cursor; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + + +public class FoldersListAdapter extends CursorAdapter { + //CursorAdapter是Cursor和ListView的接口 + //FoldersListAdapter继承了CursorAdapter的类 + //主要作用是便签数据库和用户的交互 + //这里就是用folder(文件夹)的形式展现给用户 + public static final String [] PROJECTION = { + NoteColumns.ID, + NoteColumns.SNIPPET + };//调用数据库中便签的ID和片段 + + 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 + }//数据库操作 + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + //ViewGroup是容器 + return new FolderListItem(context); + }//创建一个文件夹,对于各文件夹中子标签的初始化 + + @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); + }//根据数据库中标签的ID得到标签的各项内容 + + + 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); + } + } + +} diff --git a/doc_/标注/210340041_郑培萱/NotesListItem.java b/doc_/标注/210340041_郑培萱/NotesListItem.java new file mode 100644 index 0000000..e3322de --- /dev/null +++ b/doc_/标注/210340041_郑培萱/NotesListItem.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.text.format.DateUtils; +import android.view.View; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser.NoteItemBgResources; + +//创建便签列表项目选项 +public class NotesListItem extends LinearLayout { + private ImageView mAlert;//闹钟图片 + private TextView mTitle;//标题 + private TextView mTime;//时间 + private TextView mCallName; + private NoteItemData mItemData;//标签数据 + private CheckBox mCheckBox;//打钩框 + /*初始化基本信息*/ + public NotesListItem(Context context) { + super(context);//super()它的主要作用是调整调用父类构造函数的顺序 + inflate(context, R.layout.note_item, this);//Inflate可用于将一个xml中定义的布局控件找出来,这里的xml是r。layout + //findViewById用于从contentView中查找指定ID的View,转换出来的形式根据需要而定; + mAlert = (ImageView) findViewById(R.id.iv_alert_icon); + mTitle = (TextView) findViewById(R.id.tv_title); + mTime = (TextView) findViewById(R.id.tv_time); + mCallName = (TextView) findViewById(R.id.tv_name); + mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); + } + ///根据data的属性对各个控件的属性的控制,主要是可见性Visibility,内容setText,格式setTextAppearance + public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { + if (choiceMode && data.getType() == Notes.TYPE_NOTE) { + mCheckBox.setVisibility(View.VISIBLE);///设置可见行为可见 + mCheckBox.setChecked(checked);///格子打钩 + } else { + mCheckBox.setVisibility(View.GONE); + } + + mItemData = data; + ///设置控件属性,一共三种情况,由data的id和父id是否与保存到文件夹的id一致来决定 + if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + mCallName.setVisibility(View.GONE); + mAlert.setVisibility(View.VISIBLE); + //设置该textview的style + mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); + //settext为设置内容 + mTitle.setText(context.getString(R.string.call_record_folder_name) + + context.getString(R.string.format_folder_files_count, data.getNotesCount())); + mAlert.setImageResource(R.drawable.call_record); + } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { + mCallName.setVisibility(View.VISIBLE); + mCallName.setText(data.getCallName()); + mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); + mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + ///关于闹钟的设置 + if (data.hasAlert()) { + mAlert.setImageResource(R.drawable.clock);//图片来源的设置 + mAlert.setVisibility(View.VISIBLE); + } else { + mAlert.setVisibility(View.GONE); + } + } else { + mCallName.setVisibility(View.GONE); + mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); + ///设置title格式 + if (data.getType() == Notes.TYPE_FOLDER) { + mTitle.setText(data.getSnippet() + + context.getString(R.string.format_folder_files_count, + data.getNotesCount())); + mAlert.setVisibility(View.GONE); + } else { + mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + if (data.hasAlert()) { + mAlert.setImageResource(R.drawable.clock);///设置图片来源 + mAlert.setVisibility(View.VISIBLE); + } else { + mAlert.setVisibility(View.GONE); + } + } + }///设置内容,获取相关时间,从data里编辑的日期中获取 + mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + + setBackground(data); + } + //根据data的文件属性来设置背景 + private void setBackground(NoteItemData data) { + int id = data.getBgColorId(); + //,若是note型文件,则4种情况,对于4种不同情况的背景来源 + if (data.getType() == Notes.TYPE_NOTE) { + //单个数据并且只有一个子文件夹 + if (data.isSingle() || data.isOneFollowingFolder()) { + setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); + } else if (data.isLast()) {//是最后一个数据 + setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); + } else if (data.isFirst() || data.isMultiFollowingFolder()) {//是一个数据并有多个子文件夹 + setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); + } else { + setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); + } + } else { + //若不是note直接调用文件夹的背景来源 + setBackgroundResource(NoteItemBgResources.getFolderBgRes()); + } + } + + public NoteItemData getItemData() { + return mItemData; + } +} diff --git a/doc_/标注/210340041_郑培萱/NotesPreferenceActivity.java b/doc_/标注/210340041_郑培萱/NotesPreferenceActivity.java new file mode 100644 index 0000000..8ee2575 --- /dev/null +++ b/doc_/标注/210340041_郑培萱/NotesPreferenceActivity.java @@ -0,0 +1,515 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.ActionBar; +import android.app.AlertDialog; +import android.content.BroadcastReceiver; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.Preference; +import android.preference.Preference.OnPreferenceClickListener; +import android.preference.PreferenceActivity; +import android.preference.PreferenceCategory; +import android.text.TextUtils; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.remote.GTaskSyncService; + + +/* + *该类功能:NotesPreferenceActivity,在小米便签中主要实现的是对背景颜色和字体大小的数据储存。 + * 继承了PreferenceActivity主要功能为对系统信息和配置进行自动保存的Activity + */ +public class NotesPreferenceActivity extends PreferenceActivity { + public static final String PREFERENCE_NAME = "notes_preferences"; + //优先名 + public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; + //同步账号 + public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; + //同步时间 + public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; + + private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; + //同步密码 + private static final String AUTHORITIES_FILTER_KEY = "authorities"; + //本地密码 + private PreferenceCategory mAccountCategory; + //账户分组 + private GTaskReceiver mReceiver; + //同步任务接收器 + private Account[] mOriAccounts; + //账户 + private boolean mHasAddedAccount; + //账户的hash标记 + @Override + /* + *函数功能:创建一个activity,在函数里要完成所有的正常静态设置 + *参数:Bundle icicle:存放了 activity 当前的状态 + *函数实现:如下注释 + */ + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + //先执行父类的创建函数 + /* using the app icon for navigation */ + getActionBar().setDisplayHomeAsUpEnabled(true); + //给左上角图标的左边加上一个返回的图标 + addPreferencesFromResource(R.xml.preferences); + //添加xml来源并显示 xml + mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); + //根据同步账户关键码来初始化分组 + mReceiver = new GTaskReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); + registerReceiver(mReceiver, filter); + //初始化同步组件 + mOriAccounts = null; + View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); + //获取listvivew,ListView的作用:用于列出所有选择 + getListView().addHeaderView(header, null, true); + //在listview组件上方添加其他组件 + } + + @Override + /* + * 函数功能:activity交互功能的实现,用于接受用户的输入 + * 函数实现:如下注释 + */ + protected void onResume() { + //先执行父类 的交互实现 + super.onResume(); + + // need to set sync account automatically if user has added a new + // account + if (mHasAddedAccount) { + //若用户新加了账户则自动设置同步账户 + Account[] accounts = getGoogleAccounts(); + //获取google同步账户 + if (mOriAccounts != null && accounts.length > mOriAccounts.length) { + //若原账户不为空且当前账户有增加 + for (Account accountNew : accounts) { + boolean found = false; + for (Account accountOld : mOriAccounts) { + if (TextUtils.equals(accountOld.name, accountNew.name)) { + //更新账户 + found = true; + //若是没有找到旧的账户,那么同步账号中就只添加新账户 + break; + } + } + if (!found) { + setSyncAccount(accountNew.name); + break; + } + } + } + } + + refreshUI(); + //刷新标签界面 + } + + @Override + /* + * 函数功能:销毁一个activity + * 函数实现:如下注释 + */ + protected void onDestroy() { + if (mReceiver != null) { + unregisterReceiver(mReceiver); + //注销接收器 + } + super.onDestroy(); + //执行父类的销毁动作 + } + /* + * 函数功能:重新设置账户信息 + * 函数实现:如下注释 + */ + private void loadAccountPreference() { + mAccountCategory.removeAll(); + //销毁所有的分组 + Preference accountPref = new Preference(this); + //建立首选项 + final String defaultAccount = getSyncAccountName(this); + accountPref.setTitle(getString(R.string.preferences_account_title)); + accountPref.setSummary(getString(R.string.preferences_account_summary)); + //设置首选项的大标题和小标题 + accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + public boolean onPreferenceClick(Preference preference) { + //建立监听器 + if (!GTaskSyncService.isSyncing()) { + if (TextUtils.isEmpty(defaultAccount)) { + // the first time to set account + //若是第一次建立账户显示选择账户提示对话框 + showSelectAccountAlertDialog(); + } else { + // if the account has already been set, we need to promp + // user about the risk + //若是已经建立则显示修改对话框并进行修改操作 + showChangeAccountConfirmAlertDialog(); + } + } else { + //若在没有同步的情况下,则在toast中显示不能修改 + Toast.makeText(NotesPreferenceActivity.this, + R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) + .show(); + } + return true; + } + }); + //根据新建首选项编辑新的账户分组 + mAccountCategory.addPreference(accountPref); + } + /* + *函数功能:设置按键的状态和最后同步的时间 + *函数实现:如下注释 + */ + private void loadSyncButton() { + Button syncButton = (Button) findViewById(R.id.preference_sync_button); + TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); + //获取同步按钮控件和最终同步时间的的窗口 + //设置按钮的状态 + // set button state + if (GTaskSyncService.isSyncing()) { + //若是在同步状态下 + syncButton.setText(getString(R.string.preferences_button_sync_cancel)); + syncButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + GTaskSyncService.cancelSync(NotesPreferenceActivity.this); + } + }); + //设置按钮显示的文本为“取消同步”以及监听器 + } else { + syncButton.setText(getString(R.string.preferences_button_sync_immediately)); + syncButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + GTaskSyncService.startSync(NotesPreferenceActivity.this); + } + }); + //若是不同步则设置按钮显示的文本为“立即同步”以及对应监听器 + } + syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); + //设置按键可用还是不可用 + // set last sync time + // 设置最终同步时间 + if (GTaskSyncService.isSyncing()) { + //若是在同步的情况下 + lastSyncTimeView.setText(GTaskSyncService.getProgressString()); + lastSyncTimeView.setVisibility(View.VISIBLE); + // 根据当前同步服务器设置时间显示框的文本以及可见性 + } else { + //若是非同步情况 + long lastSyncTime = getLastSyncTime(this); + if (lastSyncTime != 0) { + lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time, + DateFormat.format(getString(R.string.preferences_last_sync_time_format), + lastSyncTime))); + lastSyncTimeView.setVisibility(View.VISIBLE); + //则根据最后同步时间的信息来编辑时间显示框的文本内容和可见性 + + + } else { + //若时间为空直接设置为不可见状态 + lastSyncTimeView.setVisibility(View.GONE); + } + } + } + /* + *函数功能:刷新标签界面 + *函数实现:调用上文设置账号和设置按键两个函数来实现 + */ + private void refreshUI() { + loadAccountPreference(); + loadSyncButton(); + } + /* + * 函数功能:显示账户选择的对话框并进行账户的设置 + * 函数实现:如下注释 + */ + private void showSelectAccountAlertDialog() { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + //创建一个新的对话框 + View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); + TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); + titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); + TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); + subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); + //设置标题以及子标题的内容 + dialogBuilder.setCustomTitle(titleView); + dialogBuilder.setPositiveButton(null, null); + //设置对话框的自定义标题,建立一个YES的按钮 + + Account[] accounts = getGoogleAccounts(); + String defAccount = getSyncAccountName(this);//获取同步账户信息 + + mOriAccounts = accounts; + mHasAddedAccount = false; + + if (accounts.length > 0) { //若账户不为空 + CharSequence[] items = new CharSequence[accounts.length]; + final CharSequence[] itemMapping = items; + int checkedItem = -1; + int index = 0; + for (Account account : accounts) { + if (TextUtils.equals(account.name, defAccount)) { + checkedItem = index; + //在账户列表中查询到所需账户 + } + items[index++] = account.name; + } + dialogBuilder.setSingleChoiceItems(items, checkedItem, + //在对话框建立一个单选的复选框 + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + setSyncAccount(itemMapping[which].toString()); + dialog.dismiss(); + //取消对话框 + refreshUI(); + }//设置点击后执行的事件,包括检录新同步账户和刷新标签界面 + });//建立对话框网络版的监听器 + } + + View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); + dialogBuilder.setView(addAccountView); + //给新加账户对话框设置自定义样式 + + final AlertDialog dialog = dialogBuilder.show(); + //显示对话框 + addAccountView.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + mHasAddedAccount = true; + //将新加账户的hash置true + Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); + //建立网络建立组件 + + intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { + "gmail-ls" + }); + startActivityForResult(intent, -1); + //跳回上一个选项 + dialog.dismiss(); + } + }); + //建立新加账户对话框的监听器 + } + /* + * 函数功能:显示账户选择对话框和相关账户操作 + * 函数实现:如下注释 + */ + private void showChangeAccountConfirmAlertDialog() { + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + //创建一个新的对话框 + View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); + TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); + titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, + getSyncAccountName(this))); + TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); + subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); + //根据同步修改的账户信息设置标题以及子标题的内容 + + dialogBuilder.setCustomTitle(titleView); + //设置对话框的自定义标题 + CharSequence[] menuItemArray = new CharSequence[] { + getString(R.string.preferences_menu_change_account), + getString(R.string.preferences_menu_remove_account), + getString(R.string.preferences_menu_cancel) + }; + //定义一些标记字符串 + + dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { + //设置对话框要显示的一个list,用于显示几个命令时,即change,remove,cancel + public void onClick(DialogInterface dialog, int which) { + //按键功能,由which来决定 + + if (which == 0) { + //进入账户选择对话框 + showSelectAccountAlertDialog(); + } else if (which == 1) { + //删除账户并且跟新便签界面 + removeSyncAccount(); + refreshUI(); + } + } + }); + dialogBuilder.show(); + //显示对话框 + } + /* + *函数功能:获取谷歌账户 + *函数实现:通过账户管理器直接获取 + */ + private Account[] getGoogleAccounts() { + AccountManager accountManager = AccountManager.get(this); + return accountManager.getAccountsByType("com.google"); + } + /* + * 函数功能:设置同步账户 + * 函数实现:如下注释: + */ + private void setSyncAccount(String account) { + if (!getSyncAccountName(this).equals(account)) { + //假如该账号不在同步账号列表中 + SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + //编辑共享的首选项 + + if (account != null) { + editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); + } else { + editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); + } + //将该账号加入到首选项中 + editor.commit(); + //提交修改的数据 + // clean up last sync time + setLastSyncTime(this, 0); + //将最后同步时间清零 + // clean up local gtask related info + new Thread(new Runnable() { + public void run() { + ContentValues values = new ContentValues(); + values.put(NoteColumns.GTASK_ID, ""); + values.put(NoteColumns.SYNC_ID, 0); + getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); + } + }).start(); + //重置当地同步任务的信息 + Toast.makeText(NotesPreferenceActivity.this, + getString(R.string.preferences_toast_success_set_accout, account), + Toast.LENGTH_SHORT).show(); + //将toast的文本信息置为“设置账户成功”并显示出来 + + } + } + /* + * 函数功能:删除同步账户 + * 函数实现:如下注释: + */ + private void removeSyncAccount() { + SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + //设置共享首选项 + if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { + editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); + //假如当前首选项中有账户就删除 + } + if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) { + editor.remove(PREFERENCE_LAST_SYNC_TIME); + //删除当前首选项中有账户时间 + } + editor.commit(); + //提交更新后的数据 + + // clean up local gtask related info + new Thread(new Runnable() { + public void run() { + ContentValues values = new ContentValues(); + values.put(NoteColumns.GTASK_ID, ""); + values.put(NoteColumns.SYNC_ID, 0); + getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); + } + }).start(); + //重置当地同步任务的信息 + } + /* + * 函数功能:获取同步账户名称 + * 函数实现:通过共享的首选项里的信息直接获取 + */ + public static String getSyncAccountName(Context context) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, + Context.MODE_PRIVATE); + return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); + } + /* + * 函数功能:设置最终同步的时间 + * 函数实现:如下注释 + */ + public static void setLastSyncTime(Context context, long time) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, + Context.MODE_PRIVATE); + SharedPreferences.Editor editor = settings.edit(); + // 从共享首选项中找到相关账户并获取其编辑器 + editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); + editor.commit(); + //编辑最终同步时间并提交更新 + } + /* + * 函数功能:获取最终同步时间 + * 函数实现:通过共享的首选项里的信息直接获取 + */ + public static long getLastSyncTime(Context context) { + SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, + Context.MODE_PRIVATE); + return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); + } + /* + * 函数功能:接受同步信息 + * 函数实现:继承BroadcastReceiver + */ + private class GTaskReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + refreshUI(); + if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { + //获取随广播而来的Intent中的同步服务的数据 + TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); + syncStatus.setText(intent + .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); + //通过获取的数据在设置系统的状态 + } + + } + } + /* + * 函数功能:处理菜单的选项 + * 函数实现:如下注释 + * 参数:MenuItem菜单选项 + */ + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + //根据选项的id选择,这里只有一个主页 + case android.R.id.home: + Intent intent = new Intent(this, NotesListActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + return true; + //在主页情况下在创建连接组件intent,发出清空的信号并开始一个相应的activity + + default: + return false; + } + } +} diff --git a/doc_/标注/210340044_左丹妮/Contact.docx b/doc_/标注/210340044_左丹妮/Contact.docx new file mode 100644 index 0000000..50490b7 --- /dev/null +++ b/doc_/标注/210340044_左丹妮/Contact.docx @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.data; + +import android.content.Context; +import android.database.Cursor; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; +import android.telephony.PhoneNumberUtils; +import android.util.Log; + +import java.util.HashMap; +//change +public class Contact { //联系人 + private static HashMap sContactCache; + private static final String TAG = "Contact"; + + // 定义字符串CALLER_ID_SELECTION + private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + + " AND " + Data.RAW_CONTACT_ID + " IN " + + "(SELECT raw_contact_id " + + " FROM phone_lookup" + + " WHERE min_match = '+')"; + + // 获取联系人 + public static String getContact(Context context, String phoneNumber) { + if(sContactCache == null) { + sContactCache = new HashMap(); + } + + // 查找HashMap中是否已有phoneNumber信息 + if(sContactCache.containsKey(phoneNumber)) { + return sContactCache.get(phoneNumber); + } + + String selection = CALLER_ID_SELECTION.replace("+", + PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + // 查找数据库中phoneNumber的信息 + Cursor cursor = context.getContentResolver().query( + Data.CONTENT_URI, + new String [] { Phone.DISPLAY_NAME }, + selection, + new String[] { phoneNumber }, + null); + + // 判定查询结果 + // moveToFirst()返回第一条 + if (cursor != null && cursor.moveToFirst()) { + try { + // 找到相关信息 + String name = cursor.getString(0); + sContactCache.put(phoneNumber, name); + return name; + // 异常 + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, " Cursor get string error " + e.toString()); + return null; + } finally { + cursor.close(); + } + // 未找到相关信息 + } else { + Log.d(TAG, "No contact matched with number:" + phoneNumber); + return null; + } + } +} diff --git a/doc_/标注/210340044_左丹妮/Contact.java b/doc_/标注/210340044_左丹妮/Contact.java new file mode 100644 index 0000000..50490b7 --- /dev/null +++ b/doc_/标注/210340044_左丹妮/Contact.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.data; + +import android.content.Context; +import android.database.Cursor; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; +import android.telephony.PhoneNumberUtils; +import android.util.Log; + +import java.util.HashMap; +//change +public class Contact { //联系人 + private static HashMap sContactCache; + private static final String TAG = "Contact"; + + // 定义字符串CALLER_ID_SELECTION + private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + + " AND " + Data.RAW_CONTACT_ID + " IN " + + "(SELECT raw_contact_id " + + " FROM phone_lookup" + + " WHERE min_match = '+')"; + + // 获取联系人 + public static String getContact(Context context, String phoneNumber) { + if(sContactCache == null) { + sContactCache = new HashMap(); + } + + // 查找HashMap中是否已有phoneNumber信息 + if(sContactCache.containsKey(phoneNumber)) { + return sContactCache.get(phoneNumber); + } + + String selection = CALLER_ID_SELECTION.replace("+", + PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + // 查找数据库中phoneNumber的信息 + Cursor cursor = context.getContentResolver().query( + Data.CONTENT_URI, + new String [] { Phone.DISPLAY_NAME }, + selection, + new String[] { phoneNumber }, + null); + + // 判定查询结果 + // moveToFirst()返回第一条 + if (cursor != null && cursor.moveToFirst()) { + try { + // 找到相关信息 + String name = cursor.getString(0); + sContactCache.put(phoneNumber, name); + return name; + // 异常 + } catch (IndexOutOfBoundsException e) { + Log.e(TAG, " Cursor get string error " + e.toString()); + return null; + } finally { + cursor.close(); + } + // 未找到相关信息 + } else { + Log.d(TAG, "No contact matched with number:" + phoneNumber); + return null; + } + } +} diff --git a/doc_/标注/210340044_左丹妮/Data_Notes_NoteColumns.jpg b/doc_/标注/210340044_左丹妮/Data_Notes_NoteColumns.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c74cc98eb959e295cf8510b56b2f395f961912bc GIT binary patch literal 44288 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 q@}u0*&=^e&+n mGTaskListHashMap; + + private HashMap mGTaskHashMap; + + private HashMap mMetaHashMap; + + private TaskList mMetaList; + + private HashSet mLocalDeleteIdMap; + + private HashMap mGidToNid; + + private HashMap mNidToGid; + + private GTaskManager() { //对象初始化函数 + mSyncing = false; //正在同步,flase代表未执行 + mCancelled = false; //全局标识,flase代表可以执行 + mGTaskListHashMap = new HashMap(); //<>代表Java的泛型,就是创建一个用类型作为参数的类。 + mGTaskHashMap = new HashMap(); + mMetaHashMap = new HashMap(); + mMetaList = null; + mLocalDeleteIdMap = new HashSet(); + mGidToNid = new HashMap(); //GoogleID to NodeID?? + mNidToGid = new HashMap(); //NodeID to GoogleID???通过hashmap散列表建立映射 + } + /** + * 包含关键字synchronized,语言级同步,指明该函数可能运行在多线程的环境下。 + * 功能:类初始化函数 + * @author TTS + * @return GtaskManger + */ + + public static synchronized GTaskManager getInstance() { //可能运行在多线程环境下,使用语言级同步--synchronized + if (mInstance == null) { + mInstance = new GTaskManager(); + } + return mInstance; + } + /** + * 包含关键字synchronized,语言级同步,指明该函数可能运行在多线程的环境下。 + * @author TTS + * @param activity + */ + + public synchronized void setActivityContext(Activity activity) { + // used for getting authtoken + mActivity = activity; + } + + /** + * 核心函数 + * 功能:实现了本地同步操作和远端同步操作 + * @author TTS + * @param context-----获取上下文 + * @param asyncTask-------用于同步的异步操作类 + * @return int + */ + public int sync(Context context, GTaskASyncTask asyncTask) { //核心函数 + if (mSyncing) { + Log.d(TAG, "Sync is in progress"); //创建日志文件(调试信息),debug + return STATE_SYNC_IN_PROGRESS; + } + mContext = context; + mContentResolver = mContext.getContentResolver(); + mSyncing = true; + mCancelled = false; + mGTaskListHashMap.clear(); + mGTaskHashMap.clear(); + mMetaHashMap.clear(); + mLocalDeleteIdMap.clear(); + mGidToNid.clear(); + mNidToGid.clear(); + + try { + GTaskClient client = GTaskClient.getInstance(); + //getInstance即为创建一个实例,client--客户机 + client.resetUpdateArray(); + //JSONArray类型,reset即置为NULL + + // login google task + if (!mCancelled) { + if (!client.login(mActivity)) { + throw new NetworkFailureException("login google task failed"); + } + } + + // get the task list from google + asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); + initGTaskList(); + //获取Google上的JSONtasklist转为本地TaskList + + // do content sync work + asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); + syncContent(); + } catch (NetworkFailureException e) { //分为两种异常,此类异常为网络异常 + Log.e(TAG, e.toString()); //创建日志文件(调试信息),error + return STATE_NETWORK_ERROR; + } catch (ActionFailureException e) { //此类异常为操作异常 + Log.e(TAG, e.toString()); + return STATE_INTERNAL_ERROR; + } catch (Exception e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + return STATE_INTERNAL_ERROR; + } finally { + mGTaskListHashMap.clear(); + mGTaskHashMap.clear(); + mMetaHashMap.clear(); + mLocalDeleteIdMap.clear(); + mGidToNid.clear(); + mNidToGid.clear(); + mSyncing = false; + } + + return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; + } + /** + *功能:初始化GtaskList,获取Google上的JSONtasklist转为本地TaskList。 + *获得的数据存储在mMetaList,mGTaskListHashMap,mGTaskHashMap + *@author TTS + *@exception NetworkFailureException + *@return void + */ + + private void initGTaskList() throws NetworkFailureException { + if (mCancelled) + return; + GTaskClient client = GTaskClient.getInstance();//getInstance即为创建一个实例,client应指远端客户机 + try { + //Json对象是Name Value对(即子元素)的无序集合,相当于一个Map对象。JsonObject类是bantouyan-json库对Json对象的抽象,提供操纵Json对象的各种方法。 + //其格式为{"key1":value1,"key2",value2....};key 必须是字符串。 + //因为ajax请求不刷新页面,但配合js可以实现局部刷新,因此json常常被用来作为异步请求的返回对象使用。 + JSONArray jsTaskLists = client.getTaskLists(); //原注释为get task list------lists??? + + // init meta list first + mMetaList = null; //TaskList类型 + for (int i = 0; i < jsTaskLists.length(); i++) { + JSONObject object = jsTaskLists.getJSONObject(i); //JSONObject与JSONArray一个为对象,一个为数组。此处取出单个JASONObject + String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); + String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); + + if (name + .equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + mMetaList = new TaskList(); //MetaList意为元表,Tasklist类型,此处为初始化 + mMetaList.setContentByRemoteJSON(object); //将JSON中部分数据复制到自己定义的对象中相对应的数据:name->mname... + + // load meta data + JSONArray jsMetas = client.getTaskList(gid); //原注释为get action_list------list??? + for (int j = 0; j < jsMetas.length(); j++) { + object = (JSONObject) jsMetas.getJSONObject(j); + MetaData metaData = new MetaData(); //继承自Node + metaData.setContentByRemoteJSON(object); + if (metaData.isWorthSaving()) { //if not worth to save,metadata将不加入mMetaList + mMetaList.addChildTask(metaData); + if (metaData.getGid() != null) { + mMetaHashMap.put(metaData.getRelatedGid(), metaData); + } + } + } + } + } + + // create meta list if not existed + if (mMetaList == null) { + mMetaList = new TaskList(); + mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_META); + GTaskClient.getInstance().createTaskList(mMetaList); + } + + // init task list + for (int i = 0; i < jsTaskLists.length(); i++) { + JSONObject object = jsTaskLists.getJSONObject(i); + String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); + //通过getString函数传入本地某个标志数据的名称,获取其在远端的名称。 + String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); + + if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) + && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_META)) { + TaskList tasklist = new TaskList(); //继承自Node + tasklist.setContentByRemoteJSON(object); + mGTaskListHashMap.put(gid, tasklist); + mGTaskHashMap.put(gid, tasklist); //为什么加两遍??? + + // load tasks + JSONArray jsTasks = client.getTaskList(gid); + for (int j = 0; j < jsTasks.length(); j++) { + object = (JSONObject) jsTasks.getJSONObject(j); + gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); + Task task = new Task(); + task.setContentByRemoteJSON(object); + if (task.isWorthSaving()) { + task.setMetaInfo(mMetaHashMap.get(gid)); + tasklist.addChildTask(task); + mGTaskHashMap.put(gid, task); + } + } + } + } + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("initGTaskList: handing JSONObject failed"); + } + } + + /** + * 功能:本地内容同步操作 + * @throws NetworkFailureException + * @return 无返回值 + */ + private void syncContent() throws NetworkFailureException { //本地内容同步操作 + int syncType; + Cursor c = null; //数据库指针 + String gid; //GoogleID? + Node node; //Node包含Sync_Action的不同类型 + + mLocalDeleteIdMap.clear(); //HashSet类型 + + if (mCancelled) { + return; + } + + // for local deleted note + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type<>? AND parent_id=?)", new String[] { + String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) + }, null); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); + } + + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); + } + } else { + Log.w(TAG, "failed to query trash folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // sync folder first + syncFolder(); + + // for note existing in database + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type=? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); //通过hashmap建立联系 + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); //通过hashmap建立联系 + syncType = node.getSyncAction(c); + } else { + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + // local add + syncType = Node.SYNC_ACTION_ADD_REMOTE; + } else { + // remote delete + syncType = Node.SYNC_ACTION_DEL_LOCAL; + } + } + doContentSync(syncType, node, c); + } + } else { + Log.w(TAG, "failed to query existing note in database"); + } + + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // go through remaining items + Iterator> iter = mGTaskHashMap.entrySet().iterator(); //Iterator迭代器 + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + node = entry.getValue(); + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + } + + // mCancelled can be set by another thread, so we neet to check one by + // one + // clear local delete table + if (!mCancelled) { + if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { + throw new ActionFailureException("failed to batch-delete local deleted notes"); + } + } + + // refresh local sync id + if (!mCancelled) { + GTaskClient.getInstance().commitUpdate(); + refreshLocalSyncId(); + } + + } + /** + * 功能: + * @author TTS + * @throws NetworkFailureException + */ + + private void syncFolder() throws NetworkFailureException { + Cursor c = null; + String gid; + Node node; + int syncType; + + if (mCancelled) { + return; + } + + // for root folder + try { + c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, + Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); + if (c != null) { + c.moveToNext(); + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); + mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); + // for system folder, only update remote name if necessary + if (!node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) + doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); + } else { + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); + } + } else { + Log.w(TAG, "failed to query root folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // for call-note folder + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", + new String[] { + String.valueOf(Notes.ID_CALL_RECORD_FOLDER) + }, null); + if (c != null) { + if (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); + mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); + // for system folder, only update remote name if + // necessary + if (!node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_CALL_NOTE)) + doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); + } else { + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); + } + } + } else { + Log.w(TAG, "failed to query call note folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // for local existing folders + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type=? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); + syncType = node.getSyncAction(c); + } else { + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + // local add + syncType = Node.SYNC_ACTION_ADD_REMOTE; + } else { + // remote delete + syncType = Node.SYNC_ACTION_DEL_LOCAL; + } + } + doContentSync(syncType, node, c); + } + } else { + Log.w(TAG, "failed to query existing folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // for remote add folders + Iterator> iter = mGTaskListHashMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + gid = entry.getKey(); + node = entry.getValue(); + if (mGTaskHashMap.containsKey(gid)) { + mGTaskHashMap.remove(gid); + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + } + } + + if (!mCancelled) + GTaskClient.getInstance().commitUpdate(); + } + + /** + * 功能:syncType分类,addLocalNode,addRemoteNode,deleteNode,updateLocalNode,updateRemoteNode + * @author TTS + * @param syncType + * @param node + * @param c + * @throws NetworkFailureException + */ + + private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + MetaData meta; + switch (syncType) { + case Node.SYNC_ACTION_ADD_LOCAL: + addLocalNode(node); + break; + case Node.SYNC_ACTION_ADD_REMOTE: + addRemoteNode(node, c); + break; + case Node.SYNC_ACTION_DEL_LOCAL: + meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); + if (meta != null) { + GTaskClient.getInstance().deleteNode(meta); + } + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); + break; + case Node.SYNC_ACTION_DEL_REMOTE: + meta = mMetaHashMap.get(node.getGid()); + if (meta != null) { + GTaskClient.getInstance().deleteNode(meta); + } + GTaskClient.getInstance().deleteNode(node); + break; + case Node.SYNC_ACTION_UPDATE_LOCAL: + updateLocalNode(node, c); + break; + case Node.SYNC_ACTION_UPDATE_REMOTE: + updateRemoteNode(node, c); + break; + case Node.SYNC_ACTION_UPDATE_CONFLICT: + // merging both modifications maybe a good idea + // right now just use local update simply + updateRemoteNode(node, c); + break; + case Node.SYNC_ACTION_NONE: + break; + case Node.SYNC_ACTION_ERROR: + default: + throw new ActionFailureException("unkown sync action type"); + } + } + + /** + * 功能:本地增加Node + * @author TTS + * @param node + * @throws NetworkFailureException + */ + private void addLocalNode(Node node) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote; + if (node instanceof TaskList) { + if (node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { + sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); + } else if (node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { + sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); + } else { + sqlNote = new SqlNote(mContext); + sqlNote.setContent(node.getLocalJSONFromContent()); + sqlNote.setParentId(Notes.ID_ROOT_FOLDER); + } + } else { + sqlNote = new SqlNote(mContext); + JSONObject js = node.getLocalJSONFromContent(); + try { + if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + if (note.has(NoteColumns.ID)) { + long id = note.getLong(NoteColumns.ID); + if (DataUtils.existInNoteDatabase(mContentResolver, id)) { + // the id is not available, have to create a new one + note.remove(NoteColumns.ID); + } + } + } + + if (js.has(GTaskStringUtils.META_HEAD_DATA)) { + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); + if (data.has(DataColumns.ID)) { + long dataId = data.getLong(DataColumns.ID); + if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { + // the data id is not available, have to create + // a new one + data.remove(DataColumns.ID); + } + } + } + + } + } catch (JSONException e) { + Log.w(TAG, e.toString()); + e.printStackTrace(); + } + sqlNote.setContent(js); + + Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); + if (parentId == null) { + Log.e(TAG, "cannot find task's parent id locally"); + throw new ActionFailureException("cannot add local node"); + } + sqlNote.setParentId(parentId.longValue()); + } + + // create the local node + sqlNote.setGtaskId(node.getGid()); + sqlNote.commit(false); + + // update gid-nid mapping + mGidToNid.put(node.getGid(), sqlNote.getId()); + mNidToGid.put(sqlNote.getId(), node.getGid()); + + // update meta + updateRemoteMeta(node.getGid(), sqlNote); + } + + /** + * 功能:update本地node + * @author TTS + * @param node + * ----同步操作的基础数据类型 + * @param c + * ----Cursor + * @throws NetworkFailureException + */ + private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote; + // update the note locally + sqlNote = new SqlNote(mContext, c); + sqlNote.setContent(node.getLocalJSONFromContent()); + + Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) + : new Long(Notes.ID_ROOT_FOLDER); + if (parentId == null) { + Log.e(TAG, "cannot find task's parent id locally"); + throw new ActionFailureException("cannot update local node"); + } + sqlNote.setParentId(parentId.longValue()); + sqlNote.commit(true); + + // update meta info + updateRemoteMeta(node.getGid(), sqlNote); + } + + /** + * 功能:远程增加Node + * 需要updateRemoteMeta + * @author TTS + * @param node + * ----同步操作的基础数据类型 + * @param c + * --Cursor + * @throws NetworkFailureException + */ + private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote = new SqlNote(mContext, c); //从本地mContext中获取内容 + Node n; + + // update remotely + if (sqlNote.isNoteType()) { + Task task = new Task(); + task.setContentByLocalJSON(sqlNote.getContent()); + + String parentGid = mNidToGid.get(sqlNote.getParentId()); + if (parentGid == null) { + Log.e(TAG, "cannot find task's parent tasklist"); //调试信息 + throw new ActionFailureException("cannot add remote task"); + } + mGTaskListHashMap.get(parentGid).addChildTask(task); //在本地生成的GTaskList中增加子结点 + + //登录远程服务器,创建Task + GTaskClient.getInstance().createTask(task); + n = (Node) task; + + // add meta + updateRemoteMeta(task.getGid(), sqlNote); + } else { + TaskList tasklist = null; + + // we need to skip folder if it has already existed + String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; + if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) + folderName += GTaskStringUtils.FOLDER_DEFAULT; + else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER) + folderName += GTaskStringUtils.FOLDER_CALL_NOTE; + else + folderName += sqlNote.getSnippet(); + + //iterator迭代器,通过统一的接口迭代所有的map元素 + Iterator> iter = mGTaskListHashMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + String gid = entry.getKey(); + TaskList list = entry.getValue(); + + if (list.getName().equals(folderName)) { + tasklist = list; + if (mGTaskHashMap.containsKey(gid)) { + mGTaskHashMap.remove(gid); + } + break; + } + } + + // no match we can add now + if (tasklist == null) { + tasklist = new TaskList(); + tasklist.setContentByLocalJSON(sqlNote.getContent()); + GTaskClient.getInstance().createTaskList(tasklist); + mGTaskListHashMap.put(tasklist.getGid(), tasklist); + } + n = (Node) tasklist; + } + + // update local note + sqlNote.setGtaskId(n.getGid()); + sqlNote.commit(false); + sqlNote.resetLocalModified(); + sqlNote.commit(true); + + // gid-id mapping 创建id间的映射 + mGidToNid.put(n.getGid(), sqlNote.getId()); + mNidToGid.put(sqlNote.getId(), n.getGid()); + } + + /** + * 功能:更新远端的Node,包含meta更新(updateRemoteMeta) + * @author TTS + * @param node + * ----同步操作的基础数据类型 + * @param c + * --Cursor + * @throws NetworkFailureException + */ + private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote = new SqlNote(mContext, c); + + // update remotely + node.setContentByLocalJSON(sqlNote.getContent()); + GTaskClient.getInstance().addUpdateNode(node); + //GTaskClient用途为从本地登陆远端服务器 + + // update meta + updateRemoteMeta(node.getGid(), sqlNote); + + // move task if necessary + if (sqlNote.isNoteType()) { + Task task = (Task) node; + TaskList preParentList = task.getParent(); + //preParentList为通过node获取的父节点列表 + + String curParentGid = mNidToGid.get(sqlNote.getParentId()); + //curParentGid为通过光标在数据库中找到sqlNote的mParentId,再通过mNidToGid由long类型转为String类型的Gid + if (curParentGid == null) { + Log.e(TAG, "cannot find task's parent tasklist"); + throw new ActionFailureException("cannot update remote task"); + } + TaskList curParentList = mGTaskListHashMap.get(curParentGid); + + if (preParentList != curParentList) { //? + preParentList.removeChildTask(task); + curParentList.addChildTask(task); + GTaskClient.getInstance().moveTask(task, preParentList, curParentList); + } + } + + // clear local modified flag + sqlNote.resetLocalModified(); + //commit到本地数据库 + sqlNote.commit(true); + } + + /** + * 功能:升级远程meta。 meta---元数据----计算机文件系统管理数据---管理数据的数据。 + * @author TTS + * @param gid + * ---GoogleID为String类型 + * @param sqlNote + * ---同步前的数据库操作,故使用类SqlNote + * @throws NetworkFailureException + */ + private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { + if (sqlNote != null && sqlNote.isNoteType()) { + MetaData metaData = mMetaHashMap.get(gid); + if (metaData != null) { + metaData.setMeta(gid, sqlNote.getContent()); + GTaskClient.getInstance().addUpdateNode(metaData); + } else { + metaData = new MetaData(); + metaData.setMeta(gid, sqlNote.getContent()); + mMetaList.addChildTask(metaData); + mMetaHashMap.put(gid, metaData); + GTaskClient.getInstance().createTask(metaData); + } + } + } + + /** + * 功能:刷新本地,给sync的ID对应上最后更改过的对象 + * @author TTS + * @return void + * @throws NetworkFailureException + */ + private void refreshLocalSyncId() throws NetworkFailureException { + if (mCancelled) { + return; + } + + // get the latest gtask list 获取最近的(最晚的)gtask list + mGTaskHashMap.clear(); + mGTaskListHashMap.clear(); + mMetaHashMap.clear(); + initGTaskList(); + + Cursor c = null; + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type<>? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + //query语句:五个参数,NoteColumns.TYPE + " DESC"-----为按类型递减顺序返回查询结果。 + // new String[] {String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)}------为选择参数。 + // "(type<>? AND parent_id<>?)"-------指明返回行过滤器。SqlNote.PROJECTION_NOTE--------应返回的数据列的名字。 + // Notes.CONTENT_NOTE_URI--------contentProvider包含所有数据集所对应的uri + if (c != null) { + while (c.moveToNext()) { + String gid = c.getString(SqlNote.GTASK_ID_COLUMN); + Node node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + ContentValues values = new ContentValues(); + //在ContentValues中创建键值对。准备通过contentResolver写入数据 + values.put(NoteColumns.SYNC_ID, node.getLastModified()); + mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, + //进行批量更改,选择参数为NULL,应该可以用insert替换,参数分别为表名和需要更新的value对象。 + c.getLong(SqlNote.ID_COLUMN)), values, null, null); + } else { + Log.e(TAG, "something is missed"); + throw new ActionFailureException( + "some local items don't have gid after sync"); + } + } + } else { + Log.w(TAG, "failed to query local note to refresh sync id"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + } + + /** + * 功能:获取同步账号,mAccount.name + * @author TTS + * @return String + */ + public String getSyncAccount() { + return GTaskClient.getInstance().getSyncAccount().name; + } + + /** + * 功能:取消同步,置mCancelled为true + * @author TTS + */ + public void cancelSync() { + mCancelled = true; + } +} diff --git a/doc_/标注/210340044_左丹妮/GTaskManager.java b/doc_/标注/210340044_左丹妮/GTaskManager.java new file mode 100644 index 0000000..15cf01d --- /dev/null +++ b/doc_/标注/210340044_左丹妮/GTaskManager.java @@ -0,0 +1,922 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.remote; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.util.Log; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.data.MetaData; +import net.micode.notes.gtask.data.Node; +import net.micode.notes.gtask.data.SqlNote; +import net.micode.notes.gtask.data.Task; +import net.micode.notes.gtask.data.TaskList; +import net.micode.notes.gtask.exception.ActionFailureException; +import net.micode.notes.gtask.exception.NetworkFailureException; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.GTaskStringUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; + + +public class GTaskManager { + private static final String TAG = GTaskManager.class.getSimpleName(); + + public static final int STATE_SUCCESS = 0; + + public static final int STATE_NETWORK_ERROR = 1; + + public static final int STATE_INTERNAL_ERROR = 2; + + public static final int STATE_SYNC_IN_PROGRESS = 3; + + public static final int STATE_SYNC_CANCELLED = 4; + + private static GTaskManager mInstance = null; + + private Activity mActivity; + + private Context mContext; + + private ContentResolver mContentResolver; + + private boolean mSyncing; + + private boolean mCancelled; + + private HashMap mGTaskListHashMap; + + private HashMap mGTaskHashMap; + + private HashMap mMetaHashMap; + + private TaskList mMetaList; + + private HashSet mLocalDeleteIdMap; + + private HashMap mGidToNid; + + private HashMap mNidToGid; + + private GTaskManager() { //对象初始化函数 + mSyncing = false; //正在同步,flase代表未执行 + mCancelled = false; //全局标识,flase代表可以执行 + mGTaskListHashMap = new HashMap(); //<>代表Java的泛型,就是创建一个用类型作为参数的类。 + mGTaskHashMap = new HashMap(); + mMetaHashMap = new HashMap(); + mMetaList = null; + mLocalDeleteIdMap = new HashSet(); + mGidToNid = new HashMap(); //GoogleID to NodeID?? + mNidToGid = new HashMap(); //NodeID to GoogleID???通过hashmap散列表建立映射 + } + /** + * 包含关键字synchronized,语言级同步,指明该函数可能运行在多线程的环境下。 + * 功能:类初始化函数 + * @author TTS + * @return GtaskManger + */ + + public static synchronized GTaskManager getInstance() { //可能运行在多线程环境下,使用语言级同步--synchronized + if (mInstance == null) { + mInstance = new GTaskManager(); + } + return mInstance; + } + /** + * 包含关键字synchronized,语言级同步,指明该函数可能运行在多线程的环境下。 + * @author TTS + * @param activity + */ + + public synchronized void setActivityContext(Activity activity) { + // used for getting authtoken + mActivity = activity; + } + + /** + * 核心函数 + * 功能:实现了本地同步操作和远端同步操作 + * @author TTS + * @param context-----获取上下文 + * @param asyncTask-------用于同步的异步操作类 + * @return int + */ + public int sync(Context context, GTaskASyncTask asyncTask) { //核心函数 + if (mSyncing) { + Log.d(TAG, "Sync is in progress"); //创建日志文件(调试信息),debug + return STATE_SYNC_IN_PROGRESS; + } + mContext = context; + mContentResolver = mContext.getContentResolver(); + mSyncing = true; + mCancelled = false; + mGTaskListHashMap.clear(); + mGTaskHashMap.clear(); + mMetaHashMap.clear(); + mLocalDeleteIdMap.clear(); + mGidToNid.clear(); + mNidToGid.clear(); + + try { + GTaskClient client = GTaskClient.getInstance(); + //getInstance即为创建一个实例,client--客户机 + client.resetUpdateArray(); + //JSONArray类型,reset即置为NULL + + // login google task + if (!mCancelled) { + if (!client.login(mActivity)) { + throw new NetworkFailureException("login google task failed"); + } + } + + // get the task list from google + asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); + initGTaskList(); + //获取Google上的JSONtasklist转为本地TaskList + + // do content sync work + asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); + syncContent(); + } catch (NetworkFailureException e) { //分为两种异常,此类异常为网络异常 + Log.e(TAG, e.toString()); //创建日志文件(调试信息),error + return STATE_NETWORK_ERROR; + } catch (ActionFailureException e) { //此类异常为操作异常 + Log.e(TAG, e.toString()); + return STATE_INTERNAL_ERROR; + } catch (Exception e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + return STATE_INTERNAL_ERROR; + } finally { + mGTaskListHashMap.clear(); + mGTaskHashMap.clear(); + mMetaHashMap.clear(); + mLocalDeleteIdMap.clear(); + mGidToNid.clear(); + mNidToGid.clear(); + mSyncing = false; + } + + return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; + } + /** + *功能:初始化GtaskList,获取Google上的JSONtasklist转为本地TaskList。 + *获得的数据存储在mMetaList,mGTaskListHashMap,mGTaskHashMap + *@author TTS + *@exception NetworkFailureException + *@return void + */ + + private void initGTaskList() throws NetworkFailureException { + if (mCancelled) + return; + GTaskClient client = GTaskClient.getInstance();//getInstance即为创建一个实例,client应指远端客户机 + try { + //Json对象是Name Value对(即子元素)的无序集合,相当于一个Map对象。JsonObject类是bantouyan-json库对Json对象的抽象,提供操纵Json对象的各种方法。 + //其格式为{"key1":value1,"key2",value2....};key 必须是字符串。 + //因为ajax请求不刷新页面,但配合js可以实现局部刷新,因此json常常被用来作为异步请求的返回对象使用。 + JSONArray jsTaskLists = client.getTaskLists(); //原注释为get task list------lists??? + + // init meta list first + mMetaList = null; //TaskList类型 + for (int i = 0; i < jsTaskLists.length(); i++) { + JSONObject object = jsTaskLists.getJSONObject(i); //JSONObject与JSONArray一个为对象,一个为数组。此处取出单个JASONObject + String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); + String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); + + if (name + .equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + mMetaList = new TaskList(); //MetaList意为元表,Tasklist类型,此处为初始化 + mMetaList.setContentByRemoteJSON(object); //将JSON中部分数据复制到自己定义的对象中相对应的数据:name->mname... + + // load meta data + JSONArray jsMetas = client.getTaskList(gid); //原注释为get action_list------list??? + for (int j = 0; j < jsMetas.length(); j++) { + object = (JSONObject) jsMetas.getJSONObject(j); + MetaData metaData = new MetaData(); //继承自Node + metaData.setContentByRemoteJSON(object); + if (metaData.isWorthSaving()) { //if not worth to save,metadata将不加入mMetaList + mMetaList.addChildTask(metaData); + if (metaData.getGid() != null) { + mMetaHashMap.put(metaData.getRelatedGid(), metaData); + } + } + } + } + } + + // create meta list if not existed + if (mMetaList == null) { + mMetaList = new TaskList(); + mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_META); + GTaskClient.getInstance().createTaskList(mMetaList); + } + + // init task list + for (int i = 0; i < jsTaskLists.length(); i++) { + JSONObject object = jsTaskLists.getJSONObject(i); + String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); + //通过getString函数传入本地某个标志数据的名称,获取其在远端的名称。 + String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); + + if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) + && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_META)) { + TaskList tasklist = new TaskList(); //继承自Node + tasklist.setContentByRemoteJSON(object); + mGTaskListHashMap.put(gid, tasklist); + mGTaskHashMap.put(gid, tasklist); //为什么加两遍??? + + // load tasks + JSONArray jsTasks = client.getTaskList(gid); + for (int j = 0; j < jsTasks.length(); j++) { + object = (JSONObject) jsTasks.getJSONObject(j); + gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); + Task task = new Task(); + task.setContentByRemoteJSON(object); + if (task.isWorthSaving()) { + task.setMetaInfo(mMetaHashMap.get(gid)); + tasklist.addChildTask(task); + mGTaskHashMap.put(gid, task); + } + } + } + } + } catch (JSONException e) { + Log.e(TAG, e.toString()); + e.printStackTrace(); + throw new ActionFailureException("initGTaskList: handing JSONObject failed"); + } + } + + /** + * 功能:本地内容同步操作 + * @throws NetworkFailureException + * @return 无返回值 + */ + private void syncContent() throws NetworkFailureException { //本地内容同步操作 + int syncType; + Cursor c = null; //数据库指针 + String gid; //GoogleID? + Node node; //Node包含Sync_Action的不同类型 + + mLocalDeleteIdMap.clear(); //HashSet类型 + + if (mCancelled) { + return; + } + + // for local deleted note + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type<>? AND parent_id=?)", new String[] { + String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) + }, null); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); + } + + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); + } + } else { + Log.w(TAG, "failed to query trash folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // sync folder first + syncFolder(); + + // for note existing in database + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type=? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); //通过hashmap建立联系 + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); //通过hashmap建立联系 + syncType = node.getSyncAction(c); + } else { + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + // local add + syncType = Node.SYNC_ACTION_ADD_REMOTE; + } else { + // remote delete + syncType = Node.SYNC_ACTION_DEL_LOCAL; + } + } + doContentSync(syncType, node, c); + } + } else { + Log.w(TAG, "failed to query existing note in database"); + } + + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // go through remaining items + Iterator> iter = mGTaskHashMap.entrySet().iterator(); //Iterator迭代器 + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + node = entry.getValue(); + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + } + + // mCancelled can be set by another thread, so we neet to check one by + // one + // clear local delete table + if (!mCancelled) { + if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { + throw new ActionFailureException("failed to batch-delete local deleted notes"); + } + } + + // refresh local sync id + if (!mCancelled) { + GTaskClient.getInstance().commitUpdate(); + refreshLocalSyncId(); + } + + } + /** + * 功能: + * @author TTS + * @throws NetworkFailureException + */ + + private void syncFolder() throws NetworkFailureException { + Cursor c = null; + String gid; + Node node; + int syncType; + + if (mCancelled) { + return; + } + + // for root folder + try { + c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, + Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); + if (c != null) { + c.moveToNext(); + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); + mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); + // for system folder, only update remote name if necessary + if (!node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) + doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); + } else { + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); + } + } else { + Log.w(TAG, "failed to query root folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // for call-note folder + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", + new String[] { + String.valueOf(Notes.ID_CALL_RECORD_FOLDER) + }, null); + if (c != null) { + if (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); + mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); + // for system folder, only update remote name if + // necessary + if (!node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + + GTaskStringUtils.FOLDER_CALL_NOTE)) + doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); + } else { + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); + } + } + } else { + Log.w(TAG, "failed to query call note folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // for local existing folders + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type=? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + if (c != null) { + while (c.moveToNext()) { + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); + syncType = node.getSyncAction(c); + } else { + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + // local add + syncType = Node.SYNC_ACTION_ADD_REMOTE; + } else { + // remote delete + syncType = Node.SYNC_ACTION_DEL_LOCAL; + } + } + doContentSync(syncType, node, c); + } + } else { + Log.w(TAG, "failed to query existing folder"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + + // for remote add folders + Iterator> iter = mGTaskListHashMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + gid = entry.getKey(); + node = entry.getValue(); + if (mGTaskHashMap.containsKey(gid)) { + mGTaskHashMap.remove(gid); + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + } + } + + if (!mCancelled) + GTaskClient.getInstance().commitUpdate(); + } + + /** + * 功能:syncType分类,addLocalNode,addRemoteNode,deleteNode,updateLocalNode,updateRemoteNode + * @author TTS + * @param syncType + * @param node + * @param c + * @throws NetworkFailureException + */ + + private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + MetaData meta; + switch (syncType) { + case Node.SYNC_ACTION_ADD_LOCAL: + addLocalNode(node); + break; + case Node.SYNC_ACTION_ADD_REMOTE: + addRemoteNode(node, c); + break; + case Node.SYNC_ACTION_DEL_LOCAL: + meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); + if (meta != null) { + GTaskClient.getInstance().deleteNode(meta); + } + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); + break; + case Node.SYNC_ACTION_DEL_REMOTE: + meta = mMetaHashMap.get(node.getGid()); + if (meta != null) { + GTaskClient.getInstance().deleteNode(meta); + } + GTaskClient.getInstance().deleteNode(node); + break; + case Node.SYNC_ACTION_UPDATE_LOCAL: + updateLocalNode(node, c); + break; + case Node.SYNC_ACTION_UPDATE_REMOTE: + updateRemoteNode(node, c); + break; + case Node.SYNC_ACTION_UPDATE_CONFLICT: + // merging both modifications maybe a good idea + // right now just use local update simply + updateRemoteNode(node, c); + break; + case Node.SYNC_ACTION_NONE: + break; + case Node.SYNC_ACTION_ERROR: + default: + throw new ActionFailureException("unkown sync action type"); + } + } + + /** + * 功能:本地增加Node + * @author TTS + * @param node + * @throws NetworkFailureException + */ + private void addLocalNode(Node node) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote; + if (node instanceof TaskList) { + if (node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { + sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); + } else if (node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { + sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); + } else { + sqlNote = new SqlNote(mContext); + sqlNote.setContent(node.getLocalJSONFromContent()); + sqlNote.setParentId(Notes.ID_ROOT_FOLDER); + } + } else { + sqlNote = new SqlNote(mContext); + JSONObject js = node.getLocalJSONFromContent(); + try { + if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + if (note.has(NoteColumns.ID)) { + long id = note.getLong(NoteColumns.ID); + if (DataUtils.existInNoteDatabase(mContentResolver, id)) { + // the id is not available, have to create a new one + note.remove(NoteColumns.ID); + } + } + } + + if (js.has(GTaskStringUtils.META_HEAD_DATA)) { + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); + if (data.has(DataColumns.ID)) { + long dataId = data.getLong(DataColumns.ID); + if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { + // the data id is not available, have to create + // a new one + data.remove(DataColumns.ID); + } + } + } + + } + } catch (JSONException e) { + Log.w(TAG, e.toString()); + e.printStackTrace(); + } + sqlNote.setContent(js); + + Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); + if (parentId == null) { + Log.e(TAG, "cannot find task's parent id locally"); + throw new ActionFailureException("cannot add local node"); + } + sqlNote.setParentId(parentId.longValue()); + } + + // create the local node + sqlNote.setGtaskId(node.getGid()); + sqlNote.commit(false); + + // update gid-nid mapping + mGidToNid.put(node.getGid(), sqlNote.getId()); + mNidToGid.put(sqlNote.getId(), node.getGid()); + + // update meta + updateRemoteMeta(node.getGid(), sqlNote); + } + + /** + * 功能:update本地node + * @author TTS + * @param node + * ----同步操作的基础数据类型 + * @param c + * ----Cursor + * @throws NetworkFailureException + */ + private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote; + // update the note locally + sqlNote = new SqlNote(mContext, c); + sqlNote.setContent(node.getLocalJSONFromContent()); + + Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) + : new Long(Notes.ID_ROOT_FOLDER); + if (parentId == null) { + Log.e(TAG, "cannot find task's parent id locally"); + throw new ActionFailureException("cannot update local node"); + } + sqlNote.setParentId(parentId.longValue()); + sqlNote.commit(true); + + // update meta info + updateRemoteMeta(node.getGid(), sqlNote); + } + + /** + * 功能:远程增加Node + * 需要updateRemoteMeta + * @author TTS + * @param node + * ----同步操作的基础数据类型 + * @param c + * --Cursor + * @throws NetworkFailureException + */ + private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote = new SqlNote(mContext, c); //从本地mContext中获取内容 + Node n; + + // update remotely + if (sqlNote.isNoteType()) { + Task task = new Task(); + task.setContentByLocalJSON(sqlNote.getContent()); + + String parentGid = mNidToGid.get(sqlNote.getParentId()); + if (parentGid == null) { + Log.e(TAG, "cannot find task's parent tasklist"); //调试信息 + throw new ActionFailureException("cannot add remote task"); + } + mGTaskListHashMap.get(parentGid).addChildTask(task); //在本地生成的GTaskList中增加子结点 + + //登录远程服务器,创建Task + GTaskClient.getInstance().createTask(task); + n = (Node) task; + + // add meta + updateRemoteMeta(task.getGid(), sqlNote); + } else { + TaskList tasklist = null; + + // we need to skip folder if it has already existed + String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; + if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) + folderName += GTaskStringUtils.FOLDER_DEFAULT; + else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER) + folderName += GTaskStringUtils.FOLDER_CALL_NOTE; + else + folderName += sqlNote.getSnippet(); + + //iterator迭代器,通过统一的接口迭代所有的map元素 + Iterator> iter = mGTaskListHashMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + String gid = entry.getKey(); + TaskList list = entry.getValue(); + + if (list.getName().equals(folderName)) { + tasklist = list; + if (mGTaskHashMap.containsKey(gid)) { + mGTaskHashMap.remove(gid); + } + break; + } + } + + // no match we can add now + if (tasklist == null) { + tasklist = new TaskList(); + tasklist.setContentByLocalJSON(sqlNote.getContent()); + GTaskClient.getInstance().createTaskList(tasklist); + mGTaskListHashMap.put(tasklist.getGid(), tasklist); + } + n = (Node) tasklist; + } + + // update local note + sqlNote.setGtaskId(n.getGid()); + sqlNote.commit(false); + sqlNote.resetLocalModified(); + sqlNote.commit(true); + + // gid-id mapping 创建id间的映射 + mGidToNid.put(n.getGid(), sqlNote.getId()); + mNidToGid.put(sqlNote.getId(), n.getGid()); + } + + /** + * 功能:更新远端的Node,包含meta更新(updateRemoteMeta) + * @author TTS + * @param node + * ----同步操作的基础数据类型 + * @param c + * --Cursor + * @throws NetworkFailureException + */ + private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; + } + + SqlNote sqlNote = new SqlNote(mContext, c); + + // update remotely + node.setContentByLocalJSON(sqlNote.getContent()); + GTaskClient.getInstance().addUpdateNode(node); + //GTaskClient用途为从本地登陆远端服务器 + + // update meta + updateRemoteMeta(node.getGid(), sqlNote); + + // move task if necessary + if (sqlNote.isNoteType()) { + Task task = (Task) node; + TaskList preParentList = task.getParent(); + //preParentList为通过node获取的父节点列表 + + String curParentGid = mNidToGid.get(sqlNote.getParentId()); + //curParentGid为通过光标在数据库中找到sqlNote的mParentId,再通过mNidToGid由long类型转为String类型的Gid + if (curParentGid == null) { + Log.e(TAG, "cannot find task's parent tasklist"); + throw new ActionFailureException("cannot update remote task"); + } + TaskList curParentList = mGTaskListHashMap.get(curParentGid); + + if (preParentList != curParentList) { //? + preParentList.removeChildTask(task); + curParentList.addChildTask(task); + GTaskClient.getInstance().moveTask(task, preParentList, curParentList); + } + } + + // clear local modified flag + sqlNote.resetLocalModified(); + //commit到本地数据库 + sqlNote.commit(true); + } + + /** + * 功能:升级远程meta。 meta---元数据----计算机文件系统管理数据---管理数据的数据。 + * @author TTS + * @param gid + * ---GoogleID为String类型 + * @param sqlNote + * ---同步前的数据库操作,故使用类SqlNote + * @throws NetworkFailureException + */ + private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { + if (sqlNote != null && sqlNote.isNoteType()) { + MetaData metaData = mMetaHashMap.get(gid); + if (metaData != null) { + metaData.setMeta(gid, sqlNote.getContent()); + GTaskClient.getInstance().addUpdateNode(metaData); + } else { + metaData = new MetaData(); + metaData.setMeta(gid, sqlNote.getContent()); + mMetaList.addChildTask(metaData); + mMetaHashMap.put(gid, metaData); + GTaskClient.getInstance().createTask(metaData); + } + } + } + + /** + * 功能:刷新本地,给sync的ID对应上最后更改过的对象 + * @author TTS + * @return void + * @throws NetworkFailureException + */ + private void refreshLocalSyncId() throws NetworkFailureException { + if (mCancelled) { + return; + } + + // get the latest gtask list 获取最近的(最晚的)gtask list + mGTaskHashMap.clear(); + mGTaskListHashMap.clear(); + mMetaHashMap.clear(); + initGTaskList(); + + Cursor c = null; + try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type<>? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + //query语句:五个参数,NoteColumns.TYPE + " DESC"-----为按类型递减顺序返回查询结果。 + // new String[] {String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)}------为选择参数。 + // "(type<>? AND parent_id<>?)"-------指明返回行过滤器。SqlNote.PROJECTION_NOTE--------应返回的数据列的名字。 + // Notes.CONTENT_NOTE_URI--------contentProvider包含所有数据集所对应的uri + if (c != null) { + while (c.moveToNext()) { + String gid = c.getString(SqlNote.GTASK_ID_COLUMN); + Node node = mGTaskHashMap.get(gid); + if (node != null) { + mGTaskHashMap.remove(gid); + ContentValues values = new ContentValues(); + //在ContentValues中创建键值对。准备通过contentResolver写入数据 + values.put(NoteColumns.SYNC_ID, node.getLastModified()); + mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, + //进行批量更改,选择参数为NULL,应该可以用insert替换,参数分别为表名和需要更新的value对象。 + c.getLong(SqlNote.ID_COLUMN)), values, null, null); + } else { + Log.e(TAG, "something is missed"); + throw new ActionFailureException( + "some local items don't have gid after sync"); + } + } + } else { + Log.w(TAG, "failed to query local note to refresh sync id"); + } + } finally { + if (c != null) { + c.close(); + c = null; + } + } + } + + /** + * 功能:获取同步账号,mAccount.name + * @author TTS + * @return String + */ + public String getSyncAccount() { + return GTaskClient.getInstance().getSyncAccount().name; + } + + /** + * 功能:取消同步,置mCancelled为true + * @author TTS + */ + public void cancelSync() { + mCancelled = true; + } +} diff --git a/doc_/标注/210340044_左丹妮/GTaskSyncService.docx b/doc_/标注/210340044_左丹妮/GTaskSyncService.docx new file mode 100644 index 0000000..8d5c4a0 --- /dev/null +++ b/doc_/标注/210340044_左丹妮/GTaskSyncService.docx @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.remote; + +import android.app.Activity; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; + +/* + * Service是在一段不定的时间运行在后台,不和用户交互的应用组件 + * 主要方法: + * private void startSync() 启动一个同步工作 + * private void cancelSync() 取消同步 + * public void onCreate() + * public int onStartCommand(Intent intent, int flags, int startId) service生命周期的组成部分,相当于重启service(比如在被暂停之后),而不是创建一个新的service + * public void onLowMemory() 在没有内存的情况下如果存在service则结束掉这的service + * public IBinder onBind() + * public void sendBroadcast(String msg) 发送同步的相关通知 + * public static void startSync(Activity activity) + * public static void cancelSync(Context context) + * public static boolean isSyncing() 判读是否在进行同步 + * public static String getProgressString() 获取当前进度的信息 + */ + +public class GTaskSyncService extends Service { + public final static String ACTION_STRING_NAME = "sync_action_type"; + + public final static int ACTION_START_SYNC = 0; + + public final static int ACTION_CANCEL_SYNC = 1; + + public final static int ACTION_INVALID = 2; + + public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; + + public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; + + public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; + + private static GTaskASyncTask mSyncTask = null; + + private static String mSyncProgress = ""; + + //开始一个同步的工作 + private void startSync() { + if (mSyncTask == null) { + mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { + public void onComplete() { + mSyncTask = null; + sendBroadcast(""); + stopSelf(); + } + }); + sendBroadcast(""); + mSyncTask.execute(); + //这个函数让任务是以单线程队列方式或线程池队列方式运行 + } + } + + private void cancelSync() { + if (mSyncTask != null) { + mSyncTask.cancelSync(); + } + } + + @Override + public void onCreate() { //初始化一个service + mSyncTask = null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Bundle bundle = intent.getExtras(); + if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { + switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { + //两种情况,开始同步或者取消同步 + case ACTION_START_SYNC: + startSync(); + break; + case ACTION_CANCEL_SYNC: + cancelSync(); + break; + default: + break; + } + return START_STICKY; + //等待新的intent来是这个service继续运行 + } + return super.onStartCommand(intent, flags, startId); + } + + @Override + public void onLowMemory() { + if (mSyncTask != null) { + mSyncTask.cancelSync(); + } + } + + public IBinder onBind(Intent intent) { //? + return null; + } + + public void sendBroadcast(String msg) { + mSyncProgress = msg; + Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); + //创建一个新的Intent + intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); + //附加INTENT中的相应参数的值 + intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); + sendBroadcast(intent); + //发送这个通知 + } + + public static void startSync(Activity activity) { + //执行一个service,service的内容里的同步动作就是开始同步 + GTaskManager.getInstance().setActivityContext(activity); + Intent intent = new Intent(activity, GTaskSyncService.class); + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); + activity.startService(intent); + } + + public static void cancelSync(Context context) { + //执行一个service,service的内容里的同步动作就是取消同步 + Intent intent = new Intent(context, GTaskSyncService.class); + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); + context.startService(intent); + } + + public static boolean isSyncing() { + return mSyncTask != null; + } + + public static String getProgressString() { + return mSyncProgress; + } +} diff --git a/doc_/标注/210340044_左丹妮/GTaskSyncService.java b/doc_/标注/210340044_左丹妮/GTaskSyncService.java new file mode 100644 index 0000000..8d5c4a0 --- /dev/null +++ b/doc_/标注/210340044_左丹妮/GTaskSyncService.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.gtask.remote; + +import android.app.Activity; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; + +/* + * Service是在一段不定的时间运行在后台,不和用户交互的应用组件 + * 主要方法: + * private void startSync() 启动一个同步工作 + * private void cancelSync() 取消同步 + * public void onCreate() + * public int onStartCommand(Intent intent, int flags, int startId) service生命周期的组成部分,相当于重启service(比如在被暂停之后),而不是创建一个新的service + * public void onLowMemory() 在没有内存的情况下如果存在service则结束掉这的service + * public IBinder onBind() + * public void sendBroadcast(String msg) 发送同步的相关通知 + * public static void startSync(Activity activity) + * public static void cancelSync(Context context) + * public static boolean isSyncing() 判读是否在进行同步 + * public static String getProgressString() 获取当前进度的信息 + */ + +public class GTaskSyncService extends Service { + public final static String ACTION_STRING_NAME = "sync_action_type"; + + public final static int ACTION_START_SYNC = 0; + + public final static int ACTION_CANCEL_SYNC = 1; + + public final static int ACTION_INVALID = 2; + + public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; + + public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; + + public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; + + private static GTaskASyncTask mSyncTask = null; + + private static String mSyncProgress = ""; + + //开始一个同步的工作 + private void startSync() { + if (mSyncTask == null) { + mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { + public void onComplete() { + mSyncTask = null; + sendBroadcast(""); + stopSelf(); + } + }); + sendBroadcast(""); + mSyncTask.execute(); + //这个函数让任务是以单线程队列方式或线程池队列方式运行 + } + } + + private void cancelSync() { + if (mSyncTask != null) { + mSyncTask.cancelSync(); + } + } + + @Override + public void onCreate() { //初始化一个service + mSyncTask = null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Bundle bundle = intent.getExtras(); + if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { + switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { + //两种情况,开始同步或者取消同步 + case ACTION_START_SYNC: + startSync(); + break; + case ACTION_CANCEL_SYNC: + cancelSync(); + break; + default: + break; + } + return START_STICKY; + //等待新的intent来是这个service继续运行 + } + return super.onStartCommand(intent, flags, startId); + } + + @Override + public void onLowMemory() { + if (mSyncTask != null) { + mSyncTask.cancelSync(); + } + } + + public IBinder onBind(Intent intent) { //? + return null; + } + + public void sendBroadcast(String msg) { + mSyncProgress = msg; + Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); + //创建一个新的Intent + intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); + //附加INTENT中的相应参数的值 + intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); + sendBroadcast(intent); + //发送这个通知 + } + + public static void startSync(Activity activity) { + //执行一个service,service的内容里的同步动作就是开始同步 + GTaskManager.getInstance().setActivityContext(activity); + Intent intent = new Intent(activity, GTaskSyncService.class); + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); + activity.startService(intent); + } + + public static void cancelSync(Context context) { + //执行一个service,service的内容里的同步动作就是取消同步 + Intent intent = new Intent(context, GTaskSyncService.class); + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); + context.startService(intent); + } + + public static boolean isSyncing() { + return mSyncTask != null; + } + + public static String getProgressString() { + return mSyncProgress; + } +} diff --git a/doc_/标注/210340044_左丹妮/Gtask_remote_GTaskMnager.jpg b/doc_/标注/210340044_左丹妮/Gtask_remote_GTaskMnager.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5269cc6d34f1a3c2cb42875a0e57d2483e2fd5e9 GIT binary patch literal 109524 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�%Oc6wP6Cf6;zTf@MT1r{(lc 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 IZ~vM3Z-uU*d;kCd literal 0 HcmV?d00001 diff --git a/doc_/标注/210340044_左丹妮/Gtask_remote_GTaskMnager_initGTaskList.jpg b/doc_/标注/210340044_左丹妮/Gtask_remote_GTaskMnager_initGTaskList.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3cdcc779accbc912aa618833257d8bfa88ee5d28 GIT binary patch literal 106276 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

EG@OKd-cJ_oJzpJO^QBf_>&0D*p*-uGxJ7is0 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 e|IE4g*UM%3KQpoa%$M}P`&<9(FPHz%K! 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

xnttd3O) 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 z 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