Merge branch 'develop' of https://bdgit.educoder.net/pvg45a2sy/git-xiaomibianqian into lihaoyang_branch

lihaoyang_branch
李昊阳 2 years ago
commit 8554a70193

@ -0,0 +1,268 @@
/*
* 便
* 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.model;//包名
import android.content.ContentProviderOperation;//批量的更新、插入、删除数据
import android.content.ContentProviderResult;//操作结果
import android.content.ContentUris;//添加或者修改uri后面的ID
import android.content.ContentValues;//存储基本数据类型的数据
import android.content.Context;// 获取调用内容
import android.content.OperationApplicationException;//操作数据容错
import android.net.Uri;//Uri相关操作标识唯一的资源
import android.os.RemoteException;//异常处理
import android.util.Log;//输出日志,比如说出错、警告等
import net.micode.notes.data.Notes;//小米便签一些属性的操作
import net.micode.notes.data.Notes.CallNote;//调用操作
import net.micode.notes.data.Notes.DataColumns;//数据栏
import net.micode.notes.data.Notes.NoteColumns;//笔记栏
import net.micode.notes.data.Notes.TextNote;//操作区
import java.util.ArrayList;//导入Java,util,ArrayList
public class Note {//定义note类处理单个便签
private ContentValues mNoteDiffValues;//声明一个ContentValues私有变量ContentValues用来存储note与上次修改后的改动
private NoteData mNoteData;//声明一个私有变量NoteData用来记录单个便签的基本信息
private static final String TAG = "Note";//设置软件标签
/**
* Create a new note id for adding a new note to databases
*/
public static synchronized long getNewNoteId(Context context, long folderId) {
//Create a new note in the database
/*
* 便ID
* 便便IDuri
* 便IDID
* @context便
* @folderId便ID
* @return便ID
*/
ContentValues values = new ContentValues();
long createdTime = System.currentTimeMillis();
values.put(NoteColumns.CREATED_DATE, createdTime);
values.put(NoteColumns.MODIFIED_DATE, createdTime);
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.PARENT_ID, folderId);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
long noteId = 0;
try {
noteId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
noteId = 0;
}
if (noteId == -1) {
throw new IllegalStateException("Wrong note id:" + noteId);
}
return noteId;
}
public Note() {//定义两个变量用来存储便签的数据,一个是存储便签属性、一个是存储便签内容
mNoteDiffValues = new ContentValues();
mNoteData = new NoteData();
}
public void setNoteValue(String key, String value) {
/*
* 便
* key便valuemNoteDiffValues
* @key
* @value
*/
mNoteDiffValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
public void setTextData(String key, String value) {//设置数据库表格的标签文本内容的数据
mNoteData.setTextData(key, value);
}
public void setTextDataId(long id) {//设置文本数据的ID
mNoteData.setTextDataId(id);
}
public long getTextDataId() {//获取文本数据的ID
return mNoteData.mTextDataId;
}
public void setCallDataId(long id) {//设置电话号码数据的ID
mNoteData.setCallDataId(id);
}
public void setCallData(String key, String value) {//设置电话号码的数据
mNoteData.setCallData(key, value);
}
public boolean isLocalModified() {//判断便签是否进行了本地修改
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
public boolean syncNote(Context context, long noteId) {//判断对修改过的便签是否进行同步
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
if (!isLocalModified()) {
return true;
}
/**
* In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the
* note data info
*/
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
Log.e(TAG, "Update note error, should not happen");
// Do not return, fall through
}
mNoteDiffValues.clear();
if (mNoteData.isLocalModified()
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
return false;
}
return true;
}
private class NoteData {//定义一个基本的便签内容的数据类,主要包含文本数据和电话号码数据
private long mTextDataId;//文本数据id
private ContentValues mTextDataValues;//文本数据内容
private long mCallDataId;//电话号码数据ID
private ContentValues mCallDataValues;//电话号码数据内容
private static final String TAG = "NoteData";//默认构造函数
public NoteData() {//NoteData的构造函数初始化四个变量
mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues();
mTextDataId = 0;
mCallDataId = 0;
}
boolean isLocalModified() {//判断是否本地修改
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
void setTextDataId(long id) {//设置文本数据的ID
if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
mTextDataId = id;
}
void setCallDataId(long id) {//设置电话号码对应的id
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
}
mCallDataId = id;
}
void setCallData(String key, String value) {//设置电话号码数据内容,并且保存修改时间
mCallDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
void setTextData(String key, String value) {//设置文本数据
mTextDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
Uri pushIntoContentResolver(Context context, long noteId) {//使用uri将数据添加到数据库
/**
* Check for safety
*/
if (noteId <= 0) {//判断数据是否合法
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder = null;
if(mTextDataValues.size() > 0) {//把文本数据存入DataColumns
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) {//文本数据ID为零意味着这个id是新建默认的id
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mTextDataValues);
try {
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
mTextDataValues.clear();
return null;
}
} else {
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId));
builder.withValues(mTextDataValues);
operationList.add(builder.build());
}
mTextDataValues.clear();
}
if(mCallDataValues.size() > 0) {//电话号码同步
mCallDataValues.put(DataColumns.NOTE_ID, noteId);
if (mCallDataId == 0) {//将电话号码的id设定为uri提供的id
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mCallDataValues);
try {
setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new call data fail with noteId" + noteId);
mCallDataValues.clear();
return null;
}
} else {//当电话号码不为新建时更新电话号码ID
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mCallDataId));
builder.withValues(mCallDataValues);
operationList.add(builder.build());
}
mCallDataValues.clear();
}
if (operationList.size() > 0) {//存储过程中如果遇到异常,通过如下进行处理
try {//抛出远程异常,并写回日志
ContentProviderResult[] results = context.getContentResolver().applyBatch(
Notes.AUTHORITY, operationList);
return (results == null || results.length == 0 || results[0] == null) ? null
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
} catch (RemoteException e) {//捕捉操作异常并写回日志
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));//异常日志
return null;
}
}
return null;
}
}
}

@ -0,0 +1,370 @@
/*便
* 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.model;//在model包中
import android.appwidget.AppWidgetManager;//需要调用的其他类
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
public class WorkingNote {/*WorkingNote,便
便便便便*/
// Note for the working note
private Note mNote;
// Note Id
private long mNoteId;
// Note content
private String mContent;
// Note mode
private int mMode;
private long mAlertDate;
private long mModifiedDate;
private int mBgColorId;
private int mWidgetId;
private int mWidgetType;
private long mFolderId;
private Context mContext;
private static final String TAG = "WorkingNote";
private boolean mIsDeleted;
private NoteSettingChangedListener mNoteSettingStatusListener;
public static final String[] DATA_PROJECTION = new String[] {/*NOTE_PROJECTION
*/
DataColumns.ID,
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
public static final String[] NOTE_PROJECTION = new String[] {//保存便签属性信息的string数组
NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE
};
private static final int DATA_ID_COLUMN = 0;//以下定义的是上述两个列表中各元素的索引
private static final int DATA_CONTENT_COLUMN = 1;
private static final int DATA_MIME_TYPE_COLUMN = 2;
private static final int DATA_MODE_COLUMN = 3;
private static final int NOTE_PARENT_ID_COLUMN = 0;
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
private static final int NOTE_WIDGET_ID_COLUMN = 3;
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
// New note construct
private WorkingNote(Context context, long folderId) {//初始化WorkingNote类的内部数据变量
mContext = context;
mAlertDate = 0;
mModifiedDate = System.currentTimeMillis();//获取系统当前时间的方法,返回当前时间
mFolderId = folderId;
mNote = new Note();//加载一个已存在的便签
mNoteId = 0;
mIsDeleted = false;
mMode = 0;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;//默认是不可见
}
// Existing note construct
private WorkingNote(Context context, long noteId, long folderId) {//载入便签
mContext = context;
mNoteId = noteId;
mFolderId = folderId;
mIsDeleted = false;
mNote = new Note();
loadNote();
}
private void loadNote() {//加载已有的便签
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN);
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN);
mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
}
cursor.close();
} else {
Log.e(TAG, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
}
loadNoteData();
}
private void loadNoteData() {//加载便签数据
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
String.valueOf(mNoteId)
}, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
do {
String type = cursor.getString(DATA_MIME_TYPE_COLUMN);
if (DataConstants.NOTE.equals(type)) {
mContent = cursor.getString(DATA_CONTENT_COLUMN);
mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
} else if (DataConstants.CALL_NOTE.equals(type)) {
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
} else {
Log.d(TAG, "Wrong note type with type:" + type);
}
} while (cursor.moveToNext());
}
cursor.close();
} else {
Log.e(TAG, "No data with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
}
}
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {//创建一个空便签
WorkingNote note = new WorkingNote(context, folderId);
note.setBgColorId(defaultBgColorId);
note.setWidgetId(widgetId);
note.setWidgetType(widgetType);
return note;
}
public static WorkingNote load(Context context, long id) {//加载已经创建的便签
return new WorkingNote(context, id, 0);
}
public synchronized boolean saveNote() {//保存便签成功保存返回true否则返回false
if (isWorthSaving()) {
if (!existInDatabase()) {
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
}
}
mNote.syncNote(mContext, mNoteId);
/**
* Update widget content if there exist any widget of this note
*/
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
return true;
} else {
return false;
}
}
public boolean existInDatabase() {//查看该note是否已经存放到数据库中
return mNoteId > 0;
}
private boolean isWorthSaving() {//判断是否需要保存
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) {
return false;
} else {
return true;
}
}
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {//设置状态改变的监听器
mNoteSettingStatusListener = l;
}
public void setAlertDate(long date, boolean set) {//设置提醒日期
if (date != mAlertDate) {
mAlertDate = date;
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
}
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onClockAlertChanged(date, set);
}
}
public void markDeleted(boolean mark) {//设置删除标志
mIsDeleted = mark;
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
}
public void setBgColorId(int id) {//设定背景颜色
if (id != mBgColorId) {
mBgColorId = id;
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged();
}
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
}
}
public void setCheckListMode(int mode) {//设置检查列表模式
if (mMode != mode) {
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
}
mMode = mode;
mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
}
}
public void setWidgetType(int type) {//设置窗口类型
if (type != mWidgetType) {
mWidgetType = type;
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
}
}
public void setWidgetId(int id) {//设置窗口编号
if (id != mWidgetId) {
mWidgetId = id;
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
}
}
public void setWorkingText(String text) {//写入文本内容
if (!TextUtils.equals(mContent, text)) {
mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent);
}
}
public void convertToCallNote(String phoneNumber, long callDate) {//转换成电话提醒的便签
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
}
public boolean hasClockAlert() {//检测是否有时钟提醒mAlertDate > 0返回真否则返回假
return (mAlertDate > 0 ? true : false);
}
public String getContent() {//获取便签内容
return mContent;
}
public long getAlertDate() {//获取提醒时间
return mAlertDate;
}
public long getModifiedDate() {//获取修改时间
return mModifiedDate;
}
public int getBgColorResId() {//获取背景颜色来源id
return NoteBgResources.getNoteBgResource(mBgColorId);
}
public int getBgColorId() {//获取背景颜色id
return mBgColorId;
}
public int getTitleBgResId() {//获取标题背景颜色资源的id
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
public int getCheckListMode() {//获取清单模式
return mMode;
}
public long getNoteId() {//获取便签id
return mNoteId;
}
public long getFolderId() {//获取文件夹id
return mFolderId;
}
public int getWidgetId() {//获取窗口id
return mWidgetId;
}
public int getWidgetType() {//监听检测便签设置变化的接口
return mWidgetType;
}
public interface NoteSettingChangedListener {//监听便签设置是否改变
/**
* Called when the background color of current note has just changed
*/
void onBackgroundColorChanged();//背景颜色改变
/**
* Called when user set clock
*/
void onClockAlertChanged(long date, boolean set);//提醒时间按钮,可进行时间的更改和提醒的开关
/**
* Call when user create note from widget
*/
void onWidgetChanged();//窗口改变
/**
* Call when switch between check list mode and normal mode
* @param oldMode is previous mode before change
* @param newMode is new mode
*/
void onCheckListModeChanged(int oldMode, int newMode);//便签检查列表模式改变
}
}

@ -0,0 +1,343 @@
/*
* 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.tool;//定义小米便签 功能类
import android.content.Context;//导入各种需要的类
import android.database.Cursor;//查询数据类型
import android.os.Environment;//导入安卓环境包
import android.text.TextUtils;//导入安卓文本功能包
import android.text.format.DateFormat;//用于转化日期格式
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.DataConstants;//常数类
import net.micode.notes.data.Notes.NoteColumns;//便签类
import java.io.File;//引入文件操作类
import java.io.FileNotFoundException;//文件未找到的错误处理
import java.io.FileOutputStream;//文件的输入
import java.io.IOException;//操作错误处理
import java.io.PrintStream;//导入需要的包
public class BackupUtils {//此处为备份工具包
private static final String TAG = "BackupUtils";//实化一个BackupUtils的对象
// Singleton stuff
private static BackupUtils sInstance;//初始化sInstance
public static synchronized BackupUtils getInstance(Context context) {//如果当前备份不在,则再新声明一个
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
return sInstance;//返回当前的sInstance值
}
/**
* Following states are signs to represents backup or restore
* status
*/
// Currently, the sdcard is not mounted
public static final int STATE_SD_CARD_UNMOUONTED = 0;//备份文件夹不存在
// The backup file not exist
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;//变量表示备份的文件不存在
// The data is not well formated, may be changed by other programs
public static final int STATE_DATA_DESTROIED = 2;//超时异常
// Some run-time exception which causes restore or backup fails
public static final int STATE_SYSTEM_ERROR = 3;//系统错误状态为3
// Backup or restore success
public static final int STATE_SUCCESS = 4;//变量表示备份成功
private TextExport mTextExport;//实例化一个为mTextExport的对象
private BackupUtils(Context context) {//初始化构造函数
mTextExport = new TextExport(context);
}
private static boolean externalStorageAvailable() {//判断外部存储设备是否有效
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
public int exportToText() {//输出至文本
return mTextExport.exportToText();
}
public String getExportedTextFileName() {//返回获取的文件名
return mTextExport.mFileName;
}
public String getExportedTextFileDir() {//返回文件的目录
return mTextExport.mFileDirectory;
}
private static class TextExport {//内部类:文本输出
private static final String[] NOTE_PROJECTION = {//定义了一个数组存储便签的信息
NoteColumns.ID,//ID
NoteColumns.MODIFIED_DATE,//日期
NoteColumns.SNIPPET,
NoteColumns.TYPE
};
private static final int NOTE_COLUMN_ID = 0;//初始化便签ID
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;//初始化修改时间
private static final int NOTE_COLUMN_SNIPPET = 2;//初始化小段消息状态
private static final String[] DATA_PROJECTION = {//定义字符串存储函数的基本信息
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
private static final int DATA_COLUMN_CONTENT = 0;//表示设定数据内容表示为0
private static final int DATA_COLUMN_MIME_TYPE = 1;//数据媒体类型标识为1
private static final int DATA_COLUMN_CALL_DATE = 2;//访问日期表示为2
private static final int DATA_COLUMN_PHONE_NUMBER = 4;//电话号码表示为4
private final String [] TEXT_FORMAT;//文档格式标识
private static final int FORMAT_FOLDER_NAME = 0;//文件命名格式表示为0
private static final int FORMAT_NOTE_DATE = 1;//便签日期格式表示为1
private static final int FORMAT_NOTE_CONTENT = 2;//便签目录格式
private Context mContext;//定义上下文类
private String mFileName;//定义文件名
private String mFileDirectory;//文件路径
public TextExport(Context context) {//从context类实例中花去信息给对应的属性赋初始值
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
private String getFormat(int id) {
return TEXT_FORMAT[id];
}//获取文本组成部分
/**
* Export the folder identified by folder id to text
*/
private void exportFolderToText(String folderId, PrintStream ps) {//通过文件夹目录ID将目录导出后成为文件
// Query notes belong to this folder
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId
}, null);
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// Print note's last modified date
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close();//关闭游标
}
}
/**
* Export note identified by id to a print stream
*/
private void exportNoteToText(String noteId, PrintStream ps) {//函数:将便签的内容以文本的形式显示在屏幕上
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,//利用光标来扫描内容
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
if (dataCursor != null) {//如果光标不为空
if (dataCursor.moveToFirst()) {//游标已经在第一步就位
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);//获取便签媒体类型
if (DataConstants.CALL_NOTE.equals(mimeType)) {//判断便签的内容
// Print phone number
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);//获取电话号码
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);//获取通话时间
String location = dataCursor.getString(DATA_COLUMN_CONTENT);//获取地址
if (!TextUtils.isEmpty(phoneNumber)) {//判断是否为空字符
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// Print call date
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat//输出电话号码
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// Print call attachment location
if (!TextUtils.isEmpty(location)) {//输出位置location
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {//如果便签的内容存在,则直接输出
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {//如果便签内容不为空
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
content));
}
}
} while (dataCursor.moveToNext());//光标下移
}
dataCursor.close();//关闭光标
}
// print a line separator between note
try {//正常情况再note下面输出一条线
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {//获取错误信息
Log.e(TAG, e.toString());
}
}
/**
* Note will be exported as text which is user readable
*/
public int exportToText() {//以TEXT形式输出到外部设备
if (!externalStorageAvailable()) {//如果外部设备未安装好,返回对应状态
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
PrintStream ps = getExportToTextPrintStream();//获取存储信息
if (ps == null) {//如果没有得到正常的数据流,则输出错误信息
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes
Cursor folderCursor = mContext.getContentResolver().query(//定位需要导出的文件夹
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {//如果文件夹不为空,即存在文件夹
if (folderCursor.moveToFirst()) {//文件光标再第一行就位
do {
// Print folder's name
String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {//找到文件名
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);//取便签为便签名字
}
if (!TextUtils.isEmpty(folderName)) {//判断folderName是否存在
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);//通过便签ID获得folderID
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());//文件夹的光标下移
}
folderCursor.close();//关闭光标
}
// Export notes in root's folder
Cursor noteCursor = mContext.getContentResolver().query(//输出根目录下面的笔记
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
if (noteCursor != null) {//如果笔记游标不为空
if (noteCursor.moveToFirst()) {//便签光标下移
do {
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(//输出修改日期
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = noteCursor.getString(NOTE_COLUMN_ID);//找到这块数据的ID
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());//便签光标下移
}
noteCursor.close();//关闭游标
}
ps.close();
return STATE_SUCCESS; //返回成功导出状态
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
*/
private PrintStream getExportToTextPrintStream() {//获取指向文件的打印流
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,//创建文件
R.string.file_name_txt_format);
if (file == null) {//如果文件夹为空,则创建失败
Log.e(TAG, "create file to exported failed");
return null;
}
mFileName = file.getName();//得到文件名
mFileDirectory = mContext.getString(R.string.file_path);//获取文件路径
PrintStream ps = null;//初始化ps
try {//将ps输出流输出到特定的文件目的是导出文件
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {//给出错误信息
e.printStackTrace();
return null;
} catch (NullPointerException e) {//捕获空指针异常
e.printStackTrace();
return null;
}
return ps;
}
}
/**
* Generate the text file to store imported data
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
//产生用于存储输入信息的文件
StringBuilder sb = new StringBuilder();//构建一个动态字符串将外部信息加入其中
sb.append(Environment.getExternalStorageDirectory());//加入路径
sb.append(context.getString(filePathResId));//文件的存储路径
File filedir = new File(sb.toString());//存储路径信息
sb.append(context.getString(//格式化输出当前系统时间
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),//将当前的系统时间以预定的格式输出
System.currentTimeMillis())));
File file = new File(sb.toString());//将输出连接到一个文件里
try {
if (!filedir.exists()) {//如果文件不存在,则重建
filedir.mkdir();
}
if (!file.exists()) {
file.createNewFile();
}
return file;//返回导出的文件
} catch (SecurityException e) {//处理碰到的异常
e.printStackTrace();
} catch (IOException e) {//捕获异常
e.printStackTrace();
}
return null;
}
}

@ -0,0 +1,307 @@
/*
* 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.tool;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
public class DataUtils {//数据的集成工具类
public static final String TAG = "DataUtils";
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
//方法:实现了批量删除便签
if (ids == null) {//判断笔记id是否为空
Log.d(TAG, "the ids is null");
return true;
}
if (ids.size() == 0) {//判断笔记大小是否为空
Log.d(TAG, "no id is in the hashset");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();//提供一个事件的列表
for (long id : ids) {//遍历数据,如果此数据为根目录则跳过此数据不删除,如果不是根目录则将此数据删除
if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
continue;
}
ContentProviderOperation.Builder builder = ContentProviderOperation//使用newdelete进行删除
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
operationList.add(builder.build());
}
try {//这是一个错误处理机制,并分别针对两种不同的错误进行处理。
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
//数据库事项,由一系列的数据库操作序列构成,这个事项作为一个整体来处理
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) {//对错误进行处理,并将错误存储到日志当中
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
}
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
//将标签移动到另一个目录下
ContentValues values = new ContentValues();//实例化一个contentValues类
values.put(NoteColumns.PARENT_ID, desFolderId);//将PARENT_ID更改为目标目录ID
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);//设置origin也即原本的父节点为原本的文件夹的id
values.put(NoteColumns.LOCAL_MODIFIED, 1);//设置修改符号为1
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
//对需要移动的便签进行数据更新然后用update实现
}
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {//批量的将标签移动到另一个目录下
if (ids == null) {//判断便签ID是否为空
Log.d(TAG, "the ids is null");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
//将ids里包含的每一列的数据逐次加入到operationList中等待最后的批量处理
for (long id : ids) {//遍历所有选中的便签的id
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
builder.withValue(NoteColumns.PARENT_ID, folderId);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
operationList.add(builder.build());//将ids里的数据添加到operationlist中以便接下来批量处理
}
try {//利用Log输出信息来进行错误检测
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
//applyBatch一次性处理一个操作列表
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
return false;
}
return true;
} catch (RemoteException e) {//异常处理
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
}
return false;
}
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/
public static int getUserFolderCount(ContentResolver resolver) {//获取用户文件夹数
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
//resolver.query()方法第二个参数是要返回的列第三个参数是Section查询where字句第四个是查询条件属性值第五个是筛选规则
new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
null);//String.valueof将形参转成字符串返回
int count = 0;
if(cursor != null) {//尝试得到用户文件夹的数量
if(cursor.moveToFirst()) {
try {//异常处理
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {//索引序号超出界限
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
cursor.close();//关闭游标
}
}
}
return count;
}
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {//是否在便签数据库中可见
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),//通过withappendedid的方法为uri加上id
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
null);//sql语句表示筛选出列中type等于string数组中type且每一项的PARENT_ID不等于Notes.ID.TRAXH_FOLDER
boolean exist = false;//查询文件
if (cursor != null) {//用getcount函数判断cursor是否为空
if (cursor.getCount() > 0) {//如果有满足条件的条目那么就是可见exist为真否则不可见exist为假
exist = true;
}
cursor.close();//关闭游标
}
return exist;//在数据数据库中是否存在
}
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {//判断该note是否在数据库中存在
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
//相比于上面,这个只是存在性判定,因此不需要过多筛选条件
boolean exist = false;//初始化存在状态
if (cursor != null) {//根据getcount此时的值可以判断dataID的存在性
if (cursor.getCount() > 0) {//根据筛选出来的条数判断存在性
exist = true;
}
cursor.close();//关闭游标
}
return exist;
}
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {//检查文件名字是否可见
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
//通过URI与dataId在数据库中查找数据
null, null, null, null);
boolean exist = false;//根据数据的有无返回相应的布尔值
if (cursor != null) {
if (cursor.getCount() > 0) {//调用对应的uri的数据值进行查询
exist = true;
}
cursor.close();//关闭游标
}
return exist;//通过名字查询文件是否存在
}
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
//这个则是判断某个文件夹名字是否对应一个未被删除的文件夹
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,//筛选类型正确的、未被删除的和名字对应得上的
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
" AND " + NoteColumns.SNIPPET + "=?",
new String[] { name }, null);//通过名字查询文件是否存在
boolean exist = false;
if(cursor != null) {//判断找到的文件名返回文件是否存在的bool值
if(cursor.getCount() > 0) {
exist = true;
}
cursor.close();//关闭游标
}
return exist;
}
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
//使用hashset来存储不同窗口的id和type并且建立对应关系
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,//父id为传入的文件夹id
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) },//hash集合为空的情况
null);
HashSet<AppWidgetAttribute> set = null;//根据窗口的记录一一添加对应的属性值
if (c != null) {//将app窗口的属性加入到HashSet中
if (c.moveToFirst()) {
set = new HashSet<AppWidgetAttribute>();
do {
try {//把每一个条目对应的窗口id和type记录下来放到set里面。每一行的第0个int和第1个int分别对应widgetId和widgetType
AppWidgetAttribute widget = new AppWidgetAttribute();//新建一个区块
widget.widgetId = c.getInt(0);//0对应的NoteColumns.WIDGET_ID
widget.widgetType = c.getInt(1);//1对应的NoteColumns.WIDGET_TYPE
set.add(widget);
} catch (IndexOutOfBoundsException e) {//当下标超过边界,那么返回错误
Log.e(TAG, e.toString());
}
} while (c.moveToNext());//访问下一条
}
c.close();//关闭游标
}
return set;
}
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {//通过笔记ID获取号码
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE },//新建字符列表
null);
if (cursor != null && cursor.moveToFirst()) {// 获取电话号码,并处理异常。
try {//返回电话号码
return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {//捕获字符
Log.e(TAG, "Get call number fails " + e.toString());
} finally {
cursor.close();
}
}
return "";
}
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
//同样的通过映射关系通过号码和日期获取ID
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,//通过数据库操作查询条件是callDate和phoneNumber匹配传入参数的值
new String [] { CallNote.NOTE_ID },
CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL("
+ CallNote.PHONE_NUMBER + ",?)",
new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber },
null);//通过数据库操作查询条件callDate和phoneNumber匹配传入参数的值找到对应的note
if (cursor != null) {//得到该note的系统属性并以Long值的形式来保存
if (cursor.moveToFirst()) {
try {
return cursor.getLong(0);//0对应的CallNote.NOTE_ID
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call note id fails " + e.toString());
}
}
cursor.close();//关闭游标
}
return 0;
}
public static String getSnippetById(ContentResolver resolver, long noteId) {//按ID获取片段
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,//通过ID查询
new String [] { NoteColumns.SNIPPET },
NoteColumns.ID + "=?",
new String [] { String.valueOf(noteId)},
null);//通过数据库操作查询条件是callDate和phoneNumber匹配传入参数的值
if (cursor != null) {//以string的形式获取该对象当前行指定列的值。
String snippet = "";//对字符串进行格式处理,将字符串两头的空格去掉同时将换行符去掉
if (cursor.moveToFirst()) {
snippet = cursor.getString(0);
}
cursor.close();
return snippet;
}
throw new IllegalArgumentException("Note is not found with id: " + noteId);
//IllegalArgumentException是非法传参异常也就是参数传的类型冲突属于RunTimeException运行时异常
}
public static String getFormattedSnippet(String snippet) {//对字符串进行格式处理,将字符串两头的空格去掉,同时将换行符去掉
if (snippet != null) {
snippet = snippet.trim();// trim()函数: 移除字符串两侧的空白字符或其他预定义字符
int index = snippet.indexOf('\n');
if (index != -1) {
snippet = snippet.substring(0, index);//截取到第一个换行符
}
}
return snippet;
}
}

@ -0,0 +1,113 @@
/*
* 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.tool;//定义了很多的静态字符串目的就是为了提供jsonObject中相应字符串的"key"
public class GTaskStringUtils {
public final static String GTASK_JSON_ACTION_ID = "action_id";//行动ID
public final static String GTASK_JSON_ACTION_LIST = "action_list";//任务列表
public final static String GTASK_JSON_ACTION_TYPE = "action_type";//任务类型
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";//新建
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";//移动
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";//更新
public final static String GTASK_JSON_CREATOR_ID = "creator_id";//id
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";//子实体
public final static String GTASK_JSON_CLIENT_VERSION = "client_version";//客户端
public final static String GTASK_JSON_COMPLETED = "completed";
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";//当前列表位置
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";//失败
public final static String GTASK_JSON_DELETED = "deleted";//删除
public final static String GTASK_JSON_DEST_LIST = "dest_list";
public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
public final static String GTASK_JSON_GET_DELETED = "get_deleted";
public final static String GTASK_JSON_ID = "id";
public final static String GTASK_JSON_INDEX = "index";//索引
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
public final static String GTASK_JSON_LIST_ID = "list_id";
public final static String GTASK_JSON_LISTS = "lists";
public final static String GTASK_JSON_NAME = "name";
public final static String GTASK_JSON_NEW_ID = "new_id";
public final static String GTASK_JSON_NOTES = "notes";
public final static String GTASK_JSON_PARENT_ID = "parent_id";//搭档ID
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
public final static String GTASK_JSON_RESULTS = "results";
public final static String GTASK_JSON_SOURCE_LIST = "source_list";
public final static String GTASK_JSON_TASKS = "tasks";//任务栏
public final static String GTASK_JSON_TYPE = "type";
public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
public final static String GTASK_JSON_TYPE_TASK = "TASK";//任务
public final static String GTASK_JSON_USER = "user";
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";
public final static String FOLDER_DEFAULT = "Default";
public final static String FOLDER_CALL_NOTE = "Call_Note";//呼叫小米便签
public final static String FOLDER_META = "METADATA";
public final static String META_HEAD_GTASK_ID = "meta_gid";
public final static String META_HEAD_NOTE = "meta_note";
public final static String META_HEAD_DATA = "meta_data";//数据
public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE";
}

@ -0,0 +1,181 @@
/*
* 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.tool;
import android.content.Context;
import android.preference.PreferenceManager;
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
public class ResourceParser {//ResourceParser类获取程序资源如图片颜色等
public static final int YELLOW = 0;//为颜色常量和字体常量赋值,为字体默认大小赋值
public static final int BLUE = 1;
public static final int WHITE = 2;
public static final int GREEN = 3;
public static final int RED = 4;
public static final int BG_DEFAULT_COLOR = YELLOW;//默认背景颜色(黄)
public static final int TEXT_SMALL = 0;//为颜色常量和字体常量赋值,为字体默认大小赋值
public static final int TEXT_MEDIUM = 1;
public static final int TEXT_LARGE = 2;
public static final int TEXT_SUPER = 3;
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;//默认字体大小(中)
public static class NoteBgResources {// Note的背景颜色
private final static int [] BG_EDIT_RESOURCES = new int [] {//.调用drawable中的五种颜色的背景图片png文件
R.drawable.edit_yellow,//调用drawable中的五种颜色的标题背景图片png文件
R.drawable.edit_blue,
R.drawable.edit_white,
R.drawable.edit_green,
R.drawable.edit_red
};
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {//背景标题的资源常量数组
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
R.drawable.edit_title_white,
R.drawable.edit_title_green,
R.drawable.edit_title_red
};
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}//获取便签背景资源id
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}
}//获取标题使用的资源的ID
public static int getDefaultBgId(Context context) {//获取默认背景颜色对应Id
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(//如果颜色设定为随机的颜色,那么就随机返回一个颜色
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
} else {
return BG_DEFAULT_COLOR;//否则返回默认背景颜色
}
}
public static class NoteItemBgResources {//便签项目背景资源子类,包括方法:获得首尾项目等背景资源
private final static int [] BG_FIRST_RESOURCES = new int [] {//不同drawable的变量声明
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
R.drawable.list_white_up,
R.drawable.list_green_up,
R.drawable.list_red_up
};
private final static int [] BG_NORMAL_RESOURCES = new int [] {//定义了背景的默认资源
R.drawable.list_yellow_middle,
R.drawable.list_blue_middle,
R.drawable.list_white_middle,
R.drawable.list_green_middle,
R.drawable.list_red_middle
};
private final static int [] BG_LAST_RESOURCES = new int [] {//定义背景的下方资源
R.drawable.list_yellow_down,
R.drawable.list_blue_down,
R.drawable.list_white_down,
R.drawable.list_green_down,
R.drawable.list_red_down,
};
private final static int [] BG_SINGLE_RESOURCES = new int [] {
R.drawable.list_yellow_single,
R.drawable.list_blue_single,
R.drawable.list_white_single,
R.drawable.list_green_single,
R.drawable.list_red_single
};
public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id];
}//通过ID获取所需要的资源
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}//通过ID寻找last的颜色值
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}//通过ID获取单个便签背景颜色资源
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}//通过ID寻找normal的颜色值
public static int getFolderBgRes() {
return R.drawable.list_folder;
}//设置窗口的资源
}
public static class WidgetBgResources {//小窗口情况下的背景资源类
private final static int [] BG_2X_RESOURCES = new int [] {//2x小窗口背景资源初始化
R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
R.drawable.widget_2x_white,
R.drawable.widget_2x_green,
R.drawable.widget_2x_red,
};
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id];
}//根据ID加载BG_2X_RESOURCES数组里的颜色资源序号。
private final static int [] BG_4X_RESOURCES = new int [] {//本条与下一条private定义与上两条相同只不过由2倍扩大成了4倍
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,
R.drawable.widget_4x_white,
R.drawable.widget_4x_green,
R.drawable.widget_4x_red
};
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id];
}//根据ID加载BG_4X_RESOURCES数组里的颜色资源序号。
}
public static class TextAppearanceResources {//文本外观资源,包括默认字体,以及获取资源大小
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {//定义外观资源
R.style.TextAppearanceNormal,//通过ID找到格式没有要求的话就设置为默认格式
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
};
public static int getTexAppearanceResource(int id) {//这是一个容错的函数防止输入的id大于资源总量。如果大于资源总量则自动返回默认的设置结果
/**
* 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 (id >= TEXTAPPEARANCE_RESOURCES.length) {//若输入id大于字体编号最大值则返回默认值
return BG_DEFAULT_FONT_SIZE;
}
return TEXTAPPEARANCE_RESOURCES[id];
}
public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length;
}//直接返回为资源的长度
}
}

@ -0,0 +1,214 @@
/*
* 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;
public class NoteEditText extends EditText {//继承EditText设置便签设置文本框
private static final String TAG = "NoteEditText";//类的名称,可以用来输出日志文件
private int mIndex;//建立一个字符和整数的表,存放电话号码、网址、邮箱
private int mSelectionStartBeforeDelete;//声明整型变量,获取删除文本前的位置
private static final String SCHEME_TEL = "tel:" ;//声明字符串常量,标志电话、网址、邮件
private static final String SCHEME_HTTP = "http:" ;//文本中网页内容
private static final String SCHEME_EMAIL = "mailto:" ;//文本中邮件内容
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();//设置映射,将文本内容(电话、网址、邮件)做链接处理
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);
}//在NoteEditActivity进行文本的操作删除进入编辑
/**
* Call by the {@link NoteEditActivity} to delete or add edit text
*/
public interface OnTextViewChangeListener {//接口OnTextViewChangeListener会被NoteEditActivity大量调用其中定义了在编辑文本中delete、点击enter、text改变时的方法
/**
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
*/
void onEditTextDelete(int index, String text);//当delete键按下时删除当前编辑的文字块
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*/
void onEditTextEnter(int index, String text);//当触发输入文本KeyEvent时增添文本
/**
* Hide or show item option when text change
*/
void onTextChange(int index, boolean hasText);//文字更改时隐藏或显示项目选项
}
private OnTextViewChangeListener mOnTextViewChangeListener;//新建私有变量:文本改变的监听器
public NoteEditText(Context context) {//根据context设置文本
super(context, null);//用super引用父类变量
mIndex = 0;//设置当前光标
}
public void setIndex(int index) {
mIndex = index;
}//初始化文本修改标记,更新光标指向的索引值
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}//设置文本视图变化监听器
public NoteEditText(Context context, AttributeSet attrs) {//自定义空控件属性,用于维护便签动态变化,这个函数功能为初始化便签
super(context, attrs, android.R.attr.editTextStyle);
}
// public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);\
// TODO Auto-generated constructor stub
}//自定义控件,用于维护便签动态变化的属性。初始化便签
@Override
public boolean onTouchEvent(MotionEvent event) {//view里的函数处理手机屏幕的所有事件。参数event为手机屏幕触摸事件封装类的对象其中封装了该事件的所有信息例如触摸的位置、触摸的类型以及触摸的时间等。该对象会在用户触摸手机屏幕时被创建。
switch (event.getAction()) {//重写屏幕触发事件
case MotionEvent.ACTION_DOWN://更新坐标
int x = (int) event.getX();//更新x值为当前触摸处的x值
int y = (int) event.getY();//更新y值为当前触摸处的y值
x -= getTotalPaddingLeft();//减去左边控件的距离
y -= getTotalPaddingTop();//减去上方控件的距离
x += getScrollX();
y += getScrollY();//加上滚轮滚过的距离
Layout layout = getLayout();//用布局控件layout根据x,y的新值设置新的位置
int line = layout.getLineForVertical(y);//获取纵向行数
int off = layout.getOffsetForHorizontal(line, x);//获取横向偏移量
Selection.setSelection(getText(), off);//更新光标位置
break;
}
return super.onTouchEvent(event);//调用父类当屏幕有Touch事件时此方法就会被调用。
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {//处理用户按下一个键盘按键时会触发 的事件
switch (keyCode) {//根据按键的KeyCode来处理
case KeyEvent.KEYCODE_ENTER://按下回车时,如果mOnTextViewChangeListener存在则返回false
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL://删除按键
mSelectionStartBeforeDelete = getSelectionStart();//获取删除文本开始位置
break;
default://其他情况,返回父类的onKeyDown值
break;
}
return super.onKeyDown(keyCode, event);//继续执行父类的其他点击事件
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {//处理用户松开一个键盘按键时会触发的事件
switch(keyCode) {//根据按键的 Unicode 编码值来处理有删除和进入2种操作
case KeyEvent.KEYCODE_DEL://若触发修改且文档不为空则调用前面代码的onEditTextDelete函数进行文本删除
if (mOnTextViewChangeListener != null) {
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;//利用上文OnTextViewChangeListener对KEYCODE_DEL按键情况的删除函数进行删除
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");//其他情况报错,文档的改动监听器并没有建立
}
break;
case KeyEvent.KEYCODE_ENTER://若文档改动监听器已建立则获取当前位置和文本并根据获取的信息调用onEditTextEnter函数进行文本增添
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString();//获取选择区域后面的文本信息
setText(getText().subSequence(0, selectionStart));//实现文本换行的功能
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);//将选择区域内的文字移到下一行
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");//其他情况报错监听器OnTextViewChangeListener并没有建立。
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event);//继续执行父类的其他按键弹起的事件
}
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {//这个函数规定了编辑文本焦点改变时的系统响应
if (mOnTextViewChangeListener != null) {//若监听器已建立
if (!focused && TextUtils.isEmpty(getText())) {//获取到焦点并且文本不为空
mOnTextViewChangeListener.onTextChange(mIndex, false);//置false隐藏事件选项
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true);//置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;// 默认的资源ID值为0
for(String schema: sSchemaActionResMap.keySet()) {//获取计划表中所有的key值
if(urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema);
break;
}//若url可以添加则在添加后将defaultResId置为key所映射的值
}
if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
}//defaultResId == 0则说明url并没有添加任何东西所以置为连接其他SchemaActionResMap的值
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);//创建文本菜单
}
}

@ -0,0 +1,223 @@
/*
* 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 [] {//将便签id提醒时间背景颜色id创建时间关联桌面挂件修改时间便签数量父文件夹id文件夹id便签的一段内容便签的种类桌面挂件的id桌面挂件的类型的名称放在一个PROJECTION的字符串数组里
NoteColumns.ID,//每个便签的序号ID
NoteColumns.ALERTED_DATE,//提醒日期
NoteColumns.BG_COLOR_ID,//背景颜色的序号id
NoteColumns.CREATED_DATE,//创建日期
NoteColumns.HAS_ATTACHMENT,//是否含有附件
NoteColumns.MODIFIED_DATE,//修改日期
NoteColumns.NOTES_COUNT,//便签数量
NoteColumns.PARENT_ID,
NoteColumns.SNIPPET,//文件夹名称或者文本注释内容
NoteColumns.TYPE,//note的种类
NoteColumns.WIDGET_ID,//挂件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;//PROJECT的字符串名称对应的值
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private boolean mHasAttachment;
private long mModifiedDate;
private int mNotesCount;
private long mParentId;//对方ID
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;//判断文件夹下是否有多个便签
public NoteItemData(Context context, Cursor cursor) {//初始化NoteItemData,利用光标和context获取的内容
mId = cursor.getLong(ID_COLUMN);//获取指定数据并以type类型传回
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) {//如果父文件的id是call_record_folder
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);//使用DataUtils类中定义的函数获取电话号码信息
if (!TextUtils.isEmpty(mPhoneNumber)) {//若mphonenumber里有符合字符串则用contart功能连接
mName = Contact.getContact(context, mPhoneNumber);//通过phonenumber调用getContact利用键值对获取对应的name
if (mName == null) {
mName = mPhoneNumber;//若未保存名字,以号码命名
}
}
}
if (mName == null) {
mName = "";
}//若没有对name复制成功则把name设置为空值
checkPostion(cursor);//检查光标位置
}
private void checkPostion(Cursor cursor) {//根据光标位置设置标记
mIsLastItem = cursor.isLast() ? true : false;//分别为各种描述状态的变量进行赋值
mIsFirstItem = cursor.isFirst() ? true : false;
mIsOnlyOneItem = (cursor.getCount() == 1);
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;//初始化“多重子文件”“单一子文件”2个标记
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {//如果对象的类型为便签且不为第一个项
int position = cursor.getPosition();//获得光标位置
if (cursor.moveToPrevious()) {//如果光标移动
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER//若光标满足SYSTEM或FOLDER格式
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
if (cursor.getCount() > (position + 1)) {//若光标位置大于当前位置,置多便签子文件夹标记为真
mIsMultiNotesFollowingFolder = true;//设置为多重便签
} else {
mIsOneNoteFollowingFolder = 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;
}//若数据父id为保存至文件夹模式的id且满足电话号码单元不为空则isCallRecord为true
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;
}//获得对应的ID值
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;
}//获取父进程id
public int getNotesCount() {
return mNotesCount;
}//获取便签数量
public long getFolderId () {
return mParentId;
}//获取文件夹id
public int getType() {
return mType;
}//获得项的类型
public int getWidgetType() {
return mWidgetType;
}//获取挂件类型
public int getWidgetId() {
return mWidgetId;
}//获取挂件id
public String getSnippet(){
return mSnippet;
}//获取便签的外观片段
public boolean hasAlert() {
return (mAlertDate > 0);
}//判读此便签是否有提醒功能
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}//如果父类id保存至文件夹模式并且电话号码单元不为空
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}//获取便签类型
}

@ -0,0 +1,954 @@
/*
* 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 {//在类的声明中通过关键字extends来创建一个类的子类。一个类通过关键字implements声明自己使用一个或者多个接口。
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;//首次触摸时屏幕上的垂直距离y值
private int mDispatchY;//重新调度时的触摸的在屏幕上的垂直距离
private TextView mTitleBar;//子文件夹下标头
private long mCurrentFolderId;//当前文件夹的ID
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//功能为Activity生命周期开始时调用的函数用来保存读取状态设置界面
protected void onCreate(Bundle savedInstanceState) {//创建类
super.onCreate(savedInstanceState);//引用父类对象
setContentView(R.layout.note_list);//设置内容的视图
initResources();//初始化类的资源
/**
* Insert an introduction when user firstly use this application
*/
setAppInfoFromRawRes();//当用户第一次访问APP时提供相关信息
}
@Override// 代表执行这个方法时,重写并调用了父类的方法
protected void onActivityResult(int requestCode, int resultCode, Intent data) {//返回一些子模块完成的数据交给主Activity处理
if (resultCode == RESULT_OK//当被销毁的活动是打开便签或者新建便签且结果码匹配时,把从编辑界面的光标数据删除
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) {
mNotesListAdapter.changeCursor(null);
} else {//如果条件不满足则将数据返回给父类即调用父类的onActivityResult()。
super.onActivityResult(requestCode, resultCode, data);//调用父类Activity的onActivityResult方法 将数据返回给父类处理
}
}
private void setAppInfoFromRawRes() {//利用原始资源文件设置APP的相关信息
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);//保存配置参数
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {//判断偏好,增加说明
StringBuilder sb = new StringBuilder();//读取原生资源信息
InputStream in = null;//输入流初始设置为空
try {//使用getResources获取资源后,以openRawResource方法不带后缀的资源文件名打开这个文件。
in = getResources().openRawResource(R.raw.introduction);//加载Welcome to use MIUI notes
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) {//不断在buf中读取数据放入sb里
sb.append(buf, 0, len);//使用append函数在指定元素的结尾插入内容
}
} else {
Log.e(TAG, "Read introduction file error");//报错,读取文件错误
return;
}
} catch (IOException e) {//IO出错打印异常信息
e.printStackTrace();//在命令行打印异常信息在程序中出错的位置及原因
return;
} finally {//必然执行部分
if(in != null) {
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();//输出栈轨迹
}
}
}
WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER,//创建新便签
AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE,
ResourceParser.RED);
note.setWorkingText(sb.toString());//设置文本数据
if (note.saveNote()) {//若存储条件成立,保存该便签
sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit();//更新保存note的信息
} else {
Log.e(TAG, "Save introduction note error");
return;
}//若便签保存不成功,则把错误信息打印到日志里
}
}
@Override
protected void onStart() {//代表activity生命周期开始
super.onStart();//调用父类,启动活动
startAsyncNotesListQuery();//同步列表中的便签信息
}
private void initResources() {//初始化资源
mContentResolver = this.getContentResolver();//获取应用程序的数据
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());//动态创建后台请求处理器的实例
mCurrentFolderId = Notes.ID_ROOT_FOLDER;//设置当前文件夹ID是根目录ID
mNotesListView = (ListView) findViewById(R.id.notes_list);//根据R文件中的id值查询到相应的View,然后返回
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;//初始y值设置为0
mOriginY = 0;//加载文件夹下的标头资源
mTitleBar = (TextView) findViewById(R.id.tv_title_bar);//加载文件夹下的标头资源
mState = ListEditState.NOTE_LIST;//设置状态为主界面
mModeCallBack = new ModeCallback();//对便签的方法调用,包括删除和移动
}
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {//implements声明自己使用一个或多个接口
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的布局
menu.findItem(R.id.delete).setOnMenuItemClickListener(this);//为便签删除模块设定监听器
mMoveMenu = menu.findItem(R.id.move);//获取菜单项目
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER//若父类id在文件夹中保存或者用户文件数量为零设置移动菜单为不可见否者设为可见
|| 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);//为view添加dropDownMenu
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()) {//根据id号判断是删除还是移动
case R.id.delete://删除菜单项
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);//警告对话框
builder.setTitle(getString(R.string.alert_title_delete));//设置“删除选中的便签”的title
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+94Unit: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<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> 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<AppWidgetAttribute> 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<Long> ids = new HashSet<Long>();
ids.add(folderId);
HashSet<AppWidgetAttribute> 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
}
});
}
@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;
}
}
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);
}
@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;
}
@Override
public boolean onSearchRequested() {
startSearch(null, false, null /* appData */, false);
return true;
}
private void exportNoteToText() {
final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this);
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... unused) {
return backup.exportToText();
}
@Override
protected void onPostExecute(Integer result) {
if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) {
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();
}
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
private void startPreferenceActivity() {
Activity from = getParent() != null ? getParent() : this;
Intent intent = new Intent(from, NotesPreferenceActivity.class);
from.startActivityIfNeeded(intent, -1);
}
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");
}
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;
}
}

@ -0,0 +1,184 @@
/*
* 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;//引入tools包
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;
public class NotesListAdapter extends CursorAdapter {//继承自CursorAdapter它为cursor和ListView提供了连接的桥梁。因此该类为cursor和便签编辑提供了沟通渠道
private static final String TAG = "NotesListAdapter";
private Context mContext;
private HashMap<Integer, Boolean> mSelectedIndex;
private int mNotesCount;
private boolean mChoiceMode;
public static class AppWidgetAttribute {//表示桌面widget的属性包括编号和类型
public int widgetId;
public int widgetType;
};
public NotesListAdapter(Context context) {//初始化便签链接
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();
mContext = context;
mNotesCount = 0;
}
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {//利用NotesListLtem类创建新布局
return new NotesListItem(context);
}
@Override
public void bindView(View view, Context context, Cursor cursor) {//将光标指向的数据与创建的视图捆绑起来
if (view instanceof NotesListItem) {
NoteItemData itemData = new NoteItemData(context, cursor);
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
}
}
public void setCheckedItem(final int position, final boolean checked) {//设置勾选框
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
}
public boolean isInChoiceMode() {//判断是否被勾选
return mChoiceMode;
}
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<Long> getSelectedItemIds() {// 获取已经被勾选的项目的id号加入到散列表itemSet中
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
}
}
return itemSet;
}
public HashSet<AppWidgetAttribute> getSelectedWidget() {//获取桌面组件选项表
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {//遍历被选中的列表
if (mSelectedIndex.get(position) == true) {
Cursor c = (Cursor) getItem(position);
if (c != null) {
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
/**
* Don't close cursor here, only the adapter could close it
*/
} else {
Log.e(TAG, "Invalid cursor");//设置标签非法的cursor
return null;
}
}
}
return itemSet;
}
public int getSelectedCount() {//获取选项个数
Collection<Boolean> values = mSelectedIndex.values();//获取选项下标的值
if (null == values) {
return 0;
}
Iterator<Boolean> iter = values.iterator();//初始化叠加器
int count = 0;
while (iter.hasNext()) {
if (true == iter.next()) {
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() {//在activity发生变化时重新计算便签数量
super.onContentChanged();
calcNotesCount();
}
@Override
public void changeCursor(Cursor cursor) {//光标变动时重新计算便签数量
super.changeCursor(cursor);
calcNotesCount();
}
private void calcNotesCount() {//计算当前界面便签的数量
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {//遍历所有数据
Cursor c = (Cursor) getItem(i);
if (c != null) {//判断语句如果光标不是null那么便得到信息便签数目加1
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {//若选项的数据类型为便签类型,那么计数+1
mNotesCount++;
}
} else {//设置为无效的光标
Log.e(TAG, "Invalid cursor");
return;
}
}
}
}

@ -0,0 +1,122 @@
/*
* 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);//调整调用父类构造函数的顺序
inflate(context, R.layout.note_item, this);//将xml定义的一个布局找出来
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);//从contentView中查找指定ID的View
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);//获取复选框
}
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {//根据data的属性对各个控件的属性的控制
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {//如果当前处于选择模式下且数据类型为便签
mCheckBox.setVisibility(View.VISIBLE);//设置View可见
mCheckBox.setChecked(checked);//设置勾选
} else {
mCheckBox.setVisibility(View.GONE);//设置复选框不可见
}
mItemData = data;//把数据传给标签
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {//设置控件属性通过判断保存到文件夹的ID、当前ID以及父ID之间关系决定
mCallName.setVisibility(View.GONE);//设置联系人名字不可见
mAlert.setVisibility(View.VISIBLE);//设置闹钟图标可见
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);//设置外观风格
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);//设置title文本风格
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));//设置title的文本内容为便签内容的前面片段
if (data.hasAlert()) {//如果时间提醒存在,设置图片来源,将时间提醒图标定为可见
mAlert.setImageResource(R.drawable.clock);//图片来源的设置
mAlert.setVisibility(View.VISIBLE);//将提醒图标设置为可见
} else {//否则将提醒图标设置为不可见
mAlert.setVisibility(View.GONE);
}
} else {//如果父类和当前id均与保存在文件夹中的id不同
mCallName.setVisibility(View.GONE);//设置联系人姓名不可见
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);//设置title的文本格式
if (data.getType() == Notes.TYPE_FOLDER) {//设置Type格式
mTitle.setText(data.getSnippet()//设置便签标题内容为便签的前面部分的内容+文件数+便签数
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));//设置内容从data编辑的日期中获取时间
mAlert.setVisibility(View.GONE);//设置时间提醒图标为不可见
} else {//如果不是文件夹类型,设置便签的title为便签内容的前面片段
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {//若当前便签存在提醒闹钟时间,则显示相应的图片
mAlert.setImageResource(R.drawable.clock);//将提醒图标设置为闹钟样式
mAlert.setVisibility(View.VISIBLE);//设置提醒闹钟可见
} else {//否则设置提醒图标不可见
mAlert.setVisibility(View.GONE);
}
}
}
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));//将时间设置为编辑便签的时间
setBackground(data);//从data里编辑的日期中获取内容和相关时间
}
private void setBackground(NoteItemData data) {//根据data的文件属性来设置背景
int id = data.getBgColorId();//获取id用此id用来获取背景颜色
if (data.getType() == Notes.TYPE_NOTE) {//根据data的属性来是否为Note属性分为4种情况
if (data.isSingle() || data.isOneFollowingFolder()) {//单个数据或只有一个子文件夹
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));//设置背景来源为id的单个数据
} else if (data.isLast()) {//若当前便签为最后一个,设置背景来源为id的最后一个数据
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {//若是第一个数据或者有很多个子文件夹, 设置背景来源为id的第一个数据
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
} else {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));//将便签设置为普通类型便签的背景
}
} else {//则将背景设置为文件夹的背景
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
public NoteItemData getItemData() {
return mItemData;
}//返回当前便签的数据信息
}

@ -0,0 +1,389 @@
/*
* 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;
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
protected void onCreate(Bundle icicle) {//创建一个activity在函数里完成所有的正常静态设置
super.onCreate(icicle);//执行父类创建函数
/* using the app icon for navigation */
getActionBar().setDisplayHomeAsUpEnabled(true);//给左上角图标的左边加上一个返回的图标
addPreferencesFromResource(R.xml.preferences);//从资源里添加布局文件
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);//从xml获取Listview
getListView().addHeaderView(header, null, true);//列出所有选择
}
@Override//功能描述:重启活动
protected void onResume() {
super.onResume();//activity交互功能的实现用于接受用户的输入
// 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//功能描述:销毁活动
protected void onDestroy() {//销毁Activity
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);//配置资源设置一个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) {//若最近同步时间不为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);//不设置“确定”的按钮
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() {//定义一些标记字符串
public void onClick(DialogInterface dialog, int 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);
}//清除本地的gtask关联的信息
}).start();//重置当地同步任务的信息
Toast.makeText(NotesPreferenceActivity.this,//设置一个toast提示信息提示用户成功设置同步
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);
}//清除本地的gtask关联的信息将一些参数设置为0或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);
}
private class GTaskReceiver extends BroadcastReceiver {//接受同步信息
@Override
public void onReceive(Context context, Intent intent) {//响应接收广播的情况
refreshUI();
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
syncStatus.setText(intent
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));////通过获取的数据在设置系统的状态
}//获取随广播而来的Intent中的同步服务的数据
}
}
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;
default://在主页情况下在创建连接组件intent发出清空的信号并开始一个相应的activity
return false;
}
}
}
Loading…
Cancel
Save