Compare commits

...

No commits in common. 'main' and 'master' have entirely different histories.
main ... master

@ -1,68 +0,0 @@
# Code-Reading-MiNote
## 说明
此项目为“开源软件阅读标注”小组作业,小组成员为程星桦、张晋菡、张鹏展、文渤乔
阅读项目为[MiCode便签-小米便签的社区开源版](https://github.com/MiCode/Notes)
具体环境部署和测试在本地进行
## 协作规范
### 协作流程
每个参与者将版本库克隆到本地
```
git clone https://bdgit.educoder.net/pqfx375oj/Code-Reading-MiNote.git
```
切换到到develop分支
```
git checkout develop
```
在本地创建自己的分支
```
git checkout -b your_branch_name (e.g. git checkout -b zjh)
```
进行修改后,将修改提交到自己的分支
```
git add .
git commit -m "your commit message"
```
提交修改前拉取develop分支最新代码解决可能存在的冲突
```
git pull origin develop
```
将自己的分支推送到远程仓库
```
git push origin your_branch_name
```
**提交pull request**将个人分支合并到develop分支由组长进行代码审查合并代码并将develop的稳定版本合并到master分支。
### 代码阅读规范
协作者可将代码阅读记录通过注释的方式添加到源代码中,并标注注释的添加者;
各协作者在doc目录中创建一个命名“xxx_notes.md"的文件,进行自己的阅读记录。
具体分工待定。
## 参考资料(小组成员可另行补充)
[源码以及阅读报告](https://gitee.com/mi-note-development-team/minote/tree/master/)
[知识荟-过往讨论](https://www.learnerhub.net/#/spaces/146/search?text=%E5%B0%8F%E7%B1%B3%E4%BE%BF%E7%AD%BE&t=1729487479088&type=docs):有许多往届关于小米便签的阅读讨论帖,有很强的参考价值

@ -1,43 +0,0 @@
import numpy as np
import scipy.stats as stats
# 假设数据
# 老龄化程度较高的群体数据
high_aging_data = {
'老龄化给社会带来的主要问题': np.array([75.47, 85.85, 84.91, 53.77, 83.96]),
'政府应该采取哪些措施': np.array([62.26, 16.04, 91.51, 88.68, 51.89,0.94]),
'life_expectancy': np.array([75, 76, 74, 77, 75]),
# 其他可能的因素...
}
# 老龄化程度较低的群体数据
low_aging_data = {
'老龄化给社会带来的主要问题': np.array([84.62, 69.23, 69.23, 15.38, 84.62]),
'政府应该采取哪些措施': np.array([46.15, 7.69, 69.23, 76.92, 30.77,7.69]),
'life_expectancy': np.array([70, 71, 69, 72, 70]),
# 其他可能的因素...
}
# 进行t检验并分析结果
factors = ['老龄化给社会带来的主要问题', '政府应该采取哪些措施', 'life_expectancy']
system_evaluation = {}
for factor in factors:
high_group = high_aging_data[factor]
low_group = low_aging_data[factor]
t_stat, p_value = stats.ttest_ind(high_group, low_group)
system_evaluation[factor] = {
't_statistic': t_stat,
'p_value': p_value,
'effect_size': np.abs(t_stat) * np.sqrt((len(high_group) + len(low_group)) / (len(high_group) * len(low_group)))
}
print(f"{factor}的t统计量: {t_stat}, P值: {p_value}")
if p_value < 0.05:
print(f"{factor}在两个群体之间存在显著差异。\n")
else:
print(f"{factor}在两个群体之间没有显著差异。\n")
# 打印系统评价
print("系统评价:")
for factor, evaluation in system_evaluation.items():
print(f"{factor}: t统计量={evaluation['t_statistic']}, P值={evaluation['p_value']}, 影响大小={evaluation['effect_size']}")

@ -26,12 +26,9 @@ import android.util.Log;
import java.util.HashMap; import java.util.HashMap;
public class Contact { public class Contact {
// 保存联系人名与电话号码映射的缓存
private static HashMap<String, String> sContactCache; private static HashMap<String, String> sContactCache;
// 日志标记
private static final String TAG = "Contact"; private static final String TAG = "Contact";
// 查询联系人所用的选择条件
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN " + " AND " + Data.RAW_CONTACT_ID + " IN "
@ -39,54 +36,36 @@ public class Contact {
+ " FROM phone_lookup" + " FROM phone_lookup"
+ " WHERE min_match = '+')"; + " WHERE min_match = '+')";
/**
*
*
* @param context
* @param phoneNumber
* @return null
*/
public static String getContact(Context context, String phoneNumber) { public static String getContact(Context context, String phoneNumber) {
// 初始化联系人缓存
if(sContactCache == null) { if(sContactCache == null) {
sContactCache = new HashMap<String, String>(); sContactCache = new HashMap<String, String>();
} }
// 如果缓存中已经存在该电话号码的联系人名,直接返回
if(sContactCache.containsKey(phoneNumber)) { if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber); return sContactCache.get(phoneNumber);
} }
// 替换选择条件中的占位符
String selection = CALLER_ID_SELECTION.replace("+", String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
// 执行查询
Cursor cursor = context.getContentResolver().query( Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI, Data.CONTENT_URI,
new String [] { Phone.DISPLAY_NAME }, // 查询显示名称 new String [] { Phone.DISPLAY_NAME },
selection, selection,
new String[] { phoneNumber }, new String[] { phoneNumber },
null); null);
// 如果查询结果不为空且移动到结果集的第一行
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
try { try {
// 获取联系人姓名
String name = cursor.getString(0); String name = cursor.getString(0);
// 将联系人姓名缓存起来
sContactCache.put(phoneNumber, name); sContactCache.put(phoneNumber, name);
return name; return name;
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
// 捕获索引越界异常,记录错误日志
Log.e(TAG, " Cursor get string error " + e.toString()); Log.e(TAG, " Cursor get string error " + e.toString());
return null; return null;
} finally { } finally {
// 关闭cursor释放资源
cursor.close(); cursor.close();
} }
} else { } else {
// 如果没有找到匹配的联系人,记录日志
Log.d(TAG, "No contact matched with number:" + phoneNumber); Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null; return null;
} }

@ -1,302 +1,279 @@
package net.micode.notes.data; /*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
import android.net.Uri; *
public class Notes { * Licensed under the Apache License, Version 2.0 (the "License");
// 用于表示笔记应用中的各种类型、标识符以及Intent的额外数据 * you may not use this file except in compliance with the License.
public static final String AUTHORITY = "micode_notes"; * You may obtain a copy of the License at
public static final String TAG = "Notes"; *
* http://www.apache.org/licenses/LICENSE-2.0
//对NoteColumns.TYPE的值进行设置时使用 *
//即不同种类:笔记、文件夹和系统文件夹 * Unless required by applicable law or agreed to in writing, software
public static final int TYPE_NOTE = 0; * distributed under the License is distributed on an "AS IS" BASIS,
public static final int TYPE_FOLDER = 1; * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
public static final int TYPE_SYSTEM = 2; * See the License for the specific language governing permissions and
* limitations under the License.
/** */
* Following IDs are system folders' identifiers
* {@link Notes#ID_ROOT_FOLDER } is default folder package net.micode.notes.data;
* {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records import android.net.Uri;
*/ public class Notes {
//以下id是系统文件夹的标识符即系统文件夹的分类 public static final String AUTHORITY = "micode_notes";
//ID_ROOT_FOLDER默认文件夹 public static final String TAG = "Notes";
//ID_TEMPARAY_FOLDER不属于文件夹的笔记 public static final int TYPE_NOTE = 0;
//ID_CALL_RECORD_FOLDER用于存储通话记录以便返回 public static final int TYPE_FOLDER = 1;
//ID_TRASH_FOLER垃圾回收站 public static final int TYPE_SYSTEM = 2;
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; * Following IDs are system folders' identifiers
public static final int ID_TRASH_FOLER = -3; * {@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
// 额外的数据键个人理解为就是定义一些布局的ID */
// 这部分就是用于设置UI界面的一些布局或小组件的id给它定义成常量了。 public static final int ID_ROOT_FOLDER = 0;
// 这样的封装性可能比较好因为如果有部分要修改则直接来这边修改即可不用在activity部分一个一个修改。 public static final int ID_TEMPARAY_FOLDER = -1;
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; public static final int ID_CALL_RECORD_FOLDER = -2;
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; public static final int ID_TRASH_FOLER = -3;
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_ALERT_DATE = "net.micode.notes.alert_date";
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id";
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; 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 int TYPE_WIDGET_INVALIDE = -1; public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id";
public static final int TYPE_WIDGET_2X = 0; public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date";
public static final int TYPE_WIDGET_4X = 1;
public static final int TYPE_WIDGET_INVALIDE = -1;
// 数据常量:里面定义了两种类型:文本便签和通话记录 public static final int TYPE_WIDGET_2X = 0;
public static class DataConstants { public static final int TYPE_WIDGET_4X = 1;
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; public static class DataConstants {
} public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
//下面这些有类似指针的效果? 其实就是定义一堆访问笔记和文件的uri }
//GPTAndroid开发中常见的用于定义内容提供者Content ProviderURI
//内容提供者是一种Android组件它允许应用程序共享和存储数据。这里定义了一个URI来查询数据 /**
/** * Uri to query all notes and folders
* Uri to query all notes and folders */
*/ public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
/** * Uri to query data
* Uri to query data */
*/ public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
public interface NoteColumns {
public interface NoteColumns { /**
// 雨:这个接口定义了一系列静态的、最终的字符串常量,这些常量代表数据库表中的列名。 * The unique ID for a row
// 作用:用于后面创建数据库的表头 * <P> Type: INTEGER (long) </P>
// 总的属性有ID、父级ID、创建日期、修改日期、提醒日期、文件标签摘要、小部件ID、小部件类型、背景颜色ID、附件、文件中的标签数量、 */
// 文件标签类型、最后一个同步ID、本地修改标签、移动前的ID、谷歌任务ID、代码版本信息。 public static final String ID = "_id";
// GPT提示在Android开发中当使用SQLite数据库时通常会为表中的每一列定义一个常量以便在代码中引用。
// 这样做的好处是,如果以后需要更改列名,只需要在一个地方修改,而不需要在整个代码中搜索和替换。 /**
* The parent's id for note or folder
/** * <P> Type: INTEGER (long) </P>
* The unique ID for a row */
* <P> Type: INTEGER (long) </P> public static final String PARENT_ID = "parent_id";
*/
public static final String ID = "_id"; /**
* Created data for note or folder
/** * <P> Type: INTEGER (long) </P>
* The parent's id for note or folder */
* <P> Type: INTEGER (long) </P> public static final String CREATED_DATE = "created_date";
*/
public static final String PARENT_ID = "parent_id"; /**
* Latest modified date
/** * <P> Type: INTEGER (long) </P>
* Created data for note or folder */
* <P> Type: INTEGER (long) </P> public static final String MODIFIED_DATE = "modified_date";
*/
public static final String CREATED_DATE = "created_date";
/**
/** * Alert date
* Latest modified date * <P> Type: INTEGER (long) </P>
* <P> Type: INTEGER (long) </P> */
*/ public static final String ALERTED_DATE = "alert_date";
public static final String MODIFIED_DATE = "modified_date";
/**
* Folder's name or text content of note
/** * <P> Type: TEXT </P>
* Alert date */
* <P> Type: INTEGER (long) </P> public static final String SNIPPET = "snippet";
*/
public static final String ALERTED_DATE = "alert_date"; /**
* Note's widget id
/** * <P> Type: INTEGER (long) </P>
* Folder's name or text content of note */
* <P> Type: TEXT </P> public static final String WIDGET_ID = "widget_id";
*/
// 摘要? /**
public static final String SNIPPET = "snippet"; * Note's widget type
* <P> Type: INTEGER (long) </P>
/** */
* Note's widget id public static final String WIDGET_TYPE = "widget_type";
* <P> Type: INTEGER (long) </P>
*/ /**
public static final String WIDGET_ID = "widget_id"; * Note's background color's id
* <P> Type: INTEGER (long) </P>
/** */
* Note's widget type public static final String BG_COLOR_ID = "bg_color_id";
* <P> Type: INTEGER (long) </P>
*/ /**
public static final String WIDGET_TYPE = "widget_type"; * For text note, it doesn't has attachment, for multi-media
* note, it has at least one attachment
/** * <P> Type: INTEGER </P>
* Note's background color's id */
* <P> Type: INTEGER (long) </P> public static final String HAS_ATTACHMENT = "has_attachment";
*/
public static final String BG_COLOR_ID = "bg_color_id"; /**
* Folder's count of notes
/** * <P> Type: INTEGER (long) </P>
* For text note, it doesn't has attachment, for multi-media */
* note, it has at least one attachment public static final String NOTES_COUNT = "notes_count";
* <P> Type: INTEGER </P>
*/ /**
public static final String HAS_ATTACHMENT = "has_attachment"; * The file type: folder or note
* <P> Type: INTEGER </P>
/** */
* Folder's count of notes public static final String TYPE = "type";
* <P> Type: INTEGER (long) </P>
*/ /**
public static final String NOTES_COUNT = "notes_count"; * The last sync id
* <P> Type: INTEGER (long) </P>
/** */
* The file type: folder or note public static final String SYNC_ID = "sync_id";
* <P> Type: INTEGER </P>
*/ /**
public static final String TYPE = "type"; * Sign to indicate local modified or not
* <P> Type: INTEGER </P>
/** */
* The last sync id public static final String LOCAL_MODIFIED = "local_modified";
* <P> Type: INTEGER (long) </P>
*/ /**
//雨在数据同步过程中这个ID可能用来跟踪和识别每次同步操作的唯一性确保数据的一致性。 * Original parent id before moving into temporary folder
public static final String SYNC_ID = "sync_id"; * <P> Type : INTEGER </P>
*/
/** public static final String ORIGIN_PARENT_ID = "origin_parent_id";
* Sign to indicate local modified or not
* <P> Type: INTEGER </P> /**
*/ * The gtask id
public static final String LOCAL_MODIFIED = "local_modified"; * <P> Type : TEXT </P>
*/
/** public static final String GTASK_ID = "gtask_id";
* Original parent id before moving into temporary folder
* <P> Type : INTEGER </P> /**
*/ * The version code
public static final String ORIGIN_PARENT_ID = "origin_parent_id"; * <P> Type : INTEGER (long) </P>
*/
/** public static final String VERSION = "version";
* The gtask id }
* <P> Type : TEXT </P>
*/ public interface DataColumns {
public static final String GTASK_ID = "gtask_id"; /**
* The unique ID for a row
/** * <P> Type: INTEGER (long) </P>
* The version code */
* <P> Type : INTEGER (long) </P> public static final String ID = "_id";
*/
public static final String VERSION = "version"; /**
} * The MIME type of the item represented by this row.
* <P> Type: Text </P>
public interface DataColumns { */
// DataColumns的接口这个接口包含了一系列静态常量这些常量代表了数据库表中用于存储数据的列名。 public static final String MIME_TYPE = "mime_type";
// 每个常量都有相应的注释,说明该列的作用和数据类型。
/**
/** * The reference id to note that this data belongs to
* The unique ID for a row * <P> Type: INTEGER (long) </P>
* <P> Type: INTEGER (long) </P> */
*/ public static final String NOTE_ID = "note_id";
public static final String ID = "_id";
/**
/** * Created data for note or folder
* The MIME type of the item represented by this row. * <P> Type: INTEGER (long) </P>
* <P> Type: Text </P> */
*/ public static final String CREATED_DATE = "created_date";
//MIME类型是一种标准用于标识文档、文件或字节流的性质和格式。在数据库中这个字段可以用来识别不同类型的数据例如文本、图片、音频或视频等。
public static final String MIME_TYPE = "mime_type"; /**
* Latest modified date
/** * <P> Type: INTEGER (long) </P>
* The reference id to note that this data belongs to */
* <P> Type: INTEGER (long) </P> public static final String MODIFIED_DATE = "modified_date";
*/
//归属的Note的ID /**
public static final String NOTE_ID = "note_id"; * Data's content
* <P> Type: TEXT </P>
/** */
* Created data for note or folder public static final String CONTENT = "content";
* <P> Type: INTEGER (long) </P>
*/
//创建日期 /**
public static final String CREATED_DATE = "created_date"; * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
/** * <P> Type: INTEGER </P>
* Latest modified date */
* <P> Type: INTEGER (long) </P> public static final String DATA1 = "data1";
*/
//最近修改日期 /**
public static final String MODIFIED_DATE = "modified_date"; * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
/** * <P> Type: INTEGER </P>
* Data's content */
* <P> Type: TEXT </P> public static final String DATA2 = "data2";
*/
//数据内容 /**
public static final String CONTENT = "content"; * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
// 以下5个是通用数据列它们的具体意义取决于MIME类型由MIME_TYPE字段指定 */
// 不同的MIME类型可能需要存储不同类型的数据这五个字段提供了灵活性允许根据MIME类型来存储相应的数据。 public static final String DATA3 = "data3";
// 读后面的代码感觉这部分是在表示内容的不同状态?
/** /**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type * TEXT data type
* <P> Type: INTEGER </P> * <P> Type: TEXT </P>
*/ */
public static final String DATA1 = "data1"; public static final String DATA4 = "data4";
/** /**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for * Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type * TEXT data type
* <P> Type: INTEGER </P> * <P> Type: TEXT </P>
*/ */
public static final String DATA2 = "data2"; public static final String DATA5 = "data5";
}
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for public static final class TextNote implements DataColumns {
* TEXT data type /**
* <P> Type: TEXT </P> * 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 DATA3 = "data3"; */
public static final String MODE = DATA1;
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for public static final int MODE_CHECK_LIST = 1;
* TEXT data type
* <P> Type: TEXT </P> public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
*/
public static final String DATA4 = "data4"; 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");
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for }
* TEXT data type
* <P> Type: TEXT </P> public static final class CallNote implements DataColumns {
*/ /**
public static final String DATA5 = "data5"; * Call date for this record
} * <P> Type: INTEGER (long) </P>
*/
//以下是文本便签的定义 public static final String CALL_DATE = DATA1;
public static final class TextNote implements DataColumns {
/** /**
* Mode to indicate the text in check list mode or not * Phone number for this record
* <P> Type: Integer 1:check list mode 0: normal mode </P> * <P> Type: TEXT </P>
*/ */
public static final String MODE = DATA1; //模式这个被存在DATA1列中 public static final String PHONE_NUMBER = DATA3;
public static final int MODE_CHECK_LIST = 1; //所处检查列表模式? public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; // 定义了MIME类型用于标识文本标签的目录 public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";// 定义了MIME类型用于标识文本标签的单个项 public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");//文本标签内容提供者Content Provider的URI用于访问文本标签数据 }
}
// 通话记录的定义?
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; //意味着在数据库表中这个电话号码信息将被存储在DATA3列中
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";// 同样定义了MIME类型是用于标识通话记录的目录。
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";// 同样定义了MIME类型是用于标识通话记录的单个项。
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");//定义了通话记录内容提供者的URI用于访问通话记录数据。
}
}

@ -1,99 +1,92 @@
/* /*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
* *
* Apache 2.0 * 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 * 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; package net.micode.notes.data;
// 导入必要的类 import android.content.ContentValues;
import android.content.ContentValues; // 用于数据库内容的存储 import android.content.Context;
import android.content.Context; // 上下文类 import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase; // SQLite数据库类 import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteOpenHelper; // SQLite数据库帮助类 import android.util.Log;
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;
// 导入自定义数据列和常量
import net.micode.notes.data.Notes.DataColumns; // 数据列模型
import net.micode.notes.data.Notes.DataConstants; // 数据常量
import net.micode.notes.data.Notes.NoteColumns; // 笔记列模型
// 创建NotesDatabaseHelper类继承自SQLiteOpenHelper
public class NotesDatabaseHelper extends SQLiteOpenHelper { public class NotesDatabaseHelper extends SQLiteOpenHelper {
// 数据库名称
private static final String DB_NAME = "note.db"; private static final String DB_NAME = "note.db";
// 数据库版本号
private static final int DB_VERSION = 4; private static final int DB_VERSION = 4;
// 定义表名的接口
public interface TABLE { public interface TABLE {
// 笔记表的名称
public static final String NOTE = "note"; public static final String NOTE = "note";
// 数据表的名称
public static final String DATA = "data"; public static final String DATA = "data";
} }
// 日志标签
private static final String TAG = "NotesDatabaseHelper"; private static final String TAG = "NotesDatabaseHelper";
// 单例实例
private static NotesDatabaseHelper mInstance; private static NotesDatabaseHelper mInstance;
// 创建笔记表的SQL语句
private static final String CREATE_NOTE_TABLE_SQL = private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" + "CREATE TABLE " + TABLE.NOTE + "(" +
NoteColumns.ID + " INTEGER PRIMARY KEY," + // 笔记ID主键 NoteColumns.ID + " INTEGER PRIMARY KEY," +
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 父级ID NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 提醒日期 NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + // 背景颜色ID NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建日期 NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + // 是否有附件 NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改日期 NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + // 笔记数量 NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + // 笔记摘要 NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + // 笔记类型 NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + // 小部件ID NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 小部件类型 NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," +
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步ID NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + // 本地修改标志 NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 原始父级ID NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // GTASK ID NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," +
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 版本号 NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")"; ")";
// 创建数据表的SQL语句
private static final String CREATE_DATA_TABLE_SQL = private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" + "CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," + // 数据ID主键 DataColumns.ID + " INTEGER PRIMARY KEY," +
DataColumns.MIME_TYPE + " TEXT NOT NULL," + // MIME类型 DataColumns.MIME_TYPE + " TEXT NOT NULL," +
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 关联的笔记ID DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," +
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建日期 NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +
NoteColumns.MODIFIED_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.CONTENT + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA1 + " INTEGER," + // 数据字段1 DataColumns.DATA1 + " INTEGER," +
DataColumns.DATA2 + " INTEGER," + // 数据字段2 DataColumns.DATA2 + " INTEGER," +
DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + // 数据字段3 DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + // 数据字段4 DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + // 数据字段5 DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
")"; ")";
// 创建数据表中NOTE_ID字段的索引
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " + "CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
// 当更新笔记时增加文件夹的笔记计数的触发器 /**
* Increase folder's note count when move note to the folder
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_update "+ "CREATE TRIGGER increase_folder_count_on_update "+
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " + " BEGIN " +
" UPDATE " + TABLE.NOTE + " UPDATE " + TABLE.NOTE +
@ -101,7 +94,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END"; " END";
// 当从文件夹中移动笔记时减少文件夹的笔记计数的触发器 /**
* Decrease folder's note count when move note from folder
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " + "CREATE TRIGGER decrease_folder_count_on_update " +
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
@ -112,7 +107,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +
" END"; " END";
// 当在文件夹中插入新笔记时增加文件夹的笔记计数的触发器 /**
* Increase folder's note count when insert new note to the folder
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " + "CREATE TRIGGER increase_folder_count_on_insert " +
" AFTER INSERT ON " + TABLE.NOTE + " AFTER INSERT ON " + TABLE.NOTE +
@ -122,7 +119,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END"; " END";
// 当从文件夹中删除笔记时减少文件夹的笔记计数的触发器 /**
* Decrease folder's note count when delete note from the folder
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_delete " + "CREATE TRIGGER decrease_folder_count_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE + " AFTER DELETE ON " + TABLE.NOTE +
@ -133,7 +132,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" AND " + NoteColumns.NOTES_COUNT + ">0;" + " AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END"; " END";
// 在插入类型为NOTE的数据时更新笔记内容的触发器 /**
* Update note's content when insert data with type {@link DataConstants#NOTE}
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " + "CREATE TRIGGER update_note_content_on_insert " +
" AFTER INSERT ON " + TABLE.DATA + " AFTER INSERT ON " + TABLE.DATA +
@ -144,7 +145,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END"; " END";
// 当类型为NOTE的数据发生变化时更新笔记内容的触发器 /**
* Update note's content when data with {@link DataConstants#NOTE} type has changed
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " + "CREATE TRIGGER update_note_content_on_update " +
" AFTER UPDATE ON " + TABLE.DATA + " AFTER UPDATE ON " + TABLE.DATA +
@ -155,7 +158,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END"; " END";
// 当类型为NOTE的数据被删除时更新笔记内容的触发器 /**
* Update note's content when data with {@link DataConstants#NOTE} type has deleted
*/
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " + "CREATE TRIGGER update_note_content_on_delete " +
" AFTER delete ON " + TABLE.DATA + " AFTER delete ON " + TABLE.DATA +
@ -166,7 +171,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +
" END"; " END";
// 删除已删除笔记的所有数据的触发器 /**
* Delete datas belong to note which has been deleted
*/
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
"CREATE TRIGGER delete_data_on_delete " + "CREATE TRIGGER delete_data_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE + " AFTER DELETE ON " + TABLE.NOTE +
@ -175,7 +182,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +
" END"; " END";
// 删除已删除文件夹中的笔记的触发器 /**
* Delete notes belong to folder which has been deleted
*/
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
"CREATE TRIGGER folder_delete_notes_on_delete " + "CREATE TRIGGER folder_delete_notes_on_delete " +
" AFTER DELETE ON " + TABLE.NOTE + " AFTER DELETE ON " + TABLE.NOTE +
@ -184,7 +193,9 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END"; " END";
// 当文件夹被移动到垃圾桶时移动文件夹中的笔记的触发器 /**
* Move notes belong to folder which has been moved to trash folder
*/
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
"CREATE TRIGGER folder_move_notes_on_trash " + "CREATE TRIGGER folder_move_notes_on_trash " +
" AFTER UPDATE ON " + TABLE.NOTE + " AFTER UPDATE ON " + TABLE.NOTE +
@ -195,30 +206,26 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END"; " END";
// 构造函数,接受上下文参数
public NotesDatabaseHelper(Context context) { public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION); // 调用父类构造函数 super(context, DB_NAME, null, DB_VERSION);
} }
// 创建笔记表
public void createNoteTable(SQLiteDatabase db) { public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL); // 执行创建表的SQL语句 db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db); // 重新创建触发器 reCreateNoteTableTriggers(db);
createSystemFolder(db); // 创建系统文件夹 createSystemFolder(db);
Log.d(TAG, "note table has been created"); // 日志输出 Log.d(TAG, "note table has been created");
} }
// 重新创建笔记表的所有触发器
private void reCreateNoteTableTriggers(SQLiteDatabase db) { private void reCreateNoteTableTriggers(SQLiteDatabase db) {
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); // 删除触发器 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_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); // 删除触发器 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 delete_data_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); // 删除触发器 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_delete_notes_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); // 删除触发器 db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
// 创建新的触发器
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); 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_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
@ -228,130 +235,128 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
} }
// 创建系统文件夹
private void createSystemFolder(SQLiteDatabase db) { private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues(); // 创建内容值对象 ContentValues values = new ContentValues();
// 为通话记录文件夹插入记录 /**
* call record foler for call notes
*/
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); // 插入记录 db.insert(TABLE.NOTE, null, values);
// 创建根文件夹 /**
values.clear(); // 清空内容值 * root folder which is default folder
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); // 插入记录 db.insert(TABLE.NOTE, null, values);
// 创建临时文件夹 /**
values.clear(); // 清空内容值 * temporary folder which is used for moving note
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); // 插入记录 db.insert(TABLE.NOTE, null, values);
// 创建垃圾桶文件夹 /**
values.clear(); // 清空内容值 * create trash folder
*/
values.clear();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); // 插入记录 db.insert(TABLE.NOTE, null, values);
} }
// 创建数据表
public void createDataTable(SQLiteDatabase db) { public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL); // 执行创建表的SQL语句 db.execSQL(CREATE_DATA_TABLE_SQL);
reCreateDataTableTriggers(db); // 重新创建触发器 reCreateDataTableTriggers(db);
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建索引 db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
Log.d(TAG, "data table has been created"); // 日志输出 Log.d(TAG, "data table has been created");
} }
// 重新创建数据表的所有触发器
private void reCreateDataTableTriggers(SQLiteDatabase db) { 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_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); // 删除触发器 db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete"); // 删除触发器 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_INSERT_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
} }
// 获取NotesDatabaseHelper的单例实例
static synchronized NotesDatabaseHelper getInstance(Context context) { static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) { // 如果实例为空,则创建新的实例 if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context); mInstance = new NotesDatabaseHelper(context);
} }
return mInstance; // 返回实例 return mInstance;
} }
// 当创建数据库时被调用
@Override @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
createNoteTable(db); // 创建笔记表 createNoteTable(db);
createDataTable(db); // 创建数据表 createDataTable(db);
} }
// 当升级数据库时被调用
@Override @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false; // 重创建触发器的标志 boolean reCreateTriggers = false;
boolean skipV2 = false; // 跳过V2的标志 boolean skipV2 = false;
if (oldVersion == 1) { // 如果旧版本是1 if (oldVersion == 1) {
upgradeToV2(db); // 升级到V2 upgradeToV2(db);
skipV2 = true; // 跳过V2的标志设为true skipV2 = true; // this upgrade including the upgrade from v2 to v3
oldVersion++; // 增加版本号 oldVersion++;
} }
if (oldVersion == 2 && !skipV2) { // 如果旧版本是2且没有跳过 if (oldVersion == 2 && !skipV2) {
upgradeToV3(db); // 升级到V3 upgradeToV3(db);
reCreateTriggers = true; // 设置重创建触发器的标志为true reCreateTriggers = true;
oldVersion++; // 增加版本号 oldVersion++;
} }
if (oldVersion == 3) { // 如果旧版本是3 if (oldVersion == 3) {
upgradeToV4(db); // 升级到V4 upgradeToV4(db);
oldVersion++; // 增加版本号 oldVersion++;
} }
if (reCreateTriggers) { // 如果需要重创建触发器 if (reCreateTriggers) {
reCreateNoteTableTriggers(db); // 重新创建笔记表的触发器 reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db); // 重新创建数据表的触发器 reCreateDataTableTriggers(db);
} }
if (oldVersion != newVersion) { // 如果版本不一致,则抛出异常 if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ " fails"); + "fails");
} }
} }
// 升级到V2
private void upgradeToV2(SQLiteDatabase db) { private void upgradeToV2(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); // 删除笔记表 db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); // 删除数据表 db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
createNoteTable(db); // 创建笔记表 createNoteTable(db);
createDataTable(db); // 创建数据表 createDataTable(db);
} }
// 升级到V3
private void upgradeToV3(SQLiteDatabase db) { 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_insert");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// 为笔记添加一个新的字符ID列 // add a column for gtask id
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''"); + " TEXT NOT NULL DEFAULT ''");
// 添加一个垃圾桶系统文件夹 // add a trash system folder
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values); // 插入记录 db.insert(TABLE.NOTE, null, values);
} }
// 升级到V4
private void upgradeToV4(SQLiteDatabase db) { private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0"); // 为笔记表添加版本列 + " INTEGER NOT NULL DEFAULT 0");
} }
} }

@ -16,7 +16,7 @@
package net.micode.notes.data; package net.micode.notes.data;
// 导入需要的Android和本地类
import android.app.SearchManager; import android.app.SearchManager;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.ContentUris; import android.content.ContentUris;
@ -34,317 +34,271 @@ import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE; import net.micode.notes.data.NotesDatabaseHelper.TABLE;
/**
* NotesProvider ContentProvider
*
*/
public class NotesProvider extends ContentProvider { public class NotesProvider extends ContentProvider {
// UriMatcher用于匹配不同的URI请求
private static final UriMatcher mMatcher; private static final UriMatcher mMatcher;
// 数据库助手实例
private NotesDatabaseHelper mHelper; private NotesDatabaseHelper mHelper;
// 日志标签
private static final String TAG = "NotesProvider"; private static final String TAG = "NotesProvider";
// 定义URI常量 private static final int URI_NOTE = 1;
private static final int URI_NOTE = 1; // 笔记 URI private static final int URI_NOTE_ITEM = 2;
private static final int URI_NOTE_ITEM = 2; // 单个笔记 URI private static final int URI_DATA = 3;
private static final int URI_DATA = 3; // 数据 URI private static final int URI_DATA_ITEM = 4;
private static final int URI_DATA_ITEM = 4; // 单个数据 URI
private static final int URI_SEARCH = 5; // 搜索 URI private static final int URI_SEARCH = 5;
private static final int URI_SEARCH_SUGGEST = 6; // 搜索建议 URI private static final int URI_SEARCH_SUGGEST = 6;
// 静态代码块,初始化 URI 匹配器并添加各种 URI 匹配规则
static { static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH); mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); // 笔记的 URI mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); // 单个笔记的 URI mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); // 数据的 URI mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); // 单个数据的 URI mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); // 搜索 URI mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); // 搜索建议 URI mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); // 带参数的搜索建议 URI mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
} }
/** /**
* x'0A' * 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.
*/ */
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," // 笔记 ID private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," // 笔记 ID 作为意图额外数据 + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," // 修剪后的内容作为建议文本1 + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," // 修剪后的内容作为建议文本2 + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + ","
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," // 搜索结果图标 + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + ","
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," // 意图行为 + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; // 笔记内容类型 + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;
// 搜索笔记的 SQL 查询语句
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION
+ " FROM " + TABLE.NOTE // 查询的表为笔记表 + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?" // 根据内容片段进行模糊匹配 + " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER // 排除回收站的内容 + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; // 仅匹配笔记类型 + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
@Override @Override
public boolean onCreate() { public boolean onCreate() {
// 初始化数据库助手
mHelper = NotesDatabaseHelper.getInstance(getContext()); mHelper = NotesDatabaseHelper.getInstance(getContext());
return true; // 创建成功 return true;
} }
@Override @Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) { String sortOrder) {
// 查询方法处理不同的URI请求并返回结果的Cursor
Cursor c = null; Cursor c = null;
SQLiteDatabase db = mHelper.getReadableDatabase(); // 获取可读数据库 SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null; // 用于存储ID String id = null;
switch (mMatcher.match(uri)) {
switch (mMatcher.match(uri)) { // 根据URI匹配不同的查询类型
case URI_NOTE: case URI_NOTE:
// 查询所有笔记 c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder); sortOrder);
break; break;
case URI_NOTE_ITEM: case URI_NOTE_ITEM:
// 查询单个笔记 id = uri.getPathSegments().get(1);
id = uri.getPathSegments().get(1); // 从URI中获取笔记ID
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder); + parseSelection(selection), selectionArgs, null, null, sortOrder);
break; break;
case URI_DATA: case URI_DATA:
// 查询所有数据 c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder); sortOrder);
break; break;
case URI_DATA_ITEM: case URI_DATA_ITEM:
// 查询单个数据 id = uri.getPathSegments().get(1);
id = uri.getPathSegments().get(1); // 从URI中获取数据ID
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder); + parseSelection(selection), selectionArgs, null, null, sortOrder);
break; break;
case URI_SEARCH: case URI_SEARCH:
case URI_SEARCH_SUGGEST: case URI_SEARCH_SUGGEST:
if (sortOrder != null || projection != null) { if (sortOrder != null || projection != null) {
// 搜索请求不支持排序或选择
throw new IllegalArgumentException( throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
} }
String searchString = null; String searchString = null;
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
// 获取搜索建议的参数
if (uri.getPathSegments().size() > 1) { if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1); searchString = uri.getPathSegments().get(1);
} }
} else { } else {
// 从查询参数获取搜索字符串
searchString = uri.getQueryParameter("pattern"); searchString = uri.getQueryParameter("pattern");
} }
if (TextUtils.isEmpty(searchString)) { if (TextUtils.isEmpty(searchString)) {
return null; // 如果搜索字符串为空则返回null return null;
} }
try { try {
// 使用通配符模糊匹配
searchString = String.format("%%%s%%", searchString); searchString = String.format("%%%s%%", searchString);
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[] { searchString }); // 执行搜索查询 new String[] { searchString });
} catch (IllegalStateException ex) { } catch (IllegalStateException ex) {
Log.e(TAG, "got exception: " + ex.toString()); // 记录异常 Log.e(TAG, "got exception: " + ex.toString());
} }
break; break;
default: default:
// 未知的URI抛出异常
throw new IllegalArgumentException("Unknown URI " + uri); throw new IllegalArgumentException("Unknown URI " + uri);
} }
if (c != null) { if (c != null) {
// 设置通知URI数据变化时通知的URI
c.setNotificationUri(getContext().getContentResolver(), uri); c.setNotificationUri(getContext().getContentResolver(), uri);
} }
return c; // 返回查询结果 return c;
} }
@Override @Override
public Uri insert(Uri uri, ContentValues values) { public Uri insert(Uri uri, ContentValues values) {
// 插入数据方法 SQLiteDatabase db = mHelper.getWritableDatabase();
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库 long dataId = 0, noteId = 0, insertedId = 0;
long dataId = 0, noteId = 0, insertedId = 0; // 初始化ID switch (mMatcher.match(uri)) {
switch (mMatcher.match(uri)) { // 根据URI匹配不同的插入类型
case URI_NOTE: case URI_NOTE:
// 插入新的笔记
insertedId = noteId = db.insert(TABLE.NOTE, null, values); insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break; break;
case URI_DATA: case URI_DATA:
// 插入新的数据
if (values.containsKey(DataColumns.NOTE_ID)) { if (values.containsKey(DataColumns.NOTE_ID)) {
// 获取关联的笔记ID
noteId = values.getAsLong(DataColumns.NOTE_ID); noteId = values.getAsLong(DataColumns.NOTE_ID);
} else { } else {
Log.d(TAG, "Wrong data format without note id:" + values.toString()); // 记录错误日志 Log.d(TAG, "Wrong data format without note id:" + values.toString());
} }
insertedId = dataId = db.insert(TABLE.DATA, null, values); insertedId = dataId = db.insert(TABLE.DATA, null, values);
break; break;
default: default:
// 未知的URI抛出异常
throw new IllegalArgumentException("Unknown URI " + uri); throw new IllegalArgumentException("Unknown URI " + uri);
} }
// Notify the note uri
// 当笔记插入成功时通知笔记URI
if (noteId > 0) { if (noteId > 0) {
getContext().getContentResolver().notifyChange( getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
} }
// 当数据插入成功时通知数据URI // Notify the data uri
if (dataId > 0) { if (dataId > 0) {
getContext().getContentResolver().notifyChange( getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
} }
return ContentUris.withAppendedId(uri, insertedId); // 返回新插入项的URI return ContentUris.withAppendedId(uri, insertedId);
} }
@Override @Override
public int delete(Uri uri, String selection, String[] selectionArgs) { public int delete(Uri uri, String selection, String[] selectionArgs) {
// 删除数据方法 int count = 0;
int count = 0; // 计数删除的行数 String id = null;
String id = null; // 用于存储ID SQLiteDatabase db = mHelper.getWritableDatabase();
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库 boolean deleteData = false;
boolean deleteData = false; // 标记是否删除数据 switch (mMatcher.match(uri)) {
switch (mMatcher.match(uri)) { // 根据URI匹配不同的删除类型
case URI_NOTE: case URI_NOTE:
// 删除所有笔记 selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; // 除去系统文件夹
count = db.delete(TABLE.NOTE, selection, selectionArgs); count = db.delete(TABLE.NOTE, selection, selectionArgs);
break; break;
case URI_NOTE_ITEM: case URI_NOTE_ITEM:
// 删除单个笔记 id = uri.getPathSegments().get(1);
id = uri.getPathSegments().get(1); // 从URI中获取笔记ID /**
* ID that smaller than 0 is system folder which is not allowed to
* trash
*/
long noteId = Long.valueOf(id); long noteId = Long.valueOf(id);
if (noteId <= 0) { if (noteId <= 0) {
break; // 系统文件夹不允许删除 break;
} }
count = db.delete(TABLE.NOTE, count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
break; break;
case URI_DATA: case URI_DATA:
// 删除所有数据
count = db.delete(TABLE.DATA, selection, selectionArgs); count = db.delete(TABLE.DATA, selection, selectionArgs);
deleteData = true; // 标记为删除数据 deleteData = true;
break; break;
case URI_DATA_ITEM: case URI_DATA_ITEM:
// 删除单个数据 id = uri.getPathSegments().get(1);
id = uri.getPathSegments().get(1); // 从URI中获取数据ID
count = db.delete(TABLE.DATA, count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
deleteData = true; // 标记为删除数据 deleteData = true;
break; break;
default: default:
// 未知的URI抛出异常
throw new IllegalArgumentException("Unknown URI " + uri); throw new IllegalArgumentException("Unknown URI " + uri);
} }
if (count > 0) { if (count > 0) {
if (deleteData) { if (deleteData) {
// 如果删除了数据通知笔记URI
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
} }
// 通知数据URI
getContext().getContentResolver().notifyChange(uri, null); getContext().getContentResolver().notifyChange(uri, null);
} }
return count; // 返回删除的行数 return count;
} }
@Override @Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// 更新数据方法 int count = 0;
int count = 0; // 计数更新的行数 String id = null;
String id = null; // 用于存储ID SQLiteDatabase db = mHelper.getWritableDatabase();
SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库 boolean updateData = false;
boolean updateData = false; // 标记是否更新数据 switch (mMatcher.match(uri)) {
switch (mMatcher.match(uri)) { // 根据URI匹配不同的更新类型
case URI_NOTE: case URI_NOTE:
// 更新所有笔记 increaseNoteVersion(-1, selection, selectionArgs);
increaseNoteVersion(-1, selection, selectionArgs); // 更新版本
count = db.update(TABLE.NOTE, values, selection, selectionArgs); count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break; break;
case URI_NOTE_ITEM: case URI_NOTE_ITEM:
// 更新单个笔记 id = uri.getPathSegments().get(1);
id = uri.getPathSegments().get(1); // 从URI中获取笔记ID increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); // 更新版本
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs); + parseSelection(selection), selectionArgs);
break; break;
case URI_DATA: case URI_DATA:
// 更新所有数据
count = db.update(TABLE.DATA, values, selection, selectionArgs); count = db.update(TABLE.DATA, values, selection, selectionArgs);
updateData = true; // 标记为更新数据 updateData = true;
break; break;
case URI_DATA_ITEM: case URI_DATA_ITEM:
// 更新单个数据 id = uri.getPathSegments().get(1);
id = uri.getPathSegments().get(1); // 从URI中获取数据ID
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs); + parseSelection(selection), selectionArgs);
updateData = true; // 标记为更新数据 updateData = true;
break; break;
default: default:
// 未知的URI抛出异常
throw new IllegalArgumentException("Unknown URI " + uri); throw new IllegalArgumentException("Unknown URI " + uri);
} }
if (count > 0) { if (count > 0) {
if (updateData) { if (updateData) {
// 如果更新了数据通知笔记URI
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
} }
// 通知数据URI
getContext().getContentResolver().notifyChange(uri, null); getContext().getContentResolver().notifyChange(uri, null);
} }
return count; // 返回更新的行数 return count;
} }
// 辅助方法,用于解析选择条件
private String parseSelection(String selection) { private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); // 如果选择条件不为空则添加 AND return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
} }
// 辅助方法,用于增加笔记版本
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120); StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE "); sql.append("UPDATE ");
sql.append(TABLE.NOTE); sql.append(TABLE.NOTE);
sql.append(" SET "); sql.append(" SET ");
sql.append(NoteColumns.VERSION); sql.append(NoteColumns.VERSION);
sql.append("=" + NoteColumns.VERSION + "+1 "); // 增加版本号 sql.append("=" + NoteColumns.VERSION + "+1 ");
if (id > 0 || !TextUtils.isEmpty(selection)) { if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE "); // 如果有ID或选择条件添加WHERE sql.append(" WHERE ");
} }
if (id > 0) { if (id > 0) {
sql.append(NoteColumns.ID + "=" + String.valueOf(id)); // 添加ID条件 sql.append(NoteColumns.ID + "=" + String.valueOf(id));
} }
if (!TextUtils.isEmpty(selection)) { if (!TextUtils.isEmpty(selection)) {
// 处理选择参数
String selectString = id > 0 ? parseSelection(selection) : selection; String selectString = id > 0 ? parseSelection(selection) : selection;
for (String args : selectionArgs) { for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args); // 替换占位符 selectString = selectString.replaceFirst("\\?", args);
} }
sql.append(selectString); sql.append(selectString);
} }
mHelper.getWritableDatabase().execSQL(sql.toString()); // 执行SQL更新版本号 mHelper.getWritableDatabase().execSQL(sql.toString());
} }
@Override @Override
public String getType(Uri uri) { public String getType(Uri uri) {
// 返回特定URI的MIME类型这里暂时不实现 // TODO Auto-generated method stub
return null; return null;
} }

@ -0,0 +1,82 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
public class MetaData extends Task {
private final static String TAG = MetaData.class.getSimpleName();
private String mRelatedGid = null;
public void setMeta(String gid, JSONObject metaInfo) {
try {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");
}
setNotes(metaInfo.toString());
setName(GTaskStringUtils.META_NOTE_NAME);
}
public String getRelatedGid() {
return mRelatedGid;
}
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js);
if (getNotes() != null) {
try {
JSONObject metaInfo = new JSONObject(getNotes().trim());
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
mRelatedGid = null;
}
}
}
@Override
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");
}
}

@ -0,0 +1,101 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import org.json.JSONObject;
public abstract class Node {
public static final int SYNC_ACTION_NONE = 0;
public static final int SYNC_ACTION_ADD_REMOTE = 1;
public static final int SYNC_ACTION_ADD_LOCAL = 2;
public static final int SYNC_ACTION_DEL_REMOTE = 3;
public static final int SYNC_ACTION_DEL_LOCAL = 4;
public static final int SYNC_ACTION_UPDATE_REMOTE = 5;
public static final int SYNC_ACTION_UPDATE_LOCAL = 6;
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;
public static final int SYNC_ACTION_ERROR = 8;
private String mGid;
private String mName;
private long mLastModified;
private boolean mDeleted;
public Node() {
mGid = null;
mName = "";
mLastModified = 0;
mDeleted = false;
}
public abstract JSONObject getCreateAction(int actionId);
public abstract JSONObject getUpdateAction(int actionId);
public abstract void setContentByRemoteJSON(JSONObject js);
public abstract void setContentByLocalJSON(JSONObject js);
public abstract JSONObject getLocalJSONFromContent();
public abstract int getSyncAction(Cursor c);
public void setGid(String gid) {
this.mGid = gid;
}
public void setName(String name) {
this.mName = name;
}
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
}
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
}
public String getGid() {
return this.mGid;
}
public String getName() {
return this.mName;
}
public long getLastModified() {
return this.mLastModified;
}
public boolean getDeleted() {
return this.mDeleted;
}
}

@ -0,0 +1,189 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
public class SqlData {
private static final String TAG = SqlData.class.getSimpleName();
private static final int INVALID_ID = -99999;
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;
public static final int DATA_MIME_TYPE_COLUMN = 1;
public static final int DATA_CONTENT_COLUMN = 2;
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
private ContentResolver mContentResolver;
private boolean mIsCreate;
private long mDataId;
private String mDataMimeType;
private String mDataContent;
private long mDataContentData1;
private String mDataContentData3;
private ContentValues mDiffDataValues;
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true;
mDataId = INVALID_ID;
mDataMimeType = DataConstants.NOTE;
mDataContent = "";
mDataContentData1 = 0;
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
}
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDiffDataValues = new ContentValues();
}
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
mDataContent = c.getString(DATA_CONTENT_COLUMN);
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
public void setContent(JSONObject js) throws JSONException {
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
}
mDataId = dataId;
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
mDataMimeType = dataMimeType;
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
mDataContent = dataContent;
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
mDataContentData1 = dataContentData1;
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
mDataContentData3 = dataContentData3;
}
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
JSONObject 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) {
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
try {
mDataId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
} else {
if (mDiffDataValues.size() > 0) {
int result = 0;
if (!validateVersion) {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
String.valueOf(noteId), String.valueOf(version)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
}
mDiffDataValues.clear();
mIsCreate = false;
}
public long getId() {
return mDataId;
}
}

@ -0,0 +1,505 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.appwidget.AppWidgetManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.tool.ResourceParser;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
public class SqlNote {
private static final String TAG = SqlNote.class.getSimpleName();
private static final int INVALID_ID = -99999;
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;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private int mHasAttachment;
private long mModifiedDate;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private long mOriginParent;
private long mVersion;
private ContentValues mDiffNoteValues;
private ArrayList<SqlData> mDataList;
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>();
}
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();
}
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();
}
private void loadFromCursor(long id) {
Cursor c = null;
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(id)
}, null);
if (c != null) {
c.moveToNext();
loadFromCursor(c);
} else {
Log.w(TAG, "loadFromCursor: cursor = null");
}
} finally {
if (c != null)
c.close();
}
}
private void loadFromCursor(Cursor c) {
mId = c.getLong(ID_COLUMN);
mAlertDate = c.getLong(ALERTED_DATE_COLUMN);
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = c.getLong(CREATED_DATE_COLUMN);
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN);
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN);
mParentId = c.getLong(PARENT_ID_COLUMN);
mSnippet = c.getString(SNIPPET_COLUMN);
mType = c.getInt(TYPE_COLUMN);
mWidgetId = c.getInt(WIDGET_ID_COLUMN);
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
mVersion = c.getLong(VERSION_COLUMN);
}
private void loadDataContent() {
Cursor c = null;
mDataList.clear();
try {
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] {
String.valueOf(mId)
}, null);
if (c != null) {
if (c.getCount() == 0) {
Log.w(TAG, "it seems that the note has not data");
return;
}
while (c.moveToNext()) {
SqlData data = new SqlData(mContext, c);
mDataList.add(data);
}
} else {
Log.w(TAG, "loadDataContent: cursor = null");
}
} finally {
if (c != null)
c.close();
}
}
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
.getString(NoteColumns.SNIPPET) : "";
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
mSnippet = snippet;
int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE)
: Notes.TYPE_NOTE;
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
mType = type;
} else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) {
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID;
if (mIsCreate || mId != id) {
mDiffNoteValues.put(NoteColumns.ID, id);
}
mId = id;
long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note
.getLong(NoteColumns.ALERTED_DATE) : 0;
if (mIsCreate || mAlertDate != alertDate) {
mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate);
}
mAlertDate = alertDate;
int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note
.getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext);
if (mIsCreate || mBgColorId != bgColorId) {
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId);
}
mBgColorId = bgColorId;
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;
}
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();
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;
}
public void setParentId(long id) {
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
}
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
}
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
}
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
}
public long getId() {
return mId;
}
public long getParentId() {
return mParentId;
}
public String getSnippet() {
return mSnippet;
}
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE;
}
public void commit(boolean validateVersion) {
if (mIsCreate) {
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID);
}
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
try {
mId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
if (mId == 0) {
throw new IllegalStateException("Create thread id failed");
}
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, false, -1);
}
}
} else {
if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) {
Log.e(TAG, "No such note");
throw new IllegalStateException("Try to update note with invalid id");
}
if (mDiffNoteValues.size() > 0) {
mVersion ++;
int result = 0;
if (!validateVersion) {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?)", new String[] {
String.valueOf(mId)
});
} else {
result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "("
+ NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)",
new String[] {
String.valueOf(mId), String.valueOf(mVersion)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, validateVersion, mVersion);
}
}
}
// refresh local info
loadFromCursor(mId);
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues.clear();
mIsCreate = false;
}
}

@ -0,0 +1,351 @@
/*
* 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();
mCompleted = false;
mNotes = null;
mPriorSibling = null;
mParent = null;
mMetaInfo = null;
}
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, 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) {
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;
}
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;
}
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");
}
}
}
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}
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++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
setName(data.getString(DataColumns.CONTENT));
break;
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
public JSONObject getLocalJSONFromContent() {
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 {
mMetaInfo = new JSONObject(metaData.getNotes());
} catch (JSONException e) {
Log.w(TAG, e.toString());
mMetaInfo = null;
}
}
}
public int getSyncAction(Cursor c) {
try {
JSONObject noteInfo = null;
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
}
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() {
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;
}
}

@ -16,169 +16,156 @@
package net.micode.notes.gtask.data; package net.micode.notes.gtask.data;
// 导入所需的类
import android.database.Cursor; import android.database.Cursor;
import android.util.Log; import android.util.Log;
import net.micode.notes.data.Notes; import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException; import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils; import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
// TaskList类继承自Node类表示一个任务列表
public class TaskList extends Node { public class TaskList extends Node {
// 定义日志标签
private static final String TAG = TaskList.class.getSimpleName(); private static final String TAG = TaskList.class.getSimpleName();
// 任务列表的索引
private int mIndex; private int mIndex;
// 存储子任务的列表
private ArrayList<Task> mChildren; private ArrayList<Task> mChildren;
// TaskList的构造函数初始化子任务列表和索引
public TaskList() { public TaskList() {
super(); super();
mChildren = new ArrayList<Task>(); mChildren = new ArrayList<Task>();
mIndex = 1; // 默认索引为1 mIndex = 1;
} }
// 获取创建任务列表的JSON对象
public JSONObject getCreateAction(int actionId) { public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject(); // 创建一个新的JSON对象 JSONObject js = new JSONObject();
try { try {
// 设置动作类型为创建 // action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// 设置动作ID // action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置索引 // index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
// 创建实体的变化对象 // entity_delta
JSONObject entity = new JSONObject(); JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 设置名称 entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 创建者ID entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP); // 实体类型 GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 将实体变化对象放入主对象中 js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, e.toString()); // 打印错误日志 Log.e(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-create jsonobject"); // 抛出异常 throw new ActionFailureException("fail to generate tasklist-create jsonobject");
} }
return js; // 返回生成的JSON对象 return js;
} }
// 获取更新任务列表的JSON对象
public JSONObject getUpdateAction(int actionId) { public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject(); // 创建一个新的JSON对象 JSONObject js = new JSONObject();
try { try {
// 设置动作类型为更新 // action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// 设置动作ID // action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// 设置任务列表ID // id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// 创建实体的变化对象 // entity_delta
JSONObject entity = new JSONObject(); JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 设置名称 entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // 设置删除状态 entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 将实体变化对象放入主对象中 js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, e.toString()); // 打印错误日志 Log.e(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
throw new ActionFailureException("fail to generate tasklist-update jsonobject"); // 抛出异常 throw new ActionFailureException("fail to generate tasklist-update jsonobject");
} }
return js; // 返回生成的JSON对象 return js;
} }
// 根据远程JSON设置任务列表的内容
public void setContentByRemoteJSON(JSONObject js) { public void setContentByRemoteJSON(JSONObject js) {
if (js != null) { if (js != null) {
try { try {
// 设置任务列表ID // id
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
} }
// 设置最后修改时间 // last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
} }
// 设置名称 // name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
} }
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, e.toString()); // 打印错误日志 Log.e(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
throw new ActionFailureException("fail to get tasklist content from jsonobject"); // 抛出异常 throw new ActionFailureException("fail to get tasklist content from jsonobject");
} }
} }
} }
// 根据本地JSON设置任务列表的内容
public void setContentByLocalJSON(JSONObject js) { public void setContentByLocalJSON(JSONObject js) {
// 检查JSON对象是否有效
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); // 发出警告日志 Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
} }
try { try {
// 获取任务列表的文件夹对象
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
// 根据文件夹类型设置名称
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
String name = folder.getString(NoteColumns.SNIPPET); String name = folder.getString(NoteColumns.SNIPPET);
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); // 设置文件夹名称 setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name);
} else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { } else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
// 检查系统文件夹ID并设置相应名称
if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT);
else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER) else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE); + GTaskStringUtils.FOLDER_CALL_NOTE);
else else
Log.e(TAG, "invalid system folder"); // 打印错误日志 Log.e(TAG, "invalid system folder");
} else { } else {
Log.e(TAG, "error type"); // 打印错误日志 Log.e(TAG, "error type");
} }
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, e.toString()); // 打印错误日志 Log.e(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
} }
} }
// 从内容生成本地JSON对象
public JSONObject getLocalJSONFromContent() { public JSONObject getLocalJSONFromContent() {
try { try {
JSONObject js = new JSONObject(); // 创建一个新的JSON对象 JSONObject js = new JSONObject();
JSONObject folder = new JSONObject(); // 创建文件夹的JSON对象 JSONObject folder = new JSONObject();
String folderName = getName(); // 获取任务列表名称 String folderName = getName();
if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX))
folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(), folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(),
folderName.length()); // 去掉前缀 folderName.length());
// 设置文件夹名称和类型
folder.put(NoteColumns.SNIPPET, folderName); folder.put(NoteColumns.SNIPPET, folderName);
if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT)
|| folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE))
@ -186,183 +173,171 @@ public class TaskList extends Node {
else else
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
js.put(GTaskStringUtils.META_HEAD_NOTE, folder); // 将文件夹对象放入主对象中 js.put(GTaskStringUtils.META_HEAD_NOTE, folder);
return js; // 返回生成的JSON对象 return js;
} catch (JSONException e) { } catch (JSONException e) {
Log.e(TAG, e.toString()); // 打印错误日志 Log.e(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
return null; // 出现异常时返回null return null;
} }
} }
// 获取同步操作
public int getSyncAction(Cursor c) { public int getSyncAction(Cursor c) {
try { try {
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// 本地没有更新 // there is no local update
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 本地和远程都没有更新 // no update both side
return SYNC_ACTION_NONE; // 返回无操作 return SYNC_ACTION_NONE;
} else { } else {
// 将远程应用到本地 // apply remote to local
return SYNC_ACTION_UPDATE_LOCAL; return SYNC_ACTION_UPDATE_LOCAL;
} }
} else { } else {
// 校验任务ID // validate gtask id
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match"); // 打印错误日志 Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR; // 返回错误状态 return SYNC_ACTION_ERROR;
} }
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// 仅本地修改 // local modification only
return SYNC_ACTION_UPDATE_REMOTE; return SYNC_ACTION_UPDATE_REMOTE;
} else { } else {
// 对于文件夹冲突,采用本地修改 // for folder conflicts, just apply local modification
return SYNC_ACTION_UPDATE_REMOTE; return SYNC_ACTION_UPDATE_REMOTE;
} }
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, e.toString()); // 打印错误日志 Log.e(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
} }
return SYNC_ACTION_ERROR; // 出现异常时返回错误状态 return SYNC_ACTION_ERROR;
} }
// 获取子任务数量
public int getChildTaskCount() { public int getChildTaskCount() {
return mChildren.size(); // 返回子任务列表大小 return mChildren.size();
} }
// 添加子任务
public boolean addChildTask(Task task) { public boolean addChildTask(Task task) {
boolean ret = false; boolean ret = false;
if (task != null && !mChildren.contains(task)) { if (task != null && !mChildren.contains(task)) {
ret = mChildren.add(task); // 尝试添加任务 ret = mChildren.add(task);
if (ret) { if (ret) {
// 需要设置前一个兄弟任务和父任务 // need to set prior sibling and parent
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren task.setPriorSibling(mChildren.isEmpty() ? null : mChildren
.get(mChildren.size() - 1)); .get(mChildren.size() - 1));
task.setParent(this); // 设置父任务 task.setParent(this);
} }
} }
return ret; // 返回添加结果 return ret;
} }
// 根据索引添加子任务
public boolean addChildTask(Task task, int index) { public boolean addChildTask(Task task, int index) {
if (index < 0 || index > mChildren.size()) { if (index < 0 || index > mChildren.size()) {
Log.e(TAG, "add child task: invalid index"); // 打印错误日志 Log.e(TAG, "add child task: invalid index");
return false; // 返回失败 return false;
} }
int pos = mChildren.indexOf(task); // 查找任务在列表中的位置 int pos = mChildren.indexOf(task);
if (task != null && pos == -1) { if (task != null && pos == -1) {
mChildren.add(index, task); // 根据索引添加任务 mChildren.add(index, task);
// 更新任务列表 // update the task list
Task preTask = null; Task preTask = null;
Task afterTask = null; Task afterTask = null;
if (index != 0) if (index != 0)
preTask = mChildren.get(index - 1); // 获取前一个任务 preTask = mChildren.get(index - 1);
if (index != mChildren.size() - 1) if (index != mChildren.size() - 1)
afterTask = mChildren.get(index + 1); // 获取后一个任务 afterTask = mChildren.get(index + 1);
task.setPriorSibling(preTask); // 设置前一个兄弟任务 task.setPriorSibling(preTask);
if (afterTask != null) if (afterTask != null)
afterTask.setPriorSibling(task); // 设置后一个兄弟任务 afterTask.setPriorSibling(task);
} }
return true; // 返回成功 return true;
} }
// 移除子任务
public boolean removeChildTask(Task task) { public boolean removeChildTask(Task task) {
boolean ret = false; boolean ret = false;
int index = mChildren.indexOf(task); // 查找任务在列表中的位置 int index = mChildren.indexOf(task);
if (index != -1) { if (index != -1) {
ret = mChildren.remove(task); // 尝试移除任务 ret = mChildren.remove(task);
if (ret) { if (ret) {
// 重置前一个兄弟任务和父任务 // reset prior sibling and parent
task.setPriorSibling(null); task.setPriorSibling(null);
task.setParent(null); task.setParent(null);
// 更新任务列表 // update the task list
if (index != mChildren.size()) { if (index != mChildren.size()) {
mChildren.get(index).setPriorSibling( mChildren.get(index).setPriorSibling(
index == 0 ? null : mChildren.get(index - 1)); // 更新前一个兄弟任务 index == 0 ? null : mChildren.get(index - 1));
} }
} }
} }
return ret; // 返回移除结果 return ret;
} }
// 移动子任务
public boolean moveChildTask(Task task, int index) { public boolean moveChildTask(Task task, int index) {
if (index < 0 || index >= mChildren.size()) { if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "move child task: invalid index"); // 打印错误日志 Log.e(TAG, "move child task: invalid index");
return false; // 返回失败 return false;
} }
int pos = mChildren.indexOf(task); // 查找任务在列表中的位置 int pos = mChildren.indexOf(task);
if (pos == -1) { if (pos == -1) {
Log.e(TAG, "move child task: the task should in the list"); // 打印错误日志 Log.e(TAG, "move child task: the task should in the list");
return false; // 返回失败 return false;
} }
if (pos == index) if (pos == index)
return true; // 位置不变返回成功 return true;
return (removeChildTask(task) && addChildTask(task, index)); // 移除并重新添加任务 return (removeChildTask(task) && addChildTask(task, index));
} }
// 根据GID查找子任务
public Task findChildTaskByGid(String gid) { public Task findChildTaskByGid(String gid) {
for (int i = 0; i < mChildren.size(); i++) { for (int i = 0; i < mChildren.size(); i++) {
Task t = mChildren.get(i); Task t = mChildren.get(i);
if (t.getGid().equals(gid)) { if (t.getGid().equals(gid)) {
return t; // 找到任务返回 return t;
} }
} }
return null; // 未找到返回null return null;
} }
// 获取子任务索引
public int getChildTaskIndex(Task task) { public int getChildTaskIndex(Task task) {
return mChildren.indexOf(task); // 返回任务在列表中的位置 return mChildren.indexOf(task);
} }
// 根据索引获取子任务
public Task getChildTaskByIndex(int index) { public Task getChildTaskByIndex(int index) {
if (index < 0 || index >= mChildren.size()) { if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "getTaskByIndex: invalid index"); // 打印错误日志 Log.e(TAG, "getTaskByIndex: invalid index");
return null; // 返回null return null;
} }
return mChildren.get(index); // 返回任务 return mChildren.get(index);
} }
// 根据GID获取子任务
public Task getChilTaskByGid(String gid) { public Task getChilTaskByGid(String gid) {
for (Task task : mChildren) { for (Task task : mChildren) {
if (task.getGid().equals(gid)) if (task.getGid().equals(gid))
return task; // 找到任务返回 return task;
} }
return null; // 未找到返回null return null;
} }
// 获取子任务列表
public ArrayList<Task> getChildTaskList() { public ArrayList<Task> getChildTaskList() {
return this.mChildren; // 返回子任务列表 return this.mChildren;
} }
// 设置索引
public void setIndex(int index) { public void setIndex(int index) {
this.mIndex = index; // 设置任务列表索引 this.mIndex = index;
} }
// 获取索引
public int getIndex() { public int getIndex() {
return this.mIndex; // 返回任务列表索引 return this.mIndex;
} }
} }

@ -0,0 +1,33 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.exception;
public class ActionFailureException extends RuntimeException {
private static final long serialVersionUID = 4425249765923293627L;
public ActionFailureException() {
super();
}
public ActionFailureException(String paramString) {
super(paramString);
}
public ActionFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}
}

@ -0,0 +1,33 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.exception;
public class NetworkFailureException extends Exception {
private static final long serialVersionUID = 2107610287180234136L;
public NetworkFailureException() {
super();
}
public NetworkFailureException(String paramString) {
super(paramString);
}
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}
}

@ -0,0 +1,123 @@
/*
* 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;
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
public interface OnCompleteListener {
void onComplete();
}
private Context mContext;
private NotificationManager mNotifiManager;
private GTaskManager mTaskManager;
private OnCompleteListener mOnCompleteListener;
public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context;
mOnCompleteListener = listener;
mNotifiManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
mTaskManager = GTaskManager.getInstance();
}
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;
if (tickerId != R.string.ticker_success) {
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);
}
@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]);
}
}
@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) {
new Thread(new Runnable() {
public void run() {
mOnCompleteListener.onComplete();
}
}).start();
}
}
}

@ -0,0 +1,585 @@
/*
* 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 {
private static final String TAG = GTaskClient.class.getSimpleName();
private static final String GTASK_URL = "https://mail.google.com/tasks/";
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
private static GTaskClient mInstance = null;
private DefaultHttpClient mHttpClient;
private String mGetUrl;
private String mPostUrl;
private long mClientVersion;
private boolean mLoggedin;
private long mLastLoginTime;
private int mActionId;
private Account mAccount;
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;
}
public static synchronized GTaskClient getInstance() {
if (mInstance == null) {
mInstance = new GTaskClient();
}
return mInstance;
}
public boolean login(Activity activity) {
// we suppose that the cookie would expire after 5 minutes
// then we need to re-login
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;
}
if (mLoggedin) {
Log.d(TAG, "already logged in");
return true;
}
mLastLoginTime = System.currentTimeMillis();
String authToken = loginGoogleAccount(activity, false);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
// 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;
}
}
// 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;
}
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
AccountManager accountManager = AccountManager.get(activity);
Account[] accounts = accountManager.getAccountsByType("com.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;
}
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) {
// maybe the auth token is out of date, now let's invalidate the
// token and try again
authToken = loginGoogleAccount(activity, true);
if (authToken == null) {
Log.e(TAG, "login google account failed");
return false;
}
if (!loginGtask(authToken)) {
Log.e(TAG, "login gtask failed");
return false;
}
}
return true;
}
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000;
int timeoutSocket = 15000;
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
mHttpClient = new DefaultHttpClient(httpParameters);
BasicCookieStore localBasicCookieStore = new BasicCookieStore();
mHttpClient.setCookieStore(localBasicCookieStore);
HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false);
// login gtask
try {
String loginUrl = mGetUrl + "?auth=" + authToken;
HttpGet httpGet = new HttpGet(loginUrl);
HttpResponse response = null;
response = mHttpClient.execute(httpGet);
// get the cookie now
List<Cookie> cookies = mHttpClient.getCookieStore().getCookies();
boolean hasAuthCookie = false;
for (Cookie cookie : cookies) {
if (cookie.getName().contains("GTL")) {
hasAuthCookie = true;
}
}
if (!hasAuthCookie) {
Log.w(TAG, "it seems that there is no auth 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());
e.printStackTrace();
return false;
} catch (Exception e) {
// simply catch all exceptions
Log.e(TAG, "httpget gtask_url failed");
return false;
}
return true;
}
private int getActionId() {
return mActionId++;
}
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
httpPost.setHeader("AT", "1");
return httpPost;
}
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
if (entity.getContentEncoding() != null) {
contentEncoding = entity.getContentEncoding().getValue();
Log.d(TAG, "encoding: " + contentEncoding);
}
InputStream input = entity.getContent();
if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) {
input = new GZIPInputStream(entity.getContent());
} else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) {
Inflater inflater = new Inflater(true);
input = new InflaterInputStream(entity.getContent(), inflater);
}
try {
InputStreamReader isr = new InputStreamReader(input);
BufferedReader br = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
while (true) {
String buff = br.readLine();
if (buff == null) {
return sb.toString();
}
sb = sb.append(buff);
}
} finally {
input.close();
}
}
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
throw new ActionFailureException("not logged in");
}
HttpPost httpPost = createHttpPost();
try {
LinkedList<BasicNameValuePair> list = new LinkedList<BasicNameValuePair>();
list.add(new BasicNameValuePair("r", js.toString()));
UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8");
httpPost.setEntity(entity);
// execute the post
HttpResponse response = mHttpClient.execute(httpPost);
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) {
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 {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// action_list
actionList.put(task.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("create task: handing jsonobject failed");
}
}
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate();
try {
JSONObject jsPost = new JSONObject();
JSONArray actionList = new JSONArray();
// action_list
actionList.put(tasklist.getCreateAction(getActionId()));
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
// post
JSONObject jsResponse = postRequest(jsPost);
JSONObject jsResult = (JSONObject) jsResponse.getJSONArray(
GTaskStringUtils.GTASK_JSON_RESULTS).get(0);
tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID));
} catch (JSONException e) {
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();
// action_list
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
postRequest(jsPost);
mUpdateArray = null;
} catch (JSONException e) {
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) {
// too many update items may result in an error
// set max to 10 items
if (mUpdateArray != null && mUpdateArray.length() > 10) {
commitUpdate();
}
if (mUpdateArray == null)
mUpdateArray = new JSONArray();
mUpdateArray.put(node.getUpdateAction(getActionId()));
}
}
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
commitUpdate();
try {
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());
action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid());
if (preParent == curParent && task.getPriorSibling() != null) {
// 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());
action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid());
if (preParent != curParent) {
// 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);
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 {
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);
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 = 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);
}
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");
}
}
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate();
try {
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());
action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid);
action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false);
actionList.put(action);
jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList);
// client_version
jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion);
JSONObject jsResponse = postRequest(jsPost);
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;
}
}

@ -1,54 +1,31 @@
/* /*
* (c) 2010-2011, MiCode (www.micode.net) * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
* *
* Apache 2.0 * 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 * 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; package net.micode.notes.gtask.remote;
// 导入需要的类 import android.app.Activity;
import android.app.Activity; // 用于活动管理的类 import android.content.ContentResolver;
import android.content.ContentResolver; // 用于内容提供者的类 import android.content.ContentUris;
import android.content.ContentUris; // 处理内容 URI 的工具类 import android.content.ContentValues;
import android.content.ContentValues; // 包含 ContentProvider 插入或更新所需值的类 import android.content.Context;
import android.content.Context; // 应用上下文类 import android.database.Cursor;
import android.database.Cursor; // 数据库查询结果的类 import android.util.Log;
import android.util.Log; // 记录日志的工具类
// 导入项目中的相关类
import net.micode.notes.R; import net.micode.notes.R;
import net.micode.notes.data.Notes; import net.micode.notes.data.Notes;
<<<<<<< HEAD
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; // SQL 笔记类
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; // GTask 字符串工具类
import org.json.JSONArray; // JSON 数组类
import org.json.JSONException; // JSON 异常类
import org.json.JSONObject; // JSON 对象类
import java.util.HashMap; // 哈希表实现
import java.util.HashSet; // 哈希集合实现
import java.util.Iterator; // 迭代器类
=======
import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.data.MetaData; import net.micode.notes.gtask.data.MetaData;
@ -70,49 +47,46 @@ import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
// GTaskManager类用于管理与Google Tasks的同步操作包括初始化任务列表、同步内容、处理各种同步类型的操作等功能
public class GTaskManager { public class GTaskManager {
private static final String TAG = GTaskManager.class.getSimpleName(); private static final String TAG = GTaskManager.class.getSimpleName();
// 同步成功状态码
public static final int STATE_SUCCESS = 0; public static final int STATE_SUCCESS = 0;
// 网络错误状态码
public static final int STATE_NETWORK_ERROR = 1; public static final int STATE_NETWORK_ERROR = 1;
// 内部错误状态码
public static final int STATE_INTERNAL_ERROR = 2; public static final int STATE_INTERNAL_ERROR = 2;
// 同步正在进行状态码
public static final int STATE_SYNC_IN_PROGRESS = 3; public static final int STATE_SYNC_IN_PROGRESS = 3;
// 同步被取消状态码
public static final int STATE_SYNC_CANCELLED = 4; public static final int STATE_SYNC_CANCELLED = 4;
private static GTaskManager mInstance = null; private static GTaskManager mInstance = null;
// 关联的Activity用于获取授权令牌等操作
private Activity mActivity; private Activity mActivity;
// 应用上下文
private Context mContext; private Context mContext;
// 用于操作内容提供器,进行数据查询、更新等操作
private ContentResolver mContentResolver; private ContentResolver mContentResolver;
// 标记是否正在同步
private boolean mSyncing; private boolean mSyncing;
// 标记同步是否被取消
private boolean mCancelled; private boolean mCancelled;
// 存储Google Tasks任务列表的哈希表以任务列表的全局唯一IDGID为键任务列表对象为值
private HashMap<String, TaskList> mGTaskListHashMap; private HashMap<String, TaskList> mGTaskListHashMap;
// 存储Google Tasks节点任务、任务列表等的哈希表以节点的全局唯一IDGID为键节点对象为值
private HashMap<String, Node> mGTaskHashMap; private HashMap<String, Node> mGTaskHashMap;
// 存储元数据的哈希表以相关的全局唯一ID为键元数据对象为值
private HashMap<String, MetaData> mMetaHashMap; private HashMap<String, MetaData> mMetaHashMap;
// 元数据对应的任务列表
private TaskList mMetaList; private TaskList mMetaList;
// 存储本地已删除记录的ID集合用于后续清理本地数据
private HashSet<Long> mLocalDeleteIdMap; private HashSet<Long> mLocalDeleteIdMap;
// 存储从Google Tasks的全局唯一IDGID到本地记录IDNID的映射关系
private HashMap<String, Long> mGidToNid; private HashMap<String, Long> mGidToNid;
// 存储从本地记录IDNID到Google Tasks的全局唯一IDGID的映射关系
private HashMap<Long, String> mNidToGid; private HashMap<Long, String> mNidToGid;
// 私有构造函数,初始化相关成员变量
private GTaskManager() { private GTaskManager() {
mSyncing = false; mSyncing = false;
mCancelled = false; mCancelled = false;
@ -125,7 +99,6 @@ public class GTaskManager {
mNidToGid = new HashMap<Long, String>(); mNidToGid = new HashMap<Long, String>();
} }
// 获取GTaskManager的单例实例
public static synchronized GTaskManager getInstance() { public static synchronized GTaskManager getInstance() {
if (mInstance == null) { if (mInstance == null) {
mInstance = new GTaskManager(); mInstance = new GTaskManager();
@ -133,13 +106,11 @@ public class GTaskManager {
return mInstance; return mInstance;
} }
// 设置关联的Activity上下文主要用于获取授权令牌等相关操作
public synchronized void setActivityContext(Activity activity) { public synchronized void setActivityContext(Activity activity) {
// used for getting authtoken // used for getting authtoken
mActivity = activity; mActivity = activity;
} }
// 执行同步操作与Google Tasks进行数据同步
public int sync(Context context, GTaskASyncTask asyncTask) { public int sync(Context context, GTaskASyncTask asyncTask) {
if (mSyncing) { if (mSyncing) {
Log.d(TAG, "Sync is in progress"); Log.d(TAG, "Sync is in progress");
@ -149,7 +120,6 @@ public class GTaskManager {
mContentResolver = mContext.getContentResolver(); mContentResolver = mContext.getContentResolver();
mSyncing = true; mSyncing = true;
mCancelled = false; mCancelled = false;
// 清除之前同步相关的数据缓存如任务列表、节点、元数据等的哈希表以及ID映射关系等
mGTaskListHashMap.clear(); mGTaskListHashMap.clear();
mGTaskHashMap.clear(); mGTaskHashMap.clear();
mMetaHashMap.clear(); mMetaHashMap.clear();
@ -161,18 +131,18 @@ public class GTaskManager {
GTaskClient client = GTaskClient.getInstance(); GTaskClient client = GTaskClient.getInstance();
client.resetUpdateArray(); client.resetUpdateArray();
// 登录Google Tasks如果取消同步则不再进行登录操作如果登录失败则抛出网络异常 // login google task
if (!mCancelled) { if (!mCancelled) {
if (!client.login(mActivity)) { if (!client.login(mActivity)) {
throw new NetworkFailureException("login google task failed"); throw new NetworkFailureException("login google task failed");
} }
} }
// 发布同步进度信息,初始化任务列表阶段 // get the task list from google
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList(); initGTaskList();
// 发布同步进度信息,进行内容同步阶段 // do content sync work
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent(); syncContent();
} catch (NetworkFailureException e) { } catch (NetworkFailureException e) {
@ -186,7 +156,6 @@ public class GTaskManager {
e.printStackTrace(); e.printStackTrace();
return STATE_INTERNAL_ERROR; return STATE_INTERNAL_ERROR;
} finally { } finally {
// 无论同步是否成功,最终都清除同步相关的数据缓存
mGTaskListHashMap.clear(); mGTaskListHashMap.clear();
mGTaskHashMap.clear(); mGTaskHashMap.clear();
mMetaHashMap.clear(); mMetaHashMap.clear();
@ -199,16 +168,14 @@ public class GTaskManager {
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
} }
// 初始化Google Tasks任务列表相关信息包括获取任务列表、元数据列表以及加载任务等操作
private void initGTaskList() throws NetworkFailureException { private void initGTaskList() throws NetworkFailureException {
if (mCancelled) if (mCancelled)
return; return;
GTaskClient client = GTaskClient.getInstance(); GTaskClient client = GTaskClient.getInstance();
try { try {
// 从Google Tasks获取任务列表信息返回JSON数组形式的数据
JSONArray jsTaskLists = client.getTaskLists(); JSONArray jsTaskLists = client.getTaskLists();
// 先初始化元数据列表,查找名为特定前缀加上"meta"的任务列表作为元数据列表 // init meta list first
mMetaList = null; mMetaList = null;
for (int i = 0; i < jsTaskLists.length(); i++) { for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i); JSONObject object = jsTaskLists.getJSONObject(i);
@ -220,7 +187,7 @@ public class GTaskManager {
mMetaList = new TaskList(); mMetaList = new TaskList();
mMetaList.setContentByRemoteJSON(object); mMetaList.setContentByRemoteJSON(object);
// 加载元数据,获取元数据列表对应的任务列表中的具体元数据信息 // load meta data
JSONArray jsMetas = client.getTaskList(gid); JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) { for (int j = 0; j < jsMetas.length(); j++) {
object = (JSONObject) jsMetas.getJSONObject(j); object = (JSONObject) jsMetas.getJSONObject(j);
@ -236,7 +203,7 @@ public class GTaskManager {
} }
} }
// 如果元数据列表不存在则创建一个新的元数据列表并发送到Google Tasks服务器创建对应的任务列表 // create meta list if not existed
if (mMetaList == null) { if (mMetaList == null) {
mMetaList = new TaskList(); mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
@ -244,7 +211,7 @@ public class GTaskManager {
GTaskClient.getInstance().createTaskList(mMetaList); GTaskClient.getInstance().createTaskList(mMetaList);
} }
// 初始化普通任务列表,遍历获取到的任务列表信息,创建任务列表对象并加载对应的任务信息 // init task list
for (int i = 0; i < jsTaskLists.length(); i++) { for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i); JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
@ -258,7 +225,7 @@ public class GTaskManager {
mGTaskListHashMap.put(gid, tasklist); mGTaskListHashMap.put(gid, tasklist);
mGTaskHashMap.put(gid, tasklist); mGTaskHashMap.put(gid, tasklist);
// 加载任务,获取当前任务列表对应的任务信息 // load tasks
JSONArray jsTasks = client.getTaskList(gid); JSONArray jsTasks = client.getTaskList(gid);
for (int j = 0; j < jsTasks.length(); j++) { for (int j = 0; j < jsTasks.length(); j++) {
object = (JSONObject) jsTasks.getJSONObject(j); object = (JSONObject) jsTasks.getJSONObject(j);
@ -280,7 +247,6 @@ public class GTaskManager {
} }
} }
// 进行内容同步操作处理本地与Google Tasks之间的数据同步包括本地删除、文件夹同步、数据库中已有记录的同步等情况
private void syncContent() throws NetworkFailureException { private void syncContent() throws NetworkFailureException {
int syncType; int syncType;
Cursor c = null; Cursor c = null;
@ -293,7 +259,7 @@ public class GTaskManager {
return; return;
} }
// 处理本地已删除的笔记记录查询本地回收站文件夹中的记录对比Google Tasks中的对应节点进行相应的同步操作 // for local deleted note
try { try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id=?)", new String[] { "(type<>? AND parent_id=?)", new String[] {
@ -320,10 +286,10 @@ public class GTaskManager {
} }
} }
// 同步文件夹相关信息 // sync folder first
syncFolder(); syncFolder();
// 处理数据库中已存在的笔记记录根据其在Google Tasks中的对应情况确定同步类型然后进行相应的同步操作 // for note existing in database
try { try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] { "(type=? AND parent_id<>?)", new String[] {
@ -340,10 +306,10 @@ public class GTaskManager {
syncType = node.getSyncAction(c); syncType = node.getSyncAction(c);
} else { } else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地新增情况 // local add
syncType = Node.SYNC_ACTION_ADD_REMOTE; syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else { } else {
// 远程已删除情况 // remote delete
syncType = Node.SYNC_ACTION_DEL_LOCAL; syncType = Node.SYNC_ACTION_DEL_LOCAL;
} }
} }
@ -360,7 +326,7 @@ public class GTaskManager {
} }
} }
// 处理剩余的在Google Tasks中存在但本地未处理的节点进行相应的同步操作一般是添加到本地 // go through remaining items
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator(); Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
Map.Entry<String, Node> entry = iter.next(); Map.Entry<String, Node> entry = iter.next();
@ -368,14 +334,16 @@ public class GTaskManager {
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
} }
// mCancelled可能被其他线程设置所以需要逐个检查清除本地已删除记录的相关数据如果同步未取消 // mCancelled can be set by another thread, so we neet to check one by
// one
// clear local delete table
if (!mCancelled) { if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("failed to batch-delete local deleted notes"); throw new ActionFailureException("failed to batch-delete local deleted notes");
} }
} }
// 刷新本地同步ID如果同步未取消提交更新并进行相关操作来更新本地记录的同步ID // refresh local sync id
if (!mCancelled) { if (!mCancelled) {
GTaskClient.getInstance().commitUpdate(); GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId(); refreshLocalSyncId();
@ -383,7 +351,6 @@ public class GTaskManager {
} }
// 同步文件夹相关信息,包括根文件夹、通话记录文件夹、本地已存在的文件夹以及远程新增的文件夹等情况的处理
private void syncFolder() throws NetworkFailureException { private void syncFolder() throws NetworkFailureException {
Cursor c = null; Cursor c = null;
String gid; String gid;
@ -394,7 +361,7 @@ public class GTaskManager {
return; return;
} }
// 处理根文件夹的同步情况根据其在Google Tasks中的对应情况进行相应的同步操作添加、更新等 // for root folder
try { try {
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null);
@ -406,7 +373,7 @@ public class GTaskManager {
mGTaskHashMap.remove(gid); mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// 对于系统文件夹,仅在必要时更新远程名称 // for system folder, only update remote name if necessary
if (!node.getName().equals( if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
@ -423,101 +390,77 @@ public class GTaskManager {
} }
} }
// for call-note文件夹相关的同步操作处理 // for call-note folder
// 查询通话记录文件夹在本地数据库中的信息根据其在Google Tasks中的对应情况进行相应的同步操作添加、更新等
// 如果查询到该文件夹信息存在即游标c不为空且能移动到下一条记录则进一步处理
try { try {
// 通过ContentResolver查询本地数据库中通话记录文件夹的记录信息指定查询条件为_id等于通话记录文件夹的特定ID
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
new String[] { new String[] {
String.valueOf(Notes.ID_CALL_RECORD_FOLDER) String.valueOf(Notes.ID_CALL_RECORD_FOLDER)
}, null); }, null);
if (c != null) { if (c != null) {
if (c.moveToNext()) { if (c.moveToNext()) {
// 获取该文件夹在Google Tasks中对应的全局唯一IDGID
gid = c.getString(SqlNote.GTASK_ID_COLUMN); gid = c.getString(SqlNote.GTASK_ID_COLUMN);
// 根据GID从存储Google Tasks节点的哈希表中获取对应的节点对象
node = mGTaskHashMap.get(gid); node = mGTaskHashMap.get(gid);
if (node != null) { if (node != null) {
// 如果节点存在,从哈希表中移除该节点(表示已处理)
mGTaskHashMap.remove(gid); mGTaskHashMap.remove(gid);
// 在本地ID到Google Tasks的GID映射表中记录该文件夹的映射关系键为GID值为通话记录文件夹的本地ID
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);
// 在Google Tasks的GID到本地ID映射表中记录映射关系键为本地ID值为GID
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);
// 对于系统文件夹,仅在其名称与预期的通话记录文件夹名称不一致时(即可能有更新情况),进行远程更新操作 // for system folder, only update remote name if
// necessary
if (!node.getName().equals( if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE)) + GTaskStringUtils.FOLDER_CALL_NOTE))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else { } else {
// 如果节点不存在则执行添加远程节点的操作意味着本地有该文件夹记录但在Google Tasks中还未创建对应节点
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
} }
} }
} else { } else {
// 如果查询失败,打印警告日志
Log.w(TAG, "failed to query call note folder"); Log.w(TAG, "failed to query call note folder");
} }
} finally { } finally {
// 无论查询是否成功,最终都要关闭游标,释放资源,并将游标置空
if (c != null) { if (c != null) {
c.close(); c.close();
c = null; c = null;
} }
} }
// 处理本地已存在的文件夹的同步情况 // for local existing folders
// 查询本地除回收站外的文件夹记录信息根据其在Google Tasks中的对应情况进行相应的同步操作
try { try {
// 通过ContentResolver查询本地数据库中符合条件的文件夹记录信息条件为类型是文件夹且父ID不等于回收站文件夹的ID
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] { "(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC"); }, NoteColumns.TYPE + " DESC");
if (c != null) { if (c != null) {
while (c.moveToNext()) { while (c.moveToNext()) {
// 获取文件夹在Google Tasks中对应的全局唯一IDGID
gid = c.getString(SqlNote.GTASK_ID_COLUMN); gid = c.getString(SqlNote.GTASK_ID_COLUMN);
// 根据GID从存储Google Tasks节点的哈希表中获取对应的节点对象
node = mGTaskHashMap.get(gid); node = mGTaskHashMap.get(gid);
if (node != null) { if (node != null) {
// 如果节点存在,从哈希表中移除该节点(表示已处理)
mGTaskHashMap.remove(gid); mGTaskHashMap.remove(gid);
// 在本地ID到Google Tasks的GID映射表中记录该文件夹的映射关系键为GID值为文件夹的本地ID
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
// 在Google Tasks的GID到本地ID映射表中记录映射关系键为本地ID值为GID
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
// 获取该节点对应的同步操作类型(根据节点和游标信息判断是添加、更新、删除等哪种类型)
syncType = node.getSyncAction(c); syncType = node.getSyncAction(c);
} else { } else {
// 如果节点不存在根据其GID是否为空字符串判断是本地新增还是远程删除情况进而确定同步类型
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// 本地新增情况,同步类型设置为添加远程节点 // local add
syncType = Node.SYNC_ACTION_ADD_REMOTE; syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else { } else {
// 远程删除情况,同步类型设置为删除本地节点 // remote delete
syncType = Node.SYNC_ACTION_DEL_LOCAL; syncType = Node.SYNC_ACTION_DEL_LOCAL;
} }
} }
// 根据确定的同步类型执行相应的内容同步操作
doContentSync(syncType, node, c); doContentSync(syncType, node, c);
} }
} else { } else {
// 如果查询失败,打印警告日志
Log.w(TAG, "failed to query existing folder"); Log.w(TAG, "failed to query existing folder");
} }
} finally { } finally {
// 无论查询是否成功,最终都要关闭游标,释放资源,并将游标置空
if (c != null) { if (c != null) {
c.close(); c.close();
c = null; c = null;
} }
} }
// 处理远程新增文件夹的同步情况 // for remote add folders
// 遍历存储Google Tasks任务列表的哈希表对于在Google
// Tasks中存在但本地未处理还在mGTaskHashMap中的文件夹进行添加本地节点的同步操作
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator(); Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next(); Map.Entry<String, TaskList> entry = iter.next();
@ -529,12 +472,10 @@ public class GTaskManager {
} }
} }
// 如果同步未被取消提交Google Tasks客户端的更新操作将之前暂存的更新操作发送到服务器执行
if (!mCancelled) if (!mCancelled)
GTaskClient.getInstance().commitUpdate(); GTaskClient.getInstance().commitUpdate();
} }
// 根据给定的同步类型执行具体的内容同步操作
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) { if (mCancelled) {
return; return;
@ -542,16 +483,12 @@ public class GTaskManager {
MetaData meta; MetaData meta;
switch (syncType) { switch (syncType) {
// 同步类型为添加本地节点,调用相应方法进行本地节点添加操作
case Node.SYNC_ACTION_ADD_LOCAL: case Node.SYNC_ACTION_ADD_LOCAL:
addLocalNode(node); addLocalNode(node);
break; break;
// 同步类型为添加远程节点,调用相应方法进行远程节点添加操作,并传入节点和游标信息
case Node.SYNC_ACTION_ADD_REMOTE: case Node.SYNC_ACTION_ADD_REMOTE:
addRemoteNode(node, c); addRemoteNode(node, c);
break; break;
// 同步类型为删除本地节点先从元数据哈希表中获取对应的元数据若存在则调用Google Tasks客户端删除该元数据节点
// 同时将本地记录的ID添加到本地已删除记录集合中
case Node.SYNC_ACTION_DEL_LOCAL: case Node.SYNC_ACTION_DEL_LOCAL:
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN));
if (meta != null) { if (meta != null) {
@ -559,8 +496,6 @@ public class GTaskManager {
} }
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
break; break;
// 同步类型为删除远程节点先从元数据哈希表中获取对应节点的元数据若存在则调用Google Tasks客户端删除该元数据节点
// 然后再删除该节点本身
case Node.SYNC_ACTION_DEL_REMOTE: case Node.SYNC_ACTION_DEL_REMOTE:
meta = mMetaHashMap.get(node.getGid()); meta = mMetaHashMap.get(node.getGid());
if (meta != null) { if (meta != null) {
@ -568,66 +503,51 @@ public class GTaskManager {
} }
GTaskClient.getInstance().deleteNode(node); GTaskClient.getInstance().deleteNode(node);
break; break;
// 同步类型为更新本地节点,调用相应方法进行本地节点更新操作,并传入节点和游标信息
case Node.SYNC_ACTION_UPDATE_LOCAL: case Node.SYNC_ACTION_UPDATE_LOCAL:
updateLocalNode(node, c); updateLocalNode(node, c);
break; break;
// 同步类型为更新远程节点,调用相应方法进行远程节点更新操作,并传入节点和游标信息
case Node.SYNC_ACTION_UPDATE_REMOTE: case Node.SYNC_ACTION_UPDATE_REMOTE:
updateRemoteNode(node, c); updateRemoteNode(node, c);
break; break;
// 同步类型为更新冲突,目前简单采用本地更新的方式(可能后续可以考虑合并修改等更好的处理方式),调用远程节点更新方法
case Node.SYNC_ACTION_UPDATE_CONFLICT: case Node.SYNC_ACTION_UPDATE_CONFLICT:
// merging both modifications maybe a good idea // merging both modifications maybe a good idea
// right now just use local update simply // right now just use local update simply
updateRemoteNode(node, c); updateRemoteNode(node, c);
break; break;
// 同步类型为无操作,直接跳过不做任何处理
case Node.SYNC_ACTION_NONE: case Node.SYNC_ACTION_NONE:
break; break;
// 同步类型为错误或其他未知类型,抛出操作失败异常,表示遇到了未知的同步操作类型
case Node.SYNC_ACTION_ERROR: case Node.SYNC_ACTION_ERROR:
default: default:
throw new ActionFailureException("unkown sync action type"); throw new ActionFailureException("unkown sync action type");
} }
} }
// 添加本地节点的操作方法
private void addLocalNode(Node node) throws NetworkFailureException { private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) { if (mCancelled) {
return; return;
} }
SqlNote sqlNote; SqlNote sqlNote;
// 如果节点是任务列表类型TaskList
if (node instanceof TaskList) { if (node instanceof TaskList) {
// 如果节点名称是默认根文件夹的名称创建一个对应根文件夹的SqlNote对象传入上下文和根文件夹的本地ID
if (node.getName().equals( if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) {
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER);
} else if (node.getName().equals( } else if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) {
// 如果节点名称是通话记录文件夹的名称创建一个对应通话记录文件夹的SqlNote对象传入上下文和通话记录文件夹的本地ID
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER);
} else { } else {
// 其他普通任务列表情况创建一个新的SqlNote对象传入上下文
sqlNote = new SqlNote(mContext); sqlNote = new SqlNote(mContext);
// 设置SqlNote的内容为节点的本地JSON格式内容从节点获取
sqlNote.setContent(node.getLocalJSONFromContent()); sqlNote.setContent(node.getLocalJSONFromContent());
// 设置父ID为根文件夹的本地ID
sqlNote.setParentId(Notes.ID_ROOT_FOLDER); sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
} }
} else { } else {
// 如果节点是普通任务Task类型创建一个新的SqlNote对象传入上下文
sqlNote = new SqlNote(mContext); sqlNote = new SqlNote(mContext);
JSONObject js = node.getLocalJSONFromContent(); JSONObject js = node.getLocalJSONFromContent();
try { try {
// 如果节点的本地JSON数据中包含特定的笔记头部信息META_HEAD_NOTE
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { if (js.has(GTaskStringUtils.META_HEAD_NOTE)) {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.has(NoteColumns.ID)) { if (note.has(NoteColumns.ID)) {
long id = note.getLong(NoteColumns.ID); long id = note.getLong(NoteColumns.ID);
// 检查该笔记ID在本地笔记数据库中是否已存在如果存在则移除该ID可能需要重新生成新的ID
if (DataUtils.existInNoteDatabase(mContentResolver, id)) { if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
// the id is not available, have to create a new one // the id is not available, have to create a new one
note.remove(NoteColumns.ID); note.remove(NoteColumns.ID);
@ -635,14 +555,12 @@ public class GTaskManager {
} }
} }
// 如果节点的本地JSON数据中包含特定的数据头部信息META_HEAD_DATA
if (js.has(GTaskStringUtils.META_HEAD_DATA)) { if (js.has(GTaskStringUtils.META_HEAD_DATA)) {
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
for (int i = 0; i < dataArray.length(); i++) { for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i); JSONObject data = dataArray.getJSONObject(i);
if (data.has(DataColumns.ID)) { if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID); long dataId = data.getLong(DataColumns.ID);
// 检查该数据ID在本地数据数据库中是否已存在如果存在则移除该ID可能需要重新生成新的ID
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
// the data id is not available, have to create // the data id is not available, have to create
// a new one // a new one
@ -656,43 +574,36 @@ public class GTaskManager {
Log.w(TAG, e.toString()); Log.w(TAG, e.toString());
e.printStackTrace(); e.printStackTrace();
} }
// 设置SqlNote的内容为处理后的JSON数据
sqlNote.setContent(js); sqlNote.setContent(js);
// 获取任务节点的父节点在本地ID到Google Tasks的GID映射表中的对应父ID如果不存在则抛出异常表示找不到本地父ID
Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); Long parentId = mGidToNid.get(((Task) node).getParent().getGid());
if (parentId == null) { if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally"); Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot add local node"); throw new ActionFailureException("cannot add local node");
} }
// 设置SqlNote的父ID为获取到的父节点的本地ID
sqlNote.setParentId(parentId.longValue()); sqlNote.setParentId(parentId.longValue());
} }
// 设置SqlNote的Google Tasks全局唯一IDGID为节点的GID // create the local node
sqlNote.setGtaskId(node.getGid()); sqlNote.setGtaskId(node.getGid());
// 将SqlNote对象提交保存到本地数据库传入参数false可能表示不触发某些额外的相关操作
sqlNote.commit(false); sqlNote.commit(false);
// 在本地ID到Google Tasks的GID映射表中记录该节点的映射关系键为节点的GID值为SqlNote的本地ID // update gid-nid mapping
mGidToNid.put(node.getGid(), sqlNote.getId()); mGidToNid.put(node.getGid(), sqlNote.getId());
// 在Google Tasks的GID到本地ID映射表中记录映射关系键为SqlNote的本地ID值为节点的GID
mNidToGid.put(sqlNote.getId(), node.getGid()); mNidToGid.put(sqlNote.getId(), node.getGid());
// 更新远程元数据传入节点的GID和对应的SqlNote对象 // update meta
updateRemoteMeta(node.getGid(), sqlNote); updateRemoteMeta(node.getGid(), sqlNote);
} }
// 更新本地节点的操作方法
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) { if (mCancelled) {
return; return;
} }
SqlNote sqlNote; SqlNote sqlNote;
// 根据传入的上下文和游标信息创建一个新的SqlNote对象用于更新本地节点信息 // update the note locally
sqlNote = new SqlNote(mContext, c); sqlNote = new SqlNote(mContext, c);
// 设置SqlNote的内容为节点的本地JSON格式内容从节点获取
sqlNote.setContent(node.getLocalJSONFromContent()); sqlNote.setContent(node.getLocalJSONFromContent());
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
@ -701,16 +612,13 @@ public class GTaskManager {
Log.e(TAG, "cannot find task's parent id locally"); Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot update local node"); throw new ActionFailureException("cannot update local node");
} }
// 设置SqlNote的父ID为获取到的父节点的本地ID
sqlNote.setParentId(parentId.longValue()); sqlNote.setParentId(parentId.longValue());
// 将更新后的SqlNote对象提交保存到本地数据库传入参数true可能表示触发某些额外的相关操作
sqlNote.commit(true); sqlNote.commit(true);
// 更新远程元数据传入节点的GID和对应的SqlNote对象 // update meta info
updateRemoteMeta(node.getGid(), sqlNote); updateRemoteMeta(node.getGid(), sqlNote);
} }
// 添加远程节点的操作方法
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) { if (mCancelled) {
return; return;
@ -719,10 +627,9 @@ public class GTaskManager {
SqlNote sqlNote = new SqlNote(mContext, c); SqlNote sqlNote = new SqlNote(mContext, c);
Node n; Node n;
// 如果SqlNote对应的是笔记类型即普通任务 // update remotely
if (sqlNote.isNoteType()) { if (sqlNote.isNoteType()) {
Task task = new Task(); Task task = new Task();
// 设置任务的内容为从本地SqlNote获取的JSON格式内容
task.setContentByLocalJSON(sqlNote.getContent()); task.setContentByLocalJSON(sqlNote.getContent());
String parentGid = mNidToGid.get(sqlNote.getParentId()); String parentGid = mNidToGid.get(sqlNote.getParentId());
@ -730,20 +637,17 @@ public class GTaskManager {
Log.e(TAG, "cannot find task's parent tasklist"); Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot add remote task"); throw new ActionFailureException("cannot add remote task");
} }
// 将任务添加到对应的父任务列表中通过本地ID到Google Tasks的GID映射表找到父任务列表
mGTaskListHashMap.get(parentGid).addChildTask(task); mGTaskListHashMap.get(parentGid).addChildTask(task);
// 通过Google Tasks客户端创建该任务发送到服务器创建对应的任务节点
GTaskClient.getInstance().createTask(task); GTaskClient.getInstance().createTask(task);
n = (Node) task; n = (Node) task;
// 更新远程元数据传入任务的GID和对应的SqlNote对象 // add meta
updateRemoteMeta(task.getGid(), sqlNote); updateRemoteMeta(task.getGid(), sqlNote);
} else { } else {
TaskList tasklist = null; TaskList tasklist = null;
// 构建文件夹名称先添加特定的前缀然后根据SqlNote的本地ID判断是否是根文件夹或通话记录文件夹添加相应的后缀名称 // we need to skip folder if it has already existed
// 否则添加SqlNote的摘要信息作为后缀
String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX;
if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) if (sqlNote.getId() == Notes.ID_ROOT_FOLDER)
folderName += GTaskStringUtils.FOLDER_DEFAULT; folderName += GTaskStringUtils.FOLDER_DEFAULT;
@ -752,166 +656,121 @@ public class GTaskManager {
else else
folderName += sqlNote.getSnippet(); folderName += sqlNote.getSnippet();
// 遍历存储Google Tasks任务列表的哈希表mGTaskListHashMap的迭代器
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator(); Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) { while (iter.hasNext()) {
// 获取哈希表中的每一个键值对键是任务列表的全局唯一IDGID值是对应的任务列表对象TaskList
Map.Entry<String, TaskList> entry = iter.next(); Map.Entry<String, TaskList> entry = iter.next();
String gid = entry.getKey(); String gid = entry.getKey();
TaskList list = entry.getValue(); TaskList list = entry.getValue();
// 判断当前任务列表的名称是否与之前构建的文件夹名称folderName相等
if (list.getName().equals(folderName)) { if (list.getName().equals(folderName)) {
// 如果相等说明找到了对应的任务列表将其赋值给tasklist变量
tasklist = list; tasklist = list;
// 如果在存储Google Tasks节点的哈希表mGTaskHashMap中包含该任务列表的GID说明已经处理过将其移除
if (mGTaskHashMap.containsKey(gid)) { if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid); mGTaskHashMap.remove(gid);
} }
// 找到匹配的任务列表后就可以结束循环了
break; break;
} }
} }
// 如果经过上述查找没有找到匹配的任务列表tasklist仍为null则创建一个新的任务列表对象 // no match we can add now
if (tasklist == null) { if (tasklist == null) {
tasklist = new TaskList(); tasklist = new TaskList();
// 设置新任务列表的内容为从本地SqlNote获取的JSON格式内容
tasklist.setContentByLocalJSON(sqlNote.getContent()); tasklist.setContentByLocalJSON(sqlNote.getContent());
// 通过Google Tasks客户端创建该任务列表发送到服务器创建对应的任务列表节点
GTaskClient.getInstance().createTaskList(tasklist); GTaskClient.getInstance().createTaskList(tasklist);
// 将新创建的任务列表添加到存储Google Tasks任务列表的哈希表中键为任务列表的GID值为任务列表对象
mGTaskListHashMap.put(tasklist.getGid(), tasklist); mGTaskListHashMap.put(tasklist.getGid(), tasklist);
} }
// 将创建或找到的任务列表对象转换为Node类型并赋值给n因为Node是更通用的节点类型TaskList是一种特殊的Node
n = (Node) tasklist; n = (Node) tasklist;
} }
// 更新本地笔记相关信息 // update local note
// 设置本地SqlNote的Google Tasks全局唯一IDGID为n的GID即与远程任务列表或任务对应的GID保持一致
sqlNote.setGtaskId(n.getGid()); sqlNote.setGtaskId(n.getGid());
// 将SqlNote对象提交保存到本地数据库传入参数false可能表示不触发某些额外的相关操作此处用于保存基本的关联信息等
sqlNote.commit(false); sqlNote.commit(false);
// 重置本地修改标志(可能表示将本地记录标记为已与远程同步,不再是本地修改状态)
sqlNote.resetLocalModified(); sqlNote.resetLocalModified();
// 再次提交保存SqlNote对象到本地数据库传入参数true可能表示触发一些更新相关的额外操作确保修改标志等信息更新成功
sqlNote.commit(true); sqlNote.commit(true);
// gid-id映射更新 // gid-id mapping
// 在本地ID到Google Tasks的GID映射表mGidToNid中记录该节点n的映射关系键为节点的GID值为SqlNote的本地ID
mGidToNid.put(n.getGid(), sqlNote.getId()); mGidToNid.put(n.getGid(), sqlNote.getId());
// 在Google Tasks的GID到本地ID映射表mNidToGid中记录映射关系键为SqlNote的本地ID值为节点的GID
mNidToGid.put(sqlNote.getId(), n.getGid()); mNidToGid.put(sqlNote.getId(), n.getGid());
} }
// 更新远程节点的操作方法
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) { if (mCancelled) {
return; return;
} }
// 根据传入的上下文和游标信息创建一个新的SqlNote对象用于后续操作
SqlNote sqlNote = new SqlNote(mContext, c); SqlNote sqlNote = new SqlNote(mContext, c);
// 远程更新操作 // update remotely
// 设置远程节点node的内容为从本地SqlNote获取的JSON格式内容即将本地修改同步到远程节点
node.setContentByLocalJSON(sqlNote.getContent()); node.setContentByLocalJSON(sqlNote.getContent());
// 通过Google Tasks客户端将更新后的节点添加到更新队列中后续会批量提交更新到服务器
GTaskClient.getInstance().addUpdateNode(node); GTaskClient.getInstance().addUpdateNode(node);
// 更新远程元数据传入节点的GID和对应的SqlNote对象 // update meta
updateRemoteMeta(node.getGid(), sqlNote); updateRemoteMeta(node.getGid(), sqlNote);
// 如果SqlNote对应的是笔记类型即普通任务则可能需要处理任务移动相关操作 // move task if necessary
if (sqlNote.isNoteType()) { if (sqlNote.isNoteType()) {
Task task = (Task) node; Task task = (Task) node;
// 获取任务当前的父任务列表
TaskList preParentList = task.getParent(); TaskList preParentList = task.getParent();
// 通过本地ID到Google Tasks的GID映射表根据本地记录的父ID获取当前任务在远程对应的父任务列表的GID
String curParentGid = mNidToGid.get(sqlNote.getParentId()); String curParentGid = mNidToGid.get(sqlNote.getParentId());
if (curParentGid == null) { if (curParentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist"); Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot update remote task"); throw new ActionFailureException("cannot update remote task");
} }
// 根据获取到的GID从存储Google Tasks任务列表的哈希表中获取当前任务在远程对应的父任务列表对象
TaskList curParentList = mGTaskListHashMap.get(curParentGid); TaskList curParentList = mGTaskListHashMap.get(curParentGid);
// 如果当前任务的前后父任务列表不一致,说明任务需要在远程进行移动操作
if (preParentList != curParentList) { if (preParentList != curParentList) {
// 先从原父任务列表中移除该任务
preParentList.removeChildTask(task); preParentList.removeChildTask(task);
// 将任务添加到新的父任务列表中
curParentList.addChildTask(task); curParentList.addChildTask(task);
// 通过Google Tasks客户端执行任务移动操作告知服务器任务在远程的位置变更
GTaskClient.getInstance().moveTask(task, preParentList, curParentList); GTaskClient.getInstance().moveTask(task, preParentList, curParentList);
} }
} }
// 清除本地修改标志,标记本地记录已同步到远程,不再处于本地修改状态 // clear local modified flag
sqlNote.resetLocalModified(); sqlNote.resetLocalModified();
// 将更新后的SqlNote对象提交保存到本地数据库传入参数true可能表示触发一些更新相关的额外操作确保修改标志等信息更新成功
sqlNote.commit(true); sqlNote.commit(true);
} }
// 更新远程元数据的操作方法
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
if (sqlNote != null && sqlNote.isNoteType()) { if (sqlNote != null && sqlNote.isNoteType()) {
// 从存储元数据的哈希表mMetaHashMap中根据GID获取对应的元数据对象MetaData
MetaData metaData = mMetaHashMap.get(gid); MetaData metaData = mMetaHashMap.get(gid);
if (metaData != null) { if (metaData != null) {
// 如果元数据对象存在设置其元数据内容传入GID和从本地SqlNote获取的JSON格式内容
metaData.setMeta(gid, sqlNote.getContent()); metaData.setMeta(gid, sqlNote.getContent());
// 通过Google Tasks客户端将更新后的元数据添加到更新队列中后续会批量提交更新到服务器
GTaskClient.getInstance().addUpdateNode(metaData); GTaskClient.getInstance().addUpdateNode(metaData);
} else { } else {
// 如果元数据对象不存在,则创建一个新的元数据对象
metaData = new MetaData(); metaData = new MetaData();
// 设置新元数据对象的元数据内容传入GID和从本地SqlNote获取的JSON格式内容
metaData.setMeta(gid, sqlNote.getContent()); metaData.setMeta(gid, sqlNote.getContent());
// 将新创建的元数据对象添加到元数据列表mMetaList中作为子任务从逻辑上关联起来
mMetaList.addChildTask(metaData); mMetaList.addChildTask(metaData);
// 将新创建的元数据对象添加到存储元数据的哈希表中键为GID值为元数据对象
mMetaHashMap.put(gid, metaData); mMetaHashMap.put(gid, metaData);
// 通过Google Tasks客户端创建该元数据任务发送到服务器创建对应的元数据节点
GTaskClient.getInstance().createTask(metaData); GTaskClient.getInstance().createTask(metaData);
} }
} }
} }
// 刷新本地同步ID的操作方法
private void refreshLocalSyncId() throws NetworkFailureException { private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) { if (mCancelled) {
return; return;
} }
// 为了获取最新的Google Tasks列表信息先清空之前存储相关信息的哈希表 // get the latest gtask list
mGTaskHashMap.clear(); mGTaskHashMap.clear();
mGTaskListHashMap.clear(); mGTaskListHashMap.clear();
mMetaHashMap.clear(); mMetaHashMap.clear();
// 重新初始化Google Tasks列表信息重新获取任务列表、元数据等相关内容
initGTaskList(); initGTaskList();
Cursor c = null; Cursor c = null;
try { try {
// 通过ContentResolver查询本地数据库中除系统类型且不在回收站的笔记记录信息按照类型降序排列
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id<>?)", new String[] { "(type<>? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC"); }, NoteColumns.TYPE + " DESC");
if (c != null) { if (c != null) {
while (c.moveToNext()) { while (c.moveToNext()) {
// 获取笔记在Google Tasks中对应的全局唯一IDGID
String gid = c.getString(SqlNote.GTASK_ID_COLUMN); String gid = c.getString(SqlNote.GTASK_ID_COLUMN);
// 根据GID从存储Google Tasks节点的哈希表中获取对应的节点对象
Node node = mGTaskHashMap.get(gid); Node node = mGTaskHashMap.get(gid);
if (node != null) { if (node != null) {
// 如果节点存在,从哈希表中移除该节点(表示已处理)
mGTaskHashMap.remove(gid); mGTaskHashMap.remove(gid);
// 创建一个ContentValues对象用于存放要更新的数据此处将同步ID设置为节点的最后修改时间
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put(NoteColumns.SYNC_ID, node.getLastModified()); values.put(NoteColumns.SYNC_ID, node.getLastModified());
// 通过ContentResolver更新本地数据库中对应笔记的记录设置同步ID字段的值
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
c.getLong(SqlNote.ID_COLUMN)), values, null, null); c.getLong(SqlNote.ID_COLUMN)), values, null, null);
} else { } else {
@ -924,7 +783,6 @@ public class GTaskManager {
Log.w(TAG, "failed to query local note to refresh sync id"); Log.w(TAG, "failed to query local note to refresh sync id");
} }
} finally { } finally {
// 无论查询是否成功,最终都要关闭游标,释放资源,并将游标置空
if (c != null) { if (c != null) {
c.close(); c.close();
c = null; c = null;
@ -932,14 +790,11 @@ public class GTaskManager {
} }
} }
// 获取同步账户名称的方法通过Google Tasks客户端获取同步账户对象并返回其名称
public String getSyncAccount() { public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name; return GTaskClient.getInstance().getSyncAccount().name;
} }
// 取消同步的方法将表示同步是否取消的标志mCancelled设置为true其他相关操作可以根据该标志来判断是否停止执行
public void cancelSync() { public void cancelSync() {
mCancelled = true; mCancelled = true;
} }
} }
>>>>>>> a495b394fa4686564cc2bfe7d054eb66276713ae

@ -0,0 +1,128 @@
/*
* 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;
public class GTaskSyncService extends Service {
public final static String ACTION_STRING_NAME = "sync_action_type";
public final static int ACTION_START_SYNC = 0;
public final static int ACTION_CANCEL_SYNC = 1;
public final static int ACTION_INVALID = 2;
public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service";
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing";
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg";
private static GTaskASyncTask mSyncTask = null;
private static String mSyncProgress = "";
private void startSync() {
if (mSyncTask == null) {
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
public void onComplete() {
mSyncTask = null;
sendBroadcast("");
stopSelf();
}
});
sendBroadcast("");
mSyncTask.execute();
}
}
private void cancelSync() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
@Override
public void onCreate() {
mSyncTask = null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle bundle = intent.getExtras();
if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) {
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) {
case ACTION_START_SYNC:
startSync();
break;
case ACTION_CANCEL_SYNC:
cancelSync();
break;
default:
break;
}
return START_STICKY;
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onLowMemory() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
public IBinder onBind(Intent intent) {
return null;
}
public void sendBroadcast(String msg) {
mSyncProgress = msg;
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
intent.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(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC);
activity.startService(intent);
}
public static void cancelSync(Context context) {
Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
context.startService(intent);
}
public static boolean isSyncing() {
return mSyncTask != null;
}
public static String getProgressString() {
return mSyncProgress;
}
}

Binary file not shown.

Binary file not shown.

@ -1,9 +0,0 @@
# generated files
bin/
gen/
# Local configuration file (sdk path, etc)
project.properties
.settings/
.classpath
.project

@ -1,150 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.micode.notes"
android:versionCode="1"
android:versionName="0.1" >
<uses-sdk android:minSdkVersion="14" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:icon="@drawable/icon_app"
android:label="@string/app_name" >
<activity
android:name=".ui.NotesListActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/app_name"
android:launchMode="singleTop"
android:theme="@style/NoteTheme"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="adjustPan" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.NoteEditActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:launchMode="singleTop"
android:theme="@style/NoteTheme" >
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/text_note" />
<data android:mimeType="vnd.android.cursor.item/call_note" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.INSERT_OR_EDIT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="vnd.android.cursor.item/text_note" />
<data android:mimeType="vnd.android.cursor.item/call_note" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
</activity>
<provider
android:name="net.micode.notes.data.NotesProvider"
android:authorities="micode_notes"
android:multiprocess="true" />
<receiver
android:name=".widget.NoteWidgetProvider_2x"
android:label="@string/app_widget2x2" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_2x_info" />
</receiver>
<receiver
android:name=".widget.NoteWidgetProvider_4x"
android:label="@string/app_widget4x4" >
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
<action android:name="android.appwidget.action.APPWIDGET_DELETED" />
<action android:name="android.intent.action.PRIVACY_MODE_CHANGED" />
</intent-filter>
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/widget_4x_info" />
</receiver>
<receiver android:name=".ui.AlarmInitReceiver" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<receiver
android:name="net.micode.notes.ui.AlarmReceiver"
android:process=":remote" >
</receiver>
<activity
android:name=".ui.AlarmAlertActivity"
android:label="@string/app_name"
android:launchMode="singleInstance"
android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" >
</activity>
<activity
android:name="net.micode.notes.ui.NotesPreferenceActivity"
android:label="@string/preferences_title"
android:launchMode="singleTop"
android:theme="@android:style/Theme.Holo.Light" >
</activity>
<service
android:name="net.micode.notes.gtask.remote.GTaskSyncService"
android:exported="false" >
</service>
<meta-data
android:name="android.app.default_searchable"
android:value=".ui.NoteEditActivity" />
</application>
</manifest>

@ -1,190 +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.
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.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

@ -1,23 +0,0 @@
[中文]
1. MiCode便签是小米便签的社区开源版由MIUI团队(www.miui.com) 发起并贡献第一批代码遵循NOTICE文件所描述的开源协议
今后为MiCode社区(www.micode.net) 拥有,并由社区发布和维护。
2. Bug反馈和跟踪请访问Github,
https://github.com/MiCode/Notes/issues?sort=created&direction=desc&state=open
3. 功能建议和综合讨论请访问MiCode,
http://micode.net/forum.php?mod=forumdisplay&fid=38
[English]
1. MiCode Notes is open source edition of XM notepad, it's first initiated and sponsored by MIUI team (www.miui.com).
It's opened under license described by NOTICE file. It's owned by the MiCode community (www.micode.net). In future,
the MiCode community will release and maintain this project.
2. Regarding issue tracking, please visit Github,
https://github.com/MiCode/Notes/issues?sort=created&direction=desc&state=open
3. Regarding feature request and general discussion, please visit Micode forum,
http://micode.net/forum.php?mod=forumdisplay&fid=38

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:color="#88555555" />
<item android:state_selected="true" android:color="#ff999999" />
<item android:color="#ff000000" />
</selector>

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#50000000" />
</selector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/new_note_pressed" />
<item
android:drawable="@drawable/new_note_normal" />
</selector>

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/account_dialog_title"
style="?android:attr/textAppearanceMedium"
android:singleLine="true"
android:ellipsize="end"
android:gravity="center"
android:layout_marginTop="-2.7dip"
android:layout_marginBottom="-2.7dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<TextView
android:id="@+id/account_dialog_subtitle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dip"
android:layout_marginBottom="1dip"
android:gravity="center"/>
</LinearLayout>

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="50dip"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/preferences_add_account" />
</LinearLayout>

@ -1,56 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<NumberPicker
android:id="@+id/date"
android:layout_width="120dip"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
/>
<NumberPicker
android:id="@+id/hour"
android:layout_width="50dip"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
<NumberPicker
android:id="@+id/minute"
android:layout_width="50dip"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
<NumberPicker
android:id="@+id/amPm"
android:layout_width="50dip"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
</LinearLayout>

@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<EditText
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/et_foler_name"
android:layout_width="fill_parent"
android:hint="@string/hint_foler_name"
android:layout_height="fill_parent" />

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save