Compare commits

...

70 Commits

Author SHA1 Message Date
AetherPendragon 10ebf36d2b 对齐图片、表格
12 hours ago
Surponess fdb58b2ce3 修改图片
12 hours ago
Surponess a05a3c9e88 修改图片表格大小
12 hours ago
p82feo7wg 18d180145f Merge pull request '修改文档' (#12) from wangjiaqi_branch into master
1 day ago
Surponess 9835f0233b 修改文档内容和格式
1 day ago
p82feo7wg 0b51d25e68 Merge pull request '修改表述' (#11) from luogang_branch into master
4 days ago
AetherPendragon 191977fd41 修改了部分格式与表述问题
5 days ago
AetherPendragon 9933db62e3 增加对软件设计模式的解释
5 days ago
AetherPendragon e4abf5ea50 修改了小米便签概述
5 days ago
AetherPendragon 4a866f68d0 解决图标标题未居中问题
6 days ago
p82feo7wg b63ada829b Merge pull request '首行缩进' (#10) from wangjiaqi_branch into master
7 days ago
Surponess 1ec3bea45c 修改首行缩进
7 days ago
Surponess f6ca9fff5b Merge branch 'master' of https://bdgit.educoder.net/p82feo7wg/MiNoteRead into wangjiaqi_branch
7 days ago
Surponess f6a6b2befd 修改名称
7 days ago
Surponess 5877d3343d 修改
7 days ago
p82feo7wg c22d81e4b8 Merge pull request '代码质量报告的修改' (#9) from wangjiaqi_branch into master
1 week ago
Surponess 11435ed73e 新增代码质量分析报告的工具部分以及一部分图片增添
1 week ago
p82feo7wg c1d214ea2f Merge pull request '新增质量分析报告' (#8) from luogang_branch into master
1 week ago
AetherPendragon 9806893045 删除多余文档
1 week ago
AetherPendragon 60fd670f65 重命名质量分析报告
1 week ago
AetherPendragon 4c29ffde61 增加分析报告初版
1 week ago
AetherPendragon 6b46196a7e 删除多余文件
1 week ago
AetherPendragon 89da939801 Merge branch 'luogang_branch' of https://bdgit.educoder.net/p82feo7wg/MiNoteRead into LogOn
1 week ago
AetherPendragon 97d9e270e3 增加代码质量分析
1 week ago
AetherPendragon 2e26c09241 更新项目
1 week ago
Surponess 0d49124274 修改注释
2 weeks ago
Surponess b2baf6412a 提交文档
3 weeks ago
Surponess 593b63aa8e 修改一处注释
3 weeks ago
Surponess 14c867db2e Merge branch 'master' of https://bdgit.educoder.net/p82feo7wg/MiNoteRead
3 weeks ago
Surponess bfddefbc3b 修改一处注释
3 weeks ago
Surponess 74bb086f1a Merge branch 'luogang_branch' of https://bdgit.educoder.net/p82feo7wg/MiNoteRead
3 weeks ago
AetherPendragon bc5ca6f0a0 修改部分注释
3 weeks ago
p82feo7wg 31d5f4a816 Merge pull request '修改' (#7) from wangjiaqi_branch into master
3 weeks ago
Surponess 4969f8d43f 修改
3 weeks ago
p82feo7wg 2222fb1190 Merge pull request '增添部分代码' (#5) from wangjiaqi_branch into master
3 weeks ago
AetherPendragon 06711ccca6 更改了部分注释
3 weeks ago
Surponess 90a23a35c7 修改注释
3 weeks ago
Surponess bf28d55bb8 删除修改
3 weeks ago
Surponess 5be1730a68 Merge branch 'master' of https://bdgit.educoder.net/p82feo7wg/MiNoteRead into wangjiaqi_branch
3 weeks ago
AetherPendragon 4cfeb6bfd5 增加了所有java文件的代码注释
3 weeks ago
Surponess 054e52d9bb 增添了代码注释
3 weeks ago
AetherPendragon 30e36d76c6 增加 Contact 注释
1 month ago
Surponess e17ef4489c 修改内容
1 month ago
Surponess 5eef703b07 新增类间关系图文字说明
1 month ago
Surponess 79380a4def 修改部分图片细节
1 month ago
Surponess e0e5fde489 新增部分图片
1 month ago
AetherPendragon 838814c023 增加用例图
1 month ago
Surponess c0e5e79940 修改题注
1 month ago
AetherPendragon 42781320fa Merge branch 'master' of https://bdgit.educoder.net/p82feo7wg/MiNoteRead
1 month ago
AetherPendragon a90c214089 添加图片
1 month ago
Surponess 1e1467d648 删除了多余的部分
1 month ago
Surponess de16c6c133 修改了新的模板,但是还没有加入图片
1 month ago
Surponess e4cb36a422 修改部分内容
1 month ago
Surponess c76dd1738e 修改了部分内容
1 month ago
Surponess c1550fccfb Merge branch 'master' of https://bdgit.educoder.net/p82feo7wg/MiNoteRead
2 months ago
Surponess 20759a3fc7 Merge branch 'luogang_branch' of https://bdgit.educoder.net/p82feo7wg/MiNoteRead
2 months ago
AetherPendragon 62812e09c9 add xlsx,添加图片
2 months ago
AetherPendragon 12844bd277 add MiNote
2 months ago
AetherPendragon 107b1b255f add MiNote
2 months ago
Surponess 5d0521357e 泛读报告的填写和按照现有理解注释了部分代码
2 months ago
p82feo7wg 4c0e9afbf2 Merge pull request '修改泛读报告模板' (#2) from wangjiaqi_branch into master
2 months ago
Surponess 5c6e7f4ffa 修改
2 months ago
Surponess 8f0ced53aa 新增小米便签泛读报告的模板
2 months ago
Surponess 0cc453c367 修改了泛读报告的模板
2 months ago
Surponess 062cafa9b7 修改文档名称
2 months ago
AetherPendragon 7f90be4072 添加图片
2 months ago
p82feo7wg c2fcbfad45 Merge pull request '更新便签功能文档' (#1) from wangjiaqi_branch into master
2 months ago
Surponess 390fac8a45 更新便签基本功能文档
2 months ago
Surponess 977f0a88c9 doc新增小米便签功能的结构图
2 months ago
Surponess 6ca33b3ec1 新修改源码res/values/styles.xml第65道68行代码
2 months ago

Binary file not shown.

@ -1,3 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#JAVA_HOME" />
</GradleProjectSettings>
</option>
</component>
</project>

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

@ -0,0 +1,7 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.Notesmaster" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
</style>
</resources>

@ -63,7 +63,6 @@
</style>
<style name="NoteActionBarStyle" parent="@android:style/Widget.Holo.Light.ActionBar.Solid">
<item name="android:displayOptions" />
<item name="android:visibility">gone</item>
<item name="android:visibility">visible</item>
</style>
</resources>

@ -0,0 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.Notesmaster" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.Notesmaster" parent="Base.Theme.Notesmaster" />
</resources>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older than API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

@ -25,47 +25,92 @@ import android.util.Log;
import java.util.HashMap;
/**
*
* 便
*
*/
public class Contact {
// 功能:缓存查询结果,,暂时存储电话号码与联系人的映射关系,避免重复查询,节省开销
private static HashMap<String, String> sContactCache;
// 日志标签
private static final String TAG = "Contact";
// 查询联系人的选择条件
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN "
+ "(SELECT raw_contact_id "
+ " FROM phone_lookup"
+ " WHERE min_match = '+')";
/**
* SQL
* AND
* PHONE_NUMBERS_EQUAL(Phone.NUMBER,?)
* PHONE_NUMBERS_EQUAL AndroidPhone.NUMBER
* Data.MIMETYPE = 'Phone.CONTENT_ITEM_TYPE'
*
* Data.RAW_CONTACT_ID IN A
* IDA
* A(SELECT raw_contact_id FROM phone_lookup WHERE min_match = '+')
* phone_lookupAndroid
* min_match
*
*/
/**
*
*
* @param context 访 ContentResolver
* @param phoneNumber
* @return null
*/
public static String getContact(Context context, String phoneNumber) {
// 如果缓存哈希表为空,则创建新的哈希表
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
// 检查缓存中是否已有该电话号码对应的联系人
// 检查缓存中是否已存在该电话号码的查询结果
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}
// 构建查询条件替换min_match参数
// PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)用于计算电话号码的最小匹配位数
// 将占位符"+"替换为最小匹配数,以构建完整的查询语句
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI,
new String [] { Phone.DISPLAY_NAME },
selection,
new String[] { phoneNumber },
null);
/**
*
* context.getContentResolver().query
*/
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI, // 查询URI
new String [] { Phone.DISPLAY_NAME }, // 联系人的名称作为返回值
selection, // 查询条件
new String[] { phoneNumber }, // 电话号码作为查询参数
null); // 无排序方式
// 如果找到联系人,返回名称并缓存
// 查询系统联系人数据库
if (cursor != null && cursor.moveToFirst()) {
try {
// 获取第一行第一列的联系人姓名
String name = cursor.getString(0);
// 将联系人和电话号码存入缓存中,以便下次查找
sContactCache.put(phoneNumber, name);
return name;
} catch (IndexOutOfBoundsException e) {
// 发生数组越界异常将异常记录到日志Log中
Log.e(TAG, " Cursor get string error " + e.toString());
return null;
} finally {
// 关闭Cursor防止内存泄露
cursor.close();
}
} else {
// 没有找到匹配的联系人,将信息写入日志中
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}

@ -17,24 +17,43 @@
package net.micode.notes.data;
import android.net.Uri;
/**
*
*
*
*/
public class Notes {
// ContentProvider的授权标识
public static final String AUTHORITY = "micode_notes";
// 日志标签
public static final String TAG = "Notes";
// 0类型普通笔记
public static final int TYPE_NOTE = 0;
// 1类型文件夹
public static final int TYPE_FOLDER = 1;
// 2类型系统文件夹
public static final int TYPE_SYSTEM = 2;
// 系统文件夹的 ID 定义。
// #ID_ROOT_FOLDER 默认根文件夹
// #ID_TEMPARAY_FOLDER 临时文件夹,移动笔记时的中转
// #ID_CALL_RECORD_FOLDER 通话记录专用文件夹
// #ID_TRASH_FOLER 回收站
public static final int ID_ROOT_FOLDER = 0; // 默认根文件夹
public static final int ID_TEMPARAY_FOLDER = -1; // 临时文件夹(用于无归属的笔记)
public static final int ID_CALL_RECORD_FOLDER = -2; // 通话记录文件夹
public static final int ID_TRASH_FOLER = -3; // 回收站文件夹
/**
* Following IDs are system folders' identifiers
* {@link Notes#ID_ROOT_FOLDER } is default folder
* {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
* Intent Extra Android Intent
* 1.
* 2. ID
* 3. ID
* 4.
* 5. ID
* 6.
*/
public static final int ID_ROOT_FOLDER = 0;
public static final int ID_TEMPARAY_FOLDER = -1;
public static final int ID_CALL_RECORD_FOLDER = -2;
public static final int ID_TRASH_FOLER = -3;
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date";
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id";
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id";
@ -42,238 +61,176 @@ public class Notes {
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id";
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date";
// 无效的小部件类型 -1
public static final int TYPE_WIDGET_INVALIDE = -1;
// 2x2小部件类型 0
public static final int TYPE_WIDGET_2X = 0;
// 4x4小部件类型 1
public static final int TYPE_WIDGET_4X = 1;
/**
*
*/
public static class DataConstants {
// 普通笔记的MIME类型
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
// 通话记录笔记的MIME类型
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
}
/**
* Uri to query all notes and folders
*/
// URI 是 ContentProvider 的标准访问入口,用于统一访问应用的数据。
// 查询所有笔记和文件夹的URI
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
/**
* Uri to query data
*/
// 查询数据表的URI
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
/**
* NoteColumns - 便(notes)
* 便
*/
/**
*
*/
public interface NoteColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
// 行的唯一ID
public static final String ID = "_id";
/**
* The parent's id for note or folder
* <P> Type: INTEGER (long) </P>
*/
// 笔记或文件夹的父ID
public static final String PARENT_ID = "parent_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
// 笔记或文件夹的创建日期
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
// 最后修改日期
public static final String MODIFIED_DATE = "modified_date";
/**
* Alert date
* <P> Type: INTEGER (long) </P>
*/
// 提醒日期
public static final String ALERTED_DATE = "alert_date";
/**
* Folder's name or text content of note
* <P> Type: TEXT </P>
*/
// 文件夹名称或笔记的文本内容摘要
public static final String SNIPPET = "snippet";
/**
* Note's widget id
* <P> Type: INTEGER (long) </P>
*/
// 笔记的小部件ID
public static final String WIDGET_ID = "widget_id";
/**
* Note's widget type
* <P> Type: INTEGER (long) </P>
*/
// 笔记的小部件类型
public static final String WIDGET_TYPE = "widget_type";
/**
* Note's background color's id
* <P> Type: INTEGER (long) </P>
*/
// 笔记的背景颜色ID
public static final String BG_COLOR_ID = "bg_color_id";
/**
* For text note, it doesn't has attachment, for multi-media
* note, it has at least one attachment
* <P> Type: INTEGER </P>
*/
// 是否有附件;对于文本笔记没有附件,多媒体笔记至少有一个附件
public static final String HAS_ATTACHMENT = "has_attachment";
/**
* Folder's count of notes
* <P> Type: INTEGER (long) </P>
*/
// 文件夹中的笔记数量
public static final String NOTES_COUNT = "notes_count";
/**
* The file type: folder or note
* <P> Type: INTEGER </P>
*/
// 文件类型:文件夹或笔记
public static final String TYPE = "type";
/**
* The last sync id
* <P> Type: INTEGER (long) </P>
*/
// 最后一次同步的ID
public static final String SYNC_ID = "sync_id";
/**
* Sign to indicate local modified or not
* <P> Type: INTEGER </P>
*/
// 标记是否在本地被修改
public static final String LOCAL_MODIFIED = "local_modified";
/**
* Original parent id before moving into temporary folder
* <P> Type : INTEGER </P>
*/
// 移动到临时文件夹之前的原始父ID
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* The gtask id
* <P> Type : TEXT </P>
*/
// Google Task的ID
public static final String GTASK_ID = "gtask_id";
/**
* The version code
* <P> Type : INTEGER (long) </P>
*/
// 版本号
public static final String VERSION = "version";
}
/**
* DataColumns - 便(data)
* 便
* 使MIME_TYPE便便
*/
public interface DataColumns {
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
// 行的唯一ID
public static final String ID = "_id";
/**
* The MIME type of the item represented by this row.
* <P> Type: Text </P>
*/
// 该行数据项的MIME类型
public static final String MIME_TYPE = "mime_type";
/**
* The reference id to note that this data belongs to
* <P> Type: INTEGER (long) </P>
*/
// 该数据所属笔记的引用ID
public static final String NOTE_ID = "note_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
// 笔记或文件夹的创建日期
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
// 最后修改日期
public static final String MODIFIED_DATE = "modified_date";
/**
* Data's content
* <P> Type: TEXT </P>
*/
// 数据内容
public static final String CONTENT = "content";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
// 通用数据列1
public static final String DATA1 = "data1";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
// 通用数据列2
public static final String DATA2 = "data2";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
// 通用数据列3
public static final String DATA3 = "data3";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
// 通用数据列4
public static final String DATA4 = "data4";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
// 通用数据列5
public static final String DATA5 = "data5";
}
/**
* TextNote - 便
* 便URI
*/
public static final class TextNote implements DataColumns {
/**
* Mode to indicate the text in check list mode or not
* <P> Type: Integer 1:check list mode 0: normal mode </P>
*/
// 模式标识:文本是否为清单模式
public static final String MODE = DATA1;
// 清单模式常量
public static final int MODE_CHECK_LIST = 1;
// 普通模式常量
public static final int MODE_NORMAL = 0;
// 文本笔记的目录MIME类型
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
// 文本笔记的单项MIME类型
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";
// 文本笔记的URI
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
/**
* CallNote - 便
* 便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;
// 联系人姓名
public static final String CONTACT_NAME = DATA4;
// 通话记录笔记的目录MIME类型
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";
// 通话记录笔记的URI
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
}

@ -26,21 +26,31 @@ import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
/**
*
*
*/
public class NotesDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "note.db";
private static final int DB_VERSION = 4;
private static final String DB_NAME = "note.db"; // 数据库名称
private static final int DB_VERSION = 4; // 数据库版本
;
/**
*
*/
public interface TABLE {
/** 笔记表名 */
public static final String NOTE = "note";
/** 数据表名 */
public static final String DATA = "data";
}
private static final String TAG = "NotesDatabaseHelper";
private static NotesDatabaseHelper mInstance;
private static final String TAG = "NotesDatabaseHelper"; // 日志标签
private static NotesDatabaseHelper mInstance; // 单例实例
/** 数据库帮助类的单例实例 */
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" +
@ -63,6 +73,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" +
")";
// 创建笔记数据表的SQL语句
private static final String CREATE_DATA_TABLE_SQL =
"CREATE TABLE " + TABLE.DATA + "(" +
DataColumns.ID + " INTEGER PRIMARY KEY," +
@ -78,24 +89,18 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +
")";
// 创建笔记数据表中note_id列的索引
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =
"CREATE INDEX IF NOT EXISTS note_id_index ON " +
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";
/**
* Increase folder's note count when move note to the folder
*
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_update "+
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +
" BEGIN " +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER ="CREATE TRIGGER increase_folder_count_on_update "+" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +" BEGIN " +" UPDATE " + TABLE.NOTE +" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +" END";
/**
* Decrease folder's note count when move note from folder
*
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_update " +
@ -108,7 +113,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" END";
/**
* Increase folder's note count when insert new note to the folder
*
*/
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =
"CREATE TRIGGER increase_folder_count_on_insert " +
@ -120,7 +125,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" END";
/**
* Decrease folder's note count when delete note from the folder
*
*/
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =
"CREATE TRIGGER decrease_folder_count_on_delete " +
@ -132,9 +137,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END";
/**
* Update note's content when insert data with type {@link DataConstants#NOTE}
*/
/**插入 TEXT_NOTE 类型的数据行时,用数据内容刷新 note.snippet */
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =
"CREATE TRIGGER update_note_content_on_insert " +
" AFTER INSERT ON " + TABLE.DATA +
@ -145,9 +148,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has changed
*/
/**更新 TEXT_NOTE 数据行时,同步更新 note.snippet */
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =
"CREATE TRIGGER update_note_content_on_update " +
" AFTER UPDATE ON " + TABLE.DATA +
@ -158,9 +159,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +
" END";
/**
* Update note's content when data with {@link DataConstants#NOTE} type has deleted
*/
/** 删除 TEXT_NOTE 数据行时,清空 note.snippet */
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =
"CREATE TRIGGER update_note_content_on_delete " +
" AFTER delete ON " + TABLE.DATA +
@ -172,7 +171,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" END";
/**
* Delete datas belong to note which has been deleted
*
*/
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =
"CREATE TRIGGER delete_data_on_delete " +
@ -183,7 +182,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" END";
/**
* Delete notes belong to folder which has been deleted
*
*/
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =
"CREATE TRIGGER folder_delete_notes_on_delete " +
@ -194,7 +193,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" END";
/**
* Move notes belong to folder which has been moved to trash folder
*
*/
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =
"CREATE TRIGGER folder_move_notes_on_trash " +
@ -206,18 +205,31 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
/**
*
* @param context
*/
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
/**
*
* @param db
*/
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
db.execSQL(CREATE_NOTE_TABLE_SQL); // 创建笔记表结构
reCreateNoteTableTriggers(db); // 重建笔记表触发器
createSystemFolder(db); // 创建系统文件夹
Log.d(TAG, "note table has been created");
}
/**
*
* @param db
*/
private void reCreateNoteTableTriggers(SQLiteDatabase db) {
// 删除现有触发器
db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");
db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");
@ -226,6 +238,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");
db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");
// 创建新触发器
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);
@ -235,58 +248,71 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);
}
/**
*
* @param db
*/
private void createSystemFolder(SQLiteDatabase db) {
ContentValues values = new ContentValues();
/**
* call record foler for call notes
*
*/
values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
* root folder which is default folder
*/
// 创建根文件夹(默认文件夹)
values.clear();
values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
* temporary folder which is used for moving note
*/
// 创建临时文件夹(用于移动笔记)
values.clear();
values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
/**
* create trash folder
*/
// 创建回收站文件夹
values.clear();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
/**
*
* @param db
*/
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL);
reCreateDataTableTriggers(db);
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);
db.execSQL(CREATE_DATA_TABLE_SQL); // 创建笔记数据表结构
reCreateDataTableTriggers(db); // 重建笔记数据表触发器
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建note_id列索引
Log.d(TAG, "data table has been created");
}
/**
*
* @param 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_update");
db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete");
// 创建新触发器
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER);
db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);
}
/**
* NotesDatabaseHelper
* @param context
* @return NotesDatabaseHelper
*/
static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
@ -294,67 +320,97 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper {
return mInstance;
}
/** 创建数据库:依次创建 note 表与 data 表 */
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db);
createDataTable(db);
createNoteTable(db); // 创建笔记表
createDataTable(db); // 创建笔记数据表
}
/**
*
* @param db
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false;
boolean skipV2 = false;
boolean reCreateTriggers = false; // 是否需要重建触发器
boolean skipV2 = false; // 是否跳过版本2的升级
// 从版本1升级
if (oldVersion == 1) {
upgradeToV2(db);
skipV2 = true; // this upgrade including the upgrade from v2 to v3
oldVersion++;
}
// 从版本2升级跳过已在版本1升级中处理的情况
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db);
reCreateTriggers = true;
oldVersion++;
}
// 从版本3升级
if (oldVersion == 3) {
upgradeToV4(db);
oldVersion++;
}
// 如果需要,重建触发器
if (reCreateTriggers) {
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
}
// 检查升级是否成功
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
throw new IllegalStateException("Upgrade notes database to version " + newVersion);
}
}
/**
* 2
* @param db
*/
private void upgradeToV2(SQLiteDatabase db) {
// 删除旧表
db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);
// 创建新表
createNoteTable(db);
createDataTable(db);
}
/**
* 3
* @param 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_delete");
db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update");
// add a column for gtask id
// 添加gtask_id列用于同步
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''");
// add a trash system folder
// 添加回收站系统文件夹
ContentValues values = new ContentValues();
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER);
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
db.insert(TABLE.NOTE, null, values);
}
/**
* 4
* @param db
*/
private void upgradeToV4(SQLiteDatabase db) {
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0");

@ -35,35 +35,58 @@ import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
/**
*
* CRUDContentProvider
*
*/
public class NotesProvider extends ContentProvider {
private static final UriMatcher mMatcher;
private static final UriMatcher mMatcher; // URI匹配器用于解析不同类型的URI请求
// 数据库帮助类实例,负责获取读写数据库
private NotesDatabaseHelper mHelper;
private static final String TAG = "NotesProvider";
private static final String TAG = "NotesProvider"; // 日志标签
// URI匹配码笔记列表
private static final int URI_NOTE = 1;
// URI匹配码单个笔记
private static final int URI_NOTE_ITEM = 2;
// URI匹配码数据列表
private static final int URI_DATA = 3;
// URI匹配码单个数据项
private static final int URI_DATA_ITEM = 4;
// URI匹配码搜索
private static final int URI_SEARCH = 5;
// URI匹配码搜索建议
private static final int URI_SEARCH_SUGGEST = 6;
/**
* - URI
*/
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
// 笔记集合URI
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
// 单个笔记URI带ID
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
// 笔记数据集合URI
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
// 单个笔记数据URI带ID
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
// 搜索URI
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
// 搜索建议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);
}
/**
* 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.
*
* x'0A' SQLite 便
*/
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
@ -73,18 +96,35 @@ public class NotesProvider extends ContentProvider {
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;
/**
*
*
*/
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION
+ " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
/**
*
*
*/
@Override
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
/**
* -
* @param uri URI
* @param projection
* @param selection
* @param selectionArgs
* @param sortOrder
* @return
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
@ -92,26 +132,27 @@ public class NotesProvider extends ContentProvider {
SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null;
switch (mMatcher.match(uri)) {
case URI_NOTE:
case URI_NOTE: // 查询笔记集合
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_NOTE_ITEM:
case URI_NOTE_ITEM: // 查询单个笔记
id = uri.getPathSegments().get(1);
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_DATA:
case URI_DATA: // 查询笔记数据集合
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_DATA_ITEM:
case URI_DATA_ITEM: // 查询单个笔记数据
id = uri.getPathSegments().get(1);
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_SEARCH:
case URI_SEARCH_SUGGEST:
case URI_SEARCH: // 搜索请求
case URI_SEARCH_SUGGEST: // 搜索建议请求
if (sortOrder != null || projection != null) {
throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
@ -142,20 +183,27 @@ public class NotesProvider extends ContentProvider {
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (c != null) {
// 通知游标所关联的 URI用于后续数据变更时自动刷新
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
/**
* -
* @param uri URI
* @param values
* @return URI
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase();
long dataId = 0, noteId = 0, insertedId = 0;
switch (mMatcher.match(uri)) {
case URI_NOTE:
case URI_NOTE: // 插入笔记
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break;
case URI_DATA:
case URI_DATA: // 插入笔记数据
if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID);
} else {
@ -166,13 +214,13 @@ public class NotesProvider extends ContentProvider {
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// Notify the note uri
// 通知笔记 URI 变化noteId>0 时)
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// Notify the data uri
// 通知数据 URI 变化dataId>0 时)
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
@ -181,6 +229,13 @@ public class NotesProvider extends ContentProvider {
return ContentUris.withAppendedId(uri, insertedId);
}
/**
* -
* @param uri URI
* @param selection
* @param selectionArgs
* @return
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
@ -197,6 +252,7 @@ public class NotesProvider extends ContentProvider {
/**
* ID that smaller than 0 is system folder which is not allowed to
* trash
* ID0
*/
long noteId = Long.valueOf(id);
if (noteId <= 0) {
@ -220,6 +276,7 @@ public class NotesProvider extends ContentProvider {
}
if (count > 0) {
if (deleteData) {
// 数据行删除会影响笔记摘要,通知笔记 URI
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
getContext().getContentResolver().notifyChange(uri, null);
@ -227,28 +284,37 @@ public class NotesProvider extends ContentProvider {
return count;
}
/**
*
* @param uri URI
* @param values
* @param selection
* @param selectionArgs
* @return
*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean updateData = false;
boolean updateData = false; // 是否更新了数据内容
switch (mMatcher.match(uri)) {
case URI_NOTE:
increaseNoteVersion(-1, selection, selectionArgs);
case URI_NOTE: // 更新所有笔记
increaseNoteVersion(-1, selection, selectionArgs); // 增加笔记版本号
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
case URI_NOTE_ITEM: // 更新单个笔记
id = uri.getPathSegments().get(1);
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); // 增加笔记版本号
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
break;
case URI_DATA:
case URI_DATA: // 更新所有笔记数据
count = db.update(TABLE.DATA, values, selection, selectionArgs);
updateData = true;
break;
case URI_DATA_ITEM:
case URI_DATA_ITEM: // 更新单个笔记数据
id = uri.getPathSegments().get(1);
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
@ -260,17 +326,32 @@ public class NotesProvider extends ContentProvider {
if (count > 0) {
if (updateData) {
// 数据行更新影响笔记摘要,通知笔记 URI
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
// 通知URI的观察者数据已更改
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
/**
* WHERE selection AND (...)
*
* @param selection
* @return
*/
private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ")" : "");
}
/**
*
*
* @param id ID0ID使 selection
* @param selection
* @param selectionArgs
*/
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
@ -296,6 +377,11 @@ public class NotesProvider extends ContentProvider {
mHelper.getWritableDatabase().execSQL(sql.toString());
}
/**
* URIMIME
* @param uri URI
* @return MIME - null
*/
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub

@ -25,11 +25,22 @@ import org.json.JSONException;
import org.json.JSONObject;
/**
* - Task
* Google
*/
public class MetaData extends Task {
// 日志标签
private final static String TAG = MetaData.class.getSimpleName();
// 相关的Google任务ID
private String mRelatedGid = null;
/**
*
* @param gid GoogleID
* @param metaInfo JSON
*/
public void setMeta(String gid, JSONObject metaInfo) {
try {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
@ -40,15 +51,27 @@ public class MetaData extends Task {
setName(GTaskStringUtils.META_NOTE_NAME);
}
/**
* GoogleID
* @return GoogleID
*/
public String getRelatedGid() {
return mRelatedGid;
}
/**
*
* @return truefalse
*/
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
/**
* JSON
* @param js JSON
*/
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js);
@ -63,17 +86,33 @@ public class MetaData extends Task {
}
}
/**
* JSON
*
* @param js JSON
*/
@Override
public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
/**
* JSON
*
* @return JSON
*/
@Override
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
}
/**
*
*
* @param c
* @return
*/
@Override
public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called");

@ -20,33 +20,32 @@ 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 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; // Google任务ID
private String mName; // 节点名称
private long mLastModified; // 最后修改时间戳
private boolean mDeleted; // 是否已删除
/**
*
*
*/
public Node() {
mGid = null;
mName = "";
@ -54,46 +53,105 @@ public abstract class Node {
mDeleted = false;
}
/**
* JSON
* @param actionId ID
* @return JSON
*/
public abstract JSONObject getCreateAction(int actionId);
/**
* JSON
* @param actionId ID
* @return JSON
*/
public abstract JSONObject getUpdateAction(int actionId);
/**
* JSON
* @param js JSON
*/
public abstract void setContentByRemoteJSON(JSONObject js);
/**
* JSON
* @param js JSON
*/
public abstract void setContentByLocalJSON(JSONObject js);
/**
* JSON
* @return JSON
*/
public abstract JSONObject getLocalJSONFromContent();
/**
*
* @param c
* @return
*/
public abstract int getSyncAction(Cursor c);
/**
* GoogleID
* @param gid GoogleID
*/
public void setGid(String gid) {
this.mGid = gid;
}
/**
*
* @param name
*/
public void setName(String name) {
this.mName = name;
}
/**
*
* @param lastModified
*/
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
}
/**
*
* @param deleted
*/
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
}
/**
* GoogleID
* @return GoogleID
*/
public String getGid() {
return this.mGid;
}
/**
*
* @return
*/
public String getName() {
return this.mName;
}
/**
*
* @return
*/
public long getLastModified() {
return this.mLastModified;
}
/**
*
* @return
*/
public boolean getDeleted() {
return this.mDeleted;
}

@ -35,42 +35,44 @@ import org.json.JSONException;
import org.json.JSONObject;
/**
* SQL - CRUD
* ContentResolver
*/
public class SqlData {
// 日志标签
private static final String TAG = SqlData.class.getSimpleName();
// 无效ID常量
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 static final int DATA_ID_COLUMN = 0; // 数据ID列
public static final int DATA_MIME_TYPE_COLUMN = 1; // MIME类型列
public static final int DATA_CONTENT_COLUMN = 2; // 内容列
public static final int DATA_CONTENT_DATA_1_COLUMN = 3; // DATA1列
public static final int DATA_CONTENT_DATA_3_COLUMN = 4; // DATA3列
// 成员变量
private ContentResolver mContentResolver; // 内容解析器
private boolean mIsCreate; // 是否为创建模式
private long mDataId; // 数据ID
private String mDataMimeType; // 数据MIME类型
private String mDataContent; // 数据内容
private long mDataContentData1; // DATA1字段值
private String mDataContentData3; // DATA3字段值
private ContentValues mDiffDataValues; // 差异数据值,用于更新操作
/**
* -
* @param context
*/
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true;
@ -82,6 +84,11 @@ public class SqlData {
mDiffDataValues = new ContentValues();
}
/**
* -
* @param context
* @param c
*/
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false;
@ -89,6 +96,10 @@ public class SqlData {
mDiffDataValues = new ContentValues();
}
/**
*
* @param c
*/
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
@ -97,6 +108,11 @@ public class SqlData {
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
/**
* JSON
* @param js JSON
* @throws JSONException JSON
*/
public void setContent(JSONObject js) throws JSONException {
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId != dataId) {
@ -130,6 +146,11 @@ public class SqlData {
mDataContentData3 = dataContentData3;
}
/**
* JSON
* @return JSON
* @throws JSONException JSON
*/
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
@ -144,6 +165,13 @@ public class SqlData {
return js;
}
/**
*
* @param noteId ID
* @param validateVersion
* @param version
* @throws ActionFailureException
*/
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
@ -183,6 +211,10 @@ public class SqlData {
mIsCreate = false;
}
/**
* ID
* @return ID
*/
public long getId() {
return mDataId;
}

@ -38,11 +38,21 @@ import org.json.JSONObject;
import java.util.ArrayList;
/**
* SqlNote -
* Google Tasks
*/
public class SqlNote {
private static final String TAG = SqlNote.class.getSimpleName();
/**
* ID - ID
*/
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,
@ -52,76 +62,185 @@ public class SqlNote {
NoteColumns.VERSION
};
/**
* ID - PROJECTION_NOTEID
*/
public static final int ID_COLUMN = 0;
/**
* - PROJECTION_NOTE
*/
public static final int ALERTED_DATE_COLUMN = 1;
/**
* ID - PROJECTION_NOTEID
*/
public static final int BG_COLOR_ID_COLUMN = 2;
/**
* - PROJECTION_NOTE
*/
public static final int CREATED_DATE_COLUMN = 3;
/**
* - PROJECTION_NOTE
*/
public static final int HAS_ATTACHMENT_COLUMN = 4;
/**
* - PROJECTION_NOTE
*/
public static final int MODIFIED_DATE_COLUMN = 5;
/**
* - PROJECTION_NOTE
*/
public static final int NOTES_COUNT_COLUMN = 6;
/**
* ID - PROJECTION_NOTEID
*/
public static final int PARENT_ID_COLUMN = 7;
/**
* - PROJECTION_NOTE
*/
public static final int SNIPPET_COLUMN = 8;
/**
* - PROJECTION_NOTE
*/
public static final int TYPE_COLUMN = 9;
/**
* ID - PROJECTION_NOTEID
*/
public static final int WIDGET_ID_COLUMN = 10;
/**
* - PROJECTION_NOTE
*/
public static final int WIDGET_TYPE_COLUMN = 11;
/**
* ID - PROJECTION_NOTEID
*/
public static final int SYNC_ID_COLUMN = 12;
/**
* - PROJECTION_NOTE
*/
public static final int LOCAL_MODIFIED_COLUMN = 13;
/**
* ID - PROJECTION_NOTEID
*/
public static final int ORIGIN_PARENT_ID_COLUMN = 14;
/**
* Google Tasks ID - PROJECTION_NOTEGoogle Tasks ID
*/
public static final int GTASK_ID_COLUMN = 15;
/**
* - PROJECTION_NOTE
*/
public static final int VERSION_COLUMN = 16;
/**
* - 访
*/
private Context mContext;
/**
* - 访ContentProvider
*/
private ContentResolver mContentResolver;
/**
* -
*/
private boolean mIsCreate;
/**
* ID -
*/
private long mId;
/**
* -
*/
private long mAlertDate;
/**
* ID - ID
*/
private int mBgColorId;
/**
* -
*/
private long mCreatedDate;
/**
* - 01
*/
private int mHasAttachment;
/**
* -
*/
private long mModifiedDate;
/**
* ID - ID
*/
private long mParentId;
/**
* -
*/
private String mSnippet;
/**
* -
*/
private int mType;
/**
* ID - ID
*/
private int mWidgetId;
/**
* -
*/
private int mWidgetType;
/**
* ID - ID
*/
private long mOriginParent;
/**
* -
*/
private long mVersion;
/**
* -
*/
private ContentValues mDiffNoteValues;
/**
* -
*/
private ArrayList<SqlData> mDataList;
/**
* -
* @param context
*/
public SqlNote(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
@ -143,6 +262,11 @@ public class SqlNote {
mDataList = new ArrayList<SqlData>();
}
/**
* -
* @param context
* @param c
*/
public SqlNote(Context context, Cursor c) {
mContext = context;
mContentResolver = context.getContentResolver();
@ -154,6 +278,11 @@ public class SqlNote {
mDiffNoteValues = new ContentValues();
}
/**
* - ID
* @param context
* @param id ID
*/
public SqlNote(Context context, long id) {
mContext = context;
mContentResolver = context.getContentResolver();
@ -163,9 +292,12 @@ public class SqlNote {
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues();
}
/**
* ID
* @param id ID
*/
private void loadFromCursor(long id) {
Cursor c = null;
try {
@ -185,6 +317,10 @@ public class SqlNote {
}
}
/**
*
* @param c
*/
private void loadFromCursor(Cursor c) {
mId = c.getLong(ID_COLUMN);
mAlertDate = c.getLong(ALERTED_DATE_COLUMN);
@ -200,6 +336,10 @@ public class SqlNote {
mVersion = c.getLong(VERSION_COLUMN);
}
/**
*
*
*/
private void loadDataContent() {
Cursor c = null;
mDataList.clear();
@ -226,13 +366,19 @@ public class SqlNote {
}
}
/**
* JSON
* @param js JSON
* @return truefalse
*/
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)) {
@ -247,6 +393,7 @@ public class SqlNote {
}
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) {
@ -331,9 +478,11 @@ public class SqlNote {
}
mOriginParent = originParent;
// 处理关联的数据项
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
SqlData sqlData = null;
// 查找是否已存在相同ID的数据项
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
for (SqlData temp : mDataList) {
@ -343,11 +492,13 @@ public class SqlNote {
}
}
// 如果不存在,则创建新的数据项
if (sqlData == null) {
sqlData = new SqlData(mContext);
mDataList.add(sqlData);
}
// 设置数据项内容
sqlData.setContent(data);
}
}
@ -359,17 +510,23 @@ public class SqlNote {
return true;
}
/**
* JSON
* @return JSONnull
*/
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);
@ -384,6 +541,7 @@ public class SqlNote {
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();
@ -393,6 +551,7 @@ public class SqlNote {
}
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);
@ -407,47 +566,87 @@ public class SqlNote {
return null;
}
/**
* ID
* @param id ID
*/
public void setParentId(long id) {
mParentId = id;
mDiffNoteValues.put(NoteColumns.PARENT_ID, id);
}
/**
* Google Tasks ID
* @param gid Google Tasks ID
*/
public void setGtaskId(String gid) {
mDiffNoteValues.put(NoteColumns.GTASK_ID, gid);
}
/**
* ID
* @param syncId ID
*/
public void setSyncId(long syncId) {
mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId);
}
/**
*
* 0
*/
public void resetLocalModified() {
mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0);
}
/**
* ID
* @return ID
*/
public long getId() {
return mId;
}
/**
* ID
* @return ID
*/
public long getParentId() {
return mParentId;
}
/**
*
* @return
*/
public String getSnippet() {
return mSnippet;
}
/**
*
* @return truefalse
*/
public boolean isNoteType() {
return mType == Notes.TYPE_NOTE;
}
/**
*
* @param validateVersion
*/
public void commit(boolean validateVersion) {
if (mIsCreate) {
// 创建新笔记
// 如果ID无效且包含ID字段则移除该字段由数据库自动生成
if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) {
mDiffNoteValues.remove(NoteColumns.ID);
}
// 插入新笔记到数据库
Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues);
try {
// 从返回的URI中提取新生成的笔记ID
mId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
@ -457,25 +656,32 @@ public class SqlNote {
throw new IllegalStateException("Create thread id failed");
}
// 如果是普通笔记,提交关联的数据项
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, false, -1);
}
}
} else {
// 更新现有笔记
// 验证笔记ID的有效性根文件夹和通话记录文件夹是特殊情况
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 ++;
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[] {
@ -487,6 +693,7 @@ public class SqlNote {
}
}
// 如果是普通笔记,提交关联的数据项
if (mType == Notes.TYPE_NOTE) {
for (SqlData sqlData : mDataList) {
sqlData.commit(mId, validateVersion, mVersion);
@ -494,11 +701,12 @@ public class SqlNote {
}
}
// refresh local info
// 刷新本地信息,确保与数据库保持一致
loadFromCursor(mId);
if (mType == Notes.TYPE_NOTE)
loadDataContent();
// 清除差异内容值并标记为已创建
mDiffNoteValues.clear();
mIsCreate = false;
}

@ -32,64 +32,91 @@ import org.json.JSONException;
import org.json.JSONObject;
/**
* Task - Google Tasks
* Node
*/
public class Task extends Node {
private static final String TAG = Task.class.getSimpleName();
/**
* -
*/
private boolean mCompleted;
/**
* -
*/
private String mNotes;
/**
* - JSON
*/
private JSONObject mMetaInfo;
/**
* -
*/
private Task mPriorSibling;
/**
* -
*/
private TaskList mParent;
/**
* -
*/
public Task() {
super();
mCompleted = false;
mNotes = null;
mPriorSibling = null;
mParent = null;
mMetaInfo = null;
mCompleted = false; // 默认未完成
mNotes = null; // 默认无备注
mPriorSibling = null; // 默认无前一个兄弟任务
mParent = null; // 默认无父任务列表
mMetaInfo = null; // 默认无元信息
}
/**
* JSON
* @param actionId ID
* @return JSON
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
// 设置操作类型为创建
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// action_id
// 设置操作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_NAME, getName()); // 任务名称
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 创建者ID
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_TASK);
GTaskStringUtils.GTASK_JSON_TYPE_TASK); // 实体类型为任务
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 任务备注(如果有)
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
// parent_id
// 设置父任务列表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
// 设置列表ID
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// prior_sibling_id
// 设置前一个兄弟任务ID如果有
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
@ -103,27 +130,32 @@ public class Task extends Node {
return js;
}
/**
* JSON
* @param actionId ID
* @return JSON
*/
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
// 设置操作类型为更新
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// action_id
// 设置操作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// id
// 设置任务ID
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// entity_delta
// 设置要更新的实体信息
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 更新任务名称
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 更新任务备注(如果有)
}
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // 更新删除状态
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
@ -135,35 +167,39 @@ public class Task extends Node {
return js;
}
/**
* JSON
* @param js JSON
*/
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// id
// 设置任务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));
}
@ -175,21 +211,28 @@ public class Task extends Node {
}
}
/**
* JSON
* @param js JSON
*/
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");
return;
}
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)) {
@ -204,31 +247,37 @@ public class Task extends Node {
}
}
/**
* JSON
* @return JSON
*/
public JSONObject getLocalJSONFromContent() {
String name = getName();
try {
if (mMetaInfo == null) {
// new task created from web
// 从网络创建的新任务
if (name == null) {
Log.w(TAG, "the note seems to be an empty one");
return null;
}
// 创建新的JSON对象结构
JSONObject js = new JSONObject();
JSONObject note = new JSONObject();
JSONArray dataArray = new JSONArray();
JSONObject data = new JSONObject();
data.put(DataColumns.CONTENT, name);
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);
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)) {
@ -237,7 +286,7 @@ public class Task extends Node {
}
}
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // 确保笔记类型为普通笔记
return mMetaInfo;
}
} catch (JSONException e) {
@ -247,9 +296,14 @@ public class Task extends Node {
}
}
/**
*
* @param metaData MetaData
*/
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) {
try {
// 从元数据中解析JSON对象
mMetaInfo = new JSONObject(metaData.getNotes());
} catch (JSONException e) {
Log.w(TAG, e.toString());
@ -258,6 +312,11 @@ public class Task extends Node {
}
}
/**
*
* @param c
* @return SYNC_ACTION_NONESYNC_ACTION_UPDATE_LOCALSYNC_ACTION_UPDATE_REMOTESYNC_ACTION_ERROR
*/
public int getSyncAction(Cursor c) {
try {
JSONObject noteInfo = null;
@ -266,40 +325,43 @@ public class Task extends Node {
}
if (noteInfo == null) {
// 元信息已删除,更新远程
Log.w(TAG, "it seems that note meta has been deleted");
return SYNC_ACTION_UPDATE_REMOTE;
}
if (!noteInfo.has(NoteColumns.ID)) {
// 远程笔记ID已删除更新本地
Log.w(TAG, "remote note id seems to be deleted");
return SYNC_ACTION_UPDATE_LOCAL;
}
// validate the note id now
// 验证笔记ID
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
// 本地有更新验证Google Tasks 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;
}
}
@ -311,39 +373,75 @@ public class Task extends Node {
return SYNC_ACTION_ERROR;
}
/**
*
* @return truefalse
*/
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0);
}
/**
*
* @param completed
*/
public void setCompleted(boolean completed) {
this.mCompleted = completed;
}
/**
*
* @param notes
*/
public void setNotes(String notes) {
this.mNotes = notes;
}
/**
*
* @param priorSibling
*/
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling;
}
/**
*
* @param parent
*/
public void setParent(TaskList parent) {
this.mParent = parent;
}
/**
*
* @return
*/
public boolean getCompleted() {
return this.mCompleted;
}
/**
*
* @return
*/
public String getNotes() {
return this.mNotes;
}
/**
*
* @return
*/
public Task getPriorSibling() {
return this.mPriorSibling;
}
/**
*
* @return
*/
public TaskList getParent() {
return this.mParent;
}

@ -30,39 +30,57 @@ import org.json.JSONObject;
import java.util.ArrayList;
/**
* TaskList - Google Tasks
* Node
*/
public class TaskList extends Node {
private static final String TAG = TaskList.class.getSimpleName();
/**
* -
*/
private int mIndex;
/**
* -
*/
private ArrayList<Task> mChildren;
/**
* -
*/
public TaskList() {
super();
mChildren = new ArrayList<Task>();
mIndex = 1;
mChildren = new ArrayList<Task>(); // 初始化子任务列表
mIndex = 1; // 默认索引为1
}
/**
* JSON
* @param actionId ID
* @return JSON
*/
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
// 设置操作类型为创建
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// action_id
// 设置操作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// index
// 设置任务列表索引
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex);
// entity_delta
// 设置任务列表实体信息
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 任务列表名称
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // 创建者ID
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);
} catch (JSONException e) {
@ -74,24 +92,29 @@ public class TaskList extends Node {
return js;
}
/**
* JSON
* @param actionId ID
* @return JSON
*/
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject();
try {
// action_type
// 设置操作类型为更新
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// action_id
// 设置操作ID
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// id
// 设置任务列表ID
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// entity_delta
// 设置要更新的实体信息
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // 更新任务列表名称
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // 更新删除状态
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) {
@ -103,20 +126,24 @@ public class TaskList extends Node {
return js;
}
/**
* JSON
* @param js JSON
*/
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// id
// 设置任务列表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));
}
@ -129,18 +156,26 @@ public class TaskList extends Node {
}
}
/**
* JSON
* @param js JSON
*/
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) {
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
return;
}
try {
JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
// 根据本地文件夹类型设置任务列表名称
if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) {
// 普通文件夹,添加前缀
String name = folder.getString(NoteColumns.SNIPPET);
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name);
} else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) {
// 系统文件夹根据ID设置不同名称
if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER)
setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT);
else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER)
@ -157,21 +192,30 @@ public class TaskList extends Node {
}
}
/**
* JSON
* @return JSON
*/
public JSONObject getLocalJSONFromContent() {
try {
JSONObject js = new JSONObject();
JSONObject folder = new JSONObject();
// 移除任务列表名称的前缀
String folderName = getName();
if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX))
folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(),
folderName.length());
// 设置文件夹名称
folder.put(NoteColumns.SNIPPET, folderName);
// 根据名称设置文件夹类型
if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT)
|| folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE))
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM);
folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 系统文件夹
else
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER);
folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 普通文件夹
js.put(GTaskStringUtils.META_HEAD_NOTE, folder);
@ -183,28 +227,33 @@ public class TaskList extends Node {
}
}
/**
*
* @param c
* @return SYNC_ACTION_NONESYNC_ACTION_UPDATE_LOCALSYNC_ACTION_UPDATE_REMOTESYNC_ACTION_ERROR
*/
public int getSyncAction(Cursor c) {
try {
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// there is no local update
// 本地没有更新
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// no update both side
// 双方都没有更新
return SYNC_ACTION_NONE;
} else {
// apply remote to local
// 将远程更新应用到本地
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// validate gtask id
// 本地有更新验证Google Tasks ID
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// local modification only
// 只有本地更新
return SYNC_ACTION_UPDATE_REMOTE;
} else {
// for folder conflicts, just apply local modification
// 对于文件夹冲突,直接应用本地修改
return SYNC_ACTION_UPDATE_REMOTE;
}
}
@ -216,16 +265,25 @@ public class TaskList extends Node {
return SYNC_ACTION_ERROR;
}
/**
*
* @return
*/
public int getChildTaskCount() {
return mChildren.size();
}
/**
*
* @param task
* @return truefalse
*/
public boolean addChildTask(Task task) {
boolean ret = false;
if (task != null && !mChildren.contains(task)) {
ret = mChildren.add(task);
if (ret) {
// need to set prior sibling and parent
// 设置前一个兄弟任务和父任务
task.setPriorSibling(mChildren.isEmpty() ? null : mChildren
.get(mChildren.size() - 1));
task.setParent(this);
@ -234,6 +292,12 @@ public class TaskList extends Node {
return ret;
}
/**
*
* @param task
* @param index
* @return truefalse
*/
public boolean addChildTask(Task task, int index) {
if (index < 0 || index > mChildren.size()) {
Log.e(TAG, "add child task: invalid index");
@ -244,7 +308,7 @@ public class TaskList extends Node {
if (task != null && pos == -1) {
mChildren.add(index, task);
// update the task list
// 更新任务列表的兄弟关系
Task preTask = null;
Task afterTask = null;
if (index != 0)
@ -260,6 +324,11 @@ public class TaskList extends Node {
return true;
}
/**
*
* @param task
* @return truefalse
*/
public boolean removeChildTask(Task task) {
boolean ret = false;
int index = mChildren.indexOf(task);
@ -267,11 +336,11 @@ public class TaskList extends Node {
ret = mChildren.remove(task);
if (ret) {
// reset prior sibling and parent
// 重置前一个兄弟任务和父任务
task.setPriorSibling(null);
task.setParent(null);
// update the task list
// 更新任务列表的兄弟关系
if (index != mChildren.size()) {
mChildren.get(index).setPriorSibling(
index == 0 ? null : mChildren.get(index - 1));
@ -281,8 +350,13 @@ public class TaskList extends Node {
return ret;
}
/**
*
* @param task
* @param index
* @return truefalse
*/
public boolean moveChildTask(Task task, int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "move child task: invalid index");
return false;
@ -299,6 +373,11 @@ public class TaskList extends Node {
return (removeChildTask(task) && addChildTask(task, index));
}
/**
* Google Tasks ID
* @param gid Google Tasks ID
* @return null
*/
public Task findChildTaskByGid(String gid) {
for (int i = 0; i < mChildren.size(); i++) {
Task t = mChildren.get(i);
@ -309,10 +388,20 @@ public class TaskList extends Node {
return null;
}
/**
*
* @param task
* @return
*/
public int getChildTaskIndex(Task task) {
return mChildren.indexOf(task);
}
/**
*
* @param index
* @return null
*/
public Task getChildTaskByIndex(int index) {
if (index < 0 || index >= mChildren.size()) {
Log.e(TAG, "getTaskByIndex: invalid index");
@ -321,6 +410,11 @@ public class TaskList extends Node {
return mChildren.get(index);
}
/**
* Google Tasks ID
* @param gid Google Tasks ID
* @return null
*/
public Task getChilTaskByGid(String gid) {
for (Task task : mChildren) {
if (task.getGid().equals(gid))
@ -329,14 +423,26 @@ public class TaskList extends Node {
return null;
}
/**
*
* @return
*/
public ArrayList<Task> getChildTaskList() {
return this.mChildren;
}
/**
*
* @param index
*/
public void setIndex(int index) {
this.mIndex = index;
}
/**
*
* @return
*/
public int getIndex() {
return this.mIndex;
}

@ -16,17 +16,33 @@
package net.micode.notes.gtask.exception;
/**
* Google Tasks
*/
public class ActionFailureException extends RuntimeException {
/** 序列化版本UID */
private static final long serialVersionUID = 4425249765923293627L;
/**
*
*/
public ActionFailureException() {
super();
}
/**
*
* @param paramString
*/
public ActionFailureException(String paramString) {
super(paramString);
}
/**
*
* @param paramString
* @param paramThrowable
*/
public ActionFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}

@ -16,17 +16,33 @@
package net.micode.notes.gtask.exception;
/**
* Google Tasks
*/
public class NetworkFailureException extends Exception {
/** 序列化版本UID */
private static final long serialVersionUID = 2107610287180234136L;
/**
*
*/
public NetworkFailureException() {
super();
}
/**
*
* @param paramString
*/
public NetworkFailureException(String paramString) {
super(paramString);
}
/**
*
* @param paramString
* @param paramThrowable
*/
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}

@ -29,22 +29,51 @@ import net.micode.notes.ui.NotesListActivity;
import net.micode.notes.ui.NotesPreferenceActivity;
/**
* GTaskASyncTask- Google Tasks
*/
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
/**
* ID - Google Tasks
*/
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
/**
* OnCompleteListener -
*/
public interface OnCompleteListener {
/**
*
*/
void onComplete();
}
/**
* - 访
*/
private Context mContext;
/**
* -
*/
private NotificationManager mNotifiManager;
/**
* -
*/
private GTaskManager mTaskManager;
/**
* -
*/
private OnCompleteListener mOnCompleteListener;
/**
* - Google Tasks
* @param context
* @param listener
*/
public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context;
mOnCompleteListener = listener;
@ -53,35 +82,70 @@ public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
mTaskManager = GTaskManager.getInstance();
}
/**
*
*/
public void cancelSync() {
mTaskManager.cancelSync();
}
/**
*
* @param message
*/
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);
//}
/**
*
* @param tickerId ID
* @param content
*/
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);
NotesPreferenceActivity.class), PendingIntent.FLAG_IMMUTABLE);
} else {
// 同步成功时,点击通知跳转到笔记列表页面
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), 0);
NotesListActivity.class), PendingIntent.FLAG_IMMUTABLE);
}
notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
pendingIntent);
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
// 创建通知构建器
Notification.Builder builder = new Notification.Builder(mContext)
.setAutoCancel(true) // 点击后自动取消通知
.setContentTitle(mContext.getString(R.string.app_name)) // 设置通知标题
.setContentText(content) // 设置通知内容
.setContentIntent(pendingIntent) // 设置点击通知时的跳转意图
.setWhen(System.currentTimeMillis()) // 设置通知时间
.setOngoing(true); // 设置为持续通知
Notification notification=builder.getNotification();
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); // 显示通知
}
@Override
protected Integer doInBackground(Void... unused) {
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity

@ -61,35 +61,44 @@ import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
/**
* Google Tasks
* Google Tasks APICRUD
* 使
*/
public class GTaskClient {
private static final String TAG = GTaskClient.class.getSimpleName();
private static final String TAG = GTaskClient.class.getSimpleName(); // 日志标签
private static final String GTASK_URL = "https://mail.google.com/tasks/";
private static final String GTASK_URL = "https://mail.google.com/tasks/"; // Google Tasks基础URL
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig";
private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; // Google Tasks GET请求URL
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig";
private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; // Google Tasks POST请求URL
private static GTaskClient mInstance = null;
private static GTaskClient mInstance = null; // 单例实例
private DefaultHttpClient mHttpClient;
private DefaultHttpClient mHttpClient; // HTTP客户端实例
private String mGetUrl;
private String mGetUrl; // 当前使用的GET请求URL
private String mPostUrl;
private String mPostUrl; // 当前使用的POST请求URL
private long mClientVersion;
private long mClientVersion; // 客户端版本号
private boolean mLoggedin;
private boolean mLoggedin; // 登录状态标识
private long mLastLoginTime;
private long mLastLoginTime; // 最后登录时间
private int mActionId;
private int mActionId; // 操作ID计数器
private Account mAccount;
private Account mAccount; // Google账户
private JSONArray mUpdateArray;
private JSONArray mUpdateArray; // 更新操作数组,用于批量提交更新
/**
* -
*
*/
private GTaskClient() {
mHttpClient = null;
mGetUrl = GTASK_GET_URL;
@ -102,6 +111,11 @@ public class GTaskClient {
mUpdateArray = null;
}
/**
* GTaskClient
* 使线
* @return GTaskClient
*/
public static synchronized GTaskClient getInstance() {
if (mInstance == null) {
mInstance = new GTaskClient();
@ -109,6 +123,13 @@ public class GTaskClient {
return mInstance;
}
/**
* Google Tasks
* true
* GoogleGoogle Tasks
* @param activity Activity
* @return
*/
public boolean login(Activity activity) {
// we suppose that the cookie would expire after 5 minutes
// then we need to re-login
@ -164,6 +185,12 @@ public class GTaskClient {
return true;
}
/**
* Google
* @param activity Activity
* @param invalidateToken 使
* @return null
*/
private String loginGoogleAccount(Activity activity, boolean invalidateToken) {
String authToken;
AccountManager accountManager = AccountManager.get(activity);
@ -207,6 +234,13 @@ public class GTaskClient {
return authToken;
}
/**
* 使Google Tasks
*
* @param activity Activity
* @param authToken
* @return
*/
private boolean tryToLoginGtask(Activity activity, String authToken) {
if (!loginGtask(authToken)) {
// maybe the auth token is out of date, now let's invalidate the
@ -225,6 +259,12 @@ public class GTaskClient {
return true;
}
/**
* 使Google Tasks
* HTTP
* @param authToken
* @return
*/
private boolean loginGtask(String authToken) {
int timeoutConnection = 10000;
int timeoutSocket = 15000;
@ -280,10 +320,20 @@ public class GTaskClient {
return true;
}
/**
* ID
* ID
* @return ID
*/
private int getActionId() {
return mActionId++;
}
/**
* HTTP POST
* URL
* @return HttpPost
*/
private HttpPost createHttpPost() {
HttpPost httpPost = new HttpPost(mPostUrl);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
@ -291,6 +341,13 @@ public class GTaskClient {
return httpPost;
}
/**
* HTTP
* gzipdeflate
* @param entity HTTP
* @return
* @throws IOException
*/
private String getResponseContent(HttpEntity entity) throws IOException {
String contentEncoding = null;
if (entity.getContentEncoding() != null) {
@ -323,6 +380,14 @@ public class GTaskClient {
}
}
/**
* POSTGoogle Tasks API
* JSONJSON
* @param js JSON
* @return JSON
* @throws NetworkFailureException
* @throws ActionFailureException
*/
private JSONObject postRequest(JSONObject js) throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
@ -360,6 +425,13 @@ public class GTaskClient {
}
}
/**
* Google Tasks
* GID
* @param task
* @throws NetworkFailureException
* @throws ActionFailureException
*/
public void createTask(Task task) throws NetworkFailureException {
commitUpdate();
try {
@ -386,6 +458,13 @@ public class GTaskClient {
}
}
/**
* Google Tasks
* GID
* @param tasklist
* @throws NetworkFailureException
* @throws ActionFailureException
*/
public void createTaskList(TaskList tasklist) throws NetworkFailureException {
commitUpdate();
try {
@ -412,6 +491,12 @@ public class GTaskClient {
}
}
/**
* Google Tasks
* mUpdateArray
* @throws NetworkFailureException
* @throws ActionFailureException
*/
public void commitUpdate() throws NetworkFailureException {
if (mUpdateArray != null) {
try {
@ -433,6 +518,12 @@ public class GTaskClient {
}
}
/**
*
* 10
* @param node
* @throws NetworkFailureException
*/
public void addUpdateNode(Node node) throws NetworkFailureException {
if (node != null) {
// too many update items may result in an error
@ -447,6 +538,15 @@ public class GTaskClient {
}
}
/**
*
*
* @param task
* @param preParent
* @param curParent
* @throws NetworkFailureException
* @throws ActionFailureException
*/
public void moveTask(Task task, TaskList preParent, TaskList curParent)
throws NetworkFailureException {
commitUpdate();
@ -486,6 +586,13 @@ public class GTaskClient {
}
}
/**
*
* Google Tasks
* @param node
* @throws NetworkFailureException
* @throws ActionFailureException
*/
public void deleteNode(Node node) throws NetworkFailureException {
commitUpdate();
try {
@ -509,6 +616,13 @@ public class GTaskClient {
}
}
/**
*
* Google Tasks
* @return JSON
* @throws NetworkFailureException
* @throws ActionFailureException
*/
public JSONArray getTaskLists() throws NetworkFailureException {
if (!mLoggedin) {
Log.e(TAG, "please login first");
@ -547,6 +661,14 @@ public class GTaskClient {
}
}
/**
*
* Google Tasks
* @param listGid GID
* @return JSON
* @throws NetworkFailureException
* @throws ActionFailureException
*/
public JSONArray getTaskList(String listGid) throws NetworkFailureException {
commitUpdate();
try {
@ -575,10 +697,18 @@ public class GTaskClient {
}
}
/**
* Google
* @return Account
*/
public Account getSyncAccount() {
return mAccount;
}
/**
*
* mUpdateArray
*/
public void resetUpdateArray() {
mUpdateArray = null;
}

@ -48,45 +48,45 @@ import java.util.Iterator;
import java.util.Map;
/**
* Google Tasks
* Google Tasks
*
*/
public class GTaskManager {
private static final String TAG = GTaskManager.class.getSimpleName();
public static final int STATE_SUCCESS = 0;
public static final int STATE_NETWORK_ERROR = 1;
public static final int STATE_INTERNAL_ERROR = 2;
public static final int STATE_SYNC_IN_PROGRESS = 3;
public static final int STATE_SYNC_CANCELLED = 4;
private static GTaskManager mInstance = null;
private Activity mActivity;
private Context mContext;
private ContentResolver mContentResolver;
private boolean mSyncing;
private boolean mCancelled;
private HashMap<String, TaskList> mGTaskListHashMap;
private HashMap<String, Node> mGTaskHashMap;
private HashMap<String, MetaData> mMetaHashMap;
private TaskList mMetaList;
private HashSet<Long> mLocalDeleteIdMap;
private HashMap<String, Long> mGidToNid;
private HashMap<Long, String> mNidToGid;
private static final String TAG = GTaskManager.class.getSimpleName(); // 日志标签
// 同步状态常量定义
public static final int STATE_SUCCESS = 0; // 同步成功
public static final int STATE_NETWORK_ERROR = 1; // 网络错误
public static final int STATE_INTERNAL_ERROR = 2; // 内部错误
public static final int STATE_SYNC_IN_PROGRESS = 3; // 同步进行中
public static final int STATE_SYNC_CANCELLED = 4; // 同步已取消
private static GTaskManager mInstance = null; // 单例实例
private Activity mActivity; // 用于获取认证令牌的Activity实例
private Context mContext; // 应用上下文
private ContentResolver mContentResolver; // 内容解析器,用于访问本地数据库
private boolean mSyncing; // 同步状态标志
private boolean mCancelled; // 同步取消标志
// Google Tasks数据结构映射
private HashMap<String, TaskList> mGTaskListHashMap; // Google任务列表映射
private HashMap<String, Node> mGTaskHashMap; // Google任务节点映射
private HashMap<String, MetaData> mMetaHashMap; // 元数据映射
private TaskList mMetaList; // 元数据任务列表
// 同步辅助数据结构
private HashSet<Long> mLocalDeleteIdMap; // 本地删除的笔记ID集合
private HashMap<String, Long> mGidToNid; // Google任务ID到本地笔记ID的映射
private HashMap<Long, String> mNidToGid; // 本地笔记ID到Google任务ID的映射
/**
* -
*
*/
private GTaskManager() {
mSyncing = false;
mCancelled = false;
@ -99,6 +99,11 @@ public class GTaskManager {
mNidToGid = new HashMap<Long, String>();
}
/**
* GTaskManager
* 使线
* @return GTaskManager
*/
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
mInstance = new GTaskManager();
@ -106,11 +111,28 @@ public class GTaskManager {
return mInstance;
}
/**
* Activity
* Google
* @param activity Activity
*/
public synchronized void setActivityContext(Activity activity) {
// used for getting authtoken
mActivity = activity;
}
/**
* Google Tasks
* GTaskManager
* @param context
* @param asyncTask
* @return
* STATE_SUCCESS -
* STATE_NETWORK_ERROR -
* STATE_INTERNAL_ERROR -
* STATE_SYNC_IN_PROGRESS -
* STATE_SYNC_CANCELLED -
*/
public int sync(Context context, GTaskASyncTask asyncTask) {
if (mSyncing) {
Log.d(TAG, "Sync is in progress");
@ -168,6 +190,13 @@ public class GTaskManager {
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
/**
* Google Tasks
* Google
*
* @throws NetworkFailureException
* @throws ActionFailureException JSON
*/
private void initGTaskList() throws NetworkFailureException {
if (mCancelled)
return;
@ -247,6 +276,18 @@ public class GTaskManager {
}
}
/**
*
* Google Tasks
*
* 1.
* 2.
* 3.
* 4. Google
* 5.
* 6. ID
* @throws NetworkFailureException
*/
private void syncContent() throws NetworkFailureException {
int syncType;
Cursor c = null;
@ -351,6 +392,16 @@ public class GTaskManager {
}
/**
*
* Google Tasks
*
* 1.
* 2.
* 3.
* 4. Google
* @throws NetworkFailureException
*/
private void syncFolder() throws NetworkFailureException {
Cursor c = null;
String gid;
@ -476,6 +527,21 @@ public class GTaskManager {
GTaskClient.getInstance().commitUpdate();
}
/**
*
*
* @param syncType
* Node.SYNC_ACTION_ADD_LOCAL -
* Node.SYNC_ACTION_ADD_REMOTE -
* Node.SYNC_ACTION_DEL_LOCAL -
* Node.SYNC_ACTION_DEL_REMOTE -
* Node.SYNC_ACTION_UPDATE_LOCAL -
* Node.SYNC_ACTION_UPDATE_REMOTE -
* Node.SYNC_ACTION_UPDATE_CONFLICT -
* @param node
* @param c
* @throws NetworkFailureException
*/
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
@ -522,6 +588,12 @@ public class GTaskManager {
}
}
/**
*
* Google Tasks
* @param node Google Tasks
* @throws NetworkFailureException
*/
private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) {
return;
@ -596,6 +668,13 @@ public class GTaskManager {
updateRemoteMeta(node.getGid(), sqlNote);
}
/**
*
* 使Google Tasks
* @param node Google Tasks
* @param c
* @throws NetworkFailureException
*/
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
@ -619,6 +698,13 @@ public class GTaskManager {
updateRemoteMeta(node.getGid(), sqlNote);
}
/**
*
* Google Tasks
* @param node Google Tasksnull
* @param c
* @throws NetworkFailureException
*/
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
@ -692,6 +778,14 @@ public class GTaskManager {
mNidToGid.put(sqlNote.getId(), n.getGid());
}
/**
*
* Google Tasks
*
* @param node Google Tasks
* @param c
* @throws NetworkFailureException
*/
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
@ -730,6 +824,13 @@ public class GTaskManager {
sqlNote.commit(true);
}
/**
*
* Google Tasks
* @param gid Google TasksID
* @param sqlNote
* @throws NetworkFailureException
*/
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
if (sqlNote != null && sqlNote.isNoteType()) {
MetaData metaData = mMetaHashMap.get(gid);
@ -746,6 +847,12 @@ public class GTaskManager {
}
}
/**
* ID
* IDGoogle Tasks
*
* @throws NetworkFailureException
*/
private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) {
return;
@ -790,10 +897,18 @@ public class GTaskManager {
}
}
/**
*
* @return Google Tasks
*/
public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name;
}
/**
*
* 使
*/
public void cancelSync() {
mCancelled = true;
}

@ -23,25 +23,33 @@ import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
/**
* Google Tasks
* Google TasksService
* 广
*/
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 = "";
// 动作类型常量定义
public final static String ACTION_STRING_NAME = "sync_action_type"; // 动作类型的Intent额外数据键
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"; // 广播的Intent动作名称
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 = ""; // 当前同步进度消息
/**
*
* Google Tasks
*
*/
private void startSync() {
if (mSyncTask == null) {
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
@ -56,6 +64,10 @@ public class GTaskSyncService extends Service {
}
}
/**
*
*
*/
private void cancelSync() {
if (mSyncTask != null) {
mSyncTask.cancelSync();
@ -64,11 +76,13 @@ public class GTaskSyncService extends Service {
@Override
public void onCreate() {
// 服务创建时初始化同步任务为null
mSyncTask = null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 处理传入的Intent根据动作类型执行相应的同步操作
Bundle bundle = intent.getExtras();
if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) {
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) {
@ -81,6 +95,7 @@ public class GTaskSyncService extends Service {
default:
break;
}
// 返回START_STICKY表示如果服务被系统杀死会在资源允许时重新创建
return START_STICKY;
}
return super.onStartCommand(intent, flags, startId);
@ -88,15 +103,28 @@ public class GTaskSyncService extends Service {
@Override
public void onLowMemory() {
// 系统内存不足时,如果有同步任务正在执行,则取消它以释放资源
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
/**
* Service
* null
* @param intent
* @return null
*/
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* 广
*
* @param msg
*/
public void sendBroadcast(String msg) {
mSyncProgress = msg;
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
@ -105,6 +133,11 @@ public class GTaskSyncService extends Service {
sendBroadcast(intent);
}
/**
* Google Tasks
* ActivityIntent
* @param activity GoogleActivity
*/
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity);
Intent intent = new Intent(activity, GTaskSyncService.class);
@ -112,16 +145,29 @@ public class GTaskSyncService extends Service {
activity.startService(intent);
}
/**
* Google Tasks
* Intent
* @param context
*/
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);
}
/**
*
* @return truefalse
*/
public static boolean isSyncing() {
return mSyncTask != null;
}
/**
*
* @return
*/
public static String getProgressString() {
return mSyncProgress;
}

@ -34,15 +34,26 @@ import net.micode.notes.data.Notes.TextNote;
import java.util.ArrayList;
/**
*
*
*
*/
public class Note {
/** 笔记差异值,用于记录笔记表的修改字段(增量更新) */
private ContentValues mNoteDiffValues;
/** 笔记数据对象,负责文本与通话相关的数据行 */
private NoteData mNoteData;
private static final String TAG = "Note";
/**
* Create a new note id for adding a new note to databases
* ID
*
* @param context
* @param folderId ID
* @return ID>00
*/
public static synchronized long getNewNoteId(Context context, long folderId) {
// Create a new note in the database
// 在数据库中创建新笔记:设置创建/修改时间、类型、父文件夹等初始值
ContentValues values = new ContentValues();
long createdTime = System.currentTimeMillis();
values.put(NoteColumns.CREATED_DATE, createdTime);
@ -65,41 +76,82 @@ public class Note {
return noteId;
}
/**
* -
*/
public Note() {
mNoteDiffValues = new ContentValues();
mNoteData = new NoteData();
}
/**
*
* @param key
* @param value
*/
public void setNoteValue(String key, String value) {
mNoteDiffValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
* data
* @param key
* @param value
*/
public void setTextData(String key, String value) {
mNoteData.setTextData(key, value);
}
/**
* ID
* @param id ID
*/
public void setTextDataId(long id) {
mNoteData.setTextDataId(id);
}
/**
* ID
* @return ID
*/
public long getTextDataId() {
return mNoteData.mTextDataId;
}
/**
* ID
* @param id ID
*/
public void setCallDataId(long id) {
mNoteData.setCallDataId(id);
}
/**
* data
* @param key
* @param value
*/
public void setCallData(String key, String value) {
mNoteData.setCallData(key, value);
}
/**
*
* @return truefalse
*/
public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
/**
*
*
* @param context
* @param noteId ID
* @return true false
*/
public boolean syncNote(Context context, long noteId) {
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
@ -109,16 +161,12 @@ public class Note {
return true;
}
/**
* In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the
* note data info
*/
// 理论上任何变更都应更新 LOCAL_MODIFIED 与 MODIFIED_DATE即便 note 表更新失败,也继续尝试写 data
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
Log.e(TAG, "Update note error, should not happen");
// Do not return, fall through
// 不返回,继续执行
}
mNoteDiffValues.clear();
@ -130,17 +178,22 @@ public class Note {
return true;
}
/**
*
*
*/
private class NoteData {
private long mTextDataId;
private long mTextDataId; // 文本笔记数据ID
private ContentValues mTextDataValues; // 文本笔记数据值
private long mCallDataId; // 通话笔记数据ID
private ContentValues mCallDataValues; // 通话笔记数据值
private static final String TAG = "NoteData"; // 日志标签
private ContentValues mTextDataValues;
private long mCallDataId;
private ContentValues mCallDataValues;
private static final String TAG = "NoteData";
/** 构造函数:初始化两个数据集与对应 ID */
public NoteData() {
mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues();
@ -148,10 +201,19 @@ public class Note {
mCallDataId = 0;
}
/**
*
* @return truefalse
*/
boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
/**
* ID
* @param id ID
* @throws IllegalArgumentException ID0
*/
void setTextDataId(long id) {
if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
@ -159,6 +221,11 @@ public class Note {
mTextDataId = id;
}
/**
* ID
* @param id ID
* @throws IllegalArgumentException ID0
*/
void setCallDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
@ -166,22 +233,37 @@ public class Note {
mCallDataId = id;
}
/**
* note
* @param key
* @param value
*/
void setCallData(String key, String value) {
mCallDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
* note
* @param key
* @param value
*/
void setTextData(String key, String value) {
mTextDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
/**
*
*
* @param context
* @param noteId ID
* @return URInull
*/
Uri pushIntoContentResolver(Context context, long noteId) {
/**
* Check for safety
*/
// 安全检查
if (noteId <= 0) {
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
@ -190,6 +272,7 @@ public class Note {
ContentProviderOperation.Builder builder = null;
if(mTextDataValues.size() > 0) {
// 写入或更新文本数据MIME: text_note
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) {
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
@ -212,6 +295,7 @@ public class Note {
}
if(mCallDataValues.size() > 0) {
// 写入或更新通话数据MIME: call_note
mCallDataValues.put(DataColumns.NOTE_ID, noteId);
if (mCallDataId == 0) {
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);

@ -31,77 +31,79 @@ import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.tool.ResourceParser.NoteBgResources;
/**
*
* <p>/</p>
* <p>使</p>
*/
/**
*
*
*
*/
public class WorkingNote {
// Note for the working note
private Note mNote;
// Note Id
private long mNoteId;
// Note content
private String mContent;
// Note mode
private int mMode;
private long mAlertDate;
private long mModifiedDate;
private int mBgColorId;
private int mWidgetId;
private int mWidgetType;
private long mFolderId;
private Note mNote; // 实际的笔记数据模型
private long mNoteId; // 笔记ID
private String mContent; // 笔记内容
private int mMode; // 笔记模式(普通模式/清单模式)
private long mAlertDate; // 提醒日期时间
private long mModifiedDate; // 最后修改时间
private int mBgColorId; // 背景颜色ID
private int mWidgetId; // 小部件ID
private int mWidgetType; // 小部件类型
private long mFolderId; // 所属文件夹ID
private Context mContext; // 上下文对象
private static final String TAG = "WorkingNote"; // 日志标签
private boolean mIsDeleted; // 是否已删除
private NoteSettingChangedListener mNoteSettingStatusListener; // 笔记设置变化监听器
private Context mContext;
private static final String TAG = "WorkingNote";
private boolean mIsDeleted;
private NoteSettingChangedListener mNoteSettingStatusListener;
public static final String[] DATA_PROJECTION = new String[] {
DataColumns.ID,
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
DataColumns.ID, // 数据ID
DataColumns.CONTENT, // 笔记内容
DataColumns.MIME_TYPE, // 数据类型
DataColumns.DATA1, // 附加数据1
DataColumns.DATA2, // 附加数据2
DataColumns.DATA3, // 附加数据3
DataColumns.DATA4, // 附加数据4
};
/**
*
*
*/
public static final String[] NOTE_PROJECTION = new String[] {
NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.MODIFIED_DATE
NoteColumns.PARENT_ID, // 所属文件夹ID
NoteColumns.ALERTED_DATE, // 提醒日期
NoteColumns.BG_COLOR_ID, // 背景颜色ID
NoteColumns.WIDGET_ID, // 小部件ID
NoteColumns.WIDGET_TYPE, // 小部件类型
NoteColumns.MODIFIED_DATE // 修改日期
};
private static final int DATA_ID_COLUMN = 0;
private static final int DATA_CONTENT_COLUMN = 1;
private static final int DATA_MIME_TYPE_COLUMN = 2;
private static final int DATA_MODE_COLUMN = 3;
private static final int NOTE_PARENT_ID_COLUMN = 0;
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
// 数据查询结果列索引
private static final int DATA_ID_COLUMN = 0; // 数据ID列索引
private static final int DATA_CONTENT_COLUMN = 1; // 内容列索引
private static final int DATA_MIME_TYPE_COLUMN = 2; // MIME类型列索引
private static final int DATA_MODE_COLUMN = 3; // 模式列索引
private static final int NOTE_WIDGET_ID_COLUMN = 3;
// 笔记查询结果列索引
private static final int NOTE_PARENT_ID_COLUMN = 0; // 父文件夹ID列索引
private static final int NOTE_ALERTED_DATE_COLUMN = 1; // 提醒日期列索引
private static final int NOTE_BG_COLOR_ID_COLUMN = 2; // 背景颜色ID列索引
private static final int NOTE_WIDGET_ID_COLUMN = 3; // 小部件ID列索引
private static final int NOTE_WIDGET_TYPE_COLUMN = 4; // 小部件类型列索引
private static final int NOTE_MODIFIED_DATE_COLUMN = 5; // 修改日期列索引
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
// New note construct
/**
*
*
* @param context
* @param folderId ID
*/
private WorkingNote(Context context, long folderId) {
mContext = context;
mAlertDate = 0;
@ -114,7 +116,12 @@ public class WorkingNote {
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
}
// Existing note construct
/**
* -
* @param context
* @param noteId ID
* @param folderId ID
*/
private WorkingNote(Context context, long noteId, long folderId) {
mContext = context;
mNoteId = noteId;
@ -124,6 +131,10 @@ public class WorkingNote {
loadNote();
}
/**
* note
* data
*/
private void loadNote() {
Cursor cursor = mContext.getContentResolver().query(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null,
@ -146,6 +157,10 @@ public class WorkingNote {
loadNoteData();
}
/**
* data
* <p>NOTECALL_NOTEID</p>
*/
private void loadNoteData() {
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
@ -174,6 +189,16 @@ public class WorkingNote {
}
}
/**
*
*
* @param context
* @param folderId ID
* @param widgetId ID
* @param widgetType
* @param defaultBgColorId
* @return
*/
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId);
@ -183,10 +208,21 @@ public class WorkingNote {
return note;
}
/**
* ID
* @param context
* @param id ID
* @return
*/
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
}
/**
* note/data
*
* @return truefalse
*/
public synchronized boolean saveNote() {
if (isWorthSaving()) {
if (!existInDatabase()) {
@ -198,9 +234,7 @@ public class WorkingNote {
mNote.syncNote(mContext, mNoteId);
/**
* Update widget content if there exist any widget of this note
*/
// 如果该笔记存在小部件,更新小部件内容
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType != Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener != null) {
@ -212,10 +246,19 @@ public class WorkingNote {
}
}
/**
* noteId>0
* @return true
*/
public boolean existInDatabase() {
return mNoteId > 0;
}
/**
*
*
* @return truefalse
*/
private boolean isWorthSaving() {
if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent))
|| (existInDatabase() && !mNote.isLocalModified())) {
@ -225,10 +268,19 @@ public class WorkingNote {
}
}
/**
* UI
* @param l
*/
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l;
}
/**
*
* @param date
* @param set
*/
public void setAlertDate(long date, boolean set) {
if (date != mAlertDate) {
mAlertDate = date;
@ -239,6 +291,10 @@ public class WorkingNote {
}
}
/**
*
* @param mark
*/
public void markDeleted(boolean mark) {
mIsDeleted = mark;
if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID
@ -247,6 +303,10 @@ public class WorkingNote {
}
}
/**
*
* @param id ID
*/
public void setBgColorId(int id) {
if (id != mBgColorId) {
mBgColorId = id;
@ -257,6 +317,10 @@ public class WorkingNote {
}
}
/**
*
* @param mode 0 1
*/
public void setCheckListMode(int mode) {
if (mMode != mode) {
if (mNoteSettingStatusListener != null) {
@ -267,6 +331,10 @@ public class WorkingNote {
}
}
/**
*
* @param type
*/
public void setWidgetType(int type) {
if (type != mWidgetType) {
mWidgetType = type;
@ -274,6 +342,10 @@ public class WorkingNote {
}
}
/**
* ID
* @param id ID
*/
public void setWidgetId(int id) {
if (id != mWidgetId) {
mWidgetId = id;
@ -281,6 +353,10 @@ public class WorkingNote {
}
}
/**
*
* @param text
*/
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
mContent = text;
@ -288,80 +364,139 @@ public class WorkingNote {
}
}
/**
*
* @param phoneNumber
* @param callDate
*/
public void convertToCallNote(String phoneNumber, long callDate) {
mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate));
mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber);
mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER));
}
/**
*
* @return truefalse
*/
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
/**
*
* @return
*/
public String getContent() {
return mContent;
}
/**
*
* @return
*/
public long getAlertDate() {
return mAlertDate;
}
/**
*
* @return
*/
public long getModifiedDate() {
return mModifiedDate;
}
/**
* ID
* @return ID
*/
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId);
}
/**
* ID
* @return ID
*/
public int getBgColorId() {
return mBgColorId;
}
/**
* ID
* @return ID
*/
public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
/**
* /
* @return
*/
public int getCheckListMode() {
return mMode;
}
/**
* ID
* @return ID
*/
public long getNoteId() {
return mNoteId;
}
/**
* ID
* @return ID
*/
public long getFolderId() {
return mFolderId;
}
/**
* ID
* @return ID
*/
public int getWidgetId() {
return mWidgetId;
}
/**
*
* @return
*/
public int getWidgetType() {
return mWidgetType;
}
/**
*
*
*/
public interface NoteSettingChangedListener {
/**
* Called when the background color of current note has just changed
*
*/
void onBackgroundColorChanged();
/**
* Called when user set clock
*
* @param date
* @param set
*/
void onClockAlertChanged(long date, boolean set);
/**
* Call when user create note from widget
*
*/
void onWidgetChanged();
/**
* Call when switch between check list mode and normal mode
* @param oldMode is previous mode before change
* @param newMode is new mode
* /
* @param oldMode
* @param newMode
*/
void onCheckListModeChanged(int oldMode, int newMode);
}

@ -36,11 +36,20 @@ import java.io.IOException;
import java.io.PrintStream;
/**
* -
*
*/
public class BackupUtils {
private static final String TAG = "BackupUtils";
// Singleton stuff
private static BackupUtils sInstance;
private static final String TAG = "BackupUtils"; // 日志标签
/** 单例实例 */
private static BackupUtils sInstance; // 单例实例
/**
* BackupUtils
* @param context
* @return BackupUtils
*/
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
@ -48,83 +57,109 @@ public class BackupUtils {
return sInstance;
}
/**
* Following states are signs to represents backup or restore
* status
*/
// Currently, the sdcard is not mounted
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// The backup file not exist
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// The data is not well formated, may be changed by other programs
public static final int STATE_DATA_DESTROIED = 2;
// Some run-time exception which causes restore or backup fails
public static final int STATE_SYSTEM_ERROR = 3;
// Backup or restore success
public static final int STATE_SUCCESS = 4;
/** 备份或恢复状态常量 */
/** SD卡未挂载 */
public static final int STATE_SD_CARD_UNMOUONTED = 0; // SD卡未挂载
/** 备份文件不存在 */
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; // 备份文件不存在
/** 数据格式损坏,可能被其他程序修改 */
public static final int STATE_DATA_DESTROIED = 2; // 数据格式错误
/** 运行时异常导致恢复或备份失败 */
public static final int STATE_SYSTEM_ERROR = 3; // 系统错误
/** 备份或恢复成功 */
public static final int STATE_SUCCESS = 4; // 操作成功
/** 文本导出对象 */
private TextExport mTextExport;
/**
* -
* @param context
*/
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
/**
*
* @return true
*/
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
/**
*
* @return
*/
public int exportToText() {
return mTextExport.exportToText();
}
/**
*
* @return
*/
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
/**
*
* @return
*/
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
/**
* note/data SD txt
*/
private static class TextExport {
// 笔记查询投影列
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET,
NoteColumns.TYPE
NoteColumns.ID, // 笔记ID
NoteColumns.MODIFIED_DATE, // 修改日期
NoteColumns.SNIPPET, // 笔记摘要
NoteColumns.TYPE // 笔记类型
};
private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2;
// 笔记查询结果列索引
private static final int NOTE_COLUMN_ID = 0; // ID列索引
private static final int NOTE_COLUMN_MODIFIED_DATE = 1; // 修改日期列索引
private static final int NOTE_COLUMN_SNIPPET = 2; // 摘要列索引
// 笔记数据查询投影列
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
DataColumns.CONTENT, // 内容
DataColumns.MIME_TYPE, // MIME类型
DataColumns.DATA1, // 数据1
DataColumns.DATA2, // 数据2
DataColumns.DATA3, // 数据3
DataColumns.DATA4, // 数据4
};
private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2;
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
// 笔记数据查询结果列索引
private static final int DATA_COLUMN_CONTENT = 0; // 内容列索引
private static final int DATA_COLUMN_MIME_TYPE = 1; // MIME类型列索引
private static final int DATA_COLUMN_CALL_DATE = 2; // 通话日期列索引
private static final int DATA_COLUMN_PHONE_NUMBER = 4; // 电话号码列索引
// 文本格式模板
private final String [] TEXT_FORMAT;
private static final int FORMAT_FOLDER_NAME = 0;
private static final int FORMAT_NOTE_DATE = 1;
private static final int FORMAT_NOTE_CONTENT = 2;
private static final int FORMAT_FOLDER_NAME = 0; // 文件夹名称格式
private static final int FORMAT_NOTE_DATE = 1; // 笔记日期格式
private static final int FORMAT_NOTE_CONTENT = 2; // 笔记内容格式
private Context mContext;
private String mFileName;
private String mFileDirectory;
// 成员变量
private Context mContext; // 上下文对象
private String mFileName; // 导出文件名
private String mFileDirectory; // 导出文件目录
/**
* -
* @param context
*/
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
@ -132,15 +167,22 @@ public class BackupUtils {
mFileDirectory = "";
}
/**
* ID
* @param id ID
* @return
*/
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
/**
* Export the folder identified by folder id to text
* ID
* @param folderId ID
* @param ps
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// Query notes belong to this folder
// 查询该文件夹下的所有笔记
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId
@ -149,11 +191,11 @@ public class BackupUtils {
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// Print note's last modified date
// 打印笔记的最后修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
// 查询该笔记的详细数据
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
@ -163,9 +205,12 @@ public class BackupUtils {
}
/**
* Export note identified by id to a print stream
* ID
* @param noteId ID
* @param ps
*/
private void exportNoteToText(String noteId, PrintStream ps) {
// 查询该笔记的详细数据
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
@ -175,26 +220,29 @@ public class BackupUtils {
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
// 处理通话记录类型笔记
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
// 打印电话号码
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// Print call date
// 打印通话日期
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// Print call attachment location
// 打印通话附件位置
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {
}
// 处理普通类型笔记
else if (DataConstants.NOTE.equals(mimeType)) {
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
@ -205,10 +253,10 @@ public class BackupUtils {
}
dataCursor.close();
}
// print a line separator between note
// 在笔记之间打印分隔符
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
Character.LINE_SEPARATOR, Character.LINE_SEPARATOR
});
} catch (IOException e) {
Log.e(TAG, e.toString());
@ -216,20 +264,24 @@ public class BackupUtils {
}
/**
* Note will be exported as text which is user readable
*
* @return
*/
public int exportToText() {
// 检查外部存储是否可用
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
// 获取输出流
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes
// 首先导出文件夹及其包含的笔记
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
@ -240,8 +292,9 @@ public class BackupUtils {
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// Print folder's name
// 打印文件夹名称
String folderName = "";
// 特殊处理通话记录文件夹
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
@ -250,6 +303,8 @@ public class BackupUtils {
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
// 导出该文件夹下的所有笔记
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
@ -257,7 +312,7 @@ public class BackupUtils {
folderCursor.close();
}
// Export notes in root's folder
// 导出根目录下的笔记
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
@ -267,33 +322,42 @@ public class BackupUtils {
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
// 打印笔记修改日期
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
// 导出笔记内容
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
noteCursor.close();
}
// 关闭输出流
ps.close();
return STATE_SUCCESS;
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
*
* @return null
*/
private PrintStream getExportToTextPrintStream() {
// 生成导出文件
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
Log.e(TAG, "create file to exported failed");
return null;
}
// 保存文件名和目录信息
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
// 创建输出流
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(file);
@ -310,19 +374,27 @@ public class BackupUtils {
}
/**
* Generate the text file to store imported data
* SD
* @param context
* @param filePathResId ID
* @param fileNameFormatResId ID
* @return null
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
// 构建文件路径SD卡根目录 + 自定义路径
sb.append(Environment.getExternalStorageDirectory());
sb.append(context.getString(filePathResId));
File filedir = new File(sb.toString());
// 构建完整文件名:路径 + 格式化文件名(包含当前日期)
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
File file = new File(sb.toString());
// 创建目录和文件
try {
if (!filedir.exists()) {
filedir.mkdir();

@ -34,9 +34,28 @@ import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
/**
*
* <p></p>
* <p>使// note data </p>
*/
/**
* -
*
*/
public class DataUtils {
/**
*
*/
public static final String TAG = "DataUtils";
/**
*
*
* @param resolver ContentResolver
* @param ids ID
* @return truefalse
*/
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
if (ids == null) {
Log.d(TAG, "the ids is null");
@ -72,6 +91,13 @@ public class DataUtils {
return false;
}
/**
*
* @param resolver
* @param id ID
* @param srcFolderId ID
* @param desFolderId ID
*/
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, desFolderId);
@ -80,6 +106,13 @@ public class DataUtils {
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
/**
*
* @param resolver
* @param ids ID
* @param folderId ID
* @return
*/
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {
if (ids == null) {
@ -112,7 +145,9 @@ public class DataUtils {
}
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*
* @param resolver
* @return
*/
public static int getUserFolderCount(ContentResolver resolver) {
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
@ -136,6 +171,13 @@ public class DataUtils {
return count;
}
/**
* ID
* @param resolver
* @param noteId ID
* @param type
* @return
*/
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
@ -153,6 +195,12 @@ public class DataUtils {
return exist;
}
/**
* ID
* @param resolver
* @param noteId ID
* @return
*/
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
@ -167,6 +215,12 @@ public class DataUtils {
return exist;
}
/**
* ID
* @param resolver
* @param dataId ID
* @return
*/
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
@ -181,6 +235,12 @@ public class DataUtils {
return exist;
}
/**
*
* @param resolver
* @param name
* @return
*/
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
@ -197,6 +257,12 @@ public class DataUtils {
return exist;
}
/**
*
* @param resolver
* @param folderId ID
* @return
*/
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
@ -224,6 +290,13 @@ public class DataUtils {
return set;
}
/**
* ID
*
* @param resolver
* @param noteId ID
* @return
*/
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
@ -243,6 +316,14 @@ public class DataUtils {
return "";
}
/**
* ID
*
* @param resolver
* @param phoneNumber
* @param callDate
* @return ID
*/
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID },
@ -264,6 +345,13 @@ public class DataUtils {
return 0;
}
/**
* ID
* @param resolver
* @param noteId ID
* @return
* @throws IllegalArgumentException
*/
public static String getSnippetById(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String [] { NoteColumns.SNIPPET },
@ -282,6 +370,12 @@ public class DataUtils {
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
/**
*
*
* @param snippet
* @return
*/
public static String getFormattedSnippet(String snippet) {
if (snippet != null) {
snippet = snippet.trim();

@ -16,98 +16,285 @@
package net.micode.notes.tool;
/**
* Google Tasks - Google Tasks
* JSONGoogle Tasks API
*/
public class GTaskStringUtils {
/**
* JSONID
*/
public final static String GTASK_JSON_ACTION_ID = "action_id";
/**
* JSON
*/
public final static String GTASK_JSON_ACTION_LIST = "action_list";
/**
* JSON
*/
public final static String GTASK_JSON_ACTION_TYPE = "action_type";
/**
* JSON -
*/
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";
/**
* JSON -
*/
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
/**
* JSON -
*/
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";
/**
* JSON -
*/
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";
/**
* JSONID
*/
public final static String GTASK_JSON_CREATOR_ID = "creator_id";
/**
* JSON
*/
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";
/**
* JSON
*/
public final static String GTASK_JSON_CLIENT_VERSION = "client_version";
/**
* JSON
*/
public final static String GTASK_JSON_COMPLETED = "completed";
/**
* JSONID
*/
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";
/**
* JSONID
*/
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
/**
* JSON
*/
public final static String GTASK_JSON_DELETED = "deleted";
/**
* JSON
*/
public final static String GTASK_JSON_DEST_LIST = "dest_list";
/**
* JSON
*/
public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
/**
* JSON
*/
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
/**
* JSON
*/
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
/**
* JSON
*/
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
/**
* JSON
*/
public final static String GTASK_JSON_GET_DELETED = "get_deleted";
/**
* JSONID
*/
public final static String GTASK_JSON_ID = "id";
/**
* JSON
*/
public final static String GTASK_JSON_INDEX = "index";
/**
* JSON
*/
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
/**
* JSON
*/
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
/**
* JSONID
*/
public final static String GTASK_JSON_LIST_ID = "list_id";
/**
* JSON
*/
public final static String GTASK_JSON_LISTS = "lists";
/**
* JSON
*/
public final static String GTASK_JSON_NAME = "name";
/**
* JSONID
*/
public final static String GTASK_JSON_NEW_ID = "new_id";
/**
* JSON
*/
public final static String GTASK_JSON_NOTES = "notes";
/**
* JSONID
*/
public final static String GTASK_JSON_PARENT_ID = "parent_id";
/**
* JSONID
*/
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
/**
* JSON
*/
public final static String GTASK_JSON_RESULTS = "results";
/**
* JSON
*/
public final static String GTASK_JSON_SOURCE_LIST = "source_list";
/**
* JSON
*/
public final static String GTASK_JSON_TASKS = "tasks";
/**
* JSON
*/
public final static String GTASK_JSON_TYPE = "type";
/**
* JSON -
*/
public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
/**
* JSON -
*/
public final static String GTASK_JSON_TYPE_TASK = "TASK";
/**
* JSON
*/
public final static String GTASK_JSON_USER = "user";
/**
* MIUI
*/
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";
/**
*
*/
public final static String FOLDER_DEFAULT = "Default";
/**
*
*/
public final static String FOLDER_CALL_NOTE = "Call_Note";
/**
*
*/
public final static String FOLDER_META = "METADATA";
/**
* Google Tasks ID
*/
public final static String META_HEAD_GTASK_ID = "meta_gid";
/**
*
*/
public final static String META_HEAD_NOTE = "meta_note";
/**
*
*/
public final static String META_HEAD_DATA = "meta_data";
/**
*
*/
public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE";
}

@ -22,24 +22,76 @@ import android.preference.PreferenceManager;
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
/**
* - UI
*
*/
public class ResourceParser {
/**
* -
*/
public static final int YELLOW = 0;
/**
* -
*/
public static final int BLUE = 1;
/**
* -
*/
public static final int WHITE = 2;
/**
* - 绿
*/
public static final int GREEN = 3;
/**
* -
*/
public static final int RED = 4;
/**
*
*/
public static final int BG_DEFAULT_COLOR = YELLOW;
/**
* -
*/
public static final int TEXT_SMALL = 0;
/**
* -
*/
public static final int TEXT_MEDIUM = 1;
/**
* -
*/
public static final int TEXT_LARGE = 2;
/**
* -
*/
public static final int TEXT_SUPER = 3;
/**
*
*/
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
/**
*
*
*/
public static class NoteBgResources {
/**
*
*
*/
private final static int [] BG_EDIT_RESOURCES = new int [] {
R.drawable.edit_yellow,
R.drawable.edit_blue,
@ -48,6 +100,10 @@ public class ResourceParser {
R.drawable.edit_red
};
/**
*
*
*/
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
@ -56,15 +112,31 @@ public class ResourceParser {
R.drawable.edit_title_red
};
/**
*
* @param id ID
* @return ID
*/
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}
/**
*
* @param id ID
* @return ID
*/
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}
}
/**
* ID
* 使
* @param context
* @return ID
*/
public static int getDefaultBgId(Context context) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
@ -74,7 +146,15 @@ public class ResourceParser {
}
}
/**
*
*
*/
public static class NoteItemBgResources {
/**
*
*
*/
private final static int [] BG_FIRST_RESOURCES = new int [] {
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
@ -83,6 +163,10 @@ public class ResourceParser {
R.drawable.list_red_up
};
/**
*
*
*/
private final static int [] BG_NORMAL_RESOURCES = new int [] {
R.drawable.list_yellow_middle,
R.drawable.list_blue_middle,
@ -91,6 +175,10 @@ public class ResourceParser {
R.drawable.list_red_middle
};
/**
*
*
*/
private final static int [] BG_LAST_RESOURCES = new int [] {
R.drawable.list_yellow_down,
R.drawable.list_blue_down,
@ -99,6 +187,10 @@ public class ResourceParser {
R.drawable.list_red_down,
};
/**
*
*
*/
private final static int [] BG_SINGLE_RESOURCES = new int [] {
R.drawable.list_yellow_single,
R.drawable.list_blue_single,
@ -107,28 +199,60 @@ public class ResourceParser {
R.drawable.list_red_single
};
/**
*
* @param id ID
* @return ID
*/
public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id];
}
/**
*
* @param id ID
* @return ID
*/
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}
/**
*
* @param id ID
* @return ID
*/
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}
/**
*
* @param id ID
* @return ID
*/
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}
/**
*
* @return ID
*/
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
}
/**
*
*
*/
public static class WidgetBgResources {
/**
* 2x
*
*/
private final static int [] BG_2X_RESOURCES = new int [] {
R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
@ -137,10 +261,19 @@ public class ResourceParser {
R.drawable.widget_2x_red,
};
/**
* 2x
* @param id ID
* @return 2xID
*/
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id];
}
/**
* 4x
*
*/
private final static int [] BG_4X_RESOURCES = new int [] {
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,
@ -149,31 +282,48 @@ public class ResourceParser {
R.drawable.widget_4x_red
};
/**
* 4x
* @param id ID
* @return 4xID
*/
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id];
}
}
/**
*
*
*/
public static class TextAppearanceResources {
/**
*
*
*/
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
R.style.TextAppearanceNormal, // TEXT_SMALL
R.style.TextAppearanceMedium, // TEXT_MEDIUM
R.style.TextAppearanceLarge, // TEXT_LARGE
R.style.TextAppearanceSuper // TEXT_SUPER
};
/**
*
* @param id ID
* @return ID
*/
public static int getTexAppearanceResource(int id) {
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if (id >= TEXTAPPEARANCE_RESOURCES.length) {
return BG_DEFAULT_FONT_SIZE;
}
return TEXTAPPEARANCE_RESOURCES[id];
}
/**
*
* @return
*/
public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length;
}

@ -39,22 +39,51 @@ import net.micode.notes.tool.DataUtils;
import java.io.IOException;
/**
*
*
*/
/**
*
*
*/
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
/**
* ID
*/
private long mNoteId;
/**
*
*/
private String mSnippet;
/**
*
*/
private static final int SNIPPET_PREW_MAX_LEN = 60;
/**
*
*/
MediaPlayer mPlayer;
/**
*
*
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
final Window win = getWindow();
// 允许在锁屏时显示
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
if (!isScreenOn()) {
// 如果屏幕关闭,添加标志唤醒屏幕并保持点亮
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
@ -64,8 +93,11 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
Intent intent = getIntent();
try {
// 从Intent中获取笔记ID
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
// 获取笔记摘要
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
// 限制摘要长度
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
: mSnippet;
@ -74,7 +106,9 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
return;
}
// 初始化媒体播放器
mPlayer = new MediaPlayer();
// 检查笔记是否可见,如果可见则显示对话框并播放声音
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog();
playAlarmSound();
@ -83,17 +117,28 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
}
}
/**
*
* @return
*/
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
/**
*
*
*/
private void playAlarmSound() {
// 获取系统默认闹钟铃声URI
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
// 获取静音模式下受影响的音频流
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
// 设置音频流类型
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) {
mPlayer.setAudioStreamType(silentModeStreams);
} else {
@ -102,57 +147,75 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
try {
mPlayer.setDataSource(this, url);
mPlayer.prepare();
mPlayer.setLooping(true);
mPlayer.setLooping(true); // 设置循环播放
mPlayer.start();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
*
*
*/
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.app_name);
dialog.setMessage(mSnippet);
dialog.setPositiveButton(R.string.notealert_ok, this);
dialog.setMessage(mSnippet); // 显示笔记摘要
dialog.setPositiveButton(R.string.notealert_ok, this); // 确定按钮
if (isScreenOn()) {
// 如果屏幕已开启,显示"进入"按钮
dialog.setNegativeButton(R.string.notealert_enter, this);
}
// 显示对话框并设置消失监听器
dialog.show().setOnDismissListener(this);
}
/**
*
* @param dialog
* @param which ID
*/
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
// 点击"进入"按钮,打开笔记编辑界面查看完整笔记
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
startActivity(intent);
break;
default:
// 点击"确定"按钮,不执行额外操作
break;
}
}
/**
*
*
* @param dialog
*/
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
stopAlarmSound(); // 停止闹钟声音
finish(); // 结束活动
}
/**
*
*
*/
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
mPlayer.release();
mPlayer = null;
mPlayer.release(); // 释放资源
mPlayer = null; // 清空引用
}
}
}

@ -27,20 +27,42 @@ import android.database.Cursor;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
/**
*
*
*/
/**
* 广
*
*/
public class AlarmInitReceiver extends BroadcastReceiver {
/**
*
* ID
*/
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE
NoteColumns.ID, // 笔记ID
NoteColumns.ALERTED_DATE // 提醒日期
};
private static final int COLUMN_ID = 0;
private static final int COLUMN_ALERTED_DATE = 1;
/**
*
*/
private static final int COLUMN_ID = 0; // 笔记ID列索引
private static final int COLUMN_ALERTED_DATE = 1; // 提醒日期列索引
/**
* 广
*
* @param context
* @param intent
*/
@Override
public void onReceive(Context context, Intent intent) {
long currentDate = System.currentTimeMillis();
// 查询数据库中所有设置了未来提醒日期的笔记
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
@ -50,16 +72,23 @@ public class AlarmInitReceiver extends BroadcastReceiver {
if (c != null) {
if (c.moveToFirst()) {
do {
// 获取提醒日期和笔记ID
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
// 创建闹钟接收器的意图
Intent sender = new Intent(context, AlarmReceiver.class);
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
// 创建PendingIntent用于触发闹钟
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 获取闹钟管理器并设置闹钟
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
c.close();
c.close(); // 关闭游标
}
}
}

@ -20,11 +20,24 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
/**
*
*
*/
public class AlarmReceiver extends BroadcastReceiver {
/**
* 广
*
* @param context
* @param intent ID
*/
@Override
public void onReceive(Context context, Intent intent) {
// 将意图的目标类设置为闹钟提醒活动
intent.setClass(context, AlarmAlertActivity.class);
// 添加新任务标志,允许从非活动上下文启动活动
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 启动闹钟提醒活动
context.startActivity(intent);
}
}

@ -28,85 +28,135 @@ import android.view.View;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
/**
*
* 2412
* AM/PM
*/
public class DateTimePicker extends FrameLayout {
/** 默认启用状态 */
private static final boolean DEFAULT_ENABLE_STATE = true;
/** 半天的小时数 */
private static final int HOURS_IN_HALF_DAY = 12;
/** 全天的小时数 */
private static final int HOURS_IN_ALL_DAY = 24;
/** 一周的天数 */
private static final int DAYS_IN_ALL_WEEK = 7;
/** 日期选择器的最小值 */
private static final int DATE_SPINNER_MIN_VAL = 0;
/** 日期选择器的最大值 */
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
/** 24小时制小时选择器的最小值 */
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
/** 24小时制小时选择器的最大值 */
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
/** 12小时制小时选择器的最小值 */
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
/** 12小时制小时选择器的最大值 */
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
/** 分钟选择器的最小值 */
private static final int MINUT_SPINNER_MIN_VAL = 0;
/** 分钟选择器的最大值 */
private static final int MINUT_SPINNER_MAX_VAL = 59;
/** AM/PM选择器的最小值 */
private static final int AMPM_SPINNER_MIN_VAL = 0;
/** AM/PM选择器的最大值 */
private static final int AMPM_SPINNER_MAX_VAL = 1;
/** 日期选择器 */
private final NumberPicker mDateSpinner;
/** 小时选择器 */
private final NumberPicker mHourSpinner;
/** 分钟选择器 */
private final NumberPicker mMinuteSpinner;
/** AM/PM选择器 */
private final NumberPicker mAmPmSpinner;
/** 当前选择的日期 */
private Calendar mDate;
/** 日期显示值数组 */
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
/** 是否为上午 */
private boolean mIsAm;
/** 是否为24小时制 */
private boolean mIs24HourView;
/** 是否启用 */
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
/** 是否正在初始化 */
private boolean mInitialising;
/** 日期时间变化监听器 */
private OnDateTimeChangedListener mOnDateTimeChangedListener;
/** 日期变化监听器 */
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 根据日期选择器的变化调整当前日期
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
// 更新日期选择器显示
updateDateControl();
// 通知日期时间变化
onDateTimeChanged();
}
};
/** 小时变化监听器 */
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false;
Calendar cal = Calendar.getInstance();
if (!mIs24HourView) {
// 处理12小时制下的日期变更逻辑
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
// 从下午11点变为12点日期加1天
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
// 从上午12点变为11点日期减1天
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
// 处理12小时制下的AM/PM切换
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||
oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm;
updateAmPmControl();
}
} else {
// 处理24小时制下的日期变更逻辑
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
// 从23点变为0点日期加1天
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
// 从0点变为23点日期减1天
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
}
// 更新当前小时
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour);
// 通知日期时间变化
onDateTimeChanged();
// 如果日期发生变化,更新年、月、日
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
@ -115,21 +165,31 @@ public class DateTimePicker extends FrameLayout {
}
};
/** 分钟变化监听器 */
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();
int offset = 0;
// 处理分钟溢出/下溢的情况
if (oldVal == maxValue && newVal == minValue) {
// 分钟从59变为0小时加1
offset += 1;
} else if (oldVal == minValue && newVal == maxValue) {
// 分钟从0变为59小时减1
offset -= 1;
}
if (offset != 0) {
// 更新小时
mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour());
// 更新日期显示
updateDateControl();
// 更新AM/PM状态
int newHour = getCurrentHourOfDay();
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
@ -139,38 +199,78 @@ public class DateTimePicker extends FrameLayout {
updateAmPmControl();
}
}
// 设置当前分钟
mDate.set(Calendar.MINUTE, newVal);
// 通知日期时间变化
onDateTimeChanged();
}
};
/** AM/PM变化监听器 */
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 切换AM/PM状态
mIsAm = !mIsAm;
// 根据AM/PM状态调整小时
if (mIsAm) {
// 从PM切换到AM小时减12
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {
// 从AM切换到PM小时加12
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
}
// 更新AM/PM选择器显示
updateAmPmControl();
// 通知日期时间变化
onDateTimeChanged();
}
};
/**
*
*
*/
public interface OnDateTimeChangedListener {
/**
*
* @param view
* @param year
* @param month
* @param dayOfMonth
* @param hourOfDay 24
* @param minute
*/
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
}
/**
* 使
* @param context
*/
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
/**
* 使
* @param context
* @param date
*/
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
}
/**
* 使
* @param context
* @param date
* @param is24HourView 使24
*/
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
mDate = Calendar.getInstance();
@ -178,19 +278,24 @@ public class DateTimePicker extends FrameLayout {
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
inflate(context, R.layout.datetime_picker, this);
// 初始化日期选择器
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);
// 初始化小时选择器
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
// 初始化分钟选择器
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100);
mMinuteSpinner.setOnLongPressUpdateInterval(100); // 设置长按更新间隔
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
// 初始化AM/PM选择器
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
@ -198,19 +303,21 @@ public class DateTimePicker extends FrameLayout {
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// update controls to initial state
// 更新控件到初始状态
updateDateControl();
updateHourControl();
updateAmPmControl();
// 设置时间格式24小时制/12小时制
set24HourView(is24HourView);
// set to current time
// 设置初始时间
setCurrentDate(date);
// 设置启用状态
setEnabled(isEnabled());
// set the content descriptions
// 初始化完成
mInitialising = false;
}
@ -348,14 +455,22 @@ public class DateTimePicker extends FrameLayout {
return mDate.get(Calendar.HOUR_OF_DAY);
}
/**
*
* @return 240-23121-12
*/
private int getCurrentHour() {
if (mIs24HourView){
// 24小时制直接返回小时
return getCurrentHourOfDay();
} else {
// 12小时制需要转换
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) {
// 下午时间减去12
return hour - HOURS_IN_HALF_DAY;
} else {
// 上午时间0点转换为12点
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
@ -434,35 +549,54 @@ public class DateTimePicker extends FrameLayout {
updateAmPmControl();
}
/**
*
*
*/
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
// 计算起始日期当前日期减去3天
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null);
// 生成一周的日期显示值
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1);
// 格式化日期为"MM.dd EEEE"(月.日 星期几)
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
mDateSpinner.setDisplayedValues(mDateDisplayValues);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); // 默认选择当前日期
mDateSpinner.invalidate();
}
/**
* AM/PM
*/
private void updateAmPmControl() {
if (mIs24HourView) {
// 24小时制隐藏AM/PM选择器
mAmPmSpinner.setVisibility(View.GONE);
} else {
// 12小时制设置AM/PM值并显示
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}
/**
*
*/
private void updateHourControl() {
if (mIs24HourView) {
// 24小时制范围0-23
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
// 12小时制范围1-12
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
@ -476,6 +610,9 @@ public class DateTimePicker extends FrameLayout {
mOnDateTimeChangedListener = callback;
}
/**
*
*/
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),

@ -29,59 +29,114 @@ import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
/**
*
* DateTimePicker
*/
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
/** 当前选择的日期 */
private Calendar mDate = Calendar.getInstance();
/** 是否为24小时制 */
private boolean mIs24HourView;
/** 日期时间设置监听器 */
private OnDateTimeSetListener mOnDateTimeSetListener;
/** 日期时间选择器组件 */
private DateTimePicker mDateTimePicker;
/**
*
*
*/
public interface OnDateTimeSetListener {
/**
*
* @param dialog
* @param date
*/
void OnDateTimeSet(AlertDialog dialog, long date);
}
/**
*
* @param context
* @param date
*/
public DateTimePickerDialog(Context context, long date) {
super(context);
// 初始化日期时间选择器
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker);
// 设置日期时间变化监听器
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
// 更新当前日期时间
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
// 更新对话框标题
updateTitle(mDate.getTimeInMillis());
}
});
// 设置初始日期时间(清除秒数)
mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0);
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
// 设置对话框按钮
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
// 设置时间格式24小时制/12小时制
set24HourView(DateFormat.is24HourFormat(this.getContext()));
// 更新对话框标题
updateTitle(mDate.getTimeInMillis());
}
/**
* 使24
* @param is24HourView true使24false使12
*/
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
/**
*
* @param callBack
*/
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
/**
*
* @param date
*/
private void updateTitle(long date) {
int flag =
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
// 根据时间格式设置标志
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
// 格式化并设置标题
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
/**
*
* @param arg0
* @param arg1
*/
public void onClick(DialogInterface arg0, int arg1) {
// 当点击确认按钮时,通知监听器日期时间已设置
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}

@ -27,17 +27,34 @@ import android.widget.PopupMenu.OnMenuItemClickListener;
import net.micode.notes.R;
/**
*
* AndroidPopupMenu
*/
public class DropdownMenu {
/** 下拉菜单的按钮 */
private Button mButton;
/** 弹出菜单实例 */
private PopupMenu mPopupMenu;
/** 菜单实例 */
private Menu mMenu;
/**
*
* @param context
* @param button
* @param menuId ID
*/
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button;
// 设置按钮背景为下拉图标
mButton.setBackgroundResource(R.drawable.dropdown_icon);
// 创建弹出菜单实例
mPopupMenu = new PopupMenu(context, mButton);
mMenu = mPopupMenu.getMenu();
// 填充菜单内容
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
// 设置按钮点击事件,显示弹出菜单
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mPopupMenu.show();
@ -45,16 +62,29 @@ public class DropdownMenu {
});
}
/**
*
* @param listener
*/
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
}
}
/**
* ID
* @param id ID
* @return null
*/
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}
/**
*
* @param title
*/
public void setTitle(CharSequence title) {
mButton.setText(title);
}

@ -28,50 +28,98 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
/**
*
*
*/
/**
*
* CursorAdapter
*/
public class FoldersListAdapter extends CursorAdapter {
/** 查询投影数组 */
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
NoteColumns.ID, // 文件夹ID
NoteColumns.SNIPPET // 文件夹名称
};
/** 文件夹ID列索引 */
public static final int ID_COLUMN = 0;
/** 文件夹名称列索引 */
public static final int NAME_COLUMN = 1;
/**
*
* @param context
* @param c Cursor
*/
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}
/**
*
* @param context
* @param cursor Cursor
* @param parent
* @return
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
/**
*
* @param view
* @param context
* @param cursor Cursor
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
// 如果是根文件夹,显示"移动到父文件夹"文本,否则显示文件夹名称
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
((FolderListItem) view).bind(folderName);
}
}
/**
*
* @param context
* @param position
* @return
*/
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
// 如果是根文件夹,返回"移动到父文件夹"文本,否则返回文件夹名称
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
/**
*
*/
private class FolderListItem extends LinearLayout {
/** 文件夹名称文本视图 */
private TextView mName;
/**
*
* @param context
*/
public FolderListItem(Context context) {
super(context);
// 填充布局
inflate(context, R.layout.folder_list_item, this);
mName = (TextView) findViewById(R.id.tv_folder_name);
}
/**
*
* @param name
*/
public void bind(String name) {
mName.setText(name);
}

@ -72,18 +72,28 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*
*
*
*/
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
/**
*
*
*/
private class HeadViewHolder {
public TextView tvModified;
public ImageView ivAlertIcon;
public TextView tvAlertDate;
public ImageView ibSetBgColor;
public TextView tvModified; // 最后修改时间文本视图
public ImageView ivAlertIcon; // 提醒图标
public TextView tvAlertDate; // 提醒日期文本视图
public ImageView ibSetBgColor; // 设置背景颜色按钮
}
/**
*
* ID
*/
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>();
static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
@ -93,6 +103,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
}
/**
*
* ID
*/
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
@ -102,6 +116,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
}
/**
*
* ID
*/
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>();
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
@ -110,6 +128,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
}
/**
*
* ID
*/
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);
@ -118,52 +140,47 @@ public class NoteEditActivity extends Activity implements OnClickListener,
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
}
private static final String TAG = "NoteEditActivity";
private static final String TAG = "NoteEditActivity"; // 日志标签
private HeadViewHolder mNoteHeaderHolder;
private HeadViewHolder mNoteHeaderHolder; // 头部视图持有者
private View mHeadViewPanel; // 头部视图面板
private View mNoteBgColorSelector; // 背景颜色选择器
private View mFontSizeSelector; // 字体大小选择器
private EditText mNoteEditor; // 笔记编辑器
private View mNoteEditorPanel; // 笔记编辑器面板
private WorkingNote mWorkingNote; // 工作笔记对象
private SharedPreferences mSharedPrefs; // 共享偏好设置
private int mFontSizeId; // 字体大小ID
private View mHeadViewPanel;
private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; // 字体大小偏好设置键
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; // 快捷图标标题最大长度
private View mNoteBgColorSelector;
public static final String TAG_CHECKED = String.valueOf('\u221A'); // 已勾选标记
public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); // 未勾选标记
private View mFontSizeSelector;
private EditText mNoteEditor;
private View mNoteEditorPanel;
private WorkingNote mWorkingNote;
private SharedPreferences mSharedPrefs;
private int mFontSizeId;
private static final String PREFERENCE_FONT_SIZE = "pref_font_size";
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;
public static final String TAG_CHECKED = String.valueOf('\u221A');
public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
private LinearLayout mEditTextList;
private String mUserQuery;
private Pattern mPattern;
private LinearLayout mEditTextList; // 编辑文本列表(用于清单模式)
private String mUserQuery; // 用户查询字符串
private Pattern mPattern; // 正则表达式模式(用于高亮查询结果)
/**
*
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit);
this.setContentView(R.layout.note_edit); // 设置布局
// 如果没有保存的状态且无法初始化活动状态,则结束活动
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
}
initResources();
initResources(); // 初始化资源
}
/**
* Current activity may be killed when the memory is low. Once it is killed, for another time
* user load this activity, we should restore the former state
*
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
@ -179,6 +196,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
}
/**
*
*
* @param intent
* @return
*/
private boolean initActivityState(Intent intent) {
/**
* If the user specified the {@link Intent#ACTION_VIEW} but not provided with id,
@ -262,27 +285,44 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return true;
}
/**
*
*/
@Override
protected void onResume() {
super.onResume();
initNoteScreen();
initNoteScreen(); // 初始化笔记屏幕
}
/**
*
*
*/
private void initNoteScreen() {
// 设置笔记编辑器的文本外观
mNoteEditor.setTextAppearance(this, TextAppearanceResources
.getTexAppearanceResource(mFontSizeId));
// 根据笔记模式设置显示方式
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
switchToListMode(mWorkingNote.getContent());
switchToListMode(mWorkingNote.getContent()); // 切换到清单模式
} else {
// 设置笔记内容并高亮查询结果
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
// 将光标定位到文本末尾
mNoteEditor.setSelection(mNoteEditor.getText().length());
}
// 隐藏所有背景选择的选中状态
for (Integer id : sBgSelectorSelectionMap.keySet()) {
findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);
}
// 设置头部视图和编辑器面板的背景
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
// 设置最后修改时间
mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this,
mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME
@ -292,9 +332,13 @@ public class NoteEditActivity extends Activity implements OnClickListener,
* TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker
* is not ready
*/
showAlertHeader();
showAlertHeader(); // 显示提醒头部
}
/**
*
*
*/
private void showAlertHeader() {
if (mWorkingNote.hasClockAlert()) {
long time = System.currentTimeMillis();
@ -318,6 +362,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
initActivityState(intent);
}
/**
*
* @param outState Bundle
*/
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@ -333,6 +381,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
}
/**
*
*
* @param ev
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
@ -349,6 +403,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return super.dispatchTouchEvent(ev);
}
/**
*
* @param view
* @param ev
* @return
*/
private boolean inRangeOfView(View view, MotionEvent ev) {
int []location = new int[2];
view.getLocationOnScreen(location);
@ -363,6 +423,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return true;
}
/**
*
* UI
*/
private void initResources() {
mHeadViewPanel = findViewById(R.id.note_title);
mNoteHeaderHolder = new HeadViewHolder();
@ -374,18 +438,25 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mNoteEditor = (EditText) findViewById(R.id.note_edit_view);
mNoteEditorPanel = findViewById(R.id.sv_note_edit);
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
// 为所有背景选择按钮设置点击监听器
for (int id : sBgSelectorBtnsMap.keySet()) {
ImageView iv = (ImageView) findViewById(id);
iv.setOnClickListener(this);
}
mFontSizeSelector = findViewById(R.id.font_size_selector);
// 为所有字体大小选择按钮设置点击监听器
for (int id : sFontSizeBtnsMap.keySet()) {
View view = findViewById(id);
view.setOnClickListener(this);
};
// 初始化共享偏好设置和字体大小
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
@ -394,9 +465,14 @@ public class NoteEditActivity extends Activity implements OnClickListener,
if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
}
/**
*
*
*/
@Override
protected void onPause() {
super.onPause();
@ -406,8 +482,14 @@ public class NoteEditActivity extends Activity implements OnClickListener,
clearSettingState();
}
/**
*
*
*/
private void updateWidget() {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
// 根据小部件类型设置对应的小部件提供器
if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
@ -421,26 +503,36 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mWorkingNote.getWidgetId()
});
sendBroadcast(intent);
sendBroadcast(intent); // 发送广播更新小部件
setResult(RESULT_OK, intent);
}
/**
*
* UI
* @param v
*/
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_set_bg_color) {
// 显示背景颜色选择器
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);
View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
// 处理背景颜色选择
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
mNoteBgColorSelector.setVisibility(View.GONE);
} else if (sFontSizeBtnsMap.containsKey(id)) {
// 处理字体大小选择
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
mFontSizeId = sFontSizeBtnsMap.get(id);
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
// 根据当前模式更新字体大小
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
getWorkingText();
switchToListMode(mWorkingNote.getContent());
@ -452,6 +544,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
}
/**
*
*/
@Override
public void onBackPressed() {
if(clearSettingState()) {
@ -462,6 +557,11 @@ public class NoteEditActivity extends Activity implements OnClickListener,
super.onBackPressed();
}
/**
*
*
* @return
*/
private boolean clearSettingState() {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
@ -473,6 +573,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return false;
}
/**
*
* NoteSettingChangedListener
*/
public void onBackgroundColorChanged() {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE);
@ -480,6 +584,11 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
}
/**
*
* @param menu
* @return
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (isFinishing()) {
@ -505,13 +614,20 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return true;
}
/**
*
*
* @param item
* @return
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_note:
createNewNote();
createNewNote(); // 创建新笔记
break;
case R.id.menu_delete:
// 显示删除确认对话框
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
@ -519,7 +635,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteCurrentNote();
deleteCurrentNote(); // 删除当前笔记
finish();
}
});
@ -527,25 +643,27 @@ public class NoteEditActivity extends Activity implements OnClickListener,
builder.show();
break;
case R.id.menu_font_size:
// 显示字体大小选择器
mFontSizeSelector.setVisibility(View.VISIBLE);
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
break;
case R.id.menu_list_mode:
// 切换笔记模式(普通模式/清单模式)
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);
break;
case R.id.menu_share:
getWorkingText();
sendTo(this, mWorkingNote.getContent());
getWorkingText(); // 获取当前工作文本
sendTo(this, mWorkingNote.getContent()); // 分享笔记内容
break;
case R.id.menu_send_to_desktop:
sendToDesktop();
sendToDesktop(); // 发送到桌面
break;
case R.id.menu_alert:
setReminder();
setReminder(); // 设置提醒
break;
case R.id.menu_delete_remind:
mWorkingNote.setAlertDate(0, false);
mWorkingNote.setAlertDate(0, false); // 删除提醒
break;
default:
break;
@ -553,6 +671,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return true;
}
/**
*
*
*/
private void setReminder() {
DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());
d.setOnDateTimeSetListener(new OnDateTimeSetListener() {
@ -564,8 +686,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
/**
* Share note to apps that support {@link Intent#ACTION_SEND} action
* and {@text/plain} type
*
* {@link Intent#ACTION_SEND}{@text/plain}
* @param context
* @param info
*/
private void sendTo(Context context, String info) {
Intent intent = new Intent(Intent.ACTION_SEND);
@ -574,11 +698,15 @@ public class NoteEditActivity extends Activity implements OnClickListener,
context.startActivity(intent);
}
/**
*
*
*/
private void createNewNote() {
// Firstly, save current editing notes
// 首先保存当前编辑的笔记
saveNote();
// For safety, start a new NoteEditActivity
// 为了安全起见,启动一个新的NoteEditActivity
finish();
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
@ -586,6 +714,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
startActivity(intent);
}
/**
*
*
*/
private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) {
HashSet<Long> ids = new HashSet<Long>();
@ -596,10 +728,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
Log.d(TAG, "Wrong note id, should not happen");
}
if (!isSyncMode()) {
// 非同步模式下直接删除
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "Delete Note error");
}
} else {
// 同步模式下移动到回收站
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
@ -608,10 +742,20 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mWorkingNote.markDeleted(true);
}
/**
*
* @return
*/
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
/**
*
* NoteSettingChangedListener
* @param date
* @param set
*/
public void onClockAlertChanged(long date, boolean set) {
/**
* User could set clock to an unsaved note, so before setting the
@ -642,10 +786,20 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
}
/**
*
* NoteSettingChangedListener
*/
public void onWidgetChanged() {
updateWidget();
}
/**
*
* OnTextViewChangeListener
* @param index
* @param text
*/
public void onEditTextDelete(int index, String text) {
int childCount = mEditTextList.getChildCount();
if (childCount == 1) {
@ -672,6 +826,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
edit.setSelection(length);
}
/**
*
* OnTextViewChangeListener
* @param index
* @param text
*/
public void onEditTextEnter(int index, String text) {
/**
* Should not happen, check for debug
@ -691,30 +851,43 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
}
/**
*
*
* @param text
*/
private void switchToListMode(String text) {
mEditTextList.removeAllViews();
String[] items = text.split("\n");
mEditTextList.removeAllViews(); // 清空编辑文本列表
String[] items = text.split("\n"); // 按行分割文本
int index = 0;
for (String item : items) {
if(!TextUtils.isEmpty(item)) {
mEditTextList.addView(getListItem(item, index));
mEditTextList.addView(getListItem(item, index)); // 添加列表项
index++;
}
}
mEditTextList.addView(getListItem("", index));
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();
mEditTextList.addView(getListItem("", index)); // 添加最后一个空列表项
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); // 设置焦点
mNoteEditor.setVisibility(View.GONE);
mEditTextList.setVisibility(View.VISIBLE);
mNoteEditor.setVisibility(View.GONE); // 隐藏普通编辑器
mEditTextList.setVisibility(View.VISIBLE); // 显示清单编辑器
}
/**
*
*
* @param fullText
* @param userQuery
* @return
*/
private Spannable getHighlightQueryResult(String fullText, String userQuery) {
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
if (!TextUtils.isEmpty(userQuery)) {
mPattern = Pattern.compile(userQuery);
Matcher m = mPattern.matcher(fullText);
mPattern = Pattern.compile(userQuery); // 编译正则表达式
Matcher m = mPattern.matcher(fullText); // 创建匹配器
int start = 0;
while (m.find(start)) {
// 设置高亮背景色
spannable.setSpan(
new BackgroundColorSpan(this.getResources().getColor(
R.color.user_query_highlight)), m.start(), m.end(),
@ -725,10 +898,19 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return spannable;
}
/**
*
*
* @param item
* @param index
* @return
*/
private View getListItem(String item, int index) {
View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);
final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
// 设置复选框的选中状态变化监听器
CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@ -740,6 +922,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
});
// 处理已勾选和未勾选的标记
if (item.startsWith(TAG_CHECKED)) {
cb.setChecked(true);
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
@ -756,6 +939,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return view;
}
/**
*
* OnTextViewChangeListener
* @param index
* @param hasText
*/
public void onTextChange(int index, boolean hasText) {
if (index >= mEditTextList.getChildCount()) {
Log.e(TAG, "Wrong index, should not happen");
@ -768,6 +957,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
}
/**
*
* NoteSettingChangedListener
* @param oldMode
* @param newMode
*/
public void onCheckListModeChanged(int oldMode, int newMode) {
if (newMode == TextNote.MODE_CHECK_LIST) {
switchToListMode(mNoteEditor.getText().toString());
@ -782,6 +977,11 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
}
/**
*
*
* @return
*/
private boolean getWorkingText() {
boolean hasChecked = false;
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
@ -805,9 +1005,13 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return hasChecked;
}
/**
*
* @return
*/
private boolean saveNote() {
getWorkingText();
boolean saved = mWorkingNote.saveNote();
getWorkingText(); // 获取当前工作文本
boolean saved = mWorkingNote.saveNote(); // 保存笔记到数据库
if (saved) {
/**
* There are two modes from List view to edit view, open one note,
@ -821,6 +1025,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return saved;
}
/**
*
*
*/
private void sendToDesktop() {
/**
* Before send message to home, we should make sure that current
@ -856,6 +1064,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
}
/**
*
*
* @param content
* @return
*/
private String makeShortcutIconTitle(String content) {
content = content.replace(TAG_CHECKED, "");
content = content.replace(TAG_UNCHECKED, "");
@ -863,10 +1077,19 @@ public class NoteEditActivity extends Activity implements OnClickListener,
SHORTCUT_ICON_TITLE_MAX_LEN) : content;
}
/**
*
* @param resId ID
*/
private void showToast(int resId) {
showToast(resId, Toast.LENGTH_SHORT);
}
/**
*
* @param resId ID
* @param duration
*/
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show();
}

@ -37,20 +37,31 @@ import net.micode.notes.R;
import java.util.HashMap;
import java.util.Map;
/**
*
* EditText
*/
public class NoteEditText extends EditText {
/** 日志标签 */
private static final String TAG = "NoteEditText";
/** 当前文本框的索引 */
private int mIndex;
/** 删除前的选择起始位置 */
private int mSelectionStartBeforeDelete;
/** 电话链接协议 */
private static final String SCHEME_TEL = "tel:" ;
/** HTTP链接协议 */
private static final String SCHEME_HTTP = "http:" ;
/** 电子邮件链接协议 */
private static final String SCHEME_EMAIL = "mailto:" ;
/** 链接协议与对应的操作资源ID映射 */
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); // 电话链接操作文本
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); // 网页链接操作文本
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); // 电子邮件链接操作文本
}
/**
@ -77,33 +88,60 @@ public class NoteEditText extends EditText {
private OnTextViewChangeListener mOnTextViewChangeListener;
/**
*
* @param context
*/
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
/**
*
* @param index
*/
public void setIndex(int index) {
mIndex = index;
}
/**
*
* @param listener
*/
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
/**
*
* @param context
* @param attrs
*/
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
/**
*
* @param context
* @param attrs
* @param defStyle
*/
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
/**
*
* @param event
* @return
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 计算触摸位置在文本中的偏移量
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
@ -114,6 +152,7 @@ public class NoteEditText extends EditText {
Layout layout = getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
// 设置选择位置
Selection.setSelection(getText(), off);
break;
}
@ -121,15 +160,23 @@ public class NoteEditText extends EditText {
return super.onTouchEvent(event);
}
/**
*
* @param keyCode
* @param event
* @return
*/
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
// 如果设置了监听器,不拦截回车键
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
// 记录删除前的选择起始位置
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
@ -138,11 +185,18 @@ public class NoteEditText extends EditText {
return super.onKeyDown(keyCode, event);
}
/**
*
* @param keyCode
* @param event
* @return
*/
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
if (mOnTextViewChangeListener != null) {
// 如果在文本框开头按下删除键且不是第一个文本框,触发删除事件
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
@ -153,6 +207,7 @@ public class NoteEditText extends EditText {
break;
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
// 处理回车键,将光标后的文本移到新的文本框
int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString();
setText(getText().subSequence(0, selectionStart));
@ -167,9 +222,16 @@ public class NoteEditText extends EditText {
return super.onKeyUp(keyCode, event);
}
/**
*
* @param focused
* @param direction
* @param previouslyFocusedRect
*/
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener != null) {
// 当失去焦点且文本为空时,通知监听器文本已清空
if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
@ -179,6 +241,10 @@ public class NoteEditText extends EditText {
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
/**
*
* @param menu
*/
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
@ -188,8 +254,10 @@ public class NoteEditText extends EditText {
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
// 获取选中文本中的URL链接
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
// 根据链接协议获取对应的操作文本
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
@ -199,13 +267,14 @@ public class NoteEditText extends EditText {
}
if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
defaultResId = R.string.note_link_other; // 默认操作文本
}
// 添加菜单项并设置点击事件
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// goto a new intent
// 处理链接点击事件
urls[0].onClick(NoteEditText.this);
return true;
}

@ -25,57 +25,101 @@ import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
/**
*
*
*/
/**
*
*
*/
public class NoteItemData {
/** 数据库查询投影数组 */
static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE,
NoteColumns.HAS_ATTACHMENT,
NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT,
NoteColumns.PARENT_ID,
NoteColumns.SNIPPET,
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
NoteColumns.ID, // 笔记ID
NoteColumns.ALERTED_DATE, // 提醒日期
NoteColumns.BG_COLOR_ID, // 背景颜色ID
NoteColumns.CREATED_DATE, // 创建日期
NoteColumns.HAS_ATTACHMENT, // 是否有附件
NoteColumns.MODIFIED_DATE, // 修改日期
NoteColumns.NOTES_COUNT, // 笔记数量
NoteColumns.PARENT_ID, // 父文件夹ID
NoteColumns.SNIPPET, // 笔记摘要
NoteColumns.TYPE, // 笔记类型
NoteColumns.WIDGET_ID, // 小部件ID
NoteColumns.WIDGET_TYPE, // 小部件类型
};
/** 笔记ID列索引 */
private static final int ID_COLUMN = 0;
/** 提醒日期列索引 */
private static final int ALERTED_DATE_COLUMN = 1;
/** 背景颜色ID列索引 */
private static final int BG_COLOR_ID_COLUMN = 2;
/** 创建日期列索引 */
private static final int CREATED_DATE_COLUMN = 3;
/** 是否有附件列索引 */
private static final int HAS_ATTACHMENT_COLUMN = 4;
/** 修改日期列索引 */
private static final int MODIFIED_DATE_COLUMN = 5;
/** 笔记数量列索引 */
private static final int NOTES_COUNT_COLUMN = 6;
/** 父文件夹ID列索引 */
private static final int PARENT_ID_COLUMN = 7;
/** 笔记摘要列索引 */
private static final int SNIPPET_COLUMN = 8;
/** 笔记类型列索引 */
private static final int TYPE_COLUMN = 9;
/** 小部件ID列索引 */
private static final int WIDGET_ID_COLUMN = 10;
/** 小部件类型列索引 */
private static final int WIDGET_TYPE_COLUMN = 11;
/** 笔记ID */
private long mId;
/** 提醒日期 */
private long mAlertDate;
/** 背景颜色ID */
private int mBgColorId;
/** 创建日期 */
private long mCreatedDate;
/** 是否有附件 */
private boolean mHasAttachment;
/** 修改日期 */
private long mModifiedDate;
/** 笔记数量 */
private int mNotesCount;
/** 父文件夹ID */
private long mParentId;
/** 笔记摘要 */
private String mSnippet;
/** 笔记类型 */
private int mType;
/** 小部件ID */
private int mWidgetId;
/** 小部件类型 */
private int mWidgetType;
/** 联系人姓名(用于通话记录) */
private String mName;
/** 电话号码(用于通话记录) */
private String mPhoneNumber;
/** 是否为最后一项 */
private boolean mIsLastItem;
/** 是否为第一项 */
private boolean mIsFirstItem;
/** 是否只有一项 */
private boolean mIsOnlyOneItem;
/** 是否是文件夹后的唯一笔记 */
private boolean mIsOneNoteFollowingFolder;
/** 是否是文件夹后的多个笔记 */
private boolean mIsMultiNotesFollowingFolder;
/**
*
* @param context
* @param cursor Cursor
*/
public NoteItemData(Context context, Cursor cursor) {
mId = cursor.getLong(ID_COLUMN);
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
@ -86,6 +130,7 @@ public class NoteItemData {
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
mParentId = cursor.getLong(PARENT_ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
// 移除复选框标签
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
mType = cursor.getInt(TYPE_COLUMN);
@ -93,6 +138,7 @@ public class NoteItemData {
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
mPhoneNumber = "";
// 如果是通话记录文件夹中的笔记,获取电话号码和联系人姓名
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) {
@ -106,9 +152,14 @@ public class NoteItemData {
if (mName == null) {
mName = "";
}
// 检查笔记在列表中的位置
checkPostion(cursor);
}
/**
*
* @param cursor Cursor
*/
private void checkPostion(Cursor cursor) {
mIsLastItem = cursor.isLast() ? true : false;
mIsFirstItem = cursor.isFirst() ? true : false;
@ -116,17 +167,20 @@ public class NoteItemData {
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
// 如果是笔记类型且不是第一项,检查前一项是否为文件夹
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
int position = cursor.getPosition();
if (cursor.moveToPrevious()) {
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
// 检查是否有多个笔记跟随在文件夹后面
if (cursor.getCount() > (position + 1)) {
mIsMultiNotesFollowingFolder = true;
} else {
mIsOneNoteFollowingFolder = true;
}
}
// 恢复Cursor位置
if (!cursor.moveToNext()) {
throw new IllegalStateException("cursor move to previous but can't move back");
}
@ -134,90 +188,179 @@ public class NoteItemData {
}
}
/**
*
* @return truefalse
*/
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
/**
*
* @return truefalse
*/
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
/**
*
* @return truefalse
*/
public boolean isLast() {
return mIsLastItem;
}
/**
*
* @return
*/
public String getCallName() {
return mName;
}
/**
*
* @return truefalse
*/
public boolean isFirst() {
return mIsFirstItem;
}
/**
*
* @return truefalse
*/
public boolean isSingle() {
return mIsOnlyOneItem;
}
/**
* ID
* @return ID
*/
public long getId() {
return mId;
}
/**
*
* @return
*/
public long getAlertDate() {
return mAlertDate;
}
/**
*
* @return
*/
public long getCreatedDate() {
return mCreatedDate;
}
/**
*
* @return truefalse
*/
public boolean hasAttachment() {
return mHasAttachment;
}
/**
*
* @return
*/
public long getModifiedDate() {
return mModifiedDate;
}
/**
* ID
* @return ID
*/
public int getBgColorId() {
return mBgColorId;
}
/**
* ID
* @return ID
*/
public long getParentId() {
return mParentId;
}
/**
*
* @return
*/
public int getNotesCount() {
return mNotesCount;
}
/**
* ID
* @return ID
*/
public long getFolderId () {
return mParentId;
}
/**
*
* @return
*/
public int getType() {
return mType;
}
/**
*
* @return
*/
public int getWidgetType() {
return mWidgetType;
}
/**
* ID
* @return ID
*/
public int getWidgetId() {
return mWidgetId;
}
/**
*
* @return
*/
public String getSnippet() {
return mSnippet;
}
/**
*
* @return truefalse
*/
public boolean hasAlert() {
return (mAlertDate > 0);
}
/**
*
* @return truefalse
*/
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
}
/**
* Cursor
* @param cursor Cursor
* @return
*/
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
}

@ -78,85 +78,90 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
/**
* -
*
*
*/
public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener {
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0;
private static final int FOLDER_LIST_QUERY_TOKEN = 1;
private static final int MENU_FOLDER_DELETE = 0;
// 异步查询处理的标记常量
private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; // 查询笔记列表的标记
private static final int FOLDER_LIST_QUERY_TOKEN = 1; // 查询文件夹列表的标记
private static final int MENU_FOLDER_VIEW = 1;
private static final int MENU_FOLDER_CHANGE_NAME = 2;
// 文件夹上下文菜单的菜单项ID
private static final int MENU_FOLDER_DELETE = 0; // 删除文件夹
private static final int MENU_FOLDER_VIEW = 1; // 查看文件夹
private static final int MENU_FOLDER_CHANGE_NAME = 2; // 修改文件夹名称
// 用于存储应用介绍是否已添加的偏好设置键
private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction";
/**
*
* NOTE_LIST SUB_FOLDER CALL_RECORD_FOLDER
*/
private enum ListEditState {
NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER
NOTE_LIST, // 笔记列表状态
SUB_FOLDER, // 子文件夹状态
CALL_RECORD_FOLDER // 通话记录文件夹状态
};
private ListEditState mState;
private BackgroundQueryHandler mBackgroundQueryHandler;
private NotesListAdapter mNotesListAdapter;
private ListView mNotesListView;
private Button mAddNewNote;
private boolean mDispatch;
private int mOriginY;
private int mDispatchY;
private TextView mTitleBar;
private long mCurrentFolderId;
private ContentResolver mContentResolver;
private ModeCallback mModeCallBack;
private static final String TAG = "NotesListActivity";
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;
private NoteItemData mFocusNoteDataItem;
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?";
// 成员变量
private ListEditState mState; // 当前列表编辑状态
private BackgroundQueryHandler mBackgroundQueryHandler; // 异步查询处理器
private NotesListAdapter mNotesListAdapter; // 笔记列表适配器
private ListView mNotesListView; // 笔记列表视图
private Button mAddNewNote; // 新建笔记按钮
private boolean mDispatch; // 是否分发触摸事件给列表视图
private int mOriginY; // 原始触摸Y坐标
private int mDispatchY; // 分发触摸Y坐标
private TextView mTitleBar; // 标题栏
private long mCurrentFolderId; // 当前文件夹ID
private ContentResolver mContentResolver; // 内容解析器
private ModeCallback mModeCallBack; // 多选模式回调
private static final String TAG = "NotesListActivity"; // 日志标签
public static final int NOTES_LISTVIEW_SCROLL_RATE = 30;// 笔记列表滚动速率
private NoteItemData mFocusNoteDataItem; // 当前焦点的笔记数据项
// 查询条件常量
private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; // 普通文件夹查询条件
private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>"
+ Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR ("
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND "
+ NoteColumns.NOTES_COUNT + ">0)";
+ NoteColumns.NOTES_COUNT + ">0)"; // 根文件夹查询条件(显示非系统文件夹和非空通话记录文件夹)
private final static int REQUEST_CODE_OPEN_NODE = 102;
private final static int REQUEST_CODE_NEW_NODE = 103;
// 请求码常量
private final static int REQUEST_CODE_OPEN_NODE = 102; // 打开笔记的请求码
private final static int REQUEST_CODE_NEW_NODE = 103; // 新建笔记的请求码
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.note_list);
initResources();
setContentView(R.layout.note_list); // 设置布局
initResources(); // 初始化资源
/**
* Insert an introduction when user firstly use this application
*/
setAppInfoFromRawRes();
// 首次进入时插入“介绍”笔记
setAppInfoFromRawRes(); // 设置应用信息(首次使用时添加介绍笔记)
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// 处理从NoteEditActivity返回的结果
if (resultCode == RESULT_OK
&& (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) {
// 如果是打开或新建笔记的请求返回成功,刷新列表
mNotesListAdapter.changeCursor(null);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
/**
*
* 使
*/
private void setAppInfoFromRawRes() {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this);
if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) {
@ -203,12 +208,20 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
/**
*
*
*/
@Override
protected void onStart() {
super.onStart();
startAsyncNotesListQuery();
}
/**
*
*
*/
private void initResources() {
mContentResolver = this.getContentResolver();
mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver());
@ -231,11 +244,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mModeCallBack = new ModeCallback();
}
/**
* -
*
*/
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
private DropdownMenu mDropDownMenu;
private ActionMode mActionMode;
private MenuItem mMoveMenu;
/**
*
*
*/
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
getMenuInflater().inflate(R.menu.note_list_options, menu);
menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
@ -269,6 +290,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return true;
}
/**
*
*
*/
private void updateMenu() {
int selectedCount = mNotesListAdapter.getSelectedCount();
// Update dropdown menu
@ -296,22 +321,37 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return false;
}
/**
*
*
*/
public void onDestroyActionMode(ActionMode mode) {
mNotesListAdapter.setChoiceMode(false);
mNotesListView.setLongClickable(true);
mAddNewNote.setVisibility(View.VISIBLE);
}
/**
*
*/
public void finishActionMode() {
mActionMode.finish();
}
/**
*
*
*/
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
mNotesListAdapter.setCheckedItem(position, checked);
updateMenu();
}
/**
*
*
*/
public boolean onMenuItemClick(MenuItem item) {
if (mNotesListAdapter.getSelectedCount() == 0) {
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
@ -321,6 +361,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
switch (item.getItemId()) {
case R.id.delete:
// 显示删除确认对话框
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
@ -330,13 +371,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
batchDelete();
batchDelete(); // 执行批量删除
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.move:
// 查询目标文件夹列表
startQueryDestinationFolders();
break;
default:
@ -346,6 +388,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
/**
*
*
*/
private class NewNoteOnTouchListener implements OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
@ -356,22 +402,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
int newNoteViewHeight = mAddNewNote.getHeight();
int start = screenHeight - newNoteViewHeight;
int eventY = start + (int) event.getY();
/**
* Minus TitleBar's height
*/
// 子文件夹状态需减去标题栏高度
if (mState == ListEditState.SUB_FOLDER) {
eventY -= mTitleBar.getHeight();
start -= mTitleBar.getHeight();
}
/**
* HACKME:When click the transparent part of "New Note" button, dispatch
* the event to the list view behind this button. The transparent part of
* "New Note" button could be expressed by formula y=-0.12x+94Unit:pixel
* and the line top of the button. The coordinate based on left of the "New
* Note" button. The 94 represents maximum height of the transparent part.
* Notice that, if the background of the button changes, the formula should
* also change. This is very bad, just for the UI designer's strong requirement.
*/
// HACKME: 点击“新建”按钮透明区时,将事件派发给下方列表。
// 透明区近似线性公式 y = -0.12x + 94px背景改动需同步更新该公式。
if (event.getY() < (event.getX() * (-0.12) + 94)) {
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- mNotesListView.getFooterViewsCount());
@ -408,6 +445,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
};
/**
*
* ID
*/
private void startAsyncNotesListQuery() {
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
@ -417,18 +458,27 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}
/**
* -
*/
private final class BackgroundQueryHandler extends AsyncQueryHandler {
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
/**
*
*
*/
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case FOLDER_NOTE_LIST_QUERY_TOKEN:
// 查询笔记列表完成,更新适配器
mNotesListAdapter.changeCursor(cursor);
break;
case FOLDER_LIST_QUERY_TOKEN:
// 查询文件夹列表完成,显示文件夹菜单
if (cursor != null && cursor.getCount() > 0) {
showFolderListMenu(cursor);
} else {
@ -441,6 +491,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
/**
*
*
*/
private void showFolderListMenu(Cursor cursor) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(R.string.menu_title_select_folder);
@ -462,6 +516,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
builder.show();
}
/**
*
*
*/
private void createNewNote() {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
@ -469,6 +527,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
}
/**
*
*
*/
private void batchDelete() {
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
@ -506,6 +568,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}.execute();
}
/**
*
*
*/
private void deleteFolder(long folderId) {
if (folderId == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Wrong folder id, should not happen " + folderId);
@ -533,6 +599,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
/**
*
* /
*/
private void openNode(NoteItemData data) {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
@ -540,6 +610,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE);
}
/**
*
* ID
*/
private void openFolder(NoteItemData data) {
mCurrentFolderId = data.getId();
startAsyncNotesListQuery();
@ -557,6 +631,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
mTitleBar.setVisibility(View.VISIBLE);
}
/**
*
*
*/
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_new_note:
@ -567,6 +645,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
/**
*
*/
private void showSoftInput() {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
@ -574,11 +655,18 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
/**
*
*/
private void hideSoftInput(View view) {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
/**
*
* @param create
*/
private void showCreateOrModifyFolderDialog(final boolean create) {
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null);
@ -664,6 +752,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
});
}
/**
*
* 退
*/
@Override
public void onBackPressed() {
switch (mState) {
@ -688,6 +780,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
}
/**
*
* 广
*/
private void updateWidget(int appWidgetId, int appWidgetType) {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
if (appWidgetType == Notes.TYPE_WIDGET_2X) {
@ -707,6 +803,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
setResult(RESULT_OK, intent);
}
/**
*
*
*/
private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() {
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
if (mFocusNoteDataItem != null) {
@ -718,6 +818,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
}
};
/**
*
*
*/
@Override
public void onContextMenuClosed(Menu menu) {
if (mNotesListView != null) {
@ -726,6 +830,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
super.onContextMenuClosed(menu);
}
/**
*
*
*/
@Override
public boolean onContextItemSelected(MenuItem item) {
if (mFocusNoteDataItem == null) {
@ -760,6 +868,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return true;
}
/**
*
*
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
menu.clear();
@ -778,6 +890,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt
return true;
}
/**
*
*
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {

@ -30,19 +30,41 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
/**
*
* <p>//</p>
*/
/**
*
* CursorAdapter
*
*/
public class NotesListAdapter extends CursorAdapter {
private static final String TAG = "NotesListAdapter";
/** 上下文对象 */
private Context mContext;
/** 选中的索引映射position -> 是否选中) */
private HashMap<Integer, Boolean> mSelectedIndex;
/** 笔记数量 */
private int mNotesCount;
/** 是否处于选择模式 */
private boolean mChoiceMode;
/**
*
* ID
*/
public static class AppWidgetAttribute {
/** 小部件ID */
public int widgetId;
/** 小部件类型 */
public int widgetType;
};
/**
*
* @param context
*/
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();
@ -50,11 +72,25 @@ public class NotesListAdapter extends CursorAdapter {
mNotesCount = 0;
}
/**
*
* @param context
* @param cursor
* @param parent
* @return NotesListItem
*/
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
/**
*
* NotesListItem
* @param view
* @param context
* @param cursor
*/
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) {
@ -64,24 +100,42 @@ public class NotesListAdapter extends CursorAdapter {
}
}
/**
*
* @param position
* @param checked truefalse
*/
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
}
/**
*
* @return truefalse
*/
public boolean isInChoiceMode() {
return mChoiceMode;
}
/**
*
* @param mode truefalse
*/
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mSelectedIndex.clear(); // 清除所有选中项
mChoiceMode = mode;
}
/**
*
* @param checked truefalse
*/
public void selectAll(boolean checked) {
Cursor cursor = getCursor();
for (int i = 0; i < getCount(); i++) {
if (cursor.moveToPosition(i)) {
// 只处理类型为笔记的项
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
setCheckedItem(i, checked);
}
@ -89,6 +143,10 @@ public class NotesListAdapter extends CursorAdapter {
}
}
/**
* ID
* @return IDHashSet
*/
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
@ -105,6 +163,10 @@ public class NotesListAdapter extends CursorAdapter {
return itemSet;
}
/**
*
* @return HashSet
*/
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
@ -128,6 +190,10 @@ public class NotesListAdapter extends CursorAdapter {
return itemSet;
}
/**
*
* @return
*/
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
@ -143,11 +209,20 @@ public class NotesListAdapter extends CursorAdapter {
return count;
}
/**
*
* @return truefalse
*/
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
}
/**
*
* @param position
* @return truefalse
*/
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
@ -158,20 +233,25 @@ public class NotesListAdapter extends CursorAdapter {
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount();
calcNotesCount(); // 重新计算笔记总数
}
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
calcNotesCount();
calcNotesCount(); // 重新计算笔记总数
}
/**
*
*
*/
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
Cursor c = (Cursor) getItem(i);
if (c != null) {
// 只统计类型为笔记的项
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
}

@ -29,18 +29,37 @@ import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
/**
*
*
*/
/**
*
*
*/
public class NotesListItem extends LinearLayout {
/** 提醒图标 */
private ImageView mAlert;
/** 笔记标题文本视图 */
private TextView mTitle;
/** 时间文本视图 */
private TextView mTime;
/** 联系人姓名文本视图(用于通话记录) */
private TextView mCallName;
/** 笔记项数据 */
private NoteItemData mItemData;
/** 复选框(用于选择模式) */
private CheckBox mCheckBox;
/**
*
* @param context
*/
public NotesListItem(Context context) {
super(context);
// 填充布局
inflate(context, R.layout.note_item, this);
// 获取视图组件
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
@ -48,7 +67,15 @@ public class NotesListItem extends LinearLayout {
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
}
/**
*
* @param context
* @param data
* @param choiceMode
* @param checked
*/
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
// 处理选择模式下的复选框显示
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
@ -57,6 +84,8 @@ public class NotesListItem extends LinearLayout {
}
mItemData = data;
// 处理通话记录文件夹
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
@ -64,7 +93,9 @@ public class NotesListItem extends LinearLayout {
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
}
// 处理通话记录笔记
else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
@ -75,16 +106,20 @@ public class NotesListItem extends LinearLayout {
} else {
mAlert.setVisibility(View.GONE);
}
} else {
}
// 处理普通文件夹或笔记
else {
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
if (data.getType() == Notes.TYPE_FOLDER) {
// 文件夹显示格式:名称 + 数量
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
} else {
// 笔记显示格式:摘要
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
@ -94,14 +129,23 @@ public class NotesListItem extends LinearLayout {
}
}
}
// 设置修改时间
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 设置背景
setBackground(data);
}
/**
*
* @param data
*/
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
if (data.getType() == Notes.TYPE_NOTE) {
// 根据笔记在列表中的位置选择不同的背景
if (data.isSingle() || data.isOneFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {
@ -112,10 +156,15 @@ public class NotesListItem extends LinearLayout {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
// 文件夹使用固定背景
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
/**
*
* @return
*/
public NoteItemData getItemData() {
return mItemData;
}

@ -47,56 +47,84 @@ import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
/**
*
* <p>/GTask </p>
* <p> gtask 广</p>
*/
/**
*
*/
public class NotesPreferenceActivity extends PreferenceActivity {
/** 偏好设置文件名 */
public static final String PREFERENCE_NAME = "notes_preferences";
/** 同步账户名存储键 */
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
/** 最后同步时间存储键 */
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
/** 设置背景颜色的偏好键 */
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
/** 同步账户的偏好键 */
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
/** 账户权限过滤键 */
private static final String AUTHORITIES_FILTER_KEY = "authorities";
/** 账户偏好分类 */
private PreferenceCategory mAccountCategory;
/** 同步状态广播接收器 */
private GTaskReceiver mReceiver;
/** 原始账户列表 */
private Account[] mOriAccounts;
/** 是否添加了新账户 */
private boolean mHasAddedAccount;
/**
* 广
* @param icicle
*/
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
/* using the app icon for navigation */
/* 使用应用图标作为导航 */
getActionBar().setDisplayHomeAsUpEnabled(true);
// 从资源文件加载偏好设置
addPreferencesFromResource(R.xml.preferences);
// 获取账户偏好分类
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
// 创建并注册同步状态广播接收器
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
registerReceiver(mReceiver, filter);
mOriAccounts = null;
// 加载设置头部布局
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
getListView().addHeaderView(header, null, true);
}
/**
* UI
*/
@Override
protected void onResume() {
super.onResume();
// need to set sync account automatically if user has added a new
// account
// 若用户刚新增了 Google 账号,自动选中新账号为同步账号
if (mHasAddedAccount) {
Account[] accounts = getGoogleAccounts();
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
// 查找新增的账户
for (Account accountNew : accounts) {
boolean found = false;
for (Account accountOld : mOriAccounts) {
@ -106,6 +134,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
if (!found) {
// 设置新增账户为同步账户
setSyncAccount(accountNew.name);
break;
}
@ -113,36 +142,48 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
// 刷新UI界面
refreshUI();
}
/**
* 广
*/
@Override
protected void onDestroy() {
// 取消注册同步状态广播接收器
if (mReceiver != null) {
unregisterReceiver(mReceiver);
}
super.onDestroy();
}
/**
*
*/
private void loadAccountPreference() {
// 清空账户偏好分类中的所有项
mAccountCategory.removeAll();
// 创建账户选择偏好项
Preference accountPref = new Preference(this);
final String defaultAccount = getSyncAccountName(this);
accountPref.setTitle(getString(R.string.preferences_account_title));
accountPref.setSummary(getString(R.string.preferences_account_summary));
// 设置偏好项点击监听器
accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
// 只有当不同步时才能更改账户
if (!GTaskSyncService.isSyncing()) {
if (TextUtils.isEmpty(defaultAccount)) {
// the first time to set account
// 首次设置账户,显示账户选择对话框
showSelectAccountAlertDialog();
} else {
// if the account has already been set, we need to promp
// user about the risk
// 已绑定过账号,提示更换风险
showChangeAccountConfirmAlertDialog();
}
} else {
// 同步中不能更改账户,显示提示信息
Toast.makeText(NotesPreferenceActivity.this,
R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT)
.show();
@ -151,85 +192,113 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
});
// 将账户偏好项添加到分类中
mAccountCategory.addPreference(accountPref);
}
/**
*
*/
private void loadSyncButton() {
// 获取同步按钮和最后同步时间文本视图
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// set button state
// 根据同步状态配置按钮文案与行为
if (GTaskSyncService.isSyncing()) {
// 同步中,按钮显示取消同步
syncButton.setText(getString(R.string.preferences_button_sync_cancel));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// 取消同步
GTaskSyncService.cancelSync(NotesPreferenceActivity.this);
}
});
} else {
// 未同步,按钮显示立即同步
syncButton.setText(getString(R.string.preferences_button_sync_immediately));
syncButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// 开始同步
GTaskSyncService.startSync(NotesPreferenceActivity.this);
}
});
}
// 只有设置了同步账户,按钮才可用
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
// set last sync time
// 展示最近同步时间或实时进度
if (GTaskSyncService.isSyncing()) {
// 同步中,显示同步进度
lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
// 未同步,显示最后同步时间
long lastSyncTime = getLastSyncTime(this);
if (lastSyncTime != 0) {
// 格式化并显示最后同步时间
lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time,
DateFormat.format(getString(R.string.preferences_last_sync_time_format),
lastSyncTime)));
lastSyncTimeView.setVisibility(View.VISIBLE);
} else {
// 从未同步过,隐藏时间显示
lastSyncTimeView.setVisibility(View.GONE);
}
}
}
/**
* UI
*/
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
/**
*
*/
private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 加载自定义标题视图
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_select_account_title));
TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle);
subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips));
// 设置对话框标题和空的确定按钮
dialogBuilder.setCustomTitle(titleView);
dialogBuilder.setPositiveButton(null, null);
// 获取所有Google账户
Account[] accounts = getGoogleAccounts();
String defAccount = getSyncAccountName(this);
// 保存原始账户列表和添加账户标志
mOriAccounts = accounts;
mHasAddedAccount = false;
if (accounts.length > 0) {
// 创建账户列表项
CharSequence[] items = new CharSequence[accounts.length];
final CharSequence[] itemMapping = items;
int checkedItem = -1;
int index = 0;
for (Account account : accounts) {
// 标记当前默认账户
if (TextUtils.equals(account.name, defAccount)) {
checkedItem = index;
}
items[index++] = account.name;
}
// 设置单选列表项和选择监听器
dialogBuilder.setSingleChoiceItems(items, checkedItem,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
// 设置选择的账户为同步账户
setSyncAccount(itemMapping[which].toString());
dialog.dismiss();
refreshUI();
@ -237,13 +306,17 @@ public class NotesPreferenceActivity extends PreferenceActivity {
});
}
// 加载添加账户视图
View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null);
dialogBuilder.setView(addAccountView);
// 显示对话框并设置添加账户点击监听器
final AlertDialog dialog = dialogBuilder.show();
addAccountView.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// 标记已添加账户
mHasAddedAccount = true;
// 打开添加账户设置界面
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
"gmail-ls"
@ -254,9 +327,13 @@ public class NotesPreferenceActivity extends PreferenceActivity {
});
}
/**
*
*/
private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
// 加载自定义标题视图
View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null);
TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title);
titleTextView.setText(getString(R.string.preferences_dialog_change_account_title,
@ -265,31 +342,46 @@ public class NotesPreferenceActivity extends PreferenceActivity {
subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg));
dialogBuilder.setCustomTitle(titleView);
// 创建菜单项数组
CharSequence[] menuItemArray = new CharSequence[] {
getString(R.string.preferences_menu_change_account),
getString(R.string.preferences_menu_remove_account),
getString(R.string.preferences_menu_cancel)
};
// 设置菜单项点击监听器
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
// 更改账户,显示账户选择对话框
showSelectAccountAlertDialog();
} else if (which == 1) {
// 移除账户
removeSyncAccount();
refreshUI();
}
}
});
// 显示对话框
dialogBuilder.show();
}
/**
* Google
* @return Google
*/
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.google");
}
/**
*
* @param account
*/
private void setSyncAccount(String account) {
// 只有当账户名不同时才更新
if (!getSyncAccountName(this).equals(account)) {
// 更新账户偏好设置
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
if (account != null) {
@ -299,10 +391,10 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
editor.commit();
// clean up last sync time
// 清除最后同步时间
setLastSyncTime(this, 0);
// clean up local gtask related info
// 在后台线程中清除本地Google Tasks相关信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
@ -312,13 +404,18 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}).start();
// 显示设置成功提示
Toast.makeText(NotesPreferenceActivity.this,
getString(R.string.preferences_toast_success_set_accout, account),
Toast.LENGTH_SHORT).show();
}
}
/**
*
*/
private void removeSyncAccount() {
// 清除账户偏好设置和最后同步时间
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) {
@ -329,7 +426,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
editor.commit();
// clean up local gtask related info
// 在后台线程中清除本地Google Tasks相关信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
@ -340,12 +437,22 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}).start();
}
/**
*
* @param context
* @return
*/
public static String getSyncAccountName(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
/**
*
* @param context
* @param time
*/
public static void setLastSyncTime(Context context, long time) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
@ -354,17 +461,32 @@ public class NotesPreferenceActivity extends PreferenceActivity {
editor.commit();
}
/**
*
* @param context
* @return 0
*/
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
/**
* 广UI
*/
private class GTaskReceiver extends BroadcastReceiver {
/**
* 广
* @param context
* @param intent
*/
@Override
public void onReceive(Context context, Intent intent) {
// 刷新UI
refreshUI();
// 如果正在同步,更新同步状态文本
if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) {
TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
syncStatus.setText(intent
@ -374,9 +496,15 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
/**
*
* @param item
* @return truefalse
*/
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// 点击返回按钮,返回笔记列表活动
Intent intent = new Intent(this, NotesListActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

@ -32,101 +32,169 @@ import net.micode.notes.tool.ResourceParser;
import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListActivity;
/**
*
*
*/
public abstract class NoteWidgetProvider extends AppWidgetProvider {
/** 数据库查询投影包含笔记ID、背景颜色ID和摘要 */
public static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET
NoteColumns.ID, // 笔记ID
NoteColumns.BG_COLOR_ID, // 背景颜色ID
NoteColumns.SNIPPET // 笔记摘要
};
/** 投影列索引 - 笔记ID */
public static final int COLUMN_ID = 0;
/** 投影列索引 - 背景颜色ID */
public static final int COLUMN_BG_COLOR_ID = 1;
/** 投影列索引 - 笔记摘要 */
public static final int COLUMN_SNIPPET = 2;
/** 日志标签 */
private static final String TAG = "NoteWidgetProvider";
/**
*
* @param context
* @param appWidgetIds ID
*/
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
// 创建要更新的内容值将WIDGET_ID设置为无效值
ContentValues values = new ContentValues();
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
// 遍历所有被删除的小部件ID
for (int i = 0; i < appWidgetIds.length; i++) {
// 更新与小部件关联且不在回收站中的笔记清除其WIDGET_ID
context.getContentResolver().update(Notes.CONTENT_NOTE_URI,
values,
NoteColumns.WIDGET_ID + "=?",
new String[] { String.valueOf(appWidgetIds[i])});
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(appWidgetIds[i]), String.valueOf(Notes.ID_TRASH_FOLER) });
}
}
/**
*
* @param context
* @param widgetId ID
* @return Cursornull
*/
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
// 查询与指定小部件ID关联且不在回收站中的笔记
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null);
PROJECTION, // 查询的列
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", // 查询条件
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, // 参数值
null); // 排序方式
}
/**
*
* @param context
* @param appWidgetManager
* @param appWidgetIds ID
*/
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false);
}
/**
*
* @param context
* @param appWidgetManager
* @param appWidgetIds ID
* @param privacyMode
*/
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {
// 遍历所有小部件ID
for (int i = 0; i < appWidgetIds.length; i++) {
// 只处理有效的小部件ID
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {
// 默认背景颜色
int bgId = ResourceParser.getDefaultBgId(context);
String snippet = "";
// 创建跳转到笔记编辑活动的意图
Intent intent = new Intent(context, NoteEditActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]);
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType());
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); // 避免创建新的活动实例
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); // 传递小部件ID
intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); // 传递小部件类型
// 获取与小部件关联的笔记信息
Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]);
if (c != null && c.moveToFirst()) {
// 检查是否有多个笔记与同一小部件关联
if (c.getCount() > 1) {
Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]);
c.close();
return;
}
// 获取笔记信息
snippet = c.getString(COLUMN_SNIPPET);
bgId = c.getInt(COLUMN_BG_COLOR_ID);
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID));
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); // 传递笔记ID
intent.setAction(Intent.ACTION_VIEW); // 设置操作为查看
} else {
// 无关联笔记,显示默认提示文本
snippet = context.getResources().getString(R.string.widget_havenot_content);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 设置操作为插入或编辑
}
// 关闭Cursor
if (c != null) {
c.close();
}
// 创建RemoteViews以更新小部件UI
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); // 设置背景图片
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); // 传递背景颜色ID
/**
* Generate the pending intent to start host for the widget
*
*/
PendingIntent pendingIntent = null;
if (privacyMode) {
// 隐私模式下,显示访问模式提示并跳转到笔记列表
rv.setTextViewText(R.id.widget_text,
context.getString(R.string.widget_under_visit_mode));
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent(
context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
} else {
// 正常模式下,显示笔记摘要并跳转到笔记编辑
rv.setTextViewText(R.id.widget_text, snippet);
pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent,
PendingIntent.FLAG_UPDATE_CURRENT);
}
// 设置小部件点击事件
rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent);
// 更新小部件
appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
}
}
}
/**
* IDID
* @param bgId ID
* @return ID
*/
protected abstract int getBgResourceId(int bgId);
/**
* ID
* @return ID
*/
protected abstract int getLayoutId();
/**
*
* @return
*/
protected abstract int getWidgetType();
}

@ -23,23 +23,49 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
/**
* 2x2
* 2x2
*/
/**
* 2x2
*/
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
/**
*
* @param context
* @param appWidgetManager
* @param appWidgetIds ID
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的update方法更新小部件
super.update(context, appWidgetManager, appWidgetIds);
}
/**
* 2x2ID
* @return ID
*/
@Override
protected int getLayoutId() {
return R.layout.widget_2x;
}
/**
* 2x2IDID
* @param bgId ID
* @return ID
*/
@Override
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
}
/**
* 2x2
* @return
*/
@Override
protected int getWidgetType() {
return Notes.TYPE_WIDGET_2X;

@ -23,22 +23,49 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
/**
* 4x4
* 4x4
*/
/**
* 4x4
*/
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
/**
*
* @param context
* @param appWidgetManager
* @param appWidgetIds ID
*/
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的update方法更新小部件
super.update(context, appWidgetManager, appWidgetIds);
}
/**
* 4x4ID
* @return ID
*/
@Override
protected int getLayoutId() {
return R.layout.widget_4x;
}
/**
* 4x4IDID
* @param bgId ID
* @return ID
*/
@Override
protected int getBgResourceId(int bgId) {
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
}
/**
* 4x4
* @return
*/
@Override
protected int getWidgetType() {
return Notes.TYPE_WIDGET_4X;

Loading…
Cancel
Save