pull/6/head
gpl 2 years ago
parent 9d2333387d
commit 7fdd8e4611

@ -0,0 +1,74 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Data;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.HashMap;
public class Contact {//联系人
private static HashMap<String, String> sContactCache;
private static final String TAG = "Contact";
//定义字符串"CALLER_ID_SELECTION"
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN "
+ "(SELECT raw_contact_id "
+ " FROM phone_lookup"
+ " WHERE min_match = '+')";
//获取联系人
public static String getContact(Context context, String phoneNumber) {
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}//如果为空则创建新的sContactCache
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}//查询hash表中已有的phoneNumber信息
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
// 查找数据库中phoneNumber的信息
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI,
new String [] { Phone.DISPLAY_NAME },
selection,
new String[] { phoneNumber },
null);
//判断查找的结果
if (cursor != null && cursor.moveToFirst()) {
try {
String name = cursor.getString(0);
sContactCache.put(phoneNumber, name);
return name;//找到相关信息返回name
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, " Cursor get string error " + e.toString());
return null;//异常
} finally {
cursor.close();//没找到相关信息
}
} else {//未找到相关信息
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}
}
}

@ -0,0 +1,82 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
public class MetaData extends Task {
private final static String TAG = MetaData.class.getSimpleName();
private String mRelatedGid = null;
public void setMeta(String gid, JSONObject metaInfo) {
try {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);//将这对键值放入metaInfo这个jsonobject对象中
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");//输出错误
}
setNotes(metaInfo.toString());
setName(GTaskStringUtils.META_NOTE_NAME);
}
public String getRelatedGid() {
return mRelatedGid;
}
//获取相关联的Gid
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
//判断当前数据是否为空,若为空则返回真即值得保存
@Override//使用远程json数据对象设置元数据内容
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js);
if (getNotes() != null) {
try {
JSONObject metaInfo = new JSONObject(getNotes().trim());
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
mRelatedGid = null;
}
}
}
@Override//使用本地json数据对象设置元数据内容一般不会用到若用到则抛出异常
public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
@Override//从元数据内容中获取本地json对象,用到便抛出异常
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
}
@Override//获取同步动作状态,若用到,则抛出异常
public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called");
}
}

@ -0,0 +1,103 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import org.json.JSONObject;
public abstract class Node {
//定义了各种用于表征同步状态的常量
public static final int SYNC_ACTION_NONE = 0;// 本地和云端都无可更新内容
public static final int SYNC_ACTION_ADD_REMOTE = 1;// 需要在远程云端增加内容
public static final int SYNC_ACTION_ADD_LOCAL = 2;// 需要在本地增加内容
public static final int SYNC_ACTION_DEL_REMOTE = 3;// 需要在远程云端删除内容
public static final int SYNC_ACTION_DEL_LOCAL = 4;// 需要在本地删除内容
public static final int SYNC_ACTION_UPDATE_REMOTE = 5;//远程更新内容
public static final int SYNC_ACTION_UPDATE_LOCAL = 6;//本地更新内容
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;//同步出现冲突
public static final int SYNC_ACTION_ERROR = 8;//同步错误
private String mGid;
private String mName;
private long mLastModified;//记录最后一次修改时间
private boolean mDeleted;//表征是否被删除
public Node() {
mGid = null;
mName = "";
mLastModified = 0;
mDeleted = false;
}
public abstract JSONObject getCreateAction(int actionId);
public abstract JSONObject getUpdateAction(int actionId);
public abstract void setContentByRemoteJSON(JSONObject js);
public abstract void setContentByLocalJSON(JSONObject js);
public abstract JSONObject getLocalJSONFromContent();
public abstract int getSyncAction(Cursor c);
public void setGid(String gid) {
this.mGid = gid;
}
public void setName(String name) {
this.mName = name;
}
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
}
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
}
public String getGid() {
return this.mGid;
}
public String getName() {
return this.mName;
}
public long getLastModified() {
return this.mLastModified;
}
public boolean getDeleted() {
return this.mDeleted;
}
}

@ -0,0 +1,281 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.net.Uri;
//
// Notes 类中定义了很多常量这些常量大多是int型和string型
public class Notes {
public static final String AUTHORITY = "micode_notes";
public static final String TAG = "Notes";
public static final int TYPE_NOTE = 0;
public static final int TYPE_FOLDER = 1;
public static final int TYPE_SYSTEM = 2;
/**
* Following IDs are system folders' identifiers
* {@link Notes#ID_ROOT_FOLDER } is default folder
* {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
public static final int ID_ROOT_FOLDER = 0;//文件夹ID
public static final int ID_TEMPARAY_FOLDER = -1;//暂时文件夹ID
public static final int ID_CALL_RECORD_FOLDER = -2;//电话记录文件夹
public static final int ID_TRASH_FOLER = -3;//垃圾文件夹
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date";
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id";
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id";
public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type";
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id";
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date";
public static final int TYPE_WIDGET_INVALIDE = -1;
public static final int TYPE_WIDGET_2X = 0;
public static final int TYPE_WIDGET_4X = 1;
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
}
/**
* Uri to query all notes and folders
*/
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* Uri to query data
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
public interface NoteColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* The parent's id for note or folder
* <P> Type: INTEGER (long) </P>
*/
public static final String PARENT_ID = "parent_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
* Alert date
* <P> Type: INTEGER (long) </P>
*/
public static final String ALERTED_DATE = "alert_date";
/**
* Folder's name or text content of note
* <P> Type: TEXT </P>
*/
public static final String SNIPPET = "snippet";
/**
* Note's widget id
* <P> Type: INTEGER (long) </P>
*/
public static final String WIDGET_ID = "widget_id";
/**
* Note's widget type
* <P> Type: INTEGER (long) </P>
*/
public static final String WIDGET_TYPE = "widget_type";
/**
* Note's background color's id
* <P> Type: INTEGER (long) </P>
*/
public static final String BG_COLOR_ID = "bg_color_id";
/**
* For text note, it doesn't has attachment, for multi-media
* note, it has at least one attachment
* <P> Type: INTEGER </P>
*/
public static final String HAS_ATTACHMENT = "has_attachment";
/**
* Folder's count of notes
* <P> Type: INTEGER (long) </P>
*/
public static final String NOTES_COUNT = "notes_count";
/**
* The file type: folder or note
* <P> Type: INTEGER </P>
*/
public static final String TYPE = "type";
/**
* The last sync id
* <P> Type: INTEGER (long) </P>
*/
public static final String SYNC_ID = "sync_id";
/**
* Sign to indicate local modified or not
* <P> Type: INTEGER </P>
*/
public static final String LOCAL_MODIFIED = "local_modified";
/**
* Original parent id before moving into temporary folder
* <P> Type : INTEGER </P>
*/
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* The gtask id
* <P> Type : TEXT </P>
*/
public static final String GTASK_ID = "gtask_id";
/**
* The version code
* <P> Type : INTEGER (long) </P>
*/
public static final String VERSION = "version";
}
//数据栏目
public interface DataColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* The MIME type of the item represented by this row.
* <P> Type: Text </P>
*/
public static final String MIME_TYPE = "mime_type";
/**
* The reference id to note that this data belongs to
* <P> Type: INTEGER (long) </P>
*/
public static final String NOTE_ID = "note_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
* Data's content
* <P> Type: TEXT </P>
*/
public static final String CONTENT = "content";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
public static final String DATA1 = "data1";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
public static final String DATA2 = "data2";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
public static final String DATA3 = "data3";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
public static final String DATA4 = "data4";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
public static final String DATA5 = "data5";
}
//测试内容的数据
public static final class TextNote implements DataColumns {
/**
* Mode to indicate the text in check list mode or not
* <P> Type: Integer 1:check list mode 0: normal mode </P>
*/
public static final String MODE = DATA1;
public static final int MODE_CHECK_LIST = 1;
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
public static final class CallNote implements DataColumns {
/**
* Call date for this record
* <P> Type: INTEGER (long) </P>
*/
public static final String CALL_DATE = DATA1;
/**
* Phone number for this record
* <P> Type: TEXT </P>
*/
public static final String PHONE_NUMBER = DATA3;
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
}

@ -0,0 +1,367 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
//数据库操作用SQLOpenhelper,对一些note和文件进行数据库的操作
public class NotesDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "note.db";
private static final int DB_VERSION = 4;
public interface TABLE {//接口分成note和data在后面的程序里分别使用过
public static final String NOTE = "note";
public static final String DATA = "data";
}
private static final String TAG = "NotesDatabaseHelper";
private static NotesDatabaseHelper mInstance;
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," +
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," +
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")";
//数据库中需要存储的项目的名称,就相当于创建一个表格的表头的内容
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," +
DataColumns.MIME_TYPE + " TEXT NOT NULL," +
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA1 + " INTEGER," +
DataColumns.DATA2 + " INTEGER," +
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
")";//和上面的功能一样,主要是存储的项目不同
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";//存储便签编号的一个数据表格
/**
* Increase folder's note count when move note to the folder
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_update "+
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";//在文件夹中移出一个Note之后需要更改的数据的表格。
/**
* Decrease folder's note count when move note from folder
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " +
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
" END";//在文件夹中插入一个Note之后需要更改的数据的表格
/**
* Increase folder's note count when insert new note to the folder
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " +
" AFTER INSERT ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";//在文件夹中删除一个Note之后需要更改的数据的表格。
/**
* Decrease folder's note count when delete note from the folder
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END";//在文件夹中对一个Note导入新的数据之后需要更改的数据的表格。
/**
* Update note's content when insert data with type {@link DataConstants#NOTE}
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
" AFTER INSERT ON " + TABLE.DATA +
" WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";//Note数据被修改后需要更改的数据的表格
/**
* Update note's content when data with {@link DataConstants#NOTE} type has changed
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " +
" AFTER UPDATE ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";//Note数据被修改后需要更改的数据的表格
/**
* Update note's content when data with {@link DataConstants#NOTE} type has deleted
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " +
" AFTER delete ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.SNIPPET + "=''" +
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
" END";//Note数据被修改后需要更改的数据的表格
/**
* Delete datas belong to note which has been deleted
*/
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
"CREATE TRIGGER delete_data_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
" DELETE FROM " + TABLE.DATA +
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +
" END";//删除已删除便签后需要更改的数据的表格。
/**
* Delete notes belong to folder which has been deleted
*/
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
"CREATE TRIGGER folder_delete_notes_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE +
" BEGIN" +
" DELETE FROM " + TABLE.NOTE +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";//删除已删除的文件夹的便签后需要更改的数据的表格。
/**
* Move notes belong to folder which has been moved to trash folder
*/
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
"CREATE TRIGGER folder_move_notes_on_trash " +
" AFTER UPDATE ON " + TABLE.NOTE +
" WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";////还原垃圾桶中便签后需要更改的数据的表格。
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
Log.d(TAG, "note table has been created");
}//创建note数据表格
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER);
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER);
db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER);
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
//先删除原来有的数据库的触发器再重新创建新的数据库,重新创建上述定义的表格用的。
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues();
/**
* call record foler for call notes
*/
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
* root folder which is default folder
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
* temporary folder which is used for moving note
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
* create trash folder
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}//创建几个系统文件夹
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL);
reCreateDataTableTriggers(db);
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
Log.d(TAG, "data table has been created");
}//创建表格来存储便签内容
private void reCreateDataTableTriggers(SQLiteDatabase db) {
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete");
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
}//execSQL是数据库操作的API主要是更改行为的SQL语句。
static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
}
return mInstance;
}
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db);
createDataTable(db);
}//实现两个表格(上面创建的两个表格)
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false;
boolean skipV2 = false;
if (oldVersion == 1) {
upgradeToV2(db);
skipV2 = true; // this upgrade including the upgrade from v2 to v3
oldVersion++;
}
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db);
reCreateTriggers = true;
oldVersion++;
}
if (oldVersion == 3) {
upgradeToV4(db);
oldVersion++;
}
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
}
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
}
}//数据库版本的更新
private void upgradeToV2(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
createNoteTable(db);
createDataTable(db);
}//更新到V2版本
private void upgradeToV3(SQLiteDatabase db) {
// drop unused triggers
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// add a column for gtask id
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// add a trash system folder
ContentValues values = new ContentValues();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}//更新到V3版本
private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
}//更新到V4版本

@ -0,0 +1,318 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
//为存储和获取数据提供接口。可以在不同的应用程序之间共享数据
public class NotesProvider extends ContentProvider {
// UriMatcher用于匹配Uri
private static final UriMatcher mMatcher;
private NotesDatabaseHelper mHelper;
private static final String TAG = "NotesProvider";
private static final int URI_NOTE = 1;
private static final int URI_NOTE_ITEM = 2;
private static final int URI_DATA = 3;
private static final int URI_DATA_ITEM = 4;
private static final int URI_SEARCH = 5;
private static final int URI_SEARCH_SUGGEST = 6;
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 把需要匹配Uri路径全部给注册上
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
}
/**
* x'0A' represents the '\n' character in sqlite. For title and content in the search result,
* we will trim '\n' and white space in order to show more information.
*/
// 声明 NOTES_SEARCH_PROJECTION
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + ","
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + ","
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;
// 声明NOTES_SNIPPET_SEARCH_QUERY
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION
+ " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
@Override
// Context只有在onCreate()中才被初始化
// 对mHelper进行实例化
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
@Override
// 查询uri在数据库中对应的位置
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null; // 获取可读数据库
SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null;
// 匹配查找uri
//不同的匹配值,在数据库中查找相应的条目
switch (mMatcher.match(uri)) {
case URI_NOTE:
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_DATA:
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_SEARCH:
case URI_SEARCH_SUGGEST:
if (sortOrder != null || projection != null) {
throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
}//异常
String searchString = null;
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1);
}
} else {
searchString = uri.getQueryParameter("pattern");
}
if (TextUtils.isEmpty(searchString)) {
return null;
}
try {
searchString = String.format("%%%s%%", searchString);
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[] { searchString });//异常
} catch (IllegalStateException ex) {
Log.e(TAG, "got exception: " + ex.toString());
}
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);// 抛出异常
}
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
@Override//插入一个Uri
public Uri insert(Uri uri, ContentValues values) {
//获取可写数据库
SQLiteDatabase db = mHelper.getWritableDatabase();
long dataId = 0, noteId = 0, insertedId = 0;
switch (mMatcher.match(uri)) {
//新增一个条目
case URI_NOTE:
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break;
// 如果存在查找NOTE_ID
case URI_DATA:
if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID);
} else {
Log.d(TAG, "Wrong data format without note id:" + values.toString());
}
insertedId = dataId = db.insert(TABLE.DATA, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// Notify the note uri
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// Notify the data uri
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
// 返回插入的uri的路径
return ContentUris.withAppendedId(uri, insertedId);
}
@Override// 删除一个uri
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获得可写的数据库
boolean deleteData = false;
switch (mMatcher.match(uri)) {
case URI_NOTE:
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
/**
* ID that smaller than 0 is system folder which is not allowed to
* trash
*/
long noteId = Long.valueOf(id);
if (noteId <= 0) {
break;
}
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
break;
case URI_DATA:
count = db.delete(TABLE.DATA, selection, selectionArgs);
deleteData = true;
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
deleteData = true;
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count > 0) {
if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
@Override // 更新一个uri
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean updateData = false;
switch (mMatcher.match(uri)) {
case URI_NOTE:
increaseNoteVersion(-1, selection, selectionArgs);
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
break;
case URI_DATA:
count = db.update(TABLE.DATA, values, selection, selectionArgs);
updateData = true;
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
updateData = true;
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count > 0) {
if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
// 将字符串解析成规定格式
private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
//增加NoteVersion
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
sql.append(TABLE.NOTE);
sql.append(" SET ");
sql.append(NoteColumns.VERSION);
sql.append("=" + NoteColumns.VERSION + "+1 ");
if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
}
if (id > 0) {
sql.append(NoteColumns.ID + "=" + String.valueOf(id));
}
if (!TextUtils.isEmpty(selection)) {
String selectString = id > 0 ? parseSelection(selection) : selection;
for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args);
}
sql.append(selectString);
}
// execSQL()方法可以执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句
mHelper.getWritableDatabase().execSQL(sql.toString());
}
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
}

@ -0,0 +1,200 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
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 net.micode.notes.data.NotesDatabaseHelper.TABLE;
import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
public class SqlData {//得到类的简写名称存入字符串TAG中
private static final String TAG = SqlData.class.getSimpleName();
private static final int INVALID_ID = -99999;//为mDataId初始值设为-99999
// 集合了interface DataColumns中所有SF常量
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
//以下五个变量作为sql表中5列的编号
public static final int DATA_ID_COLUMN = 0;
public static final int DATA_MIME_TYPE_COLUMN = 1;
public static final int DATA_CONTENT_COLUMN = 2;
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
private ContentResolver mContentResolver;
//判断是否直接用Content生成是为true否则为false
private boolean mIsCreate;
private long mDataId;
private String mDataMimeType;
private String mDataContent;
private long mDataContentData1;
private String mDataContentData3;
private ContentValues mDiffDataValues;
/*
*
* mContentResolverContentProvider
* mIsCreate
*/
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true;
mDataId = INVALID_ID;
mDataMimeType = DataConstants.NOTE;
mDataContent = "";
mDataContentData1 = 0;
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
}
/*
*
* mContentResolverContentProvider
* mIsCreate*/
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDiffDataValues = new ContentValues();
}
//从光标处加载数据
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
mDataContent = c.getString(DATA_CONTENT_COLUMN);
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
//设置用于共享的数据,并提供异常抛出与处理机制
public void setContent(JSONObject js) throws JSONException {
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
//如果传入的JSONObject对象中有DataColumns.ID这一项则设置否则设为INVALID_ID
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
}
mDataId = dataId;
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
mDataMimeType = dataMimeType;
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
mDataContent = dataContent;
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
mDataContentData1 = dataContentData1;
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
mDataContentData3 = dataContentData3;
}
//获取共享的数据内容,并提供异常抛出与处理机制
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
//创建JSONObject对象。并将相关数据放入其中并返回。
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId);
js.put(DataColumns.MIME_TYPE, mDataMimeType);
js.put(DataColumns.CONTENT, mDataContent);
js.put(DataColumns.DATA1, mDataContentData1);
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
//commit函数用于把当前造作所做的修改保存到数据库
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
//创建便签
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
try {
mDataId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}//异常
} else {
if (mDiffDataValues.size() > 0) {
int result = 0;
if (!validateVersion) {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
String.valueOf(noteId), String.valueOf(version)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}//没有更新
}
}
mDiffDataValues.clear();
mIsCreate = false;
}
//获取当前ID
public long getId() {
return mDataId;
}
}

@ -0,0 +1,562 @@
* 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 WAR/*
RANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//sqlNote代码注释
/* sqlNotenotedata
便
便便便便
*/
package net.micode.notes.gtask.data;
import android.appwidget.AppWidgetManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.tool.ResourceParser;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class SqlNote {
/*
* TAG
* getSimpleName ()
*/
private static final String TAG = SqlNote.class.getSimpleName();
//定义一个标识便于后面数据的输入输出
private static final int INVALID_ID = -99999;
public static final String[] PROJECTION_NOTE = new String[] {
NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT, NoteColumns.PARENT_ID, NoteColumns.SNIPPET, NoteColumns.TYPE,
NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, NoteColumns.SYNC_ID,
NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID,
NoteColumns.VERSION
};
//设置17个列的编号
public static final int ID_COLUMN = 0;
public static final int ALERTED_DATE_COLUMN = 1;
public static final int BG_COLOR_ID_COLUMN = 2;
public static final int CREATED_DATE_COLUMN = 3;
public static final int HAS_ATTACHMENT_COLUMN = 4;
public static final int MODIFIED_DATE_COLUMN = 5;
public static final int NOTES_COUNT_COLUMN = 6;
public static final int PARENT_ID_COLUMN = 7;
public static final int SNIPPET_COLUMN = 8;
public static final int TYPE_COLUMN = 9;
public static final int WIDGET_ID_COLUMN = 10;
public static final int WIDGET_TYPE_COLUMN = 11;
public static final int SYNC_ID_COLUMN = 12;
public static final int LOCAL_MODIFIED_COLUMN = 13;
public static final int ORIGIN_PARENT_ID_COLUMN = 14;
public static final int GTASK_ID_COLUMN = 15;
public static final int VERSION_COLUMN = 16;
//定义了17个内部变量。12个可以从content中获得5个需要初始化为0或new
private Context mContext;
private ContentResolver mContentResolver; //定义了一个私有全局变量
private boolean mIsCreate; //后续选择生成
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private int mHasAttachment;
private long mModifiedDate;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private long mOriginParent;
private long mVersion;
private ContentValues mDiffNoteValues;
private ArrayList<SqlData> mDataList;
/*
*
* mIsCreate
*/
//构造函数只有context对所有的变量进行初始化
public SqlNote(Context context) { //构造函数进行初始化
mContext = context;
mContentResolver = context.getContentResolver(); //获取对象或直接查询数据
mIsCreate = true; //当前数据的创建
mId = INVALID_ID; //初始值定义
mAlertDate = 0;
mBgColorId = ResourceParser.getDefaultBgId(context);
mCreatedDate = System.currentTimeMillis();//调用系统函数获取创建时间
mHasAttachment = 0;
mModifiedDate = System.currentTimeMillis();//最后一次修改时间初始化为创建时间
mParentId = 0;
mSnippet = "";
mType = Notes.TYPE_NOTE;
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
mOriginParent = 0;
mVersion = 0;
mDiffNoteValues = new ContentValues(); //创建内容
mDataList = new ArrayList<SqlData>();
}
/*
*
* mIsCreate
*/
//构造函数有context和一个数据库的cursor多数变量通过cursor指向的一条记录直接进行初始化
public SqlNote(Context context, Cursor c) {
mContext = context;
mContentResolver = context.getContentResolver(); //获取程序之间的数据共享
mIsCreate = false; //记录当前数据创建方式
loadFromCursor(c); //从光标处载入数据
mDataList = new ArrayList<SqlData>();
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues();
}
/*
*
* mIsCreate
*/
public SqlNote(Context context, long id) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(id);
mDataList = new ArrayList<SqlData>();
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues();
}
/*
*id
*/
private void loadFromCursor(long id) {
Cursor c = null;
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(id)
}, null);//通过id获取对应的ContentResolver中的cursor
if (c != null) {
c.moveToNext();
loadFromCursor(c);
//加载数据进行初始化
//使函数SqlNote(Context context, long id)与SqlNote(Context context, long id)的实现方式基本相同
} else {
Log.w(TAG, "loadFromCursor: cursor = null");
}
} finally {
if (c != null)
c.close();
}
}
//功能描述:通过游标从光标处加载数据
private void loadFromCursor(Cursor c) {
//直接从一条记录中的获得以下变量的初始值
mId = c.getLong(ID_COLUMN);
mAlertDate = c.getLong(ALERTED_DATE_COLUMN);
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = c.getLong(CREATED_DATE_COLUMN);
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN);
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN);
mParentId = c.getLong(PARENT_ID_COLUMN);
mSnippet = c.getString(SNIPPET_COLUMN);
mType = c.getInt(TYPE_COLUMN);
mWidgetId = c.getInt(WIDGET_ID_COLUMN);
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
mVersion = c.getLong(VERSION_COLUMN);
}
//功能描述通过content机制获取共享数据并加载到数据库当前游标处
private void loadDataContent() {
Cursor c = null;
mDataList.clear();
try {
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] {
String.valueOf(mId)
}, null);
if (c != null) {
if (c.getCount() == 0) {
Log.w(TAG, "it seems that the note has not data");
return;
}
while (c.moveToNext()) {
SqlData data = new SqlData(mContext, c);
mDataList.add(data);
}
} else {
Log.w(TAG, "loadDataContent: cursor = null");
}
} finally {
if (c != null)
c.close();
}
}
//功能描述通过设置content机制用于共享的数据信息
public boolean setContent(JSONObject js) {
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); //创建一个JSONObject对象
if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
Log.w(TAG, "cannot set system folder");
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
// for folder we can only update the snnipet and type
//对于文件夹只能更新snnipet并键入
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : ""; //如果共享数据存在摘要需要将其赋给声明的snippct变量否则变量为空
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
} //如果SQLNote采用的是第一种构造方式或snippet为空则将snippet键值存入contenttvalue中
mSnippet = snippet;
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
: Notes.TYPE_NOTE;
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
} //判断类型是否匹配
mType = type;
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) {
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID;
if (mIsCreate || mId != id) {
mDiffNoteValues.put(NoteColumns.ID, id);
} //判断id是否匹配
mId = id;
long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note
.getLong(NoteColumns.ALERTED_DATE) : 0;
if (mIsCreate || mAlertDate != alertDate) {
mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate);
} //如果仅通过上下文对Note进行数据库修改或该标记日与原标记日不同
mAlertDate = alertDate;
int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note
.getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext);
if (mIsCreate || mBgColorId != bgColorId) {
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId);
} //判断背景颜色是否一致
mBgColorId = bgColorId;
long createDate = note.has(NoteColumns.CREATED_DATE) ? note
.getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis();
if (mIsCreate || mCreatedDate != createDate) {
mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate);
} //判断日期是否一致
mCreatedDate = createDate;
int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note
.getInt(NoteColumns.HAS_ATTACHMENT) : 0; //修改附件
if (mIsCreate || mHasAttachment != hasAttachment) {
mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment);
} //判断附件是否一致
mHasAttachment = hasAttachment;
long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note
.getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis(); //修改日期
if (mIsCreate || mModifiedDate != modifiedDate) {
mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate);
} //判断修改的日期是否一致
mModifiedDate = modifiedDate;
long parentId = note.has(NoteColumns.PARENT_ID) ? note
.getLong(NoteColumns.PARENT_ID) : 0;
if (mIsCreate || mParentId != parentId) {
mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId);
}
mParentId = parentId;
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : ""; //定义小部件
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
} //判断小部件是否一致
mSnippet = snippet;
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
: Notes.TYPE_NOTE;
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
} //判断类型是否一致
mType = type;
int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID)
: AppWidgetManager.INVALID_APPWIDGET_ID;
if (mIsCreate || mWidgetId != widgetId) {
mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId);
} //判断控件id是否一致
mWidgetId = widgetId;
int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note
.getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE;
if (mIsCreate || mWidgetType != widgetType) {
mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType);
}
mWidgetType = widgetType;
long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note
.getLong(NoteColumns.ORIGIN_PARENT_ID) : 0;
if (mIsCreate || mOriginParent != originParent) {
mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent);
}
mOriginParent = originParent;
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
SqlData sqlData = null;
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
for (SqlData temp : mDataList) {
if (dataId == temp.getId()) {
sqlData = temp;
}
}
}
if (sqlData == null) {
sqlData = new SqlData(mContext);
mDataList.add(sqlData);
}
sqlData.setContent(data);
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
}
return true;
}
//功能描述获取content机制提供的数据并加载到note中
public JSONObject getContent() { //获取content机制提供的数据并加载到note中
try {
JSONObject js = new JSONObject();
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
} //采用的是第一种构造方式,自然实施初始化而已,显示错误:没创建数据库
JSONObject note = new JSONObject();
if (mType == Notes.TYPE_NOTE) { //类型为note时//判断类型是否note类型
note.put(NoteColumns.ID, mId); //设置以下12个内部变量
note.put(NoteColumns.ALERTED_DATE, mAlertDate);//提醒时间
note.put(NoteColumns.BG_COLOR_ID, mBgColorId);//背景颜色
note.put(NoteColumns.CREATED_DATE, mCreatedDate);//创建日期
note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment);
note.put(NoteColumns.MODIFIED_DATE, mModifiedDate);//修改日期
note.put(NoteColumns.PARENT_ID, mParentId);
note.put(NoteColumns.SNIPPET, mSnippet);//摘要
note.put(NoteColumns.TYPE, mType);//类型
note.put(NoteColumns.WIDGET_ID, mWidgetId);
note.put(NoteColumns.WIDGET_TYPE, mWidgetType);//小部件类型
note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent);//最初的父类
js.put(GTaskStringUtils.META_HEAD_NOTE, note);;//将这个便签存入元数据中
JSONArray dataArray = new JSONArray();
for (SqlData sqlData : mDataList) {
JSONObject data = sqlData.getContent();
if (data != null) {
dataArray.put(data);
}
}
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
} else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { //类型为文件夹或者系统时
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.SNIPPET, mSnippet);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
}
//对于文件夹类型或者系统文件夹类型将id类型以及摘要存入jsonobject,然后对应META_HEAD_NOTE键存入共享其实也只有那么多可以传输
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return null;
}
//功能描述给当前id设置父id
public void setParentId(long id) {
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
}
//功能描述给当前id设置Gtaskid
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
}
//功能描述给当前id设置同步id
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
}
//功能描述:将本地修改初始化,即撤销当前所有修改
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
}
//功能描述获得当前id
public long getId() {
return mId;
}
//功能描述获得当前id的父id
public long getParentId() {
return mParentId;
}
//功能描述:获取小片段,即用于显示的部分便签内容
public String getSnippet() {
return mSnippet;
}
//功能描述:判断是否为便签类型
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE;
}
//功能描述commit函数作用于把当前所做的修改传送并保存在数据库中
public void commit(boolean validateVersion) {
if (mIsCreate) {
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID);
//如果是一个无效的id并且还含有这个id就将它移除
}
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
//插入便签的uri
try {
mId = Long.valueOf(uri.getPathSegments().get(1));
//强制转换path为id Long型
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
} //捕获异常转换出错显示错误“获取note的id出现错误”
if (mId == 0) {
throw new IllegalStateException("Create thread id failed");
} //创建线程 id 失败
if (mType == Notes.TYPE_NOTE) { //对于note类型引用sqlData.commit
for (SqlData sqlData : mDataList) { //直接使用sqldata中实现
sqlData.commit(mId, false, -1);
}
}
} else {
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
Log.e(TAG, "No such note"); //判断是否含有这个便签
throw new IllegalStateException("Try to update note with invalid id");
//如果没有就是无效id
}
if (mDiffNoteValues.size() > 0) {
mVersion ++;
int result = 0;
if (!validateVersion) { //构造字符串
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] {
String.valueOf(mId)
});
} else {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
String.valueOf(mId), String.valueOf(mVersion)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
} //如果没有更新,可能是用户在同步的时候更新了
}
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, validateVersion, mVersion);
}
} //对note类型还是对其中的data引用commit从而实现目的
}
// refresh local info
//刷新本地信息
loadFromCursor(mId);
if (mType == Notes.TYPE_NOTE)
loadDataContent(); //如果是便签类型,获取共享数据并加载到数据库
mDiffNoteValues.clear(); //清空共享数据
mIsCreate = false; //重置
}
}

@ -0,0 +1,365 @@
/*
* 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.
*/
//Task代码注释
//主要作用同步任务将创建、更新和同步动作包装成JSON对象同时使用本地和远程的JSON对结点内容进行设置从而获取同步信息实现本地和远程的信息同步
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
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 net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class Task extends Node { //定义一个Task类,继承Node类
private static final String TAG = Task.class.getSimpleName();
//调用函数得到类简写名称并存入字符串TAG内
private boolean mCompleted; //判断当前任务是否完成
private String mNotes; //在实例中存储数据的类型,表示任务的注释信息
private JSONObject mMetaInfo; //将在实例中存储数据的类型
private Task mPriorSibling; //对应的优先兄弟Task的指针
private TaskList mParent; //所在的任务列表的指针
public Task() {
super();
mCompleted = false;
mNotes = null;
mPriorSibling = null; //TaskList中当前Task前面的Task的指针
mParent = null; //当前Task所在的TaskList
mMetaInfo = null;
}
public JSONObject getCreateAction(int actionId) {
//getCreateAction方法接收一个整型参数actionId将该参数作为返回JSONObject对象的action_id属性值。
JSONObject js = new JSONObject();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, //存入当前task指针
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
//发送对actiontype的请求
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// index 父任务列表中的索引
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); //设置索引
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); //获取任务名
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); //对任务创建id进行判空操作
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, //实体类数据
GTaskStringUtils.GTASK_JSON_TYPE_TASK);
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
} //若存在文本输入放入实体内
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); //讲实体存入js
// parent_id
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); //父类id类型
// dest_parent_type
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP); //所在列表的id存入父id
// list_id
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// prior_sibling_id
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
//如果该任务在兄弟任务中不是第一个则设置prior_sibling_id属性值为前一个兄弟任务的gid
//将其优先存入ID序列中
} catch (JSONException e) {
Log.e(TAG, e.toString()); //e.toString()获取异常类型和异常详细消息
e.printStackTrace();
throw new ActionFailureException("fail to generate task-create jsonobject");
} //抛出异常处理机制
return js;
}
public JSONObject getUpdateAction(int actionId) { //更新action
//getUpdateAction方法接收一个整型参数actionId将该参数作为返回JSONObject对象的action_id属性值
JSONObject js = new JSONObject();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
//action_type属性值为update表示更新操作
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}//如果存在 notes ,则将其也放入 entity 中
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
//deleted属性表示任务是否被删除如果该值为true则该任务会被移除
} catch (JSONException e) { //获取异常
Log.e(TAG, e.toString());
e.printStackTrace(); //命令行打印异常信息在程序中出错的位置及原因
throw new ActionFailureException("fail to generate task-update jsonobject");
} //生成任务更新的数据传输失败
return js;
}
public void setContentByRemoteJSON(JSONObject js) //通过云端传输数据设置内容
//定义了一个setContentByRemoteJSON方法该方法从远程JSON对象中获取任务的属性值并将这些属性值设置到本地任务对象中
//表示从远程服务器获取的任务信息
{
if (js != null) {
try {
// id
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { //如果js指针指向的目标存在
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); //利用类自身的set函数并且使用了JSONObject的get类型函数
} //判断记录是否被删除
// last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
// notes
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}
// deleted
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}
// completed
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
} //记录已经完成的
} catch (JSONException e) { //抛出异常
Log.e(TAG, e.toString()); //获取异常类型和异常信息
e.printStackTrace(); //打印
throw new ActionFailureException("fail to get task content from jsonobject");
}
}
}
public void setContentByLocalJSON(JSONObject js) { //通过本地的jsonobject获取内容
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) { //如果js不存在或者js没有元数据的开头或者js指针没有元数据
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); //反馈给用户出错信息
}
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { //note类型不匹配
Log.e(TAG, "invalid type"); //无效
return;
}
for (int i = 0; i < dataArray.length(); i++) {
//遍历 dataArray 查找与数据库中DataConstants.NOTE 记录信息一致的 data
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
setName(data.getString(DataColumns.CONTENT)); //判断两个字符串是否相等
break;
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
public JSONObject getLocalJSONFromContent() { //从content获取本地json
String name = getName(); //获取名称
try {
if (mMetaInfo == null) {
// new task created from web
if (name == null) {
Log.w(TAG, "the note seems to be an empty one");
return null;
}
//若命名为空则创建一个新的
JSONObject js = new JSONObject();
JSONObject note = new JSONObject();
JSONArray dataArray = new JSONArray();
JSONObject data = new JSONObject(); //对四个指针进行初始化
data.put(DataColumns.CONTENT, name);
dataArray.put(data);
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); //元数据类型信息非空,则进行同步更新操作
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
js.put(GTaskStringUtils.META_HEAD_NOTE, note); //实现数据同步
return js;
} else { //否则将元数据同步至数据中
// synced task
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); //同步任务
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); //同步数据
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
data.put(DataColumns.CONTENT, getName());
break;
}
}//通过循环查找与数据库中DataConstants.NOTE 记录信息一致的 data
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
return mMetaInfo;
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}
}
public void setMetaInfo(MetaData metaData) { //设置元数据信息
if (metaData != null && metaData.getNotes() != null) {
try {
mMetaInfo = new JSONObject(metaData.getNotes());
} catch (JSONException e) {
Log.w(TAG, e.toString());
mMetaInfo = null;
}
} //如果元数据非空且其 notes 非空,则修改元数据类型信息
}
public int getSyncAction(Cursor c) { //实现同步操作
try {
JSONObject noteInfo = null; //新建一个 JSONObject 的对象实体
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
} //元数据信息不为空并且元数据信息还含有“META_HEAD_NOTE”说明便签存在
if (noteInfo == null) {
Log.w(TAG, "it seems that note meta has been deleted");
return SYNC_ACTION_UPDATE_REMOTE;
} //云端便签 id 已被删除,不存在,返回更新本地数据的同步行为
if (!noteInfo.has(NoteColumns.ID)) {
Log.w(TAG, "remote note id seems to be deleted");
return SYNC_ACTION_UPDATE_LOCAL;
} //匹配note的id
// validate the note id now
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
Log.w(TAG, "note id doesn't match");
return SYNC_ACTION_UPDATE_LOCAL; //判断修改后的ID匹配是否成功成功则返回无同步操作未成功则应用云端到本地返回本地同步更新操作
}
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// there is no local update
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// no update both side
return SYNC_ACTION_NONE;
} else {
// apply remote to local
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// validate gtask id
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
//判断gtask的id与获取的id是否匹配
Log.e(TAG, "gtask id doesn't match"); //错误,未找到 gtask 的 id
return SYNC_ACTION_ERROR;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
////错误,未找到 gtask 的 id
// local modification only
return SYNC_ACTION_UPDATE_REMOTE;
} else {
return SYNC_ACTION_UPDATE_CONFLICT;
}
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return SYNC_ACTION_ERROR;
}
public boolean isWorthSaving() { //判断是否修改并同步操作
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0);
}
public void setCompleted(boolean completed) {
this.mCompleted = completed;
} //设定是否完成的成员变量
public void setNotes(String notes) {
this.mNotes = notes;
} //设定note成员变量
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling;
} //设置优先兄弟 task 的优先级
public void setParent(TaskList parent) {
this.mParent = parent;
} //设置父类节点
public boolean getCompleted() {
return this.mCompleted;
} //判断是否完成
public String getNotes() {
return this.mNotes;
} //获取成员变量 mNotes 的信息
public Task getPriorSibling() {
return this.mPriorSibling;
} //获取优先兄弟列表
public TaskList getParent() {
return this.mParent;
} //获取父类节点列表
}

@ -0,0 +1,431 @@
/*
* 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.
*/
//TaskList代码标注
//主要作用同步任务列表将Task组织成同步任务列表进行管理
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class TaskList extends Node {//创建继承 Node的任务表类
private static final String TAG = TaskList.class.getSimpleName(); //tag标记
//调用getSimplename函数获取名字并赋值给TAG作为标记
private int mIndex; //当前TaskList的指针
private ArrayList<Task> mChildren; //类中主要的保存数据的单元用来实现一个以Task为元素的ArrayList
public TaskList() { //构造函数进行初始化
super(); //调用父类构造方法
mChildren = new ArrayList<Task>(); //声明数组
mIndex = 1;
}
/* (non-Javadoc)
* @see net.micode.notes.gtask.data.Node#getCreateAction(int)
* JSONObject
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject(); //直接构建即直接实例化一个JSONObject对象
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
//操作的是列表而不只是单个task了
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
// entity_delta
// 新建一个 JSONObject 对象,名为实体,将 namecreator identity type 三个信息存在一起
JSONObject entity = new JSONObject(); //创建一个实体类并进行初始化
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
//getName是父类的一个函数
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
//初始化id
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
//将实体类型设置为“GROUP”
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-create jsonobject");
}
return js;
}
/* (non-Javadoc)
* @see net.micode.notes.gtask.data.Node#getUpdateAction(int)
* JSONObject
*/
public JSONObject getUpdateAction(int actionId) { //接受更新action返回jsonobject
JSONObject js = new JSONObject(); //创建一个对象并进行初始化
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-update jsonobject");
}
return js;
}
public void setContentByRemoteJSON(JSONObject js) {
//通过云端 JSON 数据设置实例化对象 js 的内容
if (js != null) {
try {
// id
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
//判断js对象是否为空如果为空即没有内容就不需要进行设置了
// last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
//调用JSONObject中的getLong函数得到last_modified的号并作为参数传给setLastModified函数父类函数进行设置
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
} //对任务的name进行设置
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get tasklist content from jsonobject");
}
}
}
public void setContentByLocalJSON(JSONObject js) { //通过本地 JSON 数据设置对象 js 内容
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
//若 js 创建失败或 js 中不存在 META_HEAD_NOTE信息
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
} //警告,没有可用资源
try {
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
//NullPointerException这个异常出现在处理对象时对象不存在但又没有捕捉到进行处理的时候
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
//对文件夹,设置文件名
String name = folder.getString(NoteColumns.SNIPPET);
//获取文件夹片段字符串作为文件夹名称
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name);
//设置名称为MIUI系统文件夹前缀+文件夹名称
} else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
//判断是不是系统文件
if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT);
//如果是根文件夹设置名称MIUI系统文件夹前缀+默认文件夹名称
else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE);
//如果是通话文件夹设置名称MIUI系统文件夹前缀+通话便签文件夹名称
else
Log.e(TAG, "invalid system folder");
;//无效系统文件夹警告
} else {
Log.e(TAG, "error type");
//错误的文件夹类型
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
public JSONObject getLocalJSONFromContent() { //从content获取本地json
try {
JSONObject js = new JSONObject(); //创建对象并初始化
JSONObject folder = new JSONObject(); //创建文件夹对象并初始化
String folderName = getName();
if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX))
folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(),
folderName.length());
folder.put(NoteColumns.SNIPPET, folderName);
//如果获取的文件夹名称是以[MIUI_Notes]开头,则文件夹名称应删掉前缀
if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT)
|| folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE))
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
//这里与上一个函数setContentByRemoteJSON(JSONObject js)是一个逆过程可以参看上一个函数是如何构造出foldername的
else
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
js.put(GTaskStringUtils.META_HEAD_NOTE, folder);
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}
}
public int getSyncAction(Cursor c) { //获取同步信息
try {
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { //若本地修改未记录
// there is no local update
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
//记录最后一次修改
// no update both side
return SYNC_ACTION_NONE;
} else {
// apply remote to local
return SYNC_ACTION_UPDATE_LOCAL;
} //匹配失败,返回更新本地数据的同步行为
} else {
// validate gtask id
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
} //如果获取的ID不匹配返回同步动作失败
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// local modification only
return SYNC_ACTION_UPDATE_REMOTE;
//如果是最近一次修改的 id则返回云端更新的同步动作
} else {
// for folder conflicts, just apply local modification
return SYNC_ACTION_UPDATE_REMOTE;
}
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return SYNC_ACTION_ERROR;
}
// @return
//功能获得TaskList的大小即mChildren的大小
public int getChildTaskCount() {
return mChildren.size();
}
//获得TaskList的大小即mChildren的大小mChildren 是TaskList 的一个实例
//@param task
//@return 返回值为是否成功添加任务。
//功能:在当前任务表末尾添加新的任务。
public boolean addChildTask(Task task) {
boolean ret = false;
if (task != null && !mChildren.contains(task)) { //任务非空且任务表中不存在该任务
ret = mChildren.add(task);
if (ret) {
// need to set prior sibling and parent
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren
.get(mChildren.size() - 1));
task.setParent(this);
//ArrayList的每一次修改变化都要紧跟相关Task中PriorSibling的修改
}
}
return ret;
}
/**
* @param task
* @param index
* @return
*
*/
public boolean addChildTask(Task task, int index) {
if (index < 0 || index > mChildren.size()) {
Log.e(TAG, "add child task: invalid index");
return false;
}
//在当前任务表的指定位置添加新的任务index是指针
int pos = mChildren.indexOf(task);
if (task != null && pos == -1) {
mChildren.add(index, task);
//获取要添加的任务在列表中的位置,如果不在列表中则返回-1
// update the task list
Task preTask = null; //更新任务表
Task afterTask = null;
if (index != 0)
preTask = mChildren.get(index - 1);
if (index != mChildren.size() - 1)
afterTask = mChildren.get(index + 1);
task.setPriorSibling(preTask); //使得前后三个任务连在一起
if (afterTask != null)
afterTask.setPriorSibling(task);
}
return true;
}
/**
* @param task
* @return
* TaskListTask
*/
public boolean removeChildTask(Task task) { //删除任务表中的子任务
boolean ret = false;
int index = mChildren.indexOf(task); //获取该任务在任务表中的索引
if (index != -1) { //索引有效,任务在表中存在
ret = mChildren.remove(task); //移除任务
if (ret) {
// reset prior sibling and parent
task.setPriorSibling(null);
task.setParent(null); //删除成功task的上一个任务指针和父指针置空
// update the task list
if (index != mChildren.size()) {
mChildren.get(index).setPriorSibling(
index == 0 ? null : mChildren.get(index - 1));
} //删除成功后,要对任务列表进行更新
}
}
return ret;
}
/**
* @param task
* @param index
* @return
* TaskListTaskindex
*/
public boolean moveChildTask(Task task, int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "move child task: invalid index");
return false;
}
int pos = mChildren.indexOf(task);
if (pos == -1) {
Log.e(TAG, "move child task: the task should in the list");
return false;
}
if (pos == index)
return true;
return (removeChildTask(task) && addChildTask(task, index));
}
//利用已经实现好的功能完成当下功能
/**
* @param gid
* @return
* gidTask
*/
public Task findChildTaskByGid(String gid) {
for (int i = 0; i < mChildren.size(); i++) {
Task t = mChildren.get(i);
if (t.getGid().equals(gid)) {
return t;
}
}
return null;
}
/**
* @param task
* @return
* Taskindex
*/
public int getChildTaskIndex(Task task) {
return mChildren.indexOf(task);
}
/**
* @param index
* @return
* indexTask
*/
public Task getChildTaskByIndex(int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "getTaskByIndex: invalid index"); //如果指针不在范围内,无效索引
return null;
}
return mChildren.get(index);
}
/**
* @param gid
* @return
* gidTask
*/
public Task getChilTaskByGid(String gid) {
for (Task task : mChildren) { //ArrayList的遍历方法 //通过索引获取子任务
if (task.getGid().equals(gid))
return task;
}
return null;
}
public ArrayList<Task> getChildTaskList() {
return this.mChildren;
} //获取子任务列表
public void setIndex(int index) {
this.mIndex = index;
} //设置任务索引
public int getIndex() {
return this.mIndex;
} //获取任务索引
}

@ -0,0 +1,264 @@
/*
* 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;//抽象类提供了一组通用的API,通过 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 {//NoteEditText继承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>();
//建立由字符和整数的hash表链接电话数据、网站数据与邮箱数据
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
* Call by the {@link NoteEditActivity} to delete or add edit text
*/
//在NoteEditActivity中进行删除或添加文本的操作并对进行更改的数据进行标记
public interface OnTextViewChangeListener {
/**
* Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens
* and the text is null
*/
//当{@link KeyEvent#KEYCODE_DEL发生并且文本为空时删除当前编辑文本
void onEditTextDelete(int index, String text);
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*/
//当{@link KeyEvent#KEYCODE_ENTER发生时在当前编辑文本后添加编辑文本
void onEditTextEnter(int index, String text);
/**
* Hide or show item option when text change
*/
//文本更改时隐藏或显示项目选项
void onTextChange(int index, boolean hasText);
}
private OnTextViewChangeListener mOnTextViewChangeListener;
public NoteEditText(Context context) {
super(context, null);//用super引用父类变量
mIndex = 0;
}//根据context设置文本
public void setIndex(int index) {
mIndex = index;
}//设置光标,并对光标进行初始化
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}//令listener赋值给mOnTextViewChangeListener实现文本修改标记初始化
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}//规定context控件属性便于维护便签动态变化的属性
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}//通过类型定义和引用父类变量实现defStyle的自动初始化
@Override
//view里的函数处理手机屏幕的所有事件
/*event
*/
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
//对每一次屏幕被按下的情况进行重述
case MotionEvent.ACTION_DOWN:
//更新触屏坐标值
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
Layout layout = getLayout();
//运用布局控件Layout对每一次的坐标新值xy进行设置新位置
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
Selection.setSelection(getText(), off);
//同过selection寻找并更新当前光标新坐标值
break;
}
return super.onTouchEvent(event);
//回溯至函数,实现对每一次触屏事件的计算
}
@Override
// 函数功能:处理用户按下一个键盘按键时会触发 的事件
public boolean onKeyDown(int keyCode, KeyEvent event) //当作为一个构造函数(带有运算符 new调用时Boolean() 将把它的参数转换成一个布尔值,并且返回一个包含该值的 Boolean 对象
{
switch (keyCode) {//根据按键的unicode编码值判断
case KeyEvent.KEYCODE_ENTER://按键编码值传入系统
if (mOnTextViewChangeListener != null)//对输入命令进行判空
{
return false;//对非法输入进行错误提示
}
break;
case KeyEvent.KEYCODE_DEL://删除按键
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
return super.onKeyDown(keyCode, event);//继续进行父类其他点击事件
}
@Override
// 函数功能:处理用户松开一个键盘按键时会触发 的事件
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
//根据按键Unicode编码值进行操作包括进入和删除两种操作
case KeyEvent.KEYCODE_DEL:
if (mOnTextViewChangeListener != null) //判断是否被修改
{
if (0 == mSelectionStartBeforeDelete && mIndex != 0)//判断是否有被编辑修改并且文档不为空
{
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
////利用上文OnTextViewChangeListener对KEYCODE_DEL按键情况的删除函数进行删除
return true;
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
//出现其他情况进行报错,文档修改监听器未被建立
}
break;
case KeyEvent.KEYCODE_ENTER://对监听器错误情况进行判断
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();//获取当前起始位置
String text = getText().subSequence(selectionStart, length()).toString();//获取当前文本
setText(getText().subSequence(0, selectionStart));//根据获取文本设置当前文本
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
//当错误发生及当{@link KeyEvent#KEYCODE_ENTER}时添加新文本
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");////其他情况报错,文档的改动监听器并没有建立
}
break;
default:
break;
}
return super.onKeyUp(keyCode, event);
//回溯至前面函数继续执行父类的其他按键弹起的事件
}
@Override
//函数功能:当焦点发生变化时,会自动调用该方法来处理焦点改变的事件
// * 参数focused表示触发该事件的View是否获得了焦点当该控件获得焦点时Focused等于true否则等于false。
// direction表示焦点移动的方向用数值表示
// Rect表示在触发事件的View的坐标系中前一个获得焦点的矩形区域即表示焦点是从哪里来的。如果不可用则为null
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) //判断监听器是否建立
{
if (!focused && TextUtils.isEmpty(getText())) //截取焦点并且判断文本是否为空
{
mOnTextViewChangeListener.onTextChange(mIndex, false);
//mOnTextViewChangeListener子函数置false隐藏事件选项
} else {
mOnTextViewChangeListener.onTextChange(mIndex, true);
//mOnTextViewChangeListener子函数置true显示事件选项
}
}
super.onFocusChanged(focused, direction, previouslyFocusedRect);
//回溯至前面函数继续执行父类的其他焦点变化事件
}
@Override
//函数意义:生成上下文菜单
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) //判断文本是否存在
{
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
//获取开始位置和截至位置
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
//计算开始到结尾的最大值和最小值
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
//设置url信息范围值
if (urls.length == 1) {
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) //获取计划表中的所有key值
{
if(urls[0].getURL().indexOf(schema) >= 0) //若url可以添加则在添加后将defaultResId置为key所映射的值
{
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
if (defaultResId == 0) //defaultResId == 0则说明url并没有添加任何东西所以置为连接其他SchemaActionResMap的值
{
defaultResId = R.string.note_link_other;
}
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener//建立菜单
(
new OnMenuItemClickListener() //新建按键监听器
{
public boolean onMenuItemClick(MenuItem item) {
// goto a new intent
urls[0].onClick(NoteEditText.this);//根据文本进行按键设置
return true;
}
});
}
}
super.onCreateContextMenu(menu);
//回溯至前面函数继续执行父类的其他菜单创建的事件
}
}

@ -0,0 +1,243 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
public class NoteItemData {
static final String [] PROJECTION = new String [] {
NoteColumns.ID,//备注列ID
NoteColumns.ALERTED_DATE,//提醒日期
NoteColumns.BG_COLOR_ID,//背景ID颜色
NoteColumns.CREATED_DATE,//创建数据
NoteColumns.HAS_ATTACHMENT, //具有附件
NoteColumns.MODIFIED_DATE,//修改数据
NoteColumns.NOTES_COUNT,//备注计数
NoteColumns.PARENT_ID,//家长ID
NoteColumns.SNIPPET,//片段
NoteColumns.TYPE,//类型
NoteColumns.WIDGET_ID,//部件ID
NoteColumns.WIDGET_TYPE,//部件类型
};
private static final int ID_COLUMN = 0;//ID列
private static final int ALERTED_DATE_COLUMN = 1;//警报日期列
private static final int BG_COLOR_ID_COLUMN = 2;//背景颜色ID列
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;//父ID列
private static final int SNIPPET_COLUMN = 8;//代码段列
private static final int TYPE_COLUMN = 9;//类型栏
private static final int WIDGET_ID_COLUMN = 10;//小部件ID列
private static final int WIDGET_TYPE_COLUMN = 11;//小部件类型列
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private boolean mHasAttachment;
private long mModifiedDate;
private int mNotesCount;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private String mName;
private String mPhoneNumber;
private boolean mIsLastItem;
private boolean mIsFirstItem;
private boolean mIsOnlyOneItem;
private boolean mIsOneNoteFollowingFolder;
private boolean mIsMultiNotesFollowingFolder;
public NoteItemData(Context context, Cursor cursor) //利用光标cursor获取数据初始化NoteItemData
{
mId = cursor.getLong(ID_COLUMN);
//将数据类型变为long类
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
//将数据类型变为long类
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
//将数据类型变为int类
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
//将数据类型变为long类
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
//对HAS_ATTACHMENT_COLUMN进行判0>0为ture否则为false
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
//将数据类型变为long类
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
//将数据类型变为int类
mParentId = cursor.getLong(PARENT_ID_COLUMN);
//将数据类型变为long类
mSnippet = cursor.getString(SNIPPET_COLUMN);
//将数据类型变为string类
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
mPhoneNumber = "";
//初始化电话号码信息
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber))//mphonenumber里有符合字符串则用contart功能连接
{
mName = Contact.getContact(context, mPhoneNumber);
if (mName == null)//如果用户姓名为空,则令姓名等于电话号
{
mName = mPhoneNumber;
}
}
}
if (mName == null)//若名字为空,则输入名字
{
mName = "";
}
checkPostion(cursor);
}
//据鼠标的位置设置标记,记录位置
private void checkPostion(Cursor cursor) {
//初始化标记
mIsLastItem = cursor.isLast() ? true : false;//寻找光标最后位置
mIsFirstItem = cursor.isFirst() ? true : false;//寻找光标的初始位置
mIsOnlyOneItem = (cursor.getCount() == 1);//记录是否遍历完所有路程
//初始化“多重子文件”“单一子文件”
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
//设置标记
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {//若此时标记为note格式并且不为第一个元素
int position = cursor.getPosition();//用getPosition得到此时位置
if (cursor.moveToPrevious()) {//获取光标位置后,回溯至上一行,
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {//若光标满足系统格式或note格式
if (cursor.getCount() > (position + 1))//如果得到的步数大于地址+1
{
mIsMultiNotesFollowingFolder = true;//若是数据行数大于但前位置+1则设置成正确
} else {
mIsOneNoteFollowingFolder = true;//否则单一文件夹标记为true
}
}
if (!cursor.moveToNext()) {//若不能再往下走则报错
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
}
}
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}//文件夹注释行
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
public boolean isLast() {
return mIsLastItem;
}
public String getCallName() {
return mName;
}//得到用户名
public boolean isFirst() {
return mIsFirstItem;
}//得到初始地址
public boolean isSingle() {
return mIsOnlyOneItem;
}
public long getId() {
return mId;
}
public long getAlertDate() {
return mAlertDate;
}
public long getCreatedDate() {
return mCreatedDate;
}
public boolean hasAttachment() {
return mHasAttachment;
}
public long getModifiedDate() {
return mModifiedDate;
}
public int getBgColorId() {
return mBgColorId;
}
public long getParentId() {
return mParentId;
}
public int getNotesCount() {
return mNotesCount;
}
public long getFolderId () {
return mParentId;
}
public int getType() {
return mType;
}
public int getWidgetType() {
return mWidgetType;
}
public int getWidgetId() {
return mWidgetId;
}
public String getSnippet() {
return mSnippet;
}
public boolean hasAlert() {
return (mAlertDate > 0);
}
//若数据父id为保存至文件夹模式的id且满足电话号码单元不为空则isCallRecord为true
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,554 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.appwidget.AppWidgetManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.tool.ResourceParser;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class SqlNote {
private static final String TAG/*私有静态常量*/ = SqlNote.class.getSimpleName();
private static final int INVALID_ID = -99999;//无效的 ID
public static final String[] PROJECTION_NOTE = new String[]
//需要在查询中选择的笔记属性
{
NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT, NoteColumns.PARENT_ID, NoteColumns.SNIPPET, NoteColumns.TYPE,
NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, NoteColumns.SYNC_ID,
NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID,
NoteColumns.VERSION
};
public static final int ID_COLUMN = 0;//ID 属性在 PROJECTION_NOTE 数组中的下标为 0
public static final int ALERTED_DATE_COLUMN = 1;
public static final int BG_COLOR_ID_COLUMN = 2;
public static final int CREATED_DATE_COLUMN = 3;
public static final int HAS_ATTACHMENT_COLUMN = 4;
public static final int MODIFIED_DATE_COLUMN = 5;
public static final int NOTES_COUNT_COLUMN = 6;
public static final int PARENT_ID_COLUMN = 7;
public static final int SNIPPET_COLUMN = 8;
public static final int TYPE_COLUMN = 9;
public static final int WIDGET_ID_COLUMN = 10;
public static final int WIDGET_TYPE_COLUMN = 11;
public static final int SYNC_ID_COLUMN = 12;
public static final int LOCAL_MODIFIED_COLUMN = 13;
public static final int ORIGIN_PARENT_ID_COLUMN = 14;
public static final int GTASK_ID_COLUMN = 15;
public static final int VERSION_COLUMN = 16;
private Context mContext;//上下文
private ContentResolver mContentResolver;//ContentResolver 对象
private boolean mIsCreate;//表示是否在数据库中创建新笔记
private long mId;//笔记的 ID
private long mAlertDate;//提醒日期
private int mBgColorId;//背景颜色 ID
private long mCreatedDate;//创建日期
private int mHasAttachment;//是否有附件
private long mModifiedDate;//修改日期
private long mParentId;//父笔记 ID
private String mSnippet;//笔记的摘要
private int mType;//笔记类型
private int mWidgetId;//小部件 ID
private int mWidgetType;//小部件类型
private long mOriginParent;//原始父笔记 ID
private long mVersion;//版本号
private ContentValues mDiffNoteValues;// ContentValues 类型的 mDiffNoteValues
private ArrayList<SqlData> mDataList;//SqlData 对象的列表
public SqlNote(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
//将传入的上下文对象赋值给成员变量 mContext 和 mContentResolver
mIsCreate = true;
//将 mIsCreate 置为 true表示该笔记是要被创建的
mId = INVALID_ID;
//mId 赋值为常量 INVALID_ID表示该笔记的 ID 是无效的
mAlertDate = 0;
mBgColorId = ResourceParser.getDefaultBgId(context);//
// 将 mBgColorId 设为默认背景颜色 ID
mCreatedDate = System.currentTimeMillis();
// 将 mCreatedDate 和 mModifiedDate 设为当前时间戳。
mHasAttachment = 0;
//mHasAttachment 设为 0表示该笔记没有附件
mModifiedDate = System.currentTimeMillis();
mParentId = 0;
//mParentId 设为 0表示该笔记的父 ID 是无效的
mSnippet = "";//mSnippet 设为空字符串,表示该笔记没有摘要
mType = Notes.TYPE_NOTE;
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;//该笔记没有小部件 ID
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;//该笔记不是小部件
mOriginParent = 0;//该笔记没有原始父笔记
mVersion = 0;//笔记的版本号是 0
mDiffNoteValues = new ContentValues();
//建一个空的 ContentValues 对象赋值给 mDiffNoteValues
mDataList = new ArrayList<SqlData>();
//创建一个空的 SqlData 对象列表 mDataList
}
public SqlNote(Context context, Cursor c)
//将传入的 Cursor 对象中的数据加载到 SqlNote 对象中
{
mContext = context;
mContentResolver = context.getContentResolver();
//分别赋值为传入的上下文
mIsCreate = false;//这是一个已经存在的笔记
loadFromCursor(c);//从 Cursor 对象中读取数据并加载到 SqlNote 对象中
mDataList = new ArrayList<SqlData>();//创建一个空的 SqlData 对象列表 mDataList
if (mType == Notes.TYPE_NOTE)
//如果该笔记是普通笔记,则调用 loadDataContent() 方法从数据库读取笔记内容和附件信息
loadDataContent();
mDiffNoteValues = new ContentValues();
//创建一个空的 ContentValues 对象赋值给 mDiffNoteValues
}
public SqlNote(Context context, long id)
//根据笔记 ID 加载笔记数据
{
mContext = context;
mContentResolver = context.getContentResolver();//初始化
mIsCreate = false;//已经存在的笔记
loadFromCursor(id);
mDataList = new ArrayList<SqlData>();
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues();
//创建一个空的 ContentValues 对象赋值给 mDiffNoteValues
}
private void loadFromCursor(long id)
//根据给定的笔记 ID 从数据库中加载笔记信息
{
Cursor c = null;
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
//查询指定 ID 的笔记数据
new String[] {
String.valueOf(id)
}, null);//对获取数据进行判空
if (c != null) {
c.moveToNext();
loadFromCursor(c);
// 不为空,将游标移动到第一行,
// 并调用 loadFromCursor(Cursor c) 方法将数据加载到 SqlNote 对象中
} else {
Log.w(TAG, "loadFromCursor: cursor = null");//为空,则打印一条警告日志
}
} finally {
if (c != null)
c.close();
}
}
private void loadFromCursor(Cursor c)
//从 Cursor 对象中加载笔记数据
{
mId = c.getLong(ID_COLUMN);//笔记的 ID
mAlertDate = c.getLong(ALERTED_DATE_COLUMN);//提醒时间
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN);//背景颜色 ID
mCreatedDate = c.getLong(CREATED_DATE_COLUMN);//创建时间
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN);//是否有附件
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN);//修改时间
mParentId = c.getLong(PARENT_ID_COLUMN);//父亲笔记的 ID
mSnippet = c.getString(SNIPPET_COLUMN);//笔记片段
mType = c.getInt(TYPE_COLUMN);//笔记类型
mWidgetId = c.getInt(WIDGET_ID_COLUMN);//挂件 ID
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);//挂件类型
mVersion = c.getLong(VERSION_COLUMN);//版本号
}
private void loadDataContent()
//从数据库中读取笔记的内容和附件信息,并将其存储在 mDataList
{
Cursor c = null;
mDataList.clear();
//清空 mDataList 列表
try {
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] {
String.valueOf(mId)
}, null);//判断获取到的 Cursor 对象是否为空
if (c != null) {// 不为空,则判断游标中是否有数据
if (c.getCount() == 0) {
Log.w(TAG, "it seems that the note has not data");
//无数据,则打印一条警告日志并返回
return;
}
while (c.moveToNext()) {
SqlData data = new SqlData(mContext, c);
mDataList.add(data);
}//遍历游标中的每一行数据,创建 SqlData 对象并添加到 mDataList 列表
} else {
Log.w(TAG, "loadDataContent: cursor = null");
}
} finally {
if (c != null)
c.close();
}
}
public boolean setContent(JSONObject js)
//据传入的 JSONObject 对象设置笔记的内容
{
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
Log.w(TAG, "cannot set system folder");
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
// for folder we can only update the snnipet and type
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
mSnippet = snippet;
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
: Notes.TYPE_NOTE;
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
mType = type;
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) {
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID;
if (mIsCreate || mId != id) {
mDiffNoteValues.put(NoteColumns.ID, id);
}
mId = id;
long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note
.getLong(NoteColumns.ALERTED_DATE) : 0;
if (mIsCreate || mAlertDate != alertDate) {
mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate);
}
mAlertDate = alertDate;
int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note
.getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext);
if (mIsCreate || mBgColorId != bgColorId) {
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId);
}
mBgColorId = bgColorId;
//系统文件夹或者文件夹类型的笔记,只能更新摘要和类型,而对于笔记类型的笔记,会解析出一系列字段,并将它们保存在一个 List 中
long createDate = note.has(NoteColumns.CREATED_DATE) ? note
.getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis();
if (mIsCreate || mCreatedDate != createDate) {
mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate);
}
mCreatedDate = createDate;
int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note
.getInt(NoteColumns.HAS_ATTACHMENT) : 0;
if (mIsCreate || mHasAttachment != hasAttachment) {
mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment);
}
mHasAttachment = hasAttachment;
long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note
.getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis();
if (mIsCreate || mModifiedDate != modifiedDate) {
mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate);
}
mModifiedDate = modifiedDate;
long parentId = note.has(NoteColumns.PARENT_ID) ? note
.getLong(NoteColumns.PARENT_ID) : 0;
if (mIsCreate || mParentId != parentId) {
mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId);
}
mParentId = parentId;
String snippet = note.has(NoteColumns.SNIPPET) ? note
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
mSnippet = snippet;
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
: Notes.TYPE_NOTE;
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
mType = type;
int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID)
: AppWidgetManager.INVALID_APPWIDGET_ID;
if (mIsCreate || mWidgetId != widgetId) {
mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId);
}
mWidgetId = widgetId;
int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note
.getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE;
if (mIsCreate || mWidgetType != widgetType) {
mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType);
}
mWidgetType = widgetType;
long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note
.getLong(NoteColumns.ORIGIN_PARENT_ID) : 0;
if (mIsCreate || mOriginParent != originParent) {
mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent);
}
mOriginParent = originParent;
//分别解析
for (int i = 0; i < dataArray.length(); i++)
{
JSONObject data = dataArray.getJSONObject(i);
SqlData sqlData = null;//未找到设置为null
if (data.has(DataColumns.ID)) //根据数据块 ID 在 mDataList 中查找对应的 SqlData 对象
{
long dataId = data.getLong(DataColumns.ID);//赋值
for (SqlData temp : mDataList) {
if (dataId == temp.getId()) {
sqlData = temp;
}
}
}
if (sqlData == null) {
sqlData = new SqlData(mContext);
mDataList.add(sqlData);//没找到对像,则新建一个对象并添加到 mDataList 中
}
sqlData.setContent(data);
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return false;
}//当 JSON 解析出错时,将会抛出 JSONException 异常并被 catch 住
return true;
}
public JSONObject getContent()
//将 SqlData 对象转换为 JSONObject
{
try {
JSONObject js = new JSONObject();
//判断 SqlData 对象是否已经存入数据库中
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
//未存储,返回 null
}
JSONObject note = new JSONObject();
if (mType == Notes.TYPE_NOTE) {
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.ALERTED_DATE, mAlertDate);
note.put(NoteColumns.BG_COLOR_ID, mBgColorId);
note.put(NoteColumns.CREATED_DATE, mCreatedDate);
note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment);
note.put(NoteColumns.MODIFIED_DATE, mModifiedDate);
note.put(NoteColumns.PARENT_ID, mParentId);
note.put(NoteColumns.SNIPPET, mSnippet);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.WIDGET_ID, mWidgetId);
note.put(NoteColumns.WIDGET_TYPE, mWidgetType);
note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
JSONArray dataArray = new JSONArray();
for (SqlData sqlData : mDataList) {
JSONObject data = sqlData.getContent();
if (data != null) {
dataArray.put(data);
}
}//根据 SqlData 对象的类型不同,分别构造 Note 和 Data 两个 JSONObject
// 并放入 META_HEAD_NOTE 和 META_HEAD_DATA 字段中
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
} else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) {
note.put(NoteColumns.ID, mId);
note.put(NoteColumns.TYPE, mType);
note.put(NoteColumns.SNIPPET, mSnippet);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
}
return js;//将其内容转换为 JSONObject 对象并返回
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return null;//遇到错误会打印日志并返回 null
}
public void setParentId(long id)
//设置 SqlData 对象的父节点 ID
{
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
}
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
}
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
}
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
}
public long getId() {
return mId;
}
public long getParentId() {
return mParentId;
}
public String getSnippet() {
return mSnippet;
}
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE;
}
public void commit(boolean validateVersion)
//将 SqlData 对象中的属性变化保存到数据库中
{
if (mIsCreate)
//判断该对象是新创建的还是已经存在于数据库中
{
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID);
}
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
//新建的对象,则调用 insert 方法将其保存到数据库中
try {
mId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
if (mId == 0) {
throw new IllegalStateException("Create thread id failed");
}//对是否为新值进行判定
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, false, -1);
//validateVersion 参数指定为 falsemVersion 参数指定为 -1
}
}
} else {
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
Log.e(TAG, "No such note");
throw new IllegalStateException("Try to update note with invalid id");
}
//判断当前对象的 ID 是否合法,如果不合法则抛出异常
if (mDiffNoteValues.size() > 0) {
mVersion ++;
int result = 0;
if (!validateVersion) {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] {
String.valueOf(mId)
});
} else {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
String.valueOf(mId), String.valueOf(mVersion)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}//判断笔记是否被成功更新,并在更新失败时输出一些日志信息。
if (mType == Notes.TYPE_NOTE)//检查当前对象是否表示笔记对象
{
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, validateVersion, mVersion);//对比参数,不对校验版本号
}
}
}
// refresh local info
loadFromCursor(mId);//逐一转换为 SqlData 对象,并添加到 mDataList 变量中
if (mType == Notes.TYPE_NOTE)//加载笔记内容
loadDataContent();
mDiffNoteValues.clear();//清空 mDiffNoteValues 变量
mIsCreate = false;//标记设置为 false表示当前对象已经存在于数据库中
}
}

@ -0,0 +1,396 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
//包含了一些用于处理GTaskGoogle Task相关数据的类。具体来说这些类实现了将本地笔记同步到Google Task中以及从Google Task中同步笔记到本地的功能。
import android.database.Cursor;//引入Android类cursor为Android提供接口用于访问数据源中的数据并支持对数据进行查询、排序、更新等操作。
import android.text.TextUtils;// 引入Android类TextUtilsAndroid提供的一个工具类包含了一些字符串处理相关的方法如检查字符串是否为空、去除字符串两端的空格等
import android.util.Log;//引入Android类Log是Android提供的一个工具类用于在开发和调试过程中输出日志信息
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 net.micode.notes.gtask.exception.ActionFailureException;//是自定义异常类,表示某个操作失败了
import net.micode.notes.tool.GTaskStringUtils;//是一个字符串处理工具类定义了一些与Google Task有关的字符串处理方法
import org.json.JSONArray;//是一个表示JSON数组的类可以方便地读取和操作JSON数组中的元素。
import org.json.JSONException;//是一个异常类表示在解析JSON数据时发生了错误。
import org.json.JSONObject;//是一个表示JSON对象的类可以方便地读取和操作JSON对象中的属性和值。
public class Task extends Node//定义了一个Task类继承自Node类
{
private static final String TAG = Task.class.getSimpleName();
private boolean mCompleted;//表示任务是否已完成
private String mNotes;//表示任务的注释信息
private JSONObject mMetaInfo;//表示任务的元信息是一个JSONObject对象
private Task mPriorSibling;//表示该任务在同级任务中的前一个任务对象
private TaskList mParent;//表示该任务所属的任务列表对象
public Task() {
super();
mCompleted = false;
mNotes = null;
mPriorSibling = null;
mParent = null;
mMetaInfo = null;
}//无参构造函数初始化所有属性值均为null或false
public JSONObject getCreateAction(int actionId)
//getCreateAction方法接收一个整型参数actionId将该参数作为返回JSONObject对象的action_id属性值。
{
JSONObject js = new JSONObject();
try {
// action_type,创建操作
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// index父任务列表中的索引
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
// entity_delta一个JSONObject对象
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());//得到任务名
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");//对任务创建者id进行判空操作
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,//实体类数据task
GTaskStringUtils.GTASK_JSON_TYPE_TASK);
if (getNotes() != null)
//如果任务有注释则设置notes属性表示任务注释信息。
{
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
// parent_id
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
// dest_parent_type
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// list_id
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// prior_sibling_id
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
//如果该任务在兄弟任务中不是第一个则设置prior_sibling_id属性值为前一个兄弟任务的gid。
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-create jsonobject");
}
//如果在生成JSONObject对象时发生异常则会抛出一个自定义异常ActionFailureException。
return js;
}
public JSONObject getUpdateAction(int actionId)
//getUpdateAction方法接收一个整型参数actionId将该参数作为返回JSONObject对象的action_id属性值
{
JSONObject js = new JSONObject();
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
//action_type属性值为update表示更新操作
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
//id属性值为该任务的gid。
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());//任务名称
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}//如果该任务有注释则设置notes属性表示任务注释信息
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
}//deleted属性表示任务是否被删除如果该值为true则该任务会被移除
catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-update jsonobject");
}//如果在生成JSONObject对象时发生异常则会抛出一个自定义异常ActionFailureException。
return js;
}
public void setContentByRemoteJSON(JSONObject js)
//定义了一个setContentByRemoteJSON方法该方法从远程JSON对象中获取任务的属性值并将这些属性值设置到本地任务对象中
//表示从远程服务器获取的任务信息
{
if (js != null) //首先检查传入的JSON对象是否为空如果不是空对象则依次从JSON对象中获取以下属性值
{
try {
// id
if (js.has(GTaskStringUtils.GTASK_JSON_ID))
//id属性值并调用setGid方法将其赋值给本地任务对象的gid属性
{
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}//last_modified属性值并调用setLastModified方法将其赋值给本地任务对象的lastModified属性
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}//name属性值并调用setName方法将其赋值给本地任务对象的name属性
// notes
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}//notes属性值并调用setNotes方法将其赋值给本地任务对象的notes属性
// deleted
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}//deleted属性值并调用setDeleted方法将其赋值给本地任务对象的deleted属性
// completed
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
}//completed属性值并调用setCompleted方法将其赋值给本地任务对象的completed属性
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get task content from jsonobject");
}//如果在解析JSON对象时发生异常则会抛出一个自定义异常ActionFailureException
}
}
public void setContentByLocalJSON(JSONObject js)
//定义了一个setContentByLocalJSON方法该方法从本地JSON对象中获取任务的属性值并将这些属性值设置到本地任务对象中
//setContentByLocalJSON方法接收一个JSONObject参数js用来表示本地存储的任务信息
{
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}//该方法首先检查传入的JSON对象是否为空以及是否包含必要属性。如果不满足这些条件则打印一条警告日志并退出方法
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
//从JSON对象中获取META_HEAD_NOTE和META_HEAD_DATA属性的值并分别赋给变量note和dataArray
if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) {
Log.e(TAG, "invalid type");
return;
}
//该方法检查获取到的note对象的type属性是否为Notes.TYPE_NOTE如果不是则打印一条错误日志并退出方法
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
setName(data.getString(DataColumns.CONTENT));
break;
}
}//该方法遍历dataArray数组中的每个元素找到mime_type属性为DataConstants.NOTE的元素并将其content属性值赋给本地任务对象的name属性
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}//如果在解析JSON对象时发生异常则会打印一条错误日志
}
public JSONObject getLocalJSONFromContent()
//定义了一个getLocalJSONFromContent方法该方法从本地任务对象中获取任务的属性值并将这些属性值构造成JSON对象返回
{
String name = getName();
try {
if (mMetaInfo == null)//获取任务的名称
{
// new task created from web
if (name == null) {
Log.w(TAG, "the note seems to be an empty one");
return null;
}
//根据任务的元信息即mMetaInfo属性判断任务是从Web端创建还是同步而来。如果任务的元信息为空则认为该任务是从Web端创建的。
JSONObject js = new JSONObject();
JSONObject note = new JSONObject();
JSONArray dataArray = new JSONArray();
JSONObject data = new JSONObject();
data.put(DataColumns.CONTENT, name);
dataArray.put(data);
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
return js;//对于从Web端创建的任务如果任务名称为空则会打印一条警告日志并返回null。
// 否则该方法构造一个JSONObject对象其中包含META_HEAD_DATA和META_HEAD_NOTE两个属性
// 并将任务的名称设置为META_HEAD_DATA属性所表示的JSONArray对象中的data属性值
} else {
// synced task
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
//对于同步而来的任务该方法首先获取任务的META_HEAD_DATA和META_HEAD_NOTE属性的值
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
data.put(DataColumns.CONTENT, getName());
break;
}
}//遍历META_HEAD_DATA属性值所表示的JSONArray对象中的所有元素找到mime_type属性为DataConstants.NOTE的元素并将其content属性值设置为本地任务对象的名称
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
return mMetaInfo;
}//最后该方法将note对象的type属性设置为Notes.TYPE_NOTE并返回元信息对应的JSONObject对象
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}//如果在构造JSON对象时发生异常则会打印一条错误日志并返回null
}
public void setMetaInfo(MetaData metaData) //用于设置mMetaInfo属性
//接收一个MetaData对象参数metaData
{
if (metaData != null && metaData.getNotes() != null) {
try {
mMetaInfo = new JSONObject(metaData.getNotes());
}
//metaData不为空并且其notes属性不为空字符串则将notes属性值解析为JSON对象并将其赋给mMetaInfo属性
catch (JSONException e) {
Log.w(TAG, e.toString());
mMetaInfo = null;
}//如果在解析JSON对象时发生异常则会打印一条警告日志并将mMetaInfo属性赋为null
}//如果metaData对象或者其notes属性为空则不会对mMetaInfo属性做任何更改
}
public int getSyncAction(Cursor c)
//用于返回同步任务的操作类型
{
try {
JSONObject noteInfo = null;
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
}//mMetaInfo属性,中获取与任务相关联的笔记信息
if (noteInfo == null) {
Log.w(TAG, "it seems that note meta has been deleted");
return SYNC_ACTION_UPDATE_REMOTE;
}//// 如果不存在则会打印一条警告日志并返回SYNC_ACTION_UPDATE_REMOTE操作类型
if (!noteInfo.has(NoteColumns.ID)) {
Log.w(TAG, "remote note id seems to be deleted");
return SYNC_ACTION_UPDATE_LOCAL;
}//检查笔记信息中是否存在NoteColumns.ID属性
// 如果不存在则会打印一条警告日志并返回SYNC_ACTION_UPDATE_LOCAL操作类型
// validate the note id now
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
Log.w(TAG, "note id doesn't match");
return SYNC_ACTION_UPDATE_LOCAL;
}//获取查询结果集c中的本地笔记的id属性值并将其与笔记信息中的NoteColumns.ID属性值进行比较。
// 如果两者不相等则会打印一条警告日志并返回SYNC_ACTION_UPDATE_LOCAL操作类型。
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// there is no local update
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// no update both side
return SYNC_ACTION_NONE;
} else {
// apply remote to local
return SYNC_ACTION_UPDATE_LOCAL;
}//根据本地笔记对象的LOCAL_MODIFIED_COLUMN属性值是否为0来确定是否存在本地更新。如果该属性值为0则表示没有本地更新。
// 如果本地笔记对象的SYNC_ID_COLUMN属性值等于当前任务对象的最后修改时间则表示两者都没有更新
// 返回SYNC_ACTION_NONE操作类型否则返回SYNC_ACTION_UPDATE_LOCAL操作类型。
} else {
// validate gtask id
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// local modification only
return SYNC_ACTION_UPDATE_REMOTE;
} else {
return SYNC_ACTION_UPDATE_CONFLICT;
}//如果本地笔记对象的LOCAL_MODIFIED_COLUMN属性值不为0则表示存在本地更新。
// 此时检查SqlNote.GTASK_ID_COLUMN属性值是否与当前任务对象的Google Task ID相等。
// 如果不相等打印一条错误日志并返回SYNC_ACTION_ERROR操作类型。
// 接着比较本地笔记对象的SYNC_ID_COLUMN属性值与getLastModified()方法返回值是否相等。
// 如果相等则表示只存在本地更新返回SYNC_ACTION_UPDATE_REMOTE操作类型
// 否则返回SYNC_ACTION_UPDATE_CONFLICT操作类型
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return SYNC_ACTION_ERROR;
}//如果在执行过程中发生异常则会打印一条错误日志并返回SYNC_ACTION_ERROR操作类型。
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0);
}//isWorthSaving()方法用于判断当前任务对象是否值得保存,返回一个布尔值。
//该方法会判断以下三种情况只要其中任意一种情况成立则返回true表示该任务对象值得保存
//mMetaInfo属性不为null即当前任务对象存在元信息。
//getName()方法返回值不为null且去除空格后长度大于0表示当前任务对象的名称不为空。
//getNotes()方法返回值不为null且去除空格后长度大于0表示当前任务对象的笔记内容不为空。
//如果上述所有条件都不成立则返回false表示当前任务对象不值得保存
public void setCompleted(boolean completed) {
this.mCompleted = completed;
}
public void setNotes(String notes) {
this.mNotes = notes;
}
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling;
}
public void setParent(TaskList parent) {
this.mParent = parent;
}
public boolean getCompleted() {
return this.mCompleted;
}
public String getNotes() {
return this.mNotes;
}
public Task getPriorSibling() {
return this.mPriorSibling;
}
public TaskList getParent() {
return this.mParent;
}
}

@ -0,0 +1,435 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class TaskList extends Node
//TaskList 类继承了 Node 类
// 表示它是可以存储在树形结构中的一个节点
// TaskList 类有三个属性mIndex、mChildren 和 TAG
{
private static final String TAG = TaskList.class.getSimpleName();
//用于记录 TaskList 类的类名,方便在调试过程中进行日志记录和错误跟踪
private int mIndex;
//表示 TaskList 对象在树形结构中的序号。默认情况下,它被初始化为 1
private ArrayList<Task> mChildren;
// ArrayList<Task> 类型的属性,表示 TaskList 对象的子节点列表
public TaskList() {
super();
mChildren = new ArrayList<Task>();
mIndex = 1;
}
public JSONObject getCreateAction(int actionId)
//据当前文件夹对象的属性值,构造一个包含创建操作信息的 JSON 对象,并返回给调用者
{
JSONObject js = new JSONObject();
//创建一个空的 JSONObject 对象 js
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
//添加 GTASK_JSON_ACTION_TYPE字段及对应的值
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 添加GTASK_JSON_ACTION_ID字段及对应的值
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
// entity_delta
// 添加GTASK_JSON_INDEX 和 GTASK_JSON_ENTITY_DELTA 等字段及对应的值。
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
//创建一个包含实体信息的 JSONObject 对象 entity并向其中添加文件夹名称 GTASK_JSON_NAME
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
//创建人 ID GTASK_JSON_CREATOR_ID 和实体类型 GTASK_JSON_ENTITY_TYPE 等字段及对应的值
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
//将 entity 对象作为值添加到父级 JSONObject 对象 js 中的 GTASK_JSON_ENTITY_DELTA 字段中
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-create jsonobject");
}//如果生成 JSON 对象时出现异常,则记录错误日志并抛出 ActionFailureException 异常
return js;
}
public JSONObject getUpdateAction(int actionId)
//根据当前文件夹对象的属性值,构造一个包含更新操作信息的 JSON 对象,并返回给调用者
{
JSONObject js = new JSONObject();//创建一个空的 JSONObject 对象 js
try {
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
//添加 GTASK_JSON_ACTION_TYPE字段及对应的值
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
//添加GTASK_JSON_ACTION_ID字段及对应的值
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
//添加GTASK_JSON_ID字段及对应的值
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
//添加GTASK_JSON_DELETED字段及对应的值
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
////然后将 entity 对象作为值添加到父级 JSONObject 对象 js 中的 GTASK_JSON_ENTITY_DELTA 字段
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-update jsonobject");
}
//如果生成 JSON 对象时出现异常,则记录错误日志并抛出 ActionFailureException 异常
return js;
}
public void setContentByRemoteJSON(JSONObject js)
//从给定的 JSONObject 对象 js 中读取云端 Google Tasks 的文件夹信息,并将其应用到当前的文件夹对象中
{
if (js != null)
//对输入进行判空
{
try {
// id
if (js.has(GTaskStringUtils.GTASK_JSON_ID))
//检查是否含有字段 GTASK_JSON_ID
{
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}//检查是否含有字段 GTASK_JSON_LAST_MODIFIED
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}//检查是否含有字段GTASK_JSON_NAME
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get tasklist content from jsonobject");
}
//如果解析 JSON 对象时出现异常
// 则记录错误日志并抛出 ActionFailureException 异常
}
}
public void setContentByLocalJSON(JSONObject js)
//从给定的 JSONObject 对象 js 中读取文件夹信息,并将其应用到当前的文件夹对象中
{
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}
//如果输入的 JSON 对象为 null 或不含有 GTaskStringUtils.META_HEAD_NOTE 字段
// 则会记录警告日志并跳过后续处理
try {
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
//从 GTaskStringUtils.META_HEAD_NOTE 字段中获取 folder 子对象,并根据 NoteColumns.TYPE 属性判断文件夹类型
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER)
// 如果文件夹类型是 Notes.TYPE_FOLDER
{
String name = folder.getString(NoteColumns.SNIPPET);//则从 NoteColumns.SNIPPET 属性中获取文件夹名称
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name);//为当前文件夹对象设置名称,以 GTaskStringUtils.MIUI_FOLDER_PREFFIX 作为前缀
} else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM)
//如果文件夹类型是 Notes.TYPE_SYSTEM
{
if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER)
//根据 NoteColumns.ID 属性的值判断系统文件夹类型,并为当前文件夹对象设置相应的名称
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT);
else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE);
else
Log.e(TAG, "invalid system folder");
} else {
Log.e(TAG, "error type");
}//如果出现错误,则记录错误日志并跳过后续处理。如果文件夹类型不合法,则同样记录错误日志并跳过后续处理。
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
public JSONObject getLocalJSONFromContent()
//将应用中的文件夹信息转换为 JSON 格式的方法
//返回一个包含文件夹信息的 JSONObject 对象
{
try {
JSONObject js = new JSONObject();
JSONObject folder = new JSONObject();
//创建一个新的 JSONObject 对象
// 并创建一个名为 folder 的子对象,用于存储文件夹的属性信息
String folderName = getName();
if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX))
folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(),
folderName.length());
//从当前文件夹对象中获取文件夹名称,并根据名称的前缀判断该文件夹是否是系统文件夹
folder.put(NoteColumns.SNIPPET, folderName);
if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT)
|| folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE))
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
// //如果是系统文件夹,则设置 NoteColumns.TYPE 属性为 Notes.TYPE_SYSTEM
else
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);//否则设置为 Notes.TYPE_FOLDER
js.put(GTaskStringUtils.META_HEAD_NOTE, folder);
//将文件夹的名称和类型信息添加到 folder 子对象中,并将 folder 作为 GTaskStringUtils.META_HEAD_NOTE 的值添加到主 JSONObject 中
// 用于指定待更新笔记的文件夹信息。
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}//如果转换过程中出现异常,则记录错误日志并返回 null
}
public int getSyncAction(Cursor c)
//从给定的 Cursor 对象 c 中读取本地笔记的信息,并将其与云端笔记进行比较,
// 从而决定应该执行哪种同步操作
{
try {
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0)
//判断本地笔记是否有修改
{
// there is no local update
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// no update both side
//如果本地笔记没有被修改
// 则进一步判断云端笔记的最后修改时间和本地笔记最后同步时间是否相等
// 如果相等,则不需要更新,返回 SYNC_ACTION_NON
return SYNC_ACTION_NONE;
} else {
// apply remote to local
//如果不相等,则需要将云端笔记的内容同步到本地,返回 SYNC_ACTION_UPDATE_LOCAL
return SYNC_ACTION_UPDATE_LOCAL;
}
} else
//被修改过
{
// validate gtask id
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid()))
//验证云端笔记的 ID 和本地笔记的 ID 是否匹配
{
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
}//如果不匹配,则说明出现了错误
// 返回 SYNC_ACTION_ERROR 表示同步失败
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// local modification only
return SYNC_ACTION_UPDATE_REMOTE;
//如果 ID 匹配,则需要进一步判断本地笔记和云端笔记的最后修改时间。
// 如果相等,则只需要将本地笔记同步到云端
//返回 SYNC_ACTION_UPDATE_REMOTE
} else {
// for folder conflicts, just apply local modification
return SYNC_ACTION_UPDATE_REMOTE;
}//存在冲突,优先选择本地笔记的修改内容
// 返回 SYNC_ACTION_UPDATE_REMOTE
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return SYNC_ACTION_ERROR;
}
public int getChildTaskCount() {
return mChildren.size();
}
public boolean addChildTask(Task task)
//向任务列表中添加指定的子任务
{
boolean ret = false;
if (task != null && !mChildren.contains(task))
//判断待添加的任务是否为空,并且是否已经存在于列表中
//如果待添加任务为空,或者已经在列表中,则直接返回添加失败
{
ret = mChildren.add(task);
if (ret) {
// need to set prior sibling and parent
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren
.get(mChildren.size() - 1));
task.setParent(this);//将待添加任务的前驱节点设置为列表中的最后一个任务(如果列表不为空,将其父节点设置为该任务列表的实例对象。
}//该方法返回操作结果,如果添加成功则返回 true否则返回 false。
}
return ret;
}
public boolean addChildTask(Task task, int index)
//用于向任务列表中添加指定的子任务
{
if (index < 0 || index > mChildren.size())
//判断待添加任务的索引是否越界
{
Log.e(TAG, "add child task: invalid index");
return false;
}//越界则记录一条错误日志,并返回 false 表示添加失败
int pos = mChildren.indexOf(task);
if (task != null && pos == -1) {
mChildren.add(index, task);
// update the task list
Task preTask = null;
Task afterTask = null;
if (index != 0)//如果待添加任务不在列表中,则将其插入到指定位置上,并更新列表中其他任务的“父兄弟”关系
preTask = mChildren.get(index - 1);
//将待添加任务的前驱节点设置为索引 index - 1
if (index != mChildren.size() - 1)
afterTask = mChildren.get(index + 1);
//后继节点设置为索引 index + 1 的任务
task.setPriorSibling(preTask);
if (afterTask != null)
afterTask.setPriorSibling(task);
}
return true;
}//待添加任务已经在列表中,则不执行操作并直接返回 true 表示添加成功
public boolean removeChildTask(Task task)
//从任务列表中删除指定的子任务
{
boolean ret = false;
int index = mChildren.indexOf(task);
if (index != -1) {
ret = mChildren.remove(task);
//通过 indexOf() 方法查找待删除任务在列表中的索引
if (ret) {
// reset prior sibling and parent
task.setPriorSibling(null);
task.setParent(null);
//执行删除操作,重置为 null
// update the task list
if (index != mChildren.size()) {
mChildren.get(index).setPriorSibling(
index == 0 ? null : mChildren.get(index - 1));
}
//更新列表中其他任务的关系:如果待删除任务的前一个任务存在,则将其设置为待删除任务的前驱节点;
// 否则,设置为 null。
}
}
return ret;
}
public boolean moveChildTask(Task task, int index)
//将指定的子任务移动到新的位置
{
if (index < 0 || index >= mChildren.size())//判断待移动任务的索引是否越界
{
Log.e(TAG, "move child task: invalid index");
return false;
}
//若存在越界,返回 false 表示操作失败
int pos = mChildren.indexOf(task);
if (pos == -1) {
Log.e(TAG, "move child task: the task should in the list");
return false;
}//如果待移动任务不在列表中,则会记录一条错误日志,并返回 false 表示操作失败
if (pos == index)////如果待移动任务已经在列表中,并且新的索引与当前位置相同
return true;
return (removeChildTask(task) && addChildTask(task, index));
}//直接返回 true 表示操作成功
public Task findChildTaskByGid(String gid)
//根据子任务的GID全局唯一标识符查找指定的子任务
{
for (int i = 0; i < mChildren.size(); i++)
//当前任务列表节点下所有的子任务中依次查找判断每个子任务的GID是否等于输入的参数gid
{
Task t = mChildren.get(i);
if (t.getGid().equals(gid)) {
return t;
}//若找到对应的子任务,则返回该子任务;
}
return null;
}//否则返回null表示找不到指定的子任务
public int getChildTaskIndex(Task task) {
return mChildren.indexOf(task);
}
public Task getChildTaskByIndex(int index)
//根据子任务在任务列表中的位置获取指定的子任务
{
if (index < 0 || index >= mChildren.size())
{
Log.e(TAG, "getTaskByIndex: invalid index");
return null;
}//判断输入的参数index是否越界如果小于0或大于等于当前子任务的个数则会记录一条错误日志并返回null
return mChildren.get(index);//返回该位置上对应的子任务
}
public Task getChilTaskByGid(String gid)
//根据子任务的GID全局唯一标识符查找指定的子任务
{
for (Task task : mChildren)
//遍历当前任务列表节点下所有的子任务并依次判断每个子任务的GID是否等于输入的参数gid
{
if (task.getGid().equals(gid))
return task;
}
return null;
}
//若找到对应的子任务则返回该子任务否则返回null表示找不到指定的子任务
public ArrayList<Task> getChildTaskList() {
return this.mChildren;
}
public void setIndex(int index) {
this.mIndex = index;
}
public int getIndex() {
return this.mIndex;
}
}

@ -0,0 +1,162 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import android.view.Window;
import android.view.WindowManager;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import java.io.IOException;
/*
author:
2023-4-12
*/
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
private long mNoteId;
private String mSnippet;
private static final int SNIPPET_PREW_MAX_LEN = 60;
MediaPlayer mPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);//创建实例
requestWindowFeature(Window.FEATURE_NO_TITLE);//没有标题
final Window win = getWindow();//获取一个窗口对象
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);//允许应用程序在设备处于锁定状态时显示窗口,如果该标志被设置,则即使用户没有解锁设备,该应用程序仍然可以正常运行。
if (!isScreenOn()) {//在屏幕是关闭的情况下
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON//保持屏幕常亮,以防止设备进入休眠状态。
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON//在锁定状态下唤醒屏幕。如果该标志被设置,则当 Activity 启动时,系统将唤醒设备并点亮屏幕。
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON//允许在屏幕常亮时锁定设备。
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);//允许布局扩展到屏幕装饰区域(例如状态栏)之下。
}
Intent intent = getIntent();//获取页面
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));//找到是否有对应的NoteId
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
: mSnippet;
} catch (IllegalArgumentException e) {
e.printStackTrace();
return;
}
mPlayer = new MediaPlayer();
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {//如果存在对应的便签
showActionDialog();//发出对话框
playAlarmSound();//发出声音
} else {
finish();
}
}
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
private void playAlarmSound() {
//获取系统中当前默认的闹钟铃声所对应的 Uri并将其赋值给 url 变量
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
//获取当前系统中哪些音频流会受到静音模式的影响
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
//如果STREAM_ALARM受到影响那么就将音频流形式设置为silentModeStreams
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
mPlayer.setAudioStreamType(silentModeStreams);
} else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
}
try {
mPlayer.setDataSource(this, url);
mPlayer.prepare();//于为音频播放做一些准备工作。具体来说,该方法会解析音频数据、分离音频轨道、获取音频格式信息等操作
mPlayer.setLooping(true);
mPlayer.start();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.app_name);//设置对话框的标题
dialog.setMessage(mSnippet);//设置对话框的信息
dialog.setPositiveButton(R.string.notealert_ok, this);//设置确认按钮,在确认按钮按下后显示对话框
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);//关闭对话框
}
dialog.show().setOnDismissListener(this);//执行相关的动作
}
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
Intent intent = new Intent(this, NoteEditActivity.class);//新创建一个页面
intent.setAction(Intent.ACTION_VIEW);//显示页面
intent.putExtra(Intent.EXTRA_UID, mNoteId);//放入对应的数据库中
startActivity(intent);//运行该页面
break;
default:
break;
}
}
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();//关闭闹钟铃声
finish();//该页面结束
}
private void stopAlarmSound() {
if (mPlayer != null) {//如果该播放器存在
mPlayer.stop();//停下
mPlayer.release();//释放资源
mPlayer = null;
}
}
}

@ -0,0 +1,70 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
/*
Author:
Data:2023-4-12
*/
public class AlarmInitReceiver extends BroadcastReceiver {
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE
};
private static final int COLUMN_ID = 0;
private static final int COLUMN_ALERTED_DATE = 1;
@Override
public void onReceive(Context context, Intent intent) {
long currentDate = System.currentTimeMillis();//找到目前的时间段
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
new String[] { String.valueOf(currentDate) },
null);//筛选出来那些提醒时间大于当前时间并且类型是便签类型的
if (c != null) {
if (c.moveToFirst()) {//如果下一个数据存在的话
do {
long alertDate = c.getLong(COLUMN_ALERTED_DATE);//获取当前数据的日期
Intent sender = new Intent(context, AlarmReceiver.class);//获取一个新的页面
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);//采用系统实时时钟RTC闹钟
//当闹钟到达alertDate时唤醒设备保证不会在睡眠模式错过闹钟第二个参数是闹钟的触发时间第三个就是指定谁到时候就会
//被触发
} while (c.moveToNext());//只要下一个数据存在的话
}
c.close();//关闭流文件
}
}
}

@ -0,0 +1,33 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/*
Author:
Data:2023/4/12
*/
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
intent.setClass(context, AlarmAlertActivity.class);//由当前页面跳转到对应界面
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//该标记表示在新任务栈中启动 Activity当它不在前台的时候保证其可以在后台显示
context.startActivity(intent);//启动对应的活动
}
}

@ -0,0 +1,489 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import net.micode.notes.R;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
/*
Author:
Data:2023/4/12
*/
public class DateTimePicker extends FrameLayout {
private static final boolean DEFAULT_ENABLE_STATE = true;
private static final int HOURS_IN_HALF_DAY = 12;
private static final int HOURS_IN_ALL_DAY = 24;
private static final int DAYS_IN_ALL_WEEK = 7;
private static final int DATE_SPINNER_MIN_VAL = 0;
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
private static final int MINUT_SPINNER_MIN_VAL = 0;
private static final int MINUT_SPINNER_MAX_VAL = 59;
private static final int AMPM_SPINNER_MIN_VAL = 0;
private static final int AMPM_SPINNER_MAX_VAL = 1;
private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
private Calendar mDate;
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
private boolean mIsAm;
private boolean mIs24HourView;
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
private boolean mInitialising;
private OnDateTimeChangedListener mOnDateTimeChangedListener;
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {//更改日期
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
updateDateControl();
onDateTimeChanged();
}
};//当用户更改值之后改变便签的修改日期
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {//各自的参数代表被改变值得NumberPicker对象旧值新值
boolean isDateChanged = false;//用户是否改变值
Calendar cal = Calendar.getInstance();//获取当前的时间
if (!mIs24HourView) {//不是用的24小时制度
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {//是下午旧值是11新值是12代表到了下一天
cal.setTimeInMillis(mDate.getTimeInMillis());//将修改时间修改为当前的时间
cal.add(Calendar.DAY_OF_YEAR, 1);//过了一天将日期加1
isDateChanged = true;
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {//如果当前是上午,并且
//旧值是12新值是11那么就代表回到了上一天
cal.setTimeInMillis(mDate.getTimeInMillis());//获取当前的时间
cal.add(Calendar.DAY_OF_YEAR, -1);//日期减一
isDateChanged = true;
}
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||
oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {//只要之前是11现在是12或者是之前是12现在是11AM和PM都会发生改变取反
mIsAm = !mIsAm;
updateAmPmControl();//更新限制
}
} else {
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {//如果当前时间比之前时间早,代表已经过了一天了
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {//如果当前时间比之前时间晚代表是同一天
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
}
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour);
onDateTimeChanged();
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
}
};
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {//通过调整分钟来调整时间
int minValue = mMinuteSpinner.getMinValue();//获取最小值
int maxValue = mMinuteSpinner.getMaxValue();//获取最大值
int offset = 0;//判断是否跨越边界
if (oldVal == maxValue && newVal == minValue) {//如果新的是最小的旧的值是最大的那么小时加1
offset += 1;
} else if (oldVal == minValue && newVal == maxValue) {//如果新的是最大的值,旧的是最小值,那么代表我们是往上调整的,那么小时减小
offset -= 1;
}
if (offset != 0) {//如果跨越边界了
mDate.add(Calendar.HOUR_OF_DAY, offset);//日期进行相应的改变
mHourSpinner.setValue(getCurrentHour());//获取当前的时间
updateDateControl();//更新限制
int newHour = getCurrentHourOfDay();//更新小时
if (newHour >= HOURS_IN_HALF_DAY) {//判断是上午还是下午
mIsAm = false;//不是上午
updateAmPmControl();//更新限制
} else {
mIsAm = true;//是上午
updateAmPmControl();
}
}
mDate.set(Calendar.MINUTE, newVal);//设置成新的值
onDateTimeChanged();//更改小时
}
};
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {//更新上午还是下午
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;//上午变成下午,下午变成上午
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);//如果是下午变成上午那么需要减去12小时
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);//如果是上午变成下午那么需要加上12小时
}
updateAmPmControl();
onDateTimeChanged();
}
};
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
}
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
}
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
mDate = Calendar.getInstance();
mInitialising = true;
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
inflate(context, R.layout.datetime_picker, this);//将小时、分钟和日期、在上午还是下午填充到这个文件中
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);//选择当前月份的最小的那一天
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);//选择当前月份最大的那一天
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);//设置日期监听器
mHourSpinner = (NumberPicker) findViewById(R.id.hour);//找到小时
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);//设置小时监听器
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);//设置分钟最小值
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);//设置分钟最大值
mMinuteSpinner.setOnLongPressUpdateInterval(100);//设置按下更新的间隔是100ms
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);//设置分钟监听器
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();//获取当前设备上的AM/PM字符串数组第一个元素为AM第二个元素为PM
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);//设置上午下午监听器
// update controls to initial state
updateDateControl();//更新限制
updateHourControl();
updateAmPmControl();
set24HourView(is24HourView);
// set to current time
setCurrentDate(date);
setEnabled(isEnabled());
// set the content descriptions
mInitialising = false;
}
@Override
public void setEnabled(boolean enabled) {//用来设置组件显示出来
if (mIsEnabled == enabled) {//如果相同则不用修改
return;
}
super.setEnabled(enabled);//设置可行
mDateSpinner.setEnabled(enabled);//设置日期监听器可行
mMinuteSpinner.setEnabled(enabled);//设置分钟监听器可行
mHourSpinner.setEnabled(enabled);//设置小时监听器可行
mAmPmSpinner.setEnabled(enabled);//设置上午下午监听器可行
mIsEnabled = enabled;//设置为已经更新为可用
}
@Override
public boolean isEnabled() {
return mIsEnabled;
}//返回是否可用
/**
* Get the current date in millis
*
* @return the current date in millis
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}//获取当前的日期
/**
* Set the current date
*
* @param date The current date in millis
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}
/**
* Set the current date
*
* @param year The current year
* @param month The current month
* @param dayOfMonth The current dayOfMonth
* @param hourOfDay The current hourOfDay
* @param minute The current minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
}//设置时间
/**
* Get current year
*
* @return The current year
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}//返回当前年
/**
* Set current year
*
* @param year The current year
*/
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) {//如果已经初始化并且年份和当前的相同
return;
}
mDate.set(Calendar.YEAR, year);//更新
updateDateControl();
onDateTimeChanged();
}
/**
* Get current month in the year
*
* @return The current month in the year
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}//获得月份
/**
* Set current month in the year
*
* @param month The month in the year
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);//更新月份
updateDateControl();
onDateTimeChanged();
}
/**
* Get current day of the month
*
* @return The day of the month
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}//获取月份
/**
* Set current day of the month
*
* @param dayOfMonth The day of the month
*/
public void setCurrentDay(int dayOfMonth) {//更新天
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}//获取当前的小时
private int getCurrentHour() {
if (mIs24HourView){//如果是24小时
return getCurrentHourOfDay();
} else {
int hour = getCurrentHourOfDay();//获取小时
if (hour > HOURS_IN_HALF_DAY) {//如果大于12应该减去12
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour;//否则就和当前的相同
}
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
*
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {//如果已经初始化并且和现在的小时一样就不用管
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);//设置小时
if (!mIs24HourView) {//如果不是24小时制度
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;//代表是下午
if (hourOfDay > HOURS_IN_HALF_DAY) {//如果大于12
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
mIsAm = true;//时上午
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;//代表到达了最后
}
}
updateAmPmControl();
}
mHourSpinner.setValue(hourOfDay);//更新时间
onDateTimeChanged();
}
/**
* Get currentMinute
*
* @return The Current Minute
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* Set current minute
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute);//设置时间
mDate.set(Calendar.MINUTE, minute);//设置分钟
onDateTimeChanged();
}
/**
* @return true if this is in 24 hour view else false.
*/
public boolean is24HourView () {
return mIs24HourView;
}//返回是否是24小时制
/**
* Set whether in 24 hour or AM/PM mode.
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {//如果是24小时制就无需更改
return;
}
mIs24HourView = is24HourView;//更改为24小时制度
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);//讲对应的界面显示出来
int hour = getCurrentHourOfDay();//获取当前的小时
updateHourControl();
setCurrentHour(hour);//更新时间
updateAmPmControl();
}
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);//按照一周来显示,以当前这一天为中心进行扩展
mDateSpinner.setDisplayedValues(null);//清空mDateSpinner显示的数据
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1);//将当前的cal的最后一个数据加1
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);//格式化日期
}
mDateSpinner.setDisplayedValues(mDateDisplayValues);//把加入的数据显示出来
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);//将选择器的初始位置设置为中心位置
mDateSpinner.invalidate();//使选择器无效,从而更新显示值
}
private void updateAmPmControl() {
if (mIs24HourView) {//如果是24小时制
mAmPmSpinner.setVisibility(View.GONE);//上午下午不显示
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM;//显示是上午还是下午
mAmPmSpinner.setValue(index);//显示对应的内容
mAmPmSpinner.setVisibility(View.VISIBLE);//可见
}
}
private void updateHourControl() {
if (mIs24HourView) {//如果是24小时制
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);//显示24小时最小值
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);//显示24小时最大值
} else {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);//显示12小时最小值
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);//显示12小时最大值
}
}
/**
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
private void onDateTimeChanged() {//更改便签时间
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}

@ -0,0 +1,90 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import java.util.Calendar;
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance();
private boolean mIs24HourView;
private OnDateTimeSetListener mOnDateTimeSetListener;
private DateTimePicker mDateTimePicker;
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
}
public DateTimePickerDialog(Context context, long date) {
super(context);
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker);//将事件显示出来
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
updateTitle(mDate.getTimeInMillis());//更新题目
}//根据对话框参数定位到对应的日期
});
mDate.setTimeInMillis(date);//将其转化为时间戳
mDate.set(Calendar.SECOND, 0);//设置s为0
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());//将其设置为时间
setButton(context.getString(R.string.datetime_dialog_ok), this);//确认
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);//显示提示是去校
set24HourView(DateFormat.is24HourFormat(this.getContext()));//根据24小时制或者是12小时进行设置
updateTitle(mDate.getTimeInMillis());//更新题目
}
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}//设置为24小时页面
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
private void updateTitle(long date) {
int flag =
DateUtils.FORMAT_SHOW_YEAR |//显示年日时间
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;//是否按照24小时制显示
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));//设置对应的题目
}
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

@ -0,0 +1,61 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import net.micode.notes.R;
public class DropdownMenu {
private Button mButton;
private PopupMenu mPopupMenu;
private Menu mMenu;
public DropdownMenu(Context context, Button button, int menuId) {//放下菜单
mButton = button;
mButton.setBackgroundResource(R.drawable.dropdown_icon);//设置背景资源,改变对应的背景图片
mPopupMenu = new PopupMenu(context, mButton);//显示菜单
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);//填充菜单
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
}//显示对应的菜单
});
}
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}
public void setTitle(CharSequence title) {
mButton.setText(title);
}
}

@ -0,0 +1,84 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
public class FoldersListAdapter extends CursorAdapter {
//CursorAdapter是Cursor和ListView的接口
//FoldersListAdapter继承了CursorAdapter的类
//主要作用是便签数据库和用户的交互
//这里就是用folder文件夹的形式展现给用户
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
};//调用数据库中便签的ID和片段
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}//数据库操作
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}//创建一个文件夹,对于各文件夹中子标签的初始化
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {//FolderListItem是显示子图如果view是该类的实例那么就去cursor中获取文件夹的名称并将其绑定到FolderListItem上
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
((FolderListItem) view).bind(folderName);
}
}//将各个布局文件绑定起来
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}//根据数据库中标签的ID得到标签的各项内容
private class FolderListItem extends LinearLayout {
private TextView mName;
public FolderListItem(Context context) {
super(context);//操作数据库
inflate(context, R.layout.folder_list_item, this);
mName = (TextView) findViewById(R.id.tv_folder_name);//根据布局文件的名字等信息将其找出来
}
public void bind(String name) {
mName.setText(name);
}
}
}

@ -0,0 +1,873 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.BackgroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
private class HeadViewHolder {
public TextView tvModified;
public ImageView ivAlertIcon;
public TextView tvAlertDate;
public ImageView ibSetBgColor;
}
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>();//将小米便签的背景颜色映射成一个个值
static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
}
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>();//将映射的颜色在反射为小米便签对应颜色的一个值
static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
}
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>();//将字体大小映射为一个值
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);
sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM);
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
}
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>();//将映射的值重新反射为字体大小对应的值
static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
}
private static final String TAG = "NoteEditActivity";
private HeadViewHolder mNoteHeaderHolder;
private View mHeadViewPanel;
private View mNoteBgColorSelector;
private View mFontSizeSelector;
private EditText mNoteEditor;
private View mNoteEditorPanel;
private WorkingNote mWorkingNote;
private SharedPreferences mSharedPrefs;
private int mFontSizeId;
private static final String PREFERENCE_FONT_SIZE = "pref_font_size";
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;
public static final String TAG_CHECKED = String.valueOf('\u221A');
public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
private LinearLayout mEditTextList;
private String mUserQuery;
private Pattern mPattern;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);//执行父类的初始化操作
this.setContentView(R.layout.note_edit);//将布局文件node_edit加载到activity中并设置为当前的试图
if (savedInstanceState == null && !initActivityState(getIntent())) {//初始化失败
finish();
return;
}
initResources();//进行资源的初始化和加载
}
/**
* Current activity may be killed when the memory is low. Once it is killed, for another time
* user load this activity, we should restore the former state
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {//回复Activities的状态
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {//是否包含Intent.EXTRA_UID如果包含则重新恢复
Intent intent = new Intent(Intent.ACTION_VIEW);//创建新的对象并显示出来
intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));//把之前的数据加载到该intent上面来
if (!initActivityState(intent)) {//如果不存在
finish();
return;
}
Log.d(TAG, "Restoring from killed activity");//状态恢复成功
}
}
private boolean initActivityState(Intent intent) {
/**
* If the user specified the {@link Intent#ACTION_VIEW} but not provided with id,
* then jump to the NotesListActivity
*/
mWorkingNote = null;
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);//获取intent所携带的附加参数
mUserQuery = "";
/**
* Starting from the searched result
*/
if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) {//是否有额外的数据,有的话代表用户想要查看这一个便签
noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));//取出来对应的id以便于之后数据库搜索
mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY);//将mUserQuery设置为用户输入的查询的字符串
}
if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) {//这个便签不能在数据库中找到
Intent jump = new Intent(this, NotesListActivity.class);//跳转到对应NoteListActivities上
startActivity(jump);//跳转
showToast(R.string.error_note_not_exist);//输出这个便签不存在
finish();//关闭页面
return false;
} else {
mWorkingNote = WorkingNote.load(this, noteId);//加载对应的便签
if (mWorkingNote == null) {//加载失败会记录当前的Activity
Log.e(TAG, "load note failed with note id" + noteId);//输出打开失败
finish();
return false;
}
}
getWindow().setSoftInputMode(//设置窗口为软件盘模式,窗口会自动调整布局大小以适应软键盘的显示或隐藏
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN//此标志表示启动该 Activity 时软键盘不会自动弹出
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);//Activity 中的窗口大小会根据软键盘的状态自动调整
} else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) {//如果是增添或者编辑
// New note
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);//获取FolderId
int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID,//获取传入的组件Id如果没有找到对应的组件Id那么就会返回无效的ID
AppWidgetManager.INVALID_APPWIDGET_ID);
int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE,//获取组件的类型
Notes.TYPE_WIDGET_INVALIDE);
int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID,//获取组件的背景Id如果没有就默认的背景颜色
ResourceParser.getDefaultBgId(this));
// Parse call-record note
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);//获取手机号码
long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0);//获取通话时间
if (callDate != 0 && phoneNumber != null) {
if (TextUtils.isEmpty(phoneNumber)) {//如果没有该电话号
Log.w(TAG, "The call record number is null");
}
long noteId = 0;
if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(),
phoneNumber, callDate)) > 0) {//如果从数据库找到对应的电话号和日期号了那么就进行此操作
mWorkingNote = WorkingNote.load(this, noteId);//加载该便签
if (mWorkingNote == null) {//打开失败
Log.e(TAG, "load call note failed with note id" + noteId);
finish();
return false;
}
} else {//没有对应的日期或者没有对应的电话号码
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId,
widgetType, bgResId);//新建立一个便签
mWorkingNote.convertToCallNote(phoneNumber, callDate);//转化为电话记录便签
}
} else {//如果有一者为空那么就代表是创建一个普通的便签
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType,
bgResId);
}
getWindow().setSoftInputMode(//也是软键盘形式
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
} else {
Log.e(TAG, "Intent not specified action, should not support");//不支持此类操作。
finish();
return false;
}
mWorkingNote.setOnSettingStatusChangedListener(this);//建立好之后进行监听,对更改或删除便签做相应的改变
return true;
}
@Override
protected void onResume() {//当由后台到前台的时候进行调用
super.onResume();
initNoteScreen();
}
private void initNoteScreen() {
mNoteEditor.setTextAppearance(this, TextAppearanceResources
.getTexAppearanceResource(mFontSizeId));//获取用户当前字体的大小
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//转化为清单模式
switchToListMode(mWorkingNote.getContent());//获取对应的内容
} else {//如果不是清单模式
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));//对用户输入的关键字文本进行高亮显示
mNoteEditor.setSelection(mNoteEditor.getText().length());//获取选取的长度
}
for (Integer id : sBgSelectorSelectionMap.keySet()) {
findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);//找到当前键的值也就是id并隐藏背景选择器选中的背景图
}
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());//在头部显示标题
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());//在背景显示对应的背景颜色
mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this,
mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR));//将修改时间文本视图中的数据写入标题
/**
* TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker
* is not ready
*/
showAlertHeader();//决定是否显示提醒的表头
}
private void showAlertHeader() {
if (mWorkingNote.hasClockAlert()) {//如果有时间提醒
long time = System.currentTimeMillis();//获得当前的时间
if (time > mWorkingNote.getAlertDate()) {//如果超过时间提醒
mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired);//设置提醒日期文本视图为过期显示内容
} else {
mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString(//显示距离提示还有多长时间
mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS));
}
mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE);//显示提醒日期
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE);//显示提醒图标
} else {//没有时间提醒就隐藏对应的提醒标志
mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE);
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE);
};
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
initActivityState(intent);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
/**
* For new note without note id, we should firstly save it to
* generate a id. If the editing note is not worth saving, there
* is no id which is equivalent to create new note
*/
if (!mWorkingNote.existInDatabase()) {//如果数据库没有就进行保存
saveNote();
}
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());//初始化
Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");//显示对应id信息和保存成功
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {//笔记编辑器的触摸事件
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mNoteBgColorSelector, ev)) {//如果不在背景颜色选择器的触摸范围之内,并且背景选择器是可视化的
mNoteBgColorSelector.setVisibility(View.GONE);//将背景选择器隐藏起来
return true;
}
if (mFontSizeSelector.getVisibility() == View.VISIBLE//判断字体大小选择器是否显示,并且是否在改变字体大小的控制范围内
&& !inRangeOfView(mFontSizeSelector, ev)) {
mFontSizeSelector.setVisibility(View.GONE);//将其隐藏起来
return true;
}
return super.dispatchTouchEvent(ev);
}
private boolean inRangeOfView(View view, MotionEvent ev) {
int []location = new int[2];//获取位置
view.getLocationOnScreen(location);//获取鼠标所在屏幕的左上角对应位置
int x = location[0];//横坐标
int y = location[1];//纵坐标
if (ev.getX() < x
|| ev.getX() > (x + view.getWidth())
|| ev.getY() < y
|| ev.getY() > (y + view.getHeight())) {
return false;
}//ev.getX获取了触摸事件的横坐标getY则是获取纵坐标该判断语句就是判断触摸事件的横纵坐标在该视图的外部
return true;//在管辖范畴内
}
private void initResources() {
mHeadViewPanel = findViewById(R.id.note_title);//获取对应id的视图
mNoteHeaderHolder = new HeadViewHolder();//实例化头部信息
mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);//把修改日期视图封装在mNoteHeaderHolder中
mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);//把提醒图标视图封装在对象中
mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);//把提醒日期视图封装在对象中
mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);//把改变背景颜色的视图封装在对象中
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);//设置对应的监听器
mNoteEditor = (EditText) findViewById(R.id.note_edit_view);//找到对应的编辑器视图
mNoteEditorPanel = findViewById(R.id.sv_note_edit);//把保存便签的视图放在该对象中
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);//将背景颜色选择器捆绑给mNoteBgColorSelector中
for (int id : sBgSelectorBtnsMap.keySet()) {//为每一种背景颜色对应的视图设置监听器
ImageView iv = (ImageView) findViewById(id);
iv.setOnClickListener(this);
}
mFontSizeSelector = findViewById(R.id.font_size_selector);//捆绑字体大小选择器
for (int id : sFontSizeBtnsMap.keySet()) {//为每一种字体大小设置监听器
View view = findViewById(id);
view.setOnClickListener(this);
};
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);//获取默认的共享偏好
mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);//设置字体大小
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {//如果设置的字体大小的id超过了限制那么就重新调回默认的字体大小
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);//线性放置
}
@Override
protected void onPause() {
super.onPause();
if(saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}//提示已保存对应文本
clearSettingState();
}
private void updateWidget() {//更新widget
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {//更新到2x大小
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {//更新到4x大小
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
Log.e(TAG, "Unspported widget type");
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {//把对应id附加上去
mWorkingNote.getWidgetId()
});
sendBroadcast(intent);//让widget进行刷新操作从而实现数据同步
setResult(RESULT_OK, intent);
}
public void onClick(View v) {
int id = v.getId();//获取id号
if (id == R.id.btn_set_bg_color) {//如果设置背景颜色
mNoteBgColorSelector.setVisibility(View.VISIBLE);//将改变颜色的视图显示出来
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {//如果是背景选择按钮包含此id
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);//将选择视图关闭
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));//设置对应的背景颜色
mNoteBgColorSelector.setVisibility(View.GONE);//
} else if (sFontSizeBtnsMap.containsKey(id)) {//点击的是某个字体大小的图标
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);//将选择字体大小的视图关闭
mFontSizeId = sFontSizeBtnsMap.get(id);//将当前的字体大小设置为所选的大小
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();//共享字体大小偏好设置为对应的字体大小
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);//显示对应的字体
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//判断是否为检查模式
getWorkingText();//获取工作中的文本内容
switchToListMode(mWorkingNote.getContent());//转化为列表模式
} else {
mNoteEditor.setTextAppearance(this,
TextAppearanceResources.getTexAppearanceResource(mFontSizeId));//显示字体大小
}
mFontSizeSelector.setVisibility(View.GONE);//关闭字体大小显示
}
}
@Override
public void onBackPressed() {
if(clearSettingState()) {//关闭对应的窗口
return;
}
saveNote();//保存笔记
super.onBackPressed();
}
private boolean clearSettingState() {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {//判断背景颜色选择器是否可见
mNoteBgColorSelector.setVisibility(View.GONE);//关闭
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {//判断字体大小是否可见
mFontSizeSelector.setVisibility(View.GONE);//关闭
return true;
}
return false;
}
public void onBackgroundColorChanged() {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE);//找到对应颜色的id并进行视图显示
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());//编辑面板进行显示
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());//试图面板显示
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (isFinishing()) {//页面完成
return true;
}
clearSettingState();//关闭设置的状态
menu.clear();//清空菜单
if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_note_edit, menu);//进行菜单的填充
} else {
getMenuInflater().inflate(R.menu.note_edit, menu);
}
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);//切换为正常模式
} else {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
}
if (mWorkingNote.hasClockAlert()) {//显示是否有闹钟提示
menu.findItem(R.id.menu_alert).setVisible(false);//关闭提示
} else {
menu.findItem(R.id.menu_delete_remind).setVisible(false);//菜单删除提醒关闭
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {//根据选项的id号进行对应的选择
case R.id.menu_new_note:
createNewNote();//创建新的标签
break;
case R.id.menu_delete:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));//提醒是否要删除
builder.setIcon(android.R.drawable.ic_dialog_alert);//设置删除的图标
builder.setMessage(getString(R.string.alert_message_delete_note));//提示要不要删除
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {//确定按钮
public void onClick(DialogInterface dialog, int which) {
deleteCurrentNote();//删除当前的便签
finish();
}
});
builder.setNegativeButton(android.R.string.cancel, null);//不确定
builder.show();//继续展示对应界面
break;
case R.id.menu_font_size://字体大小菜单
mFontSizeSelector.setVisibility(View.VISIBLE);//显示字体选择器
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);//将字体大小选择器显示出来
break;
case R.id.menu_list_mode:
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);//设置列表模式
break;
case R.id.menu_share://进行菜单分享
getWorkingText();//获取便签的内容
sendTo(this, mWorkingNote.getContent());
break;
case R.id.menu_send_to_desktop:
sendToDesktop();//发送到桌面上
break;
case R.id.menu_alert:
setReminder();//设置提醒器
break;
case R.id.menu_delete_remind:
mWorkingNote.setAlertDate(0, false);//设置日期提醒器
break;
default:
break;
}
return true;
}
private void setReminder() {//设置提醒器
DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());//设置新的对话框
d.setOnDateTimeSetListener(new OnDateTimeSetListener() {//设置监听器
public void OnDateTimeSet(AlertDialog dialog, long date) {
mWorkingNote.setAlertDate(date , true);//设置信息
}
});
d.show();
}
/**
* Share note to apps that support {@link Intent#ACTION_SEND} action
* and {@text/plain} type
*/
private void sendTo(Context context, String info) {
Intent intent = new Intent(Intent.ACTION_SEND);//代表发送一个文本信息
intent.putExtra(Intent.EXTRA_TEXT, info);//向intend中加入文本信息
intent.setType("text/plain");//文本类型
context.startActivity(intent);//启动这个intent也就是发送信息
}
private void createNewNote() {
// Firstly, save current editing notes
saveNote();
// For safety, start a new NoteEditActivity
finish();
Intent intent = new Intent(this, NoteEditActivity.class);//新建一个intent
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);//设置为编辑或者插入
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());//给id
startActivity(intent);//进行相应的编辑活动
}
private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) {//如果对应的便签在数据库中能找到的话
HashSet<Long> ids = new HashSet<Long>();
long id = mWorkingNote.getNoteId();
if (id != Notes.ID_ROOT_FOLDER) {//如果不是无效值
ids.add(id);//加入
} else {
Log.d(TAG, "Wrong note id, should not happen");//不存在这样的便签id
}
if (!isSyncMode()) {//不是在同步模式下
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {//批量删除失败
Log.e(TAG, "Delete Note error");
}
} else {
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {//无法移动到垃圾箱
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
}
mWorkingNote.markDeleted(true);//标记为已经删除
}
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;//获取账号名称,如果不是空则代表是同步模式下
}
public void onClockAlertChanged(long date, boolean set) {
/**
* User could set clock to an unsaved note, so before setting the
* alert clock, we should save the note first
*/
if (!mWorkingNote.existInDatabase()) {//如果不在数据库中
saveNote();//保存
}
if (mWorkingNote.getNoteId() > 0) {//如果id为有效id
Intent intent = new Intent(this, AlarmReceiver.class);//新建intent
intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));//设置对应的日期,和对应的信息
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);//设置触发intent
AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));//设置闹钟提醒
showAlertHeader();//显示提醒的标题
if(!set) {//如果要关闭提醒
alarmManager.cancel(pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);//设置提醒
}
} else {
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Clock alert setting error");
showToast(R.string.error_note_empty_for_clock);
}
}
public void onWidgetChanged() {
updateWidget();
}
public void onEditTextDelete(int index, String text) {
int childCount = mEditTextList.getChildCount();//获取当前便签列表的孩子个数
if (childCount == 1) {//如果只有一个孩子
return;
}
for (int i = index + 1; i < childCount; i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i - 1);//将index后的文本往前移动
}
mEditTextList.removeViewAt(index);//把对应下标的列表内容删除
NoteEditText edit = null;
if(index == 0) {//如果下标是0那么就获取第一个标签
edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(
R.id.et_edit_text);
} else {//否则就是下标减一,因为删除了一个元素
edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById(
R.id.et_edit_text);
}
int length = edit.length();
edit.append(text);//把对应的文本内容加入
edit.requestFocus();//获取焦点
edit.setSelection(length);//设置光标到对应的长度
}
public void onEditTextEnter(int index, String text) {
/**
* Should not happen, check for debug
*/
if(index > mEditTextList.getChildCount()) {//如果输入的下标大于孩子的数量,越界访问
Log.e(TAG, "Index out of mEditTextList boundrary, should not happen");
}
View view = getListItem(text, index);//获取对应下标的列表物品的视图
mEditTextList.addView(view, index);//把对应下标的视图放到编辑文本的列表中
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);//
edit.requestFocus();//获取编辑文本的焦点
edit.setSelection(0);//设置光标到最开始的位置
for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {//保证NoteEditText的索引值和列表元素的实际位置一致
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i);
}
}
private void switchToListMode(String text) {
mEditTextList.removeAllViews();//关掉所有的视图
String[] items = text.split("\n");//将对应的文本据回车分开
int index = 0;
for (String item : items) {//遍历里面的元素
if(!TextUtils.isEmpty(item)) {//如果对应文本不是空
mEditTextList.addView(getListItem(item, index));//增加一个视图,并标上下标
index++;//到达下一个
}
}
mEditTextList.addView(getListItem("", index));//用于添加新的列表项目
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();//请求焦点
mNoteEditor.setVisibility(View.GONE);//将便签编辑器取消
mEditTextList.setVisibility(View.VISIBLE);//将便文本列表显示出来
}
private Spannable getHighlightQueryResult(String fullText, String userQuery) {//将搜索到的关键字通过高亮显示出来
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
if (!TextUtils.isEmpty(userQuery)) {//判断用户查询的内容是否为空
mPattern = Pattern.compile(userQuery);//将对应得文本转化为匹配模式
Matcher m = mPattern.matcher(fullText);//把对应得文本匹配出来
int start = 0;//定义开始
while (m.find(start)) {//到达下一个位置
spannable.setSpan(
new BackgroundColorSpan(this.getResources().getColor(
R.color.user_query_highlight)), m.start(), m.end(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);//设置对应得高亮模式
start = m.end();//更新扫描得开始位置
}
}
return spannable;
}
private View getListItem(String item, int index) {
View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);//获取布局解析器对象
final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);//找到对应id得控件对象
edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));//设置文本形式
CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));//根据id找到对应的多选框
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {//监听复选框的变化
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {//勾选了对应的复选框
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);//加上对应的删除线
} else {//取消了勾选
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);//删除对应的删除线
}
}
});
if (item.startsWith(TAG_CHECKED)) {//如果该列表项已经被勾选了
cb.setChecked(true);//设置为选中状态
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);//
item = item.substring(TAG_CHECKED.length(), item.length()).trim();//修剪掉对应的空格
} else if (item.startsWith(TAG_UNCHECKED)) {
cb.setChecked(false);
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);//未勾选状态
item = item.substring(TAG_UNCHECKED.length(), item.length()).trim();//去除TAG_UNCHECKED前缀
}
edit.setOnTextViewChangeListener(this);//给当前事件捆绑监听器
edit.setIndex(index);
edit.setText(getHighlightQueryResult(item, mUserQuery));//对用户的查询显示高亮
return view;
}
public void onTextChange(int index, boolean hasText) {
if (index >= mEditTextList.getChildCount()) {//看坐标是否大于子控件数目如果大于,是无效操作
Log.e(TAG, "Wrong index, should not happen");
return;
}
if(hasText) {//决定左侧检查框是否显示,如果是有文本的,获取对应位置的子控件
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE);//将对应的多选框筛查出来
} else {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);
}
}
public void onCheckListModeChanged(int oldMode, int newMode) {
if (newMode == TextNote.MODE_CHECK_LIST) {//如果是在检查列表模式下
switchToListMode(mNoteEditor.getText().toString());//转化为列表模式
} else {
if (!getWorkingText()) {
mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ",
""));
}
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));//
mEditTextList.setVisibility(View.GONE);//
mNoteEditor.setVisibility(View.VISIBLE);//
}
}
private boolean getWorkingText() {//根据文本内容和勾选状态生成字符串,统计用户的输入状态
boolean hasChecked = false;
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//如果是勾选列表模式
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mEditTextList.getChildCount(); i++) {
View view = mEditTextList.getChildAt(i);//获取对应构件
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
if (!TextUtils.isEmpty(edit.getText())) {//获取输入框的内容
if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) {//如果当前的遍历的多选框被勾选了
sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");//将该信息加入到sb中
hasChecked = true;
} else {
sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");//没有勾选
}
}
}
mWorkingNote.setWorkingText(sb.toString());//将其转化为String类
} else {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());//设置文本内容
}
return hasChecked;//存在勾选的选项
}
private boolean saveNote() {
getWorkingText();
boolean saved = mWorkingNote.saveNote();
if (saved) {
/**
* There are two modes from List view to edit view, open one note,
* create/edit a node. Opening node requires to the original
* position in the list when back from edit view, while creating a
* new node requires to the top of the list. This code
* {@link #RESULT_OK} is used to identify the create/edit state
*/
setResult(RESULT_OK);
}
return saved;
}
private void sendToDesktop() {
/**
* Before send message to home, we should make sure that current
* editing note is exists in databases. So, for new note, firstly
* save it
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent sender = new Intent();
Intent shortcutIntent = new Intent(this, NoteEditActivity.class);//表示点击桌面快捷方式需要启动的activity
shortcutIntent.setAction(Intent.ACTION_VIEW);
shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId());
sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
sender.putExtra(Intent.EXTRA_SHORTCUT_NAME,
makeShortcutIconTitle(mWorkingNote.getContent()));//表示快捷方式的名字
sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app));//快捷方式的图标
sender.putExtra("duplicate", true);
sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT");//拟定发送的广播类型
showToast(R.string.info_note_enter_desktop);
sendBroadcast(sender);//创建对应的快捷方式
} else {//对应的便签不存在
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Send to desktop error");
showToast(R.string.error_note_empty_for_send_to_desktop);
}
}
private String makeShortcutIconTitle(String content) {
content = content.replace(TAG_CHECKED, "");//去掉对应的标记
content = content.replace(TAG_UNCHECKED, "");
return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0,
SHORTCUT_ICON_TITLE_MAX_LEN) : content;//将便签前几个字作为标题
}
private void showToast(int resId) {
showToast(resId, Toast.LENGTH_SHORT);
}//显示提示信息
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show();//在指定时间内显示对应的信息
}
}
Loading…
Cancel
Save