pull/6/head
xuningshuo 1 month ago
parent 911d904eaa
commit 37aee3a282

@ -1,3 +0,0 @@
{
"CurrentProjectSetting": null
}

@ -1,7 +0,0 @@
{
"ExpandedNodes": [
"",
"\\data"
],
"PreviewInSolutionExplorer": false
}

Binary file not shown.

Binary file not shown.

@ -1,37 +0,0 @@
{
"Version": 1,
"WorkspaceRootPath": "C:\\Users\\Lenovo\\Desktop\\\u5B66\u4E60\u8D44\u6599\\\u8F6F\u4EF6\u5DE5\u7A0B\\tsrxns\\src\\",
"Documents": [
{
"AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\Lenovo\\Desktop\\\u5B66\u4E60\u8D44\u6599\\\u8F6F\u4EF6\u5DE5\u7A0B\\tsrxns\\src\\data\\Contact.java||{3B902123-F8A7-4915-9F01-361F908088D0}",
"RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:data\\Contact.java||{3B902123-F8A7-4915-9F01-361F908088D0}"
}
],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": 0,
"Children": [
{
"$type": "Document",
"DocumentIndex": 0,
"Title": "Contact.java",
"DocumentMoniker": "C:\\Users\\Lenovo\\Desktop\\\u5B66\u4E60\u8D44\u6599\\\u8F6F\u4EF6\u5DE5\u7A0B\\tsrxns\\src\\data\\Contact.java",
"RelativeDocumentMoniker": "data\\Contact.java",
"ToolTip": "C:\\Users\\Lenovo\\Desktop\\\u5B66\u4E60\u8D44\u6599\\\u8F6F\u4EF6\u5DE5\u7A0B\\tsrxns\\src\\data\\Contact.java",
"RelativeToolTip": "data\\Contact.java",
"ViewState": "AgIAAAAAAAAAAAAAAADwvwAAAAAAAAAAAAAAAA==",
"Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.001001|",
"WhenOpened": "2026-01-06T13:41:29.245Z",
"EditorCaption": ""
}
]
}
]
}
]
}

@ -1,23 +0,0 @@
{
"Version": 1,
"WorkspaceRootPath": "C:\\Users\\Lenovo\\Desktop\\\u5B66\u4E60\u8D44\u6599\\\u8F6F\u4EF6\u5DE5\u7A0B\\tsrxns\\src\\",
"Documents": [],
"DocumentGroupContainers": [
{
"Orientation": 0,
"VerticalTabListWidth": 256,
"DocumentGroups": [
{
"DockedWidth": 200,
"SelectedChildIndex": -1,
"Children": [
{
"$type": "Bookmark",
"Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}"
}
]
}
]
}
]
}

@ -1,83 +0,0 @@
/*
* 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 的含义是选择电话号码字段为特定号码MIME 类型为电话号码类型,且原始联系人 ID 符合特定条件(通过子查询获取)的联系人数据。
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>();
}
//查询联系人的号码,如果已有联系人信息,则直接返回
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}
//使用 CALLER_ID_SELECTION 中定义的查询条件,并将其中的 + 替换为经过处理后的最小匹配号码
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
//PhoneNumberUtils.toCallerIDMinMatch(phoneNumber) 是一个 Android 平台提供的用于处理电话号码的工具方法之一。
// 它通常用于将输入的电话号码转换为适合作为 CALLER_ID_SELECTION 筛选条件中的最小匹配号码。
//这段代码使用 Android 的内容解析器Content Resolver来执行查询操作并返回一个 Cursor 对象,用于获取符合查询条件的联系人信息。
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI,
new String [] { Phone.DISPLAY_NAME },
selection,
new String[] { phoneNumber },
null);
if (cursor != null && cursor.moveToFirst()) { //如果查询返回的 cursor 不为空且能够移动到第一个结果位置(即有匹配的联系人信息)
try {
String name = cursor.getString(0);//获取第一个字段的值,即联系人的显示名称
sContactCache.put(phoneNumber, name);//将该信息存储在 sContactCache 中,以电话号码作为键,联系人名称作为值。
return name;
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, " Cursor get string error " + e.toString()); //记录错误日志
return null;
} finally { //无论是否发生异常,都会在最终执行块中关闭查询结果的 cursor
cursor.close();
}
} else { //如果查询结果为空或未找到匹配的联系人信息,则记录调试日志表示未找到匹配的联系人信息,并返回 null。
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}
}
}

@ -1,317 +0,0 @@
/*
* 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;
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;//系统类型
//这段注释解释了一些系统文件夹的标识符,以及它们对应的含义
// Notes#ID_ROOT_FOLDER: 默认文件夹的标识符。
// Notes#ID_TEMPARAY_FOLDER: 用于存储不属于任何文件夹的临时笔记的标识符。
//Notes#ID_CALL_RECORD_FOLDER: 用于存储通话记录的文件夹的标识符。
/**
* 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;//默认文件夹的标识符
public static final int ID_TEMPARAY_FOLDER = -1;//用于存储不属于任何文件夹的临时笔记的标识符
public static final int ID_CALL_RECORD_FOLDER = -2;//用于存储通话记录的文件夹的标识符
public static final int ID_TRASH_FOLER = -3;//用于存储回收站文件夹的标识符
// Intent是Android应用程序组件之间进行通信的一种重要方式
// 这段代码定义了一些静态常量,用于表示在 Intent 中传递时所需的额外参数的键值对。这些静态常量的含义如下:
// INTENT_EXTRA_ALERT_DATE: 表示提醒日期的额外参数键值,在 Intent 中使用时对应的键名为 "net.micode.notes.alert_date"。
// INTENT_EXTRA_BACKGROUND_ID: 表示背景颜色 ID 的额外参数键值,在 Intent 中使用时对应的键名为 "net.micode.notes.background_color_id"。
// INTENT_EXTRA_WIDGET_ID: 表示小部件 ID 的额外参数键值,在 Intent 中使用时对应的键名为 "net.micode.notes.widget_id"。
// INTENT_EXTRA_WIDGET_TYPE: 表示小部件类型的额外参数键值,在 Intent 中使用时对应的键名为 "net.micode.notes.widget_type"。
// INTENT_EXTRA_FOLDER_ID: 表示文件夹 ID 的额外参数键值,在 Intent 中使用时对应的键名为 "net.micode.notes.folder_id"。
// INTENT_EXTRA_CALL_DATE: 表示通话日期的额外参数键值,在 Intent 中使用时对应的键名为 "net.micode.notes.call_date"。
// 通过定义这些静态常量,可以避免在代码中硬编码这些字符串,提高了代码的可读性和可维护性。
// 这些常量的命名也很清晰地表达了它们所代表的含义,让开发人员能够准确地理解它们在 Intent 中所承载的数据。
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; //2x2小部件
public static final int TYPE_WIDGET_4X = 1; //4x4小部件
//MIMEMultipurpose Internet Mail Extensions类型是一种标准化的方法用于表示各种数据的类型和格式
//在 Android 开发中MIME 类型也经常用于指示 Intent 的数据类型,以便系统能够选择合适的应用程序来处理特定类型的数据。
//定义了一个静态内部类 DataConstants,将数据类型的常量集中定义在一个内部类中,有助于组织和管理相关的常量,并使得代码结构更清晰。
public static class DataConstants {
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;//表示笔记的 MIME 类型
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;//表示通话记录的 MIME 类型
}
//URI统一资源标识符是用于标识互联网上资源的字符串文本它提供了一种统一的方式来定位资源。
/**
* Uri to query all notes and folders
*/
//表示用于查询所有笔记和文件夹的 URI
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* Uri to query data
*/
//表示用于查询数据的 URI
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
//定义了一个接口 NoteColumns其中包含了一系列常量用于表示笔记或文件夹的各种属性和信息。
// 这些常量提供了对笔记或文件夹数据结构中不同字段的访问,使得代码可以更清晰地操作和管理这些数据。
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";
}
//定义了另一个接口 DataColumns其中包含了一系列常量用于表示数据的各种属性和信息。
// 这些常量提供了对数据结构中不同字段的访问,使得代码可以更清晰地操作和管理这些数据。
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";
}
//定义了一个静态内部类 TextNote它实现了接口 DataColumns。
// 这个内部类包含了一些常量和字段,用于表示文本笔记的相关信息。
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;
//用于指示文本笔记是否处于待办事项列表模式的模式。值为 1 表示待办事项列表模式,值为 0 表示普通模式
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");//用于表示文本笔记的内容 URI
}
//静态内部类 CallNote它也实现了接口 DataColumns。这个内部类包含了一些常量和字段用于表示通话记录的相关信息。
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");//用于表示通话记录的内容 URI
}
}

@ -1,396 +0,0 @@
/*
* 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;
public class NotesDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "note.db";
//表示数据库的名称为 "note.db"。这个常量用于指定应用程序中使用的数据库的名称。
private static final int DB_VERSION = 4;
//表示数据库的版本号为 4。
public interface TABLE { //接口
public static final String NOTE = "note";
//表示数据库中的一个表名为 "note"。这个常量用于指定应用程序中使用的表名。
public static final String DATA = "data";
//表示数据库中的一个表名为 "data"。这个常量也用于指定应用程序中使用的表名。
}
private static final String TAG = "NotesDatabaseHelper"; //日志标签
private static NotesDatabaseHelper mInstance;//类静态变量
//创建一个名为"NOTE"的数据库表的SQL语句
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," + //ID,主键
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + //父ID
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + //提醒日期
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + //背景颜色ID
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," +//小部件ID
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," +//小部件类型
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," +//同步ID
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +//本地修改
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +//原始父ID
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +//文本类型
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +//版本
")";
//创建一个名为"DATA"的数据库表的SQL语句
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," +//ID,主键
DataColumns.MIME_TYPE + " TEXT NOT NULL," +//MIME类型
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," +//笔记ID
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 ''" +
")";
//在"DATA"的数据库表上创建一个名为"note_id_index"的索引的SQL语句。
// 该索引是基于"NOTE_ID"字段的旨在提高根据笔记ID进行查询时的检索效率。
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
*/
//创建一个触发器,在"NOTE"表中的"parent_id"字段更新之后执行目的是在更新父文件夹ID后自动增加相应文件夹的笔记计数notes_count
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";
/**
* Decrease folder's note count when move note from folder
*/
//创建一个触发器,在"NOTE"表中的"parent_id"字段更新之后执行目的是在更新父文件夹ID后自动减少相应文件夹的笔记计数notes_count
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";
/**
* Increase folder's note count when insert new note to the folder
*/
//在"NOTE"表中插入新记录之后执行目的是在新笔记被添加到特定文件夹时自动增加该文件夹的笔记计数notes_count
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";
/**
* Decrease folder's note count when delete note from the folder
*/
//在删除笔记时自动减少相应父文件夹的笔记计数notes_count
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";
/**
* Update note's content when insert data with type {@link DataConstants#NOTE}
*/
//向"DATA"表中插入记录之后执行当插入数据的MIME类型为"NOTE"时会更新"NOTE"表中相应笔记的摘要snippet字段。
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";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has changed
*/
//在更新"DATA"表中的记录之后执行当更新数据的MIME类型为"NOTE"时,会更新"NOTE"表中相应笔记的摘要snippet字段。
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";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has deleted
*/
//在删除"DATA"表中的记录之后执行当被删除数据的MIME类型为"NOTE"时会将对应笔记的摘要snippet字段置空。
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";
/**
* Delete datas belong to note which has been deleted
*/
//在删除"NOTE"表中的记录之后执行,会同时删除"DATA"表中与被删除笔记相关联的记录。
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
*/
//触发器在删除"NOTE"表中的记录之后执行,会同时删除"NOTE"表中与被删除文件夹相关联的所有笔记记录。
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
*/
//更新"NOTE"表中的记录之后执行当将笔记移动到垃圾箱Trash Folder会将相关笔记的父级IDParent ID更新为垃圾箱的ID。
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);
}//调用了父类 SQLiteOpenHelper 的构造函数
//数据库中创建笔记表
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);//重新创建与笔记表相关的触发器
createSystemFolder(db);//在数据库中创建系统文件夹
Log.d(TAG, "note table has been created");
}
//重新创建与笔记表相关的触发器
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();
// 创建通话记录文件夹:向 Note 表插入一条记录,指定 ID 为 Notes.ID_CALL_RECORD_FOLDER类型为系统类型。
// 创建根文件夹:向 Note 表插入一条记录,指定 ID 为 Notes.ID_ROOT_FOLDER类型为系统类型。
// 创建临时文件夹:向 Note 表插入一条记录,指定 ID 为 Notes.ID_TEMPARAY_FOLDER类型为系统类型。
// 创建垃圾箱文件夹:向 Note 表插入一条记录,指定 ID 为 Notes.ID_TRASH_FOLER类型为系统类型。
/**
* 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);
}
//在数据库中创建DATA表
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL);//创建数据表
reCreateDataTableTriggers(db);//重新创建与DATA表相关的触发器
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);//索引
Log.d(TAG, "data table has been created");//日志
}
//重新创建与DATA表相关的触发器
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);
}
//用于获取 NotesDatabaseHelper 的单例实例
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");
}
}
//将数据库升级到版本 2
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);
}
//将数据库升级到版本 3
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);
}
//将数据库升级到版本 4
private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");
}
}

@ -1,364 +0,0 @@
/*
* 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;
//为存储和获取数据提供接口。可以在不同的应用程序之间共享数据
//ContentProvider提供的方法
//query查询
//insert插入
//update更新
//delete删除
//getType得到数据类型
public class NotesProvider extends ContentProvider {
// 用于将URI与相应代码匹配的UriMatcher
private static final UriMatcher mMatcher;
// 数据库助手,用于管理数据库操作
private NotesDatabaseHelper mHelper;
// 用于记录日志的标签
private static final String TAG = "NotesProvider";
// 用于定义不同URI模式的常量
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;
// 静态初始化器用于填充UriMatcher
static {
// 创建UriMatcher时调用UriMatcher(UriMatcher.NO_MATCH)表示不匹配任何路径的返回码
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);
// 根据提供的ID查询特定的笔记项
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_DATA:
// 在数据库中查询 "data" 表
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
// 根据提供的ID查询特定的数据项
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) {
// getPathSegments()方法得到一个String的List
// 在uri.getPathSegments().get(1)为第2个元素
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:
// 如果 URI 匹配通用的 note URI
// 修改选择条件以确保仅删除 ID 大于 0 的笔记
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
// notifyChange获得一个ContextResolver对象并且更新里面的内容
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) {
//Uri代表要操作的数据Android上可用的每种资源 -包括 图像、视频片段、音频资源等都可以用Uri来表示。
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:
// 如果 URI 匹配特定的 note 项,
// 从 URI 路径段中提取 ID
id = uri.getPathSegments().get(1);
/**
* ID that smaller than 0 is system folder which is not allowed to
* trash
*/
long noteId = Long.valueOf(id);
// 如果笔记 ID 小于或等于 0系统文件夹则退出 switch 语句
if (noteId <= 0) {
break;
}
// 否则,删除指定 ID 的笔记并应用选择条件
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:
// 如果 URI 匹配特定的 data 项,从 URI 路径段中提取 ID
id = uri.getPathSegments().get(1);
// 删除指定 ID 的数据并应用选择条件
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
deleteData = true;
break;
default:
// 如果 URI 不匹配任何已知模式,则抛出 IllegalArgumentException
throw new IllegalArgumentException("Unknown URI " + uri);
}
// 如果删除了任何行,则通知已注册的 ContentObservers
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);
// 更新 "note" 表中的数据
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
// 减少特定笔记项的版本号
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
// 更新 "note" 表中特定ID的数据
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
break;
case URI_DATA:
// 更新 "data" 表中的数据
count = db.update(TABLE.DATA, values, selection, selectionArgs);
updateData = true;
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
// 更新 "data" 表中特定ID的数据
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);
}
// 通知URI的观察者数据发生变化
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;
}
}

@ -1,83 +0,0 @@
/*
* 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;
//MetaData类继承自Task类专门用于处理Google任务相关的元数据
public class MetaData extends Task {
private final static String TAG = MetaData.class.getSimpleName();// 日志输出时使用的TAG标识
private String mRelatedGid = null;// 存储与本元数据相关的GTask全局ID
// 设置元数据方法将gid添加到metaInfo中并将整个metaInfo以字符串形式保存到notes同时设置名称
public void setMeta(String gid, JSONObject metaInfo) {
try {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");// 添加gid失败时的日志输出
}
setNotes(metaInfo.toString());
setName(GTaskStringUtils.META_NOTE_NAME);
}
// 获取与本元数据相关的GTask全局ID
public String getRelatedGid() {
return mRelatedGid;
}
// 重写父类方法判断当前元数据是否值得保存根据notes是否为空
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
// 从远程JSON数据设置内容并提取gid信息
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js);// 调用父类的setContentByRemoteJSON方法
if (getNotes() != null) { // 检查notes字段是否为空
try {
JSONObject metaInfo = new JSONObject(getNotes().trim());// 将notes字段转换为JSONObject对象
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);// 从JSONObject中获取特定字段并赋值给mRelatedGid
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");// 记录日志提示无法获取相关的gid
mRelatedGid = null;
}
}
}
@Override
public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");// 抛出异常,指示不应调用此函数
}
@Override
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");// 抛出异常,指示不应调用此函数
}
}

@ -1,102 +0,0 @@
/*
* 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; // 节点ID
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); // 抽象方法根据远程JSON数据设置节点内容
public abstract void setContentByLocalJSON(JSONObject js); // 抽象方法根据本地JSON数据设置节点内容
public abstract JSONObject getLocalJSONFromContent();// 抽象方法从节点内容获取本地JSON数据
public abstract int getSyncAction(Cursor c); // 抽象方法,根据游标获取同步操作类型
public void setGid(String gid) {
this.mGid = gid;
}// 设置节点ID
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;
}// 获取节点ID
public String getName() {
return this.mName;
}// 获取节点名称
public long getLastModified() {
return this.mLastModified;
}// 获取最后修改时间
public boolean getDeleted() {
return this.mDeleted;
} // 获取是否已删除
}

@ -1,205 +0,0 @@
/*
* 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;
//SqlData 类是一个数据对象用于封装数据表中的一行数据。SqlData 类提供了一些方法,用于从 Cursor 对象中加载数据、从 JSONObject 对象中加载数据、将数据提交到数据库等操作。
public class SqlData {
private static final String TAG = SqlData.class.getSimpleName();
private static final int INVALID_ID = -99999;
//通常情况下,将一个特殊数值(比如负数)作为无效 ID 的标识,这样在程序中可以通过判断是否等于 INVALID_ID 来表示某个 ID 是否有效。
//定义了一个名为 PROJECTION_DATA 的公共静态常量数组
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
public static final int DATA_ID_COLUMN = 0;//数据表ID列索引
public static final int DATA_MIME_TYPE_COLUMN = 1;//数据表MIME_TYPE列索引
public static final int DATA_CONTENT_COLUMN = 2;//数据表CONTENT列索引
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;//数据表DATA1列索引
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;//数据表DATA3列索引
private ContentResolver mContentResolver;//访问内容提供者的接口,可以进行数据库操作
private boolean mIsCreate;//标识当前数据是否创建
private long mDataId;//数据ID
private String mDataMimeType;//数据MIME类型
private String mDataContent;//数据内容
private long mDataContentData1;//数据DATA1
private String mDataContentData3;//数据DATA3
private ContentValues mDiffDataValues;
//ContentValues 是一个键值对存储容器,通常用于封装一组待插入或更新到数据库的数据。
//mDiffDataValues 主要用来存储数据对象与数据库中实际数据的差异。通过比较 mDiffDataValues 中的数据与数据库中对应的数据,
// 可以确定数据对象是否需要进行更新操作,并且可以方便地获取到数据的变化部分。
public SqlData(Context context) {//构造函数
mContentResolver = context.getContentResolver();
mIsCreate = true;//新创建的数据
mDataId = INVALID_ID;//数据ID初始值为无效ID
mDataMimeType = DataConstants.NOTE;//数据MIME类型
mDataContent = "";
mDataContentData1 = 0;
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
}
public SqlData(Context context, Cursor c) {//构造函数2
mContentResolver = context.getContentResolver();
mIsCreate = false;//不是新创建的数据
loadFromCursor(c);
mDiffDataValues = new ContentValues();
}
//用于从传入的 Cursor 对象中加载数据到当前 SqlData 对象中
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);
}
//用于将传入的 JSONObject 对象中的数据加载到当前 SqlData 对象中
public void setContent(JSONObject js) throws JSONException {
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;//如果JSONObject对象中包含ID则获取ID否则ID为无效ID
if (mIsCreate || mDataId != dataId) {//如果是新创建的数据或者数据ID不等于传入的数据ID
mDiffDataValues.put(DataColumns.ID, dataId);//将数据ID放入mDiffDataValues中
}
mDataId = dataId;//更新数据ID
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;
}
//用于获取对象的内容并返回一个 JSON 对象
public JSONObject getContent() throws JSONException {
if (mIsCreate) {//若数据未创建
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
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;
}
//用于提交数据更改到数据库
public void commit(long noteId, boolean validateVersion, long version) {
// 在 if (mIsCreate) 分支中:
//
// 首先检查条件如果数据ID无效且包含ID字段则将ID字段从差异数据值中移除。
// 将指定的笔记IDnoteId放入差异数据值中。
// 调用内容解析器ContentResolver的insert()方法向数据库中插入数据并获取返回的URI。
// 解析URI以获取新插入数据的ID并将其赋值给 mDataId 变量。
// 如果出现异常NumberFormatException则记录错误日志并抛出 ActionFailureException 异常。
// 在 else 分支中:
//
// 如果差异数据值的大小大于0则继续执行更新操作。
// 根据是否需要验证版本调用内容解析器的update()方法更新数据库中的数据。
// 如果更新结果为0则记录警告日志表示可能在同步期间用户更新了笔记。
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();//清空mDiffDataValues
mIsCreate = false;//数据已经创建
}
//返回数据ID
public long getId() {
return mDataId;
}
}

@ -1,540 +0,0 @@
/*
* 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;
//Sql类的作用是用于处理笔记相关的数据库操作其中包含了对笔记的增删改查以及与笔记相关的数据操作。
//具体来说,这个类实现了以下功能:
//
// 提供了构造函数,可以根据上下文对象初始化一个 SqlNote 对象,并设置默认的属性。
// 可以根据传入的 Cursor 对象或笔记 ID 加载数据到当前 SqlNote 对象中。
// 可以根据传入的 JSON 对象设置笔记对象的内容。
// 可以获取当前对象的内容并返回一个 JSON 对象。
// 提供了设置父ID、Google任务ID、同步ID等属性的方法。
// 包含了提交笔记对象更改的方法,可以将笔记对象的数据插入到数据库中或更新数据库中已有的笔记数据。
// 提供了获取数据ID、父ID、摘要等属性的方法。
// 包含了加载数据内容、设置内容、提交更改等私有方法,用于具体的数据处理和操作。
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;
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;// 访问内容提供者的接口,可以进行数据库操作
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 是一个键值对存储容器,通常用于封装一组待插入或更新到数据库的数据
private ArrayList<SqlData> mDataList;//数据列表
//构造函数
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>();
}
//构造函数2,根据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();
}
//构造函数3,根据ID初始化数据
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加载数据,利用loadFromCursor(Cursor c)方法加载到当前 SqlNote 对象中
private void loadFromCursor(long id) {
Cursor c = null;
try { //通过mContentResolver查询获取Cursor对象
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(id)
}, null);
if (c != null) {
c.moveToNext();//将光标移动到第一条记录
loadFromCursor(c);
} else {
Log.w(TAG, "loadFromCursor: cursor = null");
}
} finally { //无论是否发生异常都会执行finally块中的代码
if (c != null)
c.close();
}
}
//从传入的 Cursor 对象中加载数据到当前 SqlNote 对象中
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);
}
//从数据库中加载指定笔记ID相关的数据并将这些数据转化为SqlData对象并存储在mDataList中供后续使用
private void loadDataContent() {
Cursor c = null;
mDataList.clear();
try {
//通过内容解析器(ContentResolver)执行查询操作从笔记内容的URI(Notes.CONTENT_DATA_URI)中获取与当前笔记ID(mId)相关联的数据
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) { //如果Cursor对象中没有数据
Log.w(TAG, "it seems that the note has not data");
return;
}
while (c.moveToNext()) { //遍历Cursor对象
SqlData data = new SqlData(mContext, c); //对于每一行数据创建一个SqlData对象
mDataList.add(data);
}
} else {
Log.w(TAG, "loadDataContent: cursor = null");
}
} finally {
if (c != null)
c.close();
}
}
//将传入的 JSONObject 对象中的数据加载到当前 SqlNote 对象中
public boolean setContent(JSONObject js) {
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 //如果JSONObject对象中包含摘要则获取摘要否则摘要为空字符串
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) { //如果是新创建的数据或者摘要不等于传入的摘要
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); //将摘要放入mDiffNoteValues中
}
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;
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;
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;
}
//获取对象的内容并返回一个 JSON 对象
public JSONObject getContent() {
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.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();
//将数据列表中的数据加载到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);
}
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return null;
}
//设置父ID
public void setParentId(long id) {
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
}
//设置Google任务ID
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
}
//设置同步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
public long getParentId() {
return mParentId;
}
//摘要
public String getSnippet() {
return mSnippet;
}
//返回类型
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE;
}
//提交笔记对象的更改
public void commit(boolean validateVersion) {
if (mIsCreate) {
//如果数据未创建,下面创建新笔记
//首先检查条件如果数据ID无效且mDiffNoteValues中包含ID字段则将ID字段从差异数据值中移除。
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID);
}
//通过内容解析器ContentResolver的insert方法将mDiffNoteValues中的数据插入到Notes.CONTENT_NOTE_URI所代表的位置并获取返回的URI。
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
try {
//解析URI以获取新创建数据的ID并将其赋值给 mId 变量。
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);//提交数据更改到数据库
}
}
} else {
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) { //检查ID是否有效
Log.e(TAG, "No such note");
throw new IllegalStateException("Try to update note with invalid id");
}
if (mDiffNoteValues.size() > 0) { //如果差异数据值的大小大于0表示有数据需要更新
mVersion ++;
int result = 0;
if (!validateVersion) {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] {
String.valueOf(mId)
});
} else {
//根据笔记的IDmId将mDiffNoteValues中的数据更新到指定的Notes.CONTENT_NOTE_URI所代表的位置
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
String.valueOf(mId), String.valueOf(mVersion)
});
// Notes.CONTENT_NOTE_URI表示要操作的数据的URI可能是笔记数据的地址。
// mDiffNoteValues包含了需要更新的字段和对应数值的键值对。
// ("(" + NoteColumns.ID + "=?)")这是用于指定更新条件的部分其中NoteColumns.ID是笔记的ID字段"=?"表示该位置需要传入一个值。
// new String[] {String.valueOf(mId)}:这是要替换"=?"的具体数值即要更新的笔记的ID。
}
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); //根据ID加载数据将数据加载到当前 SqlNote 对象中
if (mType == Notes.TYPE_NOTE)
loadDataContent(); //从数据库中加载指定笔记ID相关的数据并将这些数据转化为SqlData对象并存储在mDataList中供后续使用
mDiffNoteValues.clear();//清空待提交的数据
mIsCreate = false;
}
}

@ -1,378 +0,0 @@
/*
* 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.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 {
private static final String TAG = Task.class.getSimpleName();//日志
private boolean mCompleted; //标识任务是否完成
private String mNotes;//任务的备注
private JSONObject mMetaInfo;//任务的元数据
private Task mPriorSibling;//任务列表的前一个任务
private TaskList mParent;//任务的父任务列表
public Task() {//构造方法
super();//调用父类Node的构造方法
mCompleted = false;
mNotes = null;
mPriorSibling = null;
mParent = null;
mMetaInfo = null;
}
//获取创建任务的json对象
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();//创建json对象
try {
//设置各种属性
// action_type
//将键值对放入json对象中
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 entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_TASK);
if (getNotes() != null) { //如果任务的备注不为空
//将备注信息放入json对象中
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());
}
} catch (JSONException e) {
Log.e(TAG, e.toString());//打印日志
e.printStackTrace();//打印异常堆栈跟踪信息,将异常信息输出到标准错误流
throw new ActionFailureException("fail to generate task-create jsonobject");
}
return js;
}
//生成更新任务的json对象
public JSONObject getUpdateAction(int actionId) {
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());
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
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 task-update jsonobject");
}
return js;
}
//根据远程传入的json对象设置任务的内容
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// id
if (js.has(GTaskStringUtils.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));
}
// 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");
}
}
}
//根据本地的json对象设置任务的内容
public void setContentByLocalJSON(JSONObject js) {
//若传入的json对象为空或者没包含特定键
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) {
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) {
Log.e(TAG, "invalid type");
return;
}
for (int i = 0; i < dataArray.length(); i++) {
// 获取数组中索引为 i 的元素,该元素是一个 JSON 对象。
// 检查该对象中的 MIME_TYPE 字段是否等于 DataConstants.NOTE。
// 如果上述条件成立,则将该对象中的 CONTENT 字段的值设置为本地对象的 name 属性。
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();
}
}
//根据当前对象的状态生成本地的json对象
public JSONObject getLocalJSONFromContent() {
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;
}
}
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 {
//将notes字符串的值转换为json对象
mMetaInfo = new JSONObject(metaData.getNotes());
} catch (JSONException e) {
Log.w(TAG, e.toString());
mMetaInfo = null;
}
}
}
//根据传入Cursor对象和当前对象的mMetainfo属性判断当前对象的同步状态
public int getSyncAction(Cursor c) {
// 首先尝试获取当前对象的mMetaInfo中的note信息并进行一些基本的验证。
// 如果note信息为空则输出警告日志并返回SYNC_ACTION_UPDATE_REMOTE表示需要更新远程数据。
// 如果note信息中没有ID字段则输出警告日志并返回SYNC_ACTION_UPDATE_LOCAL表示需要更新本地数据。
// 验证note的ID与传入Cursor的ID是否匹配如果不匹配则返回SYNC_ACTION_UPDATE_LOCAL。
// 检查本地是否有更新如果没有则比较最后修改时间如果远程数据有更新则返回SYNC_ACTION_UPDATE_LOCAL否则返回SYNC_ACTION_NONE。
// 如果本地有更新则验证gtask id如果不匹配则返回SYNC_ACTION_ERROR。
// 最后根据本地修改时间和最后同步时间的比较,确定具体的同步操作类型,返回相应的同步操作常量。
// 在处理过程中如果出现异常则会捕获该异常并打印错误日志最终返回SYNC_ACTION_ERROR。
try {
JSONObject noteInfo = null;
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
}
if (noteInfo == null) {
Log.w(TAG, "it seems that note meta has been deleted");
return SYNC_ACTION_UPDATE_REMOTE;
}
if (!noteInfo.has(NoteColumns.ID)) {
Log.w(TAG, "remote note id seems to be deleted");
return 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;
}
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;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 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() {
// 首先检查当前对象的mMetaInfo属性是否不为null。
// 如果mMetaInfo属性为null则继续检查当前对象的name属性是否不为null且去除首尾空格后长度大于0。
// 接着检查当前对象的notes属性是否不为null且去除首尾空格后长度大于0。
// 如果上述条件有一个成立则返回true否则返回false。
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;
}//设置任务的备注
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;
}//获取任务的父任务列表
}

@ -1,384 +0,0 @@
/*
* 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 {
private static final String TAG = TaskList.class.getSimpleName();//日志
private int mIndex;//可能代表列表中的索引或者用于排序的新任务添加顺序。
private ArrayList<Task> mChildren;
//ArrayList<Task> 类型的数据成员,用来存储属于该任务列表的所有子任务实例。
//构造函数
public TaskList() {
super();
mChildren = new ArrayList<Task>();
mIndex = 1;
}
//生成一个表示创建任务列表操作的JSON对象以便在网络传输或者持久化存储中使用。
public JSONObject getCreateAction(int actionId) {
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, mIndex);
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_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;
}
//该Java方法用于构建并返回一个表示更新动作的JSONObject对象该动作与给定的actionId相关联。
// 这个JSON对象遵循预定义的结构其中键值由GTaskStringUtils类提供。
public JSONObject getUpdateAction(int actionId) {
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;
}
//setContentByRemoteJSON的作用是从传入的远程JSON对象JSONObject中提取并设置相关的属性值。
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// id
if (js.has(GTaskStringUtils.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));
}
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get tasklist content from jsonobject");
}
}
}
//接受一个 JSONObject 参数 js该方法用于从给定的 JSON 对象中解析并设置本地内容
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { //如果传入的JSON对象为空或者不包含指定的键值对
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}
try {
//获取名为 META_HEAD_NOTE 的键所对应的 JSON 对象,并将其赋值给 folder 变量。
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
//检查 folder 对象中键为 NoteColumns.TYPE 所对应的值是否等于 Notes.TYPE_FOLDER即判断是否为文件夹类型的笔记。
//如果是文件夹类型的笔记,则获取文件夹的名称,并设置为当前任务列表的名称。
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
String name = folder.getString(NoteColumns.SNIPPET);
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name);
}
//如果不是文件夹类型的笔记,则检查是否为系统类型的笔记。
//如果是系统类型的笔记则根据笔记的ID来设置当前任务列表的名称。
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);
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();
}
}
//用于将当前任务列表的内容转换为本地的 JSON 对象。
public JSONObject getLocalJSONFromContent() {
try {
JSONObject js = new JSONObject();
JSONObject folder = new JSONObject();
String folderName = getName();//获取当前任务列表的名称,并将其赋值给 folderName 变量。
//检查任务列表的名称是否以 GTaskStringUtils.MIUI_FOLDER_PREFFIX 开头。
//如果是,则截取名称中 GTaskStringUtils.MIUI_FOLDER_PREFFIX 后面的部分作为文件夹名称。
if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX))
folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(),
folderName.length());
folder.put(NoteColumns.SNIPPET, folderName);//将文件夹名称放入 folder 对象中的 NoteColumns.SNIPPET 键下。
//检查文件夹名称是否等于默认文件夹名称或通话笔记文件夹名称。
//如果是,则将文件夹类型设置为系统类型 (Notes.TYPE_SYSTEM)。
if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT)
|| folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE))
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
//如果文件夹名称不是默认文件夹名称或通话笔记文件夹名称,则将文件夹类型设置为普通文件夹类型 (Notes.TYPE_FOLDER)。
else
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
//将存储了文件夹信息的 folder 对象放入主 JSON 对象 js 中的 GTaskStringUtils.META_HEAD_NOTE 键下。
js.put(GTaskStringUtils.META_HEAD_NOTE, folder);
return js;
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}
}
//用于确定与同步相关的操作。它接收一个 Cursor 对象作为参数,该 Cursor 对象包含了任务列表的数据。
public int getSyncAction(Cursor c) {
try {
//检查是否存在本地修改。
//如果没有本地修改:
//如果任务的最后修改时间与本地最后修改时间相同,则表示没有任何更新,返回 SYNC_ACTION_NONE。
//如果任务的最后修改时间与本地最后修改时间不同,则表示需要将远程更新应用到本地,返回 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;
}
}
//else { ... }
//如果存在本地修改:
//验证任务的 GTasks ID 是否与当前任务的 GTasks ID 匹配,如果不匹配则记录错误并返回 SYNC_ACTION_ERROR。
//如果任务的最后修改时间与本地最后修改时间相同,则表示只有本地修改,需要将本地修改应用到远程,返回 SYNC_ACTION_UPDATE_REMOTE。
//如果任务的最后修改时间与本地最后修改时间不同,目前代码中注释为针对文件夹冲突的情况,仍然将本地修改应用到远程,返回 SYNC_ACTION_UPDATE_REMOTE。
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 {
// 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;
}
//返回当前任务的子任务数量
public int getChildTaskCount() {
return mChildren.size();
}
//添加一个子任务
public boolean addChildTask(Task task) {
boolean ret = false;//用于标记是否添加成功
//检查传入的子任务对象 task 不为空,并且当前任务的子任务列表 mChildren 中不包含该子任务。
if (task != null && !mChildren.contains(task)) {
//如果满足上述条件,将子任务 task 添加到当前任务的子任务列表 mChildren 中,并将返回值赋给 ret表示添加操作是否成功。
ret = mChildren.add(task);
if (ret) {
//如果添加操作成功:
//设置子任务的前一个兄弟任务prior sibling如果当前任务的子任务列表为空则前一个兄弟任务为 null否则为列表中最后一个子任务。
//设置子任务的父任务为当前任务。
// need to set prior sibling and parent
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren
.get(mChildren.size() - 1));
task.setParent(this);
}
}
return ret;
}
//另一个版本的添加子任务方法,与之前的版本相比,这个方法允许在指定索引处添加子任务
public boolean addChildTask(Task task, int index) {
//首先检查传入的索引 index 是否有效,即确保它在合理的范围内。如果索引小于 0 或大于子任务列表的大小
if (index < 0 || index > mChildren.size()) {
Log.e(TAG, "add child task: invalid index");
return false;
}
//使用 indexOf() 方法检查子任务列表中是否已经存在传入的子任务 task并将其索引存储在变量 pos 中。
int pos = mChildren.indexOf(task);
if (task != null && pos == -1) {
//将传入的子任务 task 添加到子任务列表 mChildren 的指定索引 index 处。
mChildren.add(index, task);
// update the task list
//初始化变量 preTask 和 afterTask用于存储新添加的子任务的前一个兄弟任务和后一个兄弟任务。
Task preTask = null;
Task afterTask = null;
if (index != 0)
//如果新添加的子任务不是列表中的第一个任务,则将其前一个兄弟任务设置为索引为 index - 1 的任务。
preTask = mChildren.get(index - 1);
if (index != mChildren.size() - 1)
//如果新添加的子任务不是列表中的最后一个任务,则将其后一个兄弟任务设置为索引为 index + 1 的任务。
afterTask = mChildren.get(index + 1);
//将新添加的子任务的前一个兄弟任务设置为 preTask。
task.setPriorSibling(preTask);
if (afterTask != null)
//如果存在后一个兄弟任务,则将其前一个兄弟任务设置为新添加的子任务 task。
afterTask.setPriorSibling(task);
}
return true;
}
//移除子任务
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);//将移除的子任务的前一个兄弟任务设置为 null。
task.setParent(null);//将移除的子任务的父任务设置为 null。
// update the task list
if (index != mChildren.size()) { //如果移除的子任务不是列表中的最后一个任务,则执行以下操作:
//将下一个任务的前一个兄弟任务设置为移除任务的前一个兄弟任务,即更新列表中移除任务后的任务的前一个兄弟关系。
mChildren.get(index).setPriorSibling(
index == 0 ? null : mChildren.get(index - 1));
}
}
}
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;
}
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));
}
//根据子任务的 gid在子任务列表中查找特定子任务的方法
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;
}
//获取特定子任务在子任务列表中的索引位置
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;
}
return mChildren.get(index);
}
//根据子任务的 gid 查找特定子任务
public Task getChilTaskByGid(String gid) {
for (Task task : mChildren) {
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;
}
}

@ -1,33 +0,0 @@
/*
* 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.exception;// 导入包声明,这个类属于小米便签应用中的异常处理模块
public class ActionFailureException extends RuntimeException {//ActionFailureException 类是小米便签应用中定义的一个运行时异常类,
private static final long serialVersionUID = 4425249765923293627L;//这是一个固定的序列化版本号,用于确保在序列化和反序列化过程中,类的兼容性和一致性。
public ActionFailureException() {
super();
}
public ActionFailureException(String paramString) {//创建一个携带失败原因信息的 ActionFailureException 异常实例。
super(paramString);
}
public ActionFailureException(String paramString, Throwable paramThrowable) {//同时还接收一个 Throwable 对象paramThrowable该对象通常是指引发当前异常的实际底层异常。
super(paramString, paramThrowable);
}
}

@ -1,33 +0,0 @@
/*
* 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.exception;
//网络异常检测检测和处理
public class NetworkFailureException extends Exception {
private static final long serialVersionUID = 2107610287180234136L; //异常版本ID
public NetworkFailureException() { //重写并调用超类方法,
super(); //当网络操作失败且没有特定的错误信息需要传达时,可以使用这个构造函数抛出异常。
}
public NetworkFailureException(String paramString) {
super(paramString); //网络发生异常,需要传递相关排除异常的详细信息,有助于了解和解决异常
}
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable); //获得易于理解的错误描述,还能跟踪到原始的异常堆栈信息,有利于问题排查
}
}

@ -1,146 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import net.micode.notes.R;
import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
//定义一个异步任务类GTaskASyncTask用于在后台线程执行Google Tasks同步操作
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;// 定义一个静态常量用于通知的唯一ID
// 定义一个接口,用于在同步完成时回调
public interface OnCompleteListener {
void onComplete();
}
private Context mContext;// 定义成员变量,存储上下文环境
private NotificationManager mNotifiManager;// 定义NotificationManager实例用于管理通知
private GTaskManager mTaskManager; // 定义GTaskManager实例负责与Google Tasks服务交互
private OnCompleteListener mOnCompleteListener;// 定义监听器当同步完成时触发onComplete方法
// 构造函数,初始化相关组件和变量
public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context;// 设置应用程序上下文
mOnCompleteListener = listener;// 设置完成监听器
mNotifiManager = (NotificationManager)mContext
.getSystemService(Context.NOTIFICATION_SERVICE);// 获取系统通知管理器
mTaskManager = GTaskManager.getInstance();// 获取GTaskManager单例
}
// 取消正在进行的同步任务
public void cancelSync() {
mTaskManager.cancelSync();
}
// 发布进度更新消息
public void publishProgess(String message) {
publishProgress(new String[]{
message
});// 调用父类方法发布进度更新
}
// private void showNotification(int tickerId, String content) {
// Notification notification = new Notification(R.drawable.notification, mContext
// .getString(tickerId), System.currentTimeMillis());
// notification.defaults = Notification.DEFAULT_LIGHTS;// notification.flags = Notification.FLAG_AUTO_CANCEL;
// PendingIntent pendingIntent;
// pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
// NotesPreferenceActivity.class), 0);
// } else {
// pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
// NotesListActivity.class), 0);
// }
// notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
// pendingIntent);
// mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
// }
// 创建并显示通知的方法
private void showNotification(int tickerId, String content) {
PendingIntent pendingIntent;// 根据不同状态创建不同的PendingIntent
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE);// 同步过程中的通知跳转至偏好设置界面
}
else {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE);// 同步成功后的通知跳转至笔记列表界面
}
Notification.Builder builder = new Notification.Builder(mContext)
.setAutoCancel(true)
.setContentTitle(mContext.getString(R.string.app_name))
.setContentText(content)
.setContentIntent(pendingIntent)
.setWhen(System.currentTimeMillis())
.setOngoing(true);
Notification notification = builder.getNotification();
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);// 显示通知
}
@Override// 在后台线程执行同步逻辑
protected Integer doInBackground(Void... unused) {
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
.getSyncAccountName(mContext)));// 发布登录账号的同步进度
return mTaskManager.sync(mContext, this);// 执行实际的同步操作,并返回同步结果状态码
}
@Override// 当后台线程发布进度更新时,在主线程中处理更新
protected void onProgressUpdate(String... progress) {
showNotification(R.string.ticker_syncing, progress[0]);// 显示同步进度通知
if (mContext instanceof GTaskSyncService) {
((GTaskSyncService)mContext).sendBroadcast(progress[0]);// 如果当前上下文是GTaskSyncService则发送广播更新
}
}
@Override // 当后台同步操作完成后,在主线程中处理结果
protected void onPostExecute(Integer result) {
if (result == GTaskManager.STATE_SUCCESS) {
showNotification(R.string.ticker_success, mContext.getString(
R.string.success_sync_account, mTaskManager.getSyncAccount()));
NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis());// 记录最后同步时间
}
else if (result == GTaskManager.STATE_NETWORK_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network));
}
else if (result == GTaskManager.STATE_INTERNAL_ERROR) {
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal));
}
else if (result == GTaskManager.STATE_SYNC_CANCELLED) {
showNotification(R.string.ticker_cancel, mContext
.getString(R.string.error_sync_cancelled));
}
if (mOnCompleteListener != null) { // 若存在监听器则在新线程中触发onComplete方法
new Thread(new Runnable(){
public void run() {
mOnCompleteListener.onComplete();
}
}).start();
}
}
}

@ -1,688 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.ui.NotesPreferenceActivity;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.LinkedList;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
public class GTaskClient {
// 定义日志TAG方便调试输出
private static final String TAG = GTaskClient.class.getSimpleName();
// 定义Google Tasks服务的基本URL
private static final String GTASK_URL = "https://mail.google.com/tasks/";
// 定义获取任务信息的GET请求URL
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
// 定义提交任务更新等POST请求URL
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
// 单例模式确保全局只有一个GTaskClient实例
private static GTaskClient mInstance = null;
// HTTP客户端实例用于发起网络请求
private DefaultHttpClient mHttpClient;
// 存储GET请求的实际URL
private String mGetUrl;
// 存储POST请求的实际URL
private String mPostUrl;
// 客户端版本号
private long mClientVersion;
// 登录状态标志位,标识是否已登录
private boolean mLoggedin;
// 最后登录时间戳
private long mLastLoginTime;
// 行动操作ID用于跟踪每个请求的唯一性
private int mActionId;
// Google账户对象当前正在使用的同步账户
private Account mAccount;
// 用于缓存待更新的任务数据的JSONArray
private JSONArray mUpdateArray;
// 私有构造函数,防止外部直接实例化
private GTaskClient() {
// 初始化各个成员变量
mHttpClient = null;
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
mClientVersion = -1;
mLoggedin = false;
mLastLoginTime = 0;
mActionId = 1;
mAccount = null;
mUpdateArray = null;
}
// 静态工厂方法获取GTaskClient的单例
public static synchronized GTaskClient getInstance() {
// 如果实例尚未创建则创建并返回一个新的GTaskClient实例
if (mInstance == null) {
mInstance = new GTaskClient();
}
return mInstance;
}
// 登录方法尝试登录Google账户并验证GTask服务访问权限
public boolean login(Activity activity) {
// we suppose that the cookie would expire after 5 minutes
// then we need to re-login
// 判断上次登录时间是否超过5分钟若超过则需要重新登录
final long interval = 1000 * 60 * 5;
if (mLastLoginTime + interval < System.currentTimeMillis()) {
mLoggedin = false;
}
// 判断是否切换了同步账户,如果切换则需重新登录
// need to re-login after account switch
if (mLoggedin
&& !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity
.getSyncAccountName(activity))) {
mLoggedin = false;
}
// 如果已登录则直接返回true
if (mLoggedin) {
Log.d(TAG, "already logged in");
return true;
}
// 更新最后登录时间
mLastLoginTime = System.currentTimeMillis();
// 获取Google账户授权令牌
String authToken = loginGoogleAccount(activity, false);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// 对于非Gmail或Googlemail域的账户尝试使用自定义域名登录GTask服务
// login with custom domain if necessary
if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase()
.endsWith("googlemail.com"))) {
StringBuilder url = new StringBuilder(GTASK_URL).append("a/");
int index = mAccount.name.indexOf('@') + 1;
String suffix = mAccount.name.substring(index);
url.append(suffix + "/");
mGetUrl = url.toString() + "ig";
mPostUrl = url.toString() + "r/ig";
if (tryToLoginGtask(activity, authToken)) {
mLoggedin = true;
}
}
// 若未登录成功则尝试使用官方URL登录GTask服务
// try to login with google official url
if (!mLoggedin) {
mGetUrl = GTASK_GET_URL;
mPostUrl = GTASK_POST_URL;
if (!tryToLoginGtask(activity, authToken)) {
return false;
}
}
// 登录成功,设置登录状态为已登录
mLoggedin = true;
return true;
}
// 辅助方法从Google账户获取授权令牌
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
// 获取AccountManager实例用于管理账户认证
AccountManager accountManager = AccountManager.get(activity);
// 获取所有类型为"com.google"的账户
Account[] accounts = accountManager.getAccountsByType("com.google");
// 检查是否存在可用的Google账户
if (accounts.length == 0) {
Log.e(TAG, "there is no available google account");
return null;
}
// 获取用户在设置中选择的同步账户名
String accountName = NotesPreferenceActivity.getSyncAccountName(activity);
Account account = null;
// 查找与设置中所选账户名相匹配的账户
for (Account a : accounts) {
if (a.name.equals(accountName)) {
account = a;
break;
}
}
// 如果找到匹配的账户,则设置当前账户为找到的账户
if (account != null) {
mAccount = account;
}
else {
Log.e(TAG, "unable to get an account with the same name in the settings");
return null;
}
// 从账户管理器中获取授权令牌
// get the token now
AccountManagerFuture<Bundle> accountManagerFuture = accountManager.getAuthToken(account,
"goanna_mobile", null, activity, null, null);
try {
Bundle authTokenBundle = accountManagerFuture.getResult();
authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
// 如果需要刷新令牌,则先使当前令牌失效,然后重新获取
if (invalidateToken) {
accountManager.invalidateAuthToken("com.google", authToken);
loginGoogleAccount(activity, false);
}
}
catch (Exception e) {
Log.e(TAG, "get auth token failed");
authToken = null;
}
return authToken;
}
// 这个方法尝试登录GTask服务。首先使用提供的authToken进行登录如果失败则尝试刷新并重新登录。
// 登录成功后返回true否则返回false。
private boolean tryToLoginGtask(Activity activity, String authToken) {
// 使用给定的authToken尝试登录GTask
if (!loginGtask(authToken)) {
// 如果登录失败可能是authToken过期则尝试通过Google账号刷新并获取新的authToken
// maybe the auth token is out of date, now let's invalidate the
// token and try again
authToken = loginGoogleAccount(activity, true);
// 如果刷新并获取新的authToken失败
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;// 返回登录失败
}
// 使用新的authToken再次尝试登录GTask
if (!loginGtask(authToken)) {
Log.e(TAG, "login gtask failed");// 记录错误日志
return false;// 返回登录失败
}
}
// 登录成功
return true;
}
// 这个私有方法用于执行实际的GTask登录过程根据提供的authToken发送HTTP请求并检查响应中的认证Cookie是否存在。
private boolean loginGtask(String authToken) {
// 设置HTTP连接和读取超时时间
int timeoutConnection = 10000;
int timeoutSocket = 15000;
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
mHttpClient = new DefaultHttpClient(httpParameters); // 创建一个HTTP客户端实例
// 创建并设置Cookie存储对象
BasicCookieStore localBasicCookieStore = new BasicCookieStore();
mHttpClient.setCookieStore(localBasicCookieStore);
// 设置HTTP协议参数
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// login gtask
try {
// 构建登录URL并发起GET请求
String loginUrl = mGetUrl + "?auth=" + authToken;
HttpGet httpGet = new HttpGet(loginUrl);
// 执行HTTP请求并获取响应
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// 检查响应中是否包含认证Cookie
// get the cookie now
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false;
for (Cookie cookie : cookies) {
if (cookie.getName().contains("GTL")) {
hasAuthCookie = true;// 如果找到名为"GTL"的Cookie则表示存在认证Cookie
}
}
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth cookie");// 若未发现认证Cookie则记录警告日志
}
// 从响应内容中解析客户端版本信息
// get the client version
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
JSONObject js = new JSONObject(jsString);
mClientVersion = js.getLong("v");// 获取并保存客户端版本号
}
catch (JSONException e) {
Log.e(TAG, e.toString());// 记录JSON解析异常的日志
e.printStackTrace();
return false;// JSON解析出错返回登录失败
}
catch (Exception e) {// 捕获所有其他可能的异常
// simply catch all exceptions
Log.e(TAG, "httpget gtask_url failed");// 记录HTTP GET请求失败的日志
return false;// HTTP请求出错返回登录失败
}
// 如果执行到此处,说明登录过程顺利完成
return true;
}
// 这是一个辅助方法用于生成并返回唯一的动作ID。
private int getActionId() {
return mActionId++;// 自增并返回mActionId的当前值
}
// 这个私有方法用于创建并配置一个HttpPost对象指定目标URL和相关HTTP头部信息。
private HttpPost createHttpPost() {
// 创建一个新的HttpPost对象指向预定义的POST请求URL
HttpPost httpPost = new HttpPost(mPostUrl);
// 设置HTTP头部Content-Type字段表明发送的数据格式为application/x-www-form-urlencoded并采用UTF-8编码
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
// 添加自定义HTTP头部信息这里设定了一个名为"AT"的字段,其值为"1"
httpPost.setHeader("AT", "1");
// 返回已配置好的HttpPost对象
return httpPost;
}
// 这个私有方法用于从HttpEntity中获取并解析响应内容支持gzip和deflate压缩格式。
private String getResponseContent(HttpEntity entity) throws IOException {
// 获取实体的Content-Encoding属性即数据的压缩编码方式
String contentEncoding = null;
if (entity.getContentEncoding() != null) {
contentEncoding = entity.getContentEncoding().getValue();
Log.d(TAG, "encoding: " + contentEncoding);// 输出编码方式至日志
}
// 获取实体内容的输入流
InputStream input = entity.getContent();
// 根据Content-Encoding选择合适的解压方式
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
// 如果数据被gzip压缩使用GZIPInputStream解压
input = new GZIPInputStream(entity.getContent());
}
else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
// 如果数据被deflate压缩使用InflaterInputStream解压注意这里的Inflater构造函数传入true启用ZLIB RFC1950格式
Inflater inflater = new Inflater(true);
input = new InflaterInputStream(entity.getContent(), inflater);
}
// 使用Reader和BufferedReader逐行读取并拼接响应内容
try {
InputStreamReader isr = new InputStreamReader(input);// 使用UTF-8编码创建StreamReader
BufferedReader br = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
// 循环读取每一行内容直到结束
while (true) {
String buff = br.readLine();
if (buff == null) {
// 当读取到最后一行null返回累积的字符串内容
return sb.toString();
}
sb = sb.append(buff);// 将读取到的一行追加到StringBuilder中
}
}
finally {
// 在所有操作结束后确保关闭输入流
input.close();
}
}
// 这个私有方法用于执行一个POST请求并将JSON对象作为请求体发送。若发生网络错误或无法将响应转换为JSON对象将抛出异常
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
// 验证用户是否已登录,未登录则抛出自定义异常
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
// 创建并初始化HttpPost对象
HttpPost httpPost = createHttpPost();
try {
// 创建一个包含请求参数的链表,参数名为'r'值为传入的JSON对象的字符串形式
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
list.add(new BasicNameValuePair("r", js.toString()));
// 创建一个UrlEncodedFormEntity对象编码格式为UTF-8用于封装请求参数
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httpPost.setEntity(entity);
// execute the post
// 执行POST请求
HttpResponse response = mHttpClient.execute(httpPost);
// 从响应中获取内容并转换为JSON对象
String jsString = getResponseContent(response.getEntity());
return new JSONObject(jsString);
}
catch (ClientProtocolException e) {
// 记录并抛出网络协议错误异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
}
catch (IOException e) {
// 记录并抛出JSON解析错误异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("postRequest failed");
}
catch (JSONException e) {
// 记录并抛出任何未知错误异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("unable to convert response content to jsonobject");
}
catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("error occurs when posting request");
}
}
// 公共方法,用于创建一个新的任务
public void createTask(Task task) throws NetworkFailureException {
// 提交更新前的操作
commitUpdate();
try {
// 创建一个用于发送的JSON对象
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();// 创建一个JSONArray存放动作列表
// action_list
// 将新任务的动作信息添加到action_list中
actionList.put(task.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// 添加客户端版本信息到JSON对象
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 调用postRequest方法执行POST请求
// post
JSONObject jsResponse = postRequest(jsPost);
// 从响应的JSON对象中提取结果数组的第一个元素
JSONObject jsResult = (JSONObject)jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
// 从响应结果中获取新创建的任务ID并将其设置到task对象中
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
}
catch (JSONException e) {
// 记录并抛出JSON处理异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create task: handing jsonobject failed");
}
}
// 此公共方法用于创建一个新的任务列表,将任务列表的创建动作添加到请求体中并提交至服务器
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
// 在更新数据库前进行必要的提交操作
commitUpdate();
try {
// 创建用于发送请求的JSON对象
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();// 创建一个JSONArray来存储动作列表
// 将新建任务列表的动作信息加入到动作列表中
// action_list
actionList.put(tasklist.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);// 将动作列表放入JSON对象
// 添加客户端版本信息到JSON对象
// client version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送POST请求
// post
JSONObject jsResponse = postRequest(jsPost);
// 从响应的JSON对象中获取结果数组的第一个元素
JSONObject jsResult = (JSONObject)jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
// 从响应结果中取出新创建的任务列表ID并设置到tasklist对象中
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
}
catch (JSONException e) {
// 处理JSON异常并抛出操作失败异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create tasklist: handing jsonobject failed");
}
}
// 此公共方法用于提交当前待更新的动作列表至服务器
public void commitUpdate() throws NetworkFailureException {
// 如果有待提交的更新数组(动作列表)
if (mUpdateArray != null) {
try {
JSONObject jsPost = new JSONObject();// 创建用于发送请求的JSON对象
// action_list
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);// 将待提交的动作列表放入JSON对象
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); // 添加客户端版本信息到JSON对象
postRequest(jsPost);// 发送POST请求
mUpdateArray = null; // 清空已提交的更新数组
}
catch (JSONException e) {// 处理JSON异常并抛出操作失败异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("commit update: handing jsonobject failed");
}
}
}
public void addUpdateNode(Node node) throws NetworkFailureException {// 此公共方法用于向待更新的动作列表中添加一个节点的更新动作
if (node != null) {// 如果节点非空
// 避免一次性更新过多导致错误限制最多允许10个更新项
// too many update items may result in an error
// set max to 10 items
if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate();// 超过10个则先提交现有的更新
}
if (mUpdateArray == null)
mUpdateArray = new JSONArray(); // 如果待更新数组为空则创建一个新的JSONArray
mUpdateArray.put(node.getUpdateAction(getActionId()));// 将节点的更新动作添加到待更新数组中
}
}
public void moveTask(Task task, TaskList preParent, TaskList curParent)// 此方法用于移动一个任务到另一个任务列表中,包括在同一任务列表内调整位置和跨任务列表移动
throws NetworkFailureException{
commitUpdate();// 在移动任务之前先提交任何待更新的操作
try {// 创建用于发送请求的JSON对象
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
// action_list
// 构建动作列表
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE);// 动作类型:移动
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());// 动作ID
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid());// 目标任务ID
if (preParent == curParent && task.getPriorSibling() != null) {// 如果当前任务列表与原父任务列表相同并且该任务不是第一个子任务则添加前置兄弟任务ID
// put prioring_sibing_id only if moving within the tasklist and
// it is not the first one
action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling());
}
action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid());// 添加原父任务列表ID
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid());// 添加目标父任务列表ID
if (preParent != curParent) {// 如果任务要从一个任务列表移动到另一个任务列表则添加目标任务列表ID
// put the dest_list only if moving between tasklists
action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid());
}
actionList.put(action);// 将移动动作添加到动作列表中
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
// 添加客户端版本信息
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送POST请求
postRequest(jsPost);
}
catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("move task: handing jsonobject failed");
}
}
// 此方法用于删除指定的节点(任务或任务列表)
public void deleteNode(Node node) throws NetworkFailureException {
commitUpdate();// 提交任何待更新的操作
try {// 创建用于发送请求的JSON对象
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// action_list
node.setDeleted(true);// 将节点标记为已删除,并将其更新动作添加到动作列表中
actionList.put(node.getUpdateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);// 添加客户端版本信息
postRequest(jsPost);// 发送POST请求
mUpdateArray = null;// 清空已提交的更新数组
}
catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("delete node: handing jsonobject failed");
}
}
public JSONArray getTaskLists() throws NetworkFailureException {// 此方法用于获取所有的任务列表
if (!mLoggedin) {// 检查用户是否已登录,未登录则抛出异常
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
try {// 创建并执行HttpGet请求
HttpGet httpGet = new HttpGet(mGetUrl);
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// get the task list
// 从响应内容中解析出任务列表数据
String resString = getResponseContent(response.getEntity());
String jsBegin = "_setup(";
String jsEnd = ")}</script>";
int begin = resString.indexOf(jsBegin);
int end = resString.lastIndexOf(jsEnd);
String jsString = null;
if (begin != -1 && end != -1 && begin < end) {
jsString = resString.substring(begin + jsBegin.length(), end);
}
// 将解析出的JSON字符串转换为JSONObject并从中提取任务列表数据
JSONObject js = new JSONObject(jsString);
return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS);
}
catch (ClientProtocolException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
}
catch (IOException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new NetworkFailureException("gettasklists: httpget failed");
}
catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("get task lists: handing jasonobject failed");
}
}
// 此方法用于获取指定任务列表ID下的所有任务
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
// 在获取任务列表前先提交任何待更新的操作
commitUpdate();
try {// 创建用于发送请求的JSON对象及动作列表
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
JSONObject action = new JSONObject();
// action_list
// 构建动作详情,动作类型为获取全部任务
action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL);
action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId());// 设置动作ID
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid);// 设置要获取的任务列表ID
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false);// 不获取已删除的任务
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); // 将动作列表放入JSON请求体
// client_version
// 添加客户端版本信息到JSON请求体
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// 发送POST请求并获取响应
JSONObject jsResponse = postRequest(jsPost);
// 从响应的JSON对象中提取任务列表tasks并返回
return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS);
}
catch (JSONException e) {
Log.e(TAG, e.toString());// 记录并打印异常信息
e.printStackTrace();
throw new ActionFailureException("get task list: handing jsonobject failed");// 抛出操作失败异常
}
}
public Account getSyncAccount() {// 此方法用于获取同步账户对象
return mAccount;// 直接返回当前持有的同步账户对象
}
public void resetUpdateArray() {// 此方法用于重置待更新动作数组
mUpdateArray = null;// 将待更新动作数组设为null表示清空所有待更新项
}
}

@ -1,948 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.data.MetaData;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.SqlNote;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
//GTaskManager类用于管理小米便签与Google Tasks之间的同步逻辑
public class GTaskManager {
// 类日志标签
private static final String TAG = GTaskManager.class.getSimpleName();
// 定义同步状态常量
public static final int STATE_SUCCESS = 0; // 成功
public static final int STATE_NETWORK_ERROR = 1; // 网络错误
public static final int STATE_INTERNAL_ERROR = 2; // 内部错误
public static final int STATE_SYNC_IN_PROGRESS = 3; // 同步进行中
public static final int STATE_SYNC_CANCELLED = 4; // 同步已取消
// 单例模式实现
private static GTaskManager mInstance = null;
private Activity mActivity; // 活动对象
private Context mContext; // 上下文对象
private ContentResolver mContentResolver; // 内容解析器
private boolean mSyncing; // 是否正在进行同步
private boolean mCancelled; // 同步是否被取消
private HashMap<String, TaskList> mGTaskListHashMap; // Google任务列表哈希映射
private HashMap<String, Node> mGTaskHashMap; // Google任务哈希映射
private HashMap<String, MetaData> mMetaHashMap; // 元数据哈希映射
private TaskList mMetaList; // 元数据列表
private HashSet<Long> mLocalDeleteIdMap; // 本地删除ID集合
private HashMap<String, Long> mGidToNid; // Google ID到本地ID的映射
private HashMap<Long, String> mNidToGid; // 本地ID到Google ID的映射
// 私有构造函数,初始化各变量
private GTaskManager() {
mSyncing = false;
mCancelled = false;
mGTaskListHashMap = new HashMap<String, TaskList>();
mGTaskHashMap = new HashMap<String, Node>();
mMetaHashMap = new HashMap<String, MetaData>();
mMetaList = null;
mLocalDeleteIdMap = new HashSet<Long>();
mGidToNid = new HashMap<String, Long>();
mNidToGid = new HashMap<Long, String>();
} // 初始化各个哈希表和集合
// 获取唯一实例
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
mInstance = new GTaskManager();
}
return mInstance;
}
// 设置活动上下文
public synchronized void setActivityContext(Activity activity) {
// 用于获取授权令牌
mActivity = activity;
}
// 同步方法
public int sync(Context context, GTaskASyncTask asyncTask) {
if (mSyncing) {
Log.d(TAG, "Sync is in progress");
return STATE_SYNC_IN_PROGRESS;
}
mContext = context;
mContentResolver = mContext.getContentResolver();
mSyncing = true;
mCancelled = false;
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();// 清除所有临时存储的数据结构
try {
GTaskClient client = GTaskClient.getInstance();
client.resetUpdateArray();
// 登录Google任务
if (!mCancelled) {
if (!client.login(mActivity)) {
throw new NetworkFailureException("login google task failed");
}
}
// 从Google获取任务列表
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList();
// 执行内容同步工作
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent();
} catch (NetworkFailureException e) {
Log.e(TAG, e.toString());
return STATE_NETWORK_ERROR;
} catch (ActionFailureException e) {
Log.e(TAG, e.toString());
return STATE_INTERNAL_ERROR;
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return STATE_INTERNAL_ERROR;
} finally {
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
mSyncing = false;
}// 清除所有临时存储的数据结构
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; // 返回最终同步状态
}
private void initGTaskList() throws NetworkFailureException {
// 如果同步已取消则直接返回
if (mCancelled)
return;
// 获取 GTaskClient 实例
GTaskClient client = GTaskClient.getInstance();
try {
// 获取任务列表的 JSON 数组
JSONArray jsTaskLists = client.getTaskLists();
// 初始化元数据列表
mMetaList = null;
for (int i = 0; i < jsTaskLists.length(); i++) {
// 遍历任务列表数组中的每一个元素
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
// 如果当前任务列表是元数据文件夹
if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
// 创建新的元数据列表
mMetaList = new TaskList();
mMetaList.setContentByRemoteJSON(object);
// 加载元数据
JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) {
// 遍历元数据列表数组中的每一个元素
object = (JSONObject) jsMetas.getJSONObject(j);
MetaData metaData = new MetaData();
metaData.setContentByRemoteJSON(object);
// 如果元数据值得保存
if (metaData.isWorthSaving()) {
// 添加元数据到元数据列表
mMetaList.addChildTask(metaData);
if (metaData.getGid() != null) {
// 将元数据与相关的元数据 ID 映射存储
mMetaHashMap.put(metaData.getRelatedGid(), metaData);
}
}
}
}
}
// 如果元数据列表为空,则创建一个新的元数据列表
if (mMetaList == null) {
mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META);
GTaskClient.getInstance().createTaskList(mMetaList);
}
// 初始化任务列表
for (int i = 0; i < jsTaskLists.length(); i++) {
// 遍历任务列表数组中的每一个元素
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
// 如果任务列表以 MIUI_FOLDER_PREFIX 开头且不是元数据文件夹
if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)
&& !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
// 创建新的任务列表对象
TaskList tasklist = new TaskList();
tasklist.setContentByRemoteJSON(object);
mGTaskListHashMap.put(gid, tasklist);
mGTaskHashMap.put(gid, tasklist);
// 加载任务
JSONArray jsTasks = client.getTaskList(gid);
for (int j = 0; j < jsTasks.length(); j++) {
// 遍历任务数组中的每一个元素
object = (JSONObject) jsTasks.getJSONObject(j);
gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
Task task = new Task();
task.setContentByRemoteJSON(object);
// 如果任务值得保存
if (task.isWorthSaving()) {
// 设置任务的元数据信息
task.setMetaInfo(mMetaHashMap.get(gid));
// 添加任务到任务列表
tasklist.addChildTask(task);
mGTaskHashMap.put(gid, task);
}
}
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("initGTaskList: handing JSONObject failed");
}
}
private void syncContent() throws NetworkFailureException {
int syncType; // 同步类型
Cursor c = null; // 游标对象初始化为null
String gid; // Google Task ID
Node node; // 节点对象
mLocalDeleteIdMap.clear(); // 清空本地删除 ID 映射表
if (mCancelled) { // 如果同步已取消,则直接返回
return;
}
// 处理本地已删除的便签
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id=?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, null);
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); // 执行内容同步操作:删除远程节点
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 将本地删除 ID 添加到映射表中
}
} else {
Log.w(TAG, "failed to query trash folder");
}
} finally {
if (c != null) {
c.close(); // 关闭游标
c = null;
}
}
// 先同步文件夹
syncFolder();
// 处理数据库中存在的便签
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c); // 获取同步操作类型
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地添加
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// 远程删除
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c); // 执行内容同步操作
}
} else {
Log.w(TAG, "failed to query existing note in database");
}
} finally {
if (c != null) {
c.close(); // 关闭游标
c = null;
}
}
// 处理剩余的项
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Node> entry = iter.next();
node = entry.getValue();
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 执行内容同步操作:添加本地节点
}
// 可能会被另一个线程设置mCancelled所以我们需要逐个检查
// 清除本地删除表
if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("failed to batch-delete local deleted notes");
}
}
// 刷新本地同步 ID
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId();
}
}
private void syncFolder() throws NetworkFailureException {
Cursor c = null; // 定义游标对象
String gid; // Google 任务 ID
Node node; // 节点对象
int syncType; // 同步类型
if (mCancelled) { // 如果已取消同步,则直接返回
return;
}
// 对根文件夹进行处理
try {
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null);
if (c != null) {
c.moveToNext();
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// 对系统文件夹,仅在必要时更新远程名称
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);// 执行内容同步操作:更新远程节点
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);// 执行内容同步操作:添加远程节点
}
} else {
Log.w(TAG, "failed to query root folder");
}
} finally {
if (c != null) {
c.close();// 关闭游标
c = null;
}
}
// 对通话便签文件夹进行处理
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(Notes.ID_CALL_RECORD_FOLDER)
}, null);
if (c != null) {
if (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);
// for system folder, only update remote name if
// 对系统文件夹,仅在必要时更新远程名称
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);// 执行内容同步操作:更新远程节点
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);// 执行内容同步操作:添加远程节点
}
}
} else {
Log.w(TAG, "failed to query call note folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 对本地存在的文件夹进行处理
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);// 获取同步操作类型
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add
syncType = Node.SYNC_ACTION_ADD_REMOTE; // 本地添加
} else {
// remote delete
syncType = Node.SYNC_ACTION_DEL_LOCAL;// 远程删除
}
}
doContentSync(syncType, node, c);// 执行内容同步操作
}
} else {
Log.w(TAG, "failed to query existing folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// 遍历远程任务列表映射mGTaskListHashMap处理每个映射项
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
// 获取当前映射项
Map.Entry<String, TaskList> entry = iter.next();
// 提取映射项的键Google 任务 ID与值TaskList 对象)
gid = entry.getKey();
node = entry.getValue();
// 检查本地任务哈希表mGTaskHashMap中是否存在该 Google 任务 ID
if (mGTaskHashMap.containsKey(gid)) {
// 若存在,从本地任务哈希表中移除该 ID 对应的任务
mGTaskHashMap.remove(gid);
// 执行内容同步操作,将远程任务添加到本地
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
}
// 如果同步过程未被取消
if (!mCancelled) {
// 提交更新至 Google 任务服务端
GTaskClient.getInstance().commitUpdate();
}
}
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
// 如果同步过程已取消,直接返回
return;
}
MetaData meta;
// 根据同步类型执行相应操作
switch (syncType) {
case Node.SYNC_ACTION_ADD_LOCAL:
// 将远程任务添加到本地
addLocalNode(node);
break;
case Node.SYNC_ACTION_ADD_REMOTE:
// 将本地任务添加到远程
addRemoteNode(node, c);
break;
case Node.SYNC_ACTION_DEL_LOCAL:
// 获取待删除任务的元数据MetaData
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN));
// 如果元数据存在,调用 Google 任务客户端删除该任务
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta);
}
// 将待删除的本地任务 ID 添加到本地删除 ID 集合
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
break;
case Node.SYNC_ACTION_DEL_REMOTE:
// 获取待删除任务的元数据MetaData
meta = mMetaHashMap.get(node.getGid());
// 如果元数据存在,调用 Google 任务客户端删除该任务
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta);
}
// 调用 Google 任务客户端删除指定任务
GTaskClient.getInstance().deleteNode(node);
break;
case Node.SYNC_ACTION_UPDATE_LOCAL:
// 更新本地任务
updateLocalNode(node, c);
break;
case Node.SYNC_ACTION_UPDATE_REMOTE:
// 更新远程任务
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_UPDATE_CONFLICT:
// 处理冲突:目前仅使用本地更新简单处理
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_NONE:
// 无需执行任何操作
break;
case Node.SYNC_ACTION_ERROR:
default:
// 抛出异常,表示未知的同步操作类型
throw new ActionFailureException("unkown sync action type");
}
}
private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) {
// 如果同步过程已取消,直接返回
return;
}
SqlNote sqlNote;
// 处理不同类型的节点TaskList 或 Task
if (node instanceof TaskList) {
// 节点为 TaskList 类型
if (node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) {
// 根文件夹
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER);
} else if (node.getName().equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) {
// 通话便签文件夹
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER);
} else {
// 其他自定义文件夹
sqlNote = new SqlNote(mContext);
sqlNote.setContent(node.getLocalJSONFromContent());
sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
}
} else {
// 节点为 Task 类型
sqlNote = new SqlNote(mContext);
// 获取节点的本地 JSON 表示,并处理 ID 冲突
JSONObject js = node.getLocalJSONFromContent();
try {
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.has(NoteColumns.ID)) {
long id = note.getLong(NoteColumns.ID);
if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
// 当前 ID 已存在,需创建新 ID
note.remove(NoteColumns.ID);
}
}
}
if (js.has(GTaskStringUtils.META_HEAD_DATA)) {
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
// 数据 ID 已存在,需创建新 ID
data.remove(DataColumns.ID);
}
}
}
}
} catch (JSONException e) {
Log.w(TAG, e.toString());
e.printStackTrace();
}
sqlNote.setContent(js);
// 设置父任务 ID
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot add local node");
}
sqlNote.setParentId(parentId.longValue());
}
// 创建本地节点
sqlNote.setGtaskId(node.getGid());
sqlNote.commit(false);
// 更新 GID-NID 映射
mGidToNid.put(node.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), node.getGid());
// 更新远程元数据
updateRemoteMeta(node.getGid(), sqlNote);
}
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
// 如果同步过程已被取消,直接返回,不再执行后续操作
return;
}
SqlNote sqlNote;
// 1. 创建并初始化 SqlNote 对象,用于更新本地节点
// 使用传入的上下文 mContext 和游标 c 初始化 SqlNote 实例
sqlNote = new SqlNote(mContext, c);
// 2. 设置 SqlNote 的内容为当前节点的本地 JSON 表示
// 调用 Node 对象的 getLocalJSONFromContent() 方法获取其本地 JSON 表示,并设置到 SqlNote 对象中
sqlNote.setContent(node.getLocalJSONFromContent());
// 3. 获取待更新节点的父任务 ID
// 根据节点类型Task 或其他类型)区分处理:
// - 如果节点是 Task 类型,通过 mGidToNid 映射表获取其父任务的 Google ID再转换为本地 ID
// - 否则,将其父任务 ID 默认设置为根文件夹 IDNotes.ID_ROOT_FOLDER
Long parentId;
if (node instanceof Task) {
parentId = mGidToNid.get(((Task) node).getParent().getGid());
} else {
parentId = new Long(Notes.ID_ROOT_FOLDER);
}
// 4. 检查父任务 ID 是否有效
// 如果父任务 ID 为 null说明无法找到对应的本地父任务记录错误信息并抛出异常
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot update local node");
}
// 5. 设置 SqlNote 的父任务 ID并提交更新
// 将计算得到的父任务 ID 设置到 SqlNote 对象中,然后调用 commit(true) 方法提交更新至本地数据库
sqlNote.setParentId(parentId.longValue());
sqlNote.commit(true);
// 6. 更新远程元数据
// 调用 updateRemoteMeta() 方法,同步更新远程元数据以保持与本地数据一致
updateRemoteMeta(node.getGid(), sqlNote);
}
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
// 如果同步过程已被取消,直接返回,不再执行后续操作
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
Node n;
// 1. 根据 SqlNote 类型(任务或文件夹),执行相应的远程添加操作
// 如果 SqlNote 是任务类型
if (sqlNote.isNoteType()) {
// 创建 Task 对象并设置内容
Task task = new Task();
task.setContentByLocalJSON(sqlNote.getContent());
// 获取父任务列表的 Google ID
String parentGid = mNidToGid.get(sqlNote.getParentId());
// 检查父任务列表 ID 是否有效
if (parentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot add remote task");
}
// 在对应的父任务列表中添加子任务
mGTaskListHashMap.get(parentGid).addChildTask(task);
// 调用 Google 任务客户端创建远程任务
GTaskClient.getInstance().createTask(task);
// 将 Task 对象赋值给 Node 变量 n
n = (Node) task;
// 更新远程元数据
updateRemoteMeta(task.getGid(), sqlNote);
} else {
// 如果 SqlNote 是文件夹类型
// 初始化 TaskList 变量 tasklist用于存储已匹配或新建的远程文件夹
TaskList tasklist = null;
// 构建文件夹名称
String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX;
if (sqlNote.getId() == Notes.ID_ROOT_FOLDER)
folderName += GTaskStringUtils.FOLDER_DEFAULT;
else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER)
folderName += GTaskStringUtils.FOLDER_CALL_NOTE;
else
folderName += sqlNote.getSnippet();
// 遍历已缓存的远程文件夹,查找匹配的文件夹
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
String gid = entry.getKey();
TaskList list = entry.getValue();
if (list.getName().equals(folderName)) {
tasklist = list;
// 如果找到匹配的文件夹且其已存在于本地删除队列,从队列中移除
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
}
break;
}
}
// 如果未找到匹配的文件夹,创建新的 TaskList 对象并添加到远程
if (tasklist == null) {
tasklist = new TaskList();
tasklist.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().createTaskList(tasklist);
mGTaskListHashMap.put(tasklist.getGid(), tasklist);
}
// 将 TaskList 对象赋值给 Node 变量 n
n = (Node) tasklist;
}
// 2. 更新本地 SqlNote 记录
// 设置 SqlNote 的 Google ID
sqlNote.setGtaskId(n.getGid());
// 提交更新false不触发本地更新
sqlNote.commit(false);
// 清除本地修改标志
sqlNote.resetLocalModified();
// 提交更新true触发本地更新
sqlNote.commit(true);
// 3. 更新 GID-NID 映射表
// 将新添加的远程节点的 Google ID 和本地 ID 添加到映射表
mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid());
}
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
// 如果同步过程已被取消,直接返回,不再执行后续操作
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
// 1. 更新远程节点
// 根据 SqlNote 设置 Node 对象的内容
node.setContentByLocalJSON(sqlNote.getContent());
// 调用 Google 任务客户端更新远程节点
GTaskClient.getInstance().addUpdateNode(node);
// 更新远程元数据
updateRemoteMeta(node.getGid(), sqlNote);
// 2. 若为任务类型,检查是否需要移动任务
if (sqlNote.isNoteType()) {
Task task = (Task) node;
TaskList preParentList = task.getParent();
// 获取当前父任务列表的 Google ID
String curParentGid = mNidToGid.get(sqlNote.getParentId());
// 检查当前父任务列表 ID 是否有效
if (curParentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot update remote task");
}
// 获取当前父任务列表对象
TaskList curParentList = mGTaskListHashMap.get(curParentGid);
// 如果任务的原父任务列表与当前父任务列表不同,移动任务
if (preParentList != curParentList) {
preParentList.removeChildTask(task);
curParentList.addChildTask(task);
GTaskClient.getInstance().moveTask(task, preParentList, curParentList);
}
}
// 3. 清除本地修改标志并提交更新
// 清除 SqlNote 的本地修改标志
sqlNote.resetLocalModified();
// 提交更新true触发本地更新
sqlNote.commit(true);
}
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
if (sqlNote != null && sqlNote.isNoteType()) {
// 1. 获取或创建 MetaData 对象
MetaData metaData = mMetaHashMap.get(gid);
if (metaData != null) {
// 若已存在 MetaData更新其内容并添加到更新队列
metaData.setMeta(gid, sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(metaData);
} else {
// 若不存在 MetaData创建新的 MetaData 对象,设置内容,添加到元数据列表、映射表和更新队列
metaData = new MetaData();
metaData.setMeta(gid, sqlNote.getContent());
mMetaList.addChildTask(metaData);
mMetaHashMap.put(gid, metaData);
GTaskClient.getInstance().createTask(metaData);
}
}
}
private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) {
// 如果同步过程已被取消,直接返回,不再执行后续操作
return;
}
// 1. 获取最新的 Google 任务列表
// 清空本地缓存的 Google 任务哈希表、Google 任务列表哈希表和元数据哈希表
mGTaskHashMap.clear();
mGTaskListHashMap.clear();
mMetaHashMap.clear();
// 初始化 Google 任务列表
initGTaskList();
// 2. 查询本地便签数据,更新同步 ID
Cursor c = null;
try {
// 构建查询条件,排除系统类型和垃圾箱中的便签
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
// 遍历查询结果,更新每个便签的同步 ID
while (c.moveToNext()) {
// 获取便签的 Google ID
String gid = c.getString(SqlNote.GTASK_ID_COLUMN);
// 查找对应的 Google 任务
Node node = mGTaskHashMap.get(gid);
if (node != null) {
// 移除已处理的 Google 任务
mGTaskHashMap.remove(gid);
// 构建用于更新同步 ID 的 ContentValues
ContentValues values = new ContentValues();
values.put(NoteColumns.SYNC_ID, node.getLastModified());
// 更新便签的同步 ID
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
c.getLong(SqlNote.ID_COLUMN)), values, null, null);
} else {
// 记录错误信息并抛出异常,表示本地便签缺少 Google ID
Log.e(TAG, "something is missed");
throw new ActionFailureException(
"some local items don't have gid after sync");
}
}
} else {
// 记录警告信息,表示未能成功查询本地便签
Log.w(TAG, "failed to query local note to refresh sync id");
}
} finally {
// 关闭游标
if (c != null) {
c.close();
c = null;
}
}
}
public String getSyncAccount() {
// 返回 Google 任务客户端使用的同步账户名
return GTaskClient.getInstance().getSyncAccount().name;
}
public void cancelSync() {
// 标记同步过程已取消
mCancelled = true;
}
}

@ -1,158 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
//定义一个服务类GTaskSyncService用于实现Google Tasks的同步服务
public class GTaskSyncService extends Service {
// 定义常量,用于标识不同的同步动作类型
public final static String ACTION_STRING_NAME = "sync_action_type";
public final static int ACTION_START_SYNC = 0; // 开始同步动作
public final static int ACTION_CANCEL_SYNC = 1; // 取消同步动作
public final static int ACTION_INVALID = 2; // 无效动作标识符
// 定义广播相关的常量
public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service";
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; // 标识是否正在进行同步
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; // 同步进度消息
// 定义静态成员变量,用于保存异步同步任务实例
private static GTaskASyncTask mSyncTask = null;
// 定义同步进度信息
private static String mSyncProgress = "";
// 开始同步任务的方法
private void startSync() {
// 如果当前没有同步任务正在进行
if (mSyncTask == null) {
// 创建新的GTaskASyncTask实例
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
@Override
public void onComplete() {
// 当同步完成时,取消引用并停止服务
mSyncTask = null;
sendBroadcast(""); // 发送广播通知同步已完成
stopSelf(); // 停止当前服务
}
});
// 发送初始广播通知开始同步
sendBroadcast("");
// 执行异步同步任务
mSyncTask.execute();
}
}
// 取消同步任务的方法
private void cancelSync() {
// 如果当前有同步任务正在进行
if (mSyncTask != null) {
// 取消同步任务
mSyncTask.cancelSync();
}
}
// 服务创建时重置同步任务
@Override
public void onCreate() {
mSyncTask = null;
}
// 服务启动时接收并处理Intent
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle bundle = intent.getExtras();
// 如果Intent携带了ACTION_STRING_NAME键的数据
if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) {
// 获取并判断动作类型
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) {
case ACTION_START_SYNC:
startSync(); // 开始同步
break;
case ACTION_CANCEL_SYNC:
cancelSync(); // 取消同步
break;
default:
break;
}
// 返回START_STICKY使得服务在被系统杀死后自动重启
return START_STICKY;
}
// 若Intent不满足条件调用父类方法
return super.onStartCommand(intent, flags, startId);
}
// 当系统内存不足时,尝试取消同步任务
@Override
public void onLowMemory() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
// 绑定服务时返回null表明此服务不支持绑定
@Override
public IBinder onBind(Intent intent) {
return null;
}
// 发送广播的方法,用于传递同步状态和进度信息
public void sendBroadcast(String msg) {
mSyncProgress = msg; // 更新同步进度信息
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); // 是否正在进行同步
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); // 同步进度消息
sendBroadcast(intent); // 发送广播
}
// 静态方法,启动同步服务
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity);
Intent intent = new Intent(activity, GTaskSyncService.class);
intent.putExtra(ACTION_STRING_NAME, ACTION_START_SYNC);
activity.startService(intent); // 由Activity启动服务并开始同步
}
// 静态方法,取消同步服务
public static void cancelSync(Context context) {
Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(ACTION_STRING_NAME, ACTION_CANCEL_SYNC);
context.startService(intent); // 由Context启动服务并取消同步
}
// 静态方法,检查是否正在进行同步
public static boolean isSyncing() {
return mSyncTask != null;
}
// 静态方法,获取当前同步进度的字符串描述
public static String getProgressString() {
return mSyncProgress;
}
}

@ -1,261 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.model;
import android.content.ContentProviderOperation;//批量的更新、插入、删除数据。
import android.content.ContentProviderResult;//操作的结果
import android.content.ContentUris;//用于添加和获取Uri后面的ID
import android.content.ContentValues;//一种用来存储基本数据类型数据的存储机制
import android.content.Context;//需要用该类来弄清楚调用者的实例
import android.content.OperationApplicationException;//操作应用程序容错
import android.net.Uri;//表示待操作的数据
import android.os.RemoteException;//远程容错
import android.util.Log;//输出日志,比如说出错、警告等
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import java.util.ArrayList;
public class Note {
private ContentValues mNoteDiffValues;
private NoteData mNoteData;
private static final String TAG = "Note";
/**
* Create a new note id for adding a new note to databases
*/
public static synchronized long getNewNoteId(Context context, long folderId) {
// Create a new note in the database
ContentValues values = new ContentValues();
long createdTime = System.currentTimeMillis();
values.put(NoteColumns.CREATED_DATE, createdTime);
values.put(NoteColumns.MODIFIED_DATE, createdTime);
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
values.put(NoteColumns.PARENT_ID, folderId);//将数据写入数据库表格
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
//ContentResolver()主要是实现外部应用对ContentProvider中的数据
//进行添加、删除、修改和查询操作
long noteId = 0;
try {
noteId = Long.valueOf(uri.getPathSegments().get(1));
}
catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
noteId = 0;
}//try-catch异常处理
if (noteId == -1) {
throw new IllegalStateException("Wrong note id:" + noteId);
}
return noteId;
}
public Note() {
mNoteDiffValues = new ContentValues();
mNoteData = new NoteData();
}//定义两个变量用来存储便签的数据,一个是存储便签属性、一个是存储便签内容
public void setNoteValue(String key, String value) {
mNoteDiffValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}//设置数据库表格的标签属性数据
public void setTextData(String key, String value) {
mNoteData.setTextData(key, value);
}//设置数据库表格的标签文本内容的数据
public void setTextDataId(long id) {
mNoteData.setTextDataId(id);
}//设置文本数据的ID
public long getTextDataId() {
return mNoteData.mTextDataId;
}//得到文本数据的ID
public void setCallDataId(long id) {
mNoteData.setCallDataId(id);
}//设置电话号码数据的ID
public void setCallData(String key, String value) {
mNoteData.setCallData(key, value);
}//得到电话号码数据的ID
public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}//判断是否是本地修改
public boolean syncNote(Context context, long noteId) {
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
if (!isLocalModified()) {
return true;
}
/**
* In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the
* note data info
*/
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
Log.e(TAG, "Update note error, should not happen");
// Do not return, fall through
}
mNoteDiffValues.clear();
if (mNoteData.isLocalModified()
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
return false;
}
return true;
}//判断数据是否同步
private class NoteData {//定义一个基本的便签内容的数据类,主要包含文本数据和电话号码数据
private long mTextDataId;
private ContentValues mTextDataValues;//文本数据
private long mCallDataId;
private ContentValues mCallDataValues;//电话号码数据
private static final String TAG = "NoteData";
public NoteData() {
mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues();
mTextDataId = 0;
mCallDataId = 0;
}
//下面是上述几个函数的具体实现
boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
void setTextDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
mTextDataId = id;
}
void setCallDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
}
mCallDataId = id;
}
void setCallData(String key, String value) {
mCallDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
void setTextData(String key, String value) {
mTextDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
//下面函数的作用是将新的数据通过Uri的操作存储到数据库
Uri pushIntoContentResolver(Context context, long noteId) {
/**
* Check for safety
*/
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}//判断数据是否合法
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
ContentProviderOperation.Builder builder = null;//数据库的操作列表
if (mTextDataValues.size() > 0) {
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) {
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mTextDataValues);
try {
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
}
catch (NumberFormatException e) {
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
mTextDataValues.clear();
return null;
}
}
else {
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId));
builder.withValues(mTextDataValues);
operationList.add(builder.build());
}
mTextDataValues.clear();
}//把文本数据存入DataColumns
if (mCallDataValues.size() > 0) {
mCallDataValues.put(DataColumns.NOTE_ID, noteId);
if (mCallDataId == 0) {
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mCallDataValues);
try {
setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
}
catch (NumberFormatException e) {
Log.e(TAG, "Insert new call data fail with noteId" + noteId);
mCallDataValues.clear();
return null;
}
}
else {
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mCallDataId));
builder.withValues(mCallDataValues);
operationList.add(builder.build());
}
mCallDataValues.clear();
}//把电话号码数据存入DataColumns
if (operationList.size() > 0) {
try {
ContentProviderResult[] results = context.getContentResolver().applyBatch(
Notes.AUTHORITY, operationList);
return (results == null || results.length == 0 || results[0] == null) ? null
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
}
catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
}
catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
}
}//存储过程中的异常处理
return null;
}
}
}

@ -1,406 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.model;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
public class WorkingNote {
// Note for the working note
private Note mNote;
// Note Id
private long mNoteId;
// Note content
private String mContent;
// Note mode
private int mMode;
private long mAlertDate;
private long mModifiedDate;
private int mBgColorId;
private int mWidgetId;
private int mWidgetType;
private long mFolderId;
private Context mContext;
private static final String TAG = "WorkingNote";
private boolean mIsDeleted;
private NoteSettingChangedListener mNoteSettingStatusListener;
// 声明 DATA_PROJECTION字符串数组
public static final String[] DATA_PROJECTION = new String[] {
DataColumns.ID,
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
// 声明 NOTE_PROJECTION字符串数组
public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE
};
private static final int DATA_ID_COLUMN = 0;
private static final int DATA_CONTENT_COLUMN = 1;
private static final int DATA_MIME_TYPE_COLUMN = 2;
private static final int DATA_MODE_COLUMN = 3;
private static final int NOTE_PARENT_ID_COLUMN = 0;
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
private static final int NOTE_WIDGET_ID_COLUMN = 3;
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
// New note construct
public WorkingNote(Context context, long folderId) {
mContext = context;
mAlertDate = 0;
mModifiedDate = System.currentTimeMillis();
mFolderId = folderId;
mNote = new Note();
mNoteId = 0;
mIsDeleted = false;
mMode = 0;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
}
// WorkingNote的构造函数
// Existing note construct
private WorkingNote(Context context, long noteId, long folderId) {
mContext = context;
mNoteId = noteId;
mFolderId = folderId;
mIsDeleted = false;
mNote = new Note();
loadNote();
}
// 加载Note
// 通过数据库调用query函数找到第一个条目
private void loadNote() {
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
null, null);
// 若存在,储存相应信息
if (cursor != null) {
if (cursor.moveToFirst()) {
mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN);
mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN);
mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
}
cursor.close();
// 若不存在,报错
} else {
Log.e(TAG, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
}
loadNoteData();
}
// 加载NoteData
private void loadNoteData() {
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
String.valueOf(mNoteId)
}, null);
if (cursor != null) {
// 查到信息不为空
if (cursor.moveToFirst()) { // 查看第一项是否存在
do {
String type = cursor.getString(DATA_MIME_TYPE_COLUMN);
if (DataConstants.NOTE.equals(type)) {
mContent = cursor.getString(DATA_CONTENT_COLUMN);
mMode = cursor.getInt(DATA_MODE_COLUMN);
mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN));
} else if (DataConstants.CALL_NOTE.equals(type)) {
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
} else {
Log.d(TAG, "Wrong note type with type:" + type);
}
} while (cursor.moveToNext());//查阅所有项,直到为空
}
cursor.close();
} else {
Log.e(TAG, "No data with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
}
}
// 创建空的Note
// 传参context文件夹idwidget背景颜色
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId);
// 设定相关属性
note.setBgColorId(defaultBgColorId);
note.setWidgetId(widgetId);
note.setWidgetType(widgetType);
return note;
}
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
}
// 保存Note
public synchronized boolean saveNote() {
if (isWorthSaving()) { //是否值得保存
if (!existInDatabase()) { // 是否存在数据库中
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
}
}
mNote.syncNote(mContext, mNoteId);
/**
* Update widget content if there exist any widget of this note
*/
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
}
return true;
} else {
return false;
}
}
// 是否在数据库中存在
public boolean existInDatabase() {
return mNoteId > 0;
}
// 是否值得保存
private boolean isWorthSaving() {
// 被删除,或(不在数据库中 内容为空),或 本地已保存过
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) {
return false;
} else {
return true;
}
}
// 设置mNoteSettingStatusListener
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l;
}
// 设置AlertDate
// 若 mAlertDate与data不同则更改mAlertDate并设定NoteValue
public void setAlertDate(long date, boolean set) {
if (date != mAlertDate) {
mAlertDate = date;
mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate));
}
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onClockAlertChanged(date, set);
}
}
// 设定删除标记
public void markDeleted(boolean mark) {
// 设定标记
mIsDeleted = mark;
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onWidgetChanged();
// 调用mNoteSettingStatusListener的 onWidgetChanged方法
}
}
// 设定背景颜色
public void setBgColorId(int id) {
if (id != mBgColorId) { //设定条件 id != mBgColorId
mBgColorId = id;
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onBackgroundColorChanged();
}
mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id));
}
}
// 设定检查列表模式
// 参数mode
public void setCheckListMode(int mode) {
if (mMode != mode) { //设定条件 mMode != mode
if (mNoteSettingStatusListener != null) {
mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode);
}
mMode = mode;
mNote.setTextData(TextNote.MODE, String.valueOf(mMode));
}
}
// 设定WidgetType
// 参数type
public void setWidgetType(int type) {
if (type != mWidgetType) {//设定条件 type != mWidgetType
mWidgetType = type;
mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType));
// 调用Note的setNoteValue方法更改WidgetType
}
}
// 设定WidgetId
// 参数id
public void setWidgetId(int id) {
if (id != mWidgetId) {//设定条件 id != mWidgetId
mWidgetId = id;
mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId));
// 调用Note的setNoteValue方法更改WidgetId
}
}
// 设定WorkingTex
// 参数更改的text
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {//设定条件 mContent, text内容不同
mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent);
// 调用Note的setTextData方法更改WorkingText
}
}
// 转变mNote的CallData及CallNote信息
// 参数String phoneNumber, long callDate
public void convertToCallNote(String phoneNumber, long callDate) {
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
}
// 判断是否有时钟题型
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
// 获取Content
public String getContent() {
return mContent;
}
// 获取AlertDate
public long getAlertDate() {
return mAlertDate;
}
// 获取ModifiedDate
public long getModifiedDate() {
return mModifiedDate;
}
// 获取背景颜色来源id
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId);
}
// 获取背景颜色id
public int getBgColorId() {
return mBgColorId;
}
// 获取标题背景颜色id
public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
// 获取CheckListMode
public int getCheckListMode() {
return mMode;
}
// 获取便签id
public long getNoteId() {
return mNoteId;
}
// 获取文件夹id
public long getFolderId() {
return mFolderId;
}
// 获取WidgetId
public int getWidgetId() {
return mWidgetId;
}
// 获取WidgetType
public int getWidgetType() {
return mWidgetType;
}
// 创建接口 NoteSettingChangedListener,便签更新监视
// 为NoteEditActivity提供接口
// 提供函数有
public interface NoteSettingChangedListener {
/**
* Called when the background color of current note has just changed
*/
void onBackgroundColorChanged();
/**
* Called when user set clock
*/
void onClockAlertChanged(long date, boolean set);
/**
* Call when user create note from widget
*/
void onWidgetChanged();
/**
* Call when switch between check list mode and normal mode
* @param oldMode is previous mode before change
* @param newMode is new mode
*/
void onCheckListModeChanged(int oldMode, int newMode);
}
}

@ -1,346 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;//Android提供的一个上下文对象可以用于访问应用程序的资源和执行一些操作。
import android.database.Cursor;//一个用于查询数据库结果集的接口,可以用于获取查询结果中的各个数据项。
import android.os.Environment;//提供了访问系统环境的方法,例如获取外部存储路径。
import android.text.TextUtils;//个包含各种字符串处理方法的工具类,例如判断字符串是否为空。
import android.text.format.DateFormat;//提供了各种日期和时间格式化的方法。
import android.util.Log;//Android提供的日志工具类用于打印调试信息。
import net.micode.notes.R;//应用程序的资源类,用于引用应用程序中的各种资源,如布局文件、图标、字符串等。
import net.micode.notes.data.Notes;//一个自定义的Notes数据类用于访问和操作备忘录的数据。
import net.micode.notes.data.Notes.DataColumns;//Notes数据类中定义的备忘录数据列常量。
import net.micode.notes.data.Notes.DataConstants;//Notes数据类中定义的备忘录数据常量。
import net.micode.notes.data.Notes.NoteColumns;//Notes数据类中定义的备忘录笔记列常量。
//用于文件操作,如创建文件、写入内容等。
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
public class BackupUtils {
private static final String TAG = "BackupUtils";
// Singleton stuff
private static BackupUtils sInstance;
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
//如果当前的备份不存在,则重新声明一个
sInstance = new BackupUtils(context);
}
return sInstance;
}
/**
* Following states are signs to represents backup or restore
* status
*///以下状态是表示备份或恢复状态的标志。
// Currently, the sdcard is not mounted SD卡没有被装到手机里
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// The backup file not exist 备份文件夹不存在
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// The data is not well formated, may be changed by other programs 数据已被破坏,可能被修改
public static final int STATE_DATA_DESTROIED = 2;
// Some run-time exception which causes restore or backup fails 超时异常
public static final int STATE_SYSTEM_ERROR = 3;
// Backup or restore success 成功存储
public static final int STATE_SUCCESS = 4;
private TextExport mTextExport;
private BackupUtils(Context context) {//函数初始化
mTextExport = new TextExport(context);
}
private static boolean externalStorageAvailable() {//外部存储功能是否可用
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
public int exportToText() {
return mTextExport.exportToText();
}
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
private static class TextExport {
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET,
NoteColumns.TYPE
};
private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2;
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2;
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
private final String [] TEXT_FORMAT;
private static final int FORMAT_FOLDER_NAME = 0;
private static final int FORMAT_NOTE_DATE = 1;
private static final int FORMAT_NOTE_CONTENT = 2;
private Context mContext;
private String mFileName;
private String mFileDirectory;
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
private String getFormat(int id) {//获取文本的组成部分
return TEXT_FORMAT[id];
}
/**
* Export the folder identified by folder id to text
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// Query notes belong to this folder 通过查询parentid是文件夹id的note来选出制定ID文件夹下的Note
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId
}, null);
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// Print note's last modified date ps里面保存有这份note的日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note 将文件导出到text
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
/**
* Export note identified by id to a print stream
*/
private void exportNoteToText(String noteId, PrintStream ps) {
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
if (dataCursor != null) {//利用光标来扫面内容区别为callnote和note两种靠ps.printline输出
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number 判断是否为空字符
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// Print call date
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// Print call attachment location
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
content));
}
}
} while (dataCursor.moveToNext());
}
dataCursor.close();
}
// print a line separator between note
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
/**
* Note will be exported as text which is user readable
*/
public int exportToText() {//总函数调用上面的exportFolder和exportNote
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes 导出文件夹,就是导出里面包含的便签
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// Print folder's name
String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close();
}
// Export notes in root's folder 将根目录里的便签导出
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
noteCursor.close();
}
ps.close();
return STATE_SUCCESS;
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
*/
private PrintStream getExportToTextPrintStream() {
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
Log.e(TAG, "create file to exported failed");
return null;
}
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);//将ps输出流输出到特定文件目的是导出到文件而不是直接输出
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();
return null;
}
return ps;
}
}
/**
* Generate the text file to store imported data
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory());//外部的储存路径
sb.append(context.getString(filePathResId));//文件的储存路径
File filedir = new File(sb.toString());//filedir应该就是用来存储路径信息
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
File file = new File(sb.toString());
try {
if (!filedir.exists()) {
filedir.mkdir();//如果文件不存在就新建文件
}
if (!file.exists()) {
file.createNewFile();
}
return file;
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//try catch 异常处理
return null;
}
}

@ -1,295 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.CallNote;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
//处理数据的转换和格式化
public class DataUtils {
public static final String TAG = "DataUtils";
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) { //批量删除笔记
if (ids == null) { //为空返回处理处理完成
Log.d(TAG, "the ids is null");
return true;
}
if (ids.size() == 0) { //大小为零返回处理完成
Log.d(TAG, "no id is in the hashset");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
if(id == Notes.ID_ROOT_FOLDER) { //判断批量删除的文件是否是系统文件,如果是就继续
Log.e(TAG, "Don't delete system folder root");
continue;
}
ContentProviderOperation.Builder builder = ContentProviderOperation //创建欲删除对象
.newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
operationList.add(builder.build()); //将欲删除对象批量加入操作列表
}
try { //抛出异常
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) { //发生错误
Log.d(TAG, "delete notes failed, ids:" + ids.toString()); //返回错误
return false;
}
return true;
} catch (RemoteException e) { //捕捉异常
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); //日志文件加入错误信息
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); //日志文件加入错误信息
}
return false;
}
//将笔记放入文件夹中
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, desFolderId);
values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId);
values.put(NoteColumns.LOCAL_MODIFIED, 1);
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
//批量移动文件到文件夹中
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); //欲更新的操作者
builder.withValue(NoteColumns.PARENT_ID, folderId);
builder.withValue(NoteColumns.LOCAL_MODIFIED, 1);
operationList.add(builder.build()); //将欲更新的操作者加入操作列表
}
try {
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString()); //删除失败
return false;
}
return true;
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); //日志文件加入错误信息
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); //日志文件加入错误信息
}
return false;
}
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/ //计数用户除开系统文件之外的其余文件。
public static int getUserFolderCount(ContentResolver resolver) {
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, //光标指向uri资源地址
new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
null);
int count = 0;
if(cursor != null) {
if(cursor.moveToFirst()) { //移动到第一位开始计数
try {
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "get folder count failed:" + e.toString());
} finally {
cursor.close(); //最终关闭可能存在问题cursor可能无法正常释放从而导致程序崩溃
}
}
}
return count;
}
//可视化操作数据库搭建ui界面方便用户操作本地数据库
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
//判断是否存在笔记数据库
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) { //计数如果大于零,就说明存在
exist = true;
}
cursor.close();
}
return exist;
}
//判断是否存在数据库
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
}
return exist;
}
//判断可视化文件夹名称是否合法
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, //相应的文件名称规范
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
" AND " + NoteColumns.SNIPPET + "=?",
new String[] { name }, null);
boolean exist = false;
if(cursor != null) {
if(cursor.getCount() > 0) { //判断是否存在
exist = true;
}
cursor.close();
}
return exist;
}
//哈希设置获取文件夹部件
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, //设置光标
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) },
null);
HashSet<AppWidgetAttribute> set = null;
if (c != null) { //光标不指向空,意味着存在
if (c.moveToFirst()) { //移动到第一位
set = new HashSet<AppWidgetAttribute>();
do {
try {
AppWidgetAttribute widget = new AppWidgetAttribute();
widget.widgetId = c.getInt(0); //得到部件id0是请求代码
widget.widgetType = c.getInt(1); //得到部件种类1是请求代码
set.add(widget); //将部件加入到哈希设置中
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, e.toString()); //检测异常,加入到错误日志文件中去
}
} while (c.moveToNext());
}
c.close();
}
return set;
}
//得到电话号码笔记id
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, //设置光标
new String [] { CallNote.PHONE_NUMBER },
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE },
null);
if (cursor != null && cursor.moveToFirst()) { //光标不为空
try {
return cursor.getString(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call number fails " + e.toString());//检测异常,加入到错误日志文件中去
} finally {
cursor.close();
}
}
return "";
}
//得到电话号码和电话日期
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID },//设置光标
CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL("
+ CallNote.PHONE_NUMBER + ",?)",
new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber },
null);
if (cursor != null) { //光标不为空
if (cursor.moveToFirst()) {
try {
return cursor.getLong(0);
} catch (IndexOutOfBoundsException e) { //检测异常,加入到错误日志文件中去
Log.e(TAG, "Get call note id fails " + e.toString());
}
}
cursor.close();
}
return 0;
}
//得到片段id
public static String getSnippetById(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, //设置光标
new String [] { NoteColumns.SNIPPET },
NoteColumns.ID + "=?",
new String [] { String.valueOf(noteId)},
null);
if (cursor != null) { //光标不为空
String snippet = "";
if (cursor.moveToFirst()) { //移动到开始
snippet = cursor.getString(0); //通过请求代码0请求获取字符串并传递给snippet
}
cursor.close(); //关闭光标
return snippet;
} //抛出不合法提示
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
//得到规范的片段
public static String getFormattedSnippet(String snippet) {
if (snippet != null) { //片段若不为空
snippet = snippet.trim();
int index = snippet.indexOf('\n'); //开始索引换行符
if (index != -1) { //如果是换行符
snippet = snippet.substring(0, index); //截取字符串
}
}
return snippet;
}
}

@ -1,113 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
public class GTaskStringUtils {
public final static String GTASK_JSON_ACTION_ID = "action_id";
public final static String GTASK_JSON_ACTION_LIST = "action_list";
public final static String GTASK_JSON_ACTION_TYPE = "action_type";
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";
public final static String GTASK_JSON_CREATOR_ID = "creator_id";
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";
public final static String GTASK_JSON_CLIENT_VERSION = "client_version";
public final static String GTASK_JSON_COMPLETED = "completed";
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
public final static String GTASK_JSON_DELETED = "deleted";
public final static String GTASK_JSON_DEST_LIST = "dest_list";
public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
public final static String GTASK_JSON_GET_DELETED = "get_deleted";
public final static String GTASK_JSON_ID = "id";
public final static String GTASK_JSON_INDEX = "index";
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
public final static String GTASK_JSON_LIST_ID = "list_id";
public final static String GTASK_JSON_LISTS = "lists";
public final static String GTASK_JSON_NAME = "name";
public final static String GTASK_JSON_NEW_ID = "new_id";
public final static String GTASK_JSON_NOTES = "notes";
public final static String GTASK_JSON_PARENT_ID = "parent_id";
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
public final static String GTASK_JSON_RESULTS = "results";
public final static String GTASK_JSON_SOURCE_LIST = "source_list";
public final static String GTASK_JSON_TASKS = "tasks";
public final static String GTASK_JSON_TYPE = "type";
public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
public final static String GTASK_JSON_TYPE_TASK = "TASK";
public final static String GTASK_JSON_USER = "user";
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";
public final static String FOLDER_DEFAULT = "Default";
public final static String FOLDER_CALL_NOTE = "Call_Note";
public final static String FOLDER_META = "METADATA";
public final static String META_HEAD_GTASK_ID = "meta_gid";
public final static String META_HEAD_NOTE = "meta_note";
public final static String META_HEAD_DATA = "meta_data";
public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE";
}

@ -1,261 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.preference.PreferenceManager;
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
public class ResourceParser {
// 定义不同颜色的常量
public static final int YELLOW = 0;
public static final int BLUE = 1;
public static final int WHITE = 2;
public static final int GREEN = 3;
public static final int RED = 4;
// 默认背景颜色为黄色
public static final int BG_DEFAULT_COLOR = YELLOW;
// 定义不同字体大小的常量
public static final int TEXT_SMALL = 0;
public static final int TEXT_MEDIUM = 1;
public static final int TEXT_LARGE = 2;
public static final int TEXT_SUPER = 3;
// 默认字体大小为中等
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
/**
*
*/
public static class NoteBgResources {
// 编辑状态下的背景资源数组
private final static int [] BG_EDIT_RESOURCES = new int [] {
R.drawable.edit_yellow,
R.drawable.edit_blue,
R.drawable.edit_white,
R.drawable.edit_green,
R.drawable.edit_red
};
// 编辑状态下的标题背景资源数组
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
R.drawable.edit_title_white,
R.drawable.edit_title_green,
R.drawable.edit_title_red
};
/**
* id
* @param id id
* @return id
*/
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}
/**
* id
* @param id id
* @return id
*/
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}
}
/**
* id
* @param context 访SharedPreferences
* @return id
*/
public static int getDefaultBgId(Context context) {
// 如果用户设置了背景颜色,则随机选择一个背景,否则使用默认背景
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length);
} else {
return BG_DEFAULT_COLOR;
}
}
/**
*
*/
public static class NoteItemBgResources {
// 第一个笔记项的背景资源数组
private final static int [] BG_FIRST_RESOURCES = new int [] {
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
R.drawable.list_white_up,
R.drawable.list_green_up,
R.drawable.list_red_up
};
// 普通笔记项的背景资源数组
private final static int [] BG_NORMAL_RESOURCES = new int [] {
R.drawable.list_yellow_middle,
R.drawable.list_blue_middle,
R.drawable.list_white_middle,
R.drawable.list_green_middle,
R.drawable.list_red_middle
};
// 最后一个笔记项的背景资源数组
private final static int [] BG_LAST_RESOURCES = new int [] {
R.drawable.list_yellow_down,
R.drawable.list_blue_down,
R.drawable.list_white_down,
R.drawable.list_green_down,
R.drawable.list_red_down,
};
// 单个笔记项的背景资源数组
private final static int [] BG_SINGLE_RESOURCES = new int [] {
R.drawable.list_yellow_single,
R.drawable.list_blue_single,
R.drawable.list_white_single,
R.drawable.list_green_single,
R.drawable.list_red_single
};
/**
*
* @param id id
* @return id
*/
public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id];
}
/**
*
* @param id id
* @return id
*/
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}
/**
*
* @param id id
* @return id
*/
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}
/**
*
* @param id id
* @return id
*/
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}
/**
*
* @return id
*/
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
}
/**
*
*/
public static class WidgetBgResources {
// 2x小部件背景资源数组
private final static int [] BG_2X_RESOURCES = new int [] {
R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
R.drawable.widget_2x_white,
R.drawable.widget_2x_green,
R.drawable.widget_2x_red,
};
/**
* id2x
* @param id id
* @return id
*/
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id];
}
// 4x小部件背景资源数组
private final static int [] BG_4X_RESOURCES = new int [] {
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,
R.drawable.widget_4x_white,
R.drawable.widget_4x_green,
R.drawable.widget_4x_red
};
/**
* id4x
* @param id id
* @return id
*/
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id];
}
}
/**
*
*/
public static class TextAppearanceResources {
// 文本外观资源数组
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
};
/**
* id
* @param id id
* @return id
*/
public static int getTexAppearanceResource(int id) {
// 若id超出资源数组范围则返回默认字体大小
if (id >= TEXTAPPEARANCE_RESOURCES.length) {
return BG_DEFAULT_FONT_SIZE;
}
return TEXTAPPEARANCE_RESOURCES[id];
}
/**
*
* @return
*/
public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length;
}
}
}

@ -1,208 +0,0 @@
/*
* 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; //表示引入 Android 应用程序中的 Activity 类,用于创建用户界面
import android.app.AlertDialog; //表示引入 Android 应用程序中的 AlertDialog 类,用于创建对话框。
import android.content.Context; //表示引入 Android 应用程序中的 Context 类,用于访问应用程序的资源和执行操作。
import android.content.DialogInterface; //表示引入 Android 应用程序中的 DialogInterface 类,用于处理对话框事件
import android.content.DialogInterface.OnClickListener; //表示引入 Android 应用程序中的 OnClickListener 接口,用于处理对话框按钮点击事件。
import android.content.DialogInterface.OnDismissListener; //表示引入 Android 应用程序中的 OnDismissListener 接口,用于处理对话框消失事件
import android.content.Intent; //表示引入 Android 应用程序中的 Intent 类,用于在应用程序组件之间进行通信。
import android.media.AudioManager; //表示引入 Android 应用程序中的 AudioManager 类,用于控制音频管理器。
import android.media.MediaPlayer; //表示引入 Android 应用程序中的 MediaPlayer 类,用于实现音频播放功能。
import android.media.RingtoneManager; //表示引入 Android 应用程序中的 RingtoneManager 类,用于管理铃声。
import android.net.Uri; //表示引入 Android 应用程序中的 Uri 类,用于表示统一资源标识符。
import android.os.Bundle; //表示引入 Android 应用程序中的 Bundle 类,用于传递数据。
import android.os.PowerManager; //表示引入 Android 应用程序中的 PowerManager 类,用于管理设备电源。
import android.provider.Settings; //表示引入 Android 应用程序中的 Settings 类,用于访问设备设置。
import android.view.Window; //表示引入 Android 应用程序中的 Window 类,用于管理窗口。
import android.view.WindowManager; //表示引入 Android 应用程序中的 WindowManager 类,用于管理窗口布局和显示。
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import java.io.IOException;
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
private long mNoteId; //文本在数据库存储中的ID号
private String mSnippet; //闹钟提示时出现的文本片段
private static final int SNIPPET_PREW_MAX_LEN = 60; //表示文本片段的最大长度。
MediaPlayer mPlayer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//Bundle类型的数据与Map类型的数据相似都是以key-value的形式存储数据的
//onsaveInstanceState方法是用来保存Activity的状态的
//能从onCreate的参数savedInsanceState中获得状态数据
requestWindowFeature(Window.FEATURE_NO_TITLE);
//界面显示——无标题
final Window win = getWindow();
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
if (!isScreenOn()) {
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
//保持窗体点亮
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
//将窗体点亮
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
//允许窗体点亮时锁屏
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
}//在手机锁屏后如果到了闹钟提示时间,点亮屏幕
Intent intent = getIntent();
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
//根据ID从数据库中获取标签的内容
//getContentResolver是实现数据共享实例存储。
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
: mSnippet;
//判断标签片段是否达到符合长度
} catch (IllegalArgumentException e) {
e.printStackTrace();
return;
}
/*
try
{
// 代码区
}
catch(Exception e)
{
// 异常处理
}
*/
mPlayer = new MediaPlayer();
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog();
//弹出对话框
playAlarmSound();
//闹钟提示音激发
} else {
finish();
//完成闹钟动作
}
}
private boolean isScreenOn() {
//判断屏幕是否锁屏,调用系统函数判断,最后返回值是布尔类型
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
private void playAlarmSound() {
//闹钟提示音激发
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
//调用系统的铃声管理URI得到闹钟提示音
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
mPlayer.setAudioStreamType(silentModeStreams);
} else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
}
try {
mPlayer.setDataSource(this, url);
//方法setDataSource(Context context, Uri uri)
//解释:无返回值,设置多媒体数据来源【根据 Uri】
mPlayer.prepare();
//准备同步
mPlayer.setLooping(true);
//设置是否循环播放
mPlayer.start();
//开始播放
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
//e.printStackTrace()函数功能是抛出异常, 还将显示出更深的调用信息
//System.out.println(e),这个方法打印出异常,并且输出在哪里出现的异常
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
//AlertDialog的构造方法全部是Protected的
//所以不能直接通过new一个AlertDialog来创建出一个AlertDialog。
//要创建一个AlertDialog就要用到AlertDialog.Builder中的create()方法
//如这里的dialog就是新建了一个AlertDialog
dialog.setTitle(R.string.app_name);
//为对话框设置标题
dialog.setMessage(mSnippet);
//为对话框设置内容
dialog.setPositiveButton(R.string.notealert_ok, this);
//给对话框添加"Yes"按钮
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);
}//对话框添加"No"按钮
dialog.show().setOnDismissListener(this);
}
public void onClick(DialogInterface dialog, int which) {
switch (which) {
//用which来选择click后下一步的操作
case DialogInterface.BUTTON_NEGATIVE:
//这是取消操作
Intent intent = new Intent(this, NoteEditActivity.class);
//实现两个类间的数据传输
intent.setAction(Intent.ACTION_VIEW);
//设置动作属性
intent.putExtra(Intent.EXTRA_UID, mNoteId);
//实现key-value对
//EXTRA_UID为keymNoteId为键
startActivity(intent);
//开始动作
break;
default:
//这是确定操作
break;
}
}
public void onDismiss(DialogInterface dialog) {
//忽略
stopAlarmSound();
//停止闹钟声音
finish();
//完成该动作
}
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
//停止播放
mPlayer.release();
//释放MediaPlayer对象
mPlayer = null;
}
}
}

@ -1,72 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
public class AlarmInitReceiver extends BroadcastReceiver {
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE
};
//对数据库的操作调用标签ID和闹钟时间
private static final int COLUMN_ID = 0;
private static final int COLUMN_ALERTED_DATE = 1;
@Override
public void onReceive(Context context, Intent intent) {
long currentDate = System.currentTimeMillis();
//System.currentTimeMillis()产生一个当前的毫秒
//这个毫秒其实就是自1970年1月1日0时起的毫秒数
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
new String[] { String.valueOf(currentDate) },
//将long变量currentDate转化为字符串
null);
//Cursor在这里的作用是通过查找数据库中的标签内容找到和当前系统时间相等的标签
if (c != null) {
if (c.moveToFirst()) {
do {
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
Intent sender = new Intent(context, AlarmReceiver.class);
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
c.close();
}
//然而通过网上查找资料发现,对于闹钟机制的启动,通常需要上面的几个步骤
//如新建Intent、PendingIntent以及AlarmManager等
//这里就是根据数据库里的闹钟时间创建一个闹钟机制
}
}

@ -1,35 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
intent.setClass(context, AlarmAlertActivity.class);
//启动AlarmAlertActivity
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//activity要存在于activity的栈中而非activity的途径启动activity时必然不存在一个activity的栈
//所以要新起一个栈装入启动的activity
context.startActivity(intent);
}
}
//这是实现alarm这个功能最接近用户层的包基于上面的两个包
//作用还需要深究但是对于setClass和addFlags的

@ -1,508 +0,0 @@
/*
* 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;//将当前的 Java 文件放置在 net.micode.notes.ui 这个包中。
import java.text.DateFormatSymbols;
import java.util.Calendar;
import net.micode.notes.R;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
public class DateTimePicker extends FrameLayout {
//FrameLayout是布局模板之一
//所有的子元素全部在屏幕的右上方
private static final boolean DEFAULT_ENABLE_STATE = true;
private static final int HOURS_IN_HALF_DAY = 12;
private static final int HOURS_IN_ALL_DAY = 24;
private static final int DAYS_IN_ALL_WEEK = 7;
private static final int DATE_SPINNER_MIN_VAL = 0;
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
private static final int MINUT_SPINNER_MIN_VAL = 0;
private static final int MINUT_SPINNER_MAX_VAL = 59;
private static final int AMPM_SPINNER_MIN_VAL = 0;
private static final int AMPM_SPINNER_MAX_VAL = 1;
//初始化控件
private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
//NumberPicker是数字选择器
//这里定义的四个变量全部是在设置闹钟时需要选择的变量(如日期、时、分、上午或者下午)
private Calendar mDate;
//定义了Calendar类型的变量mDate用于操作时间
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
private boolean mIsAm;
private boolean mIs24HourView;
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
private boolean mInitialising;
private OnDateTimeChangedListener mOnDateTimeChangedListener;
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
updateDateControl();
onDateTimeChanged();
}
};//OnValueChangeListener这是时间改变监听器这里主要是对日期的监听
//将现在日期的值传递给mDateupdateDateControl是同步操作
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
//这里是对 小时Hour 的监听
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false;
Calendar cal = Calendar.getInstance();
//声明一个Calendar的变量cal便于后续的操作
if (!mIs24HourView) {
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
//这里是对于12小时制时晚上11点和12点交替时对日期的更改
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
//这里是对于12小时制时凌晨11点和12点交替时对日期的更改
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||
oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm;
updateAmPmControl();
}//这里是对于12小时制时中午11点和12点交替时对AM和PM的更改
} else {
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
//这里是对于24小时制时晚上11点和12点交替时对日期的更改
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
} //这里是对于12小时制时凌晨11点和12点交替时对日期的更改
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
//通过数字选择器对newHour的赋值
mDate.set(Calendar.HOUR_OF_DAY, newHour);
//通过set函数将新的Hour值传给mDate
onDateTimeChanged();
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
}
};
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
//这里是对 分钟Minute改变的监听
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();
int offset = 0;
//设置offset作为小时改变的一个记录数据
if (oldVal == maxValue && newVal == minValue) {
offset += 1;
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1;
}
//如果原值为59新值为0则offset加1
//如果原值为0新值为59则offset减1
if (offset != 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour());
updateDateControl();
int newHour = getCurrentHourOfDay();
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
updateAmPmControl();
} else {
mIsAm = true;
updateAmPmControl();
}
}
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
}
};
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
//对AM和PM的监听
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
}
updateAmPmControl();
onDateTimeChanged();
}
};
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
}
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}//通过对数据库的访问,获取当前的系统时间
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
}//上面函数的得到的是一个天文数字1970至今的秒数需要DateFormat将其变得有意义
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
//获取系统时间
mDate = Calendar.getInstance();
mInitialising = true;
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
inflate(context, R.layout.datetime_picker, this);
//如果当前Activity里用到别的layout比如对话框layout
//还要设置这个layout上的其他组件的内容就必须用inflate()方法先将对话框的layout找出来
//然后再用findViewById()找到它上面的其它组件
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100);
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// update controls to initial state
updateDateControl();
updateHourControl();
updateAmPmControl();
set24HourView(is24HourView);
// set to current time
setCurrentDate(date);
setEnabled(isEnabled());
// set the content descriptions
mInitialising = false;
}
@Override
public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) {
return;
}
super.setEnabled(enabled);
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled);
mIsEnabled = enabled;
}
//存在疑问setEnabled的作用
//下面的代码通过原程序的注释已经比较清晰,另外可以通过函数名来判断
//下面的各函数主要是对上面代码引用到的各函数功能的实现
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
* Get the current date in millis
*
* @return the current date in millis
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}//实现函数——得到当前的秒数
/**
* Set the current date
*
* @param date The current date in millis
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}//实现函数功能——设置当前的时间参数是date
/**
* Set the current date
*
* @param year The current year
* @param month The current month
* @param dayOfMonth The current dayOfMonth
* @param hourOfDay The current hourOfDay
* @param minute The current minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
}//实现函数功能——设置当前的时间,参数是各详细的变量
/**
* Get current year
*
* @return The current year
*/
//下面是得到year、month、day等值
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
/**
* Set current year
*
* @param year The current year
*/
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) {
return;
}
mDate.set(Calendar.YEAR, year);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current month in the year
*
* @return The current month in the year
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* Set current month in the year
*
* @param month The month in the year
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current day of the month
*
* @return The day of the month
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Set current day of the month
*
* @param dayOfMonth The day of the month
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
private int getCurrentHour() {
if (mIs24HourView){
return getCurrentHourOfDay();
} else {
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
*
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
if (!mIs24HourView) {
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
mIsAm = true;
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;
}
}
updateAmPmControl();
}
mHourSpinner.setValue(hourOfDay);
onDateTimeChanged();
}
/**
* Get currentMinute
*
* @return The Current Minute
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* Set current minute
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute);
mDate.set(Calendar.MINUTE, minute);
onDateTimeChanged();
}
/**
* @return true if this is in 24 hour view else false.
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* Set whether in 24 hour or AM/PM mode.
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView;
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
int hour = getCurrentHourOfDay();
updateHourControl();
setCurrentHour(hour);
updateAmPmControl();
}
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null);
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1);
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
mDateSpinner.setDisplayedValues(mDateDisplayValues);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
mDateSpinner.invalidate();
}// 对于星期几的算法
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
}// 对于上下午操作的算法
}
private void updateHourControl() {
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}// 对与小时的算法
}
/**
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}

@ -1,103 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import java.util.Calendar;
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance();
//创建一个Calendar类型的变量 mDate方便时间的操作
private boolean mIs24HourView;
private OnDateTimeSetListener mOnDateTimeSetListener;
//声明一个时间日期滚动选择控件 mOnDateTimeSetListener
private DateTimePicker mDateTimePicker;
//DateTimePicker控件控件一般用于让用户可以从日期列表中选择单个值。
//运行时,单击控件边上的下拉箭头,会显示为两个部分:一个下拉列表,一个用于选择日期的
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
}
public DateTimePickerDialog(Context context, long date) {
//对该界面对话框的实例化
super(context);
//对数据库的操作
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker);
//添加一个子视图
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
//将视图中的各选项设置为系统当前时间
updateTitle(mDate.getTimeInMillis());
}
});
mDate.setTimeInMillis(date);
//得到系统时间
mDate.set(Calendar.SECOND, 0);
//将秒数设置为0
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
//设置按钮
set24HourView(DateFormat.is24HourFormat(this.getContext()));
//时间标准化打印
updateTitle(mDate.getTimeInMillis());
}
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}//将时间日期滚动选择控件实例化
private void updateTitle(long date) {
int flag =
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}//android开发中常见日期管理工具类API——DateUtils按照上下午显示时间
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}//第一个参数arg0是接收到点击事件的对话框
//第二个参数arg1是该对话框上的按钮
}

@ -1,65 +0,0 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.content.Context;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import net.micode.notes.R;
public class DropdownMenu {
private Button mButton;
private PopupMenu mPopupMenu;
//声明一个下拉菜单
private Menu mMenu;
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button;
mButton.setBackgroundResource(R.drawable.dropdown_icon);
//设置这个view的背景
mPopupMenu = new PopupMenu(context, mButton);
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
//MenuInflater是用来实例化Menu目录下的Menu布局文件
//根据ID来确认menu的内容选项
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
}
});
}
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}//设置菜单的监听
}
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}//对于菜单选项的初始化,根据索引搜索菜单需要的选项
public void setTitle(CharSequence title) {
mButton.setText(title);
}//布局文件,设置标题
}

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

File diff suppressed because it is too large Load Diff

@ -1,278 +0,0 @@
package net.micode.notes.ui;
import android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
//继承edittext设置便签设置文本框
public class NoteEditText extends androidx.appcompat.widget.AppCompatEditText {
private static final String TAG = "NoteEditText";
private int mIndex;
private int mSelectionStartBeforeDelete;
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
///建立一个字符和整数的hash表用于链接电话网站还有邮箱
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
* 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
*/
//处理删除按键时的操作
void onEditTextDelete(int index, String text);
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*/
//处理进入按键时的操作
void onEditTextEnter(int index, String text);
/**
* Hide or show item option when text change
*/
void onTextChange(int index, boolean hasText);
}
private OnTextViewChangeListener mOnTextViewChangeListener;
//根据context设置文本
public NoteEditText(Context context) {
super(context, null);//用super引用父类变量
mIndex = 0;
}
//设置当前光标
public void setIndex(int index) {
mIndex = index;
}
//初始化文本修改标记
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
//AttributeSet 百度了一下是自定义空控件属性,用于维护便签动态变化的属性
//初始化便签
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
// 根据defstyle自动初始化
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated construct or stub
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (isInEditMode()) {
break;
}
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
Layout layout = getLayout();
if (layout != null) {
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
Selection.setSelection(getText(), off);
}
break;
}
return super.onTouchEvent(event);
}
@Override
/*
*
*
*/
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
//根据按键的 Unicode 编码值来处理
case KeyEvent.KEYCODE_ENTER:
//“进入”按键
// 检查是否是输入法的确认操作,如果是则不拦截
if (mOnTextViewChangeListener != null) {
if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) == 0 && !isInEditMode()) {
// 允许事件正常传递给父类
return super.onKeyDown(keyCode, event);
}
}
break;
case KeyEvent.KEYCODE_DEL:
//“删除”按键
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
//继续执行父类的其他点击事件
return super.onKeyDown(keyCode, event);
}
@Override
/*
*
*
*/
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
//根据按键的 Unicode 编码值来处理有删除和进入2种操作
case KeyEvent.KEYCODE_DEL:
if (mOnTextViewChangeListener != null) {
//若是被修改过
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
//若之前有被修改并且文档不为空
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
//利用上文OnTextViewChangeListener对KEYCODE_DEL按键情况的删除函数进行删除
return true;
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
//其他情况报错,文档的改动监听器并没有建立
}
break;
case KeyEvent.KEYCODE_ENTER:
//同上也是分为监听器是否建立2种情况
if (mOnTextViewChangeListener != null) {
// 检查是否是输入法的确认操作,如果是则不拦截
// 同时检查是否有文本输入,避免拦截中文输入法选择候选词的操作
if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) == 0 && !isInEditMode() && event.getAction() == KeyEvent.ACTION_UP) {
// 只有当没有输入文本时才执行换行操作
if (TextUtils.isEmpty(getText())) {
int selectionStart = getSelectionStart();
//获取当前位置
String text = getText().subSequence(selectionStart, length()).toString();
//获取当前文本
setText(getText().subSequence(0, selectionStart));
//根据获取的文本设置当前文本
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
//当{@link KeyEvent#KEYCODE_ENTER}添加新文本
return true;
}
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
//其他情况报错,文档的改动监听器并没有建立
}
break;
default:
break;
}
//继续执行父类的其他按键弹起的事件
return super.onKeyUp(keyCode, event);
}
@Override
/*
*
*
* focusedViewFocusedtruefalse
direction
RectViewnull
*/
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);
}
}

@ -1,214 +0,0 @@
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
public class NoteItemData {
static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE,
NoteColumns.HAS_ATTACHMENT,
NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT,
NoteColumns.PARENT_ID,
NoteColumns.SNIPPET,
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
};
//常量标记和数据就不一一标记了,意义翻译基本就知道
private static final int ID_COLUMN = 0;
private static final int ALERTED_DATE_COLUMN = 1;
private static final int BG_COLOR_ID_COLUMN = 2;
private static final int CREATED_DATE_COLUMN = 3;
private static final int HAS_ATTACHMENT_COLUMN = 4;
private static final int MODIFIED_DATE_COLUMN = 5;
private static final int NOTES_COUNT_COLUMN = 6;
private static final int PARENT_ID_COLUMN = 7;
private static final int SNIPPET_COLUMN = 8;
private static final int TYPE_COLUMN = 9;
private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11;
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private boolean mHasAttachment;
private long mModifiedDate;
private int mNotesCount;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private String mName;
private String mPhoneNumber;
private boolean mIsLastItem;
private boolean mIsFirstItem;
private boolean mIsOnlyOneItem;
private boolean mIsOneNoteFollowingFolder;
private boolean mIsMultiNotesFollowingFolder;
//初始化NoteItemData主要利用光标cursor获取的东西
public NoteItemData(Context context, Cursor cursor) {
//getxxx为转换格式
mId = cursor.getLong(ID_COLUMN);
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
mParentId = cursor.getLong(PARENT_ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
//初始化电话号码的信息
mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) {//mphonenumber里有符合字符串则用contart功能连接
mName = Contact.getContact(context, mPhoneNumber);
if (mName == null) {
mName = mPhoneNumber;
}
}
}
if (mName == null) {
mName = "";
}
checkPostion(cursor);
}
///根据鼠标的位置设置标记,和位置
private void checkPostion(Cursor cursor) {
//初始化几个标记cursor具体功能笔记中已提到不一一叙述
mIsLastItem = cursor.isLast() ? true : false;
mIsFirstItem = cursor.isFirst() ? true : false;
mIsOnlyOneItem = (cursor.getCount() == 1);
//初始化“多重子文件”“单一子文件”2个标记
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
//主要是设置上诉2标记
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {//若是note格式并且不是第一个元素
int position = cursor.getPosition();
if (cursor.moveToPrevious()) {//获取光标位置后看上一行
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {//若光标满足系统或note格式
if (cursor.getCount() > (position + 1)) {
mIsMultiNotesFollowingFolder = true;//若是数据行数大于但前位置+1则设置成正确
} else {
mIsOneNoteFollowingFolder = true;//否则单一文件夹标记为true
}
}
if (!cursor.moveToNext()) {//若不能再往下走则报错
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
}
}
///以下都是获取标记没什么好说的,不过倒数第二个需要说明下,很具体看下面
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
public boolean isLast() {
return mIsLastItem;
}
public String getCallName() {
return mName;
}
public boolean isFirst() {
return mIsFirstItem;
}
public boolean isSingle() {
return mIsOnlyOneItem;
}
public long getId() {
return mId;
}
public long getAlertDate() {
return mAlertDate;
}
public long getCreatedDate() {
return mCreatedDate;
}
public boolean hasAttachment() {
return mHasAttachment;
}
public long getModifiedDate() {
return mModifiedDate;
}
public int getBgColorId() {
return mBgColorId;
}
public long getParentId() {
return mParentId;
}
public int getNotesCount() {
return mNotesCount;
}
public long getFolderId () {
return mParentId;
}
public int getType() {
return mType;
}
public int getWidgetType() {
return mWidgetType;
}
public int getWidgetId() {
return mWidgetId;
}
public String getSnippet() {
return mSnippet;
}
public boolean hasAlert() {
return (mAlertDate > 0);
}
//若数据父id为保存至文件夹模式的id且满足电话号码单元不为空则isCallRecord为true
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}
}

File diff suppressed because it is too large Load Diff

@ -1,257 +0,0 @@
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import net.micode.notes.data.Notes;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
/*
* 便CursorAdaptercursorListView
* NotesListAdapter便
*/
public class NotesListAdapter extends CursorAdapter {
private static final String TAG = "NotesListAdapter";
private Context mContext;
private HashMap<Integer, Boolean> mSelectedIndex;
private int mNotesCount; //便签数
private boolean mChoiceMode; //选择模式标记
/*
* widget
*/
public static class AppWidgetAttribute {
public int widgetId;
public int widgetType;
};
/*
* 便
*
*/
public NotesListAdapter(Context context) {
super(context, null); //父类对象置空
mSelectedIndex = new HashMap<Integer, Boolean>(); //新建选项下标的hash表
mContext = context;
mNotesCount = 0;
}
@Override
/*
*
* 使NotesListItem
*/
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
/*
*
*
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) {
//若view是NotesListItem的一个实例
NoteItemData itemData = new NoteItemData(context, cursor);
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
//则新建一个项目选项并且用bind跟将view和鼠标内容便签数据捆绑在一起
}
}
/*
*
*
*/
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
//根据定位和是否勾选设置下标
notifyDataSetChanged();
//在修改后刷新activity
}
/*
*
*/
public boolean isInChoiceMode() {
return mChoiceMode;
}
/*
*
* mode
*/
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mChoiceMode = mode;
}
/*
*
*
*/
public void selectAll(boolean checked) {
Cursor cursor = getCursor();
//获取光标位置
for (int i = 0; i < getCount(); i++) {
if (cursor.moveToPosition(i)) {
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
setCheckedItem(i, checked);
}
}
}
//遍历所有光标可用的位置在判断为便签类型之后勾选单项框
}
/*
*
*
*/
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
//建立hash表
for (Integer position : mSelectedIndex.keySet()) {
//遍历所有的关键
if (mSelectedIndex.get(position) == true) {
//若光标位置可用
Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
//原文件不需要添加
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
//则将id该下标假如选项集合中
}
}
return itemSet;
}
/*
* Widget
*
*/
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Cursor c = (Cursor) getItem(position);
//以上4句和getSelectedItemIds一样不再重复
if (c != null) {
//光标位置可用的话就建立新的Widget属性并编辑下标和类型最后添加到选项集中
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
/**
* Don't close cursor here, only the adapter could close it
*/
} else {
Log.e(TAG, "Invalid cursor");
return null;
}
}
}
return itemSet;
}
/*
*
*
*/
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
//首先获取选项下标的值
if (null == values) {
return 0;
}
Iterator<Boolean> iter = values.iterator();
//初始化叠加器
int count = 0;
while (iter.hasNext()) {
if (true == iter.next()) {
//若value值为真计数+1
count++;
}
}
return count;
}
/*
*
*
*/
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
//获取选项数看是否等于便签的个数
}
/*
*
*
*/
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
}
return mSelectedIndex.get(position);
}
@Override
/*
* activity便
*
*/
protected void onContentChanged() {
super.onContentChanged();
//执行基类函数
calcNotesCount();
}
@Override
/*
* activity便
*/
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
//执行基类函数
calcNotesCount();
}
/*
* 便
*
*/
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
//获取总数同时遍历
Cursor c = (Cursor) getItem(i);
if (c != null) {
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
//若该位置不为空并且文本类型为便签就+1
}
} else {
Log.e(TAG, "Invalid cursor");
return;
}
//否则报错
}
}
}

@ -1,116 +0,0 @@
package net.micode.notes.ui;
import android.content.Context;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
//创建便签列表项目选项
public class NotesListItem extends LinearLayout {
private ImageView mAlert;//闹钟图片
private TextView mTitle; //标题
private TextView mTime; //时间
private TextView mCallName; //
private NoteItemData mItemData; //标签数据
private CheckBox mCheckBox; //打钩框
/*初始化基本信息*/
public NotesListItem(Context context) {
super(context); //super()它的主要作用是调整调用父类构造函数的顺序
inflate(context, R.layout.note_item, this);//Inflate可用于将一个xml中定义的布局控件找出来,这里的xml是r。layout
//findViewById用于从contentView中查找指定ID的View转换出来的形式根据需要而定;
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
}
///根据data的属性对各个控件的属性的控制主要是可见性Visibility内容setText格式setTextAppearance
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE); ///设置可见行为可见
mCheckBox.setChecked(checked); ///格子打钩
} else {
mCheckBox.setVisibility(View.GONE);
}
mItemData = data;
///设置控件属性一共三种情况由data的id和父id是否与保存到文件夹的id一致来决定
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
//设置该textview的style
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
//settext为设置内容
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
///关于闹钟的设置
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);//图片来源的设置
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
} else {
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
///设置title格式
if (data.getType() == Notes.TYPE_FOLDER) {
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
} else {
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);///设置图片来源
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
}
}
///设置内容获取相关时间从data里编辑的日期中获取
mTime. setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
setBackground(data);
}
//根据data的文件属性来设置背景
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
//若是note型文件则4种情况对于4种不同情况的背景来源
if (data.getType() == Notes.TYPE_NOTE) {
//单个数据并且只有一个子文件夹
if (data.isSingle() || data.isOneFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {//是最后一个数据
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {//是一个数据并有多个子文件夹
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
} else {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
//若不是note直接调用文件夹的背景来源
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
public NoteItemData getItemData() {
return mItemData;
}
}

@ -1,516 +0,0 @@
package net.micode.notes.ui;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceActivity;
import android.preference.PreferenceCategory;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
/*
*NotesPreferenceActivity便
* PreferenceActivityActivity
*/
public class NotesPreferenceActivity extends PreferenceActivity {
public static final String PREFERENCE_NAME = "notes_preferences";
//优先名
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
//同步账号
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
//同步时间
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
//同步密码
private static final String AUTHORITIES_FILTER_KEY = "authorities";
//本地密码
private PreferenceCategory mAccountCategory;
//账户分组
private GTaskReceiver mReceiver;
//同步任务接收器
private Account[] mOriAccounts;
//账户
private boolean mHasAddedAccount;
//账户的hash标记
@SuppressLint("UnspecifiedRegisterReceiverFlag")
@Override
/*
*activity
*Bundle icicle activity
*
*/
protected void onCreate(Bundle icicle) {
//先执行父类的创建函数
super.onCreate(icicle);
/* using the app icon for navigation */
getActionBar().setDisplayHomeAsUpEnabled(true);
//给左上角图标的左边加上一个返回的图标
addPreferencesFromResource(R.xml.preferences);
//添加xml来源并显示 xml
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
//根据同步账户关键码来初始化分组
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
registerReceiver(mReceiver, filter);
//初始化同步组件
mOriAccounts = null;
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
//获取listvivewListView的作用:用于列出所有选择
getListView().addHeaderView(header, null, true);
//在listview组件上方添加其他组件
}
@Override
/*
* activity
*
*/
protected void onResume() {
//先执行父类 的交互实现
super.onResume();
// need to set sync account automatically if user has added a new
// account
if (mHasAddedAccount) {
//若用户新加了账户则自动设置同步账户
Account[] accounts = getGoogleAccounts();
//获取google同步账户
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
//若原账户不为空且当前账户有增加
for (Account accountNew : accounts) {
boolean found = false;
for (Account accountOld : mOriAccounts) {
if (TextUtils.equals(accountOld.name, accountNew.name)) {
//更新账户
found = true;
break;
}
}
if (!found) {
setSyncAccount(accountNew.name);
//若是没有找到旧的账户,那么同步账号中就只添加新账户
break;
}
}
}
}
refreshUI();
//刷新标签界面
}
@Override
/*
* activity
*
*/
protected void onDestroy() {
if (mReceiver != null) {
unregisterReceiver(mReceiver);
//注销接收器
}
super.onDestroy();
//执行父类的销毁动作
}
/*
*
*
*/
private void loadAccountPreference() {
mAccountCategory.removeAll();
//销毁所有的分组
Preference accountPref = new Preference(this);
//建立首选项
final String defaultAccount = getSyncAccountName(this);
accountPref.setTitle(getString(R.string.preferences_account_title));
accountPref.setSummary(getString(R.string.preferences_account_summary));
//设置首选项的大标题和小标题
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
//建立监听器
if (!GTaskSyncService.isSyncing()) {
if (TextUtils.isEmpty(defaultAccount)) {
// the first time to set account
//若是第一次建立账户显示选择账户提示对话框
showSelectAccountAlertDialog();
} else {
// if the account has already been set, we need to promp
// user about the risk
//若是已经建立则显示修改对话框并进行修改操作
showChangeAccountConfirmAlertDialog();
}
} else {
//若在没有同步的情况下则在toast中显示不能修改
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
}
return true;
}
});
//根据新建首选项编辑新的账户分组
mAccountCategory.addPreference(accountPref);
}
/*
*
*
*/
private void loadSyncButton() {
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
//获取同步按钮控件和最终同步时间的的窗口
// set button state
//设置按钮的状态
if (GTaskSyncService.isSyncing()) {
//若是在同步状态下
syncButton.setText(getString(R.string.preferences_button_sync_cancel));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.cancelSync(NotesPreferenceActivity.this);
}
});
//设置按钮显示的文本为“取消同步”以及监听器
} else {
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
GTaskSyncService.startSync(NotesPreferenceActivity.this);
}
});
//若是不同步则设置按钮显示的文本为“立即同步”以及对应监听器
}
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
//设置按键可用还是不可用
// set last sync time
// 设置最终同步时间
if (GTaskSyncService.isSyncing()) {
//若是在同步的情况下
lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE);
// 根据当前同步服务器设置时间显示框的文本以及可见性
} else {
//若是非同步情况
long lastSyncTime = getLastSyncTime(this);
if (lastSyncTime != 0) {
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime)));
lastSyncTimeView.setVisibility(View.VISIBLE);
//则根据最后同步时间的信息来编辑时间显示框的文本内容和可见性
} else {
//若时间为空直接设置为不可见状态
lastSyncTimeView.setVisibility(View.GONE);
}
}
}
/*
*
*
*/
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
/*
*
*
*/
private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
//创建一个新的对话框
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));
//设置标题以及子标题的内容
dialogBuilder.setCustomTitle(titleView);
dialogBuilder.setPositiveButton(null, null);
//设置对话框的自定义标题建立一个YES的按钮
Account[] accounts = getGoogleAccounts();
String defAccount = getSyncAccountName(this);
//获取同步账户信息
mOriAccounts = accounts;
mHasAddedAccount = false;
if (accounts.length > 0) {
//若账户不为空
CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items;
int checkedItem = -1;
int index = 0;
for (Account account : accounts) {
if (TextUtils.equals(account.name, defAccount)) {
checkedItem = index;
//在账户列表中查询到所需账户
}
items[index++] = account.name;
}
dialogBuilder.setSingleChoiceItems(items, checkedItem,
//在对话框建立一个单选的复选框
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
setSyncAccount(itemMapping[which].toString());
dialog.dismiss();
//取消对话框
refreshUI();
}
//设置点击后执行的事件,包括检录新同步账户和刷新标签界面
});
//建立对话框网络版的监听器
}
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
dialogBuilder.setView(addAccountView);
//给新加账户对话框设置自定义样式
final AlertDialog dialog = dialogBuilder.show();
//显示对话框
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mHasAddedAccount = true;
//将新加账户的hash置true
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
//建立网络建立组件
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
"gmail-ls"
});
startActivityForResult(intent, -1);
//跳回上一个选项
dialog.dismiss();
}
});
//建立新加账户对话框的监听器
}
/*
*
*
*/
private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
//创建一个新的对话框
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
getSyncAccountName(this)));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg));
//根据同步修改的账户信息设置标题以及子标题的内容
dialogBuilder.setCustomTitle(titleView);
//设置对话框的自定义标题
CharSequence[] menuItemArray = new CharSequence[] {
getString(R.string.preferences_menu_change_account),
getString(R.string.preferences_menu_remove_account),
getString(R.string.preferences_menu_cancel)
};
//定义一些标记字符串
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
//设置对话框要显示的一个list用于显示几个命令时,即changeremovecancel
public void onClick(DialogInterface dialog, int which) {
//按键功能由which来决定
if (which == 0) {
//进入账户选择对话框
showSelectAccountAlertDialog();
} else if (which == 1) {
//删除账户并且跟新便签界面
removeSyncAccount();
refreshUI();
}
}
});
dialogBuilder.show();
//显示对话框
}
/*
*
*
*/
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.google");
}
/*
*
*
*/
private void setSyncAccount(String account) {
if (!getSyncAccountName(this).equals(account)) {
//假如该账号不在同步账号列表中
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
//编辑共享的首选项
if (account != null) {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account);
} else {
editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
//将该账号加入到首选项中
editor.commit();
//提交修改的数据
setLastSyncTime(this, 0);
//将最后同步时间清零
// clean up local gtask related info
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
//重置当地同步任务的信息
Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show();
//将toast的文本信息置为“设置账户成功”并显示出来
}
}
/*
*
*
*/
private void removeSyncAccount() {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
//设置共享首选项
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME);
//假如当前首选项中有账户就删除
}
if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) {
editor.remove(PREFERENCE_LAST_SYNC_TIME);
//删除当前首选项中有账户时间
}
editor.commit();
//提交更新后的数据
// clean up local gtask related info
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
values.put(NoteColumns.GTASK_ID, "");
values.put(NoteColumns.SYNC_ID, 0);
getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null);
}
}).start();
//重置当地同步任务的信息
}
/*
*
*
*/
public static String getSyncAccountName(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
/*
*
*
*/
public static void setLastSyncTime(Context context, long time) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
// 从共享首选项中找到相关账户并获取其编辑器
editor.putLong(PREFERENCE_LAST_SYNC_TIME, time);
editor.commit();
//编辑最终同步时间并提交更新
}
/*
*
*
*/
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
/*
*
* BroadcastReceiver
*/
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
refreshUI();
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
//获取随广播而来的Intent中的同步服务的数据
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
syncStatus.setText(intent
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
//通过获取的数据在设置系统的状态
}
}
}
/*
*
*
* :MenuItem
*/
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
//根据选项的id选择这里只有一个主页
case android.R.id.home:
Intent intent = new Intent(this, NotesListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true;
//在主页情况下在创建连接组件intent发出清空的信号并开始一个相应的activity
default:
return false;
}
}
}

@ -1,132 +0,0 @@
/*
* 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.widget; //隶属于widget这个包中
import android.app.PendingIntent; //在Android.app包中的用于提供暂缓的服务当某种条件达到时就可以执行某些活动
import android.appwidget.AppWidgetManager; //继承object类更新appwidget状态和获取安装appwidget提供信息以及其它相关状态信息。
import android.appwidget.AppWidgetProvider; //本质是广播作用用来对widget进行更新
import android.content.ContentValues; //此类用于存储ContentResolver 可以处理的一组值,是一些基本类型的值
import android.content.Context; //可以理解为一个接口(抽象类),它提供了有关应用程序的全局信息。从它继承或者从它的直接或间接子类继承,那么继承的子类就可以拿到应用程序的全局信息。
import android.content.Intent; //联系各个Activity的关键对象。告诉android该做什么
import android.database.Cursor; //是数据库指针,用于查询并操作数据
import android.util.Log; //更新删改等的日志文件
import android.widget.RemoteViews; //它表示的是一个View结构可以在其他进程中显示小部件的旋转效果则是通过不断地更新RemoteViews来实现的
import net.micode.notes.R;
import net.micode.notes.data.Notes; //引入其他包中类
import net.micode.notes.data.Notes.NoteColumns; //引入笔记换列的类
import net.micode.notes.tool.ResourceParser; //引入工具类
import net.micode.notes.ui.NoteEditActivity; //引入ui包中的编辑笔记类
import net.micode.notes.ui.NotesListActivity; //引入ui包中的列表活动类
public abstract class NoteWidgetProvider extends AppWidgetProvider { //继承AppWidgetProvider的抽象类NoteWidgetProvider可以给子类提供接口
public static final String [] PROJECTION = new String [] { //最终静态字符串数组
NoteColumns.ID, //笔记框ID号
NoteColumns.BG_COLOR_ID, //笔记框颜色ID
NoteColumns.SNIPPET //一小段笔记框
};
public static final int COLUMN_ID = 0; //最终静态笔记框ID号
public static final int COLUMN_BG_COLOR_ID = 1; //最终静态笔记框颜色ID
public static final int COLUMN_SNIPPET = 2; //最终静态一小段笔记框
private static final String TAG = "NoteWidgetProvider"; //最终静态 TAG
@Override //重写
public void onDeleted(Context context, int[] appWidgetIds) { //方法,删除部件
ContentValues values = new ContentValues(); //和Bundle类一样键值对
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); //通过put来存放数据。大多是基础数据类型
for (int i = 0; i < appWidgetIds.length; i++) {
context.getContentResolver().update(Notes.CONTENT_NOTE_URI, //根据长度批量修改数据uri表名, 属性键值对,修改对象,修改内容
values,
NoteColumns.WIDGET_ID + "=?",
new String[] { String.valueOf(appWidgetIds[i])});
}
}
//操作数据库,查询操作
private Cursor getNoteWidgetInfo(Context context, int widgetId) { //得到一些部件的信息和全局消息,返回类型为游标
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, //查找数据并返回含有数据信息的cursor对象。
PROJECTION, //参数为 uri 查询表名,查询列名,属性,属性域值,排序
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null);
}
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { //更新部件
update(context, appWidgetManager, appWidgetIds, false);
}
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, //隐私模式下更新相关部件
boolean privacyMode) {
for (int i = 0; i < appWidgetIds.length; i++) {
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {
int bgId = ResourceParser.getDefaultBgId(context);
String snippet = "";
Intent intent = new Intent(context, NoteEditActivity.class); //意图从context到NoteEditActivity类的意图
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); //设置标志,当栈顶为活动实例时,重用该实例,不用新建
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); //传递参数
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType());
Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); //数据库光标,用于读取数据库
if (c != null && c.moveToFirst()) {
if (c.getCount() > 1) {
Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); //日志信息,打印错误
c.close();
return;
}
snippet = c.getString(COLUMN_SNIPPET);
bgId = c.getInt(COLUMN_BG_COLOR_ID);
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); //传递参数
intent.setAction(Intent.ACTION_VIEW); //隐式intent设置意图参数为向用户展示意图。
} else {
snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); //隐式intent设置联系人
}
if (c != null) { //关闭
c.close();
}
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); //布局文件layout的远程视图
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); //设置图像资源从布局文件layout中找到ID
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); //产生待定意图
/**
* Generate the pending intent to start host for the widget
*/
PendingIntent pendingIntent = null; //待定意图,等待触发时机
if (privacyMode) { //隐私模式
rv.setTextViewText(R.id.widget_text, //设置视图文本
context.getString(R.string.widget_under_visit_mode));
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( //开始意图启动Activity
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); //上下文,请求码,跳转对象,
} else { //FLAG_UPDATE_CURRENT当前描述的PendingIntent如果已经存在那么它们都会被更新即它们的Intent中的Extras会被替换成最新的
rv.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, //先前的意图
PendingIntent.FLAG_UPDATE_CURRENT);
} //FLAG_UPDATE_CURRENT当前描述的PendingIntent如果已经存在那么它们都会被更新即它们的Intent中的Extras会被替换成最新的
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); //启动点击待定意图
appWidgetManager.updateAppWidget(appWidgetIds[i], rv); //更新部件
}
}
}
protected abstract int getBgResourceId(int bgId); //抽象类获取Bg资源ID
protected abstract int getLayoutId(); //抽象方法获取布局ID
protected abstract int getWidgetType(); //获取部件的种类
}

@ -1,47 +0,0 @@
/*
* 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.widget;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
//二倍部件
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
@Override //更新
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);
}
@Override //获取xml中的二倍部件id
protected int getLayoutId() {
return R.layout.widget_2x;
}
@Override //获取资源id
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
}
@Override //获取二倍部件种类
protected int getWidgetType() {
return Notes.TYPE_WIDGET_2X;
}
}

@ -1,46 +0,0 @@
/*
* 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.widget;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
//四倍部件
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
@Override //更新部件
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.update(context, appWidgetManager, appWidgetIds);
}
//获取四倍部件id
protected int getLayoutId() {
return R.layout.widget_4x;
}
@Override //获取xml资源id
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
}
@Override //获取四倍部件种类
protected int getWidgetType() {
return Notes.TYPE_WIDGET_4X;
}
}
Loading…
Cancel
Save